mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
scsi, lpcm: MMC-6 disc presence and tray handling; LPCM decoded to plain PCM
Disc presence is tri-state per MMC-6, an empty drive fails fast, a cleaning cartridge reads as no disc, and the tray is unlocked only when we locked it. BD and DVD LPCM decode to plain PCM, with whole-frame DVD blocks and the BD layout carried through M2TS.
This commit is contained in:
+148
-39
@@ -80,62 +80,104 @@ fn max_substream_channels(data: &[u8]) -> Option<u8> {
|
||||
/// stream (in IFO order), picks the physical `0x8x` sub-stream whose probed channel count
|
||||
/// equals the declared count, never reusing a claimed sub-stream, and writes its PID (`0xBD00 |
|
||||
/// sub_id`) back onto the `Stream::Audio`. Conservative: only reassigns when a better match
|
||||
/// exists. Returns the number of streams whose PID was changed.
|
||||
/// exists or keeping the ordinal sub would share a PID. Sharing is left (and logged) only when
|
||||
/// there are more AC-3 streams than sub-streams. Returns the number of changed PIDs.
|
||||
pub fn remap_audio_pids(streams: &mut [Stream], probed: &BTreeMap<u8, u8>) -> usize {
|
||||
if probed.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
// Sub-streams already claimed by a remapped (or matching) earlier stream,
|
||||
// so two declared streams never collide on one physical sub-stream.
|
||||
let mut claimed: Vec<u8> = Vec::new();
|
||||
let ac3: Vec<usize> = streams
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, s)| matches!(s, Stream::Audio(a) if a.codec == crate::disc::Codec::Ac3))
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
let info = |s: &Stream| match s {
|
||||
Stream::Audio(a) => (a.channels.count(), (a.pid & 0x00FF) as u8),
|
||||
_ => (0, 0),
|
||||
};
|
||||
// Ordinal subs before any re-route: candidates for a displaced stream in pass 3.
|
||||
let ordinal: Vec<u8> = ac3.iter().map(|&i| info(&streams[i]).1).collect();
|
||||
let mut changed = 0usize;
|
||||
|
||||
for s in streams.iter_mut() {
|
||||
let Stream::Audio(a) = s else { continue };
|
||||
if a.codec != crate::disc::Codec::Ac3 {
|
||||
continue;
|
||||
// Each physical sub-stream is claimed at most once, so two declared streams
|
||||
// never collide on one PID. Pass 1: streams already on a matching sub-stream stay.
|
||||
let mut claimed: Vec<u8> = Vec::new();
|
||||
let mut pending: Vec<usize> = Vec::new();
|
||||
for &i in &ac3 {
|
||||
let (declared, current) = info(&streams[i]);
|
||||
if probed.get(¤t) == Some(&declared) && !claimed.contains(¤t) {
|
||||
claimed.push(current);
|
||||
} else {
|
||||
pending.push(i);
|
||||
}
|
||||
let declared = a.channels.count();
|
||||
// The sub-id this stream currently routes by (low byte of its PID).
|
||||
let current_sub = (a.pid & 0x00FF) as u8;
|
||||
|
||||
// If the stream's current physical sub-stream already matches its
|
||||
// declared channel count, keep it and claim it.
|
||||
if probed.get(¤t_sub) == Some(&declared) {
|
||||
claimed.push(current_sub);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise find an unclaimed physical sub-stream whose REAL channel
|
||||
// count equals the declared count.
|
||||
}
|
||||
// Pass 2: route the rest to an unclaimed sub-stream with the declared count.
|
||||
let mut unmatched: Vec<usize> = Vec::new();
|
||||
for i in pending {
|
||||
let (declared, _) = info(&streams[i]);
|
||||
let pick = probed
|
||||
.iter()
|
||||
.find(|(sub, ch)| **ch == declared && !claimed.contains(*sub))
|
||||
.map(|(sub, _)| *sub);
|
||||
|
||||
if let Some(sub) = pick {
|
||||
let new_pid = 0xBD00 | sub as u16;
|
||||
if new_pid != a.pid {
|
||||
tracing::debug!(
|
||||
target: "freemkv::scan",
|
||||
old_pid = a.pid,
|
||||
new_pid,
|
||||
declared_channels = declared,
|
||||
"dvd: re-routed AC-3 audio to physical sub-stream matching channel count"
|
||||
);
|
||||
a.pid = new_pid;
|
||||
changed += 1;
|
||||
match pick {
|
||||
Some(sub) => {
|
||||
claimed.push(sub);
|
||||
changed += set_sub(&mut streams[i], sub);
|
||||
}
|
||||
claimed.push(sub);
|
||||
None => unmatched.push(i),
|
||||
}
|
||||
}
|
||||
// Pass 3: no physical match — keep the ordinal sub when still free, else take a
|
||||
// free one rather than share a PID.
|
||||
let mut displaced: Vec<usize> = Vec::new();
|
||||
for i in unmatched {
|
||||
let (_, current) = info(&streams[i]);
|
||||
if claimed.contains(¤t) {
|
||||
displaced.push(i);
|
||||
} else {
|
||||
// No physical match — leave the ordinal assignment, but claim its
|
||||
// current sub so later streams don't steal a slot it may still use.
|
||||
claimed.push(current_sub);
|
||||
claimed.push(current);
|
||||
}
|
||||
}
|
||||
for i in displaced {
|
||||
// Prefer a sub-stream that physically carries data, then a vacated ordinal one.
|
||||
let free = probed
|
||||
.keys()
|
||||
.copied()
|
||||
.chain(ordinal.iter().copied())
|
||||
.find(|sub| !claimed.contains(sub));
|
||||
match free {
|
||||
Some(sub) => {
|
||||
claimed.push(sub);
|
||||
changed += set_sub(&mut streams[i], sub);
|
||||
}
|
||||
None => tracing::warn!(
|
||||
target: "freemkv::scan",
|
||||
pid = 0xBD00 | u16::from(info(&streams[i]).1),
|
||||
"dvd: no free AC-3 sub-stream; audio track shares a PID with another"
|
||||
),
|
||||
}
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
// Route an AC-3 stream to physical sub-stream `sub`; returns 1 when its PID changed.
|
||||
fn set_sub(s: &mut Stream, sub: u8) -> usize {
|
||||
let Stream::Audio(a) = s else { return 0 };
|
||||
let new_pid = 0xBD00 | sub as u16;
|
||||
if new_pid == a.pid {
|
||||
return 0;
|
||||
}
|
||||
tracing::debug!(
|
||||
target: "freemkv::scan",
|
||||
old_pid = a.pid,
|
||||
new_pid,
|
||||
declared_channels = a.channels.count(),
|
||||
"dvd: re-routed AC-3 audio to physical sub-stream matching channel count"
|
||||
);
|
||||
a.pid = new_pid;
|
||||
1
|
||||
}
|
||||
|
||||
/// Probe the first feature extent of a DVD title through a (decrypted) sector
|
||||
/// source and re-route its AC-3 audio PIDs to the physically-correct
|
||||
/// sub-streams. A bounded, best-effort scan: any read error or empty probe
|
||||
@@ -379,6 +421,73 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// A stream already sitting on its matching sub-stream must not share it with
|
||||
/// an earlier stream that was re-routed there: 0x80=5.1, 0x81=2.0, both
|
||||
/// declared 2.0 must stay on distinct sub-streams.
|
||||
#[test]
|
||||
fn remap_keep_current_respects_claimed() {
|
||||
let mut probed = BTreeMap::new();
|
||||
probed.insert(0x80u8, 6u8);
|
||||
probed.insert(0x81u8, 2u8);
|
||||
let mut streams = vec![
|
||||
ac3_stream(0xBD80, AudioChannels::Stereo),
|
||||
ac3_stream(0xBD81, AudioChannels::Stereo),
|
||||
];
|
||||
remap_audio_pids(&mut streams, &probed);
|
||||
let pids: Vec<u16> = streams
|
||||
.iter()
|
||||
.filter_map(|s| match s {
|
||||
Stream::Audio(a) => Some(a.pid),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(pids, vec![0xBD80, 0xBD81], "no two tracks on one PID");
|
||||
}
|
||||
|
||||
/// A displaced stream (its ordinal sub taken by a re-route) prefers an unclaimed
|
||||
/// sub-stream that physically exists over a vacated ordinal one with no data.
|
||||
#[test]
|
||||
fn remap_displaced_stream_prefers_a_probed_sub() {
|
||||
let mut probed = BTreeMap::new();
|
||||
probed.insert(0x80u8, 2u8);
|
||||
probed.insert(0x81u8, 6u8);
|
||||
let mut streams = vec![
|
||||
ac3_stream(0xBD82, AudioChannels::Surround51),
|
||||
ac3_stream(0xBD81, AudioChannels::Mono),
|
||||
];
|
||||
remap_audio_pids(&mut streams, &probed);
|
||||
let pids: Vec<u16> = streams
|
||||
.iter()
|
||||
.filter_map(|s| match s {
|
||||
Stream::Audio(a) => Some(a.pid),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
pids,
|
||||
vec![0xBD81, 0xBD80],
|
||||
"displaced -> probed 0x80, not empty 0x82"
|
||||
);
|
||||
}
|
||||
|
||||
/// More AC-3 streams than sub-streams: nothing is free, so the extra stream keeps
|
||||
/// its (shared) ordinal PID — logged, never re-routed onto a random sub.
|
||||
#[test]
|
||||
fn remap_exhausted_subs_leave_the_ordinal_pid() {
|
||||
let mut probed = BTreeMap::new();
|
||||
probed.insert(0x80u8, 2u8);
|
||||
let mut streams = vec![
|
||||
ac3_stream(0xBD80, AudioChannels::Stereo),
|
||||
ac3_stream(0xBD80, AudioChannels::Stereo),
|
||||
];
|
||||
assert_eq!(remap_audio_pids(&mut streams, &probed), 0);
|
||||
assert!(
|
||||
streams
|
||||
.iter()
|
||||
.all(|s| matches!(s, Stream::Audio(a) if a.pid == 0xBD80))
|
||||
);
|
||||
}
|
||||
|
||||
/// Empty probe (unreadable / scrambled VOB) is a no-op — the ordinal
|
||||
/// assignment survives so behaviour never regresses below today's.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user