use crate::BitstreamError;
pub const VP8_KEYFRAME_START_CODE: [u8; 3] = [0x9D, 0x01, 0x2A];
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Vp8FrameHeader {
pub frame_type: u8,
pub version: u8,
pub show_frame: bool,
pub first_part_size: u32,
pub width: u32,
pub height: u32,
pub horizontal_scale: u8,
pub vertical_scale: u8,
}
pub fn parse_frame_header(stream: &[u8]) -> Result<Vp8FrameHeader, BitstreamError> {
if stream.len() < 3 {
return Err(BitstreamError::unexpected_end(
"VP8 frame shorter than 3 bytes",
));
}
let tag = (stream[0] as u32) | ((stream[1] as u32) << 8) | ((stream[2] as u32) << 16);
let frame_type = (tag & 0x1) as u8;
let version = ((tag >> 1) & 0x7) as u8;
let show_frame = ((tag >> 4) & 0x1) != 0;
let first_part_size = (tag >> 5) & 0x7_FFFF;
let mut hdr = Vp8FrameHeader {
frame_type,
version,
show_frame,
first_part_size,
..Vp8FrameHeader::default()
};
if frame_type == 0 {
if stream.len() < 10 {
return Err(BitstreamError::unexpected_end(
"VP8 keyframe shorter than 10-byte uncompressed-data-chunk",
));
}
if stream[3..6] != VP8_KEYFRAME_START_CODE {
return Err(BitstreamError::invalid(format!(
"VP8 keyframe start-code mismatch: {:02x?} != 9d 01 2a",
&stream[3..6]
)));
}
let w_word = u16::from_le_bytes([stream[6], stream[7]]) as u32;
let h_word = u16::from_le_bytes([stream[8], stream[9]]) as u32;
hdr.width = w_word & 0x3FFF;
hdr.horizontal_scale = ((w_word >> 14) & 0x3) as u8;
hdr.height = h_word & 0x3FFF;
hdr.vertical_scale = ((h_word >> 14) & 0x3) as u8;
}
Ok(hdr)
}
pub fn parse_keyframe(stream: &[u8]) -> Result<Vp8FrameHeader, BitstreamError> {
let hdr = parse_frame_header(stream)?;
if hdr.frame_type != 0 {
return Err(BitstreamError::invalid(format!(
"expected VP8 keyframe (frame_type=0), got frame_type={}",
hdr.frame_type
)));
}
Ok(hdr)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_frame_header_decodes_synthetic_keyframe() {
let first_part_size: u32 = 42;
let tag: u32 = (1 << 4) | (first_part_size << 5);
let mut stream = vec![
(tag & 0xff) as u8,
((tag >> 8) & 0xff) as u8,
((tag >> 16) & 0xff) as u8,
];
stream.extend_from_slice(&VP8_KEYFRAME_START_CODE);
stream.extend_from_slice(&8u16.to_le_bytes());
stream.extend_from_slice(&8u16.to_le_bytes());
let h = parse_frame_header(&stream).unwrap();
assert_eq!(h.frame_type, 0);
assert_eq!(h.version, 0);
assert!(h.show_frame);
assert_eq!(h.first_part_size, 42);
assert_eq!(h.width, 8);
assert_eq!(h.height, 8);
assert_eq!(h.horizontal_scale, 0);
assert_eq!(h.vertical_scale, 0);
}
#[test]
fn parse_keyframe_rejects_interframe_tag() {
let stream = [0x01u8, 0x00, 0x00];
assert!(matches!(
parse_keyframe(&stream),
Err(BitstreamError::InvalidData(_))
));
}
#[test]
fn parse_keyframe_rejects_bad_start_code() {
let mut stream = vec![0x10u8, 0x00, 0x00]; stream.extend_from_slice(&[0xAA, 0xBB, 0xCC]); stream.extend_from_slice(&8u16.to_le_bytes());
stream.extend_from_slice(&8u16.to_le_bytes());
assert!(matches!(
parse_frame_header(&stream),
Err(BitstreamError::InvalidData(_))
));
}
#[test]
fn parse_frame_header_rejects_short_input() {
let stream = [0u8, 0u8];
assert!(matches!(
parse_frame_header(&stream),
Err(BitstreamError::UnexpectedEnd(_))
));
}
}