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

132 lines
4.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```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 <command> [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 <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).