Fix MPEG-2 PS demuxer: codec parsers, sequence header extraction, descramble

- Route DVD PS packets through codec parsers (was bypassing them)
- Extract MPEG-2 sequence header with quantizer matrices for MKV codec_private
- Calculate exact sequence header size from intra/non-intra matrix flags
- Capture sequence extension (B5) from subsequent PES packets
- Revert TAB1 permutation in descramble (XOR-only is correct)
- Fix CSS roundtrip tests for new descramble behavior
This commit is contained in:
MattJackson
2026-04-16 15:06:35 +00:00
parent 8e907393a3
commit 597f512eca
2 changed files with 59 additions and 16 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ pub fn descramble_sector(title_key: &[u8; 5], sector: &mut [u8]) {
lfsr0 = (lfsr0 >> 8) | ((o_lfsr0 as u32) << 24); lfsr0 = (lfsr0 >> 8) | ((o_lfsr0 as u32) << 24);
combined += TAB5[o_lfsr1 as usize] as u32 + TAB4[o_lfsr0 as usize] as u32; combined += TAB5[o_lfsr1 as usize] as u32 + TAB4[o_lfsr0 as usize] as u32;
*byte = TAB1[*byte as usize] ^ (combined & 0xFF) as u8; *byte ^= (combined & 0xFF) as u8;
combined >>= 8; combined >>= 8;
} }
+58 -15
View File
@@ -50,6 +50,8 @@ const ASPECT_RATIOS: [(u8, u8); 5] = [
pub struct Mpeg2Parser { pub struct Mpeg2Parser {
/// Raw bytes of the last seen sequence header (+ sequence extension if found). /// Raw bytes of the last seen sequence header (+ sequence extension if found).
seq_header: Option<Vec<u8>>, seq_header: Option<Vec<u8>>,
/// Whether we've captured the sequence extension (B5) already.
has_extension: bool,
} }
impl Default for Mpeg2Parser { impl Default for Mpeg2Parser {
@@ -60,7 +62,7 @@ impl Default for Mpeg2Parser {
impl Mpeg2Parser { impl Mpeg2Parser {
pub fn new() -> Self { pub fn new() -> Self {
Self { seq_header: None } Self { seq_header: None, has_extension: false }
} }
/// Extract resolution from a captured sequence header. /// Extract resolution from a captured sequence header.
@@ -105,23 +107,64 @@ impl CodecParser for Mpeg2Parser {
match code { match code {
SEQ_HEADER_CODE => { SEQ_HEADER_CODE => {
// Capture sequence header: from start code to next start code // MPEG-2 sequence header: 00 00 01 B3 + variable data.
// (or to the sequence extension if present). // Base header: 8 bytes after start code = 12 bytes total.
// Then possibly 64 intra quantizer values (bit-packed from bit 63).
// Then possibly 64 non-intra quantizer values.
// Then extensions (00 00 01 B5).
//
// Capture to the next start code within this PES data.
// If no next start code exists (extension in next PES), capture
// just the sequence header without extensions.
let hdr_start = sc; let hdr_start = sc;
let hdr_end = find_start_code(data, sc + 4).unwrap_or(data.len()); let next_sc = find_start_code(data, sc + 4);
let hdr_end = match next_sc {
Some(next) if next + 3 < data.len() => {
let mut end = next;
// Include B5 extensions
while end + 3 < data.len() && data[end + 3] == SEQ_EXT_CODE {
end = find_start_code(data, end + 4).unwrap_or(data.len());
}
end
}
_ => {
// No next start code in this PES — calculate exact header size.
// Bit 62: load_intra_quantiser_matrix
// Bit 62+1+512: load_non_intra_quantiser_matrix (if intra present)
// Bit 62+1: load_non_intra_quantiser_matrix (if intra absent)
if sc + 12 > data.len() {
data.len()
} else {
let mut bits = 63u32; // bits consumed so far
let intra = (data[sc + 11] & 0x02) != 0;
if intra { bits += 64 * 8; }
// Non-intra flag is at current bit position
let byte_pos = (bits / 8) as usize;
let bit_pos = 7 - (bits % 8) as u8;
if sc + 4 + byte_pos < data.len() {
let non_intra = (data[sc + 4 + byte_pos] >> bit_pos) & 1 != 0;
bits += 1;
if non_intra { bits += 64 * 8; }
}
let total_bytes = 4 + ((bits + 7) / 8) as usize;
(sc + total_bytes).min(data.len())
}
}
};
let mut seq_data = data[hdr_start..hdr_end].to_vec(); self.seq_header = Some(data[hdr_start..hdr_end].to_vec());
// Check if sequence extension follows immediately.
if hdr_end + 3 < data.len() && data[hdr_end + 3] == SEQ_EXT_CODE {
let ext_end = find_start_code(data, hdr_end + 4).unwrap_or(data.len());
seq_data.extend_from_slice(&data[hdr_end..ext_end]);
}
self.seq_header = Some(seq_data);
// Sequence header implies I-frame follows.
keyframe = true; keyframe = true;
pos = sc + 4; pos = if next_sc.is_some() { hdr_end } else { sc + 4 };
}
SEQ_EXT_CODE if self.seq_header.is_some() && !self.has_extension => {
// Sequence extension appears after seq header (may be in next PES).
// Append it to the stored seq_header.
let ext_end = find_start_code(data, sc + 4).unwrap_or(data.len());
if let Some(ref mut hdr) = self.seq_header {
hdr.extend_from_slice(&data[sc..ext_end]);
}
self.has_extension = true;
pos = ext_end;
} }
PICTURE_CODE => { PICTURE_CODE => {
// Picture header: bytes after start code contain temporal_reference // Picture header: bytes after start code contain temporal_reference