add SharedMemoryWriter support
This commit is contained in:
@@ -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, ×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/<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
|
||||
|
||||
Reference in New Issue
Block a user