1use crate::BitstreamError;
26
27pub const IVF_FOURCC_VP80: [u8; 4] = *b"VP80";
29pub const IVF_FOURCC_VP90: [u8; 4] = *b"VP90";
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct IvfHeader {
35 pub fourcc: [u8; 4],
36 pub width: u16,
37 pub height: u16,
38 pub framerate_num: u32,
39 pub framerate_den: u32,
40 pub frame_count: u32,
41}
42
43#[derive(Debug, Clone, Copy)]
45pub struct IvfFrame<'a> {
46 pub timestamp: u64,
47 pub payload: &'a [u8],
48}
49
50pub fn parse_header(buf: &[u8]) -> Result<(IvfHeader, &[u8]), BitstreamError> {
53 if buf.len() < 32 {
54 return Err(BitstreamError::unexpected_end(
55 "IVF file shorter than 32-byte header",
56 ));
57 }
58 if &buf[0..4] != b"DKIF" {
59 return Err(BitstreamError::invalid(format!(
60 "IVF magic mismatch: {:?}",
61 &buf[0..4]
62 )));
63 }
64 let version = u16::from_le_bytes([buf[4], buf[5]]);
65 if version != 0 {
66 return Err(BitstreamError::invalid(format!(
67 "IVF version {version} != 0"
68 )));
69 }
70 let header_len = u16::from_le_bytes([buf[6], buf[7]]);
71 if header_len != 32 {
72 return Err(BitstreamError::invalid(format!(
73 "IVF header_len {header_len} != 32"
74 )));
75 }
76 let mut fourcc = [0u8; 4];
77 fourcc.copy_from_slice(&buf[8..12]);
78 let header = IvfHeader {
79 fourcc,
80 width: u16::from_le_bytes([buf[12], buf[13]]),
81 height: u16::from_le_bytes([buf[14], buf[15]]),
82 framerate_num: u32::from_le_bytes([buf[16], buf[17], buf[18], buf[19]]),
83 framerate_den: u32::from_le_bytes([buf[20], buf[21], buf[22], buf[23]]),
84 frame_count: u32::from_le_bytes([buf[24], buf[25], buf[26], buf[27]]),
85 };
86 Ok((header, &buf[32..]))
87}
88
89pub fn parse_frame(buf: &[u8]) -> Result<Option<(IvfFrame<'_>, &[u8])>, BitstreamError> {
92 if buf.is_empty() {
93 return Ok(None);
94 }
95 if buf.len() < 12 {
96 return Err(BitstreamError::unexpected_end("truncated IVF frame header"));
97 }
98 let size = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
99 let timestamp = u64::from_le_bytes([
100 buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11],
101 ]);
102 if buf.len() < 12 + size {
103 return Err(BitstreamError::unexpected_end(
104 "truncated IVF frame payload",
105 ));
106 }
107 let payload = &buf[12..12 + size];
108 let rest = &buf[12 + size..];
109 Ok(Some((IvfFrame { timestamp, payload }, rest)))
110}
111
112pub fn parse_all(buf: &[u8]) -> Result<(IvfHeader, Vec<IvfFrame<'_>>), BitstreamError> {
115 let (hdr, mut rest) = parse_header(buf)?;
116 let mut frames = Vec::new();
117 while let Some((frame, next)) = parse_frame(rest)? {
118 frames.push(frame);
119 rest = next;
120 }
121 Ok((hdr, frames))
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 fn synth_ivf(fourcc: &[u8; 4]) -> Vec<u8> {
129 let mut buf = Vec::new();
130 buf.extend_from_slice(b"DKIF");
131 buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&32u16.to_le_bytes()); buf.extend_from_slice(fourcc);
134 buf.extend_from_slice(&320u16.to_le_bytes()); buf.extend_from_slice(&240u16.to_le_bytes()); buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf
141 }
142
143 #[test]
144 fn parse_header_extracts_basic_fields() {
145 let buf = synth_ivf(&IVF_FOURCC_VP80);
146 let (h, rest) = parse_header(&buf).unwrap();
147 assert_eq!(&h.fourcc, &IVF_FOURCC_VP80);
148 assert_eq!(h.width, 320);
149 assert_eq!(h.height, 240);
150 assert_eq!(rest.len(), 0);
151 }
152
153 #[test]
154 fn parse_header_rejects_bad_magic() {
155 let mut buf = synth_ivf(&IVF_FOURCC_VP80);
156 buf[0] = b'X';
157 assert!(matches!(
158 parse_header(&buf),
159 Err(BitstreamError::InvalidData(_))
160 ));
161 }
162
163 #[test]
164 fn parse_header_rejects_short_input() {
165 let buf = vec![0u8; 16];
166 assert!(matches!(
167 parse_header(&buf),
168 Err(BitstreamError::UnexpectedEnd(_))
169 ));
170 }
171
172 #[test]
173 fn parse_frame_reads_payload_and_advances() {
174 let mut buf = synth_ivf(&IVF_FOURCC_VP90);
175 buf.extend_from_slice(&5u32.to_le_bytes());
177 buf.extend_from_slice(&7u64.to_le_bytes());
178 buf.extend_from_slice(&[1, 2, 3, 4, 5]);
179 let (_h, body) = parse_header(&buf).unwrap();
180 let (frame, rest) = parse_frame(body).unwrap().unwrap();
181 assert_eq!(frame.timestamp, 7);
182 assert_eq!(frame.payload, &[1, 2, 3, 4, 5]);
183 assert!(rest.is_empty());
184 assert!(parse_frame(rest).unwrap().is_none());
185 }
186
187 #[test]
188 fn parse_frame_rejects_truncated_payload() {
189 let mut buf = synth_ivf(&IVF_FOURCC_VP90);
190 buf.extend_from_slice(&5u32.to_le_bytes());
191 buf.extend_from_slice(&0u64.to_le_bytes());
192 buf.extend_from_slice(&[1, 2]); let (_h, body) = parse_header(&buf).unwrap();
194 assert!(matches!(
195 parse_frame(body),
196 Err(BitstreamError::UnexpectedEnd(_))
197 ));
198 }
199}