Files
video-setup/include/protocol.h
mikael-lovqvists-claude-agent 835cbbafba Fix connect accumulation; add display sinks to enum-devices
- controller_cli: drain semaphore and reset pending_cmd in do_connect
  so stale posts from old connection don't unblock the next command
- protocol: add Proto_Display_Device_Info; extend
  proto_write_enum_devices_response and proto_read_enum_devices_response
  with display section; backward-compatible (absent in older messages)
- node: handle_enum_devices snapshots active Display_Slots under mutex
  and includes them in the response
- controller_cli: on_display callback prints display window info in
  enum-devices output
- query_cli: updated to pass NULL on_display (no display interest)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 19:48:22 +00:00

535 lines
18 KiB
C

#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
#define PROTO_CMD_START_INGEST 0x0008u
#define PROTO_CMD_STOP_INGEST 0x0009u
#define PROTO_CMD_START_DISPLAY 0x000Au
#define PROTO_CMD_STOP_DISPLAY 0x000Bu
/* -------------------------------------------------------------------------
* 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
/* -------------------------------------------------------------------------
* Transport mode codes (START_INGEST transport_mode field)
* ------------------------------------------------------------------------- */
#define PROTO_TRANSPORT_ENCAPSULATED 0x0001u /* framed: message_type + payload_length header */
#define PROTO_TRANSPORT_OPAQUE 0x0002u /* raw byte stream, no frame boundaries */
/* -------------------------------------------------------------------------
* 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;
};
/*
* An active display window (video sink role).
* stream_id is the stream being displayed; win_* are current geometry.
*/
struct Proto_Display_Device_Info {
uint16_t stream_id;
int16_t win_x, win_y;
uint16_t win_w, win_h;
uint8_t scale;
uint8_t anchor;
};
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;
};
/*
* START_INGEST: controller tells a source node to open a V4L2 device and
* connect outbound to a sink at dest_host:dest_port.
* format/width/height/fps_n/fps_d of 0 mean auto-select.
* Strings point into the caller's payload buffer; not NUL-terminated.
*/
struct Proto_Start_Ingest {
uint16_t request_id;
uint16_t stream_id;
uint16_t format; /* PROTO_FORMAT_* code; 0 = auto (best MJPEG) */
uint16_t width; /* 0 = auto */
uint16_t height; /* 0 = auto */
uint16_t fps_n; /* 0 = auto */
uint16_t fps_d;
uint16_t dest_port;
uint16_t transport_mode; /* PROTO_TRANSPORT_ENCAPSULATED or PROTO_TRANSPORT_OPAQUE */
const char *device_path;
uint8_t device_path_len;
const char *dest_host;
uint8_t dest_host_len;
};
struct Proto_Stop_Ingest {
uint16_t request_id;
uint16_t stream_id;
};
/*
* START_DISPLAY: controller tells a sink node to open a viewer window and
* display incoming VIDEO_FRAME messages for the given stream_id.
* win_x/win_y are screen-space window position (signed: multi-monitor).
* win_w/win_h of 0 mean use a default size.
* scale: 0=stretch 1=fit 2=fill 3=1:1 (PROTO_DISPLAY_SCALE_*)
* anchor: 0=center 1=topleft (PROTO_DISPLAY_ANCHOR_*)
*/
struct Proto_Start_Display {
uint16_t request_id;
uint16_t stream_id;
int16_t win_x;
int16_t win_y;
uint16_t win_w;
uint16_t win_h;
uint8_t scale;
uint8_t anchor;
uint8_t no_signal_fps; /* 0 = default (15); no-signal animation frame rate */
/* 1 byte reserved */
};
struct Proto_Stop_Display {
uint16_t request_id;
uint16_t stream_id;
};
/* Scale/anchor constants for Proto_Start_Display */
#define PROTO_DISPLAY_SCALE_STRETCH 0u
#define PROTO_DISPLAY_SCALE_FIT 1u
#define PROTO_DISPLAY_SCALE_FILL 2u
#define PROTO_DISPLAY_SCALE_1_1 3u
#define PROTO_DISPLAY_ANCHOR_CENTER 0u
#define PROTO_DISPLAY_ANCHOR_TOPLEFT 1u
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_REQUEST: START_INGEST */
struct App_Error proto_write_start_ingest(struct Transport_Conn *conn,
uint16_t request_id, uint16_t stream_id,
uint16_t format, uint16_t width, uint16_t height,
uint16_t fps_n, uint16_t fps_d,
uint16_t transport_mode,
const char *device_path, const char *dest_host, uint16_t dest_port);
/* CONTROL_REQUEST: STOP_INGEST */
struct App_Error proto_write_stop_ingest(struct Transport_Conn *conn,
uint16_t request_id, uint16_t stream_id);
/* CONTROL_REQUEST: START_DISPLAY */
struct App_Error proto_write_start_display(struct Transport_Conn *conn,
uint16_t request_id, uint16_t stream_id,
int16_t win_x, int16_t win_y, uint16_t win_w, uint16_t win_h,
uint8_t scale, uint8_t anchor, uint8_t no_signal_fps);
/* CONTROL_REQUEST: STOP_DISPLAY */
struct App_Error proto_write_stop_display(struct Transport_Conn *conn,
uint16_t request_id, uint16_t stream_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,
const struct Proto_Display_Device_Info *displays, uint16_t display_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);
struct App_Error proto_read_start_ingest(
const uint8_t *payload, uint32_t length,
struct Proto_Start_Ingest *out);
struct App_Error proto_read_stop_ingest(
const uint8_t *payload, uint32_t length,
struct Proto_Stop_Ingest *out);
struct App_Error proto_read_start_display(
const uint8_t *payload, uint32_t length,
struct Proto_Start_Display *out);
struct App_Error proto_read_stop_display(
const uint8_t *payload, uint32_t length,
struct Proto_Stop_Display *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 (*on_display)(
uint16_t stream_id,
int16_t win_x, int16_t win_y,
uint16_t win_w, uint16_t win_h,
uint8_t scale, uint8_t anchor,
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);