scuffle_h264/
config.rs

1use std::io::{
2    Write, {self},
3};
4
5use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
6use bytes::{Buf, Bytes};
7use scuffle_bytes_util::{BitReader, BitWriter, BytesCursorExt};
8
9use crate::sps::SpsExtended;
10
11/// The AVC (H.264) Decoder Configuration Record.
12/// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
13#[derive(Debug, Clone, PartialEq)]
14pub struct AVCDecoderConfigurationRecord {
15    /// The `configuration_version` is set to 1 (as a u8) defined by the h264 spec until further notice.
16    ///
17    /// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
18    pub configuration_version: u8,
19
20    /// The `profile_indication` (aka AVCProfileIndication) contains the `profile_idc` u8 from SPS.
21    ///
22    /// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
23    pub profile_indication: u8,
24
25    /// The `profile_compatibility` is a u8, similar to the `profile_idc` and `level_idc` bytes from SPS.
26    ///
27    /// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
28    pub profile_compatibility: u8,
29
30    /// The `level_indication` (aka AVCLevelIndication) contains the `level_idc` u8 from SPS.
31    ///
32    /// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
33    pub level_indication: u8,
34
35    /// The `length_size_minus_one` is the u8 length of the NALUnitLength minus one.
36    ///
37    /// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
38    pub length_size_minus_one: u8,
39
40    /// The `sps` is a vec of SPS Bytes.
41    ///
42    /// Note that these should be ordered by ascending SPS ID.
43    ///
44    /// Refer to the [`crate::Sps`] struct in the SPS docs for more info.
45    pub sps: Vec<Bytes>,
46
47    /// The `pps` is a vec of PPS Bytes.
48    ///
49    /// These contain syntax elements that can apply layer repesentation(s).
50    ///
51    /// Note that these should be ordered by ascending PPS ID.
52    ///
53    /// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
54    pub pps: Vec<Bytes>,
55
56    /// An optional `AvccExtendedConfig`.
57    ///
58    /// Refer to the AvccExtendedConfig for more info.
59    pub extended_config: Option<AvccExtendedConfig>,
60}
61
62/// The AVC (H.264) Extended Configuration.
63/// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
64#[derive(Debug, Clone, PartialEq)]
65pub struct AvccExtendedConfig {
66    /// The `chroma_format_idc` as a u8.
67    ///
68    /// Also labelled as `chroma_format`, this contains the `chroma_format_idc` from
69    /// ISO/IEC 14496-10.
70    ///
71    /// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
72    pub chroma_format_idc: u8,
73
74    /// The `bit_depth_luma_minus8` is the bit depth of samples in the Luma arrays as a u8.
75    ///
76    /// The value of this ranges from \[0, 4\].
77    ///
78    /// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
79    pub bit_depth_luma_minus8: u8,
80
81    /// The `bit_depth_chroma_minus8` is the bit depth of the samples in the Chroma arrays as a u8.
82    ///
83    /// The value of this ranges from \[0, 4\].
84    ///
85    /// ISO/IEC 14496-15:2022(E) - 5.3.2.1.2
86    pub bit_depth_chroma_minus8: u8,
87
88    /// The `sequence_parameter_set_ext` is a vec of SpsExtended Bytes.
89    ///
90    /// Refer to the [`crate::SpsExtended`] struct in the SPS docs for more info.
91    pub sequence_parameter_set_ext: Vec<SpsExtended>,
92}
93
94impl AVCDecoderConfigurationRecord {
95    /// Parses an AVCDecoderConfigurationRecord from a byte stream.
96    /// Returns a parsed AVCDecoderConfigurationRecord.
97    pub fn parse(reader: &mut io::Cursor<Bytes>) -> io::Result<Self> {
98        let configuration_version = reader.read_u8()?;
99        let profile_indication = reader.read_u8()?;
100        let profile_compatibility = reader.read_u8()?;
101        let level_indication = reader.read_u8()?;
102        let length_size_minus_one = reader.read_u8()? & 0b00000011;
103        let num_of_sequence_parameter_sets = reader.read_u8()? & 0b00011111;
104
105        let mut sps = Vec::with_capacity(num_of_sequence_parameter_sets as usize);
106        for _ in 0..num_of_sequence_parameter_sets {
107            let sps_length = reader.read_u16::<BigEndian>()?;
108            let sps_data = reader.extract_bytes(sps_length as usize)?;
109            sps.push(sps_data);
110        }
111
112        let num_of_picture_parameter_sets = reader.read_u8()?;
113        let mut pps = Vec::with_capacity(num_of_picture_parameter_sets as usize);
114        for _ in 0..num_of_picture_parameter_sets {
115            let pps_length = reader.read_u16::<BigEndian>()?;
116            let pps_data = reader.extract_bytes(pps_length as usize)?;
117            pps.push(pps_data);
118        }
119
120        // It turns out that sometimes the extended config is not present, even though
121        // the avc_profile_indication is not 66, 77 or 88. We need to be lenient here on
122        // decoding.
123        let extended_config = match profile_indication {
124            66 | 77 | 88 => None,
125            _ => {
126                if reader.has_remaining() {
127                    let chroma_format_idc = reader.read_u8()? & 0b00000011; // 2 bits (6 bits reserved)
128                    let bit_depth_luma_minus8 = reader.read_u8()? & 0b00000111; // 3 bits (5 bits reserved)
129                    let bit_depth_chroma_minus8 = reader.read_u8()? & 0b00000111; // 3 bits (5 bits reserved)
130                    let number_of_sequence_parameter_set_ext = reader.read_u8()?; // 8 bits
131
132                    let mut sequence_parameter_set_ext = Vec::with_capacity(number_of_sequence_parameter_set_ext as usize);
133                    for _ in 0..number_of_sequence_parameter_set_ext {
134                        let sps_ext_length = reader.read_u16::<BigEndian>()?;
135                        let sps_ext_data = reader.extract_bytes(sps_ext_length as usize)?;
136
137                        let mut bit_reader = BitReader::new_from_slice(sps_ext_data);
138                        let sps_ext_parsed = SpsExtended::parse(&mut bit_reader)?;
139                        sequence_parameter_set_ext.push(sps_ext_parsed);
140                    }
141
142                    Some(AvccExtendedConfig {
143                        chroma_format_idc,
144                        bit_depth_luma_minus8,
145                        bit_depth_chroma_minus8,
146                        sequence_parameter_set_ext,
147                    })
148                } else {
149                    // No extended config present even though avc_profile_indication is not 66, 77
150                    // or 88
151                    None
152                }
153            }
154        };
155
156        Ok(Self {
157            configuration_version,
158            profile_indication,
159            profile_compatibility,
160            level_indication,
161            length_size_minus_one,
162            sps,
163            pps,
164            extended_config,
165        })
166    }
167
168    /// Returns the total byte size of the AVCDecoderConfigurationRecord.
169    pub fn size(&self) -> u64 {
170        1 // configuration_version
171        + 1 // avc_profile_indication
172        + 1 // profile_compatibility
173        + 1 // avc_level_indication
174        + 1 // length_size_minus_one
175        + 1 // num_of_sequence_parameter_sets (5 bits reserved, 3 bits)
176        + self.sps.iter().map(|sps| {
177            2 // sps_length
178            + sps.len() as u64
179        }).sum::<u64>() // sps
180        + 1 // num_of_picture_parameter_sets
181        + self.pps.iter().map(|pps| {
182            2 // pps_length
183            + pps.len() as u64
184        }).sum::<u64>() // pps
185        + match &self.extended_config {
186            Some(config) => {
187                1 // chroma_format_idc (6 bits reserved, 2 bits)
188                + 1 // bit_depth_luma_minus8 (5 bits reserved, 3 bits)
189                + 1 // bit_depth_chroma_minus8 (5 bits reserved, 3 bits)
190                + 1 // number_of_sequence_parameter_set_ext
191                + config.sequence_parameter_set_ext.iter().map(|sps_ext| {
192                    2 // sps_ext_length
193                    + sps_ext.bytesize() // sps_ext
194                }).sum::<u64>()
195            }
196            None => 0,
197        }
198    }
199
200    /// Builds the AVCDecoderConfigurationRecord into a byte stream.
201    /// Returns a built byte stream.
202    pub fn build<T: io::Write>(&self, writer: &mut T) -> io::Result<()> {
203        let mut bit_writer = BitWriter::new(writer);
204
205        bit_writer.write_u8(self.configuration_version)?;
206        bit_writer.write_u8(self.profile_indication)?;
207        bit_writer.write_u8(self.profile_compatibility)?;
208        bit_writer.write_u8(self.level_indication)?;
209        bit_writer.write_bits(0b111111, 6)?;
210        bit_writer.write_bits(self.length_size_minus_one as u64, 2)?;
211        bit_writer.write_bits(0b111, 3)?;
212
213        bit_writer.write_bits(self.sps.len() as u64, 5)?;
214        for sps in &self.sps {
215            bit_writer.write_u16::<BigEndian>(sps.len() as u16)?;
216            bit_writer.write_all(sps)?;
217        }
218
219        bit_writer.write_bits(self.pps.len() as u64, 8)?;
220        for pps in &self.pps {
221            bit_writer.write_u16::<BigEndian>(pps.len() as u16)?;
222            bit_writer.write_all(pps)?;
223        }
224
225        if let Some(config) = &self.extended_config {
226            bit_writer.write_bits(0b111111, 6)?;
227            bit_writer.write_bits(config.chroma_format_idc as u64, 2)?;
228            bit_writer.write_bits(0b11111, 5)?;
229            bit_writer.write_bits(config.bit_depth_luma_minus8 as u64, 3)?;
230            bit_writer.write_bits(0b11111, 5)?;
231            bit_writer.write_bits(config.bit_depth_chroma_minus8 as u64, 3)?;
232
233            bit_writer.write_bits(config.sequence_parameter_set_ext.len() as u64, 8)?;
234            for sps_ext in &config.sequence_parameter_set_ext {
235                bit_writer.write_u16::<BigEndian>(sps_ext.bytesize() as u16)?;
236                // SpsExtended::build() does not automatically align the writer
237                // due to the fact that it's used when building the Sps.
238                // If SpsExtended::build() were to align the writer, it could
239                // potentially cause a mismatch as it might introduce 0-padding in
240                // the middle of the bytestream, as the bytestream should only be aligned
241                // at the very end.
242                // In this case however, we want to intentionally align the writer as
243                // the sps is the only thing here.
244                sps_ext.build(&mut bit_writer)?;
245                bit_writer.align()?;
246            }
247        }
248
249        bit_writer.finish()?;
250
251        Ok(())
252    }
253}
254
255#[cfg(test)]
256#[cfg_attr(all(test, coverage_nightly), coverage(off))]
257mod tests {
258    use std::io::{self, Write};
259
260    use byteorder::{BigEndian, WriteBytesExt};
261    use bytes::Bytes;
262    use scuffle_bytes_util::BitWriter;
263
264    use crate::config::{AVCDecoderConfigurationRecord, AvccExtendedConfig};
265    use crate::sps::SpsExtended;
266
267    #[test]
268    fn test_config_parse() {
269        let sample_sps = b"gd\0\x1f\xac\xd9A\xe0m\xf9\xe6\xa0  (\0\0\x03\0\x08\0\0\x03\x01\xe0x\xc1\x8c\xb0";
270        let mut data = Vec::new();
271        let mut writer = BitWriter::new(&mut data);
272
273        // configuration_version
274        writer.write_bits(1, 8).unwrap();
275        // profile_indication
276        writer.write_bits(100, 8).unwrap();
277        // profile_compatibility
278        writer.write_bits(0, 8).unwrap();
279        // level_indication
280        writer.write_bits(31, 8).unwrap();
281        // length_size_minus_one
282        writer.write_bits(3, 8).unwrap();
283
284        // num_of_sequence_parameter_sets
285        writer.write_bits(1, 8).unwrap();
286        // sps_length
287        writer.write_u16::<BigEndian>(sample_sps.len() as u16).unwrap();
288        // sps
289        // this was from the old test
290        writer.write_all(sample_sps).unwrap();
291
292        // num_of_picture_parameter_sets
293        writer.write_bits(1, 8).unwrap();
294        // pps_length
295        writer.write_bits(6, 16).unwrap();
296        writer.write_all(b"h\xeb\xe3\xcb\"\xc0\x00\x00").unwrap();
297
298        // chroma_format_idc
299        writer.write_bits(1, 8).unwrap();
300        // bit_depth_luma_minus8
301        writer.write_bits(0, 8).unwrap();
302        // bit_depth_chroma_minus8
303        writer.write_bits(0, 8).unwrap();
304        // number_of_sequence_parameter_set_ext
305        writer.write_bits(0, 8).unwrap();
306        writer.finish().unwrap();
307
308        let result = AVCDecoderConfigurationRecord::parse(&mut io::Cursor::new(data.into())).unwrap();
309
310        let sps = &result.sps[0];
311
312        assert_eq!(**sps, *sample_sps);
313    }
314
315    #[test]
316    fn test_config_build() {
317        // these may not be the same size due to the natural reduction from the SPS parsing.
318        // in specific, the sps size function may return a lower size than the original bitstring.
319        // reduction will occur from rebuilding the sps and from rebuilding the sps_ext.
320        let data = Bytes::from(b"\x01d\0\x1f\xff\xe1\0\x19\x67\x64\x00\x1F\xAC\xD9\x41\xE0\x6D\xF9\xE6\xA0\x20\x20\x28\x00\x00\x03\x00\x08\x00\x00\x03\x01\xE0\x01\0\x06h\xeb\xe3\xcb\"\xc0\xfd\xf8\xf8\0".to_vec());
321
322        let config = AVCDecoderConfigurationRecord::parse(&mut io::Cursor::new(data.clone())).unwrap();
323
324        assert_eq!(config.size(), data.len() as u64);
325
326        let mut buf = Vec::new();
327        config.build(&mut buf).unwrap();
328
329        assert_eq!(buf, data.to_vec());
330    }
331
332    #[test]
333    fn test_no_ext_cfg_for_profiles_66_77_88() {
334        let data = Bytes::from(b"\x01B\x00\x1F\xFF\xE1\x00\x1Dgd\x00\x1F\xAC\xD9A\xE0m\xF9\xE6\xA0  (\x00\x00\x03\x00\x08\x00\x00\x03\x01\xE0x\xC1\x8C\xB0\x01\x00\x06h\xEB\xE3\xCB\"\xC0\xFD\xF8\xF8\x00".to_vec());
335        let config = AVCDecoderConfigurationRecord::parse(&mut io::Cursor::new(data)).unwrap();
336
337        assert_eq!(config.extended_config, None);
338    }
339
340    #[test]
341    fn test_size_calculation_with_sequence_parameter_set_ext() {
342        let extended_config = AvccExtendedConfig {
343            chroma_format_idc: 1,
344            bit_depth_luma_minus8: 0,
345            bit_depth_chroma_minus8: 0,
346            sequence_parameter_set_ext: vec![SpsExtended {
347                chroma_format_idc: 1,
348                separate_color_plane_flag: false,
349                bit_depth_luma_minus8: 2,
350                bit_depth_chroma_minus8: 3,
351                qpprime_y_zero_transform_bypass_flag: false,
352                scaling_matrix: vec![],
353            }],
354        };
355        let config = AVCDecoderConfigurationRecord {
356            configuration_version: 1,
357            profile_indication: 100,
358            profile_compatibility: 0,
359            level_indication: 31,
360            length_size_minus_one: 3,
361            sps: vec![Bytes::from_static(
362                b"\x67\x64\x00\x1F\xAC\xD9\x41\xE0\x6D\xF9\xE6\xA0\x20\x20\x28\x00\x00\x00\x08\x00\x00\x01\xE0",
363            )],
364            pps: vec![Bytes::from_static(b"ppsdata")],
365            extended_config: Some(extended_config),
366        };
367
368        assert_eq!(config.size(), 49);
369        insta::assert_debug_snapshot!(config, @r#"
370        AVCDecoderConfigurationRecord {
371            configuration_version: 1,
372            profile_indication: 100,
373            profile_compatibility: 0,
374            level_indication: 31,
375            length_size_minus_one: 3,
376            sps: [
377                b"gd\0\x1f\xac\xd9A\xe0m\xf9\xe6\xa0  (\0\0\0\x08\0\0\x01\xe0",
378            ],
379            pps: [
380                b"ppsdata",
381            ],
382            extended_config: Some(
383                AvccExtendedConfig {
384                    chroma_format_idc: 1,
385                    bit_depth_luma_minus8: 0,
386                    bit_depth_chroma_minus8: 0,
387                    sequence_parameter_set_ext: [
388                        SpsExtended {
389                            chroma_format_idc: 1,
390                            separate_color_plane_flag: false,
391                            bit_depth_luma_minus8: 2,
392                            bit_depth_chroma_minus8: 3,
393                            qpprime_y_zero_transform_bypass_flag: false,
394                            scaling_matrix: [],
395                        },
396                    ],
397                },
398            ),
399        }
400        "#);
401    }
402
403    #[test]
404    fn test_build_with_sequence_parameter_set_ext() {
405        let extended_config = AvccExtendedConfig {
406            chroma_format_idc: 1,
407            bit_depth_luma_minus8: 0,
408            bit_depth_chroma_minus8: 0,
409            sequence_parameter_set_ext: vec![SpsExtended {
410                chroma_format_idc: 1,
411                separate_color_plane_flag: false,
412                bit_depth_luma_minus8: 2,
413                bit_depth_chroma_minus8: 3,
414                qpprime_y_zero_transform_bypass_flag: false,
415                scaling_matrix: vec![],
416            }],
417        };
418        let config = AVCDecoderConfigurationRecord {
419            configuration_version: 1,
420            profile_indication: 100,
421            profile_compatibility: 0,
422            level_indication: 31,
423            length_size_minus_one: 3,
424            sps: vec![Bytes::from_static(
425                b"gd\0\x1f\xac\xd9A\xe0m\xf9\xe6\xa0  (\0\0\x03\0\x08\0\0\x03\x01\xe0x\xc1\x8c\xb0",
426            )],
427            pps: vec![Bytes::from_static(b"ppsdata")],
428            extended_config: Some(extended_config),
429        };
430
431        let mut buf = Vec::new();
432        config.build(&mut buf).unwrap();
433
434        let parsed = AVCDecoderConfigurationRecord::parse(&mut io::Cursor::new(buf.into())).unwrap();
435        assert_eq!(parsed.extended_config.unwrap().sequence_parameter_set_ext.len(), 1);
436        insta::assert_debug_snapshot!(config, @r#"
437        AVCDecoderConfigurationRecord {
438            configuration_version: 1,
439            profile_indication: 100,
440            profile_compatibility: 0,
441            level_indication: 31,
442            length_size_minus_one: 3,
443            sps: [
444                b"gd\0\x1f\xac\xd9A\xe0m\xf9\xe6\xa0  (\0\0\x03\0\x08\0\0\x03\x01\xe0x\xc1\x8c\xb0",
445            ],
446            pps: [
447                b"ppsdata",
448            ],
449            extended_config: Some(
450                AvccExtendedConfig {
451                    chroma_format_idc: 1,
452                    bit_depth_luma_minus8: 0,
453                    bit_depth_chroma_minus8: 0,
454                    sequence_parameter_set_ext: [
455                        SpsExtended {
456                            chroma_format_idc: 1,
457                            separate_color_plane_flag: false,
458                            bit_depth_luma_minus8: 2,
459                            bit_depth_chroma_minus8: 3,
460                            qpprime_y_zero_transform_bypass_flag: false,
461                            scaling_matrix: [],
462                        },
463                    ],
464                },
465            ),
466        }
467        "#);
468    }
469}