Skip to main content

codec/pixel_format/av1/
frame.rs

1//! AV1 frame-header parser, `Av1FrameHeader`, and `Av1FrameType`.
2//! See AV1 specification §5.9.1 (uncompressed_header).
3
4use super::super::bitreader::BitReader;
5use super::obu::find_av1_obu;
6use super::sequence::Av1SequenceHeader;
7
8// ─── Av1FrameHeader ────────────────────────────────────────────────
9
10/// Parsed AV1 frame header — full §5.9.1 uncompressed_header parse.
11/// Provides everything needed to populate `StdVideoDecodeAV1PictureInfo`
12/// + its 7 sub-struct pointers for a Vulkan Video AV1 decode submit.
13/// Vec fields (tile MI-unit arrays) forced the drop of `Copy`.
14#[derive(Debug, Clone)]
15pub struct Av1FrameHeader {
16    pub show_frame: bool,
17    pub showable_frame: bool,
18    pub frame_type: Av1FrameType,
19    pub error_resilient_mode: bool,
20    pub disable_cdf_update: bool,
21    pub allow_screen_content_tools: bool,
22    pub force_integer_mv: bool,
23    pub order_hint: u32,
24    pub primary_ref_frame: u8,
25    pub refresh_frame_flags: u8,
26    pub frame_width: u32,
27    pub frame_height: u32,
28    pub render_width: u32,
29    pub render_height: u32,
30    pub use_ref_frame_mvs: bool,
31    pub allow_high_precision_mv: bool,
32    pub is_filter_switchable: bool,
33    pub disable_frame_end_update_cdf: bool,
34    pub allow_warped_motion: bool,
35    pub reduced_tx_set: bool,
36    // ─── Extended fields (full §5.9.1 parse) ────────────────────
37    pub allow_intrabc: bool,
38    pub frame_size_override_flag: bool,
39    pub use_superres: bool,
40    pub is_motion_mode_switchable: bool,
41    pub reference_select: bool,
42    pub skip_mode_present: bool,
43    // Tile info (§5.9.15) — derived MI-unit arrays feed
44    // `StdVideoAV1TileInfo.pMi{Col,Row}Starts` etc.
45    pub tile_cols: u8,
46    pub tile_rows: u8,
47    pub uniform_tile_spacing_flag: bool,
48    pub tile_cols_log2: u8,
49    pub tile_rows_log2: u8,
50    pub mi_col_starts: Vec<u16>,         // len = tile_cols + 1
51    pub mi_row_starts: Vec<u16>,         // len = tile_rows + 1
52    pub width_in_sbs_minus_1: Vec<u16>,  // len = tile_cols
53    pub height_in_sbs_minus_1: Vec<u16>, // len = tile_rows
54    pub context_update_tile_id: u16,
55    pub tile_size_bytes_minus_1: u8,
56    // Quantization (§5.9.12)
57    pub base_q_idx: u8,
58    pub delta_q_y_dc: i8,
59    pub delta_q_u_dc: i8,
60    pub delta_q_u_ac: i8,
61    pub delta_q_v_dc: i8,
62    pub delta_q_v_ac: i8,
63    pub using_qmatrix: bool,
64    pub qm_y: u8,
65    pub qm_u: u8,
66    pub qm_v: u8,
67    // Delta-Q / delta-LF (§5.9.17 / §5.9.18)
68    pub delta_q_present: bool,
69    pub delta_q_res: u8,
70    pub delta_lf_present: bool,
71    pub delta_lf_res: u8,
72    pub delta_lf_multi: bool,
73    // Segmentation (§5.9.14) — scaffolded as "disabled" for the
74    // Vulkan scope; real feature arrays populated when
75    // segmentation_enabled is 1.
76    pub segmentation_enabled: bool,
77    pub segmentation_update_map: bool,
78    pub segmentation_temporal_update: bool,
79    pub segmentation_update_data: bool,
80    pub feature_enabled: [[bool; 8]; 8],
81    pub feature_data: [[i16; 8]; 8],
82    // Loop filter (§5.9.11)
83    pub loop_filter_level: [u8; 4],
84    pub loop_filter_sharpness: u8,
85    pub loop_filter_delta_enabled: bool,
86    pub loop_filter_delta_update: bool,
87    pub update_ref_delta_mask: u8, // 8 bits
88    pub loop_filter_ref_deltas: [i8; 8],
89    pub update_mode_delta_mask: u8, // 2 bits (modes 0..=1)
90    pub loop_filter_mode_deltas: [i8; 2],
91    // CDEF (§5.9.19)
92    pub cdef_damping_minus_3: u8,
93    pub cdef_bits: u8,
94    pub cdef_y_pri_strength: [u8; 8],
95    pub cdef_y_sec_strength: [u8; 8],
96    pub cdef_uv_pri_strength: [u8; 8],
97    pub cdef_uv_sec_strength: [u8; 8],
98    // Loop restoration (§5.9.20)
99    pub lr_type: [u8; 3], // per-plane: 0=None, 1=Wiener, 2=SGrproj, 3=Switchable
100    pub lr_unit_shift: u8,
101    pub lr_uv_shift: u8,
102    // TX mode (§5.9.22) — 0=ONLY_4X4, 1=LARGEST, 2=SELECT
103    pub tx_mode: u8,
104    pub interpolation_filter: u8,
105    // Byte offset from the start of the OBU payload (NOT from the
106    // start of the sample buffer) at which tile_group data begins.
107    // For a Frame OBU (type 6) this is after uncompressed_header +
108    // byte_alignment. For a pair of separate frame_header + tile_group
109    // OBUs (types 3 and 4), the caller looks up the type 4 OBU's
110    // payload start directly and ignores this value.
111    pub tile_group_offset_in_obu: u32,
112    // Coded lossless flag (derived from q-idx 0 + deltas all zero)
113    pub coded_lossless: bool,
114}
115
116impl Default for Av1FrameHeader {
117    fn default() -> Self {
118        Self {
119            show_frame: false,
120            showable_frame: false,
121            frame_type: Av1FrameType::Key,
122            error_resilient_mode: false,
123            disable_cdf_update: false,
124            allow_screen_content_tools: false,
125            force_integer_mv: false,
126            order_hint: 0,
127            primary_ref_frame: 7,
128            refresh_frame_flags: 0,
129            frame_width: 0,
130            frame_height: 0,
131            render_width: 0,
132            render_height: 0,
133            use_ref_frame_mvs: false,
134            allow_high_precision_mv: false,
135            is_filter_switchable: false,
136            disable_frame_end_update_cdf: false,
137            allow_warped_motion: false,
138            reduced_tx_set: false,
139            allow_intrabc: false,
140            frame_size_override_flag: false,
141            use_superres: false,
142            is_motion_mode_switchable: false,
143            reference_select: false,
144            skip_mode_present: false,
145            tile_cols: 1,
146            tile_rows: 1,
147            uniform_tile_spacing_flag: true,
148            tile_cols_log2: 0,
149            tile_rows_log2: 0,
150            mi_col_starts: Vec::new(),
151            mi_row_starts: Vec::new(),
152            width_in_sbs_minus_1: Vec::new(),
153            height_in_sbs_minus_1: Vec::new(),
154            context_update_tile_id: 0,
155            tile_size_bytes_minus_1: 3,
156            base_q_idx: 0,
157            delta_q_y_dc: 0,
158            delta_q_u_dc: 0,
159            delta_q_u_ac: 0,
160            delta_q_v_dc: 0,
161            delta_q_v_ac: 0,
162            using_qmatrix: false,
163            qm_y: 0,
164            qm_u: 0,
165            qm_v: 0,
166            delta_q_present: false,
167            delta_q_res: 0,
168            delta_lf_present: false,
169            delta_lf_res: 0,
170            delta_lf_multi: false,
171            segmentation_enabled: false,
172            segmentation_update_map: false,
173            segmentation_temporal_update: false,
174            segmentation_update_data: false,
175            feature_enabled: [[false; 8]; 8],
176            feature_data: [[0; 8]; 8],
177            loop_filter_level: [0; 4],
178            loop_filter_sharpness: 0,
179            loop_filter_delta_enabled: false,
180            loop_filter_delta_update: false,
181            update_ref_delta_mask: 0,
182            loop_filter_ref_deltas: [0; 8],
183            update_mode_delta_mask: 0,
184            loop_filter_mode_deltas: [0; 2],
185            cdef_damping_minus_3: 0,
186            cdef_bits: 0,
187            cdef_y_pri_strength: [0; 8],
188            cdef_y_sec_strength: [0; 8],
189            cdef_uv_pri_strength: [0; 8],
190            cdef_uv_sec_strength: [0; 8],
191            lr_type: [0; 3],
192            lr_unit_shift: 0,
193            lr_uv_shift: 0,
194            tx_mode: 0,
195            interpolation_filter: 0,
196            tile_group_offset_in_obu: 0,
197            coded_lossless: false,
198        }
199    }
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
203pub enum Av1FrameType {
204    Key,
205    Inter,
206    IntraOnly,
207    Switch,
208}
209
210// ─── Full frame header parser ───────────────────────────────────────
211
212/// Parse an AV1 frame_header_obu (or the frame_header part of a
213/// frame_obu) from the given sample. Requires the sequence header
214/// for branch predicates (order_hint_bits, enable flags).
215///
216/// Returns an `Av1FrameHeader` with just enough fields populated for
217/// Vulkan Video decode to build `StdVideoDecodeAV1PictureInfo` +
218/// sub-structs. Does NOT fully parse the bitstream (skips large
219/// parts of the uncompressed_header — tile_info, segmentation,
220/// global motion, etc. — that can be defaulted for key frames).
221///
222/// Per AV1 spec §5.9.1 — complex, branching parse. Only handles
223/// single-tile key frames at first; inter frames need more work
224/// on ref_frame_idx + delta_frame_id resolution.
225pub fn parse_av1_frame_header(sample: &[u8], seq: &Av1SequenceHeader) -> Option<Av1FrameHeader> {
226    let obu_bytes = find_av1_obu(sample, 3).or_else(|| find_av1_obu(sample, 6))?;
227    let mut br = BitReader::new(obu_bytes);
228    let mut h = Av1FrameHeader::default();
229
230    // ─── Phase 1: frame-level flags ────────────────────────────
231    if seq.reduced_still_picture_header {
232        h.frame_type = Av1FrameType::Key;
233        h.show_frame = true;
234        h.showable_frame = false;
235        h.error_resilient_mode = true;
236    } else {
237        let show_existing_frame = br.read_bits(1)? == 1;
238        if show_existing_frame {
239            // Early-out: a show-existing-frame OBU is a thin pointer
240            // to a previously-decoded DPB slot. No new bitstream to
241            // decode, no uncompressed_header payload past this point.
242            // Return a minimal header marked with show_frame=true so
243            // callers know to skip bitstream decode.
244            let _frame_to_show_map_idx = br.read_bits(3)?;
245            h.show_frame = true;
246            h.showable_frame = true;
247            h.frame_type = Av1FrameType::Key;
248            h.frame_width = seq.max_frame_width_minus1 + 1;
249            h.frame_height = seq.max_frame_height_minus1 + 1;
250            h.render_width = h.frame_width;
251            h.render_height = h.frame_height;
252            return Some(h);
253        }
254        let ft_code = br.read_bits(2)?;
255        h.frame_type = match ft_code {
256            0 => Av1FrameType::Key,
257            1 => Av1FrameType::Inter,
258            2 => Av1FrameType::IntraOnly,
259            3 => Av1FrameType::Switch,
260            _ => return None,
261        };
262        h.show_frame = br.read_bits(1)? == 1;
263        h.showable_frame = if h.show_frame {
264            !matches!(h.frame_type, Av1FrameType::Key)
265        } else {
266            br.read_bits(1)? == 1
267        };
268        let is_key = matches!(h.frame_type, Av1FrameType::Key);
269        let is_switch = matches!(h.frame_type, Av1FrameType::Switch);
270        h.error_resilient_mode = if is_switch || (is_key && h.show_frame) {
271            true
272        } else {
273            br.read_bits(1)? == 1
274        };
275    }
276
277    let frame_is_intra = matches!(h.frame_type, Av1FrameType::Key | Av1FrameType::IntraOnly);
278
279    h.disable_cdf_update = br.read_bits(1)? == 1;
280    // Per AV1 §5.9.1 — when seq_force_screen_content_tools == SELECT (2),
281    // each frame signals its own bit; otherwise the seq-level force
282    // fully determines the frame-level value.
283    h.allow_screen_content_tools = if seq.seq_force_screen_content_tools == 2 {
284        br.read_bits(1)? == 1
285    } else {
286        seq.seq_force_screen_content_tools == 1
287    };
288    if h.allow_screen_content_tools {
289        h.force_integer_mv = if seq.seq_force_integer_mv == 2 {
290            br.read_bits(1)? == 1
291        } else {
292            seq.seq_force_integer_mv == 1
293        };
294    } else {
295        h.force_integer_mv = false;
296    }
297    if frame_is_intra {
298        h.force_integer_mv = true;
299    }
300
301    // frame_size_override_flag
302    let is_switch = matches!(h.frame_type, Av1FrameType::Switch);
303    h.frame_size_override_flag = if is_switch {
304        true
305    } else if seq.reduced_still_picture_header {
306        false
307    } else {
308        br.read_bits(1)? == 1
309    };
310
311    // order_hint
312    if seq.enable_order_hint && seq.order_hint_bits > 0 {
313        h.order_hint = br.read_bits(seq.order_hint_bits as usize)?;
314    }
315
316    // primary_ref_frame (only for non-intra, non-error-resilient)
317    h.primary_ref_frame = if frame_is_intra || h.error_resilient_mode {
318        7 // PRIMARY_REF_NONE
319    } else {
320        br.read_bits(3)? as u8
321    };
322
323    // refresh_frame_flags
324    let all_frames = 0xFFu8;
325    h.refresh_frame_flags = if matches!(h.frame_type, Av1FrameType::Key) && h.show_frame {
326        all_frames
327    } else if is_switch {
328        all_frames
329    } else {
330        br.read_bits(8)? as u8
331    };
332
333    // ─── Phase 2: size / render size / ref frames ──────────────
334    let (frame_width, frame_height) = if frame_is_intra {
335        let (w, h2) = parse_av1_frame_size(&mut br, seq, h.frame_size_override_flag)?;
336        // superres_params() is INSIDE frame_size() per §5.9.5 /
337        // §5.9.6 — before render_size().
338        h.use_superres = if seq.enable_superres {
339            br.read_bits(1)? == 1
340        } else {
341            false
342        };
343        if h.use_superres {
344            let _superres_denom_minus9 = br.read_bits(3)?;
345        }
346        parse_av1_render_size(&mut br, w, h2, &mut h.render_width, &mut h.render_height)?;
347        if h.allow_screen_content_tools
348        /* && UpscaledWidth == FrameWidth */
349        {
350            h.allow_intrabc = br.read_bits(1)? == 1;
351        }
352        (w, h2)
353    } else {
354        // Inter-frame path: ref_frame_idx[], frame_size_with_refs,
355        // interpolation_filter, is_motion_mode_switchable,
356        // use_ref_frame_mvs. For our key-frame-focused scope, this
357        // branch ISN'T the critical path — but we still read bits
358        // to keep the parser position in sync.
359        let frame_refs_short_signaling = if seq.enable_order_hint {
360            br.read_bits(1)? == 1
361        } else {
362            false
363        };
364        if frame_refs_short_signaling {
365            let _last_frame_idx = br.read_bits(3)?;
366            let _gold_frame_idx = br.read_bits(3)?;
367        }
368        for _ in 0..7u8
369        /* REFS_PER_FRAME */
370        {
371            if !frame_refs_short_signaling {
372                let _ref_frame_idx = br.read_bits(3)?;
373            }
374            // frame_id_numbers_present_flag is false in our minimal
375            // seq, so no delta_frame_id read.
376        }
377        let (w, h2) = if h.frame_size_override_flag && !h.error_resilient_mode {
378            parse_av1_frame_size_with_refs(&mut br, seq)?
379        } else {
380            let (w, h2) = parse_av1_frame_size(&mut br, seq, h.frame_size_override_flag)?;
381            // superres_params() inside frame_size() per spec.
382            h.use_superres = if seq.enable_superres {
383                br.read_bits(1)? == 1
384            } else {
385                false
386            };
387            if h.use_superres {
388                let _superres_denom_minus9 = br.read_bits(3)?;
389            }
390            parse_av1_render_size(&mut br, w, h2, &mut h.render_width, &mut h.render_height)?;
391            (w, h2)
392        };
393        h.allow_high_precision_mv = if h.force_integer_mv {
394            false
395        } else {
396            br.read_bits(1)? == 1
397        };
398        // read_interpolation_filter (§5.9.10)
399        h.is_filter_switchable = br.read_bits(1)? == 1;
400        h.interpolation_filter = if h.is_filter_switchable {
401            4 // SWITCHABLE
402        } else {
403            br.read_bits(2)? as u8
404        };
405        h.is_motion_mode_switchable = br.read_bits(1)? == 1;
406        h.use_ref_frame_mvs = if h.error_resilient_mode || !seq.enable_ref_frame_mvs {
407            false
408        } else {
409            br.read_bits(1)? == 1
410        };
411        (w, h2)
412    };
413    h.frame_width = frame_width;
414    h.frame_height = frame_height;
415    if h.render_width == 0 {
416        h.render_width = frame_width;
417    }
418    if h.render_height == 0 {
419        h.render_height = frame_height;
420    }
421
422    h.disable_frame_end_update_cdf = if seq.reduced_still_picture_header {
423        true
424    } else {
425        br.read_bits(1)? == 1
426    };
427
428    // ─── Phase 5: tile_info() (§5.9.15) ────────────────────────
429    // MI (mode-info) units = 4 luma samples. SB (superblock) size in
430    // MI units = 16 (64x64 SB) or 32 (128x128 SB). Our seq parser
431    // doesn't capture `use_128x128_superblock` yet; default to 16 MI
432    // per SB — the common case for current AV1 streams.
433    let sb_size_log2: u32 = 4; // log2(16)
434    let mi_cols_raw = 2 * ((frame_width.saturating_sub(1) + 8) >> 3);
435    let mi_rows_raw = 2 * ((frame_height.saturating_sub(1) + 8) >> 3);
436    // Align MI dims to SB boundaries for tile-spacing math.
437    let sb_cols = (mi_cols_raw + (1 << sb_size_log2) - 1) >> sb_size_log2;
438    let sb_rows = (mi_rows_raw + (1 << sb_size_log2) - 1) >> sb_size_log2;
439    parse_av1_tile_info(
440        &mut br,
441        &mut h,
442        sb_cols,
443        sb_rows,
444        sb_size_log2,
445        mi_cols_raw,
446        mi_rows_raw,
447    )?;
448
449    // ─── Phase 6: quantization_params() (§5.9.12) ──────────────
450    parse_av1_quantization_params(&mut br, &mut h, seq)?;
451
452    // ─── Phase 7: segmentation_params() (§5.9.14) ──────────────
453    parse_av1_segmentation_params(&mut br, &mut h)?;
454
455    // ─── Phase 8: delta_q_params / delta_lf_params ─────────────
456    h.delta_q_present = if h.base_q_idx > 0 {
457        br.read_bits(1)? == 1
458    } else {
459        false
460    };
461    h.delta_q_res = if h.delta_q_present {
462        br.read_bits(2)? as u8
463    } else {
464        0
465    };
466    h.delta_lf_present = if h.delta_q_present && !h.allow_intrabc {
467        br.read_bits(1)? == 1
468    } else {
469        false
470    };
471    if h.delta_lf_present {
472        h.delta_lf_res = br.read_bits(2)? as u8;
473        h.delta_lf_multi = br.read_bits(1)? == 1;
474    }
475
476    // ─── Compute CodedLossless (§5.9.1) ─────────────────────────
477    // lossless requires base_q_idx=0 and ALL delta-q values == 0.
478    // We don't iterate segment features for per-seg q deltas here;
479    // coded_lossless only affects the later cdef_params gate and
480    // tx_mode coding (both set to 0 when lossless).
481    h.coded_lossless = h.base_q_idx == 0
482        && h.delta_q_y_dc == 0
483        && h.delta_q_u_dc == 0
484        && h.delta_q_u_ac == 0
485        && h.delta_q_v_dc == 0
486        && h.delta_q_v_ac == 0;
487
488    // ─── Phase 9: loop_filter_params() (§5.9.11) ───────────────
489    parse_av1_loop_filter_params(&mut br, &mut h, frame_is_intra)?;
490
491    // ─── Phase 10: cdef_params() (§5.9.19) ─────────────────────
492    let num_planes_u32: u32 = if seq.monochrome { 1 } else { 3 };
493    if !h.coded_lossless && !h.allow_intrabc && seq.enable_cdef {
494        parse_av1_cdef_params(&mut br, &mut h, num_planes_u32)?;
495    } else {
496        // Spec defaults when cdef is skipped (§5.9.19 conformance).
497        h.cdef_bits = 0;
498        h.cdef_damping_minus_3 = 0;
499        h.cdef_y_pri_strength = [0; 8];
500        h.cdef_y_sec_strength = [0; 8];
501        h.cdef_uv_pri_strength = [0; 8];
502        h.cdef_uv_sec_strength = [0; 8];
503    }
504
505    // ─── Phase 11: lr_params() (§5.9.20) ───────────────────────
506    if !h.coded_lossless && !h.allow_intrabc && seq.enable_restoration {
507        parse_av1_lr_params(&mut br, &mut h, num_planes_u32, seq)?;
508    }
509
510    // ─── Phase 12: read_tx_mode (§5.9.22) ──────────────────────
511    h.tx_mode = if h.coded_lossless {
512        0 // ONLY_4X4
513    } else if br.read_bits(1)? == 1 {
514        2 // TX_MODE_SELECT
515    } else {
516        1 // TX_MODE_LARGEST
517    };
518
519    // ─── Phase 13: frame_reference_mode (§5.9.23) ──────────────
520    h.reference_select = if !frame_is_intra {
521        br.read_bits(1)? == 1
522    } else {
523        false
524    };
525
526    // ─── Phase 14: skip_mode_params (§5.9.24) ──────────────────
527    let skip_mode_allowed = false; // For KEY/INTRA_ONLY, skip_mode is
528    // implicitly disabled (requires 2
529    // forward/backward refs). Inter
530    // would derive from ref_frame_idx
531    // + order hints — scaffolded.
532    h.skip_mode_present = if skip_mode_allowed {
533        br.read_bits(1)? == 1
534    } else {
535        false
536    };
537
538    // allow_warped_motion (§5.9.1) — 1 bit gated by seq.enable_warped_motion
539    // AND !error_resilient_mode AND !FrameIsIntra.
540    h.allow_warped_motion =
541        if !frame_is_intra && !h.error_resilient_mode && seq.enable_warped_motion {
542            br.read_bits(1)? == 1
543        } else {
544            false
545        };
546
547    // reduced_tx_set (1 bit) — the last bitstream bit we care about
548    // for Vulkan Std picture info. global_motion_params,
549    // film_grain_params, and the tile_group_obu() that follows the
550    // byte_alignment() at the end are all parsed by the driver from
551    // the bitstream, not from our Std struct.
552    h.reduced_tx_set = br.read_bits(1)? == 1;
553
554    // ─── Phase 15: global_motion_params (§5.9.21) ──────────────
555    // Read-only — we don't carry gm params across into Vulkan's
556    // StdVideoAV1GlobalMotion at this time (zero-init GmType[]
557    // → IDENTITY for every ref, matching the implicit default).
558    if !frame_is_intra {
559        skip_av1_global_motion_params(&mut br)?;
560    }
561
562    // ─── Phase 16: film_grain_params (§5.9.25) ─────────────────
563    if seq.film_grain_params_present && (h.show_frame || h.showable_frame) {
564        skip_av1_film_grain_params(&mut br, seq)?;
565    }
566
567    // ─── Phase 17: byte_align() + record tile_group_offset ────
568    // Per §5.3.5, uncompressed_header ends with byte_alignment. The
569    // tile_group_obu starts at the next byte boundary in the same
570    // Frame OBU (type 6) or in a separate Tile Group OBU (type 4).
571    br.byte_align();
572    h.tile_group_offset_in_obu = (br.bit_pos() / 8) as u32;
573
574    Some(h)
575}
576
577// ─── AV1 frame-header helper functions ─────────────────────────────
578
579/// §5.9.5 frame_size()
580fn parse_av1_frame_size(
581    br: &mut BitReader,
582    seq: &Av1SequenceHeader,
583    frame_size_override_flag: bool,
584) -> Option<(u32, u32)> {
585    if frame_size_override_flag {
586        let w_bits = av1_bits_for_max(seq.max_frame_width_minus1 + 1);
587        let h_bits = av1_bits_for_max(seq.max_frame_height_minus1 + 1);
588        let w = br.read_bits(w_bits)? + 1;
589        let hgt = br.read_bits(h_bits)? + 1;
590        Some((w, hgt))
591    } else {
592        Some((
593            seq.max_frame_width_minus1 + 1,
594            seq.max_frame_height_minus1 + 1,
595        ))
596    }
597}
598
599/// §5.9.6 render_size()
600fn parse_av1_render_size(
601    br: &mut BitReader,
602    frame_w: u32,
603    frame_h: u32,
604    out_w: &mut u32,
605    out_h: &mut u32,
606) -> Option<()> {
607    let render_and_frame_size_different = br.read_bits(1)? == 1;
608    if render_and_frame_size_different {
609        *out_w = br.read_bits(16)? + 1;
610        *out_h = br.read_bits(16)? + 1;
611    } else {
612        *out_w = frame_w;
613        *out_h = frame_h;
614    }
615    Some(())
616}
617
618/// §5.9.7 frame_size_with_refs() — for inter frames with size override.
619/// Returns (frame_width, frame_height). The per-ref "found_ref" loop
620/// here requires access to the ref frames' dims, which our scaffold
621/// doesn't track. We treat `found_ref=0` uniformly (falls back to
622/// frame_size()).
623fn parse_av1_frame_size_with_refs(
624    br: &mut BitReader,
625    seq: &Av1SequenceHeader,
626) -> Option<(u32, u32)> {
627    let mut found_ref = false;
628    for _ in 0..7u8 {
629        if br.read_bits(1)? == 1 {
630            found_ref = true;
631        }
632    }
633    if !found_ref {
634        let (w, hgt) = parse_av1_frame_size(br, seq, true)?;
635        let mut rw = 0;
636        let mut rh = 0;
637        parse_av1_render_size(br, w, hgt, &mut rw, &mut rh)?;
638        // superres_params inlined
639        if seq.enable_superres && br.read_bits(1)? == 1 {
640            let _denom = br.read_bits(3)?;
641        }
642        Some((w, hgt))
643    } else {
644        // found_ref branch: dims come from one of the refs. No ref
645        // tracking → fall back to the sequence header max.
646        Some((
647            seq.max_frame_width_minus1 + 1,
648            seq.max_frame_height_minus1 + 1,
649        ))
650    }
651}
652
653fn av1_bits_for_max(v: u32) -> usize {
654    // Inclusive ceil-log2 for a max-value field (AV1 uses
655    // `n_bits = ceil(log2(max + 1))`).
656    let mut bits = 0usize;
657    let mut x = v.saturating_sub(1);
658    while x > 0 {
659        bits += 1;
660        x >>= 1;
661    }
662    bits.max(1)
663}
664
665/// §5.9.15 tile_info()
666fn parse_av1_tile_info(
667    br: &mut BitReader,
668    h: &mut Av1FrameHeader,
669    sb_cols: u32,
670    sb_rows: u32,
671    sb_size_log2: u32,
672    mi_cols: u32,
673    mi_rows: u32,
674) -> Option<()> {
675    // Derive MAX_TILE_AREA_SB, MAX_TILE_WIDTH_SB etc. (§5.9.15)
676    // Constants from AV1 spec for 64x64 SB (log2=4).
677    let max_tile_width_sb = 4096 >> (sb_size_log2 + 2); // typically 64
678    let max_tile_area_sb = (4096 * 2304) >> (2 * sb_size_log2 + 4); // 4608
679    let min_log2_tile_cols = av1_tile_log2(max_tile_width_sb, sb_cols);
680    let max_log2_tile_cols = av1_tile_log2(1, sb_cols.min(64));
681    let max_log2_tile_rows = av1_tile_log2(1, sb_rows.min(64));
682    let min_log2_tiles =
683        min_log2_tile_cols.max(av1_tile_log2(max_tile_area_sb, sb_rows * sb_cols));
684
685    h.uniform_tile_spacing_flag = br.read_bits(1)? == 1;
686    let tile_cols_log2: u32;
687    let tile_rows_log2: u32;
688    h.mi_col_starts.clear();
689    h.mi_row_starts.clear();
690    h.width_in_sbs_minus_1.clear();
691    h.height_in_sbs_minus_1.clear();
692
693    if h.uniform_tile_spacing_flag {
694        let mut tcl = min_log2_tile_cols;
695        while tcl < max_log2_tile_cols {
696            if br.read_bits(1)? == 0 {
697                break;
698            }
699            tcl += 1;
700        }
701        tile_cols_log2 = tcl;
702        let tile_width_sb = (sb_cols + (1 << tile_cols_log2) - 1) >> tile_cols_log2;
703        let mut start_sb = 0u32;
704        let mut mi_starts: Vec<u16> = vec![0];
705        let mut widths: Vec<u16> = Vec::new();
706        while start_sb < sb_cols {
707            let size_sb = tile_width_sb.min(sb_cols - start_sb);
708            widths.push((size_sb - 1) as u16);
709            start_sb += size_sb;
710            mi_starts.push(((start_sb << sb_size_log2).min(mi_cols)) as u16);
711        }
712        h.mi_col_starts = mi_starts;
713        h.width_in_sbs_minus_1 = widths;
714        h.tile_cols = h.width_in_sbs_minus_1.len() as u8;
715
716        let min_log2_tile_rows = min_log2_tiles.saturating_sub(tile_cols_log2);
717        let mut trl = min_log2_tile_rows;
718        while trl < max_log2_tile_rows {
719            if br.read_bits(1)? == 0 {
720                break;
721            }
722            trl += 1;
723        }
724        tile_rows_log2 = trl;
725        let tile_height_sb = (sb_rows + (1 << tile_rows_log2) - 1) >> tile_rows_log2;
726        let mut start_sb_r = 0u32;
727        let mut mi_starts_r: Vec<u16> = vec![0];
728        let mut heights: Vec<u16> = Vec::new();
729        while start_sb_r < sb_rows {
730            let size_sb = tile_height_sb.min(sb_rows - start_sb_r);
731            heights.push((size_sb - 1) as u16);
732            start_sb_r += size_sb;
733            mi_starts_r.push(((start_sb_r << sb_size_log2).min(mi_rows)) as u16);
734        }
735        h.mi_row_starts = mi_starts_r;
736        h.height_in_sbs_minus_1 = heights;
737        h.tile_rows = h.height_in_sbs_minus_1.len() as u8;
738    } else {
739        // Non-uniform tile spacing
740        let mut start_sb = 0u32;
741        let mut mi_starts: Vec<u16> = vec![0];
742        let mut widths: Vec<u16> = Vec::new();
743        while start_sb < sb_cols {
744            let max_width = (sb_cols - start_sb).min(max_tile_width_sb);
745            let size_minus_1 = av1_read_ns(br, max_width)?;
746            let size = size_minus_1 + 1;
747            widths.push(size_minus_1 as u16);
748            start_sb += size;
749            mi_starts.push(((start_sb << sb_size_log2).min(mi_cols)) as u16);
750        }
751        h.mi_col_starts = mi_starts;
752        h.width_in_sbs_minus_1 = widths;
753        h.tile_cols = h.width_in_sbs_minus_1.len() as u8;
754        tile_cols_log2 = av1_tile_log2(1, h.tile_cols as u32);
755
756        let tile_cols = h.tile_cols as u32;
757        let max_tile_area_sb_r = if min_log2_tiles > 0 {
758            (sb_rows * sb_cols) >> (min_log2_tiles + 1)
759        } else {
760            sb_rows * sb_cols
761        };
762        let max_tile_height_sb = (max_tile_area_sb_r / tile_cols).max(1);
763
764        let mut start_sb_r = 0u32;
765        let mut mi_starts_r: Vec<u16> = vec![0];
766        let mut heights: Vec<u16> = Vec::new();
767        while start_sb_r < sb_rows {
768            let max_height = (sb_rows - start_sb_r).min(max_tile_height_sb);
769            let size_minus_1 = av1_read_ns(br, max_height)?;
770            let size = size_minus_1 + 1;
771            heights.push(size_minus_1 as u16);
772            start_sb_r += size;
773            mi_starts_r.push(((start_sb_r << sb_size_log2).min(mi_rows)) as u16);
774        }
775        h.mi_row_starts = mi_starts_r;
776        h.height_in_sbs_minus_1 = heights;
777        h.tile_rows = h.height_in_sbs_minus_1.len() as u8;
778        tile_rows_log2 = av1_tile_log2(1, h.tile_rows as u32);
779    }
780    h.tile_cols_log2 = tile_cols_log2 as u8;
781    h.tile_rows_log2 = tile_rows_log2 as u8;
782
783    if (tile_cols_log2 + tile_rows_log2) > 0 {
784        let n = (tile_cols_log2 + tile_rows_log2) as usize;
785        h.context_update_tile_id = br.read_bits(n)? as u16;
786        h.tile_size_bytes_minus_1 = br.read_bits(2)? as u8;
787    } else {
788        h.context_update_tile_id = 0;
789        h.tile_size_bytes_minus_1 = 0;
790    }
791    Some(())
792}
793
794/// AV1 tile_log2 helper (§5.9.15) — smallest k s.t. (blksize << k) >= target.
795fn av1_tile_log2(blksize: u32, target: u32) -> u32 {
796    let mut k = 0u32;
797    while (blksize << k) < target {
798        k += 1;
799    }
800    k
801}
802
803/// AV1 ns(n) — non-symmetric fixed-length code (§4.10.6)
804fn av1_read_ns(br: &mut BitReader, n: u32) -> Option<u32> {
805    if n == 0 {
806        return Some(0);
807    }
808    let w = av1_ceil_log2(n);
809    if w == 0 {
810        return Some(0);
811    }
812    let m = (1u32 << w) - n;
813    let v = br.read_bits((w - 1) as usize)?;
814    if v < m {
815        Some(v)
816    } else {
817        let extra = br.read_bits(1)?;
818        Some((v << 1) - m + extra)
819    }
820}
821
822fn av1_ceil_log2(n: u32) -> u32 {
823    if n <= 1 {
824        return 1;
825    }
826    let mut k = 0;
827    let mut x = n - 1;
828    while x > 0 {
829        k += 1;
830        x >>= 1;
831    }
832    k
833}
834
835/// §5.9.12 quantization_params()
836fn parse_av1_quantization_params(
837    br: &mut BitReader,
838    h: &mut Av1FrameHeader,
839    seq: &Av1SequenceHeader,
840) -> Option<()> {
841    h.base_q_idx = br.read_bits(8)? as u8;
842    h.delta_q_y_dc = read_delta_q(br)?;
843    let (diff_uv_delta, num_planes) = if seq.monochrome {
844        (false, 1u32)
845    } else {
846        let diff = if seq.seq_profile == 2 {
847            br.read_bits(1)? == 1
848        } else {
849            false
850        };
851        (diff, 3u32)
852    };
853    if num_planes > 1 {
854        h.delta_q_u_dc = read_delta_q(br)?;
855        h.delta_q_u_ac = read_delta_q(br)?;
856        if diff_uv_delta {
857            h.delta_q_v_dc = read_delta_q(br)?;
858            h.delta_q_v_ac = read_delta_q(br)?;
859        } else {
860            h.delta_q_v_dc = h.delta_q_u_dc;
861            h.delta_q_v_ac = h.delta_q_u_ac;
862        }
863    }
864    h.using_qmatrix = br.read_bits(1)? == 1;
865    if h.using_qmatrix {
866        h.qm_y = br.read_bits(4)? as u8;
867        h.qm_u = br.read_bits(4)? as u8;
868        h.qm_v = if seq.monochrome {
869            h.qm_u
870        } else if br.read_bits(1)? == 0 {
871            h.qm_u
872        } else {
873            br.read_bits(4)? as u8
874        };
875    }
876    Some(())
877}
878
879fn read_delta_q(br: &mut BitReader) -> Option<i8> {
880    let present = br.read_bits(1)? == 1;
881    if present {
882        Some(br.read_su(7)? as i8)
883    } else {
884        Some(0)
885    }
886}
887
888/// §5.9.14 segmentation_params()
889fn parse_av1_segmentation_params(br: &mut BitReader, h: &mut Av1FrameHeader) -> Option<()> {
890    h.segmentation_enabled = br.read_bits(1)? == 1;
891    if h.segmentation_enabled {
892        if h.primary_ref_frame == 7 {
893            // PRIMARY_REF_NONE → forced-fresh segment tree
894            h.segmentation_update_map = true;
895            h.segmentation_temporal_update = false;
896            h.segmentation_update_data = true;
897        } else {
898            h.segmentation_update_map = br.read_bits(1)? == 1;
899            if h.segmentation_update_map {
900                h.segmentation_temporal_update = br.read_bits(1)? == 1;
901            }
902            h.segmentation_update_data = br.read_bits(1)? == 1;
903        }
904        if h.segmentation_update_data {
905            // SEG_FEATURE_DATA table (§5.9.14) — per-feature bit counts
906            // and sign flags.
907            // (bits, signed)
908            const FEAT_INFO: [(u32, bool); 8] = [
909                (8, true),  // SEG_LVL_ALT_Q
910                (6, true),  // SEG_LVL_ALT_LF_Y_V
911                (6, true),  // SEG_LVL_ALT_LF_Y_H
912                (6, true),  // SEG_LVL_ALT_LF_U
913                (6, true),  // SEG_LVL_ALT_LF_V
914                (3, false), // SEG_LVL_REF_FRAME
915                (0, false), // SEG_LVL_SKIP
916                (0, false), // SEG_LVL_GLOBALMV
917            ];
918            for seg in 0..8 {
919                for (feat, &(bits, signed)) in FEAT_INFO.iter().enumerate() {
920                    let enabled = br.read_bits(1)? == 1;
921                    h.feature_enabled[seg][feat] = enabled;
922                    if enabled {
923                        if bits == 0 {
924                            h.feature_data[seg][feat] = 1;
925                        } else if signed {
926                            h.feature_data[seg][feat] = br.read_su(bits as usize + 1)? as i16;
927                        } else {
928                            h.feature_data[seg][feat] = br.read_bits(bits as usize)? as i16;
929                        }
930                    }
931                }
932            }
933        }
934    }
935    Some(())
936}
937
938/// §5.9.11 loop_filter_params()
939fn parse_av1_loop_filter_params(
940    br: &mut BitReader,
941    h: &mut Av1FrameHeader,
942    frame_is_intra: bool,
943) -> Option<()> {
944    if h.coded_lossless || h.allow_intrabc {
945        h.loop_filter_level = [0; 4];
946        h.loop_filter_sharpness = 0;
947        h.loop_filter_delta_enabled = false;
948        h.loop_filter_ref_deltas = [1, 0, 0, 0, -1, 0, -1, -1];
949        h.loop_filter_mode_deltas = [0, 0];
950        return Some(());
951    }
952    h.loop_filter_level[0] = br.read_bits(6)? as u8;
953    h.loop_filter_level[1] = br.read_bits(6)? as u8;
954    if h.loop_filter_level[0] > 0 || h.loop_filter_level[1] > 0 {
955        h.loop_filter_level[2] = br.read_bits(6)? as u8;
956        h.loop_filter_level[3] = br.read_bits(6)? as u8;
957    }
958    h.loop_filter_sharpness = br.read_bits(3)? as u8;
959    h.loop_filter_delta_enabled = br.read_bits(1)? == 1;
960    // Defaults for ref/mode deltas (§5.9.11)
961    h.loop_filter_ref_deltas = [1, 0, 0, 0, -1, 0, -1, -1];
962    h.loop_filter_mode_deltas = [0, 0];
963    if h.loop_filter_delta_enabled {
964        h.loop_filter_delta_update = br.read_bits(1)? == 1;
965        if h.loop_filter_delta_update {
966            let mut update_mask = 0u8;
967            for i in 0..8 {
968                let update = br.read_bits(1)? == 1;
969                if update {
970                    update_mask |= 1 << i;
971                    h.loop_filter_ref_deltas[i] = br.read_su(7)? as i8;
972                }
973            }
974            h.update_ref_delta_mask = update_mask;
975            let mut mode_mask = 0u8;
976            for i in 0..2 {
977                let update = br.read_bits(1)? == 1;
978                if update {
979                    mode_mask |= 1 << i;
980                    h.loop_filter_mode_deltas[i] = br.read_su(7)? as i8;
981                }
982            }
983            h.update_mode_delta_mask = mode_mask;
984        }
985    }
986    let _ = frame_is_intra; // reserved for future spec tweaks
987    Some(())
988}
989
990/// §5.9.19 cdef_params()
991fn parse_av1_cdef_params(
992    br: &mut BitReader,
993    h: &mut Av1FrameHeader,
994    num_planes: u32,
995) -> Option<()> {
996    h.cdef_damping_minus_3 = br.read_bits(2)? as u8;
997    h.cdef_bits = br.read_bits(2)? as u8;
998    let count = 1usize << h.cdef_bits;
999    for i in 0..count {
1000        h.cdef_y_pri_strength[i] = br.read_bits(4)? as u8;
1001        let y_sec = br.read_bits(2)? as u8;
1002        // Spec §5.9.19: after reading cdef_y_sec_strength, if the
1003        // decoded value == 3 it's remapped to 4 (the "== 3 → 4" gap
1004        // in the 2-bit encoding). Same for chroma below.
1005        h.cdef_y_sec_strength[i] = if y_sec == 3 { 4 } else { y_sec };
1006        if num_planes > 1 {
1007            h.cdef_uv_pri_strength[i] = br.read_bits(4)? as u8;
1008            let uv_sec = br.read_bits(2)? as u8;
1009            h.cdef_uv_sec_strength[i] = if uv_sec == 3 { 4 } else { uv_sec };
1010        }
1011    }
1012    Some(())
1013}
1014
1015/// §5.9.20 lr_params()
1016fn parse_av1_lr_params(
1017    br: &mut BitReader,
1018    h: &mut Av1FrameHeader,
1019    num_planes: u32,
1020    seq: &Av1SequenceHeader,
1021) -> Option<()> {
1022    let mut uses_lr = false;
1023    let mut uses_chroma_lr = false;
1024    for i in 0..(num_planes as usize) {
1025        let lr_type = br.read_bits(2)? as u8;
1026        h.lr_type[i] = lr_type;
1027        if lr_type != 0 {
1028            uses_lr = true;
1029            if i > 0 {
1030                uses_chroma_lr = true;
1031            }
1032        }
1033    }
1034    if uses_lr {
1035        // 64x64 SB path (use_128x128_superblock=0 — we assume this):
1036        // read 1 bit; if set, read another for lr_unit_extra_shift.
1037        // 128x128 SB path: read 1 bit and add 1 (to get 128/256).
1038        // We don't track use_128x128_superblock — stick to 64x64.
1039        let base = br.read_bits(1)? as u8;
1040        h.lr_unit_shift = if base != 0 {
1041            let extra = br.read_bits(1)? as u8;
1042            base + extra
1043        } else {
1044            0
1045        };
1046        // lr_uv_shift only present when chroma is 4:2:0 (subx && suby)
1047        // AND chroma plane has LR enabled.
1048        if num_planes > 1 && uses_chroma_lr && seq.chroma_subsampling_x && seq.chroma_subsampling_y
1049        {
1050            h.lr_uv_shift = br.read_bits(1)? as u8;
1051        }
1052    }
1053    Some(())
1054}
1055
1056/// §5.9.21 global_motion_params() — read-only; we don't populate
1057/// StdVideoAV1GlobalMotion so just consume the bits to keep the
1058/// parser position in sync.
1059fn skip_av1_global_motion_params(br: &mut BitReader) -> Option<()> {
1060    for _ in 0..7 {
1061        let is_global = br.read_bits(1)? == 1;
1062        let is_rot_zoom = if is_global {
1063            br.read_bits(1)? == 1
1064        } else {
1065            false
1066        };
1067        let _is_translation = if is_global && !is_rot_zoom {
1068            br.read_bits(1)? == 1
1069        } else {
1070            false
1071        };
1072        let gm_type = if is_global && !is_rot_zoom {
1073            2u8 /*TRANSLATION*/
1074        } else if is_rot_zoom {
1075            3u8 /*ROTZOOM*/
1076        } else if is_global {
1077            4u8 /*AFFINE*/
1078        } else {
1079            0u8 /*IDENTITY*/
1080        };
1081        if gm_type >= 3 {
1082            // 2 × 6 subexp params
1083            for _ in 0..2 {
1084                let _a = av1_read_subexp(br, 12, 0)?;
1085                let _b = av1_read_subexp(br, 12, 0)?;
1086            }
1087        }
1088        if gm_type >= 2 {
1089            // 2 × 6 subexp params for translation
1090            for _ in 0..2 {
1091                let _a = av1_read_subexp(br, 12, 0)?;
1092            }
1093        }
1094    }
1095    Some(())
1096}
1097
1098fn av1_read_subexp(br: &mut BitReader, num_syms: u32, _ref: i32) -> Option<i32> {
1099    // Simplified: read the inv_remap_and_deltaAV1 signed field. We
1100    // only need to advance the bit cursor — value is discarded.
1101    // §5.11.21: inv_remap_and_delta recurrence. The simplified "skip
1102    // enough bits" form reads ceil(log2(num_syms)) + sign bits.
1103    let bits = av1_ceil_log2(num_syms) as usize + 1; // value + sign
1104    let _ = br.read_bits(bits.min(16))?;
1105    Some(0)
1106}
1107
1108/// §5.9.25 film_grain_params() — we don't ship film-grain support in
1109/// the Vulkan scope; skip past the bits to keep parser position in
1110/// sync for byte_align().
1111fn skip_av1_film_grain_params(br: &mut BitReader, seq: &Av1SequenceHeader) -> Option<()> {
1112    let apply_grain = br.read_bits(1)? == 1;
1113    if !apply_grain {
1114        return Some(());
1115    }
1116    let _grain_seed = br.read_bits(16)?;
1117    let update_grain = br.read_bits(1)? == 1;
1118    if !update_grain {
1119        let _film_grain_params_ref_idx = br.read_bits(3)?;
1120        return Some(());
1121    }
1122    let num_y_points = br.read_bits(4)?;
1123    for _ in 0..num_y_points {
1124        let _point_y_value = br.read_bits(8)?;
1125        let _point_y_scaling = br.read_bits(8)?;
1126    }
1127    let chroma_scaling_from_luma = if seq.monochrome {
1128        false
1129    } else {
1130        br.read_bits(1)? == 1
1131    };
1132    let num_cb_points: u32;
1133    let num_cr_points: u32;
1134    if seq.monochrome
1135        || chroma_scaling_from_luma
1136        || (seq.chroma_subsampling_x && seq.chroma_subsampling_y && num_y_points == 0)
1137    {
1138        num_cb_points = 0;
1139        num_cr_points = 0;
1140    } else {
1141        num_cb_points = br.read_bits(4)?;
1142        for _ in 0..num_cb_points {
1143            let _point_cb_value = br.read_bits(8)?;
1144            let _point_cb_scaling = br.read_bits(8)?;
1145        }
1146        num_cr_points = br.read_bits(4)?;
1147        for _ in 0..num_cr_points {
1148            let _point_cr_value = br.read_bits(8)?;
1149            let _point_cr_scaling = br.read_bits(8)?;
1150        }
1151    }
1152    let _grain_scaling_minus_8 = br.read_bits(2)?;
1153    let ar_coeff_lag = br.read_bits(2)?;
1154    let num_pos_y = 2 * ar_coeff_lag * (ar_coeff_lag + 1);
1155    let num_pos_chroma = if num_y_points > 0 {
1156        num_pos_y + 1
1157    } else {
1158        num_pos_y
1159    };
1160    for _ in 0..num_pos_y {
1161        let _ar_coeff_y_plus_128 = br.read_bits(8)?;
1162    }
1163    if chroma_scaling_from_luma || num_cb_points > 0 {
1164        for _ in 0..num_pos_chroma {
1165            let _ar_coeff_cb_plus_128 = br.read_bits(8)?;
1166        }
1167    }
1168    if chroma_scaling_from_luma || num_cr_points > 0 {
1169        for _ in 0..num_pos_chroma {
1170            let _ar_coeff_cr_plus_128 = br.read_bits(8)?;
1171        }
1172    }
1173    let _ar_coeff_shift_minus_6 = br.read_bits(2)?;
1174    let _grain_scale_shift = br.read_bits(2)?;
1175    if num_cb_points > 0 {
1176        let _cb_mult = br.read_bits(8)?;
1177        let _cb_luma_mult = br.read_bits(8)?;
1178        let _cb_offset = br.read_bits(9)?;
1179    }
1180    if num_cr_points > 0 {
1181        let _cr_mult = br.read_bits(8)?;
1182        let _cr_luma_mult = br.read_bits(8)?;
1183        let _cr_offset = br.read_bits(9)?;
1184    }
1185    let _overlap_flag = br.read_bits(1)?;
1186    let _clip_to_restricted_range = br.read_bits(1)?;
1187    Some(())
1188}