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:
66
include/ingest.h
Normal file
66
include/ingest.h
Normal 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);
|
||||
99
include/reconciler.h
Normal file
99
include/reconciler.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* Generic declarative state machine reconciler.
|
||||
*
|
||||
* Each managed resource is described as a directed graph of states
|
||||
* with labelled transitions. The reconciler finds the shortest path
|
||||
* (BFS) from a resource's current state to its wanted state and
|
||||
* executes one transition per tick.
|
||||
*
|
||||
* Dependencies between resources prevent a resource from advancing
|
||||
* past a threshold state until a prerequisite resource reaches a
|
||||
* minimum state.
|
||||
*
|
||||
* Usage:
|
||||
* struct Reconciler *r = reconciler_create();
|
||||
*
|
||||
* static const struct Rec_Transition dev_trans[] = {
|
||||
* {0, 1, open_device},
|
||||
* {1, 0, close_device},
|
||||
* {1, 2, start_capture},
|
||||
* {2, 1, stop_capture},
|
||||
* {-1, -1, NULL}
|
||||
* };
|
||||
* static const char *dev_states[] = {"CLOSED", "OPEN", "STREAMING"};
|
||||
* struct Rec_Resource *dev = reconciler_add_resource(r, "device",
|
||||
* dev_trans, 3, dev_states, 0, &my_device);
|
||||
*
|
||||
* reconciler_set_wanted(dev, 2);
|
||||
* while (!reconciler_is_stable(r)) {
|
||||
* reconciler_tick(r);
|
||||
* }
|
||||
*/
|
||||
|
||||
/* Transition table entry. Sentinel: {-1, -1, NULL}.
|
||||
* action: return 1 on success, 0 on failure.
|
||||
* On failure the resource stays in 'from' state. */
|
||||
struct Rec_Transition {
|
||||
int from;
|
||||
int to;
|
||||
int (*action)(void *userdata);
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
REC_STATUS_STABLE, /* current == wanted */
|
||||
REC_STATUS_WORKING, /* current != wanted, next transition is eligible */
|
||||
REC_STATUS_BLOCKED, /* current != wanted, a dependency is unsatisfied */
|
||||
REC_STATUS_NO_PATH, /* current != wanted, no transition path exists */
|
||||
} Rec_Status;
|
||||
|
||||
struct Reconciler;
|
||||
struct Rec_Resource;
|
||||
|
||||
/* Optional log callback — called after each transition attempt. */
|
||||
typedef void (*Rec_Log_Fn)(
|
||||
const struct Rec_Resource *res,
|
||||
int from, int to, int success,
|
||||
void *userdata);
|
||||
|
||||
struct Reconciler *reconciler_create(void);
|
||||
void reconciler_destroy(struct Reconciler *r);
|
||||
|
||||
/* Set a log callback. Called after every transition attempt. */
|
||||
void reconciler_set_log(struct Reconciler *r, Rec_Log_Fn fn, void *userdata);
|
||||
|
||||
/* Add a resource.
|
||||
* transitions: caller-owned, sentinel-terminated {-1,-1,NULL}.
|
||||
* state_names: optional array of state_count strings; NULL for numeric display.
|
||||
* initial_state: sets both current and wanted initially. */
|
||||
struct Rec_Resource *reconciler_add_resource(
|
||||
struct Reconciler *r,
|
||||
const char *name,
|
||||
const struct Rec_Transition *transitions,
|
||||
int state_count,
|
||||
const char **state_names,
|
||||
int initial_state,
|
||||
void *userdata);
|
||||
|
||||
/* Add a dependency: resource cannot reach state >= blocked_below
|
||||
* unless dep is currently in state >= dep_min_state. */
|
||||
void reconciler_add_dep(
|
||||
struct Rec_Resource *resource,
|
||||
int blocked_below,
|
||||
struct Rec_Resource *dep,
|
||||
int dep_min_state);
|
||||
|
||||
void reconciler_set_wanted(struct Rec_Resource *r, int wanted_state);
|
||||
int reconciler_get_current(const struct Rec_Resource *r);
|
||||
int reconciler_get_wanted(const struct Rec_Resource *r);
|
||||
const char *reconciler_get_name(const struct Rec_Resource *r);
|
||||
const char *reconciler_state_name(const struct Rec_Resource *r, int state);
|
||||
Rec_Status reconciler_get_status(const struct Rec_Resource *r);
|
||||
|
||||
/* Run one reconciliation pass over all resources.
|
||||
* Returns number of transitions attempted (success or failure). */
|
||||
int reconciler_tick(struct Reconciler *r);
|
||||
|
||||
/* Returns 1 if all resources have current == wanted. */
|
||||
int reconciler_is_stable(const struct Reconciler *r);
|
||||
Reference in New Issue
Block a user