Skip to main content

oxideav_bitstream/
ivf.rs

1//! Tiny IVF demuxer shared by the VP8 and VP9 modules.
2//!
3//! IVF is the container ffmpeg / libvpx use for raw VP8 / VP9
4//! elementary streams when no other container is needed:
5//!
6//! - 32-byte global header:
7//!   - bytes 0..4   `DKIF` magic
8//!   - bytes 4..6   version (LE u16, must be 0)
9//!   - bytes 6..8   header length (LE u16, must be 32)
10//!   - bytes 8..12  codec FourCC (e.g. `VP80`, `VP90`)
11//!   - bytes 12..14 width (LE u16)
12//!   - bytes 14..16 height (LE u16)
13//!   - bytes 16..20 framerate numerator (LE u32, ignored here)
14//!   - bytes 20..24 framerate denominator (LE u32, ignored here)
15//!   - bytes 24..28 frame count (LE u32, ignored here)
16//!   - bytes 28..32 reserved/unused
17//! - per frame: 12-byte header (LE u32 size + LE u64 timestamp)
18//!   followed by the frame payload bytes.
19//!
20//! There is no spec PDF for IVF — it is a libvpx-historical container
21//! format whose layout is publicly documented in many places
22//! (Wikipedia, Matroska wiki, etc.). This module is a clean
23//! reimplementation.
24
25use crate::BitstreamError;
26
27/// FourCC for a VP8 IVF stream.
28pub const IVF_FOURCC_VP80: [u8; 4] = *b"VP80";
29/// FourCC for a VP9 IVF stream.
30pub const IVF_FOURCC_VP90: [u8; 4] = *b"VP90";
31
32/// Parsed IVF global header.
33#[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/// Borrowed view of one IVF frame.
44#[derive(Debug, Clone, Copy)]
45pub struct IvfFrame<'a> {
46    pub timestamp: u64,
47    pub payload: &'a [u8],
48}
49
50/// Parse the 32-byte IVF global header and return it together with a
51/// slice positioned at the first per-frame block.
52pub 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
89/// Parse the next IVF frame. Returns `Ok(None)` on EOF, `Err` on
90/// truncation.
91pub 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
112/// Convenience: parse the entire IVF (header + every frame) into a
113/// single [`IvfHeader`] plus a `Vec<IvfFrame>`.
114pub 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()); // version
132        buf.extend_from_slice(&32u16.to_le_bytes()); // header_len
133        buf.extend_from_slice(fourcc);
134        buf.extend_from_slice(&320u16.to_le_bytes()); // width
135        buf.extend_from_slice(&240u16.to_le_bytes()); // height
136        buf.extend_from_slice(&1u32.to_le_bytes()); // num
137        buf.extend_from_slice(&1u32.to_le_bytes()); // den
138        buf.extend_from_slice(&0u32.to_le_bytes()); // count
139        buf.extend_from_slice(&0u32.to_le_bytes()); // reserved
140        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        // 5-byte payload, ts = 7.
176        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]); // less than declared 5 bytes
193        let (_h, body) = parse_header(&buf).unwrap();
194        assert!(matches!(
195            parse_frame(body),
196            Err(BitstreamError::UnexpectedEnd(_))
197        ));
198    }
199}