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

Merged
marfrit merged 1 commits from noether/tools-h264-callback-wiring into main 2026-05-26 07:06:39 +00:00
Owner

Validates marfrit-packages patch 0016 (PR #106, merged) 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: new 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 DAEDALUS_HAVE_H264_MB_INSPECT_CB is compiled in. When unset, the CLI falls back to pkg-config'd system FFmpeg and behaves as PR-A1b did (identity-passthrough only).

Opaqueness: the H264Context struct stays forward-declared in the CLI — its real definition lives in libavcodec's internal h264dec.h, not installed. Real per-MB state extraction (sl->mb coeffs, mb_type, intra modes, deblock params) lands in PR-A3 alongside an internal-header include path.

Callback work: assert (mb_x, mb_y) ∈ coded grid → mark seen in per-frame bitmap → count invocations. At end-of-frame: assert seen-count == mb_w*mb_h, 0 duplicates, 0 OOB.

Init timing: callbacks fire from inside avcodec_send_packet, before the first receive_frame ever returns. So the per-frame bitmap MUST be allocated before the first send_packet, not lazy-on-first-frame (which 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).

Order check considered + dropped: libavcodec uses MB-level threading in some configs so callbacks fire out of strict 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)

dims frames invocations missing/dup/oob bit-exact
320×240 3 900 (= 3×300) 0/0/0 Y 0 / UV 0
1920×1088 3 24480 (= 3×8160) 0/0/0 Y 0 / UV 0

Identity-passthrough still PASSES — the callback path is additive, doesn't break PR-A1b semantics.

Followups

  • PR-A3: include libavcodec/h264dec.h via -I/path/to/ffmpeg/src 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). Use avctx->skip_loop_filter = AVDISCARD_ALL so libavcodec's output is pre-deblock and the subtraction is exact.
  • PR-A4+: P/B frames, chroma DC, intra prediction coverage.
Validates marfrit-packages patch 0016 (PR #106, merged) 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**: new `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 `DAEDALUS_HAVE_H264_MB_INSPECT_CB` is compiled in. When unset, the CLI falls back to pkg-config'd system FFmpeg and behaves as PR-A1b did (identity-passthrough only). **Opaqueness**: the `H264Context` struct stays forward-declared in the CLI — its real definition lives in libavcodec's internal `h264dec.h`, not installed. Real per-MB state extraction (`sl->mb` coeffs, mb_type, intra modes, deblock params) lands in PR-A3 alongside an internal-header include path. **Callback work**: assert `(mb_x, mb_y)` ∈ coded grid → mark seen in per-frame bitmap → count invocations. At end-of-frame: assert seen-count == `mb_w*mb_h`, 0 duplicates, 0 OOB. **Init timing**: callbacks fire from inside `avcodec_send_packet`, before the first `receive_frame` ever returns. So the per-frame bitmap MUST be allocated before the first `send_packet`, not lazy-on-first-frame (which 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). **Order check considered + dropped**: libavcodec uses MB-level threading in some configs so callbacks fire out of strict 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) | dims | frames | invocations | missing/dup/oob | bit-exact | |---|---|---|---|---| | 320×240 | 3 | 900 (= 3×300) | 0/0/0 | Y 0 / UV 0 | | 1920×1088 | 3 | 24480 (= 3×8160) | 0/0/0 | Y 0 / UV 0 | Identity-passthrough still PASSES — the callback path is additive, doesn't break PR-A1b semantics. ## Followups - **PR-A3**: include `libavcodec/h264dec.h` via `-I/path/to/ffmpeg/src` 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)`. Use `avctx->skip_loop_filter = AVDISCARD_ALL` so libavcodec's output is pre-deblock and the subtraction is exact. - **PR-A4+**: P/B frames, chroma DC, intra prediction coverage.
marfrit added 1 commit 2026-05-26 05:06:57 +00:00
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.
marfrit merged commit 69d68e0323 into main 2026-05-26 07:06:39 +00:00
marfrit deleted branch noether/tools-h264-callback-wiring 2026-05-26 07:06:40 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marfrit/daedalus-decoder#14