Skip to main content

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(
68                "Could not parse WMV2 picture header".into(),
69            ));
70        }
71        Ok(cands.remove(0))
72    }
73
74    /// upstream: `wmv2_decode_picture_header()`.
75    ///
76    /// Layout (MSB-first):
77    ///   - 1 bit: `get_bits1()` => 0 = I, 1 = P
78    ///   - if I: 7 bits "I7" (ignored)
79    ///   - 5 bits: `qscale` (1..31)
80    ///   - if P and the next bit is 1: run a skipped-frame probe on a cloned bitreader
81    fn parse_ref_picture_header(data: &[u8], mb_w: u32, mb_h: u32) -> Option<Self> {
82        const SKIP_TYPE_COL: u32 = 3;
83
84        let mut br = BitReader::new(data);
85
86        // upstream: h->c.pict_type = get_bits1(&gb) + 1;
87        let is_p = br.read_bit()?;
88        let frame_type = if is_p {
89            Wmv2FrameType::P
90        } else {
91            Wmv2FrameType::I
92        };
93
94        if frame_type == Wmv2FrameType::I {
95            let _i7 = br.read_bits(7)?;
96            let _ = _i7;
97        }
98
99        let qscale = br.read_bits(5)? as u8;
100        if qscale == 0 {
101            return None;
102        }
103
104        let mut frame_skipped = false;
105
106        // upstream skipped-frame probe (P only):
107        // if (pict_type != I && show_bits(1)) { ...; if (!run) return FRAME_SKIPPED; }
108        if frame_type == Wmv2FrameType::P {
109            if br.peek_bits(1)? == 1 {
110                let mut gb = br.clone();
111                let skip_type = gb.read_bits(2)?;
112                let mut run: i32 = if skip_type == SKIP_TYPE_COL {
113                    mb_w as i32
114                } else {
115                    mb_h as i32
116                };
117
118                while run > 0 {
119                    let block = run.min(25);
120                    let bits = gb.read_bits(block as u8)?;
121                    if bits != ((1u32 << block) - 1) {
122                        break;
123                    }
124                    run -= block;
125                }
126
127                if run == 0 {
128                    frame_skipped = true;
129                }
130            }
131        }
132
133        let header_bits = br.bits_read();
134
135        Some(Wmv2FrameHeader {
136            frame_type,
137            pquant: qscale,
138            frame_skipped,
139            header_bits,
140        })
141    }
142}
143
144impl std::fmt::Display for Wmv2FrameType {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        match self {
147            Wmv2FrameType::I => write!(f, "I"),
148            Wmv2FrameType::P => write!(f, "P"),
149        }
150    }
151}