# 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 `. - `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 ```sh 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 [args] ... ``` Clean error reporting; exits with non-zero code. ## Design notes ### Why dlopen instead of static or dynamic-link? - **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 ` 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).