1mod import;
11
12pub use import::*;
13
14#[derive(Debug, Clone, thiserror::Error)]
16#[non_exhaustive]
17pub enum Error {
18 #[error("VP8 frame too short for tag")]
19 FrameTooShort,
20
21 #[error("VP8 key frame too short for header")]
22 KeyframeHeaderTooShort,
23
24 #[error("VP8 key frame start code mismatch")]
25 StartCodeMismatch,
26
27 #[error("empty VP8 frame")]
28 EmptyFrame,
29}
30
31pub type Result<T> = std::result::Result<T, Error>;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub(crate) struct FrameHeader {
38 pub keyframe: bool,
40 pub dimensions: Option<(u16, u16)>,
42}
43
44impl FrameHeader {
45 pub fn parse(data: &[u8]) -> Result<Self> {
51 if data.len() < 3 {
52 return Err(Error::FrameTooShort);
53 }
54
55 let tag = u32::from(data[0]) | (u32::from(data[1]) << 8) | (u32::from(data[2]) << 16);
57 let keyframe = tag & 0x1 == 0;
58
59 if !keyframe {
60 return Ok(Self {
61 keyframe: false,
62 dimensions: None,
63 });
64 }
65
66 if data.len() < 10 {
67 return Err(Error::KeyframeHeaderTooShort);
68 }
69 if !(data[3] == 0x9d && data[4] == 0x01 && data[5] == 0x2a) {
70 return Err(Error::StartCodeMismatch);
71 }
72
73 let width = (u16::from(data[6]) | (u16::from(data[7]) << 8)) & 0x3fff;
75 let height = (u16::from(data[8]) | (u16::from(data[9]) << 8)) & 0x3fff;
76
77 Ok(Self {
78 keyframe: true,
79 dimensions: Some((width, height)),
80 })
81 }
82}
83
84pub(crate) fn vpcc() -> mp4_atom::VpcC {
90 mp4_atom::VpcC {
91 profile: 0,
92 level: 0,
93 bit_depth: 8,
94 ..Default::default()
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn parses_keyframe_dimensions() {
104 let frame = [0x10, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0x40, 0x01, 0xf0, 0x00];
106 let header = FrameHeader::parse(&frame).expect("parse key frame");
107 assert!(header.keyframe);
108 assert_eq!(header.dimensions, Some((320, 240)));
109 }
110
111 #[test]
112 fn parses_interframe() {
113 let frame = [0x31, 0x00, 0x00];
115 let header = FrameHeader::parse(&frame).expect("parse interframe");
116 assert!(!header.keyframe);
117 assert_eq!(header.dimensions, None);
118 }
119
120 #[test]
121 fn rejects_bad_start_code() {
122 let frame = [0x10, 0x00, 0x00, 0xde, 0xad, 0xbe, 0x40, 0x01, 0xf0, 0x00];
123 assert!(FrameHeader::parse(&frame).is_err());
124 }
125
126 #[test]
127 fn rejects_short_frame() {
128 assert!(FrameHeader::parse(&[0x10, 0x00]).is_err());
129 }
130}