mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
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.
190 lines
9.1 KiB
Markdown
190 lines
9.1 KiB
Markdown
# 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 and
|
|
`cur` updated.
|
|
|
|
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.
|