From cd290bdd07ab4a432138ede601abaa5b740cbdcd Mon Sep 17 00:00:00 2001 From: Maik Jurischka Date: Thu, 29 Jan 2026 09:56:36 +0100 Subject: [PATCH] add SharedMemoryWriter support --- docs/SOCKET_API.md | 435 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) diff --git a/docs/SOCKET_API.md b/docs/SOCKET_API.md index 3b5d3ee..4f7235b 100644 --- a/docs/SOCKET_API.md +++ b/docs/SOCKET_API.md @@ -598,6 +598,104 @@ Retrieve all current eHDR settings. --- +### 21. Enable Shared Memory + +Enable shared memory output for direct frame access by external processes. This creates a shared memory region at `/dev/shm/` where frames are written in parallel to the GStreamer pipeline. + +**Note:** Cannot be enabled while streaming is active. Must be enabled before starting the stream. + +**Command:** +```json +{ + "command": "enable_shared_memory", + "params": { + "name": "/vizion_frame", + "buffer_size": "8294528" + } +} +``` + +**Parameters:** +- `name`: Shared memory object name (optional, default: "/vizion_frame") +- `buffer_size`: Buffer size in bytes (optional, default: 8294528 for 1080p RGBA + header) + +**Response:** +```json +{ + "status": "success", + "message": "Shared memory enabled", + "name": "/vizion_frame", + "size": 8294528 +} +``` + +**Shared Memory Layout:** +- **Header (128 bytes):** Contains frame metadata + - Magic number (0x56495A4E = "VIZN") + - Width, height, format + - Data size, timestamp (nanoseconds) + - Frame sequence counter + - Atomic write sequence (for lock-free synchronization) +- **Frame Data:** Raw frame bytes + +**Example Buffer Sizes:** +- 1920×1080 RGBA: 8294528 bytes (1920×1080×4 + 128) +- 1280×720 RGBA: 3686528 bytes (1280×720×4 + 128) + +--- + +### 22. Disable Shared Memory + +Disable and cleanup shared memory output. + +**Command:** +```json +{ + "command": "disable_shared_memory" +} +``` + +**Response:** +```json +{ + "status": "success", + "message": "Shared memory disabled" +} +``` + +--- + +### 23. Get Shared Memory Status + +Query the current shared memory configuration. + +**Command:** +```json +{ + "command": "get_shared_memory_status" +} +``` + +**Response (when enabled):** +```json +{ + "status": "success", + "shared_memory_enabled": true, + "name": "/vizion_frame", + "size": 8294528 +} +``` + +**Response (when disabled):** +```json +{ + "status": "success", + "shared_memory_enabled": false +} +``` + +--- + ## Usage Examples ### Complete Workflow Example @@ -620,14 +718,21 @@ echo '{"command":"set_ehdr_exposure_max","params":{"value":"4"}}' | socat - UNIX echo '{"command":"set_ehdr_ratio_min","params":{"value":"12"}}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock echo '{"command":"set_ehdr_ratio_max","params":{"value":"24"}}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock +# 3b. (Optional) Enable shared memory output +echo '{"command":"enable_shared_memory","params":{"name":"/vizion_frame","buffer_size":"8294528"}}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock + # 4. Start streaming echo '{"command":"start_stream"}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock # 5. Check status echo '{"command":"get_status"}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock +echo '{"command":"get_shared_memory_status"}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock # 6. Stop streaming when done echo '{"command":"stop_stream"}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock + +# 7. (Optional) Disable shared memory +echo '{"command":"disable_shared_memory"}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock ``` ### GStreamer Pipeline Examples @@ -692,6 +797,28 @@ echo '{"command":"set_ehdr_ratio_max","params":{"value":"24"}}' | socat - UNIX-C echo '{"command":"get_ehdr_status"}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock ``` +### Shared Memory Control Examples + +```bash +# Enable shared memory with default settings +echo '{"command":"enable_shared_memory"}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock + +# Enable shared memory with custom name and size +echo '{"command":"enable_shared_memory","params":{"name":"/vizion_cam0","buffer_size":"8294528"}}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock + +# Get shared memory status +echo '{"command":"get_shared_memory_status"}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock + +# Disable shared memory +echo '{"command":"disable_shared_memory"}' | socat - UNIX-CONNECT:/tmp/vizion_control.sock + +# Verify shared memory file exists (should show ~8.3MB file) +ls -lh /dev/shm/vizion_frame + +# Watch shared memory updates in real-time (check modification time) +watch -n 0.1 'ls -l /dev/shm/vizion_frame' +``` + ### Using `nc` (netcat with Unix socket support) ```bash @@ -736,6 +863,17 @@ print(send_command("set_ehdr_exposure_max", {"value": "4"})) print(send_command("set_ehdr_ratio_min", {"value": "12"})) print(send_command("set_ehdr_ratio_max", {"value": "24"})) print(send_command("get_ehdr_status")) # Get current eHDR settings + +# Shared memory control examples +print(send_command("enable_shared_memory", { + "name": "/vizion_frame", + "buffer_size": "8294528" +})) +print(send_command("get_shared_memory_status")) +print(send_command("start_stream")) +# ... streaming active, external process can read /dev/shm/vizion_frame ... +print(send_command("stop_stream")) +print(send_command("disable_shared_memory")) ``` ### Using C++ @@ -777,6 +915,14 @@ int main() { std::cout << sendCommand(R"({"command":"set_ehdr_exposure_max","params":{"value":"4"}})") << std::endl; std::cout << sendCommand(R"({"command":"get_ehdr_status"})") << std::endl; + // Shared memory control examples + std::cout << sendCommand(R"({"command":"enable_shared_memory","params":{"name":"/vizion_frame","buffer_size":"8294528"}})") << std::endl; + std::cout << sendCommand(R"({"command":"get_shared_memory_status"})") << std::endl; + std::cout << sendCommand(R"({"command":"start_stream"})") << std::endl; + // ... streaming active, external process can read /dev/shm/vizion_frame ... + std::cout << sendCommand(R"({"command":"stop_stream"})") << std::endl; + std::cout << sendCommand(R"({"command":"disable_shared_memory"})") << std::endl; + return 0; } ``` @@ -873,6 +1019,291 @@ ffplay http://192.168.1.100:8080 curl http://192.168.1.100:8080 > stream.mjpg ``` +## Shared Memory Reader Implementation + +When shared memory output is enabled, external processes can directly read frame data from `/dev/shm/`. Here's how to implement a reader: + +### Shared Memory Structure + +```c +struct SharedMemoryHeader { + uint32_t magic; // 0x56495A4E ("VIZN") - for validation + uint32_t width; // Frame width in pixels + uint32_t height; // Frame height in pixels + uint32_t format; // Format enum (VX_IMAGE_FORMAT) + uint32_t data_size; // Frame data size in bytes + uint64_t timestamp_ns; // Timestamp in nanoseconds + uint32_t frame_sequence; // Monotonic frame counter + atomic_uint32_t write_sequence; // Lock-free sync counter + char format_str[16]; // Format string ("YUY2", "MJPG", etc.) + uint8_t reserved[72]; // Reserved (padding to 128 bytes) +}; +// Frame data starts at offset 128 +``` + +### Lock-Free Read Protocol + +The `write_sequence` counter enables lock-free synchronization: +- **Even values**: Write complete, data is consistent +- **Odd values**: Write in progress, data may be inconsistent + +**Reader Algorithm:** +1. Read `write_sequence` (must be even) +2. Read header and frame data +3. Read `write_sequence` again +4. If values match → data is consistent +5. If values differ → retry + +### C++ Reader Example + +```cpp +#include +#include +#include +#include +#include +#include +#include + +struct SharedMemoryHeader { + uint32_t magic; + uint32_t width; + uint32_t height; + uint32_t format; + uint32_t data_size; + uint64_t timestamp_ns; + uint32_t frame_sequence; + std::atomic write_sequence; + char format_str[16]; + uint8_t reserved[72]; +}; + +class SharedMemoryReader { +private: + int fd_; + void* ptr_; + size_t size_; + uint32_t last_sequence_; + +public: + SharedMemoryReader(const char* name, size_t size) + : fd_(-1), ptr_(nullptr), size_(size), last_sequence_(0) { + + // Open shared memory + fd_ = shm_open(name, O_RDONLY, 0666); + if (fd_ < 0) { + throw std::runtime_error("Failed to open shared memory"); + } + + // Map memory + ptr_ = mmap(nullptr, size_, PROT_READ, MAP_SHARED, fd_, 0); + if (ptr_ == MAP_FAILED) { + close(fd_); + throw std::runtime_error("Failed to map shared memory"); + } + } + + ~SharedMemoryReader() { + if (ptr_ != nullptr && ptr_ != MAP_FAILED) { + munmap(ptr_, size_); + } + if (fd_ >= 0) { + close(fd_); + } + } + + bool readFrame(uint8_t* buffer, size_t buffer_size, + uint32_t* width, uint32_t* height, + char* format, uint64_t* timestamp) { + + auto* header = static_cast(ptr_); + auto* frame_data = static_cast(ptr_) + sizeof(SharedMemoryHeader); + + uint32_t seq1, seq2; + do { + // Read sequence number + seq1 = header->write_sequence.load(std::memory_order_acquire); + + // Wait if write is in progress (odd sequence) + while (seq1 & 1) { + seq1 = header->write_sequence.load(std::memory_order_acquire); + } + + // Validate magic number + if (header->magic != 0x56495A4E) { + std::cerr << "Invalid magic number" << std::endl; + return false; + } + + // Check if this is a new frame + if (header->frame_sequence <= last_sequence_) { + return false; // Already seen this frame + } + + // Check buffer size + if (header->data_size > buffer_size) { + std::cerr << "Buffer too small" << std::endl; + return false; + } + + // Read metadata + *width = header->width; + *height = header->height; + *timestamp = header->timestamp_ns; + strncpy(format, header->format_str, 15); + format[15] = '\0'; + + // Copy frame data + memcpy(buffer, frame_data, header->data_size); + + // Verify sequence hasn't changed + seq2 = header->write_sequence.load(std::memory_order_acquire); + + } while (seq1 != seq2); + + last_sequence_ = header->frame_sequence; + return true; + } +}; + +// Usage example +int main() { + try { + SharedMemoryReader reader("/vizion_frame", 8294528); + + std::vector frame_buffer(8294400); // 1920x1080x4 + uint32_t width, height; + char format[16]; + uint64_t timestamp; + + while (true) { + if (reader.readFrame(frame_buffer.data(), frame_buffer.size(), + &width, &height, format, ×tamp)) { + std::cout << "New frame: " << width << "x" << height + << " format=" << format + << " timestamp=" << timestamp << std::endl; + + // Process frame data here... + } + + usleep(10000); // Poll every 10ms + } + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + + return 0; +} +``` + +### Python Reader Example + +```python +import mmap +import struct +import time +from pathlib import Path + +class SharedMemoryReader: + HEADER_SIZE = 128 + MAGIC = 0x56495A4E # "VIZN" + + def __init__(self, name, size): + self.name = name + self.size = size + self.last_sequence = 0 + + # Open shared memory file + shm_path = Path(f"/dev/shm{name}") + self.fd = open(shm_path, "rb") + self.mmap = mmap.mmap(self.fd.fileno(), size, access=mmap.ACCESS_READ) + + def close(self): + if self.mmap: + self.mmap.close() + if self.fd: + self.fd.close() + + def read_frame(self): + while True: + # Read write_sequence (offset 28) + self.mmap.seek(28) + seq1 = struct.unpack('I', self.mmap.read(4))[0] + + # Wait if write in progress (odd) + while seq1 & 1: + time.sleep(0.0001) + self.mmap.seek(28) + seq1 = struct.unpack('I', self.mmap.read(4))[0] + + # Read header + self.mmap.seek(0) + header_bytes = self.mmap.read(self.HEADER_SIZE) + + magic, width, height, fmt, data_size, timestamp, frame_seq = \ + struct.unpack('IIIIIQII', header_bytes[:40]) + + format_str = header_bytes[40:56].decode('utf-8').strip('\x00') + + # Validate magic + if magic != self.MAGIC: + return None + + # Check if new frame + if frame_seq <= self.last_sequence: + return None + + # Read frame data + frame_data = self.mmap.read(data_size) + + # Verify sequence + self.mmap.seek(28) + seq2 = struct.unpack('I', self.mmap.read(4))[0] + + if seq1 == seq2: + self.last_sequence = frame_seq + return { + 'width': width, + 'height': height, + 'format': format_str, + 'timestamp': timestamp, + 'sequence': frame_seq, + 'data': frame_data + } + +# Usage +reader = SharedMemoryReader("/vizion_frame", 8294528) +try: + while True: + frame = reader.read_frame() + if frame: + print(f"New frame: {frame['width']}x{frame['height']} " + f"format={frame['format']} seq={frame['sequence']}") + # Process frame['data'] here... + time.sleep(0.01) # Poll every 10ms +finally: + reader.close() +``` + +### Performance Considerations + +**Polling vs. Blocking:** +- The reader examples use polling (checking periodically) +- For lower CPU usage, increase poll interval +- For lower latency, decrease poll interval +- Alternative: Use inotify to watch `/dev/shm/` for modifications + +**Memory Bandwidth:** +- Reading shared memory creates an additional memcpy +- For zero-copy processing, process data in-place (requires careful synchronization) +- Multiple readers can access the same shared memory simultaneously + +**Frame Rate:** +- Reader must keep up with writer's frame rate +- Use `frame_sequence` to detect dropped frames +- Monitor `last_sequence` vs `header->frame_sequence` difference + ## Notes - The socket file is automatically created when VizionStreamer starts @@ -885,3 +1316,7 @@ curl http://192.168.1.100:8080 > stream.mjpg - Default pipeline: `videoconvert ! autovideosink` (display locally) - eHDR features require compatible camera models (VCI/VCS/VLS3/VLS-GM2/TEVS-AR0821/AR0822) - eHDR settings may be reset to defaults when the camera starts streaming (driver behavior) +- Shared memory output is independent of GStreamer pipeline (both run in parallel) +- Shared memory must be enabled before starting the stream +- Shared memory files are automatically cleaned up when disabled or on clean exit +- Multiple processes can read from the same shared memory region simultaneously