Add reconciler and ingest modules with CLI driver

reconciler: generic resource state machine — BFS pathfinding from current
to wanted state, dependency constraints, event/periodic tick model.
reconciler_cli exercises it with simulated device/transport/stream resources.

ingest: V4L2 capture module — open device, negotiate MJPEG format, MMAP
buffer pool, capture thread with on_frame callback. start/stop lifecycle
designed for reconciler management. Transport-agnostic: caller wires
on_frame to proto_write_video_frame.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 01:52:17 +00:00
parent 4e40223478
commit 639a84b1b9
9 changed files with 1238 additions and 5 deletions

66
include/ingest.h Normal file
View File

@@ -0,0 +1,66 @@
#pragma once
#include <stdint.h>
#include "error.h"
typedef struct Ingest_Handle Ingest_Handle;
/*
* Called from the capture thread for each dequeued frame.
* data points into the mmap'd buffer — valid only for the duration of the call.
* Do not free data; copy if you need to retain it beyond the callback.
*/
typedef void (*Ingest_Frame_Fn)(
const uint8_t *data, uint32_t len,
int width, int height, uint32_t pixfmt,
void *userdata);
/*
* Called from the capture thread when a fatal error terminates the capture loop.
* After this callback returns, the thread exits and the handle is in a stopped
* state (equivalent to after ingest_stop). msg is a static string.
*/
typedef void (*Ingest_Error_Fn)(const char *msg, void *userdata);
struct Ingest_Config {
const char *device; /* e.g. "/dev/video0" */
uint32_t pixfmt; /* V4L2_PIX_FMT_MJPEG etc.; 0 = auto (best MJPEG) */
int width; /* 0 = auto */
int height; /* 0 = auto */
Ingest_Frame_Fn on_frame;
Ingest_Error_Fn on_error; /* may be NULL */
void *userdata;
};
/*
* Open the V4L2 device, negotiate format, allocate MMAP buffers.
* Does NOT start streaming. on_frame must not be NULL.
*/
struct App_Error ingest_open(const struct Ingest_Config *cfg, Ingest_Handle **out);
/*
* Enable streaming and start the capture thread.
* Must be called on a handle in the OPEN (not streaming) state.
*/
struct App_Error ingest_start(Ingest_Handle *h);
/*
* Signal the capture thread to stop and block until it exits.
* Disables streaming. The handle returns to the OPEN state and can be
* restarted with ingest_start or released with ingest_close.
*/
struct App_Error ingest_stop(Ingest_Handle *h);
/*
* Release MMAP buffers and close the device fd.
* Must be called only when the handle is not streaming (before ingest_start
* or after ingest_stop).
*/
void ingest_close(Ingest_Handle *h);
/* Query the negotiated format — valid after a successful ingest_open. */
int ingest_width(const Ingest_Handle *h);
int ingest_height(const Ingest_Handle *h);
uint32_t ingest_pixfmt(const Ingest_Handle *h);
int ingest_fps_n(const Ingest_Handle *h);
int ingest_fps_d(const Ingest_Handle *h);