oxideav_h265/sps.rs
1//! Sequence Parameter Set (SPS) parser per ITU-T Rec. H.265 §7.3.2.2.
2//!
3//! Scope: parse the full SPS RBSP body through the
4//! `strong_intra_smoothing_enabled_flag` field, the
5//! `vui_parameters_present_flag` gate (whose §E.2.1
6//! `vui_parameters()` body is decoded into [`crate::vui::VuiParameters`]),
7//! and the `sps_extension_present_flag` gate. When the extension gate
8//! is set, the trailing bytes (extension payload + RBSP trailing
9//! bits) are surfaced as an **opaque tail**: a copy of the
10//! still-unparsed RBSP bytes plus the bit offset within the first
11//! byte at which the opaque tail begins. The per-extension
12//! `sps_*_extension( )` syntax structures are not materialised yet.
13//!
14//! When `scaling_list_enabled_flag == 1` and
15//! `sps_scaling_list_data_present_flag == 1`, the §7.3.4
16//! `scaling_list_data()` block is parsed via the shared
17//! [`crate::scaling_list`] module; otherwise the §7.4.5 default lists
18//! apply.
19//!
20//! ## Layout summary
21//!
22//! ```text
23//! sps_video_parameter_set_id u(4)
24//! sps_max_sub_layers_minus1 u(3)
25//! sps_temporal_id_nesting_flag u(1)
26//! profile_tier_level( 1, sps_max_sub_layers_minus1 )
27//! sps_seq_parameter_set_id ue(v)
28//! chroma_format_idc ue(v)
29//! if( chroma_format_idc == 3 )
30//! separate_colour_plane_flag u(1)
31//! pic_width_in_luma_samples ue(v)
32//! pic_height_in_luma_samples ue(v)
33//! conformance_window_flag u(1)
34//! if( conformance_window_flag ) {
35//! conf_win_left_offset ue(v)
36//! conf_win_right_offset ue(v)
37//! conf_win_top_offset ue(v)
38//! conf_win_bottom_offset ue(v)
39//! }
40//! bit_depth_luma_minus8 ue(v)
41//! bit_depth_chroma_minus8 ue(v)
42//! log2_max_pic_order_cnt_lsb_minus4 ue(v)
43//! sps_sub_layer_ordering_info_present_flag u(1)
44//! for( i = (...) ; i <= sps_max_sub_layers_minus1; i++ ) {
45//! sps_max_dec_pic_buffering_minus1[i] ue(v)
46//! sps_max_num_reorder_pics[i] ue(v)
47//! sps_max_latency_increase_plus1[i] ue(v)
48//! }
49//! log2_min_luma_coding_block_size_minus3 ue(v)
50//! log2_diff_max_min_luma_coding_block_size ue(v)
51//! log2_min_luma_transform_block_size_minus2 ue(v)
52//! log2_diff_max_min_luma_transform_block_size ue(v)
53//! max_transform_hierarchy_depth_inter ue(v)
54//! max_transform_hierarchy_depth_intra ue(v)
55//! scaling_list_enabled_flag u(1)
56//! if( scaling_list_enabled_flag ) {
57//! sps_scaling_list_data_present_flag u(1)
58//! if( sps_scaling_list_data_present_flag )
59//! scaling_list_data( ) /* §7.3.4 */
60//! }
61//! amp_enabled_flag u(1)
62//! sample_adaptive_offset_enabled_flag u(1)
63//! pcm_enabled_flag u(1)
64//! if( pcm_enabled_flag ) {
65//! pcm_sample_bit_depth_luma_minus1 u(4)
66//! pcm_sample_bit_depth_chroma_minus1 u(4)
67//! log2_min_pcm_luma_coding_block_size_minus3 ue(v)
68//! log2_diff_max_min_pcm_luma_coding_block_size ue(v)
69//! pcm_loop_filter_disabled_flag u(1)
70//! }
71//! num_short_term_ref_pic_sets ue(v)
72//! for( i = 0; i < num_short_term_ref_pic_sets; i++ )
73//! st_ref_pic_set( i )
74//! long_term_ref_pics_present_flag u(1)
75//! if( long_term_ref_pics_present_flag ) {
76//! num_long_term_ref_pics_sps ue(v)
77//! for( i = 0; i < num_long_term_ref_pics_sps; i++ ) {
78//! lt_ref_pic_poc_lsb_sps[i] u(v) /* log2_max_poc_lsb+4 */
79//! used_by_curr_pic_lt_sps_flag[i] u(1)
80//! }
81//! }
82//! sps_temporal_mvp_enabled_flag u(1)
83//! strong_intra_smoothing_enabled_flag u(1)
84//! vui_parameters_present_flag u(1)
85//! if( vui_parameters_present_flag )
86//! vui_parameters( ) /* §E.2.1 */
87//! sps_extension_present_flag u(1)
88//! if( sps_extension_present_flag ) {
89//! sps_range_extension_flag u(1)
90//! sps_multilayer_extension_flag u(1)
91//! sps_3d_extension_flag u(1)
92//! sps_scc_extension_flag u(1)
93//! sps_extension_4bits u(4)
94//! /* opaque tail begins at the first set body, or stays
95//! empty when every flag is 0 */
96//! }
97//! ```
98//!
99//! When `sps_extension_present_flag == 1` the eight bits of typed
100//! extension flags (`sps_range_extension_flag`,
101//! `sps_multilayer_extension_flag`, `sps_3d_extension_flag`,
102//! `sps_scc_extension_flag`, `sps_extension_4bits`) are decoded into
103//! [`SpsExtensionFlags`]. When all five sub-fields are zero only the
104//! RBSP trailing byte remains and no opaque tail is captured; otherwise
105//! the extension bodies (`sps_range_extension()`,
106//! `sps_multilayer_extension()`, `sps_3d_extension()`,
107//! `sps_scc_extension()`, and the `sps_extension_data_flag` while-loop
108//! gated by `sps_extension_4bits`) are surfaced as a single
109//! [`OpaqueTail`] starting at the first body's bit position.
110//!
111//! Validity checks performed here, sourced from §7.4.3.2:
112//!
113//! * `sps_max_sub_layers_minus1` range 0..=6.
114//! * `sps_video_parameter_set_id` and `sps_seq_parameter_set_id`
115//! range 0..=15.
116//! * `chroma_format_idc` range 0..=3.
117//! * `pic_width_in_luma_samples` and `pic_height_in_luma_samples` not
118//! equal to zero (the modulo-`MinCbSizeY` check is deferred — it
119//! depends on the not-yet-validated `log2_min_cb_size`).
120//! * `bit_depth_luma_minus8` and `bit_depth_chroma_minus8` range
121//! 0..=8 (§7.4.3.2 caps the encoded bit depth at 16).
122//! * `log2_max_pic_order_cnt_lsb_minus4` range 0..=12.
123//! * `pcm_sample_bit_depth_luma_minus1` / `pcm_sample_bit_depth_chroma_minus1`
124//! must satisfy `PcmBitDepthY <= BitDepthY` /
125//! `PcmBitDepthC <= BitDepthC` per §7.4.3.2 / equation (7-25 / 7-26).
126//! * `num_short_term_ref_pic_sets` range 0..=64.
127//! * `num_long_term_ref_pics_sps` range 0..=32.
128//! * `num_negative_pics` / `num_positive_pics` capped at 16 (the
129//! §7.4.8 bound is
130//! `sps_max_dec_pic_buffering_minus1[sps_max_sub_layers_minus1]`,
131//! itself bounded at 16 because `MaxDpbSize` per §A.4.2 is 16).
132//! * `delta_poc_s0_minus1` / `delta_poc_s1_minus1` /
133//! `abs_delta_rps_minus1` range 0..=2^15-1.
134//! * `sps_scaling_list_data_present_flag == 1` triggers a §7.3.4
135//! `scaling_list_data()` parse into [`ScalingListData`] via the
136//! shared [`crate::scaling_list`] module.
137
138use crate::bitreader::{BitReader, BitReaderError};
139use crate::scaling_list::{ScalingListData, ScalingListError};
140use crate::vps::{ProfileTierLevel, SubLayerOrderingInfo, VpsError, HEVC_MAX_SUB_LAYERS};
141use crate::vui::{VuiError, VuiParameters};
142
143/// Maximum number of short-term reference picture sets an SPS may
144/// carry. Per §7.4.3.2 the field is bounded at 64 inclusive.
145pub const HEVC_MAX_NUM_SHORT_TERM_RPS: usize = 64;
146
147/// Maximum number of long-term reference pictures an SPS may carry.
148/// Per §7.4.3.2 the field is bounded at 32 inclusive.
149pub const HEVC_MAX_NUM_LONG_TERM_RPS: usize = 32;
150
151/// Maximum number of negative / positive entries permitted in one
152/// short-term RPS. Per §7.4.8 the bound is
153/// `sps_max_dec_pic_buffering_minus1[sps_max_sub_layers_minus1]`
154/// which §A.4.2 caps at `MaxDpbSize - 1 = 15` (for typical levels);
155/// 16 is a defensive upper bound for the parser.
156pub const HEVC_MAX_RPS_PICS: usize = 16;
157
158/// Errors that can arise while parsing an SPS RBSP.
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum SpsError {
161 /// The RBSP ran out of bits before the SPS was fully parsed.
162 Truncated,
163 /// A syntax element's parsed value was outside the legal range
164 /// specified for it in §7.4.3.2.
165 ValueOutOfRange {
166 /// Name of the offending syntax element.
167 field: &'static str,
168 /// The (illegal) value as a `u32` (signed elements are
169 /// re-cast at the call site).
170 got: u32,
171 },
172 /// A `scaling_list_data()` parse (§7.3.4) from the SPS failed.
173 ScalingList(ScalingListError),
174 /// An unexpected bitstream-level error surfaced from the reader.
175 Bitstream(BitReaderError),
176 /// A `profile_tier_level()` parse from the §7.3.3 walk failed.
177 Ptl(VpsError),
178 /// A `vui_parameters()` parse (§E.2.1) from the SPS failed.
179 Vui(VuiError),
180}
181
182impl core::fmt::Display for SpsError {
183 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
184 match self {
185 Self::Truncated => f.write_str("SPS RBSP truncated"),
186 Self::ValueOutOfRange { field, got } => {
187 write!(f, "SPS syntax element {field} out of range: {got}")
188 }
189 Self::ScalingList(e) => write!(f, "scaling-list error during SPS parse: {e}"),
190 Self::Bitstream(e) => write!(f, "bitstream error during SPS parse: {e}"),
191 Self::Ptl(e) => write!(f, "profile_tier_level error during SPS parse: {e}"),
192 Self::Vui(e) => write!(f, "vui_parameters error during SPS parse: {e}"),
193 }
194 }
195}
196
197impl std::error::Error for SpsError {}
198
199impl From<BitReaderError> for SpsError {
200 fn from(e: BitReaderError) -> Self {
201 match e {
202 BitReaderError::EndOfBuffer => Self::Truncated,
203 other => Self::Bitstream(other),
204 }
205 }
206}
207
208impl From<VpsError> for SpsError {
209 fn from(e: VpsError) -> Self {
210 // Surface the inner reader-truncation directly so callers can
211 // distinguish "ran off the end mid-PTL" from other PTL faults.
212 if matches!(e, VpsError::Truncated) {
213 Self::Truncated
214 } else {
215 Self::Ptl(e)
216 }
217 }
218}
219
220impl From<ScalingListError> for SpsError {
221 fn from(e: ScalingListError) -> Self {
222 // Flatten truncation / raw-reader faults to the SPS-level
223 // equivalents so the public surface stays predictable; carry
224 // the structured scaling-list faults through as-is.
225 match e {
226 ScalingListError::Truncated => Self::Truncated,
227 ScalingListError::Bitstream(b) => Self::Bitstream(b),
228 other => Self::ScalingList(other),
229 }
230 }
231}
232
233impl From<VuiError> for SpsError {
234 fn from(e: VuiError) -> Self {
235 // Flatten truncation / raw-reader faults to the SPS-level
236 // equivalents so the public surface stays predictable; carry
237 // the structured VUI faults through as-is.
238 match e {
239 VuiError::Truncated => Self::Truncated,
240 VuiError::Bitstream(b) => Self::Bitstream(b),
241 other => Self::Vui(other),
242 }
243 }
244}
245
246/// Conformance-window offsets, in §6.5.5 SubWidthC/SubHeightC units
247/// (not pixel units — the multiplier depends on
248/// [`SeqParameterSet::chroma_format_idc`]).
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
250pub struct ConformanceWindow {
251 /// `conf_win_left_offset` (`ue(v)`).
252 pub left_offset: u32,
253 /// `conf_win_right_offset` (`ue(v)`).
254 pub right_offset: u32,
255 /// `conf_win_top_offset` (`ue(v)`).
256 pub top_offset: u32,
257 /// `conf_win_bottom_offset` (`ue(v)`).
258 pub bottom_offset: u32,
259}
260
261/// PCM-related SPS block per §7.3.2.2 (`pcm_enabled_flag == 1` only).
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub struct PcmInfo {
264 /// `pcm_sample_bit_depth_luma_minus1` (`u(4)`). The §7.4.3.2
265 /// `PcmBitDepthY` derivation is `value + 1`.
266 pub bit_depth_luma_minus1: u8,
267 /// `pcm_sample_bit_depth_chroma_minus1` (`u(4)`). `PcmBitDepthC = value + 1`.
268 pub bit_depth_chroma_minus1: u8,
269 /// `log2_min_pcm_luma_coding_block_size_minus3` (`ue(v)`).
270 /// `Log2MinIpcmCbSizeY = value + 3`.
271 pub log2_min_pcm_luma_coding_block_size_minus3: u8,
272 /// `log2_diff_max_min_pcm_luma_coding_block_size` (`ue(v)`).
273 /// `Log2MaxIpcmCbSizeY = Log2MinIpcmCbSizeY + value`.
274 pub log2_diff_max_min_pcm_luma_coding_block_size: u8,
275 /// `pcm_loop_filter_disabled_flag` (`u(1)`).
276 pub loop_filter_disabled_flag: bool,
277}
278
279/// Short-term reference picture set per §7.3.7. Only the explicit
280/// (non-inter-predicted) form materialises the per-entry POC and
281/// `used_by_curr_pic` arrays; the inter-RPS-prediction form materialises
282/// only the inputs to the §7.4.8 derivation (`delta_idx_minus1`,
283/// `delta_rps_sign`, `abs_delta_rps_minus1`, and the
284/// `used_by_curr_pic_flag` / `use_delta_flag` arrays of length
285/// `NumDeltaPocs[RefRpsIdx] + 1`).
286#[derive(Debug, Clone, Default, PartialEq, Eq)]
287pub struct ShortTermRefPicSet {
288 /// `inter_ref_pic_set_prediction_flag` — inferred to 0 when not
289 /// signalled (i.e. always 0 for `stRpsIdx == 0`).
290 pub inter_ref_pic_set_prediction_flag: bool,
291 /// `delta_idx_minus1` (only meaningful when
292 /// `inter_ref_pic_set_prediction_flag == 1` and the index this RPS
293 /// is being constructed at equals `num_short_term_ref_pic_sets`;
294 /// otherwise inferred to 0). The §7.4.8 derivation is
295 /// `RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1)`.
296 pub delta_idx_minus1: u32,
297 /// `delta_rps_sign` (1-bit).
298 pub delta_rps_sign: bool,
299 /// `abs_delta_rps_minus1` (`ue(v)`, range 0..=2^15-1).
300 pub abs_delta_rps_minus1: u32,
301 /// Per-entry `used_by_curr_pic_flag[j]` for the inter-RPS-prediction
302 /// case. Length is `NumDeltaPocs[RefRpsIdx] + 1` when the form
303 /// applies, zero otherwise.
304 pub used_by_curr_pic_flag: Vec<bool>,
305 /// Per-entry `use_delta_flag[j]` for the inter-RPS-prediction case.
306 /// Same length / population rule as
307 /// [`used_by_curr_pic_flag`](Self::used_by_curr_pic_flag); per
308 /// §7.4.8 the value is inferred to 1 when the
309 /// `used_by_curr_pic_flag[j]` was 1.
310 pub use_delta_flag: Vec<bool>,
311 /// `num_negative_pics` (`ue(v)`), only meaningful in the explicit
312 /// form.
313 pub num_negative_pics: u32,
314 /// `num_positive_pics` (`ue(v)`), only meaningful in the explicit
315 /// form.
316 pub num_positive_pics: u32,
317 /// `delta_poc_s0_minus1[i]` (`ue(v)`), explicit form. Length
318 /// `num_negative_pics`.
319 pub delta_poc_s0_minus1: Vec<u32>,
320 /// `used_by_curr_pic_s0_flag[i]` (`u(1)`), explicit form. Length
321 /// `num_negative_pics`.
322 pub used_by_curr_pic_s0_flag: Vec<bool>,
323 /// `delta_poc_s1_minus1[i]` (`ue(v)`), explicit form. Length
324 /// `num_positive_pics`.
325 pub delta_poc_s1_minus1: Vec<u32>,
326 /// `used_by_curr_pic_s1_flag[i]` (`u(1)`), explicit form. Length
327 /// `num_positive_pics`.
328 pub used_by_curr_pic_s1_flag: Vec<bool>,
329}
330
331impl ShortTermRefPicSet {
332 /// `NumDeltaPocs[stRpsIdx]` per §7.4.8: in the explicit form this
333 /// is `num_negative_pics + num_positive_pics`; in the inter-RPS
334 /// form the exact count requires the §7.4.8 derivation
335 /// (equations 7-61 / 7-62 / 7-71) against a materialised source
336 /// RPS. See [`Self::materialize`] / [`MaterializedShortTermRefPicSet`]
337 /// for the full derivation.
338 ///
339 /// For the inter-RPS form this helper returns the count of
340 /// `use_delta_flag[j] == 1` entries, which is an upper bound that
341 /// happens to be exact when none of the source POCs flip sign
342 /// across `deltaRps`. Callers that need the exact wire-conformant
343 /// count must materialise the RPS chain.
344 pub fn num_delta_pocs(&self) -> u32 {
345 if self.inter_ref_pic_set_prediction_flag {
346 self.use_delta_flag.iter().filter(|&&v| v).count() as u32
347 } else {
348 self.num_negative_pics + self.num_positive_pics
349 }
350 }
351
352 /// Materialise this `st_ref_pic_set(stRpsIdx)` into the post-§7.4.8
353 /// per-position arrays `(NumNegativePics, DeltaPocS0[],
354 /// UsedByCurrPicS0[], NumPositivePics, DeltaPocS1[],
355 /// UsedByCurrPicS1[])` consumed by §7.4.7.2 and downstream paths.
356 ///
357 /// * For the explicit form (`inter_ref_pic_set_prediction_flag ==
358 /// 0`) this is equations 7-63..7-70: `NumNegativePics =
359 /// num_negative_pics`, `NumPositivePics = num_positive_pics`,
360 /// `UsedByCurrPicSk[i] = used_by_curr_pic_sk_flag[i]`, and the
361 /// cumulative `DeltaPocS0[i] = DeltaPocS0[i-1] -
362 /// (delta_poc_s0_minus1[i] + 1)` recurrence with the
363 /// first-element seeds (`DeltaPocS0` at i=0 is
364 /// `-(delta_poc_s0_minus1 at 0 + 1)` and `DeltaPocS1` at i=0 is
365 /// `delta_poc_s1_minus1 at 0 + 1`). `source` is ignored in this
366 /// branch.
367 /// * For the inter-RPS-prediction form
368 /// (`inter_ref_pic_set_prediction_flag == 1`) this runs
369 /// equations 7-61 (negative side, source-S1-reverse +
370 /// `deltaRps`-self + source-S0-forward) and 7-62 (positive side,
371 /// source-S0-reverse + `deltaRps`-self + source-S1-forward) over
372 /// the already-materialised source RPS supplied via `source`,
373 /// with `deltaRps = (1 - 2*delta_rps_sign) *
374 /// (abs_delta_rps_minus1 + 1)`. The output's
375 /// `NumNegativePics` / `NumPositivePics` reflect the surviving
376 /// `dPoc < 0` / `dPoc > 0` entries with their `use_delta_flag[j]
377 /// == 1` gates, per equation 7-71's `NumDeltaPocs`
378 /// summation. The `source` argument must be `Some(_)` for the
379 /// inter form; the function returns
380 /// [`ShortTermRefPicSetMaterializeError::MissingSource`] if
381 /// absent. Bounds on `used_by_curr_pic_flag` /
382 /// `use_delta_flag` length are checked against the source's
383 /// `NumDeltaPocs + 1`; a mismatch returns
384 /// [`ShortTermRefPicSetMaterializeError::SourceLengthMismatch`].
385 pub fn materialize(
386 &self,
387 source: Option<&MaterializedShortTermRefPicSet>,
388 ) -> Result<MaterializedShortTermRefPicSet, ShortTermRefPicSetMaterializeError> {
389 if self.inter_ref_pic_set_prediction_flag {
390 let source = source.ok_or(ShortTermRefPicSetMaterializeError::MissingSource)?;
391 // `deltaRps` per equation 7-60. The bit-width of
392 // `abs_delta_rps_minus1` is bounded by `ue(v)` plus the
393 // §7.4.8 range check (<= 2^15-1 -> deltaRps fits in i32).
394 let abs = self.abs_delta_rps_minus1 as i64 + 1;
395 let delta_rps = if self.delta_rps_sign { -abs } else { abs } as i32;
396 let num_neg_src = source.num_negative_pics();
397 let num_pos_src = source.num_positive_pics();
398 let num_delta_src = source.num_delta_pocs(); // == num_neg_src + num_pos_src
399 // Per §7.4.8 the `used_by_curr_pic_flag` / `use_delta_flag`
400 // arrays have length `NumDeltaPocs[RefRpsIdx] + 1`.
401 let expected = (num_delta_src + 1) as usize;
402 if self.used_by_curr_pic_flag.len() != expected || self.use_delta_flag.len() != expected
403 {
404 return Err(ShortTermRefPicSetMaterializeError::SourceLengthMismatch {
405 expected: expected as u32,
406 got_used: self.used_by_curr_pic_flag.len(),
407 got_delta: self.use_delta_flag.len(),
408 });
409 }
410 // Negative side (equation 7-61): walk source's positive
411 // POCs in reverse (their `dPoc = DeltaPocS1[RefRpsIdx][j]
412 // + deltaRps` may have crossed zero), then optionally
413 // `deltaRps` itself (only if negative), then source's
414 // negative POCs in forward order.
415 let mut delta_poc_s0 = Vec::new();
416 let mut used_by_curr_pic_s0 = Vec::new();
417 for j in (0..num_pos_src).rev() {
418 let d_poc = source.delta_poc_s1[j as usize] + delta_rps;
419 if d_poc < 0 && self.use_delta_flag[(num_neg_src + j) as usize] {
420 delta_poc_s0.push(d_poc);
421 used_by_curr_pic_s0
422 .push(self.used_by_curr_pic_flag[(num_neg_src + j) as usize]);
423 }
424 }
425 if delta_rps < 0 && self.use_delta_flag[num_delta_src as usize] {
426 delta_poc_s0.push(delta_rps);
427 used_by_curr_pic_s0.push(self.used_by_curr_pic_flag[num_delta_src as usize]);
428 }
429 for j in 0..num_neg_src {
430 let d_poc = source.delta_poc_s0[j as usize] + delta_rps;
431 if d_poc < 0 && self.use_delta_flag[j as usize] {
432 delta_poc_s0.push(d_poc);
433 used_by_curr_pic_s0.push(self.used_by_curr_pic_flag[j as usize]);
434 }
435 }
436 // Positive side (equation 7-62): walk source's negative
437 // POCs in reverse (their `dPoc = DeltaPocS0[RefRpsIdx][j]
438 // + deltaRps` may have crossed zero), then optionally
439 // `deltaRps` itself (only if positive), then source's
440 // positive POCs in forward order.
441 let mut delta_poc_s1 = Vec::new();
442 let mut used_by_curr_pic_s1 = Vec::new();
443 for j in (0..num_neg_src).rev() {
444 let d_poc = source.delta_poc_s0[j as usize] + delta_rps;
445 if d_poc > 0 && self.use_delta_flag[j as usize] {
446 delta_poc_s1.push(d_poc);
447 used_by_curr_pic_s1.push(self.used_by_curr_pic_flag[j as usize]);
448 }
449 }
450 if delta_rps > 0 && self.use_delta_flag[num_delta_src as usize] {
451 delta_poc_s1.push(delta_rps);
452 used_by_curr_pic_s1.push(self.used_by_curr_pic_flag[num_delta_src as usize]);
453 }
454 for j in 0..num_pos_src {
455 let d_poc = source.delta_poc_s1[j as usize] + delta_rps;
456 if d_poc > 0 && self.use_delta_flag[(num_neg_src + j) as usize] {
457 delta_poc_s1.push(d_poc);
458 used_by_curr_pic_s1
459 .push(self.used_by_curr_pic_flag[(num_neg_src + j) as usize]);
460 }
461 }
462 Ok(MaterializedShortTermRefPicSet {
463 delta_poc_s0,
464 used_by_curr_pic_s0,
465 delta_poc_s1,
466 used_by_curr_pic_s1,
467 })
468 } else {
469 // Explicit form, equations 7-63..7-70.
470 let mut delta_poc_s0 = Vec::with_capacity(self.num_negative_pics as usize);
471 let mut prev: i32 = 0;
472 for (i, &delta_minus1) in self.delta_poc_s0_minus1.iter().enumerate() {
473 // delta_minus1 has been range-checked to 0..=2^15-1 on
474 // parse; the cumulative sum cannot exceed i32 capacity
475 // given num_negative_pics <= 16.
476 let step = delta_minus1 as i32 + 1;
477 let d = if i == 0 { -step } else { prev - step };
478 delta_poc_s0.push(d);
479 prev = d;
480 }
481 let mut delta_poc_s1 = Vec::with_capacity(self.num_positive_pics as usize);
482 let mut prev: i32 = 0;
483 for (i, &delta_minus1) in self.delta_poc_s1_minus1.iter().enumerate() {
484 let step = delta_minus1 as i32 + 1;
485 let d = if i == 0 { step } else { prev + step };
486 delta_poc_s1.push(d);
487 prev = d;
488 }
489 Ok(MaterializedShortTermRefPicSet {
490 delta_poc_s0,
491 used_by_curr_pic_s0: self.used_by_curr_pic_s0_flag.clone(),
492 delta_poc_s1,
493 used_by_curr_pic_s1: self.used_by_curr_pic_s1_flag.clone(),
494 })
495 }
496 }
497}
498
499/// Errors that can arise while running the §7.4.8 inter-RPS-prediction
500/// derivation via [`ShortTermRefPicSet::materialize`].
501#[derive(Debug, Clone, Copy, PartialEq, Eq)]
502pub enum ShortTermRefPicSetMaterializeError {
503 /// The RPS uses inter-RPS prediction
504 /// (`inter_ref_pic_set_prediction_flag == 1`) but the caller did
505 /// not supply a materialised source RPS.
506 MissingSource,
507 /// The on-wire `used_by_curr_pic_flag` / `use_delta_flag` arrays
508 /// did not match the source RPS's `NumDeltaPocs[RefRpsIdx] + 1`
509 /// length. This indicates the parser and the materialiser saw
510 /// different source RPSes (most likely a `RefRpsIdx` mismatch).
511 SourceLengthMismatch {
512 /// Expected length: `NumDeltaPocs[RefRpsIdx] + 1`.
513 expected: u32,
514 /// Actual length of `used_by_curr_pic_flag`.
515 got_used: usize,
516 /// Actual length of `use_delta_flag`.
517 got_delta: usize,
518 },
519}
520
521impl core::fmt::Display for ShortTermRefPicSetMaterializeError {
522 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
523 match self {
524 Self::MissingSource => f.write_str(
525 "short-term RPS materialise: inter_ref_pic_set_prediction_flag is set but no source RPS supplied",
526 ),
527 Self::SourceLengthMismatch {
528 expected,
529 got_used,
530 got_delta,
531 } => write!(
532 f,
533 "short-term RPS materialise: per-position array length mismatch: expected {expected}, got used={got_used} delta={got_delta}"
534 ),
535 }
536 }
537}
538
539impl std::error::Error for ShortTermRefPicSetMaterializeError {}
540
541/// Materialised short-term reference-picture-set per §7.4.8 — the
542/// post-derivation form that exposes the per-position `DeltaPocS0[]`,
543/// `UsedByCurrPicS0[]`, `DeltaPocS1[]`, `UsedByCurrPicS1[]` arrays as
544/// signed POC deltas. `NumNegativePics[stRpsIdx]` and
545/// `NumPositivePics[stRpsIdx]` are exposed as the array lengths.
546///
547/// The DeltaPocS0 array is in source order — i.e. the negative POCs
548/// in the order §7.4.8 produces them — which for the explicit form
549/// (equations 7-67 / 7-69) is descending (each element is the next
550/// further-in-the-past POC) and for the inter-RPS-prediction form
551/// (equation 7-61) walks source positives in reverse, then optionally
552/// `deltaRps`, then source negatives in forward order.
553#[derive(Debug, Clone, Default, PartialEq, Eq)]
554pub struct MaterializedShortTermRefPicSet {
555 /// `DeltaPocS0[stRpsIdx][i]` for `i` in
556 /// `0..NumNegativePics[stRpsIdx]`. All entries are strictly
557 /// negative.
558 pub delta_poc_s0: Vec<i32>,
559 /// `UsedByCurrPicS0[stRpsIdx][i]` for `i` in
560 /// `0..NumNegativePics[stRpsIdx]`.
561 pub used_by_curr_pic_s0: Vec<bool>,
562 /// `DeltaPocS1[stRpsIdx][i]` for `i` in
563 /// `0..NumPositivePics[stRpsIdx]`. All entries are strictly
564 /// positive.
565 pub delta_poc_s1: Vec<i32>,
566 /// `UsedByCurrPicS1[stRpsIdx][i]` for `i` in
567 /// `0..NumPositivePics[stRpsIdx]`.
568 pub used_by_curr_pic_s1: Vec<bool>,
569}
570
571impl MaterializedShortTermRefPicSet {
572 /// `NumNegativePics[stRpsIdx]` per §7.4.8.
573 pub fn num_negative_pics(&self) -> u32 {
574 self.delta_poc_s0.len() as u32
575 }
576 /// `NumPositivePics[stRpsIdx]` per §7.4.8.
577 pub fn num_positive_pics(&self) -> u32 {
578 self.delta_poc_s1.len() as u32
579 }
580 /// `NumDeltaPocs[stRpsIdx]` per equation 7-71.
581 pub fn num_delta_pocs(&self) -> u32 {
582 self.num_negative_pics() + self.num_positive_pics()
583 }
584}
585
586/// Long-term reference picture entry per the `long_term_ref_pics_present_flag`
587/// block of §7.3.2.2.
588#[derive(Debug, Clone, Copy, PartialEq, Eq)]
589pub struct LongTermRefPicEntry {
590 /// `lt_ref_pic_poc_lsb_sps[i]` — `u(v)` whose width is
591 /// `log2_max_pic_order_cnt_lsb_minus4 + 4` bits.
592 pub poc_lsb: u32,
593 /// `used_by_curr_pic_lt_sps_flag[i]`.
594 pub used_by_curr_pic: bool,
595}
596
597/// Opaque suffix surfaced when the parser hits an extension body it
598/// does not yet decode (any of `sps_range_extension()`,
599/// `sps_multilayer_extension()`, `sps_3d_extension()`,
600/// `sps_scc_extension()`, or the `sps_extension_data_flag` while-loop
601/// gated by `sps_extension_4bits != 0`; also reused by the
602/// [`crate::pps::PicParameterSet`] extension tail). The bytes captured
603/// are the still-unparsed RBSP body, starting at the byte that
604/// contains the next un-read bit. `start_bit_in_first_byte` is the
605/// bit offset of that next bit within `bytes[0]` (0 = MSB).
606#[derive(Debug, Clone, PartialEq, Eq)]
607pub struct OpaqueTail {
608 /// Raw RBSP bytes from the first byte containing the next
609 /// un-read bit onwards (including the `rbsp_trailing_bits()` byte
610 /// at the end).
611 pub bytes: Vec<u8>,
612 /// Bit offset within `bytes[0]` where the opaque tail begins,
613 /// in MSB-first order (0..=7).
614 pub start_bit_in_first_byte: u8,
615}
616
617/// SPS extension-flag block per §7.3.2.2.1
618/// (`sps_extension_present_flag == 1`), holding the four typed
619/// extension-present flags and the reserved-for-future-use
620/// `sps_extension_4bits` group.
621///
622/// Per §7.4.3.2.1, when `sps_extension_present_flag == 0` every flag
623/// in this block is inferred to 0 and `sps_extension_4bits` is
624/// inferred to 0; the parser surfaces that case as
625/// [`SeqParameterSet::extension_flags`] = `None`.
626#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
627pub struct SpsExtensionFlags {
628 /// `sps_range_extension_flag` (§7.3.2.2.1). When true, a
629 /// `sps_range_extension()` body (§7.3.2.2.2) follows in the bit
630 /// stream and is currently surfaced inside the SPS
631 /// [`SeqParameterSet::opaque_tail`]. This flag selects the §A.3.5
632 /// Format Range Extensions (RExt) profiles family.
633 pub sps_range_extension_flag: bool,
634 /// `sps_multilayer_extension_flag` (§7.3.2.2.1, Annex F /
635 /// scalable & multi-view extensions). When true, a
636 /// `sps_multilayer_extension()` body follows and is surfaced
637 /// inside the opaque tail.
638 pub sps_multilayer_extension_flag: bool,
639 /// `sps_3d_extension_flag` (§7.3.2.2.1, Annex I). When true, a
640 /// `sps_3d_extension()` body follows and is surfaced inside the
641 /// opaque tail.
642 pub sps_3d_extension_flag: bool,
643 /// `sps_scc_extension_flag` (§7.3.2.2.1). When true, a
644 /// `sps_scc_extension()` body follows and is surfaced inside the
645 /// opaque tail. This flag selects the §A.3.7 Screen Content
646 /// Coding (SCC) profiles family.
647 pub sps_scc_extension_flag: bool,
648 /// `sps_extension_4bits` (`u(4)`). For bitstreams conforming to
649 /// the current version of the specification this value shall be
650 /// 0; non-zero values are reserved for future use. The §7.4.3.2.1
651 /// decoder-side rule is to allow any value and (if it is non-zero)
652 /// consume but ignore the `sps_extension_data_flag` while-loop it
653 /// gates, so the parser surfaces the value verbatim. The trailing
654 /// `while( more_rbsp_data() ) sps_extension_data_flag` block (only
655 /// signalled when this field is non-zero) is surfaced inside the
656 /// opaque tail.
657 pub sps_extension_4bits: u8,
658}
659
660impl SpsExtensionFlags {
661 /// True when at least one of the four extension flags is set or
662 /// `sps_extension_4bits` is non-zero — i.e. when at least one
663 /// downstream extension body follows in the bit stream and the
664 /// SPS therefore carries an opaque tail starting at the first
665 /// body's bit position.
666 pub fn has_body(&self) -> bool {
667 self.sps_range_extension_flag
668 || self.sps_multilayer_extension_flag
669 || self.sps_3d_extension_flag
670 || self.sps_scc_extension_flag
671 || self.sps_extension_4bits != 0
672 }
673
674 /// True when the `sps_scc_extension()` body can be decoded in
675 /// place — it is signalled and no still-opaque body
676 /// (`sps_multilayer_extension()` / `sps_3d_extension()`) precedes
677 /// it in the bit stream. When a multilayer / 3D body precedes it,
678 /// the SCC body stays inside the opaque tail.
679 fn scc_decodable_in_place(&self) -> bool {
680 self.sps_scc_extension_flag
681 && !self.sps_multilayer_extension_flag
682 && !self.sps_3d_extension_flag
683 }
684
685 /// True when an extension body still follows the (range +
686 /// optionally SCC) bodies decoded in place — i.e. a multilayer /
687 /// 3D body, the `sps_extension_data_flag` while-loop, or an
688 /// SCC body whose multilayer/3D predecessor kept it opaque.
689 fn has_opaque_body_after_decoded(&self) -> bool {
690 if self.sps_multilayer_extension_flag || self.sps_3d_extension_flag {
691 // The first un-decoded body is the multilayer / 3D one;
692 // everything from there (incl. any SCC body) is opaque.
693 return true;
694 }
695 // No multilayer / 3D body: SCC (if present) was decoded in
696 // place, so only the sps_extension_data_flag while-loop may
697 // remain.
698 self.sps_extension_4bits != 0
699 }
700}
701
702/// Decoded `sps_scc_extension()` body per §7.3.2.2.3, present when
703/// [`SpsExtensionFlags::sps_scc_extension_flag`] is set and no opaque
704/// multilayer / 3D body precedes it. Per §7.4.3.2.3 the absent fields
705/// are inferred to 0 / empty when this struct is `None`.
706#[derive(Debug, Clone, PartialEq, Eq, Default)]
707pub struct SpsSccExtension {
708 /// `sps_curr_pic_ref_enabled_flag` — when 1, a picture referring
709 /// to the SPS may be in a reference picture list of one of its own
710 /// slices (intra block copy).
711 pub sps_curr_pic_ref_enabled_flag: bool,
712 /// `palette_mode_enabled_flag` — when 1, the palette-mode decoding
713 /// process may be used for intra blocks.
714 pub palette_mode_enabled_flag: bool,
715 /// `palette_max_size` (`ue(v)`), present only when
716 /// `palette_mode_enabled_flag`; the maximum allowed palette size
717 /// (inferred 0 otherwise).
718 pub palette_max_size: u32,
719 /// `delta_palette_max_predictor_size` (`ue(v)`), present only when
720 /// `palette_mode_enabled_flag`. `PaletteMaxPredictorSize =
721 /// palette_max_size + value` (eq. 7-35).
722 pub delta_palette_max_predictor_size: u32,
723 /// `sps_palette_predictor_initializers_present_flag` — when 1, the
724 /// sequence palette predictor is initialised from
725 /// [`Self::sps_palette_predictor_initializer`].
726 pub sps_palette_predictor_initializers_present_flag: bool,
727 /// `sps_num_palette_predictor_initializers_minus1` (`ue(v)`),
728 /// present only when the initializers-present flag is set; the
729 /// initializer table then holds `value + 1` entries per component.
730 pub sps_num_palette_predictor_initializers_minus1: u32,
731 /// `sps_palette_predictor_initializer[comp][i]` (§7.3.2.2.3),
732 /// indexed `[comp][i]`. `comp` runs over `numComps` (1 when
733 /// `chroma_format_idc == 0`, else 3). Each value is `u(v)` —
734 /// `BitDepthY` bits for `comp == 0`, `BitDepthC` bits otherwise.
735 /// Empty when no initializers are signalled.
736 pub sps_palette_predictor_initializer: Vec<Vec<u32>>,
737 /// `motion_vector_resolution_control_idc` (`u(2)`) — controls the
738 /// presence / inference of `use_integer_mv_flag`.
739 pub motion_vector_resolution_control_idc: u8,
740 /// `intra_boundary_filtering_disabled_flag` — when 1, the intra
741 /// boundary filtering process is unconditionally disabled.
742 pub intra_boundary_filtering_disabled_flag: bool,
743}
744
745impl SpsSccExtension {
746 /// Decode `sps_scc_extension()` (§7.3.2.2.3). `chroma_format_idc`
747 /// selects `numComps` (1 if 0, else 3) and `bit_depth_luma` /
748 /// `bit_depth_chroma` give the `u(v)` width of each palette
749 /// predictor initializer component.
750 fn parse(
751 br: &mut BitReader,
752 chroma_format_idc: u8,
753 bit_depth_luma: u8,
754 bit_depth_chroma: u8,
755 ) -> Result<Self, SpsError> {
756 let sps_curr_pic_ref_enabled_flag = br.u1()? != 0;
757 let palette_mode_enabled_flag = br.u1()? != 0;
758 let mut palette_max_size = 0u32;
759 let mut delta_palette_max_predictor_size = 0u32;
760 let mut sps_palette_predictor_initializers_present_flag = false;
761 let mut sps_num_palette_predictor_initializers_minus1 = 0u32;
762 let mut sps_palette_predictor_initializer = Vec::new();
763 if palette_mode_enabled_flag {
764 palette_max_size = br.ue()?;
765 delta_palette_max_predictor_size = br.ue()?;
766 // §7.4.3.2.3: when palette_max_size == 0 the
767 // delta_palette_max_predictor_size must be 0 (bitstream
768 // conformance — a zero-size palette cannot grow the
769 // predictor).
770 if palette_max_size == 0 && delta_palette_max_predictor_size != 0 {
771 return Err(SpsError::ValueOutOfRange {
772 field: "delta_palette_max_predictor_size",
773 got: delta_palette_max_predictor_size,
774 });
775 }
776 sps_palette_predictor_initializers_present_flag = br.u1()? != 0;
777 // §7.4.3.2.3: likewise the initializers-present flag must be
778 // 0 when palette_max_size == 0.
779 if palette_max_size == 0 && sps_palette_predictor_initializers_present_flag {
780 return Err(SpsError::ValueOutOfRange {
781 field: "sps_palette_predictor_initializers_present_flag",
782 got: 1,
783 });
784 }
785 if sps_palette_predictor_initializers_present_flag {
786 sps_num_palette_predictor_initializers_minus1 = br.ue()?;
787 // §7.4.3.2.3: bounded by PaletteMaxPredictorSize − 1,
788 // itself capped by the profile limits (§A.3.7:
789 // PaletteMaxPredictorSize <= 128); reject anything past
790 // the largest representable predictor so a malformed
791 // count cannot drive the initializer allocation.
792 if sps_num_palette_predictor_initializers_minus1 >= 128 {
793 return Err(SpsError::ValueOutOfRange {
794 field: "sps_num_palette_predictor_initializers_minus1",
795 got: sps_num_palette_predictor_initializers_minus1,
796 });
797 }
798 let num_comps = if chroma_format_idc == 0 { 1 } else { 3 };
799 let num_entries = sps_num_palette_predictor_initializers_minus1 as usize + 1;
800 sps_palette_predictor_initializer.reserve(num_comps);
801 for comp in 0..num_comps {
802 let width = if comp == 0 {
803 bit_depth_luma
804 } else {
805 bit_depth_chroma
806 };
807 let mut row = Vec::with_capacity(num_entries);
808 for _ in 0..num_entries {
809 row.push(br.u(width)?);
810 }
811 sps_palette_predictor_initializer.push(row);
812 }
813 }
814 }
815 let motion_vector_resolution_control_idc = br.u(2)? as u8;
816 // §7.4.3.2.3: the value 3 is reserved for future use and must
817 // not appear in a conforming bitstream of this version.
818 if motion_vector_resolution_control_idc == 3 {
819 return Err(SpsError::ValueOutOfRange {
820 field: "motion_vector_resolution_control_idc",
821 got: 3,
822 });
823 }
824 let intra_boundary_filtering_disabled_flag = br.u1()? != 0;
825 Ok(Self {
826 sps_curr_pic_ref_enabled_flag,
827 palette_mode_enabled_flag,
828 palette_max_size,
829 delta_palette_max_predictor_size,
830 sps_palette_predictor_initializers_present_flag,
831 sps_num_palette_predictor_initializers_minus1,
832 sps_palette_predictor_initializer,
833 motion_vector_resolution_control_idc,
834 intra_boundary_filtering_disabled_flag,
835 })
836 }
837
838 /// `PaletteMaxPredictorSize` (eq. 7-35) — the maximum allowed
839 /// palette predictor size, `palette_max_size +
840 /// delta_palette_max_predictor_size`.
841 pub fn palette_max_predictor_size(&self) -> u32 {
842 self.palette_max_size + self.delta_palette_max_predictor_size
843 }
844}
845
846/// Decoded `sps_range_extension()` body per §7.3.2.2.2, present when
847/// [`SpsExtensionFlags::sps_range_extension_flag`] is set. Every field
848/// is a single `u(1)` flag; per §7.4.3.2.2 each is inferred to 0 when
849/// the `sps_range_extension()` body is absent (i.e. when
850/// [`SeqParameterSet::sps_range_extension`] is `None`).
851#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
852pub struct SpsRangeExtension {
853 /// `transform_skip_rotation_enabled_flag` — when 1, a rotation is
854 /// applied to the residual of intra 4×4 transform-skip /
855 /// transform-bypass blocks.
856 pub transform_skip_rotation_enabled_flag: bool,
857 /// `transform_skip_context_enabled_flag` — when 1, a particular
858 /// context is used for parsing `sig_coeff_flag` / coefficient
859 /// magnitudes of transform-skip / transform-bypass blocks.
860 pub transform_skip_context_enabled_flag: bool,
861 /// `implicit_rdpcm_enabled_flag` — when 1, residual modification
862 /// for blocks using a transform-bypass may be used for intra
863 /// blocks referring to the SPS.
864 pub implicit_rdpcm_enabled_flag: bool,
865 /// `explicit_rdpcm_enabled_flag` — when 1, residual modification
866 /// for blocks using a transform-bypass may be used for inter
867 /// blocks referring to the SPS.
868 pub explicit_rdpcm_enabled_flag: bool,
869 /// `extended_precision_processing_flag` — when 1, an extended
870 /// dynamic range is used for coefficient parsing and inverse
871 /// transform processing.
872 pub extended_precision_processing_flag: bool,
873 /// `intra_smoothing_disabled_flag` — when 1, the filtering process
874 /// of neighbouring samples is unconditionally disabled for intra
875 /// prediction.
876 pub intra_smoothing_disabled_flag: bool,
877 /// `high_precision_offsets_enabled_flag` — when 1, weighted
878 /// prediction offsets and SAO offsets use a bit depth of
879 /// `BitDepth` rather than the default precision.
880 pub high_precision_offsets_enabled_flag: bool,
881 /// `persistent_rice_adaptation_enabled_flag` — when 1, the Rice
882 /// parameter derivation for the binarization of
883 /// `coeff_abs_level_remaining[]` is initialised at the start of
884 /// each sub-block using mode-dependent statistics accumulated from
885 /// previous sub-blocks.
886 pub persistent_rice_adaptation_enabled_flag: bool,
887 /// `cabac_bypass_alignment_enabled_flag` — when 1, a CABAC
888 /// alignment process is used prior to bypass decoding of the
889 /// syntax elements `coeff_sign_flag[]` and
890 /// `coeff_abs_level_remaining[]`.
891 pub cabac_bypass_alignment_enabled_flag: bool,
892}
893
894impl SpsRangeExtension {
895 /// Decode the nine `u(1)` flags of `sps_range_extension()`
896 /// (§7.3.2.2.2) in bit-stream order.
897 fn parse(br: &mut BitReader) -> Result<Self, SpsError> {
898 Ok(Self {
899 transform_skip_rotation_enabled_flag: br.u1()? != 0,
900 transform_skip_context_enabled_flag: br.u1()? != 0,
901 implicit_rdpcm_enabled_flag: br.u1()? != 0,
902 explicit_rdpcm_enabled_flag: br.u1()? != 0,
903 extended_precision_processing_flag: br.u1()? != 0,
904 intra_smoothing_disabled_flag: br.u1()? != 0,
905 high_precision_offsets_enabled_flag: br.u1()? != 0,
906 persistent_rice_adaptation_enabled_flag: br.u1()? != 0,
907 cabac_bypass_alignment_enabled_flag: br.u1()? != 0,
908 })
909 }
910}
911
912/// Parsed Sequence Parameter Set per §7.3.2.2.
913#[derive(Debug, Clone, PartialEq, Eq)]
914pub struct SeqParameterSet {
915 /// `sps_video_parameter_set_id` (`u(4)`, range 0..=15).
916 pub vps_id: u8,
917 /// `sps_max_sub_layers_minus1` (`u(3)`, range 0..=6).
918 pub max_sub_layers_minus1: u8,
919 /// `sps_temporal_id_nesting_flag`.
920 pub temporal_id_nesting_flag: bool,
921 /// Parsed `profile_tier_level()` subroutine.
922 pub ptl: ProfileTierLevel,
923 /// `sps_seq_parameter_set_id` (`ue(v)`, range 0..=15).
924 pub sps_id: u8,
925 /// `chroma_format_idc` (`ue(v)`, range 0..=3). 0 monochrome, 1
926 /// 4:2:0, 2 4:2:2, 3 4:4:4.
927 pub chroma_format_idc: u8,
928 /// `separate_colour_plane_flag`. Inferred to false when not
929 /// signalled (which is whenever `chroma_format_idc != 3`).
930 pub separate_colour_plane_flag: bool,
931 /// `pic_width_in_luma_samples` (`ue(v)`).
932 pub pic_width_in_luma_samples: u32,
933 /// `pic_height_in_luma_samples` (`ue(v)`).
934 pub pic_height_in_luma_samples: u32,
935 /// `conformance_window_flag`.
936 pub conformance_window_flag: bool,
937 /// `conf_win_*_offset` triple plus bottom — zeroed when
938 /// `conformance_window_flag` is false (§7.4.3.2.1).
939 pub conformance_window: ConformanceWindow,
940 /// `bit_depth_luma_minus8` (`ue(v)`, range 0..=8). The decoded
941 /// `BitDepthY` is `8 + value`.
942 pub bit_depth_luma_minus8: u8,
943 /// `bit_depth_chroma_minus8` (`ue(v)`, range 0..=8).
944 pub bit_depth_chroma_minus8: u8,
945 /// `log2_max_pic_order_cnt_lsb_minus4` (`ue(v)`, range 0..=12).
946 pub log2_max_pic_order_cnt_lsb_minus4: u8,
947 /// `sps_sub_layer_ordering_info_present_flag`.
948 pub sub_layer_ordering_info_present_flag: bool,
949 /// Per-sub-layer DPB / reorder / latency triples. Indices outside
950 /// `0..=max_sub_layers_minus1` are zero-initialised; when the
951 /// present flag was 0 every lower-indexed entry is copied from
952 /// the `[max_sub_layers_minus1]` slot (§7.4.3.2.1).
953 pub sub_layer_ordering_info: [SubLayerOrderingInfo; HEVC_MAX_SUB_LAYERS],
954 /// `log2_min_luma_coding_block_size_minus3` (`ue(v)`).
955 pub log2_min_luma_coding_block_size_minus3: u8,
956 /// `log2_diff_max_min_luma_coding_block_size` (`ue(v)`). Per the
957 /// `log2_ctb_size` derivation in §7.4.3.2.1, the CTU size is
958 /// `1 << (3 + log2_min_cb_minus3 + log2_diff_max_min_luma_cb)`.
959 pub log2_diff_max_min_luma_coding_block_size: u8,
960 /// `log2_min_luma_transform_block_size_minus2` (`ue(v)`).
961 pub log2_min_luma_transform_block_size_minus2: u8,
962 /// `log2_diff_max_min_luma_transform_block_size` (`ue(v)`).
963 pub log2_diff_max_min_luma_transform_block_size: u8,
964 /// `max_transform_hierarchy_depth_inter` (`ue(v)`).
965 pub max_transform_hierarchy_depth_inter: u8,
966 /// `max_transform_hierarchy_depth_intra` (`ue(v)`).
967 pub max_transform_hierarchy_depth_intra: u8,
968 /// `scaling_list_enabled_flag` (§7.3.2.2). When set, the SPS
969 /// either carries an explicit [`Self::scaling_list_data`] (when
970 /// `sps_scaling_list_data_present_flag == 1`) or the §7.4.5 default
971 /// scaling lists apply.
972 pub scaling_list_enabled_flag: bool,
973 /// `sps_scaling_list_data_present_flag` (§7.3.2.2). Inferred to
974 /// `false` (the §7.4.5 default lists apply) when
975 /// `scaling_list_enabled_flag == 0`.
976 pub sps_scaling_list_data_present_flag: bool,
977 /// The parsed §7.3.4 `scaling_list_data()` structure when
978 /// `sps_scaling_list_data_present_flag == 1`; `None` otherwise (the
979 /// §7.4.5 default lists apply).
980 pub scaling_list_data: Option<ScalingListData>,
981 /// `amp_enabled_flag` — asymmetric motion partitions.
982 pub amp_enabled_flag: bool,
983 /// `sample_adaptive_offset_enabled_flag` — SPS-level gate for
984 /// SAO; the per-slice gates are in the slice header.
985 pub sample_adaptive_offset_enabled_flag: bool,
986 /// `pcm_enabled_flag` — when set, [`Self::pcm`] is populated.
987 pub pcm_enabled_flag: bool,
988 /// `pcm_*` block per §7.3.2.2 (only meaningful when
989 /// [`Self::pcm_enabled_flag`] is true).
990 pub pcm: Option<PcmInfo>,
991 /// `num_short_term_ref_pic_sets` (`ue(v)`, range 0..=64).
992 pub num_short_term_ref_pic_sets: u32,
993 /// Parsed `st_ref_pic_set()` entries, length
994 /// [`Self::num_short_term_ref_pic_sets`].
995 pub short_term_ref_pic_sets: Vec<ShortTermRefPicSet>,
996 /// `long_term_ref_pics_present_flag`.
997 pub long_term_ref_pics_present_flag: bool,
998 /// `num_long_term_ref_pics_sps` (`ue(v)`, range 0..=32). Zero
999 /// when [`Self::long_term_ref_pics_present_flag`] is false.
1000 pub num_long_term_ref_pics_sps: u32,
1001 /// Per-entry long-term ref pic POC + used-by-curr-pic flag.
1002 /// Empty when [`Self::long_term_ref_pics_present_flag`] is false.
1003 pub long_term_ref_pics: Vec<LongTermRefPicEntry>,
1004 /// `sps_temporal_mvp_enabled_flag`.
1005 pub sps_temporal_mvp_enabled_flag: bool,
1006 /// `strong_intra_smoothing_enabled_flag`.
1007 pub strong_intra_smoothing_enabled_flag: bool,
1008 /// `vui_parameters_present_flag`. When true, the §E.2.1
1009 /// `vui_parameters( )` body is decoded into
1010 /// [`Self::vui_parameters`] and parsing continues to
1011 /// `sps_extension_present_flag`.
1012 pub vui_parameters_present_flag: bool,
1013 /// Parsed §E.2.1 `vui_parameters()` body when
1014 /// [`Self::vui_parameters_present_flag`] is true; `None`
1015 /// otherwise.
1016 pub vui_parameters: Option<VuiParameters>,
1017 /// `sps_extension_present_flag`. Read in both the VUI-present and
1018 /// VUI-absent paths now that the VUI body is fully decoded. When
1019 /// true, the typed extension flag block is decoded into
1020 /// [`Self::extension_flags`]; any extension body that follows
1021 /// (plus the RBSP trailing bits) is surfaced as
1022 /// [`Self::opaque_tail`].
1023 pub sps_extension_present_flag: bool,
1024 /// Typed extension-flag block, decoded when
1025 /// `sps_extension_present_flag == 1` per §7.3.2.2.1. `None` when
1026 /// the gate is 0; every flag is then inferred to 0 per §7.4.3.2.1.
1027 pub extension_flags: Option<SpsExtensionFlags>,
1028 /// Decoded `sps_range_extension()` body (§7.3.2.2.2), present when
1029 /// `extension_flags.sps_range_extension_flag` is set. `None`
1030 /// otherwise; per §7.4.3.2.2 every field is then inferred to 0.
1031 pub sps_range_extension: Option<SpsRangeExtension>,
1032 /// Decoded `sps_scc_extension()` body (§7.3.2.2.3), present when
1033 /// `extension_flags.sps_scc_extension_flag` is set **and** no
1034 /// opaque multilayer / 3D body precedes it. `None` otherwise; per
1035 /// §7.4.3.2.3 every field is then inferred to 0 / empty.
1036 pub sps_scc_extension: Option<SpsSccExtension>,
1037 /// Opaque suffix of the SPS RBSP. Populated when
1038 /// `sps_extension_present_flag == 1` **and**
1039 /// [`SpsExtensionFlags::has_body`] is true on the decoded flags —
1040 /// the captured bytes start at the first set body
1041 /// (`sps_range_extension()` if `sps_range_extension_flag`,
1042 /// otherwise the next set flag's body) and run through
1043 /// `rbsp_trailing_bits()`. `None` when the SPS ended cleanly
1044 /// after `sps_extension_present_flag == 0` or after a typed flag
1045 /// block in which every flag is 0.
1046 pub opaque_tail: Option<OpaqueTail>,
1047}
1048
1049impl SeqParameterSet {
1050 /// Parse `seq_parameter_set_rbsp()` starting from the first bit
1051 /// of the (already-unescaped) RBSP body — i.e. after the two-byte
1052 /// NAL header has been removed (see [`crate::nal::NalUnit`]).
1053 pub fn parse(rbsp: &[u8]) -> Result<Self, SpsError> {
1054 let mut br = BitReader::new(rbsp);
1055 Self::parse_inner(&mut br, rbsp)
1056 }
1057
1058 /// Materialise the full SPS-level `short_term_ref_pic_sets[]`
1059 /// list into the post-§7.4.8 form, chaining inter-RPS-prediction
1060 /// entries through their `RefRpsIdx = stRpsIdx -
1061 /// (delta_idx_minus1 + 1)` source.
1062 ///
1063 /// The returned vector is the same length as
1064 /// [`Self::short_term_ref_pic_sets`], and the `idx`-th element is
1065 /// the materialisation of
1066 /// `self.short_term_ref_pic_sets[idx]`. Returns an error if any
1067 /// in-chain materialisation fails (e.g. `RefRpsIdx` underflow,
1068 /// `used_by_curr_pic_flag` / `use_delta_flag` length mismatch).
1069 pub fn materialize_short_term_ref_pic_sets(
1070 &self,
1071 ) -> Result<Vec<MaterializedShortTermRefPicSet>, ShortTermRefPicSetMaterializeError> {
1072 let mut out: Vec<MaterializedShortTermRefPicSet> =
1073 Vec::with_capacity(self.short_term_ref_pic_sets.len());
1074 for (st_rps_idx, rps) in self.short_term_ref_pic_sets.iter().enumerate() {
1075 // `RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1)` per
1076 // equation 7-59. For the explicit form `source` is unused
1077 // and `RefRpsIdx` is not derived. For the inter form
1078 // `delta_idx_minus1` was parsed as 0 for any SPS-resident
1079 // entry that did not signal it explicitly (the wire signal
1080 // is only present at the slice-inline call site), which
1081 // maps to the immediately-preceding entry.
1082 let source = if rps.inter_ref_pic_set_prediction_flag {
1083 let ref_rps_idx = (st_rps_idx as i64) - (rps.delta_idx_minus1 as i64 + 1);
1084 if ref_rps_idx < 0 {
1085 return Err(ShortTermRefPicSetMaterializeError::MissingSource);
1086 }
1087 out.get(ref_rps_idx as usize)
1088 } else {
1089 None
1090 };
1091 out.push(rps.materialize(source)?);
1092 }
1093 Ok(out)
1094 }
1095
1096 fn parse_inner(br: &mut BitReader<'_>, rbsp: &[u8]) -> Result<Self, SpsError> {
1097 let vps_id = br.u(4)? as u8;
1098 let max_sub_layers_minus1 = br.u(3)? as u8;
1099 if max_sub_layers_minus1 > 6 {
1100 return Err(SpsError::ValueOutOfRange {
1101 field: "sps_max_sub_layers_minus1",
1102 got: max_sub_layers_minus1 as u32,
1103 });
1104 }
1105 let temporal_id_nesting_flag = br.u1()? != 0;
1106
1107 // profile_tier_level( 1, sps_max_sub_layers_minus1 )
1108 let ptl = ProfileTierLevel::parse(br, true, max_sub_layers_minus1)?;
1109
1110 let sps_id_raw = br.ue()?;
1111 if sps_id_raw > 15 {
1112 return Err(SpsError::ValueOutOfRange {
1113 field: "sps_seq_parameter_set_id",
1114 got: sps_id_raw,
1115 });
1116 }
1117 let sps_id = sps_id_raw as u8;
1118
1119 let chroma_format_idc_raw = br.ue()?;
1120 if chroma_format_idc_raw > 3 {
1121 return Err(SpsError::ValueOutOfRange {
1122 field: "chroma_format_idc",
1123 got: chroma_format_idc_raw,
1124 });
1125 }
1126 let chroma_format_idc = chroma_format_idc_raw as u8;
1127
1128 let separate_colour_plane_flag = if chroma_format_idc == 3 {
1129 br.u1()? != 0
1130 } else {
1131 false
1132 };
1133
1134 // §A.4.1 items b) / c): each dimension "shall be less than or
1135 // equal to Sqrt( MaxLumaPs * 8 )". The largest Table A.8
1136 // MaxLumaPs is 142 606 336 (levels 7 .. 7.2), giving
1137 // Sqrt( 142 606 336 * 8 ) = 33 776 (integer part). Enforcing
1138 // the ceiling here keeps every downstream PicWidthInCtbsY /
1139 // PicSizeInCtbsY derivation (eqs. 7-15 .. 7-19) inside u32.
1140 const MAX_LUMA_DIMENSION: u32 = 33_776;
1141 let pic_width_in_luma_samples = br.ue()?;
1142 if pic_width_in_luma_samples == 0 || pic_width_in_luma_samples > MAX_LUMA_DIMENSION {
1143 return Err(SpsError::ValueOutOfRange {
1144 field: "pic_width_in_luma_samples",
1145 got: pic_width_in_luma_samples,
1146 });
1147 }
1148 let pic_height_in_luma_samples = br.ue()?;
1149 if pic_height_in_luma_samples == 0 || pic_height_in_luma_samples > MAX_LUMA_DIMENSION {
1150 return Err(SpsError::ValueOutOfRange {
1151 field: "pic_height_in_luma_samples",
1152 got: pic_height_in_luma_samples,
1153 });
1154 }
1155
1156 let conformance_window_flag = br.u1()? != 0;
1157 let conformance_window = if conformance_window_flag {
1158 ConformanceWindow {
1159 left_offset: br.ue()?,
1160 right_offset: br.ue()?,
1161 top_offset: br.ue()?,
1162 bottom_offset: br.ue()?,
1163 }
1164 } else {
1165 ConformanceWindow::default()
1166 };
1167
1168 let bit_depth_luma_minus8_raw = br.ue()?;
1169 if bit_depth_luma_minus8_raw > 8 {
1170 return Err(SpsError::ValueOutOfRange {
1171 field: "bit_depth_luma_minus8",
1172 got: bit_depth_luma_minus8_raw,
1173 });
1174 }
1175 let bit_depth_chroma_minus8_raw = br.ue()?;
1176 if bit_depth_chroma_minus8_raw > 8 {
1177 return Err(SpsError::ValueOutOfRange {
1178 field: "bit_depth_chroma_minus8",
1179 got: bit_depth_chroma_minus8_raw,
1180 });
1181 }
1182
1183 let log2_max_pic_order_cnt_lsb_minus4_raw = br.ue()?;
1184 if log2_max_pic_order_cnt_lsb_minus4_raw > 12 {
1185 return Err(SpsError::ValueOutOfRange {
1186 field: "log2_max_pic_order_cnt_lsb_minus4",
1187 got: log2_max_pic_order_cnt_lsb_minus4_raw,
1188 });
1189 }
1190
1191 let sub_layer_ordering_info_present_flag = br.u1()? != 0;
1192 let last = max_sub_layers_minus1 as usize;
1193 let start = if sub_layer_ordering_info_present_flag {
1194 0usize
1195 } else {
1196 last
1197 };
1198 let mut sub_layer_ordering_info = [SubLayerOrderingInfo::default(); HEVC_MAX_SUB_LAYERS];
1199 for entry in sub_layer_ordering_info
1200 .iter_mut()
1201 .take(last + 1)
1202 .skip(start)
1203 {
1204 let max_dpb = br.ue()?;
1205 let max_reorder = br.ue()?;
1206 let max_lat = br.ue()?;
1207 *entry = SubLayerOrderingInfo {
1208 max_dec_pic_buffering_minus1: max_dpb,
1209 max_num_reorder_pics: max_reorder,
1210 max_latency_increase_plus1: max_lat,
1211 };
1212 }
1213 if !sub_layer_ordering_info_present_flag {
1214 // §7.4.3.2.1: when the present flag is 0, every lower-indexed
1215 // sub-layer inherits the [max_sub_layers_minus1] triple.
1216 let copy = sub_layer_ordering_info[last];
1217 for entry in sub_layer_ordering_info.iter_mut().take(last) {
1218 *entry = copy;
1219 }
1220 }
1221
1222 let log2_min_luma_coding_block_size_minus3_raw = br.ue()?;
1223 let log2_diff_max_min_luma_coding_block_size_raw = br.ue()?;
1224 // §7.4.3.2.1 eqs. 7-10 / 7-11: MinCbLog2SizeY =
1225 // log2_min_luma_coding_block_size_minus3 + 3 and CtbLog2SizeY =
1226 // MinCbLog2SizeY + log2_diff_max_min_luma_coding_block_size.
1227 // Every Annex A profile requires "CtbLog2SizeY derived
1228 // according to active SPSs ... shall be in the range of 4 to 6,
1229 // inclusive" (e.g. the §A.3.2 Main-profile item), and the
1230 // eq.-7-13 `CtbSizeY = 1 << CtbLog2SizeY` shift (re-derived all
1231 // over the slice/CTB layers) is only meaningful under that
1232 // bound — reject out-of-range values here.
1233 let ctb_log2_size_y = log2_min_luma_coding_block_size_minus3_raw
1234 .saturating_add(3)
1235 .saturating_add(log2_diff_max_min_luma_coding_block_size_raw);
1236 if !(4..=6).contains(&ctb_log2_size_y) {
1237 return Err(SpsError::ValueOutOfRange {
1238 field: "CtbLog2SizeY",
1239 got: ctb_log2_size_y,
1240 });
1241 }
1242 let log2_min_luma_coding_block_size_minus3 =
1243 log2_min_luma_coding_block_size_minus3_raw as u8;
1244 let log2_diff_max_min_luma_coding_block_size =
1245 log2_diff_max_min_luma_coding_block_size_raw as u8;
1246 let min_cb_log2_size_y = u32::from(log2_min_luma_coding_block_size_minus3) + 3;
1247
1248 let log2_min_luma_transform_block_size_minus2_raw = br.ue()?;
1249 // §7.4.3.2.1: "The CVS shall not contain data that result in
1250 // MinTbLog2SizeY greater than or equal to MinCbLog2SizeY"
1251 // (MinTbLog2SizeY = log2_min_luma_transform_block_size_minus2
1252 // + 2).
1253 let min_tb_log2_size_y = log2_min_luma_transform_block_size_minus2_raw.saturating_add(2);
1254 if min_tb_log2_size_y >= min_cb_log2_size_y {
1255 return Err(SpsError::ValueOutOfRange {
1256 field: "log2_min_luma_transform_block_size_minus2",
1257 got: log2_min_luma_transform_block_size_minus2_raw,
1258 });
1259 }
1260 let log2_min_luma_transform_block_size_minus2 =
1261 log2_min_luma_transform_block_size_minus2_raw as u8;
1262
1263 let log2_diff_max_min_luma_transform_block_size_raw = br.ue()?;
1264 // §7.4.3.2.1: "The CVS shall not contain data that result in
1265 // MaxTbLog2SizeY greater than Min( CtbLog2SizeY, 5 )".
1266 let max_tb_log2_size_y =
1267 min_tb_log2_size_y.saturating_add(log2_diff_max_min_luma_transform_block_size_raw);
1268 if max_tb_log2_size_y > ctb_log2_size_y.min(5) {
1269 return Err(SpsError::ValueOutOfRange {
1270 field: "log2_diff_max_min_luma_transform_block_size",
1271 got: log2_diff_max_min_luma_transform_block_size_raw,
1272 });
1273 }
1274 let log2_diff_max_min_luma_transform_block_size =
1275 log2_diff_max_min_luma_transform_block_size_raw as u8;
1276
1277 // §7.4.3.2.1: both hierarchy depths "shall be in the range of
1278 // 0 to CtbLog2SizeY − MinTbLog2SizeY, inclusive".
1279 let max_hierarchy_depth = ctb_log2_size_y - min_tb_log2_size_y;
1280 let max_transform_hierarchy_depth_inter_raw = br.ue()?;
1281 if max_transform_hierarchy_depth_inter_raw > max_hierarchy_depth {
1282 return Err(SpsError::ValueOutOfRange {
1283 field: "max_transform_hierarchy_depth_inter",
1284 got: max_transform_hierarchy_depth_inter_raw,
1285 });
1286 }
1287 let max_transform_hierarchy_depth_inter = max_transform_hierarchy_depth_inter_raw as u8;
1288 let max_transform_hierarchy_depth_intra_raw = br.ue()?;
1289 if max_transform_hierarchy_depth_intra_raw > max_hierarchy_depth {
1290 return Err(SpsError::ValueOutOfRange {
1291 field: "max_transform_hierarchy_depth_intra",
1292 got: max_transform_hierarchy_depth_intra_raw,
1293 });
1294 }
1295 let max_transform_hierarchy_depth_intra = max_transform_hierarchy_depth_intra_raw as u8;
1296
1297 let scaling_list_enabled_flag = br.u1()? != 0;
1298 let mut sps_scaling_list_data_present_flag = false;
1299 let mut scaling_list_data = None;
1300 if scaling_list_enabled_flag {
1301 // §7.3.2.2: when scaling_list_enabled_flag == 1, an inner
1302 // sps_scaling_list_data_present_flag gates the explicit
1303 // scaling_list_data() structure (§7.3.4). When the inner
1304 // flag is 0 the default scaling lists (§7.4.5 Tables 7-5 /
1305 // 7-6) apply, so the SPS still parses.
1306 sps_scaling_list_data_present_flag = br.u1()? != 0;
1307 if sps_scaling_list_data_present_flag {
1308 scaling_list_data = Some(ScalingListData::parse(br)?);
1309 }
1310 }
1311
1312 let amp_enabled_flag = br.u1()? != 0;
1313 let sample_adaptive_offset_enabled_flag = br.u1()? != 0;
1314
1315 let pcm_enabled_flag = br.u1()? != 0;
1316 let pcm = if pcm_enabled_flag {
1317 let bit_depth_luma_minus1 = br.u(4)? as u8;
1318 let pcm_bit_depth_y = bit_depth_luma_minus1 as u32 + 1;
1319 let bit_depth_y = 8 + bit_depth_luma_minus8_raw;
1320 if pcm_bit_depth_y > bit_depth_y {
1321 return Err(SpsError::ValueOutOfRange {
1322 field: "pcm_sample_bit_depth_luma_minus1",
1323 got: bit_depth_luma_minus1 as u32,
1324 });
1325 }
1326 let bit_depth_chroma_minus1 = br.u(4)? as u8;
1327 let pcm_bit_depth_c = bit_depth_chroma_minus1 as u32 + 1;
1328 let bit_depth_c = 8 + bit_depth_chroma_minus8_raw;
1329 if pcm_bit_depth_c > bit_depth_c {
1330 return Err(SpsError::ValueOutOfRange {
1331 field: "pcm_sample_bit_depth_chroma_minus1",
1332 got: bit_depth_chroma_minus1 as u32,
1333 });
1334 }
1335 let log2_min_pcm_luma_coding_block_size_minus3 = br.ue()? as u8;
1336 let log2_diff_max_min_pcm_luma_coding_block_size = br.ue()? as u8;
1337 let loop_filter_disabled_flag = br.u1()? != 0;
1338 Some(PcmInfo {
1339 bit_depth_luma_minus1,
1340 bit_depth_chroma_minus1,
1341 log2_min_pcm_luma_coding_block_size_minus3,
1342 log2_diff_max_min_pcm_luma_coding_block_size,
1343 loop_filter_disabled_flag,
1344 })
1345 } else {
1346 None
1347 };
1348
1349 let num_short_term_ref_pic_sets_raw = br.ue()?;
1350 if num_short_term_ref_pic_sets_raw > HEVC_MAX_NUM_SHORT_TERM_RPS as u32 {
1351 return Err(SpsError::ValueOutOfRange {
1352 field: "num_short_term_ref_pic_sets",
1353 got: num_short_term_ref_pic_sets_raw,
1354 });
1355 }
1356 let num_short_term_ref_pic_sets = num_short_term_ref_pic_sets_raw;
1357 let mut short_term_ref_pic_sets = Vec::with_capacity(num_short_term_ref_pic_sets as usize);
1358 for st_rps_idx in 0..num_short_term_ref_pic_sets as usize {
1359 let prev = if st_rps_idx == 0 {
1360 None
1361 } else {
1362 short_term_ref_pic_sets.last()
1363 };
1364 let rps = ShortTermRefPicSet::parse(
1365 br,
1366 st_rps_idx as u32,
1367 num_short_term_ref_pic_sets,
1368 prev,
1369 &short_term_ref_pic_sets,
1370 )?;
1371 short_term_ref_pic_sets.push(rps);
1372 }
1373
1374 let long_term_ref_pics_present_flag = br.u1()? != 0;
1375 let mut num_long_term_ref_pics_sps = 0u32;
1376 let mut long_term_ref_pics = Vec::new();
1377 if long_term_ref_pics_present_flag {
1378 let raw = br.ue()?;
1379 if raw > HEVC_MAX_NUM_LONG_TERM_RPS as u32 {
1380 return Err(SpsError::ValueOutOfRange {
1381 field: "num_long_term_ref_pics_sps",
1382 got: raw,
1383 });
1384 }
1385 num_long_term_ref_pics_sps = raw;
1386 let poc_lsb_bits = log2_max_pic_order_cnt_lsb_minus4_raw as u8 + 4;
1387 long_term_ref_pics.reserve(num_long_term_ref_pics_sps as usize);
1388 for _ in 0..num_long_term_ref_pics_sps {
1389 let poc_lsb = br.u(poc_lsb_bits)?;
1390 let used = br.u1()? != 0;
1391 long_term_ref_pics.push(LongTermRefPicEntry {
1392 poc_lsb,
1393 used_by_curr_pic: used,
1394 });
1395 }
1396 }
1397
1398 let sps_temporal_mvp_enabled_flag = br.u1()? != 0;
1399 let strong_intra_smoothing_enabled_flag = br.u1()? != 0;
1400
1401 let vui_parameters_present_flag = br.u1()? != 0;
1402 // §E.2.1: the vui_parameters() body is decoded in full when
1403 // signalled, with the nested hrd_parameters( 1,
1404 // sps_max_sub_layers_minus1 ) call taking the SPS-level
1405 // maxNumSubLayersMinus1. Parsing then continues to
1406 // sps_extension_present_flag in both paths.
1407 let vui_parameters = if vui_parameters_present_flag {
1408 Some(VuiParameters::parse(br, max_sub_layers_minus1)?)
1409 } else {
1410 None
1411 };
1412
1413 let (
1414 sps_extension_present_flag,
1415 extension_flags,
1416 sps_range_extension,
1417 sps_scc_extension,
1418 opaque_tail,
1419 ) = if br.bits_left() == 0 {
1420 // The fixture corpus encoders sometimes elide the
1421 // sps_extension_present_flag if no extension is signalled
1422 // and the rbsp_trailing_bits happens to land on a byte
1423 // boundary; the field is still required, so a buffer with
1424 // no bits left here is a truncation.
1425 return Err(SpsError::Truncated);
1426 } else {
1427 let gate = br.u1()? != 0;
1428 if gate {
1429 // §7.3.2.2.1: when the gate is open, decode the eight
1430 // bits of typed extension flags first.
1431 let sps_range_extension_flag = br.u1()? != 0;
1432 let sps_multilayer_extension_flag = br.u1()? != 0;
1433 let sps_3d_extension_flag = br.u1()? != 0;
1434 let sps_scc_extension_flag = br.u1()? != 0;
1435 let sps_extension_4bits = br.u(4)? as u8;
1436 let flags = SpsExtensionFlags {
1437 sps_range_extension_flag,
1438 sps_multilayer_extension_flag,
1439 sps_3d_extension_flag,
1440 sps_scc_extension_flag,
1441 sps_extension_4bits,
1442 };
1443 // §7.3.2.2.1: the range extension body (if signalled)
1444 // is the first to follow the eight typed flag bits, so
1445 // decode it in full.
1446 let range_ext = if flags.sps_range_extension_flag {
1447 Some(SpsRangeExtension::parse(br)?)
1448 } else {
1449 None
1450 };
1451 // §7.3.2.2.1 body order is range, multilayer, 3d, scc.
1452 // The SCC body can be decoded in place only when no
1453 // (still-opaque) multilayer / 3D body precedes it;
1454 // otherwise it stays inside the opaque tail.
1455 let scc_ext = if flags.scc_decodable_in_place() {
1456 Some(SpsSccExtension::parse(
1457 br,
1458 chroma_format_idc,
1459 8 + bit_depth_luma_minus8_raw as u8,
1460 8 + bit_depth_chroma_minus8_raw as u8,
1461 )?)
1462 } else {
1463 None
1464 };
1465 // If any still-opaque body (a multilayer / 3D body, an
1466 // SCC body kept opaque by such a predecessor, or the
1467 // sps_extension_data_flag while-loop) follows, capture
1468 // the rest of the RBSP as an opaque tail starting at
1469 // the first un-decoded body's bit position. Otherwise
1470 // only rbsp_trailing_bits remains, consumed implicitly.
1471 let tail = if flags.has_opaque_body_after_decoded() {
1472 Some(OpaqueTail::capture_at(br.bit_pos(), rbsp))
1473 } else {
1474 None
1475 };
1476 (true, Some(flags), range_ext, scc_ext, tail)
1477 } else {
1478 // No extension present. Only the rbsp_trailing_bits
1479 // remain — a single `1` bit followed by zero-padding
1480 // to a byte boundary. We do not require the caller to
1481 // have validated it; surface nothing for the opaque tail.
1482 (false, None, None, None, None)
1483 }
1484 };
1485
1486 Ok(Self {
1487 vps_id,
1488 max_sub_layers_minus1,
1489 temporal_id_nesting_flag,
1490 ptl,
1491 sps_id,
1492 chroma_format_idc,
1493 separate_colour_plane_flag,
1494 pic_width_in_luma_samples,
1495 pic_height_in_luma_samples,
1496 conformance_window_flag,
1497 conformance_window,
1498 bit_depth_luma_minus8: bit_depth_luma_minus8_raw as u8,
1499 bit_depth_chroma_minus8: bit_depth_chroma_minus8_raw as u8,
1500 log2_max_pic_order_cnt_lsb_minus4: log2_max_pic_order_cnt_lsb_minus4_raw as u8,
1501 sub_layer_ordering_info_present_flag,
1502 sub_layer_ordering_info,
1503 log2_min_luma_coding_block_size_minus3,
1504 log2_diff_max_min_luma_coding_block_size,
1505 log2_min_luma_transform_block_size_minus2,
1506 log2_diff_max_min_luma_transform_block_size,
1507 max_transform_hierarchy_depth_inter,
1508 max_transform_hierarchy_depth_intra,
1509 scaling_list_enabled_flag,
1510 sps_scaling_list_data_present_flag,
1511 scaling_list_data,
1512 amp_enabled_flag,
1513 sample_adaptive_offset_enabled_flag,
1514 pcm_enabled_flag,
1515 pcm,
1516 num_short_term_ref_pic_sets,
1517 short_term_ref_pic_sets,
1518 long_term_ref_pics_present_flag,
1519 num_long_term_ref_pics_sps,
1520 long_term_ref_pics,
1521 sps_temporal_mvp_enabled_flag,
1522 strong_intra_smoothing_enabled_flag,
1523 vui_parameters_present_flag,
1524 vui_parameters,
1525 sps_extension_present_flag,
1526 extension_flags,
1527 sps_range_extension,
1528 sps_scc_extension,
1529 opaque_tail,
1530 })
1531 }
1532
1533 /// Convenience: derive the SPS-level `BitDepthY` (luma bit depth).
1534 pub fn bit_depth_luma(&self) -> u8 {
1535 8 + self.bit_depth_luma_minus8
1536 }
1537
1538 /// Derive the SPS-level `BitDepthC` (chroma bit depth).
1539 pub fn bit_depth_chroma(&self) -> u8 {
1540 8 + self.bit_depth_chroma_minus8
1541 }
1542
1543 /// Derive `MinCbLog2SizeY = log2_min_luma_coding_block_size_minus3 + 3`
1544 /// (§7.4.3.2.1).
1545 pub fn log2_min_cb_size(&self) -> u8 {
1546 self.log2_min_luma_coding_block_size_minus3 + 3
1547 }
1548
1549 /// Derive `CtbLog2SizeY = MinCbLog2SizeY
1550 /// + log2_diff_max_min_luma_coding_block_size` (§7.4.3.2.1).
1551 pub fn log2_ctb_size(&self) -> u8 {
1552 self.log2_min_cb_size() + self.log2_diff_max_min_luma_coding_block_size
1553 }
1554
1555 /// Derive `MinTbLog2SizeY = log2_min_luma_transform_block_size_minus2 + 2`
1556 /// (§7.4.3.2.1).
1557 pub fn log2_min_tb_size(&self) -> u8 {
1558 self.log2_min_luma_transform_block_size_minus2 + 2
1559 }
1560
1561 /// Derive `MaxPicOrderCntLsb = 1 << (log2_max_pic_order_cnt_lsb_minus4 + 4)`
1562 /// (§7.4.3.2.1).
1563 pub fn max_pic_order_cnt_lsb(&self) -> u32 {
1564 1u32 << (self.log2_max_pic_order_cnt_lsb_minus4 + 4)
1565 }
1566}
1567
1568impl OpaqueTail {
1569 /// Capture all RBSP bytes from the byte holding the bit at
1570 /// `bit_pos` (counted MSB-first from the start of `rbsp`) through
1571 /// end-of-buffer. Used by both the SPS extension tail and the
1572 /// [`crate::pps::PicParameterSet`] extension tail.
1573 pub fn capture_at(bit_pos: usize, rbsp: &[u8]) -> Self {
1574 let byte_index = bit_pos / 8;
1575 let bit_in_byte = (bit_pos % 8) as u8;
1576 Self {
1577 bytes: rbsp[byte_index..].to_vec(),
1578 start_bit_in_first_byte: bit_in_byte,
1579 }
1580 }
1581}
1582
1583impl ShortTermRefPicSet {
1584 /// Parse the in-line slice-header `st_ref_pic_set( num_short_term_ref_pic_sets )`
1585 /// per §7.3.6.1 / §7.3.7.
1586 ///
1587 /// This is the entry point the slice-header parser uses when
1588 /// `short_term_ref_pic_set_sps_flag == 0`: the picture's short-term
1589 /// RPS is constructed *inline* in the slice header at index
1590 /// `stRpsIdx == num_short_term_ref_pic_sets`, with the SPS's
1591 /// pre-existing list ([`SeqParameterSet::short_term_ref_pic_sets`])
1592 /// supplying `all_rps` for the §7.4.8 `RefRpsIdx` derivation.
1593 ///
1594 /// `br` must be positioned at the first bit of `st_ref_pic_set()`.
1595 /// On success the reader is advanced past the structure; on a
1596 /// truncation or range-check failure the reader state is undefined.
1597 pub fn parse_slice_inline(
1598 br: &mut BitReader<'_>,
1599 sps: &SeqParameterSet,
1600 ) -> Result<Self, SpsError> {
1601 Self::parse(
1602 br,
1603 sps.num_short_term_ref_pic_sets,
1604 sps.num_short_term_ref_pic_sets,
1605 sps.short_term_ref_pic_sets.last(),
1606 &sps.short_term_ref_pic_sets,
1607 )
1608 }
1609
1610 /// Parse one `st_ref_pic_set( stRpsIdx )` per §7.3.7.
1611 ///
1612 /// * `st_rps_idx` is `stRpsIdx`.
1613 /// * `num_short_term_ref_pic_sets` is the SPS-level count being
1614 /// constructed (used to detect when `delta_idx_minus1` is signalled).
1615 /// * `prev` is the previously-parsed RPS, used when the
1616 /// inter-RPS-prediction form is invoked without explicit
1617 /// `delta_idx_minus1` (i.e. `stRpsIdx < num_short_term_ref_pic_sets`).
1618 /// * `all_rps` is the full list of RPSes parsed so far; the
1619 /// §7.4.8 `RefRpsIdx` computation indexes into it.
1620 fn parse(
1621 br: &mut BitReader<'_>,
1622 st_rps_idx: u32,
1623 num_short_term_ref_pic_sets: u32,
1624 prev: Option<&ShortTermRefPicSet>,
1625 all_rps: &[ShortTermRefPicSet],
1626 ) -> Result<Self, SpsError> {
1627 let inter_ref_pic_set_prediction_flag = if st_rps_idx != 0 {
1628 br.u1()? != 0
1629 } else {
1630 false
1631 };
1632 if inter_ref_pic_set_prediction_flag {
1633 // delta_idx_minus1 is only signalled when the RPS being
1634 // constructed is the slice-header in-line RPS, i.e.
1635 // stRpsIdx == num_short_term_ref_pic_sets. For SPS-resident
1636 // entries the value is inferred to 0 per §7.4.8.
1637 let delta_idx_minus1 = if st_rps_idx == num_short_term_ref_pic_sets {
1638 br.ue()?
1639 } else {
1640 0
1641 };
1642 if delta_idx_minus1 >= st_rps_idx {
1643 return Err(SpsError::ValueOutOfRange {
1644 field: "delta_idx_minus1",
1645 got: delta_idx_minus1,
1646 });
1647 }
1648 let delta_rps_sign = br.u1()? != 0;
1649 let abs_delta_rps_minus1 = br.ue()?;
1650 if abs_delta_rps_minus1 > (1 << 15) - 1 {
1651 return Err(SpsError::ValueOutOfRange {
1652 field: "abs_delta_rps_minus1",
1653 got: abs_delta_rps_minus1,
1654 });
1655 }
1656 // RefRpsIdx = stRpsIdx − (delta_idx_minus1 + 1)
1657 let ref_rps_idx = (st_rps_idx as i64) - (delta_idx_minus1 as i64 + 1);
1658 let ref_rps = if ref_rps_idx >= 0 && (ref_rps_idx as usize) < all_rps.len() {
1659 Some(&all_rps[ref_rps_idx as usize])
1660 } else {
1661 // For SPS entries we expect ref_rps_idx in-range; the
1662 // only legal use of an out-of-range RefRpsIdx is when
1663 // st_rps_idx == num_short_term_ref_pic_sets, which is
1664 // the slice-header in-line case (handled elsewhere).
1665 prev
1666 };
1667 let num_delta_pocs = ref_rps.map(|r| r.num_delta_pocs()).unwrap_or(0);
1668 let entries = num_delta_pocs as usize + 1;
1669 let mut used_by_curr_pic_flag = Vec::with_capacity(entries);
1670 let mut use_delta_flag = Vec::with_capacity(entries);
1671 for _ in 0..entries {
1672 let used = br.u1()? != 0;
1673 used_by_curr_pic_flag.push(used);
1674 if !used {
1675 let ud = br.u1()? != 0;
1676 use_delta_flag.push(ud);
1677 } else {
1678 // Per §7.4.8: when used_by_curr_pic_flag[j] is 1,
1679 // use_delta_flag[j] is inferred to be 1.
1680 use_delta_flag.push(true);
1681 }
1682 }
1683 Ok(Self {
1684 inter_ref_pic_set_prediction_flag,
1685 delta_idx_minus1,
1686 delta_rps_sign,
1687 abs_delta_rps_minus1,
1688 used_by_curr_pic_flag,
1689 use_delta_flag,
1690 num_negative_pics: 0,
1691 num_positive_pics: 0,
1692 delta_poc_s0_minus1: Vec::new(),
1693 used_by_curr_pic_s0_flag: Vec::new(),
1694 delta_poc_s1_minus1: Vec::new(),
1695 used_by_curr_pic_s1_flag: Vec::new(),
1696 })
1697 } else {
1698 let num_negative_pics = br.ue()?;
1699 if num_negative_pics > HEVC_MAX_RPS_PICS as u32 {
1700 return Err(SpsError::ValueOutOfRange {
1701 field: "num_negative_pics",
1702 got: num_negative_pics,
1703 });
1704 }
1705 let num_positive_pics = br.ue()?;
1706 if num_positive_pics > HEVC_MAX_RPS_PICS as u32 {
1707 return Err(SpsError::ValueOutOfRange {
1708 field: "num_positive_pics",
1709 got: num_positive_pics,
1710 });
1711 }
1712 let mut delta_poc_s0_minus1 = Vec::with_capacity(num_negative_pics as usize);
1713 let mut used_by_curr_pic_s0_flag = Vec::with_capacity(num_negative_pics as usize);
1714 for _ in 0..num_negative_pics {
1715 let dp = br.ue()?;
1716 if dp > (1 << 15) - 1 {
1717 return Err(SpsError::ValueOutOfRange {
1718 field: "delta_poc_s0_minus1",
1719 got: dp,
1720 });
1721 }
1722 delta_poc_s0_minus1.push(dp);
1723 used_by_curr_pic_s0_flag.push(br.u1()? != 0);
1724 }
1725 let mut delta_poc_s1_minus1 = Vec::with_capacity(num_positive_pics as usize);
1726 let mut used_by_curr_pic_s1_flag = Vec::with_capacity(num_positive_pics as usize);
1727 for _ in 0..num_positive_pics {
1728 let dp = br.ue()?;
1729 if dp > (1 << 15) - 1 {
1730 return Err(SpsError::ValueOutOfRange {
1731 field: "delta_poc_s1_minus1",
1732 got: dp,
1733 });
1734 }
1735 delta_poc_s1_minus1.push(dp);
1736 used_by_curr_pic_s1_flag.push(br.u1()? != 0);
1737 }
1738 Ok(Self {
1739 inter_ref_pic_set_prediction_flag,
1740 delta_idx_minus1: 0,
1741 delta_rps_sign: false,
1742 abs_delta_rps_minus1: 0,
1743 used_by_curr_pic_flag: Vec::new(),
1744 use_delta_flag: Vec::new(),
1745 num_negative_pics,
1746 num_positive_pics,
1747 delta_poc_s0_minus1,
1748 used_by_curr_pic_s0_flag,
1749 delta_poc_s1_minus1,
1750 used_by_curr_pic_s1_flag,
1751 })
1752 }
1753 }
1754}
1755
1756#[cfg(test)]
1757mod tests {
1758 use super::*;
1759 use crate::nal::{collect_nal_units, strip_emulation_prevention};
1760
1761 /// Helper: convert a bit string (any non-`0`/`1` characters are
1762 /// ignored, useful for visual spacing) into a packed MSB-first
1763 /// byte vector with zero-padding up to the next byte boundary.
1764 fn bits_to_bytes(s: &str) -> Vec<u8> {
1765 let mut bits: Vec<u8> = Vec::new();
1766 for c in s.chars() {
1767 if c == '0' || c == '1' {
1768 bits.push((c as u8) - b'0');
1769 }
1770 }
1771 while bits.len() % 8 != 0 {
1772 bits.push(0);
1773 }
1774 let mut out = Vec::with_capacity(bits.len() / 8);
1775 for chunk in bits.chunks(8) {
1776 let mut b = 0u8;
1777 for &bit in chunk {
1778 b = (b << 1) | bit;
1779 }
1780 out.push(b);
1781 }
1782 out
1783 }
1784
1785 /// Header bits through the per-sub-layer ordering triple, with the
1786 /// `pic_width_in_luma_samples` / `pic_height_in_luma_samples`
1787 /// `ue(v)` bit strings supplied by the caller (the fixture default
1788 /// is 16 × 16 → `000010001` each). Everything else is fixed:
1789 /// `vps_id=0, max_sub_layers_minus1=0, nesting=1, a §7.3.3 PTL
1790 /// walk (profile_idc=1, level=30), sps_id=0, chroma_format_idc=1,
1791 /// conf_win=0, bit_depths=0, log2_max_poc_lsb_minus4=4,
1792 /// ordering_info present with single triple {0,0,0}`.
1793 fn synthesised_header_through_ordering(width_ue: &str, height_ue: &str) -> String {
1794 let mut s = String::new();
1795 s += "0000"; // vps_id
1796 s += "000"; // max_sub_layers_minus1
1797 s += "1"; // nesting flag
1798 // profile_tier_level(1, 0)
1799 s += "00"; // profile_space
1800 s += "0"; // tier
1801 s += "00001"; // profile_idc
1802 for _ in 0..32 {
1803 s += "0";
1804 }
1805 s += "0000";
1806 for _ in 0..43 {
1807 s += "0";
1808 }
1809 s += "0";
1810 s += "00011110"; // level=30
1811 // sps_id ue(v)=0
1812 s += "1";
1813 // chroma_format_idc ue(v)=1 → '010'
1814 s += "010";
1815 // width ue(v)
1816 s += width_ue;
1817 // height ue(v)
1818 s += height_ue;
1819 // conf_win = 0
1820 s += "0";
1821 // bd_luma=0, bd_chroma=0
1822 s += "1";
1823 s += "1";
1824 // log2_max_poc_lsb_minus4 = 4 → '00101'
1825 s += "00101";
1826 // ordering present = 1, single triple {0,0,0}
1827 s += "1";
1828 s += "1";
1829 s += "1";
1830 s += "1";
1831 s
1832 }
1833
1834 /// Prefix of bits that get every SPS hand-assembled fixture through
1835 /// the structural header up to (but not including) the round-4 tail.
1836 /// `chroma_format_idc=1, width=16, height=16, conf_win=0, bit_depths=0,
1837 /// log2_max_poc_lsb_minus4=4, max_sub_layers_minus1=0, ordering_info
1838 /// present with single triple {dpb=0, reorder=0, latency=0},
1839 /// log2_min_cb=0, log2_diff=1, log2_min_tb=0, log2_diff_tb=2,
1840 /// max_transform_depth_*=0, scaling_list=0, amp=0, sao=1`.
1841 ///
1842 /// This is the EXACT same prefix the round-3 tests used; the round-4
1843 /// tail tests then concatenate the tail bits they want to exercise.
1844 fn synthesised_prefix_bits() -> String {
1845 let mut s = synthesised_header_through_ordering("000010001", "000010001");
1846 // log2_min_cb_minus3 = 0
1847 s += "1";
1848 // log2_diff = 1 → '010'
1849 s += "010";
1850 // log2_min_tb_minus2 = 0
1851 s += "1";
1852 // log2_diff_tb = 2 → '011'
1853 s += "011";
1854 // max_transform_depth_{inter,intra} = 0
1855 s += "1";
1856 s += "1";
1857 // scaling_list_enabled = 0
1858 s += "0";
1859 // amp_enabled = 0
1860 s += "0";
1861 // sao_enabled = 1
1862 s += "1";
1863 s
1864 }
1865
1866 /// SPS RBSP body extracted from
1867 /// `docs/video/h265/fixtures/tiny-i-only-16x16-main/input.hevc`,
1868 /// after the Annex B start code and the two-byte NAL header have
1869 /// been removed and emulation-prevention bytes stripped. The wire
1870 /// SPS (NAL idx 1, type 33) was 38 bytes including the two-byte
1871 /// header; after §7.4.1.1 strip (3 escape bytes removed) the body
1872 /// is 32 bytes.
1873 const TINY_SPS_RBSP: &[u8] = &[
1874 0x01, 0x04, 0x08, 0x00, 0x00, 0x00, 0x9F, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x1E, 0xA0, 0x88,
1875 0x45, 0x96, 0xEA, 0xAF, 0x2B, 0xC0, 0x5A, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
1876 0x32, 0x10,
1877 ];
1878
1879 #[test]
1880 fn parses_tiny_fixture_sps() {
1881 let sps = SeqParameterSet::parse(TINY_SPS_RBSP).expect("SPS parse");
1882 // Trace cross-check (docs/video/h265/fixtures/tiny-i-only-16x16-main/trace.txt):
1883 // SPS sps_id=0 vps_id=0 max_sub_layers=1 profile_idc=4 level_idc=30
1884 // chroma_format_idc=1 bit_depth=8 bit_depth_chroma=8
1885 // width=16 height=16 log2_ctb_size=4 log2_min_cb_size=3
1886 // log2_min_tb_size=2 sao_enabled=1 amp_enabled=0 pcm_enabled=0
1887 // scaling_list_enabled=0 long_term_ref_pics=0 temporal_mvp=1
1888 // strong_intra_smoothing=1
1889 assert_eq!(sps.vps_id, 0);
1890 assert_eq!(sps.max_sub_layers_minus1, 0); // max_sub_layers = 1
1891 assert!(sps.temporal_id_nesting_flag);
1892 assert_eq!(sps.ptl.general_profile_idc, 4);
1893 assert_eq!(sps.ptl.general_level_idc, 30);
1894 assert_eq!(sps.sps_id, 0);
1895 assert_eq!(sps.chroma_format_idc, 1);
1896 assert!(!sps.separate_colour_plane_flag);
1897 assert_eq!(sps.pic_width_in_luma_samples, 16);
1898 assert_eq!(sps.pic_height_in_luma_samples, 16);
1899 assert!(!sps.conformance_window_flag);
1900 assert_eq!(sps.conformance_window, ConformanceWindow::default());
1901 assert_eq!(sps.bit_depth_luma_minus8, 0);
1902 assert_eq!(sps.bit_depth_chroma_minus8, 0);
1903 assert_eq!(sps.bit_depth_luma(), 8);
1904 assert_eq!(sps.bit_depth_chroma(), 8);
1905 assert_eq!(sps.log2_max_pic_order_cnt_lsb_minus4, 4); // MaxPicOrderCntLsb = 256
1906 assert_eq!(sps.max_pic_order_cnt_lsb(), 256);
1907 assert!(sps.sub_layer_ordering_info_present_flag);
1908 assert_eq!(
1909 sps.sub_layer_ordering_info[0].max_dec_pic_buffering_minus1,
1910 2
1911 );
1912 assert_eq!(sps.sub_layer_ordering_info[0].max_num_reorder_pics, 0);
1913 assert_eq!(sps.sub_layer_ordering_info[0].max_latency_increase_plus1, 1);
1914 assert_eq!(sps.log2_min_cb_size(), 3); // 8x8 minimum CU
1915 assert_eq!(sps.log2_ctb_size(), 4); // 16x16 CTU
1916 assert_eq!(sps.log2_min_tb_size(), 2); // 4x4 minimum TU
1917 assert_eq!(sps.max_transform_hierarchy_depth_inter, 0);
1918 assert_eq!(sps.max_transform_hierarchy_depth_intra, 0);
1919 assert!(!sps.scaling_list_enabled_flag);
1920 assert!(!sps.amp_enabled_flag);
1921 assert!(sps.sample_adaptive_offset_enabled_flag);
1922 // Round-4 tail.
1923 assert!(!sps.pcm_enabled_flag);
1924 assert!(sps.pcm.is_none());
1925 assert_eq!(sps.num_short_term_ref_pic_sets, 0);
1926 assert!(sps.short_term_ref_pic_sets.is_empty());
1927 assert!(!sps.long_term_ref_pics_present_flag);
1928 assert_eq!(sps.num_long_term_ref_pics_sps, 0);
1929 assert!(sps.long_term_ref_pics.is_empty());
1930 assert!(sps.sps_temporal_mvp_enabled_flag);
1931 assert!(sps.strong_intra_smoothing_enabled_flag);
1932 // The fixture's x265 CLI encode signals a §E.2.1 VUI body. It
1933 // decodes to a square (1:1) sample aspect ratio, an
1934 // unspecified-but-present video_signal_type, and a timing-info
1935 // block of vui_num_units_in_tick = 1 / vui_time_scale = 25
1936 // (25 fps) with neither HRD nor bitstream restriction. After
1937 // the VUI the SPS ends cleanly: sps_extension_present_flag == 0
1938 // and only the rbsp_trailing_bits() remain, so no opaque tail
1939 // is captured.
1940 assert!(sps.vui_parameters_present_flag);
1941 let vui = sps.vui_parameters.as_ref().expect("VUI body");
1942 assert!(vui.aspect_ratio_info_present_flag);
1943 assert_eq!(vui.aspect_ratio_idc, 1); // 1:1 square
1944 assert!(vui.sar_width.is_none());
1945 assert!(!vui.overscan_info_present_flag);
1946 assert!(vui.video_signal_type_present_flag);
1947 let vst = vui.video_signal_type.as_ref().expect("video signal type");
1948 assert_eq!(vst.video_format, 5); // unspecified
1949 assert!(!vst.video_full_range_flag);
1950 assert!(vst.colour_description.is_none());
1951 assert!(!vui.chroma_loc_info_present_flag);
1952 assert!(!vui.neutral_chroma_indication_flag);
1953 assert!(!vui.field_seq_flag);
1954 assert!(!vui.frame_field_info_present_flag);
1955 assert!(!vui.default_display_window_flag);
1956 assert!(vui.vui_timing_info_present_flag);
1957 let ti = vui.timing_info.as_ref().expect("timing info");
1958 assert_eq!(ti.num_units_in_tick, 1);
1959 assert_eq!(ti.time_scale, 25);
1960 assert!(!ti.poc_proportional_to_timing_flag);
1961 assert!(!ti.hrd_parameters_present_flag);
1962 assert!(!vui.bitstream_restriction_flag);
1963 // No extension, no opaque tail.
1964 assert!(!sps.sps_extension_present_flag);
1965 assert!(sps.opaque_tail.is_none());
1966 }
1967
1968 /// End-to-end: pull the SPS NAL out of the raw Annex B stream
1969 /// through the walker, then parse it. The fixture's full byte
1970 /// sequence is captured inline so the test does not depend on the
1971 /// `docs/` tree at run time.
1972 #[test]
1973 fn parses_tiny_fixture_sps_via_nal_walker() {
1974 // VPS NAL + SPS NAL only — the rest of the fixture (PPS / SEI
1975 // / slice) is unrelated to this test.
1976 let raw = &[
1977 // VPS: 00 00 00 01 40 01 ...
1978 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0C, 0x01, 0xFF, 0xFF, 0x04, 0x08, 0x00, 0x00,
1979 0x03, 0x00, 0x9F, 0xA8, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1E, 0xBA, 0x02, 0x40,
1980 // SPS: 00 00 00 01 42 01 ...
1981 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9F,
1982 0xA8, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1E, 0xA0, 0x88, 0x45, 0x96, 0xEA, 0xAF, 0x2B,
1983 0xC0, 0x5A, 0x02, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x32, 0x10,
1984 ];
1985 let units = collect_nal_units(raw).expect("walker");
1986 assert_eq!(units.len(), 2);
1987 assert_eq!(units[1].header.nal_unit_type, 33); // SPS_NUT
1988 let sps = SeqParameterSet::parse(&units[1].rbsp).expect("SPS parse");
1989 assert_eq!(sps.sps_id, 0);
1990 assert_eq!(sps.chroma_format_idc, 1);
1991 assert_eq!(sps.pic_width_in_luma_samples, 16);
1992 assert_eq!(sps.pic_height_in_luma_samples, 16);
1993 assert_eq!(sps.log2_ctb_size(), 4);
1994 assert!(sps.sample_adaptive_offset_enabled_flag);
1995 assert!(!sps.pcm_enabled_flag);
1996 assert_eq!(sps.num_short_term_ref_pic_sets, 0);
1997 assert!(!sps.long_term_ref_pics_present_flag);
1998 assert!(sps.sps_temporal_mvp_enabled_flag);
1999 assert!(sps.strong_intra_smoothing_enabled_flag);
2000 assert!(sps.vui_parameters_present_flag);
2001 }
2002
2003 #[test]
2004 fn strip_emulation_prevention_then_parse_matches_inline_decode() {
2005 // The wire SPS body (post NAL header, with the 3 emulation-
2006 // prevention escapes left in) must, after a §7.4.1.1 strip,
2007 // match TINY_SPS_RBSP exactly.
2008 let wire = &[
2009 0x01, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9F, 0xA8, 0x00, 0x00, 0x03, 0x00, 0x00,
2010 0x1E, 0xA0, 0x88, 0x45, 0x96, 0xEA, 0xAF, 0x2B, 0xC0, 0x5A, 0x02, 0x00, 0x00, 0x03,
2011 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x32, 0x10,
2012 ];
2013 let unesc = strip_emulation_prevention(wire);
2014 assert_eq!(unesc, TINY_SPS_RBSP);
2015 let a = SeqParameterSet::parse(&unesc).expect("SPS parse");
2016 let b = SeqParameterSet::parse(TINY_SPS_RBSP).expect("SPS parse");
2017 assert_eq!(a, b);
2018 }
2019
2020 #[test]
2021 fn rejects_truncated_rbsp() {
2022 // Cut the buffer just past the leading `vps_id / max_sub /
2023 // nesting` byte — well before profile_tier_level finishes.
2024 let err = SeqParameterSet::parse(&TINY_SPS_RBSP[..3]).unwrap_err();
2025 assert_eq!(err, SpsError::Truncated);
2026 }
2027
2028 /// Hand-assembled SPS exercising the `chroma_format_idc == 3`
2029 /// path (so `separate_colour_plane_flag` is signalled) plus the
2030 /// `conformance_window_flag == 1` four-`ue(v)` block. The remaining
2031 /// fields are kept at minimal values to make the bit string
2032 /// hand-traceable.
2033 #[test]
2034 fn parses_444_with_conformance_window() {
2035 let mut s = String::new();
2036 s += "0000"; // vps_id
2037 s += "000"; // max_sub_layers_minus1
2038 s += "1"; // nesting
2039 // PTL(1,0)
2040 s += "00";
2041 s += "0";
2042 s += "00001";
2043 for _ in 0..32 {
2044 s += "0";
2045 }
2046 s += "0000";
2047 for _ in 0..43 {
2048 s += "0";
2049 }
2050 s += "0";
2051 s += "00011110";
2052 // sps_id=0
2053 s += "1";
2054 // chroma_format_idc=3 → '00100'
2055 s += "00100";
2056 // separate_colour_plane_flag=1
2057 s += "1";
2058 // width=16, height=16
2059 s += "000010001";
2060 s += "000010001";
2061 // conf_win=1, four ue(v)=0
2062 s += "1";
2063 s += "1";
2064 s += "1";
2065 s += "1";
2066 s += "1";
2067 // bd_luma=2 → '011'
2068 s += "011";
2069 s += "011";
2070 // log2_max_poc_lsb_minus4=4
2071 s += "00101";
2072 // ordering present, single triple of zeros
2073 s += "1";
2074 s += "1";
2075 s += "1";
2076 s += "1";
2077 // log2_min_cb=0
2078 s += "1";
2079 // log2_diff=1
2080 s += "010";
2081 s += "1";
2082 s += "011";
2083 s += "1";
2084 s += "1";
2085 // scaling=0
2086 s += "0";
2087 // amp=1
2088 s += "1";
2089 // sao=1
2090 s += "1";
2091 // pcm_enabled=0
2092 s += "0";
2093 // num_short_term_ref_pic_sets=0
2094 s += "1";
2095 // long_term_ref_pics_present=0
2096 s += "0";
2097 // temporal_mvp=1
2098 s += "1";
2099 // strong_intra_smoothing=0
2100 s += "0";
2101 // vui=0
2102 s += "0";
2103 // sps_extension_present=0
2104 s += "0";
2105 // rbsp trailing bits (stop bit then zero pad)
2106 s += "1";
2107
2108 let bytes = bits_to_bytes(&s);
2109 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2110 assert_eq!(sps.chroma_format_idc, 3);
2111 assert!(sps.separate_colour_plane_flag);
2112 assert_eq!(sps.bit_depth_luma(), 10);
2113 assert!(sps.amp_enabled_flag);
2114 assert!(sps.sample_adaptive_offset_enabled_flag);
2115 assert!(!sps.pcm_enabled_flag);
2116 assert_eq!(sps.num_short_term_ref_pic_sets, 0);
2117 assert!(!sps.long_term_ref_pics_present_flag);
2118 assert!(sps.sps_temporal_mvp_enabled_flag);
2119 assert!(!sps.strong_intra_smoothing_enabled_flag);
2120 assert!(!sps.vui_parameters_present_flag);
2121 assert!(!sps.sps_extension_present_flag);
2122 assert!(sps.opaque_tail.is_none());
2123 }
2124
2125 /// Hand-assembled SPS with two sub-layers and the ordering-info
2126 /// present flag set to 0 — the [0] sub-layer must inherit the
2127 /// [1] triple per §7.4.3.2.1.
2128 #[test]
2129 fn ordering_info_present_flag_zero_propagates() {
2130 let mut s = String::new();
2131 s += "0000"; // vps_id
2132 s += "001"; // max_sub_layers_minus1 = 1
2133 s += "1";
2134 // PTL(1, 1):
2135 s += "00";
2136 s += "0";
2137 s += "00001";
2138 for _ in 0..32 {
2139 s += "0";
2140 }
2141 s += "0000";
2142 for _ in 0..43 {
2143 s += "0";
2144 }
2145 s += "0";
2146 s += "00011110"; // level=30
2147 // sub_layer_profile_present[0]/level_present[0] = 0,0
2148 s += "00";
2149 for _ in 0..14 {
2150 s += "0";
2151 }
2152 // sps_id=0
2153 s += "1";
2154 // chroma=1
2155 s += "010";
2156 // width=16, height=16
2157 s += "000010001";
2158 s += "000010001";
2159 // conf_win=0
2160 s += "0";
2161 // bd_luma=0
2162 s += "1";
2163 // bd_chroma=0
2164 s += "1";
2165 // log2_max_poc_lsb_minus4=4
2166 s += "00101";
2167 // sub_layer_ordering_info_present_flag = 0
2168 s += "0";
2169 // single triple at i=max_sub_layers_minus1 (=1): dpb=2 ('011'), reorder=0 ('1'), latency=0 ('1')
2170 s += "011";
2171 s += "1";
2172 s += "1";
2173 s += "1";
2174 s += "010";
2175 s += "1";
2176 s += "011";
2177 s += "1";
2178 s += "1";
2179 s += "0";
2180 s += "0";
2181 s += "1";
2182 // pcm_enabled=0
2183 s += "0";
2184 // num_short_term_ref_pic_sets=0
2185 s += "1";
2186 // long_term=0
2187 s += "0";
2188 // temporal_mvp=1
2189 s += "1";
2190 // strong_intra_smoothing=1
2191 s += "1";
2192 // vui=0
2193 s += "0";
2194 // sps_extension_present=0
2195 s += "0";
2196 // stop bit
2197 s += "1";
2198
2199 let bytes = bits_to_bytes(&s);
2200 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2201 assert!(!sps.sub_layer_ordering_info_present_flag);
2202 assert_eq!(sps.max_sub_layers_minus1, 1);
2203 assert_eq!(
2204 sps.sub_layer_ordering_info[1].max_dec_pic_buffering_minus1,
2205 2
2206 );
2207 // [0] inherited from [1]
2208 assert_eq!(
2209 sps.sub_layer_ordering_info[0].max_dec_pic_buffering_minus1,
2210 2
2211 );
2212 assert!(sps.strong_intra_smoothing_enabled_flag);
2213 }
2214
2215 /// SPS prefix bits identical to `synthesised_prefix_bits()` but
2216 /// stopping just *before* `scaling_list_enabled_flag` (i.e. after
2217 /// `max_transform_hierarchy_depth_intra`). Round 8 scaling-list
2218 /// tests append their own scaling_list block + the amp/sao bits +
2219 /// the SPS tail.
2220 fn synthesised_prefix_before_scaling_list() -> String {
2221 let mut s = String::new();
2222 s += "0000"; // vps_id
2223 s += "000"; // max_sub_layers_minus1
2224 s += "1"; // nesting flag
2225 s += "00"; // profile_space
2226 s += "0"; // tier
2227 s += "00001"; // profile_idc
2228 for _ in 0..32 {
2229 s += "0";
2230 }
2231 s += "0000";
2232 for _ in 0..43 {
2233 s += "0";
2234 }
2235 s += "0";
2236 s += "00011110"; // level=30
2237 s += "1"; // sps_id=0
2238 s += "010"; // chroma_format_idc=1
2239 s += "000010001"; // width=16
2240 s += "000010001"; // height=16
2241 s += "0"; // conf_win=0
2242 s += "1"; // bd_luma=0
2243 s += "1"; // bd_chroma=0
2244 s += "00101"; // log2_max_poc_lsb_minus4=4
2245 s += "1"; // ordering present=1
2246 s += "1"; // dpb=0
2247 s += "1"; // reorder=0
2248 s += "1"; // latency=0
2249 s += "1"; // log2_min_cb_minus3=0
2250 s += "010"; // log2_diff=1
2251 s += "1"; // log2_min_tb_minus2=0
2252 s += "011"; // log2_diff_tb=2
2253 s += "1"; // max_transform_depth_inter=0
2254 s += "1"; // max_transform_depth_intra=0
2255 s
2256 }
2257
2258 /// SPS tail bits from `amp_enabled` through the stop bit, matching
2259 /// the round-4 minimal tail (sao=1, pcm=0, num_short_term_rps=0,
2260 /// long_term=0, temporal_mvp=1, strong_intra_smoothing=1, vui=0,
2261 /// extension=0, stop=1).
2262 fn synthesised_tail_after_scaling_list() -> String {
2263 let mut s = String::new();
2264 s += "0"; // amp_enabled=0
2265 s += "1"; // sao_enabled=1
2266 s += "0"; // pcm_enabled=0
2267 s += "1"; // num_short_term_ref_pic_sets ue=0
2268 s += "0"; // long_term_ref_pics=0
2269 s += "1"; // temporal_mvp=1
2270 s += "1"; // strong_intra_smoothing=1
2271 s += "0"; // vui=0
2272 s += "0"; // sps_extension_present=0
2273 s += "1"; // stop bit
2274 s
2275 }
2276
2277 /// `scaling_list_enabled_flag == 1` with
2278 /// `sps_scaling_list_data_present_flag == 0`: per §7.4.5 the
2279 /// default scaling lists apply and the SPS parses end to end with
2280 /// no explicit [`ScalingListData`].
2281 #[test]
2282 fn scaling_list_enabled_default_lists() {
2283 let mut s = synthesised_prefix_before_scaling_list();
2284 s += "1"; // scaling_list_enabled = 1
2285 s += "0"; // sps_scaling_list_data_present_flag = 0
2286 s += &synthesised_tail_after_scaling_list();
2287 let bytes = bits_to_bytes(&s);
2288 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2289 assert!(sps.scaling_list_enabled_flag);
2290 assert!(!sps.sps_scaling_list_data_present_flag);
2291 assert!(sps.scaling_list_data.is_none());
2292 }
2293
2294 /// `scaling_list_enabled_flag == 1` with
2295 /// `sps_scaling_list_data_present_flag == 1`: the SPS carries an
2296 /// explicit `scaling_list_data()` (§7.3.4). Here every one of the
2297 /// 24 slots signals pred_mode=0, delta=0 (use the default list), so
2298 /// the parsed lists equal the §7.4.5 default tables.
2299 #[test]
2300 fn scaling_list_enabled_explicit_all_default() {
2301 let mut s = synthesised_prefix_before_scaling_list();
2302 s += "1"; // scaling_list_enabled = 1
2303 s += "1"; // sps_scaling_list_data_present_flag = 1
2304 // scaling_list_data(): 24 slots, each pred_mode=0
2305 // ('0') + scaling_list_pred_matrix_id_delta ue=0 ('1').
2306 // sizeId 0/1/2 each 6 slots; sizeId 3 only 2 slots.
2307 for size_id in 0..4 {
2308 let step = if size_id == 3 { 3 } else { 1 };
2309 let mut m = 0;
2310 while m < 6 {
2311 s += "0"; // scaling_list_pred_mode_flag = 0
2312 s += "1"; // scaling_list_pred_matrix_id_delta ue = 0
2313 m += step;
2314 }
2315 }
2316 s += &synthesised_tail_after_scaling_list();
2317 let bytes = bits_to_bytes(&s);
2318 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2319 assert!(sps.scaling_list_enabled_flag);
2320 assert!(sps.sps_scaling_list_data_present_flag);
2321 let data = sps.scaling_list_data.expect("scaling_list_data present");
2322 // 4x4 default = all 16.
2323 assert_eq!(data.lists[0][0].coef, vec![16u16; 16]);
2324 // 8x8 inter default for matrixId 3.
2325 assert_eq!(data.lists[1][3].coef[63], 91);
2326 }
2327
2328 /// `chroma_format_idc` is u-Exp-Golomb so on-wire codeNum 4 would
2329 /// decode to a value of 4 — outside the legal 0..=3 range. The
2330 /// parser must reject it.
2331 #[test]
2332 fn rejects_chroma_format_idc_out_of_range() {
2333 let mut s = String::new();
2334 s += "0000";
2335 s += "000";
2336 s += "1";
2337 s += "00";
2338 s += "0";
2339 s += "00001";
2340 for _ in 0..32 {
2341 s += "0";
2342 }
2343 s += "0000";
2344 for _ in 0..43 {
2345 s += "0";
2346 }
2347 s += "0";
2348 s += "00011110";
2349 // sps_id=0
2350 s += "1";
2351 // chroma_format_idc ue=4 → '00101'
2352 s += "00101";
2353 let bytes = bits_to_bytes(&s);
2354 let err = SeqParameterSet::parse(&bytes).unwrap_err();
2355 assert_eq!(
2356 err,
2357 SpsError::ValueOutOfRange {
2358 field: "chroma_format_idc",
2359 got: 4
2360 }
2361 );
2362 }
2363
2364 /// Fuzz regression (r282 `parse_annexb`): an SPS whose
2365 /// coding-block-size pair drives `CtbLog2SizeY` (§7.4.3.2.1
2366 /// eqs. 7-10 / 7-11) past the Annex A profile bound of 4..=6
2367 /// previously survived the parse and panicked downstream in every
2368 /// `CtbSizeY = 1 << CtbLog2SizeY` (eq. 7-13) re-derivation.
2369 #[test]
2370 fn rejects_ctb_log2_size_above_6() {
2371 let mut s = synthesised_header_through_ordering("000010001", "000010001");
2372 // log2_min_luma_coding_block_size_minus3 = 0 → MinCbLog2SizeY = 3
2373 s += "1";
2374 // log2_diff_max_min_luma_coding_block_size ue = 4 → CtbLog2SizeY = 7
2375 s += "00101";
2376 let bytes = bits_to_bytes(&s);
2377 let err = SeqParameterSet::parse(&bytes).unwrap_err();
2378 assert_eq!(
2379 err,
2380 SpsError::ValueOutOfRange {
2381 field: "CtbLog2SizeY",
2382 got: 7
2383 }
2384 );
2385 }
2386
2387 /// Annex A also bounds `CtbLog2SizeY` from below (4): a
2388 /// 8×8-CTB SPS must be rejected.
2389 #[test]
2390 fn rejects_ctb_log2_size_below_4() {
2391 let mut s = synthesised_header_through_ordering("000010001", "000010001");
2392 // log2_min_cb_minus3 = 0, log2_diff = 0 → CtbLog2SizeY = 3
2393 s += "1";
2394 s += "1";
2395 let bytes = bits_to_bytes(&s);
2396 let err = SeqParameterSet::parse(&bytes).unwrap_err();
2397 assert_eq!(
2398 err,
2399 SpsError::ValueOutOfRange {
2400 field: "CtbLog2SizeY",
2401 got: 3
2402 }
2403 );
2404 }
2405
2406 /// §7.4.3.2.1: "The CVS shall not contain data that result in
2407 /// MinTbLog2SizeY greater than or equal to MinCbLog2SizeY".
2408 #[test]
2409 fn rejects_min_tb_log2_size_reaching_min_cb() {
2410 let mut s = synthesised_header_through_ordering("000010001", "000010001");
2411 s += "1"; // MinCbLog2SizeY = 3
2412 s += "010"; // CtbLog2SizeY = 4
2413 s += "010"; // log2_min_tb_minus2 = 1 → MinTbLog2SizeY = 3 == MinCb
2414 let bytes = bits_to_bytes(&s);
2415 let err = SeqParameterSet::parse(&bytes).unwrap_err();
2416 assert_eq!(
2417 err,
2418 SpsError::ValueOutOfRange {
2419 field: "log2_min_luma_transform_block_size_minus2",
2420 got: 1
2421 }
2422 );
2423 }
2424
2425 /// §7.4.3.2.1: "The CVS shall not contain data that result in
2426 /// MaxTbLog2SizeY greater than Min( CtbLog2SizeY, 5 )".
2427 #[test]
2428 fn rejects_max_tb_log2_size_above_cap() {
2429 let mut s = synthesised_header_through_ordering("000010001", "000010001");
2430 s += "1"; // MinCbLog2SizeY = 3
2431 s += "010"; // CtbLog2SizeY = 4 → cap = Min(4, 5) = 4
2432 s += "1"; // MinTbLog2SizeY = 2
2433 s += "00100"; // log2_diff_tb = 3 → MaxTbLog2SizeY = 5 > 4
2434 let bytes = bits_to_bytes(&s);
2435 let err = SeqParameterSet::parse(&bytes).unwrap_err();
2436 assert_eq!(
2437 err,
2438 SpsError::ValueOutOfRange {
2439 field: "log2_diff_max_min_luma_transform_block_size",
2440 got: 3
2441 }
2442 );
2443 }
2444
2445 /// §7.4.3.2.1: both hierarchy depths "shall be in the range of 0
2446 /// to CtbLog2SizeY − MinTbLog2SizeY, inclusive" (= 2 here).
2447 #[test]
2448 fn rejects_transform_hierarchy_depth_above_cap() {
2449 let mut s = synthesised_header_through_ordering("000010001", "000010001");
2450 s += "1"; // MinCbLog2SizeY = 3
2451 s += "010"; // CtbLog2SizeY = 4
2452 s += "1"; // MinTbLog2SizeY = 2
2453 s += "011"; // log2_diff_tb = 2 → MaxTbLog2SizeY = 4 (legal)
2454 s += "00100"; // max_transform_hierarchy_depth_inter = 3 > 2
2455 let bytes = bits_to_bytes(&s);
2456 let err = SeqParameterSet::parse(&bytes).unwrap_err();
2457 assert_eq!(
2458 err,
2459 SpsError::ValueOutOfRange {
2460 field: "max_transform_hierarchy_depth_inter",
2461 got: 3
2462 }
2463 );
2464 }
2465
2466 /// §A.4.1 item b): `pic_width_in_luma_samples` ≤
2467 /// `Sqrt( MaxLumaPs * 8 )` = 33 776 at the largest Table A.8
2468 /// level. An unbounded width previously let the
2469 /// PicWidthInCtbsY × PicHeightInCtbsY product (eq. 7-19 territory)
2470 /// overflow u32 downstream.
2471 #[test]
2472 fn rejects_oversized_pic_width() {
2473 // ue(33 777): codeNum + 1 = 33 778 needs 16 bits → 15-zero
2474 // prefix followed by the 16-bit value.
2475 let code: u32 = 33_778;
2476 let len = 32 - code.leading_zeros();
2477 let mut ue = "0".repeat(len as usize - 1);
2478 for i in (0..len).rev() {
2479 ue.push(if (code >> i) & 1 == 1 { '1' } else { '0' });
2480 }
2481 let s = synthesised_header_through_ordering(&ue, "000010001");
2482 let bytes = bits_to_bytes(&s);
2483 let err = SeqParameterSet::parse(&bytes).unwrap_err();
2484 assert_eq!(
2485 err,
2486 SpsError::ValueOutOfRange {
2487 field: "pic_width_in_luma_samples",
2488 got: 33_777
2489 }
2490 );
2491 }
2492
2493 /// Hand-assembled SPS exercising the `pcm_enabled_flag == 1`
2494 /// branch (§7.3.2.2 PCM block).
2495 #[test]
2496 fn parses_pcm_enabled() {
2497 let mut s = synthesised_prefix_bits();
2498 // pcm_enabled_flag = 1
2499 s += "1";
2500 // pcm_sample_bit_depth_luma_minus1 = 7 (PcmBitDepthY = 8)
2501 s += "0111";
2502 // pcm_sample_bit_depth_chroma_minus1 = 7
2503 s += "0111";
2504 // log2_min_pcm_luma_coding_block_size_minus3 = 0 → '1'
2505 s += "1";
2506 // log2_diff_max_min_pcm_luma_coding_block_size = 0 → '1'
2507 s += "1";
2508 // pcm_loop_filter_disabled_flag = 1
2509 s += "1";
2510 // num_short_term_ref_pic_sets = 0
2511 s += "1";
2512 // long_term_ref_pics_present_flag = 0
2513 s += "0";
2514 // sps_temporal_mvp_enabled_flag = 1
2515 s += "1";
2516 // strong_intra_smoothing_enabled_flag = 1
2517 s += "1";
2518 // vui_parameters_present_flag = 0
2519 s += "0";
2520 // sps_extension_present_flag = 0
2521 s += "0";
2522 // stop bit
2523 s += "1";
2524 let bytes = bits_to_bytes(&s);
2525 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2526 assert!(sps.pcm_enabled_flag);
2527 let pcm = sps.pcm.expect("pcm block");
2528 assert_eq!(pcm.bit_depth_luma_minus1, 7);
2529 assert_eq!(pcm.bit_depth_chroma_minus1, 7);
2530 assert_eq!(pcm.log2_min_pcm_luma_coding_block_size_minus3, 0);
2531 assert_eq!(pcm.log2_diff_max_min_pcm_luma_coding_block_size, 0);
2532 assert!(pcm.loop_filter_disabled_flag);
2533 assert!(sps.sps_temporal_mvp_enabled_flag);
2534 assert!(sps.strong_intra_smoothing_enabled_flag);
2535 }
2536
2537 /// PCM bit depth above luma bit depth must be rejected per
2538 /// §7.4.3.2 / equation (7-25).
2539 #[test]
2540 fn rejects_pcm_bit_depth_exceeding_luma() {
2541 let mut s = synthesised_prefix_bits();
2542 s += "1"; // pcm_enabled_flag
2543 // pcm_sample_bit_depth_luma_minus1 = 15 (PcmBitDepthY = 16)
2544 // BitDepthY in the synthesised prefix is 8.
2545 s += "1111";
2546 let bytes = bits_to_bytes(&s);
2547 let err = SeqParameterSet::parse(&bytes).unwrap_err();
2548 assert_eq!(
2549 err,
2550 SpsError::ValueOutOfRange {
2551 field: "pcm_sample_bit_depth_luma_minus1",
2552 got: 15
2553 }
2554 );
2555 }
2556
2557 /// Hand-assembled SPS with one explicit short-term RPS (no
2558 /// inter-RPS-prediction): 1 negative pic, 0 positive pics.
2559 #[test]
2560 fn parses_one_short_term_rps_explicit() {
2561 let mut s = synthesised_prefix_bits();
2562 // pcm_enabled_flag = 0
2563 s += "0";
2564 // num_short_term_ref_pic_sets = 1 → ue(v) codeNum 1 → '010'
2565 s += "010";
2566 // st_ref_pic_set(0): inter_ref_pic_set_prediction_flag is NOT
2567 // signalled (st_rps_idx == 0); implicit 0.
2568 // num_negative_pics = 1 → '010'
2569 s += "010";
2570 // num_positive_pics = 0 → '1'
2571 s += "1";
2572 // delta_poc_s0_minus1[0] = 0 → '1'
2573 s += "1";
2574 // used_by_curr_pic_s0_flag[0] = 1
2575 s += "1";
2576 // long_term_ref_pics_present_flag = 0
2577 s += "0";
2578 // sps_temporal_mvp_enabled_flag = 1
2579 s += "1";
2580 // strong_intra_smoothing_enabled_flag = 0
2581 s += "0";
2582 // vui_parameters_present_flag = 0
2583 s += "0";
2584 // sps_extension_present_flag = 0
2585 s += "0";
2586 s += "1"; // stop bit
2587
2588 let bytes = bits_to_bytes(&s);
2589 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2590 assert_eq!(sps.num_short_term_ref_pic_sets, 1);
2591 let rps = &sps.short_term_ref_pic_sets[0];
2592 assert!(!rps.inter_ref_pic_set_prediction_flag);
2593 assert_eq!(rps.num_negative_pics, 1);
2594 assert_eq!(rps.num_positive_pics, 0);
2595 assert_eq!(rps.delta_poc_s0_minus1, vec![0]);
2596 assert_eq!(rps.used_by_curr_pic_s0_flag, vec![true]);
2597 assert!(rps.delta_poc_s1_minus1.is_empty());
2598 assert_eq!(rps.num_delta_pocs(), 1);
2599 }
2600
2601 /// Hand-assembled SPS with two short-term RPSes, the second one
2602 /// using inter-RPS-prediction relative to the first. Exercises
2603 /// the `for( j = 0; j <= NumDeltaPocs[RefRpsIdx]; j++ )` loop in
2604 /// §7.3.7 with the `use_delta_flag` inference of §7.4.8.
2605 #[test]
2606 fn parses_inter_rps_prediction() {
2607 let mut s = synthesised_prefix_bits();
2608 s += "0"; // pcm_enabled_flag
2609 // num_short_term_ref_pic_sets = 2 → codeNum 2 → '011'
2610 s += "011";
2611 // st_ref_pic_set(0): explicit, num_negative_pics=1, num_positive=0,
2612 // delta_poc_s0_minus1[0]=0, used_by_curr_pic_s0_flag[0]=1
2613 s += "010"; // num_neg=1
2614 s += "1"; // num_pos=0
2615 s += "1"; // dp0=0
2616 s += "1"; // used=1
2617 // st_ref_pic_set(1): inter_ref_pic_set_prediction_flag = 1
2618 s += "1";
2619 // delta_idx_minus1 is NOT signalled (st_rps_idx=1, num=2; only
2620 // signalled when st_rps_idx == num). Inferred to 0 →
2621 // RefRpsIdx = 1 - (0+1) = 0.
2622 // delta_rps_sign = 0
2623 s += "0";
2624 // abs_delta_rps_minus1 = 0 → codeNum 0 → '1'
2625 s += "1";
2626 // NumDeltaPocs[0] = 1, so loop runs j=0..1 (2 entries):
2627 // j=0: used_by_curr_pic_flag = 1 → use_delta_flag inferred 1
2628 s += "1";
2629 // j=1: used_by_curr_pic_flag = 0, use_delta_flag = 1
2630 s += "0";
2631 s += "1";
2632 // long_term=0, temporal_mvp=1, strong_intra_smoothing=1, vui=0, ext=0, stop
2633 s += "0";
2634 s += "1";
2635 s += "1";
2636 s += "0";
2637 s += "0";
2638 s += "1";
2639
2640 let bytes = bits_to_bytes(&s);
2641 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2642 assert_eq!(sps.num_short_term_ref_pic_sets, 2);
2643 let r1 = &sps.short_term_ref_pic_sets[1];
2644 assert!(r1.inter_ref_pic_set_prediction_flag);
2645 assert_eq!(r1.delta_idx_minus1, 0);
2646 assert!(!r1.delta_rps_sign);
2647 assert_eq!(r1.abs_delta_rps_minus1, 0);
2648 assert_eq!(r1.used_by_curr_pic_flag, vec![true, false]);
2649 assert_eq!(r1.use_delta_flag, vec![true, true]);
2650 }
2651
2652 /// Hand-assembled SPS exercising the long-term-ref-pic block.
2653 #[test]
2654 fn parses_long_term_ref_pics() {
2655 let mut s = synthesised_prefix_bits();
2656 s += "0"; // pcm_enabled_flag = 0
2657 s += "1"; // num_short_term_ref_pic_sets = 0
2658 s += "1"; // long_term_ref_pics_present_flag = 1
2659 // num_long_term_ref_pics_sps = 2 → codeNum 2 → '011'
2660 s += "011";
2661 // log2_max_pic_order_cnt_lsb_minus4 = 4 in the synthesised
2662 // prefix, so lt_ref_pic_poc_lsb_sps[i] is 8 bits wide.
2663 // i=0: poc_lsb = 0x10, used_by_curr_pic_lt_sps_flag = 1
2664 s += "00010000";
2665 s += "1";
2666 // i=1: poc_lsb = 0x20, used = 0
2667 s += "00100000";
2668 s += "0";
2669 // temporal_mvp=1, strong_intra=0, vui=0, ext=0, stop
2670 s += "1";
2671 s += "0";
2672 s += "0";
2673 s += "0";
2674 s += "1";
2675
2676 let bytes = bits_to_bytes(&s);
2677 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2678 assert!(sps.long_term_ref_pics_present_flag);
2679 assert_eq!(sps.num_long_term_ref_pics_sps, 2);
2680 assert_eq!(sps.long_term_ref_pics.len(), 2);
2681 assert_eq!(sps.long_term_ref_pics[0].poc_lsb, 0x10);
2682 assert!(sps.long_term_ref_pics[0].used_by_curr_pic);
2683 assert_eq!(sps.long_term_ref_pics[1].poc_lsb, 0x20);
2684 assert!(!sps.long_term_ref_pics[1].used_by_curr_pic);
2685 }
2686
2687 /// SPS with `vui_parameters_present_flag == 1`: the parser now
2688 /// decodes the §E.2.1 `vui_parameters()` body in full and
2689 /// continues to `sps_extension_present_flag`. Here the VUI is the
2690 /// minimal all-flags-off body (ten `u(1)` flags), so no opaque
2691 /// tail is captured.
2692 #[test]
2693 fn decodes_vui_then_continues_to_extension_flag() {
2694 let mut s = synthesised_prefix_bits();
2695 s += "0"; // pcm_enabled_flag = 0
2696 s += "1"; // num_short_term_ref_pic_sets = 0
2697 s += "0"; // long_term = 0
2698 s += "1"; // temporal_mvp = 1
2699 s += "1"; // strong_intra_smoothing = 1
2700 s += "1"; // vui_parameters_present_flag = 1
2701 // vui_parameters(): ten flags all 0 (minimal body)
2702 s += "0000000000";
2703 s += "0"; // sps_extension_present_flag = 0
2704 s += "1"; // rbsp stop bit
2705
2706 let bytes = bits_to_bytes(&s);
2707 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2708 assert!(sps.vui_parameters_present_flag);
2709 let vui = sps.vui_parameters.as_ref().expect("VUI body");
2710 assert!(!vui.aspect_ratio_info_present_flag);
2711 assert!(!vui.overscan_info_present_flag);
2712 assert!(!vui.video_signal_type_present_flag);
2713 assert!(!vui.chroma_loc_info_present_flag);
2714 assert!(!vui.vui_timing_info_present_flag);
2715 assert!(!vui.bitstream_restriction_flag);
2716 assert!(!sps.sps_extension_present_flag);
2717 assert!(sps.opaque_tail.is_none());
2718 }
2719
2720 /// SPS with `vui_parameters_present_flag == 1` whose VUI signals a
2721 /// timing-info block, followed by `sps_extension_present_flag == 1`
2722 /// — the extension body after the (now fully decoded) VUI is
2723 /// surfaced as an opaque tail.
2724 #[test]
2725 fn decodes_vui_then_captures_extension_tail() {
2726 let mut s = synthesised_prefix_bits();
2727 s += "0"; // pcm_enabled_flag = 0
2728 s += "1"; // num_short_term_ref_pic_sets = 0
2729 s += "0"; // long_term = 0
2730 s += "1"; // temporal_mvp = 1
2731 s += "1"; // strong_intra_smoothing = 1
2732 s += "1"; // vui_parameters_present_flag = 1
2733 // vui_parameters():
2734 s += "0"; // aspect_ratio_info_present_flag = 0
2735 s += "0"; // overscan_info_present_flag = 0
2736 s += "0"; // video_signal_type_present_flag = 0
2737 s += "0"; // chroma_loc_info_present_flag = 0
2738 s += "0"; // neutral_chroma_indication_flag = 0
2739 s += "0"; // field_seq_flag = 0
2740 s += "0"; // frame_field_info_present_flag = 0
2741 s += "0"; // default_display_window_flag = 0
2742 s += "1"; // vui_timing_info_present_flag = 1
2743 s += "00000000000000000000000000000001"; // vui_num_units_in_tick = 1
2744 s += "00000000000000000000000000011001"; // vui_time_scale = 25
2745 s += "0"; // vui_poc_proportional_to_timing_flag = 0
2746 s += "0"; // vui_hrd_parameters_present_flag = 0
2747 s += "0"; // bitstream_restriction_flag = 0
2748 // sps_extension_present_flag = 1, followed by the
2749 // typed extension flag block. Set the range-extension
2750 // flag so the nine `sps_range_extension()` flags are
2751 // decoded; with no further body, no opaque tail follows.
2752 s += "1"; // sps_extension_present_flag
2753 s += "1"; // sps_range_extension_flag = 1
2754 s += "000"; // sps_multilayer / sps_3d / sps_scc = 0
2755 s += "0000"; // sps_extension_4bits = 0
2756 s += "101010101"; // sps_range_extension() nine flags
2757 s += "1"; // rbsp_trailing_bits stop bit
2758
2759 let bytes = bits_to_bytes(&s);
2760 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2761 let vui = sps.vui_parameters.as_ref().expect("VUI body");
2762 let ti = vui.timing_info.as_ref().expect("timing info");
2763 assert_eq!(ti.num_units_in_tick, 1);
2764 assert_eq!(ti.time_scale, 25);
2765 assert!(sps.sps_extension_present_flag);
2766 let flags = sps.extension_flags.expect("extension flag block");
2767 assert!(flags.sps_range_extension_flag);
2768 assert!(!flags.sps_multilayer_extension_flag);
2769 assert!(!flags.sps_3d_extension_flag);
2770 assert!(!flags.sps_scc_extension_flag);
2771 assert_eq!(flags.sps_extension_4bits, 0);
2772 assert!(flags.has_body());
2773 // The range-extension body is now decoded, not opaque: with no
2774 // multilayer/3d/scc/4bits body after it, no opaque tail remains.
2775 assert!(sps.opaque_tail.is_none());
2776 let re = sps.sps_range_extension.expect("range extension body");
2777 assert!(re.transform_skip_rotation_enabled_flag); // 1
2778 assert!(!re.transform_skip_context_enabled_flag); // 0
2779 assert!(re.implicit_rdpcm_enabled_flag); // 1
2780 assert!(!re.explicit_rdpcm_enabled_flag); // 0
2781 assert!(re.extended_precision_processing_flag); // 1
2782 assert!(!re.intra_smoothing_disabled_flag); // 0
2783 assert!(re.high_precision_offsets_enabled_flag); // 1
2784 assert!(!re.persistent_rice_adaptation_enabled_flag); // 0
2785 assert!(re.cabac_bypass_alignment_enabled_flag); // 1
2786 }
2787
2788 /// `sps_extension_present_flag == 1` with `sps_range_extension_flag
2789 /// == 1` decodes the typed flag block then the nine
2790 /// `sps_range_extension()` flags (§7.3.2.2.2) in bit-stream order.
2791 /// This is the RExt-profile entry point (§A.3.5).
2792 #[test]
2793 fn decodes_sps_range_extension_body() {
2794 let mut s = synthesised_prefix_bits();
2795 s += "0"; // pcm
2796 s += "1"; // num_short_term=0
2797 s += "0"; // long_term=0
2798 s += "1"; // temporal_mvp
2799 s += "1"; // strong_intra_smoothing
2800 s += "0"; // vui=0
2801 s += "1"; // sps_extension_present_flag = 1
2802 s += "1"; // sps_range_extension_flag = 1
2803 s += "000"; // sps_multilayer / sps_3d / sps_scc = 0
2804 s += "0000"; // sps_extension_4bits = 0
2805 // sps_range_extension() nine `u(1)` flags, all set —
2806 // every RExt tool enabled.
2807 s += "111111111";
2808 s += "1"; // rbsp_trailing_bits stop bit
2809 let bytes = bits_to_bytes(&s);
2810 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2811 assert!(!sps.vui_parameters_present_flag);
2812 assert!(sps.sps_extension_present_flag);
2813 let flags = sps.extension_flags.expect("extension flag block");
2814 assert!(flags.sps_range_extension_flag);
2815 assert_eq!(flags.sps_extension_4bits, 0);
2816 // No body after the range extension → no opaque tail.
2817 assert!(sps.opaque_tail.is_none());
2818 let re = sps.sps_range_extension.expect("range extension body");
2819 assert!(re.transform_skip_rotation_enabled_flag);
2820 assert!(re.transform_skip_context_enabled_flag);
2821 assert!(re.implicit_rdpcm_enabled_flag);
2822 assert!(re.explicit_rdpcm_enabled_flag);
2823 assert!(re.extended_precision_processing_flag);
2824 assert!(re.intra_smoothing_disabled_flag);
2825 assert!(re.high_precision_offsets_enabled_flag);
2826 assert!(re.persistent_rice_adaptation_enabled_flag);
2827 assert!(re.cabac_bypass_alignment_enabled_flag);
2828 }
2829
2830 /// `sps_range_extension_flag == 1` followed by `sps_scc_extension_flag
2831 /// == 1` (no multilayer/3D body between them): the nine
2832 /// range-extension flags AND the `sps_scc_extension()` body
2833 /// (§7.3.2.2.3) are both decoded in place — no opaque tail remains.
2834 /// Palette mode disabled, so only the three leading/trailing fields
2835 /// are present.
2836 #[test]
2837 fn decodes_range_extension_then_scc_body_no_palette() {
2838 let mut s = synthesised_prefix_bits();
2839 s += "0"; // pcm
2840 s += "1"; // num_short_term=0
2841 s += "0"; // long_term=0
2842 s += "1"; // temporal_mvp
2843 s += "1"; // strong_intra_smoothing
2844 s += "0"; // vui=0
2845 s += "1"; // sps_extension_present_flag = 1
2846 s += "1"; // sps_range_extension_flag = 1
2847 s += "00"; // sps_multilayer / sps_3d = 0
2848 s += "1"; // sps_scc_extension_flag = 1
2849 s += "0000"; // sps_extension_4bits = 0
2850 s += "000000000"; // sps_range_extension() nine flags, all 0
2851 // sps_scc_extension():
2852 s += "1"; // sps_curr_pic_ref_enabled_flag = 1
2853 s += "0"; // palette_mode_enabled_flag = 0 (palette block absent)
2854 s += "10"; // motion_vector_resolution_control_idc = 2 (u(2))
2855 s += "1"; // intra_boundary_filtering_disabled_flag = 1
2856 s += "1"; // rbsp_trailing_bits stop bit
2857 let bytes = bits_to_bytes(&s);
2858 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2859 let flags = sps.extension_flags.expect("extension flag block");
2860 assert!(flags.sps_range_extension_flag);
2861 assert!(flags.sps_scc_extension_flag);
2862 let re = sps.sps_range_extension.expect("range extension body");
2863 assert_eq!(re, SpsRangeExtension::default());
2864 // Both bodies are decoded in place; nothing is left opaque.
2865 assert!(sps.opaque_tail.is_none());
2866 let scc = sps.sps_scc_extension.expect("scc extension body");
2867 assert!(scc.sps_curr_pic_ref_enabled_flag);
2868 assert!(!scc.palette_mode_enabled_flag);
2869 assert_eq!(scc.palette_max_size, 0);
2870 assert_eq!(scc.delta_palette_max_predictor_size, 0);
2871 assert!(!scc.sps_palette_predictor_initializers_present_flag);
2872 assert!(scc.sps_palette_predictor_initializer.is_empty());
2873 assert_eq!(scc.motion_vector_resolution_control_idc, 2);
2874 assert!(scc.intra_boundary_filtering_disabled_flag);
2875 }
2876
2877 /// `sps_scc_extension()` with `palette_mode_enabled_flag == 1` and
2878 /// palette predictor initializers present: the `palette_max_size`,
2879 /// `delta_palette_max_predictor_size`, and per-component initializer
2880 /// table (§7.3.2.2.3) are decoded, each `u(v)` sized by `BitDepthY` /
2881 /// `BitDepthC`. `chroma_format_idc == 1` here gives `numComps == 3`.
2882 #[test]
2883 fn decodes_scc_extension_palette_initializers() {
2884 let mut s = synthesised_prefix_bits();
2885 s += "0"; // pcm
2886 s += "1"; // num_short_term=0
2887 s += "0"; // long_term=0
2888 s += "1"; // temporal_mvp
2889 s += "1"; // strong_intra_smoothing
2890 s += "0"; // vui=0
2891 s += "1"; // sps_extension_present_flag = 1
2892 s += "0001"; // range/multilayer/3d = 0, scc = 1
2893 s += "0000"; // sps_extension_4bits = 0
2894 // sps_scc_extension():
2895 s += "0"; // sps_curr_pic_ref_enabled_flag = 0
2896 s += "1"; // palette_mode_enabled_flag = 1
2897 s += "00100"; // palette_max_size = ue(3) → "00100"
2898 s += "010"; // delta_palette_max_predictor_size = ue(1) → "010"
2899 s += "1"; // sps_palette_predictor_initializers_present_flag = 1
2900 s += "010"; // sps_num_palette_predictor_initializers_minus1 = ue(1) → 1
2901 // numComps = 3, num_entries = 2; default BitDepthY/C = 8.
2902 // comp 0 (8-bit): 0x01, 0x02
2903 s += "00000001";
2904 s += "00000010";
2905 // comp 1 (8-bit): 0x03, 0x04
2906 s += "00000011";
2907 s += "00000100";
2908 // comp 2 (8-bit): 0x05, 0x06
2909 s += "00000101";
2910 s += "00000110";
2911 s += "10"; // motion_vector_resolution_control_idc = 2 (u(2))
2912 s += "0"; // intra_boundary_filtering_disabled_flag = 0
2913 s += "1"; // rbsp_trailing_bits stop bit
2914 let bytes = bits_to_bytes(&s);
2915 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
2916 assert!(sps.opaque_tail.is_none());
2917 let scc = sps.sps_scc_extension.expect("scc extension body");
2918 assert!(!scc.sps_curr_pic_ref_enabled_flag);
2919 assert!(scc.palette_mode_enabled_flag);
2920 assert_eq!(scc.palette_max_size, 3);
2921 assert_eq!(scc.delta_palette_max_predictor_size, 1);
2922 // PaletteMaxPredictorSize = 3 + 1 (eq. 7-35).
2923 assert_eq!(scc.palette_max_predictor_size(), 4);
2924 assert!(scc.sps_palette_predictor_initializers_present_flag);
2925 assert_eq!(scc.sps_num_palette_predictor_initializers_minus1, 1);
2926 assert_eq!(
2927 scc.sps_palette_predictor_initializer,
2928 vec![vec![1, 2], vec![3, 4], vec![5, 6]],
2929 );
2930 assert_eq!(scc.motion_vector_resolution_control_idc, 2);
2931 assert!(!scc.intra_boundary_filtering_disabled_flag);
2932 }
2933
2934 /// §7.4.3.2.3 / §A.3.7: `sps_num_palette_predictor_initializers_minus1`
2935 /// past the largest representable predictor (PaletteMaxPredictorSize
2936 /// <= 128) is rejected BEFORE any initializer allocation — a fuzzed
2937 /// count once drove a multi-GiB `Vec::with_capacity`.
2938 #[test]
2939 fn rejects_oversized_palette_predictor_initializer_count() {
2940 let mut s = synthesised_prefix_bits();
2941 s += "0"; // pcm
2942 s += "1"; // num_short_term=0
2943 s += "0"; // long_term=0
2944 s += "1"; // temporal_mvp
2945 s += "1"; // strong_intra_smoothing
2946 s += "0"; // vui=0
2947 s += "1"; // sps_extension_present_flag = 1
2948 s += "0001"; // range/multilayer/3d = 0, scc = 1
2949 s += "0000"; // sps_extension_4bits = 0
2950 // sps_scc_extension():
2951 s += "0"; // sps_curr_pic_ref_enabled_flag = 0
2952 s += "1"; // palette_mode_enabled_flag = 1
2953 s += "00100"; // palette_max_size = 3
2954 s += "010"; // delta_palette_max_predictor_size = 1
2955 s += "1"; // sps_palette_predictor_initializers_present_flag = 1
2956 // sps_num_palette_predictor_initializers_minus1 = ue(128):
2957 // 129 in 8 bits = 10000001, prefix of 7 zeros.
2958 s += "0000000";
2959 s += "10000001";
2960 s += "1"; // (whatever follows is unreached)
2961 let bytes = bits_to_bytes(&s);
2962 let err = SeqParameterSet::parse(&bytes).expect_err("oversized initializer count");
2963 assert!(matches!(
2964 err,
2965 SpsError::ValueOutOfRange {
2966 field: "sps_num_palette_predictor_initializers_minus1",
2967 got: 128
2968 }
2969 ));
2970 }
2971
2972 /// §7.4.3.2.3: `motion_vector_resolution_control_idc == 3` is
2973 /// reserved and rejected as out-of-range.
2974 #[test]
2975 fn rejects_reserved_motion_vector_resolution_control_idc() {
2976 let mut s = synthesised_prefix_bits();
2977 s += "0"; // pcm
2978 s += "1"; // num_short_term=0
2979 s += "0"; // long_term=0
2980 s += "1"; // temporal_mvp
2981 s += "1"; // strong_intra_smoothing
2982 s += "0"; // vui=0
2983 s += "1"; // sps_extension_present_flag = 1
2984 s += "0001"; // range/multilayer/3d = 0, scc = 1
2985 s += "0000"; // sps_extension_4bits = 0
2986 // sps_scc_extension():
2987 s += "0"; // sps_curr_pic_ref_enabled_flag = 0
2988 s += "0"; // palette_mode_enabled_flag = 0
2989 s += "11"; // motion_vector_resolution_control_idc = 3 (reserved)
2990 s += "0"; // intra_boundary_filtering_disabled_flag = 0
2991 s += "1"; // rbsp_trailing_bits stop bit
2992 let bytes = bits_to_bytes(&s);
2993 let err = SeqParameterSet::parse(&bytes).expect_err("reserved mvr idc");
2994 assert!(matches!(
2995 err,
2996 SpsError::ValueOutOfRange {
2997 field: "motion_vector_resolution_control_idc",
2998 got: 3
2999 }
3000 ));
3001 }
3002
3003 /// §7.4.3.2.3: when `palette_max_size == 0`, a non-zero
3004 /// `delta_palette_max_predictor_size` violates bitstream
3005 /// conformance and is rejected.
3006 #[test]
3007 fn rejects_delta_palette_when_max_size_zero() {
3008 let mut s = synthesised_prefix_bits();
3009 s += "0"; // pcm
3010 s += "1"; // num_short_term=0
3011 s += "0"; // long_term=0
3012 s += "1"; // temporal_mvp
3013 s += "1"; // strong_intra_smoothing
3014 s += "0"; // vui=0
3015 s += "1"; // sps_extension_present_flag = 1
3016 s += "0001"; // range/multilayer/3d = 0, scc = 1
3017 s += "0000"; // sps_extension_4bits = 0
3018 // sps_scc_extension():
3019 s += "0"; // sps_curr_pic_ref_enabled_flag = 0
3020 s += "1"; // palette_mode_enabled_flag = 1
3021 s += "1"; // palette_max_size = 0 (ue)
3022 s += "010"; // delta_palette_max_predictor_size = 1 (ue) → illegal
3023 s += "0"; // sps_palette_predictor_initializers_present_flag = 0
3024 s += "00"; // motion_vector_resolution_control_idc = 0
3025 s += "0"; // intra_boundary_filtering_disabled_flag = 0
3026 s += "1"; // rbsp_trailing_bits stop bit
3027 let bytes = bits_to_bytes(&s);
3028 let err = SeqParameterSet::parse(&bytes).expect_err("delta vs max_size 0");
3029 assert!(matches!(
3030 err,
3031 SpsError::ValueOutOfRange {
3032 field: "delta_palette_max_predictor_size",
3033 got: 1
3034 }
3035 ));
3036 }
3037
3038 /// `sps_extension_present_flag == 1` with every typed extension
3039 /// flag (and `sps_extension_4bits`) equal to 0 decodes the
3040 /// flag block but consumes only `rbsp_trailing_bits()` afterwards
3041 /// — no opaque tail is surfaced because no extension body follows.
3042 #[test]
3043 fn decodes_extension_flag_block_without_bodies() {
3044 let mut s = synthesised_prefix_bits();
3045 s += "0"; // pcm
3046 s += "1"; // num_short_term=0
3047 s += "0"; // long_term=0
3048 s += "1"; // temporal_mvp
3049 s += "1"; // strong_intra_smoothing
3050 s += "0"; // vui=0
3051 s += "1"; // sps_extension_present_flag = 1
3052 s += "0000"; // four typed flags all 0
3053 s += "0000"; // sps_extension_4bits = 0
3054 s += "1"; // rbsp_trailing_bits stop bit
3055 let bytes = bits_to_bytes(&s);
3056 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
3057 assert!(sps.sps_extension_present_flag);
3058 let flags = sps.extension_flags.expect("extension flag block");
3059 assert!(!flags.sps_range_extension_flag);
3060 assert!(!flags.sps_multilayer_extension_flag);
3061 assert!(!flags.sps_3d_extension_flag);
3062 assert!(!flags.sps_scc_extension_flag);
3063 assert_eq!(flags.sps_extension_4bits, 0);
3064 assert!(!flags.has_body());
3065 assert!(sps.opaque_tail.is_none());
3066 }
3067
3068 /// `sps_extension_present_flag == 1` with `sps_scc_extension_flag
3069 /// == 1` (and no preceding range/multilayer/3D body) selects the
3070 /// §A.3.7 Screen Content Coding profile family; the typed block
3071 /// decodes cleanly and the `sps_scc_extension()` body (§7.3.2.2.3)
3072 /// is decoded in place with no opaque tail. Palette mode disabled.
3073 #[test]
3074 fn decodes_scc_extension_body_no_range() {
3075 let mut s = synthesised_prefix_bits();
3076 s += "0"; // pcm
3077 s += "1"; // num_short_term=0
3078 s += "0"; // long_term=0
3079 s += "1"; // temporal_mvp
3080 s += "1"; // strong_intra_smoothing
3081 s += "0"; // vui=0
3082 s += "1"; // sps_extension_present_flag = 1
3083 s += "0"; // sps_range_extension_flag = 0
3084 s += "0"; // sps_multilayer_extension_flag = 0
3085 s += "0"; // sps_3d_extension_flag = 0
3086 s += "1"; // sps_scc_extension_flag = 1
3087 s += "0000"; // sps_extension_4bits = 0
3088 // sps_scc_extension():
3089 s += "1"; // sps_curr_pic_ref_enabled_flag = 1
3090 s += "0"; // palette_mode_enabled_flag = 0
3091 s += "01"; // motion_vector_resolution_control_idc = 1 (u(2))
3092 s += "0"; // intra_boundary_filtering_disabled_flag = 0
3093 s += "1"; // rbsp_trailing_bits stop bit
3094 let bytes = bits_to_bytes(&s);
3095 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
3096 let flags = sps.extension_flags.expect("extension flag block");
3097 assert!(!flags.sps_range_extension_flag);
3098 assert!(!flags.sps_multilayer_extension_flag);
3099 assert!(!flags.sps_3d_extension_flag);
3100 assert!(flags.sps_scc_extension_flag);
3101 assert_eq!(flags.sps_extension_4bits, 0);
3102 assert!(flags.has_body());
3103 assert!(sps.opaque_tail.is_none());
3104 let scc = sps.sps_scc_extension.expect("scc extension body");
3105 assert!(scc.sps_curr_pic_ref_enabled_flag);
3106 assert!(!scc.palette_mode_enabled_flag);
3107 assert_eq!(scc.motion_vector_resolution_control_idc, 1);
3108 assert!(!scc.intra_boundary_filtering_disabled_flag);
3109 }
3110
3111 /// When `sps_scc_extension_flag == 1` but a `sps_multilayer_extension()`
3112 /// body precedes it (§7.3.2.2.1 body order), the SCC body cannot be
3113 /// decoded in place and the whole multilayer-onward span — including
3114 /// the SCC body — stays in the opaque tail.
3115 #[test]
3116 fn scc_stays_opaque_behind_multilayer_body() {
3117 let mut s = synthesised_prefix_bits();
3118 s += "0"; // pcm
3119 s += "1"; // num_short_term=0
3120 s += "0"; // long_term=0
3121 s += "1"; // temporal_mvp
3122 s += "1"; // strong_intra_smoothing
3123 s += "0"; // vui=0
3124 s += "1"; // sps_extension_present_flag = 1
3125 s += "0"; // sps_range_extension_flag = 0
3126 s += "1"; // sps_multilayer_extension_flag = 1
3127 s += "0"; // sps_3d_extension_flag = 0
3128 s += "1"; // sps_scc_extension_flag = 1
3129 s += "0000"; // sps_extension_4bits = 0
3130 s += "11001100"; // opaque multilayer + scc span sentinel
3131 s += "1"; // rbsp_trailing_bits stop bit
3132 let bytes = bits_to_bytes(&s);
3133 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
3134 let flags = sps.extension_flags.expect("extension flag block");
3135 assert!(flags.sps_multilayer_extension_flag);
3136 assert!(flags.sps_scc_extension_flag);
3137 // Multilayer body is still opaque, so the SCC body cannot be
3138 // decoded in place; both stay in the captured tail.
3139 assert!(sps.sps_scc_extension.is_none());
3140 assert!(sps.opaque_tail.is_some());
3141 }
3142
3143 /// `sps_extension_present_flag == 1` with the four typed
3144 /// extension flags all 0 but `sps_extension_4bits != 0` still
3145 /// surfaces an opaque tail — the §7.3.2.2.1
3146 /// `while( more_rbsp_data() ) sps_extension_data_flag` block is
3147 /// gated by `sps_extension_4bits` (the §7.4.3.2.1 decoder rule is
3148 /// to ignore the data flags but they must be skipped past
3149 /// rbsp_trailing_bits, so the bytes are surfaced as opaque).
3150 #[test]
3151 fn captures_extension_data_flag_tail_when_4bits_nonzero() {
3152 let mut s = synthesised_prefix_bits();
3153 s += "0"; // pcm
3154 s += "1"; // num_short_term=0
3155 s += "0"; // long_term=0
3156 s += "1"; // temporal_mvp
3157 s += "1"; // strong_intra_smoothing
3158 s += "0"; // vui=0
3159 s += "1"; // sps_extension_present_flag = 1
3160 s += "0000"; // four typed flags = 0
3161 s += "0001"; // sps_extension_4bits = 1 (reserved, non-zero)
3162 s += "0"; // a single sps_extension_data_flag value
3163 s += "1"; // rbsp_trailing_bits stop bit
3164 let bytes = bits_to_bytes(&s);
3165 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
3166 let flags = sps.extension_flags.expect("extension flag block");
3167 assert_eq!(flags.sps_extension_4bits, 1);
3168 assert!(flags.has_body());
3169 assert!(sps.opaque_tail.is_some());
3170 }
3171
3172 /// When `sps_extension_present_flag == 0` the typed
3173 /// extension-flag block is absent and every flag is inferred to
3174 /// 0 per §7.4.3.2.1.
3175 #[test]
3176 fn extension_flags_absent_when_gate_zero() {
3177 let mut s = synthesised_prefix_bits();
3178 s += "0"; // pcm
3179 s += "1"; // num_short_term=0
3180 s += "0"; // long_term=0
3181 s += "1"; // temporal_mvp
3182 s += "1"; // strong_intra_smoothing
3183 s += "0"; // vui=0
3184 s += "0"; // sps_extension_present_flag = 0
3185 s += "1"; // rbsp_trailing_bits stop bit
3186 let bytes = bits_to_bytes(&s);
3187 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
3188 assert!(!sps.sps_extension_present_flag);
3189 assert!(sps.extension_flags.is_none());
3190 assert!(sps.opaque_tail.is_none());
3191 }
3192
3193 /// SPS with both `vui` and `extension` flags off — no opaque
3194 /// tail is captured (only the rbsp_trailing_bits remain).
3195 #[test]
3196 fn no_opaque_tail_when_flags_clear() {
3197 let mut s = synthesised_prefix_bits();
3198 s += "0"; // pcm
3199 s += "1"; // num_short=0
3200 s += "0"; // long_term=0
3201 s += "1"; // temporal_mvp
3202 s += "1"; // strong_intra_smoothing
3203 s += "0"; // vui=0
3204 s += "0"; // sps_extension_present=0
3205 s += "1"; // stop bit
3206 let bytes = bits_to_bytes(&s);
3207 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
3208 assert!(!sps.vui_parameters_present_flag);
3209 assert!(!sps.sps_extension_present_flag);
3210 assert!(sps.opaque_tail.is_none());
3211 }
3212
3213 /// `num_short_term_ref_pic_sets > 64` is illegal per §7.4.3.2.
3214 #[test]
3215 fn rejects_too_many_short_term_rps() {
3216 let mut s = synthesised_prefix_bits();
3217 s += "0"; // pcm
3218 // num_short_term_ref_pic_sets = 65 → codeNum 65
3219 // 65 in 0-th order Exp-Golomb: leadingZeroBits=6,
3220 // suffix = 65 - (2^6 - 1) = 2 = '000010'
3221 // so the bit string is '000000 1 000010' = 13 bits.
3222 s += "0000001000010";
3223 let bytes = bits_to_bytes(&s);
3224 let err = SeqParameterSet::parse(&bytes).unwrap_err();
3225 assert!(matches!(
3226 err,
3227 SpsError::ValueOutOfRange {
3228 field: "num_short_term_ref_pic_sets",
3229 ..
3230 }
3231 ));
3232 }
3233
3234 /// §7.4.8 explicit-form materialisation: the cumulative recurrence
3235 /// for `DeltaPocS0[i]` (equation 7-69) and `DeltaPocS1[i]`
3236 /// (equation 7-70) starting from the equation-7-67 / 7-68 seeds.
3237 #[test]
3238 fn materialize_explicit_form_recurrence() {
3239 // Hand-assembled RPS: num_negative_pics = 3,
3240 // delta_poc_s0_minus1 = [0, 1, 0], used_by_curr_pic_s0_flag =
3241 // [true, false, true]; num_positive_pics = 2,
3242 // delta_poc_s1_minus1 = [1, 0], used_by_curr_pic_s1_flag =
3243 // [true, true]. Per §7.4.8:
3244 // DeltaPocS0 = [-1, -3, -4]
3245 // DeltaPocS1 = [ 2, 3]
3246 let rps = ShortTermRefPicSet {
3247 inter_ref_pic_set_prediction_flag: false,
3248 delta_idx_minus1: 0,
3249 delta_rps_sign: false,
3250 abs_delta_rps_minus1: 0,
3251 used_by_curr_pic_flag: Vec::new(),
3252 use_delta_flag: Vec::new(),
3253 num_negative_pics: 3,
3254 num_positive_pics: 2,
3255 delta_poc_s0_minus1: vec![0, 1, 0],
3256 used_by_curr_pic_s0_flag: vec![true, false, true],
3257 delta_poc_s1_minus1: vec![1, 0],
3258 used_by_curr_pic_s1_flag: vec![true, true],
3259 };
3260 let m = rps.materialize(None).expect("explicit materialise");
3261 assert_eq!(m.delta_poc_s0, vec![-1, -3, -4]);
3262 assert_eq!(m.used_by_curr_pic_s0, vec![true, false, true]);
3263 assert_eq!(m.delta_poc_s1, vec![2, 3]);
3264 assert_eq!(m.used_by_curr_pic_s1, vec![true, true]);
3265 assert_eq!(m.num_negative_pics(), 3);
3266 assert_eq!(m.num_positive_pics(), 2);
3267 assert_eq!(m.num_delta_pocs(), 5);
3268 }
3269
3270 /// §7.4.8 inter-RPS-prediction (equations 7-61 / 7-62): a tiny
3271 /// chain where the source has one negative POC at -1 and the
3272 /// derived RPS uses `deltaRps = +1` to shift it past zero, so the
3273 /// source's negative drops out and `deltaRps` itself lands as a
3274 /// positive entry gated by the wire flags. Matches the
3275 /// `parses_inter_rps_prediction` fixture above.
3276 #[test]
3277 fn materialize_inter_rps_prediction_matches_fixture() {
3278 let src = MaterializedShortTermRefPicSet {
3279 delta_poc_s0: vec![-1],
3280 used_by_curr_pic_s0: vec![true],
3281 delta_poc_s1: vec![],
3282 used_by_curr_pic_s1: vec![],
3283 };
3284 // The inter-form RPS from the existing
3285 // `parses_inter_rps_prediction` test: deltaRps = +1,
3286 // used_by_curr_pic_flag = [true, false], use_delta_flag = [true,
3287 // true].
3288 let inter = ShortTermRefPicSet {
3289 inter_ref_pic_set_prediction_flag: true,
3290 delta_idx_minus1: 0,
3291 delta_rps_sign: false,
3292 abs_delta_rps_minus1: 0,
3293 used_by_curr_pic_flag: vec![true, false],
3294 use_delta_flag: vec![true, true],
3295 num_negative_pics: 0,
3296 num_positive_pics: 0,
3297 delta_poc_s0_minus1: Vec::new(),
3298 used_by_curr_pic_s0_flag: Vec::new(),
3299 delta_poc_s1_minus1: Vec::new(),
3300 used_by_curr_pic_s1_flag: Vec::new(),
3301 };
3302 let m = inter
3303 .materialize(Some(&src))
3304 .expect("inter-RPS materialise");
3305 // Negative side (equation 7-61):
3306 // * No source positives.
3307 // * deltaRps = +1 ≥ 0, skip the self-term.
3308 // * Source negative j=0: dPoc = -1 + 1 = 0, not < 0, skip.
3309 // ⇒ NumNegativePics = 0.
3310 assert!(m.delta_poc_s0.is_empty());
3311 assert!(m.used_by_curr_pic_s0.is_empty());
3312 // Positive side (equation 7-62):
3313 // * Source negative j=0 reverse: dPoc = -1 + 1 = 0, not > 0,
3314 // skip.
3315 // * deltaRps = +1 > 0 and use_delta_flag[NumDeltaPocs=1] =
3316 // true ⇒ DeltaPocS1[0] = +1, UsedByCurrPicS1[0] =
3317 // used_by_curr_pic_flag[1] = false.
3318 // * No source positives.
3319 // ⇒ NumPositivePics = 1.
3320 assert_eq!(m.delta_poc_s1, vec![1]);
3321 assert_eq!(m.used_by_curr_pic_s1, vec![false]);
3322 }
3323
3324 /// §7.4.8 inter-RPS-prediction with `deltaRps < 0`: a source
3325 /// positive POC at +1 with `deltaRps = -2` falls onto the negative
3326 /// side via the source-positives-reverse step of equation 7-61.
3327 #[test]
3328 fn materialize_inter_rps_prediction_negative_delta_rps() {
3329 // Source: one positive POC at +1 (no negatives).
3330 let src = MaterializedShortTermRefPicSet {
3331 delta_poc_s0: vec![],
3332 used_by_curr_pic_s0: vec![],
3333 delta_poc_s1: vec![1],
3334 used_by_curr_pic_s1: vec![true],
3335 };
3336 // Inter-form: deltaRps = -(1+1) = -2; arrays sized
3337 // NumDeltaPocs[src]+1 = 2.
3338 // used_by_curr_pic_flag indexed:
3339 // * [0..NumNegativePics[src]) = none
3340 // * [NumNegativePics, NumNegativePics + NumPositivePics) = [0..1) = j_src=0
3341 // * [NumDeltaPocs] = trailing slot for `deltaRps` self-term
3342 // So used_by_curr_pic_flag = [true_for_src_pos0, true_for_self_term].
3343 let inter = ShortTermRefPicSet {
3344 inter_ref_pic_set_prediction_flag: true,
3345 delta_idx_minus1: 0,
3346 delta_rps_sign: true, // negative
3347 abs_delta_rps_minus1: 1,
3348 used_by_curr_pic_flag: vec![true, true],
3349 use_delta_flag: vec![true, true],
3350 num_negative_pics: 0,
3351 num_positive_pics: 0,
3352 delta_poc_s0_minus1: Vec::new(),
3353 used_by_curr_pic_s0_flag: Vec::new(),
3354 delta_poc_s1_minus1: Vec::new(),
3355 used_by_curr_pic_s1_flag: Vec::new(),
3356 };
3357 let m = inter
3358 .materialize(Some(&src))
3359 .expect("inter-RPS negative deltaRps");
3360 // Negative side (equation 7-61):
3361 // * Source positive j=0 reverse: dPoc = 1 + (-2) = -1 < 0 and
3362 // use_delta_flag[NumNeg=0 + 0] = use_delta_flag[0] = true
3363 // ⇒ DeltaPocS0[0] = -1, UsedByCurrPicS0[0] =
3364 // used_by_curr_pic_flag[0] = true.
3365 // * deltaRps = -2 < 0 and use_delta_flag[NumDeltaPocs=1] =
3366 // true ⇒ DeltaPocS0[1] = -2, UsedByCurrPicS0[1] =
3367 // used_by_curr_pic_flag[1] = true.
3368 // * No source negatives.
3369 assert_eq!(m.delta_poc_s0, vec![-1, -2]);
3370 assert_eq!(m.used_by_curr_pic_s0, vec![true, true]);
3371 // Positive side: source negatives reverse: none. deltaRps < 0,
3372 // skip self-term. Source positive j=0: dPoc = +1 + (-2) = -1,
3373 // not > 0, skip. ⇒ empty.
3374 assert!(m.delta_poc_s1.is_empty());
3375 assert!(m.used_by_curr_pic_s1.is_empty());
3376 }
3377
3378 /// [`ShortTermRefPicSet::materialize`] rejects the inter form
3379 /// without a source RPS.
3380 #[test]
3381 fn materialize_inter_rps_rejects_missing_source() {
3382 let inter = ShortTermRefPicSet {
3383 inter_ref_pic_set_prediction_flag: true,
3384 delta_idx_minus1: 0,
3385 delta_rps_sign: false,
3386 abs_delta_rps_minus1: 0,
3387 used_by_curr_pic_flag: vec![true],
3388 use_delta_flag: vec![true],
3389 ..Default::default()
3390 };
3391 assert_eq!(
3392 inter.materialize(None),
3393 Err(ShortTermRefPicSetMaterializeError::MissingSource),
3394 );
3395 }
3396
3397 /// [`ShortTermRefPicSet::materialize`] surfaces a per-position
3398 /// array-length mismatch against the source RPS's `NumDeltaPocs +
3399 /// 1`.
3400 #[test]
3401 fn materialize_inter_rps_rejects_length_mismatch() {
3402 let src = MaterializedShortTermRefPicSet {
3403 delta_poc_s0: vec![-1, -2],
3404 used_by_curr_pic_s0: vec![true, true],
3405 delta_poc_s1: vec![],
3406 used_by_curr_pic_s1: vec![],
3407 };
3408 // NumDeltaPocs[src] = 2, so the inter form expects arrays of
3409 // length 3. Pass 2-element arrays to provoke the mismatch.
3410 let inter = ShortTermRefPicSet {
3411 inter_ref_pic_set_prediction_flag: true,
3412 delta_idx_minus1: 0,
3413 delta_rps_sign: false,
3414 abs_delta_rps_minus1: 0,
3415 used_by_curr_pic_flag: vec![true, true],
3416 use_delta_flag: vec![true, true],
3417 ..Default::default()
3418 };
3419 assert!(matches!(
3420 inter.materialize(Some(&src)),
3421 Err(ShortTermRefPicSetMaterializeError::SourceLengthMismatch {
3422 expected: 3,
3423 got_used: 2,
3424 got_delta: 2,
3425 }),
3426 ));
3427 }
3428
3429 /// [`SeqParameterSet::materialize_short_term_ref_pic_sets`]
3430 /// chains explicit and inter-RPS-predicted entries through their
3431 /// `RefRpsIdx` lookups using the same fixture as
3432 /// `parses_inter_rps_prediction`.
3433 #[test]
3434 fn sps_materialize_chains_inter_rps_prediction() {
3435 let mut s = synthesised_prefix_bits();
3436 s += "0"; // pcm_enabled_flag
3437 s += "011"; // num_short_term_ref_pic_sets = 2
3438 // st_ref_pic_set(0): explicit num_neg=1, num_pos=0,
3439 // delta_poc_s0_minus1[0]=0, used_by_curr_pic_s0_flag=1
3440 s += "010";
3441 s += "1";
3442 s += "1";
3443 s += "1";
3444 // st_ref_pic_set(1): inter_ref_pic_set_prediction_flag = 1,
3445 // delta_rps_sign=0, abs_delta_rps_minus1=0 (deltaRps=+1),
3446 // used_by_curr_pic_flag = [1, 0], use_delta_flag[1] = 1.
3447 s += "1";
3448 s += "0";
3449 s += "1";
3450 s += "1"; // used_by_curr_pic_flag[0] = 1 (use_delta inferred)
3451 s += "0"; // used_by_curr_pic_flag[1] = 0
3452 s += "1"; // use_delta_flag[1] = 1
3453 // long_term=0, temporal_mvp=1, strong_intra_smoothing=1,
3454 // vui=0, ext=0, stop bit
3455 s += "0";
3456 s += "1";
3457 s += "1";
3458 s += "0";
3459 s += "0";
3460 s += "1";
3461
3462 let bytes = bits_to_bytes(&s);
3463 let sps = SeqParameterSet::parse(&bytes).expect("SPS parse");
3464 let m = sps
3465 .materialize_short_term_ref_pic_sets()
3466 .expect("materialize");
3467 assert_eq!(m.len(), 2);
3468 // Explicit source (idx 0): DeltaPocS0=[-1], UsedByCurrPicS0=[true].
3469 assert_eq!(m[0].delta_poc_s0, vec![-1]);
3470 assert_eq!(m[0].used_by_curr_pic_s0, vec![true]);
3471 assert_eq!(m[0].num_delta_pocs(), 1);
3472 // Inter-predicted (idx 1): NumNegativePics=0,
3473 // NumPositivePics=1 with DeltaPocS1=[+1] from the deltaRps
3474 // self-term (see `materialize_inter_rps_prediction_matches_fixture`).
3475 assert!(m[1].delta_poc_s0.is_empty());
3476 assert_eq!(m[1].delta_poc_s1, vec![1]);
3477 assert_eq!(m[1].used_by_curr_pic_s1, vec![false]);
3478 }
3479
3480 /// `num_long_term_ref_pics_sps > 32` is illegal per §7.4.3.2.
3481 #[test]
3482 fn rejects_too_many_long_term_rps() {
3483 let mut s = synthesised_prefix_bits();
3484 s += "0"; // pcm
3485 s += "1"; // num_short_term=0
3486 s += "1"; // long_term_ref_pics_present
3487 // num_long_term_ref_pics_sps = 33: codeNum 33
3488 // leadingZeroBits=5, suffix = 33 - (2^5 - 1) = 2 = '00010'
3489 // bit string: '00000 1 00010' = 11 bits.
3490 s += "00000100010";
3491 let bytes = bits_to_bytes(&s);
3492 let err = SeqParameterSet::parse(&bytes).unwrap_err();
3493 assert!(matches!(
3494 err,
3495 SpsError::ValueOutOfRange {
3496 field: "num_long_term_ref_pics_sps",
3497 ..
3498 }
3499 ));
3500 }
3501}