A Linux library for sharing video buffers between C and Python processes using POSIX shared memory.
Publisher(s) ──write──► Shared Memory Context ──read──► Consumer(s) / Server
(up to 10 named buffers)
One or more publisher processes write raw pixel data into named shared memory buffers. Consumer or server processes read those buffers concurrently. Writers are coordinated via a spin-lock embedded in each VideoFrame struct; it records the writer's PID, so a lock left by a writer that crashed mid-write is taken over.
Supported data types:
- Video frames — width × height × channels raw pixel data
- Generic binary structs — arbitrary fixed-size data via
createGenericMetaData()
| Dependency | Purpose |
|---|---|
gcc |
C compiler |
libpthread, librt, libm |
POSIX threads, shared memory, math |
libX11-dev |
X11 viewer (viewer target only) |
valgrind (optional) |
Memory debugging scripts |
Python ctypes, numpy, opencv-python, Pillow |
Python utilities |
make # Build everything: server, client, consumer, publisher, viewer, libSharedMemoryVideoBuffers.so
make server # Build server only
make client # Build client only
make consumer # Build consumer only
make publisher # Build publisher only
make viewer # Build X11 viewer only
make install # Install library to /usr/local/lib and run ldconfig
make clean # Remove all build artefactsCompiler flags: -Wall -O2 -pthread -g, linked with -pthread -lrt -lm (after the objects, so it also links on glibc < 2.34)
Object files land in obj/. The shared library is built as libSharedMemoryVideoBuffers.so.
mkdir build && cd build
cmake ..
makeCMake mirrors all Makefile targets and uses the same compiler flags.
Reads all populated shared memory buffers and writes them to disk as PNM images each time Enter is pressed. A debugging tool, not for production. Publishers don't need it running: SharedMemoryManager publishers and the C client, viewer and publisher_data create the context themselves.
./server # Press Enter to snapshot all buffersOutput files: data/server_stream{i}.pnm
Stops on SIGINT/SIGTERM (also while waiting for Enter) or when standard input is closed, so it can't run in the background.
Connects to the shared memory context, creates a stream ("stream1", 640×480 RGB, unless another name and size are given), and writes a frame every 115 ms. 16 frames of random pixels are generated once at startup and published in turn.
./publisher [stream_name] [width] [height] [channels]
./publisher stream2 1920 1080 3Like publisher but uses createGenericMetaData() to write an arbitrary binary struct (not tied to a specific video resolution) to stream "data_stream1".
./publisher_dataCreates stream "stream1" (640×480 RGB), writes random data in a tight loop (5 ms intervals), and reads it back to verify round-trip correctness.
./clientConnects to an existing stream "stream1", locks it for reading, and saves the frame to data/consumer_stream0.pnm every 115 ms.
./consumerOpens an X11 window that shows the frames of a stream ("stream1" unless another name is given) and follows the stream's size. 3-channel frames are shown as RGB, 1-channel frames as grayscale, and frames with any other channel count (2, 4 or more) as grayscale averaged over the channels. It can start before the publisher: it waits for the stream. Press any key or close the window to exit.
./viewer [stream_name]
./viewer stream3 # e.g. the default stream of openCVStream.pyRequires libX11. Links against libSharedMemoryVideoBuffers.so.
All Python scripts depend on libSharedMemoryVideoBuffers.so. The SharedMemoryManager wrapper loads it via ctypes.
Central Python wrapper around the C library. Used by all other Python utilities.
from SharedMemoryManager import SharedMemoryManager
# Publisher / server mode
smm = SharedMemoryManager(
libraryPath="libSharedMemoryVideoBuffers.so",
frameName="stream1",
connect=False, # False = create/write
width=640, height=480, channels=3
)
ok = smm.copy_numpy_to_shared_memory(numpy_array) # False = frame dropped, every slot was busy
# Consumer / client mode
smm = SharedMemoryManager(
libraryPath="libSharedMemoryVideoBuffers.so",
frameName="stream1",
connect=True # True = read
)
frame = smm.read_from_shared_memory() # returns numpy array (a copy), or None
# Zero-copy read: view points straight into shared memory
with smm.read_frame() as view: # view is a read-only numpy array, or None
if view is not None:
edges = cv2.Canny(view, 100, 200)
# smm.width, smm.height, smm.channels, smm.unix_timestamp (Unix nanoseconds) describe this frameread_frame() keeps the frame's slot protected until the block exits, so view can't change while you use it:
- Don't hold many frames at once. A stream has 4 slots by default (
SHMVB_BUFFER_COUNT, 1-4, set by the process that creates the stream): the latest frame, the one being written, and two more. So up to two readers can hold older frames without slowing the publisher. When readers hold every slot, the publisher waits up to the lock timeout, then drops the frame (copy_numpy_to_shared_memoryreturnsFalse). - Don't use
viewafter the block. The writer will reuse the slot, so copy anything you want to keep (view.copy()). - Don't read the same manager again inside the block.
read_frame(),read_from_shared_memory()andget_timestamp()raiseRuntimeErrorthere, because a second read would release the block's protection. - Exit the block on the same thread that entered it.
- Streams created with
SHMVB_BUFFER_COUNT=1have no protection.
Python equivalent of the C server executable. Waits for Enter, then snapshots all populated buffers to data/server_stream{i}.pnm. Handles KeyboardInterrupt and unmaps all buffers on exit.
python3 src/python/SharedMemoryServer.pyCaptures from a webcam (camera 0, 800×600) and streams frames into shared memory.
python3 src/python/client_upstream.py [stream_name]
# default stream_name: stream1Press q to quit.
Reads frames from shared memory and displays them in an OpenCV window. Handles RGB and RGBA inputs.
python3 src/python/client_downstream.py [stream_name]
# default stream_name: stream1Press q to quit.
Streams a numbered image sequence from disk into shared memory. Expects files named colorFrame_0_<number>.[jpg|png|pnm].
python3 src/python/folderStream.py <folder_path> [stream_name]
# example:
python3 src/python/folderStream.py /path/to/frames/ stream2Loops through the sequence continuously. Supports optional aspect-ratio-preserving resize with padding.
Streams any OpenCV-readable video file (MP4, AVI, …) or camera index into shared memory.
python3 src/python/openCVStream.py <video_source> [stream_name]
# examples:
python3 src/python/openCVStream.py test.mp4 stream3
python3 src/python/openCVStream.py 0 stream3 # camera indexDefault stream name: stream3. Loops video on end-of-file.
Reads from one shared memory stream, applies a Sobel edge-detection filter, and writes the result to a second stream. Demonstrates a processing pipeline between two SharedMemoryManager instances.
python3 src/python/processStreamOpenCV.py
# Input: stream "street" on video_frames.shm
# Output: stream "dance_output" on depth_frames.shmPress q to quit.
Fetches an MJPEG HTTP multipart stream from an ESP32 camera and writes decoded frames to shared memory. Includes exponential-backoff reconnect logic.
python3 src/python/espStream.py [camera_ip] [stream_name] [sleep_ms]
# example:
python3 src/python/espStream.py 192.168.1.119 stream2 0Captures the desktop (or a screen region) via PIL ImageGrab and streams frames into shared memory at ~30 FPS.
python3 src/python/screenStream.py # full screen
python3 src/python/screenStream.py <x> <y> <w> <h> # capture region# Terminal 1 — start the server
./server
# Terminal 2 — publish random frames
./publisher
# Terminal 3 (optional) — consume and display
./viewer# Terminal 1 (optional) — snapshot streams to disk for debugging
python3 src/python/SharedMemoryServer.py
# Terminal 2 — stream a video file
python3 src/python/openCVStream.py test.mp4 stream1
# Terminal 3 (optional) — display the stream
python3 src/python/client_downstream.py stream1# C server saves frames to disk each time Enter is pressed (separate terminal)
./server
# Python publishes a webcam feed
python3 src/python/client_upstream.py stream1
# Python runs edge detection and writes to a second context
python3 src/python/processStreamOpenCV.pyValgrind wrapper scripts are provided in scripts/:
./scripts/debug_server.sh # Valgrind server
./scripts/debug_publisher.sh # Valgrind publisher
./scripts/debug_consumer.sh # Valgrind consumer
./scripts/debug_viewer.sh # Valgrind viewer
./scripts/debug.sh # Valgrind default targetEach script runs make first and writes the Valgrind report to error.txt.
Key constants:
| Constant | Value | Meaning |
|---|---|---|
MAX_NUMBER_OF_BUFFERS |
10 | Maximum concurrent named streams |
MAX_SHM_NAME |
256 | Maximum length of a stream or context name |
ATTEMPTS_TO_LOCK_A_BUFFER |
1000 | Spin-lock retry limit |
SLEEP_TIME_BETWEEN_LOCK_ATTEMPTS_MICROSECONDS |
10 | Sleep between retries (µs) |
Key function groups:
| Group | Functions |
|---|---|
| Context lifecycle | createSharedMemoryContextDescriptor, connectToSharedMemoryContextDescriptor |
| Frame lifecycle | createVideoFrameMetaData, createGenericMetaData, destroyVideoFrame |
| Memory mapping | map_frame_shared_memory, mapRemoteToLocal, unmapLocalMappingItem |
| Write locking | startWritingToVideoBufferPointer, stopWritingToVideoBufferPointer |
| Read locking | startReadingFromVideoBufferPointer, stopReadingFromVideoBufferPointer |
| Data access | getVideoFrameDataPointer, copy_to_shared_memory, getVideoBufferPointer |
| Image output | writePNM, writeVideoFrameToImage |
| Diagnostics | printSharedMemoryContextState |