mux/truehd: capture PTS only at an access-unit boundary

A continuation PES (one that merely extends a TrueHD AU spanning PES
packets) carries its own later PTS, which the parser was adopting
mid-assembly, snapping the AU's timestamp and breaking the monotonic
per-AU cadence (A/V drift). Capture the PTS base only when the
reassembly buffer is empty, i.e. when a PES actually begins a new AU.
This commit is contained in:
MattJackson
2026-06-06 21:19:53 -07:00
parent 7306f661b9
commit a5962be86c
+52
View File
@@ -96,9 +96,18 @@ impl CodecParser for TrueHdParser {
return Vec::new(); return Vec::new();
} }
// Capture the PTS base ONLY at an access-unit boundary, i.e. when no AU
// is mid-assembly in `buf`. TrueHD access units span PES packets; a PES
// that merely continues an AU already in progress carries its own (later)
// PTS, which must NOT override the running per-AU timestamp. Adopting it
// mid-AU would snap that AU's PTS backward/forward and break the
// monotonic +AU_DURATION_NS cadence (A/V drift). Once the buffer is empty
// the next PES legitimately begins a new AU and seeds the base.
if self.buf.is_empty() {
if let Some(pts) = pes.pts { if let Some(pts) = pes.pts {
self.next_pts_ns = pts_to_ns(pts); self.next_pts_ns = pts_to_ns(pts);
} }
}
self.buf.extend_from_slice(&pes.data); self.buf.extend_from_slice(&pes.data);
@@ -300,6 +309,49 @@ mod tests {
assert_eq!(frames[0].data.len(), 200); assert_eq!(frames[0].data.len(), 200);
} }
#[test]
fn continuation_pes_pts_does_not_override_au_in_progress() {
// An AU split across two PES packets: the FIRST PES (pts 90000) begins
// the AU; the SECOND PES (pts 99999, a later timestamp) merely continues
// it. The emitted AU must keep the first PES's PTS, not adopt the
// continuation PES's later timestamp.
let mut parser = TrueHdParser::new();
let unit = make_truehd_unit(200);
let mid = 100;
let pes1 = make_pes(unit[..mid].to_vec(), Some(90000));
assert!(parser.parse(&pes1).is_empty(), "AU held mid-assembly");
// Continuation PES carries a later PTS that must be ignored for this AU.
let pes2 = make_pes(unit[mid..].to_vec(), Some(99999));
let frames = parser.parse(&pes2);
assert_eq!(frames.len(), 1);
assert_eq!(
frames[0].pts_ns,
pts_to_ns(90000),
"AU keeps the PTS of the PES that began it, not the continuation PES"
);
}
#[test]
fn new_au_after_empty_buffer_takes_new_pes_pts() {
// After an AU fully drains (buffer empty), the next PES legitimately
// seeds a fresh PTS base.
let mut parser = TrueHdParser::new();
let f1 = parser.parse(&make_pes(make_truehd_unit(200), Some(90000)));
assert_eq!(f1.len(), 1);
assert_eq!(f1[0].pts_ns, pts_to_ns(90000));
// Buffer is now empty; a new PES with a new PTS starts a new AU.
let f2 = parser.parse(&make_pes(make_truehd_unit(200), Some(180000)));
assert_eq!(f2.len(), 1);
assert_eq!(
f2[0].pts_ns,
pts_to_ns(180000),
"new AU after empty buffer adopts the new PES PTS"
);
}
#[test] #[test]
fn codec_private_none() { fn codec_private_none() {
let parser = TrueHdParser::new(); let parser = TrueHdParser::new();