Skip to main content

videocall_codecs/vp9/
mod.rs

1/*
2 * Copyright 2025 Security Union LLC
3 *
4 * Licensed under either of
5 *
6 * * Apache License, Version 2.0
7 *   (http://www.apache.org/licenses/LICENSE-2.0)
8 * * MIT license
9 *   (http://opensource.org/licenses/MIT)
10 *
11 * at your option.
12 *
13 * Unless you explicitly state otherwise, any contribution intentionally
14 * submitted for inclusion in the work by you, as defined in the Apache-2.0
15 * license, shall be dual licensed as above, without any additional terms or
16 * conditions.
17 */
18
19//! Pure-Rust VP9 encoder.
20//!
21//! This is a from-scratch, dependency-free VP9 encoder targeting a one-pass,
22//! realtime, error-resilient subset of VP9 Profile 0 (8-bit I420). It is a
23//! drop-in replacement for the C libvpx encoder used elsewhere in the project
24//! and compiles on every target, including `wasm32`.
25//!
26//! The implementation was built up behind an oracle-based TDD harness (encode
27//! with this encoder, decode with libvpx, assert PSNR/correctness); the
28//! milestones live in `tests/oracle_vp9.rs`. It now encodes keyframes and
29//! single-reference integer-pel inter frames, with one-pass VBR rate control and
30//! `cpu_used` speed presets.
31//!
32//! ## Planned module layout
33//!
34//! - `common/` — decoder-mandated, bit-exact machinery ported faithfully from
35//!   libvpx `vp9/common/` and `vpx_dsp/`: the boolean arithmetic coder, default
36//!   probability tables, context derivations, scan orders, dequant tables,
37//!   inverse transforms, intra predictors, motion-compensation filters, and
38//!   header syntax. Errors here silently corrupt the bitstream.
39//! - `enc/` — encoder-free choices that may be simplified aggressively: forward
40//!   transforms, quantizer rounding, mode decision, motion search, rate control,
41//!   and partitioning. Errors here only cost quality.
42//! - `debug/` — a minimal stream parser for round-trip validation plus IVF glue.
43
44use crate::encoder::{Encodable, EncodedFrame, EncoderConfig};
45use crate::vp9::common::frame_buffer::FrameBuffer;
46use crate::vp9::enc::encoder::{encode_inter_frame, encode_keyframe};
47use crate::vp9::enc::ratectrl::RateControl;
48use crate::vp9::enc::speed::SpeedFeatures;
49use anyhow::Result;
50
51pub(crate) mod common;
52#[cfg(any(test, feature = "test-utils"))]
53pub(crate) mod debug;
54pub mod dec;
55pub(crate) mod enc;
56
57/// Multiple of the per-frame keyframe target above which a keyframe is re-encoded
58/// once at a higher quantizer to rein in a gross overshoot.
59const KF_OVERSHOOT_MULT: i64 = 3;
60/// Quantizer step applied to the one keyframe overshoot re-encode.
61const KF_OVERSHOOT_QSTEP: i32 = 8;
62
63/// The pure-Rust VP9 encoder.
64///
65/// Construct via [`Encodable::new`]. The first frame (and every
66/// `keyframe_interval`-th frame, plus any frame after [`Vp9Encoder::force_keyframe`])
67/// is an intra keyframe; the rest are single-reference inter frames with
68/// integer-pel motion compensation. A one-pass VBR [`RateControl`] picks the
69/// per-frame quantizer within the app's `[min_quantizer, max_quantizer]` window;
70/// [`SpeedFeatures`] derived from `cpu_used` tune the motion search.
71pub struct Vp9Encoder {
72    config: EncoderConfig,
73    frame_count: u64,
74    /// Reconstruction of the most recently encoded frame with borders extended:
75    /// the LAST reference for the next inter frame. Also exposed (via the
76    /// cropped export) for the bit-exact oracle drift test.
77    reference: Option<FrameBuffer>,
78    /// One-pass VBR rate controller (target bitrate, qindex window, bit balance).
79    rc: RateControl,
80    /// `cpu_used`-derived speed knobs.
81    sf: SpeedFeatures,
82    /// Set by [`Vp9Encoder::force_keyframe`]; forces the next frame to be a
83    /// keyframe, then clears.
84    force_kf: bool,
85    /// Base qindex chosen for the most recently encoded frame (0 before the
86    /// first). Surfaced for rate-control drift tests via
87    /// [`Vp9Encoder::last_base_qindex`]; unread in non-test builds.
88    #[cfg_attr(not(any(test, feature = "test-utils")), allow(dead_code))]
89    last_qindex: u8,
90}
91
92impl Vp9Encoder {
93    /// The most recently encoded frame's reconstruction as a tight-packed I420
94    /// buffer at the cropped dimensions, or `None` before the first frame.
95    ///
96    /// This is exactly what a conformant decoder must reproduce, so tests can
97    /// assert bit-exactness against the libvpx oracle.
98    pub fn last_reconstruction_i420(&self) -> Option<Vec<u8>> {
99        self.reference.as_ref().map(|fb| fb.export_i420())
100    }
101
102    /// Base qindex the rate controller selected for the most recently encoded
103    /// frame. Exposed for tests that assert the quantizer varies across a
104    /// sequence (bit-exactness must hold under a changing per-frame qindex).
105    #[cfg(any(test, feature = "test-utils"))]
106    pub fn last_base_qindex(&self) -> u8 {
107        self.last_qindex
108    }
109
110    /// Request that the next encoded frame be a keyframe, regardless of the
111    /// keyframe interval. The flag clears after that frame is emitted. Useful
112    /// when the app needs a decodable refresh point (e.g. a new participant
113    /// joins).
114    pub fn force_keyframe(&mut self) {
115        self.force_kf = true;
116    }
117
118    /// Whether the next call to [`Encodable::encode`] will emit a keyframe (the
119    /// first frame, every `keyframe_interval`-th frame, or when forced).
120    fn is_keyframe(&self) -> bool {
121        let interval = self.config.keyframe_interval.max(1) as u64;
122        self.force_kf || self.reference.is_none() || self.frame_count.is_multiple_of(interval)
123    }
124
125    /// Encode `src` at `qindex` as a keyframe, or as an inter frame against the
126    /// current reference when one exists and `is_keyframe` is false.
127    fn encode_at(
128        &self,
129        src: &FrameBuffer,
130        is_keyframe: bool,
131        qindex: u8,
132    ) -> (Vec<u8>, FrameBuffer) {
133        match (is_keyframe, self.reference.as_ref()) {
134            (false, Some(reference)) => encode_inter_frame(src, reference, qindex, &self.sf),
135            _ => encode_keyframe(src, qindex),
136        }
137    }
138}
139
140impl Encodable for Vp9Encoder {
141    fn new(config: EncoderConfig) -> Result<Self> {
142        let rc = RateControl::new(&config);
143        let sf = SpeedFeatures::for_cpu_used(config.cpu_used);
144        Ok(Self {
145            config,
146            frame_count: 0,
147            reference: None,
148            rc,
149            sf,
150            force_kf: false,
151            last_qindex: 0,
152        })
153    }
154
155    fn update_bitrate_kbps(&mut self, kbps: u32) -> Result<()> {
156        self.config.bitrate_kbps = kbps;
157        self.rc.update_bitrate_kbps(kbps);
158        Ok(())
159    }
160
161    fn encode(&mut self, pts: i64, i420: &[u8]) -> Result<Option<EncodedFrame>> {
162        let (w, h) = (self.config.width, self.config.height);
163        let mut src = FrameBuffer::new(w, h);
164        src.import_i420(i420, w, h)
165            .map_err(|e| anyhow::anyhow!("i420 import failed: {e}"))?;
166
167        let is_keyframe = self.is_keyframe();
168        let target_bits = self.rc.frame_target_bits(is_keyframe);
169        let mut qindex = self.rc.select_qindex(is_keyframe, target_bits);
170        let (mut data, mut recon) = self.encode_at(&src, is_keyframe, qindex);
171
172        // Keyframe overshoot guard: a keyframe that blows far past its target is
173        // re-encoded once at a higher quantizer (the two-phase pipeline makes a
174        // one-shot re-encode cheap). Inter frames rely on the balance instead.
175        if is_keyframe {
176            let (_, qmax) = self.rc.qindex_window();
177            let actual_bits = (data.len() as i64) * 8;
178            if actual_bits > KF_OVERSHOOT_MULT * target_bits && (qindex as i32) < qmax {
179                let q2 = ((qindex as i32) + KF_OVERSHOOT_QSTEP).min(qmax) as u8;
180                let (d2, r2) = self.encode_at(&src, true, q2);
181                data = d2;
182                recon = r2;
183                qindex = q2;
184            }
185        }
186
187        let actual_bits = (data.len() as i64) * 8;
188        self.rc
189            .update_after_encode(is_keyframe, qindex, target_bits, actual_bits);
190        self.last_qindex = qindex;
191
192        // The reconstruction becomes the LAST reference; extend its borders so
193        // motion compensation of the next frame can read past the frame edge.
194        recon.extend_borders();
195        self.reference = Some(recon);
196        self.frame_count += 1;
197        self.force_kf = false;
198
199        Ok(Some(EncodedFrame {
200            data,
201            is_keyframe,
202            pts,
203        }))
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    fn cfg(keyframe_interval: u32) -> EncoderConfig {
212        EncoderConfig {
213            width: 64,
214            height: 64,
215            framerate: 30,
216            bitrate_kbps: 500,
217            keyframe_interval,
218            min_quantizer: 40,
219            max_quantizer: 60,
220            cpu_used: 7,
221        }
222    }
223
224    /// A flat mid-gray I420 frame of the config's dimensions.
225    fn gray(w: u32, h: u32) -> Vec<u8> {
226        vec![128u8; (w * h + 2 * (w.div_ceil(2)) * (h.div_ceil(2))) as usize]
227    }
228
229    fn is_kf(enc: &mut Vp9Encoder, pts: i64) -> bool {
230        enc.encode(pts, &gray(64, 64)).unwrap().unwrap().is_keyframe
231    }
232
233    #[test]
234    fn first_frame_is_keyframe_rest_are_inter() {
235        let mut enc = Vp9Encoder::new(cfg(150)).unwrap();
236        assert!(is_kf(&mut enc, 0), "frame 0 must be a keyframe");
237        for t in 1..10 {
238            assert!(!is_kf(&mut enc, t), "frame {t} must be inter");
239        }
240    }
241
242    #[test]
243    fn keyframe_cadence_follows_interval() {
244        let mut enc = Vp9Encoder::new(cfg(5)).unwrap();
245        // Keyframes at 0, 5, 10; inter elsewhere.
246        for t in 0..12i64 {
247            let key = is_kf(&mut enc, t);
248            let expect = t % 5 == 0;
249            assert_eq!(key, expect, "frame {t}: keyframe={key}, expected {expect}");
250        }
251    }
252
253    #[test]
254    fn force_keyframe_forces_next_frame_only() {
255        let mut enc = Vp9Encoder::new(cfg(150)).unwrap();
256        assert!(is_kf(&mut enc, 0)); // frame 0 keyframe
257        assert!(!is_kf(&mut enc, 1)); // frame 1 inter
258        enc.force_keyframe();
259        assert!(is_kf(&mut enc, 2), "forced frame must be a keyframe");
260        assert!(!is_kf(&mut enc, 3), "flag must clear after one frame");
261    }
262
263    #[test]
264    fn qindex_stays_within_configured_window() {
265        // q 40..60 → qindex window [160, 240].
266        let mut enc = Vp9Encoder::new(cfg(150)).unwrap();
267        for t in 0..8i64 {
268            enc.encode(t, &gray(64, 64)).unwrap();
269            let q = enc.last_base_qindex();
270            assert!((160..=240).contains(&q), "qindex {q} outside window");
271        }
272    }
273}