diff --git a/README.md b/README.md
index c3234a3..2d717c2 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,24 @@ Install the following packages from your distribution package manager:
 - `meson`
 - `opencv`
 
+### Setup Meson
+
+```shell
+meson setup build
+```
+
+*NOTE FOR JETBRAINS / CLION USERS: PLEASE SET YOUR MESON BUILD DIRECTORY TO `build` IN THE IDE SETTINGS UNDER "Build /
+Execution / Deployment" -> "Meson"*
+
 ### Compiling
 
 ```shell
-meson build client
+meson build client # for client only
+meson build server # for server only
+```
+
+## Running
+```shell
+./build/client
+./build/server
 ```
\ No newline at end of file
diff --git a/client.cpp b/client.cpp
index 5185f23..6b47673 100644
--- a/client.cpp
+++ b/client.cpp
@@ -1,8 +1,10 @@
 #include <iostream>
+#include <opencv2/videoio.hpp>
 
 using namespace std;
 
 int main() {
+    cv::VideoCapture cap = cv::VideoCapture(0);
     cout << "Hello World!" << endl;
     return 0;
 }
\ No newline at end of file
diff --git a/meson.build b/meson.build
index 6d282b8..a38c26d 100644
--- a/meson.build
+++ b/meson.build
@@ -1,13 +1,30 @@
+#=======================================================================================================================
+#  PROJECT SETTINGS
+#=======================================================================================================================
 project('video-streaming-poc', 'cpp')
-add_project_arguments('-Wall', '-Wextra', language : 'cpp')
 
+#=======================================================================================================================
+#  DEPENDENCIES
+#=======================================================================================================================
+# opencv dependency
 opencv = dependency('opencv4', version : '>=4.0.0')
-opencv_incl_dir = opencv.get_variable(cmake : 'OpenCV_INCLUDE_DIRECTORIES', pkgconfig : 'includedir')
-include = include_directories(opencv_incl_dir)
 
-common = []
+#=======================================================================================================================
+#  SOURCE FILES
+#=======================================================================================================================
+# common files between client / server
+common = ['packets/ImagePacket.cpp', 'packets/ImagePacket.h']
+# client-only files
 client = common + ['client.cpp']
+# server-only files
 server = common + ['server.cpp']
 
-client_exe = executable('client', client, dependencies : opencv, include_directories : include)
-server_exe = executable('server', server, dependencies : opencv, include_directories : include)
\ No newline at end of file
+#=======================================================================================================================
+#  BUILD TARGETS
+#=======================================================================================================================
+# client executable
+client_exe = executable('client', client,
+                        dependencies : opencv)
+# server executable
+server_exe = executable('server', server,
+                        dependencies : opencv)
\ No newline at end of file
diff --git a/packets/ImagePacket.cpp b/packets/ImagePacket.cpp
new file mode 100644
index 0000000..113a135
--- /dev/null
+++ b/packets/ImagePacket.cpp
@@ -0,0 +1,38 @@
+#include "ImagePacket.h"
+
+/*
+ * Construct a packet from an OpenCV Mat, and a beginning index. It will take PACKET_SIZE bytes from the start and add
+ * it to its slice
+ */
+ImagePacket::ImagePacket(const cv::Mat *image, const int begin) {
+    const uchar *target = &image->data[begin];
+    // TODO: handle out of index cases, pad with zeroes. probably also instantiate our byte array with zeroes
+    for (int i = 0; i < PACKET_SIZE; i++) {
+        slice[i] = target[begin + i];
+    }
+}
+
+/*
+ * Construct a packet from a raw array of unsigned chars, and a beginning index. It will take PACKET_SIZE bytes from the
+ * start and add it to its slice. This will be more useful for embedded scenarios where OpenCV will likely not be used.
+ */
+ImagePacket::ImagePacket(const uchar *image, const int begin) {
+    const uchar *target = &image[begin];
+    // TODO: handle out of index cases, pad with zeroes. probably also instantiate our byte array with zeroes
+    for (int i = 0; i < PACKET_SIZE; i++) {
+        slice[i] = target[begin + i];
+    }
+}
+
+/*
+ * Apply the packet to an OpenCV Mat.
+ */
+int ImagePacket::apply(const cv::Mat *image) const {
+    uchar *target = &image->data[begin];
+    // TODO: handle out of index cases
+    for (int i = 0; i < PACKET_SIZE; i++) {
+        target[begin + i] = slice[i];
+    }
+    // TODO: return the actual written size of the packet
+    return PACKET_SIZE;
+}
diff --git a/packets/ImagePacket.h b/packets/ImagePacket.h
new file mode 100644
index 0000000..caa7da6
--- /dev/null
+++ b/packets/ImagePacket.h
@@ -0,0 +1,18 @@
+#ifndef IMAGEPACKET_H
+#define IMAGEPACKET_H
+#include <opencv2/core/mat.hpp>
+
+#define PACKET_SIZE 768
+
+class ImagePacket {
+  public:
+    ImagePacket(const cv::Mat *image, int begin);
+    ImagePacket(const uchar *image, int begin);
+    int apply(const cv::Mat *image) const;
+
+  private:
+    int begin;
+    uchar slice[PACKET_SIZE];
+};
+
+#endif //IMAGEPACKET_H
diff --git a/server.cpp b/server.cpp
index 592c0aa..fca6e35 100644
--- a/server.cpp
+++ b/server.cpp
@@ -1,12 +1,16 @@
 #include <opencv2/highgui.hpp>
+#include <opencv2/videoio.hpp>
 #include <opencv2/core/mat.hpp>
 
 using namespace std;
 
 int main() {
+    // TODO: read image data from socket instead of VideoCapture
     cv::VideoCapture cap = cv::VideoCapture(0);
     bool running = true;
+    // TODO: handle multiple images
     cv::Mat image = cv::Mat::zeros(480, 640, CV_8UC3);
+    // TODO: make this asynchronous. probably do that in tandem with setting up networking
     while (running) {
         cap.read(image);
         imshow("image", image);