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