wmv_decoder/
wmv2.rs

1//! WMV2 (Windows Media Video 8 / MS-MPEG4 V3) picture header parsing.
2//!
3//! This module intentionally mirrors upstream's `wmv2dec.c` picture-header logic:
4//!
5//!   - `wmv2_decode_picture_header()` for the primary picture header
6//!   - Frame-skipped probe for P pictures
7//!
8//! Any alternative/custom headers are deliberately not supported.
9
10use crate::bitreader::BitReader;
11use crate::error::{DecoderError, Result};
12
13#[derive(Debug, Clone)]
14pub struct Wmv2Params {
15    pub width:  u32,
16    pub height: u32,
17}
18
19impl Wmv2Params {
20    pub fn new(width: u32, height: u32) -> Self {
21        Wmv2Params { width, height }
22    }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum Wmv2FrameType {
27    I,
28    P,
29}
30
31#[derive(Debug, Clone)]
32pub struct Wmv2FrameHeader {
33    pub frame_type: Wmv2FrameType,
34    /// Quantizer (upstream: `qscale`, range 1..31).
35    pub pquant: u8,
36
37    /// Whether upstream would return `FRAME_SKIPPED` from `wmv2_decode_picture_header()`.
38    /// For such frames, the decoder should just output the previous reference.
39    pub frame_skipped: bool,
40
41    /// Bit offset (from the beginning of the payload slice) where the secondary picture
42    /// header / macroblock layer begins.
43    pub header_bits: usize,
44}
45
46impl Wmv2FrameHeader {
47    /// Parse all plausible upstream-aligned picture headers for this payload.
48    ///
49    /// `mb_w/mb_h` are macroblock dimensions used by upstream's skipped-frame probe.
50    pub fn parse_candidates(data: &[u8], mb_w: u32, mb_h: u32) -> Vec<Self> {
51        let mut out = Vec::new();
52        if data.is_empty() {
53            return out;
54        }
55        if let Some(h) = Self::parse_ref_picture_header(data, mb_w, mb_h) {
56            out.push(h);
57        }
58        out
59    }
60
61    pub fn parse(data: &[u8], mb_w: u32, mb_h: u32) -> Result<Self> {
62        if data.is_empty() {
63            return Err(DecoderError::InvalidData("Empty WMV2 payload".into()));
64        }
65        let mut cands = Self::parse_candidates(data, mb_w, mb_h);
66        if cands.is_empty() {
67            return Err(DecoderError::InvalidData("Could not parse WMV2 picture header".into()));
68        }
69        Ok(cands.remove(0))
70    }
71
72    /// upstream: `wmv2_decode_picture_header()`.
73    ///
74    /// Layout (MSB-first):
75    ///   - 1 bit: `get_bits1()` => 0 = I, 1 = P
76    ///   - if I: 7 bits "I7" (ignored)
77    ///   - 5 bits: `qscale` (1..31)
78    ///   - if P and the next bit is 1: run a skipped-frame probe on a cloned bitreader
79    fn parse_ref_picture_header(data: &[u8], mb_w: u32, mb_h: u32) -> Option<Self> {
80        const SKIP_TYPE_COL: u32 = 3;
81
82        let mut br = BitReader::new(data);
83
84        // upstream: h->c.pict_type = get_bits1(&gb) + 1;
85        let is_p = br.read_bit()?;
86        let frame_type = if is_p { Wmv2FrameType::P } else { Wmv2FrameType::I };
87
88        if frame_type == Wmv2FrameType::I {
89            let _i7 = br.read_bits(7)?;
90            let _ = _i7;
91        }
92
93        let qscale = br.read_bits(5)? as u8;
94        if qscale == 0 {
95            return None;
96        }
97
98        let mut frame_skipped = false;
99
100        // upstream skipped-frame probe (P only):
101        // if (pict_type != I && show_bits(1)) { ...; if (!run) return FRAME_SKIPPED; }
102        if frame_type == Wmv2FrameType::P {
103            if br.peek_bits(1)? == 1 {
104                let mut gb = br.clone();
105                let skip_type = gb.read_bits(2)?;
106                let mut run: i32 = if skip_type == SKIP_TYPE_COL {
107                    mb_w as i32
108                } else {
109                    mb_h as i32
110                };
111
112                while run > 0 {
113                    let block = run.min(25);
114                    let bits = gb.read_bits(block as u8)?;
115                    if bits != ((1u32 << block) - 1) {
116                        break;
117                    }
118                    run -= block;
119                }
120
121                if run == 0 {
122                    frame_skipped = true;
123                }
124            }
125        }
126
127        let header_bits = br.bits_read();
128
129        Some(Wmv2FrameHeader {
130            frame_type,
131            pquant: qscale,
132            frame_skipped,
133            header_bits,
134        })
135    }
136}
137
138impl std::fmt::Display for Wmv2FrameType {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        match self {
141            Wmv2FrameType::I => write!(f, "I"),
142            Wmv2FrameType::P => write!(f, "P"),
143        }
144    }
145}