Files
daedalus-v4l2/docs/phase_8_3_closure.md
marfrit 873a04c622 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>
2026-05-18 15:10:22 +00:00

4.5 KiB
Raw Permalink Blame History

Phase 8.3 closure — daemon with FFmpeg dlopen + parse path

Status: closed 2026-05-18.

Userspace daemon scaffold that loads FFmpeg via dlopen at runtime (Option γ, per locked Phase 8 architecture), opens container files via libavformat, and iterates per-frame metadata. No V4L2 / chardev / decode work yet — pure parse-path validation.

What lands

  • daemon/CMakeLists.txt — CMake build; pkg-config for FFmpeg headers; -Wall -Wextra -Wpedantic clean.
  • daemon/src/main.c — entry point, signal handlers, command dispatcher. Currently exposes parse <file>.
  • daemon/src/ffmpeg_loader.{c,h} — runtime FFmpeg loader. dlopens libavformat.so.61, libavcodec.so.61, libavutil.so.59; resolves the 22 function pointers we use.
  • daemon/src/parser.{c,h} — demux loop using avformat_open_input + av_read_frame; logs per-frame metadata.
  • daemon/src/log.{c,h} — minimal logging facade (stderr-only Phase 8.3; syslog/journal in Phase 8.5+).

Build

cd ~/src/daedalus-v4l2/daemon
mkdir build && cd build
cmake ..
cmake --build .

Build dependencies (Debian Trixie): libavformat-dev libavcodec-dev libavutil-dev. Runtime dependencies are just ffmpeg (or rather: any package shipping the .so.61/.so.59 files).

Verification

$ ffmpeg -hide_banner -f lavfi -i testsrc=duration=2:size=320x240:rate=30 \
        -c:v libvpx-vp9 -y /tmp/testsrc.ivf
$ ls /tmp/testsrc.ivf
-rw-rw-r-- 1 mfritsche mfritsche 18611 May 18 17:09 /tmp/testsrc.ivf

$ ~/src/daedalus-v4l2/daemon/build/daedalus_v4l2_daemon parse /tmp/testsrc.ivf
[2026-05-18 17:09:54 INFO] FFmpeg loaded: 7.1.3-0+deb13u1+rpt1 (libavformat 61.7.100)
[2026-05-18 17:09:54 INFO] video stream #0: codec=vp9 (Google VP9) 320x240, 0/0 fps
[2026-05-18 17:09:54 INFO] parse complete: 60 frames (1 key) total 17859 bytes

60-frame VP9 file demuxed correctly; keyframe count and byte total match expectations. -v flag enables per-frame DEBUG logs (size/pts/dts/flags).

Error paths

$ daedalus_v4l2_daemon parse /tmp/does_not_exist
[INFO] FFmpeg loaded: ...
[ERR ] avformat_open_input(/tmp/does_not_exist): code -2

$ daedalus_v4l2_daemon
usage: daedalus_v4l2_daemon <command> [args]
...

Clean error reporting; exits with non-zero code.

Design notes

  • No version lock: the daemon binary works against any FFmpeg .so.61 even if the patch version differs from build time. Future FFmpeg updates on the host don't force daemon rebuilds.
  • No transitive license issues: linking dynamically against FFmpeg (LGPL-2.1+) is fine for our BSD-2-Clause daemon, but dlopen makes it explicit and inspection-friendly.
  • Optional: callers that don't need parsing (e.g., a pre-parsed-bitstream test mode) can skip ffmpeg_loader_init entirely.

Headers vs runtime

We #include <libavformat/avformat.h> etc. at compile time for the struct + enum definitions (so our function-pointer signatures are type-safe). We do NOT link against FFmpeg — the only loader dependency is -ldl.

POSIX dlsym to function pointer

The dlsym → function pointer assignment uses the POSIX- recommended *(void **) &fn_ptr = dlsym(...) form to avoid the ISO C "function pointer is not a void pointer" warning. POSIX.1-2017 dlsym(3p) Rationale explicitly endorses this pattern.

What's NOT here

  • Per-block metadata: Phase 8.3 only demuxes containers and logs frame-level info. Per-block VP9/H.264 metadata (block positions, MVs, coefficients) is Phase 8.4 work and requires either FFmpeg internal APIs or our own bitstream parsing.
  • V4L2 / chardev wiring: the daemon doesn't yet open /dev/daedalus-v4l2. Phase 8.4 connects the parse output to the chardev request/response loop.
  • Daemon mode: no fork / setsid / pid file / systemd unit yet. Phase 8.5+ adds those when the daemon needs to run as a long-running service.

Phase 8.4 plan

  1. Open /dev/daedalus-v4l2 chardev.
  2. Wait for REQ_DECODE messages from kernel.
  3. For each request:
    • Feed the bitstream blob into FFmpeg's parse path.
    • Get an AVFrame back (FFmpeg decodes on CPU initially).
    • Send RESP_FRAME with the decoded frame back.
  4. Validate end-to-end via a userspace V4L2 client that pushes a VP9 file at the kernel module and reads decoded frames.

The QPU dispatch via daedalus_dispatch_* from the sibling daedalus-fourier kernel library lands in Phase 8.4 as well — once the FFmpeg-driven decoder is working with CPU paths, we can substitute QPU dispatch for the kernels that benefit (cycles 1, 2, 4).