h264_set_controls overwrites stream's SPS profile/level with session-derived values; max_num_ref_frames not populated #8
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
h264_set_controlsin src/h264.c builds thev4l2_ctrl_h264_spsit ships to the kernel by:sps.profile_idcfrom the libva session profile (h264_profile_to_idc(profile), h264.c:919) — overwrites the bitstream's actualprofile_idc.sps.level_idcfrom the frame size in MBs (h264_derive_level_idc(...), h264.c:928) — overwrites the bitstream's actuallevel_idc.sps.max_num_ref_framesfromVAPicture->num_ref_frames(h264.c:516), which appears to be0for at least some streams ffmpeg-vaapi feeds.For hardware V4L2 stateless decoders (rkvdec, rpi-hevc-dec) this may work in practice — their firmware re-parses SPS from the slice prefix or tolerates SPS-control mismatch. For the new daedalus_v4l2 / Option γ path (libavcodec via dlopen, in a userspace daemon), it's fatal: the daedalus daemon synthesises a fresh AnnexB SPS NAL from these V4L2 controls, libavcodec parses that synthesised SPS, and the slice data fails to match.
This became visible when DAEMON-PPS landed (daedalus-v4l2 PR #1, commit
3dd0eb0). The wire-protocol + kernel collect + daemon NAL synth + libavcodec integration all work end-to-end; the synthesised SPS is byte-correct for the input data — but the input data is wrong before it ever reaches daedalus.Concrete reproduction on higgs (Pi CM5, kernel 6.18.29, daedalus_v4l2 + rpi-hevc-dec both loaded)
(setup-time DECODE_MODE/START_CODE probe — best-effort, ignored. Per-frame S_EXT_CTRLS succeeds and sets SPS+PPS into the daedalus_v4l2 request handler.)
Daedalus daemon journal then shows what came out of the kernel ctrl handler:
But the stream's actual SPS (extracted via
ffmpeg -bsf:v h264_mp4toannexb+ raw scan):The libva-shipped values (profile=100/level=41) don't match the stream (profile=66/level=13).
max_num_ref_frames=0is also wrong —libx264 -bf 0 -g 32writes a stream with at least 1 reference.Libavcodec then complains:
ffmpeg still exits rc=0 with a (corrupted) decoded frame because libavcodec is lenient about returning best-effort output.
Why this hits only the daedalus path
Hardware decoders accept the SPS struct fields as hints; they have their own bitstream parsers in firmware (rkvdec) or accept slice data with embedded SPS prefix (rpi-hevc-dec uses NONE start-code mode). They don't strictly enforce that the V4L2 SPS control matches what's in the slice.
Libavcodec via daedalus_v4l2 has no such tolerance: it parses the SPS NAL we hand it and uses those values to interpret the slice. Mismatch → decode garbage.
Proposed fix
Read actual stream-level SPS fields from the VAAPI parameter buffer (
VAPictureParameterBufferH264) and the slice prefix — don't derive from session profile. Concretely:sps.profile_idc: useVAPicture->seq_fields.bits.profile_idcif present, or extract from the SPS NAL the client embedded. Fall back toh264_profile_to_idc(profile)only if neither is available.sps.level_idc: same — read the real level, fall back to the size-derived value only when nothing else.sps.max_num_ref_frames: ifVAPicture->num_ref_frames == 0, derive fromVAPicture->seq_fields.bits.log2_max_frame_num_minus4and reference-count ofReferenceFrames[], or fall back to a safe default (e.g. 1 for baseline, 4 for high) rather than 0.pic_fields.bits.entropy_coding_mode_flagetc. is being read off the wrong offset; should be reproducible against any libx264-encoded sample.Scope / blast radius
Fix affects only what the libva driver writes to V4L2 controls. Existing hardware decoder paths (rkvdec, rpi-hevc-dec) should be unaffected if their firmware was already ignoring the SPS struct; if some path relied on the hardcoded
profile_idc=100, that's a worse bug that the fix surfaces. Worth running on rkvdec testbed (RK3399, RK3588) before merging to confirm no regression.Related
3dd0eb0) — daemon-side AnnexB SPS+PPS synth from V4L2 ctrls. All works correctly; surfaces this bug.c332d34) and LIBVA-2 (PR #7,9898331) — per-codec dispatch. Required for any decode at all on higgs.Triage note 2026-05-20 — proposed fix #1 needs to be repointed; VAAPI doesn't expose
profile_idc/level_idc.Read libva 2.22.0-3's
VAPictureParameterBufferH264struct on higgs (/usr/include/va/va.h:3571-3622):No
profile_idc. Nolevel_idc. Noconstraint_set*_flag. Same family of VAAPI-blindspot asfeedback_vaapi_blind_to_some_hevc_sps_fields.Implication for each proposed fix
sps.profile_idcfromseq_fields.bits.profile_idcsurface->source_data(slice prefix), add a wire-protocol field daedalus client → daemon, OR keep the current session-derived hardcode (correct for hardware decoders, wrong for daedalus).sps.level_idcfromseq_fields.bits.level_idclevel_idcrequires SPS-NAL parsing or wire-protocol pass-through.sps.max_num_ref_framesfallback when 0VAPicture->ReferenceFrames[](which has 16 slots); if still 0, default per-profile (1 for baseline, 4 for main/high).pic_fields.bits.*_flagcorrectly per the libva struct. If they're 0 after-the-fact, the cause is upstream (ffmpeg-vaapi didn't populate them) OR daedalus wire-protocol serialization clobbering them. Need a one-line log dumpingpic_fields.value(raw uint32) at h264_set_controls entry to disambiguate before/after.What's actually fixable today, narrowed scope
Three changes I'd ship as one commit:
Fix 1+2 are operator decisions on whether to pursue SPS-NAL parsing in this backend. The slice prefix typically contains the SPS+PPS+slice when ffmpeg-vaapi writes it — confirmable by dumping the first 64 bytes of
surface->source_dataand grepping for NAL0x00 0x00 0x01 0x67(SPS) prefix. If the SPS is there for libx264 baseline output, the backend can parse it; if it's not, daedalus needs the wire-protocol pass-through path.Anomaly to flag: the reproducer log shows
level=41from a 320x240 baseline stream (300 MBs →h264_derive_level_idcreturns 11, not 41). So either the daedalus daemon's level field comes from somewhere other than the SPS struct (maybe an older code path / cached state), or the log is misformatted. Worth running the reproducer with arequest_log("h264_set_controls: derived level_idc=%u", sps.level_idc);to confirm the value at the libva boundary matches what daedalus prints.Recommended next move
Ship Fix 3 + Fix 4 instrumentation now. After Fix 4 log lines come back from higgs, decide whether Fix 1+2 need NAL-parsing (a real ~100 LoC addition) vs wire-protocol pass-through (operator's daedalus design call). I can do the small fix + ship a PR; the SPS-NAL parse work is a separate scoped commit.
Want me to write the patch?