use crate::{ChromaSubsamplingModes, GopStartDetection, VideoEncodingDetails};
pub fn detect_vp9_gop(data: &[u8]) -> GopStartDetection {
let Some(header) = Vp9KeyframeHeader::parse(data) else {
return GopStartDetection::NotStartOfGop;
};
let codec_string = format!("vp09.{:02}.10.{:02}", header.profile, header.bit_depth);
let chroma_subsampling = match (header.subsampling_x, header.subsampling_y) {
(true, true) => Some(ChromaSubsamplingModes::Yuv420),
(true, false) => Some(ChromaSubsamplingModes::Yuv422),
(false, true) => None, (false, false) => Some(ChromaSubsamplingModes::Yuv444),
};
GopStartDetection::StartOfGop(VideoEncodingDetails {
codec_string,
coded_dimensions: [header.width, header.height],
bit_depth: Some(header.bit_depth),
chroma_subsampling,
stsd: None,
})
}
struct Vp9KeyframeHeader {
profile: u8,
bit_depth: u8,
subsampling_x: bool,
subsampling_y: bool,
width: u16,
height: u16,
}
impl Vp9KeyframeHeader {
fn parse(data: &[u8]) -> Option<Self> {
let mut reader = BitReader::new(data);
let frame_marker = reader.read_bits(2)?;
if frame_marker != 0b10 {
return None;
}
let profile_low_bit = reader.read_bits(1)?;
let profile_high_bit = reader.read_bits(1)?;
let profile = ((profile_high_bit << 1) | profile_low_bit) as u8;
if profile == 3 {
reader.read_bits(1)?;
}
let show_existing_frame = reader.read_bits(1)? != 0;
if show_existing_frame {
return None;
}
let frame_type = reader.read_bits(1)?;
if frame_type != 0 {
return None;
}
reader.read_bits(2)?;
let sync_code = reader.read_bits(24)?;
if sync_code != 0x498342 {
return None;
}
let bit_depth = if matches!(profile, 2 | 3) {
if reader.read_bits(1)? != 0 { 12 } else { 10 }
} else {
8
};
let color_space = reader.read_bits(3)?;
let (subsampling_x, subsampling_y) = if color_space != 7 {
reader.read_bits(1)?;
if matches!(profile, 1 | 3) {
let subsampling_x = reader.read_bits(1)? != 0;
let subsampling_y = reader.read_bits(1)? != 0;
reader.read_bits(1)?;
(subsampling_x, subsampling_y)
} else {
(true, true)
}
} else if matches!(profile, 1 | 3) {
let subsampling_x = false;
let subsampling_y = false;
reader.read_bits(1)?;
(subsampling_x, subsampling_y)
} else {
(false, false)
};
let width = u16::try_from(reader.read_bits(16)? + 1).ok()?;
let height = u16::try_from(reader.read_bits(16)? + 1).ok()?;
Some(Self {
profile,
bit_depth,
subsampling_x,
subsampling_y,
width,
height,
})
}
}
struct BitReader<'a> {
data: &'a [u8],
bit_pos: usize,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, bit_pos: 0 }
}
fn read_bits(&mut self, num_bits: usize) -> Option<u32> {
let mut out = 0;
for _ in 0..num_bits {
let byte = *self.data.get(self.bit_pos / 8)?;
let bit = (byte >> (7 - self.bit_pos % 8)) & 1;
out = (out << 1) | u32::from(bit);
self.bit_pos += 1;
}
Some(out)
}
}
pub fn vp9_is_keyframe(data: &[u8]) -> bool {
if data.is_empty() {
return false;
}
let mut reader = BitReader::new(data);
let Some(frame_marker) = reader.read_bits(2) else {
return false;
};
if frame_marker != 0b10 {
return false;
}
let profile_low_bit = reader.read_bits(1).unwrap_or(0);
let profile_high_bit = reader.read_bits(1).unwrap_or(0);
let profile = (profile_high_bit << 1) | profile_low_bit;
if profile == 3 {
reader.read_bits(1); }
let Some(show_existing_frame) = reader.read_bits(1) else {
return false;
};
if show_existing_frame != 0 {
return false;
}
let Some(frame_type) = reader.read_bits(1) else {
return false;
};
frame_type == 0
}