Skip to main content

h264_reader/nal/
sps.rs

1use crate::rbsp::{BitRead, BitReaderError, BitWrite};
2use std::convert::TryFrom as _;
3use std::fmt::{self, Debug};
4use std::num::NonZeroU8;
5
6#[derive(Debug, PartialEq)]
7pub enum SeqParamSetIdError {
8    IdTooLarge(u32),
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct SeqParamSetId(u8);
13impl SeqParamSetId {
14    pub fn from_u32(id: u32) -> Result<SeqParamSetId, SeqParamSetIdError> {
15        if id > 31 {
16            Err(SeqParamSetIdError::IdTooLarge(id))
17        } else {
18            Ok(SeqParamSetId(id as u8))
19        }
20    }
21    pub fn id(self) -> u8 {
22        self.0
23    }
24}
25
26#[derive(Debug)]
27pub enum SpsError {
28    /// Signals that bit_depth_luma_minus8 was greater than the max value, 6
29    BitDepthOutOfRange(u32),
30    RbspReaderError(BitReaderError),
31    PicOrderCnt(PicOrderCntError),
32    ScalingMatrix(ScalingMatrixError),
33    /// log2_max_frame_num_minus4 must be between 0 and 12
34    Log2MaxFrameNumMinus4OutOfRange(u32),
35    BadSeqParamSetId(SeqParamSetIdError),
36    UnknownSeqParamSetId(SeqParamSetId),
37    /// A field in the bitstream had a value too large for a subsequent calculation
38    FieldValueTooLarge {
39        name: &'static str,
40        value: u32,
41    },
42    /// A field in the bitstream had a value that is too small
43    FieldValueTooSmall {
44        name: &'static str,
45        value: u32,
46    },
47    /// The frame-cropping values are too large vs. the coded picture size,
48    CroppingError(FrameCropping),
49    /// The `cpb_cnt_minus1` field must be between 0 and 31 inclusive.
50    CpbCountOutOfRange(u32),
51}
52
53impl From<BitReaderError> for SpsError {
54    fn from(e: BitReaderError) -> Self {
55        SpsError::RbspReaderError(e)
56    }
57}
58
59#[derive(Debug)]
60pub enum Profile {
61    Unknown(u8),
62    Baseline,
63    ConstrainedBaseline,
64    Main,
65    High,
66    ProgressiveHigh,
67    ConstrainedHigh,
68    High422,
69    High422Intra,
70    High10,
71    High10Intra,
72    High444,
73    High444Intra,
74    Extended,
75    ScalableBase,
76    ScalableConstrainedBaseline,
77    ScalableHigh,
78    ScalableConstrainedHigh,
79    ScalableHighIntra,
80    MultiviewHigh,
81    StereoHigh,
82    CavlcIntra444,
83    MFCHigh,
84    MFCDepthHigh,
85    MultiviewDepthHigh,
86    EnhancedMultiviewDepthHigh,
87}
88
89impl Profile {
90    pub fn from_profile_idc(profile_idc: ProfileIdc, constraint_flags: ConstraintFlags) -> Profile {
91        match profile_idc.0 {
92            66 if constraint_flags.flag1() => Profile::ConstrainedBaseline,
93            66 => Profile::Baseline,
94            77 => Profile::Main,
95            100 if constraint_flags.flag4() && constraint_flags.flag5() => Profile::ConstrainedHigh,
96            100 if constraint_flags.flag4() => Profile::ProgressiveHigh,
97            100 => Profile::High,
98            110 if constraint_flags.flag3() => Profile::High10Intra,
99            110 => Profile::High10,
100            122 if constraint_flags.flag3() => Profile::High422Intra,
101            122 => Profile::High422,
102            244 if constraint_flags.flag3() => Profile::High444Intra,
103            244 => Profile::High444,
104            88 => Profile::Extended,
105            83 if constraint_flags.flag5() => Profile::ScalableConstrainedBaseline,
106            83 => Profile::ScalableBase,
107            86 if constraint_flags.flag3() => Profile::ScalableHighIntra,
108            86 if constraint_flags.flag5() => Profile::ScalableConstrainedHigh,
109            86 => Profile::ScalableHigh,
110            118 => Profile::MultiviewHigh,
111            128 => Profile::StereoHigh,
112            44 => Profile::CavlcIntra444,
113            134 => Profile::MFCHigh,
114            135 => Profile::MFCDepthHigh,
115            138 => Profile::MultiviewDepthHigh,
116            139 => Profile::EnhancedMultiviewDepthHigh,
117            other => Profile::Unknown(other),
118        }
119    }
120    pub fn profile_idc(&self) -> u8 {
121        match *self {
122            Profile::Baseline | Profile::ConstrainedBaseline => 66,
123            Profile::Main => 77,
124            Profile::High | Profile::ProgressiveHigh | Profile::ConstrainedHigh => 100,
125            Profile::High422 | Profile::High422Intra => 122,
126            Profile::High10 | Profile::High10Intra => 110,
127            Profile::High444 | Profile::High444Intra => 244,
128            Profile::Extended => 88,
129            Profile::ScalableBase | Profile::ScalableConstrainedBaseline => 83,
130            Profile::ScalableHigh
131            | Profile::ScalableConstrainedHigh
132            | Profile::ScalableHighIntra => 86,
133            Profile::MultiviewHigh => 118,
134            Profile::StereoHigh => 128,
135            Profile::CavlcIntra444 => 44,
136            Profile::MFCHigh => 134,
137            Profile::MFCDepthHigh => 135,
138            Profile::MultiviewDepthHigh => 138,
139            Profile::EnhancedMultiviewDepthHigh => 139,
140            Profile::Unknown(profile_idc) => profile_idc,
141        }
142    }
143}
144
145#[derive(Copy, Clone, PartialEq, Eq)]
146pub struct ConstraintFlags(u8);
147impl From<u8> for ConstraintFlags {
148    fn from(v: u8) -> Self {
149        ConstraintFlags(v)
150    }
151}
152impl From<ConstraintFlags> for u8 {
153    fn from(v: ConstraintFlags) -> Self {
154        v.0
155    }
156}
157impl ConstraintFlags {
158    pub fn flag0(self) -> bool {
159        self.0 & 0b1000_0000 != 0
160    }
161    pub fn flag1(self) -> bool {
162        self.0 & 0b0100_0000 != 0
163    }
164    pub fn flag2(self) -> bool {
165        self.0 & 0b0010_0000 != 0
166    }
167    pub fn flag3(self) -> bool {
168        self.0 & 0b0001_0000 != 0
169    }
170    pub fn flag4(self) -> bool {
171        self.0 & 0b0000_1000 != 0
172    }
173    pub fn flag5(self) -> bool {
174        self.0 & 0b0000_0100 != 0
175    }
176    pub fn reserved_zero_two_bits(self) -> u8 {
177        self.0 & 0b0000_0011
178    }
179}
180impl Debug for ConstraintFlags {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
182        f.debug_struct("ConstraintFlags")
183            .field("flag0", &self.flag0())
184            .field("flag1", &self.flag1())
185            .field("flag2", &self.flag2())
186            .field("flag3", &self.flag3())
187            .field("flag4", &self.flag4())
188            .field("flag5", &self.flag5())
189            .field("reserved_zero_two_bits", &self.reserved_zero_two_bits())
190            .finish()
191    }
192}
193
194#[derive(Debug, PartialEq, Hash, Eq)]
195#[allow(non_camel_case_types)]
196pub enum Level {
197    Unknown(u8),
198    L1,
199    L1_b,
200    L1_1,
201    L1_2,
202    L1_3,
203    L2,
204    L2_1,
205    L2_2,
206    L3,
207    L3_1,
208    L3_2,
209    L4,
210    L4_1,
211    L4_2,
212    L5,
213    L5_1,
214    L5_2,
215    L6,
216    L6_1,
217    L6_2,
218}
219impl Level {
220    pub fn from_constraint_flags_and_level_idc(
221        constraint_flags: ConstraintFlags,
222        level_idc: u8,
223    ) -> Level {
224        match level_idc {
225            10 => Level::L1,
226            11 => {
227                if constraint_flags.flag3() {
228                    Level::L1_b
229                } else {
230                    Level::L1_1
231                }
232            }
233            12 => Level::L1_2,
234            13 => Level::L1_3,
235            20 => Level::L2,
236            21 => Level::L2_1,
237            22 => Level::L2_2,
238            30 => Level::L3,
239            31 => Level::L3_1,
240            32 => Level::L3_2,
241            40 => Level::L4,
242            41 => Level::L4_1,
243            42 => Level::L4_2,
244            50 => Level::L5,
245            51 => Level::L5_1,
246            52 => Level::L5_2,
247            60 => Level::L6,
248            61 => Level::L6_1,
249            62 => Level::L6_2,
250            _ => Level::Unknown(level_idc),
251        }
252    }
253    pub fn level_idc(&self) -> u8 {
254        match *self {
255            Level::L1 => 10,
256            Level::L1_1 | Level::L1_b => 11,
257            Level::L1_2 => 12,
258            Level::L1_3 => 13,
259            Level::L2 => 20,
260            Level::L2_1 => 21,
261            Level::L2_2 => 22,
262            Level::L3 => 30,
263            Level::L3_1 => 31,
264            Level::L3_2 => 32,
265            Level::L4 => 40,
266            Level::L4_1 => 41,
267            Level::L4_2 => 42,
268            Level::L5 => 50,
269            Level::L5_1 => 51,
270            Level::L5_2 => 52,
271            Level::L6 => 60,
272            Level::L6_1 => 61,
273            Level::L6_2 => 62,
274            Level::Unknown(level_idc) => level_idc,
275        }
276    }
277
278    /// Returns the level limits from Table A-1 of the spec,
279    /// or `None` for unknown levels.
280    pub const fn limits(&self) -> Option<LevelLimit> {
281        match self {
282            Level::L1 => Some(LevelLimit {
283                max_mbps: 1485,
284                max_fs: 99,
285                max_dpb_mbs: 396,
286                max_br: 64,
287                max_cpb: 175,
288                max_vmv_r: 64,
289                min_cr: 2,
290                max_mvs_per2mb: None,
291            }),
292            Level::L1_b => Some(LevelLimit {
293                max_mbps: 1485,
294                max_fs: 99,
295                max_dpb_mbs: 396,
296                max_br: 128,
297                max_cpb: 350,
298                max_vmv_r: 64,
299                min_cr: 2,
300                max_mvs_per2mb: None,
301            }),
302            Level::L1_1 => Some(LevelLimit {
303                max_mbps: 3000,
304                max_fs: 396,
305                max_dpb_mbs: 900,
306                max_br: 192,
307                max_cpb: 500,
308                max_vmv_r: 128,
309                min_cr: 2,
310                max_mvs_per2mb: None,
311            }),
312            Level::L1_2 => Some(LevelLimit {
313                max_mbps: 6000,
314                max_fs: 396,
315                max_dpb_mbs: 2376,
316                max_br: 384,
317                max_cpb: 1000,
318                max_vmv_r: 128,
319                min_cr: 2,
320                max_mvs_per2mb: None,
321            }),
322            Level::L1_3 => Some(LevelLimit {
323                max_mbps: 11880,
324                max_fs: 396,
325                max_dpb_mbs: 2376,
326                max_br: 768,
327                max_cpb: 2000,
328                max_vmv_r: 128,
329                min_cr: 2,
330                max_mvs_per2mb: None,
331            }),
332            Level::L2 => Some(LevelLimit {
333                max_mbps: 11880,
334                max_fs: 396,
335                max_dpb_mbs: 2376,
336                max_br: 2000,
337                max_cpb: 2000,
338                max_vmv_r: 128,
339                min_cr: 2,
340                max_mvs_per2mb: None,
341            }),
342            Level::L2_1 => Some(LevelLimit {
343                max_mbps: 19800,
344                max_fs: 792,
345                max_dpb_mbs: 4752,
346                max_br: 4000,
347                max_cpb: 4000,
348                max_vmv_r: 256,
349                min_cr: 2,
350                max_mvs_per2mb: None,
351            }),
352            Level::L2_2 => Some(LevelLimit {
353                max_mbps: 20250,
354                max_fs: 1620,
355                max_dpb_mbs: 8100,
356                max_br: 4000,
357                max_cpb: 4000,
358                max_vmv_r: 256,
359                min_cr: 2,
360                max_mvs_per2mb: None,
361            }),
362            Level::L3 => Some(LevelLimit {
363                max_mbps: 40500,
364                max_fs: 1620,
365                max_dpb_mbs: 8100,
366                max_br: 10000,
367                max_cpb: 10000,
368                max_vmv_r: 256,
369                min_cr: 2,
370                max_mvs_per2mb: NonZeroU8::new(32),
371            }),
372            Level::L3_1 => Some(LevelLimit {
373                max_mbps: 108000,
374                max_fs: 3600,
375                max_dpb_mbs: 18000,
376                max_br: 14000,
377                max_cpb: 14000,
378                max_vmv_r: 512,
379                min_cr: 4,
380                max_mvs_per2mb: NonZeroU8::new(16),
381            }),
382            Level::L3_2 => Some(LevelLimit {
383                max_mbps: 216000,
384                max_fs: 5120,
385                max_dpb_mbs: 20480,
386                max_br: 20000,
387                max_cpb: 20000,
388                max_vmv_r: 512,
389                min_cr: 4,
390                max_mvs_per2mb: NonZeroU8::new(16),
391            }),
392            Level::L4 => Some(LevelLimit {
393                max_mbps: 245760,
394                max_fs: 8192,
395                max_dpb_mbs: 32768,
396                max_br: 20000,
397                max_cpb: 25000,
398                max_vmv_r: 512,
399                min_cr: 4,
400                max_mvs_per2mb: NonZeroU8::new(16),
401            }),
402            Level::L4_1 => Some(LevelLimit {
403                max_mbps: 245760,
404                max_fs: 8192,
405                max_dpb_mbs: 32768,
406                max_br: 50000,
407                max_cpb: 62500,
408                max_vmv_r: 512,
409                min_cr: 2,
410                max_mvs_per2mb: NonZeroU8::new(16),
411            }),
412            Level::L4_2 => Some(LevelLimit {
413                max_mbps: 522240,
414                max_fs: 8704,
415                max_dpb_mbs: 34816,
416                max_br: 50000,
417                max_cpb: 62500,
418                max_vmv_r: 512,
419                min_cr: 2,
420                max_mvs_per2mb: NonZeroU8::new(16),
421            }),
422            Level::L5 => Some(LevelLimit {
423                max_mbps: 589824,
424                max_fs: 22080,
425                max_dpb_mbs: 110400,
426                max_br: 135000,
427                max_cpb: 135000,
428                max_vmv_r: 512,
429                min_cr: 2,
430                max_mvs_per2mb: NonZeroU8::new(16),
431            }),
432            Level::L5_1 => Some(LevelLimit {
433                max_mbps: 983040,
434                max_fs: 36864,
435                max_dpb_mbs: 184320,
436                max_br: 240000,
437                max_cpb: 240000,
438                max_vmv_r: 512,
439                min_cr: 2,
440                max_mvs_per2mb: NonZeroU8::new(16),
441            }),
442            Level::L5_2 => Some(LevelLimit {
443                max_mbps: 2073600,
444                max_fs: 36864,
445                max_dpb_mbs: 184320,
446                max_br: 240000,
447                max_cpb: 240000,
448                max_vmv_r: 512,
449                min_cr: 2,
450                max_mvs_per2mb: NonZeroU8::new(16),
451            }),
452            Level::L6 => Some(LevelLimit {
453                max_mbps: 4177920,
454                max_fs: 139264,
455                max_dpb_mbs: 696320,
456                max_br: 240000,
457                max_cpb: 240000,
458                max_vmv_r: 8192,
459                min_cr: 2,
460                max_mvs_per2mb: NonZeroU8::new(16),
461            }),
462            Level::L6_1 => Some(LevelLimit {
463                max_mbps: 8355840,
464                max_fs: 139264,
465                max_dpb_mbs: 696320,
466                max_br: 480000,
467                max_cpb: 480000,
468                max_vmv_r: 8192,
469                min_cr: 2,
470                max_mvs_per2mb: NonZeroU8::new(16),
471            }),
472            Level::L6_2 => Some(LevelLimit {
473                max_mbps: 16711680,
474                max_fs: 139264,
475                max_dpb_mbs: 696320,
476                max_br: 800000,
477                max_cpb: 800000,
478                max_vmv_r: 8192,
479                min_cr: 2,
480                max_mvs_per2mb: NonZeroU8::new(16),
481            }),
482            Level::Unknown(_) => None,
483        }
484    }
485}
486
487#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
488pub enum ChromaFormat {
489    Monochrome,
490    #[default]
491    YUV420,
492    YUV422,
493    YUV444,
494    Invalid(u32),
495}
496impl ChromaFormat {
497    fn from_chroma_format_idc(chroma_format_idc: u32) -> ChromaFormat {
498        match chroma_format_idc {
499            0 => ChromaFormat::Monochrome,
500            1 => ChromaFormat::YUV420,
501            2 => ChromaFormat::YUV422,
502            3 => ChromaFormat::YUV444,
503            _ => ChromaFormat::Invalid(chroma_format_idc),
504        }
505    }
506    pub fn to_u32(self) -> u32 {
507        match self {
508            ChromaFormat::Monochrome => 0,
509            ChromaFormat::YUV420 => 1,
510            ChromaFormat::YUV422 => 2,
511            ChromaFormat::YUV444 => 3,
512            ChromaFormat::Invalid(chroma_format_idc) => chroma_format_idc,
513        }
514    }
515}
516
517// _Profile Indication_ value
518#[derive(Copy, Clone, Debug, PartialEq, Eq)]
519pub struct ProfileIdc(u8);
520impl ProfileIdc {
521    pub fn has_chroma_info(self) -> bool {
522        match self.0 {
523            100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 134 | 135 | 138 | 139 => true,
524            _ => false,
525        }
526    }
527}
528impl From<u8> for ProfileIdc {
529    fn from(v: u8) -> Self {
530        ProfileIdc(v)
531    }
532}
533impl From<ProfileIdc> for u8 {
534    fn from(v: ProfileIdc) -> Self {
535        v.0
536    }
537}
538
539#[derive(Debug)]
540pub enum ScalingMatrixError {
541    ReaderError(BitReaderError),
542    /// The `delta_scale` field must be between -128 and 127 inclusive.
543    DeltaScaleOutOfRange(i32),
544}
545
546impl From<BitReaderError> for ScalingMatrixError {
547    fn from(e: BitReaderError) -> Self {
548        ScalingMatrixError::ReaderError(e)
549    }
550}
551
552/// Sequence-level scaling matrix parsed from the SPS.
553///
554/// The fields store raw `next_scale` intermediate values from the bitstream, not the
555/// final `ScalingList` values used by the decoding process. To obtain usable scaling
556/// list values with all spec fallback rules applied, use the helper methods on the
557/// structs that contain this type:
558///
559/// - [`ChromaInfo::scaling_lists_4x4`] / [`ChromaInfo::scaling_lists_8x8`] — the
560///   simplest entry point. Handles the case where `seq_scaling_matrix_present_flag`
561///   is 0 (returns flat-16 lists) and delegates here otherwise.
562/// - [`SeqScalingMatrix::scaling_lists_4x4`] / [`SeqScalingMatrix::scaling_lists_8x8`]
563///   — resolves using SPS fall-back rule set A (Table 7-2).
564///
565/// For picture-level scaling lists, see
566/// [`PicScalingMatrix::scaling_lists_4x4`](crate::nal::pps::PicScalingMatrix::scaling_lists_4x4).
567#[derive(Clone, Debug, PartialEq, Eq)]
568pub struct SeqScalingMatrix {
569    pub scaling_lists4x4: ScalingLists4x4,
570    pub scaling_lists8x8: ScalingLists8x8,
571}
572
573/// Each `u8` is the `next_scale` intermediate value at that position (see H.264
574/// spec 7.3.2.1.1). A value of `0` at position `j` means the list was frozen at
575/// `j`; all positions from `j` onward will also be `0`. A value of `0` at position
576/// 0 means the list is entirely the default value.
577#[derive(Clone, Debug, Default, PartialEq, Eq)]
578pub struct ScalingLists4x4(pub [Option<[u8; 16]>; 6]);
579
580impl ScalingLists4x4 {
581    pub(crate) fn read<R: BitRead>(r: &mut R) -> Result<Self, ScalingMatrixError> {
582        read_lists(r).map(Self)
583    }
584
585    pub(crate) fn write<W: BitWrite>(&self, w: &mut W) -> std::io::Result<()> {
586        write_lists(&self.0, w)
587    }
588}
589
590/// As with [`ScalingLists4x4`], each `u8` is a `next_scale` intermediate value.
591#[derive(Clone, Debug, PartialEq, Eq)]
592#[allow(clippy::large_enum_variant)]
593pub enum ScalingLists8x8 {
594    /// 8x8 scaling lists for formats other than [`ChromaFormat::YUV444`], used only for luma (Y) components.
595    Y([Option<[u8; 64]>; 2]),
596
597    /// 8x8 scaling lists for [`ChromaFormat::YUV444`], used for both luma (Y) and chroma (Cb/Cr) components.
598    YCbCr([Option<[u8; 64]>; 6]),
599}
600
601/// Resolved 8x8 scaling lists with all fallback rules applied.
602/// Unlike [`ScalingLists8x8`], these contain final `ScalingList` values
603/// ready for use in the decoding process (section 8.5.9).
604#[derive(Clone, Debug, PartialEq, Eq)]
605#[allow(clippy::large_enum_variant)]
606pub enum ScalingLists8x8Resolved {
607    /// For chroma formats other than [`ChromaFormat::YUV444`].
608    Y([[u8; 64]; 2]),
609
610    /// For [`ChromaFormat::YUV444`].
611    YCbCr([[u8; 64]; 6]),
612}
613
614impl ScalingLists8x8Resolved {
615    /// Access the resolved lists as a slice (length 2 or 6).
616    pub fn as_slice(&self) -> &[[u8; 64]] {
617        match self {
618            Self::Y(a) => a.as_slice(),
619            Self::YCbCr(a) => a.as_slice(),
620        }
621    }
622}
623
624impl ScalingLists8x8 {
625    pub(crate) fn read<R: BitRead>(
626        r: &mut R,
627        chroma_format: ChromaFormat,
628    ) -> Result<Self, ScalingMatrixError> {
629        match chroma_format {
630            ChromaFormat::YUV444 => Ok(Self::YCbCr(read_lists(r)?)),
631            _ => Ok(Self::Y(read_lists(r)?)),
632        }
633    }
634
635    pub(crate) fn write<W: BitWrite>(
636        &self,
637        w: &mut W,
638        chroma_format: ChromaFormat,
639    ) -> std::io::Result<()> {
640        let expect_full = chroma_format == ChromaFormat::YUV444;
641        match (self, expect_full) {
642            (Self::Y(ls), false) => write_lists(ls, w),
643            (Self::YCbCr(ls), true) => write_lists(ls, w),
644            _ => Err(std::io::Error::new(
645                std::io::ErrorKind::InvalidData,
646                "inconsistent chroma format",
647            )),
648        }
649    }
650}
651
652impl SeqScalingMatrix {
653    /// Resolve 4x4 scaling lists using SPS fall-back rules (rule set A, Table 7-2).
654    ///
655    /// Converts the stored `next_scale` intermediates into final `ScalingList` values,
656    /// applying default-table and chain fallbacks for absent lists.
657    pub fn scaling_lists_4x4(&self) -> [[u8; 16]; 6] {
658        resolve_4x4_lists(&self.scaling_lists4x4.0, None)
659    }
660
661    /// Resolve 8x8 scaling lists using SPS fall-back rules (rule set A, Table 7-2).
662    pub fn scaling_lists_8x8(&self) -> ScalingLists8x8Resolved {
663        match &self.scaling_lists8x8 {
664            ScalingLists8x8::Y(lists) => ScalingLists8x8Resolved::Y(resolve_8x8_lists(lists, None)),
665            ScalingLists8x8::YCbCr(lists) => {
666                ScalingLists8x8Resolved::YCbCr(resolve_8x8_lists(lists, None))
667            }
668        }
669    }
670
671    fn read<R: BitRead>(
672        r: &mut R,
673        chroma_format: ChromaFormat,
674    ) -> Result<SeqScalingMatrix, ScalingMatrixError> {
675        Ok(SeqScalingMatrix {
676            scaling_lists4x4: ScalingLists4x4::read(r)?,
677            scaling_lists8x8: ScalingLists8x8::read(r, chroma_format)?,
678        })
679    }
680
681    fn write<W: BitWrite>(&self, w: &mut W, chroma_format: ChromaFormat) -> std::io::Result<()> {
682        self.scaling_lists4x4.write(w)?;
683        self.scaling_lists8x8.write(w, chroma_format)
684    }
685}
686
687fn read_lists<R: BitRead, const C: usize, const S: usize>(
688    r: &mut R,
689) -> Result<[Option<[u8; S]>; C], ScalingMatrixError> {
690    let mut lists = [None; C];
691    for l in &mut lists {
692        if r.read_bit("*_scaling_list_present_flag")? {
693            fill_list(r, &mut l.insert([0u8; S])[..])?;
694        }
695    }
696    Ok(lists)
697}
698
699/// Helper for `read_scaling_lists` which fills the non-zero prefix of `scaling_list`.
700/// Separating this portion reduces monomorphization bloat with multiple scaling list sizes.
701fn fill_list<R: BitRead>(r: &mut R, scaling_list: &mut [u8]) -> Result<(), ScalingMatrixError> {
702    let mut last_scale = 8u8;
703
704    for dst in scaling_list.iter_mut() {
705        let delta_scale = r.read_se("delta_scale")?;
706        let delta_scale = i8::try_from(delta_scale)
707            .map_err(|_| ScalingMatrixError::DeltaScaleOutOfRange(delta_scale))?;
708        last_scale = last_scale.wrapping_add_signed(delta_scale);
709        *dst = last_scale;
710        if last_scale == 0 {
711            // following values are implicitly 0 also.
712            break;
713        }
714    }
715
716    Ok(())
717}
718
719fn write_lists<W: BitWrite, const C: usize, const S: usize>(
720    lists: &[Option<[u8; S]>; C],
721    w: &mut W,
722) -> std::io::Result<()> {
723    for l in lists {
724        write_list(l.as_ref().map(|l| &l[..]), w)?;
725    }
726    Ok(())
727}
728
729fn write_list<W: BitWrite>(next_scale: Option<&[u8]>, w: &mut W) -> std::io::Result<()> {
730    let next_scale = match next_scale {
731        None => return w.write_bit(false),
732        Some(l) => l,
733    };
734    w.write_bit(true)?;
735    let mut last_scale = 8u8;
736    for &ns in next_scale.iter() {
737        // nextScale = ( lastScale + delta_scale + 256 ) % 256
738        let delta = ns.wrapping_sub(last_scale) as i8;
739        w.write_se(delta.into())?;
740        if ns == 0 {
741            // Freeze: next_scale = 0 signals end of explicit deltas.
742            break;
743        }
744        last_scale = ns;
745    }
746    Ok(())
747}
748
749// ---- Default and flat scaling lists (Tables 7-2 through 7-4, equations 7-8/7-9) ----
750
751const FLAT_4X4: [u8; 16] = [16; 16];
752
753const FLAT_8X8: [u8; 64] = [16; 64];
754
755/// Default 4x4 Intra scaling list (Table 7-3).
756const DEFAULT_4X4_INTRA: [u8; 16] = [
757    6, 13, 13, 20, 20, 20, 28, 28, 28, 28, 32, 32, 32, 37, 37, 42,
758];
759
760/// Default 4x4 Inter scaling list (Table 7-3).
761const DEFAULT_4X4_INTER: [u8; 16] = [
762    10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, 34,
763];
764
765/// Default 8x8 Intra scaling list (Table 7-4).
766const DEFAULT_8X8_INTRA: [u8; 64] = [
767    6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, 23, 23, 23, 23, 23, 23, 25, 25, 25,
768    25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 29, 29, 29, 29, 29, 29, 29, 31, 31, 31, 31, 31,
769    31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42,
770];
771
772/// Default 8x8 Inter scaling list (Table 7-4).
773const DEFAULT_8X8_INTER: [u8; 64] = [
774    9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, 21, 21, 21, 21, 21, 21, 22, 22, 22,
775    22, 22, 22, 22, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27,
776    27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35,
777];
778
779/// Default scaling list for a given 4x4 list index (Table 7-2 "Default scaling list" column).
780fn default_4x4(i: usize) -> &'static [u8; 16] {
781    if i < 3 {
782        &DEFAULT_4X4_INTRA
783    } else {
784        &DEFAULT_4X4_INTER
785    }
786}
787
788/// Default scaling list for a given 8x8 list index (Table 7-2 "Default scaling list" column).
789/// Index is local (0-based within the 8x8 array): even = Intra, odd = Inter.
790fn default_8x8(i: usize) -> &'static [u8; 64] {
791    if i % 2 == 0 {
792        &DEFAULT_8X8_INTRA
793    } else {
794        &DEFAULT_8X8_INTER
795    }
796}
797
798/// Resolve stored `next_scale` intermediate values into final `ScalingList` values
799/// (section 7.3.2.1.1.1).
800///
801/// When the first element is 0 (`useDefaultScalingMatrixFlag`), returns `default`.
802/// Otherwise, replaces trailing zeros with the last non-zero value (carry-forward).
803fn resolve_list<const S: usize>(next_scales: &[u8; S], default: &[u8; S]) -> [u8; S] {
804    if next_scales[0] == 0 {
805        return *default;
806    }
807    let mut result = [0u8; S];
808    let mut last = 0u8;
809    for j in 0..S {
810        if next_scales[j] != 0 {
811            last = next_scales[j];
812        }
813        result[j] = last;
814    }
815    result
816}
817
818/// Resolve 4x4 scaling lists with fallback rules from Table 7-2.
819///
820/// `sps_fallback` is `None` for rule set A (SPS context), or `Some` with the
821/// SPS-resolved lists for rule set B (PPS context with `seq_scaling_matrix_present_flag` = 1).
822pub(crate) fn resolve_4x4_lists(
823    lists: &[Option<[u8; 16]>; 6],
824    sps_fallback: Option<&[[u8; 16]; 6]>,
825) -> [[u8; 16]; 6] {
826    let mut result = [[0u8; 16]; 6];
827    for i in 0..6 {
828        result[i] = match &lists[i] {
829            Some(ns) => resolve_list(ns, default_4x4(i)),
830            None => match (i, sps_fallback) {
831                (0, Some(sps)) | (3, Some(sps)) => sps[i],
832                (0, None) => DEFAULT_4X4_INTRA,
833                (3, None) => DEFAULT_4X4_INTER,
834                _ => result[i - 1],
835            },
836        };
837    }
838    result
839}
840
841/// Resolve 8x8 scaling lists with fallback rules from Table 7-2.
842///
843/// `sps_fallback` is `None` for rule set A, or `Some` with the SPS-resolved 8x8
844/// lists (as a slice) for rule set B.
845pub(crate) fn resolve_8x8_lists<const C: usize>(
846    lists: &[Option<[u8; 64]>; C],
847    sps_fallback: Option<&[[u8; 64]]>,
848) -> [[u8; 64]; C] {
849    let mut result = [[0u8; 64]; C];
850    for i in 0..C {
851        result[i] = match &lists[i] {
852            Some(ns) => resolve_list(ns, default_8x8(i)),
853            None => match (i, sps_fallback) {
854                (0, Some(sps)) | (1, Some(sps)) => sps[i],
855                (0, None) => DEFAULT_8X8_INTRA,
856                (1, None) => DEFAULT_8X8_INTER,
857                _ => result[i - 2],
858            },
859        };
860    }
861    result
862}
863
864#[derive(Debug, Default, Clone, PartialEq, Eq)]
865pub struct ChromaInfo {
866    pub chroma_format: ChromaFormat,
867    pub separate_colour_plane_flag: bool,
868    pub bit_depth_luma_minus8: u8,
869    pub bit_depth_chroma_minus8: u8,
870    pub qpprime_y_zero_transform_bypass_flag: bool,
871    pub scaling_matrix: Option<Box<SeqScalingMatrix>>,
872}
873impl ChromaInfo {
874    /// Returns the resolved sequence-level 4x4 scaling lists.
875    ///
876    /// When `seq_scaling_matrix_present_flag` is 0 (no scaling matrix), returns
877    /// `Flat_4x4_16` (all values 16) for every list. Otherwise delegates to
878    /// [`SeqScalingMatrix::scaling_lists_4x4`].
879    pub fn scaling_lists_4x4(&self) -> [[u8; 16]; 6] {
880        match &self.scaling_matrix {
881            Some(m) => m.scaling_lists_4x4(),
882            None => [FLAT_4X4; 6],
883        }
884    }
885
886    /// Returns the resolved sequence-level 8x8 scaling lists.
887    ///
888    /// When `seq_scaling_matrix_present_flag` is 0, returns `Flat_8x8_16` for every
889    /// list. The variant (`Y` vs `YCbCr`) is determined by `chroma_format`.
890    pub fn scaling_lists_8x8(&self) -> ScalingLists8x8Resolved {
891        match &self.scaling_matrix {
892            Some(m) => m.scaling_lists_8x8(),
893            None => match self.chroma_format {
894                ChromaFormat::YUV444 => ScalingLists8x8Resolved::YCbCr([FLAT_8X8; 6]),
895                _ => ScalingLists8x8Resolved::Y([FLAT_8X8; 2]),
896            },
897        }
898    }
899
900    /// Returns `ChromaArrayType` as defined by the spec: 0 if `separate_colour_plane_flag` is
901    /// true, otherwise equal to `chroma_format_idc`.
902    pub fn chroma_array_type(&self) -> u8 {
903        if self.separate_colour_plane_flag {
904            0
905        } else {
906            self.chroma_format.to_u32() as u8
907        }
908    }
909
910    pub fn read<R: BitRead>(r: &mut R, profile_idc: ProfileIdc) -> Result<ChromaInfo, SpsError> {
911        if profile_idc.has_chroma_info() {
912            let chroma_format_idc = r.read_ue("chroma_format_idc")?;
913            let chroma_format = ChromaFormat::from_chroma_format_idc(chroma_format_idc);
914            Ok(ChromaInfo {
915                chroma_format,
916                separate_colour_plane_flag: if chroma_format_idc == 3 {
917                    r.read_bit("separate_colour_plane_flag")?
918                } else {
919                    false
920                },
921                bit_depth_luma_minus8: Self::read_bit_depth_minus8(r)?,
922                bit_depth_chroma_minus8: Self::read_bit_depth_minus8(r)?,
923                qpprime_y_zero_transform_bypass_flag: r
924                    .read_bit("qpprime_y_zero_transform_bypass_flag")?,
925                scaling_matrix: Self::read_scaling_matrix(r, chroma_format)?,
926            })
927        } else {
928            Ok(ChromaInfo::default())
929        }
930    }
931    fn read_bit_depth_minus8<R: BitRead>(r: &mut R) -> Result<u8, SpsError> {
932        let value = r.read_ue("read_bit_depth_minus8")?;
933        if value > 6 {
934            Err(SpsError::BitDepthOutOfRange(value))
935        } else {
936            Ok(value as u8)
937        }
938    }
939    fn read_scaling_matrix<R: BitRead>(
940        r: &mut R,
941        chroma_format: ChromaFormat,
942    ) -> Result<Option<Box<SeqScalingMatrix>>, SpsError> {
943        let scaling_matrix_present_flag = r.read_bit("scaling_matrix_present_flag")?;
944        if scaling_matrix_present_flag {
945            Ok(Some(Box::new(
946                SeqScalingMatrix::read(r, chroma_format).map_err(SpsError::ScalingMatrix)?,
947            )))
948        } else {
949            Ok(None)
950        }
951    }
952
953    fn write<W: BitWrite>(&self, w: &mut W, profile_idc: ProfileIdc) -> std::io::Result<()> {
954        if profile_idc.has_chroma_info() {
955            let chroma_format_idc = self.chroma_format.to_u32();
956            w.write_ue(chroma_format_idc)?;
957            if chroma_format_idc == 3 {
958                w.write_bit(self.separate_colour_plane_flag)?;
959            }
960            w.write_ue(self.bit_depth_luma_minus8 as u32)?;
961            w.write_ue(self.bit_depth_chroma_minus8 as u32)?;
962            w.write_bit(self.qpprime_y_zero_transform_bypass_flag)?;
963            match &self.scaling_matrix {
964                None => w.write_bit(false)?,
965                Some(matrix) => {
966                    w.write_bit(true)?;
967                    matrix.write(w, self.chroma_format)?;
968                }
969            }
970        }
971        Ok(())
972    }
973}
974
975#[derive(Debug)]
976pub enum PicOrderCntError {
977    InvalidPicOrderCountType(u32),
978    ReaderError(BitReaderError),
979    /// log2_max_pic_order_cnt_lsb_minus4 must be between 0 and 12
980    Log2MaxPicOrderCntLsbMinus4OutOfRange(u32),
981    /// num_ref_frames_in_pic_order_cnt_cycle must be between 0 and 255
982    NumRefFramesInPicOrderCntCycleOutOfRange(u32),
983}
984
985impl From<BitReaderError> for PicOrderCntError {
986    fn from(e: BitReaderError) -> Self {
987        PicOrderCntError::ReaderError(e)
988    }
989}
990
991#[derive(Clone, Debug, PartialEq, Eq)]
992pub enum PicOrderCntType {
993    TypeZero {
994        log2_max_pic_order_cnt_lsb_minus4: u8,
995    },
996    TypeOne {
997        delta_pic_order_always_zero_flag: bool,
998        offset_for_non_ref_pic: i32,
999        offset_for_top_to_bottom_field: i32,
1000        offsets_for_ref_frame: Vec<i32>,
1001    },
1002    TypeTwo,
1003}
1004impl PicOrderCntType {
1005    fn read<R: BitRead>(r: &mut R) -> Result<PicOrderCntType, PicOrderCntError> {
1006        let pic_order_cnt_type = r.read_ue("pic_order_cnt_type")?;
1007        match pic_order_cnt_type {
1008            0 => Ok(PicOrderCntType::TypeZero {
1009                log2_max_pic_order_cnt_lsb_minus4: Self::read_log2_max_pic_order_cnt_lsb_minus4(r)?,
1010            }),
1011            1 => Ok(PicOrderCntType::TypeOne {
1012                delta_pic_order_always_zero_flag: r.read_bit("delta_pic_order_always_zero_flag")?,
1013                offset_for_non_ref_pic: r.read_se("offset_for_non_ref_pic")?,
1014                offset_for_top_to_bottom_field: r.read_se("offset_for_top_to_bottom_field")?,
1015                offsets_for_ref_frame: Self::read_offsets_for_ref_frame(r)?,
1016            }),
1017            2 => Ok(PicOrderCntType::TypeTwo),
1018            _ => Err(PicOrderCntError::InvalidPicOrderCountType(
1019                pic_order_cnt_type,
1020            )),
1021        }
1022    }
1023
1024    fn read_log2_max_pic_order_cnt_lsb_minus4<R: BitRead>(
1025        r: &mut R,
1026    ) -> Result<u8, PicOrderCntError> {
1027        let val = r.read_ue("log2_max_pic_order_cnt_lsb_minus4")?;
1028        if val > 12 {
1029            Err(PicOrderCntError::Log2MaxPicOrderCntLsbMinus4OutOfRange(val))
1030        } else {
1031            Ok(val as u8)
1032        }
1033    }
1034
1035    fn read_offsets_for_ref_frame<R: BitRead>(r: &mut R) -> Result<Vec<i32>, PicOrderCntError> {
1036        let num_ref_frames_in_pic_order_cnt_cycle =
1037            r.read_ue("num_ref_frames_in_pic_order_cnt_cycle")?;
1038        if num_ref_frames_in_pic_order_cnt_cycle > 255 {
1039            return Err(PicOrderCntError::NumRefFramesInPicOrderCntCycleOutOfRange(
1040                num_ref_frames_in_pic_order_cnt_cycle,
1041            ));
1042        }
1043        let mut offsets = Vec::with_capacity(num_ref_frames_in_pic_order_cnt_cycle as usize);
1044        for _ in 0..num_ref_frames_in_pic_order_cnt_cycle {
1045            offsets.push(r.read_se("offset_for_ref_frame")?);
1046        }
1047        Ok(offsets)
1048    }
1049
1050    fn write<W: BitWrite>(&self, w: &mut W) -> std::io::Result<()> {
1051        match self {
1052            PicOrderCntType::TypeZero {
1053                log2_max_pic_order_cnt_lsb_minus4,
1054            } => {
1055                w.write_ue(0)?;
1056                w.write_ue(*log2_max_pic_order_cnt_lsb_minus4 as u32)?;
1057            }
1058            PicOrderCntType::TypeOne {
1059                delta_pic_order_always_zero_flag,
1060                offset_for_non_ref_pic,
1061                offset_for_top_to_bottom_field,
1062                offsets_for_ref_frame,
1063            } => {
1064                w.write_ue(1)?;
1065                w.write_bit(*delta_pic_order_always_zero_flag)?;
1066                w.write_se(*offset_for_non_ref_pic)?;
1067                w.write_se(*offset_for_top_to_bottom_field)?;
1068                w.write_ue(offsets_for_ref_frame.len() as u32)?;
1069                for &offset in offsets_for_ref_frame {
1070                    w.write_se(offset)?;
1071                }
1072            }
1073            PicOrderCntType::TypeTwo => {
1074                w.write_ue(2)?;
1075            }
1076        }
1077        Ok(())
1078    }
1079}
1080
1081#[derive(Clone, Debug, PartialEq, Eq)]
1082pub enum FrameMbsFlags {
1083    Frames,
1084    Fields { mb_adaptive_frame_field_flag: bool },
1085}
1086impl FrameMbsFlags {
1087    fn read<R: BitRead>(r: &mut R) -> Result<FrameMbsFlags, BitReaderError> {
1088        let frame_mbs_only_flag = r.read_bit("frame_mbs_only_flag")?;
1089        if frame_mbs_only_flag {
1090            Ok(FrameMbsFlags::Frames)
1091        } else {
1092            Ok(FrameMbsFlags::Fields {
1093                mb_adaptive_frame_field_flag: r.read_bit("mb_adaptive_frame_field_flag")?,
1094            })
1095        }
1096    }
1097
1098    fn write<W: BitWrite>(&self, w: &mut W) -> std::io::Result<()> {
1099        match self {
1100            FrameMbsFlags::Frames => w.write_bit(true),
1101            FrameMbsFlags::Fields {
1102                mb_adaptive_frame_field_flag,
1103            } => {
1104                w.write_bit(false)?;
1105                w.write_bit(*mb_adaptive_frame_field_flag)
1106            }
1107        }
1108    }
1109}
1110
1111#[derive(Clone, Debug, Default, PartialEq, Eq)]
1112pub struct FrameCropping {
1113    pub left_offset: u32,
1114    pub right_offset: u32,
1115    pub top_offset: u32,
1116    pub bottom_offset: u32,
1117}
1118impl FrameCropping {
1119    fn read<R: BitRead>(r: &mut R) -> Result<Option<FrameCropping>, BitReaderError> {
1120        let frame_cropping_flag = r.read_bit("frame_cropping_flag")?;
1121        Ok(if frame_cropping_flag {
1122            Some(FrameCropping {
1123                left_offset: r.read_ue("left_offset")?,
1124                right_offset: r.read_ue("right_offset")?,
1125                top_offset: r.read_ue("top_offset")?,
1126                bottom_offset: r.read_ue("bottom_offset")?,
1127            })
1128        } else {
1129            None
1130        })
1131    }
1132
1133    fn write<W: BitWrite>(this: Option<&Self>, w: &mut W) -> std::io::Result<()> {
1134        match this {
1135            None => w.write_bit(false),
1136            Some(crop) => {
1137                w.write_bit(true)?;
1138                w.write_ue(crop.left_offset)?;
1139                w.write_ue(crop.right_offset)?;
1140                w.write_ue(crop.top_offset)?;
1141                w.write_ue(crop.bottom_offset)
1142            }
1143        }
1144    }
1145}
1146
1147#[derive(Clone, Debug, Default, PartialEq, Eq)]
1148pub enum AspectRatioInfo {
1149    #[default]
1150    Unspecified,
1151    Ratio1_1,
1152    Ratio12_11,
1153    Ratio10_11,
1154    Ratio16_11,
1155    Ratio40_33,
1156    Ratio24_11,
1157    Ratio20_11,
1158    Ratio32_11,
1159    Ratio80_33,
1160    Ratio18_11,
1161    Ratio15_11,
1162    Ratio64_33,
1163    Ratio160_99,
1164    Ratio4_3,
1165    Ratio3_2,
1166    Ratio2_1,
1167    Reserved(u8),
1168    Extended(u16, u16),
1169}
1170impl AspectRatioInfo {
1171    fn read<R: BitRead>(r: &mut R) -> Result<Option<AspectRatioInfo>, BitReaderError> {
1172        let aspect_ratio_info_present_flag = r.read_bit("aspect_ratio_info_present_flag")?;
1173        Ok(if aspect_ratio_info_present_flag {
1174            let aspect_ratio_idc = r.read::<8, _>("aspect_ratio_idc")?;
1175            Some(match aspect_ratio_idc {
1176                0 => AspectRatioInfo::Unspecified,
1177                1 => AspectRatioInfo::Ratio1_1,
1178                2 => AspectRatioInfo::Ratio12_11,
1179                3 => AspectRatioInfo::Ratio10_11,
1180                4 => AspectRatioInfo::Ratio16_11,
1181                5 => AspectRatioInfo::Ratio40_33,
1182                6 => AspectRatioInfo::Ratio24_11,
1183                7 => AspectRatioInfo::Ratio20_11,
1184                8 => AspectRatioInfo::Ratio32_11,
1185                9 => AspectRatioInfo::Ratio80_33,
1186                10 => AspectRatioInfo::Ratio18_11,
1187                11 => AspectRatioInfo::Ratio15_11,
1188                12 => AspectRatioInfo::Ratio64_33,
1189                13 => AspectRatioInfo::Ratio160_99,
1190                14 => AspectRatioInfo::Ratio4_3,
1191                15 => AspectRatioInfo::Ratio3_2,
1192                16 => AspectRatioInfo::Ratio2_1,
1193                255 => AspectRatioInfo::Extended(
1194                    r.read::<16, _>("sar_width")?,
1195                    r.read::<16, _>("sar_height")?,
1196                ),
1197                _ => AspectRatioInfo::Reserved(aspect_ratio_idc),
1198            })
1199        } else {
1200            None
1201        })
1202    }
1203
1204    /// Returns the aspect ratio as `(width, height)`, if specified.
1205    pub fn get(&self) -> Option<(u16, u16)> {
1206        match self {
1207            AspectRatioInfo::Unspecified => None,
1208            AspectRatioInfo::Ratio1_1 => Some((1, 1)),
1209            AspectRatioInfo::Ratio12_11 => Some((12, 11)),
1210            AspectRatioInfo::Ratio10_11 => Some((10, 11)),
1211            AspectRatioInfo::Ratio16_11 => Some((16, 11)),
1212            AspectRatioInfo::Ratio40_33 => Some((40, 33)),
1213            AspectRatioInfo::Ratio24_11 => Some((24, 11)),
1214            AspectRatioInfo::Ratio20_11 => Some((20, 11)),
1215            AspectRatioInfo::Ratio32_11 => Some((32, 11)),
1216            AspectRatioInfo::Ratio80_33 => Some((80, 33)),
1217            AspectRatioInfo::Ratio18_11 => Some((18, 11)),
1218            AspectRatioInfo::Ratio15_11 => Some((15, 11)),
1219            AspectRatioInfo::Ratio64_33 => Some((64, 33)),
1220            AspectRatioInfo::Ratio160_99 => Some((160, 99)),
1221            AspectRatioInfo::Ratio4_3 => Some((4, 3)),
1222            AspectRatioInfo::Ratio3_2 => Some((3, 2)),
1223            AspectRatioInfo::Ratio2_1 => Some((2, 1)),
1224            AspectRatioInfo::Reserved(_) => None,
1225            &AspectRatioInfo::Extended(width, height) => {
1226                // ISO/IEC 14496-10 section E.2.1: "When ... sar_width is equal to 0 or sar_height
1227                // is equal to 0, the sample aspect ratio shall be considered unspecified by this
1228                // Recommendation | International Standard."
1229                if width == 0 || height == 0 {
1230                    None
1231                } else {
1232                    Some((width, height))
1233                }
1234            }
1235        }
1236    }
1237
1238    pub fn to_u8(&self) -> u8 {
1239        match self {
1240            AspectRatioInfo::Unspecified => 0,
1241            AspectRatioInfo::Ratio1_1 => 1,
1242            AspectRatioInfo::Ratio12_11 => 2,
1243            AspectRatioInfo::Ratio10_11 => 3,
1244            AspectRatioInfo::Ratio16_11 => 4,
1245            AspectRatioInfo::Ratio40_33 => 5,
1246            AspectRatioInfo::Ratio24_11 => 6,
1247            AspectRatioInfo::Ratio20_11 => 7,
1248            AspectRatioInfo::Ratio32_11 => 8,
1249            AspectRatioInfo::Ratio80_33 => 9,
1250            AspectRatioInfo::Ratio18_11 => 10,
1251            AspectRatioInfo::Ratio15_11 => 11,
1252            AspectRatioInfo::Ratio64_33 => 12,
1253            AspectRatioInfo::Ratio160_99 => 13,
1254            AspectRatioInfo::Ratio4_3 => 14,
1255            AspectRatioInfo::Ratio3_2 => 15,
1256            AspectRatioInfo::Ratio2_1 => 16,
1257            AspectRatioInfo::Reserved(aspect_ratio_idc) => *aspect_ratio_idc,
1258            AspectRatioInfo::Extended(..) => 255,
1259        }
1260    }
1261
1262    fn write<W: BitWrite>(this: Option<&Self>, w: &mut W) -> std::io::Result<()> {
1263        match this {
1264            None => w.write_bit(false),
1265            Some(ari) => {
1266                w.write_bit(true)?;
1267                w.write::<8, u8>(ari.to_u8())?;
1268                if let AspectRatioInfo::Extended(width, height) = ari {
1269                    w.write::<16, u16>(*width)?;
1270                    w.write::<16, u16>(*height)?;
1271                }
1272                Ok(())
1273            }
1274        }
1275    }
1276}
1277
1278#[derive(Clone, Debug, Default, PartialEq, Eq)]
1279pub enum OverscanAppropriate {
1280    #[default]
1281    Unspecified,
1282    Appropriate,
1283    Inappropriate,
1284}
1285impl OverscanAppropriate {
1286    fn read<R: BitRead>(r: &mut R) -> Result<OverscanAppropriate, BitReaderError> {
1287        let overscan_info_present_flag = r.read_bit("overscan_info_present_flag")?;
1288        Ok(if overscan_info_present_flag {
1289            let overscan_appropriate_flag = r.read_bit("overscan_appropriate_flag")?;
1290            if overscan_appropriate_flag {
1291                OverscanAppropriate::Appropriate
1292            } else {
1293                OverscanAppropriate::Inappropriate
1294            }
1295        } else {
1296            OverscanAppropriate::Unspecified
1297        })
1298    }
1299
1300    fn write<W: BitWrite>(&self, w: &mut W) -> std::io::Result<()> {
1301        match self {
1302            OverscanAppropriate::Unspecified => w.write_bit(false),
1303            OverscanAppropriate::Appropriate => {
1304                w.write_bit(true)?;
1305                w.write_bit(true)
1306            }
1307            OverscanAppropriate::Inappropriate => {
1308                w.write_bit(true)?;
1309                w.write_bit(false)
1310            }
1311        }
1312    }
1313}
1314
1315#[derive(Clone, Debug, Default, PartialEq, Eq)]
1316pub enum VideoFormat {
1317    #[default]
1318    Component,
1319    PAL,
1320    NTSC,
1321    SECAM,
1322    MAC,
1323    Unspecified,
1324    Reserved(u8),
1325}
1326impl VideoFormat {
1327    fn from(video_format: u8) -> VideoFormat {
1328        match video_format {
1329            0 => VideoFormat::Component,
1330            1 => VideoFormat::PAL,
1331            2 => VideoFormat::NTSC,
1332            3 => VideoFormat::SECAM,
1333            4 => VideoFormat::MAC,
1334            5 => VideoFormat::Unspecified,
1335            6 | 7 => VideoFormat::Reserved(video_format),
1336            _ => panic!("unsupported video_format value {}", video_format),
1337        }
1338    }
1339    pub fn to_u8(&self) -> u8 {
1340        match self {
1341            VideoFormat::Component => 0,
1342            VideoFormat::PAL => 1,
1343            VideoFormat::NTSC => 2,
1344            VideoFormat::SECAM => 3,
1345            VideoFormat::MAC => 4,
1346            VideoFormat::Unspecified => 5,
1347            VideoFormat::Reserved(video_format) => *video_format,
1348        }
1349    }
1350}
1351
1352#[derive(Clone, Debug, Default, PartialEq, Eq)]
1353pub struct ColourDescription {
1354    pub colour_primaries: u8,
1355    pub transfer_characteristics: u8,
1356    pub matrix_coefficients: u8,
1357}
1358impl ColourDescription {
1359    fn read<R: BitRead>(r: &mut R) -> Result<Option<ColourDescription>, BitReaderError> {
1360        let colour_description_present_flag = r.read_bit("colour_description_present_flag")?;
1361        Ok(if colour_description_present_flag {
1362            Some(ColourDescription {
1363                colour_primaries: r.read::<8, _>("colour_primaries")?,
1364                transfer_characteristics: r.read::<8, _>("transfer_characteristics")?,
1365                matrix_coefficients: r.read::<8, _>("matrix_coefficients")?,
1366            })
1367        } else {
1368            None
1369        })
1370    }
1371
1372    fn write<W: BitWrite>(this: Option<&Self>, w: &mut W) -> std::io::Result<()> {
1373        match this {
1374            None => w.write_bit(false),
1375            Some(cd) => {
1376                w.write_bit(true)?;
1377                w.write::<8, u8>(cd.colour_primaries)?;
1378                w.write::<8, u8>(cd.transfer_characteristics)?;
1379                w.write::<8, u8>(cd.matrix_coefficients)
1380            }
1381        }
1382    }
1383}
1384
1385#[derive(Clone, Debug, Default, PartialEq, Eq)]
1386pub struct VideoSignalType {
1387    pub video_format: VideoFormat,
1388    pub video_full_range_flag: bool,
1389    pub colour_description: Option<ColourDescription>,
1390}
1391impl VideoSignalType {
1392    fn read<R: BitRead>(r: &mut R) -> Result<Option<VideoSignalType>, BitReaderError> {
1393        let video_signal_type_present_flag = r.read_bit("video_signal_type_present_flag")?;
1394        Ok(if video_signal_type_present_flag {
1395            Some(VideoSignalType {
1396                video_format: VideoFormat::from(r.read::<3, _>("video_format")?),
1397                video_full_range_flag: r.read_bit("video_full_range_flag")?,
1398                colour_description: ColourDescription::read(r)?,
1399            })
1400        } else {
1401            None
1402        })
1403    }
1404
1405    fn write<W: BitWrite>(this: Option<&Self>, w: &mut W) -> std::io::Result<()> {
1406        match this {
1407            None => w.write_bit(false),
1408            Some(vst) => {
1409                w.write_bit(true)?;
1410                w.write::<3, u8>(vst.video_format.to_u8())?;
1411                w.write_bit(vst.video_full_range_flag)?;
1412                ColourDescription::write(vst.colour_description.as_ref(), w)
1413            }
1414        }
1415    }
1416}
1417
1418#[derive(Clone, Debug, Default, PartialEq, Eq)]
1419pub struct ChromaLocInfo {
1420    pub chroma_sample_loc_type_top_field: u32,
1421    pub chroma_sample_loc_type_bottom_field: u32,
1422}
1423impl ChromaLocInfo {
1424    fn read<R: BitRead>(r: &mut R) -> Result<Option<ChromaLocInfo>, BitReaderError> {
1425        let chroma_loc_info_present_flag = r.read_bit("chroma_loc_info_present_flag")?;
1426        Ok(if chroma_loc_info_present_flag {
1427            Some(ChromaLocInfo {
1428                chroma_sample_loc_type_top_field: r.read_ue("chroma_sample_loc_type_top_field")?,
1429                chroma_sample_loc_type_bottom_field: r
1430                    .read_ue("chroma_sample_loc_type_bottom_field")?,
1431            })
1432        } else {
1433            None
1434        })
1435    }
1436
1437    fn write<W: BitWrite>(this: Option<&Self>, w: &mut W) -> std::io::Result<()> {
1438        match this {
1439            None => w.write_bit(false),
1440            Some(cli) => {
1441                w.write_bit(true)?;
1442                w.write_ue(cli.chroma_sample_loc_type_top_field)?;
1443                w.write_ue(cli.chroma_sample_loc_type_bottom_field)
1444            }
1445        }
1446    }
1447}
1448
1449#[derive(Clone, Debug, Default, PartialEq, Eq)]
1450pub struct TimingInfo {
1451    pub num_units_in_tick: u32,
1452    pub time_scale: u32,
1453    pub fixed_frame_rate_flag: bool,
1454}
1455impl TimingInfo {
1456    pub(crate) fn read<R: BitRead>(r: &mut R) -> Result<Option<TimingInfo>, BitReaderError> {
1457        let timing_info_present_flag = r.read_bit("timing_info_present_flag")?;
1458        Ok(if timing_info_present_flag {
1459            Some(TimingInfo {
1460                num_units_in_tick: r.read::<32, _>("num_units_in_tick")?,
1461                time_scale: r.read::<32, _>("time_scale")?,
1462                fixed_frame_rate_flag: r.read_bit("fixed_frame_rate_flag")?,
1463            })
1464        } else {
1465            None
1466        })
1467    }
1468
1469    fn write<W: BitWrite>(this: Option<&Self>, w: &mut W) -> std::io::Result<()> {
1470        match this {
1471            None => w.write_bit(false),
1472            Some(ti) => {
1473                w.write_bit(true)?;
1474                w.write::<32, u32>(ti.num_units_in_tick)?;
1475                w.write::<32, u32>(ti.time_scale)?;
1476                w.write_bit(ti.fixed_frame_rate_flag)
1477            }
1478        }
1479    }
1480}
1481
1482#[derive(Clone, Debug, Default, PartialEq, Eq)]
1483pub struct CpbSpec {
1484    pub bit_rate_value_minus1: u32,
1485    pub cpb_size_value_minus1: u32,
1486    pub cbr_flag: bool,
1487}
1488impl CpbSpec {
1489    fn read<R: BitRead>(r: &mut R) -> Result<CpbSpec, BitReaderError> {
1490        Ok(CpbSpec {
1491            bit_rate_value_minus1: r.read_ue("bit_rate_value_minus1")?,
1492            cpb_size_value_minus1: r.read_ue("cpb_size_value_minus1")?,
1493            cbr_flag: r.read_bit("cbr_flag")?,
1494        })
1495    }
1496}
1497
1498#[derive(Clone, Debug, Default, PartialEq, Eq)]
1499pub struct HrdParameters {
1500    pub bit_rate_scale: u8,
1501    pub cpb_size_scale: u8,
1502    pub cpb_specs: Vec<CpbSpec>,
1503    pub initial_cpb_removal_delay_length_minus1: u8,
1504    pub cpb_removal_delay_length_minus1: u8,
1505    pub dpb_output_delay_length_minus1: u8,
1506    pub time_offset_length: u8,
1507}
1508impl HrdParameters {
1509    pub(crate) fn read<R: BitRead>(
1510        r: &mut R,
1511        hrd_parameters_present: &mut bool,
1512    ) -> Result<Option<HrdParameters>, SpsError> {
1513        let hrd_parameters_present_flag = r.read_bit("hrd_parameters_present_flag")?;
1514        *hrd_parameters_present |= hrd_parameters_present_flag;
1515        Ok(if hrd_parameters_present_flag {
1516            let cpb_cnt_minus1 = r.read_ue("cpb_cnt_minus1")?;
1517            if cpb_cnt_minus1 > 31 {
1518                return Err(SpsError::CpbCountOutOfRange(cpb_cnt_minus1));
1519            }
1520            let cpb_cnt = cpb_cnt_minus1 + 1;
1521            Some(HrdParameters {
1522                bit_rate_scale: r.read::<4, _>("bit_rate_scale")?,
1523                cpb_size_scale: r.read::<4, _>("cpb_size_scale")?,
1524                cpb_specs: Self::read_cpb_specs(r, cpb_cnt)?,
1525                initial_cpb_removal_delay_length_minus1: r
1526                    .read::<5, _>("initial_cpb_removal_delay_length_minus1")?,
1527                cpb_removal_delay_length_minus1: r
1528                    .read::<5, _>("cpb_removal_delay_length_minus1")?,
1529                dpb_output_delay_length_minus1: r.read::<5, _>("dpb_output_delay_length_minus1")?,
1530                time_offset_length: r.read::<5, _>("time_offset_length")?,
1531            })
1532        } else {
1533            None
1534        })
1535    }
1536    fn read_cpb_specs<R: BitRead>(r: &mut R, cpb_cnt: u32) -> Result<Vec<CpbSpec>, BitReaderError> {
1537        let mut cpb_specs = Vec::with_capacity(cpb_cnt as usize);
1538        for _ in 0..cpb_cnt {
1539            cpb_specs.push(CpbSpec::read(r)?);
1540        }
1541        Ok(cpb_specs)
1542    }
1543
1544    fn write<W: BitWrite>(this: Option<&Self>, w: &mut W) -> std::io::Result<()> {
1545        match this {
1546            None => w.write_bit(false),
1547            Some(hrd) => {
1548                w.write_bit(true)?;
1549                w.write_ue(hrd.cpb_specs.len() as u32 - 1)?;
1550                w.write::<4, u8>(hrd.bit_rate_scale)?;
1551                w.write::<4, u8>(hrd.cpb_size_scale)?;
1552                for spec in &hrd.cpb_specs {
1553                    w.write_ue(spec.bit_rate_value_minus1)?;
1554                    w.write_ue(spec.cpb_size_value_minus1)?;
1555                    w.write_bit(spec.cbr_flag)?;
1556                }
1557                w.write::<5, u8>(hrd.initial_cpb_removal_delay_length_minus1)?;
1558                w.write::<5, u8>(hrd.cpb_removal_delay_length_minus1)?;
1559                w.write::<5, u8>(hrd.dpb_output_delay_length_minus1)?;
1560                w.write::<5, u8>(hrd.time_offset_length)
1561            }
1562        }
1563    }
1564}
1565
1566#[derive(Clone, Debug, Default, PartialEq, Eq)]
1567pub struct BitstreamRestrictions {
1568    pub motion_vectors_over_pic_boundaries_flag: bool,
1569    pub max_bytes_per_pic_denom: u32,
1570    pub max_bits_per_mb_denom: u32,
1571    pub log2_max_mv_length_horizontal: u32,
1572    pub log2_max_mv_length_vertical: u32,
1573    pub max_num_reorder_frames: u32,
1574    pub max_dec_frame_buffering: u32,
1575}
1576impl BitstreamRestrictions {
1577    fn read<R: BitRead>(
1578        r: &mut R,
1579        sps: &SeqParameterSet,
1580    ) -> Result<Option<BitstreamRestrictions>, SpsError> {
1581        let bitstream_restriction_flag = r.read_bit("bitstream_restriction_flag")?;
1582        Ok(if bitstream_restriction_flag {
1583            let motion_vectors_over_pic_boundaries_flag =
1584                r.read_bit("motion_vectors_over_pic_boundaries_flag")?;
1585            let max_bytes_per_pic_denom = r.read_ue("max_bytes_per_pic_denom")?;
1586            if max_bytes_per_pic_denom > 16 {
1587                return Err(SpsError::FieldValueTooLarge {
1588                    name: "max_bytes_per_pic_denom",
1589                    value: max_bytes_per_pic_denom,
1590                });
1591            }
1592            let max_bits_per_mb_denom = r.read_ue("max_bits_per_mb_denom")?;
1593            if max_bits_per_mb_denom > 16 {
1594                return Err(SpsError::FieldValueTooLarge {
1595                    name: "max_bits_per_mb_denom",
1596                    value: max_bits_per_mb_denom,
1597                });
1598            }
1599            // more recent versions of the spec say log2_max_mv_length_horizontal and
1600            // log2_max_mv_length_vertical - "shall be in the range of 0 to 15, inclusive."
1601            // However, older versions of the spec say 0 to 16, and real bitstreams present 16, so
1602            // we apply the more-permissive check to avoid rejecting real files.
1603            let log2_max_mv_length_horizontal = r.read_ue("log2_max_mv_length_horizontal")?;
1604            if log2_max_mv_length_horizontal > 16 {
1605                return Err(SpsError::FieldValueTooLarge {
1606                    name: "log2_max_mv_length_horizontal",
1607                    value: log2_max_mv_length_horizontal,
1608                });
1609            }
1610            let log2_max_mv_length_vertical = r.read_ue("log2_max_mv_length_vertical")?;
1611            if log2_max_mv_length_vertical > 16 {
1612                return Err(SpsError::FieldValueTooLarge {
1613                    name: "log2_max_mv_length_vertical",
1614                    value: log2_max_mv_length_vertical,
1615                });
1616            }
1617            let max_num_reorder_frames = r.read_ue("max_num_reorder_frames")?;
1618            let max_dec_frame_buffering = r.read_ue("max_dec_frame_buffering")?;
1619            if max_num_reorder_frames > max_dec_frame_buffering {
1620                return Err(SpsError::FieldValueTooLarge {
1621                    name: "max_num_reorder_frames",
1622                    value: max_num_reorder_frames,
1623                });
1624            }
1625            // "The value of max_dec_frame_buffering shall be greater than or equal to
1626            // max_num_ref_frames."
1627            if max_dec_frame_buffering < sps.max_num_ref_frames {
1628                return Err(SpsError::FieldValueTooSmall {
1629                    name: "max_dec_frame_buffering",
1630                    value: max_dec_frame_buffering,
1631                });
1632            }
1633            if let Some(max) = max_val_for_max_dec_frame_buffering(sps) {
1634                if max_dec_frame_buffering > max {
1635                    return Err(SpsError::FieldValueTooLarge {
1636                        name: "max_dec_frame_buffering",
1637                        value: max_dec_frame_buffering,
1638                    });
1639                }
1640            }
1641            Some(BitstreamRestrictions {
1642                motion_vectors_over_pic_boundaries_flag,
1643                max_bytes_per_pic_denom,
1644                max_bits_per_mb_denom,
1645                log2_max_mv_length_horizontal,
1646                log2_max_mv_length_vertical,
1647                max_num_reorder_frames,
1648                max_dec_frame_buffering,
1649            })
1650        } else {
1651            None
1652        })
1653    }
1654
1655    fn write<W: BitWrite>(this: Option<&Self>, w: &mut W) -> std::io::Result<()> {
1656        match this {
1657            None => w.write_bit(false),
1658            Some(br) => {
1659                w.write_bit(true)?;
1660                w.write_bit(br.motion_vectors_over_pic_boundaries_flag)?;
1661                w.write_ue(br.max_bytes_per_pic_denom)?;
1662                w.write_ue(br.max_bits_per_mb_denom)?;
1663                w.write_ue(br.log2_max_mv_length_horizontal)?;
1664                w.write_ue(br.log2_max_mv_length_vertical)?;
1665                w.write_ue(br.max_num_reorder_frames)?;
1666                w.write_ue(br.max_dec_frame_buffering)
1667            }
1668        }
1669    }
1670}
1671
1672/// Calculates the maximum allowed value for the `max_dec_frame_buffering` field,
1673/// returning `None` for profiles/levels where the check cannot be performed.
1674fn max_val_for_max_dec_frame_buffering(sps: &SeqParameterSet) -> Option<u32> {
1675    let level = sps.level();
1676    let profile = sps.profile();
1677    let pic_width_in_mbs = sps.pic_width_in_mbs();
1678    let frame_height_in_mbs = match sps.frame_mbs_flags {
1679        FrameMbsFlags::Frames => sps.pic_height_in_map_units(),
1680        FrameMbsFlags::Fields { .. } => sps.pic_height_in_map_units().checked_mul(2)?,
1681    };
1682    let frame_size_in_mbs = pic_width_in_mbs.checked_mul(frame_height_in_mbs)?;
1683    let max_dpb_mbs = level.limits()?.max_dpb_mbs;
1684
1685    match profile {
1686        // A.3.1 - Baseline, Constrained Baseline, Main, Extended
1687        Profile::Baseline | Profile::ConstrainedBaseline | Profile::Main | Profile::Extended => {
1688            Some(std::cmp::min(max_dpb_mbs / frame_size_in_mbs, 16))
1689        }
1690        // A.3.2 - High, Progressive High, Constrained High, High 10, High 10 Intra,
1691        // High 4:2:2, High 4:2:2 Intra, High 4:4:4 Predictive, High 4:4:4 Intra,
1692        // CAVLC 4:4:4 Intra
1693        Profile::High
1694        | Profile::ProgressiveHigh
1695        | Profile::ConstrainedHigh
1696        | Profile::High10
1697        | Profile::High10Intra
1698        | Profile::High422
1699        | Profile::High422Intra
1700        | Profile::High444
1701        | Profile::High444Intra
1702        | Profile::CavlcIntra444 => Some(std::cmp::min(max_dpb_mbs / frame_size_in_mbs, 16)),
1703        // G.10.2.1 - Scalable profiles
1704        Profile::ScalableBase
1705        | Profile::ScalableConstrainedBaseline
1706        | Profile::ScalableHigh
1707        | Profile::ScalableConstrainedHigh
1708        | Profile::ScalableHighIntra => Some(std::cmp::min(max_dpb_mbs / frame_size_in_mbs, 16)),
1709        // H.10.2.1 - Multiview/Stereo/MFC profiles require NumViews from MVC extension data
1710        Profile::MultiviewHigh | Profile::StereoHigh | Profile::MFCHigh | Profile::MFCDepthHigh => {
1711            None
1712        }
1713        // I.10.2.1 - Multiview Depth profiles require NumViews from MVC extension data
1714        Profile::MultiviewDepthHigh | Profile::EnhancedMultiviewDepthHigh => None,
1715        Profile::Unknown(_) => None,
1716    }
1717}
1718
1719#[derive(Clone, Debug, Default, PartialEq, Eq)]
1720pub struct VuiParameters {
1721    pub aspect_ratio_info: Option<AspectRatioInfo>,
1722    pub overscan_appropriate: OverscanAppropriate,
1723    pub video_signal_type: Option<VideoSignalType>,
1724    pub chroma_loc_info: Option<ChromaLocInfo>,
1725    pub timing_info: Option<TimingInfo>,
1726    pub nal_hrd_parameters: Option<HrdParameters>,
1727    pub vcl_hrd_parameters: Option<HrdParameters>,
1728    pub low_delay_hrd_flag: Option<bool>,
1729    pub pic_struct_present_flag: bool,
1730    pub bitstream_restrictions: Option<BitstreamRestrictions>,
1731}
1732impl VuiParameters {
1733    fn read<R: BitRead>(
1734        r: &mut R,
1735        sps: &SeqParameterSet,
1736    ) -> Result<Option<VuiParameters>, SpsError> {
1737        let vui_parameters_present_flag = r.read_bit("vui_parameters_present_flag")?;
1738        Ok(if vui_parameters_present_flag {
1739            let mut hrd_parameters_present = false;
1740            Some(VuiParameters {
1741                aspect_ratio_info: AspectRatioInfo::read(r)?,
1742                overscan_appropriate: OverscanAppropriate::read(r)?,
1743                video_signal_type: VideoSignalType::read(r)?,
1744                chroma_loc_info: ChromaLocInfo::read(r)?,
1745                timing_info: TimingInfo::read(r)?,
1746                nal_hrd_parameters: HrdParameters::read(r, &mut hrd_parameters_present)?,
1747                vcl_hrd_parameters: HrdParameters::read(r, &mut hrd_parameters_present)?,
1748                low_delay_hrd_flag: if hrd_parameters_present {
1749                    Some(r.read_bit("low_delay_hrd_flag")?)
1750                } else {
1751                    None
1752                },
1753                pic_struct_present_flag: r.read_bit("pic_struct_present_flag")?,
1754                bitstream_restrictions: BitstreamRestrictions::read(r, sps)?,
1755            })
1756        } else {
1757            None
1758        })
1759    }
1760
1761    fn write<W: BitWrite>(this: Option<&Self>, w: &mut W) -> std::io::Result<()> {
1762        match this {
1763            None => w.write_bit(false),
1764            Some(vui) => {
1765                w.write_bit(true)?;
1766                AspectRatioInfo::write(vui.aspect_ratio_info.as_ref(), w)?;
1767                vui.overscan_appropriate.write(w)?;
1768                VideoSignalType::write(vui.video_signal_type.as_ref(), w)?;
1769                ChromaLocInfo::write(vui.chroma_loc_info.as_ref(), w)?;
1770                TimingInfo::write(vui.timing_info.as_ref(), w)?;
1771                HrdParameters::write(vui.nal_hrd_parameters.as_ref(), w)?;
1772                HrdParameters::write(vui.vcl_hrd_parameters.as_ref(), w)?;
1773                let has_hrd = vui.nal_hrd_parameters.is_some() || vui.vcl_hrd_parameters.is_some();
1774                if has_hrd {
1775                    w.write_bit(vui.low_delay_hrd_flag.unwrap_or(false))?;
1776                }
1777                w.write_bit(vui.pic_struct_present_flag)?;
1778                BitstreamRestrictions::write(vui.bitstream_restrictions.as_ref(), w)
1779            }
1780        }
1781    }
1782}
1783
1784#[derive(Clone, Debug, PartialEq, Eq)]
1785pub struct SeqParameterSet {
1786    pub profile_idc: ProfileIdc,
1787    pub constraint_flags: ConstraintFlags,
1788    pub level_idc: u8,
1789    pub seq_parameter_set_id: SeqParamSetId,
1790    pub chroma_info: ChromaInfo,
1791    pub log2_max_frame_num_minus4: u8,
1792    pub pic_order_cnt: PicOrderCntType,
1793    pub max_num_ref_frames: u32,
1794    pub gaps_in_frame_num_value_allowed_flag: bool,
1795    pub pic_width_in_mbs_minus1: u32,
1796    pub pic_height_in_map_units_minus1: u32,
1797    pub frame_mbs_flags: FrameMbsFlags,
1798    pub direct_8x8_inference_flag: bool,
1799    pub frame_cropping: Option<FrameCropping>,
1800    pub vui_parameters: Option<VuiParameters>,
1801}
1802impl SeqParameterSet {
1803    /// Parses `seq_parameter_set_data()` (spec 7.3.2.1.1) without consuming the RBSP trailing
1804    /// bits. This is used by both `from_bits()` and `subset_seq_parameter_set_rbsp()` parsing.
1805    pub(crate) fn read_seq_parameter_set_data<R: BitRead>(
1806        r: &mut R,
1807    ) -> Result<SeqParameterSet, SpsError> {
1808        let profile_idc = r.read::<8, u8>("profile_idc")?.into();
1809        let constraint_flags = r.read::<8, u8>("constraint_flags")?.into();
1810        let level_idc = r.read::<8, u8>("level_idc")?;
1811        let mut sps = SeqParameterSet {
1812            profile_idc,
1813            constraint_flags,
1814            level_idc,
1815            seq_parameter_set_id: SeqParamSetId::from_u32(r.read_ue("seq_parameter_set_id")?)
1816                .map_err(SpsError::BadSeqParamSetId)?,
1817            chroma_info: ChromaInfo::read(r, profile_idc)?,
1818            log2_max_frame_num_minus4: Self::read_log2_max_frame_num_minus4(r)?,
1819            pic_order_cnt: PicOrderCntType::read(r).map_err(SpsError::PicOrderCnt)?,
1820            max_num_ref_frames: r.read_ue("max_num_ref_frames")?,
1821            gaps_in_frame_num_value_allowed_flag: r
1822                .read_bit("gaps_in_frame_num_value_allowed_flag")?,
1823            pic_width_in_mbs_minus1: r.read_ue("pic_width_in_mbs_minus1")?,
1824            pic_height_in_map_units_minus1: r.read_ue("pic_height_in_map_units_minus1")?,
1825            frame_mbs_flags: FrameMbsFlags::read(r)?,
1826            direct_8x8_inference_flag: r.read_bit("direct_8x8_inference_flag")?,
1827            frame_cropping: FrameCropping::read(r)?,
1828            // read the basic SPS data without reading VUI parameters yet, since checks of the
1829            // bitstream restriction fields within the VUI parameters will need access to the
1830            // initial SPS data
1831            vui_parameters: None,
1832        };
1833        // Validate frame size: check that width * height doesn't overflow u32, and that
1834        // the result is within the level's MaxFS limit (Table A-1). For unknown levels,
1835        // use the largest known max_fs (Level 6.2 = 139264 macroblocks).
1836        let pic_size = sps
1837            .pic_width_in_mbs()
1838            .checked_mul(sps.pic_height_in_map_units())
1839            .ok_or(SpsError::FieldValueTooLarge {
1840                name: "pic_size_in_map_units",
1841                value: u32::MAX,
1842            })?;
1843        let max_fs = sps.level().limits().map_or(139264, |l| l.max_fs);
1844        if pic_size > max_fs {
1845            return Err(SpsError::FieldValueTooLarge {
1846                name: "pic_size_in_map_units",
1847                value: pic_size,
1848            });
1849        }
1850        let vui_parameters = VuiParameters::read(r, &sps)?;
1851        sps.vui_parameters = vui_parameters;
1852        Ok(sps)
1853    }
1854
1855    pub fn from_bits<R: BitRead>(mut r: R) -> Result<SeqParameterSet, SpsError> {
1856        let sps = Self::read_seq_parameter_set_data(&mut r)?;
1857        r.finish_rbsp()?;
1858        Ok(sps)
1859    }
1860
1861    pub fn id(&self) -> SeqParamSetId {
1862        self.seq_parameter_set_id
1863    }
1864
1865    fn read_log2_max_frame_num_minus4<R: BitRead>(r: &mut R) -> Result<u8, SpsError> {
1866        let val = r.read_ue("log2_max_frame_num_minus4")?;
1867        if val > 12 {
1868            Err(SpsError::Log2MaxFrameNumMinus4OutOfRange(val))
1869        } else {
1870            Ok(val as u8)
1871        }
1872    }
1873
1874    pub fn profile(&self) -> Profile {
1875        Profile::from_profile_idc(self.profile_idc, self.constraint_flags)
1876    }
1877
1878    pub fn level(&self) -> Level {
1879        Level::from_constraint_flags_and_level_idc(self.constraint_flags, self.level_idc)
1880    }
1881    /// returned value will be in the range 4 to 16 inclusive
1882    pub fn log2_max_frame_num(&self) -> u8 {
1883        self.log2_max_frame_num_minus4 + 4
1884    }
1885
1886    /// Helper to calculate the pixel-dimensions of the video image specified by this SPS, taking
1887    /// into account sample-format, interlacing and cropping.
1888    pub fn pixel_dimensions(&self) -> Result<(u32, u32), SpsError> {
1889        let width = self
1890            .pic_width_in_mbs_minus1
1891            .checked_add(1)
1892            .and_then(|w| w.checked_mul(16))
1893            .ok_or_else(|| SpsError::FieldValueTooLarge {
1894                name: "pic_width_in_mbs_minus1",
1895                value: self.pic_width_in_mbs_minus1,
1896            })?;
1897        let mul = match self.frame_mbs_flags {
1898            FrameMbsFlags::Fields { .. } => 2,
1899            FrameMbsFlags::Frames => 1,
1900        };
1901        let vsub = if self.chroma_info.chroma_format == ChromaFormat::YUV420 {
1902            1
1903        } else {
1904            0
1905        };
1906        let hsub = if self.chroma_info.chroma_format == ChromaFormat::YUV420
1907            || self.chroma_info.chroma_format == ChromaFormat::YUV422
1908        {
1909            1
1910        } else {
1911            0
1912        };
1913
1914        let step_x = 1 << hsub;
1915        let step_y = mul << vsub;
1916
1917        let height = (self.pic_height_in_map_units_minus1 + 1)
1918            .checked_mul(mul * 16)
1919            .ok_or_else(|| SpsError::FieldValueTooLarge {
1920                name: "pic_height_in_map_units_minus1",
1921                value: self.pic_height_in_map_units_minus1,
1922            })?;
1923        if let Some(ref crop) = self.frame_cropping {
1924            let left_offset = crop.left_offset.checked_mul(step_x).ok_or_else(|| {
1925                SpsError::FieldValueTooLarge {
1926                    name: "left_offset",
1927                    value: crop.left_offset,
1928                }
1929            })?;
1930            let right_offset = crop.right_offset.checked_mul(step_x).ok_or_else(|| {
1931                SpsError::FieldValueTooLarge {
1932                    name: "right_offset",
1933                    value: crop.right_offset,
1934                }
1935            })?;
1936            let top_offset = crop.top_offset.checked_mul(step_y).ok_or_else(|| {
1937                SpsError::FieldValueTooLarge {
1938                    name: "top_offset",
1939                    value: crop.top_offset,
1940                }
1941            })?;
1942            let bottom_offset = crop.bottom_offset.checked_mul(step_y).ok_or_else(|| {
1943                SpsError::FieldValueTooLarge {
1944                    name: "bottom_offset",
1945                    value: crop.bottom_offset,
1946                }
1947            })?;
1948            let width = width
1949                .checked_sub(left_offset)
1950                .and_then(|w| w.checked_sub(right_offset));
1951            let height = height
1952                .checked_sub(top_offset)
1953                .and_then(|w| w.checked_sub(bottom_offset));
1954            if let (Some(width), Some(height)) = (width, height) {
1955                Ok((width, height))
1956            } else {
1957                Err(SpsError::CroppingError(crop.clone()))
1958            }
1959        } else {
1960            Ok((width, height))
1961        }
1962    }
1963
1964    pub fn rfc6381(&self) -> rfc6381_codec::Codec {
1965        rfc6381_codec::Codec::avc1(self.profile_idc.0, self.constraint_flags.0, self.level_idc)
1966    }
1967
1968    pub fn fps(&self) -> Option<f64> {
1969        let Some(vui) = &self.vui_parameters else {
1970            return None;
1971        };
1972        let Some(timing_info) = &vui.timing_info else {
1973            return None;
1974        };
1975
1976        Some((timing_info.time_scale as f64) / (2.0 * (timing_info.num_units_in_tick as f64)))
1977    }
1978
1979    pub fn pic_width_in_mbs(&self) -> u32 {
1980        self.pic_width_in_mbs_minus1 + 1
1981    }
1982
1983    /// From the spec: `PicHeightInMapUnits = pic_height_in_map_units_minus1 + 1`
1984    pub fn pic_height_in_map_units(&self) -> u32 {
1985        self.pic_height_in_map_units_minus1 + 1
1986    }
1987
1988    /// From the spec: `PicSizeInMapUnits = PicWidthInMbs * PicHeightInMapUnits`
1989    pub fn pic_size_in_map_units(&self) -> u32 {
1990        // Validated during parsing in from_bits(), so this should never overflow.
1991        self.pic_width_in_mbs()
1992            .checked_mul(self.pic_height_in_map_units())
1993            .expect("pic_size_in_map_units overflow: should have been validated during SPS parsing")
1994    }
1995}
1996
1997impl crate::nal::WritableNal for SeqParameterSet {
1998    fn write_bits<W: BitWrite>(&self, w: &mut W) -> std::io::Result<()> {
1999        w.write::<8, u8>(self.profile_idc.0)?;
2000        w.write::<8, u8>(self.constraint_flags.0)?;
2001        w.write::<8, u8>(self.level_idc)?;
2002        w.write_ue(self.seq_parameter_set_id.0 as u32)?;
2003        self.chroma_info.write(w, self.profile_idc)?;
2004        w.write_ue(self.log2_max_frame_num_minus4 as u32)?;
2005        self.pic_order_cnt.write(w)?;
2006        w.write_ue(self.max_num_ref_frames)?;
2007        w.write_bit(self.gaps_in_frame_num_value_allowed_flag)?;
2008        w.write_ue(self.pic_width_in_mbs_minus1)?;
2009        w.write_ue(self.pic_height_in_map_units_minus1)?;
2010        self.frame_mbs_flags.write(w)?;
2011        w.write_bit(self.direct_8x8_inference_flag)?;
2012        FrameCropping::write(self.frame_cropping.as_ref(), w)?;
2013        VuiParameters::write(self.vui_parameters.as_ref(), w)?;
2014        w.write_rbsp_trailing_bits()
2015    }
2016}
2017
2018/// Level limits from Table A-1 of the spec.
2019#[derive(Debug)]
2020pub struct LevelLimit {
2021    /// Maximum macroblock processing rate (macroblocks per second)
2022    pub max_mbps: u32,
2023    /// Maximum frame size (in macroblocks)
2024    pub max_fs: u32,
2025    /// Maximum decoded picture buffer size (in macroblocks)
2026    pub max_dpb_mbs: u32,
2027    /// Maximum video bitrate (in units of 1000 bits/s or 1200 bits/s depending on profile)
2028    pub max_br: u32,
2029    /// Maximum CPB size (in units of 1000 bits or 1200 bits depending on profile)
2030    pub max_cpb: u32,
2031    /// Maximum vertical MV component range
2032    pub max_vmv_r: u32,
2033    /// Minimum compression ratio
2034    pub min_cr: u8,
2035    /// Maximum number of motion vectors per two consecutive MBs.
2036    /// `None` means no limit (applies to levels below 3.0).
2037    pub max_mvs_per2mb: Option<NonZeroU8>,
2038}
2039
2040#[cfg(test)]
2041mod test {
2042    use crate::rbsp::{self, decode_nal, BitReader};
2043
2044    use super::*;
2045    use hex_literal::*;
2046    use test_case::test_case;
2047
2048    #[test]
2049    fn test_it() {
2050        let data = hex!(
2051            "64 00 0A AC 72 84 44 26 84 00 00
2052            00 04 00 00 00 CA 3C 48 96 11 80"
2053        );
2054        let sps = SeqParameterSet::from_bits(rbsp::BitReader::new(&data[..])).unwrap();
2055        assert!(!format!("{:?}", sps).is_empty());
2056        assert_eq!(100, sps.profile_idc.0);
2057        assert_eq!(0, sps.constraint_flags.reserved_zero_two_bits());
2058        assert_eq!((64, 64), sps.pixel_dimensions().unwrap());
2059        assert!(!sps.rfc6381().to_string().is_empty())
2060    }
2061
2062    #[test]
2063    fn test_dahua() {
2064        // From a Dahua IPC-HDW5231R-Z's sub stream, which is anamorphic.
2065        let data = hex!(
2066            "64 00 16 AC 1B 1A 80 B0 3D FF FF
2067           00 28 00 21 6E 0C 0C 0C 80 00 01
2068           F4 00 00 27 10 74 30 07 D0 00 07
2069           A1 25 DE 5C 68 60 0F A0 00 0F 42
2070           4B BC B8 50"
2071        );
2072        let sps = SeqParameterSet::from_bits(rbsp::BitReader::new(&data[..])).unwrap();
2073        println!("sps: {:#?}", sps);
2074        assert_eq!(
2075            sps.vui_parameters.unwrap().aspect_ratio_info.unwrap().get(),
2076            Some((40, 33))
2077        );
2078    }
2079
2080    #[test]
2081    fn crop_removes_all_pixels() {
2082        let sps = SeqParameterSet {
2083            profile_idc: ProfileIdc(0),
2084            constraint_flags: ConstraintFlags(0),
2085            level_idc: 0,
2086            seq_parameter_set_id: SeqParamSetId::from_u32(0).unwrap(),
2087            chroma_info: ChromaInfo {
2088                chroma_format: ChromaFormat::Monochrome,
2089                separate_colour_plane_flag: false,
2090                bit_depth_luma_minus8: 0,
2091                bit_depth_chroma_minus8: 0,
2092                qpprime_y_zero_transform_bypass_flag: false,
2093                scaling_matrix: Default::default(),
2094            },
2095            log2_max_frame_num_minus4: 0,
2096            pic_order_cnt: PicOrderCntType::TypeTwo,
2097            max_num_ref_frames: 0,
2098            frame_cropping: Some(FrameCropping {
2099                bottom_offset: 20,
2100                left_offset: 20,
2101                right_offset: 20,
2102                top_offset: 20,
2103            }),
2104            pic_width_in_mbs_minus1: 1,
2105            pic_height_in_map_units_minus1: 1,
2106            frame_mbs_flags: FrameMbsFlags::Frames,
2107            gaps_in_frame_num_value_allowed_flag: false,
2108            direct_8x8_inference_flag: false,
2109            vui_parameters: None,
2110        };
2111        // should return Err, rather than assert due to integer underflow for example,
2112        let dim = sps.pixel_dimensions();
2113        assert!(matches!(dim, Err(SpsError::CroppingError(_))));
2114    }
2115
2116    #[test]
2117    fn profile_idc_roundtrip() {
2118        let no_flags = ConstraintFlags::from(0);
2119        for idc in 0..=255 {
2120            let profile = Profile::from_profile_idc(ProfileIdc(idc), no_flags);
2121            assert_eq!(
2122                idc,
2123                profile.profile_idc(),
2124                "round-trip failed for idc {idc}"
2125            );
2126        }
2127    }
2128
2129    #[test]
2130    fn profile_constraint_flags() {
2131        // Constrained Baseline: profile_idc=66, constraint_set1_flag=1
2132        let flags = ConstraintFlags::from(0b0100_0000);
2133        assert!(matches!(
2134            Profile::from_profile_idc(ProfileIdc(66), flags),
2135            Profile::ConstrainedBaseline
2136        ));
2137        // Plain Baseline without constraint_set1_flag
2138        let flags = ConstraintFlags::from(0b1000_0000);
2139        assert!(matches!(
2140            Profile::from_profile_idc(ProfileIdc(66), flags),
2141            Profile::Baseline
2142        ));
2143
2144        // Progressive High: profile_idc=100, constraint_set4_flag=1
2145        let flags = ConstraintFlags::from(0b0000_1000);
2146        assert!(matches!(
2147            Profile::from_profile_idc(ProfileIdc(100), flags),
2148            Profile::ProgressiveHigh
2149        ));
2150        // Constrained High: profile_idc=100, constraint_set4_flag=1 + constraint_set5_flag=1
2151        let flags = ConstraintFlags::from(0b0000_1100);
2152        assert!(matches!(
2153            Profile::from_profile_idc(ProfileIdc(100), flags),
2154            Profile::ConstrainedHigh
2155        ));
2156
2157        // High 10 Intra: profile_idc=110, constraint_set3_flag=1
2158        let flags = ConstraintFlags::from(0b0001_0000);
2159        assert!(matches!(
2160            Profile::from_profile_idc(ProfileIdc(110), flags),
2161            Profile::High10Intra
2162        ));
2163
2164        // High 4:2:2 Intra: profile_idc=122, constraint_set3_flag=1
2165        let flags = ConstraintFlags::from(0b0001_0000);
2166        assert!(matches!(
2167            Profile::from_profile_idc(ProfileIdc(122), flags),
2168            Profile::High422Intra
2169        ));
2170
2171        // High 4:4:4 Intra: profile_idc=244, constraint_set3_flag=1
2172        let flags = ConstraintFlags::from(0b0001_0000);
2173        assert!(matches!(
2174            Profile::from_profile_idc(ProfileIdc(244), flags),
2175            Profile::High444Intra
2176        ));
2177
2178        // Scalable Constrained Baseline: profile_idc=83, constraint_set5_flag=1
2179        let flags = ConstraintFlags::from(0b0000_0100);
2180        assert!(matches!(
2181            Profile::from_profile_idc(ProfileIdc(83), flags),
2182            Profile::ScalableConstrainedBaseline
2183        ));
2184
2185        // Scalable Constrained High: profile_idc=86, constraint_set5_flag=1
2186        let flags = ConstraintFlags::from(0b0000_0100);
2187        assert!(matches!(
2188            Profile::from_profile_idc(ProfileIdc(86), flags),
2189            Profile::ScalableConstrainedHigh
2190        ));
2191
2192        // Scalable High Intra: profile_idc=86, constraint_set3_flag=1
2193        let flags = ConstraintFlags::from(0b0001_0000);
2194        assert!(matches!(
2195            Profile::from_profile_idc(ProfileIdc(86), flags),
2196            Profile::ScalableHighIntra
2197        ));
2198
2199        // Constrained variants still roundtrip through profile_idc
2200        assert_eq!(Profile::ConstrainedBaseline.profile_idc(), 66);
2201        assert_eq!(Profile::ProgressiveHigh.profile_idc(), 100);
2202        assert_eq!(Profile::ConstrainedHigh.profile_idc(), 100);
2203        assert_eq!(Profile::High10Intra.profile_idc(), 110);
2204        assert_eq!(Profile::High422Intra.profile_idc(), 122);
2205        assert_eq!(Profile::High444Intra.profile_idc(), 244);
2206        assert_eq!(Profile::ScalableConstrainedBaseline.profile_idc(), 83);
2207        assert_eq!(Profile::ScalableConstrainedHigh.profile_idc(), 86);
2208        assert_eq!(Profile::ScalableHighIntra.profile_idc(), 86);
2209    }
2210
2211    #[test_case(
2212        vec![
2213            0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
2214            0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
2215            0x00, 0x03, 0x00, 0x3d, 0x08,
2216        ],
2217        SeqParameterSet{
2218            profile_idc: ProfileIdc::from(100),
2219            constraint_flags: ConstraintFlags::from(0),
2220            level_idc: 12,
2221            seq_parameter_set_id: SeqParamSetId::from_u32(0).unwrap(),
2222            chroma_info: ChromaInfo{
2223                chroma_format: ChromaFormat::YUV420,
2224                ..ChromaInfo::default()
2225            },
2226            log2_max_frame_num_minus4: 6,
2227            pic_order_cnt: PicOrderCntType::TypeTwo,
2228            max_num_ref_frames: 1,
2229            gaps_in_frame_num_value_allowed_flag: true,
2230            pic_width_in_mbs_minus1: 21,
2231            pic_height_in_map_units_minus1: 17,
2232            frame_mbs_flags: FrameMbsFlags::Frames,
2233            direct_8x8_inference_flag: true,
2234            frame_cropping: None,
2235            vui_parameters: Some(VuiParameters{
2236                timing_info: Some(TimingInfo{
2237                    num_units_in_tick: 1,
2238                    time_scale: 30,
2239                    fixed_frame_rate_flag: true,
2240                }),
2241                ..VuiParameters::default()
2242            }),
2243        },
2244        352,
2245        288,
2246        15.0; "352x288"
2247    )]
2248    #[test_case(
2249        vec![
2250            0x67, 0x64, 0x00, 0x1f, 0xac, 0xd9, 0x40, 0x50,
2251            0x05, 0xbb, 0x01, 0x6c, 0x80, 0x00, 0x00, 0x03,
2252            0x00, 0x80, 0x00, 0x00, 0x1e, 0x07, 0x8c, 0x18,
2253            0xcb,
2254        ],
2255        SeqParameterSet{
2256            profile_idc: ProfileIdc::from(100),
2257            constraint_flags: ConstraintFlags::from(0),
2258            level_idc: 31,
2259            seq_parameter_set_id: SeqParamSetId::from_u32(0).unwrap(),
2260            chroma_info: ChromaInfo{
2261                chroma_format: ChromaFormat::YUV420,
2262                ..ChromaInfo::default()
2263            },
2264            log2_max_frame_num_minus4: 0,
2265            pic_order_cnt: PicOrderCntType::TypeZero {
2266                log2_max_pic_order_cnt_lsb_minus4: 2
2267            },
2268            max_num_ref_frames: 4,
2269            gaps_in_frame_num_value_allowed_flag: false,
2270            pic_width_in_mbs_minus1: 79,
2271            pic_height_in_map_units_minus1: 44,
2272            frame_mbs_flags: FrameMbsFlags::Frames,
2273            direct_8x8_inference_flag: true,
2274            frame_cropping: None,
2275            vui_parameters: Some(VuiParameters{
2276                aspect_ratio_info: Some(AspectRatioInfo::Ratio1_1),
2277                video_signal_type: Some(VideoSignalType{
2278                    video_format: VideoFormat::Unspecified,
2279                    video_full_range_flag: true,
2280                    colour_description: None,
2281                }),
2282                timing_info: Some(TimingInfo{
2283                    num_units_in_tick: 1,
2284                    time_scale: 60,
2285                    fixed_frame_rate_flag: false,
2286                }),
2287                bitstream_restrictions: Some(BitstreamRestrictions{
2288                    motion_vectors_over_pic_boundaries_flag: true,
2289                    log2_max_mv_length_horizontal: 11,
2290                    log2_max_mv_length_vertical: 11,
2291                    max_num_reorder_frames: 2,
2292                    max_dec_frame_buffering: 4,
2293                    ..BitstreamRestrictions::default()
2294                }),
2295                ..VuiParameters::default()
2296            }),
2297        },
2298        1280,
2299        720,
2300        30.0; "1280x720"
2301    )]
2302    #[test_case(
2303        vec![
2304            0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
2305            0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
2306            0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20,
2307        ],
2308        SeqParameterSet{
2309            profile_idc: ProfileIdc::from(66),
2310            constraint_flags: ConstraintFlags::from(0b11000000),
2311            level_idc: 40,
2312            seq_parameter_set_id: SeqParamSetId::from_u32(0).unwrap(),
2313            chroma_info: ChromaInfo{
2314                chroma_format: ChromaFormat::YUV420,
2315                ..ChromaInfo::default()
2316            },
2317            log2_max_frame_num_minus4: 0,
2318            pic_order_cnt: PicOrderCntType::TypeTwo,
2319            max_num_ref_frames: 3,
2320            gaps_in_frame_num_value_allowed_flag: false,
2321            pic_width_in_mbs_minus1: 119,
2322            pic_height_in_map_units_minus1: 67,
2323            frame_mbs_flags: FrameMbsFlags::Frames,
2324            direct_8x8_inference_flag: true,
2325            frame_cropping: Some(FrameCropping{
2326                bottom_offset: 4,
2327                ..FrameCropping::default()
2328            }),
2329            vui_parameters: Some(VuiParameters{
2330                timing_info: Some(TimingInfo{
2331                    num_units_in_tick: 1,
2332                    time_scale:      60,
2333                    fixed_frame_rate_flag: false,
2334                }),
2335                bitstream_restrictions: Some(BitstreamRestrictions{
2336                    motion_vectors_over_pic_boundaries_flag: true,
2337                    log2_max_mv_length_horizontal: 11,
2338                    log2_max_mv_length_vertical: 11,
2339                    max_dec_frame_buffering: 3,
2340                    ..BitstreamRestrictions::default()
2341                }),
2342                ..VuiParameters::default()
2343            }),
2344        },
2345        1920,
2346        1080,
2347        30.0; "1920x1080 baseline"
2348    )]
2349    #[test_case(
2350        vec![
2351            0x67, 0x64, 0x00, 0x28, 0xac, 0xd9, 0x40, 0x78,
2352            0x02, 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00,
2353            0x04, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60,
2354            0xc6, 0x58,
2355        ],
2356        SeqParameterSet{
2357            profile_idc: ProfileIdc::from(100),
2358            constraint_flags: ConstraintFlags::from(0),
2359            level_idc: 40,
2360            seq_parameter_set_id: SeqParamSetId::from_u32(0).unwrap(),
2361            chroma_info: ChromaInfo{
2362                chroma_format: ChromaFormat::YUV420,
2363                ..ChromaInfo::default()
2364            },
2365            log2_max_frame_num_minus4: 0,
2366            pic_order_cnt: PicOrderCntType::TypeZero {
2367                log2_max_pic_order_cnt_lsb_minus4: 2
2368            },
2369            max_num_ref_frames: 4,
2370            gaps_in_frame_num_value_allowed_flag: false,
2371            pic_width_in_mbs_minus1: 119,
2372            pic_height_in_map_units_minus1: 67,
2373            frame_mbs_flags: FrameMbsFlags::Frames,
2374            direct_8x8_inference_flag: true,
2375            frame_cropping: Some(FrameCropping{
2376                bottom_offset: 4,
2377                ..FrameCropping::default()
2378            }),
2379            vui_parameters: Some(VuiParameters{
2380                timing_info: Some(TimingInfo{
2381                    num_units_in_tick: 1,
2382                    time_scale: 60,
2383                    fixed_frame_rate_flag: false,
2384                }),
2385                bitstream_restrictions: Some(BitstreamRestrictions{
2386                    motion_vectors_over_pic_boundaries_flag: true,
2387                    log2_max_mv_length_horizontal: 11,
2388                    log2_max_mv_length_vertical: 11,
2389                    max_num_reorder_frames: 2,
2390                    max_dec_frame_buffering: 4,
2391                    ..BitstreamRestrictions::default()
2392                }),
2393                ..VuiParameters::default()
2394            }),
2395        },
2396        1920,
2397        1080,
2398        30.0; "1920x1080 nvidia"
2399    )]
2400    // This fails.
2401
2402    /*#[test_case(
2403        vec![
2404            0x67, 0x64, 0x00, 0x29, 0xac, 0x13, 0x31, 0x40,
2405            0x78, 0x04, 0x47, 0xde, 0x03, 0xea, 0x02, 0x02,
2406            0x03, 0xe0, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00,
2407            0x00, 0x06, 0x52, // 0x80,
2408        ],
2409        SeqParameterSet{
2410            profile_idc: ProfileIdc::from(100),
2411            constraint_flags: ConstraintFlags::from(0),
2412            level_idc: 41,
2413            seq_parameter_set_id: SeqParamSetId::from_u32(0).unwrap(),
2414            chroma_info: ChromaInfo{
2415                chroma_format: ChromaFormat::YUV420,
2416                ..ChromaInfo::default()
2417            },
2418            log2_max_frame_num_minus4: 8,
2419            pic_order_cnt: PicOrderCntType::TypeZero {
2420                log2_max_pic_order_cnt_lsb_minus4: 5
2421            },
2422            max_num_ref_frames: 4,
2423            gaps_in_frame_num_value_allowed_flag: false,
2424            pic_width_in_mbs_minus1: 119,
2425            pic_height_in_map_units_minus1: 33,
2426            frame_mbs_flags: FrameMbsFlags::Fields{
2427                mb_adaptive_frame_field_flag: false,
2428            },
2429            direct_8x8_inference_flag: true,
2430            frame_cropping: Some(FrameCropping{
2431                bottom_offset: 2,
2432                ..FrameCropping::default()
2433            }),
2434            vui_parameters: Some(VuiParameters{
2435                aspect_ratio_info: Some(AspectRatioInfo::Ratio1_1),
2436                overscan_appropriate: OverscanAppropriate::Appropriate,
2437                video_signal_type: Some(VideoSignalType{
2438                    video_format: VideoFormat::Unspecified,
2439                    video_full_range_flag: false,
2440                    colour_description: Some(ColourDescription{
2441                        colour_primaries: 1,
2442                        transfer_characteristics: 1,
2443                        matrix_coefficients: 1,
2444                    }),
2445                }),
2446                chroma_loc_info: Some(ChromaLocInfo{
2447                    chroma_sample_loc_type_top_field: 0,
2448                    chroma_sample_loc_type_bottom_field: 0,
2449                }),
2450                timing_info: Some(TimingInfo{
2451                    num_units_in_tick: 1,
2452                    time_scale: 50,
2453                    fixed_frame_rate_flag: true,
2454                }),
2455                pic_struct_present_flag: true,
2456                ..VuiParameters::default()
2457            }),
2458        },
2459        1920,
2460        1084,
2461        25.0; "1920x1080"
2462    )]
2463    */
2464    #[test_case(
2465        vec![103, 100, 0, 32, 172, 23, 42, 1, 64, 30, 104, 64, 0, 1, 194, 0, 0, 87, 228, 33],
2466        SeqParameterSet{
2467            profile_idc: ProfileIdc::from(100),
2468            constraint_flags: ConstraintFlags::from(0),
2469            level_idc: 32,
2470            seq_parameter_set_id: SeqParamSetId::from_u32(0).unwrap(),
2471            chroma_info: ChromaInfo{
2472                chroma_format: ChromaFormat::YUV420,
2473                ..ChromaInfo::default()
2474            },
2475            log2_max_frame_num_minus4: 10,
2476            pic_order_cnt: PicOrderCntType::TypeZero {
2477                log2_max_pic_order_cnt_lsb_minus4: 4
2478            },
2479            max_num_ref_frames: 1,
2480            gaps_in_frame_num_value_allowed_flag: false,
2481            pic_width_in_mbs_minus1: 79,
2482            pic_height_in_map_units_minus1: 59,
2483            frame_mbs_flags: FrameMbsFlags::Frames,
2484            direct_8x8_inference_flag: true,
2485            frame_cropping: None,
2486            vui_parameters: Some(VuiParameters{
2487                timing_info: Some(TimingInfo{
2488                    num_units_in_tick: 1800,
2489                    time_scale: 90000,
2490                    fixed_frame_rate_flag: true,
2491                }),
2492                ..VuiParameters::default()
2493            }),
2494        },
2495        1280,
2496        960,
2497        25.0; "hikvision"
2498    )]
2499    #[test_case(
2500        vec![
2501            103, 100, 0, 50, 173, 132, 99, 210, 73, 36, 146, 73, 37, 8, 127,
2502            255, 132, 63, 255, 194, 31, 255, 225, 15, 255, 225, 218,
2503            128, 160, 2, 214, 155, 128, 128, 128, 160, 0, 0, 3, 0, 32, 0, 0, 5, 16, 128
2504        ],
2505        SeqParameterSet{
2506            profile_idc: ProfileIdc::from(100),
2507            constraint_flags: ConstraintFlags::from(0),
2508            level_idc: 50,
2509            seq_parameter_set_id: SeqParamSetId::from_u32(0).unwrap(),
2510            chroma_info: ChromaInfo{
2511                chroma_format: ChromaFormat::YUV420,
2512                scaling_matrix: Some(Box::new(SeqScalingMatrix {
2513                    scaling_lists4x4: ScalingLists4x4([
2514                        Some([0; 16]),
2515                        Some([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]),
2516                        Some([16u8; 16]),
2517                        Some([16u8; 16]),
2518                        Some([16u8; 16]),
2519                        Some([16u8; 16]),
2520                    ]),
2521                    scaling_lists8x8: ScalingLists8x8::Y([None, None]),
2522                })),
2523                ..ChromaInfo::default()
2524            },
2525            log2_max_frame_num_minus4: 6,
2526            pic_order_cnt: PicOrderCntType::TypeTwo,
2527            max_num_ref_frames: 1,
2528            gaps_in_frame_num_value_allowed_flag: true,
2529            pic_width_in_mbs_minus1: 159,
2530            pic_height_in_map_units_minus1: 89,
2531            frame_mbs_flags: FrameMbsFlags::Frames,
2532            direct_8x8_inference_flag: true,
2533            frame_cropping: None,
2534            vui_parameters: Some(VuiParameters{
2535                video_signal_type: Some(VideoSignalType{
2536                    video_format: VideoFormat::Unspecified,
2537                    video_full_range_flag: true,
2538                    colour_description: Some(ColourDescription{
2539                        colour_primaries: 1,
2540                        transfer_characteristics: 1,
2541                        matrix_coefficients: 1,
2542                    }),
2543                }),
2544                timing_info: Some(TimingInfo{
2545                    num_units_in_tick: 1,
2546                    time_scale: 40,
2547                    fixed_frame_rate_flag: true,
2548                }),
2549                ..VuiParameters::default()
2550            }),
2551        },
2552        2560,
2553        1440,
2554        20.0; "scaling matrix"
2555    )]
2556    #[test_case(
2557        vec![
2558            103, 100, 0, 42, 172, 44, 172, 7,
2559            128, 34, 126, 92, 5, 168, 8, 8,
2560            10, 0, 0, 7, 208, 0, 3, 169,
2561            129, 192, 0, 0, 76, 75, 0, 0,
2562            38, 37, 173, 222, 92, 20,
2563        ],
2564        SeqParameterSet{
2565            profile_idc: ProfileIdc::from(100),
2566            constraint_flags: ConstraintFlags::from(0),
2567            level_idc: 42,
2568            seq_parameter_set_id: SeqParamSetId::from_u32(0).unwrap(),
2569            chroma_info: ChromaInfo{
2570                chroma_format: ChromaFormat::YUV420,
2571                ..ChromaInfo::default()
2572            },
2573            log2_max_frame_num_minus4: 4,
2574            pic_order_cnt: PicOrderCntType::TypeZero {
2575                log2_max_pic_order_cnt_lsb_minus4: 4
2576            },
2577            max_num_ref_frames: 2,
2578            gaps_in_frame_num_value_allowed_flag: false,
2579            pic_width_in_mbs_minus1: 119,
2580            pic_height_in_map_units_minus1: 67,
2581            frame_mbs_flags: FrameMbsFlags::Frames,
2582            direct_8x8_inference_flag: true,
2583            frame_cropping: Some(FrameCropping{
2584                bottom_offset: 4,
2585                ..FrameCropping::default()
2586            }),
2587            vui_parameters: Some(VuiParameters{
2588                aspect_ratio_info: Some(AspectRatioInfo::Ratio1_1),
2589                video_signal_type: Some(VideoSignalType{
2590                    video_format: VideoFormat::Unspecified,
2591                    video_full_range_flag: false,
2592                    colour_description: Some(ColourDescription{
2593                        colour_primaries: 1,
2594                        transfer_characteristics: 1,
2595                        matrix_coefficients: 1,
2596                    }),
2597                }),
2598                timing_info: Some(TimingInfo{
2599                    num_units_in_tick: 1000,
2600                    time_scale: 120000,
2601                    fixed_frame_rate_flag: true,
2602                }),
2603                nal_hrd_parameters: Some(HrdParameters{
2604                    cpb_specs: vec![CpbSpec{
2605                        bit_rate_value_minus1: 39061,
2606                        cpb_size_value_minus1: 156249,
2607                        cbr_flag: true,
2608                    }],
2609                    initial_cpb_removal_delay_length_minus1: 23,
2610                    cpb_removal_delay_length_minus1: 15,
2611                    dpb_output_delay_length_minus1: 5,
2612                    time_offset_length: 24,
2613                    ..HrdParameters::default()
2614                }),
2615                low_delay_hrd_flag: Some(false),
2616                pic_struct_present_flag: true,
2617                ..VuiParameters::default()
2618            }),
2619        },
2620        1920,
2621        1080,
2622        60.0; "1920x1080 nvenc hrd"
2623    )]
2624    #[test_case(
2625        vec![
2626            103, 77, 0, 41, 154, 100, 3, 192,
2627            17, 63, 46, 2, 220, 4, 4, 5,
2628            0, 0, 3, 3, 232, 0, 0, 195,
2629            80, 232, 96, 0, 186, 180, 0, 2,
2630            234, 196, 187, 203, 141, 12, 0, 23,
2631            86, 128, 0, 93, 88, 151, 121, 112,
2632            160,
2633        ],
2634        SeqParameterSet{
2635            profile_idc: ProfileIdc::from(77),
2636            constraint_flags: ConstraintFlags::from(0),
2637            level_idc: 41,
2638            seq_parameter_set_id: SeqParamSetId::from_u32(0).unwrap(),
2639            chroma_info: ChromaInfo{
2640                chroma_format: ChromaFormat::YUV420,
2641                ..ChromaInfo::default()
2642            },
2643            log2_max_frame_num_minus4: 5,
2644            pic_order_cnt: PicOrderCntType::TypeZero {
2645                log2_max_pic_order_cnt_lsb_minus4: 5
2646            },
2647            max_num_ref_frames: 1,
2648            gaps_in_frame_num_value_allowed_flag: false,
2649            pic_width_in_mbs_minus1: 119,
2650            pic_height_in_map_units_minus1: 67,
2651            frame_mbs_flags: FrameMbsFlags::Frames,
2652            direct_8x8_inference_flag: true,
2653            frame_cropping: Some(FrameCropping{
2654                bottom_offset: 4,
2655                ..FrameCropping::default()
2656            }),
2657            vui_parameters: Some(VuiParameters{
2658                aspect_ratio_info: Some(AspectRatioInfo::Ratio1_1),
2659                video_signal_type: Some(VideoSignalType{
2660                    video_format: VideoFormat::Unspecified,
2661                    video_full_range_flag: true,
2662                    colour_description: Some(ColourDescription{
2663                        colour_primaries: 1,
2664                        transfer_characteristics: 1,
2665                        matrix_coefficients: 1,
2666                    }),
2667                }),
2668                timing_info: Some(TimingInfo{
2669                    num_units_in_tick: 1000,
2670                    time_scale: 50000,
2671                    fixed_frame_rate_flag: true,
2672                }),
2673                nal_hrd_parameters: Some(HrdParameters{
2674                    bit_rate_scale: 4,
2675                    cpb_size_scale: 3,
2676                    cpb_specs: vec![CpbSpec{
2677                        bit_rate_value_minus1: 11948,
2678                        cpb_size_value_minus1: 95585,
2679                        cbr_flag: false,
2680                    }],
2681                    initial_cpb_removal_delay_length_minus1: 23,
2682                    cpb_removal_delay_length_minus1: 15,
2683                    dpb_output_delay_length_minus1: 5,
2684                    time_offset_length: 24,
2685                }),
2686                vcl_hrd_parameters: Some(HrdParameters{
2687                    bit_rate_scale: 4,
2688                    cpb_size_scale: 3,
2689                    cpb_specs: vec![CpbSpec{
2690                        bit_rate_value_minus1: 11948,
2691                        cpb_size_value_minus1: 95585,
2692                        cbr_flag: false,
2693                    }],
2694                    initial_cpb_removal_delay_length_minus1: 23,
2695                    cpb_removal_delay_length_minus1: 15,
2696                    dpb_output_delay_length_minus1: 5,
2697                    time_offset_length: 24,
2698                    ..HrdParameters::default()
2699                }),
2700                low_delay_hrd_flag: Some(false),
2701                pic_struct_present_flag: true,
2702                ..VuiParameters::default()
2703            }),
2704        },
2705        1920,
2706        1080,
2707        25.0; "1920x1080 hikvision nal hrd + vcl hrd"
2708    )]
2709    fn test_sps(byts: Vec<u8>, sps: SeqParameterSet, width: u32, height: u32, fps: f64) {
2710        let sps_rbsp = decode_nal(&byts).unwrap();
2711        let sps2 = SeqParameterSet::from_bits(BitReader::new(&*sps_rbsp)).unwrap();
2712
2713        let (width2, height2) = sps2.pixel_dimensions().unwrap();
2714        assert_eq!(sps, sps2);
2715        assert_eq!(width, width2);
2716        assert_eq!(height, height2);
2717        assert_eq!(fps, sps2.fps().unwrap());
2718    }
2719
2720    /// Encodes and decodes an SPS from raw NAL bytes, asserting structural and byte-level
2721    /// round-trip equivalence.
2722    fn check_round_trip(byts: &[u8]) {
2723        use crate::nal::WritableNal;
2724        use crate::rbsp::BitWriter;
2725
2726        let sps_rbsp = decode_nal(byts).unwrap();
2727        let sps = SeqParameterSet::from_bits(BitReader::new(&*sps_rbsp)).unwrap();
2728
2729        // Write RBSP using WritableNal::write_bits into a plain Vec (no emulation prevention).
2730        let mut written_rbsp = Vec::new();
2731        let mut bw = BitWriter::new(&mut written_rbsp);
2732        sps.write_bits(&mut bw).unwrap();
2733
2734        // Re-decode and check structural equality.
2735        let sps2 = SeqParameterSet::from_bits(BitReader::new(&*written_rbsp)).unwrap();
2736        assert_eq!(sps, sps2, "structural round-trip mismatch");
2737
2738        // Byte-level: encode(decode(sps_rbsp)) == sps_rbsp.
2739        assert_eq!(&*written_rbsp, &*sps_rbsp, "byte-level round-trip mismatch");
2740
2741        // Also exercise write_with_header / the full NAL path.
2742        let hdr = crate::nal::NalHeader::new(byts[0]).unwrap();
2743        let nal_bytes = sps.to_vec_with_header(hdr);
2744        let nal_rbsp = decode_nal(&nal_bytes).unwrap();
2745        let sps3 = SeqParameterSet::from_bits(BitReader::new(&*nal_rbsp)).unwrap();
2746        assert_eq!(sps, sps3, "NAL round-trip mismatch");
2747    }
2748
2749    #[test_case(&[
2750        0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
2751        0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
2752        0x00, 0x03, 0x00, 0x3d, 0x08,
2753    ]; "352x288")]
2754    #[test_case(&[
2755        0x67, 0x64, 0x00, 0x1f, 0xac, 0xd9, 0x40, 0x50,
2756        0x05, 0xbb, 0x01, 0x6c, 0x80, 0x00, 0x00, 0x03,
2757        0x00, 0x80, 0x00, 0x00, 0x1e, 0x07, 0x8c, 0x18,
2758        0xcb,
2759    ]; "1280x720")]
2760    #[test_case(&[
2761        0x67, 0x64, 0x00, 0x16, 0xac, 0x1b, 0x1a, 0x80,
2762        0xb0, 0x3d, 0xff, 0xff, 0x00, 0x28, 0x00, 0x21,
2763        0x6e, 0x0c, 0x0c, 0x0c, 0x80, 0x00, 0x01, 0xf4,
2764        0x00, 0x00, 0x27, 0x10, 0x74, 0x30, 0x07, 0xd0,
2765        0x00, 0x07, 0xa1, 0x25, 0xde, 0x5c, 0x68, 0x60,
2766        0x0f, 0xa0, 0x00, 0x0f, 0x42, 0x4b, 0xbc, 0xb8,
2767        0x50,
2768    ]; "dahua_anamorphic")]
2769    fn test_sps_round_trip(byts: &[u8]) {
2770        check_round_trip(byts);
2771    }
2772
2773    /// Round-trips the SPS from each existing test_sps test case.
2774    #[test_case(
2775        vec![
2776            0x67, 0x64, 0x00, 0x0A, 0xAC, 0x72, 0x84, 0x44,
2777            0x26, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00,
2778            0x00, 0x03, 0x00, 0xCA, 0x3C, 0x48, 0x96, 0x11,
2779            0x80,
2780        ]; "existing_test_sps_0")]
2781    fn test_existing_sps_round_trip(byts: Vec<u8>) {
2782        check_round_trip(&byts);
2783    }
2784
2785    #[test]
2786    fn all_known_levels_have_limits() {
2787        let levels = [
2788            Level::L1,
2789            Level::L1_b,
2790            Level::L1_1,
2791            Level::L1_2,
2792            Level::L1_3,
2793            Level::L2,
2794            Level::L2_1,
2795            Level::L2_2,
2796            Level::L3,
2797            Level::L3_1,
2798            Level::L3_2,
2799            Level::L4,
2800            Level::L4_1,
2801            Level::L4_2,
2802            Level::L5,
2803            Level::L5_1,
2804            Level::L5_2,
2805            Level::L6,
2806            Level::L6_1,
2807            Level::L6_2,
2808        ];
2809        for level in &levels {
2810            assert!(level.limits().is_some(), "Expected limits for {:?}", level);
2811        }
2812    }
2813
2814    #[test]
2815    fn resolve_list_default_flag() {
2816        // First element is 0 → useDefaultScalingMatrixFlag → returns default
2817        let stored = [0u8; 16];
2818        assert_eq!(resolve_list(&stored, &DEFAULT_4X4_INTRA), DEFAULT_4X4_INTRA);
2819    }
2820
2821    #[test]
2822    fn resolve_list_carry_forward() {
2823        // [8, 10, 0, 0, ...] → nextScale=0 at j=2 means carry forward 10
2824        let mut stored = [0u8; 16];
2825        stored[0] = 8;
2826        stored[1] = 10;
2827        let result = resolve_list(&stored, &DEFAULT_4X4_INTRA);
2828        assert_eq!(result[0], 8);
2829        assert_eq!(result[1], 10);
2830        for j in 2..16 {
2831            assert_eq!(result[j], 10, "position {j} should carry forward");
2832        }
2833    }
2834
2835    #[test]
2836    fn resolve_list_all_explicit() {
2837        let stored: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
2838        let result = resolve_list(&stored, &DEFAULT_4X4_INTRA);
2839        assert_eq!(result, stored);
2840    }
2841
2842    #[test]
2843    fn chroma_info_no_scaling_matrix_returns_flat() {
2844        let ci = ChromaInfo {
2845            chroma_format: ChromaFormat::YUV420,
2846            ..ChromaInfo::default()
2847        };
2848        assert_eq!(ci.scaling_lists_4x4(), [FLAT_4X4; 6]);
2849        assert_eq!(
2850            ci.scaling_lists_8x8(),
2851            ScalingLists8x8Resolved::Y([FLAT_8X8; 2])
2852        );
2853    }
2854
2855    #[test]
2856    fn seq_scaling_matrix_all_none_returns_defaults() {
2857        let sm = SeqScalingMatrix {
2858            scaling_lists4x4: ScalingLists4x4([None; 6]),
2859            scaling_lists8x8: ScalingLists8x8::Y([None; 2]),
2860        };
2861        let r4 = sm.scaling_lists_4x4();
2862        // Rule set A: i=0 → Default_4x4_Intra, i=1 falls back to i=0, etc.
2863        assert_eq!(r4[0], DEFAULT_4X4_INTRA);
2864        assert_eq!(r4[1], DEFAULT_4X4_INTRA); // falls back to i=0
2865        assert_eq!(r4[2], DEFAULT_4X4_INTRA); // falls back to i=1
2866        assert_eq!(r4[3], DEFAULT_4X4_INTER);
2867        assert_eq!(r4[4], DEFAULT_4X4_INTER); // falls back to i=3
2868        assert_eq!(r4[5], DEFAULT_4X4_INTER); // falls back to i=4
2869
2870        let r8 = sm.scaling_lists_8x8();
2871        let ScalingLists8x8Resolved::Y(lists) = r8 else {
2872            panic!("expected Y variant");
2873        };
2874        assert_eq!(lists[0], DEFAULT_8X8_INTRA);
2875        assert_eq!(lists[1], DEFAULT_8X8_INTER);
2876    }
2877
2878    #[test]
2879    fn seq_scaling_matrix_chain_fallback() {
2880        // i=0 has an explicit list, i=1 is None → should fall back to i=0's resolved list
2881        let custom: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
2882        let sm = SeqScalingMatrix {
2883            scaling_lists4x4: ScalingLists4x4([
2884                Some(custom),
2885                None, // falls back to i=0
2886                None, // falls back to i=1 = i=0
2887                None, // falls back to Default_4x4_Inter
2888                None, // falls back to i=3
2889                None, // falls back to i=4
2890            ]),
2891            scaling_lists8x8: ScalingLists8x8::Y([None; 2]),
2892        };
2893        let r = sm.scaling_lists_4x4();
2894        assert_eq!(r[0], custom);
2895        assert_eq!(r[1], custom);
2896        assert_eq!(r[2], custom);
2897        assert_eq!(r[3], DEFAULT_4X4_INTER);
2898        assert_eq!(r[4], DEFAULT_4X4_INTER);
2899        assert_eq!(r[5], DEFAULT_4X4_INTER);
2900    }
2901}