mux: fix DVD subtitle/audio track collision, PGS/VobSub flush, unify TS codec table
Subtitle/DVD output-corruption + stream-mapping coverage fixes. 1. DVD subtitle/audio track-mapping collision (CRITICAL). The PS path routed 0xBD private-stream packets to a track via (sub_id & 0x1F)+1, so VobSub subtitle sub-id 0x20+j aliased audio track j+1: subtitle PES was fed to the AC-3 parser and the real subtitle track got nothing. Route by the canonical DVD PID instead via a new PsPacket::dvd_pid() that mirrors scan_dvd_titles' PID assignment (video 0xE0, audio 0xBD00+i, subtitle 0x20+j), then look up the track in pid_to_track. Fixed identically at all three sites (pipelined_stream consume_ps, disc.rs live feed, disc.rs EOF flush). Unmappable/unmapped packets now WARN instead of silently dropping. 2. PGS flush() missing. PgsParser inherited the no-op default flush, so the last subtitle of every PGS track (emitted only when a following PCS arrives) was dropped at EOF. Implemented flush() to drain the pending display set (duration_ns: None for the trailing block). 3. DVD VobSub multi-PES SPU not reassembled. A subpicture unit larger than one PES spans multiple PES (only the head carries a PTS). DvdSubParser is now stateful: it buffers per sub-stream until the leading 2-byte SPU_size is satisfied, inherits the head PTS, and emits one Frame. flush() drains a truncated trailing SPU at EOF. 4. One-table hygiene. scan_streams had a duplicate stream_type->Codec table that had drifted from Codec::from_coding_type (missing 0x80 LPCM, 0x85 mapped to DTS-HD MA vs HR, etc.). scan_streams now uses from_coding_type plus a new Codec::kind()/CodecKind category split, so the two mappings can never diverge. Silent drops in scan_streams and bluray STN parsing now WARN with PID + type. Tests: dvd_pid mapping + subtitle/audio collision regression, PGS final-subtitle flush, VobSub multi-PES reassembly + EOF flush, scan_streams 0x80 LPCM via from_coding_type.
This commit is contained in:
@@ -117,6 +117,25 @@ impl CodecParser for PgsParser {
|
||||
out
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Vec<Frame> {
|
||||
// A display set is only emitted when the *next* PCS arrives
|
||||
// (either an empty clear PCS or a replacing display PCS). At
|
||||
// end-of-stream there is no follower, so without this the last
|
||||
// subtitle of every PGS track would be silently dropped. Emit
|
||||
// the pending set with no duration — the trailing block lingers
|
||||
// until end of file, which is exactly the desired behavior for
|
||||
// the final on-screen subtitle (see the module doc).
|
||||
match self.pending.take() {
|
||||
Some((start_pts, data)) => vec![Frame {
|
||||
pts_ns: start_pts,
|
||||
keyframe: true,
|
||||
data,
|
||||
duration_ns: None,
|
||||
}],
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn codec_private(&self) -> Option<Vec<u8>> {
|
||||
None
|
||||
}
|
||||
@@ -218,6 +237,30 @@ mod tests {
|
||||
assert_eq!(frames.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flush_emits_final_pending_subtitle() {
|
||||
let mut parser = PgsParser::new();
|
||||
|
||||
// Display PCS at PTS 90000 — buffered as pending, no follower.
|
||||
let display = pcs_bytes(1);
|
||||
let frames = parser.parse(&make_pes(display.clone(), Some(90000)));
|
||||
assert!(frames.is_empty(), "display PCS should be pending");
|
||||
|
||||
// EOF: without flush() this last subtitle would be dropped.
|
||||
let frames = parser.flush();
|
||||
assert_eq!(frames.len(), 1, "final pending subtitle must flush");
|
||||
assert_eq!(frames[0].pts_ns, 1_000_000_000);
|
||||
assert_eq!(frames[0].data, display);
|
||||
// Trailing block lingers to EOF — no duration per module doc.
|
||||
assert_eq!(frames[0].duration_ns, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flush_with_nothing_pending_is_empty() {
|
||||
let mut parser = PgsParser::new();
|
||||
assert!(parser.flush().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codec_private_none() {
|
||||
let parser = PgsParser::new();
|
||||
|
||||
Reference in New Issue
Block a user