Skip to main content

oxideav_bitstream/
vp8.rs

1//! VP8 keyframe header parsing (RFC 6386).
2//!
3//! VP8 keyframes contain a 3-byte uncompressed bit-packed header
4//! followed (for keyframes only) by a 7-byte uncompressed-data-chunk
5//! that carries the start code and frame dimensions, followed by the
6//! arithmetic-coded compressed bitstream. Decoding the compressed
7//! bitstream requires a boolean entropy coder and several thousand
8//! lines of decoder logic — that is the GPU's job, not ours.
9//!
10//! This module surfaces only the **uncompressed** parts. They are
11//! enough to populate any of the slice-data HW backends' VP8
12//! parameter buffers (`VAPictureParameterBufferVP8`, NVDEC's VP8
13//! parameter struct, …).
14//!
15//! # Bit ordering
16//!
17//! VP8 is unusual: the first 3 bytes are read **byte-by-byte
18//! little-endian, with each byte read least-significant-bit first**.
19//! See RFC 6386 §9.1: the field order in the spec is
20//!
21//!   frame_type        (1 bit)  bit 0 of byte 0
22//!   version           (3 bits) bits 1..3 of byte 0
23//!   show_frame        (1 bit)  bit 4 of byte 0
24//!   first_part_size  (19 bits) bits 5..23 (spans bytes 0, 1, 2)
25//!
26//! When this packing is interpreted as a single 24-bit
27//! little-endian value, the field offsets are LSB-first and the
28//! whole header decodes via shift-and-mask.
29//!
30//! For keyframes (`frame_type == 0`), the next 7 bytes are:
31//!
32//!   start code        (3 bytes, must be `0x9D 0x01 0x2A`)
33//!   width + h-scale   (2 bytes, LE u16: low 14 bits = width,
34//!                                top 2 bits = horizontal_scale)
35//!   height + v-scale  (2 bytes, LE u16: same layout for height /
36//!                                vertical_scale)
37//!
38//! After the uncompressed-data-chunk, the remaining bytes are the
39//! arithmetic-coded compressed bitstream.
40
41use crate::BitstreamError;
42
43/// VP8 keyframe start code (RFC 6386 §9.1).
44pub const VP8_KEYFRAME_START_CODE: [u8; 3] = [0x9D, 0x01, 0x2A];
45
46/// Output of the VP8 uncompressed-header parsers.
47#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
48pub struct Vp8FrameHeader {
49    /// 0 = keyframe (RFC 6386 calls this KEY_FRAME), 1 = interframe.
50    pub frame_type: u8,
51    /// Bitstream version (0..3). RFC 6386 §9.1 — only 0 is in
52    /// widespread use; libvpx may emit 1, 2 or 3 to select the
53    /// loop-filter / motion-comp tweaks.
54    pub version: u8,
55    /// `show_frame` bit. False here means the encoder emitted an
56    /// alternate-reference frame that should not be rendered.
57    pub show_frame: bool,
58    /// `first_part_size` — length in bytes of the compressed
59    /// "first partition" (the partition that holds the bool-coded
60    /// frame header + per-MB modes, before the residual partitions).
61    pub first_part_size: u32,
62    /// Coded width in luma samples (keyframes only — 0 on
63    /// interframes since RFC 6386 only re-transmits dimensions for
64    /// keyframes).
65    pub width: u32,
66    /// Coded height in luma samples (keyframes only).
67    pub height: u32,
68    /// `horizontal_scale` — 0 means no horizontal upscale at display.
69    pub horizontal_scale: u8,
70    /// `vertical_scale` — 0 means no vertical upscale at display.
71    pub vertical_scale: u8,
72}
73
74/// Parse the uncompressed parts of a VP8 frame header (keyframe or
75/// interframe).
76pub fn parse_frame_header(stream: &[u8]) -> Result<Vp8FrameHeader, BitstreamError> {
77    if stream.len() < 3 {
78        return Err(BitstreamError::unexpected_end(
79            "VP8 frame shorter than 3 bytes",
80        ));
81    }
82    // The 3-byte tag is interpreted as a 24-bit little-endian word
83    // and the fields are extracted LSB-first per RFC 6386 §9.1.
84    let tag = (stream[0] as u32) | ((stream[1] as u32) << 8) | ((stream[2] as u32) << 16);
85    let frame_type = (tag & 0x1) as u8;
86    let version = ((tag >> 1) & 0x7) as u8;
87    let show_frame = ((tag >> 4) & 0x1) != 0;
88    let first_part_size = (tag >> 5) & 0x7_FFFF;
89
90    let mut hdr = Vp8FrameHeader {
91        frame_type,
92        version,
93        show_frame,
94        first_part_size,
95        ..Vp8FrameHeader::default()
96    };
97
98    if frame_type == 0 {
99        // Keyframe: 3-byte start code + 4-byte size word.
100        if stream.len() < 10 {
101            return Err(BitstreamError::unexpected_end(
102                "VP8 keyframe shorter than 10-byte uncompressed-data-chunk",
103            ));
104        }
105        if stream[3..6] != VP8_KEYFRAME_START_CODE {
106            return Err(BitstreamError::invalid(format!(
107                "VP8 keyframe start-code mismatch: {:02x?} != 9d 01 2a",
108                &stream[3..6]
109            )));
110        }
111        let w_word = u16::from_le_bytes([stream[6], stream[7]]) as u32;
112        let h_word = u16::from_le_bytes([stream[8], stream[9]]) as u32;
113        hdr.width = w_word & 0x3FFF;
114        hdr.horizontal_scale = ((w_word >> 14) & 0x3) as u8;
115        hdr.height = h_word & 0x3FFF;
116        hdr.vertical_scale = ((h_word >> 14) & 0x3) as u8;
117    }
118
119    Ok(hdr)
120}
121
122/// Parse a keyframe header. Errors out if `frame_type != 0` (not a
123/// keyframe) or any required byte is missing.
124pub fn parse_keyframe(stream: &[u8]) -> Result<Vp8FrameHeader, BitstreamError> {
125    let hdr = parse_frame_header(stream)?;
126    if hdr.frame_type != 0 {
127        return Err(BitstreamError::invalid(format!(
128            "expected VP8 keyframe (frame_type=0), got frame_type={}",
129            hdr.frame_type
130        )));
131    }
132    Ok(hdr)
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn parse_frame_header_decodes_synthetic_keyframe() {
141        // Build a synthetic 3-byte tag: frame_type=0, version=0,
142        // show_frame=1, first_part_size=42.
143        let first_part_size: u32 = 42;
144        let tag: u32 = (1 << 4) | (first_part_size << 5);
145        let mut stream = vec![
146            (tag & 0xff) as u8,
147            ((tag >> 8) & 0xff) as u8,
148            ((tag >> 16) & 0xff) as u8,
149        ];
150        // Keyframe extension: start code + width=8, h_scale=0, height=8, v_scale=0.
151        stream.extend_from_slice(&VP8_KEYFRAME_START_CODE);
152        stream.extend_from_slice(&8u16.to_le_bytes());
153        stream.extend_from_slice(&8u16.to_le_bytes());
154
155        let h = parse_frame_header(&stream).unwrap();
156        assert_eq!(h.frame_type, 0);
157        assert_eq!(h.version, 0);
158        assert!(h.show_frame);
159        assert_eq!(h.first_part_size, 42);
160        assert_eq!(h.width, 8);
161        assert_eq!(h.height, 8);
162        assert_eq!(h.horizontal_scale, 0);
163        assert_eq!(h.vertical_scale, 0);
164    }
165
166    #[test]
167    fn parse_keyframe_rejects_interframe_tag() {
168        // frame_type bit set → interframe.
169        let stream = [0x01u8, 0x00, 0x00];
170        assert!(matches!(
171            parse_keyframe(&stream),
172            Err(BitstreamError::InvalidData(_))
173        ));
174    }
175
176    #[test]
177    fn parse_keyframe_rejects_bad_start_code() {
178        let mut stream = vec![0x10u8, 0x00, 0x00]; // tag: keyframe
179        stream.extend_from_slice(&[0xAA, 0xBB, 0xCC]); // bad start code
180        stream.extend_from_slice(&8u16.to_le_bytes());
181        stream.extend_from_slice(&8u16.to_le_bytes());
182        assert!(matches!(
183            parse_frame_header(&stream),
184            Err(BitstreamError::InvalidData(_))
185        ));
186    }
187
188    #[test]
189    fn parse_frame_header_rejects_short_input() {
190        let stream = [0u8, 0u8];
191        assert!(matches!(
192            parse_frame_header(&stream),
193            Err(BitstreamError::UnexpectedEnd(_))
194        ));
195    }
196}