scuffle_h264/sps/
mod.rs

1mod chroma_sample_loc;
2use self::chroma_sample_loc::ChromaSampleLoc;
3
4mod color_config;
5use self::color_config::ColorConfig;
6
7mod frame_crop_info;
8use self::frame_crop_info::FrameCropInfo;
9
10mod pic_order_count_type1;
11use self::pic_order_count_type1::PicOrderCountType1;
12
13mod sample_aspect_ratio;
14use self::sample_aspect_ratio::SarDimensions;
15
16mod sps_ext;
17pub use self::sps_ext::SpsExtended;
18
19mod timing_info;
20use std::io;
21
22use byteorder::ReadBytesExt;
23use scuffle_bytes_util::{BitReader, BitWriter, EmulationPreventionIo, range_check};
24use scuffle_expgolomb::{BitReaderExpGolombExt, BitWriterExpGolombExt, size_of_exp_golomb};
25
26pub use self::timing_info::TimingInfo;
27use crate::NALUnitType;
28
29/// The Sequence Parameter Set.
30/// ISO/IEC-14496-10-2022 - 7.3.2
31#[derive(Debug, Clone, PartialEq)]
32pub struct Sps {
33    /// The `nal_ref_idc` is comprised of 2 bits.
34    ///
35    /// A nonzero value means the NAL unit has any of the following: SPS, SPS extension,
36    /// subset SPS, PPS, slice of a reference picture, slice of a data partition of a reference picture,
37    /// or a prefix NAL unit preceeding a slice of a reference picture.
38    ///
39    /// 0 means that the stream is decoded using the process from Clauses 2-9 (ISO/IEC-14496-10-2022)
40    /// that the slice or slice data partition is part of a non-reference picture.
41    /// Additionally, if `nal_ref_idc` is 0 for a NAL unit with `nal_unit_type`
42    /// ranging from \[1, 4\] then `nal_ref_idc` must be 0 for all NAL units with `nal_unit_type` between [1, 4].
43    ///
44    /// If the `nal_unit_type` is 5, then the `nal_ref_idc` cannot be 0.
45    ///
46    /// If `nal_unit_type` is 6, 9, 10, 11, or 12, then the `nal_ref_idc` must be 0.
47    ///
48    /// ISO/IEC-14496-10-2022 - 7.4.1
49    pub nal_ref_idc: u8,
50
51    /// The `nal_unit_type` is comprised of 5 bits. See the NALUnitType nutype enum for more info.
52    pub nal_unit_type: NALUnitType,
53
54    /// The `profile_idc` of the coded video sequence as a u8.
55    ///
56    /// It is comprised of 8 bits or 1 byte. ISO/IEC-14496-10-2022 - 7.4.2.1.1
57    pub profile_idc: u8,
58
59    /// `constraint_set0_flag`: `1` if it abides by the constraints in A.2.1, `0` if unsure or otherwise.
60    ///
61    /// If `profile_idc` is 44, 100, 110, 122, or 244, this is automatically set to false.
62    ///
63    /// It is a single bit. ISO/IEC-14496-10-2022 - 7.4.2.1.1
64    pub constraint_set0_flag: bool,
65
66    /// `constraint_set1_flag`: `1` if it abides by the constraints in A.2.2, `0` if unsure or otherwise.
67    ///
68    /// If `profile_idc` is 44, 100, 110, 122, or 244, this is automatically set to false.
69    ///
70    /// It is a single bit. ISO/IEC-14496-10-2022 - 7.4.2.1.1
71    pub constraint_set1_flag: bool,
72
73    /// `constraint_set2_flag`: `1` if it abides by the constraints in A.2.3, `0` if unsure or otherwise.
74    ///
75    /// If `profile_idc` is 44, 100, 110, 122, or 244, this is automatically set to false.
76    ///
77    /// It is a single bit. ISO/IEC-14496-10-2022 - 7.4.2.1.1
78    pub constraint_set2_flag: bool,
79
80    /// `constraint_set3_flag`:
81    /// ```text
82    ///     if (profile_idc == 66, 77, or 88) AND (level_idc == 11):
83    ///         1 if it abides by the constraints in Annex A for level 1b
84    ///         0 if it abides by the constraints in Annex A for level 1.1
85    ///     elif profile_idc == 100 or 110:
86    ///         1 if it abides by the constraints for the "High 10 Intra profile"
87    ///         0 if unsure or otherwise
88    ///     elif profile_idc == 122:
89    ///         1 if it abides by the constraints in Annex A for the "High 4:2:2 Intra profile"
90    ///         0 if unsure or otherwise
91    ///     elif profile_idc == 44:
92    ///         1 by default
93    ///         0 is not possible.
94    ///     elif profile_idc == 244:
95    ///         1 if it abides by the constraints in Annex A for the "High 4:4:4 Intra profile"
96    ///         0 if unsure or otherwise
97    ///     else:
98    ///         1 is reserved for future use
99    ///         0 otherwise
100    /// ```
101    ///
102    /// It is a single bit. ISO/IEC-14496-10-2022 - 7.4.2.1.1
103    pub constraint_set3_flag: bool,
104
105    /// `constraint_set4_flag`:
106    /// ```text
107    ///     if (profile_idc == 77, 88, 100, or 110):
108    ///         1 if frame_mbs_only_flag == 1
109    ///         0 if unsure or otherwise
110    ///     elif (profile_idc == 118, 128, or 134):
111    ///         1 if it abides by the constraints in G.6.1.1
112    ///         0 if unsure or otherwise
113    ///     else:
114    ///         1 is reserved for future use
115    ///         0 otherwise
116    /// ```
117    ///
118    /// It is a single bit. ISO/IEC-14496-10-2022 - 7.4.2.1.1
119    pub constraint_set4_flag: bool,
120
121    /// `constraint_set5_flag`:
122    /// ```text
123    ///     if (profile_idc == 77, 88, or 100):
124    ///         1 if there are no B slice types
125    ///         0 if unsure or otherwise
126    ///     elif profile_idc == 118:
127    ///         1 if it abides by the constraints in G.6.1.2
128    ///         0 if unsure or otherwise
129    ///     else:
130    ///         1 is reserved for future use
131    ///         0 otherwise
132    /// ```
133    ///
134    /// It is a single bit. ISO/IEC-14496-10-2022 - 7.4.2.1.1
135    pub constraint_set5_flag: bool,
136
137    /// The `level_idc` of the coded video sequence as a u8.
138    ///
139    /// It is comprised of 8 bits or 1 byte. ISO/IEC-14496-10-2022 - 7.4.2.1.1
140    pub level_idc: u8,
141
142    /// The `seq_parameter_set_id` is the id of the SPS referred to by the PPS (picture parameter set).
143    ///
144    /// The value of this ranges from \[0, 31\].
145    ///
146    /// This is a variable number of bits as it is encoded by an exp golomb (unsigned).
147    /// The smallest encoding would be for `0` which is encoded as `1`, which is a single bit.
148    /// The largest encoding would be for `31` which is encoded as `000 0010 0000`, which is 11 bits.
149    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
150    ///
151    /// For more information:
152    ///
153    /// <https://en.wikipedia.org/wiki/Exponential-Golomb_coding>
154    pub seq_parameter_set_id: u16,
155
156    /// An optional `SpsExtended`. Refer to the SpsExtended struct for more info.
157    ///
158    /// This will be parsed if `profile_idc` is equal to any of the following values:
159    /// 44, 83, 86, 100, 110, 118, 122, 128, 134, 135, 138, 139, or 244.
160    pub ext: Option<SpsExtended>,
161
162    /// The `log2_max_frame_num_minus4` is the value used when deriving MaxFrameNum from the equation:
163    /// `MaxFrameNum` = 2^(`log2_max_frame_num_minus4` + 4)
164    ///
165    /// The value of this ranges from \[0, 12\].
166    ///
167    /// This is a variable number of bits as it is encoded by an exp golomb (unsigned).
168    /// The smallest encoding would be for `0` which is encoded as `1`, which is a single bit.
169    /// The largest encoding would be for `12` which is encoded as `000 1101`, which is 7 bits.
170    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
171    ///
172    /// For more information:
173    ///
174    /// <https://en.wikipedia.org/wiki/Exponential-Golomb_coding>
175    pub log2_max_frame_num_minus4: u8,
176
177    /// The `pic_order_cnt_type` specifies how to decode the picture order count in subclause 8.2.1.
178    ///
179    /// The value of this ranges from \[0, 2\].
180    ///
181    /// This is a variable number of bits as it is encoded by an exp golomb (unsigned).
182    /// The smallest encoding would be for `0` which is encoded as `1`, which is a single bit.
183    /// The largest encoding would be for `2` which is encoded as `011`, which is 3 bits.
184    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
185    ///
186    /// For more information:
187    ///
188    /// <https://en.wikipedia.org/wiki/Exponential-Golomb_coding>
189    ///
190    /// There are a few subsequent fields that are read if `pic_order_cnt_type` is 0 or 1.
191    ///
192    /// In the case of 0, `log2_max_pic_order_cnt_lsb_minus4` is read as an exp golomb (unsigned).
193    ///
194    /// In the case of 1, `delta_pic_order_always_zero_flag`, `offset_for_non_ref_pic`,
195    /// `offset_for_top_to_bottom_field`, `num_ref_frames_in_pic_order_cnt_cycle` and
196    /// `offset_for_ref_frame` will be read and stored in pic_order_cnt_type1.
197    ///
198    /// Refer to the PicOrderCountType1 struct for more info.
199    pub pic_order_cnt_type: u8,
200
201    /// The `log2_max_pic_order_cnt_lsb_minus4` is the value used when deriving MaxFrameNum from the equation:
202    /// `MaxPicOrderCntLsb` = 2^(`log2_max_frame_num_minus4` + 4) from subclause 8.2.1.
203    ///
204    /// This is an `Option<u8>` because the value is only set if `pic_order_cnt_type == 0`.
205    ///
206    /// The value of this ranges from \[0, 12\].
207    ///
208    /// This is a variable number of bits as it is encoded by an exp golomb (unsigned).
209    /// The smallest encoding would be for `0` which is encoded as `1`, which is a single bit.
210    /// The largest encoding would be for `12` which is encoded as `000 1101`, which is 7 bits.
211    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
212    ///
213    /// For more information:
214    ///
215    /// <https://en.wikipedia.org/wiki/Exponential-Golomb_coding>
216    pub log2_max_pic_order_cnt_lsb_minus4: Option<u8>,
217
218    /// An optional `PicOrderCountType1`. This is computed from other fields, and isn't directly set.
219    ///
220    /// If `pic_order_cnt_type == 1`, then the `PicOrderCountType1` will be computed.
221    ///
222    /// Refer to the PicOrderCountType1 struct for more info.
223    pub pic_order_cnt_type1: Option<PicOrderCountType1>,
224
225    /// The `max_num_ref_frames` is the max short-term and long-term reference frames,
226    /// complementary reference field pairs, and non-paired reference fields that
227    /// can be used by the decoder for inter-prediction of pictures in the coded video.
228    ///
229    /// The value of this ranges from \[0, `MaxDpbFrames`\], which is specified in subclause A.3.1 or A.3.2.
230    ///
231    /// This is a variable number of bits as it is encoded by an exp golomb (unsigned).
232    /// The smallest encoding would be for `0` which is encoded as `1`, which is a single bit.
233    /// The largest encoding would be for `14` which is encoded as `000 1111`, which is 7 bits.
234    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
235    ///
236    /// For more information:
237    ///
238    /// <https://en.wikipedia.org/wiki/Exponential-Golomb_coding>
239    pub max_num_ref_frames: u8,
240
241    /// The `gaps_in_frame_num_value_allowed_flag` is a single bit.
242    ///
243    /// The value specifies the allowed values of `frame_num` from subclause 7.4.3 and the decoding process
244    /// if there is an inferred gap between the values of `frame_num` from subclause 8.2.5.2.
245    pub gaps_in_frame_num_value_allowed_flag: bool,
246
247    /// The `pic_width_in_mbs_minus1` is the width of each decoded picture in macroblocks as a u64.
248    ///
249    /// We then use this (along with the left and right frame crop offsets) to calculate the width as:
250    ///
251    /// `width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_right_offset * 2 - frame_crop_left_offset * 2`
252    ///
253    /// This is a variable number of bits as it is encoded by an exp golomb (unsigned).
254    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
255    ///
256    /// For more information:
257    ///
258    /// <https://en.wikipedia.org/wiki/Exponential-Golomb_coding>
259    pub pic_width_in_mbs_minus1: u64,
260
261    /// The `pic_height_in_map_units_minus1` is the height of each decoded frame in slice group map units as a u64.
262    ///
263    /// We then use this (along with the bottom and top frame crop offsets) to calculate the height as:
264    ///
265    /// `height = ((2 - frame_mbs_only_flag as u64) * (pic_height_in_map_units_minus1 + 1) * 16) -
266    /// frame_crop_bottom_offset * 2 - frame_crop_top_offset * 2`
267    ///
268    /// This is a variable number of bits as it is encoded by an exp golomb (unsigned).
269    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
270    ///
271    /// For more information:
272    ///
273    /// <https://en.wikipedia.org/wiki/Exponential-Golomb_coding>
274    pub pic_height_in_map_units_minus1: u64,
275
276    /// The `mb_adaptive_frame_field_flag` is a single bit.
277    ///
278    /// If `frame_mbs_only_flag` is NOT set then this field is read and stored.
279    ///
280    /// 0 means there is no switching between frame and field macroblocks in a picture.
281    ///
282    /// 1 means the might be switching between frame and field macroblocks in a picture.
283    ///
284    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
285    pub mb_adaptive_frame_field_flag: Option<bool>,
286
287    /// The `direct_8x8_inference_flag` specifies the method used to derive the luma motion
288    /// vectors for B_Skip, B_Direct_8x8 and B_Direct_16x16 from subclause 8.4.1.2, and is a single bit.
289    ///
290    /// ISO/IEC-14496-10-2022 - 7.4.2.1.1
291    pub direct_8x8_inference_flag: bool,
292
293    /// An optional `frame_crop_info` struct. This is computed by other fields, and isn't directly set.
294    ///
295    /// If the `frame_cropping_flag` is set, then `frame_crop_left_offset`, `frame_crop_right_offset`,
296    /// `frame_crop_top_offset`, and `frame_crop_bottom_offset` will be read and stored.
297    ///
298    /// Refer to the FrameCropInfo struct for more info.
299    pub frame_crop_info: Option<FrameCropInfo>,
300
301    /// An optional `SarDimensions` struct. This is computed by other fields, and isn't directly set.
302    ///
303    /// If the `aspect_ratio_info_present_flag` is set, then the `aspect_ratio_idc` will be read and stored.
304    ///
305    /// If the `aspect_ratio_idc` is 255, then the `sar_width` and `sar_height` will be read and stored.
306    ///
307    /// Also known as `sample_aspect_ratio` in the spec.
308    ///
309    /// The default values are set to 0 for the `aspect_ratio_idc`, `sar_width`, and `sar_height`.
310    /// Therefore, this will always be returned by the parse function.
311    /// ISO/IEC-14496-10-2022 - E.2.1
312    ///
313    /// Refer to the SarDimensions struct for more info.
314    pub sample_aspect_ratio: Option<SarDimensions>,
315
316    /// An optional `overscan_appropriate_flag` is a single bit.
317    ///
318    /// If the `overscan_info_present_flag` is set, then this field will be read and stored.
319    ///
320    /// 0 means the overscan should not be used. (ex: screensharing or security cameras)
321    ///
322    /// 1 means the overscan can be used. (ex: entertainment TV programming or live video conference)
323    ///
324    /// ISO/IEC-14496-10-2022 - E.2.1
325    pub overscan_appropriate_flag: Option<bool>,
326
327    /// An optional `ColorConfig`. This is computed from other fields, and isn't directly set.
328    ///
329    /// If `video_signal_type_present_flag` is set, then the `ColorConfig` will be computed, and
330    /// if the `color_description_present_flag` is set, then the `ColorConfig` will be
331    /// comprised of the `video_full_range_flag` (1 bit), `color_primaries` (1 byte as a u8),
332    /// `transfer_characteristics` (1 byte as a u8), and `matrix_coefficients` (1 byte as a u8).
333    ///
334    /// Otherwise, `color_primaries`, `transfer_characteristics`, and `matrix_coefficients` are set
335    /// to 2 (unspecified) by default.
336    ///
337    /// Refer to the ColorConfig struct for more info.
338    pub color_config: Option<ColorConfig>,
339
340    /// An optional `ChromaSampleLoc`. This is computed from other fields, and isn't directly set.
341    ///
342    /// If `chrome_loc_info_present_flag` is set, then the `ChromaSampleLoc` will be computed, and
343    /// is comprised of `chroma_sample_loc_type_top_field` and `chroma_sample_loc_type_bottom_field`.
344    ///
345    /// Refer to the ChromaSampleLoc struct for more info.
346    pub chroma_sample_loc: Option<ChromaSampleLoc>,
347
348    /// An optional `TimingInfo`. This is computed from other fields, and isn't directly set.
349    ///
350    /// If `timing_info_present_flag` is set, then the `TimingInfo` will be computed, and
351    /// is comprised of `num_units_in_tick` and `time_scale`.
352    ///
353    /// Refer to the TimingInfo struct for more info.
354    pub timing_info: Option<TimingInfo>,
355}
356
357impl Sps {
358    /// Parses an Sps from the input bytes.
359    ///
360    /// Returns an `Sps` struct.
361    pub fn parse(reader: impl io::Read) -> io::Result<Self> {
362        let mut bit_reader = BitReader::new(reader);
363
364        let forbidden_zero_bit = bit_reader.read_bit()?;
365        if forbidden_zero_bit {
366            return Err(io::Error::new(io::ErrorKind::InvalidData, "Forbidden zero bit is set"));
367        }
368
369        let nal_ref_idc = bit_reader.read_bits(2)? as u8;
370        let nal_unit_type = bit_reader.read_bits(5)? as u8;
371        if NALUnitType(nal_unit_type) != NALUnitType::SPS {
372            return Err(io::Error::new(io::ErrorKind::InvalidData, "NAL unit type is not SPS"));
373        }
374
375        let profile_idc = bit_reader.read_u8()?;
376
377        let constraint_set0_flag;
378        let constraint_set1_flag;
379        let constraint_set2_flag;
380
381        match profile_idc {
382            // 7.4.2.1.1
383            44 | 100 | 110 | 122 | 244 => {
384                // constraint_set0 thru 2 must be false in this case
385                bit_reader.read_bits(3)?;
386                constraint_set0_flag = false;
387                constraint_set1_flag = false;
388                constraint_set2_flag = false;
389            }
390            _ => {
391                // otherwise we parse the bits as expected
392                constraint_set0_flag = bit_reader.read_bit()?;
393                constraint_set1_flag = bit_reader.read_bit()?;
394                constraint_set2_flag = bit_reader.read_bit()?;
395            }
396        }
397
398        let constraint_set3_flag = if profile_idc == 44 {
399            bit_reader.read_bit()?;
400            false
401        } else {
402            bit_reader.read_bit()?
403        };
404
405        let constraint_set4_flag = match profile_idc {
406            // 7.4.2.1.1
407            77 | 88 | 100 | 118 | 128 | 134 => bit_reader.read_bit()?,
408            _ => {
409                bit_reader.read_bit()?;
410                false
411            }
412        };
413
414        let constraint_set5_flag = match profile_idc {
415            77 | 88 | 100 | 118 => bit_reader.read_bit()?,
416            _ => {
417                bit_reader.read_bit()?;
418                false
419            }
420        };
421        // reserved_zero_2bits
422        bit_reader.read_bits(2)?;
423
424        let level_idc = bit_reader.read_u8()?;
425        let seq_parameter_set_id = bit_reader.read_exp_golomb()?;
426        range_check!(seq_parameter_set_id, 0, 31)?;
427        let seq_parameter_set_id = seq_parameter_set_id as u16;
428
429        let sps_ext = match profile_idc {
430            100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135 => {
431                Some(SpsExtended::parse(&mut bit_reader)?)
432            }
433            _ => None,
434        };
435
436        let log2_max_frame_num_minus4 = bit_reader.read_exp_golomb()?;
437        range_check!(log2_max_frame_num_minus4, 0, 12)?;
438        let log2_max_frame_num_minus4 = log2_max_frame_num_minus4 as u8;
439
440        let pic_order_cnt_type = bit_reader.read_exp_golomb()?;
441        range_check!(pic_order_cnt_type, 0, 2)?;
442        let pic_order_cnt_type = pic_order_cnt_type as u8;
443
444        let mut log2_max_pic_order_cnt_lsb_minus4 = None;
445        let mut pic_order_cnt_type1 = None;
446
447        if pic_order_cnt_type == 0 {
448            let log2_max_pic_order_cnt_lsb_minus4_value = bit_reader.read_exp_golomb()?;
449            range_check!(log2_max_pic_order_cnt_lsb_minus4_value, 0, 12)?;
450            log2_max_pic_order_cnt_lsb_minus4 = Some(log2_max_pic_order_cnt_lsb_minus4_value as u8);
451        } else if pic_order_cnt_type == 1 {
452            pic_order_cnt_type1 = Some(PicOrderCountType1::parse(&mut bit_reader)?)
453        }
454
455        let max_num_ref_frames = bit_reader.read_exp_golomb()? as u8;
456        let gaps_in_frame_num_value_allowed_flag = bit_reader.read_bit()?;
457        let pic_width_in_mbs_minus1 = bit_reader.read_exp_golomb()?;
458        let pic_height_in_map_units_minus1 = bit_reader.read_exp_golomb()?;
459
460        let frame_mbs_only_flag = bit_reader.read_bit()?;
461        let mut mb_adaptive_frame_field_flag = None;
462        if !frame_mbs_only_flag {
463            mb_adaptive_frame_field_flag = Some(bit_reader.read_bit()?);
464        }
465
466        let direct_8x8_inference_flag = bit_reader.read_bit()?;
467
468        let mut frame_crop_info = None;
469
470        let frame_cropping_flag = bit_reader.read_bit()?;
471        if frame_cropping_flag {
472            frame_crop_info = Some(FrameCropInfo::parse(&mut bit_reader)?)
473        }
474
475        // setting default values for vui section
476        let mut sample_aspect_ratio = None;
477        let mut overscan_appropriate_flag = None;
478        let mut color_config = None;
479        let mut chroma_sample_loc = None;
480        let mut timing_info = None;
481
482        let vui_parameters_present_flag = bit_reader.read_bit()?;
483        if vui_parameters_present_flag {
484            // We read the VUI parameters to get the frame rate.
485
486            let aspect_ratio_info_present_flag = bit_reader.read_bit()?;
487            if aspect_ratio_info_present_flag {
488                sample_aspect_ratio = Some(SarDimensions::parse(&mut bit_reader)?)
489            }
490
491            let overscan_info_present_flag = bit_reader.read_bit()?;
492            if overscan_info_present_flag {
493                overscan_appropriate_flag = Some(bit_reader.read_bit()?);
494            }
495
496            let video_signal_type_present_flag = bit_reader.read_bit()?;
497            if video_signal_type_present_flag {
498                color_config = Some(ColorConfig::parse(&mut bit_reader)?)
499            }
500
501            let chroma_loc_info_present_flag = bit_reader.read_bit()?;
502            if sps_ext.as_ref().unwrap_or(&SpsExtended::default()).chroma_format_idc != 1 && chroma_loc_info_present_flag {
503                return Err(io::Error::new(
504                    io::ErrorKind::InvalidData,
505                    "chroma_loc_info_present_flag cannot be set to 1 when chroma_format_idc is not 1",
506                ));
507            }
508
509            if chroma_loc_info_present_flag {
510                chroma_sample_loc = Some(ChromaSampleLoc::parse(&mut bit_reader)?)
511            }
512
513            let timing_info_present_flag = bit_reader.read_bit()?;
514            if timing_info_present_flag {
515                timing_info = Some(TimingInfo::parse(&mut bit_reader)?)
516            }
517        }
518
519        Ok(Sps {
520            nal_ref_idc,
521            nal_unit_type: NALUnitType(nal_unit_type),
522            profile_idc,
523            constraint_set0_flag,
524            constraint_set1_flag,
525            constraint_set2_flag,
526            constraint_set3_flag,
527            constraint_set4_flag,
528            constraint_set5_flag,
529            level_idc,
530            seq_parameter_set_id,
531            ext: sps_ext,
532            log2_max_frame_num_minus4,
533            pic_order_cnt_type,
534            log2_max_pic_order_cnt_lsb_minus4,
535            pic_order_cnt_type1,
536            max_num_ref_frames,
537            gaps_in_frame_num_value_allowed_flag,
538            pic_width_in_mbs_minus1,
539            pic_height_in_map_units_minus1,
540            mb_adaptive_frame_field_flag,
541            direct_8x8_inference_flag,
542            frame_crop_info,
543            sample_aspect_ratio,
544            overscan_appropriate_flag,
545            color_config,
546            chroma_sample_loc,
547            timing_info,
548        })
549    }
550
551    /// Builds the Sps struct into a byte stream.
552    /// Returns a built byte stream.
553    pub fn build(&self, writer: impl io::Write) -> io::Result<()> {
554        let mut bit_writer = BitWriter::new(writer);
555
556        bit_writer.write_bit(false)?;
557        bit_writer.write_bits(self.nal_ref_idc as u64, 2)?;
558        bit_writer.write_bits(self.nal_unit_type.0 as u64, 5)?;
559        bit_writer.write_bits(self.profile_idc as u64, 8)?;
560
561        bit_writer.write_bit(self.constraint_set0_flag)?;
562        bit_writer.write_bit(self.constraint_set1_flag)?;
563        bit_writer.write_bit(self.constraint_set2_flag)?;
564        bit_writer.write_bit(self.constraint_set3_flag)?;
565        bit_writer.write_bit(self.constraint_set4_flag)?;
566        bit_writer.write_bit(self.constraint_set5_flag)?;
567        // reserved 2 bits
568        bit_writer.write_bits(0, 2)?;
569
570        bit_writer.write_bits(self.level_idc as u64, 8)?;
571        bit_writer.write_exp_golomb(self.seq_parameter_set_id as u64)?;
572
573        // sps ext
574        if let Some(ext) = &self.ext {
575            ext.build(&mut bit_writer)?;
576        }
577
578        bit_writer.write_exp_golomb(self.log2_max_frame_num_minus4 as u64)?;
579        bit_writer.write_exp_golomb(self.pic_order_cnt_type as u64)?;
580
581        if self.pic_order_cnt_type == 0 {
582            bit_writer.write_exp_golomb(self.log2_max_pic_order_cnt_lsb_minus4.unwrap() as u64)?;
583        } else if let Some(pic_order_cnt) = &self.pic_order_cnt_type1 {
584            pic_order_cnt.build(&mut bit_writer)?;
585        }
586
587        bit_writer.write_exp_golomb(self.max_num_ref_frames as u64)?;
588        bit_writer.write_bit(self.gaps_in_frame_num_value_allowed_flag)?;
589        bit_writer.write_exp_golomb(self.pic_width_in_mbs_minus1)?;
590        bit_writer.write_exp_golomb(self.pic_height_in_map_units_minus1)?;
591
592        bit_writer.write_bit(self.mb_adaptive_frame_field_flag.is_none())?;
593        if let Some(flag) = self.mb_adaptive_frame_field_flag {
594            bit_writer.write_bit(flag)?;
595        }
596
597        bit_writer.write_bit(self.direct_8x8_inference_flag)?;
598
599        bit_writer.write_bit(self.frame_crop_info.is_some())?;
600        if let Some(frame_crop_info) = &self.frame_crop_info {
601            frame_crop_info.build(&mut bit_writer)?;
602        }
603
604        match (
605            &self.sample_aspect_ratio,
606            &self.overscan_appropriate_flag,
607            &self.color_config,
608            &self.chroma_sample_loc,
609            &self.timing_info,
610        ) {
611            (None, None, None, None, None) => {
612                bit_writer.write_bit(false)?;
613            }
614            _ => {
615                // vui_parameters_present_flag
616                bit_writer.write_bit(true)?;
617
618                // aspect_ratio_info_present_flag
619                bit_writer.write_bit(self.sample_aspect_ratio.is_some())?;
620                if let Some(sar) = &self.sample_aspect_ratio {
621                    sar.build(&mut bit_writer)?;
622                }
623
624                // overscan_info_present_flag
625                bit_writer.write_bit(self.overscan_appropriate_flag.is_some())?;
626                if let Some(overscan) = &self.overscan_appropriate_flag {
627                    bit_writer.write_bit(*overscan)?;
628                }
629
630                // video_signal_type_prsent_flag
631                bit_writer.write_bit(self.color_config.is_some())?;
632                if let Some(color) = &self.color_config {
633                    color.build(&mut bit_writer)?;
634                }
635
636                // chroma_log_info_present_flag
637                bit_writer.write_bit(self.chroma_sample_loc.is_some())?;
638                if let Some(chroma) = &self.chroma_sample_loc {
639                    chroma.build(&mut bit_writer)?;
640                }
641
642                // timing_info_present_flag
643                bit_writer.write_bit(self.timing_info.is_some())?;
644                if let Some(timing) = &self.timing_info {
645                    timing.build(&mut bit_writer)?;
646                }
647            }
648        }
649        bit_writer.finish()?;
650
651        Ok(())
652    }
653
654    /// Parses the Sps struct from a reader that may contain emulation prevention bytes.
655    /// Is the same as calling [`Self::parse`] with an [`EmulationPreventionIo`] wrapper.
656    pub fn parse_with_emulation_prevention(reader: impl io::Read) -> io::Result<Self> {
657        Self::parse(EmulationPreventionIo::new(reader))
658    }
659
660    /// Builds the Sps struct into a byte stream that may contain emulation prevention bytes.
661    /// Is the same as calling [`Self::build`] with an [`EmulationPreventionIo`] wrapper.
662    pub fn build_with_emulation_prevention(self, writer: impl io::Write) -> io::Result<()> {
663        self.build(EmulationPreventionIo::new(writer))
664    }
665
666    /// Returns the total byte size of the Sps struct.
667    pub fn size(&self) -> u64 {
668        (1 + // forbidden zero bit
669        2 + // nal_ref_idc
670        5 + // nal_unit_type
671        8 + // profile_idc
672        8 + // 6 constraint_setn_flags + 2 reserved bits
673        8 + // level_idc
674        size_of_exp_golomb(self.seq_parameter_set_id as u64) +
675        self.ext.as_ref().map_or(0, |ext| ext.bitsize()) +
676        size_of_exp_golomb(self.log2_max_frame_num_minus4 as u64) +
677        size_of_exp_golomb(self.pic_order_cnt_type as u64) +
678        match self.pic_order_cnt_type {
679            0 => size_of_exp_golomb(self.log2_max_pic_order_cnt_lsb_minus4.unwrap() as u64),
680            1 => self.pic_order_cnt_type1.as_ref().unwrap().bitsize(),
681            _ => 0
682        } +
683        size_of_exp_golomb(self.max_num_ref_frames as u64) +
684        1 + // gaps_in_frame_num_value_allowed_flag
685        size_of_exp_golomb(self.pic_width_in_mbs_minus1) +
686        size_of_exp_golomb(self.pic_height_in_map_units_minus1) +
687        1 + // frame_mbs_only_flag
688        self.mb_adaptive_frame_field_flag.is_some() as u64 +
689        1 + // direct_8x8_inference_flag
690        1 + // frame_cropping_flag
691        self.frame_crop_info.as_ref().map_or(0, |frame| frame.bitsize()) +
692        1 + // vui_parameters_present_flag
693        if matches!(
694            (&self.sample_aspect_ratio, &self.overscan_appropriate_flag, &self.color_config, &self.chroma_sample_loc, &self.timing_info),
695            (None, None, None, None, None)
696        ) {
697            0
698        } else {
699            self.sample_aspect_ratio.as_ref().map_or(1, |sar| 1 + sar.bitsize()) +
700            self.overscan_appropriate_flag.map_or(1, |_| 2) +
701            self.color_config.as_ref().map_or(1, |color| 1 + color.bitsize()) +
702            self.chroma_sample_loc.as_ref().map_or(1, |chroma| 1 + chroma.bitsize()) +
703            self.timing_info.as_ref().map_or(1, |timing| 1 + timing.bitsize())
704        }).div_ceil(8)
705    }
706
707    /// The height as a u64. This is computed from other fields, and isn't directly set.
708    ///
709    /// `height = ((2 - frame_mbs_only_flag as u64) * (pic_height_in_map_units_minus1 + 1) * 16) -
710    /// frame_crop_bottom_offset * 2 - frame_crop_top_offset * 2`
711    ///
712    /// We don't directly store `frame_mbs_only_flag` since we can tell if it's set:
713    /// If `mb_adaptive_frame_field_flag` is None, then `frame_mbs_only_flag` is set (1).
714    /// Otherwise `mb_adaptive_frame_field_flag` unset (0).
715    pub fn height(&self) -> u64 {
716        let base_height =
717            (2 - self.mb_adaptive_frame_field_flag.is_none() as u64) * (self.pic_height_in_map_units_minus1 + 1) * 16;
718
719        self.frame_crop_info.as_ref().map_or(base_height, |crop| {
720            base_height - (crop.frame_crop_top_offset + crop.frame_crop_bottom_offset) * 2
721        })
722    }
723
724    /// The width as a u64. This is computed from other fields, and isn't directly set.
725    ///
726    /// `width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_right_offset * 2 - frame_crop_left_offset * 2`
727    pub fn width(&self) -> u64 {
728        let base_width = (self.pic_width_in_mbs_minus1 + 1) * 16;
729
730        self.frame_crop_info.as_ref().map_or(base_width, |crop| {
731            base_width - (crop.frame_crop_left_offset + crop.frame_crop_right_offset) * 2
732        })
733    }
734
735    /// Returns the frame rate as a f64.
736    ///
737    /// If `timing_info_present_flag` is set, then the `frame_rate` will be computed, and
738    /// if `num_units_in_tick` is nonzero, then the framerate will be:
739    /// `frame_rate = time_scale as f64 / (2.0 * num_units_in_tick as f64)`
740    pub fn frame_rate(&self) -> Option<f64> {
741        self.timing_info.as_ref().map(|timing| timing.frame_rate())
742    }
743}
744
745#[cfg(test)]
746#[cfg_attr(all(test, coverage_nightly), coverage(off))]
747mod tests {
748    use std::io;
749
750    use scuffle_bytes_util::BitWriter;
751    use scuffle_expgolomb::{BitWriterExpGolombExt, size_of_exp_golomb, size_of_signed_exp_golomb};
752
753    use crate::sps::Sps;
754
755    #[test]
756    fn test_parse_sps_set_forbidden_bit() {
757        let mut sps = Vec::new();
758        let mut writer = BitWriter::new(&mut sps);
759
760        writer.write_bit(true).unwrap(); // sets the forbidden bit
761        writer.finish().unwrap();
762
763        let result = Sps::parse(std::io::Cursor::new(sps));
764
765        assert!(result.is_err());
766        let err = result.unwrap_err();
767
768        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
769        assert_eq!(err.to_string(), "Forbidden zero bit is set");
770    }
771
772    #[test]
773    fn test_parse_sps_invalid_nal() {
774        let mut sps = Vec::new();
775        let mut writer = BitWriter::new(&mut sps);
776
777        writer.write_bit(false).unwrap(); // forbidden zero bit must be unset
778        writer.write_bits(0b00, 2).unwrap(); // nal_ref_idc is 00
779        writer.write_bits(0b000, 3).unwrap(); // set nal_unit_type to something that isn't 7
780        writer.finish().unwrap();
781
782        let result = Sps::parse(std::io::Cursor::new(sps));
783
784        assert!(result.is_err());
785        let err = result.unwrap_err();
786
787        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
788        assert_eq!(err.to_string(), "NAL unit type is not SPS");
789    }
790
791    #[test]
792    fn test_parse_build_sps_4k_144fps() {
793        let mut sps = Vec::new();
794        let mut writer = BitWriter::new(&mut sps);
795
796        // forbidden zero bit must be unset
797        writer.write_bit(false).unwrap();
798        // nal_ref_idc is 0
799        writer.write_bits(0, 2).unwrap();
800        // nal_unit_type must be 7
801        writer.write_bits(7, 5).unwrap();
802
803        // profile_idc = 100
804        writer.write_bits(100, 8).unwrap();
805        // constraint_setn_flags all false
806        writer.write_bits(0, 8).unwrap();
807        // level_idc = 0
808        writer.write_bits(0, 8).unwrap();
809
810        // seq_parameter_set_id is expg
811        writer.write_exp_golomb(0).unwrap();
812
813        // sps ext
814        // chroma_format_idc is expg
815        writer.write_exp_golomb(0).unwrap();
816        // bit_depth_luma_minus8 is expg
817        writer.write_exp_golomb(0).unwrap();
818        // bit_depth_chroma_minus8 is expg
819        writer.write_exp_golomb(0).unwrap();
820        // qpprime
821        writer.write_bit(false).unwrap();
822        // seq_scaling_matrix_present_flag
823        writer.write_bit(false).unwrap();
824
825        // back to sps
826        // log2_max_frame_num_minus4 is expg
827        writer.write_exp_golomb(0).unwrap();
828        // pic_order_cnt_type is expg
829        writer.write_exp_golomb(0).unwrap();
830        // log2_max_pic_order_cnt_lsb_minus4 is expg
831        writer.write_exp_golomb(0).unwrap();
832
833        // max_num_ref_frames is expg
834        writer.write_exp_golomb(0).unwrap();
835        // gaps_in_frame_num_value_allowed_flag
836        writer.write_bit(false).unwrap();
837        // 3840 width:
838        // 3840 = (p + 1) * 16 - 2 * offset1 - 2 * offset2
839        // we set offset1 and offset2 to both be 0 later
840        // 3840 = (p + 1) * 16
841        // p = 239
842        writer.write_exp_golomb(239).unwrap();
843        // we want 2160 height:
844        // 2160 = ((2 - m) * (p + 1) * 16) - 2 * offset1 - 2 * offset2
845        // we set offset1 and offset2 to both be 0 later
846        // m is frame_mbs_only_flag which we set to 1 later
847        // 2160 = (2 - 1) * (p + 1) * 16
848        // 2160 = (p + 1) * 16
849        // p = 134
850        writer.write_exp_golomb(134).unwrap();
851
852        // frame_mbs_only_flag
853        writer.write_bit(true).unwrap();
854
855        // direct_8x8_inference_flag
856        writer.write_bit(false).unwrap();
857        // frame_cropping_flag
858        writer.write_bit(false).unwrap();
859
860        // vui_parameters_present_flag
861        writer.write_bit(true).unwrap();
862
863        // enter vui to set the framerate
864        // aspect_ratio_info_present_flag
865        writer.write_bit(true).unwrap();
866        // we want square (1:1) for 16:9 for 4k w/o overscan
867        // aspect_ratio_idc
868        writer.write_bits(1, 8).unwrap();
869
870        // overscan_info_present_flag
871        writer.write_bit(true).unwrap();
872        // we dont want overscan
873        // overscan_appropriate_flag
874        writer.write_bit(false).unwrap();
875
876        // video_signal_type_present_flag
877        writer.write_bit(false).unwrap();
878        // chroma_loc_info_present_flag
879        writer.write_bit(false).unwrap();
880
881        // timing_info_present_flag
882        writer.write_bit(true).unwrap();
883        // we can set this to 100 for example
884        // num_units_in_tick is a u32
885        writer.write_bits(100, 32).unwrap();
886        // fps = time_scale / (2 * num_units_in_tick)
887        // since we want 144 fps:
888        // 144 = time_scale / (2 * 100)
889        // 28800 = time_scale
890        // time_scale is a u32
891        writer.write_bits(28800, 32).unwrap();
892        writer.finish().unwrap();
893
894        let result = Sps::parse(std::io::Cursor::new(sps)).unwrap();
895
896        insta::assert_debug_snapshot!(result, @r"
897        Sps {
898            nal_ref_idc: 0,
899            nal_unit_type: NALUnitType::SPS,
900            profile_idc: 100,
901            constraint_set0_flag: false,
902            constraint_set1_flag: false,
903            constraint_set2_flag: false,
904            constraint_set3_flag: false,
905            constraint_set4_flag: false,
906            constraint_set5_flag: false,
907            level_idc: 0,
908            seq_parameter_set_id: 0,
909            ext: Some(
910                SpsExtended {
911                    chroma_format_idc: 0,
912                    separate_color_plane_flag: false,
913                    bit_depth_luma_minus8: 0,
914                    bit_depth_chroma_minus8: 0,
915                    qpprime_y_zero_transform_bypass_flag: false,
916                    scaling_matrix: [],
917                },
918            ),
919            log2_max_frame_num_minus4: 0,
920            pic_order_cnt_type: 0,
921            log2_max_pic_order_cnt_lsb_minus4: Some(
922                0,
923            ),
924            pic_order_cnt_type1: None,
925            max_num_ref_frames: 0,
926            gaps_in_frame_num_value_allowed_flag: false,
927            pic_width_in_mbs_minus1: 239,
928            pic_height_in_map_units_minus1: 134,
929            mb_adaptive_frame_field_flag: None,
930            direct_8x8_inference_flag: false,
931            frame_crop_info: None,
932            sample_aspect_ratio: Some(
933                SarDimensions {
934                    aspect_ratio_idc: AspectRatioIdc::Square,
935                    sar_width: 0,
936                    sar_height: 0,
937                },
938            ),
939            overscan_appropriate_flag: Some(
940                false,
941            ),
942            color_config: None,
943            chroma_sample_loc: None,
944            timing_info: Some(
945                TimingInfo {
946                    num_units_in_tick: 100,
947                    time_scale: 28800,
948                },
949            ),
950        }
951        ");
952
953        assert_eq!(Some(144.0), result.frame_rate());
954        assert_eq!(3840, result.width());
955        assert_eq!(2160, result.height());
956
957        // create a writer for the builder
958        let mut buf = Vec::new();
959        let mut writer2 = BitWriter::new(&mut buf);
960
961        // build from the example sps
962        result.build(&mut writer2).unwrap();
963        writer2.finish().unwrap();
964
965        // sometimes bits can get lost because we save
966        // some space with how the SPS is rebuilt.
967        // so we can just confirm that they're the same
968        // by rebuilding it.
969        let reduced = Sps::parse(std::io::Cursor::new(&buf)).unwrap(); // <- this is where things break
970        assert_eq!(reduced, result);
971
972        // now we can check that the bitstream from
973        // the reduced version should be the same
974        let mut reduced_buf = Vec::new();
975        let mut writer3 = BitWriter::new(&mut reduced_buf);
976
977        reduced.build(&mut writer3).unwrap();
978        writer3.finish().unwrap();
979        assert_eq!(reduced_buf, buf);
980
981        // now we can check the size:
982        assert_eq!(reduced.size(), result.size());
983    }
984
985    #[test]
986    fn test_parse_build_sps_1080_480fps_scaling_matrix() {
987        let mut sps = Vec::new();
988        let mut writer = BitWriter::new(&mut sps);
989
990        // forbidden zero bit must be unset
991        writer.write_bit(false).unwrap();
992        // nal_ref_idc is 0
993        writer.write_bits(0, 2).unwrap();
994        // nal_unit_type must be 7
995        writer.write_bits(7, 5).unwrap();
996
997        // profile_idc = 44
998        writer.write_bits(44, 8).unwrap();
999        // constraint_setn_flags all false
1000        writer.write_bits(0, 8).unwrap();
1001        // level_idc = 0
1002        writer.write_bits(0, 8).unwrap();
1003        // seq_parameter_set_id is expg
1004        writer.write_exp_golomb(0).unwrap();
1005
1006        // sps ext
1007        // we want to try out chroma_format_idc = 3
1008        // chroma_format_idc is expg
1009        writer.write_exp_golomb(3).unwrap();
1010        // separate_color_plane_flag
1011        writer.write_bit(false).unwrap();
1012        // bit_depth_luma_minus8 is expg
1013        writer.write_exp_golomb(0).unwrap();
1014        // bit_depth_chroma_minus8 is expg
1015        writer.write_exp_golomb(0).unwrap();
1016        // qpprime
1017        writer.write_bit(false).unwrap();
1018        // we want to simulate a scaling matrix
1019        // seq_scaling_matrix_present_flag
1020        writer.write_bit(true).unwrap();
1021
1022        // enter scaling matrix, we loop 12 times since
1023        // chroma_format_idc = 3.
1024        // loop 1 of 12
1025        // true to enter if statement
1026        writer.write_bit(true).unwrap();
1027        // i < 6, so size is 16, so we loop 16 times
1028        // sub-loop 1 of 16
1029        // delta_scale is a SIGNED expg so we can try out
1030        // entering -4 so next_scale becomes 8 + 4 = 12
1031        writer.write_signed_exp_golomb(4).unwrap();
1032        // sub-loop 2 of 16
1033        // delta_scale is a SIGNED expg so we can try out
1034        // entering -12 so next scale becomes 12 - 12 = 0
1035        writer.write_signed_exp_golomb(-12).unwrap();
1036        // at this point next_scale is 0, which means we break
1037        // loop 2 through 12
1038        // we don't need to try anything else so we can just skip through them by writing `0` bit 11 times.
1039        writer.write_bits(0, 11).unwrap();
1040
1041        // back to sps
1042        // log2_max_frame_num_minus4 is expg
1043        writer.write_exp_golomb(0).unwrap();
1044        // we can try setting pic_order_cnt_type to 1
1045        // pic_order_cnt_type is expg
1046        writer.write_exp_golomb(1).unwrap();
1047
1048        // delta_pic_order_always_zero_flag
1049        writer.write_bit(false).unwrap();
1050        // offset_for_non_ref_pic
1051        writer.write_bit(true).unwrap();
1052        // offset_for_top_to_bottom_field
1053        writer.write_bit(true).unwrap();
1054        // num_ref_frames_in_pic_order_cnt_cycle is expg
1055        writer.write_exp_golomb(1).unwrap();
1056        // loop num_ref_frames_in_pic_order_cnt_cycle times (1)
1057        // offset_for_ref_frame is expg
1058        writer.write_exp_golomb(0).unwrap();
1059
1060        // max_num_ref_frames is expg
1061        writer.write_exp_golomb(0).unwrap();
1062        // gaps_in_frame_num_value_allowed_flag
1063        writer.write_bit(false).unwrap();
1064        // 1920 width:
1065        // 1920 = (p + 1) * 16 - 2 * offset1 - 2 * offset2
1066        // we set offset1 and offset2 to both be 4 later
1067        // 1920 = (p + 1) * 16 - 2 * 4 - 2 * 4
1068        // 1920 = (p + 1) * 16 - 16
1069        // p = 120
1070        // pic_width_in_mbs_minus1 is expg
1071        writer.write_exp_golomb(120).unwrap();
1072        // we want 1080 height:
1073        // 1080 = ((2 - m) * (p + 1) * 16) - 2 * offset1 - 2 * offset2
1074        // we set offset1 and offset2 to both be 2 later
1075        // m is frame_mbs_only_flag which we set to 0 later
1076        // 1080 = (2 - 0) * (p + 1) * 16 - 2 * 2 - 2 * 2
1077        // 1080 = 2 * (p + 1) * 16 - 8
1078        // p = 33
1079        // pic_height_in_map_units_minus1 is expg
1080        writer.write_exp_golomb(33).unwrap();
1081
1082        // frame_mbs_only_flag
1083        writer.write_bit(false).unwrap();
1084        // mb_adaptive_frame_field_flag
1085        writer.write_bit(false).unwrap();
1086
1087        // direct_8x8_inference_flag
1088        writer.write_bit(false).unwrap();
1089        // frame_cropping_flag
1090        writer.write_bit(true).unwrap();
1091
1092        // frame_crop_left_offset is expg
1093        writer.write_exp_golomb(4).unwrap();
1094        // frame_crop_left_offset is expg
1095        writer.write_exp_golomb(4).unwrap();
1096        // frame_crop_left_offset is expg
1097        writer.write_exp_golomb(2).unwrap();
1098        // frame_crop_left_offset is expg
1099        writer.write_exp_golomb(2).unwrap();
1100
1101        // vui_parameters_present_flag
1102        writer.write_bit(true).unwrap();
1103
1104        // enter vui to set the framerate
1105        // aspect_ratio_info_present_flag
1106        writer.write_bit(true).unwrap();
1107        // we can try 255 to set the sar_width and sar_height
1108        // aspect_ratio_idc
1109        writer.write_bits(255, 8).unwrap();
1110        // sar_width
1111        writer.write_bits(0, 16).unwrap();
1112        // sar_height
1113        writer.write_bits(0, 16).unwrap();
1114
1115        // overscan_info_present_flag
1116        writer.write_bit(false).unwrap();
1117
1118        // video_signal_type_present_flag
1119        writer.write_bit(true).unwrap();
1120        // video_format
1121        writer.write_bits(0, 3).unwrap();
1122        // video_full_range_flag
1123        writer.write_bit(false).unwrap();
1124        // color_description_present_flag
1125        writer.write_bit(true).unwrap();
1126        // color_primaries
1127        writer.write_bits(1, 8).unwrap();
1128        // transfer_characteristics
1129        writer.write_bits(1, 8).unwrap();
1130        // matrix_coefficients
1131        writer.write_bits(1, 8).unwrap();
1132
1133        // chroma_loc_info_present_flag
1134        writer.write_bit(false).unwrap();
1135
1136        // timing_info_present_flag
1137        writer.write_bit(true).unwrap();
1138        // we can set this to 1000 for example
1139        // num_units_in_tick is a u32
1140        writer.write_bits(1000, 32).unwrap();
1141        // fps = time_scale / (2 * num_units_in_tick)
1142        // since we want 480 fps:
1143        // 480 = time_scale / (2 * 1000)
1144        // 960 000 = time_scale
1145        // time_scale is a u32
1146        writer.write_bits(960000, 32).unwrap();
1147        writer.finish().unwrap();
1148
1149        let result = Sps::parse(std::io::Cursor::new(&sps)).unwrap();
1150
1151        insta::assert_debug_snapshot!(result, @r"
1152        Sps {
1153            nal_ref_idc: 0,
1154            nal_unit_type: NALUnitType::SPS,
1155            profile_idc: 44,
1156            constraint_set0_flag: false,
1157            constraint_set1_flag: false,
1158            constraint_set2_flag: false,
1159            constraint_set3_flag: false,
1160            constraint_set4_flag: false,
1161            constraint_set5_flag: false,
1162            level_idc: 0,
1163            seq_parameter_set_id: 0,
1164            ext: Some(
1165                SpsExtended {
1166                    chroma_format_idc: 3,
1167                    separate_color_plane_flag: false,
1168                    bit_depth_luma_minus8: 0,
1169                    bit_depth_chroma_minus8: 0,
1170                    qpprime_y_zero_transform_bypass_flag: false,
1171                    scaling_matrix: [
1172                        [
1173                            4,
1174                            -12,
1175                        ],
1176                        [],
1177                        [],
1178                        [],
1179                        [],
1180                        [],
1181                        [],
1182                        [],
1183                        [],
1184                        [],
1185                        [],
1186                        [],
1187                    ],
1188                },
1189            ),
1190            log2_max_frame_num_minus4: 0,
1191            pic_order_cnt_type: 1,
1192            log2_max_pic_order_cnt_lsb_minus4: None,
1193            pic_order_cnt_type1: Some(
1194                PicOrderCountType1 {
1195                    delta_pic_order_always_zero_flag: false,
1196                    offset_for_non_ref_pic: 0,
1197                    offset_for_top_to_bottom_field: 0,
1198                    num_ref_frames_in_pic_order_cnt_cycle: 1,
1199                    offset_for_ref_frame: [
1200                        0,
1201                    ],
1202                },
1203            ),
1204            max_num_ref_frames: 0,
1205            gaps_in_frame_num_value_allowed_flag: false,
1206            pic_width_in_mbs_minus1: 120,
1207            pic_height_in_map_units_minus1: 33,
1208            mb_adaptive_frame_field_flag: Some(
1209                false,
1210            ),
1211            direct_8x8_inference_flag: false,
1212            frame_crop_info: Some(
1213                FrameCropInfo {
1214                    frame_crop_left_offset: 4,
1215                    frame_crop_right_offset: 4,
1216                    frame_crop_top_offset: 2,
1217                    frame_crop_bottom_offset: 2,
1218                },
1219            ),
1220            sample_aspect_ratio: Some(
1221                SarDimensions {
1222                    aspect_ratio_idc: AspectRatioIdc::ExtendedSar,
1223                    sar_width: 0,
1224                    sar_height: 0,
1225                },
1226            ),
1227            overscan_appropriate_flag: None,
1228            color_config: Some(
1229                ColorConfig {
1230                    video_format: VideoFormat::Component,
1231                    video_full_range_flag: false,
1232                    color_primaries: 1,
1233                    transfer_characteristics: 1,
1234                    matrix_coefficients: 1,
1235                },
1236            ),
1237            chroma_sample_loc: None,
1238            timing_info: Some(
1239                TimingInfo {
1240                    num_units_in_tick: 1000,
1241                    time_scale: 960000,
1242                },
1243            ),
1244        }
1245        ");
1246
1247        assert_eq!(Some(480.0), result.frame_rate());
1248        assert_eq!(1920, result.width());
1249        assert_eq!(1080, result.height());
1250
1251        // create a writer for the builder
1252        let mut buf = Vec::new();
1253        result.build(&mut buf).unwrap();
1254
1255        assert_eq!(buf, sps);
1256    }
1257
1258    #[test]
1259    fn test_parse_build_sps_1280x800_0fps() {
1260        let mut sps = Vec::new();
1261        let mut writer = BitWriter::new(&mut sps);
1262
1263        // forbidden zero bit must be unset
1264        writer.write_bit(false).unwrap();
1265        // nal_ref_idc is 0
1266        writer.write_bits(0, 2).unwrap();
1267        // nal_unit_type must be 7
1268        writer.write_bits(7, 5).unwrap();
1269
1270        // profile_idc = 77
1271        writer.write_bits(77, 8).unwrap();
1272        // constraint_setn_flags all false
1273        writer.write_bits(0, 8).unwrap();
1274        // level_idc = 0
1275        writer.write_bits(0, 8).unwrap();
1276
1277        // seq_parameter_set_id is expg
1278        writer.write_exp_golomb(0).unwrap();
1279
1280        // profile_idc = 77 means we skip the sps_ext
1281        // log2_max_frame_num_minus4 is expg
1282        writer.write_exp_golomb(0).unwrap();
1283        // pic_order_cnt_type is expg
1284        writer.write_exp_golomb(0).unwrap();
1285        // log2_max_pic_order_cnt_lsb_minus4
1286        writer.write_exp_golomb(0).unwrap();
1287
1288        // max_num_ref_frames is expg
1289        writer.write_exp_golomb(0).unwrap();
1290        // gaps_in_frame_num_value_allowed_flag
1291        writer.write_bit(false).unwrap();
1292        // 1280 width:
1293        // 1280 = (p + 1) * 16 - 2 * offset1 - 2 * offset2
1294        // we set offset1 and offset2 to both be 0 later
1295        // 1280 = (p + 1) * 16
1296        // p = 79
1297        writer.write_exp_golomb(79).unwrap();
1298        // we want 800 height:
1299        // 800 = ((2 - m) * (p + 1) * 16) - 2 * offset1 - 2 * offset2
1300        // we set offset1 and offset2 to both be 0 later
1301        // m is frame_mbs_only_flag which we set to 1 later
1302        // 800 = (2 - 1) * (p + 1) * 16 - 2 * 0 - 2 * 0
1303        // 800 = (p + 1) * 16
1304        // p = 49
1305        writer.write_exp_golomb(49).unwrap();
1306
1307        // frame_mbs_only_flag
1308        writer.write_bit(true).unwrap();
1309
1310        // direct_8x8_inference_flag
1311        writer.write_bit(false).unwrap();
1312        // frame_cropping_flag
1313        writer.write_bit(false).unwrap();
1314
1315        // vui_parameters_present_flag
1316        writer.write_bit(true).unwrap();
1317
1318        // enter vui to set the framerate
1319        // aspect_ratio_info_present_flag
1320        writer.write_bit(false).unwrap();
1321
1322        // overscan_info_present_flag
1323        writer.write_bit(false).unwrap();
1324
1325        // video_signal_type_present_flag
1326        writer.write_bit(true).unwrap();
1327        // video_format
1328        writer.write_bits(0, 3).unwrap();
1329        // video_full_range_flag
1330        writer.write_bit(false).unwrap();
1331        // color_description_present_flag
1332        writer.write_bit(false).unwrap();
1333
1334        // chroma_loc_info_present_flag
1335        writer.write_bit(true).unwrap();
1336        // chroma_sample_loc_type_top_field is expg
1337        writer.write_exp_golomb(2).unwrap();
1338        // chroma_sample_loc_type_bottom_field is expg
1339        writer.write_exp_golomb(2).unwrap();
1340
1341        // timing_info_present_flag
1342        writer.write_bit(false).unwrap();
1343        writer.finish().unwrap();
1344
1345        let result = Sps::parse(std::io::Cursor::new(&sps)).unwrap();
1346
1347        insta::assert_debug_snapshot!(result, @r"
1348        Sps {
1349            nal_ref_idc: 0,
1350            nal_unit_type: NALUnitType::SPS,
1351            profile_idc: 77,
1352            constraint_set0_flag: false,
1353            constraint_set1_flag: false,
1354            constraint_set2_flag: false,
1355            constraint_set3_flag: false,
1356            constraint_set4_flag: false,
1357            constraint_set5_flag: false,
1358            level_idc: 0,
1359            seq_parameter_set_id: 0,
1360            ext: None,
1361            log2_max_frame_num_minus4: 0,
1362            pic_order_cnt_type: 0,
1363            log2_max_pic_order_cnt_lsb_minus4: Some(
1364                0,
1365            ),
1366            pic_order_cnt_type1: None,
1367            max_num_ref_frames: 0,
1368            gaps_in_frame_num_value_allowed_flag: false,
1369            pic_width_in_mbs_minus1: 79,
1370            pic_height_in_map_units_minus1: 49,
1371            mb_adaptive_frame_field_flag: None,
1372            direct_8x8_inference_flag: false,
1373            frame_crop_info: None,
1374            sample_aspect_ratio: None,
1375            overscan_appropriate_flag: None,
1376            color_config: Some(
1377                ColorConfig {
1378                    video_format: VideoFormat::Component,
1379                    video_full_range_flag: false,
1380                    color_primaries: 2,
1381                    transfer_characteristics: 2,
1382                    matrix_coefficients: 2,
1383                },
1384            ),
1385            chroma_sample_loc: Some(
1386                ChromaSampleLoc {
1387                    chroma_sample_loc_type_top_field: 2,
1388                    chroma_sample_loc_type_bottom_field: 2,
1389                },
1390            ),
1391            timing_info: None,
1392        }
1393        ");
1394
1395        assert_eq!(None, result.frame_rate());
1396        assert_eq!(1280, result.width());
1397        assert_eq!(800, result.height());
1398
1399        // create a writer for the builder
1400        let mut buf = Vec::new();
1401        result.build(&mut buf).unwrap();
1402
1403        assert_eq!(buf, sps);
1404    }
1405
1406    #[test]
1407    fn test_parse_build_sps_pic_order_cnt_type_2() {
1408        let mut sps = Vec::new();
1409        let mut writer = BitWriter::new(&mut sps);
1410
1411        // forbidden zero bit must be unset
1412        writer.write_bit(false).unwrap();
1413        // nal_ref_idc is 0
1414        writer.write_bits(0, 2).unwrap();
1415        // nal_unit_type must be 7
1416        writer.write_bits(7, 5).unwrap();
1417
1418        // profile_idc = 77
1419        writer.write_bits(77, 8).unwrap();
1420        // constraint_setn_flags all false
1421        writer.write_bits(0, 8).unwrap();
1422        // level_idc = 0
1423        writer.write_bits(0, 8).unwrap();
1424
1425        // seq_parameter_set_id is expg
1426        writer.write_exp_golomb(0).unwrap();
1427
1428        // profile_idc = 77 means we skip the sps_ext
1429        // log2_max_frame_num_minus4 is expg
1430        writer.write_exp_golomb(0).unwrap();
1431        // pic_order_cnt_type is expg
1432        writer.write_exp_golomb(2).unwrap();
1433        // log2_max_pic_order_cnt_lsb_minus4
1434        writer.write_exp_golomb(0).unwrap();
1435
1436        // max_num_ref_frames is expg
1437        writer.write_exp_golomb(0).unwrap();
1438        // gaps_in_frame_num_value_allowed_flag
1439        writer.write_bit(false).unwrap();
1440        writer.write_exp_golomb(1).unwrap();
1441        writer.write_exp_golomb(2).unwrap();
1442
1443        // frame_mbs_only_flag
1444        writer.write_bit(true).unwrap();
1445
1446        // direct_8x8_inference_flag
1447        writer.write_bit(false).unwrap();
1448        // frame_cropping_flag
1449        writer.write_bit(false).unwrap();
1450
1451        // enter vui to set redundant parameters so they get reduced
1452        // vui_parameters_present_flag
1453        writer.write_bit(false).unwrap();
1454        writer.finish().unwrap();
1455
1456        let result = Sps::parse(std::io::Cursor::new(&sps)).unwrap();
1457
1458        insta::assert_debug_snapshot!(result, @r"
1459        Sps {
1460            nal_ref_idc: 0,
1461            nal_unit_type: NALUnitType::SPS,
1462            profile_idc: 77,
1463            constraint_set0_flag: false,
1464            constraint_set1_flag: false,
1465            constraint_set2_flag: false,
1466            constraint_set3_flag: false,
1467            constraint_set4_flag: false,
1468            constraint_set5_flag: false,
1469            level_idc: 0,
1470            seq_parameter_set_id: 0,
1471            ext: None,
1472            log2_max_frame_num_minus4: 0,
1473            pic_order_cnt_type: 2,
1474            log2_max_pic_order_cnt_lsb_minus4: None,
1475            pic_order_cnt_type1: None,
1476            max_num_ref_frames: 0,
1477            gaps_in_frame_num_value_allowed_flag: true,
1478            pic_width_in_mbs_minus1: 3,
1479            pic_height_in_map_units_minus1: 0,
1480            mb_adaptive_frame_field_flag: None,
1481            direct_8x8_inference_flag: true,
1482            frame_crop_info: None,
1483            sample_aspect_ratio: None,
1484            overscan_appropriate_flag: None,
1485            color_config: None,
1486            chroma_sample_loc: None,
1487            timing_info: None,
1488        }
1489        ");
1490
1491        assert_eq!(None, result.frame_rate());
1492        assert_eq!(result.size(), 7);
1493
1494        // create a writer for the builder
1495        let mut buf = Vec::new();
1496        result.build_with_emulation_prevention(&mut buf).unwrap();
1497
1498        assert_eq!(buf, sps);
1499    }
1500
1501    #[test]
1502    fn test_parse_sps_chroma_loc_info_error() {
1503        let mut sps = Vec::new();
1504        let mut writer = BitWriter::new(&mut sps);
1505
1506        // forbidden zero bit must be unset
1507        writer.write_bit(false).unwrap();
1508        // nal_ref_idc is 0
1509        writer.write_bits(0, 2).unwrap();
1510        // nal_unit_type must be 7
1511        writer.write_bits(7, 5).unwrap();
1512
1513        // profile_idc = 100
1514        writer.write_bits(100, 8).unwrap();
1515        // constraint_setn_flags all false
1516        writer.write_bits(0, 8).unwrap();
1517        // level_idc = 0
1518        writer.write_bits(0, 8).unwrap();
1519
1520        // seq_parameter_set_id is expg
1521        writer.write_exp_golomb(0).unwrap();
1522
1523        // ext
1524        // chroma_format_idc is expg
1525        writer.write_exp_golomb(0).unwrap();
1526        // bit_depth_luma_minus8 is expg
1527        writer.write_exp_golomb(0).unwrap();
1528        // bit_depth_chroma_minus8 is expg
1529        writer.write_exp_golomb(0).unwrap();
1530        // qpprime
1531        writer.write_bit(false).unwrap();
1532        // seq_scaling_matrix_present_flag
1533        writer.write_bit(false).unwrap();
1534
1535        // return to sps
1536        // log2_max_frame_num_minus4 is expg
1537        writer.write_exp_golomb(0).unwrap();
1538        // pic_order_cnt_type is expg
1539        writer.write_exp_golomb(0).unwrap();
1540        // log2_max_pic_order_cnt_lsb_minus4
1541        writer.write_exp_golomb(0).unwrap();
1542
1543        // max_num_ref_frames is expg
1544        writer.write_exp_golomb(0).unwrap();
1545        // gaps_in_frame_num_value_allowed_flag
1546        writer.write_bit(false).unwrap();
1547        // 1280 width:
1548        // 1280 = (p + 1) * 16 - 2 * offset1 - 2 * offset2
1549        // we set offset1 and offset2 to both be 0 later
1550        // 1280 = (p + 1) * 16
1551        // p = 79
1552        writer.write_exp_golomb(79).unwrap();
1553        // we want 800 height:
1554        // 800 = ((2 - m) * (p + 1) * 16) - 2 * offset1 - 2 * offset2
1555        // we set offset1 and offset2 to both be 0 later
1556        // m is frame_mbs_only_flag which we set to 1 later
1557        // 800 = (2 - 1) * (p + 1) * 16 - 2 * 0 - 2 * 0
1558        // 800 = 2 * (p + 1) * 16 - 8
1559        // p = 33
1560        writer.write_exp_golomb(33).unwrap();
1561
1562        // frame_mbs_only_flag
1563        writer.write_bit(false).unwrap();
1564        // mb_adaptive_frame_field_flag
1565        writer.write_bit(false).unwrap();
1566
1567        // direct_8x8_inference_flag
1568        writer.write_bit(false).unwrap();
1569        // frame_cropping_flag
1570        writer.write_bit(false).unwrap();
1571
1572        // vui_parameters_present_flag
1573        writer.write_bit(true).unwrap();
1574
1575        // enter vui to set the framerate
1576        // aspect_ratio_info_present_flag
1577        writer.write_bit(false).unwrap();
1578
1579        // overscan_info_present_flag
1580        writer.write_bit(false).unwrap();
1581
1582        // video_signal_type_present_flag
1583        writer.write_bit(true).unwrap();
1584        // video_format
1585        writer.write_bits(0, 3).unwrap();
1586        // video_full_range_flag
1587        writer.write_bit(false).unwrap();
1588        // color_description_present_flag
1589        writer.write_bit(false).unwrap();
1590
1591        // chroma_loc_info_present_flag
1592        writer.write_bit(true).unwrap();
1593        writer.finish().unwrap();
1594
1595        let result = Sps::parse(std::io::Cursor::new(&sps));
1596
1597        assert!(result.is_err());
1598        let err = result.unwrap_err();
1599        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
1600        assert_eq!(
1601            err.to_string(),
1602            "chroma_loc_info_present_flag cannot be set to 1 when chroma_format_idc is not 1"
1603        );
1604    }
1605
1606    #[test]
1607    fn test_invalid_num_units_in_tick() {
1608        let mut sps = Vec::new();
1609        let mut writer = BitWriter::new(&mut sps);
1610
1611        // forbidden zero bit must be unset
1612        writer.write_bit(false).unwrap();
1613        // nal_ref_idc is 0
1614        writer.write_bits(0, 2).unwrap();
1615        // nal_unit_type must be 7
1616        writer.write_bits(7, 5).unwrap();
1617
1618        // profile_idc = 100
1619        writer.write_bits(100, 8).unwrap();
1620        // constraint_setn_flags all false
1621        writer.write_bits(0, 8).unwrap();
1622        // level_idc = 0
1623        writer.write_bits(0, 8).unwrap();
1624
1625        // seq_parameter_set_id is expg
1626        writer.write_exp_golomb(0).unwrap();
1627
1628        // ext
1629        // chroma_format_idc is expg
1630        writer.write_exp_golomb(0).unwrap();
1631        // bit_depth_luma_minus8 is expg
1632        writer.write_exp_golomb(0).unwrap();
1633        // bit_depth_chroma_minus8 is expg
1634        writer.write_exp_golomb(0).unwrap();
1635        // qpprime
1636        writer.write_bit(false).unwrap();
1637        // seq_scaling_matrix_present_flag
1638        writer.write_bit(false).unwrap();
1639
1640        // return to sps
1641        // log2_max_frame_num_minus4 is expg
1642        writer.write_exp_golomb(0).unwrap();
1643        // pic_order_cnt_type is expg
1644        writer.write_exp_golomb(0).unwrap();
1645        // log2_max_pic_order_cnt_lsb_minus4
1646        writer.write_exp_golomb(0).unwrap();
1647
1648        // max_num_ref_frames is expg
1649        writer.write_exp_golomb(0).unwrap();
1650        // gaps_in_frame_num_value_allowed_flag
1651        writer.write_bit(false).unwrap();
1652        // 1280 width:
1653        // 1280 = (p + 1) * 16 - 2 * offset1 - 2 * offset2
1654        // we set offset1 and offset2 to both be 0 later
1655        // 1280 = (p + 1) * 16
1656        // p = 79
1657        writer.write_exp_golomb(79).unwrap();
1658        // we want 800 height:
1659        // 800 = ((2 - m) * (p + 1) * 16) - 2 * offset1 - 2 * offset2
1660        // we set offset1 and offset2 to both be 0 later
1661        // m is frame_mbs_only_flag which we set to 1 later
1662        // 800 = (2 - 1) * (p + 1) * 16 - 2 * 0 - 2 * 0
1663        // 800 = 2 * (p + 1) * 16 - 8
1664        // p = 33
1665        writer.write_exp_golomb(33).unwrap();
1666
1667        // frame_mbs_only_flag
1668        writer.write_bit(false).unwrap();
1669        // mb_adaptive_frame_field_flag
1670        writer.write_bit(false).unwrap();
1671
1672        // direct_8x8_inference_flag
1673        writer.write_bit(false).unwrap();
1674        // frame_cropping_flag
1675        writer.write_bit(false).unwrap();
1676
1677        // vui_parameters_present_flag
1678        writer.write_bit(true).unwrap();
1679
1680        // enter vui to set the framerate
1681        // aspect_ratio_info_present_flag
1682        writer.write_bit(false).unwrap();
1683
1684        // overscan_info_present_flag
1685        writer.write_bit(false).unwrap();
1686
1687        // video_signal_type_present_flag
1688        writer.write_bit(true).unwrap();
1689        // video_format
1690        writer.write_bits(0, 3).unwrap();
1691        // video_full_range_flag
1692        writer.write_bit(false).unwrap();
1693        // color_description_present_flag
1694        writer.write_bit(false).unwrap();
1695
1696        // chroma_loc_info_present_flag
1697        writer.write_bit(false).unwrap();
1698
1699        // timing_info_present_flag
1700        writer.write_bit(true).unwrap();
1701        // num_units_in_tick to 0 (invalid)
1702        writer.write_bits(0, 32).unwrap();
1703        writer.finish().unwrap();
1704
1705        let result = Sps::parse(std::io::Cursor::new(&sps));
1706
1707        assert!(result.is_err());
1708        let err = result.unwrap_err();
1709        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
1710        assert_eq!(err.to_string(), "num_units_in_tick cannot be 0");
1711    }
1712
1713    #[test]
1714    fn test_invalid_time_scale() {
1715        let mut sps = Vec::new();
1716        let mut writer = BitWriter::new(&mut sps);
1717
1718        // forbidden zero bit must be unset
1719        writer.write_bit(false).unwrap();
1720        // nal_ref_idc is 0
1721        writer.write_bits(0, 2).unwrap();
1722        // nal_unit_type must be 7
1723        writer.write_bits(7, 5).unwrap();
1724
1725        // profile_idc = 100
1726        writer.write_bits(100, 8).unwrap();
1727        // constraint_setn_flags all false
1728        writer.write_bits(0, 8).unwrap();
1729        // level_idc = 0
1730        writer.write_bits(0, 8).unwrap();
1731
1732        // seq_parameter_set_id is expg
1733        writer.write_exp_golomb(0).unwrap();
1734
1735        // ext
1736        // chroma_format_idc is expg
1737        writer.write_exp_golomb(0).unwrap();
1738        // bit_depth_luma_minus8 is expg
1739        writer.write_exp_golomb(0).unwrap();
1740        // bit_depth_chroma_minus8 is expg
1741        writer.write_exp_golomb(0).unwrap();
1742        // qpprime
1743        writer.write_bit(false).unwrap();
1744        // seq_scaling_matrix_present_flag
1745        writer.write_bit(false).unwrap();
1746
1747        // return to sps
1748        // log2_max_frame_num_minus4 is expg
1749        writer.write_exp_golomb(0).unwrap();
1750        // pic_order_cnt_type is expg
1751        writer.write_exp_golomb(0).unwrap();
1752        // log2_max_pic_order_cnt_lsb_minus4
1753        writer.write_exp_golomb(0).unwrap();
1754
1755        // max_num_ref_frames is expg
1756        writer.write_exp_golomb(0).unwrap();
1757        // gaps_in_frame_num_value_allowed_flag
1758        writer.write_bit(false).unwrap();
1759        // 1280 width:
1760        // 1280 = (p + 1) * 16 - 2 * offset1 - 2 * offset2
1761        // we set offset1 and offset2 to both be 0 later
1762        // 1280 = (p + 1) * 16
1763        // p = 79
1764        writer.write_exp_golomb(79).unwrap();
1765        // we want 800 height:
1766        // 800 = ((2 - m) * (p + 1) * 16) - 2 * offset1 - 2 * offset2
1767        // we set offset1 and offset2 to both be 0 later
1768        // m is frame_mbs_only_flag which we set to 1 later
1769        // 800 = (2 - 1) * (p + 1) * 16 - 2 * 0 - 2 * 0
1770        // 800 = 2 * (p + 1) * 16 - 8
1771        // p = 33
1772        writer.write_exp_golomb(33).unwrap();
1773
1774        // frame_mbs_only_flag
1775        writer.write_bit(false).unwrap();
1776        // mb_adaptive_frame_field_flag
1777        writer.write_bit(false).unwrap();
1778
1779        // direct_8x8_inference_flag
1780        writer.write_bit(false).unwrap();
1781        // frame_cropping_flag
1782        writer.write_bit(false).unwrap();
1783
1784        // vui_parameters_present_flag
1785        writer.write_bit(true).unwrap();
1786
1787        // enter vui to set the framerate
1788        // aspect_ratio_info_present_flag
1789        writer.write_bit(false).unwrap();
1790
1791        // overscan_info_present_flag
1792        writer.write_bit(false).unwrap();
1793
1794        // video_signal_type_present_flag
1795        writer.write_bit(true).unwrap();
1796        // video_format
1797        writer.write_bits(0, 3).unwrap();
1798        // video_full_range_flag
1799        writer.write_bit(false).unwrap();
1800        // color_description_present_flag
1801        writer.write_bit(false).unwrap();
1802
1803        // chroma_loc_info_present_flag
1804        writer.write_bit(false).unwrap();
1805
1806        // timing_info_present_flag
1807        writer.write_bit(true).unwrap();
1808        // num_units_in_tick to 0 (invalid)
1809        writer.write_bits(0, 32).unwrap();
1810        writer.finish().unwrap();
1811
1812        let result = Sps::parse(std::io::Cursor::new(&sps));
1813
1814        assert!(result.is_err());
1815        let err = result.unwrap_err();
1816        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
1817        assert_eq!(err.to_string(), "num_units_in_tick cannot be 0");
1818    }
1819
1820    #[test]
1821    fn test_parse_build_sps_no_vui() {
1822        let mut sps = Vec::new();
1823        let mut writer = BitWriter::new(&mut sps);
1824
1825        // forbidden zero bit must be unset
1826        writer.write_bit(false).unwrap();
1827        // nal_ref_idc is 0
1828        writer.write_bits(0, 2).unwrap();
1829        // nal_unit_type must be 7
1830        writer.write_bits(7, 5).unwrap();
1831
1832        // profile_idc = 77
1833        writer.write_bits(77, 8).unwrap();
1834        // constraint_setn_flags all false
1835        writer.write_bits(0, 8).unwrap();
1836        // level_idc = 0
1837        writer.write_bits(0, 8).unwrap();
1838
1839        // seq_parameter_set_id is expg so 0b1 (true) = false
1840        writer.write_exp_golomb(0).unwrap();
1841
1842        // skip sps ext since profile_idc = 77
1843        // log2_max_frame_num_minus4 is expg so 0b1 (true) = false
1844        writer.write_exp_golomb(0).unwrap();
1845        // we can try setting pic_order_cnt_type to 1
1846        writer.write_exp_golomb(1).unwrap();
1847
1848        // delta_pic_order_always_zero_flag
1849        writer.write_bit(false).unwrap();
1850        // offset_for_non_ref_pic
1851        writer.write_bit(true).unwrap();
1852        // offset_for_top_to_bottom_field
1853        writer.write_bit(true).unwrap();
1854        // num_ref_frames_in_pic_order_cnt_cycle is expg so 0b010 = 1
1855        writer.write_bits(0b010, 3).unwrap();
1856        // loop num_ref_frames_in_pic_order_cnt_cycle times (1)
1857        // offset_for_ref_frame is expg so 0b1 (true) = false
1858        writer.write_bit(true).unwrap();
1859
1860        // max_num_ref_frames is expg so 0b1 (true) = false
1861        writer.write_bit(true).unwrap();
1862        // gaps_in_frame_num_value_allowed_flag
1863        writer.write_bit(false).unwrap();
1864        // 1920 width:
1865        // 1920 = (p + 1) * 16 - 2 * offset1 - 2 * offset2
1866        // we set offset1 and offset2 to both be 4 later
1867        // 1920 = (p + 1) * 16 - 2 * 4 - 2 * 4
1868        // 1920 = (p + 1) * 16 - 16
1869        // p = 120
1870        // pic_width_in_mbs_minus1 is expg so:
1871        // 0 0000 0111 1001
1872        writer.write_exp_golomb(999).unwrap();
1873        // we want 1080 height:
1874        // 1080 = ((2 - m) * (p + 1) * 16) - 2 * offset1 - 2 * offset2
1875        // we set offset1 and offset2 to both be 2 later
1876        // m is frame_mbs_only_flag which we set to 0 later
1877        // 1080 = (2 - 0) * (p + 1) * 16 - 2 * 2 - 2 * 2
1878        // 1080 = 2 * (p + 1) * 16 - 8
1879        // p = 33
1880        // pic_height_in_map_units_minus1 is expg so:
1881        // 000 0010 0010
1882        writer.write_exp_golomb(899).unwrap();
1883
1884        // frame_mbs_only_flag
1885        writer.write_bit(false).unwrap();
1886        // mb_adaptive_frame_field_flag
1887        writer.write_bit(false).unwrap();
1888
1889        // direct_8x8_inference_flag
1890        writer.write_bit(false).unwrap();
1891        // frame_cropping_flag
1892        writer.write_bit(true).unwrap();
1893
1894        // frame_crop_left_offset is expg
1895        writer.write_exp_golomb(100).unwrap();
1896        // frame_crop_right_offset is expg
1897        writer.write_exp_golomb(200).unwrap();
1898        // frame_crop_top_offset is expg
1899        writer.write_exp_golomb(300).unwrap();
1900        // frame_crop_bottom_offset is expg
1901        writer.write_exp_golomb(400).unwrap();
1902
1903        // vui_parameters_present_flag
1904        writer.write_bit(false).unwrap();
1905        writer.finish().unwrap();
1906
1907        let result = Sps::parse(std::io::Cursor::new(&sps)).unwrap();
1908
1909        insta::assert_debug_snapshot!(result, @r"
1910        Sps {
1911            nal_ref_idc: 0,
1912            nal_unit_type: NALUnitType::SPS,
1913            profile_idc: 77,
1914            constraint_set0_flag: false,
1915            constraint_set1_flag: false,
1916            constraint_set2_flag: false,
1917            constraint_set3_flag: false,
1918            constraint_set4_flag: false,
1919            constraint_set5_flag: false,
1920            level_idc: 0,
1921            seq_parameter_set_id: 0,
1922            ext: None,
1923            log2_max_frame_num_minus4: 0,
1924            pic_order_cnt_type: 1,
1925            log2_max_pic_order_cnt_lsb_minus4: None,
1926            pic_order_cnt_type1: Some(
1927                PicOrderCountType1 {
1928                    delta_pic_order_always_zero_flag: false,
1929                    offset_for_non_ref_pic: 0,
1930                    offset_for_top_to_bottom_field: 0,
1931                    num_ref_frames_in_pic_order_cnt_cycle: 1,
1932                    offset_for_ref_frame: [
1933                        0,
1934                    ],
1935                },
1936            ),
1937            max_num_ref_frames: 0,
1938            gaps_in_frame_num_value_allowed_flag: false,
1939            pic_width_in_mbs_minus1: 999,
1940            pic_height_in_map_units_minus1: 899,
1941            mb_adaptive_frame_field_flag: Some(
1942                false,
1943            ),
1944            direct_8x8_inference_flag: false,
1945            frame_crop_info: Some(
1946                FrameCropInfo {
1947                    frame_crop_left_offset: 100,
1948                    frame_crop_right_offset: 200,
1949                    frame_crop_top_offset: 300,
1950                    frame_crop_bottom_offset: 400,
1951                },
1952            ),
1953            sample_aspect_ratio: None,
1954            overscan_appropriate_flag: None,
1955            color_config: None,
1956            chroma_sample_loc: None,
1957            timing_info: None,
1958        }
1959        ");
1960
1961        // create a writer for the builder
1962        let mut buf = Vec::new();
1963        // build from the example sps
1964        result.build(&mut buf).unwrap();
1965
1966        assert_eq!(buf, sps);
1967    }
1968
1969    #[test]
1970    fn test_size_sps() {
1971        let mut bit_count = 0;
1972        let mut sps = Vec::new();
1973        let mut writer = BitWriter::new(&mut sps);
1974
1975        // forbidden zero bit must be unset
1976        writer.write_bit(false).unwrap();
1977        bit_count += 1;
1978        // nal_ref_idc is 0
1979        writer.write_bits(0, 2).unwrap();
1980        bit_count += 2;
1981        // nal_unit_type must be 7
1982        writer.write_bits(7, 5).unwrap();
1983        bit_count += 5;
1984
1985        // profile_idc = 44
1986        writer.write_bits(44, 8).unwrap();
1987        bit_count += 8;
1988        // constraint_setn_flags all false
1989        writer.write_bits(0, 8).unwrap();
1990        bit_count += 8;
1991        // level_idc = 0
1992        writer.write_bits(0, 8).unwrap();
1993        bit_count += 8;
1994        // seq_parameter_set_id is expg
1995        writer.write_exp_golomb(0).unwrap();
1996        bit_count += size_of_exp_golomb(0);
1997
1998        // sps ext
1999        // we want to try out chroma_format_idc = 3
2000        // chroma_format_idc is expg
2001        writer.write_exp_golomb(3).unwrap();
2002        bit_count += size_of_exp_golomb(3);
2003        // separate_color_plane_flag
2004        writer.write_bit(false).unwrap();
2005        bit_count += 1;
2006        // bit_depth_luma_minus8 is expg
2007        writer.write_exp_golomb(0).unwrap();
2008        bit_count += size_of_exp_golomb(0);
2009        // bit_depth_chroma_minus8 is expg
2010        writer.write_exp_golomb(0).unwrap();
2011        bit_count += size_of_exp_golomb(0);
2012        // qpprime
2013        writer.write_bit(false).unwrap();
2014        bit_count += 1;
2015        // we want to simulate a scaling matrix
2016        // seq_scaling_matrix_present_flag
2017        writer.write_bit(true).unwrap();
2018        bit_count += 1;
2019
2020        // enter scaling matrix, we loop 12 times since
2021        // chroma_format_idc = 3.
2022        // loop 1 of 12
2023        // true to enter if statement
2024        writer.write_bit(true).unwrap();
2025        bit_count += 1;
2026        // i < 6, so size is 16, so we loop 16 times
2027        // sub-loop 1 of 16
2028        // delta_scale is a SIGNED expg so we can try out
2029        // entering -4 so next_scale becomes 8 + 4 = 12
2030        writer.write_signed_exp_golomb(4).unwrap();
2031        bit_count += size_of_signed_exp_golomb(4);
2032        // sub-loop 2 of 16
2033        // delta_scale is a SIGNED expg so we can try out
2034        // entering -12 so next scale becomes 12 - 12 = 0
2035        writer.write_signed_exp_golomb(-12).unwrap();
2036        bit_count += size_of_signed_exp_golomb(-12);
2037        // at this point next_scale is 0, which means we break
2038        // loop 2 through 12
2039        // we don't need to try anything else so we can just skip through them by writing `0` bit 11 times.
2040        writer.write_bits(0, 11).unwrap();
2041        bit_count += 11;
2042
2043        // back to sps
2044        // log2_max_frame_num_minus4 is expg
2045        writer.write_exp_golomb(0).unwrap();
2046        bit_count += size_of_exp_golomb(0);
2047        // we can try setting pic_order_cnt_type to 1
2048        // pic_order_cnt_type is expg
2049        writer.write_exp_golomb(1).unwrap();
2050        bit_count += size_of_exp_golomb(1);
2051
2052        // delta_pic_order_always_zero_flag
2053        writer.write_bit(false).unwrap();
2054        bit_count += 1;
2055        // offset_for_non_ref_pic
2056        writer.write_bit(true).unwrap();
2057        bit_count += 1;
2058        // offset_for_top_to_bottom_field
2059        writer.write_bit(true).unwrap();
2060        bit_count += 1;
2061        // num_ref_frames_in_pic_order_cnt_cycle is expg
2062        writer.write_exp_golomb(1).unwrap();
2063        bit_count += size_of_exp_golomb(1);
2064        // loop num_ref_frames_in_pic_order_cnt_cycle times (1)
2065        // offset_for_ref_frame is expg
2066        writer.write_exp_golomb(0).unwrap();
2067        bit_count += size_of_exp_golomb(0);
2068
2069        // max_num_ref_frames is expg
2070        writer.write_exp_golomb(0).unwrap();
2071        bit_count += size_of_exp_golomb(0);
2072        // gaps_in_frame_num_value_allowed_flag
2073        writer.write_bit(false).unwrap();
2074        bit_count += 1;
2075        // 1920 width:
2076        // 1920 = (p + 1) * 16 - 2 * offset1 - 2 * offset2
2077        // we set offset1 and offset2 to both be 4 later
2078        // 1920 = (p + 1) * 16 - 2 * 4 - 2 * 4
2079        // 1920 = (p + 1) * 16 - 16
2080        // p = 120
2081        // pic_width_in_mbs_minus1 is expg
2082        writer.write_exp_golomb(120).unwrap();
2083        bit_count += size_of_exp_golomb(120);
2084        // we want 1080 height:
2085        // 1080 = ((2 - m) * (p + 1) * 16) - 2 * offset1 - 2 * offset2
2086        // we set offset1 and offset2 to both be 2 later
2087        // m is frame_mbs_only_flag which we set to 0 later
2088        // 1080 = (2 - 0) * (p + 1) * 16 - 2 * 2 - 2 * 2
2089        // 1080 = 2 * (p + 1) * 16 - 8
2090        // p = 33
2091        // pic_height_in_map_units_minus1 is expg
2092        writer.write_exp_golomb(33).unwrap();
2093        bit_count += size_of_exp_golomb(33);
2094
2095        // frame_mbs_only_flag
2096        writer.write_bit(false).unwrap();
2097        bit_count += 1;
2098        // mb_adaptive_frame_field_flag
2099        writer.write_bit(false).unwrap();
2100        bit_count += 1;
2101
2102        // direct_8x8_inference_flag
2103        writer.write_bit(false).unwrap();
2104        bit_count += 1;
2105        // frame_cropping_flag
2106        writer.write_bit(true).unwrap();
2107        bit_count += 1;
2108
2109        // frame_crop_left_offset is expg
2110        writer.write_exp_golomb(4).unwrap();
2111        bit_count += size_of_exp_golomb(4);
2112        // frame_crop_left_offset is expg
2113        writer.write_exp_golomb(4).unwrap();
2114        bit_count += size_of_exp_golomb(4);
2115        // frame_crop_left_offset is expg
2116        writer.write_exp_golomb(2).unwrap();
2117        bit_count += size_of_exp_golomb(2);
2118        // frame_crop_left_offset is expg
2119        writer.write_exp_golomb(2).unwrap();
2120        bit_count += size_of_exp_golomb(2);
2121
2122        // vui_parameters_present_flag
2123        writer.write_bit(true).unwrap();
2124        bit_count += 1;
2125
2126        // enter vui to set the framerate
2127        // aspect_ratio_info_present_flag
2128        writer.write_bit(true).unwrap();
2129        bit_count += 1;
2130        // we can try 255 to set the sar_width and sar_height
2131        // aspect_ratio_idc
2132        writer.write_bits(255, 8).unwrap();
2133        bit_count += 8;
2134        // sar_width
2135        writer.write_bits(0, 16).unwrap();
2136        bit_count += 16;
2137        // sar_height
2138        writer.write_bits(0, 16).unwrap();
2139        bit_count += 16;
2140
2141        // overscan_info_present_flag
2142        writer.write_bit(false).unwrap();
2143        bit_count += 1;
2144
2145        // video_signal_type_present_flag
2146        writer.write_bit(true).unwrap();
2147        bit_count += 1;
2148        // video_format
2149        writer.write_bits(0, 3).unwrap();
2150        bit_count += 3;
2151        // video_full_range_flag
2152        writer.write_bit(false).unwrap();
2153        bit_count += 1;
2154        // color_description_present_flag
2155        writer.write_bit(true).unwrap();
2156        bit_count += 1;
2157        // color_primaries
2158        writer.write_bits(1, 8).unwrap();
2159        bit_count += 8;
2160        // transfer_characteristics
2161        writer.write_bits(1, 8).unwrap();
2162        bit_count += 8;
2163        // matrix_coefficients
2164        writer.write_bits(1, 8).unwrap();
2165        bit_count += 8;
2166
2167        // chroma_loc_info_present_flag
2168        writer.write_bit(false).unwrap();
2169        bit_count += 1;
2170
2171        // timing_info_present_flag
2172        writer.write_bit(true).unwrap();
2173        bit_count += 1;
2174        // we can set this to 1000 for example
2175        // num_units_in_tick is a u32
2176        writer.write_bits(1000, 32).unwrap();
2177        bit_count += 32;
2178        // fps = time_scale / (2 * num_units_in_tick)
2179        // since we want 480 fps:
2180        // 480 = time_scale / (2 * 1000)
2181        // 960 000 = time_scale
2182        // time_scale is a u32
2183        writer.write_bits(960000, 32).unwrap();
2184        bit_count += 32;
2185        writer.finish().unwrap();
2186
2187        let result = Sps::parse(std::io::Cursor::new(&sps)).unwrap();
2188
2189        // now we can check the size:
2190        assert_eq!(result.size(), bit_count.div_ceil(8));
2191    }
2192
2193    #[test]
2194    fn test_reduce_color_config() {
2195        let mut sps = Vec::new();
2196        let mut writer = BitWriter::new(&mut sps);
2197
2198        // forbidden zero bit must be unset
2199        writer.write_bit(false).unwrap();
2200        // nal_ref_idc is 0
2201        writer.write_bits(0, 2).unwrap();
2202        // nal_unit_type must be 7
2203        writer.write_bits(7, 5).unwrap();
2204
2205        // profile_idc = 100
2206        writer.write_bits(100, 8).unwrap();
2207        // constraint_setn_flags all false
2208        writer.write_bits(0, 8).unwrap();
2209        // level_idc = 0
2210        writer.write_bits(0, 8).unwrap();
2211
2212        // seq_parameter_set_id is expg
2213        writer.write_exp_golomb(0).unwrap();
2214
2215        // sps ext
2216        // chroma_format_idc is expg
2217        writer.write_exp_golomb(0).unwrap();
2218        // bit_depth_luma_minus8 is expg
2219        writer.write_exp_golomb(0).unwrap();
2220        // bit_depth_chroma_minus8 is expg
2221        writer.write_exp_golomb(0).unwrap();
2222        // qpprime
2223        writer.write_bit(false).unwrap();
2224        // seq_scaling_matrix_present_flag
2225        writer.write_bit(false).unwrap();
2226
2227        // back to sps
2228        // log2_max_frame_num_minus4 is expg
2229        writer.write_exp_golomb(0).unwrap();
2230        // pic_order_cnt_type is expg
2231        writer.write_exp_golomb(0).unwrap();
2232        // log2_max_pic_order_cnt_lsb_minus4 is expg
2233        writer.write_exp_golomb(0).unwrap();
2234
2235        // max_num_ref_frames is expg
2236        writer.write_exp_golomb(0).unwrap();
2237        // gaps_in_frame_num_value_allowed_flag
2238        writer.write_bit(false).unwrap();
2239        // width
2240        writer.write_exp_golomb(0).unwrap();
2241        // height
2242        writer.write_exp_golomb(0).unwrap();
2243
2244        // frame_mbs_only_flag
2245        writer.write_bit(true).unwrap();
2246
2247        // direct_8x8_inference_flag
2248        writer.write_bit(false).unwrap();
2249        // frame_cropping_flag
2250        writer.write_bit(false).unwrap();
2251
2252        // vui_parameters_present_flag
2253        writer.write_bit(true).unwrap();
2254
2255        // aspect_ratio_info_present_flag
2256        writer.write_bit(false).unwrap();
2257
2258        // overscan_info_present_flag
2259        writer.write_bit(false).unwrap();
2260
2261        // we want to change the color_config
2262        // video_signal_type_present_flag
2263        writer.write_bit(true).unwrap();
2264
2265        // video_format
2266        writer.write_bits(1, 3).unwrap();
2267        // video_full_range_flag
2268        writer.write_bit(false).unwrap();
2269        // color_description_present_flag: we want this to be true
2270        writer.write_bit(true).unwrap();
2271
2272        // now we set these to redundant values (each should be 2)
2273        writer.write_bits(2, 8).unwrap();
2274        writer.write_bits(2, 8).unwrap();
2275        writer.write_bits(2, 8).unwrap();
2276
2277        // chroma_loc_info_present_flag
2278        writer.write_bit(false).unwrap();
2279
2280        // timing_info_present_flag
2281        writer.write_bit(false).unwrap();
2282        writer.finish().unwrap();
2283
2284        let reduced_sps = Sps::parse(std::io::Cursor::new(&sps)).unwrap();
2285
2286        let mut reduced_buf = Vec::new();
2287        reduced_sps.build(&mut reduced_buf).unwrap();
2288
2289        assert_ne!(sps, reduced_buf);
2290    }
2291
2292    #[test]
2293    fn test_reduce_vui() {
2294        let mut sps = Vec::new();
2295        let mut writer = BitWriter::new(&mut sps);
2296
2297        // forbidden zero bit must be unset
2298        writer.write_bit(false).unwrap();
2299        // nal_ref_idc is 0
2300        writer.write_bits(0, 2).unwrap();
2301        // nal_unit_type must be 7
2302        writer.write_bits(7, 5).unwrap();
2303
2304        // profile_idc = 100
2305        writer.write_bits(100, 8).unwrap();
2306        // constraint_setn_flags all false
2307        writer.write_bits(0, 8).unwrap();
2308        // level_idc = 0
2309        writer.write_bits(0, 8).unwrap();
2310
2311        // seq_parameter_set_id is expg
2312        writer.write_exp_golomb(0).unwrap();
2313
2314        // sps ext
2315        // chroma_format_idc is expg
2316        writer.write_exp_golomb(0).unwrap();
2317        // bit_depth_luma_minus8 is expg
2318        writer.write_exp_golomb(0).unwrap();
2319        // bit_depth_chroma_minus8 is expg
2320        writer.write_exp_golomb(0).unwrap();
2321        // qpprime
2322        writer.write_bit(false).unwrap();
2323        // seq_scaling_matrix_present_flag
2324        writer.write_bit(false).unwrap();
2325
2326        // back to sps
2327        // log2_max_frame_num_minus4 is expg
2328        writer.write_exp_golomb(0).unwrap();
2329        // pic_order_cnt_type is expg
2330        writer.write_exp_golomb(0).unwrap();
2331        // log2_max_pic_order_cnt_lsb_minus4 is expg
2332        writer.write_exp_golomb(0).unwrap();
2333
2334        // max_num_ref_frames is expg
2335        writer.write_exp_golomb(0).unwrap();
2336        // gaps_in_frame_num_value_allowed_flag
2337        writer.write_bit(false).unwrap();
2338        // width
2339        writer.write_exp_golomb(0).unwrap();
2340        // height
2341        writer.write_exp_golomb(0).unwrap();
2342
2343        // frame_mbs_only_flag
2344        writer.write_bit(true).unwrap();
2345
2346        // direct_8x8_inference_flag
2347        writer.write_bit(false).unwrap();
2348        // frame_cropping_flag
2349        writer.write_bit(false).unwrap();
2350
2351        // we want to set this flag to be true and all subsequent flags to be false.
2352        // vui_parameters_present_flag
2353        writer.write_bit(true).unwrap();
2354
2355        // aspect_ratio_info_present_flag
2356        writer.write_bit(false).unwrap();
2357
2358        // overscan_info_present_flag
2359        writer.write_bit(false).unwrap();
2360
2361        // video_signal_type_present_flag
2362        writer.write_bit(false).unwrap();
2363
2364        // chroma_loc_info_present_flag
2365        writer.write_bit(false).unwrap();
2366
2367        // timing_info_present_flag
2368        writer.write_bit(false).unwrap();
2369        writer.finish().unwrap();
2370
2371        let result = Sps::parse(std::io::Cursor::new(&sps)).unwrap();
2372
2373        let mut reduced_buf = Vec::new();
2374        result.build(&mut reduced_buf).unwrap();
2375
2376        let reduced_result = Sps::parse(std::io::Cursor::new(&reduced_buf)).unwrap();
2377        assert_eq!(result.size(), reduced_result.size());
2378
2379        insta::assert_debug_snapshot!(reduced_result, @r"
2380        Sps {
2381            nal_ref_idc: 0,
2382            nal_unit_type: NALUnitType::SPS,
2383            profile_idc: 100,
2384            constraint_set0_flag: false,
2385            constraint_set1_flag: false,
2386            constraint_set2_flag: false,
2387            constraint_set3_flag: false,
2388            constraint_set4_flag: false,
2389            constraint_set5_flag: false,
2390            level_idc: 0,
2391            seq_parameter_set_id: 0,
2392            ext: Some(
2393                SpsExtended {
2394                    chroma_format_idc: 0,
2395                    separate_color_plane_flag: false,
2396                    bit_depth_luma_minus8: 0,
2397                    bit_depth_chroma_minus8: 0,
2398                    qpprime_y_zero_transform_bypass_flag: false,
2399                    scaling_matrix: [],
2400                },
2401            ),
2402            log2_max_frame_num_minus4: 0,
2403            pic_order_cnt_type: 0,
2404            log2_max_pic_order_cnt_lsb_minus4: Some(
2405                0,
2406            ),
2407            pic_order_cnt_type1: None,
2408            max_num_ref_frames: 0,
2409            gaps_in_frame_num_value_allowed_flag: false,
2410            pic_width_in_mbs_minus1: 0,
2411            pic_height_in_map_units_minus1: 0,
2412            mb_adaptive_frame_field_flag: None,
2413            direct_8x8_inference_flag: false,
2414            frame_crop_info: None,
2415            sample_aspect_ratio: None,
2416            overscan_appropriate_flag: None,
2417            color_config: None,
2418            chroma_sample_loc: None,
2419            timing_info: None,
2420        }
2421        ");
2422    }
2423}