mod import;
pub use import::*;
use hang::catalog::VP9;
const SYNC_CODE: u32 = 0x49_8342;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct FrameHeader {
pub keyframe: bool,
pub key: Option<KeyFrame>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct KeyFrame {
pub width: u16,
pub height: u16,
pub profile: u8,
pub bit_depth: u8,
pub chroma_subsampling: u8,
pub matrix_coefficients: u8,
pub full_range: bool,
}
impl FrameHeader {
pub fn parse(data: &[u8]) -> anyhow::Result<Self> {
let mut r = BitReader::new(data);
anyhow::ensure!(r.read(2)? == 0b10, "invalid VP9 frame marker");
let profile_low = r.read(1)?;
let profile_high = r.read(1)?;
let profile = ((profile_high << 1) | profile_low) as u8;
if profile == 3 {
r.skip(1)?; }
if r.read(1)? == 1 {
r.skip(3)?; return Ok(Self {
keyframe: false,
key: None,
});
}
let keyframe = r.read(1)? == 0; r.skip(2)?;
if !keyframe {
return Ok(Self {
keyframe: false,
key: None,
});
}
anyhow::ensure!(r.read(24)? == SYNC_CODE, "invalid VP9 sync code");
let bit_depth = if profile >= 2 {
if r.read(1)? == 1 { 12 } else { 10 }
} else {
8
};
let color_space = r.read(3)? as u8;
const CS_RGB: u8 = 7;
let (subsampling_x, subsampling_y, full_range);
if color_space != CS_RGB {
full_range = r.read(1)? == 1;
if profile == 1 || profile == 3 {
subsampling_x = r.read(1)? == 1;
subsampling_y = r.read(1)? == 1;
r.skip(1)?; } else {
(subsampling_x, subsampling_y) = (true, true);
}
} else {
full_range = true;
(subsampling_x, subsampling_y) = (false, false);
if profile == 1 || profile == 3 {
r.skip(1)?; }
}
let width = (r.read(16)? + 1) as u16;
let height = (r.read(16)? + 1) as u16;
Ok(Self {
keyframe: true,
key: Some(KeyFrame {
width,
height,
profile,
bit_depth,
chroma_subsampling: chroma_subsampling(subsampling_x, subsampling_y),
matrix_coefficients: matrix_coefficients(color_space),
full_range,
}),
})
}
}
impl KeyFrame {
pub fn to_catalog(self) -> VP9 {
VP9 {
profile: self.profile,
level: level_for(self.width, self.height),
bit_depth: self.bit_depth,
chroma_subsampling: self.chroma_subsampling,
color_primaries: 2,
transfer_characteristics: 2,
matrix_coefficients: self.matrix_coefficients,
full_range: self.full_range,
}
}
}
fn chroma_subsampling(x: bool, y: bool) -> u8 {
match (x, y) {
(true, true) => 1, (true, false) => 2, (false, false) => 3, (false, true) => 0, }
}
fn matrix_coefficients(color_space: u8) -> u8 {
match color_space {
1 => 5, 2 => 1, 3 => 6, 4 => 7, 5 => 9, 7 => 0, _ => 2, }
}
fn level_for(width: u16, height: u16) -> u8 {
let area = width as u64 * height as u64;
const LEVELS: &[(u8, u64)] = &[
(10, 36_864),
(11, 73_728),
(20, 122_880),
(21, 245_760),
(30, 552_960),
(31, 983_040),
(40, 2_228_224),
(50, 8_912_896),
(60, 35_651_584),
(62, 70_254_592),
];
LEVELS
.iter()
.find(|(_, max)| area <= *max)
.map(|(level, _)| *level)
.unwrap_or(62)
}
pub(crate) fn vpcc(vp9: &VP9) -> mp4_atom::VpcC {
mp4_atom::VpcC {
profile: vp9.profile,
level: vp9.level,
bit_depth: vp9.bit_depth,
chroma_subsampling: vp9.chroma_subsampling,
video_full_range_flag: vp9.full_range,
color_primaries: vp9.color_primaries,
transfer_characteristics: vp9.transfer_characteristics,
matrix_coefficients: vp9.matrix_coefficients,
codec_initialization_data: Vec::new(),
}
}
struct BitReader<'a> {
data: &'a [u8],
bit: usize,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, bit: 0 }
}
fn read(&mut self, n: u32) -> anyhow::Result<u32> {
let mut value = 0;
for _ in 0..n {
let byte = self.bit / 8;
anyhow::ensure!(byte < self.data.len(), "VP9 header truncated");
let shift = 7 - (self.bit % 8);
value = (value << 1) | u32::from((self.data[byte] >> shift) & 1);
self.bit += 1;
}
Ok(value)
}
fn skip(&mut self, n: u32) -> anyhow::Result<()> {
self.read(n).map(|_| ())
}
}
#[cfg(test)]
mod tests {
use super::*;
const KEYFRAME_320X240: &[u8] = &[0x82, 0x49, 0x83, 0x42, 0x20, 0x13, 0xf0, 0x0e, 0xf0, 0x00];
#[test]
fn parses_keyframe() {
let header = FrameHeader::parse(KEYFRAME_320X240).expect("parse key frame");
assert!(header.keyframe);
let key = header.key.expect("key frame fields");
assert_eq!((key.width, key.height), (320, 240));
assert_eq!(key.profile, 0);
assert_eq!(key.bit_depth, 8);
assert_eq!(key.chroma_subsampling, 1); assert_eq!(key.matrix_coefficients, 5); assert!(!key.full_range);
}
#[test]
fn keyframe_to_catalog() {
let key = FrameHeader::parse(KEYFRAME_320X240).unwrap().key.unwrap();
let vp9 = key.to_catalog();
assert_eq!(vp9.profile, 0);
assert_eq!(vp9.bit_depth, 8);
assert_eq!(vp9.level, 20); assert_eq!(vp9.color_primaries, 2); }
#[test]
fn parses_interframe() {
let header = FrameHeader::parse(&[0x84, 0x00, 0x00]).expect("parse interframe");
assert!(!header.keyframe);
assert!(header.key.is_none());
}
#[test]
fn parses_show_existing() {
let header = FrameHeader::parse(&[0x88]).expect("parse show_existing");
assert!(!header.keyframe);
assert!(header.key.is_none());
}
#[test]
fn rejects_bad_sync() {
let mut frame = KEYFRAME_320X240.to_vec();
frame[1] = 0x00; assert!(FrameHeader::parse(&frame).is_err());
}
#[test]
fn vpcc_round_trips_catalog() {
let vp9 = VP9 {
profile: 2,
level: 31,
bit_depth: 10,
chroma_subsampling: 1,
color_primaries: 9,
transfer_characteristics: 16,
matrix_coefficients: 9,
full_range: true,
};
let vpcc = vpcc(&vp9);
assert_eq!(vpcc.profile, 2);
assert_eq!(vpcc.bit_depth, 10);
assert_eq!(vpcc.matrix_coefficients, 9);
assert!(vpcc.video_full_range_flag);
}
}