use crate::assert_invariant;
#[derive(Clone, Debug, PartialEq)]
pub struct Vp9Config {
pub width: u32,
pub height: u32,
pub profile: u8,
pub bit_depth: u8,
pub color_space: u8,
pub transfer_function: u8,
pub matrix_coefficients: u8,
pub level: u8,
pub full_range_flag: u8,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Vp9Error {
FrameTooShort,
InvalidFrameMarker,
UnsupportedProfile(u8),
InvalidBitDepth(u8),
ParseError(String),
}
impl std::fmt::Display for Vp9Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Vp9Error::FrameTooShort => write!(f, "VP9 frame too short for header"),
Vp9Error::InvalidFrameMarker => write!(f, "invalid VP9 frame marker"),
Vp9Error::UnsupportedProfile(p) => write!(f, "unsupported VP9 profile: {}", p),
Vp9Error::InvalidBitDepth(b) => write!(f, "invalid VP9 bit depth: {}", b),
Vp9Error::ParseError(msg) => write!(f, "VP9 parse error: {}", msg),
}
}
}
impl std::error::Error for Vp9Error {}
pub fn is_vp9_keyframe(frame: &[u8]) -> Result<bool, Vp9Error> {
if frame.len() < 3 {
return Err(Vp9Error::FrameTooShort);
}
if frame[0] != 0x49 || frame[1] != 0x83 || frame[2] != 0x42 {
return Err(Vp9Error::InvalidFrameMarker);
}
if frame.len() < 4 {
return Err(Vp9Error::FrameTooShort);
}
let profile = (frame[3] >> 6) & 0x03;
let show_existing_frame = (frame[3] >> 5) & 0x01;
let frame_type = (frame[3] >> 4) & 0x01;
assert_invariant!(
profile <= 3,
"VP9 profile must be valid (0-3)",
"codec::vp9::is_vp9_keyframe"
);
if show_existing_frame != 0 {
return Ok(false);
}
Ok(frame_type == 0)
}
pub fn extract_vp9_config(keyframe: &[u8]) -> Option<Vp9Config> {
if keyframe.len() < 3 {
return None;
}
if keyframe[0] != 0x49 || keyframe[1] != 0x83 || keyframe[2] != 0x42 {
return None;
}
assert_invariant!(
keyframe[0] == 0x49 && keyframe[1] == 0x83 && keyframe[2] == 0x42,
"INV-401: VP9 frame marker must be 0x49 0x83 0x42",
"codec::vp9::extract_vp9_config"
);
if keyframe.len() < 6 {
return None;
}
let profile = (keyframe[3] >> 6) & 0x03;
let show_existing_frame = (keyframe[3] >> 5) & 0x01;
let frame_type = (keyframe[3] >> 4) & 0x01;
assert_invariant!(
profile <= 3,
"INV-402: VP9 profile must be valid (0-3)",
"codec::vp9::extract_vp9_config"
);
if show_existing_frame != 0 || frame_type != 0 {
return None; }
let mut offset = 5;
if profile >= 2 {
if offset + 1 >= keyframe.len() {
return None;
}
offset += 1; }
let (width, new_offset) = parse_vp9_var_uint(keyframe, offset)?;
offset = new_offset;
let (height, new_offset) = parse_vp9_var_uint(keyframe, offset)?;
offset = new_offset;
let (render_width, render_height) = if offset + 1 < keyframe.len() {
let render_and_frame_size_different = (keyframe[offset] & 0x0C) != 0;
if render_and_frame_size_different {
offset += 1;
let (rw, no) = parse_vp9_var_uint(keyframe, offset)?;
offset = no;
let (rh, no) = parse_vp9_var_uint(keyframe, offset)?;
offset = no;
(rw, rh)
} else {
(width, height)
}
} else {
(width, height)
};
let (bit_depth, color_space, transfer_function, matrix_coefficients, full_range_flag) =
parse_vp9_color_config(keyframe, offset)?;
Some(Vp9Config {
width: render_width,
height: render_height,
profile,
bit_depth,
color_space,
transfer_function,
matrix_coefficients,
level: 0, full_range_flag,
})
}
fn parse_vp9_var_uint(data: &[u8], mut offset: usize) -> Option<(u32, usize)> {
let mut value = 0u32;
let mut shift = 0;
loop {
if offset >= data.len() {
return None;
}
let byte = data[offset];
offset += 1;
value |= ((byte & 0x7F) as u32) << shift;
shift += 7;
if (byte & 0x80) == 0 {
break;
}
if shift >= 32 {
return None; }
}
Some((value, offset))
}
fn parse_vp9_color_config(data: &[u8], mut offset: usize) -> Option<(u8, u8, u8, u8, u8)> {
if offset >= data.len() {
return Some((8, 0, 0, 0, 0)); }
let bit_depth = if (data[offset] & 0x01) != 0 {
10
} else {
8
};
let color_space = (data[offset] >> 1) & 0x07;
let transfer_function = (data[offset] >> 4) & 0x07;
let matrix_coefficients = (data[offset] >> 7) & 0x01;
offset += 1;
let full_range_flag = if color_space != 0 {
if offset >= data.len() {
0 } else {
data[offset] & 0x01
}
} else {
0 };
Some((bit_depth, color_space, transfer_function, matrix_coefficients, full_range_flag))
}
pub fn is_valid_vp9_frame(frame: &[u8]) -> bool {
if frame.len() < 3 {
return false;
}
frame[0] == 0x49 && frame[1] == 0x83 && frame[2] == 0x42
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_frame_marker() {
let invalid_frame = [0x00, 0x00, 0x00];
assert!(!is_valid_vp9_frame(&invalid_frame));
assert!(matches!(
is_vp9_keyframe(&invalid_frame),
Err(Vp9Error::InvalidFrameMarker)
));
}
#[test]
fn test_frame_too_short() {
let short_frame = [0x49, 0x83];
assert!(!is_valid_vp9_frame(&short_frame));
assert!(matches!(
is_vp9_keyframe(&short_frame),
Err(Vp9Error::FrameTooShort)
));
}
#[test]
fn test_valid_frame_marker() {
let valid_frame = [0x49, 0x83, 0x42, 0x00, 0x00, 0x00];
assert!(is_valid_vp9_frame(&valid_frame));
}
#[test]
fn test_is_vp9_keyframe_valid() {
let keyframe = [0x49, 0x83, 0x42, 0x00, 0x00, 0x00];
assert_eq!(is_vp9_keyframe(&keyframe), Ok(true));
}
#[test]
fn test_is_vp9_keyframe_pframe() {
let pframe = [0x49, 0x83, 0x42, 0x10, 0x00, 0x00];
assert_eq!(is_vp9_keyframe(&pframe), Ok(false));
}
#[test]
fn test_is_vp9_keyframe_show_existing() {
let show_existing = [0x49, 0x83, 0x42, 0x20, 0x00, 0x00];
assert_eq!(is_vp9_keyframe(&show_existing), Ok(false));
}
#[test]
fn test_extract_vp9_config_valid() {
let keyframe = vec![
0x49, 0x83, 0x42, 0x00, 0x00, 0x80, 0x02, 0x80, 0x02, 0x00, 0x00, ];
let config = extract_vp9_config(&keyframe);
assert!(config.is_some());
let config = config.unwrap();
assert_eq!(config.width, 256);
assert_eq!(config.height, 256);
assert_eq!(config.profile, 0);
assert_eq!(config.bit_depth, 8);
assert_eq!(config.level, 0);
assert_eq!(config.full_range_flag, 0);
}
#[test]
fn test_extract_vp9_config_invalid_marker() {
let invalid_frame = [0x00, 0x00, 0x00, 0x00];
assert!(extract_vp9_config(&invalid_frame).is_none());
}
#[test]
fn test_extract_vp9_config_pframe() {
let pframe = [0x49, 0x83, 0x42, 0x10, 0x00, 0x00];
assert!(extract_vp9_config(&pframe).is_none());
}
#[test]
fn test_parse_vp9_var_uint() {
let data = [0x7F, 0x80, 0x01, 0x80, 0x80, 0x01];
assert_eq!(parse_vp9_var_uint(&data, 0), Some((127, 1)));
assert_eq!(parse_vp9_var_uint(&data, 1), Some((128, 3)));
assert_eq!(parse_vp9_var_uint(&data, 3), Some((16384, 6)));
}
#[test]
fn test_parse_vp9_var_uint_overflow() {
let data = [0x80, 0x80, 0x80, 0x80, 0x80, 0x80]; assert_eq!(parse_vp9_var_uint(&data, 0), None);
}
#[test]
fn test_parse_vp9_color_config() {
let data = [0x00]; assert_eq!(parse_vp9_color_config(&data, 0), Some((8, 0, 0, 0, 0)));
let data_10bit = [0x01]; assert_eq!(parse_vp9_color_config(&data_10bit, 0), Some((10, 0, 0, 0, 0)));
let data_color = [0x12]; assert_eq!(parse_vp9_color_config(&data_color, 0), Some((8, 1, 1, 0, 0)));
}
#[test]
fn test_parse_vp9_color_config_empty() {
let data = [];
assert_eq!(parse_vp9_color_config(&data, 0), Some((8, 0, 0, 0, 0)));
}
}