feat: node-to-node MJPEG streaming CLIs and shared V4L2 format header

Add stream_send_cli (V4L2 capture → TCP → VIDEO_FRAME) and
stream_recv_cli (TCP → threaded frame slot → GLFW display) to
exercise end-to-end streaming between two nodes on the same machine
or across the network.

Add include/stream_stats.h (header-only rolling-window fps/Mbps tracker)
and include/v4l2_fmt.h (header-only V4L2 format enumeration shared between
v4l2_view_cli and stream_send_cli). Refactor v4l2_view_cli to use the
shared header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-28 22:31:54 +00:00
parent 611376dbc1
commit 61c81398bb
8 changed files with 889 additions and 165 deletions

76
include/stream_stats.h Normal file
View File

@@ -0,0 +1,76 @@
#pragma once
/*
* Lightweight per-stream statistics tracker.
* Header-only; include wherever stream send/receive happens.
*
* Usage:
* Stream_Stats s;
* stream_stats_init(&s, stream_id);
*
* // on each frame:
* stream_stats_record_frame(&s, byte_count);
*
* // periodically (e.g. after every frame):
* if (stream_stats_tick(&s)) {
* printf("%.1f fps %.2f Mbps\n", s.fps, s.mbps);
* }
*/
#include <stdint.h>
#include <string.h>
#include <time.h>
#define STREAM_STATS_INTERVAL 0.5 /* recompute rates every 0.5 s */
typedef struct {
uint16_t stream_id;
/* Lifetime counters — never reset. */
uint64_t total_frames;
uint64_t total_bytes;
/* Rolling window — reset each time rates are computed. */
uint64_t window_frames;
uint64_t window_bytes;
struct timespec window_start;
/* Last computed rates. */
float fps;
float mbps;
} Stream_Stats;
static inline void stream_stats_init(Stream_Stats *s, uint16_t stream_id)
{
memset(s, 0, sizeof(*s));
s->stream_id = stream_id;
clock_gettime(CLOCK_MONOTONIC, &s->window_start);
}
/* Call once per received/sent frame. */
static inline void stream_stats_record_frame(Stream_Stats *s, uint32_t nbytes)
{
s->total_frames++;
s->total_bytes += nbytes;
s->window_frames++;
s->window_bytes += nbytes;
}
/*
* Recompute fps and mbps if enough time has elapsed.
* Returns 1 when rates were updated, 0 otherwise.
*/
static inline int stream_stats_tick(Stream_Stats *s)
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
double elapsed = (double)(now.tv_sec - s->window_start.tv_sec) +
(double)(now.tv_nsec - s->window_start.tv_nsec) * 1e-9;
if (elapsed < STREAM_STATS_INTERVAL) { return 0; }
s->fps = (float)((double)s->window_frames / elapsed);
s->mbps = (float)((double)s->window_bytes * 8.0 / elapsed / 1e6);
s->window_frames = 0;
s->window_bytes = 0;
s->window_start = now;
return 1;
}