Skip to main content

oxideav_bitstream/
av1.rs

1//! AV1 minimal keyframe header parsing.
2//!
3//! This module walks an AV1 OBU bytestream (a.k.a. Low Overhead
4//! Bitstream Format), parses the sequence-header OBU and the
5//! key-frame frame-header OBU, and extracts just enough information
6//! to populate the slice-data HW backends' parameter buffers
7//! (`VAPictureParameterBufferAV1`, `VkVideoDecodeAV1PictureInfoKHR`).
8//!
9//! Refused (returned as [`BitstreamError::Unsupported`]):
10//!
11//! - Multiple operating points (`operating_points_cnt_minus_1 > 0`):
12//!   we only consume `operating_point_idc[0]` / `level_idx[0]`.
13//! - Decoder model / initial display delay info.
14//! - `seq_force_screen_content_tools` overrides beyond the default.
15//! - `enable_superres`, `enable_cdef`, `enable_restoration` advanced
16//!   handling — we read but don't return them.
17//! - Film grain.
18//! - Anything that isn't a `KEY_FRAME` (or `INTRA_ONLY_FRAME` with
19//!   `show_frame=1`).
20//!
21//! # Spec references
22//!
23//! AOMedia AV1 Bitstream & Decoding Process Specification (2018-06-25
24//! v1.0.0 errata + 2019-07-08 v1.0.0). Sections of interest:
25//! 5.3.1 (general OBU syntax), 5.3.2 (OBU header), 5.3.3 (extension
26//! header), 5.3.4 (trailing bits), 5.5 (sequence header), 5.9
27//! (frame header), 5.9.5 (uncompressed header for key frame),
28//! 4.10 (leb128).
29
30use crate::bit_reader::BitReader;
31use crate::BitstreamError;
32
33// ─────────────────────────── OBU types ───────────────────────────────────────
34
35/// 6.2.1 `obu_type` values.
36pub const OBU_RESERVED_0: u8 = 0;
37pub const OBU_SEQUENCE_HEADER: u8 = 1;
38pub const OBU_TEMPORAL_DELIMITER: u8 = 2;
39pub const OBU_FRAME_HEADER: u8 = 3;
40pub const OBU_TILE_GROUP: u8 = 4;
41pub const OBU_METADATA: u8 = 5;
42pub const OBU_FRAME: u8 = 6;
43pub const OBU_REDUNDANT_FRAME_HEADER: u8 = 7;
44pub const OBU_TILE_LIST: u8 = 8;
45pub const OBU_PADDING: u8 = 15;
46
47// ─────────────────────────── frame_type values ──────────────────────────────
48
49/// 6.8.2 `frame_type` values.
50pub const FRAME_TYPE_KEY: u8 = 0;
51pub const FRAME_TYPE_INTER: u8 = 1;
52pub const FRAME_TYPE_INTRA_ONLY: u8 = 2;
53pub const FRAME_TYPE_SWITCH: u8 = 3;
54
55// ─────────────────────────── OBU header / leb128 ─────────────────────────────
56
57/// Decoded OBU header (5.3.2 + 5.3.3).
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct ObuHeader {
60    pub obu_type: u8,
61    pub extension_flag: bool,
62    pub has_size_field: bool,
63    pub temporal_id: u8,
64    pub spatial_id: u8,
65}
66
67/// Locate one OBU starting at `offset`. Returns:
68/// `(header, payload_start, payload_end, next_offset)` where
69/// `payload_*` covers the OBU payload (excluding OBU header byte,
70/// extension byte and leb128 size bytes) and `next_offset` is where
71/// the *next* OBU begins.
72///
73/// If the OBU has no `obu_size` field and we're inside a
74/// Low-Overhead-Bitstream-Format stream (which is what aomenc's
75/// `--obu` produces), the OBU body is *required* to have a
76/// `obu_size` field — Section 5 mandates `obu_has_size_field == 1`
77/// for all OBUs in the LOBF. We enforce that here.
78pub fn read_obu(
79    bytes: &[u8],
80    offset: usize,
81) -> Result<(ObuHeader, usize, usize, usize), BitstreamError> {
82    if offset >= bytes.len() {
83        return Err(BitstreamError::unexpected_end(
84            "OBU offset past end of buffer",
85        ));
86    }
87    let h = bytes[offset];
88    let forbidden = (h >> 7) & 1;
89    if forbidden != 0 {
90        return Err(BitstreamError::invalid(
91            "OBU header has obu_forbidden_bit=1",
92        ));
93    }
94    let obu_type = (h >> 3) & 0xf;
95    let extension_flag = ((h >> 2) & 1) != 0;
96    let has_size_field = ((h >> 1) & 1) != 0;
97    let _reserved = h & 1;
98
99    let mut cur = offset + 1;
100    let (temporal_id, spatial_id) = if extension_flag {
101        if cur >= bytes.len() {
102            return Err(BitstreamError::unexpected_end("OBU extension byte missing"));
103        }
104        let e = bytes[cur];
105        cur += 1;
106        ((e >> 5) & 0x7, (e >> 3) & 0x3)
107    } else {
108        (0, 0)
109    };
110
111    if !has_size_field {
112        return Err(BitstreamError::unsupported(
113            "OBU without obu_has_size_field=1 (LOBF requires it)",
114        ));
115    }
116    let (size, size_len) = read_leb128(bytes, cur)?;
117    cur += size_len;
118    let payload_start = cur;
119    let payload_end = cur
120        .checked_add(size as usize)
121        .ok_or_else(|| BitstreamError::invalid("OBU size overflow"))?;
122    if payload_end > bytes.len() {
123        return Err(BitstreamError::unexpected_end(
124            "OBU payload extends past buffer end",
125        ));
126    }
127    Ok((
128        ObuHeader {
129            obu_type,
130            extension_flag,
131            has_size_field,
132            temporal_id,
133            spatial_id,
134        },
135        payload_start,
136        payload_end,
137        payload_end,
138    ))
139}
140
141/// 4.10 leb128 reader. Returns `(value, bytes_consumed)`. Limits
142/// itself to 8 bytes (the spec's hard maximum, encoding 56 bits of
143/// payload).
144pub fn read_leb128(bytes: &[u8], offset: usize) -> Result<(u64, usize), BitstreamError> {
145    let mut value: u64 = 0;
146    for i in 0..8 {
147        if offset + i >= bytes.len() {
148            return Err(BitstreamError::unexpected_end("leb128 truncated"));
149        }
150        let b = bytes[offset + i];
151        value |= ((b & 0x7f) as u64) << (i * 7);
152        if (b & 0x80) == 0 {
153            return Ok((value, i + 1));
154        }
155    }
156    Err(BitstreamError::invalid("leb128 longer than 8 bytes"))
157}
158
159/// Largest `u64` representable as an AV1 §4.10 leb128 code. The spec
160/// caps the encoding at 8 bytes, each contributing 7 payload bits, so
161/// values up to `2^56 - 1` round-trip. [`write_leb128`] refuses any
162/// value above this bound.
163pub const LEB128_MAX: u64 = (1u64 << 56) - 1;
164
165/// 4.10 leb128 writer — the inverse of [`read_leb128`].
166///
167/// Appends the minimal-length unsigned LEB128 encoding of `value` to
168/// `out` and returns the number of bytes written. Round-trip contract:
169/// the suffix `&out[start..start + n]` (where `start` is `out.len()`
170/// before the call and `n` is the returned length) feeds back through
171/// [`read_leb128`] as `(value, n)`.
172///
173/// AV1 caps a leb128 code at 8 bytes (56 bits of payload). Values
174/// above [`LEB128_MAX`] are rejected with
175/// [`BitstreamError::InvalidData`] rather than silently truncated, so
176/// the round-trip contract holds for every accepted input.
177pub fn write_leb128(out: &mut Vec<u8>, value: u64) -> Result<usize, BitstreamError> {
178    if value > LEB128_MAX {
179        return Err(BitstreamError::invalid(
180            "write_leb128: value exceeds AV1 §4.10 56-bit leb128 limit",
181        ));
182    }
183    let mut v = value;
184    let mut n = 0usize;
185    loop {
186        let mut byte = (v & 0x7f) as u8;
187        v >>= 7;
188        if v != 0 {
189            byte |= 0x80;
190            out.push(byte);
191            n += 1;
192        } else {
193            out.push(byte);
194            n += 1;
195            return Ok(n);
196        }
197    }
198}
199
200/// 5.3.2 caps `obu_type` at 4 bits.
201pub const OBU_TYPE_MAX: u8 = 0xf;
202/// 5.3.3 caps `temporal_id` at 3 bits.
203pub const OBU_TEMPORAL_ID_MAX: u8 = 0x7;
204/// 5.3.3 caps `spatial_id` at 2 bits.
205pub const OBU_SPATIAL_ID_MAX: u8 = 0x3;
206
207/// 5.3.1 OBU emitter — the inverse of [`read_obu`].
208///
209/// Appends `obu_header [obu_extension_header] obu_size payload` to `out`,
210/// returning `(start, end)` where the slice `&out[start..end]` is the
211/// fully-framed OBU. Round-trip contract: feeding `start` back through
212/// [`read_obu`] reproduces the same [`ObuHeader`], a payload range of
213/// `(payload_start, payload_end)` covering exactly `payload.len()`
214/// bytes, and `next_offset == end`.
215///
216/// Requirements (per the reader's contract and AV1 §5.3.1 LOBF):
217///
218/// * `header.has_size_field` must be `true`. The Low-Overhead-Bitstream
219///   Format mandates `obu_has_size_field=1` for every OBU; [`read_obu`]
220///   refuses anything else, so the writer mirrors that.
221/// * `header.obu_type` must be ≤ [`OBU_TYPE_MAX`] (4 bits).
222/// * `header.temporal_id` must be ≤ [`OBU_TEMPORAL_ID_MAX`] (3 bits) and
223///   `header.spatial_id` ≤ [`OBU_SPATIAL_ID_MAX`] (2 bits). These checks
224///   apply unconditionally — the reader zero-fills both fields when
225///   `extension_flag` is clear, so out-of-range values silently lose
226///   information without this guard.
227/// * `payload.len()` must be ≤ [`LEB128_MAX`] so the size field fits in
228///   the spec's eight-byte cap.
229///
230/// On any validation failure, `out` is left untouched (no partial OBU
231/// header is appended) and the call returns [`BitstreamError::InvalidData`].
232/// The forbidden bit and the trailing reserved bits in both header bytes
233/// are always emitted as zero, matching §5.3.2 / §5.3.3.
234pub fn write_obu(
235    out: &mut Vec<u8>,
236    header: ObuHeader,
237    payload: &[u8],
238) -> Result<(usize, usize), BitstreamError> {
239    if !header.has_size_field {
240        return Err(BitstreamError::invalid(
241            "write_obu: LOBF requires obu_has_size_field=1",
242        ));
243    }
244    if header.obu_type > OBU_TYPE_MAX {
245        return Err(BitstreamError::invalid(
246            "write_obu: obu_type exceeds 4-bit field",
247        ));
248    }
249    if header.extension_flag {
250        if header.temporal_id > OBU_TEMPORAL_ID_MAX {
251            return Err(BitstreamError::invalid(
252                "write_obu: temporal_id exceeds 3-bit field",
253            ));
254        }
255        if header.spatial_id > OBU_SPATIAL_ID_MAX {
256            return Err(BitstreamError::invalid(
257                "write_obu: spatial_id exceeds 2-bit field",
258            ));
259        }
260    } else if header.temporal_id != 0 || header.spatial_id != 0 {
261        // The reader returns (0, 0) when extension_flag is clear; refuse
262        // non-zero IDs paired with extension_flag=false so the round-trip
263        // is total — the caller would otherwise silently lose the IDs.
264        return Err(BitstreamError::invalid(
265            "write_obu: temporal/spatial_id set with extension_flag=0",
266        ));
267    }
268    if payload.len() as u64 > LEB128_MAX {
269        return Err(BitstreamError::invalid(
270            "write_obu: payload length exceeds AV1 §4.10 56-bit leb128 limit",
271        ));
272    }
273
274    let start = out.len();
275    // OBU header byte: obu_forbidden_bit (1) | obu_type (4) |
276    // obu_extension_flag (1) | obu_has_size_field (1) | obu_reserved_1bit (1).
277    let mut h = (header.obu_type & OBU_TYPE_MAX) << 3;
278    if header.extension_flag {
279        h |= 1 << 2;
280    }
281    // has_size_field is true by precondition above.
282    h |= 1 << 1;
283    out.push(h);
284    if header.extension_flag {
285        // Extension byte: temporal_id (3) | spatial_id (2) |
286        // extension_header_reserved_3bits (3).
287        let e = ((header.temporal_id & OBU_TEMPORAL_ID_MAX) << 5)
288            | ((header.spatial_id & OBU_SPATIAL_ID_MAX) << 3);
289        out.push(e);
290    }
291    // §4.10 leb128 encoding of obu_size. write_leb128 has already been
292    // bounds-checked above so this cannot Err — but if it ever did
293    // (e.g. the cap moved), truncate back to the original buffer length
294    // so the "untouched on rejection" contract still holds.
295    if let Err(e) = write_leb128(out, payload.len() as u64) {
296        out.truncate(start);
297        return Err(e);
298    }
299    out.extend_from_slice(payload);
300    Ok((start, out.len()))
301}
302
303// ─────────────────────────── Sequence header ─────────────────────────────────
304
305/// 6.4.2 `color_config`. Reduced form — we keep only the fields the
306/// HW backends actually need.
307#[derive(Debug, Clone, Default, PartialEq, Eq)]
308pub struct Av1ColorConfig {
309    pub bit_depth: u8,
310    pub monochrome: bool,
311    pub color_range: bool,
312    pub subsampling_x: bool,
313    pub subsampling_y: bool,
314    pub chroma_sample_position: u8,
315}
316
317/// 6.4.1 sequence header.
318#[derive(Debug, Clone, Default, PartialEq, Eq)]
319pub struct Av1SequenceHeader {
320    pub seq_profile: u8,
321    pub still_picture: bool,
322    pub reduced_still_picture_header: bool,
323
324    pub seq_level_idx_0: u8,
325    pub seq_tier_0: bool,
326    pub operating_point_idc_0: u16,
327
328    pub frame_width_bits: u8,
329    pub frame_height_bits: u8,
330    pub max_frame_width_minus_1: u32,
331    pub max_frame_height_minus_1: u32,
332
333    pub frame_id_numbers_present_flag: bool,
334    pub delta_frame_id_length_minus_2: u8,
335    pub additional_frame_id_length_minus_1: u8,
336
337    pub use_128x128_superblock: bool,
338    pub enable_filter_intra: bool,
339    pub enable_intra_edge_filter: bool,
340    pub enable_interintra_compound: bool,
341    pub enable_masked_compound: bool,
342    pub enable_warped_motion: bool,
343    pub enable_dual_filter: bool,
344    pub enable_order_hint: bool,
345    pub enable_jnt_comp: bool,
346    pub enable_ref_frame_mvs: bool,
347    pub seq_choose_screen_content_tools: bool,
348    pub seq_force_screen_content_tools: u8,
349    pub seq_choose_integer_mv: bool,
350    pub seq_force_integer_mv: u8,
351    pub order_hint_bits: u8,
352
353    pub enable_superres: bool,
354    pub enable_cdef: bool,
355    pub enable_restoration: bool,
356
357    pub color_config: Av1ColorConfig,
358    pub film_grain_params_present: bool,
359}
360
361impl Av1SequenceHeader {
362    /// Maximum frame width in luma samples.
363    pub fn max_frame_width(&self) -> u32 {
364        self.max_frame_width_minus_1 + 1
365    }
366
367    /// Maximum frame height in luma samples.
368    pub fn max_frame_height(&self) -> u32 {
369        self.max_frame_height_minus_1 + 1
370    }
371}
372
373/// 6.8.1 frame header — minimum fields, key-frame only.
374#[derive(Debug, Clone, Default, PartialEq, Eq)]
375pub struct Av1FrameHeader {
376    pub show_existing_frame: bool,
377    pub frame_type: u8,
378    pub show_frame: bool,
379    pub showable_frame: bool,
380    pub error_resilient_mode: bool,
381    pub disable_cdf_update: bool,
382    pub allow_screen_content_tools: bool,
383    pub force_integer_mv: bool,
384    pub frame_width: u32,
385    pub frame_height: u32,
386    pub render_width: u32,
387    pub render_height: u32,
388    pub allow_intrabc: bool,
389}
390
391/// Result of [`parse_obu_stream`].
392#[derive(Debug, Clone)]
393pub struct Av1KeyframeParse<'a> {
394    pub sequence_header: Av1SequenceHeader,
395    pub frame_header: Av1FrameHeader,
396    /// Byte range of the OBU(s) covering the keyframe. Contains
397    /// either an `OBU_FRAME` (header + tiles fused) or an
398    /// `OBU_FRAME_HEADER` followed by one or more `OBU_TILE_GROUP`s.
399    /// Includes the OBU header bytes — the HW backends submit the
400    /// raw OBU(s) to the GPU.
401    pub keyframe_obus: &'a [u8],
402}
403
404// ─────────────────────────── Sequence header parser ──────────────────────────
405
406/// Parse the payload of an `OBU_SEQUENCE_HEADER` (5.5).
407pub fn parse_sequence_header(payload: &[u8]) -> Result<Av1SequenceHeader, BitstreamError> {
408    let mut r = BitReader::new(payload);
409    let mut s = Av1SequenceHeader {
410        seq_profile: r.u(3) as u8,
411        ..Av1SequenceHeader::default()
412    };
413    if s.seq_profile > 2 {
414        return Err(BitstreamError::invalid(format!(
415            "invalid seq_profile={}",
416            s.seq_profile
417        )));
418    }
419    s.still_picture = r.u(1) != 0;
420    s.reduced_still_picture_header = r.u(1) != 0;
421    if s.reduced_still_picture_header && !s.still_picture {
422        return Err(BitstreamError::invalid(
423            "reduced_still_picture_header=1 with still_picture=0",
424        ));
425    }
426
427    if s.reduced_still_picture_header {
428        s.seq_level_idx_0 = r.u(5) as u8;
429        s.seq_tier_0 = false;
430        s.operating_point_idc_0 = 0;
431    } else {
432        let timing_info_present_flag = r.u(1) != 0;
433        if timing_info_present_flag {
434            // timing_info()
435            let _num_units_in_display_tick = r.u(32);
436            let _time_scale = r.u(32);
437            let equal_picture_interval = r.u(1) != 0;
438            if equal_picture_interval {
439                let _num_ticks_per_picture_minus_1 = read_uvlc(&mut r)?;
440            }
441            let decoder_model_info_present_flag = r.u(1) != 0;
442            if decoder_model_info_present_flag {
443                return Err(BitstreamError::unsupported(
444                    "AV1 decoder_model_info_present_flag=1 not supported",
445                ));
446            }
447        }
448        let initial_display_delay_present_flag = r.u(1) != 0;
449        let operating_points_cnt_minus_1 = r.u(5) as u8;
450        if operating_points_cnt_minus_1 > 0 {
451            return Err(BitstreamError::unsupported(
452                "AV1 operating_points_cnt_minus_1>0 not supported by minimal parser",
453            ));
454        }
455        // Read the single operating point.
456        s.operating_point_idc_0 = r.u(12) as u16;
457        s.seq_level_idx_0 = r.u(5) as u8;
458        if s.seq_level_idx_0 > 7 {
459            s.seq_tier_0 = r.u(1) != 0;
460        }
461        if initial_display_delay_present_flag {
462            let initial_display_delay_present_for_this_op = r.u(1) != 0;
463            if initial_display_delay_present_for_this_op {
464                let _initial_display_delay_minus_1 = r.u(4);
465            }
466        }
467    }
468
469    s.frame_width_bits = (r.u(4) + 1) as u8;
470    s.frame_height_bits = (r.u(4) + 1) as u8;
471    s.max_frame_width_minus_1 = r.u(s.frame_width_bits as u32);
472    s.max_frame_height_minus_1 = r.u(s.frame_height_bits as u32);
473
474    if s.reduced_still_picture_header {
475        s.frame_id_numbers_present_flag = false;
476    } else {
477        s.frame_id_numbers_present_flag = r.u(1) != 0;
478    }
479    if s.frame_id_numbers_present_flag {
480        s.delta_frame_id_length_minus_2 = r.u(4) as u8;
481        s.additional_frame_id_length_minus_1 = r.u(3) as u8;
482    }
483
484    s.use_128x128_superblock = r.u(1) != 0;
485    s.enable_filter_intra = r.u(1) != 0;
486    s.enable_intra_edge_filter = r.u(1) != 0;
487
488    if s.reduced_still_picture_header {
489        s.enable_interintra_compound = false;
490        s.enable_masked_compound = false;
491        s.enable_warped_motion = false;
492        s.enable_dual_filter = false;
493        s.enable_order_hint = false;
494        s.enable_jnt_comp = false;
495        s.enable_ref_frame_mvs = false;
496        s.seq_force_screen_content_tools = 2; // SELECT_SCREEN_CONTENT_TOOLS
497        s.seq_force_integer_mv = 2; // SELECT_INTEGER_MV
498        s.order_hint_bits = 0;
499    } else {
500        s.enable_interintra_compound = r.u(1) != 0;
501        s.enable_masked_compound = r.u(1) != 0;
502        s.enable_warped_motion = r.u(1) != 0;
503        s.enable_dual_filter = r.u(1) != 0;
504        s.enable_order_hint = r.u(1) != 0;
505        if s.enable_order_hint {
506            s.enable_jnt_comp = r.u(1) != 0;
507            s.enable_ref_frame_mvs = r.u(1) != 0;
508        }
509        s.seq_choose_screen_content_tools = r.u(1) != 0;
510        s.seq_force_screen_content_tools = if s.seq_choose_screen_content_tools {
511            2 // SELECT_SCREEN_CONTENT_TOOLS
512        } else {
513            r.u(1) as u8
514        };
515        if s.seq_force_screen_content_tools > 0 {
516            s.seq_choose_integer_mv = r.u(1) != 0;
517            s.seq_force_integer_mv = if s.seq_choose_integer_mv {
518                2 // SELECT_INTEGER_MV
519            } else {
520                r.u(1) as u8
521            };
522        } else {
523            s.seq_force_integer_mv = 2;
524        }
525        if s.enable_order_hint {
526            s.order_hint_bits = (r.u(3) + 1) as u8;
527        }
528    }
529
530    s.enable_superres = r.u(1) != 0;
531    s.enable_cdef = r.u(1) != 0;
532    s.enable_restoration = r.u(1) != 0;
533
534    // color_config
535    let high_bitdepth = r.u(1) != 0;
536    if s.seq_profile == 2 && high_bitdepth {
537        let twelve_bit = r.u(1) != 0;
538        s.color_config.bit_depth = if twelve_bit { 12 } else { 10 };
539    } else {
540        s.color_config.bit_depth = if high_bitdepth { 10 } else { 8 };
541    }
542    s.color_config.monochrome = if s.seq_profile == 1 {
543        false
544    } else {
545        r.u(1) != 0
546    };
547    let color_description_present_flag = r.u(1) != 0;
548    if color_description_present_flag {
549        let _color_primaries = r.u(8);
550        let _transfer_characteristics = r.u(8);
551        let _matrix_coefficients = r.u(8);
552    }
553    if s.color_config.monochrome {
554        s.color_config.color_range = r.u(1) != 0;
555        s.color_config.subsampling_x = true;
556        s.color_config.subsampling_y = true;
557        s.color_config.chroma_sample_position = 0;
558    } else {
559        // identity (RGB) signalling: color_primaries==BT_709 et al.
560        // For our scope we just read the fields and don't special-case
561        // identity — we still record subsampling x/y.
562        s.color_config.color_range = r.u(1) != 0;
563        if s.seq_profile == 0 {
564            s.color_config.subsampling_x = true;
565            s.color_config.subsampling_y = true;
566        } else if s.seq_profile == 1 {
567            s.color_config.subsampling_x = false;
568            s.color_config.subsampling_y = false;
569        } else {
570            // profile 2: depends on bit_depth
571            if s.color_config.bit_depth == 12 {
572                s.color_config.subsampling_x = r.u(1) != 0;
573                s.color_config.subsampling_y = if s.color_config.subsampling_x {
574                    r.u(1) != 0
575                } else {
576                    false
577                };
578            } else {
579                s.color_config.subsampling_x = true;
580                s.color_config.subsampling_y = false;
581            }
582        }
583        if s.color_config.subsampling_x && s.color_config.subsampling_y {
584            s.color_config.chroma_sample_position = r.u(2) as u8;
585        }
586        let _separate_uv_deltas_present = r.u(1);
587    }
588    s.film_grain_params_present = r.u(1) != 0;
589    // trailing_bits handled by caller (we just stop reading).
590    Ok(s)
591}
592
593/// 4.10.4 unsigned variable-length code (UVLC). Used in `timing_info()`.
594fn read_uvlc(r: &mut BitReader<'_>) -> Result<u32, BitstreamError> {
595    let mut leading_zeros = 0u32;
596    while !r.at_end() {
597        if r.u(1) != 0 {
598            break;
599        }
600        leading_zeros += 1;
601        if leading_zeros >= 32 {
602            // spec says leadingZeros >= 32 → return 0xFFFFFFFF
603            return Ok(u32::MAX);
604        }
605    }
606    let value = r.u(leading_zeros);
607    Ok(value + (1u32 << leading_zeros) - 1)
608}
609
610// ─────────────────────────── Frame header parser ─────────────────────────────
611
612/// Parse the **uncompressed** part of an AV1 frame header / frame
613/// OBU. Only the fields needed for HW-decode submission of a key
614/// frame are populated; everything past the keyframe-specific block
615/// is skipped.
616///
617/// `payload` is the OBU payload (after the OBU header / extension /
618/// size bytes have been consumed by [`read_obu`]). For an OBU_FRAME
619/// the payload is `frame_header_obu()` followed by `tile_group_obu()`;
620/// we only parse the header part.
621pub fn parse_frame_header(
622    payload: &[u8],
623    seq: &Av1SequenceHeader,
624) -> Result<Av1FrameHeader, BitstreamError> {
625    let mut r = BitReader::new(payload);
626    let mut fh = Av1FrameHeader::default();
627
628    if seq.reduced_still_picture_header {
629        fh.show_existing_frame = false;
630        fh.frame_type = FRAME_TYPE_KEY;
631        fh.show_frame = true;
632        fh.showable_frame = false;
633        fh.error_resilient_mode = true;
634    } else {
635        fh.show_existing_frame = r.u(1) != 0;
636        if fh.show_existing_frame {
637            return Err(BitstreamError::unsupported(
638                "AV1 show_existing_frame=1 not supported by minimal parser",
639            ));
640        }
641        fh.frame_type = r.u(2) as u8;
642        if fh.frame_type != FRAME_TYPE_KEY {
643            return Err(BitstreamError::unsupported(format!(
644                "AV1 frame_type={} not a KEY_FRAME (minimal parser is keyframe-only)",
645                fh.frame_type
646            )));
647        }
648        fh.show_frame = r.u(1) != 0;
649        if fh.show_frame {
650            // showable_frame is implicit for shown key frames.
651            fh.showable_frame = false;
652        } else {
653            fh.showable_frame = r.u(1) != 0;
654        }
655        // For key frames showable_frame is implicit — error_resilient
656        // signalled directly.
657        fh.error_resilient_mode = r.u(1) != 0;
658    }
659
660    let _disable_cdf_update = r.u(1);
661    fh.disable_cdf_update = _disable_cdf_update != 0;
662
663    let allow_screen_content_tools = if seq.seq_force_screen_content_tools == 2 {
664        r.u(1) != 0
665    } else {
666        seq.seq_force_screen_content_tools != 0
667    };
668    fh.allow_screen_content_tools = allow_screen_content_tools;
669    if allow_screen_content_tools {
670        fh.force_integer_mv = if seq.seq_force_integer_mv == 2 {
671            r.u(1) != 0
672        } else {
673            seq.seq_force_integer_mv != 0
674        };
675    } else {
676        fh.force_integer_mv = false;
677    }
678
679    if seq.frame_id_numbers_present_flag {
680        let id_len = seq.additional_frame_id_length_minus_1 as u32
681            + seq.delta_frame_id_length_minus_2 as u32
682            + 3;
683        let _current_frame_id = r.u(id_len);
684    }
685
686    let frame_size_override_flag = if fh.frame_type == FRAME_TYPE_SWITCH {
687        true
688    } else if seq.reduced_still_picture_header {
689        false
690    } else {
691        r.u(1) != 0
692    };
693
694    let _order_hint = if seq.enable_order_hint {
695        r.u(seq.order_hint_bits as u32)
696    } else {
697        0
698    };
699
700    // For a KEY_FRAME with show_frame=1 there's no primary_ref_frame
701    // field (spec sets it to PRIMARY_REF_NONE implicitly).
702    if !(fh.frame_type == FRAME_TYPE_KEY && fh.show_frame) {
703        let _primary_ref_frame = r.u(3);
704    }
705
706    // frame_size() — for a key frame.
707    if frame_size_override_flag {
708        fh.frame_width = r.u(seq.frame_width_bits as u32) + 1;
709        fh.frame_height = r.u(seq.frame_height_bits as u32) + 1;
710    } else {
711        fh.frame_width = seq.max_frame_width();
712        fh.frame_height = seq.max_frame_height();
713    }
714    if seq.enable_superres {
715        let use_superres = r.u(1) != 0;
716        if use_superres {
717            // SUPERRES_DENOM_BITS=3
718            let _coded_denom = r.u(3);
719        }
720    }
721    // render_size()
722    let render_and_frame_size_different = r.u(1) != 0;
723    if render_and_frame_size_different {
724        fh.render_width = r.u(16) + 1;
725        fh.render_height = r.u(16) + 1;
726    } else {
727        fh.render_width = fh.frame_width;
728        fh.render_height = fh.frame_height;
729    }
730
731    // allow_intrabc only applies to key / intra-only frames.
732    if allow_screen_content_tools && fh.frame_type == FRAME_TYPE_KEY {
733        fh.allow_intrabc = r.u(1) != 0;
734    }
735
736    // We deliberately stop parsing here. The remaining fields (loop
737    // filter parameters, quantiser, segmentation, tile info, …) are
738    // not needed to confirm this is a key frame and to populate the
739    // HW backends' picture-info struct's *high-level* fields. The HW
740    // submit ships the entire OBU bytes back to the GPU, which
741    // re-parses them itself.
742    Ok(fh)
743}
744
745// ─────────────────────────── parse_obu_stream ────────────────────────────────
746
747/// Walk an AV1 OBU stream and return the parsed sequence header,
748/// the parsed key-frame header, and a slice of bytes covering the
749/// keyframe OBU(s) (frame-header + tile-groups, or a single frame OBU).
750pub fn parse_obu_stream(bytes: &[u8]) -> Result<Av1KeyframeParse<'_>, BitstreamError> {
751    let mut offset = 0;
752    let mut sequence_header: Option<Av1SequenceHeader> = None;
753    let mut frame_header: Option<Av1FrameHeader> = None;
754    let mut keyframe_start: Option<usize> = None;
755    let mut keyframe_end: Option<usize> = None;
756    let mut last_was_frame_header = false;
757
758    while offset < bytes.len() {
759        let (h, p_start, p_end, next) = read_obu(bytes, offset)?;
760        match h.obu_type {
761            OBU_SEQUENCE_HEADER => {
762                if sequence_header.is_none() {
763                    sequence_header = Some(parse_sequence_header(&bytes[p_start..p_end])?);
764                }
765            }
766            OBU_TEMPORAL_DELIMITER => {
767                // No-op: just delimits temporal units.
768            }
769            OBU_FRAME => {
770                let seq = sequence_header.as_ref().ok_or_else(|| {
771                    BitstreamError::invalid("OBU_FRAME without preceding sequence header")
772                })?;
773                if frame_header.is_none() {
774                    let fh = parse_frame_header(&bytes[p_start..p_end], seq)?;
775                    frame_header = Some(fh);
776                    keyframe_start = Some(offset);
777                    keyframe_end = Some(next);
778                }
779            }
780            OBU_FRAME_HEADER => {
781                let seq = sequence_header.as_ref().ok_or_else(|| {
782                    BitstreamError::invalid("OBU_FRAME_HEADER without preceding sequence header")
783                })?;
784                if frame_header.is_none() {
785                    let fh = parse_frame_header(&bytes[p_start..p_end], seq)?;
786                    frame_header = Some(fh);
787                    keyframe_start = Some(offset);
788                    keyframe_end = Some(next);
789                    last_was_frame_header = true;
790                }
791            }
792            OBU_TILE_GROUP => {
793                if last_was_frame_header {
794                    keyframe_end = Some(next);
795                }
796            }
797            _ => {
798                // Metadata, tile-list, padding etc. — skip but reset
799                // tile-group accumulation flag.
800                last_was_frame_header = false;
801            }
802        }
803        offset = next;
804    }
805
806    let sequence_header = sequence_header
807        .ok_or_else(|| BitstreamError::invalid("stream has no sequence header OBU"))?;
808    let frame_header =
809        frame_header.ok_or_else(|| BitstreamError::invalid("stream has no key-frame OBU"))?;
810    let start = keyframe_start.expect("keyframe bounds set when frame_header set");
811    let end = keyframe_end.expect("keyframe bounds set when frame_header set");
812    Ok(Av1KeyframeParse {
813        sequence_header,
814        frame_header,
815        keyframe_obus: &bytes[start..end],
816    })
817}
818
819// ─────────────────────────── Tests ───────────────────────────────────────────
820
821#[cfg(test)]
822mod tests {
823    use super::*;
824
825    #[test]
826    fn leb128_roundtrip_small_values() {
827        // Encode 0, 1, 127 (single-byte) and 128 (two bytes: 0x80 0x01).
828        let bytes = [0x00, 0x01, 0x7f, 0x80, 0x01];
829        let (v0, n0) = read_leb128(&bytes, 0).unwrap();
830        assert_eq!((v0, n0), (0, 1));
831        let (v1, n1) = read_leb128(&bytes, 1).unwrap();
832        assert_eq!((v1, n1), (1, 1));
833        let (v127, n127) = read_leb128(&bytes, 2).unwrap();
834        assert_eq!((v127, n127), (127, 1));
835        let (v128, n128) = read_leb128(&bytes, 3).unwrap();
836        assert_eq!((v128, n128), (128, 2));
837    }
838
839    #[test]
840    fn write_leb128_encodes_minimal_known_values() {
841        // 0 → 0x00 (1 byte), 1 → 0x01, 127 → 0x7f.
842        let mut out = Vec::new();
843        assert_eq!(write_leb128(&mut out, 0).unwrap(), 1);
844        assert_eq!(out.as_slice(), &[0x00]);
845        out.clear();
846        assert_eq!(write_leb128(&mut out, 1).unwrap(), 1);
847        assert_eq!(out.as_slice(), &[0x01]);
848        out.clear();
849        assert_eq!(write_leb128(&mut out, 127).unwrap(), 1);
850        assert_eq!(out.as_slice(), &[0x7f]);
851        // 128 → 0x80, 0x01 (2 bytes).
852        out.clear();
853        assert_eq!(write_leb128(&mut out, 128).unwrap(), 2);
854        assert_eq!(out.as_slice(), &[0x80, 0x01]);
855        // 16384 → 0x80, 0x80, 0x01 (3 bytes).
856        out.clear();
857        assert_eq!(write_leb128(&mut out, 16384).unwrap(), 3);
858        assert_eq!(out.as_slice(), &[0x80, 0x80, 0x01]);
859    }
860
861    #[test]
862    fn write_leb128_appends_without_clobbering() {
863        // The writer is documented to append; pre-existing prefix bytes
864        // must be preserved so the caller can frame an OBU header + size
865        // pair into the same buffer.
866        let mut out = vec![0xAA, 0xBB];
867        let n = write_leb128(&mut out, 300).unwrap();
868        assert_eq!(out[0..2], [0xAA, 0xBB]);
869        let (val, consumed) = read_leb128(&out, 2).unwrap();
870        assert_eq!(val, 300);
871        assert_eq!(consumed, n);
872        // 300 = 0x12C → 0xAC, 0x02 (two bytes).
873        assert_eq!(out[2..], [0xAC, 0x02]);
874    }
875
876    #[test]
877    fn write_leb128_at_maximum_value_emits_eight_bytes() {
878        let mut out = Vec::new();
879        let n = write_leb128(&mut out, LEB128_MAX).unwrap();
880        assert_eq!(n, 8);
881        assert_eq!(out.len(), 8);
882        // All payload nibbles are 0x7f; continuation bits set on bytes
883        // 0..6, cleared on byte 7.
884        assert_eq!(out[0..7], [0xff; 7]);
885        assert_eq!(out[7], 0x7f);
886        let (val, consumed) = read_leb128(&out, 0).unwrap();
887        assert_eq!(val, LEB128_MAX);
888        assert_eq!(consumed, 8);
889    }
890
891    #[test]
892    fn write_leb128_rejects_over_56_bits() {
893        let mut out = Vec::new();
894        // LEB128_MAX is the largest representable value; the next one up
895        // would need a ninth byte, which read_leb128 refuses.
896        let err = write_leb128(&mut out, LEB128_MAX + 1).unwrap_err();
897        assert!(
898            matches!(err, BitstreamError::InvalidData(_)),
899            "expected InvalidData, got {err:?}"
900        );
901        // u64::MAX is also out of range.
902        let err = write_leb128(&mut out, u64::MAX).unwrap_err();
903        assert!(matches!(err, BitstreamError::InvalidData(_)));
904        // Buffer is left untouched on rejection.
905        assert!(out.is_empty());
906    }
907
908    #[test]
909    fn write_leb128_round_trips_against_read() {
910        // Spot-check a spread of values covering the 1-byte, 2-byte,
911        // 3-byte, …, 8-byte size classes plus byte-boundary neighbours.
912        let cases: &[u64] = &[
913            0,
914            1,
915            127,
916            128,
917            16_383,
918            16_384,
919            2_097_151,
920            2_097_152,
921            268_435_455,
922            268_435_456,
923            34_359_738_367,
924            34_359_738_368,
925            4_398_046_511_103,
926            4_398_046_511_104,
927            562_949_953_421_311,
928            LEB128_MAX,
929        ];
930        for &v in cases {
931            let mut buf = Vec::new();
932            let n = write_leb128(&mut buf, v).unwrap();
933            assert_eq!(buf.len(), n, "writer length matches returned size");
934            let (got, consumed) = read_leb128(&buf, 0).unwrap();
935            assert_eq!(got, v, "round-trip value for {v}");
936            assert_eq!(consumed, n, "round-trip byte count for {v}");
937        }
938    }
939
940    #[test]
941    fn read_obu_decodes_temporal_delimiter() {
942        // 0x12 = obu_type=2 (TD), has_size_field=1 → followed by 0x00 (size=0).
943        let bytes = [0x12, 0x00];
944        let (h, ps, pe, next) = read_obu(&bytes, 0).unwrap();
945        assert_eq!(h.obu_type, OBU_TEMPORAL_DELIMITER);
946        assert!(h.has_size_field);
947        assert_eq!(ps, 2);
948        assert_eq!(pe, 2);
949        assert_eq!(next, 2);
950    }
951
952    #[test]
953    fn read_obu_rejects_forbidden_bit() {
954        // 0x80 sets obu_forbidden_bit=1.
955        let bytes = [0x80, 0x00];
956        let err = read_obu(&bytes, 0).unwrap_err();
957        match err {
958            BitstreamError::InvalidData(_) => {}
959            e => panic!("expected InvalidData, got {e:?}"),
960        }
961    }
962
963    fn td_header() -> ObuHeader {
964        ObuHeader {
965            obu_type: OBU_TEMPORAL_DELIMITER,
966            extension_flag: false,
967            has_size_field: true,
968            temporal_id: 0,
969            spatial_id: 0,
970        }
971    }
972
973    #[test]
974    fn write_obu_emits_empty_temporal_delimiter_canonically() {
975        // A TD OBU with no extension and zero payload is exactly two
976        // bytes: 0x12 (type=2, has_size=1) and 0x00 (size=0). This
977        // matches the canonical fixture used in `read_obu_decodes_*`.
978        let mut out = Vec::new();
979        let (start, end) = write_obu(&mut out, td_header(), &[]).unwrap();
980        assert_eq!(start, 0);
981        assert_eq!(end, 2);
982        assert_eq!(out.as_slice(), &[0x12, 0x00]);
983    }
984
985    #[test]
986    fn write_obu_round_trips_through_read_obu() {
987        // Build a frame-shaped OBU with a payload of varying sizes and
988        // confirm read_obu reproduces every header field plus the
989        // payload range exactly.
990        for &len in &[0usize, 1, 16, 127, 128, 1024] {
991            let payload: Vec<u8> = (0..len).map(|i| (i as u8).wrapping_mul(31)).collect();
992            let header = ObuHeader {
993                obu_type: OBU_FRAME,
994                extension_flag: false,
995                has_size_field: true,
996                temporal_id: 0,
997                spatial_id: 0,
998            };
999            let mut out = Vec::new();
1000            let (start, end) = write_obu(&mut out, header, &payload).unwrap();
1001            let (got, p_start, p_end, next) = read_obu(&out, start).unwrap();
1002            assert_eq!(got, header, "header round-trip for len={len}");
1003            assert_eq!(p_end - p_start, len);
1004            assert_eq!(&out[p_start..p_end], payload.as_slice());
1005            assert_eq!(next, end);
1006        }
1007    }
1008
1009    #[test]
1010    fn write_obu_extension_byte_round_trips_ids() {
1011        // Sweep every legal (temporal_id, spatial_id) pair through
1012        // extension_flag=true and confirm the extension byte decodes
1013        // back to the same IDs.
1014        for t in 0..=OBU_TEMPORAL_ID_MAX {
1015            for s in 0..=OBU_SPATIAL_ID_MAX {
1016                let header = ObuHeader {
1017                    obu_type: OBU_FRAME_HEADER,
1018                    extension_flag: true,
1019                    has_size_field: true,
1020                    temporal_id: t,
1021                    spatial_id: s,
1022                };
1023                let payload = [0xaa, 0xbb, 0xcc];
1024                let mut out = Vec::new();
1025                let (start, _end) = write_obu(&mut out, header, &payload).unwrap();
1026                let (got, p_start, p_end, _next) = read_obu(&out, start).unwrap();
1027                assert_eq!(got.temporal_id, t, "t={t} s={s}");
1028                assert_eq!(got.spatial_id, s, "t={t} s={s}");
1029                assert!(got.extension_flag);
1030                assert_eq!(&out[p_start..p_end], &payload);
1031            }
1032        }
1033    }
1034
1035    #[test]
1036    fn write_obu_appends_after_existing_prefix() {
1037        // The writer is documented to append: a pre-existing prefix in
1038        // `out` must be preserved so callers can concatenate multiple
1039        // OBUs into one temporal-unit buffer.
1040        let mut out = vec![0xde, 0xad, 0xbe, 0xef];
1041        let (start, end) = write_obu(&mut out, td_header(), &[]).unwrap();
1042        assert_eq!(start, 4);
1043        assert_eq!(end, 6);
1044        assert_eq!(&out[0..4], &[0xde, 0xad, 0xbe, 0xef]);
1045        let (got, _, _, next) = read_obu(&out, start).unwrap();
1046        assert_eq!(got.obu_type, OBU_TEMPORAL_DELIMITER);
1047        assert_eq!(next, end);
1048    }
1049
1050    #[test]
1051    fn write_obu_rejects_no_size_field() {
1052        let mut h = td_header();
1053        h.has_size_field = false;
1054        let mut out = Vec::new();
1055        let err = write_obu(&mut out, h, &[]).unwrap_err();
1056        assert!(matches!(err, BitstreamError::InvalidData(_)));
1057        assert!(out.is_empty(), "rejected write must not append");
1058    }
1059
1060    #[test]
1061    fn write_obu_rejects_oversized_obu_type() {
1062        let mut h = td_header();
1063        h.obu_type = 16; // 4-bit field max is 15
1064        let mut out = Vec::new();
1065        assert!(matches!(
1066            write_obu(&mut out, h, &[]),
1067            Err(BitstreamError::InvalidData(_))
1068        ));
1069        assert!(out.is_empty());
1070    }
1071
1072    #[test]
1073    fn write_obu_rejects_oversized_ids() {
1074        // temporal_id > 7 with extension_flag=true.
1075        let mut h = ObuHeader {
1076            obu_type: OBU_FRAME,
1077            extension_flag: true,
1078            has_size_field: true,
1079            temporal_id: 8,
1080            spatial_id: 0,
1081        };
1082        let mut out = Vec::new();
1083        assert!(matches!(
1084            write_obu(&mut out, h, &[]),
1085            Err(BitstreamError::InvalidData(_))
1086        ));
1087        assert!(out.is_empty());
1088        // spatial_id > 3 with extension_flag=true.
1089        h.temporal_id = 0;
1090        h.spatial_id = 4;
1091        assert!(matches!(
1092            write_obu(&mut out, h, &[]),
1093            Err(BitstreamError::InvalidData(_))
1094        ));
1095        assert!(out.is_empty());
1096    }
1097
1098    #[test]
1099    fn write_obu_rejects_nonzero_ids_without_extension_flag() {
1100        // The reader returns (0, 0) for both IDs when extension_flag is
1101        // clear; pairing non-zero IDs with extension_flag=false would
1102        // silently lose them, so the writer refuses it for round-trip
1103        // soundness.
1104        let mut h = td_header();
1105        h.temporal_id = 1;
1106        let mut out = Vec::new();
1107        assert!(matches!(
1108            write_obu(&mut out, h, &[]),
1109            Err(BitstreamError::InvalidData(_))
1110        ));
1111        assert!(out.is_empty());
1112
1113        let mut h = td_header();
1114        h.spatial_id = 1;
1115        assert!(matches!(
1116            write_obu(&mut out, h, &[]),
1117            Err(BitstreamError::InvalidData(_))
1118        ));
1119        assert!(out.is_empty());
1120    }
1121
1122    #[test]
1123    fn write_obu_accepts_max_legal_ids() {
1124        let header = ObuHeader {
1125            obu_type: OBU_TYPE_MAX,
1126            extension_flag: true,
1127            has_size_field: true,
1128            temporal_id: OBU_TEMPORAL_ID_MAX,
1129            spatial_id: OBU_SPATIAL_ID_MAX,
1130        };
1131        let mut out = Vec::new();
1132        let (start, _) = write_obu(&mut out, header, &[]).unwrap();
1133        let (got, _, _, _) = read_obu(&out, start).unwrap();
1134        assert_eq!(got, header);
1135    }
1136
1137    #[test]
1138    fn write_obu_emits_multi_byte_leb128_size() {
1139        // payload.len() = 128 needs a 2-byte leb128 size (0x80, 0x01).
1140        // Confirms the writer's size-field encoding is delegated to
1141        // write_leb128 correctly.
1142        let payload = vec![0u8; 128];
1143        let mut out = Vec::new();
1144        let (_start, end) = write_obu(&mut out, td_header(), &payload).unwrap();
1145        // header byte + 2-byte size + 128-byte payload = 131 bytes.
1146        assert_eq!(end, 1 + 2 + 128);
1147        assert_eq!(out[0], 0x12);
1148        assert_eq!(out[1], 0x80);
1149        assert_eq!(out[2], 0x01);
1150        assert_eq!(&out[3..], payload.as_slice());
1151    }
1152
1153    #[test]
1154    fn write_obu_concatenates_into_obu_stream() {
1155        // Build a TD + SequenceHeader-shaped stream by appending two
1156        // OBUs to the same buffer, then walk the result with read_obu.
1157        let mut out = Vec::new();
1158        let (s1, e1) = write_obu(&mut out, td_header(), &[]).unwrap();
1159        let seq_header = ObuHeader {
1160            obu_type: OBU_SEQUENCE_HEADER,
1161            extension_flag: false,
1162            has_size_field: true,
1163            temporal_id: 0,
1164            spatial_id: 0,
1165        };
1166        let seq_payload = [0x11, 0x22, 0x33, 0x44];
1167        let (s2, e2) = write_obu(&mut out, seq_header, &seq_payload).unwrap();
1168        assert_eq!(s2, e1, "second OBU starts where the first ended");
1169        assert_eq!(e2, out.len());
1170
1171        let (h1, _, _, n1) = read_obu(&out, s1).unwrap();
1172        assert_eq!(h1.obu_type, OBU_TEMPORAL_DELIMITER);
1173        assert_eq!(n1, s2);
1174        let (h2, ps2, pe2, n2) = read_obu(&out, n1).unwrap();
1175        assert_eq!(h2.obu_type, OBU_SEQUENCE_HEADER);
1176        assert_eq!(&out[ps2..pe2], &seq_payload);
1177        assert_eq!(n2, e2);
1178    }
1179}