Skip to main content

oxideav_h265/
vps.rs

1//! Video Parameter Set (VPS) parser per ITU-T Rec. H.265 §7.3.2.1.
2//!
3//! Parses the VPS RBSP through the layer-set inclusion matrix and the
4//! VPS timing-info block (including `vps_num_units_in_tick` /
5//! `vps_time_scale` / `vps_poc_proportional_to_timing_flag` /
6//! `vps_num_ticks_poc_diff_one_minus1` and the `vps_num_hrd_parameters`
7//! count). The per-HRD `hrd_parameters()` bodies (§E.2.2) are decoded
8//! as a vector of [`crate::hrd::VpsHrdEntry`] values (one per
9//! `vps_num_hrd_parameters`), with the §E.2.3 sub-layer HRD payloads
10//! folded into each entry's [`crate::hrd::SubLayerHrd`]. The
11//! `vps_extension_flag` follows the HRD loop; when 1, the
12//! `vps_extension_data_flag` run + `rbsp_trailing_bits()` are surfaced
13//! as an [`crate::sps::OpaqueTail`] for callers that want the raw
14//! bytes.
15//!
16//! The profile-tier-level subroutine of §7.3.3 is also parsed
17//! structurally: the bit positions are walked but only the leading
18//! `general_profile_space` / `general_tier_flag` / `general_profile_idc`
19//! / `general_level_idc` fields and the per-sub-layer
20//! `sub_layer_profile_present_flag` / `sub_layer_level_present_flag`
21//! gates are materialised. The remaining (mostly-reserved-zero or
22//! constraint-flag) fields are skipped, but the bit-walk advances the
23//! reader correctly so subsequent VPS fields land on the right bit
24//! boundary.
25//!
26//! ## Layout summary
27//!
28//! ```text
29//! vps_video_parameter_set_id            u(4)
30//! vps_base_layer_internal_flag          u(1)
31//! vps_base_layer_available_flag         u(1)
32//! vps_max_layers_minus1                 u(6)
33//! vps_max_sub_layers_minus1             u(3)
34//! vps_temporal_id_nesting_flag          u(1)
35//! vps_reserved_0xffff_16bits            u(16)   /* must be 0xFFFF */
36//! profile_tier_level( 1, vps_max_sub_layers_minus1 )
37//! vps_sub_layer_ordering_info_present_flag  u(1)
38//! for( i = (...) ; i <= vps_max_sub_layers_minus1; i++ ) {
39//!   vps_max_dec_pic_buffering_minus1[i] ue(v)
40//!   vps_max_num_reorder_pics[i]         ue(v)
41//!   vps_max_latency_increase_plus1[i]   ue(v)
42//! }
43//! vps_max_layer_id                       u(6)
44//! vps_num_layer_sets_minus1             ue(v)
45//! for( i = 1; i <= vps_num_layer_sets_minus1; i++ )
46//!   for( j = 0; j <= vps_max_layer_id; j++ )
47//!     layer_id_included_flag[i][j]       u(1)
48//! vps_timing_info_present_flag           u(1)
49//! if( vps_timing_info_present_flag ) {
50//!   vps_num_units_in_tick               u(32)
51//!   vps_time_scale                      u(32)
52//!   vps_poc_proportional_to_timing_flag  u(1)
53//!   if( vps_poc_proportional_to_timing_flag )
54//!     vps_num_ticks_poc_diff_one_minus1 ue(v)
55//!   vps_num_hrd_parameters              ue(v)
56//!   for( i = 0; i < vps_num_hrd_parameters; i++ ) {
57//!     hrd_layer_set_idx[i]              ue(v)
58//!     if( i > 0 ) cprms_present_flag[i] u(1)
59//!     hrd_parameters( cprms_present_flag[i], vps_max_sub_layers_minus1 )  /* §E.2.2 */
60//!   }
61//! }
62//! vps_extension_flag                     u(1)
63//! /* extension payload + rbsp_trailing_bits() surfaced as opaque */
64//! ```
65
66use crate::bitreader::{BitReader, BitReaderError};
67use crate::hrd::{HrdError, VpsHrdEntry};
68use crate::sps::OpaqueTail;
69
70/// Maximum number of sub-layers an HEVC stream may declare.
71/// `vps_max_sub_layers_minus1` is u(3), so the count is bounded at 7
72/// in the bitstream; this constant is reused by [`HevcVps`] as the
73/// fixed-size capacity for per-sub-layer arrays.
74pub const HEVC_MAX_SUB_LAYERS: usize = 7;
75
76/// Maximum number of layer IDs the VPS layer-set inclusion matrix may
77/// span. `vps_max_layer_id` is u(6), so the maximum signalled value is
78/// 63; the inclusion matrix column count is `vps_max_layer_id + 1`,
79/// which is bounded at 64.
80pub const HEVC_VPS_MAX_NUM_LAYERS: usize = 64;
81
82/// Upper bound on `vps_num_layer_sets_minus1`. Per §7.4.3.1
83/// `vps_num_layer_sets_minus1` shall be in the range 0..=1023, so
84/// the layer-set count is bounded at 1024. This crate's parser caps
85/// the value here to keep an aberrantly-encoded stream from forcing a
86/// 4 MB allocation (the legal max would already be ~64 KB).
87pub const HEVC_VPS_MAX_NUM_LAYER_SETS: usize = 1024;
88
89/// Errors that can arise while parsing a VPS RBSP.
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum VpsError {
92    /// The RBSP ran out of bits before the VPS was fully parsed.
93    Truncated,
94    /// `vps_reserved_0xffff_16bits` was not `0xFFFF`. §7.4.3.1
95    /// mandates the literal value.
96    ReservedFieldMismatch {
97        /// The (incorrect) value that was actually read.
98        got: u16,
99    },
100    /// An Exp-Golomb code's `codeNum` exceeded what the corresponding
101    /// syntax element can legally hold.
102    ValueOutOfRange {
103        /// Name of the offending syntax element.
104        field: &'static str,
105        /// The (illegal) value.
106        got: u32,
107    },
108    /// An unexpected bitstream-level error surfaced from the reader.
109    Bitstream(BitReaderError),
110    /// An `hrd_parameters()` body inside the VPS HRD loop was malformed.
111    /// Propagated up from [`crate::hrd::HrdError`] so a caller that
112    /// only looks at [`VpsError`] still sees the failure.
113    Hrd(HrdError),
114}
115
116impl core::fmt::Display for VpsError {
117    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
118        match self {
119            Self::Truncated => f.write_str("VPS RBSP truncated"),
120            Self::ReservedFieldMismatch { got } => write!(
121                f,
122                "vps_reserved_0xffff_16bits was 0x{got:04X}, expected 0xFFFF"
123            ),
124            Self::ValueOutOfRange { field, got } => {
125                write!(f, "syntax element {field} out of range: {got}")
126            }
127            Self::Bitstream(e) => write!(f, "bitstream error during VPS parse: {e}"),
128            Self::Hrd(e) => write!(f, "hrd_parameters() error inside VPS: {e}"),
129        }
130    }
131}
132
133impl std::error::Error for VpsError {}
134
135impl From<BitReaderError> for VpsError {
136    fn from(e: BitReaderError) -> Self {
137        match e {
138            BitReaderError::EndOfBuffer => Self::Truncated,
139            other => Self::Bitstream(other),
140        }
141    }
142}
143
144impl From<HrdError> for VpsError {
145    fn from(e: HrdError) -> Self {
146        // Surface a bitstream-truncation reported through the HRD path
147        // as the VPS-level Truncated variant so callers can keep their
148        // single-pattern truncation handler. Any other HRD failure mode
149        // is opaque to the VPS — preserve it verbatim.
150        match e {
151            HrdError::Truncated => Self::Truncated,
152            other => Self::Hrd(other),
153        }
154    }
155}
156
157/// Parsed profile-tier-level structure (§7.3.3).
158///
159/// Only the leading "general" fields and the per-sub-layer
160/// present-flag gates are materialised at round-2 scope. The
161/// constraint flags / reserved-zero blocks are walked over to keep
162/// bit alignment but their values are intentionally discarded.
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct ProfileTierLevel {
165    /// `general_profile_space` (`u(2)`).
166    pub general_profile_space: u8,
167    /// `general_tier_flag` (`u(1)`).
168    pub general_tier_flag: bool,
169    /// `general_profile_idc` (`u(5)`). Profile mnemonics are listed in
170    /// Annex A; for the Main / Main 10 / Main Still / Main 4:2:2 family
171    /// values 1..=4 are most common.
172    pub general_profile_idc: u8,
173    /// `general_level_idc` (`u(8)`). Per A.4 the on-wire value is
174    /// `30 × level_number`, e.g. 30 == level 1.0, 90 == level 3.0,
175    /// 120 == level 4.0.
176    pub general_level_idc: u8,
177    /// For each present sub-layer, whether its profile entry was
178    /// signalled (`sub_layer_profile_present_flag[i]`).
179    pub sub_layer_profile_present: [bool; HEVC_MAX_SUB_LAYERS],
180    /// For each present sub-layer, whether its level_idc was signalled
181    /// (`sub_layer_level_present_flag[i]`).
182    pub sub_layer_level_present: [bool; HEVC_MAX_SUB_LAYERS],
183    /// Per-sub-layer `sub_layer_level_idc[i]`; only valid for indices
184    /// `i` where `sub_layer_level_present[i]` is true.
185    pub sub_layer_level_idc: [u8; HEVC_MAX_SUB_LAYERS],
186}
187
188impl ProfileTierLevel {
189    /// Parse a `profile_tier_level(profilePresentFlag, maxNumSubLayersMinus1)`
190    /// invocation per §7.3.3. `profile_present_flag` is supplied by
191    /// the calling context — for the VPS / SPS path it is always 1.
192    pub fn parse(
193        br: &mut BitReader<'_>,
194        profile_present_flag: bool,
195        max_num_sub_layers_minus1: u8,
196    ) -> Result<Self, VpsError> {
197        let mut ptl = Self {
198            general_profile_space: 0,
199            general_tier_flag: false,
200            general_profile_idc: 0,
201            general_level_idc: 0,
202            sub_layer_profile_present: [false; HEVC_MAX_SUB_LAYERS],
203            sub_layer_level_present: [false; HEVC_MAX_SUB_LAYERS],
204            sub_layer_level_idc: [0; HEVC_MAX_SUB_LAYERS],
205        };
206
207        if profile_present_flag {
208            ptl.general_profile_space = br.u(2)? as u8;
209            ptl.general_tier_flag = br.u1()? != 0;
210            ptl.general_profile_idc = br.u(5)? as u8;
211            // 32 compatibility flags — skipped wholesale; the calling
212            // application can re-parse them from the bit position if
213            // needed later.
214            br.skip(32)?;
215            // progressive / interlaced / non_packed / frame_only
216            br.skip(4)?;
217            // The conditional block beneath these flags always consumes
218            // exactly 43 bits regardless of profile_idc (per the
219            // `/* not affected by this condition */` comment in §7.3.3
220            // — the chroma-constraint, range-extension, and reserved
221            // alternatives all sum to 43 bits).
222            br.skip(43)?;
223            // general_inbld_flag OR general_reserved_zero_bit — always 1 bit.
224            br.skip(1)?;
225        }
226        // general_level_idc is always present (no `profilePresentFlag`
227        // guard in the §7.3.3 syntax).
228        ptl.general_level_idc = br.u(8)? as u8;
229
230        // Per-sub-layer present-flag gates: 2 bits per sublayer up to
231        // (but excluding) maxNumSubLayersMinus1.
232        let max = max_num_sub_layers_minus1 as usize;
233        for i in 0..max {
234            let prof = br.u1()? != 0;
235            let lvl = br.u1()? != 0;
236            ptl.sub_layer_profile_present[i] = prof;
237            ptl.sub_layer_level_present[i] = lvl;
238        }
239
240        // §7.3.3: if maxNumSubLayersMinus1 > 0, then for i in
241        // max..8: reserved_zero_2bits — exactly 2 bits each — to keep
242        // the per-sub-layer body byte-aligned regardless of how many
243        // sublayers were actually signalled.
244        if max_num_sub_layers_minus1 > 0 {
245            for _ in max..8 {
246                br.skip(2)?;
247            }
248        }
249
250        // Per-sub-layer profile/level body for each i in 0..max.
251        for i in 0..max {
252            if ptl.sub_layer_profile_present[i] {
253                // 2 + 1 + 5 + 32 + 4 + 43 + 1 = 88 bits, identical
254                // layout to the general profile block above.
255                br.skip(88)?;
256            }
257            if ptl.sub_layer_level_present[i] {
258                ptl.sub_layer_level_idc[i] = br.u(8)? as u8;
259            }
260        }
261
262        Ok(ptl)
263    }
264}
265
266/// One per-sub-layer ordering-info triple from §7.3.2.1.
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
268pub struct SubLayerOrderingInfo {
269    /// `vps_max_dec_pic_buffering_minus1[i]` (`ue(v)`).
270    /// Implies a DPB size of `value + 1`.
271    pub max_dec_pic_buffering_minus1: u32,
272    /// `vps_max_num_reorder_pics[i]` (`ue(v)`).
273    pub max_num_reorder_pics: u32,
274    /// `vps_max_latency_increase_plus1[i]` (`ue(v)`).
275    /// 0 disables the constraint.
276    pub max_latency_increase_plus1: u32,
277}
278
279/// One per-layer-set row of the §7.3.2.1
280/// `layer_id_included_flag[i][j]` matrix. `flags[j]` is the
281/// `layer_id_included_flag[i][j]` value for layer-set `i` and
282/// `nuh_layer_id == j`, with `0 <= j <= vps_max_layer_id`.
283#[derive(Debug, Clone, PartialEq, Eq)]
284pub struct LayerIdInclusionRow {
285    /// `layer_id_included_flag[i][0..=vps_max_layer_id]`.
286    pub flags: Vec<bool>,
287}
288
289/// VPS timing-info block per §7.3.2.1, when
290/// `vps_timing_info_present_flag == 1`. The `hrd_parameters()` bodies
291/// indexed by `vps_num_hrd_parameters` are decoded as
292/// [`HevcVps::hrd_parameters`] entries; consult
293/// [`Self::num_hrd_parameters`] for the count and
294/// [`HevcVps::hrd_parameters`] for the bodies.
295#[derive(Debug, Clone, PartialEq, Eq)]
296pub struct VpsTimingInfo {
297    /// `vps_num_units_in_tick` (`u(32)`). Spec constraint: shall be
298    /// > 0.
299    pub num_units_in_tick: u32,
300    /// `vps_time_scale` (`u(32)`). Spec constraint: shall be > 0.
301    pub time_scale: u32,
302    /// `vps_poc_proportional_to_timing_flag` (`u(1)`).
303    pub poc_proportional_to_timing_flag: bool,
304    /// `vps_num_ticks_poc_diff_one_minus1` (`ue(v)`), only present
305    /// when `poc_proportional_to_timing_flag` is set. Spec range
306    /// 0..=2^32 - 2 (the `ue(v)` codec ceiling).
307    pub num_ticks_poc_diff_one_minus1: Option<u32>,
308    /// `vps_num_hrd_parameters` (`ue(v)`). The corresponding
309    /// `hrd_parameters()` bodies are decoded into
310    /// [`HevcVps::hrd_parameters`]. Spec constraint:
311    /// 0..=`vps_num_layer_sets_minus1 + 1`.
312    pub num_hrd_parameters: u32,
313}
314
315/// Parsed Video Parameter Set per §7.3.2.1.
316///
317/// The structural prefix (through the per-sub-layer ordering loop) is
318/// fully materialised; the layer-set inclusion matrix and the
319/// optional VPS timing-info block follow. When
320/// `vps_timing_info_present_flag == 1` and `vps_num_hrd_parameters >
321/// 0`, the per-HRD `hrd_parameters()` payloads are decoded into
322/// [`Self::hrd_parameters`]. The parser then reads `vps_extension_flag`
323/// and, when set, surfaces the `vps_extension_data_flag` payload (plus
324/// `rbsp_trailing_bits()`) as [`Self::opaque_tail`].
325#[derive(Debug, Clone, PartialEq, Eq)]
326pub struct HevcVps {
327    /// `vps_video_parameter_set_id` (4 bits, range 0..=15).
328    pub vps_id: u8,
329    /// `vps_base_layer_internal_flag`.
330    pub base_layer_internal_flag: bool,
331    /// `vps_base_layer_available_flag`.
332    pub base_layer_available_flag: bool,
333    /// `vps_max_layers_minus1` (u(6)). The maximum number of layers is
334    /// `value + 1`; for single-layer (non-SHVC) streams this is 0.
335    pub max_layers_minus1: u8,
336    /// `vps_max_sub_layers_minus1` (u(3), range 0..=6). The number of
337    /// temporal sub-layers is `value + 1`.
338    pub max_sub_layers_minus1: u8,
339    /// `vps_temporal_id_nesting_flag`. When the stream has only one
340    /// sub-layer the spec requires this to be 1.
341    pub temporal_id_nesting_flag: bool,
342    /// Parsed `profile_tier_level()` subroutine.
343    pub ptl: ProfileTierLevel,
344    /// `vps_sub_layer_ordering_info_present_flag`. When 0, only entry
345    /// `[max_sub_layers_minus1]` is signalled and the others inherit
346    /// its value.
347    pub sub_layer_ordering_info_present_flag: bool,
348    /// Per-sub-layer DPB / reorder / latency triples. Indices outside
349    /// `0..=max_sub_layers_minus1` are zero-initialised.
350    pub sub_layer_ordering_info: [SubLayerOrderingInfo; HEVC_MAX_SUB_LAYERS],
351    /// `vps_max_layer_id` (`u(6)`, range 0..=62). The inclusion-matrix
352    /// column count is `value + 1`.
353    pub max_layer_id: u8,
354    /// `vps_num_layer_sets_minus1` (`ue(v)`, range 0..=1023). The
355    /// number of layer sets signalled by the inclusion matrix is
356    /// `value + 1`; layer set 0 is implicit and not signalled in the
357    /// matrix.
358    pub num_layer_sets_minus1: u16,
359    /// `layer_id_included_flag[i][j]` matrix. The outer index is
360    /// `i = 1..=num_layer_sets_minus1`, stored at offset `i - 1`
361    /// (layer set 0 is implicit per §7.4.3.1). Each inner row has
362    /// length `max_layer_id + 1`.
363    pub layer_id_included_flag: Vec<LayerIdInclusionRow>,
364    /// `vps_timing_info_present_flag`. Discriminates whether
365    /// [`Self::timing_info`] is populated.
366    pub timing_info_present_flag: bool,
367    /// Parsed [`VpsTimingInfo`] when [`Self::timing_info_present_flag`]
368    /// is set; `None` otherwise.
369    pub timing_info: Option<VpsTimingInfo>,
370    /// Per-`hrd_parameters()` entries from the §7.3.2.1 loop, one per
371    /// `vps_num_hrd_parameters`. Length equals
372    /// `timing_info.num_hrd_parameters` when timing info is present,
373    /// and 0 otherwise.
374    pub hrd_parameters: Vec<VpsHrdEntry>,
375    /// `vps_extension_flag`. Always populated now that the per-HRD
376    /// bodies are decoded inline; the parser unconditionally reads this
377    /// `u(1)` after the HRD loop completes.
378    pub vps_extension_flag: bool,
379    /// Opaque suffix of the RBSP. Populated when `vps_extension_flag ==
380    /// 1`: the `vps_extension_data_flag` payload and the
381    /// `rbsp_trailing_bits()` are surfaced here for callers that want
382    /// the raw bytes. `None` otherwise.
383    pub opaque_tail: Option<OpaqueTail>,
384}
385
386impl HevcVps {
387    /// Parse `video_parameter_set_rbsp()` starting from the first bit
388    /// of the (already-unescaped) RBSP body — that is, *after* the
389    /// two-byte NAL header has been removed (see
390    /// [`crate::nal::NalUnit`]).
391    pub fn parse(rbsp: &[u8]) -> Result<Self, VpsError> {
392        let mut br = BitReader::new(rbsp);
393        Self::parse_inner(&mut br, rbsp)
394    }
395
396    fn parse_inner(br: &mut BitReader<'_>, rbsp: &[u8]) -> Result<Self, VpsError> {
397        let vps_id = br.u(4)? as u8;
398        let base_layer_internal_flag = br.u1()? != 0;
399        let base_layer_available_flag = br.u1()? != 0;
400        let max_layers_minus1 = br.u(6)? as u8;
401        let max_sub_layers_minus1_raw = br.u(3)? as u8;
402        if max_sub_layers_minus1_raw > 6 {
403            // §7.4.3.1: range is 0..=6 (max 7 sub-layers); a 7 here is
404            // illegal even though u(3) can encode it.
405            return Err(VpsError::ValueOutOfRange {
406                field: "vps_max_sub_layers_minus1",
407                got: max_sub_layers_minus1_raw as u32,
408            });
409        }
410        let temporal_id_nesting_flag = br.u1()? != 0;
411        let reserved = br.u(16)? as u16;
412        if reserved != 0xFFFF {
413            return Err(VpsError::ReservedFieldMismatch { got: reserved });
414        }
415
416        let ptl = ProfileTierLevel::parse(br, true, max_sub_layers_minus1_raw)?;
417
418        let sub_layer_ordering_info_present_flag = br.u1()? != 0;
419        let start = if sub_layer_ordering_info_present_flag {
420            0usize
421        } else {
422            max_sub_layers_minus1_raw as usize
423        };
424        let mut sub_layer_ordering_info = [SubLayerOrderingInfo::default(); HEVC_MAX_SUB_LAYERS];
425        let last = max_sub_layers_minus1_raw as usize;
426        for entry in sub_layer_ordering_info
427            .iter_mut()
428            .take(last + 1)
429            .skip(start)
430        {
431            let max_dpb = br.ue()?;
432            let max_reorder = br.ue()?;
433            let max_lat = br.ue()?;
434            *entry = SubLayerOrderingInfo {
435                max_dec_pic_buffering_minus1: max_dpb,
436                max_num_reorder_pics: max_reorder,
437                max_latency_increase_plus1: max_lat,
438            };
439        }
440        // When the present flag was 0, propagate the
441        // [max_sub_layers_minus1] entry across the lower-indexed
442        // sub-layers — §7.4.3.1 dictates that they take the same value.
443        if !sub_layer_ordering_info_present_flag {
444            let copy = sub_layer_ordering_info[last];
445            for entry in sub_layer_ordering_info.iter_mut().take(last) {
446                *entry = copy;
447            }
448        }
449
450        // vps_max_layer_id u(6) — range 0..=62 per §7.4.3.1. u(6) can
451        // encode 63, which the spec marks as reserved; we accept the
452        // value but the count `max_layer_id + 1` is capped at 64
453        // (HEVC_VPS_MAX_NUM_LAYERS).
454        let max_layer_id = br.u(6)? as u8;
455
456        // vps_num_layer_sets_minus1 ue(v) — range 0..=1023.
457        let num_layer_sets_minus1_raw = br.ue()?;
458        if num_layer_sets_minus1_raw as usize >= HEVC_VPS_MAX_NUM_LAYER_SETS {
459            return Err(VpsError::ValueOutOfRange {
460                field: "vps_num_layer_sets_minus1",
461                got: num_layer_sets_minus1_raw,
462            });
463        }
464        let num_layer_sets_minus1 = num_layer_sets_minus1_raw as u16;
465
466        // Layer-set inclusion matrix. The for-loop in the spec starts
467        // at i = 1 (layer set 0 is the base set, not signalled), so the
468        // signalled-row count is `num_layer_sets_minus1`. Each row has
469        // `max_layer_id + 1` u(1) flags.
470        let row_width = max_layer_id as usize + 1;
471        let signalled_rows = num_layer_sets_minus1 as usize;
472        let mut layer_id_included_flag = Vec::with_capacity(signalled_rows);
473        for _ in 0..signalled_rows {
474            let mut row = Vec::with_capacity(row_width);
475            for _ in 0..row_width {
476                row.push(br.u1()? != 0);
477            }
478            layer_id_included_flag.push(LayerIdInclusionRow { flags: row });
479        }
480
481        // vps_timing_info_present_flag u(1).
482        let timing_info_present_flag = br.u1()? != 0;
483        let mut opaque_tail = None;
484        let mut hrd_parameters: Vec<VpsHrdEntry> = Vec::new();
485        let timing_info = if timing_info_present_flag {
486            // §E.2.1 / §7.3.2.1: num_units_in_tick and time_scale are
487            // both u(32) and "shall be greater than 0" per the
488            // semantics text — enforced here so a zeroed-out stream
489            // doesn't silently divide by zero downstream.
490            let num_units_in_tick = br.u(32)?;
491            if num_units_in_tick == 0 {
492                return Err(VpsError::ValueOutOfRange {
493                    field: "vps_num_units_in_tick",
494                    got: 0,
495                });
496            }
497            let time_scale = br.u(32)?;
498            if time_scale == 0 {
499                return Err(VpsError::ValueOutOfRange {
500                    field: "vps_time_scale",
501                    got: 0,
502                });
503            }
504            let poc_proportional_to_timing_flag = br.u1()? != 0;
505            let num_ticks_poc_diff_one_minus1 = if poc_proportional_to_timing_flag {
506                Some(br.ue()?)
507            } else {
508                None
509            };
510            let num_hrd_parameters = br.ue()?;
511            // Spec range: 0..=vps_num_layer_sets_minus1 + 1. Bound the
512            // value as a sanity check so a malformed stream cannot
513            // force an unbounded loop downstream.
514            if num_hrd_parameters > num_layer_sets_minus1 as u32 + 1 {
515                return Err(VpsError::ValueOutOfRange {
516                    field: "vps_num_hrd_parameters",
517                    got: num_hrd_parameters,
518                });
519            }
520            let info = VpsTimingInfo {
521                num_units_in_tick,
522                time_scale,
523                poc_proportional_to_timing_flag,
524                num_ticks_poc_diff_one_minus1,
525                num_hrd_parameters,
526            };
527            // Decode the per-HRD entries inline per §7.3.2.1:
528            //
529            //   for( i = 0; i < vps_num_hrd_parameters; i++ ) {
530            //     hrd_layer_set_idx[ i ]              ue(v)
531            //     if( i > 0 ) cprms_present_flag[ i ]  u(1)
532            //     hrd_parameters( cprms_present_flag[ i ], vps_max_sub_layers_minus1 )
533            //   }
534            //
535            // The per-HRD body (§E.2.2) lives in the `crate::hrd`
536            // module — the `cprms_present_flag` inheritance chain is
537            // walked here so each entry sees the previous entry's
538            // common-info gates when its own flag is 0.
539            for i in 0..num_hrd_parameters {
540                let prev = hrd_parameters.last();
541                let entry = VpsHrdEntry::parse(br, i, max_sub_layers_minus1_raw, prev)?;
542                hrd_parameters.push(entry);
543            }
544            Some(info)
545        } else {
546            None
547        };
548
549        // vps_extension_flag u(1) — always read now that the per-HRD
550        // bodies are decoded inline above. When set, the
551        // vps_extension_data_flag run plus rbsp_trailing_bits() are
552        // surfaced as the opaque tail (this parser does not interpret
553        // the extension payload).
554        let vps_extension_flag = br.u1()? != 0;
555        if vps_extension_flag {
556            opaque_tail = Some(OpaqueTail::capture_at(br.bit_pos(), rbsp));
557        }
558
559        Ok(Self {
560            vps_id,
561            base_layer_internal_flag,
562            base_layer_available_flag,
563            max_layers_minus1,
564            max_sub_layers_minus1: max_sub_layers_minus1_raw,
565            temporal_id_nesting_flag,
566            ptl,
567            sub_layer_ordering_info_present_flag,
568            sub_layer_ordering_info,
569            max_layer_id,
570            num_layer_sets_minus1,
571            layer_id_included_flag,
572            timing_info_present_flag,
573            timing_info,
574            hrd_parameters,
575            vps_extension_flag,
576            opaque_tail,
577        })
578    }
579}
580
581#[cfg(test)]
582mod tests {
583    use super::*;
584    use crate::nal::{collect_nal_units, strip_emulation_prevention};
585
586    /// VPS RBSP body extracted from the workspace fixture
587    /// `docs/video/h265/fixtures/tiny-i-only-16x16-main/input.hevc`,
588    /// after the Annex B start code and the two-byte NAL header have
589    /// been removed and emulation-prevention bytes stripped. Captured
590    /// inline so the test runs without docs/ on the include path.
591    ///
592    /// Source byte sequence (Annex B): `00 00 00 01 40 01 0C 01 FF FF
593    /// 04 08 00 00 03 00 9F A8 00 00 03 00 00 1E BA 02 40` — after
594    /// stripping the start code and the `40 01` NAL header, and after
595    /// the §7.4.1.1 strip dropping both `03` emulation bytes:
596    /// `0C 01 FF FF 04 08 00 00 00 9F A8 00 00 00 00 1E BA 02 40`.
597    const TINY_VPS_RBSP: &[u8] = &[
598        0x0C, 0x01, 0xFF, 0xFF, 0x04, 0x08, 0x00, 0x00, 0x00, 0x9F, 0xA8, 0x00, 0x00, 0x00, 0x00,
599        0x1E, 0xBA, 0x02, 0x40,
600    ];
601
602    #[test]
603    fn parses_tiny_fixture_vps() {
604        let vps = HevcVps::parse(TINY_VPS_RBSP).expect("VPS parse");
605        assert_eq!(vps.vps_id, 0);
606        assert!(vps.base_layer_internal_flag);
607        assert!(vps.base_layer_available_flag);
608        assert_eq!(vps.max_layers_minus1, 0); // 1 layer
609        assert_eq!(vps.max_sub_layers_minus1, 0); // 1 sub-layer
610        assert!(vps.temporal_id_nesting_flag);
611        assert_eq!(vps.ptl.general_profile_space, 0);
612        assert!(!vps.ptl.general_tier_flag);
613        assert_eq!(vps.ptl.general_profile_idc, 4);
614        // §A.4 maps `30 == level 1.0 × 30` ⇒ level 1.0. (Not "level
615        // 3.0" — that would be value 90.) The fixture's `notes.md`
616        // groups it as "level 3.0" but the on-wire `level_idc` is the
617        // raw encoded value, which is 30 ⇒ level 1.0.
618        assert_eq!(vps.ptl.general_level_idc, 30);
619        // With max_sub_layers_minus1 == 0 the per-sub-layer ordering
620        // loop still runs once for i == 0. The fixture encoder
621        // populates DPB size 3 / no reorder / latency 1 for a
622        // single-frame stream, so the on-wire `ue(v)` triple decodes
623        // to (2, 0, 1).
624        assert!(vps.sub_layer_ordering_info_present_flag);
625        assert_eq!(
626            vps.sub_layer_ordering_info[0].max_dec_pic_buffering_minus1,
627            2
628        );
629        assert_eq!(vps.sub_layer_ordering_info[0].max_num_reorder_pics, 0);
630        assert_eq!(vps.sub_layer_ordering_info[0].max_latency_increase_plus1, 1);
631    }
632
633    #[test]
634    fn parses_tiny_fixture_vps_via_nal_walker() {
635        // End-to-end check: feed the raw Annex B stream through the
636        // NAL walker, then parse the first NAL's RBSP as a VPS.
637        let raw = &[
638            0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0C, 0x01, 0xFF, 0xFF, 0x04, 0x08, 0x00, 0x00,
639            0x03, 0x00, 0x9F, 0xA8, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1E, 0xBA, 0x02, 0x40,
640        ];
641        let units = collect_nal_units(raw).expect("walker");
642        assert_eq!(units.len(), 1);
643        assert_eq!(units[0].header.nal_unit_type, 32); // VPS_NUT
644        let vps = HevcVps::parse(&units[0].rbsp).expect("VPS parse");
645        assert_eq!(vps.vps_id, 0);
646        assert_eq!(vps.ptl.general_profile_idc, 4);
647        assert_eq!(vps.ptl.general_level_idc, 30);
648    }
649
650    #[test]
651    fn rejects_wrong_reserved_field() {
652        // Same prefix as the fixture but flip the last bit of the
653        // 16-bit reserved field so it reads 0xFFFE instead of 0xFFFF.
654        let mut bad = TINY_VPS_RBSP.to_vec();
655        bad[3] = 0xFE; // was 0xFF
656        let err = HevcVps::parse(&bad).unwrap_err();
657        assert_eq!(err, VpsError::ReservedFieldMismatch { got: 0xFFFE });
658    }
659
660    #[test]
661    fn rejects_truncated_rbsp() {
662        // Truncate to before the reserved field finishes.
663        let err = HevcVps::parse(&TINY_VPS_RBSP[..3]).unwrap_err();
664        assert_eq!(err, VpsError::Truncated);
665    }
666
667    #[test]
668    fn strip_emulation_prevention_then_parse_matches_inline_decode() {
669        // Build the wire-format RBSP (with 03 bytes), strip them, and
670        // confirm the decoded VPS matches the inline-stripped fixture.
671        let wire = &[
672            0x0C, 0x01, 0xFF, 0xFF, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9F, 0xA8, 0x00, 0x00,
673            0x03, 0x00, 0x00, 0x1E, 0xBA, 0x02, 0x40,
674        ];
675        let unescaped = strip_emulation_prevention(wire);
676        assert_eq!(unescaped, TINY_VPS_RBSP);
677        let vps = HevcVps::parse(&unescaped).expect("VPS parse");
678        let direct = HevcVps::parse(TINY_VPS_RBSP).expect("VPS parse");
679        assert_eq!(vps, direct);
680    }
681
682    /// Hand-assembled minimal VPS to exercise the loop expansion path
683    /// when `sub_layer_ordering_info_present_flag == 1`. This test
684    /// stresses the multi-sub-layer code path without any fixture.
685    #[test]
686    fn parses_two_sub_layer_ordering_info_present() {
687        // Build a VPS with max_sub_layers_minus1 = 1 (two sublayers)
688        // and sub_layer_ordering_info_present_flag = 1, so two
689        // ordering-info triples are read.
690        //
691        // Field layout bit-by-bit:
692        //   vps_id u4                          : 0000
693        //   base_layer_internal_flag u1        : 1
694        //   base_layer_available_flag u1       : 1
695        //   max_layers_minus1 u6               : 000000
696        //   max_sub_layers_minus1 u3           : 001
697        //   temporal_id_nesting_flag u1        : 1
698        //   reserved u16                       : 1111_1111_1111_1111
699        //   profile_tier_level:
700        //     profile_space u2                 : 00
701        //     tier_flag u1                     : 0
702        //     profile_idc u5                   : 00001  (Main)
703        //     32 compat flags                  : all 0
704        //     4 source flags (prog/i/np/fo)    : 1000
705        //     43-bit block                     : all 0
706        //     1-bit reserved                   : 0
707        //     level_idc u8                     : 00011110 (= 30)
708        //     2 bits per inner sublayer for i in 0..1:
709        //       sub_layer_profile_present[0] u1: 0
710        //       sub_layer_level_present[0] u1  : 1
711        //     8-N inner reserved 2-bit pads (N=1 → 7 entries × 2 bits = 14 bits): all 0
712        //     since sub_layer_profile_present[0] == 0 → no extra body
713        //     sub_layer_level_present[0] == 1 → sub_layer_level_idc[0] u8
714        //                                       : 00011110 (= 30)
715        //   sub_layer_ordering_info_present_flag u1 : 1
716        //   loop i = 0..1 (two iterations) each with:
717        //     ue(v) = '1' (codeNum 0)
718        //     ue(v) = '1' (codeNum 0)
719        //     ue(v) = '1' (codeNum 0)
720        //
721        // We assemble the bit string and then chunk to bytes MSB-first.
722        let mut bits = Vec::<u8>::new();
723        let mut push = |s: &str| {
724            for c in s.chars() {
725                if c == '0' || c == '1' {
726                    bits.push((c as u8) - b'0');
727                }
728            }
729        };
730        push("0000"); // vps_id
731        push("1"); // base_layer_internal_flag
732        push("1"); // base_layer_available_flag
733        push("000000"); // max_layers_minus1
734        push("001"); // max_sub_layers_minus1 = 1
735        push("1"); // temporal_id_nesting_flag
736        push("1111111111111111"); // reserved
737        push("00"); // profile_space
738        push("0"); // tier_flag
739        push("00001"); // profile_idc = 1
740        push(&"0".repeat(32)); // compat flags
741        push("1000"); // prog/interlaced/non_packed/frame_only
742        push(&"0".repeat(43)); // 43-bit block
743        push("0"); // 1-bit reserved
744        push("00011110"); // general_level_idc = 30
745        push("01"); // sub_layer_profile_present[0]=0, sub_layer_level_present[0]=1
746        push(&"0".repeat(2 * 7)); // 14-bit padding for i=1..8
747        push("00011110"); // sub_layer_level_idc[0] = 30
748        push("1"); // sub_layer_ordering_info_present_flag
749        push("1"); // ue=0 (max_dec_pic_buffering_minus1[0])
750        push("1"); // ue=0 (max_num_reorder_pics[0])
751        push("1"); // ue=0 (max_latency_increase_plus1[0])
752        push("1"); // ue=0 (max_dec_pic_buffering_minus1[1])
753        push("1"); // ue=0 (max_num_reorder_pics[1])
754        push("1"); // ue=0 (max_latency_increase_plus1[1])
755                   // VPS tail (round 12 — fully parsed):
756        push("000000"); // vps_max_layer_id = 0 (single layer)
757        push("1"); // vps_num_layer_sets_minus1 ue=0 (just layer set 0)
758        push("0"); // vps_timing_info_present_flag = 0
759        push("0"); // vps_extension_flag = 0
760
761        // Pad with zeros to next byte boundary and pack.
762        while bits.len() % 8 != 0 {
763            bits.push(0);
764        }
765        let mut bytes = Vec::with_capacity(bits.len() / 8);
766        for chunk in bits.chunks(8) {
767            let mut b = 0u8;
768            for &bit in chunk {
769                b = (b << 1) | bit;
770            }
771            bytes.push(b);
772        }
773
774        let vps = HevcVps::parse(&bytes).expect("VPS parse");
775        assert_eq!(vps.vps_id, 0);
776        assert_eq!(vps.max_sub_layers_minus1, 1);
777        assert!(vps.temporal_id_nesting_flag);
778        assert_eq!(vps.ptl.general_profile_idc, 1);
779        assert_eq!(vps.ptl.general_level_idc, 30);
780        assert!(!vps.ptl.sub_layer_profile_present[0]);
781        assert!(vps.ptl.sub_layer_level_present[0]);
782        assert_eq!(vps.ptl.sub_layer_level_idc[0], 30);
783        assert!(vps.sub_layer_ordering_info_present_flag);
784        for i in 0..=1 {
785            assert_eq!(
786                vps.sub_layer_ordering_info[i].max_dec_pic_buffering_minus1,
787                0
788            );
789            assert_eq!(vps.sub_layer_ordering_info[i].max_num_reorder_pics, 0);
790            assert_eq!(vps.sub_layer_ordering_info[i].max_latency_increase_plus1, 0);
791        }
792        // Tail decoded as expected.
793        assert_eq!(vps.max_layer_id, 0);
794        assert_eq!(vps.num_layer_sets_minus1, 0);
795        assert!(!vps.timing_info_present_flag);
796        assert!(vps.timing_info.is_none());
797        assert!(!vps.vps_extension_flag);
798        assert!(vps.opaque_tail.is_none());
799        assert!(vps.hrd_parameters.is_empty());
800    }
801
802    #[test]
803    fn parses_ordering_info_present_flag_zero_propagates() {
804        // max_sub_layers_minus1 = 1, but
805        // sub_layer_ordering_info_present_flag = 0 → only the [1]
806        // entry is signalled; the [0] entry inherits it per §7.4.3.1.
807        let mut bits = Vec::<u8>::new();
808        let mut push = |s: &str| {
809            for c in s.chars() {
810                if c == '0' || c == '1' {
811                    bits.push((c as u8) - b'0');
812                }
813            }
814        };
815        push("0000"); // vps_id
816        push("1");
817        push("1");
818        push("000000");
819        push("001"); // max_sub_layers_minus1 = 1
820        push("1");
821        push("1111111111111111");
822        push("00");
823        push("0");
824        push("00001"); // profile_idc=1
825        push(&"0".repeat(32));
826        push("1000");
827        push(&"0".repeat(43));
828        push("0");
829        push("00011110"); // level_idc=30
830        push("00"); // sub_layer present flags both 0
831        push(&"0".repeat(14));
832        push("0"); // sub_layer_ordering_info_present_flag = 0
833                   // Only the i=1 triple is read; we want the [1] DPB = 2 (codeNum 2 = '011').
834        push("011"); // max_dec_pic_buffering_minus1[1] = 2
835        push("1"); // max_num_reorder_pics[1] = 0
836        push("1"); // max_latency_increase_plus1[1] = 0
837                   // VPS tail (round 12 — fully parsed):
838        push("000000"); // vps_max_layer_id = 0
839        push("1"); // vps_num_layer_sets_minus1 ue=0
840        push("0"); // vps_timing_info_present_flag = 0
841        push("0"); // vps_extension_flag = 0
842
843        while bits.len() % 8 != 0 {
844            bits.push(0);
845        }
846        let mut bytes = Vec::with_capacity(bits.len() / 8);
847        for chunk in bits.chunks(8) {
848            let mut b = 0u8;
849            for &bit in chunk {
850                b = (b << 1) | bit;
851            }
852            bytes.push(b);
853        }
854        let vps = HevcVps::parse(&bytes).expect("VPS parse");
855        assert!(!vps.sub_layer_ordering_info_present_flag);
856        // [1] was signalled; [0] inherits.
857        assert_eq!(
858            vps.sub_layer_ordering_info[1].max_dec_pic_buffering_minus1,
859            2
860        );
861        assert_eq!(
862            vps.sub_layer_ordering_info[0].max_dec_pic_buffering_minus1,
863            2
864        );
865        assert_eq!(vps.max_layer_id, 0);
866        assert_eq!(vps.num_layer_sets_minus1, 0);
867        assert!(!vps.timing_info_present_flag);
868    }
869
870    /// Hand-assembled minimal VPS that exercises the new tail: a
871    /// single extra layer set + a timing-info block with
872    /// `poc_proportional_to_timing_flag == 1` + zero HRDs +
873    /// `vps_extension_flag == 0`.
874    #[test]
875    fn parses_layer_set_matrix_and_timing_info() {
876        let mut bits = Vec::<u8>::new();
877        let mut push = |s: &str| {
878            for c in s.chars() {
879                if c == '0' || c == '1' {
880                    bits.push((c as u8) - b'0');
881                }
882            }
883        };
884        // Minimal prefix: single sub-layer, base Main profile, level 30.
885        push("0000"); // vps_id
886        push("1"); // base_layer_internal_flag
887        push("1"); // base_layer_available_flag
888        push("000000"); // max_layers_minus1
889        push("000"); // max_sub_layers_minus1 = 0
890        push("1"); // temporal_id_nesting_flag
891        push("1111111111111111"); // reserved
892        push("00"); // profile_space
893        push("0"); // tier_flag
894        push("00001"); // profile_idc = 1
895        push(&"0".repeat(32)); // compat flags
896        push("1000"); // prog/interlaced/non_packed/frame_only
897        push(&"0".repeat(43));
898        push("0");
899        push("00011110"); // level_idc = 30
900        push("1"); // sub_layer_ordering_info_present_flag = 1
901        push("1"); // ue=0 (max_dec_pic_buffering_minus1[0])
902        push("1"); // ue=0 (max_num_reorder_pics[0])
903        push("1"); // ue=0 (max_latency_increase_plus1[0])
904                   // Tail:
905        push("000001"); // vps_max_layer_id = 1 (so row width 2)
906        push("010"); // vps_num_layer_sets_minus1 ue=1 (one signalled row)
907                     // layer_id_included_flag[1][0..=1]: pick 1, 0
908        push("10");
909        push("1"); // vps_timing_info_present_flag = 1
910                   // num_units_in_tick = 1001 (NTSC-ish denominator)
911        for i in (0..32).rev() {
912            let b = (1001u32 >> i) & 1;
913            push(if b == 1 { "1" } else { "0" });
914        }
915        // time_scale = 60000
916        for i in (0..32).rev() {
917            let b = (60000u32 >> i) & 1;
918            push(if b == 1 { "1" } else { "0" });
919        }
920        push("1"); // poc_proportional_to_timing_flag = 1
921        push("1"); // num_ticks_poc_diff_one_minus1 ue=0
922        push("1"); // num_hrd_parameters ue=0 (no HRDs — keeps tail parseable)
923        push("0"); // vps_extension_flag = 0
924
925        while bits.len() % 8 != 0 {
926            bits.push(0);
927        }
928        let mut bytes = Vec::with_capacity(bits.len() / 8);
929        for chunk in bits.chunks(8) {
930            let mut b = 0u8;
931            for &bit in chunk {
932                b = (b << 1) | bit;
933            }
934            bytes.push(b);
935        }
936        let vps = HevcVps::parse(&bytes).expect("VPS parse");
937        assert_eq!(vps.max_layer_id, 1);
938        assert_eq!(vps.num_layer_sets_minus1, 1);
939        assert_eq!(vps.layer_id_included_flag.len(), 1);
940        assert_eq!(vps.layer_id_included_flag[0].flags, vec![true, false]);
941        assert!(vps.timing_info_present_flag);
942        let ti = vps.timing_info.as_ref().expect("timing info present");
943        assert_eq!(ti.num_units_in_tick, 1001);
944        assert_eq!(ti.time_scale, 60000);
945        assert!(ti.poc_proportional_to_timing_flag);
946        assert_eq!(ti.num_ticks_poc_diff_one_minus1, Some(0));
947        assert_eq!(ti.num_hrd_parameters, 0);
948        assert!(!vps.vps_extension_flag);
949        assert!(vps.opaque_tail.is_none());
950        assert!(vps.hrd_parameters.is_empty());
951    }
952
953    /// Tail with `vps_num_hrd_parameters == 1` — the parser must
954    /// decode the §E.2.2 `hrd_parameters()` body inline rather than
955    /// surface it as the opaque tail.
956    #[test]
957    fn parses_hrd_payload_inline() {
958        let mut bits = Vec::<u8>::new();
959        let mut push = |s: &str| {
960            for c in s.chars() {
961                if c == '0' || c == '1' {
962                    bits.push((c as u8) - b'0');
963                }
964            }
965        };
966        push("0000");
967        push("1");
968        push("1");
969        push("000000");
970        push("000"); // max_sub_layers_minus1 = 0
971        push("1");
972        push("1111111111111111");
973        push("00");
974        push("0");
975        push("00001");
976        push(&"0".repeat(32));
977        push("1000");
978        push(&"0".repeat(43));
979        push("0");
980        push("00011110"); // level_idc
981        push("1"); // sub_layer_ordering_info_present_flag = 1
982        push("1");
983        push("1");
984        push("1");
985        // Tail:
986        push("000000"); // max_layer_id = 0
987        push("1"); // num_layer_sets_minus1 ue=0
988        push("1"); // timing_info_present_flag = 1
989        for i in (0..32).rev() {
990            let b = (1u32 >> i) & 1;
991            push(if b == 1 { "1" } else { "0" });
992        }
993        for i in (0..32).rev() {
994            let b = (30u32 >> i) & 1;
995            push(if b == 1 { "1" } else { "0" });
996        }
997        push("0"); // poc_proportional_to_timing_flag = 0
998        push("010"); // num_hrd_parameters ue=1
999                     // HRD entry i=0:
1000        push("1"); // hrd_layer_set_idx ue=0
1001                   // cprms_present_flag not signalled (i == 0); inferred 1
1002                   // hrd_parameters( 1, 0 ):
1003        push("0"); // nal_hrd_parameters_present_flag
1004        push("0"); // vcl_hrd_parameters_present_flag
1005                   // (nal | vcl) = 0 → no common-info inner block
1006                   // sub-layer i=0:
1007        push("1"); // fixed_pic_rate_general_flag = 1 → within_cvs inferred 1
1008        push("1"); // elemental_duration_in_tc_minus1 ue=0
1009                   // low_delay not signalled, inferred 0
1010        push("1"); // cpb_cnt_minus1 ue=0
1011                   // no NAL/VCL HRD bodies (gates = 0)
1012        push("0"); // vps_extension_flag = 0
1013
1014        while bits.len() % 8 != 0 {
1015            bits.push(0);
1016        }
1017        let mut bytes = Vec::with_capacity(bits.len() / 8);
1018        for chunk in bits.chunks(8) {
1019            let mut b = 0u8;
1020            for &bit in chunk {
1021                b = (b << 1) | bit;
1022            }
1023            bytes.push(b);
1024        }
1025        let vps = HevcVps::parse(&bytes).expect("VPS parse");
1026        let ti = vps.timing_info.as_ref().expect("timing info present");
1027        assert_eq!(ti.num_hrd_parameters, 1);
1028        assert!(!ti.poc_proportional_to_timing_flag);
1029        // The HRD body is now decoded inline, not surfaced as the
1030        // opaque tail. vps_extension_flag is read after the HRD loop.
1031        assert_eq!(vps.hrd_parameters.len(), 1);
1032        let entry = &vps.hrd_parameters[0];
1033        assert_eq!(entry.hrd_layer_set_idx, 0);
1034        assert!(entry.cprms_present_flag);
1035        let common = entry.hrd.common.as_ref().expect("common present");
1036        assert!(!common.nal_hrd_parameters_present_flag);
1037        assert!(!common.vcl_hrd_parameters_present_flag);
1038        assert_eq!(entry.hrd.sub_layers.len(), 1);
1039        let sl = &entry.hrd.sub_layers[0];
1040        assert!(sl.fixed_pic_rate_general_flag);
1041        assert!(sl.fixed_pic_rate_within_cvs_flag);
1042        assert_eq!(sl.elemental_duration_in_tc_minus1, Some(0));
1043        assert_eq!(sl.cpb_cnt_minus1, 0);
1044        assert!(sl.nal_hrd.is_none());
1045        assert!(sl.vcl_hrd.is_none());
1046        assert!(!vps.vps_extension_flag);
1047        assert!(vps.opaque_tail.is_none());
1048    }
1049
1050    /// `vps_num_units_in_tick == 0` is forbidden by the §E.2.1 /
1051    /// §7.3.2.1 semantics text.
1052    #[test]
1053    fn rejects_zero_num_units_in_tick() {
1054        let mut bits = Vec::<u8>::new();
1055        let mut push = |s: &str| {
1056            for c in s.chars() {
1057                if c == '0' || c == '1' {
1058                    bits.push((c as u8) - b'0');
1059                }
1060            }
1061        };
1062        push("0000");
1063        push("1");
1064        push("1");
1065        push("000000");
1066        push("000");
1067        push("1");
1068        push("1111111111111111");
1069        push("00");
1070        push("0");
1071        push("00001");
1072        push(&"0".repeat(32));
1073        push("1000");
1074        push(&"0".repeat(43));
1075        push("0");
1076        push("00011110");
1077        push("1");
1078        push("1");
1079        push("1");
1080        push("1");
1081        push("000000");
1082        push("1");
1083        push("1"); // timing_info_present_flag = 1
1084        push(&"0".repeat(32)); // num_units_in_tick = 0  → invalid
1085        push(&"0".repeat(32)); // time_scale (unread)
1086
1087        while bits.len() % 8 != 0 {
1088            bits.push(0);
1089        }
1090        let mut bytes = Vec::with_capacity(bits.len() / 8);
1091        for chunk in bits.chunks(8) {
1092            let mut b = 0u8;
1093            for &bit in chunk {
1094                b = (b << 1) | bit;
1095            }
1096            bytes.push(b);
1097        }
1098        let err = HevcVps::parse(&bytes).unwrap_err();
1099        assert_eq!(
1100            err,
1101            VpsError::ValueOutOfRange {
1102                field: "vps_num_units_in_tick",
1103                got: 0
1104            }
1105        );
1106    }
1107
1108    /// `vps_num_hrd_parameters == 2` with the second entry's
1109    /// `cprms_present_flag[1] == 0` — common-info gates inherit from
1110    /// entry 0 per §7.4.3.1.
1111    #[test]
1112    fn parses_two_hrd_entries_with_cprms_inheritance() {
1113        let mut bits = Vec::<u8>::new();
1114        let mut push = |s: &str| {
1115            for c in s.chars() {
1116                if c == '0' || c == '1' {
1117                    bits.push((c as u8) - b'0');
1118                }
1119            }
1120        };
1121        // Same prefix as parses_layer_set_matrix_and_timing_info but
1122        // with two layer sets (so num_hrd_parameters can be 2).
1123        push("0000"); // vps_id
1124        push("1");
1125        push("1");
1126        push("000000");
1127        push("000"); // max_sub_layers_minus1 = 0
1128        push("1");
1129        push("1111111111111111");
1130        push("00");
1131        push("0");
1132        push("00001"); // profile_idc = 1
1133        push(&"0".repeat(32));
1134        push("1000");
1135        push(&"0".repeat(43));
1136        push("0");
1137        push("00011110"); // level_idc = 30
1138        push("1"); // sub_layer_ordering_info_present_flag = 1
1139        push("1");
1140        push("1");
1141        push("1");
1142        push("000000"); // max_layer_id = 0
1143        push("011"); // vps_num_layer_sets_minus1 ue=2 → 3 layer sets, room for 3 HRDs
1144                     // layer_id_included_flag[1..=2][0..=0]: two rows of one bit each
1145        push("1");
1146        push("1");
1147        push("1"); // timing_info_present_flag = 1
1148        for i in (0..32).rev() {
1149            let b = (1u32 >> i) & 1;
1150            push(if b == 1 { "1" } else { "0" });
1151        }
1152        for i in (0..32).rev() {
1153            let b = (30u32 >> i) & 1;
1154            push(if b == 1 { "1" } else { "0" });
1155        }
1156        push("0"); // poc_proportional_to_timing_flag = 0
1157        push("011"); // num_hrd_parameters ue=2
1158
1159        // HRD entry i=0: hrd_layer_set_idx=0, cprms inferred 1.
1160        push("1"); // hrd_layer_set_idx ue=0
1161                   // hrd_parameters( 1, 0 ):
1162        push("1"); // nal_hrd_parameters_present_flag = 1
1163        push("0"); // vcl
1164        push("0"); // sub_pic_hrd_params_present_flag = 0
1165                   // bit_rate_scale / cpb_size_scale u(4) each
1166        push("0000");
1167        push("0000");
1168        // initial / au / dpb_output u(5) each
1169        push("10111");
1170        push("10111");
1171        push("10111");
1172        // sub-layer i=0:
1173        push("1"); // fixed_pic_rate_general = 1
1174        push("1"); // elemental_duration_in_tc_minus1 ue=0
1175                   // cpb_cnt_minus1 ue=0
1176        push("1");
1177        // NAL HRD body: CpbCnt = 1
1178        push("1"); // bit_rate_value_minus1 ue=0
1179        push("1"); // cpb_size_value_minus1 ue=0
1180        push("1"); // cbr_flag[0] = 1
1181
1182        // HRD entry i=1: hrd_layer_set_idx ue=1 ("010"), cprms_present_flag = 0
1183        push("010"); // hrd_layer_set_idx = 1
1184        push("0"); // cprms_present_flag[1] = 0 → inherit entry 0's common info
1185                   // hrd_parameters( 0, 0 ): no common info read; gates
1186                   // inherited (nal = true).
1187                   // sub-layer i=0:
1188        push("1"); // fixed_pic_rate_general = 1
1189        push("1"); // elemental_duration_in_tc_minus1 ue=0
1190                   // cpb_cnt_minus1 ue=0
1191        push("1");
1192        push("1"); // bit_rate_value_minus1 ue=0
1193        push("1"); // cpb_size_value_minus1 ue=0
1194        push("0"); // cbr_flag = 0
1195
1196        push("0"); // vps_extension_flag = 0
1197
1198        while bits.len() % 8 != 0 {
1199            bits.push(0);
1200        }
1201        let mut bytes = Vec::with_capacity(bits.len() / 8);
1202        for chunk in bits.chunks(8) {
1203            let mut b = 0u8;
1204            for &bit in chunk {
1205                b = (b << 1) | bit;
1206            }
1207            bytes.push(b);
1208        }
1209        let vps = HevcVps::parse(&bytes).expect("VPS parse");
1210        assert_eq!(vps.hrd_parameters.len(), 2);
1211        let e0 = &vps.hrd_parameters[0];
1212        assert_eq!(e0.hrd_layer_set_idx, 0);
1213        assert!(e0.cprms_present_flag);
1214        let common = e0.hrd.common.as_ref().expect("entry 0 common");
1215        assert!(common.nal_hrd_parameters_present_flag);
1216        assert_eq!(e0.hrd.sub_layers[0].nal_hrd.as_ref().unwrap().cpb.len(), 1);
1217
1218        let e1 = &vps.hrd_parameters[1];
1219        assert_eq!(e1.hrd_layer_set_idx, 1);
1220        assert!(!e1.cprms_present_flag);
1221        // common is None (cprms_present_flag = 0), but the inherited
1222        // gates from entry 0 still drove the per-sub-layer NAL HRD body.
1223        assert!(e1.hrd.common.is_none());
1224        let nal = e1.hrd.sub_layers[0]
1225            .nal_hrd
1226            .as_ref()
1227            .expect("nal inherited via cprms");
1228        assert_eq!(nal.cpb.len(), 1);
1229        assert!(!nal.cpb[0].cbr_flag);
1230    }
1231}