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}