scuffle_av1/
config.rs

1use std::io;
2
3use byteorder::ReadBytesExt;
4use bytes::Bytes;
5use scuffle_bytes_util::{BitReader, BitWriter, BytesCursorExt};
6
7/// AV1 Video Descriptor
8/// <https://aomediacodec.github.io/av1-mpeg2-ts/#av1-video-descriptor>
9#[derive(Debug, Clone, PartialEq)]
10pub struct AV1VideoDescriptor {
11    pub tag: u8,
12    pub length: u8,
13    pub codec_configuration_record: AV1CodecConfigurationRecord,
14}
15
16impl AV1VideoDescriptor {
17    pub fn demux(reader: &mut io::Cursor<Bytes>) -> io::Result<Self> {
18        let tag = reader.read_u8()?;
19        if tag != 0x80 {
20            return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid AV1 video descriptor tag"));
21        }
22
23        let length = reader.read_u8()?;
24        if length != 4 {
25            return Err(io::Error::new(
26                io::ErrorKind::InvalidData,
27                "Invalid AV1 video descriptor length",
28            ));
29        }
30
31        Ok(AV1VideoDescriptor {
32            tag,
33            length,
34            codec_configuration_record: AV1CodecConfigurationRecord::demux(reader)?,
35        })
36    }
37}
38
39#[derive(Debug, Clone, PartialEq)]
40/// AV1 Codec Configuration Record
41/// <https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax>
42pub struct AV1CodecConfigurationRecord {
43    pub seq_profile: u8,
44    pub seq_level_idx_0: u8,
45    pub seq_tier_0: bool,
46    pub high_bitdepth: bool,
47    pub twelve_bit: bool,
48    pub monochrome: bool,
49    pub chroma_subsampling_x: bool,
50    pub chroma_subsampling_y: bool,
51    pub chroma_sample_position: u8,
52    pub hdr_wcg_idc: u8,
53    pub initial_presentation_delay_minus_one: Option<u8>,
54    pub config_obu: Bytes,
55}
56
57impl AV1CodecConfigurationRecord {
58    pub fn demux(reader: &mut io::Cursor<Bytes>) -> io::Result<Self> {
59        let mut bit_reader = BitReader::new(reader);
60
61        let marker = bit_reader.read_bit()?;
62        if !marker {
63            return Err(io::Error::new(io::ErrorKind::InvalidData, "marker is not set"));
64        }
65
66        let version = bit_reader.read_bits(7)? as u8;
67        if version != 1 {
68            return Err(io::Error::new(io::ErrorKind::InvalidData, "version is not 1"));
69        }
70
71        let seq_profile = bit_reader.read_bits(3)? as u8;
72        let seq_level_idx_0 = bit_reader.read_bits(5)? as u8;
73
74        let seq_tier_0 = bit_reader.read_bit()?;
75        let high_bitdepth = bit_reader.read_bit()?;
76        let twelve_bit = bit_reader.read_bit()?;
77        let monochrome = bit_reader.read_bit()?;
78        let chroma_subsampling_x = bit_reader.read_bit()?;
79        let chroma_subsampling_y = bit_reader.read_bit()?;
80        let chroma_sample_position = bit_reader.read_bits(2)? as u8;
81
82        // This is from the https://aomediacodec.github.io/av1-mpeg2-ts/#av1-video-descriptor spec
83        // The spec from https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-section is old and contains 3 bits reserved
84        // The newer spec takes 2 of those reserved bits to represent the HDR WCG IDC
85        // Leaving 1 bit for future use
86        let hdr_wcg_idc = bit_reader.read_bits(2)? as u8;
87
88        bit_reader.seek_bits(1)?; // reserved 1 bits
89
90        let initial_presentation_delay_minus_one = if bit_reader.read_bit()? {
91            Some(bit_reader.read_bits(4)? as u8)
92        } else {
93            bit_reader.seek_bits(4)?; // reserved 4 bits
94            None
95        };
96
97        if !bit_reader.is_aligned() {
98            return Err(io::Error::new(io::ErrorKind::InvalidData, "Bit reader is not aligned"));
99        }
100
101        let reader = bit_reader.into_inner();
102
103        Ok(AV1CodecConfigurationRecord {
104            seq_profile,
105            seq_level_idx_0,
106            seq_tier_0,
107            high_bitdepth,
108            twelve_bit,
109            monochrome,
110            chroma_subsampling_x,
111            chroma_subsampling_y,
112            chroma_sample_position,
113            hdr_wcg_idc,
114            initial_presentation_delay_minus_one,
115            config_obu: reader.extract_remaining(),
116        })
117    }
118
119    pub fn size(&self) -> u64 {
120        1 // marker, version
121        + 1 // seq_profile, seq_level_idx_0
122        + 1 // seq_tier_0, high_bitdepth, twelve_bit, monochrome, chroma_subsampling_x, chroma_subsampling_y, chroma_sample_position
123        + 1 // reserved, initial_presentation_delay_present, initial_presentation_delay_minus_one/reserved
124        + self.config_obu.len() as u64
125    }
126
127    pub fn mux<T: io::Write>(&self, writer: &mut T) -> io::Result<()> {
128        let mut bit_writer = BitWriter::new(writer);
129
130        bit_writer.write_bit(true)?; // marker
131        bit_writer.write_bits(1, 7)?; // version
132
133        bit_writer.write_bits(self.seq_profile as u64, 3)?;
134        bit_writer.write_bits(self.seq_level_idx_0 as u64, 5)?;
135
136        bit_writer.write_bit(self.seq_tier_0)?;
137        bit_writer.write_bit(self.high_bitdepth)?;
138        bit_writer.write_bit(self.twelve_bit)?;
139        bit_writer.write_bit(self.monochrome)?;
140        bit_writer.write_bit(self.chroma_subsampling_x)?;
141        bit_writer.write_bit(self.chroma_subsampling_y)?;
142        bit_writer.write_bits(self.chroma_sample_position as u64, 2)?;
143
144        bit_writer.write_bits(0, 3)?; // reserved 3 bits
145
146        if let Some(initial_presentation_delay_minus_one) = self.initial_presentation_delay_minus_one {
147            bit_writer.write_bit(true)?;
148            bit_writer.write_bits(initial_presentation_delay_minus_one as u64, 4)?;
149        } else {
150            bit_writer.write_bit(false)?;
151            bit_writer.write_bits(0, 4)?; // reserved 4 bits
152        }
153
154        bit_writer.finish()?.write_all(&self.config_obu)?;
155
156        Ok(())
157    }
158}
159
160#[cfg(test)]
161#[cfg_attr(all(test, coverage_nightly), coverage(off))]
162mod tests {
163
164    use super::*;
165
166    #[test]
167    fn test_config_demux() {
168        let data = b"\x81\r\x0c\0\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@".to_vec();
169
170        let config = AV1CodecConfigurationRecord::demux(&mut io::Cursor::new(data.into())).unwrap();
171
172        insta::assert_debug_snapshot!(config, @r#"
173        AV1CodecConfigurationRecord {
174            seq_profile: 0,
175            seq_level_idx_0: 13,
176            seq_tier_0: false,
177            high_bitdepth: false,
178            twelve_bit: false,
179            monochrome: false,
180            chroma_subsampling_x: true,
181            chroma_subsampling_y: true,
182            chroma_sample_position: 0,
183            hdr_wcg_idc: 0,
184            initial_presentation_delay_minus_one: None,
185            config_obu: b"\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@",
186        }
187        "#);
188    }
189
190    #[test]
191    fn test_marker_is_not_set() {
192        let data = vec![0b00000000];
193
194        let err = AV1CodecConfigurationRecord::demux(&mut io::Cursor::new(data.into())).unwrap_err();
195
196        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
197        assert_eq!(err.to_string(), "marker is not set");
198    }
199
200    #[test]
201    fn test_version_is_not_1() {
202        let data = vec![0b10000000];
203
204        let err = AV1CodecConfigurationRecord::demux(&mut io::Cursor::new(data.into())).unwrap_err();
205
206        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
207        assert_eq!(err.to_string(), "version is not 1");
208    }
209
210    #[test]
211    fn test_config_demux_with_initial_presentation_delay() {
212        let data = b"\x81\r\x0c\x3f\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@".to_vec();
213
214        let config = AV1CodecConfigurationRecord::demux(&mut io::Cursor::new(data.into())).unwrap();
215
216        insta::assert_debug_snapshot!(config, @r#"
217        AV1CodecConfigurationRecord {
218            seq_profile: 0,
219            seq_level_idx_0: 13,
220            seq_tier_0: false,
221            high_bitdepth: false,
222            twelve_bit: false,
223            monochrome: false,
224            chroma_subsampling_x: true,
225            chroma_subsampling_y: true,
226            chroma_sample_position: 0,
227            hdr_wcg_idc: 0,
228            initial_presentation_delay_minus_one: Some(
229                15,
230            ),
231            config_obu: b"\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@",
232        }
233        "#);
234    }
235
236    #[test]
237    fn test_config_mux() {
238        let config = AV1CodecConfigurationRecord {
239            seq_profile: 0,
240            seq_level_idx_0: 0,
241            seq_tier_0: false,
242            high_bitdepth: false,
243            twelve_bit: false,
244            monochrome: false,
245            chroma_subsampling_x: false,
246            chroma_subsampling_y: false,
247            chroma_sample_position: 0,
248            hdr_wcg_idc: 0,
249            initial_presentation_delay_minus_one: None,
250            config_obu: Bytes::from_static(b"HELLO FROM THE OBU"),
251        };
252
253        let mut buf = Vec::new();
254        config.mux(&mut buf).unwrap();
255
256        insta::assert_snapshot!(format!("{:?}", Bytes::from(buf)), @r#"b"\x81\0\0\0HELLO FROM THE OBU""#);
257    }
258
259    #[test]
260    fn test_config_mux_with_delay() {
261        let config = AV1CodecConfigurationRecord {
262            seq_profile: 0,
263            seq_level_idx_0: 0,
264            seq_tier_0: false,
265            high_bitdepth: false,
266            twelve_bit: false,
267            monochrome: false,
268            chroma_subsampling_x: false,
269            chroma_subsampling_y: false,
270            chroma_sample_position: 0,
271            hdr_wcg_idc: 0,
272            initial_presentation_delay_minus_one: Some(0),
273            config_obu: Bytes::from_static(b"HELLO FROM THE OBU"),
274        };
275
276        let mut buf = Vec::new();
277        config.mux(&mut buf).unwrap();
278
279        insta::assert_snapshot!(format!("{:?}", Bytes::from(buf)), @r#"b"\x81\0\0\x10HELLO FROM THE OBU""#);
280    }
281
282    #[test]
283    fn test_video_descriptor_demux() {
284        let data = b"\x80\x04\x81\r\x0c\x3f\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@".to_vec();
285
286        let config = AV1VideoDescriptor::demux(&mut io::Cursor::new(data.into())).unwrap();
287
288        insta::assert_debug_snapshot!(config, @r#"
289        AV1VideoDescriptor {
290            tag: 128,
291            length: 4,
292            codec_configuration_record: AV1CodecConfigurationRecord {
293                seq_profile: 0,
294                seq_level_idx_0: 13,
295                seq_tier_0: false,
296                high_bitdepth: false,
297                twelve_bit: false,
298                monochrome: false,
299                chroma_subsampling_x: true,
300                chroma_subsampling_y: true,
301                chroma_sample_position: 0,
302                hdr_wcg_idc: 0,
303                initial_presentation_delay_minus_one: Some(
304                    15,
305                ),
306                config_obu: b"\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@",
307            },
308        }
309        "#);
310    }
311
312    #[test]
313    fn test_video_descriptor_demux_invalid_tag() {
314        let data = b"\x81".to_vec();
315
316        let err = AV1VideoDescriptor::demux(&mut io::Cursor::new(data.into())).unwrap_err();
317
318        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
319        assert_eq!(err.to_string(), "Invalid AV1 video descriptor tag");
320    }
321
322    #[test]
323    fn test_video_descriptor_demux_invalid_length() {
324        let data = b"\x80\x05ju".to_vec();
325
326        let err = AV1VideoDescriptor::demux(&mut io::Cursor::new(data.into())).unwrap_err();
327
328        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
329        assert_eq!(err.to_string(), "Invalid AV1 video descriptor length");
330    }
331}