873a04c622
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>
113 lines
3.0 KiB
C
113 lines
3.0 KiB
C
/* SPDX-License-Identifier: BSD-2-Clause */
|
|
/*
|
|
* parser.c — Phase 8.3 parse-path tool.
|
|
*
|
|
* Opens a media file via FFmpeg's avformat demuxer (dlopen'd
|
|
* by ffmpeg_loader), finds the first video stream, and logs
|
|
* per-frame metadata (size, pts, dts, flags) without decoding.
|
|
*
|
|
* Run:
|
|
* daedalus_v4l2_daemon parse /path/to/sample.ivf
|
|
*
|
|
* For Phase 8.3 acceptance: parses any container FFmpeg
|
|
* supports; logs frame counts + per-codec confirmation.
|
|
* Block-level metadata extraction (the actual per-block QPU
|
|
* dispatch info) is Phase 8.4 work.
|
|
*/
|
|
#include "parser.h"
|
|
#include "ffmpeg_loader.h"
|
|
#include "log.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <libavformat/avformat.h>
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libavutil/avutil.h>
|
|
|
|
/* AVERROR_EOF without dragging the macro from FFmpeg's averror.h. */
|
|
#ifndef AVERROR_EOF
|
|
#define AVERROR_EOF (-('E' | ('O' << 8) | ('F' << 16) | (' ' << 24)))
|
|
#endif
|
|
|
|
int daedalus_parse_file(struct ffmpeg_loader *fm, const char *path)
|
|
{
|
|
AVFormatContext *fmt_ctx = NULL;
|
|
const AVCodec *codec = NULL;
|
|
AVPacket *pkt = NULL;
|
|
int video_stream = -1;
|
|
int ret;
|
|
|
|
fmt_ctx = fm->avformat_alloc_context();
|
|
if (!fmt_ctx) {
|
|
log_err("avformat_alloc_context returned NULL");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = fm->avformat_open_input(&fmt_ctx, path, NULL, NULL);
|
|
if (ret < 0) {
|
|
log_err("avformat_open_input(%s): code %d", path, ret);
|
|
return -EIO;
|
|
}
|
|
|
|
ret = fm->avformat_find_stream_info(fmt_ctx, NULL);
|
|
if (ret < 0) {
|
|
log_err("avformat_find_stream_info: code %d", ret);
|
|
fm->avformat_close_input(&fmt_ctx);
|
|
return -EIO;
|
|
}
|
|
|
|
video_stream = fm->av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO,
|
|
-1, -1, &codec, 0);
|
|
if (video_stream < 0) {
|
|
log_err("no video stream found in %s", path);
|
|
fm->avformat_close_input(&fmt_ctx);
|
|
return -ENOENT;
|
|
}
|
|
|
|
{
|
|
AVStream *st = fmt_ctx->streams[video_stream];
|
|
log_info("video stream #%d: codec=%s (%s) %dx%d, %d/%d fps",
|
|
video_stream,
|
|
codec ? codec->name : "?",
|
|
codec ? codec->long_name : "?",
|
|
st->codecpar->width, st->codecpar->height,
|
|
st->avg_frame_rate.num, st->avg_frame_rate.den);
|
|
}
|
|
|
|
pkt = fm->av_packet_alloc();
|
|
if (!pkt) {
|
|
log_err("av_packet_alloc returned NULL");
|
|
fm->avformat_close_input(&fmt_ctx);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
int n_frames = 0, n_keyframes = 0;
|
|
size_t total_bytes = 0;
|
|
while ((ret = fm->av_read_frame(fmt_ctx, pkt)) >= 0) {
|
|
if (pkt->stream_index == video_stream) {
|
|
n_frames++;
|
|
total_bytes += pkt->size;
|
|
if (pkt->flags & AV_PKT_FLAG_KEY)
|
|
n_keyframes++;
|
|
log_debug("frame %d: size=%d pts=%lld dts=%lld flags=0x%x",
|
|
n_frames, pkt->size,
|
|
(long long) pkt->pts, (long long) pkt->dts,
|
|
pkt->flags);
|
|
}
|
|
fm->av_packet_unref(pkt);
|
|
}
|
|
|
|
if (ret != AVERROR_EOF) {
|
|
log_warn("av_read_frame terminated abnormally (code %d)", ret);
|
|
}
|
|
|
|
log_info("parse complete: %d frames (%d key) total %zu bytes",
|
|
n_frames, n_keyframes, total_bytes);
|
|
|
|
fm->av_packet_free(&pkt);
|
|
fm->avformat_close_input(&fmt_ctx);
|
|
return n_frames > 0 ? 0 : -ENODATA;
|
|
}
|