Skip to main content

videocall_codecs/encoder/
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//! Codec-agnostic video encoder interface.
20//!
21//! The [`Encodable`] trait abstracts over concrete encoder backends so that
22//! consumers can encode I420 frames without depending on a particular codec or
23//! implementation. Two backends implement it today:
24//!
25//! - [`crate::vp9::Vp9Encoder`] — the pure-Rust VP9 encoder (always compiled,
26//!   including on `wasm32`).
27//! - The legacy libvpx-backed `Vp9Encoder` in [`libvpx`], available only with
28//!   the `libvpx` feature on native targets, used as a test oracle and as an
29//!   opt-in encode backend.
30//!
31//! Both are interchangeable behind `Box<dyn Encodable>`.
32
33use anyhow::Result;
34
35// The legacy libvpx encoder is only available on native targets with the
36// `libvpx` feature. `Vp9Encoder` is re-exported under the same cfg so
37// `videocall_codecs::encoder::Vp9Encoder` keeps resolving for the native bins.
38#[cfg(all(feature = "libvpx", not(target_arch = "wasm32")))]
39pub mod libvpx;
40#[cfg(all(feature = "libvpx", not(target_arch = "wasm32")))]
41pub use libvpx::Vp9Encoder;
42
43/// Configuration for a video encoder, independent of the concrete backend.
44///
45/// Backends map these fields onto their native configuration. Fields that a
46/// given backend does not support are ignored.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct EncoderConfig {
49    /// Frame width in pixels. Must be even.
50    pub width: u32,
51    /// Frame height in pixels. Must be even.
52    pub height: u32,
53    /// Nominal frame rate in frames per second (the encoder time base is 1/framerate).
54    pub framerate: u32,
55    /// Target bitrate in kilobits per second.
56    pub bitrate_kbps: u32,
57    /// Maximum distance between keyframes, in frames.
58    pub keyframe_interval: u32,
59    /// Minimum quantizer (0-63); lower means higher quality.
60    pub min_quantizer: u32,
61    /// Maximum quantizer (0-63); higher means lower quality.
62    pub max_quantizer: u32,
63    /// Speed/quality trade-off (0 = slowest/best, 8 = fastest/worst).
64    pub cpu_used: u8,
65}
66
67impl Default for EncoderConfig {
68    fn default() -> Self {
69        Self {
70            width: 640,
71            height: 480,
72            framerate: 30,
73            bitrate_kbps: 500,
74            keyframe_interval: 150,
75            min_quantizer: 40,
76            max_quantizer: 60,
77            cpu_used: 7,
78        }
79    }
80}
81
82/// A single compressed frame produced by an encoder.
83#[derive(Debug, Clone)]
84pub struct EncodedFrame {
85    /// Compressed bitstream for this frame (owned; consumers copy immediately).
86    pub data: Vec<u8>,
87    /// Whether this frame is a keyframe (decodable without references).
88    pub is_keyframe: bool,
89    /// Presentation timestamp in time-base units (as supplied to [`Encodable::encode`]).
90    pub pts: i64,
91}
92
93/// A codec-agnostic video encoder.
94///
95/// Implementations accept planar I420 frames and emit at most one compressed
96/// frame per call. Because some backends buffer frames (e.g. `lag_in_frames`),
97/// [`encode`](Encodable::encode) may return `None` for a call that produced no
98/// output yet; callers must tolerate zero-frame results.
99pub trait Encodable {
100    /// Create a new encoder with the given configuration.
101    fn new(config: EncoderConfig) -> Result<Self>
102    where
103        Self: Sized;
104
105    /// Update the target bitrate at runtime.
106    fn update_bitrate_kbps(&mut self, kbps: u32) -> Result<()>;
107
108    /// Encode one planar I420 frame.
109    ///
110    /// `pts` is the presentation timestamp in time-base units. `i420` is the
111    /// full I420 buffer (`width * height * 3 / 2` bytes). Returns the compressed
112    /// frame if one is ready, or `None` if the encoder buffered it.
113    fn encode(&mut self, pts: i64, i420: &[u8]) -> Result<Option<EncodedFrame>>;
114}
115
116/// Construct an encoder for the given codec.
117///
118/// Currently only [`VideoCodec::Vp9Profile0Level10Bit8`](crate::decoder::VideoCodec::Vp9Profile0Level10Bit8)
119/// is supported, backed by the pure-Rust [`crate::vp9::Vp9Encoder`]. Any other
120/// codec returns an error.
121pub fn create_encoder(
122    codec: crate::decoder::VideoCodec,
123    cfg: EncoderConfig,
124) -> Result<Box<dyn Encodable + Send>> {
125    use crate::decoder::VideoCodec;
126    match codec {
127        VideoCodec::Vp9Profile0Level10Bit8 => Ok(Box::new(crate::vp9::Vp9Encoder::new(cfg)?)),
128        other => Err(anyhow::anyhow!(
129            "no pure-Rust encoder available for codec {other:?}"
130        )),
131    }
132}