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}