Skip to main content

oximedia_codec/
bitrate_model.rs

1//! Bitrate modelling and estimation for video encoding.
2//!
3//! Provides rate-control mode descriptors, target bitrate helpers, and a simple
4//! estimator that predicts the required bitrate for a given resolution and
5//! quality setting.
6
7#![allow(dead_code)]
8#![allow(clippy::cast_precision_loss)]
9
10/// Rate-control mode for a video encoder.
11///
12/// Note: a `BitrateMode` type already exists in the `traits` module; this enum
13/// is a standalone model-level counterpart with richer semantics.
14#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum RcMode {
16    /// Constant Bitrate — output bitrate is held at a fixed target.
17    Cbr,
18    /// Variable Bitrate — bitrate varies within min/max bounds.
19    Vbr,
20    /// Constant Rate Factor — quality-based encoding (e.g. x264 CRF).
21    Crf,
22    /// Constant Quantisation Parameter — fixed QP per frame.
23    Qp,
24}
25
26impl RcMode {
27    /// Returns `true` for quality-based modes (CRF, QP) where the encoder
28    /// controls quality rather than a hard bitrate target.
29    pub fn is_quality_based(self) -> bool {
30        matches!(self, RcMode::Crf | RcMode::Qp)
31    }
32
33    /// Returns `true` for throughput-constrained modes (CBR, VBR).
34    pub fn is_bitrate_constrained(self) -> bool {
35        !self.is_quality_based()
36    }
37
38    /// A short string label for the mode.
39    pub fn label(self) -> &'static str {
40        match self {
41            RcMode::Cbr => "CBR",
42            RcMode::Vbr => "VBR",
43            RcMode::Crf => "CRF",
44            RcMode::Qp => "QP",
45        }
46    }
47}
48
49/// Describes the desired bitrate (or quality) target for an encoder.
50#[derive(Debug, Clone, Copy, PartialEq)]
51pub struct BitrateTarget {
52    /// Rate-control mode.
53    pub mode: RcMode,
54    /// Target bitrate in kbps (for CBR/VBR modes).
55    pub target_kbps: u32,
56    /// Minimum bitrate in kbps (VBR lower bound; 0 = no limit).
57    pub min_kbps: u32,
58    /// Maximum bitrate in kbps (VBR upper bound; 0 = no limit).
59    pub max_kbps: u32,
60    /// Quality parameter: CRF value or QP index (ignored for CBR/VBR).
61    pub quality_param: f32,
62}
63
64impl BitrateTarget {
65    /// Create a CBR target at `kbps` kilobits per second.
66    pub fn cbr(kbps: u32) -> Self {
67        Self {
68            mode: RcMode::Cbr,
69            target_kbps: kbps,
70            min_kbps: kbps,
71            max_kbps: kbps,
72            quality_param: 0.0,
73        }
74    }
75
76    /// Create a VBR target with min/max bounds.
77    pub fn vbr(target_kbps: u32, min_kbps: u32, max_kbps: u32) -> Self {
78        Self {
79            mode: RcMode::Vbr,
80            target_kbps,
81            min_kbps,
82            max_kbps,
83            quality_param: 0.0,
84        }
85    }
86
87    /// Create a CRF target with a given CRF value (lower = higher quality).
88    pub fn crf(crf: f32) -> Self {
89        Self {
90            mode: RcMode::Crf,
91            target_kbps: 0,
92            min_kbps: 0,
93            max_kbps: 0,
94            quality_param: crf,
95        }
96    }
97
98    /// Returns the effective bitrate in kbps.
99    ///
100    /// For quality-based modes, returns 0 (no hard bitrate target).
101    pub fn effective_kbps(&self) -> u32 {
102        if self.mode.is_quality_based() {
103            0
104        } else {
105            self.target_kbps
106        }
107    }
108
109    /// Returns `true` when a hard bitrate budget is set.
110    pub fn has_bitrate_budget(&self) -> bool {
111        self.effective_kbps() > 0
112    }
113}
114
115/// Estimates a reasonable bitrate target for a given resolution and frame rate.
116///
117/// The model is intentionally simple and suitable for presets/defaults — it uses
118/// empirically derived pixel-rate coefficients similar to those used by streaming
119/// platforms for H.264/AV1 recommendations.
120#[derive(Debug, Clone)]
121pub struct BitrateModelEstimator {
122    /// Coefficient: bits per pixel per second (tunable).
123    bpp_coefficient: f64,
124}
125
126impl BitrateModelEstimator {
127    /// Create an estimator with default BPP coefficient (0.07).
128    pub fn new() -> Self {
129        Self {
130            bpp_coefficient: 0.07,
131        }
132    }
133
134    /// Create an estimator with a custom BPP coefficient.
135    pub fn with_coefficient(bpp_coefficient: f64) -> Self {
136        Self { bpp_coefficient }
137    }
138
139    /// Estimate the target bitrate in kbps for the given width × height and fps.
140    ///
141    /// Formula: `width * height * fps * bpp_coefficient / 1000`
142    #[allow(clippy::cast_precision_loss)]
143    pub fn estimate_for_resolution(&self, width: u32, height: u32, fps: f64) -> u32 {
144        let pixels = width as f64 * height as f64;
145        let bits_per_sec = pixels * fps * self.bpp_coefficient;
146        (bits_per_sec / 1000.0).round() as u32
147    }
148
149    /// Estimate for a named preset (720p30, 1080p30, 4K30, etc.).
150    pub fn estimate_for_preset(&self, preset: &str) -> Option<u32> {
151        match preset {
152            "720p30" => Some(self.estimate_for_resolution(1280, 720, 30.0)),
153            "720p60" => Some(self.estimate_for_resolution(1280, 720, 60.0)),
154            "1080p30" => Some(self.estimate_for_resolution(1920, 1080, 30.0)),
155            "1080p60" => Some(self.estimate_for_resolution(1920, 1080, 60.0)),
156            "4k30" => Some(self.estimate_for_resolution(3840, 2160, 30.0)),
157            "4k60" => Some(self.estimate_for_resolution(3840, 2160, 60.0)),
158            _ => None,
159        }
160    }
161}
162
163impl Default for BitrateModelEstimator {
164    fn default() -> Self {
165        Self::new()
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn test_rc_mode_cbr_not_quality_based() {
175        assert!(!RcMode::Cbr.is_quality_based());
176    }
177
178    #[test]
179    fn test_rc_mode_crf_is_quality_based() {
180        assert!(RcMode::Crf.is_quality_based());
181    }
182
183    #[test]
184    fn test_rc_mode_qp_is_quality_based() {
185        assert!(RcMode::Qp.is_quality_based());
186    }
187
188    #[test]
189    fn test_rc_mode_vbr_is_bitrate_constrained() {
190        assert!(RcMode::Vbr.is_bitrate_constrained());
191    }
192
193    #[test]
194    fn test_rc_mode_labels() {
195        assert_eq!(RcMode::Cbr.label(), "CBR");
196        assert_eq!(RcMode::Crf.label(), "CRF");
197        assert_eq!(RcMode::Qp.label(), "QP");
198    }
199
200    #[test]
201    fn test_bitrate_target_cbr_effective_kbps() {
202        let t = BitrateTarget::cbr(5000);
203        assert_eq!(t.effective_kbps(), 5000);
204        assert!(t.has_bitrate_budget());
205    }
206
207    #[test]
208    fn test_bitrate_target_crf_effective_kbps_is_zero() {
209        let t = BitrateTarget::crf(23.0);
210        assert_eq!(t.effective_kbps(), 0);
211        assert!(!t.has_bitrate_budget());
212    }
213
214    #[test]
215    fn test_bitrate_target_vbr_bounds() {
216        let t = BitrateTarget::vbr(4000, 1000, 8000);
217        assert_eq!(t.min_kbps, 1000);
218        assert_eq!(t.max_kbps, 8000);
219    }
220
221    #[test]
222    fn test_estimator_1080p30_reasonable() {
223        let est = BitrateModelEstimator::new();
224        let kbps = est.estimate_for_resolution(1920, 1080, 30.0);
225        // 1920*1080*30*0.07/1000 ≈ 4354 kbps
226        assert!(kbps > 3000 && kbps < 6000, "Unexpected kbps: {kbps}");
227    }
228
229    #[test]
230    fn test_estimator_4k_higher_than_1080p() {
231        let est = BitrateModelEstimator::new();
232        let kbps_1080 = est.estimate_for_resolution(1920, 1080, 30.0);
233        let kbps_4k = est.estimate_for_resolution(3840, 2160, 30.0);
234        assert!(kbps_4k > kbps_1080);
235    }
236
237    #[test]
238    fn test_estimator_preset_720p30() {
239        let est = BitrateModelEstimator::new();
240        let kbps = est.estimate_for_preset("720p30").expect("should succeed");
241        let manual = est.estimate_for_resolution(1280, 720, 30.0);
242        assert_eq!(kbps, manual);
243    }
244
245    #[test]
246    fn test_estimator_preset_unknown_returns_none() {
247        let est = BitrateModelEstimator::new();
248        assert!(est.estimate_for_preset("8k120").is_none());
249    }
250
251    #[test]
252    fn test_estimator_custom_coefficient() {
253        let est = BitrateModelEstimator::with_coefficient(0.14);
254        let kbps_default = BitrateModelEstimator::new().estimate_for_resolution(1920, 1080, 30.0);
255        let kbps_custom = est.estimate_for_resolution(1920, 1080, 30.0);
256        // Double coefficient → approximately double bitrate
257        assert!(kbps_custom > kbps_default);
258    }
259
260    #[test]
261    fn test_estimator_default_impl() {
262        let _est: BitrateModelEstimator = Default::default();
263    }
264}