mux: harden audio discontinuity handling (audit follow-up)

Two defensive hardenings from the post-fix audit (vs FFmpeg/GStreamer):

1. Move the `pes.discontinuity` partial-drop ABOVE the empty-data guard in
   all three audio parsers (ac3/dts/truehd), so a discontinuity signal can
   never be stranded by an empty post-gap PES. The demuxer only emits
   non-empty PES today; this is defense-in-depth for any future caller.

2. A PES with no PTS must not reset the timeline to 0. ac3 now carries
   `flush_pts_ns`, dts continues from the most recent known base; truehd
   already kept its running cadence on a None PTS. Matches OSS behavior
   (PTS rebases off the next PES that actually carries a PTS).

Adds an ac3 regression test (empty-payload discontinuity PES still drops
the stranded partial). Loss accounting was reviewed: TS-demux CC-gaps are
NOT counted toward lost_video_secs / abort (that is sector-based via
DiscStream::errors / mapfile bytes_unreadable), so a source splice never
inflates loss — no gating needed there.
This commit is contained in:
Matthew Jackson
2026-06-29 12:50:52 -07:00
parent be08e3938b
commit 9e6af4a729
3 changed files with 92 additions and 13 deletions
+7 -4
View File
@@ -126,19 +126,22 @@ enum Ac3Size {
impl CodecParser for TrueHdParser {
fn parse(&mut self, pes: &PesPacket) -> Vec<Frame> {
if pes.data.is_empty() {
return Vec::new();
}
// B1: a concealed/lost gap means the buffered TrueHD AU is TRUNCATED.
// Splicing post-gap bytes onto it corrupts the AU framing (→ "Invalid
// data found") and strands the PTS cadence (the non-monotonic audio-DTS
// band at gaps). Drop the partial; with `buf` now empty the PTS-base block
// below re-seeds the cadence from the post-gap PES, monotonic across the
// gap. (Audio has no inter-frame refs — this is the whole audio fix.)
//
// Handle the discontinuity BEFORE the empty-data guard so the signal can
// never be stranded by an empty post-gap PES (defensive; the demuxer only
// emits non-empty PES today).
if pes.discontinuity {
self.buf.clear();
}
if pes.data.is_empty() {
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