fix(libfreemkv): rc6 hardening pass — mux timeline/colour/PCR, demux panic sentinel, parser robustness + doc accuracy
Surgical fixes (each with a regression test that fails without the change): mux/mkv.rs, mux/demux_sink.rs: drive the clip-boundary timeline epoch off the resolved PRIMARY VIDEO track, not the literal stream index 0. An M2TS/PMT title can list an audio ES before video, so streams[0] may be audio; a non-video epoch driver ratchets the frontier and inflates the timeline. mkv cluster-opening falls back to track 0 for audio-only titles so they still open clusters. mux/codec/ac3.rs: correct ACMOD_CHANNELS — acmod=5 (3/1) is 4 channels, not 3 (was undercounting a 3/1 stream); fix the A/52 Table 5.8 doc. disc/mod.rs: HDMV coding_type 0x91 (Interactive Graphics / menus) no longer maps to PGS subtitle — it falls through to Unknown so the PMT/STN walker drops it instead of surfacing a bogus subtitle track. mux/videomap.rs + mux/mkv.rs: FVI colour now mirrors the MKV muxer's CICP precedence (measured CICP authoritative; HDR-driven PQ/HLG transfer override) via a shared cicp_for_video helper, so the two sinks can't disagree (HDR10 BT.2020 no longer emits SDR transfer 14). mux/mkvstream.rs: saturating_add on cluster_ts + rel_ts so an adversarial CLUSTER_TIMESTAMP near i64::MAX can't overflow/panic before the existing saturating_mul. mux/timeline.rs: tighten the tail-straggler clamp so a normal new-epoch non-video frame leading the sparse video frontier by >3s is not demoted into the previous clip's epoch. mux/m2ts_mux/mod.rs: re-stamp PCR per video TS packet (mid-PES), not only at PES boundaries, so a large UHD I-frame can't open a multi-second PCR gap; modular 33-bit PTS rebasing so a real 90 kHz clock wrap is not collapsed to PTS 0 (pre-base frames still floor to 0). io/byte_prefetcher.rs, sector/prefetched.rs: wrap the producer feed loop in catch_unwind and emit a typed error sentinel on panic, so a mid-stream producer panic is not read as a clean EOF at the demux boundary (which would silently truncate the mux). mux/codec/h264.rs: extend HIGH_PROFILES to the full ISO/IEC 14496-15 set that mandates the avcC chroma/bit-depth extension (adds 244 et al.). Doc/comment accuracy: css/mod.rs (50000 sectors, not scrambled-sectors), aacs/decrypt.rs (decrypt_unit already-clear path), ifo.rs (TT_SRPT at 0xC4), css/lfsr.rs (LFSR0 24-bit; TAB1-then-XOR cipher; real scramble-flag predicate), disc/read_error.rs (for_sweep does bounded transient retries). Skipped: keydb.rs SSRF guard (low/latent, no live caller) — a hard loopback block breaks an existing behavioral test that exercises the header-EOF path over a loopback server; a clean fix needs a resolver test seam beyond this surgical pass. The sibling keydb_fetch.rs comment fix is out of scope (freemkv crate).
This commit is contained in:
+81
-3
@@ -65,8 +65,27 @@ pub struct Colour {
|
||||
}
|
||||
|
||||
impl Colour {
|
||||
/// Map the title's [`ColorSpace`] to CICP code points. Unknown colorimetry
|
||||
/// maps to code point 2 ("unspecified"), the CICP convention.
|
||||
/// Derive the FVI CICP code points from a full [`VideoStream`], using the
|
||||
/// SAME precedence as the MKV muxer ([`crate::mux::mkv::cicp_for_video`]):
|
||||
/// measured CICP (authoritative) → coarse `color_space` enum + HDR-driven
|
||||
/// transfer override. This is what the sidecar must use so it never reports
|
||||
/// an SDR transfer (14) for an HDR10 BT.2020 title while the MKV container
|
||||
/// reports PQ (16) — the two sinks of one title must agree.
|
||||
pub fn from_video(v: &VideoStream) -> Self {
|
||||
let (matrix, transfer, primaries, range) = crate::mux::mkv::cicp_for_video(v);
|
||||
Self {
|
||||
primaries,
|
||||
transfer,
|
||||
matrix,
|
||||
// Matroska/MeasuredCicp Range: 1 = limited (disc norm), 2 = full.
|
||||
full_range: range == 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// Map the title's [`ColorSpace`] alone to CICP code points (no HDR/measured
|
||||
/// context). Retained for the no-video header fallback and unit coverage;
|
||||
/// the title path uses [`Colour::from_video`]. Unknown colorimetry maps to
|
||||
/// code point 2 ("unspecified"), the CICP convention.
|
||||
pub fn from_color_space(cs: ColorSpace) -> Self {
|
||||
// (primaries, transfer, matrix) per ITU-T H.273.
|
||||
let (p, t, m) = match cs {
|
||||
@@ -217,7 +236,7 @@ impl MapHeader {
|
||||
} else {
|
||||
Scan::Progressive
|
||||
},
|
||||
colour: Colour::from_color_space(v.color_space),
|
||||
colour: Colour::from_video(v),
|
||||
}
|
||||
}
|
||||
None => StreamInfo {
|
||||
@@ -475,6 +494,65 @@ mod tests {
|
||||
assert_eq!(Colour::from_color_space(ColorSpace::Unknown).primaries, 2);
|
||||
}
|
||||
|
||||
/// Regression: the FVI sidecar must mirror the MKV muxer's colour precedence,
|
||||
/// not blindly map `color_space` → the SDR transfer 14 for BT.2020. An HDR10
|
||||
/// BT.2020 title's real transfer is PQ (16); a measured CICP triplet is
|
||||
/// authoritative and copied through verbatim. Before the fix the FVI Colour
|
||||
/// reported transfer=14 while the MKV container reported 16 — two sinks of
|
||||
/// one title disagreeing on the colour code points.
|
||||
#[test]
|
||||
fn fvi_colour_follows_hdr_and_measured_cicp() {
|
||||
use crate::disc::MeasuredCicp;
|
||||
let mk = |hdr: HdrFormat, cs: ColorSpace, cicp: Option<MeasuredCicp>| VideoStream {
|
||||
pid: 0x1011,
|
||||
codec: Codec::Hevc,
|
||||
resolution: Resolution::R2160p,
|
||||
frame_rate: FrameRate::F23_976,
|
||||
hdr,
|
||||
color_space: cs,
|
||||
display_aspect: None,
|
||||
secondary: false,
|
||||
label: String::new(),
|
||||
measured_cicp: cicp,
|
||||
};
|
||||
|
||||
// HDR10 BT.2020 with NO measured CICP → PQ transfer (16), NOT SDR 14.
|
||||
let c = Colour::from_video(&mk(HdrFormat::Hdr10, ColorSpace::Bt2020, None));
|
||||
assert_eq!(
|
||||
c,
|
||||
Colour {
|
||||
primaries: 9,
|
||||
transfer: 16, // PQ — not the SDR 14 the enum alone would give
|
||||
matrix: 9,
|
||||
full_range: false,
|
||||
}
|
||||
);
|
||||
|
||||
// HLG BT.2020 → transfer 18.
|
||||
let c = Colour::from_video(&mk(HdrFormat::Hlg, ColorSpace::Bt2020, None));
|
||||
assert_eq!(c.transfer, 18, "HLG transfer must be 18");
|
||||
|
||||
// Measured CICP is authoritative — copied through verbatim, incl. full
|
||||
// range (2 → full_range = true), ignoring the coarse enum/HDR guess.
|
||||
let measured = MeasuredCicp {
|
||||
matrix: 9,
|
||||
transfer: 16,
|
||||
primaries: 9,
|
||||
range: 2,
|
||||
};
|
||||
let c = Colour::from_video(&mk(HdrFormat::Sdr, ColorSpace::Bt709, Some(measured)));
|
||||
assert_eq!(
|
||||
c,
|
||||
Colour {
|
||||
primaries: 9,
|
||||
transfer: 16,
|
||||
matrix: 9,
|
||||
full_range: true,
|
||||
},
|
||||
"measured CICP must override the coarse color_space enum"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_label_full_and_codec_agnostic_fallback() {
|
||||
// coding present: full I/P/B from the agnostic coding_type().
|
||||
|
||||
Reference in New Issue
Block a user