Security - Remove the unsafe Vec::set_len over recycled sector buffers — UB reachable from safe code (GHSA-j8ww-f5fg-9pmh, low). Uses Vec::resize; pooling unchanged. CI / tooling - clippy on stable with the MSRV single-sourced from Cargo.toml; cargo-deny dependency-audit gate with first-party git sources allowed (#15); audience-based comment-guard (#17). Docs - README (keydb ownership, BD-J parser count), drive-access transport/reset notes, mux timeline module-doc links.
9.1 KiB
H.264 codec parser notes
Slice-type mapping
h264_slice_coding_type maps an H.264 slice_type (Rec. ITU-T H.264 §7.4.3,
Table 7-6) to a coding type. Values 5..=9 repeat 0..=4 (the "all slices of
this type" forms), so slice_type % 5: 0 = P, 1 = B, 2 = I, 3 = SP
(predicted → P), 4 = SI (intra → I). Returns None for values outside 0..=9
(malformed header).
unescape_ebsp_prefix
Copies the leading bytes of an EBSP with emulation-prevention bytes removed
(ISO/IEC 14496-10 §7.3.1: a 0x03 following 0x00 0x00 is not part of the
RBSP). Only a short prefix is produced, because the sole caller decodes two
ue(v) fields from the very start of a slice header. first_mb_in_slice and
slice_type are each at most 32 bits, so 16 octets is ample and keeps this
off the per-frame allocation path for anything larger.
unescape_ebsp
Copies ebsp with emulation-prevention bytes removed, stopping after at most
max_octets output bytes (pass ebsp.len() for "no cap").
The zero run-length counter is cumulative across the whole scan and is reset
only when an escape byte is actually dropped — matching the reference
decoding process in ITU-T H.264 §7.3.1, which discards a 0x03 following ANY
run of two-or-more 0x00 bytes, not just an exact 00 00 03 sliding window. A
window-based scanner that advances by 3 on a match and by 1 otherwise
disagrees with this on a run of 3+ real zero bytes ahead of an 0x03:
00 00 00 03 decodes here as 00 00 00 (the run is escaped, matching every
other H.264 decoder), where a window scan starting fresh after each
non-match would keep the 0x03 as real payload. Only one implementation of
this rule may exist in this module — see parse_sps_high_profile_ext, which
used to keep its own window-scan copy that disagreed with this one on
exactly that input.
handle_param_set
Handles an SPS/PPS NAL (mirrors the HEVC fix). The strip/emit decision is
made against the currently-ACTIVE set cur, NOT the codecPrivate copy
first: a streaming MKV decoder applies avcC once at init and thereafter
updates a parameter set only from an in-band NAL, so a switch BACK to the
first-seen body (== codecPrivate) is still a change the decoder must be told
about. Stripping on == first silently dropped that revert.
- First of its type → seeds codecPrivate; stripped (decoder gets it from avcC).
- Equal to the active set
cur→ redundant; stripped. - Different from
cur(a change in EITHER direction) → emitted in-band andcurupdated.
Returns true when the NAL was emitted in-band into frame_data.
reassert_active
Appends the active parameter set cur to prefix (length-prefixed) so
every keyframe is SELF-CONTAINED: it carries the active SPS/PPS in-band
ahead of its slices. Skipped only when this access unit ALREADY carried the
NAL in-band (emitted — avoids a duplicate) or no active set exists yet.
Unconditional (not only when the active set differs from codecPrivate): a streaming decoder applies the avcC param sets once at init, then relies on in-band repetition. Some sources stop repeating a param set at later IDRs even though its body is unchanged; if the decoder then drops it (a reset event), nothing re-sends it and every subsequent slice fails (param-set id out of range) until the next genuine change. Re-asserting at EVERY keyframe — what compliant muxers do at every IDR — makes streaming decode self-healing. Re-sending an identical param set is benign; cost is a few bytes per keyframe. This strictly supersets the change-only re-assert, so the param-set-revert fix is unaffected.
parse_sps_high_profile_ext
Parses (chroma_format_idc, bit_depth_luma_minus8, bit_depth_chroma_minus8)
from a High-Profile SPS NAL (profile_idc ∈ HIGH_PROFILES — the 14
chroma/bit-depth extended profiles codec_private invokes this for).
SPS RBSP layout (ITU-T H.264 §7.3.2.1.1) up to the fields we need:
byte 0 NAL header (already known to be type 7)
byte 1 profile_idc
byte 2 constraint_set_flags / reserved
byte 3 level_idc
ue(v) seq_parameter_set_id
— High-profile branch —
ue(v) chroma_format_idc
if chroma_format_idc == 3: u(1) separate_colour_plane_flag
ue(v) bit_depth_luma_minus8
ue(v) bit_depth_chroma_minus8
RBSP emulation-prevention bytes (0x00 0x00 0x03 → 0x00 0x00) are removed before bit-parsing so the bit reader sees clean RBSP data.
Returns None if the SPS is too short or malformed (Exp-Golomb code
overflows 32 bits, leading-zero count > 31, etc.). The caller silently omits
the extension in that case.
Test notes
open_gop_intra_access_unit_is_a_keyframe
Open-GOP resync fix: a non-IDR intra-coded access unit MUST be flagged a
keyframe so the B1 resync gate (mux/resync.rs) can disarm on a BD
open-GOP tail instead of dropping every frame to EOF and truncating the
video. BD H.264 titles reach random access at a non-IDR I-frame (the
recovery point); HEVC gets this free from the CRA NAL type, H.264 does not,
so the measured intra coding type is the signal. Red before the intra
promotion in parse — a non-IDR I-slice used to yield keyframe == false.
inter_coded_access_unit_is_not_a_keyframe
Guards the promotion's precision: a non-intra (P) access unit must NOT be promoted to a keyframe, or the resync gate would resume on a frame that carries inter-references to dropped data and the MKV cue index would gain bogus random-access points.
slice_header_parse_removes_emulation_prevention_bytes
The slice-header parse must remove emulation-prevention bytes first (ISO/IEC 14496-10 §7.3.1/§7.4.1): the payload after the NAL header is EBSP, and a 0x03 following 0x00 0x00 is not part of the RBSP.
Reachable when first_mb_in_slice is 65535 or more — a ue(v) that needs
sixteen leading zero bits, so the payload opens 0x00 0x00 and an encoder
must escape it. A UHD frame is ~32,400 macroblocks so conforming Blu-ray
never gets there, but 8K does, and the disc is untrusted input. Reading the
raw NAL decoded slice_type against the inserted byte and produced the
wrong picture type.
unescape_ebsp_drops_escape_after_a_run_of_three_zeros
Regression: parse_sps_high_profile_ext used to re-derive the
emulation-prevention rule with its own window scanner (match 00 00 03 at
position i, advance by 3; else advance by 1) instead of calling the shared
unescape_ebsp. On a run of 3+ real 0x00 bytes ahead of an 0x03 —
non-conformant, but this is untrusted disc input, not a spec-clean encoder —
the two disagreed: unescape_ebsp's cumulative zero counter (the rule the
H.264 reference decoding process in §7.3.1 specifies) drops the 0x03 as an
escape, while a fresh 3-byte window starting right after the non-matching
first byte kept it as real payload. Pin the shared function's behaviour
here so a second hand-rolled copy doesn't quietly reappear.
keyframe_param_reassert_emits_exact_bytes
The keyframe SPS/PPS re-assert must produce EXACTLY these bytes: the active SPS, then the active PPS, then the access unit's own NALs, each as a 4-byte big-endian length prefix followed by the NAL body (ISO/IEC 14496-15 length-prefixed form, lengthSizeMinusOne = 3).
This pins the full byte string, not just its length, so the in-place
splice that replaced the build-a-new-buffer-and-copy approach is proven
byte-for-byte equivalent — including the ORDER (parameter sets ahead of the
slices, which is what makes the keyframe self-contained). The literals were
captured from the pre-splice implementation's output.
keyframe_param_reassert_does_not_reallocate_the_frame
MEASURED: the keyframe parameter-set re-assert must be spliced into the front of the already-assembled access unit IN PLACE, not built as a fresh full-size buffer.
It used to prefix.extend_from_slice(&frame_data) and then replace
frame_data with prefix, which grew a few-hundred-byte prefix to the
FULL access-unit size — a fresh multi-hundred-KB allocation — memcpy'd the
whole frame into it, and dropped the presized buffer. One extra whole-frame
allocation plus one extra whole-frame copy per keyframe: a UHD title is
~200,000 frames of 150-400 KB with a keyframe every 1-2 s, i.e. thousands of
avoidable multi-hundred-KB copies per title, each large enough to go through
mmap. PARAM_REASSERT_HEADROOM exists so the splice never reallocates; this
counts the reallocations that happen, which must be zero. Mirrors the HEVC
parser's test of the same name.
avcc_profile_244_appends_extension_bytes
ISO 14496-15 §5.3.3.1.2 regression: profile_idc=244 (High 4:4:4 Predictive) ALSO mandates the chroma/bit-depth extension. It was missing from HIGH_PROFILES, so a 244 stream took the Baseline/Main path and emitted an avcC with NO extension bytes — non-conforming, and strict parsers then assume 8-bit 4:2:0. The extension must be appended.
sps_reader_read_ue_thirty_one_leading_zeros_is_still_valid
SpsReader::read_ue's truncation guard is leading_zeros > 31: 31 leading
zero bits is the longest legal code and must decode, not abort. Mirrors the
equivalent guard in the shared BitReader in startcode.rs, kept here
because SpsReader is a separate, unshared implementation used only for SPS
parsing.