Fix DTS-HD MA muxing as lossy core only (#10)

DTS-HD MA/HRA access units on Blu-ray are a DTS core frame (sync
0x7FFE8001) followed by one or more DTS extension substreams (sync
0x64582025) carrying the lossless audio. Ground-truthing the Dunkirk
ISO showed the m2ts demuxer hands these out as SEPARATE PES packets on
the same PID: one core PES (exactly core-sized, nothing trailing), then
the extension substreams in following PES packets with their own later
PTS.

The old DtsParser emitted one frame per PES the moment a core frame was
complete, and dropped any PES with no core sync. So every core became a
core-only (lossy) frame and the extension PES packets were discarded as
junk -- silently downgrading the track to lossy DTS core (1557 kb/s CBR,
16-bit) instead of DTS-HD MA (VBR, 24-bit lossless).

Rewrite the parser to assemble across PES boundaries: an access unit
runs from its core sync up to (but not including) the NEXT core sync, so
the core plus every following extension substream stays together. Add a
CodecParser::flush() (default empty) called at end-of-stream by both the
pipelined and inline DiscStream mux paths to drain the final buffered
unit. A 64 KiB cap guarantees forward progress and never stalls if a
boundary can't be found.

Validated on the rip1 testbed: Dunkirk eng+ger and Fight Club eng main
audio now ffprobe as profile=DTS-HD MA (Fight Club eng at 24-bit), with
VBR packet sizes (~2716-2788 B) well above the old fixed 2012 B lossy
core. Genuinely-lossy DTS dub tracks are left untouched.
This commit is contained in:
MattJackson
2026-06-05 06:40:27 -07:00
parent b7405e2d27
commit 3b7bee9ed4
4 changed files with 237 additions and 113 deletions
+13
View File
@@ -541,6 +541,19 @@ impl crate::pes::Stream for DiscStream {
}
}
}
// Drain any access unit a codec parser buffered past the last
// PES (DTS-HD's final core+extension unit, assembled across
// PES boundaries).
let pid_to_track = &self.pid_to_track;
let pending = &mut self.pending_frames;
for (pid, parser) in self.parsers.iter_mut() {
let Some(&(_, track)) = pid_to_track.iter().find(|(p, _)| p == pid) else {
continue;
};
for frame in parser.flush() {
pending.push_back(crate::pes::PesFrame::from_codec_frame(track, frame));
}
}
return Ok(self.pending_frames.pop_front());
}