1mod import;
12
13pub use import::*;
14
15use hang::catalog::VP9;
16
17#[derive(Debug, Clone, thiserror::Error)]
19#[non_exhaustive]
20pub enum Error {
21 #[error("invalid VP9 frame marker")]
22 InvalidFrameMarker,
23
24 #[error("invalid VP9 sync code")]
25 InvalidSyncCode,
26
27 #[error("VP9 header truncated")]
28 Truncated,
29
30 #[error("empty VP9 frame")]
31 EmptyFrame,
32}
33
34pub type Result<T> = std::result::Result<T, Error>;
36
37const SYNC_CODE: u32 = 0x49_8342;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub(crate) struct FrameHeader {
43 pub keyframe: bool,
45 pub key: Option<KeyFrame>,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub(crate) struct KeyFrame {
52 pub width: u16,
53 pub height: u16,
54 pub profile: u8,
55 pub bit_depth: u8,
56 pub chroma_subsampling: u8,
59 pub matrix_coefficients: u8,
61 pub full_range: bool,
62}
63
64impl FrameHeader {
65 pub fn parse(data: &[u8]) -> Result<Self> {
70 let mut r = BitReader::new(data);
71
72 if r.read(2)? != 0b10 {
73 return Err(Error::InvalidFrameMarker);
74 }
75
76 let profile_low = r.read(1)?;
77 let profile_high = r.read(1)?;
78 let profile = ((profile_high << 1) | profile_low) as u8;
79 if profile == 3 {
80 r.skip(1)?; }
82
83 if r.read(1)? == 1 {
85 r.skip(3)?; return Ok(Self {
87 keyframe: false,
88 key: None,
89 });
90 }
91
92 let keyframe = r.read(1)? == 0; r.skip(2)?; if !keyframe {
96 return Ok(Self {
97 keyframe: false,
98 key: None,
99 });
100 }
101
102 if r.read(24)? != SYNC_CODE {
103 return Err(Error::InvalidSyncCode);
104 }
105
106 let bit_depth = if profile >= 2 {
108 if r.read(1)? == 1 { 12 } else { 10 }
109 } else {
110 8
111 };
112 let color_space = r.read(3)? as u8;
113
114 const CS_RGB: u8 = 7;
115 let (subsampling_x, subsampling_y, full_range);
116 if color_space != CS_RGB {
117 full_range = r.read(1)? == 1;
118 if profile == 1 || profile == 3 {
119 subsampling_x = r.read(1)? == 1;
120 subsampling_y = r.read(1)? == 1;
121 r.skip(1)?; } else {
123 (subsampling_x, subsampling_y) = (true, true);
125 }
126 } else {
127 full_range = true;
129 (subsampling_x, subsampling_y) = (false, false);
130 if profile == 1 || profile == 3 {
131 r.skip(1)?; }
133 }
134
135 let width = (r.read(16)? + 1) as u16;
137 let height = (r.read(16)? + 1) as u16;
138
139 Ok(Self {
140 keyframe: true,
141 key: Some(KeyFrame {
142 width,
143 height,
144 profile,
145 bit_depth,
146 chroma_subsampling: chroma_subsampling(subsampling_x, subsampling_y),
147 matrix_coefficients: matrix_coefficients(color_space),
148 full_range,
149 }),
150 })
151 }
152}
153
154impl KeyFrame {
155 pub fn to_catalog(self) -> VP9 {
162 VP9 {
163 profile: self.profile,
164 level: level_for(self.width, self.height),
165 bit_depth: self.bit_depth,
166 chroma_subsampling: self.chroma_subsampling,
167 color_primaries: 2,
168 transfer_characteristics: 2,
169 matrix_coefficients: self.matrix_coefficients,
170 full_range: self.full_range,
171 }
172 }
173}
174
175pub(crate) fn config_from_keyframe(data: &[u8]) -> Result<Option<hang::catalog::VideoConfig>> {
183 let Some(key) = FrameHeader::parse(data)?.key else {
184 return Ok(None);
185 };
186 let (width, height) = (key.width, key.height);
187 let mut config = hang::catalog::VideoConfig::new(key.to_catalog());
188 config.coded_width = Some(width as u32);
189 config.coded_height = Some(height as u32);
190 config.container = hang::catalog::Container::Legacy;
191 Ok(Some(config))
192}
193
194fn chroma_subsampling(x: bool, y: bool) -> u8 {
198 match (x, y) {
199 (true, true) => 1, (true, false) => 2, (false, false) => 3, (false, true) => 0, }
204}
205
206fn matrix_coefficients(color_space: u8) -> u8 {
209 match color_space {
210 1 => 5, 2 => 1, 3 => 6, 4 => 7, 5 => 9, 7 => 0, _ => 2, }
218}
219
220fn level_for(width: u16, height: u16) -> u8 {
224 let area = width as u64 * height as u64;
225 const LEVELS: &[(u8, u64)] = &[
227 (10, 36_864),
228 (11, 73_728),
229 (20, 122_880),
230 (21, 245_760),
231 (30, 552_960),
232 (31, 983_040),
233 (40, 2_228_224),
234 (50, 8_912_896),
235 (60, 35_651_584),
236 (62, 70_254_592),
237 ];
238 LEVELS
239 .iter()
240 .find(|(_, max)| area <= *max)
241 .map(|(level, _)| *level)
242 .unwrap_or(62)
243}
244
245pub(crate) fn vpcc(vp9: &VP9) -> mp4_atom::VpcC {
249 mp4_atom::VpcC {
250 profile: vp9.profile,
251 level: vp9.level,
252 bit_depth: vp9.bit_depth,
253 chroma_subsampling: vp9.chroma_subsampling,
254 video_full_range_flag: vp9.full_range,
255 color_primaries: vp9.color_primaries,
256 transfer_characteristics: vp9.transfer_characteristics,
257 matrix_coefficients: vp9.matrix_coefficients,
258 codec_initialization_data: Vec::new(),
259 }
260}
261
262struct BitReader<'a> {
264 data: &'a [u8],
265 bit: usize,
266}
267
268impl<'a> BitReader<'a> {
269 fn new(data: &'a [u8]) -> Self {
270 Self { data, bit: 0 }
271 }
272
273 fn read(&mut self, n: u32) -> Result<u32> {
274 let mut value = 0;
275 for _ in 0..n {
276 let byte = self.bit / 8;
277 if byte >= self.data.len() {
278 return Err(Error::Truncated);
279 }
280 let shift = 7 - (self.bit % 8);
281 value = (value << 1) | u32::from((self.data[byte] >> shift) & 1);
282 self.bit += 1;
283 }
284 Ok(value)
285 }
286
287 fn skip(&mut self, n: u32) -> Result<()> {
288 self.read(n).map(|_| ())
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 const KEYFRAME_320X240: &[u8] = &[0x82, 0x49, 0x83, 0x42, 0x20, 0x13, 0xf0, 0x0e, 0xf0, 0x00];
299
300 #[test]
301 fn parses_keyframe() {
302 let header = FrameHeader::parse(KEYFRAME_320X240).expect("parse key frame");
303 assert!(header.keyframe);
304 let key = header.key.expect("key frame fields");
305 assert_eq!((key.width, key.height), (320, 240));
306 assert_eq!(key.profile, 0);
307 assert_eq!(key.bit_depth, 8);
308 assert_eq!(key.chroma_subsampling, 1); assert_eq!(key.matrix_coefficients, 5); assert!(!key.full_range);
311 }
312
313 #[test]
314 fn keyframe_to_catalog() {
315 let key = FrameHeader::parse(KEYFRAME_320X240).unwrap().key.unwrap();
316 let vp9 = key.to_catalog();
317 assert_eq!(vp9.profile, 0);
318 assert_eq!(vp9.bit_depth, 8);
319 assert_eq!(vp9.level, 20); assert_eq!(vp9.color_primaries, 2); }
322
323 #[test]
324 fn parses_interframe() {
325 let header = FrameHeader::parse(&[0x84, 0x00, 0x00]).expect("parse interframe");
328 assert!(!header.keyframe);
329 assert!(header.key.is_none());
330 }
331
332 #[test]
333 fn parses_show_existing() {
334 let header = FrameHeader::parse(&[0x88]).expect("parse show_existing");
336 assert!(!header.keyframe);
337 assert!(header.key.is_none());
338 }
339
340 #[test]
341 fn rejects_bad_sync() {
342 let mut frame = KEYFRAME_320X240.to_vec();
343 frame[1] = 0x00; assert!(FrameHeader::parse(&frame).is_err());
345 }
346
347 #[test]
348 fn vpcc_round_trips_catalog() {
349 let vp9 = VP9 {
350 profile: 2,
351 level: 31,
352 bit_depth: 10,
353 chroma_subsampling: 1,
354 color_primaries: 9,
355 transfer_characteristics: 16,
356 matrix_coefficients: 9,
357 full_range: true,
358 };
359 let vpcc = vpcc(&vp9);
360 assert_eq!(vpcc.profile, 2);
361 assert_eq!(vpcc.bit_depth, 10);
362 assert_eq!(vpcc.matrix_coefficients, 9);
363 assert!(vpcc.video_full_range_flag);
364 }
365}