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,131 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user