Skip to main content

rff_codec_av2/
lib.rs

1//! In-house **AV2 decoder**, backed by the pure-Rust
2//! [`rusty_av2d`] engine — no C, no FFI.
3//!
4//! This crate is the thin `rff` adapter: [`register`] wires `rusty_av2d` into a
5//! [`CodecRegistry`], and a private wrapper translates between the rff
6//! `Decoder` trait and `rusty_av2d`'s push/pull API (`Packet`/`Frame` ↔
7//! byte buffers/planes, and the `Again` control-flow error one-to-one). All
8//! codec logic — the decoder that is byte-identical to AOM's `avmdec` across a
9//! 45-clip conformance corpus — lives in `rusty_av2d`.
10//!
11//! **Decode only.** There is no AV2 encoder; [`Codec::encoder`] is `None`.
12//!
13//! AV2 is not a finalized standard: correctness is defined against the AVM
14//! reference as of the pinned `rusty_av2d` release, and the bitstream may still
15//! change. See that crate's `STATUS.md` for the full scope statement.
16
17use rff_codec::{Codec, CodecParams, CodecRegistry, Decoder};
18use rff_core::{CodecId, Error, Frame, MediaType, Packet, PixelFormat, Result, VideoFrame};
19use rusty_av2d::{PixelLayout, PlanarImageComponent, Rav1dError};
20
21// The full engine, for callers that want the native API directly.
22pub use rusty_av2d;
23
24/// Register the AV2 decoder into a [`CodecRegistry`].
25pub fn register(registry: &mut CodecRegistry) {
26    registry.register(Codec {
27        id: CodecId::Av2,
28        name: "av2",
29        long_name: "AOMedia Video 2",
30        media_type: MediaType::Video,
31        decoder: Some(|| Box::new(Av2Decoder::new())),
32        encoder: None,
33    });
34}
35
36/// Map a `rusty_av2d` error onto the rff error space. `TryAgain` is control
37/// flow (FFmpeg's `EAGAIN` convention) and must map exactly — the send/receive
38/// loops key on it.
39fn map_err(e: Rav1dError) -> Error {
40    match e {
41        Rav1dError::TryAgain => Error::Again,
42        Rav1dError::InvalidArgument => Error::InvalidData("av2: invalid bitstream".into()),
43        Rav1dError::UnsupportedBitstream => {
44            Error::Unsupported("av2: unsupported bitstream feature".into())
45        }
46        other => Error::InvalidData(format!("av2: {other:?}")),
47    }
48}
49
50// ---------------------------------------------------------------------------
51// Decoder
52// ---------------------------------------------------------------------------
53
54struct Av2Decoder {
55    /// `None` until the first packet: constructing the engine can fail, and
56    /// [`Decoder`] construction in the registry is infallible.
57    inner: Option<rusty_av2d::Decoder>,
58    /// Set when the engine accepted only part of a packet. The engine asserts
59    /// if `send_data` is called again while data is still pending, so the
60    /// remainder must be drained through `send_pending_data` first.
61    pending: bool,
62    /// Set by [`Decoder::flush`]; makes `receive_frame` report `Eof` rather
63    /// than `Again` once the engine has no more buffered pictures.
64    draining: bool,
65}
66
67impl Av2Decoder {
68    fn new() -> Self {
69        Self {
70            inner: None,
71            pending: false,
72            draining: false,
73        }
74    }
75
76    fn engine(&mut self) -> Result<&mut rusty_av2d::Decoder> {
77        if self.inner.is_none() {
78            self.inner = Some(rusty_av2d::Decoder::new().map_err(map_err)?);
79        }
80        Ok(self.inner.as_mut().expect("just initialized"))
81    }
82}
83
84impl Decoder for Av2Decoder {
85    fn configure(&mut self, _params: &CodecParams) -> Result<()> {
86        // The AV2 bitstream carries its own size / bit-depth / colour
87        // configuration in the sequence header; container hints are not needed.
88        Ok(())
89    }
90
91    fn send_packet(&mut self, packet: &Packet) -> Result<()> {
92        let pts = packet.pts;
93        let data = packet.data.clone().into_boxed_slice();
94
95        // Finish any partially-consumed previous packet before offering a new
96        // one — `send_data` asserts (panics) if data is still pending.
97        if self.pending {
98            let r = self.engine()?.send_pending_data();
99            match r {
100                Ok(()) => self.pending = false,
101                Err(Rav1dError::TryAgain) => return Err(Error::Again),
102                Err(e) => {
103                    self.pending = false;
104                    return Err(map_err(e));
105                }
106            }
107        }
108
109        let r = self.engine()?.send_data(data, None, pts, None);
110        match r {
111            Ok(()) => Ok(()),
112            Err(Rav1dError::TryAgain) => {
113                // Partially accepted: the engine holds the remainder. Pull
114                // frames out, then re-offer via the branch above.
115                self.pending = true;
116                Err(Error::Again)
117            }
118            Err(e) => Err(map_err(e)),
119        }
120    }
121
122    fn receive_frame(&mut self) -> Result<Frame> {
123        let draining = self.draining;
124        let Some(engine) = self.inner.as_mut() else {
125            // Nothing was ever sent.
126            return Err(if draining { Error::Eof } else { Error::Again });
127        };
128        match engine.get_picture() {
129            Ok(pic) => picture_to_frame(&pic),
130            // No picture ready: more input needed, unless we're draining, in
131            // which case the stream is done.
132            Err(Rav1dError::TryAgain) if draining => Err(Error::Eof),
133            Err(e) => Err(map_err(e)),
134        }
135    }
136
137    fn flush(&mut self) {
138        self.draining = true;
139    }
140}
141
142/// Map a `rusty_av2d` [`Picture`](rusty_av2d::Picture) onto an rff
143/// [`VideoFrame`].
144///
145/// The engine's planes are stride-padded for alignment; rff frames carry an
146/// explicit stride, so rows are passed through verbatim rather than repacked.
147fn picture_to_frame(pic: &rusty_av2d::Picture) -> Result<Frame> {
148    let layout = pic.pixel_layout();
149    let format = match (layout, pic.bit_depth()) {
150        (PixelLayout::I420, 8) => PixelFormat::Yuv420p,
151        (PixelLayout::I422, 8) => PixelFormat::Yuv422p,
152        (PixelLayout::I444, 8) => PixelFormat::Yuv444p,
153        (PixelLayout::I420, 10) => PixelFormat::Yuv420p10,
154        (PixelLayout::I422, 10) => PixelFormat::Yuv422p10,
155        (PixelLayout::I444, 10) => PixelFormat::Yuv444p10,
156        (PixelLayout::I420, 12) => PixelFormat::Yuv420p12,
157        (PixelLayout::I422, 12) => PixelFormat::Yuv422p12,
158        (PixelLayout::I444, 12) => PixelFormat::Yuv444p12,
159        // Monochrome has no rff pixel format, and no clip in the conformance
160        // corpus exercises it — refuse rather than fabricate neutral chroma.
161        (PixelLayout::I400, _) => {
162            return Err(Error::Unsupported("av2: monochrome (4:0:0) output".into()))
163        }
164        (_, depth) => {
165            return Err(Error::Unsupported(format!(
166                "av2: {depth}-bit output is not mapped to a pixel format"
167            )))
168        }
169    };
170
171    let mut planes = Vec::with_capacity(3);
172    let mut strides = Vec::with_capacity(3);
173    for c in [
174        PlanarImageComponent::Y,
175        PlanarImageComponent::U,
176        PlanarImageComponent::V,
177    ] {
178        planes.push(pic.plane(c).to_vec());
179        strides.push(pic.stride(c) as usize);
180    }
181
182    Ok(Frame::Video(VideoFrame {
183        width: pic.width(),
184        height: pic.height(),
185        format,
186        planes,
187        strides,
188        pts: pic.timestamp(),
189    }))
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195
196    #[test]
197    fn registers_a_decode_only_av2_codec() {
198        let mut reg = CodecRegistry::new();
199        register(&mut reg);
200        let codec = reg.by_id(CodecId::Av2).expect("av2 registered");
201        assert_eq!(codec.name, "av2");
202        assert_eq!(codec.media_type, MediaType::Video);
203        assert!(codec.decoder.is_some());
204        assert!(
205            codec.encoder.is_none(),
206            "there is no AV2 encoder; the registry must not advertise one"
207        );
208    }
209
210    #[test]
211    fn try_again_maps_to_the_control_flow_error() {
212        // The send/receive loops key on `Again`; if this ever maps to a plain
213        // decode error the pipeline deadlocks instead of pulling frames.
214        assert!(matches!(map_err(Rav1dError::TryAgain), Error::Again));
215    }
216
217    #[test]
218    fn garbage_input_errors_rather_than_panicking() {
219        let mut dec = Av2Decoder::new();
220        let packet = Packet {
221            data: vec![0xde, 0xad, 0xbe, 0xef, 0x00, 0x11, 0x22, 0x33],
222            ..Default::default()
223        };
224        // Either outcome is fine — what must not happen is a panic.
225        let _ = dec.send_packet(&packet);
226        let _ = dec.receive_frame();
227    }
228}
229
230#[cfg(test)]
231mod fuzz_smoke {
232    use super::*;
233    use rff_codec::Decoder as _;
234
235    struct Rng(u64);
236    impl Rng {
237        fn next(&mut self) -> u64 {
238            self.0 ^= self.0 << 13;
239            self.0 ^= self.0 >> 7;
240            self.0 ^= self.0 << 17;
241            self.0
242        }
243        fn byte(&mut self) -> u8 {
244            (self.next() >> 33) as u8
245        }
246        fn range(&mut self, n: usize) -> usize {
247            (self.next() >> 33) as usize % n.max(1)
248        }
249    }
250
251    /// Random bytes straight into `send_packet` must never panic: the pipeline
252    /// hands the decoder whatever the container yielded, and a container can be
253    /// lying. The workspace-wide `fuzz_decode` harness caught a real underflow
254    /// here (a corrupt tile-size prefix); this keeps a fast check close to the
255    /// adapter, including the long buffers that exercise size fields.
256    #[test]
257    fn random_bytes_never_panic() {
258        let mut rng = Rng(0xFACE_B00C_1234_5678);
259        for i in 0..1500 {
260            let len = if i % 10 == 0 {
261                rng.range(65536)
262            } else {
263                rng.range(4096)
264            } + 1;
265            let data: Vec<u8> = (0..len).map(|_| rng.byte()).collect();
266            let mut dec = Av2Decoder::new();
267            let pkt = Packet {
268                data,
269                ..Default::default()
270            };
271            let _ = dec.send_packet(&pkt);
272            for _ in 0..8 {
273                if dec.receive_frame().is_err() {
274                    break;
275                }
276            }
277            dec.flush();
278            let _ = dec.receive_frame();
279        }
280    }
281}