/* SPDX-License-Identifier: BSD-2-Clause */ /* * shadow_decoder.c — env-gated parallel daedalus-decoder wiring. * * PR-Q3a.1 scope: prove the toolchain. * * 1. DAEDALUS_SHADOW_MODE=1 + libavcodec carries marfrit-packages * 0016 (ff_h264_set_mb_inspect_cb) → shadow path active. * 2. Per-MB callback fires on every macroblock libavcodec emits. * We only count the firings here. * 3. Frame boundary creates a daedalus_decoder context lazily * (sized from the first AVFrame); destroy + recreate on * resolution change. * 4. Per-frame log line surfaces MB count + has_qpu state. * * No daedalus_decoder_append_mb / flush_frame calls yet — that * needs H264Context introspection which depends on the patched * FFmpeg source-tree headers (DAEDALUS_FFMPEG_SRC) and lands in * PR-Q3a.2. This module's job here is to confirm the link * survives, the callback resolves, the context creates, and * tearing the path back down doesn't perturb the production * AVFrame → V4L2 pipeline. */ #include "shadow_decoder.h" #include "ffmpeg_loader.h" #include "log.h" #include #include #include #include #include struct shadow_decoder { struct ffmpeg_loader *loader; daedalus_decoder *dec; /* lazily created on first frame */ int ctx_w; /* coded-frame width at last create */ int ctx_h; uint64_t mbs_this_frame; uint64_t total_frames; uint64_t total_mbs; }; static void shadow_mb_inspect(void *opaque, const struct H264Context *h __attribute__((unused)), int mb_x __attribute__((unused)), int mb_y __attribute__((unused))) { struct shadow_decoder *sh = opaque; sh->mbs_this_frame++; } struct shadow_decoder *shadow_decoder_create(struct ffmpeg_loader *loader) { const char *env = getenv("DAEDALUS_SHADOW_MODE"); if (!env || strcmp(env, "1") != 0) return NULL; if (!loader || !loader->ff_h264_set_mb_inspect_cb) { log_warn("shadow_decoder: DAEDALUS_SHADOW_MODE=1 set but " "libavcodec lacks ff_h264_set_mb_inspect_cb — disabled"); return NULL; } struct shadow_decoder *sh = calloc(1, sizeof(*sh)); if (!sh) { log_err("shadow_decoder: out of memory"); return NULL; } sh->loader = loader; log_info("shadow_decoder: enabled (DAEDALUS_SHADOW_MODE=1, " "daedalus-decoder version %s)", daedalus_decoder_version()); return sh; } void shadow_decoder_destroy(struct shadow_decoder *sh) { if (!sh) return; if (sh->dec) daedalus_decoder_destroy(sh->dec); log_info("shadow_decoder: shutdown — observed %llu frames / %llu MBs", (unsigned long long) sh->total_frames, (unsigned long long) sh->total_mbs); free(sh); } void shadow_decoder_install_cb(struct shadow_decoder *sh, struct AVCodecContext *avctx) { if (!sh || !avctx) return; /* * Loader's optional-symbol pointer was checked at create time * (we wouldn't be non-NULL otherwise), so the call is safe. */ sh->loader->ff_h264_set_mb_inspect_cb(avctx, shadow_mb_inspect, sh); log_info("shadow_decoder: per-MB callback installed on H.264 ctx"); } /* * Ensure the daedalus_decoder context matches the frame's dimensions. * Rounds up to the H.264 macroblock grid (16-pixel multiples) — the * coded picture is always 16-aligned even when the displayed crop * isn't. Returns 0 on success, -1 on failure (ctx left NULL; caller * logs and continues without shadow dispatch this frame). */ static int shadow_ensure_ctx(struct shadow_decoder *sh, int w, int h) { int coded_w = (w + 15) & ~15; int coded_h = (h + 15) & ~15; if (sh->dec && sh->ctx_w == coded_w && sh->ctx_h == coded_h) return 0; if (sh->dec) { daedalus_decoder_destroy(sh->dec); sh->dec = NULL; } sh->dec = daedalus_decoder_create(coded_w, coded_h); if (!sh->dec) { log_warn("shadow_decoder: daedalus_decoder_create(%dx%d) " "failed — shadow dispatch skipped this stream", coded_w, coded_h); sh->ctx_w = sh->ctx_h = 0; return -1; } sh->ctx_w = coded_w; sh->ctx_h = coded_h; log_info("shadow_decoder: ctx ready (%dx%d coded, has_qpu=%d)", coded_w, coded_h, daedalus_decoder_has_qpu(sh->dec)); return 0; } void shadow_decoder_on_frame(struct shadow_decoder *sh, const struct AVFrame *fr) { if (!sh || !fr) return; (void) shadow_ensure_ctx(sh, fr->width, fr->height); sh->total_frames++; sh->total_mbs += sh->mbs_this_frame; uint64_t expected = (uint64_t) ((fr->width + 15) >> 4) * (uint64_t) ((fr->height + 15) >> 4); log_info("shadow_decoder: frame #%llu %dx%d — %llu MBs observed " "(expected %llu)", (unsigned long long) sh->total_frames, fr->width, fr->height, (unsigned long long) sh->mbs_this_frame, (unsigned long long) expected); sh->mbs_this_frame = 0; }