Skip to main content

rtc_media/io/ivf_reader/
mod.rs

1//! Reading IVF files.
2//!
3//! IVF is the minimal container the VPx and AV1 tools use: a 32-byte file header naming the codec
4//! and frame size, then a 12-byte header before each frame giving its length and timestamp.
5//! [`IVFReader`](crate::io::ivf_reader::IVFReader) yields one frame at a time, which is the unit an RTP payloader wants.
6#[cfg(test)]
7mod ivf_reader_test;
8
9use std::io::Read;
10
11use byteorder::{LittleEndian, ReadBytesExt};
12use bytes::BytesMut;
13
14use crate::io::ResetFn;
15use shared::error::{Error, Result};
16
17/// The four-byte signature every IVF file starts with.
18pub const IVF_FILE_HEADER_SIGNATURE: &[u8] = b"DKIF";
19/// The size of the IVF file header in bytes.
20pub const IVF_FILE_HEADER_SIZE: usize = 32;
21/// The size of each IVF frame header in bytes.
22pub const IVF_FRAME_HEADER_SIZE: usize = 12;
23
24/// IVFFileHeader 32-byte header for IVF files
25/// <https://wiki.multimedia.cx/index.php/IVF>
26#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
27pub struct IVFFileHeader {
28    /// Bytes 0–3: always `DKIF`.
29    pub signature: [u8; 4], // 0-3
30    /// Bytes 4–5: the format version, currently 0.
31    pub version: u16, // 4-5
32    /// Bytes 6–7: the header length, normally 32.
33    pub header_size: u16, // 6-7
34    /// Bytes 8–11: the codec FourCC, such as `VP80`, `VP90` or `AV01`.
35    pub four_cc: [u8; 4], // 8-11
36    /// Bytes 12–13: frame width in pixels.
37    pub width: u16, // 12-13
38    /// Bytes 14–15: frame height in pixels.
39    pub height: u16, // 14-15
40    /// Bytes 16–19: the timebase denominator — the frame rate's numerator, confusingly.
41    pub timebase_denominator: u32, // 16-19
42    /// Bytes 20–23: the timebase numerator.
43    pub timebase_numerator: u32, // 20-23
44    /// Bytes 24–27: the frame count, if the writer knew it.
45    pub num_frames: u32, // 24-27
46    /// Bytes 28–31: reserved.
47    pub unused: u32, // 28-31
48}
49
50/// IVFFrameHeader 12-byte header for IVF frames
51/// <https://wiki.multimedia.cx/index.php/IVF>
52#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
53pub struct IVFFrameHeader {
54    /// Bytes 0–3: the frame's payload length in bytes.
55    pub frame_size: u32, // 0-3
56    /// Bytes 4–11: the frame's presentation timestamp, in timebase units.
57    pub timestamp: u64, // 4-11
58}
59
60/// IVFReader is used to read IVF files and return frame payloads
61pub struct IVFReader<R: Read> {
62    reader: R,
63    bytes_read: usize,
64}
65
66impl<R: Read> IVFReader<R> {
67    /// new returns a new IVF reader and IVF file header
68    /// with an io.Reader input
69    pub fn new(reader: R) -> Result<(IVFReader<R>, IVFFileHeader)> {
70        let mut r = IVFReader {
71            reader,
72            bytes_read: 0,
73        };
74
75        let header = r.parse_file_header()?;
76
77        Ok((r, header))
78    }
79
80    /// reset_reader resets the internal stream of IVFReader. This is useful
81    /// for live streams, where the end of the file might be read without the
82    /// data being finished.
83    pub fn reset_reader(&mut self, mut reset: ResetFn<R>) {
84        self.reader = reset(self.bytes_read);
85    }
86
87    /// parse_next_frame reads from stream and returns IVF frame payload, header,
88    /// and an error if there is incomplete frame data.
89    /// Returns all nil values when no more frames are available.
90    pub fn parse_next_frame(&mut self) -> Result<(BytesMut, IVFFrameHeader)> {
91        let frame_size = self.reader.read_u32::<LittleEndian>()?;
92        let timestamp = self.reader.read_u64::<LittleEndian>()?;
93        let header = IVFFrameHeader {
94            frame_size,
95            timestamp,
96        };
97
98        let mut payload = BytesMut::with_capacity(header.frame_size as usize);
99        payload.resize(header.frame_size as usize, 0);
100        self.reader.read_exact(&mut payload)?;
101
102        self.bytes_read += IVF_FRAME_HEADER_SIZE + header.frame_size as usize;
103
104        Ok((payload, header))
105    }
106
107    /// parse_file_header reads 32 bytes from stream and returns
108    /// IVF file header. This is always called before parse_next_frame()
109    fn parse_file_header(&mut self) -> Result<IVFFileHeader> {
110        let mut signature = [0u8; 4];
111        let mut four_cc = [0u8; 4];
112
113        self.reader.read_exact(&mut signature)?;
114        let version = self.reader.read_u16::<LittleEndian>()?;
115        let header_size = self.reader.read_u16::<LittleEndian>()?;
116        self.reader.read_exact(&mut four_cc)?;
117        let width = self.reader.read_u16::<LittleEndian>()?;
118        let height = self.reader.read_u16::<LittleEndian>()?;
119        let timebase_denominator = self.reader.read_u32::<LittleEndian>()?;
120        let timebase_numerator = self.reader.read_u32::<LittleEndian>()?;
121        let num_frames = self.reader.read_u32::<LittleEndian>()?;
122        let unused = self.reader.read_u32::<LittleEndian>()?;
123
124        let header = IVFFileHeader {
125            signature,
126            version,
127            header_size,
128            four_cc,
129            width,
130            height,
131            timebase_denominator,
132            timebase_numerator,
133            num_frames,
134            unused,
135        };
136
137        if header.signature != IVF_FILE_HEADER_SIGNATURE {
138            return Err(Error::ErrSignatureMismatch);
139        } else if header.version != 0 {
140            return Err(Error::ErrUnknownIVFVersion);
141        }
142
143        self.bytes_read += IVF_FILE_HEADER_SIZE;
144
145        Ok(header)
146    }
147}