scuffle_h265/sps/
mod.rs

1use std::io;
2use std::num::NonZero;
3
4use scuffle_bytes_util::{BitReader, EmulationPreventionIo, range_check};
5use scuffle_expgolomb::BitReaderExpGolombExt;
6
7use crate::NALUnitType;
8use crate::nal_unit_header::NALUnitHeader;
9use crate::rbsp_trailing_bits::rbsp_trailing_bits;
10
11mod conformance_window;
12mod long_term_ref_pics;
13mod pcm;
14mod profile_tier_level;
15mod scaling_list;
16mod sps_3d_extension;
17mod sps_multilayer_extension;
18mod sps_range_extension;
19mod sps_scc_extension;
20mod st_ref_pic_set;
21mod sub_layer_ordering_info;
22mod vui_parameters;
23
24pub use conformance_window::*;
25pub use long_term_ref_pics::*;
26pub use pcm::*;
27pub use profile_tier_level::*;
28pub use scaling_list::*;
29pub use sps_3d_extension::*;
30pub use sps_multilayer_extension::*;
31pub use sps_range_extension::*;
32pub use sps_scc_extension::*;
33pub use st_ref_pic_set::*;
34pub use sub_layer_ordering_info::*;
35pub use vui_parameters::*;
36
37// Some notes on the spec:
38//
39// The data appears like this on the wire: `NALU(RBSP(SODB))`
40//
41// NALU: NAL unit
42// This is the outer most encapsulation layer and what is sent over the wire.
43//
44// RBSP: Raw byte sequence payload
45// Additional encapsulation layer that adds trailing bits and emulation prevention.
46//
47// SODB: String of data bits
48// This is the actual payload data.
49
50/// Sequence parameter set contained in a NAL unit.
51///
52/// This only represents sequence parameter sets that are part of NAL units.
53/// Therefore the NAL unit header is included in this struct as [`SpsNALUnit::nal_unit_header`].
54#[derive(Debug, Clone, PartialEq)]
55pub struct SpsNALUnit {
56    /// The NAL unit header.
57    pub nal_unit_header: NALUnitHeader,
58    /// The SPS RBSP.
59    pub rbsp: SpsRbsp,
60}
61
62impl SpsNALUnit {
63    /// Parses an SPS NAL unit from the given reader.
64    pub fn parse(mut reader: impl io::Read) -> io::Result<Self> {
65        let nal_unit_header = NALUnitHeader::parse(&mut reader)?;
66        if nal_unit_header.nal_unit_type != NALUnitType::SpsNut {
67            return Err(io::Error::new(io::ErrorKind::InvalidData, "nal_unit_type is not SPS_NUT"));
68        }
69
70        let rbsp = SpsRbsp::parse(reader, nal_unit_header.nuh_layer_id)?;
71
72        Ok(SpsNALUnit { nal_unit_header, rbsp })
73    }
74}
75
76/// Sequence parameter set RBSP.
77///
78/// For parsing SPS RBSPs that are part of NAL units, please use [`SpsNALUnit::parse`].
79///
80/// `seq_parameter_set_rbsp()`
81///
82/// - ISO/IEC 23008-2 - 7.3.2.2
83/// - ISO/IEC 23008-2 - 7.4.3.2
84#[derive(Debug, Clone, PartialEq)]
85pub struct SpsRbsp {
86    /// Specifies the value of the vps_video_parameter_set_id of the active VPS.
87    pub sps_video_parameter_set_id: u8,
88    /// This value plus 1 specifies the maximum number of temporal sub-layers that may be
89    /// present in each CVS referring to the SPS.
90    ///
91    /// The value is in range \[0, 6\]. The value must be less than or equal to `vps_max_sub_layers_minus1`.
92    pub sps_max_sub_layers_minus1: u8,
93    /// Specifies whether inter prediction is additionally restricted for CVSs referring to the SPS.
94    ///
95    /// When `sps_max_sub_layers_minus1 == 0`, this flag is `true`.
96    pub sps_temporal_id_nesting_flag: bool,
97    /// The [`ProfileTierLevel`] structure contained in this SPS.
98    pub profile_tier_level: ProfileTierLevel,
99    /// Provides an identifier for the SPS for reference by other syntax elements.
100    ///
101    /// The value is in range \[0, 15\].
102    pub sps_seq_parameter_set_id: u64,
103    /// Specifies the chroma sampling relative to the luma sampling as specified in ISO/IEC 23008-2 - 6.2.
104    ///
105    /// The value is in range \[0, 3\].
106    pub chroma_format_idc: u8,
107    /// Equal to `true` specifies that the three colour components of the 4:4:4 chroma format are coded separately.
108    ///
109    /// Equal to `false` specifies that the colour components are not coded separately.
110    ///
111    /// Defines [`ChromaArrayType`](Self::chroma_array_type).
112    pub separate_colour_plane_flag: bool,
113    /// Specifies the width of each decoded picture in units of luma samples.
114    ///
115    /// This value is never zero and an integer multiple of [`MinCbSizeY`](Self::min_cb_size_y).
116    pub pic_width_in_luma_samples: NonZero<u64>,
117    /// Specifies the height of each decoded picture in units of luma samples.
118    ///
119    /// This value is never zero and an integer multiple of [`MinCbSizeY`](Self::min_cb_size_y).
120    pub pic_height_in_luma_samples: NonZero<u64>,
121    /// `conf_win_left_offset`, `conf_win_right_offset`, `conf_win_top_offset`, and `conf_win_bottom_offset`.
122    ///
123    /// See [`ConformanceWindow`] for details.
124    pub conformance_window: ConformanceWindow,
125    /// Specifies the bit depth of the samples of the luma array [`BitDepth_Y`](Self::bit_depth_y) and
126    /// the value of the luma quantization parameter range offset [`QpBdOffset_Y`](Self::qp_bd_offset_y).
127    ///
128    /// The value is in range \[0, 8\].
129    pub bit_depth_luma_minus8: u8,
130    /// specifies the bit depth of the samples of the chroma arrays [`BitDepth_C`](Self::bit_depth_c) and
131    /// the value of the chroma quantization parameter range offset [`QpBdOffset_C`](Self::qp_bd_offset_c)
132    ///
133    /// The value is in range \[0, 8\].
134    pub bit_depth_chroma_minus8: u8,
135    /// Specifies the value of the variable [`MaxPicOrderCntLsb`](Self::max_pic_order_cnt_lsb) that is used
136    /// in the decoding process for picture order count.
137    ///
138    /// The value is in range \[0, 12\].
139    pub log2_max_pic_order_cnt_lsb_minus4: u8,
140    /// `sps_max_dec_pic_buffering_minus1`, `sps_max_num_reorder_pics`, and `sps_max_latency_increase_plus1` for each sub-layer.
141    ///
142    /// See [`SubLayerOrderingInfo`] for details.
143    pub sub_layer_ordering_info: SubLayerOrderingInfo,
144    /// This value plus 3 defines the minimum luma coding block size.
145    ///
146    /// Defines [`MinCbLog2SizeY`](Self::min_cb_log2_size_y).
147    pub log2_min_luma_coding_block_size_minus3: u64,
148    /// Specifies the difference between the maximum and minimum luma coding block size.
149    pub log2_diff_max_min_luma_coding_block_size: u64,
150    /// This value plus 2 specifies the minimum luma transform block size.
151    ///
152    /// Defines [`MinTbLog2SizeY`](Self::min_tb_log2_size_y).
153    pub log2_min_luma_transform_block_size_minus2: u64,
154    /// Specifies the difference between the maximum and minimum luma transform block size.
155    ///
156    /// Defines [`MaxTbLog2SizeY`](Self::max_tb_log2_size_y).
157    pub log2_diff_max_min_luma_transform_block_size: u64,
158    /// Specifies the maximum hierarchy depth for transform units of coding units coded in inter prediction mode.
159    ///
160    /// This value is in range \[0, [`CtbLog2SizeY`](Self::ctb_log2_size_y) - [`MinTbLog2SizeY`](Self::min_tb_log2_size_y)\].
161    pub max_transform_hierarchy_depth_inter: u64,
162    /// Specifies the maximum hierarchy depth for transform units of coding units coded in intra prediction mode.
163    ///
164    /// This value is in range \[0, [`CtbLog2SizeY`](Self::ctb_log2_size_y) - [`MinTbLog2SizeY`](Self::min_tb_log2_size_y)\].
165    pub max_transform_hierarchy_depth_intra: u64,
166    /// The [`ScalingListData`] structure contained in this SPS, if present.
167    pub scaling_list_data: Option<ScalingListData>,
168    /// Equal to `true` specifies that asymmetric motion partitions, i.e. `PartMode` equal to
169    /// `PART_2NxnU`, `PART_2NxnD`, `PART_nLx2N`, or `PART_nRx2N`, may be used in CTBs.
170    ///
171    /// Equal to `false` specifies that asymmetric motion partitions cannot be used in CTBs.
172    pub amp_enabled_flag: bool,
173    /// Equal to `true` specifies that the sample adaptive offset process is applied to the reconstructed picture
174    /// after the deblocking filter process.
175    ///
176    /// Equal to `false` specifies that the sample adaptive offset process is not
177    /// applied to the reconstructed picture after the deblocking filter process.
178    pub sample_adaptive_offset_enabled_flag: bool,
179    /// `pcm_sample_bit_depth_luma_minus1`, `pcm_sample_bit_depth_chroma_minus1`, `log2_min_pcm_luma_coding_block_size_minus3`,
180    /// `log2_diff_max_min_pcm_luma_coding_block_size` and `pcm_loop_filter_disabled_flag`, if `pcm_enabled_flag` is `true`.
181    ///
182    /// See [`Pcm`] for details.
183    pub pcm: Option<Pcm>,
184    /// The [`ShortTermRefPicSets`] structure contained in this SPS.
185    pub short_term_ref_pic_sets: ShortTermRefPicSets,
186    /// `lt_ref_pic_poc_lsb_sps[i]` and `used_by_curr_pic_lt_sps_flag[i]`, if `long_term_ref_pics_present_flag` is `true`.
187    ///
188    /// See [`LongTermRefPics`] for details.
189    pub long_term_ref_pics: Option<LongTermRefPics>,
190    /// Equal to `true` specifies that `slice_temporal_mvp_enabled_flag` is present
191    /// in the slice headers of non-IDR pictures in the CVS.
192    ///
193    /// Equal to `false` specifies that `slice_temporal_mvp_enabled_flag` is not present
194    /// in slice headers and that temporal motion vector predictors are not used in the CVS.
195    pub sps_temporal_mvp_enabled_flag: bool,
196    /// Equal to `true` specifies that bi-linear interpolation is conditionally
197    /// used in the intra prediction filtering process in the CVS as specified in ISO/IEC 23008-2 - 8.4.4.2.3.
198    ///
199    /// Equal to `false` specifies that the bi-linear interpolation is not used in the CVS.
200    pub strong_intra_smoothing_enabled_flag: bool,
201    /// The [`VuiParameters`] structure contained in this SPS, if present.
202    pub vui_parameters: Option<VuiParameters>,
203    /// The [`SpsRangeExtension`] structure contained in this SPS, if present.
204    pub range_extension: Option<SpsRangeExtension>,
205    /// The [`SpsMultilayerExtension`] structure contained in this SPS, if present.
206    pub multilayer_extension: Option<SpsMultilayerExtension>,
207    /// The [`Sps3dExtension`] structure contained in this SPS, if present.
208    pub sps_3d_extension: Option<Sps3dExtension>,
209    /// The [`SpsSccExtension`] structure contained in this SPS, if present.
210    pub scc_extension: Option<SpsSccExtension>,
211}
212
213impl SpsRbsp {
214    /// Parses an SPS RBSP from the given reader.
215    ///
216    /// Uses [`EmulationPreventionIo`] to handle emulation prevention bytes.
217    ///
218    /// Returns an [`SpsRbsp`] struct.
219    pub fn parse(reader: impl io::Read, nuh_layer_id: u8) -> io::Result<Self> {
220        let mut bit_reader = BitReader::new(EmulationPreventionIo::new(reader));
221
222        let sps_video_parameter_set_id = bit_reader.read_bits(4)? as u8;
223
224        let sps_max_sub_layers_minus1 = bit_reader.read_bits(3)? as u8;
225        range_check!(sps_max_sub_layers_minus1, 0, 6)?;
226
227        let sps_temporal_id_nesting_flag = bit_reader.read_bit()?;
228
229        if sps_max_sub_layers_minus1 == 0 && !sps_temporal_id_nesting_flag {
230            return Err(io::Error::new(
231                io::ErrorKind::InvalidData,
232                "sps_temporal_id_nesting_flag must be 1 when sps_max_sub_layers_minus1 is 0",
233            ));
234        }
235
236        let profile_tier_level = ProfileTierLevel::parse(&mut bit_reader, sps_max_sub_layers_minus1)?;
237
238        let sps_seq_parameter_set_id = bit_reader.read_exp_golomb()?;
239        range_check!(sps_seq_parameter_set_id, 0, 15)?;
240
241        let chroma_format_idc = bit_reader.read_exp_golomb()?;
242        range_check!(chroma_format_idc, 0, 3)?;
243        let chroma_format_idc = chroma_format_idc as u8;
244
245        let mut separate_colour_plane_flag = false;
246        if chroma_format_idc == 3 {
247            separate_colour_plane_flag = bit_reader.read_bit()?;
248        }
249
250        // Table 6-1
251        let sub_width_c = if chroma_format_idc == 1 || chroma_format_idc == 2 {
252            2
253        } else {
254            1
255        };
256        let sub_height_c = if chroma_format_idc == 1 { 2 } else { 1 };
257
258        let pic_width_in_luma_samples = NonZero::new(bit_reader.read_exp_golomb()?)
259            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "pic_width_in_luma_samples must not be 0"))?;
260
261        let pic_height_in_luma_samples = NonZero::new(bit_reader.read_exp_golomb()?)
262            .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "pic_height_in_luma_samples must not be 0"))?;
263
264        let conformance_window_flag = bit_reader.read_bit()?;
265
266        let conformance_window = conformance_window_flag
267            .then(|| ConformanceWindow::parse(&mut bit_reader))
268            .transpose()?
269            .unwrap_or_default();
270
271        let bit_depth_luma_minus8 = bit_reader.read_exp_golomb()?;
272        range_check!(bit_depth_luma_minus8, 0, 8)?;
273        let bit_depth_luma_minus8 = bit_depth_luma_minus8 as u8;
274        let bit_depth_y = 8 + bit_depth_luma_minus8; // BitDepth_Y
275        let bit_depth_chroma_minus8 = bit_reader.read_exp_golomb()?;
276        range_check!(bit_depth_chroma_minus8, 0, 8)?;
277        let bit_depth_chroma_minus8 = bit_depth_chroma_minus8 as u8;
278        let bit_depth_c = 8 + bit_depth_chroma_minus8; // BitDepth_C
279
280        let log2_max_pic_order_cnt_lsb_minus4 = bit_reader.read_exp_golomb()?;
281        range_check!(log2_max_pic_order_cnt_lsb_minus4, 0, 12)?;
282        let log2_max_pic_order_cnt_lsb_minus4 = log2_max_pic_order_cnt_lsb_minus4 as u8;
283
284        let sps_sub_layer_ordering_info_present_flag = bit_reader.read_bit()?;
285        let sub_layer_ordering_info = SubLayerOrderingInfo::parse(
286            &mut bit_reader,
287            sps_sub_layer_ordering_info_present_flag,
288            sps_max_sub_layers_minus1,
289        )?;
290
291        let log2_min_luma_coding_block_size_minus3 = bit_reader.read_exp_golomb()?;
292        let log2_diff_max_min_luma_coding_block_size = bit_reader.read_exp_golomb()?;
293
294        let min_cb_log2_size_y = log2_min_luma_coding_block_size_minus3 + 3;
295        let ctb_log2_size_y = min_cb_log2_size_y + log2_diff_max_min_luma_coding_block_size;
296
297        let log2_min_luma_transform_block_size_minus2 = bit_reader.read_exp_golomb()?;
298
299        let min_tb_log2_size_y = log2_min_luma_transform_block_size_minus2 + 2;
300
301        let log2_diff_max_min_luma_transform_block_size = bit_reader.read_exp_golomb()?;
302        let max_transform_hierarchy_depth_inter = bit_reader.read_exp_golomb()?;
303        range_check!(max_transform_hierarchy_depth_inter, 0, ctb_log2_size_y - min_tb_log2_size_y)?;
304        let max_transform_hierarchy_depth_intra = bit_reader.read_exp_golomb()?;
305        range_check!(max_transform_hierarchy_depth_intra, 0, ctb_log2_size_y - min_tb_log2_size_y)?;
306
307        let scaling_list_enabled_flag = bit_reader.read_bit()?;
308
309        let mut scaling_list_data = None;
310        if scaling_list_enabled_flag {
311            let sps_scaling_list_data_present_flag = bit_reader.read_bit()?;
312
313            if sps_scaling_list_data_present_flag {
314                scaling_list_data = Some(ScalingListData::parse(&mut bit_reader)?);
315            }
316        }
317
318        let amp_enabled_flag = bit_reader.read_bit()?;
319        let sample_adaptive_offset_enabled_flag = bit_reader.read_bit()?;
320
321        let mut pcm = None;
322        let pcm_enabled_flag = bit_reader.read_bit()?;
323        if pcm_enabled_flag {
324            pcm = Some(Pcm::parse(
325                &mut bit_reader,
326                bit_depth_y,
327                bit_depth_c,
328                min_cb_log2_size_y,
329                ctb_log2_size_y,
330            )?);
331        }
332
333        let num_short_term_ref_pic_sets = bit_reader.read_exp_golomb()?;
334        range_check!(num_short_term_ref_pic_sets, 0, 64)?;
335        let num_short_term_ref_pic_sets = num_short_term_ref_pic_sets as u8;
336        let short_term_ref_pic_sets = ShortTermRefPicSets::parse(
337            &mut bit_reader,
338            num_short_term_ref_pic_sets as usize,
339            nuh_layer_id,
340            *sub_layer_ordering_info
341                .sps_max_dec_pic_buffering_minus1
342                .last()
343                .expect("unreachable: cannot be empty"),
344        )?;
345
346        let mut long_term_ref_pics = None;
347        let long_term_ref_pics_present_flag = bit_reader.read_bit()?;
348        if long_term_ref_pics_present_flag {
349            long_term_ref_pics = Some(LongTermRefPics::parse(&mut bit_reader, log2_max_pic_order_cnt_lsb_minus4)?);
350        }
351
352        let sps_temporal_mvp_enabled_flag = bit_reader.read_bit()?;
353        let strong_intra_smoothing_enabled_flag = bit_reader.read_bit()?;
354
355        let mut vui_parameters = None;
356        let vui_parameters_present_flag = bit_reader.read_bit()?;
357        if vui_parameters_present_flag {
358            vui_parameters = Some(VuiParameters::parse(
359                &mut bit_reader,
360                sps_max_sub_layers_minus1,
361                bit_depth_y,
362                bit_depth_c,
363                chroma_format_idc,
364                &profile_tier_level.general_profile,
365                &conformance_window,
366                sub_width_c,
367                pic_width_in_luma_samples,
368                sub_height_c,
369                pic_height_in_luma_samples,
370            )?);
371        }
372
373        // Extensions
374        let mut range_extension = None;
375        let mut multilayer_extension = None;
376        let mut sps_3d_extension = None;
377        let mut scc_extension = None;
378
379        let sps_extension_flag = bit_reader.read_bit()?;
380        if sps_extension_flag {
381            let sps_range_extension_flag = bit_reader.read_bit()?;
382            let sps_multilayer_extension_flag = bit_reader.read_bit()?;
383            let sps_3d_extension_flag = bit_reader.read_bit()?;
384            let sps_scc_extension_flag = bit_reader.read_bit()?;
385            let sps_extension_4bits = bit_reader.read_bits(4)? as u8;
386
387            if sps_extension_4bits != 0 {
388                return Err(io::Error::new(io::ErrorKind::InvalidData, "sps_extension_4bits must be 0"));
389            }
390
391            if sps_range_extension_flag {
392                range_extension = Some(SpsRangeExtension::parse(&mut bit_reader)?);
393            }
394
395            if sps_multilayer_extension_flag {
396                multilayer_extension = Some(SpsMultilayerExtension::parse(&mut bit_reader)?);
397            }
398
399            if sps_3d_extension_flag {
400                sps_3d_extension = Some(Sps3dExtension::parse(&mut bit_reader, min_cb_log2_size_y, ctb_log2_size_y)?);
401            }
402
403            if sps_scc_extension_flag {
404                scc_extension = Some(SpsSccExtension::parse(
405                    &mut bit_reader,
406                    chroma_format_idc,
407                    bit_depth_y,
408                    bit_depth_c,
409                )?);
410            }
411
412            // No sps_extension_data_flag is present because sps_extension_4bits is 0.
413        }
414
415        rbsp_trailing_bits(&mut bit_reader)?;
416
417        Ok(SpsRbsp {
418            sps_video_parameter_set_id,
419            sps_max_sub_layers_minus1,
420            sps_temporal_id_nesting_flag,
421            profile_tier_level,
422            sps_seq_parameter_set_id,
423            chroma_format_idc,
424            separate_colour_plane_flag,
425            pic_width_in_luma_samples,
426            pic_height_in_luma_samples,
427            conformance_window,
428            bit_depth_luma_minus8,
429            bit_depth_chroma_minus8,
430            log2_max_pic_order_cnt_lsb_minus4,
431            sub_layer_ordering_info,
432            log2_min_luma_coding_block_size_minus3,
433            log2_diff_max_min_luma_coding_block_size,
434            log2_min_luma_transform_block_size_minus2,
435            log2_diff_max_min_luma_transform_block_size,
436            max_transform_hierarchy_depth_inter,
437            max_transform_hierarchy_depth_intra,
438            scaling_list_data,
439            amp_enabled_flag,
440            sample_adaptive_offset_enabled_flag,
441            pcm,
442            short_term_ref_pic_sets,
443            long_term_ref_pics,
444            sps_temporal_mvp_enabled_flag,
445            strong_intra_smoothing_enabled_flag,
446            vui_parameters,
447            range_extension,
448            multilayer_extension,
449            sps_3d_extension,
450            scc_extension,
451        })
452    }
453
454    /// The `croppedWidth` as a [`u64`].
455    ///
456    /// This is computed from other fields, and doesn't directly appear in the bitstream.
457    ///
458    /// `croppedWidth = pic_width_in_luma_samples - SubWidthC * (conf_win_right_offset + conf_win_left_offset)` (D-28)
459    ///
460    /// ISO/IEC 23008-2 - D.3.29
461    pub fn cropped_width(&self) -> u64 {
462        self.pic_width_in_luma_samples.get()
463            - self.sub_width_c() as u64
464                * (self.conformance_window.conf_win_left_offset + self.conformance_window.conf_win_right_offset)
465    }
466
467    /// The `croppedHeight` as a [`u64`].
468    ///
469    /// This is computed from other fields, and doesn't directly appear in the bitstream.
470    ///
471    /// `croppedHeight = pic_height_in_luma_samples - SubHeightC * (conf_win_top_offset + conf_win_bottom_offset)` (D-29)
472    ///
473    /// ISO/IEC 23008-2 - D.3.29
474    pub fn cropped_height(&self) -> u64 {
475        self.pic_height_in_luma_samples.get()
476            - self.sub_height_c() as u64
477                * (self.conformance_window.conf_win_top_offset + self.conformance_window.conf_win_bottom_offset)
478    }
479
480    /// - If [`separate_colour_plane_flag`](Self::separate_colour_plane_flag) is equal to `false`, `ChromaArrayType` is set equal to [`chroma_format_idc`](Self::chroma_format_idc).
481    /// - Otherwise ([`separate_colour_plane_flag`](Self::separate_colour_plane_flag) is equal to `true`), `ChromaArrayType` is set equal to 0.
482    ///
483    /// ISO/IEC 23008-2 - 7.4.3.2.1
484    pub fn chroma_array_type(&self) -> u8 {
485        if self.separate_colour_plane_flag {
486            0
487        } else {
488            self.chroma_format_idc
489        }
490    }
491
492    /// ISO/IEC 23008-2 - Table 6-1
493    pub fn sub_width_c(&self) -> u8 {
494        if self.chroma_format_idc == 1 || self.chroma_format_idc == 2 {
495            2
496        } else {
497            1
498        }
499    }
500
501    /// ISO/IEC 23008-2 - Table 6-1
502    pub fn sub_height_c(&self) -> u8 {
503        if self.chroma_format_idc == 1 { 2 } else { 1 }
504    }
505
506    /// The bit depth of the samples of the luma array.
507    ///
508    /// `BitDepth_Y = 8 + bit_depth_luma_minus8` (7-4)
509    ///
510    /// ISO/IEC 23008-2 - 7.4.3.2.1
511    pub fn bit_depth_y(&self) -> u8 {
512        8 + self.bit_depth_luma_minus8
513    }
514
515    /// The luma quantization parameter range offset.
516    ///
517    /// `QpBdOffset_Y = 6 * bit_depth_luma_minus8` (7-5)
518    ///
519    /// ISO/IEC 23008-2 - 7.4.3.2.1
520    pub fn qp_bd_offset_y(&self) -> u8 {
521        6 * self.bit_depth_y()
522    }
523
524    /// The bit depth of the samples of the chroma arrays.
525    ///
526    /// `BitDepth_C = 8 + bit_depth_chroma_minus8` (7-6)
527    ///
528    /// ISO/IEC 23008-2 - 7.4.3.2.1
529    #[inline]
530    pub fn bit_depth_c(&self) -> u8 {
531        8 + self.bit_depth_chroma_minus8
532    }
533
534    /// The chroma quantization parameter range offset.
535    ///
536    /// `QpBdOffset_C = 6 * bit_depth_chroma_minus8` (7-7)
537    ///
538    /// ISO/IEC 23008-2 - 7.4.3.2.1
539    pub fn qp_bd_offset_c(&self) -> u8 {
540        6 * self.bit_depth_c()
541    }
542
543    /// Used in the decoding process for picture order count.
544    ///
545    /// `MaxPicOrderCntLsb = 2^(log2_max_pic_order_cnt_lsb_minus4 + 4)` (7-8)
546    ///
547    /// ISO/IEC 23008-2 - 7.4.3.2.1
548    pub fn max_pic_order_cnt_lsb(&self) -> u32 {
549        2u32.pow(self.log2_max_pic_order_cnt_lsb_minus4 as u32 + 4)
550    }
551
552    /// `MinCbLog2SizeY = log2_min_luma_coding_block_size_minus3 + 3` (7-10)
553    ///
554    /// ISO/IEC 23008-2 - 7.4.3.2.1
555    pub fn min_cb_log2_size_y(&self) -> u64 {
556        self.log2_min_luma_coding_block_size_minus3 + 3
557    }
558
559    /// `CtbLog2SizeY = MinCbLog2SizeY + log2_diff_max_min_luma_coding_block_size` (7-11)
560    ///
561    /// ISO/IEC 23008-2 - 7.4.3.2.1
562    pub fn ctb_log2_size_y(&self) -> u64 {
563        self.min_cb_log2_size_y() + self.log2_diff_max_min_luma_coding_block_size
564    }
565
566    /// `MinCbSizeY = 1 << MinCbLog2SizeY` (7-12)
567    ///
568    /// ISO/IEC 23008-2 - 7.4.3.2.1
569    pub fn min_cb_size_y(&self) -> u64 {
570        1 << self.min_cb_log2_size_y()
571    }
572
573    /// `CtbSizeY = 1 << CtbLog2SizeY` (7-13)
574    ///
575    /// ISO/IEC 23008-2 - 7.4.3.2.1
576    pub fn ctb_size_y(&self) -> NonZero<u64> {
577        NonZero::new(1 << self.ctb_log2_size_y()).unwrap()
578    }
579
580    /// `PicWidthInMinCbsY = pic_width_in_luma_samples / MinCbSizeY` (7-14)
581    ///
582    /// ISO/IEC 23008-2 - 7.4.3.2.1
583    pub fn pic_width_in_min_cbs_y(&self) -> u64 {
584        self.pic_width_in_luma_samples.get() / self.min_cb_size_y()
585    }
586
587    /// `PicWidthInCtbsY = Ceil(pic_width_in_luma_samples ÷ CtbSizeY)` (7-15)
588    ///
589    /// ISO/IEC 23008-2 - 7.4.3.2.1
590    pub fn pic_width_in_ctbs_y(&self) -> u64 {
591        (self.pic_width_in_luma_samples.get() / self.ctb_size_y()) + 1
592    }
593
594    /// `PicHeightInMinCbsY = pic_height_in_luma_samples / MinCbSizeY` (7-16)
595    ///
596    /// ISO/IEC 23008-2 - 7.4.3.2.1
597    pub fn pic_height_in_min_cbs_y(&self) -> u64 {
598        self.pic_height_in_luma_samples.get() / self.min_cb_size_y()
599    }
600
601    /// `PicHeightInCtbsY = Ceil(pic_height_in_luma_samples ÷ CtbSizeY)` (7-17)
602    ///
603    /// ISO/IEC 23008-2 - 7.4.3.2.1
604    pub fn pic_height_in_ctbs_y(&self) -> u64 {
605        (self.pic_height_in_luma_samples.get() / self.ctb_size_y()) + 1
606    }
607
608    /// `PicSizeInMinCbsY = PicWidthInMinCbsY * PicHeightInMinCbsY` (7-18)
609    ///
610    /// ISO/IEC 23008-2 - 7.4.3.2.1
611    pub fn pic_size_in_min_cbs_y(&self) -> u64 {
612        self.pic_width_in_min_cbs_y() * self.pic_height_in_min_cbs_y()
613    }
614
615    /// `PicSizeInCtbsY = PicWidthInCtbsY * PicHeightInCtbsY` (7-19)
616    ///
617    /// ISO/IEC 23008-2 - 7.4.3.2.1
618    pub fn pic_size_in_ctbs_y(&self) -> u64 {
619        self.pic_width_in_ctbs_y() * self.pic_height_in_ctbs_y()
620    }
621
622    /// `PicSizeInSamplesY = pic_width_in_luma_samples * pic_height_in_luma_samples` (7-20)
623    ///
624    /// ISO/IEC 23008-2 - 7.4.3.2.1
625    pub fn pic_size_in_samples_y(&self) -> u64 {
626        self.pic_width_in_luma_samples.get() * self.pic_height_in_luma_samples.get()
627    }
628
629    /// `PicWidthInSamplesC = pic_width_in_luma_samples / SubWidthC` (7-21)
630    ///
631    /// ISO/IEC 23008-2 - 7.4.3.2.1
632    pub fn pic_width_in_samples_c(&self) -> u64 {
633        self.pic_width_in_luma_samples.get() / self.sub_width_c() as u64
634    }
635
636    /// `PicHeightInSamplesC = pic_height_in_luma_samples / SubHeightC` (7-22)
637    ///
638    /// ISO/IEC 23008-2 - 7.4.3.2.1
639    pub fn pic_height_in_samples_c(&self) -> u64 {
640        self.pic_height_in_luma_samples.get() / self.sub_height_c() as u64
641    }
642
643    /// - If `chroma_format_idc` is equal to 0 (monochrome) or [`separate_colour_plane_flag`](Self::separate_colour_plane_flag) is equal to `true`,
644    ///   `CtbWidthC` is equal to 0.
645    /// - Otherwise, `CtbWidthC` is derived as follows: `CtbWidthC = CtbSizeY / SubWidthC` (7-23)
646    ///
647    /// ISO/IEC 23008-2 - 7.4.3.2.1
648    pub fn ctb_width_c(&self) -> u64 {
649        if self.chroma_format_idc == 0 || self.separate_colour_plane_flag {
650            0
651        } else {
652            self.ctb_size_y().get() / self.sub_width_c() as u64
653        }
654    }
655
656    /// - If `chroma_format_idc` is equal to 0 (monochrome) or [`separate_colour_plane_flag`](Self::separate_colour_plane_flag) is equal to `true`,
657    ///   `CtbHeightC` is equal to 0.
658    /// - Otherwise, `CtbHeightC` is derived as follows: `CtbHeightC = CtbSizeY / SubHeightC` (7-24)
659    ///
660    /// ISO/IEC 23008-2 - 7.4.3.2.1
661    pub fn ctb_height_c(&self) -> u64 {
662        if self.chroma_format_idc == 0 || self.separate_colour_plane_flag {
663            0
664        } else {
665            self.ctb_size_y().get() / self.sub_height_c() as u64
666        }
667    }
668
669    /// `MinTbLog2SizeY` is set equal to [`log2_min_luma_transform_block_size_minus2 + 2`](Self::log2_min_luma_transform_block_size_minus2).
670    ///
671    /// The CVS shall not contain data that result in `MinTbLog2SizeY`
672    /// greater than or equal to [`MinCbLog2SizeY`](Self::min_cb_log2_size_y).
673    ///
674    /// ISO/IEC 23008-2 - 7.4.3.2.1
675    pub fn min_tb_log2_size_y(&self) -> u64 {
676        self.log2_min_luma_transform_block_size_minus2 + 2
677    }
678
679    /// `MaxTbLog2SizeY = log2_min_luma_transform_block_size_minus2 + 2 + log2_diff_max_min_luma_transform_block_size`
680    ///
681    /// The CVS shall not contain data that result in `MaxTbLog2SizeY` greater than [`Min(CtbLog2SizeY, 5)`](Self::ctb_log2_size_y).
682    ///
683    /// ISO/IEC 23008-2 - 7.4.3.2.1
684    pub fn max_tb_log2_size_y(&self) -> u64 {
685        self.log2_min_luma_transform_block_size_minus2 + 2 + self.log2_diff_max_min_luma_transform_block_size
686    }
687
688    /// `RawCtuBits = CtbSizeY * CtbSizeY * BitDepthY + 2 * (CtbWidthC * CtbHeightC) * BitDepthC` (A-1)
689    ///
690    /// ISO/IEC 23008-2 - A.3.1
691    pub fn raw_ctu_bits(&self) -> u64 {
692        let ctb_size_y = self.ctb_size_y().get();
693        ctb_size_y * ctb_size_y * self.bit_depth_y() as u64
694            + 2 * (self.ctb_width_c() * self.ctb_height_c()) * self.bit_depth_c() as u64
695    }
696}
697
698#[cfg(test)]
699#[cfg_attr(all(test, coverage_nightly), coverage(off))]
700mod tests {
701    use std::io;
702
703    use crate::SpsNALUnit;
704
705    // To compare the results to an independent source, you can use: https://github.com/chemag/h265nal
706
707    #[test]
708    fn test_sps_parse() {
709        let data = b"B\x01\x01\x01@\0\0\x03\0\x90\0\0\x03\0\0\x03\0\x99\xa0\x01@ \x05\xa1e\x95R\x90\x84d_\xf8\xc0Z\x80\x80\x80\x82\0\0\x03\0\x02\0\0\x03\x01 \xc0\x0b\xbc\xa2\0\x02bX\0\x011-\x08";
710
711        let nalu = SpsNALUnit::parse(io::Cursor::new(data)).unwrap();
712        let sps = &nalu.rbsp;
713
714        assert_eq!(sps.cropped_width(), 2560);
715        assert_eq!(sps.cropped_height(), 1440);
716        assert_eq!(sps.chroma_array_type(), 1);
717        assert_eq!(sps.sub_width_c(), 2);
718        assert_eq!(sps.sub_height_c(), 2);
719        assert_eq!(sps.bit_depth_y(), 8);
720        assert_eq!(sps.qp_bd_offset_y(), 48);
721        assert_eq!(sps.bit_depth_c(), 8);
722        assert_eq!(sps.qp_bd_offset_c(), 48);
723        assert_eq!(sps.max_pic_order_cnt_lsb(), 256);
724        assert_eq!(sps.min_cb_log2_size_y(), 4);
725        assert_eq!(sps.ctb_log2_size_y(), 5);
726        assert_eq!(sps.min_cb_size_y(), 16);
727        assert_eq!(sps.ctb_size_y().get(), 32);
728        assert_eq!(sps.pic_width_in_min_cbs_y(), 160);
729        assert_eq!(sps.pic_width_in_ctbs_y(), 81);
730        assert_eq!(sps.pic_height_in_min_cbs_y(), 90);
731        assert_eq!(sps.pic_height_in_ctbs_y(), 46);
732        assert_eq!(sps.pic_size_in_min_cbs_y(), 14400);
733        assert_eq!(sps.pic_size_in_ctbs_y(), 3726);
734        assert_eq!(sps.pic_size_in_samples_y(), 3686400);
735        assert_eq!(sps.pic_width_in_samples_c(), 1280);
736        assert_eq!(sps.pic_height_in_samples_c(), 720);
737        assert_eq!(sps.ctb_width_c(), 16);
738        assert_eq!(sps.ctb_height_c(), 16);
739        assert_eq!(sps.min_tb_log2_size_y(), 2);
740        assert_eq!(sps.max_tb_log2_size_y(), 5);
741        assert_eq!(sps.raw_ctu_bits(), 12288);
742        insta::assert_debug_snapshot!(nalu);
743    }
744
745    #[test]
746    fn test_sps_parse2() {
747        // This is a real SPS from an mp4 video file recorded with OBS.
748        let data = b"\x42\x01\x01\x01\x40\x00\x00\x03\x00\x90\x00\x00\x03\x00\x00\x03\x00\x78\xa0\x03\xc0\x80\x11\x07\xcb\x96\xb4\xa4\x25\x92\xe3\x01\x6a\x02\x02\x02\x08\x00\x00\x03\x00\x08\x00\x00\x03\x00\xf3\x00\x2e\xf2\x88\x00\x02\x62\x5a\x00\x00\x13\x12\xd0\x20";
749
750        let nalu = SpsNALUnit::parse(io::Cursor::new(data)).unwrap();
751        let sps = &nalu.rbsp;
752
753        assert_eq!(sps.cropped_width(), 1920);
754        assert_eq!(sps.cropped_height(), 1080);
755        assert_eq!(sps.chroma_array_type(), 1);
756        assert_eq!(sps.sub_width_c(), 2);
757        assert_eq!(sps.sub_height_c(), 2);
758        assert_eq!(sps.bit_depth_y(), 8);
759        assert_eq!(sps.qp_bd_offset_y(), 48);
760        assert_eq!(sps.bit_depth_c(), 8);
761        assert_eq!(sps.qp_bd_offset_c(), 48);
762        assert_eq!(sps.max_pic_order_cnt_lsb(), 256);
763        assert_eq!(sps.min_cb_log2_size_y(), 4);
764        assert_eq!(sps.ctb_log2_size_y(), 5);
765        assert_eq!(sps.min_cb_size_y(), 16);
766        assert_eq!(sps.ctb_size_y().get(), 32);
767        assert_eq!(sps.pic_width_in_min_cbs_y(), 120);
768        assert_eq!(sps.pic_width_in_ctbs_y(), 61);
769        assert_eq!(sps.pic_height_in_min_cbs_y(), 68);
770        assert_eq!(sps.pic_height_in_ctbs_y(), 35);
771        assert_eq!(sps.pic_size_in_min_cbs_y(), 8160);
772        assert_eq!(sps.pic_size_in_ctbs_y(), 2135);
773        assert_eq!(sps.pic_size_in_samples_y(), 2088960);
774        assert_eq!(sps.pic_width_in_samples_c(), 960);
775        assert_eq!(sps.pic_height_in_samples_c(), 544);
776        assert_eq!(sps.ctb_width_c(), 16);
777        assert_eq!(sps.ctb_height_c(), 16);
778        assert_eq!(sps.min_tb_log2_size_y(), 2);
779        assert_eq!(sps.max_tb_log2_size_y(), 5);
780        assert_eq!(sps.raw_ctu_bits(), 12288);
781        insta::assert_debug_snapshot!(nalu);
782    }
783
784    #[test]
785    fn test_sps_parse3() {
786        // This is a real SPS from here: https://kodi.wiki/view/Samples
787        let data = b"\x42\x01\x01\x22\x20\x00\x00\x03\x00\x90\x00\x00\x03\x00\x00\x03\x00\x99\xA0\x01\xE0\x20\x02\x1C\x4D\x8D\x35\x92\x4F\x84\x14\x70\xF1\xC0\x90\x3B\x0E\x18\x36\x1A\x08\x42\xF0\x81\x21\x00\x88\x40\x10\x06\xE1\xA3\x06\xC3\x41\x08\x5C\xA0\xA0\x21\x04\x41\x70\xB0\x2A\x0A\xC2\x80\x35\x40\x70\x80\xE0\x07\xD0\x2B\x41\x80\xA8\x20\x0B\x85\x81\x50\x56\x14\x01\xAA\x03\x84\x07\x00\x3E\x81\x58\xA1\x0D\x35\xE9\xE8\x60\xD7\x43\x03\x41\xB1\xB8\xC0\xD0\x70\x3A\x1B\x1B\x18\x1A\x0E\x43\x21\x30\xC8\x60\x24\x18\x10\x1F\x1F\x1C\x1E\x30\x74\x26\x12\x0E\x0C\x04\x30\x40\x38\x10\x82\x00\x94\x0F\xF0\x86\x9A\xF2\x17\x20\x48\x26\x59\x02\x41\x20\x98\x4F\x09\x04\x83\x81\xD0\x98\x4E\x12\x09\x07\x21\x90\x98\x5C\x2C\x12\x0C\x08\x0F\x8F\x8E\x0F\x18\x3A\x13\x09\x07\x06\x02\x18\x20\x1C\x08\x41\x00\x4A\x07\xF2\x86\x89\x4D\x08\x2C\x83\x8E\x52\x18\x17\x02\xF2\xC8\x0B\x80\xDC\x06\xB0\x5F\x82\xE0\x35\x03\xA0\x66\x06\xB0\x63\x06\x00\x6A\x06\x40\xE0\x0B\x20\x73\x06\x60\xC8\x0E\x40\x58\x03\x90\x0A\xB0\x77\x07\x40\x2A\x81\xC7\xFF\xC1\x24\x34\x49\x8E\x61\x82\x62\x0C\x72\x90\xC0\xB8\x17\x96\x40\x5C\x06\xE0\x35\x82\xFC\x17\x01\xA8\x1D\x03\x30\x35\x83\x18\x30\x03\x50\x32\x07\x00\x59\x03\x98\x33\x06\x40\x72\x02\xC0\x1C\x80\x55\x83\xB8\x3A\x01\x54\x0E\x3F\xFE\x09\x0A\x10\xE9\xAF\x4F\x43\x06\xBA\x18\x1A\x0D\x8D\xC6\x06\x83\x81\xD0\xD8\xD8\xC0\xD0\x72\x19\x09\x86\x43\x01\x20\xC0\x80\xF8\xF8\xE0\xF1\x83\xA1\x30\x90\x70\x60\x21\x82\x01\xC0\x84\x10\x04\xA0\x7F\x84\x3A\x6B\xC8\x5C\x81\x20\x99\x64\x09\x04\x82\x61\x3C\x24\x12\x0E\x07\x42\x61\x38\x48\x24\x1C\x86\x42\x61\x70\xB0\x48\x30\x20\x3E\x3E\x38\x3C\x60\xE8\x4C\x24\x1C\x18\x08\x60\x80\x70\x21\x04\x01\x28\x1F\xCA\x1A\x92\x9A\x10\x59\x07\x1C\xA4\x30\x2E\x05\xE5\x90\x17\x01\xB8\x0D\x60\xBF\x05\xC0\x6A\x07\x40\xCC\x0D\x60\xC6\x0C\x00\xD4\x0C\x81\xC0\x16\x40\xE6\x0C\xC1\x90\x1C\x80\xB0\x07\x20\x15\x60\xEE\x0E\x80\x55\x03\x8F\xFF\x82\x48\x6A\x49\x8E\x61\x82\x62\x0C\x72\x90\xC0\xB8\x17\x96\x40\x5C\x06\xE0\x35\x82\xFC\x17\x01\xA8\x1D\x03\x30\x35\x83\x18\x30\x03\x50\x32\x07\x00\x59\x03\x98\x33\x06\x40\x72\x02\xC0\x1C\x80\x55\x83\xB8\x3A\x01\x54\x0E\x3F\xFE\x09\x0A\x10\xE9\xAF\x4F\x43\x06\xBA\x18\x1A\x0D\x8D\xC6\x06\x83\x81\xD0\xD8\xD8\xC0\xD0\x72\x19\x09\x86\x43\x01\x20\xC0\x80\xF8\xF8\xE0\xF1\x83\xA1\x30\x90\x70\x60\x21\x82\x01\xC0\x84\x10\x04\xA0\x7F\x86\xA4\x98\xE6\x18\x26\x20\xC7\x29\x0C\x0B\x81\x79\x64\x05\xC0\x6E\x03\x58\x2F\xC1\x70\x1A\x81\xD0\x33\x03\x58\x31\x83\x00\x35\x03\x20\x70\x05\x90\x39\x83\x30\x64\x07\x20\x2C\x01\xC8\x05\x58\x3B\x83\xA0\x15\x40\xE3\xFF\xE0\x91\x11\x5C\x96\xA5\xDE\x02\xD4\x24\x40\x26\xD9\x40\x00\x07\xD2\x00\x01\xD4\xC0\x3E\x46\x81\x8D\xC0\x00\x26\x25\xA0\x00\x13\x12\xD0\x00\x04\xC4\xB4\x00\x02\x62\x5A\x8B\x84\x02\x08\xA2\x00\x01\x00\x08\x44\x01\xC1\x72\x43\x8D\x62\x24\x00\x00\x00\x14";
788
789        let nalu = SpsNALUnit::parse(io::Cursor::new(data)).unwrap();
790        let sps = &nalu.rbsp;
791
792        assert_eq!(sps.cropped_width(), 3840);
793        assert_eq!(sps.cropped_height(), 2160);
794        assert_eq!(sps.chroma_array_type(), 1);
795        assert_eq!(sps.sub_width_c(), 2);
796        assert_eq!(sps.sub_height_c(), 2);
797        assert_eq!(sps.bit_depth_y(), 10);
798        assert_eq!(sps.qp_bd_offset_y(), 60);
799        assert_eq!(sps.bit_depth_c(), 10);
800        assert_eq!(sps.qp_bd_offset_c(), 60);
801        assert_eq!(sps.max_pic_order_cnt_lsb(), 65536);
802        assert_eq!(sps.min_cb_log2_size_y(), 3);
803        assert_eq!(sps.ctb_log2_size_y(), 6);
804        assert_eq!(sps.min_cb_size_y(), 8);
805        assert_eq!(sps.ctb_size_y().get(), 64);
806        assert_eq!(sps.pic_width_in_min_cbs_y(), 480);
807        assert_eq!(sps.pic_width_in_ctbs_y(), 61);
808        assert_eq!(sps.pic_height_in_min_cbs_y(), 270);
809        assert_eq!(sps.pic_height_in_ctbs_y(), 34);
810        assert_eq!(sps.pic_size_in_min_cbs_y(), 129600);
811        assert_eq!(sps.pic_size_in_ctbs_y(), 2074);
812        assert_eq!(sps.pic_size_in_samples_y(), 8294400);
813        assert_eq!(sps.pic_width_in_samples_c(), 1920);
814        assert_eq!(sps.pic_height_in_samples_c(), 1080);
815        assert_eq!(sps.ctb_width_c(), 32);
816        assert_eq!(sps.ctb_height_c(), 32);
817        assert_eq!(sps.min_tb_log2_size_y(), 2);
818        assert_eq!(sps.max_tb_log2_size_y(), 5);
819        assert_eq!(sps.raw_ctu_bits(), 61440);
820        insta::assert_debug_snapshot!(nalu);
821    }
822
823    #[test]
824    fn test_sps_parse4() {
825        // This is a real SPS from here: https://lf-tk-sg.ibytedtos.com/obj/tcs-client-sg/resources/video_demo_hevc.html#main-bt709-sample-5
826        let data = b"\x42\x01\x01\x01\x60\x00\x00\x03\x00\x90\x00\x00\x03\x00\x00\x03\x00\xB4\xA0\x00\xF0\x08\x00\x43\x85\x96\x56\x69\x24\xC2\xB0\x16\x80\x80\x00\x00\x03\x00\x80\x00\x00\x05\x04\x22\x00\x01";
827
828        let nalu = SpsNALUnit::parse(io::Cursor::new(data)).unwrap();
829        let sps = &nalu.rbsp;
830
831        assert_eq!(sps.cropped_width(), 7680);
832        assert_eq!(sps.cropped_height(), 4320);
833        assert_eq!(sps.chroma_array_type(), 1);
834        assert_eq!(sps.sub_width_c(), 2);
835        assert_eq!(sps.sub_height_c(), 2);
836        assert_eq!(sps.bit_depth_y(), 8);
837        assert_eq!(sps.qp_bd_offset_y(), 48);
838        assert_eq!(sps.bit_depth_c(), 8);
839        assert_eq!(sps.qp_bd_offset_c(), 48);
840        assert_eq!(sps.max_pic_order_cnt_lsb(), 256);
841        assert_eq!(sps.min_cb_log2_size_y(), 3);
842        assert_eq!(sps.ctb_log2_size_y(), 6);
843        assert_eq!(sps.min_cb_size_y(), 8);
844        assert_eq!(sps.ctb_size_y().get(), 64);
845        assert_eq!(sps.pic_width_in_min_cbs_y(), 960);
846        assert_eq!(sps.pic_width_in_ctbs_y(), 121);
847        assert_eq!(sps.pic_height_in_min_cbs_y(), 540);
848        assert_eq!(sps.pic_height_in_ctbs_y(), 68);
849        assert_eq!(sps.pic_size_in_min_cbs_y(), 518400);
850        assert_eq!(sps.pic_size_in_ctbs_y(), 8228);
851        assert_eq!(sps.pic_size_in_samples_y(), 33177600);
852        assert_eq!(sps.pic_width_in_samples_c(), 3840);
853        assert_eq!(sps.pic_height_in_samples_c(), 2160);
854        assert_eq!(sps.ctb_width_c(), 32);
855        assert_eq!(sps.ctb_height_c(), 32);
856        assert_eq!(sps.min_tb_log2_size_y(), 2);
857        assert_eq!(sps.max_tb_log2_size_y(), 5);
858        assert_eq!(sps.raw_ctu_bits(), 49152);
859        insta::assert_debug_snapshot!(nalu);
860    }
861
862    #[test]
863    fn test_sps_parse5() {
864        // This is a real SPS from here: https://lf-tk-sg.ibytedtos.com/obj/tcs-client-sg/resources/video_demo_hevc.html#msp-bt709-sample-1
865        let data = b"\x42\x01\x01\x03\x70\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x78\xA0\x03\xC0\x80\x10\xE7\xF9\x7E\x49\x1B\x65\xB2\x22\x00\x01\x00\x07\x44\x01\xC1\x90\x95\x81\x12\x00\x00\x00\x14";
866
867        let nalu = SpsNALUnit::parse(io::Cursor::new(data)).unwrap();
868        let sps = &nalu.rbsp;
869
870        assert_eq!(sps.cropped_width(), 1920);
871        assert_eq!(sps.cropped_height(), 1080);
872        assert_eq!(sps.chroma_array_type(), 1);
873        assert_eq!(sps.sub_width_c(), 2);
874        assert_eq!(sps.sub_height_c(), 2);
875        assert_eq!(sps.bit_depth_y(), 8);
876        assert_eq!(sps.qp_bd_offset_y(), 48);
877        assert_eq!(sps.bit_depth_c(), 8);
878        assert_eq!(sps.qp_bd_offset_c(), 48);
879        assert_eq!(sps.max_pic_order_cnt_lsb(), 256);
880        assert_eq!(sps.min_cb_log2_size_y(), 3);
881        assert_eq!(sps.ctb_log2_size_y(), 6);
882        assert_eq!(sps.min_cb_size_y(), 8);
883        assert_eq!(sps.ctb_size_y().get(), 64);
884        assert_eq!(sps.pic_width_in_min_cbs_y(), 240);
885        assert_eq!(sps.pic_width_in_ctbs_y(), 31);
886        assert_eq!(sps.pic_height_in_min_cbs_y(), 135);
887        assert_eq!(sps.pic_height_in_ctbs_y(), 17);
888        assert_eq!(sps.pic_size_in_min_cbs_y(), 32400);
889        assert_eq!(sps.pic_size_in_ctbs_y(), 527);
890        assert_eq!(sps.pic_size_in_samples_y(), 2073600);
891        assert_eq!(sps.pic_width_in_samples_c(), 960);
892        assert_eq!(sps.pic_height_in_samples_c(), 540);
893        assert_eq!(sps.ctb_width_c(), 32);
894        assert_eq!(sps.ctb_height_c(), 32);
895        assert_eq!(sps.min_tb_log2_size_y(), 2);
896        assert_eq!(sps.max_tb_log2_size_y(), 5);
897        assert_eq!(sps.raw_ctu_bits(), 49152);
898        insta::assert_debug_snapshot!(nalu);
899    }
900
901    #[test]
902    fn test_sps_parse6() {
903        // This is a real SPS from here: https://lf-tk-sg.ibytedtos.com/obj/tcs-client-sg/resources/video_demo_hevc.html#rext-bt709-sample-1
904        let data = b"\x42\x01\x01\x24\x08\x00\x00\x03\x00\x9D\x08\x00\x00\x03\x00\x00\x99\xB0\x01\xE0\x20\x02\x1C\x4D\x94\xD6\xED\xBE\x41\x12\x64\xEB\x25\x11\x44\x1A\x6C\x9D\x64\xA2\x29\x09\x26\xBA\xF5\xFF\xEB\xFA\xFD\x7F\xEB\xF5\x44\x51\x04\x93\x5D\x7A\xFF\xF5\xFD\x7E\xBF\xF5\xFA\xC8\xA4\x92\x4D\x75\xEB\xFF\xD7\xF5\xFA\xFF\xD7\xEA\x88\xA2\x24\x93\x5D\x7A\xFF\xF5\xFD\x7E\xBF\xF5\xFA\xC8\x94\x08\x53\x49\x29\x24\x89\x55\x12\xA5\x2A\x94\xC1\x35\x01\x01\x01\x03\xB8\x40\x20\x80\xA2\x00\x01\x00\x07\x44\x01\xC0\x72\xB0\x3C\x90\x00\x00\x00\x13\x63\x6F\x6C\x72\x6E\x63\x6C\x78\x00\x01\x00\x01\x00\x01\x00\x00\x00\x00\x18";
905
906        let nalu = SpsNALUnit::parse(io::Cursor::new(data)).unwrap();
907        let sps = &nalu.rbsp;
908
909        assert_eq!(sps.cropped_width(), 3840);
910        assert_eq!(sps.cropped_height(), 2160);
911        assert_eq!(sps.chroma_array_type(), 2);
912        assert_eq!(sps.sub_width_c(), 2);
913        assert_eq!(sps.sub_height_c(), 1);
914        assert_eq!(sps.bit_depth_y(), 10);
915        assert_eq!(sps.qp_bd_offset_y(), 60);
916        assert_eq!(sps.bit_depth_c(), 10);
917        assert_eq!(sps.qp_bd_offset_c(), 60);
918        assert_eq!(sps.max_pic_order_cnt_lsb(), 256);
919        assert_eq!(sps.min_cb_log2_size_y(), 3);
920        assert_eq!(sps.ctb_log2_size_y(), 5);
921        assert_eq!(sps.min_cb_size_y(), 8);
922        assert_eq!(sps.ctb_size_y().get(), 32);
923        assert_eq!(sps.pic_width_in_min_cbs_y(), 480);
924        assert_eq!(sps.pic_width_in_ctbs_y(), 121);
925        assert_eq!(sps.pic_height_in_min_cbs_y(), 270);
926        assert_eq!(sps.pic_height_in_ctbs_y(), 68);
927        assert_eq!(sps.pic_size_in_min_cbs_y(), 129600);
928        assert_eq!(sps.pic_size_in_ctbs_y(), 8228);
929        assert_eq!(sps.pic_size_in_samples_y(), 8294400);
930        assert_eq!(sps.pic_width_in_samples_c(), 1920);
931        assert_eq!(sps.pic_height_in_samples_c(), 2160);
932        assert_eq!(sps.ctb_width_c(), 16);
933        assert_eq!(sps.ctb_height_c(), 32);
934        assert_eq!(sps.min_tb_log2_size_y(), 2);
935        assert_eq!(sps.max_tb_log2_size_y(), 4);
936        assert_eq!(sps.raw_ctu_bits(), 20480);
937        insta::assert_debug_snapshot!(nalu);
938    }
939
940    #[test]
941    fn test_sps_parse_inter_ref_prediction() {
942        // I generated this sample using the reference encoder https://vcgit.hhi.fraunhofer.de/jvet/HM
943        let data = b"\x42\x01\x01\x01\x60\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\x03\x00\x00\xA0\x0B\x08\x04\x85\x96\x5E\x49\x1B\x60\xD9\x78\x88\x88\x8F\xE7\x9F\xCF\xE7\xF3\xF9\xFC\xF2\xFF\xFF\xFF\xCF\xE7\xF3\xF9\xFC\xFE\x7F\x3F\x3F\x9F\xCF\xE7\xF3\xF9\xDB\x20";
944
945        let nalu = SpsNALUnit::parse(io::Cursor::new(data)).unwrap();
946        insta::assert_debug_snapshot!(nalu);
947    }
948
949    #[test]
950    fn test_forbidden_zero_bit() {
951        // 0x80 = 1000 0000: forbidden_zero_bit (first bit) is 1.
952        let data = [0x80];
953        let err = SpsNALUnit::parse(io::Cursor::new(data)).unwrap_err();
954        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
955        assert_eq!(err.to_string(), "forbidden_zero_bit is not zero");
956    }
957
958    #[test]
959    fn test_invalid_nalu_type() {
960        // 1 forbidden_zero_bit = 0
961        // nal_unit_type (100000) = 32 ≠ 33
962        // nuh_layer_id (000000) = 0
963        // nuh_temporal_id_plus1 (001) = 1
964        #[allow(clippy::unusual_byte_groupings)]
965        let data = [0b0_100000_0, 0b00000_001];
966        let err = SpsNALUnit::parse(io::Cursor::new(data)).unwrap_err();
967        assert_eq!(err.kind(), io::ErrorKind::InvalidData);
968        assert_eq!(err.to_string(), "nal_unit_type is not SPS_NUT");
969    }
970}