scuffle_h265/sps/
mod.rs

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