scuffle_av1/
config.rs

1use std::io;
2
3use scuffle_bytes_util::zero_copy::{Deserialize, Serialize};
4use scuffle_bytes_util::{BitReader, BitWriter, BytesCow};
5
6/// AV1 Video Descriptor
7///
8/// <https://aomediacodec.github.io/av1-mpeg2-ts/#av1-video-descriptor>
9#[derive(Debug, Clone, PartialEq)]
10pub struct AV1VideoDescriptor<'a> {
11    /// This value shall be set to `0x80`.
12    ///
13    /// 8 bits
14    pub tag: u8,
15    /// This value shall be set to 4.
16    ///
17    /// 8 bits
18    pub length: u8,
19    /// AV1 Codec Configuration Record
20    pub codec_configuration_record: AV1CodecConfigurationRecord<'a>,
21}
22
23impl<'a> Deserialize<'a> for AV1VideoDescriptor<'a> {
24    fn deserialize<R>(mut reader: R) -> io::Result<Self>
25    where
26        R: scuffle_bytes_util::zero_copy::ZeroCopyReader<'a>,
27    {
28        let tag = u8::deserialize(&mut reader)?;
29        if tag != 0x80 {
30            return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid AV1 video descriptor tag"));
31        }
32
33        let length = u8::deserialize(&mut reader)?;
34        if length != 4 {
35            return Err(io::Error::new(
36                io::ErrorKind::InvalidData,
37                "Invalid AV1 video descriptor length",
38            ));
39        }
40
41        Ok(AV1VideoDescriptor {
42            tag,
43            length,
44            codec_configuration_record: AV1CodecConfigurationRecord::deserialize(reader)?,
45        })
46    }
47}
48
49impl Serialize for AV1VideoDescriptor<'_> {
50    fn serialize<W>(&self, mut writer: W) -> io::Result<()>
51    where
52        W: std::io::Write,
53    {
54        self.tag.serialize(&mut writer)?;
55        self.length.serialize(&mut writer)?;
56        self.codec_configuration_record.serialize(&mut writer)?;
57        Ok(())
58    }
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
62/// AV1 Codec Configuration Record
63///
64/// <https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax>
65pub struct AV1CodecConfigurationRecord<'a> {
66    /// This field shall be coded according to the semantics defined in [AV1](https://aomediacodec.github.io/av1-spec/av1-spec.pdf).
67    ///
68    /// 3 bits
69    pub seq_profile: u8,
70    /// This field shall be coded according to the semantics defined in [AV1](https://aomediacodec.github.io/av1-spec/av1-spec.pdf).
71    ///
72    /// 5 bits
73    pub seq_level_idx_0: u8,
74    /// This field shall be coded according to the semantics defined in [AV1](https://aomediacodec.github.io/av1-spec/av1-spec.pdf), when present.
75    /// If they are not present, they will be coded using the value inferred by the semantics.
76    ///
77    /// 1 bit
78    pub seq_tier_0: bool,
79    /// This field shall be coded according to the semantics defined in [AV1](https://aomediacodec.github.io/av1-spec/av1-spec.pdf).
80    ///
81    /// 1 bit
82    pub high_bitdepth: bool,
83    /// This field shall be coded according to the semantics defined in [AV1](https://aomediacodec.github.io/av1-spec/av1-spec.pdf), when present.
84    /// If they are not present, they will be coded using the value inferred by the semantics.
85    ///
86    /// 1 bit
87    pub twelve_bit: bool,
88    /// This field shall be coded according to the semantics defined in [AV1](https://aomediacodec.github.io/av1-spec/av1-spec.pdf), when present.
89    /// If they are not present, they will be coded using the value inferred by the semantics.
90    ///
91    /// 1 bit
92    pub monochrome: bool,
93    /// This field shall be coded according to the semantics defined in [AV1](https://aomediacodec.github.io/av1-spec/av1-spec.pdf), when present.
94    /// If they are not present, they will be coded using the value inferred by the semantics.
95    ///
96    /// 1 bit
97    pub chroma_subsampling_x: bool,
98    /// This field shall be coded according to the semantics defined in [AV1](https://aomediacodec.github.io/av1-spec/av1-spec.pdf), when present.
99    /// If they are not present, they will be coded using the value inferred by the semantics.
100    ///
101    /// 1 bit
102    pub chroma_subsampling_y: bool,
103    /// This field shall be coded according to the semantics defined in [AV1](https://aomediacodec.github.io/av1-spec/av1-spec.pdf), when present.
104    /// If they are not present, they will be coded using the value inferred by the semantics.
105    ///
106    /// 2 bits
107    pub chroma_sample_position: u8,
108    /// The value of this syntax element indicates the presence or absence of high dynamic range (HDR) and/or
109    /// wide color gamut (WCG) video components in the associated PID according to the table below.
110    ///
111    /// | HDR/WCG IDC | Description   |
112    /// |-------------|---------------|
113    /// | 0           | SDR           |
114    /// | 1           | WCG only      |
115    /// | 2           | HDR and WCG   |
116    /// | 3           | No indication |
117    ///
118    /// 2 bits
119    ///
120    /// From a newer spec: <https://aomediacodec.github.io/av1-mpeg2-ts/#av1-video-descriptor>
121    pub hdr_wcg_idc: u8,
122    /// Ignored for [MPEG-2 TS](https://www.iso.org/standard/83239.html) use,
123    /// included only to aid conversion to/from ISOBMFF.
124    ///
125    /// 4 bits
126    pub initial_presentation_delay_minus_one: Option<u8>,
127    /// Zero or more OBUs. Refer to the linked specification for details.
128    ///
129    /// 8 bits
130    pub config_obu: BytesCow<'a>,
131}
132
133impl<'a> Deserialize<'a> for AV1CodecConfigurationRecord<'a> {
134    fn deserialize<R>(mut reader: R) -> io::Result<Self>
135    where
136        R: scuffle_bytes_util::zero_copy::ZeroCopyReader<'a>,
137    {
138        let mut bit_reader = BitReader::new(reader.as_std());
139
140        let marker = bit_reader.read_bit()?;
141        if !marker {
142            return Err(io::Error::new(io::ErrorKind::InvalidData, "marker is not set"));
143        }
144
145        let version = bit_reader.read_bits(7)? as u8;
146        if version != 1 {
147            return Err(io::Error::new(io::ErrorKind::InvalidData, "version is not 1"));
148        }
149
150        let seq_profile = bit_reader.read_bits(3)? as u8;
151        let seq_level_idx_0 = bit_reader.read_bits(5)? as u8;
152
153        let seq_tier_0 = bit_reader.read_bit()?;
154        let high_bitdepth = bit_reader.read_bit()?;
155        let twelve_bit = bit_reader.read_bit()?;
156        let monochrome = bit_reader.read_bit()?;
157        let chroma_subsampling_x = bit_reader.read_bit()?;
158        let chroma_subsampling_y = bit_reader.read_bit()?;
159        let chroma_sample_position = bit_reader.read_bits(2)? as u8;
160
161        // This is from the https://aomediacodec.github.io/av1-mpeg2-ts/#av1-video-descriptor spec
162        // The spec from https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-section is old and contains 3 bits reserved
163        // The newer spec takes 2 of those reserved bits to represent the HDR WCG IDC
164        // Leaving 1 bit for future use
165        let hdr_wcg_idc = bit_reader.read_bits(2)? as u8;
166
167        bit_reader.read_bits(1)?; // reserved 1 bits
168
169        let initial_presentation_delay_minus_one = if bit_reader.read_bit()? {
170            Some(bit_reader.read_bits(4)? as u8)
171        } else {
172            bit_reader.read_bits(4)?; // reserved 4 bits
173            None
174        };
175
176        {
177            let bit_reader = bit_reader; // move
178            if !bit_reader.is_aligned() {
179                return Err(io::Error::new(io::ErrorKind::InvalidData, "Bit reader is not aligned"));
180            }
181        }
182
183        Ok(AV1CodecConfigurationRecord {
184            seq_profile,
185            seq_level_idx_0,
186            seq_tier_0,
187            high_bitdepth,
188            twelve_bit,
189            monochrome,
190            chroma_subsampling_x,
191            chroma_subsampling_y,
192            chroma_sample_position,
193            hdr_wcg_idc,
194            initial_presentation_delay_minus_one,
195            config_obu: reader.try_read_to_end()?,
196        })
197    }
198}
199
200impl Serialize for AV1CodecConfigurationRecord<'_> {
201    fn serialize<W>(&self, writer: W) -> io::Result<()>
202    where
203        W: std::io::Write,
204    {
205        let mut bit_writer = BitWriter::new(writer);
206
207        bit_writer.write_bit(true)?; // marker
208        bit_writer.write_bits(1, 7)?; // version
209
210        bit_writer.write_bits(self.seq_profile as u64, 3)?;
211        bit_writer.write_bits(self.seq_level_idx_0 as u64, 5)?;
212
213        bit_writer.write_bit(self.seq_tier_0)?;
214        bit_writer.write_bit(self.high_bitdepth)?;
215        bit_writer.write_bit(self.twelve_bit)?;
216        bit_writer.write_bit(self.monochrome)?;
217        bit_writer.write_bit(self.chroma_subsampling_x)?;
218        bit_writer.write_bit(self.chroma_subsampling_y)?;
219        bit_writer.write_bits(self.chroma_sample_position as u64, 2)?;
220
221        bit_writer.write_bits(0, 3)?; // reserved 3 bits
222
223        if let Some(initial_presentation_delay_minus_one) = self.initial_presentation_delay_minus_one {
224            bit_writer.write_bit(true)?;
225            bit_writer.write_bits(initial_presentation_delay_minus_one as u64, 4)?;
226        } else {
227            bit_writer.write_bit(false)?;
228            bit_writer.write_bits(0, 4)?; // reserved 4 bits
229        }
230
231        bit_writer.finish()?.write_all(self.config_obu.as_bytes())?;
232
233        Ok(())
234    }
235}
236
237#[cfg(feature = "isobmff")]
238impl isobmff::IsoSized for AV1CodecConfigurationRecord<'_> {
239    /// Returns the size of the AV1 Codec Configuration Record.
240    fn size(&self) -> usize {
241        1 // marker, version
242        + 1 // seq_profile, seq_level_idx_0
243        + 1 // seq_tier_0, high_bitdepth, twelve_bit, monochrome, chroma_subsampling_x, chroma_subsampling_y, chroma_sample_position
244        + 1 // reserved, initial_presentation_delay_present, initial_presentation_delay_minus_one/reserved
245        + self.config_obu.len()
246    }
247}
248
249#[cfg(test)]
250#[cfg_attr(all(test, coverage_nightly), coverage(off))]
251mod tests {
252    use bytes::Bytes;
253    use scuffle_bytes_util::zero_copy::Slice;
254
255    use super::*;
256
257    #[test]
258    fn test_config_demux() {
259        let data = b"\x81\r\x0c\0\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@";
260
261        let config = AV1CodecConfigurationRecord::deserialize(Slice::from(&data[..])).unwrap();
262
263        insta::assert_debug_snapshot!(config, @r#"
264        AV1CodecConfigurationRecord {
265            seq_profile: 0,
266            seq_level_idx_0: 13,
267            seq_tier_0: false,
268            high_bitdepth: false,
269            twelve_bit: false,
270            monochrome: false,
271            chroma_subsampling_x: true,
272            chroma_subsampling_y: true,
273            chroma_sample_position: 0,
274            hdr_wcg_idc: 0,
275            initial_presentation_delay_minus_one: None,
276            config_obu: b"\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@",
277        }
278        "#);
279    }
280
281    #[test]
282    fn test_marker_is_not_set() {
283        let data = [0b00000000];
284
285        let err = AV1CodecConfigurationRecord::deserialize(Slice::from(&data[..])).unwrap_err();
286
287        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
288        assert_eq!(err.to_string(), "marker is not set");
289    }
290
291    #[test]
292    fn test_version_is_not_1() {
293        let data = [0b10000000];
294
295        let err = AV1CodecConfigurationRecord::deserialize(Slice::from(&data[..])).unwrap_err();
296
297        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
298        assert_eq!(err.to_string(), "version is not 1");
299    }
300
301    #[test]
302    fn test_config_demux_with_initial_presentation_delay() {
303        let data = b"\x81\r\x0c\x3f\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@".to_vec();
304
305        let config = AV1CodecConfigurationRecord::deserialize(Slice::from(&data[..])).unwrap();
306
307        insta::assert_debug_snapshot!(config, @r#"
308        AV1CodecConfigurationRecord {
309            seq_profile: 0,
310            seq_level_idx_0: 13,
311            seq_tier_0: false,
312            high_bitdepth: false,
313            twelve_bit: false,
314            monochrome: false,
315            chroma_subsampling_x: true,
316            chroma_subsampling_y: true,
317            chroma_sample_position: 0,
318            hdr_wcg_idc: 0,
319            initial_presentation_delay_minus_one: Some(
320                15,
321            ),
322            config_obu: b"\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@",
323        }
324        "#);
325    }
326
327    #[test]
328    fn test_config_mux() {
329        let config = AV1CodecConfigurationRecord {
330            seq_profile: 0,
331            seq_level_idx_0: 0,
332            seq_tier_0: false,
333            high_bitdepth: false,
334            twelve_bit: false,
335            monochrome: false,
336            chroma_subsampling_x: false,
337            chroma_subsampling_y: false,
338            chroma_sample_position: 0,
339            hdr_wcg_idc: 0,
340            initial_presentation_delay_minus_one: None,
341            config_obu: BytesCow::from_static(b"HELLO FROM THE OBU"),
342        };
343
344        let mut buf = Vec::new();
345        config.serialize(&mut buf).unwrap();
346
347        insta::assert_snapshot!(format!("{:?}", Bytes::from(buf)), @r#"b"\x81\0\0\0HELLO FROM THE OBU""#);
348    }
349
350    #[test]
351    fn test_config_mux_with_delay() {
352        let config = AV1CodecConfigurationRecord {
353            seq_profile: 0,
354            seq_level_idx_0: 0,
355            seq_tier_0: false,
356            high_bitdepth: false,
357            twelve_bit: false,
358            monochrome: false,
359            chroma_subsampling_x: false,
360            chroma_subsampling_y: false,
361            chroma_sample_position: 0,
362            hdr_wcg_idc: 0,
363            initial_presentation_delay_minus_one: Some(0),
364            config_obu: BytesCow::from_static(b"HELLO FROM THE OBU"),
365        };
366
367        let mut buf = Vec::new();
368        config.serialize(&mut buf).unwrap();
369
370        insta::assert_snapshot!(format!("{:?}", Bytes::from(buf)), @r#"b"\x81\0\0\x10HELLO FROM THE OBU""#);
371    }
372
373    #[test]
374    fn test_video_descriptor_demux() {
375        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();
376
377        let config = AV1VideoDescriptor::deserialize(Slice::from(&data[..])).unwrap();
378
379        insta::assert_debug_snapshot!(config, @r#"
380        AV1VideoDescriptor {
381            tag: 128,
382            length: 4,
383            codec_configuration_record: AV1CodecConfigurationRecord {
384                seq_profile: 0,
385                seq_level_idx_0: 13,
386                seq_tier_0: false,
387                high_bitdepth: false,
388                twelve_bit: false,
389                monochrome: false,
390                chroma_subsampling_x: true,
391                chroma_subsampling_y: true,
392                chroma_sample_position: 0,
393                hdr_wcg_idc: 0,
394                initial_presentation_delay_minus_one: Some(
395                    15,
396                ),
397                config_obu: b"\n\x0f\0\0\0j\xef\xbf\xe1\xbc\x02\x19\x90\x10\x10\x10@",
398            },
399        }
400        "#);
401    }
402
403    #[test]
404    fn test_video_descriptor_demux_invalid_tag() {
405        let data = b"\x81".to_vec();
406
407        let err = AV1VideoDescriptor::deserialize(Slice::from(&data[..])).unwrap_err();
408
409        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
410        assert_eq!(err.to_string(), "Invalid AV1 video descriptor tag");
411    }
412
413    #[test]
414    fn test_video_descriptor_demux_invalid_length() {
415        let data = b"\x80\x05ju".to_vec();
416
417        let err = AV1VideoDescriptor::deserialize(Slice::from(&data[..])).unwrap_err();
418
419        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
420        assert_eq!(err.to_string(), "Invalid AV1 video descriptor length");
421    }
422}