Stage 2 PR-A2: per-MB inspection callback wiring + invariant checks

Validates marfrit-packages patch 0016 (PR #106) end-to-end against
the daedalus_decode_h264 CLI.  Callback fires once per macroblock
in coded order; this PR checks the count + uniqueness invariants
WITHOUT yet driving daedalus-decoder differently — that's PR-A3.

Infrastructure landed
---------------------

CMake gains DAEDALUS_FFMPEG_PREFIX option pointing at a private
FFmpeg install carrying patch 0016.  When set, the CLI links
against it (static .a's from $prefix/lib) and the inspection
codepath is compiled in (DAEDALUS_HAVE_H264_MB_INSPECT_CB).  When
unset, the CLI falls back to the pkg-config-discovered system
FFmpeg and behaves as PR-A1b did (identity-passthrough only, no
callback).

The H264Context struct stays opaque (forward-decl only — its
real definition lives in libavcodec's internal h264dec.h which
isn't installed).  Real per-MB state extraction (sl->mb coeffs,
mb_type, intra modes, deblock params) will land in PR-A3
alongside an internal-header include path.

The callback's only job in this PR: assert (mb_x, mb_y) lies in
the coded grid, mark "seen" in a per-frame bitmap, count
invocations.  At end-of-frame: assert seen-count == mb_w*mb_h,
0 duplicates, 0 out-of-bounds.

Per-frame mb-grid init goes BEFORE first avcodec_send_packet
(callbacks fire from inside send_packet, before the first
receive_frame ever returns — lazy init from AVFrame would miss
all of frame 0).  Dims come from codecpar->width/height rounded
up to 16-mod (H.264 codes 1080 display as 1088 coded).

Raster-order check considered but dropped: libavcodec uses
MB-level threading in some configs so callbacks fire out of
raster order.  The contract is "each MB exactly once", not "in
raster order"; the bitmap check captures that.

Result on hertz (Pi 5, patched FFmpeg at /tmp/ffmpeg-inspect-prefix)
-------------------------------------------------------------------

  320x240 I-only, 3 frames:
    mb-grid 20x15
    callback invocations: 900 (= 3 * 300)
    missing/duplicates/oob: 0/0/0
    identity-passthrough Y diff 0/230400, UV diff 0/115200
    PASS

  1920x1088 I-only, 3 frames:
    mb-grid 120x68
    callback invocations: 24480 (= 3 * 8160)
    missing/duplicates/oob: 0/0/0
    identity-passthrough Y diff 0/6266880, UV diff 0/3133440
    PASS

Followups
---------

  - PR-A3: include libavcodec/h264dec.h via -I to access H264Context
    internals; extract sl->mb coefficients in the callback, compute
    P = pre-deblock pixels - IDCT(C) using a transcribed C reference;
    feed daedalus_decoder with REAL (P, C, edges) instead of identity.
    Use avctx->skip_loop_filter = AVDISCARD_ALL to make libavcodec
    output pre-deblock so the subtraction is exact.
  - PR-A4 onwards: extend to P/B frames + chroma DC + intra prediction
    coverage.
This commit is contained in:
2026-05-26 07:02:29 +02:00
parent 972a79dde2
commit 86a28d2a3b
2 changed files with 150 additions and 7 deletions
+25 -1
View File
@@ -175,7 +175,31 @@ target_compile_options(bench_flush_frame PRIVATE -O2)
# so the standard ctest build doesn't pull in FFmpeg as a hard dep.
option(DAEDALUS_BUILD_TOOLS "Build daedalus-decoder CLI tools (requires libavcodec)" OFF)
if(DAEDALUS_BUILD_TOOLS)
pkg_check_modules(FFMPEG REQUIRED libavcodec libavformat libavutil)
# Optional path to a private FFmpeg install carrying the per-MB
# inspection callback (marfrit-packages patch 0016). When set,
# the CLI links against it instead of the system FFmpeg and the
# inspection-callback code path is compiled in.
set(DAEDALUS_FFMPEG_PREFIX "" CACHE PATH
"Path to a patched FFmpeg install (with 0016 mb-inspect-callback) for daedalus_decode_h264. Empty = use system pkg-config FFmpeg.")
if(DAEDALUS_FFMPEG_PREFIX)
message(STATUS "daedalus_decode_h264: patched FFmpeg at ${DAEDALUS_FFMPEG_PREFIX}")
set(FFMPEG_INCLUDE_DIRS ${DAEDALUS_FFMPEG_PREFIX}/include)
set(FFMPEG_LIBRARY_DIRS ${DAEDALUS_FFMPEG_PREFIX}/lib)
# Patched libavcodec is built static (no shared libs in the private prefix).
# System pull-ins are still needed for libav* dependencies.
set(FFMPEG_LIBRARIES
${DAEDALUS_FFMPEG_PREFIX}/lib/libavformat.a
${DAEDALUS_FFMPEG_PREFIX}/lib/libavcodec.a
${DAEDALUS_FFMPEG_PREFIX}/lib/libavutil.a
${DAEDALUS_FFMPEG_PREFIX}/lib/libswresample.a
m z pthread)
set(FFMPEG_CFLAGS_OTHER "-DDAEDALUS_HAVE_H264_MB_INSPECT_CB=1")
else()
pkg_check_modules(FFMPEG REQUIRED libavcodec libavformat libavutil)
message(STATUS "daedalus_decode_h264: system FFmpeg (no inspection callback)")
endif()
add_executable(daedalus_decode_h264 tools/daedalus_decode_h264.c)
target_link_libraries(daedalus_decode_h264
PRIVATE daedalus_decoder ${FFMPEG_LIBRARIES})