Add protocol module, video-node binary, query/web CLI tools
- Protocol module: framed binary encoding for control requests/responses (ENUM_DEVICES, ENUM_CONTROLS, GET/SET_CONTROL, STREAM_OPEN/CLOSE) - video-node: scans /dev/media* and /dev/video*, serves V4L2 device topology and controls over TCP; uses UDP discovery for peer announce - query_cli: auto-discovers a node, queries devices and controls - protocol_cli: low-level protocol frame decoder for debugging - dev/web: Express 5 ESM web inspector — live SSE discovery picker, REST bridge to video-node, controls UI with sliders/selects/checkboxes - Makefile: sequential module builds before cli/node to fix make -j races - common.mk: add DEPFLAGS (-MMD -MP) for automatic header dependencies - All module Makefiles: split compile/link, generate .d dependency files - discovery: replace 100ms poll loop with pthread_cond_timedwait; respond to all announcements (not just new peers) for instant re-discovery - ENUM_DEVICES response: carry device_caps (V4L2_CAP_*) per video node so clients can distinguish capture nodes from metadata nodes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
404
include/protocol.h
Normal file
404
include/protocol.h
Normal file
@@ -0,0 +1,404 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "error.h"
|
||||
#include "transport.h"
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Message type constants
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define PROTO_MSG_VIDEO_FRAME 0x0001u
|
||||
#define PROTO_MSG_CONTROL_REQUEST 0x0002u
|
||||
#define PROTO_MSG_CONTROL_RESPONSE 0x0003u
|
||||
#define PROTO_MSG_STREAM_EVENT 0x0004u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Command codes (carried in CONTROL_REQUEST payload offset 2)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define PROTO_CMD_STREAM_OPEN 0x0001u
|
||||
#define PROTO_CMD_STREAM_CLOSE 0x0002u
|
||||
#define PROTO_CMD_ENUM_DEVICES 0x0003u
|
||||
#define PROTO_CMD_ENUM_CONTROLS 0x0004u
|
||||
#define PROTO_CMD_GET_CONTROL 0x0005u
|
||||
#define PROTO_CMD_SET_CONTROL 0x0006u
|
||||
#define PROTO_CMD_ENUM_MONITORS 0x0007u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Response status codes (carried in CONTROL_RESPONSE payload offset 2)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define PROTO_STATUS_OK 0x0000u
|
||||
#define PROTO_STATUS_ERROR 0x0001u
|
||||
#define PROTO_STATUS_UNKNOWN_CMD 0x0002u
|
||||
#define PROTO_STATUS_INVALID_PARAMS 0x0003u
|
||||
#define PROTO_STATUS_NOT_FOUND 0x0004u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Stream event codes (carried in STREAM_EVENT payload offset 2)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define PROTO_EVENT_INTERRUPTED 0x01u
|
||||
#define PROTO_EVENT_RESUMED 0x02u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Codec format codes (STREAM_OPEN format field)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define PROTO_FORMAT_MJPEG 0x0001u
|
||||
#define PROTO_FORMAT_H264 0x0002u
|
||||
#define PROTO_FORMAT_H265 0x0003u
|
||||
#define PROTO_FORMAT_AV1 0x0004u
|
||||
#define PROTO_FORMAT_FFV1 0x0005u
|
||||
#define PROTO_FORMAT_PRORES 0x0006u
|
||||
#define PROTO_FORMAT_QOI 0x0007u
|
||||
#define PROTO_FORMAT_RAW 0x0008u
|
||||
#define PROTO_FORMAT_RAW_ZSTD 0x0009u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Pixel format codes (STREAM_OPEN pixel_format field; 0 for compressed)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define PROTO_PIXEL_BGRA8888 0x0001u
|
||||
#define PROTO_PIXEL_RGBA8888 0x0002u
|
||||
#define PROTO_PIXEL_BGR888 0x0003u
|
||||
#define PROTO_PIXEL_YUV420P 0x0004u
|
||||
#define PROTO_PIXEL_YUV422 0x0005u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Origin codes (STREAM_OPEN origin field; informational only)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define PROTO_ORIGIN_DEVICE_NATIVE 0x0001u
|
||||
#define PROTO_ORIGIN_LIBJPEG_TURBO 0x0002u
|
||||
#define PROTO_ORIGIN_FFMPEG_LIBAV 0x0003u
|
||||
#define PROTO_ORIGIN_FFMPEG_PROC 0x0004u
|
||||
#define PROTO_ORIGIN_VAAPI 0x0005u
|
||||
#define PROTO_ORIGIN_NVENC 0x0006u
|
||||
#define PROTO_ORIGIN_SOFTWARE 0x0007u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Structs used by write functions (variable-length response payloads)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
struct Proto_Menu_Item {
|
||||
uint32_t index;
|
||||
const char *name;
|
||||
int64_t int_value;
|
||||
};
|
||||
|
||||
struct Proto_Control_Info {
|
||||
uint32_t id;
|
||||
uint8_t type;
|
||||
uint32_t flags;
|
||||
const char *name;
|
||||
int32_t min, max, step, default_val, current_val;
|
||||
uint8_t menu_count;
|
||||
const struct Proto_Menu_Item *menu_items;
|
||||
};
|
||||
|
||||
/*
|
||||
* A video node associated with a media controller device.
|
||||
* entity_type and entity_flags are MEDIA_ENT_F_* / MEDIA_ENT_FL_* values.
|
||||
* pad_flags uses MEDIA_PAD_FLAG_SOURCE / MEDIA_PAD_FLAG_SINK.
|
||||
* is_capture: 1 if this node is the primary video capture output.
|
||||
*/
|
||||
struct Proto_Video_Node_Info {
|
||||
const char *path;
|
||||
const char *entity_name;
|
||||
uint32_t entity_type;
|
||||
uint32_t entity_flags;
|
||||
uint32_t device_caps; /* V4L2_CAP_* bits from VIDIOC_QUERYCAP */
|
||||
uint8_t pad_flags;
|
||||
uint8_t is_capture;
|
||||
};
|
||||
|
||||
/*
|
||||
* A media controller device and its associated video nodes.
|
||||
* video_node_count must be <= 255.
|
||||
*/
|
||||
struct Proto_Media_Device_Info {
|
||||
const char *path;
|
||||
const char *driver;
|
||||
const char *model;
|
||||
const char *bus_info;
|
||||
uint8_t video_node_count;
|
||||
const struct Proto_Video_Node_Info *video_nodes;
|
||||
};
|
||||
|
||||
/*
|
||||
* A standalone V4L2 device with no associated media controller.
|
||||
* name is the card name from VIDIOC_QUERYCAP.
|
||||
*/
|
||||
struct Proto_Standalone_Device_Info {
|
||||
const char *path;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
struct Proto_Monitor_Info {
|
||||
int32_t x, y;
|
||||
uint32_t width, height;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Structs used by read functions
|
||||
* Fields with pointer types point INTO the caller's payload buffer.
|
||||
* The caller must keep the payload alive while using those pointers.
|
||||
* Strings are NOT NUL-terminated.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
struct Proto_Video_Frame {
|
||||
uint16_t stream_id;
|
||||
const uint8_t *data;
|
||||
uint32_t data_len;
|
||||
};
|
||||
|
||||
struct Proto_Stream_Event {
|
||||
uint16_t stream_id;
|
||||
uint8_t event_code;
|
||||
};
|
||||
|
||||
struct Proto_Request_Header {
|
||||
uint16_t request_id;
|
||||
uint16_t command;
|
||||
};
|
||||
|
||||
struct Proto_Stream_Open {
|
||||
uint16_t request_id;
|
||||
uint16_t stream_id;
|
||||
uint16_t format;
|
||||
uint16_t pixel_format;
|
||||
uint16_t origin;
|
||||
};
|
||||
|
||||
struct Proto_Stream_Close {
|
||||
uint16_t request_id;
|
||||
uint16_t stream_id;
|
||||
};
|
||||
|
||||
struct Proto_Enum_Controls_Req {
|
||||
uint16_t request_id;
|
||||
uint16_t device_index;
|
||||
};
|
||||
|
||||
struct Proto_Get_Control_Req {
|
||||
uint16_t request_id;
|
||||
uint16_t device_index;
|
||||
uint32_t control_id;
|
||||
};
|
||||
|
||||
struct Proto_Set_Control_Req {
|
||||
uint16_t request_id;
|
||||
uint16_t device_index;
|
||||
uint32_t control_id;
|
||||
int32_t value;
|
||||
};
|
||||
|
||||
struct Proto_Response_Header {
|
||||
uint16_t request_id;
|
||||
uint16_t status;
|
||||
};
|
||||
|
||||
struct Proto_Get_Control_Resp {
|
||||
uint16_t request_id;
|
||||
uint16_t status;
|
||||
int32_t value;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Write functions — serialize and send via transport_send_frame.
|
||||
* All return APP_OK or an error from the transport layer.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* VIDEO_FRAME: prepends stream_id (2 bytes) to data and sends.
|
||||
* data/data_len is the compressed frame; the stream must already be open.
|
||||
*/
|
||||
struct App_Error proto_write_video_frame(struct Transport_Conn *conn,
|
||||
uint16_t stream_id, const uint8_t *data, uint32_t data_len);
|
||||
|
||||
/* STREAM_EVENT (3 bytes) */
|
||||
struct App_Error proto_write_stream_event(struct Transport_Conn *conn,
|
||||
uint16_t stream_id, uint8_t event_code);
|
||||
|
||||
/* CONTROL_REQUEST: STREAM_OPEN (12 bytes) */
|
||||
struct App_Error proto_write_stream_open(struct Transport_Conn *conn,
|
||||
uint16_t request_id, uint16_t stream_id,
|
||||
uint16_t format, uint16_t pixel_format, uint16_t origin);
|
||||
|
||||
/* CONTROL_REQUEST: STREAM_CLOSE (6 bytes) */
|
||||
struct App_Error proto_write_stream_close(struct Transport_Conn *conn,
|
||||
uint16_t request_id, uint16_t stream_id);
|
||||
|
||||
/* CONTROL_REQUEST: ENUM_DEVICES (4 bytes, no extra fields) */
|
||||
struct App_Error proto_write_enum_devices(struct Transport_Conn *conn,
|
||||
uint16_t request_id);
|
||||
|
||||
/* CONTROL_REQUEST: ENUM_CONTROLS (6 bytes) */
|
||||
struct App_Error proto_write_enum_controls(struct Transport_Conn *conn,
|
||||
uint16_t request_id, uint16_t device_index);
|
||||
|
||||
/* CONTROL_REQUEST: GET_CONTROL (10 bytes) */
|
||||
struct App_Error proto_write_get_control(struct Transport_Conn *conn,
|
||||
uint16_t request_id, uint16_t device_index, uint32_t control_id);
|
||||
|
||||
/* CONTROL_REQUEST: SET_CONTROL (14 bytes) */
|
||||
struct App_Error proto_write_set_control(struct Transport_Conn *conn,
|
||||
uint16_t request_id, uint16_t device_index,
|
||||
uint32_t control_id, int32_t value);
|
||||
|
||||
/* CONTROL_REQUEST: ENUM_MONITORS (4 bytes, no extra fields) */
|
||||
struct App_Error proto_write_enum_monitors(struct Transport_Conn *conn,
|
||||
uint16_t request_id);
|
||||
|
||||
/*
|
||||
* CONTROL_RESPONSE: generic.
|
||||
* payload/payload_len are the command-specific bytes after request_id+status.
|
||||
* Pass payload=NULL, payload_len=0 for responses with no extra fields
|
||||
* (e.g. STREAM_OPEN ok, STREAM_CLOSE, SET_CONTROL).
|
||||
*/
|
||||
struct App_Error proto_write_control_response(struct Transport_Conn *conn,
|
||||
uint16_t request_id, uint16_t status,
|
||||
const uint8_t *payload, uint32_t payload_len);
|
||||
|
||||
/* CONTROL_RESPONSE: ENUM_DEVICES */
|
||||
struct App_Error proto_write_enum_devices_response(struct Transport_Conn *conn,
|
||||
uint16_t request_id, uint16_t status,
|
||||
const struct Proto_Media_Device_Info *media_devices, uint16_t media_count,
|
||||
const struct Proto_Standalone_Device_Info *standalone, uint16_t standalone_count);
|
||||
|
||||
/* CONTROL_RESPONSE: ENUM_CONTROLS */
|
||||
struct App_Error proto_write_enum_controls_response(struct Transport_Conn *conn,
|
||||
uint16_t request_id, uint16_t status,
|
||||
const struct Proto_Control_Info *controls, uint16_t count);
|
||||
|
||||
/* CONTROL_RESPONSE: GET_CONTROL */
|
||||
struct App_Error proto_write_get_control_response(struct Transport_Conn *conn,
|
||||
uint16_t request_id, uint16_t status, int32_t value);
|
||||
|
||||
/* CONTROL_RESPONSE: ENUM_MONITORS */
|
||||
struct App_Error proto_write_enum_monitors_response(struct Transport_Conn *conn,
|
||||
uint16_t request_id, uint16_t status,
|
||||
const struct Proto_Monitor_Info *monitors, uint16_t count);
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Read functions — parse raw payload bytes into typed structs.
|
||||
* All return APP_INVALID_ERROR_MSG(0, ...) on malformed payloads.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
struct App_Error proto_read_video_frame(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Video_Frame *out);
|
||||
|
||||
struct App_Error proto_read_stream_event(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Stream_Event *out);
|
||||
|
||||
/*
|
||||
* Read the common 4-byte request header (request_id + command).
|
||||
* Dispatch on header.command, then call the appropriate specific reader.
|
||||
* ENUM_DEVICES and ENUM_MONITORS have no extra fields beyond the header.
|
||||
*/
|
||||
struct App_Error proto_read_request_header(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Request_Header *out);
|
||||
|
||||
struct App_Error proto_read_stream_open(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Stream_Open *out);
|
||||
|
||||
struct App_Error proto_read_stream_close(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Stream_Close *out);
|
||||
|
||||
struct App_Error proto_read_enum_controls_req(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Enum_Controls_Req *out);
|
||||
|
||||
struct App_Error proto_read_get_control_req(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Get_Control_Req *out);
|
||||
|
||||
struct App_Error proto_read_set_control_req(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Set_Control_Req *out);
|
||||
|
||||
/*
|
||||
* Read the common 4-byte response header (request_id + status).
|
||||
* For responses with no extra fields (STREAM_OPEN, STREAM_CLOSE, SET_CONTROL),
|
||||
* this is the complete parse.
|
||||
*/
|
||||
struct App_Error proto_read_response_header(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Response_Header *out);
|
||||
|
||||
struct App_Error proto_read_get_control_response(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Get_Control_Resp *out);
|
||||
|
||||
/*
|
||||
* Variable-length response readers use callbacks to avoid heap allocation.
|
||||
* Strings point into the payload and are NOT NUL-terminated; use *_len.
|
||||
*/
|
||||
/*
|
||||
* on_media_device is called once per media controller device.
|
||||
* on_video_node is called video_node_count times immediately after,
|
||||
* once per video node belonging to that media device.
|
||||
* on_standalone is called once per V4L2 device with no media controller.
|
||||
* Any callback may be NULL.
|
||||
*/
|
||||
struct App_Error proto_read_enum_devices_response(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Response_Header *header_out,
|
||||
void (*on_media_device)(
|
||||
const char *path, uint8_t path_len,
|
||||
const char *driver, uint8_t driver_len,
|
||||
const char *model, uint8_t model_len,
|
||||
const char *bus_info, uint8_t bus_info_len,
|
||||
uint8_t video_node_count,
|
||||
void *userdata),
|
||||
void (*on_video_node)(
|
||||
const char *path, uint8_t path_len,
|
||||
const char *entity_name, uint8_t entity_name_len,
|
||||
uint32_t entity_type, uint32_t entity_flags,
|
||||
uint32_t device_caps,
|
||||
uint8_t pad_flags, uint8_t is_capture,
|
||||
void *userdata),
|
||||
void (*on_standalone)(
|
||||
const char *path, uint8_t path_len,
|
||||
const char *name, uint8_t name_len,
|
||||
void *userdata),
|
||||
void *userdata);
|
||||
|
||||
/*
|
||||
* on_control is called once per control.
|
||||
* on_menu_item is called once per menu item immediately after its on_control
|
||||
* call; menu_count in on_control says how many to expect.
|
||||
* on_menu_item may be NULL if the caller does not need menu items.
|
||||
*/
|
||||
struct App_Error proto_read_enum_controls_response(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Response_Header *header_out,
|
||||
void (*on_control)(
|
||||
uint32_t id, uint8_t type, uint32_t flags,
|
||||
const char *name, uint8_t name_len,
|
||||
int32_t min, int32_t max, int32_t step,
|
||||
int32_t default_val, int32_t current_val,
|
||||
uint8_t menu_count, void *userdata),
|
||||
void (*on_menu_item)(
|
||||
uint32_t index,
|
||||
const char *name, uint8_t name_len,
|
||||
int64_t int_value,
|
||||
void *userdata),
|
||||
void *userdata);
|
||||
|
||||
struct App_Error proto_read_enum_monitors_response(
|
||||
const uint8_t *payload, uint32_t length,
|
||||
struct Proto_Response_Header *header_out,
|
||||
void (*on_monitor)(
|
||||
int32_t x, int32_t y, uint32_t width, uint32_t height,
|
||||
const char *name, uint8_t name_len,
|
||||
void *userdata),
|
||||
void *userdata);
|
||||
Reference in New Issue
Block a user