codec/pixel_format/mpeg2.rs
1//! MPEG-2 sequence header parser — ISO/IEC 13818-2 §6.2.2.1 + §6.2.2.3.
2
3use super::bitreader::BitReader;
4
5/// Parsed MPEG-2 sequence header + (optional) sequence extension.
6///
7/// MPEG-2 video §6.2.2.1/§6.2.2.3 (ISO/IEC 13818-2): the 12-bit
8/// `horizontal_size_value` / `vertical_size_value` from the sequence
9/// header, optionally extended to 14 bits by the 2-bit
10/// `horizontal_size_extension` / `vertical_size_extension` fields in a
11/// `sequence_extension()` start-code-prefixed NAL. Pure MPEG-1
12/// (start code 0xB3 but no 0xB5 extension) stays 12-bit — produces
13/// the same 12-bit result via the extension-less path.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct Mpeg2SeqInfo {
16 pub width: u32,
17 pub height: u32,
18}
19
20/// MPEG-2 sequence header scan — ISO/IEC 13818-2 §6.2.2.1 (sequence
21/// header, start code `00 00 01 B3`) + §6.2.2.3 (sequence extension,
22/// start code `00 00 01 B5` with `extension_start_code_identifier==1`).
23///
24/// The sequence header carries 12-bit `horizontal_size_value` and
25/// `vertical_size_value`, tight for sizes ≤ 4095. The optional sequence
26/// extension prepends 2-bit `_extension` fields that, when combined,
27/// bring the total to 14 bits (sizes ≤ 16383). Pure MPEG-1 (start code
28/// 0xB3 only, no 0xB5) never has the extension and stays 12-bit.
29pub fn parse_mpeg2_sequence_header(sample: &[u8]) -> Option<Mpeg2SeqInfo> {
30 // Walk bytes looking for 00 00 01 B3 (sequence_header_code). The
31 // following 3 bytes carry horizontal(12) + vertical(12).
32 let seq_hdr_start = find_mpeg2_start_code(sample, 0xB3)?;
33 let hdr_body_off = seq_hdr_start + 4;
34 if hdr_body_off + 3 > sample.len() {
35 return None;
36 }
37 let b = &sample[hdr_body_off..hdr_body_off + 3];
38 let mut width = (((b[0] as u32) << 4) | ((b[1] as u32) >> 4)) & 0x0FFF;
39 let mut height = (((b[1] as u32 & 0x0F) << 8) | (b[2] as u32)) & 0x0FFF;
40
41 // Look for a subsequent sequence_extension that upgrades the 12-bit
42 // values to 14-bit. Only scan forward from seq_hdr_start; a
43 // sequence_extension before the first sequence_header is
44 // nonsensical and we shouldn't confuse the parse.
45 let search_from = hdr_body_off + 3;
46 if search_from < sample.len()
47 && let Some(ext_start) = find_mpeg2_start_code(&sample[search_from..], 0xB5)
48 {
49 let ext_body_off = search_from + ext_start + 4;
50 if ext_body_off + 3 <= sample.len() {
51 let mut br = BitReader::new(&sample[ext_body_off..]);
52 if let Some(id) = br.read_bits(4)
53 && id == 1
54 {
55 // sequence_extension §6.2.2.3:
56 // extension_start_code_identifier u(4) = 0001 (already read)
57 // profile_and_level_indication u(8)
58 // progressive_sequence u(1)
59 // chroma_format u(2)
60 // horizontal_size_extension u(2)
61 // vertical_size_extension u(2)
62 let _profile_level = br.read_bits(8)?;
63 let _progressive = br.read_bits(1)?;
64 let _chroma = br.read_bits(2)?;
65 let h_ext = br.read_bits(2)?;
66 let v_ext = br.read_bits(2)?;
67 width |= h_ext << 12;
68 height |= v_ext << 12;
69 }
70 }
71 }
72
73 if width == 0 || height == 0 {
74 return None;
75 }
76 Some(Mpeg2SeqInfo { width, height })
77}
78
79/// Scan for an MPEG-2 start code (0x00 0x00 0x01 <target>) byte-aligned.
80/// Returns the file offset of the leading 0x00 on success.
81fn find_mpeg2_start_code(data: &[u8], target: u8) -> Option<usize> {
82 let mut i = 0;
83 while i + 4 <= data.len() {
84 if data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1 && data[i + 3] == target {
85 return Some(i);
86 }
87 i += 1;
88 }
89 None
90}