Skip to main content

rusty_vp9/
lib.rs

1//! **rusty_vp9** — an in-house VP9 decoder + encoder, pure Rust with no C/FFI.
2//!
3//! VP9 is a large block-based video codec. This crate carries the whole engine:
4//! the MSB bit reader and the boolean arithmetic decoder ([`BitReader`] /
5//! [`BoolDecoder`]), the **uncompressed frame header** parser (frame type,
6//! profile, colour config, frame size), the full reconstruction pipeline
7//! (compressed header, intra/inter prediction, transforms, loop filter) behind
8//! the stateful [`Vp9Decoder`], and the encoder behind [`Vp9Encoder`].
9//!
10//! The decoder is bit-exact against all 315 official libvpx conformance
11//! vectors; the encoder's output is validated pixel-exact against libvpx and
12//! ffmpeg. The push/pull API mirrors FFmpeg's send/receive convention:
13//! [`Error::Again`] means "feed more input", [`Error::Eof`] means "drained".
14
15use std::collections::VecDeque;
16
17mod error;
18pub use error::{Error, Result};
19
20mod adapt;
21mod bits;
22mod block;
23mod decode;
24#[allow(dead_code, unused_imports)] // Foundation API; consumed from plan Floor 1+.
25mod encode;
26mod geom_tables;
27mod inter;
28mod loopfilter;
29mod mv;
30mod predict;
31mod prob;
32mod prob_tables;
33/// Function-level decode profiler (`VP9_DPROF`). Public so the `video-tests`
34/// analyzer can drive it in-process; a normal decode never touches it.
35pub mod prof;
36mod quant;
37mod scan_tables;
38mod token;
39mod transform;
40pub use bits::{BitReader, BoolDecoder};
41/// The ENCODER's stage profiler (`VP9_PROF2`), re-exported for the `video-tests`
42/// analyzer. The encoder itself stays private; only the read side of the
43/// instrument is public. See [`prof`] for the decoder's.
44pub use encode::prof as encode_prof;
45pub use encode::{ref_hist_take, set_modemap_std, set_snap_pool};
46pub use encode::{EncodedPacket, Vp9Encoder, Vp9EncoderConfig};
47
48pub(crate) const FRAME_MARKER: u32 = 2;
49pub(crate) const SYNC_CODE: u32 = 0x49_8342;
50pub(crate) const CS_RGB: u32 = 7;
51
52/// The parsed VP9 uncompressed frame header (the fields decoded so far).
53#[derive(Debug, Clone, Default, PartialEq, Eq)]
54pub struct FrameHeader {
55    pub profile: u32,
56    pub show_existing_frame: bool,
57    pub frame_to_show: u32,
58    pub key_frame: bool,
59    pub show_frame: bool,
60    pub error_resilient: bool,
61    pub intra_only: bool,
62    pub bit_depth: u32,
63    pub color_space: u32,
64    pub subsampling_x: u32,
65    pub subsampling_y: u32,
66    pub width: u32,
67    pub height: u32,
68    pub lossless: bool,
69    pub base_q_idx: u32,
70    pub delta_q_y_dc: i32,
71    pub delta_q_uv_dc: i32,
72    pub delta_q_uv_ac: i32,
73    pub loop_filter_level: u32,
74    pub loop_filter_sharpness: u32,
75    pub lf_delta_enabled: bool,
76    /// Per-reference-frame loop-filter level deltas (INTRA, LAST, GOLDEN, ALT).
77    pub lf_ref_deltas: [i32; 4],
78    /// Per-mode loop-filter level deltas (ZEROMV-vs-other).
79    pub lf_mode_deltas: [i32; 2],
80    /// Which ref/mode deltas this frame signalled an update for (the rest carry
81    /// over the persisted value — resolved by the stateful decoder).
82    pub lf_ref_delta_updated: [bool; 4],
83    pub lf_mode_delta_updated: [bool; 2],
84    pub tile_cols_log2: u32,
85    pub tile_rows_log2: u32,
86    /// Compressed-header length in bytes (follows the uncompressed header).
87    pub header_size: u32,
88    /// Byte length of the uncompressed header (where the compressed one starts).
89    pub uncompressed_bytes: usize,
90    /// True once the frame is fully sized (key or intra-only).
91    pub sized: bool,
92    // ---- inter-frame fields (ISO/VP9 §6.2) ----
93    /// `reset_frame_context` (0..3) — how much frame-context state to reset.
94    pub reset_frame_context: u32,
95    /// Bitmask of the 8 reference slots refreshed by this frame.
96    pub refresh_frame_flags: u32,
97    /// Slot index in the 8-entry ref map for LAST/GOLDEN/ALTREF.
98    pub ref_frame_idx: [usize; 3],
99    /// Sign bias per reference (LAST/GOLDEN/ALTREF).
100    pub ref_sign_bias: [bool; 3],
101    /// True if MVs use 1/8-pel precision (else 1/4-pel).
102    pub allow_high_precision_mv: bool,
103    /// Interpolation filter: 0..3 fixed, 4 = SWITCHABLE.
104    pub interp_filter: u32,
105    pub refresh_frame_context: bool,
106    pub frame_parallel_decoding_mode: bool,
107    pub frame_context_idx: u32,
108    // ---- segmentation (ISO/VP9 §6.2.11) ----
109    pub seg_enabled: bool,
110    pub seg_update_map: bool,
111    pub seg_update_data: bool,
112    pub seg_temporal_update: bool,
113    pub seg_abs_delta: bool,
114    pub seg_tree_probs: [u8; 7],
115    pub seg_pred_probs: [u8; 3],
116    pub seg_feature_enabled: [[bool; 4]; 8],
117    pub seg_feature_data: [[i32; 4]; 8],
118}
119
120/// Parse the VP9 uncompressed header (ISO/VP9 spec §6.2). `ref_dims` gives the
121/// (width, height) of each of the 8 reference slots, used to resolve an inter
122/// frame's `frame_size_with_refs`; pass zeros when only key/intra frames are
123/// expected (the size then comes from the explicit fields).
124pub fn parse_uncompressed_header(
125    r: &mut BitReader,
126    ref_dims: &[(u32, u32); 8],
127) -> Result<FrameHeader> {
128    let mut h = FrameHeader::default();
129    if r.f(2)? != FRAME_MARKER {
130        return Err(Error::invalid("vp9: bad frame marker"));
131    }
132    let profile_low = r.f1()?;
133    let profile_high = r.f1()?;
134    h.profile = (profile_high << 1) | profile_low;
135    if h.profile == 3 {
136        r.f1()?; // reserved_zero
137    }
138
139    h.show_existing_frame = r.f1()? == 1;
140    if h.show_existing_frame {
141        h.frame_to_show = r.f(3)?;
142        return Ok(h);
143    }
144
145    h.key_frame = r.f1()? == 0;
146    h.show_frame = r.f1()? == 1;
147    h.error_resilient = r.f1()? == 1;
148
149    if h.key_frame {
150        frame_sync_code(r)?;
151        color_config(r, &mut h)?;
152        frame_size(r, &mut h)?;
153        render_size(r)?;
154        h.sized = true;
155    } else {
156        h.intra_only = if !h.show_frame { r.f1()? == 1 } else { false };
157        h.reset_frame_context = if !h.error_resilient { r.f(2)? } else { 0 };
158        if h.intra_only {
159            frame_sync_code(r)?;
160            if h.profile > 0 {
161                color_config(r, &mut h)?;
162            } else {
163                h.bit_depth = 8;
164                h.color_space = 1; // CS_BT_601
165                h.subsampling_x = 1;
166                h.subsampling_y = 1;
167            }
168            h.refresh_frame_flags = r.f(8)?;
169            frame_size(r, &mut h)?;
170            render_size(r)?;
171            h.sized = true;
172        } else {
173            // Inter frame: refresh mask, three references + sign bias, then a
174            // frame size that may be inherited from a reference.
175            h.refresh_frame_flags = r.f(8)?;
176            for i in 0..3 {
177                h.ref_frame_idx[i] = r.f(3)? as usize;
178                h.ref_sign_bias[i] = r.f1()? == 1;
179            }
180            // setup_frame_size_with_refs
181            let mut found = false;
182            for i in 0..3 {
183                if r.f1()? == 1 {
184                    let (w, hh) = ref_dims[h.ref_frame_idx[i]];
185                    h.width = w;
186                    h.height = hh;
187                    found = true;
188                    break;
189                }
190            }
191            if !found {
192                frame_size(r, &mut h)?;
193            }
194            render_size(r)?;
195            h.allow_high_precision_mv = r.f1()? == 1;
196            // read_interp_filter
197            h.interp_filter = if r.f1()? == 1 {
198                4 // SWITCHABLE
199            } else {
200                // literal_to_filter: {EIGHTTAP_SMOOTH(1), EIGHTTAP(0), EIGHTTAP_SHARP(2), BILINEAR(3)}
201                const L2F: [u32; 4] = [1, 0, 2, 3];
202                L2F[r.f(2)? as usize]
203            };
204            // Inter frames inherit color/subsampling from the active sequence;
205            // the decoder fills these from the key frame's color_config.
206            h.sized = true;
207        }
208    }
209
210    if !h.sized {
211        return Ok(h);
212    }
213
214    // ---- common tail (ISO/VP9 §6.2) --------------------------------------
215    if !h.error_resilient {
216        h.refresh_frame_context = r.f1()? == 1;
217        h.frame_parallel_decoding_mode = r.f1()? == 1;
218    } else {
219        h.frame_parallel_decoding_mode = true;
220    }
221    h.frame_context_idx = r.f(2)?;
222    loop_filter_params(r, &mut h)?;
223    quantization_params(r, &mut h)?;
224    segmentation_params(r, &mut h)?;
225    tile_info(r, &mut h)?;
226    h.header_size = r.f(16)?;
227    h.uncompressed_bytes = r.bit_pos().div_ceil(8);
228    Ok(h)
229}
230
231fn loop_filter_params(r: &mut BitReader, h: &mut FrameHeader) -> Result<()> {
232    h.loop_filter_level = r.f(6)?;
233    h.loop_filter_sharpness = r.f(3)?;
234    // Key/intra-only frames reset the deltas to the spec defaults
235    // (`setup_past_independence`) before any signalled update.
236    // Defaults for direct callers; the stateful decoder overrides these with the
237    // persisted values (the deltas carry frame-to-frame; only `update`-flagged
238    // ones change), resetting to these defaults on past-independence frames.
239    h.lf_ref_deltas = [1, 0, -1, -1];
240    h.lf_mode_deltas = [0, 0];
241    h.lf_ref_delta_updated = [false; 4];
242    h.lf_mode_delta_updated = [false; 2];
243    h.lf_delta_enabled = r.f1()? == 1;
244    if h.lf_delta_enabled {
245        // mode_ref_delta_update
246        if r.f1()? == 1 {
247            for i in 0..4 {
248                if r.f1()? == 1 {
249                    h.lf_ref_deltas[i] = r.s(6)?;
250                    h.lf_ref_delta_updated[i] = true;
251                }
252            }
253            for i in 0..2 {
254                if r.f1()? == 1 {
255                    h.lf_mode_deltas[i] = r.s(6)?;
256                    h.lf_mode_delta_updated[i] = true;
257                }
258            }
259        }
260    }
261    Ok(())
262}
263
264fn quantization_params(r: &mut BitReader, h: &mut FrameHeader) -> Result<()> {
265    h.base_q_idx = r.f(8)?;
266    let dc_y = read_delta_q(r)?;
267    let dc_uv = read_delta_q(r)?;
268    let ac_uv = read_delta_q(r)?;
269    h.delta_q_y_dc = dc_y;
270    h.delta_q_uv_dc = dc_uv;
271    h.delta_q_uv_ac = ac_uv;
272    h.lossless = h.base_q_idx == 0 && dc_y == 0 && dc_uv == 0 && ac_uv == 0;
273    Ok(())
274}
275
276fn read_delta_q(r: &mut BitReader) -> Result<i32> {
277    if r.f1()? == 1 {
278        r.s(4)
279    } else {
280        Ok(0)
281    }
282}
283
284fn segmentation_params(r: &mut BitReader, h: &mut FrameHeader) -> Result<()> {
285    h.seg_tree_probs = [255; 7];
286    h.seg_pred_probs = [255; 3];
287    h.seg_enabled = r.f1()? == 1;
288    if !h.seg_enabled {
289        return Ok(());
290    }
291    h.seg_update_map = r.f1()? == 1;
292    if h.seg_update_map {
293        for i in 0..7 {
294            h.seg_tree_probs[i] = if r.f1()? == 1 { r.f(8)? as u8 } else { 255 };
295        }
296        h.seg_temporal_update = r.f1()? == 1;
297        if h.seg_temporal_update {
298            for i in 0..3 {
299                h.seg_pred_probs[i] = if r.f1()? == 1 { r.f(8)? as u8 } else { 255 };
300            }
301        }
302    }
303    // update_data
304    h.seg_update_data = r.f1()? == 1;
305    if h.seg_update_data {
306        h.seg_abs_delta = r.f1()? == 1;
307        const BITS: [u32; 4] = [8, 6, 2, 0];
308        const SIGNED: [bool; 4] = [true, true, false, false];
309        h.seg_feature_enabled = [[false; 4]; 8];
310        h.seg_feature_data = [[0; 4]; 8];
311        for i in 0..8 {
312            for j in 0..4 {
313                let enabled = r.f1()? == 1;
314                h.seg_feature_enabled[i][j] = enabled;
315                if enabled {
316                    let mut data = if BITS[j] > 0 { r.f(BITS[j])? as i32 } else { 0 };
317                    if SIGNED[j] && r.f1()? == 1 {
318                        data = -data;
319                    }
320                    h.seg_feature_data[i][j] = data;
321                }
322            }
323        }
324    }
325    Ok(())
326}
327
328fn tile_info(r: &mut BitReader, h: &mut FrameHeader) -> Result<()> {
329    let mi_cols = (h.width + 7) >> 3;
330    let sb64_cols = (mi_cols + 7) >> 3;
331    // calc_min_log2_tile_cols
332    let mut min_log2 = 0u32;
333    while (64u32 << min_log2) < sb64_cols {
334        min_log2 += 1;
335    }
336    // calc_max_log2_tile_cols
337    let mut max_log2 = 1u32;
338    while (sb64_cols >> max_log2) >= 4 {
339        max_log2 += 1;
340    }
341    max_log2 -= 1;
342
343    h.tile_cols_log2 = min_log2;
344    while h.tile_cols_log2 < max_log2 {
345        if r.f1()? == 1 {
346            h.tile_cols_log2 += 1;
347        } else {
348            break;
349        }
350    }
351    h.tile_rows_log2 = r.f1()?;
352    if h.tile_rows_log2 == 1 {
353        h.tile_rows_log2 += r.f1()?;
354    }
355    Ok(())
356}
357
358fn frame_sync_code(r: &mut BitReader) -> Result<()> {
359    if r.f(24)? != SYNC_CODE {
360        return Err(Error::invalid("vp9: bad frame sync code"));
361    }
362    Ok(())
363}
364
365fn color_config(r: &mut BitReader, h: &mut FrameHeader) -> Result<()> {
366    h.bit_depth = if h.profile >= 2 {
367        if r.f1()? == 1 {
368            12
369        } else {
370            10
371        }
372    } else {
373        8
374    };
375    h.color_space = r.f(3)?;
376    if h.color_space != CS_RGB {
377        r.f1()?; // color_range
378        if h.profile == 1 || h.profile == 3 {
379            h.subsampling_x = r.f1()?;
380            h.subsampling_y = r.f1()?;
381            r.f1()?; // reserved_zero
382        } else {
383            h.subsampling_x = 1;
384            h.subsampling_y = 1;
385        }
386    } else {
387        // sRGB: full range, no chroma subsampling.
388        if h.profile == 1 || h.profile == 3 {
389            r.f1()?; // reserved_zero
390        }
391    }
392    Ok(())
393}
394
395fn frame_size(r: &mut BitReader, h: &mut FrameHeader) -> Result<()> {
396    h.width = r.f(16)? + 1;
397    h.height = r.f(16)? + 1;
398    Ok(())
399}
400
401fn render_size(r: &mut BitReader) -> Result<()> {
402    if r.f1()? == 1 {
403        r.f(16)?; // render_width_minus_1
404        r.f(16)?; // render_height_minus_1
405    }
406    Ok(())
407}
408
409// ---- compressed header (key-frame path), ISO/VP9 §6.3 --------------------
410
411/// Structurally decode the compressed header for a key/intra frame over `data`
412/// (which must be exactly `header_size` bytes), returning the bits the boolean
413/// decoder consumed. A correct structure consumes ~all of `data`.
414pub fn consume_compressed_header(data: &[u8], lossless: bool) -> Result<usize> {
415    let mut b = BoolDecoder::new(data)?;
416    let tx_mode = read_tx_mode(&mut b, lossless);
417    if tx_mode == TX_MODE_SELECT {
418        tx_mode_probs(&mut b);
419    }
420    read_coef_probs(&mut b, tx_mode);
421    read_skip_prob(&mut b);
422    Ok(b.bit_pos())
423}
424
425const TX_MODE_SELECT: u32 = 4;
426
427fn read_tx_mode(b: &mut BoolDecoder, lossless: bool) -> u32 {
428    if lossless {
429        0
430    } else {
431        let mut t = b.literal(2);
432        if t == 3 {
433            t += b.literal(1); // ALLOW_32X32 → TX_MODE_SELECT
434        }
435        t
436    }
437}
438
439fn tx_mode_probs(b: &mut BoolDecoder) {
440    for _ in 0..2 * 1 {
441        diff_update(b); // 8x8
442    }
443    for _ in 0..2 * 2 {
444        diff_update(b); // 16x16
445    }
446    for _ in 0..2 * 3 {
447        diff_update(b); // 32x32
448    }
449}
450
451/// Coefficient-probability updates (libvpx `read_coef_probs`): transform sizes
452/// `TX_4X4..=max_tx` where `max_tx = tx_mode_to_biggest_tx_size[tx_mode]`, each
453/// gated by an update-present bit, then `[plane=2][ref=2][band=6][ctx][node=3]`
454/// with band 0 having 3 contexts and bands 1-5 having 6.
455fn read_coef_probs(b: &mut BoolDecoder, tx_mode: u32) {
456    let max_tx = [0u32, 1, 2, 3, 3][tx_mode as usize];
457    for _tx in 0..=max_tx {
458        if b.read_bool(128) == 1 {
459            for _plane in 0..2 {
460                for _ref in 0..2 {
461                    for band in 0..6 {
462                        let ctxs = if band == 0 { 3 } else { 6 };
463                        for _ctx in 0..ctxs {
464                            for _node in 0..3 {
465                                diff_update(b);
466                            }
467                        }
468                    }
469                }
470            }
471        }
472    }
473}
474
475fn read_skip_prob(b: &mut BoolDecoder) {
476    for _ in 0..3 {
477        diff_update(b);
478    }
479}
480
481/// `diff_update_prob`: a flag at prob 252, then a sub-exponential delta.
482fn diff_update(b: &mut BoolDecoder) {
483    if b.read_bool(252) == 1 {
484        decode_term_subexp(b);
485    }
486}
487
488fn decode_term_subexp(b: &mut BoolDecoder) -> u32 {
489    if b.read_bool(128) == 0 {
490        return b.literal(4);
491    }
492    if b.read_bool(128) == 0 {
493        return b.literal(4) + 16;
494    }
495    if b.read_bool(128) == 0 {
496        return b.literal(5) + 32;
497    }
498    let v = b.literal(7);
499    if v < 65 {
500        return v + 64;
501    }
502    (v << 1) - 1 + b.read_bool(128)
503}
504
505/// The stateful VP9 decoder: superframe splitting, the eight reference-frame
506/// slots, frame-context management and the plane pool. Drive it FFmpeg-style:
507/// [`push`](Vp9Decoder::push) coded packets in, [`next_frame`](Vp9Decoder::next_frame)
508/// decoded pictures out ([`Error::Again`] = feed more, [`Error::Eof`] = drained
509/// after [`flush`](Vp9Decoder::flush)).
510#[derive(Default)]
511pub struct Vp9Decoder {
512    width: u32,
513    height: u32,
514    queue: VecDeque<(Vec<u8>, Option<i64>, bool)>,
515    eof: bool,
516    /// The eight reference-frame slots (`ref_frame_map`).
517    ref_frames: [Option<std::sync::Arc<decode::RefFrame>>; 8],
518    /// Retired reference-frame plane buffers, kept for reuse.
519    ///
520    /// Allocating three fresh planes per frame was measured at 80-82% of all
521    /// per-frame setup — not the zeroing but the PAGE FAULTS, ~144 first-touch
522    /// pages for a CIF frame, every frame. A reference buffer becomes reusable
523    /// the moment the last slot holding it is overwritten, which is exactly what
524    /// libvpx's frame-buffer pool exploits. Two entries is enough to keep the
525    /// steady state fed without holding memory hostage.
526    plane_pool: Vec<[Vec<u16>; 3]>,
527    /// The four saved entropy contexts (`frame_contexts`).
528    frame_contexts: [decode::FrameContext; 4],
529    /// Previous frame's per-mi motion records (for temporal MV prediction).
530    prev_mvs: Option<std::sync::Arc<Vec<mv::MvRef>>>,
531    /// Previous frame's segment map (for temporal segment-id prediction).
532    prev_seg_map: Option<std::sync::Arc<Vec<u8>>>,
533    /// Persistent segmentation features (cleared on key/intra; replaced by an
534    /// `update_data` signal; otherwise carried frame to frame).
535    seg_feature_enabled: [[bool; 4]; 8],
536    seg_feature_data: [[i32; 4]; 8],
537    seg_abs_delta: bool,
538    /// Persistent loop-filter deltas (cleared on past-independence; otherwise
539    /// carried frame to frame, with only `update`-flagged entries changing).
540    lf_ref_deltas: [i32; 4],
541    lf_mode_deltas: [i32; 2],
542    /// Whether the previously decoded frame was a key frame (coef-adapt factor).
543    last_frame_key: bool,
544    last_show_frame: bool,
545    last_intra_only: bool,
546    last_width: u32,
547    last_height: u32,
548    /// Color subsampling from the active key frame; inter frames inherit it.
549    ss_x: u32,
550    ss_y: u32,
551    /// Bit depth from the active key frame's color config; inter frames inherit it.
552    bit_depth: u32,
553}
554
555impl Vp9Decoder {
556    /// A fresh decoder. The bitstream carries all sizing/colour configuration,
557    /// so there is nothing to configure up front.
558    pub fn new() -> Vp9Decoder {
559        Vp9Decoder::default()
560    }
561
562    /// (width, height) of each of the 8 reference slots, for `frame_size_with_refs`.
563    fn ref_dims(&self) -> [(u32, u32); 8] {
564        let mut d = [(0u32, 0u32); 8];
565        for (i, slot) in self.ref_frames.iter().enumerate() {
566            if let Some(rf) = slot {
567                d[i] = (rf.w[0] as u32, rf.h[0] as u32);
568            }
569        }
570        d
571    }
572}
573
574/// Print the stage table when the decoder goes away, so `VP9_DPROF=1 ffmpeg -i
575/// clip.ivf -f null -` is a complete profiling run with no extra tooling. A
576/// no-op unless the profiler is enabled; the `video-tests` analyzer bypasses
577/// this and reads [`prof::snapshot`] directly instead.
578impl Drop for Vp9Decoder {
579    fn drop(&mut self) {
580        prof::dump();
581    }
582}
583
584impl Vp9Decoder {
585    /// Feed one coded packet (with its presentation timestamp, if known).
586    ///
587    /// A VP9 "superframe" packs several coded frames (e.g. a hidden alt-ref
588    /// plus the shown frame) into one packet, with a trailing index. Split it
589    /// so each coded frame is decoded in turn. libvpx outputs exactly one
590    /// frame per packet — the *last* displayable one — so for spatial
591    /// scalability (multiple shown layers in a superframe) only the top layer
592    /// is emitted; the lower layers are decoded as references then suppressed.
593    pub fn push(&mut self, data: &[u8], pts: Option<i64>) -> Result<()> {
594        let frames = split_superframe(data);
595        let last_disp = frames.iter().rposition(|f| peek_displayable(f));
596        for (i, f) in frames.into_iter().enumerate() {
597            self.queue.push_back((f, pts, last_disp == Some(i)));
598        }
599        Ok(())
600    }
601
602    /// Decode and return the next displayable frame. [`Error::Again`] means
603    /// more input is needed (or the just-decoded frame was hidden);
604    /// [`Error::Eof`] means the stream is drained after [`flush`](Vp9Decoder::flush).
605    pub fn next_frame(&mut self) -> Result<DecodedFrame> {
606        let Some((data, pts, display)) = self.queue.pop_front() else {
607            return if self.eof {
608                Err(Error::Eof)
609            } else {
610                Err(Error::Again)
611            };
612        };
613        // Bounds the profiler's residue to real decode work: everything outside
614        // this scope (demux, output writing, the caller's loop) is charged to the
615        // discarded idle sink instead of the reported glue bucket.
616        let _frame = prof::Scope::new(prof::S::Other);
617        let mut r = BitReader::new(&data);
618        let mut h = prof::dprof!(
619            prof::S::Header,
620            parse_uncompressed_header(&mut r, &self.ref_dims())
621        )?;
622
623        // show_existing_frame: re-emit a previously decoded reference, no decode.
624        if h.show_existing_frame {
625            let rf = self.ref_frames[h.frame_to_show as usize]
626                .clone()
627                .ok_or_else(|| Error::invalid("vp9: show_existing_frame on empty slot"))?;
628            if !display {
629                return Err(Error::Again);
630            }
631            return Ok(crop_frame(&rf, self.ss_x, self.ss_y, pts));
632        }
633
634        // Key/intra frames carry their own color config; inter frames inherit it.
635        if h.key_frame || h.intra_only {
636            self.ss_x = h.subsampling_x;
637            self.ss_y = h.subsampling_y;
638            self.bit_depth = h.bit_depth;
639        } else {
640            h.subsampling_x = self.ss_x;
641            h.subsampling_y = self.ss_y;
642            h.bit_depth = self.bit_depth;
643        }
644
645        // Resolve the three active references (LAST/GOLDEN/ALTREF).
646        let mut active: [Option<std::sync::Arc<decode::RefFrame>>; 3] = [None, None, None];
647        if !(h.key_frame || h.intra_only) {
648            for i in 0..3 {
649                active[i] = self.ref_frames[h.ref_frame_idx[i]].clone();
650            }
651        }
652
653        // Frame-context management (`setup_past_independence` + load/save).
654        let reset_all = h.key_frame || h.intra_only || h.error_resilient;
655        if reset_all {
656            for c in &mut self.frame_contexts {
657                *c = decode::FrameContext::defaults();
658            }
659        }
660
661        // Segmentation features persist across frames: cleared on a key/intra
662        // frame, replaced by an `update_data` signal, otherwise carried over.
663        if h.key_frame || h.intra_only {
664            self.seg_feature_enabled = [[false; 4]; 8];
665            self.seg_feature_data = [[0; 4]; 8];
666            self.seg_abs_delta = false;
667        }
668        if h.seg_update_data {
669            self.seg_feature_enabled = h.seg_feature_enabled;
670            self.seg_feature_data = h.seg_feature_data;
671            self.seg_abs_delta = h.seg_abs_delta;
672        }
673        h.seg_feature_enabled = self.seg_feature_enabled;
674        h.seg_feature_data = self.seg_feature_data;
675        h.seg_abs_delta = self.seg_abs_delta;
676
677        // Loop-filter deltas persist the same way: reset to spec defaults on a
678        // past-independence frame, otherwise carry over with only the entries
679        // this frame flagged `update` changing.
680        if reset_all {
681            self.lf_ref_deltas = [1, 0, -1, -1];
682            self.lf_mode_deltas = [0, 0];
683        }
684        for i in 0..4 {
685            if h.lf_ref_delta_updated[i] {
686                self.lf_ref_deltas[i] = h.lf_ref_deltas[i];
687            }
688        }
689        for i in 0..2 {
690            if h.lf_mode_delta_updated[i] {
691                self.lf_mode_deltas[i] = h.lf_mode_deltas[i];
692            }
693        }
694        h.lf_ref_deltas = self.lf_ref_deltas;
695        h.lf_mode_deltas = self.lf_mode_deltas;
696        // Key/intra/error-resilient frames are forced onto context 0.
697        let ctx_idx = if reset_all {
698            0
699        } else {
700            h.frame_context_idx as usize
701        };
702        let pre_fc = self.frame_contexts[ctx_idx].clone();
703
704        // Temporal MV prediction is valid only when the previous frame was a
705        // same-size, shown, non-intra, non-key inter frame.
706        let use_prev_mvs = !h.error_resilient
707            && h.width == self.last_width
708            && h.height == self.last_height
709            && !self.last_intra_only
710            && self.last_show_frame
711            && !self.last_frame_key;
712        let prev_mvs = self.prev_mvs.clone();
713        // The previous segment map feeds temporal segment-id prediction, but
714        // `setup_past_independence` (key / intra-only / error-resilient frames)
715        // clears it — so those frames predict from an all-zero map. It is also
716        // only reusable at the same frame size.
717        let prev_seg_map = if h.key_frame
718            || h.intra_only
719            || h.error_resilient
720            || h.width != self.last_width
721            || h.height != self.last_height
722        {
723            None
724        } else {
725            self.prev_seg_map.clone()
726        };
727
728        // Safety net for untrusted input: the decode body indexes many
729        // bitstream-derived positions, and a malformed stream can drive one out
730        // of range. `decode_frame` borrows immutable state and returns a value
731        // (it never mutates `self`), so a caught panic leaves the decoder
732        // consistent — the frame just fails. The common malformed cases are
733        // already rejected as `Err` upstream; this contains the long tail.
734        let recycled = if pool_disabled() {
735            None
736        } else {
737            self.plane_pool.pop()
738        };
739        let decode_res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
740            decode::decode_frame(
741                &h,
742                &data,
743                &active,
744                &pre_fc,
745                self.last_frame_key,
746                prev_mvs,
747                use_prev_mvs,
748                prev_seg_map,
749                recycled,
750            )
751        }));
752        // Release the decode-time reference handles BEFORE the slot update below.
753        // They are clones of the very frames that update is about to retire, so
754        // holding them keeps every refcount >= 2 and `Arc::into_inner` can never
755        // succeed — which silently made the buffer pool a no-op (measured:
756        // recycled=0 of 30 frames) until this drop was added.
757        drop(active);
758        let (decoded, out_fc, mvs, seg_map) = match decode_res {
759            Ok(inner) => inner?,
760            Err(_) => return Err(Error::invalid("vp9: decode aborted on malformed input")),
761        };
762        if h.refresh_frame_context {
763            self.frame_contexts[ctx_idx] = out_fc;
764        }
765        self.prev_mvs = Some(mvs);
766        // The segment map persists exactly like libvpx's ping-pong buffers: the
767        // swap that promotes this frame's map to the next frame's "previous"
768        // (`vp9_swap_current_and_last_seg_map`) only runs when segmentation is
769        // enabled. A frame with segmentation *disabled* therefore leaves the
770        // last enabled frame's map intact for the next frame's temporal
771        // prediction — overwriting it with this frame's all-zero map (as we did
772        // before) silently wiped the segmentation across a disabled frame and
773        // broke later SEG_LVL_SKIP blocks (the skip-02 conformance vector).
774        // Key / intra / error-resilient frames clear the map instead
775        // (setup_past_independence).
776        if h.seg_enabled {
777            self.prev_seg_map = Some(seg_map);
778        } else if h.key_frame || h.intra_only || h.error_resilient {
779            self.prev_seg_map = None;
780        }
781        self.last_frame_key = h.key_frame;
782        self.last_show_frame = h.show_frame;
783        self.last_intra_only = h.intra_only;
784        self.last_width = h.width;
785        self.last_height = h.height;
786        let _p = prof::Scope::new(prof::S::RefUpdate);
787        let rf = std::sync::Arc::new(decoded);
788
789        // Update the reference slots selected by refresh_frame_flags.
790        let refresh = if h.key_frame {
791            0xFF
792        } else {
793            h.refresh_frame_flags
794        };
795        for i in 0..8 {
796            if refresh & (1 << i) != 0 {
797                // Reclaim the outgoing buffer when this was its last holder.
798                // `into_inner` succeeds only at refcount 1, so a frame still
799                // referenced by another slot (or mid-output) is never touched.
800                if let Some(old) = self.ref_frames[i].replace(rf.clone()) {
801                    if !pool_disabled() && self.plane_pool.len() < 2 {
802                        if let Some(inner) = std::sync::Arc::into_inner(old) {
803                            self.plane_pool.push(inner.planes);
804                        }
805                    }
806                }
807            }
808        }
809        self.width = h.width;
810        self.height = h.height;
811
812        if !h.show_frame || !display {
813            // Decoded but not output: a hidden alt-ref (`!show_frame`), or a
814            // lower spatial layer superseded by a later shown frame in the same
815            // superframe (`!display`). The references are updated either way;
816            // the caller should ask again.
817            return Err(Error::Again);
818        }
819        Ok(crop_frame(&rf, self.ss_x, self.ss_y, pts))
820    }
821
822    /// Signal end of input: once the queue drains, [`next_frame`](Vp9Decoder::next_frame)
823    /// reports [`Error::Eof`] instead of [`Error::Again`].
824    pub fn flush(&mut self) {
825        self.eof = true;
826    }
827}
828
829/// Peek whether a coded frame would be displayed (`show_frame` or
830/// `show_existing_frame`), reading only the leading uncompressed-header bits.
831/// Used to pick the single output frame of a superframe. Any parse shortfall
832/// defaults to `true` so a malformed frame still reaches the decoder's own
833/// error path rather than being silently dropped.
834fn peek_displayable(data: &[u8]) -> bool {
835    let mut r = BitReader::new(data);
836    (|| -> Result<bool> {
837        if r.f(2)? != FRAME_MARKER {
838            return Ok(true);
839        }
840        let pl = r.f1()?;
841        let ph = r.f1()?;
842        if (ph << 1) | pl == 3 {
843            r.f1()?; // reserved_zero (profile 3)
844        }
845        if r.f1()? == 1 {
846            return Ok(true); // show_existing_frame
847        }
848        let _key_frame = r.f1()?;
849        Ok(r.f1()? == 1) // show_frame
850    })()
851    .unwrap_or(true)
852}
853
854/// Split a VP9 superframe packet into its component coded frames
855/// (libvpx `parse_superframe_index`). Returns `[data]` when not a superframe.
856fn split_superframe(data: &[u8]) -> Vec<Vec<u8>> {
857    let n = data.len();
858    if n >= 2 {
859        let marker = data[n - 1];
860        if marker & 0xe0 == 0xc0 {
861            let frames = (marker & 0x7) as usize + 1;
862            let mag = ((marker >> 3) & 0x3) as usize + 1;
863            let index_sz = 2 + mag * frames;
864            if n >= index_sz && data[n - index_sz] == marker {
865                let mut out = Vec::with_capacity(frames);
866                let mut off = 0usize;
867                let mut x = n - index_sz + 1;
868                for _ in 0..frames {
869                    let mut sz = 0usize;
870                    for j in 0..mag {
871                        sz |= (data[x] as usize) << (j * 8);
872                        x += 1;
873                    }
874                    if off + sz <= n {
875                        out.push(data[off..off + sz].to_vec());
876                    }
877                    off += sz;
878                }
879                if !out.is_empty() {
880                    return out;
881                }
882            }
883        }
884    }
885    vec![data.to_vec()]
886}
887
888/// A/B switch for the frame-buffer pool (DEFAULT ON; set to disable).
889fn pool_disabled() -> bool {
890    static E: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
891    *E.get_or_init(|| std::env::var("VP9_NO_PLANE_POOL").is_ok())
892}
893
894/// One decoded picture, cropped to its visible size.
895///
896/// 8-bit planes are emitted as bytes; 10/12-bit planes as little-endian `u16`
897/// pairs (so `strides` are in **bytes**, `2 × width` samples per row). The
898/// chroma layout is described by `subsampling_x`/`subsampling_y` (1,1 = 4:2:0;
899/// 1,0 = 4:2:2; 0,0 = 4:4:4).
900#[derive(Debug, Clone)]
901pub struct DecodedFrame {
902    pub width: u32,
903    pub height: u32,
904    /// 8, 10 or 12 bits per sample.
905    pub bit_depth: u32,
906    pub subsampling_x: u32,
907    pub subsampling_y: u32,
908    /// Y, U, V planes, tightly packed at `strides[p]` bytes per row.
909    pub planes: Vec<Vec<u8>>,
910    /// Bytes per row for each plane.
911    pub strides: Vec<usize>,
912    /// The presentation timestamp given to [`Vp9Decoder::push`], passed through.
913    pub pts: Option<i64>,
914}
915
916/// Crop a decoded reference frame to its visible planes and wrap it as a
917/// [`DecodedFrame`]. 8-bit planes are emitted as bytes; 10/12-bit planes as
918/// little-endian `u16`.
919fn crop_frame(rf: &decode::RefFrame, ss_x: u32, ss_y: u32, pts: Option<i64>) -> DecodedFrame {
920    let hbd = rf.bit_depth > 8;
921    let mut planes: Vec<Vec<u8>> = Vec::with_capacity(3);
922    let mut strides: Vec<usize> = Vec::with_capacity(3);
923    for p in 0..3 {
924        let (stride, w, hh) = (rf.stride[p], rf.w[p], rf.h[p]);
925        let mut v = Vec::with_capacity(w * hh * if hbd { 2 } else { 1 });
926        for y in 0..hh {
927            let row = &rf.planes[p][y * stride..y * stride + w];
928            if hbd {
929                for &px in row {
930                    v.extend_from_slice(&px.to_le_bytes());
931                }
932            } else {
933                v.extend(row.iter().map(|&px| px as u8));
934            }
935        }
936        planes.push(v);
937        strides.push(if hbd { w * 2 } else { w });
938    }
939    DecodedFrame {
940        width: rf.w[0] as u32,
941        height: rf.h[0] as u32,
942        bit_depth: rf.bit_depth,
943        subsampling_x: ss_x,
944        subsampling_y: ss_y,
945        planes,
946        strides,
947        pts,
948    }
949}
950
951#[cfg(test)]
952mod tests {
953    use super::*;
954
955    #[test]
956    fn parses_real_keyframe_header() {
957        let frame = include_bytes!("testdata/keyframe.vp9");
958        let mut r = BitReader::new(frame);
959        let h = parse_uncompressed_header(&mut r, &[(0, 0); 8]).unwrap();
960        assert_eq!(h.profile, 1);
961        assert!(h.key_frame);
962        assert!(h.show_frame);
963        assert_eq!(h.color_space, CS_RGB);
964        assert_eq!(h.bit_depth, 8);
965        assert_eq!((h.width, h.height), (96, 64));
966    }
967
968    /// Decode the real key frame and dump the three planes for bit-exact
969    /// Decode a whole inter sequence through the stateful decoder, dumping each
970    /// shown frame's planes for comparison against FFmpeg (run with `--ignored`).
971    #[test]
972    #[ignore]
973    fn dump_sequence() {
974        let dir = std::env::var("VP9_SEQ_DIR").unwrap();
975        let prefix = std::env::var("VP9_SEQ_PREFIX").unwrap_or_else(|_| "seqfp_f".into());
976        let n: usize = std::env::var("VP9_SEQ_N")
977            .unwrap_or_else(|_| "8".into())
978            .parse()
979            .unwrap();
980        let mut dec = Vp9Decoder::default();
981        for i in 0..n {
982            let data = std::fs::read(format!("{dir}/{prefix}{i}.vp9")).unwrap();
983            dec.push(&data, Some(i as i64)).unwrap();
984        }
985        dec.flush();
986        let mut idx = 0;
987        loop {
988            match dec.next_frame() {
989                Ok(vf) => {
990                    let mut buf = Vec::new();
991                    for pl in &vf.planes {
992                        buf.extend_from_slice(pl);
993                    }
994                    std::fs::write(format!("{dir}/my_seq_f{idx}.raw"), &buf).unwrap();
995                    idx += 1;
996                }
997                Err(Error::Again) => continue,
998                _ => break,
999            }
1000        }
1001        eprintln!("decoded {idx} shown frames");
1002    }
1003
1004    /// comparison against FFmpeg (run with `--ignored`).
1005    #[test]
1006    #[ignore]
1007    fn dump_keyframe_planes() {
1008        let embedded = include_bytes!("testdata/keyframe.vp9").to_vec();
1009        let frame = match std::env::var("VP9_INPUT") {
1010            Ok(p) => std::fs::read(p).unwrap(),
1011            Err(_) => embedded,
1012        };
1013        let mut r = BitReader::new(&frame);
1014        let h = parse_uncompressed_header(&mut r, &[(0, 0); 8]).unwrap();
1015        let (planes, widths, heights) = decode::decode_intra_frame(&h, &frame).unwrap();
1016        let dir = std::env::var("VP9_DUMP_DIR")
1017            .unwrap_or_else(|_| std::env::temp_dir().to_string_lossy().into_owned());
1018        for (i, p) in planes.iter().enumerate() {
1019            std::fs::write(format!("{dir}/my_plane{i}.raw"), p).unwrap();
1020        }
1021        eprintln!(
1022            "dims {:?} {:?} loop_filter_level={} -> {dir}",
1023            widths, heights, h.loop_filter_level
1024        );
1025    }
1026
1027    /// Decode-throughput benchmark. Pre-loads all packets into memory, then times
1028    /// `passes` full decodes of the sequence (no file I/O in the timed region).
1029    /// `VP9_BENCH_DIR`/`VP9_BENCH_N` locate `bench_f{i}.vp9`; optional
1030    /// `VP9_BENCH_REF` (a concatenated i420 reference) enables a correctness check.
1031    #[test]
1032    #[ignore]
1033    fn bench_decode() {
1034        use std::time::Instant;
1035        let dir = std::env::var("VP9_BENCH_DIR").unwrap();
1036        let pre = std::env::var("VP9_BENCH_PREFIX").unwrap_or_else(|_| "bench_f".into());
1037        let n: usize = std::env::var("VP9_BENCH_N").unwrap().parse().unwrap();
1038        let passes: usize = std::env::var("VP9_BENCH_PASSES")
1039            .unwrap_or_else(|_| "5".into())
1040            .parse()
1041            .unwrap();
1042        let packets: Vec<Vec<u8>> = (0..n)
1043            .map(|i| std::fs::read(format!("{dir}/{pre}{i}.vp9")).unwrap())
1044            .collect();
1045
1046        // Optional correctness check + frame geometry from the first pass.
1047        let mut shown = 0usize;
1048        let mut pix = 0u64;
1049        let refdata = std::env::var("VP9_BENCH_REF")
1050            .ok()
1051            .map(|p| std::fs::read(p).unwrap());
1052        {
1053            let mut dec = Vp9Decoder::default();
1054            let mut off = 0usize;
1055            let mut mism = 0usize;
1056            let drain = |dec: &mut Vp9Decoder,
1057                         shown: &mut usize,
1058                         pix: &mut u64,
1059                         off: &mut usize,
1060                         mism: &mut usize| loop {
1061                match dec.next_frame() {
1062                    Ok(vf) => {
1063                        *shown += 1;
1064                        for pl in &vf.planes {
1065                            *pix += pl.len() as u64;
1066                        }
1067                        if let Some(rd) = &refdata {
1068                            let mut buf = Vec::new();
1069                            for pl in &vf.planes {
1070                                buf.extend_from_slice(pl);
1071                            }
1072                            if *off + buf.len() <= rd.len() && rd[*off..*off + buf.len()] != buf[..]
1073                            {
1074                                *mism += 1;
1075                            }
1076                            *off += buf.len();
1077                        }
1078                    }
1079                    Err(Error::Again) => break,
1080                    _ => break,
1081                }
1082            };
1083            for (i, d) in packets.iter().enumerate() {
1084                dec.push(d, Some(i as i64)).unwrap();
1085                drain(&mut dec, &mut shown, &mut pix, &mut off, &mut mism);
1086            }
1087            dec.flush();
1088            drain(&mut dec, &mut shown, &mut pix, &mut off, &mut mism);
1089            if refdata.is_some() {
1090                eprintln!("[bench] correctness: {} mismatched frames / {shown}", mism);
1091            }
1092        }
1093
1094        // Timed region: `passes` full decodes.
1095        let t = Instant::now();
1096        let mut total = 0usize;
1097        for _ in 0..passes {
1098            let mut dec = Vp9Decoder::default();
1099            for (i, d) in packets.iter().enumerate() {
1100                dec.push(d, Some(i as i64)).unwrap();
1101                while dec.next_frame().is_ok() {
1102                    total += 1;
1103                }
1104            }
1105            dec.flush();
1106            while dec.next_frame().is_ok() {
1107                total += 1;
1108            }
1109        }
1110        let el = t.elapsed();
1111        let fps = total as f64 / el.as_secs_f64();
1112        let mpix = (pix as f64 * 2.0 / 3.0) * passes as f64 / 1e6 / el.as_secs_f64();
1113        eprintln!(
1114            "[bench] {total} frames in {:.3}s over {passes} passes -> {fps:.1} fps, {mpix:.1} Mpix/s ({shown} frames/pass)",
1115            el.as_secs_f64()
1116        );
1117    }
1118
1119    /// Throughput scaling across N independent decoder instances (the natural
1120    /// unit of parallelism for a decoder: a single stream is serially
1121    /// dependent, but separate streams share no mutable state). `VP9_BENCH_T`
1122    /// threads each decode the sequence `VP9_BENCH_PASSES` times concurrently.
1123    #[test]
1124    #[ignore]
1125    fn bench_decode_parallel() {
1126        use std::time::Instant;
1127        let dir = std::env::var("VP9_BENCH_DIR").unwrap();
1128        let n: usize = std::env::var("VP9_BENCH_N").unwrap().parse().unwrap();
1129        let passes: usize = std::env::var("VP9_BENCH_PASSES")
1130            .unwrap_or_else(|_| "8".into())
1131            .parse()
1132            .unwrap();
1133        let threads: usize = std::env::var("VP9_BENCH_T")
1134            .unwrap_or_else(|_| "1".into())
1135            .parse()
1136            .unwrap();
1137        let packets: Vec<Vec<u8>> = (0..n)
1138            .map(|i| std::fs::read(format!("{dir}/bench_f{i}.vp9")).unwrap())
1139            .collect();
1140        let decode_all = |packets: &[Vec<u8>]| -> usize {
1141            let mut total = 0;
1142            for _ in 0..passes {
1143                let mut dec = Vp9Decoder::default();
1144                for (i, d) in packets.iter().enumerate() {
1145                    dec.push(d, Some(i as i64)).unwrap();
1146                    while dec.next_frame().is_ok() {
1147                        total += 1;
1148                    }
1149                }
1150                dec.flush();
1151                while dec.next_frame().is_ok() {
1152                    total += 1;
1153                }
1154            }
1155            total
1156        };
1157        let t = Instant::now();
1158        let total: usize = std::thread::scope(|s| {
1159            let handles: Vec<_> = (0..threads)
1160                .map(|_| s.spawn(|| decode_all(&packets)))
1161                .collect();
1162            handles.into_iter().map(|h| h.join().unwrap()).sum()
1163        });
1164        let el = t.elapsed();
1165        eprintln!(
1166            "[bench-par] threads={threads} {total} frames in {:.3}s -> {:.0} fps aggregate ({:.0} fps/thread)",
1167            el.as_secs_f64(), total as f64 / el.as_secs_f64(), total as f64 / el.as_secs_f64() / threads as f64
1168        );
1169    }
1170
1171    #[test]
1172    fn rejects_bad_marker() {
1173        let mut r = BitReader::new(&[0x00, 0x00]);
1174        assert!(parse_uncompressed_header(&mut r, &[(0, 0); 8]).is_err());
1175    }
1176
1177    /// Full-header verification on a real 1771-byte libvpx keyframe: the
1178    /// uncompressed header parses to a sane `header_size`, and structurally
1179    /// decoding the compressed header consumes ~exactly that many bytes — which
1180    /// only holds if the whole header + probability-update structure is correct.
1181    #[test]
1182    fn full_header_consumes_compressed_header_exactly() {
1183        let frame = include_bytes!("testdata/keyframe.vp9");
1184        let mut r = BitReader::new(frame);
1185        let h = parse_uncompressed_header(&mut r, &[(0, 0); 8]).unwrap();
1186        assert!(h.sized && h.key_frame);
1187        assert_eq!((h.width, h.height), (96, 64));
1188        assert!(h.base_q_idx > 0); // not lossless
1189        assert!(h.header_size > 0 && (h.header_size as usize) < frame.len());
1190
1191        let start = h.uncompressed_bytes;
1192        let end = start + h.header_size as usize;
1193        let consumed = consume_compressed_header(&frame[start..end], h.lossless).unwrap();
1194        // The structure lands at the compressed-header end within the boolean
1195        // decoder's renorm look-ahead (~1 byte). The *exact* consume was
1196        // confirmed against three real libvpx frames (114/106/103 bytes).
1197        let hs = h.header_size as usize;
1198        assert!(
1199            consumed.div_ceil(8).abs_diff(hs) <= 1,
1200            "consumed {} bytes vs header_size {hs}",
1201            consumed.div_ceil(8),
1202        );
1203    }
1204}