mux: unknown SD aspect, coded-size square pixels, no MPEG-1 DAR guess, no BLA at gap-flagged joins

This commit is contained in:
Matthew Jackson
2026-09-29 19:30:34 -07:00
parent 7d2de0cc7b
commit d09c200de5
4 changed files with 48 additions and 22 deletions
+8 -12
View File
@@ -419,11 +419,6 @@ impl CodecParser for HevcParser {
let explicit_pts = pes.pts.or(pes.dts).map(pts_to_ns);
let pts_ns = explicit_pts.unwrap_or(0);
// A concealed gap orphans RASL that follow the next CRA; arm the CRA->BLA rewrite.
if pes.discontinuity {
self.pending_clip_boundary = true;
}
// Auto-detect a non-seamless clip boundary: mpls connection_condition
// isn't plumbed through the (threaded) mux pipeline, so a PTS backstep
// beyond `BACKSTEP_TICKS` on the unwrapped/high-water timeline arms the rewrite.
@@ -2293,18 +2288,19 @@ mod tests {
);
}
/// A gap-flagged PES arms the rewrite so the next CRA's RASL are discarded by the decoder.
/// A seamless clip join restarts CC (flagged as a gap) with continuous PTS; the next CRA's
/// RASL stay decodable, so it must not become BLA (the decoder would drop them).
#[test]
fn cra_after_a_gap_is_rewritten_to_bla() {
fn cra_after_a_seamless_join_is_left_a_cra() {
let mut parser = HevcParser::new();
let mut gap = make_pes(vec![0, 0, 1, 0x02, 0x01, 0x80], Some(0));
gap.discontinuity = true;
parser.parse(&gap);
let mut join = make_pes(vec![0, 0, 1, 0x02, 0x01, 0x80], Some(0));
join.discontinuity = true;
parser.parse(&join);
let f = parser.parse(&make_pes(cra_au(&[0x10]), Some(3000)));
assert_eq!(
nal_type_of(&nals_of(&f[0].data)[0]),
NAL_BLA_W_LP,
"the first CRA after a gap must become BLA_W_LP"
NAL_CRA_NUT,
"a CRA at a seamless join must stay a CRA"
);
}
+24 -6
View File
@@ -140,12 +140,14 @@ fn probe_video(es: &[u8], map_type: Option<u8>) -> Option<Probed> {
8 => FrameRate::F60,
_ => FrameRate::Unknown,
};
// aspect_ratio_information: 1 square pixels, 2 = 4:3, 3 = 16:9, 4 = 2.21:1.
dar = match h[3] >> 4 {
1 => res.pixels(),
2 => Some((4, 3)),
3 => Some((16, 9)),
4 => Some((221, 100)),
// aspect_ratio_information: 1 = square pixels (coded size). 2/3/4 are 4:3, 16:9, 2.21:1
// only in 13818-2; in 11172-2 they are pel ratios, so MPEG-1 leaves them unknown.
let width = (u32::from(h[0]) << 4) | u32::from(h[1] >> 4);
dar = match (h[3] >> 4, codec) {
(1, _) if width > 0 && height > 0 => Some((width, height)),
(2, Codec::Mpeg2) => Some((4, 3)),
(3, Codec::Mpeg2) => Some((16, 9)),
(4, Codec::Mpeg2) => Some((221, 100)),
_ => None,
};
}
@@ -511,4 +513,20 @@ mod dar_tests {
assert_eq!(res, Resolution::R576p);
assert_eq!(dar, Some((16, 9)));
}
#[test]
fn square_pixel_code_uses_the_coded_size() {
// 320x240, aspect code 1 (square pixels), frame-rate code 4.
let es = [0, 0, 1, 0xB3, 0x14, 0x00, 0xF0, 0x14, 0, 0];
let (_, _, _, dar) = probe_video(&es, Some(0x02)).expect("video");
assert_eq!(dar, Some((320, 240)));
}
#[test]
fn mpeg1_pel_aspect_code_is_not_a_display_ratio() {
// MPEG-1 352x288, code 3 is a pel aspect ratio (11172-2), not 16:9.
let es = [0, 0, 1, 0xB3, 0x16, 0x01, 0x20, 0x33, 0, 0];
let (_, _, _, dar) = probe_video(&es, Some(0x01)).expect("video");
assert_eq!(dar, None);
}
}
+12
View File
@@ -1874,6 +1874,18 @@ fn scan_video() -> Vec<u8> {
v
}
// scan() carries the sequence header's aspect (code 2, 4:3) onto the video stream.
#[test]
fn scan_sets_the_video_display_aspect() {
let ps = scan_pack(&[(0xE0, scan_video())], None);
let s = scan::scan_streams(&ps).unwrap();
assert!(
matches!(&s[0], DiscStream::Video(v) if v.display_aspect == Some((4, 3))),
"{:?}",
s[0]
);
}
// Design §4 step 3: without a map, a `0xD0-0xD7` packet that starts mid-frame is classified
// by the first sync word in its bytes, not by its first two bytes.
#[test]
+4 -4
View File
@@ -241,8 +241,8 @@ impl MapHeader {
}
/// Display aspect ratio as `(num, den)`. Anamorphic titles carry an explicit
/// `display_aspect`; without one, SD is never square-pixel (assume 4:3) and
/// HD uses the coded pixel dimensions.
/// `display_aspect`; without one, SD is never square-pixel so its aspect is
/// unknown `(0, 1)`, and HD uses the coded pixel dimensions.
fn display_aspect_ratio(v: &VideoStream, w: u32, h: u32) -> (u32, u32) {
let sd = matches!(
v.resolution,
@@ -250,7 +250,7 @@ fn display_aspect_ratio(v: &VideoStream, w: u32, h: u32) -> (u32, u32) {
);
match v.display_aspect {
Some((a, b)) if b != 0 => (a, b),
_ if sd => (4, 3),
_ if sd => (0, 1),
_ if h != 0 => (w, h),
_ => (0, 1),
}
@@ -617,7 +617,7 @@ mod tests {
let h = MapHeader::from_title(&t, src(Medium::Iso, "iso://x.iso", 2));
assert_eq!(h.stream.codec, "mpeg2video");
assert_eq!((h.stream.width, h.stream.height), (720, 576));
assert_eq!(h.stream.dar, (4, 3)); // SD is never square-pixel
assert_eq!(h.stream.dar, (0, 1)); // SD with no aspect: unknown
assert_eq!(h.stream.frame_rate, (25, 1));
assert_eq!(h.stream.scan, Scan::Interlaced);
assert_eq!(h.stream.colour.matrix, 5);