scuffle_h265/sps/vui_parameters/
mod.rs

1use std::io;
2use std::num::NonZero;
3
4use byteorder::{BigEndian, ReadBytesExt};
5use scuffle_bytes_util::{BitReader, range_check};
6use scuffle_expgolomb::BitReaderExpGolombExt;
7
8use super::{ConformanceWindow, Profile};
9use crate::{AspectRatioIdc, VideoFormat};
10
11mod hrd_parameters;
12
13pub use hrd_parameters::*;
14
15/// VUI parameters.
16///
17/// `vui_parameters()`
18///
19/// - ISO/IEC 23008-2 - E.2.1
20/// - ISO/IEC 23008-2 - E.3.1
21#[derive(Debug, Clone, PartialEq)]
22pub struct VuiParameters {
23    /// [`AspectRatioInfo`] if `aspect_ratio_info_present_flag` is `true`.
24    pub aspect_ratio_info: AspectRatioInfo,
25    /// Equal to `true` indicates that the cropped decoded pictures output are suitable
26    /// for display using overscan.
27    ///
28    /// Equal to `false` indicates that the cropped decoded pictures output contain visually important information
29    /// in the entire region out to the edges of the conformance cropping window of the picture, such that the
30    /// cropped decoded pictures output should not be displayed using overscan. Instead, they should be displayed
31    /// using either an exact match between the display area and the conformance cropping window, or using underscan.
32    /// As used in this paragraph, the term "overscan" refers to display processes in which some parts near the
33    /// borders of the cropped decoded pictures are not visible in the display area. The term "underscan" describes
34    /// display processes in which the entire cropped decoded pictures are visible in the display area, but they do
35    /// not cover the entire display area. For display processes that neither use overscan nor underscan, the display
36    /// area exactly matches the area of the cropped decoded pictures.
37    ///
38    /// Only present if `overscan_info_present_flag` is `true`.
39    pub overscan_appropriate_flag: Option<bool>,
40    /// `video_format`, `video_full_range_flag` and `colour_primaries`, if `video_signal_type_present_flag` is `true`.
41    ///
42    /// See [`VideoSignalType`] for details.
43    pub video_signal_type: VideoSignalType,
44    /// `chroma_sample_loc_type_top_field` and `chroma_sample_loc_type_bottom_field`, if `chroma_loc_info_present_flag` is `true`.
45    ///
46    /// See [`ChromaLocInfo`] for details.
47    pub chroma_loc_info: Option<ChromaLocInfo>,
48    /// Equal to `true` indicates that the value of all decoded chroma samples is
49    /// equal to [`1 << (BitDepthC − 1)`](crate::SpsRbsp::bit_depth_c).
50    ///
51    /// Equal to `false` provides no indication of decoded chroma sample values.
52    pub neutral_chroma_indication_flag: bool,
53    /// Equal to `true` indicates that the CVS conveys pictures that represent fields, and specifies that
54    /// a picture timing SEI message shall be present in every access unit of the current CVS.
55    ///
56    /// Equal to `false` indicates that the CVS conveys pictures that represent frames and that a picture timing SEI message
57    /// may or may not be present in any access unit of the current CVS.
58    pub field_seq_flag: bool,
59    /// Equal to `true` specifies that picture timing SEI messages are present for every
60    /// picture and include the `pic_struct`, `source_scan_type`, and `duplicate_flag` syntax elements.
61    ///
62    /// Equal to `false` specifies that the `pic_struct` syntax element is not present in
63    /// picture timing SEI messages.
64    pub frame_field_info_present_flag: bool,
65    /// `def_disp_win_left_offset`, `def_disp_win_right_offset`, `def_disp_win_top_offset` and `def_disp_win_bottom_offset`,
66    /// if `default_display_window_flag` is `true`.
67    ///
68    /// See [`DefaultDisplayWindow`] for details.
69    pub default_display_window: DefaultDisplayWindow,
70    /// `vui_num_units_in_tick`, `vui_time_scale`, `vui_poc_proportional_to_timing_flag` and `vui_num_ticks_poc_diff_one_minus1`,
71    /// if `vui_timing_info_present_flag` is `true`.
72    ///
73    /// See [`VuiTimingInfo`] for details.
74    pub vui_timing_info: Option<VuiTimingInfo>,
75    /// `tiles_fixed_structure_flag`, `motion_vectors_over_pic_boundaries_flag`, `restricted_ref_pic_lists_flag`,
76    /// `min_spatial_segmentation_idc`, `max_bytes_per_pic_denom`, `max_bits_per_min_cu_denom`,
77    /// `log2_max_mv_length_horizontal` and `log2_max_mv_length_vertical`, if `bitstream_restriction_flag` is `true`.
78    ///
79    /// See [`BitStreamRestriction`] for details.
80    pub bitstream_restriction: BitStreamRestriction,
81}
82
83impl VuiParameters {
84    // TODO: Find a solution for this
85    #[allow(clippy::too_many_arguments)]
86    pub(crate) fn parse<R: io::Read>(
87        bit_reader: &mut BitReader<R>,
88        sps_max_sub_layers_minus1: u8,
89        bit_depth_y: u8,
90        bit_depth_c: u8,
91        chroma_format_idc: u8,
92        general_profile: &Profile,
93        conformance_window: &ConformanceWindow,
94        sub_width_c: u8,
95        pic_width_in_luma_samples: NonZero<u64>,
96        sub_height_c: u8,
97        pic_height_in_luma_samples: NonZero<u64>,
98    ) -> io::Result<Self> {
99        let mut aspect_ratio_info = AspectRatioInfo::Predefined(AspectRatioIdc::Unspecified);
100        let mut overscan_appropriate_flag = None;
101        let mut video_signal_type = None;
102        let mut chroma_loc_info = None;
103        let mut default_display_window = None;
104        let mut vui_timing_info = None;
105
106        let aspect_ratio_info_present_flag = bit_reader.read_bit()?;
107        if aspect_ratio_info_present_flag {
108            let aspect_ratio_idc = bit_reader.read_u8()?;
109            if aspect_ratio_idc == AspectRatioIdc::ExtendedSar.0 {
110                let sar_width = bit_reader.read_u16::<BigEndian>()?;
111                let sar_height = bit_reader.read_u16::<BigEndian>()?;
112                aspect_ratio_info = AspectRatioInfo::ExtendedSar { sar_width, sar_height };
113            } else {
114                aspect_ratio_info = AspectRatioInfo::Predefined(aspect_ratio_idc.into());
115            }
116        }
117
118        let overscan_info_present_flag = bit_reader.read_bit()?;
119        if overscan_info_present_flag {
120            overscan_appropriate_flag = Some(bit_reader.read_bit()?);
121        }
122
123        let video_signal_type_present_flag = bit_reader.read_bit()?;
124        if video_signal_type_present_flag {
125            let video_format = VideoFormat::from(bit_reader.read_bits(3)? as u8);
126            let video_full_range_flag = bit_reader.read_bit()?;
127            let colour_description_present_flag = bit_reader.read_bit()?;
128
129            if colour_description_present_flag {
130                let colour_primaries = bit_reader.read_u8()?;
131                let transfer_characteristics = bit_reader.read_u8()?;
132                let matrix_coeffs = bit_reader.read_u8()?;
133
134                if matrix_coeffs == 0 && !(bit_depth_c == bit_depth_y && chroma_format_idc == 3) {
135                    return Err(io::Error::new(
136                        io::ErrorKind::InvalidData,
137                        "matrix_coeffs must not be 0 unless bit_depth_c == bit_depth_y and chroma_format_idc == 3",
138                    ));
139                }
140
141                if matrix_coeffs == 8
142                    && !(bit_depth_c == bit_depth_y || (bit_depth_c == bit_depth_y + 1 && chroma_format_idc == 3))
143                {
144                    return Err(io::Error::new(
145                        io::ErrorKind::InvalidData,
146                        "matrix_coeffs must not be 8 unless bit_depth_c == bit_depth_y or (bit_depth_c == bit_depth_y + 1 and chroma_format_idc == 3)",
147                    ));
148                }
149
150                video_signal_type = Some(VideoSignalType {
151                    video_format,
152                    video_full_range_flag,
153                    colour_primaries,
154                    transfer_characteristics,
155                    matrix_coeffs,
156                });
157            } else {
158                video_signal_type = Some(VideoSignalType {
159                    video_format,
160                    video_full_range_flag,
161                    ..Default::default()
162                });
163            }
164        }
165
166        let chroma_loc_info_present_flag = bit_reader.read_bit()?;
167
168        if chroma_format_idc != 1 && chroma_loc_info_present_flag {
169            return Err(io::Error::new(
170                io::ErrorKind::InvalidData,
171                "chroma_loc_info_present_flag must be 0 if chroma_format_idc != 1",
172            ));
173        }
174
175        if chroma_loc_info_present_flag {
176            let chroma_sample_loc_type_top_field = bit_reader.read_exp_golomb()?;
177            let chroma_sample_loc_type_bottom_field = bit_reader.read_exp_golomb()?;
178
179            chroma_loc_info = Some(ChromaLocInfo {
180                top_field: chroma_sample_loc_type_top_field,
181                bottom_field: chroma_sample_loc_type_bottom_field,
182            });
183        }
184
185        let neutral_chroma_indication_flag = bit_reader.read_bit()?;
186        let field_seq_flag = bit_reader.read_bit()?;
187
188        if general_profile.frame_only_constraint_flag && field_seq_flag {
189            return Err(io::Error::new(
190                io::ErrorKind::InvalidData,
191                "field_seq_flag must be 0 if general_frame_only_constraint_flag is 1",
192            ));
193        }
194
195        let frame_field_info_present_flag = bit_reader.read_bit()?;
196
197        if !frame_field_info_present_flag
198            && (field_seq_flag || (general_profile.progressive_source_flag && general_profile.interlaced_source_flag))
199        {
200            return Err(io::Error::new(
201                io::ErrorKind::InvalidData,
202                "frame_field_info_present_flag must be 1 if field_seq_flag is 1 or general_progressive_source_flag and general_interlaced_source_flag are both 1",
203            ));
204        }
205
206        let default_display_window_flag = bit_reader.read_bit()?;
207        if default_display_window_flag {
208            let def_disp_win_left_offset = bit_reader.read_exp_golomb()?;
209            let def_disp_win_right_offset = bit_reader.read_exp_golomb()?;
210            let def_disp_win_top_offset = bit_reader.read_exp_golomb()?;
211            let def_disp_win_bottom_offset = bit_reader.read_exp_golomb()?;
212            let left_offset = conformance_window.conf_win_left_offset + def_disp_win_left_offset;
213            let right_offset = conformance_window.conf_win_right_offset + def_disp_win_right_offset;
214            let top_offset = conformance_window.conf_win_top_offset + def_disp_win_top_offset;
215            let bottom_offset = conformance_window.conf_win_bottom_offset + def_disp_win_bottom_offset;
216
217            if sub_width_c as u64 * (left_offset + right_offset) >= pic_width_in_luma_samples.get() {
218                return Err(io::Error::new(
219                    io::ErrorKind::InvalidData,
220                    "sub_width_c * (left_offset + right_offset) must be less than pic_width_in_luma_samples",
221                ));
222            }
223
224            if sub_height_c as u64 * (top_offset + bottom_offset) >= pic_height_in_luma_samples.get() {
225                return Err(io::Error::new(
226                    io::ErrorKind::InvalidData,
227                    "sub_height_c * (top_offset + bottom_offset) must be less than pic_height_in_luma_samples",
228                ));
229            }
230
231            default_display_window = Some(DefaultDisplayWindow {
232                def_disp_win_left_offset,
233                def_disp_win_right_offset,
234                def_disp_win_top_offset,
235                def_disp_win_bottom_offset,
236            });
237        }
238
239        let vui_timing_info_present_flag = bit_reader.read_bit()?;
240        if vui_timing_info_present_flag {
241            let vui_num_units_in_tick = NonZero::new(bit_reader.read_u32::<BigEndian>()?).ok_or(io::Error::new(
242                io::ErrorKind::InvalidData,
243                "vui_num_units_in_tick must greater than zero",
244            ))?;
245            let vui_time_scale = NonZero::new(bit_reader.read_u32::<BigEndian>()?)
246                .ok_or(io::Error::new(io::ErrorKind::InvalidData, "vui_time_scale must not be zero"))?;
247
248            let mut num_ticks_poc_diff_one_minus1 = None;
249            let vui_poc_proportional_to_timing_flag = bit_reader.read_bit()?;
250            if vui_poc_proportional_to_timing_flag {
251                let vui_num_ticks_poc_diff_one_minus1 = bit_reader.read_exp_golomb()?;
252                range_check!(vui_num_ticks_poc_diff_one_minus1, 0, 2u64.pow(32) - 2)?;
253                num_ticks_poc_diff_one_minus1 = Some(vui_num_ticks_poc_diff_one_minus1 as u32);
254            }
255
256            let mut vui_hrd_parameters = None;
257            let vui_hrd_parameters_present_flag = bit_reader.read_bit()?;
258            if vui_hrd_parameters_present_flag {
259                vui_hrd_parameters = Some(HrdParameters::parse(bit_reader, true, sps_max_sub_layers_minus1)?);
260            }
261
262            vui_timing_info = Some(VuiTimingInfo {
263                num_units_in_tick: vui_num_units_in_tick,
264                time_scale: vui_time_scale,
265                poc_proportional_to_timing_flag: vui_poc_proportional_to_timing_flag,
266                num_ticks_poc_diff_one_minus1,
267                hrd_parameters: vui_hrd_parameters,
268            });
269        }
270
271        let mut bitstream_restriction = BitStreamRestriction::default();
272        let bitstream_restriction_flag = bit_reader.read_bit()?;
273        if bitstream_restriction_flag {
274            bitstream_restriction.tiles_fixed_structure_flag = bit_reader.read_bit()?;
275            bitstream_restriction.motion_vectors_over_pic_boundaries_flag = bit_reader.read_bit()?;
276            bitstream_restriction.restricted_ref_pic_lists_flag = Some(bit_reader.read_bit()?);
277
278            let min_spatial_segmentation_idc = bit_reader.read_exp_golomb()?;
279            range_check!(min_spatial_segmentation_idc, 0, 4095)?;
280            bitstream_restriction.min_spatial_segmentation_idc = min_spatial_segmentation_idc as u16;
281
282            let max_bytes_per_pic_denom = bit_reader.read_exp_golomb()?;
283            range_check!(max_bytes_per_pic_denom, 0, 16)?;
284            bitstream_restriction.max_bytes_per_pic_denom = max_bytes_per_pic_denom as u8;
285
286            let max_bits_per_min_cu_denom = bit_reader.read_exp_golomb()?;
287            range_check!(max_bits_per_min_cu_denom, 0, 16)?;
288            bitstream_restriction.max_bits_per_min_cu_denom = max_bits_per_min_cu_denom as u8;
289
290            let log2_max_mv_length_horizontal = bit_reader.read_exp_golomb()?;
291            range_check!(log2_max_mv_length_horizontal, 0, 15)?;
292            bitstream_restriction.log2_max_mv_length_horizontal = log2_max_mv_length_horizontal as u8;
293
294            let log2_max_mv_length_vertical = bit_reader.read_exp_golomb()?;
295            range_check!(log2_max_mv_length_vertical, 0, 15)?;
296            bitstream_restriction.log2_max_mv_length_vertical = log2_max_mv_length_vertical as u8;
297        }
298
299        Ok(Self {
300            aspect_ratio_info,
301            overscan_appropriate_flag,
302            video_signal_type: video_signal_type.unwrap_or_default(),
303            chroma_loc_info,
304            neutral_chroma_indication_flag,
305            field_seq_flag,
306            frame_field_info_present_flag,
307            default_display_window: default_display_window.unwrap_or_default(),
308            vui_timing_info,
309            bitstream_restriction,
310        })
311    }
312}
313
314/// Specifies the value of the sample aspect ratio of the luma samples.
315#[derive(Debug, Clone, PartialEq)]
316pub enum AspectRatioInfo {
317    /// Any value other than [`AspectRatioIdc::ExtendedSar`].
318    Predefined(AspectRatioIdc),
319    /// [`AspectRatioIdc::ExtendedSar`].
320    ExtendedSar {
321        /// Indicates the horizontal size of the sample aspect ratio (in arbitrary units).
322        sar_width: u16,
323        /// Indicates the vertical size of the sample aspect ratio (in the same arbitrary units as `sar_width`).
324        sar_height: u16,
325    },
326}
327
328/// Directly part of [`VuiParameters`].
329#[derive(Debug, Clone, PartialEq)]
330pub struct VideoSignalType {
331    /// Indicates the representation of the pictures as specified in ISO/IEC 23008-2 - Table E.2, before being coded
332    /// in accordance with this document.
333    ///
334    /// The values 6 and 7 for video_format are reserved for future use by ITU-T | ISO/IEC and
335    /// shall not be present in bitstreams conforming to this version of this document.
336    /// Decoders shall interpret the values 6 and 7 for video_format as equivalent to the value [`VideoFormat::Unspecified`].
337    pub video_format: VideoFormat,
338    /// Indicates the black level and range of the luma and chroma signals as derived from
339    /// `E'Y`, `E'PB`, and `E'PR` or `E'R`, `E'G`, and `E'B` real-valued component signals.
340    pub video_full_range_flag: bool,
341    /// Indicates the chromaticity coordinates of the source primaries as specified in
342    /// ISO/IEC 23008-2 - Table E.3 in terms of the CIE 1931 definition of x and y as specified in ISO 11664-1.
343    pub colour_primaries: u8,
344    /// As specified in ISO/IEC 23008-2 - Table E.4, either indicates the reference opto-electronic transfer
345    /// characteristic function of the source picture as a function of a source input linear optical intensity `Lc` with
346    /// a nominal real-valued range of 0 to 1 or indicates the inverse of the reference electro-optical transfer
347    /// characteristic function as a function of an output linear optical intensity `Lo` with a nominal real-valued
348    /// range of 0 to 1.
349    ///
350    /// For more details, see ISO/IEC 23008-2 - E.3.1.
351    pub transfer_characteristics: u8,
352    /// Describes the matrix coefficients used in deriving luma and chroma signals from the green,
353    /// blue, and red, or Y, Z, and X primaries, as specified in ISO/IEC 23008-2 - Table E.5.
354    pub matrix_coeffs: u8,
355}
356
357impl Default for VideoSignalType {
358    fn default() -> Self {
359        Self {
360            video_format: VideoFormat::Unspecified,
361            video_full_range_flag: false,
362            colour_primaries: 2,
363            transfer_characteristics: 2,
364            matrix_coeffs: 2,
365        }
366    }
367}
368
369/// Directly part of [`VuiParameters`].
370///
371/// Specifies the location of chroma samples as follows:
372/// - If [`chroma_format_idc`](crate::SpsRbsp::chroma_format_idc) is equal to 1 (4:2:0 chroma format),
373///   [`chroma_sample_loc_type_top_field`](ChromaLocInfo::top_field) and
374///   [`chroma_sample_loc_type_bottom_field`](ChromaLocInfo::bottom_field) specify the location of chroma samples
375///   for the top field and the bottom field, respectively, as shown in ISO/IEC 23008-2 - Figure E.1.
376/// - Otherwise ([`chroma_format_idc`](crate::SpsRbsp::chroma_format_idc) is not equal to 1), the values of the syntax elements
377///   [`chroma_sample_loc_type_top_field`](ChromaLocInfo::top_field) and
378///   [`chroma_sample_loc_type_bottom_field`](ChromaLocInfo::bottom_field) shall be ignored.
379///   When [`chroma_format_idc`](crate::SpsRbsp::chroma_format_idc) is equal to 2 (4:2:2 chroma format) or 3 (4:4:4 chroma format),
380///   the location of chroma samples is specified in ISO/IEC 23008-2 - 6.2.
381///   When [`chroma_format_idc`](crate::SpsRbsp::chroma_format_idc) is equal to 0, there is no chroma sample array.
382#[derive(Debug, Clone, PartialEq)]
383pub struct ChromaLocInfo {
384    /// `chroma_sample_loc_type_top_field`
385    pub top_field: u64,
386    /// `chroma_sample_loc_type_bottom_field`
387    pub bottom_field: u64,
388}
389
390/// Directly part of [`VuiParameters`].
391///
392/// Specifies the samples of the pictures in the CVS that are within the default display window,
393/// in terms of a rectangular region specified in picture coordinates for display.
394#[derive(Debug, Clone, PartialEq, Default)]
395pub struct DefaultDisplayWindow {
396    /// `def_disp_win_left_offset`
397    pub def_disp_win_left_offset: u64,
398    /// `def_disp_win_right_offset`
399    pub def_disp_win_right_offset: u64,
400    /// `def_disp_win_top_offset`
401    pub def_disp_win_top_offset: u64,
402    /// `def_disp_win_bottom_offset`
403    pub def_disp_win_bottom_offset: u64,
404}
405
406impl DefaultDisplayWindow {
407    /// `leftOffset = conf_win_left_offset + def_disp_win_left_offset` (E-68)
408    ///
409    /// ISO/IEC 23008-2 - E.3.1
410    pub fn left_offset(&self, conformance_window: &ConformanceWindow) -> u64 {
411        conformance_window.conf_win_left_offset + self.def_disp_win_left_offset
412    }
413
414    /// `rightOffset = conf_win_right_offset + def_disp_win_right_offset` (E-69)
415    ///
416    /// ISO/IEC 23008-2 - E.3.1
417    pub fn right_offset(&self, conformance_window: &ConformanceWindow) -> u64 {
418        conformance_window.conf_win_right_offset + self.def_disp_win_right_offset
419    }
420
421    /// `topOffset = conf_win_top_offset + def_disp_win_top_offset` (E-70)
422    ///
423    /// ISO/IEC 23008-2 - E.3.1
424    pub fn top_offset(&self, conformance_window: &ConformanceWindow) -> u64 {
425        conformance_window.conf_win_top_offset + self.def_disp_win_top_offset
426    }
427
428    /// `bottomOffset = conf_win_bottom_offset + def_disp_win_bottom_offset` (E-71)
429    ///
430    /// ISO/IEC 23008-2 - E.3.1
431    pub fn bottom_offset(&self, conformance_window: &ConformanceWindow) -> u64 {
432        conformance_window.conf_win_bottom_offset + self.def_disp_win_bottom_offset
433    }
434}
435
436/// Directly part of [`VuiParameters`].
437#[derive(Debug, Clone, PartialEq)]
438pub struct VuiTimingInfo {
439    /// This value is the number of time units of a clock operating at the frequency `vui_time_scale`
440    /// Hz that corresponds to one increment (called a clock tick) of a clock tick counter.
441    ///
442    /// This value is greater than 0.
443    ///
444    /// A clock tick, in units of seconds, is equal to the quotient of `vui_num_units_in_tick` divided by `vui_time_scale`.
445    /// For example, when the picture rate of a video signal is 25 Hz, `vui_time_scale`
446    /// may be equal to `27 000 000` and `vui_num_units_in_tick` may be equal to 1 080 000, and consequently a
447    /// clock tick may be equal to `0.04` seconds.
448    pub num_units_in_tick: NonZero<u32>,
449    /// This value is the number of time units that pass in one second.
450    ///
451    /// For example, a time coordinate system that measures time using a `27 MHz` clock has a `vui_time_scale` of `27 000 000`.
452    ///
453    /// The value of `vui_time_scale` is greater than 0.
454    pub time_scale: NonZero<u32>,
455    /// equal to 1 indicates that the picture order count value for each
456    /// picture in the CVS that is not the first picture in the CVS, in decoding order, is proportional to the output
457    /// time of the picture relative to the output time of the first picture in the CVS.
458    /// vui_poc_proportional_to_timing_flag equal to 0 indicates that the picture order count value for each
459    /// picture in the CVS that is not the first picture in the CVS, in decoding order, may or may not be
460    /// proportional to the output time of the picture relative to the output time of the first picture in the CVS.
461    pub poc_proportional_to_timing_flag: bool,
462    /// This value plus 1 specifies the number of clock ticks corresponding to a
463    /// difference of picture order count values equal to 1.
464    ///
465    /// The value is in range \[0, 2^32 − 2\].
466    pub num_ticks_poc_diff_one_minus1: Option<u32>,
467    /// If `vui_hrd_parameters_present_flag` is equal to 1, this value specifies the HRD parameters.
468    pub hrd_parameters: Option<HrdParameters>,
469}
470
471/// Directly part of [`VuiParameters`].
472#[derive(Debug, Clone, PartialEq)]
473pub struct BitStreamRestriction {
474    /// Equal to `true` indicates that each PPS that is active in the CVS has the same value
475    /// of the syntax elements `num_tile_columns_minus1`, `num_tile_rows_minus1`, `uniform_spacing_flag`,
476    /// `column_width_minus1[i]`, `row_height_minus1[i]` and `loop_filter_across_tiles_enabled_flag`, when
477    /// present.
478    ///
479    /// Equal to `false` indicates that tiles syntax elements in different PPSs may or
480    /// may not have the same value.
481    pub tiles_fixed_structure_flag: bool,
482    /// Equal to `false` indicates that no sample outside the picture
483    /// boundaries and no sample at a fractional sample position for which the sample value is derived using one
484    /// or more samples outside the picture boundaries is used for inter prediction of any sample.
485    ///
486    /// Equal to `true` indicates that one or more samples outside the
487    /// picture boundaries may be used in inter prediction.
488    pub motion_vectors_over_pic_boundaries_flag: bool,
489    /// Equal to `Some(true)` indicates that all P and B slices (when present) that belong to the
490    /// same picture have an identical reference picture list 0, and that all B slices (when present) that belong to
491    /// the same picture have an identical reference picture list 1.
492    pub restricted_ref_pic_lists_flag: Option<bool>,
493    /// When not equal to 0, establishes a bound on the maximum possible size
494    /// of distinct coded spatial segmentation regions in the pictures of the CVS.
495    ///
496    /// The value is in range \[0, 4095\].
497    ///
498    /// Defines [`minSpatialSegmentationTimes4`](BitStreamRestriction::min_spatial_segmentation_times4).
499    pub min_spatial_segmentation_idc: u16,
500    /// Indicates a number of bytes not exceeded by the sum of the sizes of the VCL
501    /// NAL units associated with any coded picture in the CVS.
502    ///
503    /// The number of bytes that represent a picture in the NAL unit stream is specified for this purpose as the
504    /// total number of bytes of VCL NAL unit data (i.e. the total of the `NumBytesInNalUnit` variables for the VCL
505    /// NAL units) for the picture.
506    ///
507    /// The value is in range \[0, 16\].
508    pub max_bytes_per_pic_denom: u8,
509    /// Indicates an upper bound for the number of coded bits of `coding_unit()`
510    /// data for any coding block in any picture of the CVS.
511    ///
512    /// The value is in range \[0, 16\].
513    pub max_bits_per_min_cu_denom: u8,
514    /// Indicates the maximum absolute
515    /// value of a decoded horizontal and vertical motion vector component, respectively, in quarter luma sample
516    /// units, for all pictures in the CVS. A value of n asserts that no value of a motion vector component is outside
517    /// the range of \[`−2n`, `2n − 1`\], in units of quarter luma sample displacement, where `n` refers to the
518    /// value of [`log2_max_mv_length_horizontal`](BitStreamRestriction::log2_max_mv_length_horizontal) and
519    /// [`log2_max_mv_length_vertical`](BitStreamRestriction::log2_max_mv_length_vertical) for the horizontal and
520    /// vertical component of the MV, respectively.
521    ///
522    /// The value is in range \[0, 15\].
523    pub log2_max_mv_length_horizontal: u8,
524    /// Same as [`log2_max_mv_length_horizontal`](BitStreamRestriction::log2_max_mv_length_horizontal).
525    pub log2_max_mv_length_vertical: u8,
526}
527
528impl Default for BitStreamRestriction {
529    fn default() -> Self {
530        Self {
531            tiles_fixed_structure_flag: false,
532            motion_vectors_over_pic_boundaries_flag: true,
533            restricted_ref_pic_lists_flag: None,
534            min_spatial_segmentation_idc: 0,
535            max_bytes_per_pic_denom: 2,
536            max_bits_per_min_cu_denom: 1,
537            log2_max_mv_length_horizontal: 15,
538            log2_max_mv_length_vertical: 15,
539        }
540    }
541}
542
543impl BitStreamRestriction {
544    /// `minSpatialSegmentationTimes4 = min_spatial_segmentation_idc + 4` (E-72)
545    ///
546    /// ISO/IEC 23008-2 - E.3.1
547    pub fn min_spatial_segmentation_times4(&self) -> u16 {
548        self.min_spatial_segmentation_idc + 4
549    }
550}
551
552#[cfg(test)]
553#[cfg_attr(all(test, coverage_nightly), coverage(off))]
554mod tests {
555    use std::io::Write;
556    use std::num::NonZero;
557
558    use byteorder::{BigEndian, WriteBytesExt};
559    use scuffle_bytes_util::{BitReader, BitWriter};
560    use scuffle_expgolomb::BitWriterExpGolombExt;
561
562    use crate::sps::vui_parameters::{BitStreamRestriction, DefaultDisplayWindow};
563    use crate::{AspectRatioIdc, ConformanceWindow, Profile, ProfileCompatibilityFlags, VideoFormat, VuiParameters};
564
565    #[test]
566    fn vui_parameters() {
567        let mut data = Vec::new();
568        let mut writer = BitWriter::new(&mut data);
569
570        writer.write_bit(true).unwrap(); // aspect_ratio_info_present_flag
571        writer.write_u8(AspectRatioIdc::ExtendedSar.0).unwrap(); // aspect_ratio_idc
572        writer.write_u16::<BigEndian>(1).unwrap(); // sar_width
573        writer.write_u16::<BigEndian>(1).unwrap(); // sar_height
574
575        writer.write_bit(true).unwrap(); // overscan_info_present_flag
576        writer.write_bit(true).unwrap(); // overscan_appropriate_flag
577
578        writer.write_bit(false).unwrap(); // video_signal_type_present_flag
579        writer.write_bit(false).unwrap(); // chroma_loc_info_present_flag
580        writer.write_bit(false).unwrap(); // neutral_chroma_indication_flag
581        writer.write_bit(false).unwrap(); // field_seq_flag
582        writer.write_bit(false).unwrap(); // frame_field_info_present_flag
583
584        writer.write_bit(true).unwrap(); // default_display_window_flag
585        writer.write_exp_golomb(0).unwrap(); // def_disp_win_left_offset
586        writer.write_exp_golomb(10).unwrap(); // def_disp_win_right_offset
587        writer.write_exp_golomb(0).unwrap(); // def_disp_win_top_offset
588        writer.write_exp_golomb(10).unwrap(); // def_disp_win_bottom_offset
589
590        writer.write_bit(false).unwrap(); // vui_timing_info_present_flag
591        writer.write_bit(false).unwrap(); // bitstream_restriction_flag
592
593        writer.write_bits(0, 5).unwrap(); // fill the byte
594        writer.flush().unwrap();
595
596        let conf_window = ConformanceWindow {
597            conf_win_left_offset: 2,
598            conf_win_right_offset: 2,
599            conf_win_top_offset: 2,
600            conf_win_bottom_offset: 2,
601        };
602
603        let vui_parameters = VuiParameters::parse(
604            &mut BitReader::new(data.as_slice()),
605            0,
606            8,
607            8,
608            1,
609            &Profile {
610                profile_space: 0,
611                tier_flag: false,
612                profile_idc: 0,
613                profile_compatibility_flag: ProfileCompatibilityFlags::empty(),
614                progressive_source_flag: false,
615                interlaced_source_flag: false,
616                non_packed_constraint_flag: false,
617                frame_only_constraint_flag: false,
618                additional_flags: crate::ProfileAdditionalFlags::None,
619                inbld_flag: None,
620                level_idc: Some(0),
621            },
622            &conf_window,
623            1,
624            NonZero::new(1920).unwrap(),
625            1,
626            NonZero::new(1080).unwrap(),
627        )
628        .unwrap();
629
630        assert_eq!(
631            vui_parameters.aspect_ratio_info,
632            super::AspectRatioInfo::ExtendedSar {
633                sar_width: 1,
634                sar_height: 1
635            }
636        );
637        assert_eq!(vui_parameters.overscan_appropriate_flag, Some(true));
638        assert_eq!(vui_parameters.video_signal_type.video_format, VideoFormat::Unspecified);
639        assert!(!vui_parameters.video_signal_type.video_full_range_flag);
640        assert_eq!(vui_parameters.video_signal_type.colour_primaries, 2);
641        assert_eq!(vui_parameters.video_signal_type.transfer_characteristics, 2);
642        assert_eq!(vui_parameters.video_signal_type.matrix_coeffs, 2);
643        assert_eq!(vui_parameters.chroma_loc_info, None);
644        assert!(!vui_parameters.neutral_chroma_indication_flag);
645        assert!(!vui_parameters.field_seq_flag);
646        assert!(!vui_parameters.frame_field_info_present_flag);
647        assert_eq!(
648            vui_parameters.default_display_window,
649            DefaultDisplayWindow {
650                def_disp_win_left_offset: 0,
651                def_disp_win_right_offset: 10,
652                def_disp_win_top_offset: 0,
653                def_disp_win_bottom_offset: 10,
654            }
655        );
656        assert_eq!(vui_parameters.default_display_window.left_offset(&conf_window), 2);
657        assert_eq!(vui_parameters.default_display_window.right_offset(&conf_window), 12);
658        assert_eq!(vui_parameters.default_display_window.top_offset(&conf_window), 2);
659        assert_eq!(vui_parameters.default_display_window.bottom_offset(&conf_window), 12);
660        assert_eq!(vui_parameters.vui_timing_info, None);
661        assert_eq!(vui_parameters.bitstream_restriction, BitStreamRestriction::default());
662        assert_eq!(vui_parameters.bitstream_restriction.min_spatial_segmentation_times4(), 4);
663    }
664}