Phase 8.3: userspace daemon scaffold + FFmpeg dlopen + parse path
Builds the daemon executable per the locked Phase 8 architecture
(Option γ: dlopen FFmpeg at runtime). Phase 8.3 scope: parse
path validation only — no V4L2 wiring, no decode, no chardev
connection.
Components:
- daemon/CMakeLists.txt — CMake with -Wall -Wextra -Wpedantic
clean. pkg-config for FFmpeg headers; only -ldl + -lpthread
at link time.
- daemon/src/main.c — entry point, signal handlers
(SIGINT/SIGTERM), command dispatcher. Currently `parse <file>`.
- daemon/src/ffmpeg_loader.{c,h} — runtime FFmpeg loader.
dlopens libavformat.so.61, libavcodec.so.61, libavutil.so.59.
Resolves 22 function pointers using POSIX-recommended
*(void**)& dlsym idiom (per POSIX.1-2017 dlsym(3p) Rationale).
- daemon/src/parser.{c,h} — demux loop via avformat_open_input +
av_read_frame. Per-frame logging on -v.
- daemon/src/log.{c,h} — logging facade (stderr Phase 8.3;
syslog/journal planned for 8.5+).
Verification on hertz:
$ ffmpeg -f lavfi -i testsrc=duration=2:size=320x240:rate=30 \
-c:v libvpx-vp9 -y /tmp/testsrc.ivf
$ daedalus_v4l2_daemon parse /tmp/testsrc.ivf
[INFO] FFmpeg loaded: 7.1.3-0+deb13u1+rpt1 (libavformat 61.7.100)
[INFO] video stream #0: codec=vp9 (Google VP9) 320x240, 0/0 fps
[INFO] parse complete: 60 frames (1 key) total 17859 bytes
Error paths verified:
- Missing file → "avformat_open_input(...): code -2", exit 1
- No command → usage message, exit 2
- Bad command → usage message, exit 2
Per correctness-before-speed:
- Real CMake (no Makefile hacks)
- pkg-config for headers
- POSIX-conformant dlsym pattern (no -Wpedantic suppression)
- Real signal handling + proper exit codes
- Real logging with timestamp + level
- Headers included at compile-time for type safety; dlopen
decouples runtime
- All FFmpeg resources freed on every exit path
- Builds clean on -Wall -Wextra -Wpedantic
Phase 8.3 acceptance criteria met:
- ✓ daemon binary builds
- ✓ dlopen FFmpeg at runtime
- ✓ demux a VP9 IVF file end-to-end
- ✓ per-frame metadata logged correctly
- ✓ frame count + keyframe count + byte total accurate
Phase 8.4 next: wire daemon to /dev/daedalus-v4l2 chardev,
add REQ_DECODE / RESP_FRAME handling, drive VP9 decode
end-to-end via daedalus_dispatch_* from daedalus-fourier.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/*
|
||||
* main.c — daedalus-v4l2 userspace daemon entry point.
|
||||
*
|
||||
* Phase 8.3 mode of operation: CLI tool.
|
||||
* daedalus_v4l2_daemon parse <file>
|
||||
*
|
||||
* Phase 8.4+ adds the daemon mode that opens the chardev
|
||||
* /dev/daedalus-v4l2 and services REQ_DECODE etc.
|
||||
*/
|
||||
#include "ffmpeg_loader.h"
|
||||
#include "parser.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <libavutil/log.h>
|
||||
|
||||
static volatile sig_atomic_t g_terminate = 0;
|
||||
|
||||
static void on_signal(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_terminate = 1;
|
||||
}
|
||||
|
||||
static int install_signal_handlers(void)
|
||||
{
|
||||
struct sigaction sa = { 0 };
|
||||
sa.sa_handler = on_signal;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
if (sigaction(SIGINT, &sa, NULL) < 0 ||
|
||||
sigaction(SIGTERM, &sa, NULL) < 0) {
|
||||
log_err("sigaction: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_parse(struct ffmpeg_loader *fm, int argc, char **argv)
|
||||
{
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "usage: parse <path-to-media-file>\n");
|
||||
return 2;
|
||||
}
|
||||
int rc = daedalus_parse_file(fm, argv[0]);
|
||||
return rc < 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
static void usage(const char *progname)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"usage: %s <command> [args]\n\n"
|
||||
"commands:\n"
|
||||
" parse <file> Phase 8.3: demux+enumerate frames\n"
|
||||
"\n"
|
||||
"options:\n"
|
||||
" -v, --verbose enable debug logging\n"
|
||||
" -h, --help this message\n",
|
||||
progname);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int verbose = 0;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-v") == 0 ||
|
||||
strcmp(argv[i], "--verbose") == 0)
|
||||
verbose = 1;
|
||||
else if (strcmp(argv[i], "-h") == 0 ||
|
||||
strcmp(argv[i], "--help") == 0) {
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
} else {
|
||||
break; /* first non-option = command */
|
||||
}
|
||||
}
|
||||
if (i >= argc) {
|
||||
usage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
log_init(verbose ? LOG_DEBUG : LOG_INFO);
|
||||
if (install_signal_handlers() < 0)
|
||||
return 1;
|
||||
|
||||
struct ffmpeg_loader fm;
|
||||
if (ffmpeg_loader_init(&fm) < 0) {
|
||||
log_err("ffmpeg_loader_init failed");
|
||||
return 1;
|
||||
}
|
||||
/* Mute FFmpeg's own chattiness unless the user asked. */
|
||||
fm.av_log_set_level(verbose ? AV_LOG_INFO : AV_LOG_WARNING);
|
||||
|
||||
int rc;
|
||||
const char *cmd = argv[i++];
|
||||
if (strcmp(cmd, "parse") == 0) {
|
||||
rc = cmd_parse(&fm, argc - i, argv + i);
|
||||
} else {
|
||||
fprintf(stderr, "unknown command: %s\n", cmd);
|
||||
usage(argv[0]);
|
||||
rc = 2;
|
||||
}
|
||||
|
||||
ffmpeg_loader_cleanup(&fm);
|
||||
log_cleanup();
|
||||
return rc;
|
||||
}
|
||||
Reference in New Issue
Block a user