Overview
The Video Frame module provides structures and utilities for managing video frames throughout the ascii-chat pipeline. It handles RGB image data, frame metadata, and efficient frame lifecycle management.
Implementation: lib/video_frame.h
Key Features:
- Zero-copy frame transfer (pointer swaps only)
- Lock-free double-buffering system
- Latest-frame-wins semantics (old frames dropped, not queued)
- Pre-allocated buffers for zero-allocation operation
- RGB24 image frame representation
- Frame metadata (timestamp, dimensions, client ID)
- Frame sequence numbering for drop detection
- Comprehensive quality metrics tracking
Frame Structure
video_frame_t:
typedef struct {
uint8_t *data;
uint32_t width;
uint32_t height;
size_t data_size;
uint32_t client_id;
uint64_t timestamp;
bool owns_data;
} video_frame_t;
Frame Properties:
- data: RGB24 format (3 bytes per pixel: R, G, B)
- width/height: Frame dimensions
- data_size: Always equals width * height * 3
- client_id: Which client this frame belongs to
- timestamp: Microsecond-precision capture time
- owns_data: Memory ownership flag
Operations
Double-Buffering API
The recommended way to use video frames is through the lock-free double-buffering API:
Writer API (producer thread):
memcpy(write_buffer, captured_rgb_data, width * height * 3);
video_frame_t * video_frame_begin_write(video_frame_buffer_t *vfb)
void video_frame_commit(video_frame_buffer_t *vfb)
Reader API (consumer thread):
if (frame) {
render_ascii(frame->data, frame->width, frame->height);
}
const video_frame_t * video_frame_get_latest(video_frame_buffer_t *vfb)
Manual Creation (for special cases)
For cases where you need standalone frames without the double-buffering system:
Create from Existing Data:
video_frame_t frame = {
.data = existing_rgb_data,
.width = 1920,
.height = 1080,
.data_size = 1920 * 1080 * 3,
.client_id = client_id,
.timestamp = get_timestamp_usec(),
.owns_data = false
};
Data Access
Pixel Access:
size_t offset = (y * frame->width + x) * 3;
uint8_t r = frame->data[offset + 0];
uint8_t g = frame->data[offset + 1];
uint8_t b = frame->data[offset + 2];
frame->data[offset + 0] = 255;
frame->data[offset + 1] = 128;
frame->data[offset + 2] = 0;
Row Access:
uint8_t *row = &frame->data[y * frame->width * 3];
for (uint32_t x = 0; x < frame->width; x++) {
uint8_t r = row[x * 3 + 0];
uint8_t g = row[x * 3 + 1];
uint8_t b = row[x * 3 + 2];
}
Copying
Deep Copy:
video_frame_t *copy = SAFE_MALLOC(sizeof(video_frame_t), video_frame_t *);
copy->width = src->width;
copy->height = src->height;
copy->data_size = src->data_size;
copy->client_id = src->client_id;
copy->timestamp = src->timestamp;
copy->owns_data = true;
copy->data = SAFE_MALLOC(copy->data_size, uint8_t *);
memcpy(copy->data, src->data, copy->data_size);
Shallow Copy (reference):
video_frame_t reference = *src;
reference.owns_data = false;
Cleanup
Free Frame:
if (frame->owns_data && frame->data) {
free(frame->data);
frame->data = NULL;
}
free(frame);
frame = NULL;
Validation
Validate Frame:
bool is_valid_frame(const video_frame_t *frame) {
if (!frame) return false;
if (!frame->data) return false;
if (frame->width == 0 || frame->height == 0) return false;
if (frame->data_size != frame->width * frame->height * 3) return false;
return true;
}
Check Frame Dimensions:
if (frame->width > MAX_WIDTH || frame->height > MAX_HEIGHT) {
log_error("Frame dimensions too large: %ux%u", frame->width, frame->height);
return ERROR_INVALID_PARAM;
}
Ownership Rules
Ownership Flag:
owns_data = true: Frame owns the data buffer, must free on cleanup
owns_data = false: Frame references external data, do NOT free
Ownership Transfer:
video_frame_t *frame = create_frame();
frame->owns_data = true;
send_to_queue(frame);
video_frame_t *received = receive_from_queue();
if (received->owns_data) {
free(received->data);
}
Frame Pipeline
Capture → Process → Encode → Send:
video_frame_t *frame = webcam_capture();
frame->owns_data = true;
frame->timestamp = get_timestamp_usec();
video_frame_t *processed = process_frame(frame);
ascii_frame_t *ascii = image_to_ascii(processed);
send_ascii_frame(client_id, ascii);
if (frame->owns_data) free(frame->data);
free(frame);
if (processed->owns_data) free(processed->data);
free(processed);
free(ascii);
Memory Management
Allocation Strategy:
size_t data_size = (size_t)width * (size_t)height * 3;
uint8_t *data = SAFE_MALLOC(data_size, uint8_t *);
if (!data) {
return SET_ERRNO(ERROR_OUT_OF_MEMORY, "Failed to allocate frame data");
}
Buffer Reuse:
if (frame->data_size == new_size) {
memcpy(frame->data, new_data, new_size);
} else {
if (frame->owns_data) free(frame->data);
frame->data = SAFE_MALLOC(new_size, uint8_t *);
frame->data_size = new_size;
}
Timestamps
Microsecond Precision:
#include <sys/time.h>
uint64_t get_timestamp_usec(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec;
}
frame->timestamp = get_timestamp_usec();
Calculate Frame Age:
uint64_t now = get_timestamp_usec();
uint64_t age_usec = now - frame->timestamp;
double age_ms = age_usec / 1000.0;
if (age_ms > 100.0) {
log_warn("Frame is stale: %.1fms old", age_ms);
}
Best Practices
DO:
- Always set
owns_data correctly
- Validate frame dimensions before allocation
- Use overflow-safe size calculations
- Set timestamp when capturing frames
- Free frames when done
- Check for NULL before dereferencing
DON'T:
- Don't free frame->data if owns_data is false
- Don't use frame after freeing
- Don't assume frame->data is aligned
- Don't modify shared frame data
- Don't forget to set client_id
- Don't use stale frames (check timestamp)
Integration Example
Complete Usage:
video_frame_t *frame = SAFE_MALLOC(sizeof(video_frame_t), video_frame_t *);
frame->width = 640;
frame->height = 480;
frame->data_size = 640 * 480 * 3;
frame->data = SAFE_MALLOC(frame->data_size, uint8_t *);
frame->owns_data = true;
frame->client_id = my_client_id;
frame->timestamp = get_timestamp_usec();
process_and_send_frame(frame);
if (frame->owns_data && frame->data) {
free(frame->data);
}
free(frame);
image_t * webcam_read(void)
- See also
- video_frame.h
-
video/image.h
-
os/webcam.h