Skip to main content

oximedia_codec/av1/
frame_header.rs

1//! AV1 Frame Header parsing.
2//!
3//! The frame header OBU contains the core decoding parameters for each frame.
4//! This module implements complete frame header parsing according to the AV1
5//! specification (Section 5.9).
6//!
7//! # Frame Header Contents
8//!
9//! - Frame type (KEY_FRAME, INTER_FRAME, INTRA_ONLY_FRAME, SWITCH_FRAME)
10//! - Show frame / showable frame flags
11//! - Error resilient mode
12//! - Frame size (with optional superres scaling)
13//! - Render size
14//! - Interpolation filter
15//! - Reference frame selection
16//! - Motion mode and compound prediction settings
17//! - Quantization parameters
18//! - Segmentation
19//! - Loop filter parameters
20//! - CDEF parameters
21//! - Loop restoration parameters
22//! - Tile info
23//!
24//! # Reference
25//!
26//! See AV1 Specification Section 5.9 for the complete frame header syntax.
27
28#![forbid(unsafe_code)]
29#![allow(dead_code)]
30#![allow(clippy::doc_markdown)]
31#![allow(clippy::unused_self)]
32#![allow(clippy::cast_possible_truncation)]
33#![allow(clippy::trivially_copy_pass_by_ref)]
34#![allow(clippy::match_same_arms)]
35#![allow(clippy::struct_excessive_bools)]
36#![allow(clippy::struct_field_names)]
37#![allow(clippy::manual_div_ceil)]
38#![allow(clippy::cast_sign_loss)]
39#![allow(clippy::missing_errors_doc)]
40#![allow(clippy::unnecessary_cast)]
41#![allow(clippy::identity_op)]
42#![allow(clippy::if_not_else)]
43
44use super::cdef::CdefParams;
45use super::loop_filter::LoopFilterParams;
46use super::quantization::QuantizationParams;
47use super::sequence::SequenceHeader;
48use super::tile::TileInfo;
49use crate::error::{CodecError, CodecResult};
50use oximedia_io::BitReader;
51
52// =============================================================================
53// Constants from AV1 Specification
54// =============================================================================
55
56/// Maximum number of reference frames.
57pub const NUM_REF_FRAMES: usize = 8;
58
59/// Number of reference types for inter prediction.
60pub const REFS_PER_FRAME: usize = 7;
61
62/// Maximum number of segments.
63pub const MAX_SEGMENTS: usize = 8;
64
65/// Number of segment features.
66pub const SEG_LVL_MAX: usize = 8;
67
68/// Primary reference frame index indicating no primary reference.
69pub const PRIMARY_REF_NONE: u8 = 7;
70
71/// Number of superres scale denominator bits.
72pub const SUPERRES_DENOM_BITS: u8 = 3;
73
74/// Minimum superres denominator value.
75pub const SUPERRES_DENOM_MIN: u32 = 9;
76
77/// Superres number value.
78pub const SUPERRES_NUM: u32 = 8;
79
80/// Reference frame names/indices.
81pub const LAST_FRAME: usize = 1;
82pub const LAST2_FRAME: usize = 2;
83pub const LAST3_FRAME: usize = 3;
84pub const GOLDEN_FRAME: usize = 4;
85pub const BWDREF_FRAME: usize = 5;
86pub const ALTREF2_FRAME: usize = 6;
87pub const ALTREF_FRAME: usize = 7;
88
89/// Interpolation filter types.
90pub const INTERP_FILTER_EIGHTTAP: u8 = 0;
91pub const INTERP_FILTER_EIGHTTAP_SMOOTH: u8 = 1;
92pub const INTERP_FILTER_EIGHTTAP_SHARP: u8 = 2;
93pub const INTERP_FILTER_BILINEAR: u8 = 3;
94pub const INTERP_FILTER_SWITCHABLE: u8 = 4;
95
96// =============================================================================
97// Enumerations
98// =============================================================================
99
100/// AV1 frame types as defined in the specification (Section 6.8.2).
101#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
102#[repr(u8)]
103pub enum FrameType {
104    /// Key frame - independently decodable, random access point.
105    #[default]
106    KeyFrame = 0,
107    /// Inter frame - uses references from previous frames.
108    InterFrame = 1,
109    /// Intra-only frame - uses only intra prediction, not a random access point.
110    IntraOnlyFrame = 2,
111    /// Switch frame - special frame for stream switching.
112    SwitchFrame = 3,
113}
114
115impl FrameType {
116    /// Returns true if this is an intra frame (key frame or intra-only).
117    #[must_use]
118    pub const fn is_intra(self) -> bool {
119        matches!(self, Self::KeyFrame | Self::IntraOnlyFrame)
120    }
121
122    /// Returns true if this is a key frame.
123    #[must_use]
124    pub const fn is_key(self) -> bool {
125        matches!(self, Self::KeyFrame)
126    }
127
128    /// Returns true if this frame uses inter prediction.
129    #[must_use]
130    pub const fn is_inter(self) -> bool {
131        matches!(self, Self::InterFrame | Self::SwitchFrame)
132    }
133}
134
135impl From<u8> for FrameType {
136    fn from(value: u8) -> Self {
137        match value {
138            0 => Self::KeyFrame,
139            1 => Self::InterFrame,
140            2 => Self::IntraOnlyFrame,
141            3 => Self::SwitchFrame,
142            _ => Self::KeyFrame, // Invalid values default to key frame
143        }
144    }
145}
146
147impl From<FrameType> for u8 {
148    fn from(ft: FrameType) -> Self {
149        ft as u8
150    }
151}
152
153/// Interpolation filter types for motion compensation.
154#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
155#[repr(u8)]
156pub enum InterpolationFilter {
157    /// 8-tap regular filter.
158    #[default]
159    Eighttap = 0,
160    /// 8-tap smooth filter.
161    EighttapSmooth = 1,
162    /// 8-tap sharp filter.
163    EighttapSharp = 2,
164    /// Bilinear filter.
165    Bilinear = 3,
166    /// Per-block switchable filter.
167    Switchable = 4,
168}
169
170impl From<u8> for InterpolationFilter {
171    fn from(value: u8) -> Self {
172        match value {
173            0 => Self::Eighttap,
174            1 => Self::EighttapSmooth,
175            2 => Self::EighttapSharp,
176            3 => Self::Bilinear,
177            _ => Self::Switchable,
178        }
179    }
180}
181
182impl From<InterpolationFilter> for u8 {
183    fn from(f: InterpolationFilter) -> Self {
184        f as u8
185    }
186}
187
188/// Motion mode for inter prediction.
189#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
190#[repr(u8)]
191pub enum MotionMode {
192    /// Simple translation motion.
193    #[default]
194    Simple = 0,
195    /// Obstructed motion compensation.
196    ObstructedMotion = 1,
197    /// Local warped motion.
198    LocalWarp = 2,
199}
200
201impl From<u8> for MotionMode {
202    fn from(value: u8) -> Self {
203        match value {
204            0 => Self::Simple,
205            1 => Self::ObstructedMotion,
206            2 => Self::LocalWarp,
207            _ => Self::Simple,
208        }
209    }
210}
211
212/// Frame reference mode for compound prediction.
213#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
214#[repr(u8)]
215pub enum ReferenceMode {
216    /// Single reference only.
217    #[default]
218    SingleReference = 0,
219    /// Compound reference (two references per block).
220    CompoundReference = 1,
221    /// Per-block reference selection.
222    ReferenceModeSelect = 2,
223}
224
225impl From<u8> for ReferenceMode {
226    fn from(value: u8) -> Self {
227        match value {
228            0 => Self::SingleReference,
229            1 => Self::CompoundReference,
230            _ => Self::ReferenceModeSelect,
231        }
232    }
233}
234
235/// TX mode for transform selection.
236#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
237#[repr(u8)]
238pub enum TxMode {
239    /// Only 4x4 transforms.
240    Only4x4 = 0,
241    /// Largest transform size.
242    #[default]
243    Largest = 1,
244    /// Per-block transform selection.
245    Select = 2,
246}
247
248impl From<u8> for TxMode {
249    fn from(value: u8) -> Self {
250        match value {
251            0 => Self::Only4x4,
252            1 => Self::Largest,
253            _ => Self::Select,
254        }
255    }
256}
257
258/// Restoration type for loop restoration.
259#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
260#[repr(u8)]
261pub enum RestorationType {
262    /// No restoration.
263    #[default]
264    None = 0,
265    /// Wiener filter.
266    Wiener = 1,
267    /// Self-guided filter.
268    SgrProj = 2,
269    /// Switchable between Wiener and `SgrProj`.
270    Switchable = 3,
271}
272
273impl From<u8> for RestorationType {
274    fn from(value: u8) -> Self {
275        match value {
276            0 => Self::None,
277            1 => Self::Wiener,
278            2 => Self::SgrProj,
279            _ => Self::Switchable,
280        }
281    }
282}
283
284// =============================================================================
285// Structures
286// =============================================================================
287
288/// Frame size information including superres scaling.
289#[derive(Clone, Debug, Default)]
290pub struct FrameSize {
291    /// Frame width in pixels.
292    pub frame_width: u32,
293    /// Frame height in pixels.
294    pub frame_height: u32,
295    /// Superres upscaled width (may differ from frame_width).
296    pub upscaled_width: u32,
297    /// Superres denominator (8-16, where 8 means no scaling).
298    pub superres_denom: u32,
299    /// Whether superres is enabled for this frame.
300    pub use_superres: bool,
301    /// Width in 4x4 blocks (mi_cols).
302    pub mi_cols: u32,
303    /// Height in 4x4 blocks (mi_rows).
304    pub mi_rows: u32,
305}
306
307impl FrameSize {
308    /// Calculate the number of 4x4 blocks for a given dimension.
309    #[must_use]
310    pub const fn size_to_mi(size: u32) -> u32 {
311        (size + 3) >> 2
312    }
313
314    /// Calculate the number of superblocks for a given dimension.
315    #[must_use]
316    pub const fn size_to_sb(size: u32, sb_size: u32) -> u32 {
317        (size + sb_size - 1) / sb_size
318    }
319
320    /// Get the number of superblock columns.
321    #[must_use]
322    pub fn sb_cols(&self, sb_size: u32) -> u32 {
323        Self::size_to_sb(self.upscaled_width, sb_size)
324    }
325
326    /// Get the number of superblock rows.
327    #[must_use]
328    pub fn sb_rows(&self, sb_size: u32) -> u32 {
329        Self::size_to_sb(self.frame_height, sb_size)
330    }
331}
332
333/// Render size for display (may differ from coded size).
334#[derive(Clone, Debug, Default)]
335pub struct RenderSize {
336    /// Render width in pixels.
337    pub render_width: u32,
338    /// Render height in pixels.
339    pub render_height: u32,
340    /// Whether render size differs from frame size.
341    pub render_and_frame_size_different: bool,
342}
343
344/// Reference frame selection information.
345#[derive(Clone, Debug, Default)]
346pub struct RefFrameInfo {
347    /// Indices into the reference frame buffer for each reference type.
348    /// Index 0 = LAST_FRAME, 1 = LAST2_FRAME, etc.
349    pub ref_frame_idx: [u8; REFS_PER_FRAME],
350    /// Order hints for each reference frame slot.
351    pub ref_order_hint: [u8; NUM_REF_FRAMES],
352    /// Reference frame sign bias (for temporal ordering).
353    pub ref_frame_sign_bias: [bool; NUM_REF_FRAMES],
354}
355
356/// Segmentation parameters.
357#[derive(Clone, Debug, Default)]
358pub struct SegmentationParams {
359    /// Segmentation enabled.
360    pub enabled: bool,
361    /// Update the segmentation map.
362    pub update_map: bool,
363    /// Temporal update for segmentation.
364    pub temporal_update: bool,
365    /// Update segment data.
366    pub update_data: bool,
367    /// Segment feature enabled flags.
368    pub feature_enabled: [[bool; SEG_LVL_MAX]; MAX_SEGMENTS],
369    /// Segment feature data values.
370    pub feature_data: [[i16; SEG_LVL_MAX]; MAX_SEGMENTS],
371    /// Last active segment ID.
372    pub last_active_seg_id: u8,
373}
374
375impl SegmentationParams {
376    /// Segment feature index for ALT_Q (quantizer delta).
377    pub const SEG_LVL_ALT_Q: usize = 0;
378    /// Segment feature index for ALT_LF_Y_V (loop filter delta Y vertical).
379    pub const SEG_LVL_ALT_LF_Y_V: usize = 1;
380    /// Segment feature index for ALT_LF_Y_H (loop filter delta Y horizontal).
381    pub const SEG_LVL_ALT_LF_Y_H: usize = 2;
382    /// Segment feature index for ALT_LF_U (loop filter delta U).
383    pub const SEG_LVL_ALT_LF_U: usize = 3;
384    /// Segment feature index for ALT_LF_V (loop filter delta V).
385    pub const SEG_LVL_ALT_LF_V: usize = 4;
386    /// Segment feature index for REF_FRAME (reference frame).
387    pub const SEG_LVL_REF_FRAME: usize = 5;
388    /// Segment feature index for SKIP (skip mode).
389    pub const SEG_LVL_SKIP: usize = 6;
390    /// Segment feature index for GLOBALMV (global motion).
391    pub const SEG_LVL_GLOBALMV: usize = 7;
392
393    /// Maximum values for each segment feature.
394    pub const SEG_FEATURE_DATA_MAX: [i16; SEG_LVL_MAX] = [255, 63, 63, 63, 63, 7, 0, 0];
395
396    /// Whether each segment feature is signed.
397    pub const SEG_FEATURE_DATA_SIGNED: [bool; SEG_LVL_MAX] =
398        [true, true, true, true, true, false, false, false];
399
400    /// Number of bits for each segment feature.
401    pub const SEG_FEATURE_BITS: [u8; SEG_LVL_MAX] = [8, 6, 6, 6, 6, 3, 0, 0];
402
403    /// Get a segment feature value.
404    #[must_use]
405    pub fn get_feature(&self, segment_id: usize, feature: usize) -> i16 {
406        if segment_id < MAX_SEGMENTS
407            && feature < SEG_LVL_MAX
408            && self.feature_enabled[segment_id][feature]
409        {
410            self.feature_data[segment_id][feature]
411        } else {
412            0
413        }
414    }
415
416    /// Check if a segment feature is enabled.
417    #[must_use]
418    pub fn is_feature_enabled(&self, segment_id: usize, feature: usize) -> bool {
419        segment_id < MAX_SEGMENTS
420            && feature < SEG_LVL_MAX
421            && self.feature_enabled[segment_id][feature]
422    }
423}
424
425/// Loop restoration parameters for each plane.
426#[derive(Clone, Debug, Default)]
427pub struct LoopRestorationParams {
428    /// Restoration type per plane.
429    pub frame_restoration_type: [RestorationType; 3],
430    /// Restoration unit size (log2) per plane.
431    pub loop_restoration_size: [u8; 3],
432    /// Whether restoration is used.
433    pub uses_lr: bool,
434}
435
436/// Global motion parameters for a single reference frame.
437#[derive(Clone, Debug, Default)]
438pub struct GlobalMotionParams {
439    /// Transform type (0=identity, 1=translation, 2=rotzoom, 3=affine).
440    pub gm_type: u8,
441    /// Global motion parameters (up to 6 for affine).
442    pub gm_params: [i32; 6],
443}
444
445/// Global motion information for all reference frames.
446#[derive(Clone, Debug)]
447pub struct GlobalMotion {
448    /// Global motion for each reference frame.
449    pub params: [GlobalMotionParams; NUM_REF_FRAMES],
450}
451
452impl Default for GlobalMotion {
453    fn default() -> Self {
454        Self {
455            params: std::array::from_fn(|_| GlobalMotionParams::default()),
456        }
457    }
458}
459
460/// Film grain synthesis parameters.
461#[derive(Clone, Debug, Default)]
462pub struct FilmGrainParams {
463    /// Apply grain.
464    pub apply_grain: bool,
465    /// Grain seed.
466    pub grain_seed: u16,
467    /// Update grain parameters.
468    pub update_grain: bool,
469    /// Number of Y luma points.
470    pub num_y_points: u8,
471    /// Y point values.
472    pub point_y_value: [u8; 14],
473    /// Y point scaling.
474    pub point_y_scaling: [u8; 14],
475    /// Chroma scaling from luma.
476    pub chroma_scaling_from_luma: bool,
477    /// Number of Cb chroma points.
478    pub num_cb_points: u8,
479    /// Cb point values.
480    pub point_cb_value: [u8; 10],
481    /// Cb point scaling.
482    pub point_cb_scaling: [u8; 10],
483    /// Number of Cr chroma points.
484    pub num_cr_points: u8,
485    /// Cr point values.
486    pub point_cr_value: [u8; 10],
487    /// Cr point scaling.
488    pub point_cr_scaling: [u8; 10],
489    /// Grain scaling shift.
490    pub grain_scaling_minus_8: u8,
491    /// AR coefficients lag.
492    pub ar_coeff_lag: u8,
493    /// AR coefficients for Y.
494    pub ar_coeffs_y_plus_128: [u8; 24],
495    /// AR coefficients for Cb.
496    pub ar_coeffs_cb_plus_128: [u8; 25],
497    /// AR coefficients for Cr.
498    pub ar_coeffs_cr_plus_128: [u8; 25],
499    /// AR coefficient shift.
500    pub ar_coeff_shift_minus_6: u8,
501    /// Grain scale shift.
502    pub grain_scale_shift: u8,
503    /// Cb multiplier.
504    pub cb_mult: u8,
505    /// Cb luma multiplier.
506    pub cb_luma_mult: u8,
507    /// Cb offset.
508    pub cb_offset: u16,
509    /// Cr multiplier.
510    pub cr_mult: u8,
511    /// Cr luma multiplier.
512    pub cr_luma_mult: u8,
513    /// Cr offset.
514    pub cr_offset: u16,
515    /// Overlap flag.
516    pub overlap_flag: bool,
517    /// Clip to restricted range.
518    pub clip_to_restricted_range: bool,
519}
520
521/// Complete frame header structure.
522#[derive(Clone, Debug, Default)]
523#[allow(clippy::struct_excessive_bools)]
524pub struct FrameHeader {
525    // === Frame Type and Visibility ===
526    /// Frame type (KEY_FRAME, INTER_FRAME, INTRA_ONLY_FRAME, SWITCH_FRAME).
527    pub frame_type: FrameType,
528    /// Whether to show this frame.
529    pub show_frame: bool,
530    /// Whether this frame can be shown later via show_existing_frame.
531    pub showable_frame: bool,
532    /// Show an existing frame from the buffer.
533    pub show_existing_frame: bool,
534    /// Frame to show (if show_existing_frame is true).
535    pub frame_to_show_map_idx: u8,
536    /// Error resilient mode (disables some dependencies).
537    pub error_resilient_mode: bool,
538
539    // === Frame Identification ===
540    /// Frame ID for error resilience.
541    pub current_frame_id: u32,
542    /// Order hint for temporal ordering.
543    pub order_hint: u8,
544    /// Primary reference frame index.
545    pub primary_ref_frame: u8,
546    /// Refresh frame flags (which slots to update).
547    pub refresh_frame_flags: u8,
548
549    // === Frame Size ===
550    /// Frame size information.
551    pub frame_size: FrameSize,
552    /// Render size information.
553    pub render_size: RenderSize,
554
555    // === Inter Prediction Settings ===
556    /// Allow high precision motion vectors.
557    pub allow_high_precision_mv: bool,
558    /// Interpolation filter for motion compensation.
559    pub interpolation_filter: InterpolationFilter,
560    /// Whether the filter is switchable per-block.
561    pub is_filter_switchable: bool,
562    /// Intra block copy allowed.
563    pub allow_intrabc: bool,
564
565    // === Reference Frame Management ===
566    /// Reference frame information.
567    pub ref_frame_info: RefFrameInfo,
568    /// Allow screen content tools.
569    pub allow_screen_content_tools: bool,
570    /// Force integer motion vectors.
571    pub force_integer_mv: bool,
572
573    // === Motion Settings ===
574    /// Motion mode (simple, obmc, warp).
575    pub is_motion_mode_switchable: bool,
576    /// Use reference frame motion vectors.
577    pub use_ref_frame_mvs: bool,
578    /// Reference mode (single, compound, select).
579    pub reference_mode: ReferenceMode,
580    /// Skip mode frame indices.
581    pub skip_mode_frame: [u8; 2],
582    /// Skip mode allowed.
583    pub skip_mode_allowed: bool,
584    /// Skip mode present.
585    pub skip_mode_present: bool,
586
587    // === Compound Prediction ===
588    /// Compound inter allowed.
589    pub compound_reference_allowed: bool,
590
591    // === Transform Settings ===
592    /// TX mode for transform selection.
593    pub tx_mode: TxMode,
594    /// Reduced TX set for complexity reduction.
595    pub reduced_tx_set: bool,
596
597    // === Warped Motion ===
598    /// Allow warped motion.
599    pub allow_warped_motion: bool,
600
601    // === Quantization ===
602    /// Quantization parameters.
603    pub quantization: QuantizationParams,
604
605    // === Segmentation ===
606    /// Segmentation parameters.
607    pub segmentation: SegmentationParams,
608
609    // === Loop Filter ===
610    /// Loop filter parameters.
611    pub loop_filter: LoopFilterParams,
612
613    // === CDEF ===
614    /// CDEF parameters.
615    pub cdef: CdefParams,
616
617    // === Loop Restoration ===
618    /// Loop restoration parameters.
619    pub loop_restoration: LoopRestorationParams,
620
621    // === Tile Info ===
622    /// Tile information.
623    pub tile_info: TileInfo,
624
625    // === Global Motion ===
626    /// Global motion parameters.
627    pub global_motion: GlobalMotion,
628
629    // === Film Grain ===
630    /// Film grain parameters.
631    pub film_grain: FilmGrainParams,
632
633    // === Derived Values ===
634    /// Frame is intra-only (key frame or intra-only frame).
635    pub frame_is_intra: bool,
636    /// All lossless mode.
637    pub lossless_array: [bool; MAX_SEGMENTS],
638    /// Coded lossless (all segments lossless and no chroma subsampling issues).
639    pub coded_lossless: bool,
640    /// All lossless (coded lossless and frame size equals superres size).
641    pub all_lossless: bool,
642}
643
644impl FrameHeader {
645    /// Create a new default frame header.
646    #[must_use]
647    pub fn new() -> Self {
648        Self::default()
649    }
650
651    /// Parse a frame header from the bitstream.
652    ///
653    /// # Errors
654    ///
655    /// Returns error if the frame header is malformed.
656    #[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
657    pub fn parse(data: &[u8], seq: &SequenceHeader) -> CodecResult<Self> {
658        let mut reader = BitReader::new(data);
659        let mut header = Self::new();
660
661        // Parse uncompressed header
662        header.parse_uncompressed_header(&mut reader, seq)?;
663
664        Ok(header)
665    }
666
667    /// Parse the uncompressed header portion.
668    #[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
669    fn parse_uncompressed_header(
670        &mut self,
671        reader: &mut BitReader<'_>,
672        seq: &SequenceHeader,
673    ) -> CodecResult<()> {
674        // Frame ID management
675        let frame_id_length = if seq.reduced_still_picture_header {
676            0
677        } else {
678            // Simplified: would come from sequence header
679            15
680        };
681
682        // Show existing frame
683        if seq.reduced_still_picture_header {
684            self.show_existing_frame = false;
685            self.frame_type = FrameType::KeyFrame;
686            self.show_frame = true;
687            self.showable_frame = false;
688        } else {
689            self.show_existing_frame = reader.read_bit().map_err(CodecError::Core)? != 0;
690
691            if self.show_existing_frame {
692                self.frame_to_show_map_idx = reader.read_bits(3).map_err(CodecError::Core)? as u8;
693
694                if frame_id_length > 0 {
695                    self.current_frame_id = reader
696                        .read_bits(frame_id_length)
697                        .map_err(CodecError::Core)?
698                        as u32;
699                }
700
701                // For show_existing_frame, we're done with header parsing
702                return Ok(());
703            }
704
705            self.frame_type = FrameType::from(reader.read_bits(2).map_err(CodecError::Core)? as u8);
706            self.show_frame = reader.read_bit().map_err(CodecError::Core)? != 0;
707
708            if self.show_frame && !seq.reduced_still_picture_header {
709                // decoder_model_info_present handling would go here
710            }
711
712            self.showable_frame = if self.show_frame {
713                !self.frame_type.is_key()
714            } else {
715                reader.read_bit().map_err(CodecError::Core)? != 0
716            };
717
718            self.error_resilient_mode =
719                if self.frame_type == FrameType::SwitchFrame || self.frame_type.is_key() {
720                    true
721                } else {
722                    reader.read_bit().map_err(CodecError::Core)? != 0
723                };
724        }
725
726        self.frame_is_intra = self.frame_type.is_intra();
727
728        // Disable CDF update flag (ignored for now)
729        if !seq.reduced_still_picture_header {
730            let _disable_cdf_update = reader.read_bit().map_err(CodecError::Core)?;
731        }
732
733        // Screen content tools
734        self.allow_screen_content_tools = if seq.reduced_still_picture_header {
735            false
736        } else {
737            // seq_force_screen_content_tools handling
738            reader.read_bit().map_err(CodecError::Core)? != 0
739        };
740
741        // Force integer MV
742        self.force_integer_mv = if self.allow_screen_content_tools {
743            if !seq.reduced_still_picture_header {
744                reader.read_bit().map_err(CodecError::Core)? != 0
745            } else {
746                false
747            }
748        } else {
749            false
750        };
751
752        // Frame ID
753        if frame_id_length > 0 {
754            self.current_frame_id = reader
755                .read_bits(frame_id_length)
756                .map_err(CodecError::Core)? as u32;
757        }
758
759        // Frame size
760        self.parse_frame_size(reader, seq)?;
761
762        // Render size
763        self.parse_render_size(reader)?;
764
765        // Superres params if enabled
766        if seq.enable_superres && !self.frame_size.use_superres {
767            self.frame_size.use_superres = reader.read_bit().map_err(CodecError::Core)? != 0;
768            if self.frame_size.use_superres {
769                let coded_denom = reader
770                    .read_bits(SUPERRES_DENOM_BITS)
771                    .map_err(CodecError::Core)? as u32
772                    + SUPERRES_DENOM_MIN;
773                self.frame_size.superres_denom = coded_denom;
774                self.frame_size.upscaled_width = self.frame_size.frame_width;
775                self.frame_size.frame_width =
776                    (self.frame_size.upscaled_width * SUPERRES_NUM + coded_denom / 2) / coded_denom;
777            }
778        }
779
780        if !self.frame_is_intra {
781            // Inter frame specific parsing
782            self.parse_inter_frame_params(reader, seq)?;
783        }
784
785        // Quantization params
786        self.quantization = QuantizationParams::parse(reader, seq)?;
787
788        // Segmentation params
789        self.parse_segmentation(reader)?;
790
791        // Compute lossless arrays
792        self.compute_lossless(seq);
793
794        // Loop filter params (only if not all lossless)
795        if !self.coded_lossless {
796            self.loop_filter = LoopFilterParams::parse(reader, seq, self.frame_is_intra)?;
797        }
798
799        // CDEF params
800        if seq.enable_cdef && !self.coded_lossless && !self.allow_intrabc {
801            self.cdef = CdefParams::parse(reader, seq)?;
802        }
803
804        // Loop restoration
805        if seq.enable_restoration && !self.all_lossless && !self.allow_intrabc {
806            self.parse_loop_restoration(reader, seq)?;
807        }
808
809        // TX mode
810        self.parse_tx_mode(reader)?;
811
812        // Reference mode
813        if !self.frame_is_intra {
814            self.reference_mode = if reader.read_bit().map_err(CodecError::Core)? != 0 {
815                ReferenceMode::ReferenceModeSelect
816            } else {
817                ReferenceMode::SingleReference
818            };
819        }
820
821        // Skip mode
822        self.parse_skip_mode(reader, seq)?;
823
824        // Warped motion
825        if !self.frame_is_intra && !self.error_resilient_mode {
826            self.allow_warped_motion = reader.read_bit().map_err(CodecError::Core)? != 0;
827        }
828
829        // Reduced TX set
830        self.reduced_tx_set = reader.read_bit().map_err(CodecError::Core)? != 0;
831
832        // Global motion
833        if !self.frame_is_intra {
834            self.parse_global_motion(reader)?;
835        }
836
837        // Film grain
838        if seq.film_grain_params_present && (self.show_frame || self.showable_frame) {
839            self.parse_film_grain(reader, seq)?;
840        }
841
842        // Tile info
843        self.tile_info = TileInfo::parse(reader, seq, &self.frame_size)?;
844
845        Ok(())
846    }
847
848    /// Parse frame size.
849    #[allow(clippy::cast_possible_truncation)]
850    fn parse_frame_size(
851        &mut self,
852        reader: &mut BitReader<'_>,
853        seq: &SequenceHeader,
854    ) -> CodecResult<()> {
855        if self.frame_type == FrameType::SwitchFrame {
856            // Switch frames use max dimensions
857            self.frame_size.frame_width = seq.max_frame_width();
858            self.frame_size.frame_height = seq.max_frame_height();
859        } else {
860            let frame_size_override = if seq.reduced_still_picture_header {
861                false
862            } else {
863                reader.read_bit().map_err(CodecError::Core)? != 0
864            };
865
866            if frame_size_override {
867                // Read explicit frame dimensions
868                let frame_width_bits = 16; // Simplified
869                let frame_height_bits = 16;
870                self.frame_size.frame_width = reader
871                    .read_bits(frame_width_bits)
872                    .map_err(CodecError::Core)?
873                    as u32
874                    + 1;
875                self.frame_size.frame_height = reader
876                    .read_bits(frame_height_bits)
877                    .map_err(CodecError::Core)?
878                    as u32
879                    + 1;
880            } else {
881                self.frame_size.frame_width = seq.max_frame_width();
882                self.frame_size.frame_height = seq.max_frame_height();
883            }
884        }
885
886        self.frame_size.upscaled_width = self.frame_size.frame_width;
887        self.frame_size.superres_denom = SUPERRES_NUM;
888        self.frame_size.mi_cols = FrameSize::size_to_mi(self.frame_size.upscaled_width);
889        self.frame_size.mi_rows = FrameSize::size_to_mi(self.frame_size.frame_height);
890
891        Ok(())
892    }
893
894    /// Parse render size.
895    fn parse_render_size(&mut self, reader: &mut BitReader<'_>) -> CodecResult<()> {
896        self.render_size.render_and_frame_size_different =
897            reader.read_bit().map_err(CodecError::Core)? != 0;
898
899        if self.render_size.render_and_frame_size_different {
900            let render_width_minus_1 = reader.read_bits(16).map_err(CodecError::Core)? as u32;
901            let render_height_minus_1 = reader.read_bits(16).map_err(CodecError::Core)? as u32;
902            self.render_size.render_width = render_width_minus_1 + 1;
903            self.render_size.render_height = render_height_minus_1 + 1;
904        } else {
905            self.render_size.render_width = self.frame_size.upscaled_width;
906            self.render_size.render_height = self.frame_size.frame_height;
907        }
908
909        Ok(())
910    }
911
912    /// Parse inter frame parameters.
913    #[allow(clippy::cast_possible_truncation)]
914    fn parse_inter_frame_params(
915        &mut self,
916        reader: &mut BitReader<'_>,
917        seq: &SequenceHeader,
918    ) -> CodecResult<()> {
919        // Reference frame assignment
920        for i in 0..REFS_PER_FRAME {
921            self.ref_frame_info.ref_frame_idx[i] =
922                reader.read_bits(3).map_err(CodecError::Core)? as u8;
923        }
924
925        // Frame size with refs
926        if !self.error_resilient_mode && self.frame_type != FrameType::SwitchFrame {
927            // Simplified: would parse frame_size_with_refs()
928        }
929
930        // Allow high precision MV
931        self.allow_high_precision_mv = if self.force_integer_mv {
932            false
933        } else {
934            reader.read_bit().map_err(CodecError::Core)? != 0
935        };
936
937        // Interpolation filter
938        self.is_filter_switchable = reader.read_bit().map_err(CodecError::Core)? != 0;
939        self.interpolation_filter = if self.is_filter_switchable {
940            InterpolationFilter::Switchable
941        } else {
942            InterpolationFilter::from(reader.read_bits(2).map_err(CodecError::Core)? as u8)
943        };
944
945        // Motion mode
946        self.is_motion_mode_switchable = reader.read_bit().map_err(CodecError::Core)? != 0;
947
948        // Use ref frame MVs
949        if !self.error_resilient_mode && seq.enable_order_hint {
950            self.use_ref_frame_mvs = reader.read_bit().map_err(CodecError::Core)? != 0;
951        }
952
953        Ok(())
954    }
955
956    /// Parse segmentation parameters.
957    #[allow(clippy::cast_possible_truncation)]
958    fn parse_segmentation(&mut self, reader: &mut BitReader<'_>) -> CodecResult<()> {
959        self.segmentation.enabled = reader.read_bit().map_err(CodecError::Core)? != 0;
960
961        if !self.segmentation.enabled {
962            return Ok(());
963        }
964
965        if self.primary_ref_frame == PRIMARY_REF_NONE {
966            self.segmentation.update_map = true;
967            self.segmentation.temporal_update = false;
968            self.segmentation.update_data = true;
969        } else {
970            self.segmentation.update_map = reader.read_bit().map_err(CodecError::Core)? != 0;
971            if self.segmentation.update_map {
972                self.segmentation.temporal_update =
973                    reader.read_bit().map_err(CodecError::Core)? != 0;
974            }
975            self.segmentation.update_data = reader.read_bit().map_err(CodecError::Core)? != 0;
976        }
977
978        if self.segmentation.update_data {
979            for i in 0..MAX_SEGMENTS {
980                for j in 0..SEG_LVL_MAX {
981                    self.segmentation.feature_enabled[i][j] =
982                        reader.read_bit().map_err(CodecError::Core)? != 0;
983
984                    if self.segmentation.feature_enabled[i][j] {
985                        let bits = SegmentationParams::SEG_FEATURE_BITS[j];
986                        let max = SegmentationParams::SEG_FEATURE_DATA_MAX[j];
987
988                        if SegmentationParams::SEG_FEATURE_DATA_SIGNED[j] {
989                            let value =
990                                reader.read_bits(bits + 1).map_err(CodecError::Core)? as i16;
991                            let sign = if value & (1 << bits) != 0 { -1 } else { 1 };
992                            let magnitude = value & ((1 << bits) - 1);
993                            self.segmentation.feature_data[i][j] =
994                                (sign * magnitude).clamp(-max, max);
995                        } else {
996                            self.segmentation.feature_data[i][j] =
997                                (reader.read_bits(bits).map_err(CodecError::Core)? as i16).min(max);
998                        }
999                    }
1000                }
1001            }
1002        }
1003
1004        // Find last active segment
1005        self.segmentation.last_active_seg_id = 0;
1006        for i in 0..MAX_SEGMENTS {
1007            for j in 0..SEG_LVL_MAX {
1008                if self.segmentation.feature_enabled[i][j] {
1009                    self.segmentation.last_active_seg_id = i as u8;
1010                }
1011            }
1012        }
1013
1014        Ok(())
1015    }
1016
1017    /// Compute lossless mode for each segment.
1018    fn compute_lossless(&mut self, seq: &SequenceHeader) {
1019        for seg_id in 0..MAX_SEGMENTS {
1020            let qindex = self.get_qindex(seg_id);
1021            let lossless = qindex == 0
1022                && self.quantization.delta_q_y_dc == 0
1023                && self.quantization.delta_q_u_ac == 0
1024                && self.quantization.delta_q_u_dc == 0
1025                && self.quantization.delta_q_v_ac == 0
1026                && self.quantization.delta_q_v_dc == 0;
1027            self.lossless_array[seg_id] = lossless;
1028        }
1029
1030        self.coded_lossless = self.lossless_array.iter().all(|&l| l);
1031        self.all_lossless =
1032            self.coded_lossless && self.frame_size.frame_width == self.frame_size.upscaled_width;
1033
1034        // Update loop filter disable for lossless
1035        if self.coded_lossless {
1036            self.loop_filter.level[0] = 0;
1037            self.loop_filter.level[1] = 0;
1038        }
1039
1040        // Handle monochrome case
1041        if seq.color_config.mono_chrome {
1042            self.quantization.delta_q_u_dc = 0;
1043            self.quantization.delta_q_u_ac = 0;
1044            self.quantization.delta_q_v_dc = 0;
1045            self.quantization.delta_q_v_ac = 0;
1046        }
1047    }
1048
1049    /// Get quantizer index for a segment.
1050    #[must_use]
1051    pub fn get_qindex(&self, seg_id: usize) -> u8 {
1052        let base_q = self.quantization.base_q_idx;
1053        if self.segmentation.enabled
1054            && self
1055                .segmentation
1056                .is_feature_enabled(seg_id, SegmentationParams::SEG_LVL_ALT_Q)
1057        {
1058            let delta = self
1059                .segmentation
1060                .get_feature(seg_id, SegmentationParams::SEG_LVL_ALT_Q);
1061            let q = i32::from(base_q) + i32::from(delta);
1062            q.clamp(0, 255) as u8
1063        } else {
1064            base_q
1065        }
1066    }
1067
1068    /// Parse loop restoration parameters.
1069    #[allow(clippy::cast_possible_truncation)]
1070    fn parse_loop_restoration(
1071        &mut self,
1072        reader: &mut BitReader<'_>,
1073        seq: &SequenceHeader,
1074    ) -> CodecResult<()> {
1075        let num_planes = if seq.color_config.mono_chrome { 1 } else { 3 };
1076        let mut uses_lr = false;
1077        let mut uses_chroma_lr = false;
1078
1079        for plane in 0..num_planes {
1080            let lr_type = reader.read_bits(2).map_err(CodecError::Core)? as u8;
1081            self.loop_restoration.frame_restoration_type[plane] = RestorationType::from(lr_type);
1082            if lr_type != 0 {
1083                uses_lr = true;
1084                if plane > 0 {
1085                    uses_chroma_lr = true;
1086                }
1087            }
1088        }
1089
1090        self.loop_restoration.uses_lr = uses_lr;
1091
1092        if uses_lr {
1093            // LR unit shift
1094            let lr_unit_shift = if seq.enable_superres {
1095                reader.read_bit().map_err(CodecError::Core)? as u8
1096            } else {
1097                1
1098            };
1099
1100            let lr_unit_extra_shift = if lr_unit_shift != 0 && !seq.enable_superres {
1101                reader.read_bit().map_err(CodecError::Core)? as u8
1102            } else {
1103                0
1104            };
1105
1106            // Set LR sizes
1107            let sb_size = 64; // Simplified
1108            let lr_size_base = 6 + lr_unit_shift + lr_unit_extra_shift;
1109            self.loop_restoration.loop_restoration_size[0] = lr_size_base.min(sb_size);
1110
1111            if uses_chroma_lr && !seq.color_config.is_420() {
1112                let uv_shift = reader.read_bit().map_err(CodecError::Core)? as u8;
1113                self.loop_restoration.loop_restoration_size[1] = lr_size_base - uv_shift;
1114                self.loop_restoration.loop_restoration_size[2] = lr_size_base - uv_shift;
1115            } else {
1116                self.loop_restoration.loop_restoration_size[1] = lr_size_base;
1117                self.loop_restoration.loop_restoration_size[2] = lr_size_base;
1118            }
1119        }
1120
1121        Ok(())
1122    }
1123
1124    /// Parse TX mode.
1125    fn parse_tx_mode(&mut self, reader: &mut BitReader<'_>) -> CodecResult<()> {
1126        if self.coded_lossless {
1127            self.tx_mode = TxMode::Only4x4;
1128        } else {
1129            let tx_mode_select = reader.read_bit().map_err(CodecError::Core)? != 0;
1130            self.tx_mode = if tx_mode_select {
1131                TxMode::Select
1132            } else {
1133                TxMode::Largest
1134            };
1135        }
1136        Ok(())
1137    }
1138
1139    /// Parse skip mode.
1140    #[allow(clippy::cast_possible_truncation)]
1141    fn parse_skip_mode(
1142        &mut self,
1143        reader: &mut BitReader<'_>,
1144        seq: &SequenceHeader,
1145    ) -> CodecResult<()> {
1146        if self.frame_is_intra
1147            || !self.reference_mode.eq(&ReferenceMode::ReferenceModeSelect)
1148            || !seq.enable_order_hint
1149        {
1150            self.skip_mode_allowed = false;
1151        } else {
1152            // Simplified skip mode derivation
1153            self.skip_mode_allowed = true;
1154        }
1155
1156        if self.skip_mode_allowed {
1157            self.skip_mode_present = reader.read_bit().map_err(CodecError::Core)? != 0;
1158        } else {
1159            self.skip_mode_present = false;
1160        }
1161
1162        Ok(())
1163    }
1164
1165    /// Parse global motion parameters.
1166    #[allow(clippy::cast_possible_truncation)]
1167    fn parse_global_motion(&mut self, reader: &mut BitReader<'_>) -> CodecResult<()> {
1168        for ref_frame in LAST_FRAME..=ALTREF_FRAME {
1169            // Global motion type
1170            let is_global = reader.read_bit().map_err(CodecError::Core)? != 0;
1171            if is_global {
1172                let is_rot_zoom = reader.read_bit().map_err(CodecError::Core)? != 0;
1173                if is_rot_zoom {
1174                    self.global_motion.params[ref_frame].gm_type = 2; // ROTZOOM
1175                } else {
1176                    let is_translation = reader.read_bit().map_err(CodecError::Core)? != 0;
1177                    self.global_motion.params[ref_frame].gm_type = if is_translation {
1178                        1 // TRANSLATION
1179                    } else {
1180                        3 // AFFINE
1181                    };
1182                }
1183
1184                // Parse global motion params (simplified)
1185                // Full parsing would read the actual motion parameters
1186            } else {
1187                self.global_motion.params[ref_frame].gm_type = 0; // IDENTITY
1188            }
1189        }
1190
1191        Ok(())
1192    }
1193
1194    /// Parse film grain parameters.
1195    #[allow(clippy::cast_possible_truncation)]
1196    fn parse_film_grain(
1197        &mut self,
1198        reader: &mut BitReader<'_>,
1199        seq: &SequenceHeader,
1200    ) -> CodecResult<()> {
1201        self.film_grain.apply_grain = reader.read_bit().map_err(CodecError::Core)? != 0;
1202
1203        if !self.film_grain.apply_grain {
1204            return Ok(());
1205        }
1206
1207        self.film_grain.grain_seed = reader.read_bits(16).map_err(CodecError::Core)? as u16;
1208
1209        if self.frame_type == FrameType::InterFrame {
1210            self.film_grain.update_grain = reader.read_bit().map_err(CodecError::Core)? != 0;
1211        } else {
1212            self.film_grain.update_grain = true;
1213        }
1214
1215        if !self.film_grain.update_grain {
1216            // Reference film grain params from another frame
1217            let _film_grain_params_ref_idx = reader.read_bits(3).map_err(CodecError::Core)?;
1218            return Ok(());
1219        }
1220
1221        // Y points
1222        self.film_grain.num_y_points = reader.read_bits(4).map_err(CodecError::Core)? as u8;
1223        for i in 0..self.film_grain.num_y_points as usize {
1224            self.film_grain.point_y_value[i] = reader.read_bits(8).map_err(CodecError::Core)? as u8;
1225            self.film_grain.point_y_scaling[i] =
1226                reader.read_bits(8).map_err(CodecError::Core)? as u8;
1227        }
1228
1229        // Chroma scaling from luma
1230        self.film_grain.chroma_scaling_from_luma = if !seq.color_config.mono_chrome {
1231            reader.read_bit().map_err(CodecError::Core)? != 0
1232        } else {
1233            false
1234        };
1235
1236        // Cb/Cr points
1237        if seq.color_config.mono_chrome
1238            || self.film_grain.chroma_scaling_from_luma
1239            || (seq.color_config.is_420() && self.film_grain.num_y_points == 0)
1240        {
1241            self.film_grain.num_cb_points = 0;
1242            self.film_grain.num_cr_points = 0;
1243        } else {
1244            self.film_grain.num_cb_points = reader.read_bits(4).map_err(CodecError::Core)? as u8;
1245            for i in 0..self.film_grain.num_cb_points as usize {
1246                self.film_grain.point_cb_value[i] =
1247                    reader.read_bits(8).map_err(CodecError::Core)? as u8;
1248                self.film_grain.point_cb_scaling[i] =
1249                    reader.read_bits(8).map_err(CodecError::Core)? as u8;
1250            }
1251
1252            self.film_grain.num_cr_points = reader.read_bits(4).map_err(CodecError::Core)? as u8;
1253            for i in 0..self.film_grain.num_cr_points as usize {
1254                self.film_grain.point_cr_value[i] =
1255                    reader.read_bits(8).map_err(CodecError::Core)? as u8;
1256                self.film_grain.point_cr_scaling[i] =
1257                    reader.read_bits(8).map_err(CodecError::Core)? as u8;
1258            }
1259        }
1260
1261        // Grain scaling and AR coefficients
1262        self.film_grain.grain_scaling_minus_8 =
1263            reader.read_bits(2).map_err(CodecError::Core)? as u8;
1264        self.film_grain.ar_coeff_lag = reader.read_bits(2).map_err(CodecError::Core)? as u8;
1265
1266        // AR coefficients (simplified)
1267        let num_pos_luma = 2 * self.film_grain.ar_coeff_lag * (self.film_grain.ar_coeff_lag + 1);
1268        for i in 0..num_pos_luma as usize {
1269            if self.film_grain.num_y_points > 0 && i < 24 {
1270                self.film_grain.ar_coeffs_y_plus_128[i] =
1271                    reader.read_bits(8).map_err(CodecError::Core)? as u8;
1272            }
1273        }
1274
1275        self.film_grain.ar_coeff_shift_minus_6 =
1276            reader.read_bits(2).map_err(CodecError::Core)? as u8;
1277        self.film_grain.grain_scale_shift = reader.read_bits(2).map_err(CodecError::Core)? as u8;
1278
1279        // Chroma multipliers
1280        if self.film_grain.num_cb_points > 0 {
1281            self.film_grain.cb_mult = reader.read_bits(8).map_err(CodecError::Core)? as u8;
1282            self.film_grain.cb_luma_mult = reader.read_bits(8).map_err(CodecError::Core)? as u8;
1283            self.film_grain.cb_offset = reader.read_bits(9).map_err(CodecError::Core)? as u16;
1284        }
1285
1286        if self.film_grain.num_cr_points > 0 {
1287            self.film_grain.cr_mult = reader.read_bits(8).map_err(CodecError::Core)? as u8;
1288            self.film_grain.cr_luma_mult = reader.read_bits(8).map_err(CodecError::Core)? as u8;
1289            self.film_grain.cr_offset = reader.read_bits(9).map_err(CodecError::Core)? as u16;
1290        }
1291
1292        self.film_grain.overlap_flag = reader.read_bit().map_err(CodecError::Core)? != 0;
1293        self.film_grain.clip_to_restricted_range =
1294            reader.read_bit().map_err(CodecError::Core)? != 0;
1295
1296        Ok(())
1297    }
1298
1299    /// Check if this frame is a keyframe.
1300    #[must_use]
1301    pub const fn is_key_frame(&self) -> bool {
1302        matches!(self.frame_type, FrameType::KeyFrame)
1303    }
1304
1305    /// Check if this frame uses inter prediction.
1306    #[must_use]
1307    pub const fn is_inter_frame(&self) -> bool {
1308        matches!(
1309            self.frame_type,
1310            FrameType::InterFrame | FrameType::SwitchFrame
1311        )
1312    }
1313
1314    /// Get the display order for this frame.
1315    #[must_use]
1316    pub const fn display_order(&self) -> u8 {
1317        self.order_hint
1318    }
1319}
1320
1321// =============================================================================
1322// Tests
1323// =============================================================================
1324
1325#[cfg(test)]
1326mod tests {
1327    use super::*;
1328
1329    #[test]
1330    fn test_frame_type_conversions() {
1331        assert_eq!(FrameType::from(0), FrameType::KeyFrame);
1332        assert_eq!(FrameType::from(1), FrameType::InterFrame);
1333        assert_eq!(FrameType::from(2), FrameType::IntraOnlyFrame);
1334        assert_eq!(FrameType::from(3), FrameType::SwitchFrame);
1335        assert_eq!(FrameType::from(4), FrameType::KeyFrame);
1336
1337        assert_eq!(u8::from(FrameType::KeyFrame), 0);
1338        assert_eq!(u8::from(FrameType::InterFrame), 1);
1339    }
1340
1341    #[test]
1342    fn test_frame_type_properties() {
1343        assert!(FrameType::KeyFrame.is_intra());
1344        assert!(FrameType::KeyFrame.is_key());
1345        assert!(!FrameType::KeyFrame.is_inter());
1346
1347        assert!(!FrameType::InterFrame.is_intra());
1348        assert!(!FrameType::InterFrame.is_key());
1349        assert!(FrameType::InterFrame.is_inter());
1350
1351        assert!(FrameType::IntraOnlyFrame.is_intra());
1352        assert!(!FrameType::IntraOnlyFrame.is_key());
1353        assert!(!FrameType::IntraOnlyFrame.is_inter());
1354
1355        assert!(!FrameType::SwitchFrame.is_intra());
1356        assert!(!FrameType::SwitchFrame.is_key());
1357        assert!(FrameType::SwitchFrame.is_inter());
1358    }
1359
1360    #[test]
1361    fn test_interpolation_filter_conversions() {
1362        assert_eq!(InterpolationFilter::from(0), InterpolationFilter::Eighttap);
1363        assert_eq!(
1364            InterpolationFilter::from(1),
1365            InterpolationFilter::EighttapSmooth
1366        );
1367        assert_eq!(
1368            InterpolationFilter::from(2),
1369            InterpolationFilter::EighttapSharp
1370        );
1371        assert_eq!(InterpolationFilter::from(3), InterpolationFilter::Bilinear);
1372        assert_eq!(
1373            InterpolationFilter::from(4),
1374            InterpolationFilter::Switchable
1375        );
1376    }
1377
1378    #[test]
1379    fn test_frame_size_calculations() {
1380        assert_eq!(FrameSize::size_to_mi(1920), 480);
1381        assert_eq!(FrameSize::size_to_mi(1080), 270);
1382        assert_eq!(FrameSize::size_to_mi(1), 1);
1383        assert_eq!(FrameSize::size_to_mi(4), 1);
1384        assert_eq!(FrameSize::size_to_mi(5), 2);
1385
1386        assert_eq!(FrameSize::size_to_sb(1920, 64), 30);
1387        assert_eq!(FrameSize::size_to_sb(1920, 128), 15);
1388        assert_eq!(FrameSize::size_to_sb(1080, 64), 17);
1389    }
1390
1391    #[test]
1392    fn test_frame_size_sb_calculations() {
1393        let frame_size = FrameSize {
1394            frame_width: 1920,
1395            frame_height: 1080,
1396            upscaled_width: 1920,
1397            superres_denom: 8,
1398            use_superres: false,
1399            mi_cols: 480,
1400            mi_rows: 270,
1401        };
1402
1403        assert_eq!(frame_size.sb_cols(64), 30);
1404        assert_eq!(frame_size.sb_rows(64), 17);
1405        assert_eq!(frame_size.sb_cols(128), 15);
1406        assert_eq!(frame_size.sb_rows(128), 9);
1407    }
1408
1409    #[test]
1410    fn test_segmentation_features() {
1411        let mut seg = SegmentationParams::default();
1412        seg.enabled = true;
1413        seg.feature_enabled[0][SegmentationParams::SEG_LVL_ALT_Q] = true;
1414        seg.feature_data[0][SegmentationParams::SEG_LVL_ALT_Q] = 10;
1415
1416        assert!(seg.is_feature_enabled(0, SegmentationParams::SEG_LVL_ALT_Q));
1417        assert_eq!(seg.get_feature(0, SegmentationParams::SEG_LVL_ALT_Q), 10);
1418        assert_eq!(seg.get_feature(1, SegmentationParams::SEG_LVL_ALT_Q), 0);
1419        assert!(!seg.is_feature_enabled(0, SegmentationParams::SEG_LVL_SKIP));
1420    }
1421
1422    #[test]
1423    fn test_motion_mode_conversion() {
1424        assert_eq!(MotionMode::from(0), MotionMode::Simple);
1425        assert_eq!(MotionMode::from(1), MotionMode::ObstructedMotion);
1426        assert_eq!(MotionMode::from(2), MotionMode::LocalWarp);
1427        assert_eq!(MotionMode::from(99), MotionMode::Simple);
1428    }
1429
1430    #[test]
1431    fn test_reference_mode_conversion() {
1432        assert_eq!(ReferenceMode::from(0), ReferenceMode::SingleReference);
1433        assert_eq!(ReferenceMode::from(1), ReferenceMode::CompoundReference);
1434        assert_eq!(ReferenceMode::from(2), ReferenceMode::ReferenceModeSelect);
1435    }
1436
1437    #[test]
1438    fn test_tx_mode_conversion() {
1439        assert_eq!(TxMode::from(0), TxMode::Only4x4);
1440        assert_eq!(TxMode::from(1), TxMode::Largest);
1441        assert_eq!(TxMode::from(2), TxMode::Select);
1442    }
1443
1444    #[test]
1445    fn test_restoration_type_conversion() {
1446        assert_eq!(RestorationType::from(0), RestorationType::None);
1447        assert_eq!(RestorationType::from(1), RestorationType::Wiener);
1448        assert_eq!(RestorationType::from(2), RestorationType::SgrProj);
1449        assert_eq!(RestorationType::from(3), RestorationType::Switchable);
1450    }
1451
1452    #[test]
1453    fn test_frame_header_default() {
1454        let header = FrameHeader::new();
1455        assert_eq!(header.frame_type, FrameType::KeyFrame);
1456        assert!(!header.show_frame);
1457        assert!(!header.error_resilient_mode);
1458        assert!(!header.frame_is_intra);
1459    }
1460
1461    #[test]
1462    fn test_frame_header_queries() {
1463        let mut header = FrameHeader::new();
1464        header.frame_type = FrameType::KeyFrame;
1465        assert!(header.is_key_frame());
1466        assert!(!header.is_inter_frame());
1467
1468        header.frame_type = FrameType::InterFrame;
1469        assert!(!header.is_key_frame());
1470        assert!(header.is_inter_frame());
1471
1472        header.frame_type = FrameType::SwitchFrame;
1473        assert!(!header.is_key_frame());
1474        assert!(header.is_inter_frame());
1475
1476        header.order_hint = 42;
1477        assert_eq!(header.display_order(), 42);
1478    }
1479
1480    #[test]
1481    fn test_get_qindex() {
1482        let mut header = FrameHeader::new();
1483        header.quantization.base_q_idx = 100;
1484
1485        // Without segmentation
1486        assert_eq!(header.get_qindex(0), 100);
1487
1488        // With segmentation
1489        header.segmentation.enabled = true;
1490        header.segmentation.feature_enabled[0][SegmentationParams::SEG_LVL_ALT_Q] = true;
1491        header.segmentation.feature_data[0][SegmentationParams::SEG_LVL_ALT_Q] = -20;
1492
1493        assert_eq!(header.get_qindex(0), 80);
1494        assert_eq!(header.get_qindex(1), 100);
1495    }
1496
1497    #[test]
1498    fn test_global_motion_default() {
1499        let gm = GlobalMotion::default();
1500        for i in 0..NUM_REF_FRAMES {
1501            assert_eq!(gm.params[i].gm_type, 0);
1502        }
1503    }
1504
1505    #[test]
1506    fn test_film_grain_defaults() {
1507        let fg = FilmGrainParams::default();
1508        assert!(!fg.apply_grain);
1509        assert_eq!(fg.grain_seed, 0);
1510        assert_eq!(fg.num_y_points, 0);
1511    }
1512
1513    #[test]
1514    fn test_render_size_defaults() {
1515        let rs = RenderSize::default();
1516        assert_eq!(rs.render_width, 0);
1517        assert_eq!(rs.render_height, 0);
1518        assert!(!rs.render_and_frame_size_different);
1519    }
1520
1521    #[test]
1522    fn test_loop_restoration_params_defaults() {
1523        let lr = LoopRestorationParams::default();
1524        assert!(!lr.uses_lr);
1525        assert_eq!(lr.frame_restoration_type[0], RestorationType::None);
1526    }
1527
1528    #[test]
1529    fn test_ref_frame_info_defaults() {
1530        let rfi = RefFrameInfo::default();
1531        for i in 0..REFS_PER_FRAME {
1532            assert_eq!(rfi.ref_frame_idx[i], 0);
1533        }
1534        for i in 0..NUM_REF_FRAMES {
1535            assert_eq!(rfi.ref_order_hint[i], 0);
1536            assert!(!rfi.ref_frame_sign_bias[i]);
1537        }
1538    }
1539}