Skip to main content

oxideav_av1/decoder/
mod.rs

1//! Decoder side of the crate.
2//!
3//! One decode surface remains as of r428: the **spec-faithful frame
4//! driver** ([`decode_av1_spec`] / [`SpecDecodeSession`]), the
5//! conformance-corpus-validated decoder that wires the parsing
6//! modules (`obu`, `sequence_header`, `frame_header`,
7//! `tile_group_obu`), the §5.11 `PartitionWalker` syntax walk, the
8//! §7.11/§7.12/§7.13 reconstruction chain, and the full §7.4 post
9//! chain (deblock, CDEF, superres, loop restoration, film grain)
10//! into a pixel-out entry point.
11//!
12//! ## The r428 mirror-path retirement
13//!
14//! Until r428 a second surface existed: the "encoder-mirror" decode
15//! path, the exact writer-inverse of this crate's HISTORICAL
16//! constrained intra encoders (fixed 16×16 and dyn-extent drivers
17//! from the early encoder arcs). Those encoders emitted
18//! NON-conformant streams — their leaves coded `y_mode` with the
19//! §5.11.22 non-keyframe CDFs on intra frames — so their streams
20//! could only be decoded by inverting the writer bug-for-bug, and
21//! [`decode_av1`] tried that mirror arm first. The conformance-grade
22//! encoders (r409 onward) made the mirror emit arms redundant:
23//! `encode_av1` has produced spec-conformant streams ever since, and
24//! every corpus stream rides the spec driver. r428 retires the whole
25//! mirror surface — emit arms, mirror decode arm, and the historical
26//! `Frame::Yuv420_16x16` / `Frame::Yuv420Dyn` / `Frame::YDyn`
27//! variants (the [`Frame`] enum is `#[non_exhaustive]`, so match
28//! sites already carried a wildcard arm). Every stream now decodes
29//! through the spec driver and surfaces as [`Frame::Spec`].
30
31// internal — exposed for tests/fuzz; not part of the stable API
32#[doc(hidden)]
33pub mod frame_driver;
34
35pub use frame_driver::{decode_av1_spec, decode_av1_spec_at_operating_point, SpecFrame};
36#[doc(hidden)]
37pub use frame_driver::{
38    decode_frame_spec, DecodeStageTimings, SpecDecodeSession, MAX_PICTURE_SIZE,
39};
40
41use crate::Error;
42
43/// One decoded (shown) frame surfaced by [`decode_av1`].
44///
45/// As of the r428 mirror-path retirement every stream decodes through
46/// the spec-faithful driver, so [`Frame::Spec`] is the only variant
47/// constructed. The enum stays `#[non_exhaustive]` (as it has been
48/// since its introduction), so downstream match sites are unaffected.
49#[derive(Debug, Clone, PartialEq, Eq)]
50#[non_exhaustive]
51pub enum Frame {
52    /// A frame decoded through the spec-faithful driver
53    /// ([`decode_av1_spec`]): intra + inter GOPs,
54    /// `show_existing_frame`, 4:2:0 / 4:2:2 / 4:4:4 / monochrome
55    /// layouts, 8/10/12-bit output, multi-tile frames, film grain,
56    /// superres, loop restoration — see [`SpecFrame`] for the plane
57    /// layout contract (cropped extents, per-plane dims, 10/12-bit as
58    /// little-endian 2-byte samples).
59    Spec(SpecFrame),
60}
61
62/// Decode an AV1 IVF v0 buffer.
63///
64/// Every stream rides the spec-faithful driver
65/// ([`decode_av1_spec`] — the conformance-corpus-validated decoder);
66/// each SHOWN frame surfaces as [`Frame::Spec`]. The historical
67/// encoder-mirror acceptance arm was retired in r428 (see the module
68/// docs) — the non-conformant streams it accepted could only be
69/// produced by this crate's own retired mirror emit arms, never by
70/// the conformance-grade encoders behind [`crate::encode_av1`].
71///
72/// Returns one [`Frame`] per shown frame, in output order.
73///
74/// ## Errors
75///
76/// * Buffer ends mid-IVF-header or mid-frame — [`Error::UnexpectedEnd`].
77/// * Any spec-driver parse/decode failure — the driver's typed
78///   [`Error`].
79pub fn decode_av1(input: &[u8]) -> Result<Vec<Frame>, Error> {
80    Ok(decode_av1_spec(input)?
81        .into_iter()
82        .map(Frame::Spec)
83        .collect())
84}
85
86/// r430 — [`decode_av1`] at an externally selected operating point
87/// (§6.7.5 `choose_operating_point()`).
88///
89/// `operating_point` indexes the sequence header's operating-point
90/// list (`0..=operating_points_cnt_minus_1`). With a non-zero
91/// `OperatingPointIdc` the §5.3.1 `drop_obu()` rule skips every OBU
92/// that carries an extension header and lies outside the point's
93/// temporal/spatial layer set, so a temporally scalable stream
94/// decodes to exactly the shown frames of the surviving layers —
95/// `operating_point = 0` reproduces [`decode_av1`] byte for byte on
96/// every stream.
97///
98/// ## Errors
99///
100/// * [`Error::OperatingPointOutOfRange`] — the index exceeds the
101///   sequence header's list.
102/// * Every [`decode_av1`] error surface otherwise.
103pub fn decode_av1_at_operating_point(
104    input: &[u8],
105    operating_point: u8,
106) -> Result<Vec<Frame>, Error> {
107    Ok(decode_av1_spec_at_operating_point(input, operating_point)?
108        .into_iter()
109        .map(Frame::Spec)
110        .collect())
111}
112
113/// Decode an **Annex B length-delimited** AV1 bitstream (r428).
114///
115/// The input is the raw Annex B packing (`temporal_unit_size` /
116/// `frame_unit_size` / `obu_length` `leb128` nesting per Annex B.2 —
117/// no IVF container). Each temporal unit is converted to its §5.2
118/// low-overhead equivalent by [`crate::annexb::split_temporal_units`]
119/// (enforcing the Annex B.3 consistency + temporal-delimiter
120/// placement rules) and decoded through the same
121/// [`SpecDecodeSession`] the IVF path drives — reference state and
122/// CDF carry work identically, so a stream repacked between the two
123/// formats decodes to identical pixels.
124///
125/// ## Errors
126///
127/// * [`Error::AnnexBInvalid`] / [`Error::UnexpectedEnd`] — malformed
128///   Annex B framing (see [`crate::annexb::split_temporal_units`]).
129/// * Any spec-driver parse/decode failure — the driver's typed
130///   [`Error`].
131pub fn decode_av1_annexb(input: &[u8]) -> Result<Vec<Frame>, Error> {
132    let units = crate::annexb::split_temporal_units(input)?;
133    let mut session = SpecDecodeSession::new();
134    let mut out: Vec<Frame> = Vec::new();
135    for unit in &units {
136        out.extend(
137            session
138                .decode_temporal_unit(unit)?
139                .into_iter()
140                .map(Frame::Spec),
141        );
142    }
143    Ok(out)
144}