Skip to main content

oxideav_bitstream/
mpeg2.rs

1//! MPEG-2 / H.262 sequence-header, picture-header and extension
2//! parser.
3//!
4//! Lifted from `oxideav-vdpau::mpeg2` (Round 4) under the workspace
5//! clean-room policy: same author, same workspace, moving the
6//! canonical home from the VDPAU glue crate into the shared
7//! bitstream crate so the other backends can share it.
8//!
9//! # Scope
10//!
11//! Like the H.264 / HEVC / VP9 / VP8 siblings this is **not** a full
12//! MPEG-2 decoder — only the parameter-buffer-shaped header bits the
13//! HW backends need (`VAPictureParameterBufferMPEG2`,
14//! `VdpPictureInfoMPEG1Or2`).
15//!
16//! MPEG-2 uses byte-aligned start codes `00 00 01 XX` (no Annex-B
17//! emulation-prevention bytes — those are H.264/HEVC only). The
18//! parser walks the bitstream looking for:
19//!
20//! - sequence_header (start_code 0xb3)
21//! - sequence_extension (extension_start_code 0xb5 + extension_id 0x1)
22//! - picture_header (start_code 0x00)
23//! - picture_coding_extension (start_code 0xb5 + extension_id 0x8)
24//!
25//! # References
26//!
27//! ITU-T H.262 / ISO/IEC 13818-2, sections 6.2.2 (sequence header),
28//! 6.2.2.3 (sequence extension), 6.2.3 (picture header), 6.2.3.1
29//! (picture coding extension).
30
31use crate::bit_reader::BitReader;
32use crate::BitstreamError;
33
34// ─────────────────────────── Start codes ────────────────────────────────────
35
36/// `picture_start_code` — see ITU-T H.262 §6.2.3.
37pub const START_CODE_PICTURE: u8 = 0x00;
38/// First slice start code (slice codes occupy 0x01..=0xAF).
39pub const START_CODE_SLICE_FIRST: u8 = 0x01;
40/// Last slice start code (slice codes occupy 0x01..=0xAF).
41pub const START_CODE_SLICE_LAST: u8 = 0xAF;
42/// `sequence_header_code` — §6.2.2.
43pub const START_CODE_SEQUENCE_HEADER: u8 = 0xB3;
44/// `extension_start_code` — §6.2.2.3 / §6.2.3.1.
45pub const START_CODE_EXTENSION: u8 = 0xB5;
46/// `sequence_end_code`.
47pub const START_CODE_SEQUENCE_END: u8 = 0xB7;
48/// `group_start_code` — §6.2.2.6.
49pub const START_CODE_GROUP_OF_PICTURES: u8 = 0xB8;
50
51/// `extension_start_code_identifier` for sequence extension (§6.2.2.3).
52pub const EXTENSION_ID_SEQUENCE: u8 = 0x1;
53/// `extension_start_code_identifier` for picture coding extension
54/// (§6.2.3.1).
55pub const EXTENSION_ID_PICTURE_CODING: u8 = 0x8;
56
57// ─────────────────────────── Default quantizer matrices ─────────────────────
58
59/// Default intra-quantizer matrix (zig-zag scan order, ISO 13818-2
60/// table 7-3).
61pub const DEFAULT_INTRA_QUANT: [u8; 64] = [
62    8, 16, 19, 22, 26, 27, 29, 34, 16, 16, 22, 24, 27, 29, 34, 37, 19, 22, 26, 27, 29, 34, 34, 38,
63    22, 22, 26, 27, 29, 34, 37, 40, 22, 26, 27, 29, 32, 35, 40, 48, 26, 27, 29, 32, 35, 40, 48, 58,
64    26, 27, 29, 34, 38, 46, 56, 69, 27, 29, 35, 38, 46, 56, 69, 83,
65];
66
67/// Default non-intra-quantizer matrix (uniform 16/16, ISO 13818-2
68/// table 7-4).
69pub const DEFAULT_NON_INTRA_QUANT: [u8; 64] = [16; 64];
70
71/// Convert MPEG-2 zig-zag scan order to raster scan order (ISO 13818-2
72/// figure 7-3).
73const ZIGZAG_TO_RASTER: [u8; 64] = [
74    0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20,
75    13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59,
76    52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
77];
78
79/// Convert a quantizer matrix from zig-zag to raster scan order. The
80/// HW backends expect raster order in their parameter buffers.
81pub fn zigzag_to_raster_quant(zz: &[u8; 64]) -> [u8; 64] {
82    let mut out = [0u8; 64];
83    for (zz_idx, &raster_idx) in ZIGZAG_TO_RASTER.iter().enumerate() {
84        out[raster_idx as usize] = zz[zz_idx];
85    }
86    out
87}
88
89// ─────────────────────────── Start-code framing ─────────────────────────────
90
91/// Locate every MPEG-2 start code in `buf`. Returns a vec of
92/// `(start_code_byte, payload_start_index)` — `start_code_byte` is
93/// the byte immediately after `00 00 01`, and `payload_start_index`
94/// points at the byte after that.
95pub fn find_start_codes(buf: &[u8]) -> Vec<(u8, usize)> {
96    let mut out = Vec::new();
97    let mut i = 0;
98    let n = buf.len();
99    while i + 3 < n {
100        if buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 1 {
101            out.push((buf[i + 3], i + 4));
102            i += 4;
103        } else {
104            i += 1;
105        }
106    }
107    out
108}
109
110/// Find the byte offset of the first MPEG-2 slice start code
111/// (0x00000101 .. 0x000001AF). Returns the offset of the leading
112/// `00 00 01` byte triplet so callers can pass `&buf[off..]` to the
113/// HW backend's bitstream submit.
114pub fn find_first_slice(buf: &[u8]) -> Option<usize> {
115    let n = buf.len();
116    let mut i = 0;
117    while i + 3 < n {
118        if buf[i] == 0 && buf[i + 1] == 0 && buf[i + 2] == 1 {
119            let code = buf[i + 3];
120            if (START_CODE_SLICE_FIRST..=START_CODE_SLICE_LAST).contains(&code) {
121                return Some(i);
122            }
123            i += 4;
124        } else {
125            i += 1;
126        }
127    }
128    None
129}
130
131// ─────────────────────────── Output structs ─────────────────────────────────
132
133/// Sequence header (§6.2.2).
134#[derive(Debug, Clone, Default, PartialEq, Eq)]
135pub struct Mpeg2SequenceHeader {
136    pub horizontal_size: u32,
137    pub vertical_size: u32,
138    pub aspect_ratio_information: u8,
139    pub frame_rate_code: u8,
140    pub bit_rate: u32,
141    pub vbv_buffer_size: u32,
142    pub constrained_parameters_flag: bool,
143    /// Present only if `load_intra_quantizer_matrix=1`. The matrix is
144    /// given in zig-zag scan order — use [`zigzag_to_raster_quant`]
145    /// before submitting to the HW backend.
146    pub intra_quantiser_matrix: Option<[u8; 64]>,
147    /// Same shape as `intra_quantiser_matrix`.
148    pub non_intra_quantiser_matrix: Option<[u8; 64]>,
149}
150
151impl Mpeg2SequenceHeader {
152    /// Effective intra-quantizer matrix in raster scan order (the
153    /// custom matrix if present, otherwise the spec default).
154    pub fn intra_quantizer_matrix_raster(&self) -> [u8; 64] {
155        zigzag_to_raster_quant(
156            self.intra_quantiser_matrix
157                .as_ref()
158                .unwrap_or(&DEFAULT_INTRA_QUANT),
159        )
160    }
161
162    /// Effective non-intra-quantizer matrix in raster scan order.
163    pub fn non_intra_quantizer_matrix_raster(&self) -> [u8; 64] {
164        zigzag_to_raster_quant(
165            self.non_intra_quantiser_matrix
166                .as_ref()
167                .unwrap_or(&DEFAULT_NON_INTRA_QUANT),
168        )
169    }
170}
171
172/// Picture header (§6.2.3).
173#[derive(Debug, Clone, Default, PartialEq, Eq)]
174pub struct Mpeg2PictureHeader {
175    pub temporal_reference: u32,
176    /// 1=I, 2=P, 3=B (4=D for MPEG-1 only).
177    pub picture_coding_type: u8,
178    pub vbv_delay: u32,
179    /// MPEG-1 only — kept for completeness.
180    pub full_pel_forward_vector: u8,
181    pub full_pel_backward_vector: u8,
182    pub f_code: [[u8; 2]; 2],
183}
184
185/// Picture coding extension (§6.2.3.1).
186#[derive(Debug, Clone, Default, PartialEq, Eq)]
187pub struct Mpeg2PictureCodingExtension {
188    pub f_code: [[u8; 2]; 2],
189    pub intra_dc_precision: u8,
190    pub picture_structure: u8,
191    pub top_field_first: u8,
192    pub frame_pred_frame_dct: u8,
193    pub concealment_motion_vectors: u8,
194    pub q_scale_type: u8,
195    pub intra_vlc_format: u8,
196    pub alternate_scan: u8,
197    pub repeat_first_field: u8,
198    pub chroma_420_type: u8,
199    pub progressive_frame: u8,
200}
201
202/// Sequence extension (§6.2.2.3). Carries the high-level profile /
203/// chroma-format / progressive / colour-primaries flags that
204/// MPEG-2 layered on top of MPEG-1's sequence header.
205#[derive(Debug, Clone, Default, PartialEq, Eq)]
206pub struct Mpeg2SequenceExtension {
207    pub profile_and_level_indication: u8,
208    pub progressive_sequence: bool,
209    /// 1 = 4:2:0, 2 = 4:2:2, 3 = 4:4:4.
210    pub chroma_format: u8,
211    pub horizontal_size_extension: u8,
212    pub vertical_size_extension: u8,
213    pub bit_rate_extension: u32,
214    pub vbv_buffer_size_extension: u8,
215    pub low_delay: bool,
216    pub frame_rate_extension_n: u8,
217    pub frame_rate_extension_d: u8,
218}
219
220// ─────────────────────────── Parsers ────────────────────────────────────────
221
222/// Parse a sequence header. `bytes` is the buffer starting **after**
223/// the `00 00 01 B3` start code (i.e. at the first payload byte).
224pub fn parse_sequence_header(bytes: &[u8]) -> Result<Mpeg2SequenceHeader, BitstreamError> {
225    if bytes.len() < 8 {
226        return Err(BitstreamError::unexpected_end(
227            "MPEG-2 sequence_header shorter than 8 bytes",
228        ));
229    }
230    let mut r = BitReader::new(bytes);
231    let mut s = Mpeg2SequenceHeader {
232        horizontal_size: r.u(12),
233        vertical_size: r.u(12),
234        aspect_ratio_information: r.u(4) as u8,
235        frame_rate_code: r.u(4) as u8,
236        bit_rate: r.u(18),
237        ..Mpeg2SequenceHeader::default()
238    };
239    let _marker_bit = r.u(1);
240    s.vbv_buffer_size = r.u(10);
241    s.constrained_parameters_flag = r.u(1) != 0;
242    let load_intra = r.u(1) != 0;
243    if load_intra {
244        let mut m = [0u8; 64];
245        for q in m.iter_mut() {
246            *q = r.u(8) as u8;
247        }
248        s.intra_quantiser_matrix = Some(m);
249    }
250    let load_non_intra = r.u(1) != 0;
251    if load_non_intra {
252        let mut m = [0u8; 64];
253        for q in m.iter_mut() {
254            *q = r.u(8) as u8;
255        }
256        s.non_intra_quantiser_matrix = Some(m);
257    }
258    Ok(s)
259}
260
261/// Parse a picture header. `bytes` is the buffer starting **after**
262/// the `00 00 01 00` start code.
263pub fn parse_picture_header(bytes: &[u8]) -> Result<Mpeg2PictureHeader, BitstreamError> {
264    if bytes.len() < 4 {
265        return Err(BitstreamError::unexpected_end(
266            "MPEG-2 picture_header shorter than 4 bytes",
267        ));
268    }
269    let mut r = BitReader::new(bytes);
270    let mut p = Mpeg2PictureHeader {
271        temporal_reference: r.u(10),
272        picture_coding_type: r.u(3) as u8,
273        vbv_delay: r.u(16),
274        ..Mpeg2PictureHeader::default()
275    };
276    if p.picture_coding_type == 2 || p.picture_coding_type == 3 {
277        // P or B: full_pel_forward_vector + forward_f_code.
278        p.full_pel_forward_vector = r.u(1) as u8;
279        let forward_f_code = r.u(3) as u8;
280        p.f_code[0][0] = forward_f_code;
281        p.f_code[0][1] = forward_f_code;
282    }
283    if p.picture_coding_type == 3 {
284        // B: full_pel_backward_vector + backward_f_code.
285        p.full_pel_backward_vector = r.u(1) as u8;
286        let backward_f_code = r.u(3) as u8;
287        p.f_code[1][0] = backward_f_code;
288        p.f_code[1][1] = backward_f_code;
289    }
290    Ok(p)
291}
292
293/// Parse a picture coding extension (§6.2.3.1). `bytes` starts at the
294/// first byte of the extension payload — the high 4 bits of which
295/// must equal [`EXTENSION_ID_PICTURE_CODING`] (8).
296pub fn parse_picture_coding_extension(
297    bytes: &[u8],
298) -> Result<Mpeg2PictureCodingExtension, BitstreamError> {
299    if bytes.len() < 4 {
300        return Err(BitstreamError::unexpected_end(
301            "MPEG-2 picture_coding_extension shorter than 4 bytes",
302        ));
303    }
304    let mut r = BitReader::new(bytes);
305    let ext_id = r.u(4) as u8;
306    if ext_id != EXTENSION_ID_PICTURE_CODING {
307        return Err(BitstreamError::invalid(format!(
308            "MPEG-2 picture_coding_extension: ext_id={ext_id} != 8"
309        )));
310    }
311    let mut e = Mpeg2PictureCodingExtension::default();
312    e.f_code[0][0] = r.u(4) as u8;
313    e.f_code[0][1] = r.u(4) as u8;
314    e.f_code[1][0] = r.u(4) as u8;
315    e.f_code[1][1] = r.u(4) as u8;
316    e.intra_dc_precision = r.u(2) as u8;
317    e.picture_structure = r.u(2) as u8;
318    e.top_field_first = r.u(1) as u8;
319    e.frame_pred_frame_dct = r.u(1) as u8;
320    e.concealment_motion_vectors = r.u(1) as u8;
321    e.q_scale_type = r.u(1) as u8;
322    e.intra_vlc_format = r.u(1) as u8;
323    e.alternate_scan = r.u(1) as u8;
324    e.repeat_first_field = r.u(1) as u8;
325    e.chroma_420_type = r.u(1) as u8;
326    e.progressive_frame = r.u(1) as u8;
327    Ok(e)
328}
329
330/// Parse a sequence extension (§6.2.2.3). `bytes` starts at the first
331/// byte of the extension payload — the high 4 bits of which must equal
332/// [`EXTENSION_ID_SEQUENCE`] (1).
333pub fn parse_sequence_extension(bytes: &[u8]) -> Result<Mpeg2SequenceExtension, BitstreamError> {
334    if bytes.len() < 6 {
335        return Err(BitstreamError::unexpected_end(
336            "MPEG-2 sequence_extension shorter than 6 bytes",
337        ));
338    }
339    let mut r = BitReader::new(bytes);
340    let ext_id = r.u(4) as u8;
341    if ext_id != EXTENSION_ID_SEQUENCE {
342        return Err(BitstreamError::invalid(format!(
343            "MPEG-2 sequence_extension: ext_id={ext_id} != 1"
344        )));
345    }
346    let mut e = Mpeg2SequenceExtension {
347        profile_and_level_indication: r.u(8) as u8,
348        progressive_sequence: r.u(1) != 0,
349        chroma_format: r.u(2) as u8,
350        horizontal_size_extension: r.u(2) as u8,
351        vertical_size_extension: r.u(2) as u8,
352        bit_rate_extension: r.u(12),
353        ..Mpeg2SequenceExtension::default()
354    };
355    let _marker_bit = r.u(1);
356    e.vbv_buffer_size_extension = r.u(8) as u8;
357    e.low_delay = r.u(1) != 0;
358    e.frame_rate_extension_n = r.u(2) as u8;
359    e.frame_rate_extension_d = r.u(5) as u8;
360    Ok(e)
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    #[test]
368    fn find_start_codes_locates_known_codes() {
369        let buf = [
370            0, 0, 1, 0xb3, 0xaa, 0xbb, // sequence header
371            0, 0, 1, 0xb5, 0xcc, // extension
372            0, 0, 1, 0x00, 0xdd, 0xee, // picture header
373            0, 0, 1, 0x01, 0xff, // slice 1
374        ];
375        let codes = find_start_codes(&buf);
376        assert_eq!(codes.len(), 4);
377        assert_eq!(codes[0].0, 0xb3);
378        assert_eq!(codes[1].0, 0xb5);
379        assert_eq!(codes[2].0, 0x00);
380        assert_eq!(codes[3].0, 0x01);
381    }
382
383    #[test]
384    fn find_first_slice_returns_offset_of_leading_zeros() {
385        let buf = [
386            0, 0, 1, 0xb3, 0xaa, // sequence header
387            0, 0, 1, 0x00, 0xbb, // picture header
388            0, 0, 1, 0x01, 0xcc, // slice 1
389        ];
390        let off = find_first_slice(&buf).expect("slice present");
391        assert_eq!(off, 10);
392    }
393
394    #[test]
395    fn zigzag_inverse_roundtrip() {
396        let zz: [u8; 64] = std::array::from_fn(|i| (i + 1) as u8);
397        let raster = zigzag_to_raster_quant(&zz);
398        assert_eq!(raster[0], 1); // DC
399        assert_eq!(raster[1], 2);
400        assert_eq!(raster[8], 3);
401    }
402
403    #[test]
404    fn parse_sequence_header_too_short() {
405        assert!(matches!(
406            parse_sequence_header(&[0u8; 4]),
407            Err(BitstreamError::UnexpectedEnd(_))
408        ));
409    }
410
411    #[test]
412    fn parse_picture_coding_extension_rejects_wrong_ext_id() {
413        // extension id = 1 in the high 4 bits → not picture coding.
414        let bytes = [0x10, 0x00, 0x00, 0x00];
415        assert!(matches!(
416            parse_picture_coding_extension(&bytes),
417            Err(BitstreamError::InvalidData(_))
418        ));
419    }
420}