add SharedMemoryWriter support

This commit is contained in:
Maik Jurischka
2026-01-29 09:56:36 +01:00
parent 72f6bbe8df
commit cd290bdd07

View File

@@ -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/<name>` 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/<name>`. 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 <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <atomic>
#include <cstring>
#include <iostream>
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<uint32_t> 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<SharedMemoryHeader*>(ptr_);
auto* frame_data = static_cast<uint8_t*>(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<uint8_t> 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, &timestamp)) {
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/<name>` 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