mux: fix non-monotonic audio DTS (TrueHD + DTS-HD MA) and stamp builds with git hash
TrueHD: when the PES PTS lags the access-unit cadence, resync to the PTS but never snap the running timestamp backward, so the emitted DTS stays monotonic across the resync (next_pts_ns = max(next_pts_ns, pts)). DTS-HD MA: size each EXSS extension substream exactly from its header (exss_frame_size) and skip it as a unit, so a false 0x7FFE8001 core sync inside the lossless extension payload can no longer split the access unit and truncate the extension. Falls back to a bounded scan when the header is unparseable. Provenance: build.rs bakes the git short hash into GIT_SUFFIX; the muxing/ writing-application field and the FVI generator tag now record the exact build (e.g. "freemkv 1.1.0-beta.1 (g835cc99)"), so any output file is traceable to the revision that produced it.
This commit is contained in:
+42
-1
@@ -138,7 +138,18 @@ impl CodecParser for TrueHdParser {
|
||||
// the next PES legitimately begins a new AU and seeds the base.
|
||||
if self.buf.is_empty() {
|
||||
if let Some(pts) = pes.pts {
|
||||
self.next_pts_ns = pts_to_ns(pts);
|
||||
// Resync to the authoritative PES PTS, but NEVER snap backward.
|
||||
// TrueHD AUs are a fixed sample count (40 @ 48 kHz), so the
|
||||
// per-AU `+AU_DURATION_NS` cadence is sample-accurate — more so
|
||||
// than the disc's per-PES PTS, which carries the source muxer's
|
||||
// own rounding jitter. When the buffer empties exactly on a PES
|
||||
// boundary and that PES's PTS lands a few ticks *below* the
|
||||
// running cadence, an unconditional reset would set the next
|
||||
// AU's timestamp below the AU just emitted, producing the
|
||||
// non-monotonic block timestamps a muxer rejects. Clamp to the
|
||||
// running position so output stays strictly monotonic; a
|
||||
// genuine forward gap/discontinuity is still adopted.
|
||||
self.next_pts_ns = self.next_pts_ns.max(pts_to_ns(pts));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,6 +460,36 @@ mod tests {
|
||||
assert_eq!(frames[1].pts_ns - frames[0].pts_ns, AU_DURATION_NS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pes_pts_lagging_the_au_cadence_never_emits_backward() {
|
||||
// Regression: the per-AU cadence is sample-accurate, but a PES boundary
|
||||
// can carry a PTS that lags it slightly (source-muxer rounding jitter).
|
||||
// When the buffer empties exactly on that boundary, an unconditional
|
||||
// reset to the PES PTS snapped the next AU's timestamp BELOW the AU just
|
||||
// emitted — the non-monotonic block timestamps a muxer rejects (the
|
||||
// Top Gun / Dune: Part Two case). The reset must clamp forward-only.
|
||||
let mut parser = TrueHdParser::new();
|
||||
let au = make_truehd_unit(100);
|
||||
// PES1: three complete AUs at pts 90000 — buffer empties, cadence runs
|
||||
// ahead to 90000_ns + 3*AU_DURATION_NS.
|
||||
let mut d1 = au.clone();
|
||||
d1.extend_from_slice(&au);
|
||||
d1.extend_from_slice(&au);
|
||||
let f1 = parser.parse(&make_pes(d1, Some(90000)));
|
||||
assert_eq!(f1.len(), 3);
|
||||
let last1 = f1.last().unwrap().pts_ns;
|
||||
// PES2's PTS (90001) maps to fewer ns than the running cadence — pre-fix
|
||||
// this snapped backward.
|
||||
let f2 = parser.parse(&make_pes(au.clone(), Some(90001)));
|
||||
assert_eq!(f2.len(), 1);
|
||||
assert!(
|
||||
f2[0].pts_ns >= last1,
|
||||
"AU pts must not go backward when PES PTS lags the cadence: got {} after {}",
|
||||
f2[0].pts_ns,
|
||||
last1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_interleaved_ac3() {
|
||||
let mut parser = TrueHdParser::new();
|
||||
|
||||
Reference in New Issue
Block a user