mux: wire TrueHD channel probe into iso:// mux setup
correct_truehd_channels() probes the first DECRYPTED access units of the chosen title (TrueHD PIDs, bounded 8 MiB read of the first extent) and sets AudioStream.channels from the MLP major sync — fixing the MPLS audio_format understatement (5.1 declared on a 7.1/Atmos TrueHD track). Regenerates the basic codec label for the corrected count; richer editorial labels are left untouched. Wired in resolve.rs input() for iso:// after decrypt_with (the m2ts is only decryptable post-key), using a fresh reader so the mux reader is undisturbed. generate_audio_label made pub(crate).
This commit is contained in:
@@ -375,6 +375,79 @@ pub struct Extent {
|
|||||||
pub sector_count: u32,
|
pub sector_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Correct the channel count of a title's TrueHD audio streams by probing the
|
||||||
|
/// first decrypted access units. The MPLS `audio_format` field declares the
|
||||||
|
/// BASE layout (often 5.1) even for a 7.1/Atmos TrueHD track; the real count is
|
||||||
|
/// in the MLP major sync. `reader` must yield DECRYPTED sectors (the m2ts is
|
||||||
|
/// AACS-encrypted, so this can only run at mux time, not scan). Reads a bounded
|
||||||
|
/// window of the title's first extent. Also regenerates the stream's codec
|
||||||
|
/// label when it was the basic descriptor for the (now corrected) count —
|
||||||
|
/// richer editorial labels (e.g. "Dolby Atmos") are left untouched.
|
||||||
|
pub(crate) fn correct_truehd_channels(reader: &mut dyn SectorSource, title: &mut DiscTitle) {
|
||||||
|
use crate::mux::codec::truehd::truehd_channels_from_stream;
|
||||||
|
|
||||||
|
let pids: Vec<u16> = title
|
||||||
|
.streams
|
||||||
|
.iter()
|
||||||
|
.filter_map(|s| match s {
|
||||||
|
Stream::Audio(a) if matches!(a.codec, Codec::TrueHd) => Some(a.pid),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if pids.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(ext) = title.extents.first() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// Bounded probe: up to 8 MiB from the start of the title — enough for the
|
||||||
|
// first interleaved TrueHD major sync of each stream.
|
||||||
|
const PROBE_SECTORS: u32 = 4096;
|
||||||
|
let n = ext.sector_count.min(PROBE_SECTORS) as u16;
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut buf = vec![0u8; n as usize * 2048];
|
||||||
|
if reader
|
||||||
|
.read_sectors(ext.start_lba, n, &mut buf, true)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut demux = crate::mux::ts::TsDemuxer::new(&pids);
|
||||||
|
let mut payloads: std::collections::HashMap<u16, Vec<u8>> = std::collections::HashMap::new();
|
||||||
|
for pes in demux.feed(&buf).into_iter().chain(demux.flush()) {
|
||||||
|
payloads
|
||||||
|
.entry(pes.pid)
|
||||||
|
.or_default()
|
||||||
|
.extend_from_slice(&pes.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
for s in title.streams.iter_mut() {
|
||||||
|
let Stream::Audio(a) = s else { continue };
|
||||||
|
if !matches!(a.codec, Codec::TrueHd) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(payload) = payloads.get(&a.pid) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(count) = truehd_channels_from_stream(payload) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let new_ch = AudioChannels::from_count(count);
|
||||||
|
if new_ch == AudioChannels::Unknown || new_ch == a.channels {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let was_basic =
|
||||||
|
a.label == crate::labels::generate_audio_label(&a.codec, &a.channels, a.secondary);
|
||||||
|
a.channels = new_ch;
|
||||||
|
if was_basic {
|
||||||
|
a.label = crate::labels::generate_audio_label(&a.codec, &new_ch, a.secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Calculate how many bytes of bad/unreadable data fall within a title's extents.
|
/// Calculate how many bytes of bad/unreadable data fall within a title's extents.
|
||||||
/// `pub(crate)` so autorip can use it for main-movie lost_ms computation.
|
/// `pub(crate)` so autorip can use it for main-movie lost_ms computation.
|
||||||
pub fn bytes_bad_in_title(title: &DiscTitle, bad_ranges: &[(u64, u64)]) -> u64 {
|
pub fn bytes_bad_in_title(title: &DiscTitle, bad_ranges: &[(u64, u64)]) -> u64 {
|
||||||
|
|||||||
+1
-1
@@ -412,7 +412,7 @@ fn codec_hint_adds_detail(hint: &str) -> bool {
|
|||||||
h.contains("atmos") || h.contains("dts:x") || h.contains("dts-x") || h.contains("dtsx")
|
h.contains("atmos") || h.contains("dts:x") || h.contains("dts-x") || h.contains("dtsx")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_audio_label(
|
pub(crate) fn generate_audio_label(
|
||||||
codec: &crate::disc::Codec,
|
codec: &crate::disc::Codec,
|
||||||
channels: &crate::disc::AudioChannels,
|
channels: &crate::disc::AudioChannels,
|
||||||
_secondary: bool,
|
_secondary: bool,
|
||||||
|
|||||||
+8
-1
@@ -216,8 +216,15 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
|
|||||||
}
|
}
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
let title = disc.titles[idx].clone();
|
// Correct TrueHD channel counts (MPLS understates 7.1/Atmos as 5.1)
|
||||||
|
// by probing the first DECRYPTED access units of the chosen title.
|
||||||
|
// A fresh reader avoids disturbing the mux reader below.
|
||||||
let keys = disc.decrypt_keys();
|
let keys = disc.decrypt_keys();
|
||||||
|
if let Ok(probe) = crate::io::file_sector_source::FileSectorSource::open(path) {
|
||||||
|
let mut dec = crate::sector::DecryptingSectorSource::new(probe, keys.clone());
|
||||||
|
crate::disc::correct_truehd_channels(&mut dec, &mut disc.titles[idx]);
|
||||||
|
}
|
||||||
|
let title = disc.titles[idx].clone();
|
||||||
let format = disc.content_format;
|
let format = disc.content_format;
|
||||||
// ISO file: 16 MiB batch — sequential read from fast
|
// ISO file: 16 MiB batch — sequential read from fast
|
||||||
// storage, no bad sectors. Measured optimum on the rip1
|
// storage, no bad sectors. Measured optimum on the rip1
|
||||||
|
|||||||
Reference in New Issue
Block a user