dvd: route AC-3 audio to the physically-correct sub-stream by probed channel count
Fixes the "Silence of the Lambs" R2 PAL wrong-substream rip: the feature's IFO declares one 5.1 AC-3 stream, but the scan assigned it the on-wire sub-stream id 0x80 purely by per-codec ordinal (ifo::assign_audio_sub_stream_ids). On this disc the physical 0x80 carries the 2.0 down-mix and the 5.1 main mix lives at a different 0x8x sub-stream, so the rip muxed 2.0 while labelling it "Dolby Digital 5.1" (the acmod fixup in mkv.rs then corrected only the Channels element, surfacing the mismatch as the "IFO claimed 6 but acmod says 2" warning — too late to re-route). New src/disc/dvd_audio_probe.rs probes each physical AC-3 sub-stream's real channel count from the head of the feature (the acmod/lfeon of its first frame after the 0x0B77 sync) and re-routes each IFO-declared AC-3 stream onto the physical sub-stream whose actual channel count matches the declared count, instead of trusting the ordinal. Wired into both mux demux paths (DiscStream::new and resolve::build_iso_pipeline) over the decrypting reader, so it works on CSS discs and the autorip ISO-remux path alike. Bounded 512-sector best-effort read; an empty/unreadable probe degrades to the original ordinal mapping (no regression on normal discs). The cell selection is left unchanged: the feature's cell 0 (cat=0x02, 302.4s) is chapter 1 of the movie (matches MakeMKV's chapter map and 1h53 duration exactly), so it must NOT be dropped — the perceived "wrong video at the start" was the wrong 2.0 audio over the opening, the same root cause. Diagnostics (--log-level 3): new tag=dvd.substream rows dump the ACTUAL acmod channel count of each physical 0x8x sub-stream read from the VOB, and the per-cell tag=dvd.cell verdict now spells out the keep/skip reason. With the existing tag=dvd.aattr (IFO declared sub_id + channels) a bug log alone now shows whether the ordinal 0x80 really carries the declared layout — no disc needed to diagnose this class. expose ac3::find_ac3_sync as pub(crate) for the probe.
This commit is contained in:
@@ -337,7 +337,7 @@ pub(crate) fn acmod_channels(data: &[u8]) -> Option<u8> {
|
||||
}
|
||||
|
||||
/// Find AC3/E-AC-3 syncword (0x0B77) in data.
|
||||
fn find_ac3_sync(data: &[u8]) -> Option<usize> {
|
||||
pub(crate) fn find_ac3_sync(data: &[u8]) -> Option<usize> {
|
||||
(0..data.len().saturating_sub(1)).find(|&i| data[i] == 0x0B && data[i + 1] == 0x77)
|
||||
}
|
||||
|
||||
|
||||
+16
-4
@@ -210,6 +210,7 @@ impl DiscStream {
|
||||
batch_sectors: u16,
|
||||
content_format: crate::disc::ContentFormat,
|
||||
) -> Self {
|
||||
let mut title = title;
|
||||
let extents = title.extents.clone();
|
||||
let bytes_total_extents: u64 = extents.iter().map(|e| e.sector_count as u64 * 2048).sum();
|
||||
|
||||
@@ -224,6 +225,16 @@ impl DiscStream {
|
||||
std::any::type_name_of_val(&*reader)
|
||||
);
|
||||
|
||||
// CSS/unencrypted content needs a decrypting wrapper to yield plaintext
|
||||
// VOB bytes before the AC-3 sub-stream probe can read real `acmod`s.
|
||||
let mut reader = DecryptingSectorSource::new(reader, decrypt_keys.clone());
|
||||
|
||||
// Wrong-substream fix (Silence-of-the-Lambs): re-route the title's
|
||||
// declared AC-3 audio onto the physically-correct `0x8x` sub-streams by
|
||||
// probing their real channel counts off the head of the feature. No-op
|
||||
// for non-DVD or when the probe yields nothing.
|
||||
crate::disc::dvd_audio_probe::probe_and_remap(&mut reader, &mut title);
|
||||
|
||||
let mut pids = Vec::new();
|
||||
let mut parsers = Vec::new();
|
||||
let mut pid_to_track = Vec::new();
|
||||
@@ -262,10 +273,11 @@ impl DiscStream {
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
// Wrap the input reader in DecryptingSectorSource so the internal
|
||||
// fill_extents path sees plaintext bytes. For DecryptKeys::None
|
||||
// (unencrypted / raw / test fixtures) the decorator is a pass-through.
|
||||
let reader = DecryptingSectorSource::new(reader, decrypt_keys.clone());
|
||||
// `reader` is already wrapped in DecryptingSectorSource above (so the
|
||||
// internal fill_extents path sees plaintext bytes; for DecryptKeys::None
|
||||
// the decorator is a pass-through). Reset the unit base the probe read
|
||||
// advanced so the first fill_extents read starts cleanly.
|
||||
reader.set_unit_base(0);
|
||||
// Clone the shared loss counter once here so `lost_bytes()` never
|
||||
// clones an Arc per frame on the mux hot path.
|
||||
let decrypt_loss = reader.decrypt_loss();
|
||||
|
||||
+11
-1
@@ -558,7 +558,7 @@ pub fn build_iso_pipeline<S: SectorSource + Send + 'static>(
|
||||
crate::decrypt::DecryptKeys::Aacs { .. } => 3,
|
||||
_ => 1,
|
||||
};
|
||||
let decrypting =
|
||||
let mut decrypting =
|
||||
crate::sector::DecryptingSectorSource::new(Box::new(reader) as Box<dyn SectorSource>, keys);
|
||||
// Grab the decrypt-loss counter before the decorator is moved into the
|
||||
// producer thread. It tracks bytes of scrambled AACS units no key could
|
||||
@@ -566,6 +566,16 @@ pub fn build_iso_pipeline<S: SectorSource + Send + 'static>(
|
||||
// through `lost_bytes()` so the mux abort gate sees a partial decrypt
|
||||
// failure rather than a clean rip.
|
||||
let decrypt_loss = decrypting.decrypt_loss();
|
||||
|
||||
// Wrong-substream fix (Silence-of-the-Lambs): before the prefetcher takes
|
||||
// the reader, probe the feature head through the (plaintext) decrypting
|
||||
// source and re-route the title's declared AC-3 audio onto the physically
|
||||
// correct `0x8x` sub-streams. No-op for non-DVD or an empty probe. Reset the
|
||||
// unit base afterward so the prefetcher's first batch starts clean.
|
||||
let mut title = title;
|
||||
crate::disc::dvd_audio_probe::probe_and_remap(&mut decrypting, &mut title);
|
||||
decrypting.set_unit_base(0);
|
||||
|
||||
let prefetched = crate::sector::PrefetchedSectorSource::new_with_events(
|
||||
decrypting,
|
||||
extents,
|
||||
|
||||
Reference in New Issue
Block a user