Skip to main content

oximedia_transcode/
bitrate_control.rs

1//! Bitrate control and rate estimation for transcoding pipelines.
2//!
3//! Provides rate-control mode selection, target bitrate specification,
4//! and a rolling estimator for measuring live encoding throughput.
5
6#![allow(dead_code)]
7#![allow(clippy::cast_precision_loss)]
8
9/// Rate-control algorithm used during encoding.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum RateControlMode {
12    /// Maintain a fixed output bitrate.
13    ConstantBitrate,
14    /// Allow bitrate to vary within min/peak bounds.
15    VariableBitrate,
16    /// Use a constant rate factor (quality-based).
17    ConstantRateFactor,
18    /// Encode to a constant perceptual quality level.
19    ConstantQuality,
20}
21
22impl RateControlMode {
23    /// Returns `true` for quality-based modes (`ConstantRateFactor`, `ConstantQuality`).
24    #[must_use]
25    pub fn is_quality_based(&self) -> bool {
26        matches!(self, Self::ConstantRateFactor | Self::ConstantQuality)
27    }
28
29    /// Returns a short human-readable description of the mode.
30    #[must_use]
31    pub fn description(&self) -> &str {
32        match self {
33            Self::ConstantBitrate => "CBR – constant bitrate",
34            Self::VariableBitrate => "VBR – variable bitrate",
35            Self::ConstantRateFactor => "CRF – constant rate factor",
36            Self::ConstantQuality => "CQ – constant quality",
37        }
38    }
39}
40
41/// Target bitrate specification with peak, average, and minimum limits.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct TargetBitrate {
44    /// Peak bitrate in kbps.
45    pub peak_kbps: u32,
46    /// Average bitrate in kbps.
47    pub avg_kbps: u32,
48    /// Minimum bitrate in kbps (may be 0 for unconstrained).
49    pub min_kbps: u32,
50}
51
52impl TargetBitrate {
53    /// Creates a VBR `TargetBitrate` with a peak and average; `min_kbps` is set
54    /// to half of `avg_kbps`.
55    #[must_use]
56    pub fn with_peak(peak: u32, avg: u32) -> Self {
57        Self {
58            peak_kbps: peak,
59            avg_kbps: avg,
60            min_kbps: avg / 2,
61        }
62    }
63
64    /// Creates a CBR `TargetBitrate` where peak, average, and minimum are all
65    /// equal to `kbps`.
66    #[must_use]
67    pub fn cbr(kbps: u32) -> Self {
68        Self {
69            peak_kbps: kbps,
70            avg_kbps: kbps,
71            min_kbps: kbps,
72        }
73    }
74
75    /// Returns the ratio of peak to average bitrate.
76    ///
77    /// Returns `1.0` if `avg_kbps` is zero.
78    #[must_use]
79    pub fn peak_to_avg_ratio(&self) -> f32 {
80        if self.avg_kbps == 0 {
81            return 1.0;
82        }
83        self.peak_kbps as f32 / self.avg_kbps as f32
84    }
85
86    /// Returns `true` if peak and average differ (VBR behaviour).
87    #[must_use]
88    pub fn is_vbr(&self) -> bool {
89        self.peak_kbps != self.avg_kbps
90    }
91}
92
93/// Tracks per-frame sizes and derives a rolling bitrate estimate.
94#[derive(Debug, Clone)]
95pub struct BitrateEstimator {
96    /// Rate-control mode in use.
97    pub mode: RateControlMode,
98    /// History of frame sizes in bytes (most recent last).
99    pub history: Vec<u32>,
100}
101
102impl BitrateEstimator {
103    /// Creates a new estimator for the given mode.
104    #[must_use]
105    pub fn new(mode: RateControlMode) -> Self {
106        Self {
107            mode,
108            history: Vec::new(),
109        }
110    }
111
112    /// Appends a frame's encoded size (in bytes) to the history.
113    pub fn add_frame_size_bytes(&mut self, size: u32) {
114        self.history.push(size);
115    }
116
117    /// Estimates the current bitrate in kbps given the encoding frame rate.
118    ///
119    /// Returns `0.0` if no frames have been recorded or `fps` is zero/negative.
120    #[must_use]
121    pub fn current_kbps(&self, fps: f32) -> f32 {
122        if self.history.is_empty() || fps <= 0.0 {
123            return 0.0;
124        }
125        let avg_bytes: f32 =
126            self.history.iter().map(|&b| b as f32).sum::<f32>() / self.history.len() as f32;
127        // bytes/frame * frames/sec * 8 bits/byte / 1000 = kbps
128        avg_bytes * fps * 8.0 / 1000.0
129    }
130
131    /// Returns the variance of frame sizes (in bytes²).
132    ///
133    /// Returns `0.0` if fewer than two frames have been recorded.
134    #[must_use]
135    pub fn variance(&self) -> f32 {
136        if self.history.len() < 2 {
137            return 0.0;
138        }
139        let mean: f32 =
140            self.history.iter().map(|&b| b as f32).sum::<f32>() / self.history.len() as f32;
141        let var: f32 = self
142            .history
143            .iter()
144            .map(|&b| {
145                let diff = b as f32 - mean;
146                diff * diff
147            })
148            .sum::<f32>()
149            / self.history.len() as f32;
150        var
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    // --- RateControlMode ---
159
160    #[test]
161    fn test_cbr_not_quality_based() {
162        assert!(!RateControlMode::ConstantBitrate.is_quality_based());
163    }
164
165    #[test]
166    fn test_vbr_not_quality_based() {
167        assert!(!RateControlMode::VariableBitrate.is_quality_based());
168    }
169
170    #[test]
171    fn test_crf_is_quality_based() {
172        assert!(RateControlMode::ConstantRateFactor.is_quality_based());
173    }
174
175    #[test]
176    fn test_cq_is_quality_based() {
177        assert!(RateControlMode::ConstantQuality.is_quality_based());
178    }
179
180    #[test]
181    fn test_description_not_empty() {
182        for mode in [
183            RateControlMode::ConstantBitrate,
184            RateControlMode::VariableBitrate,
185            RateControlMode::ConstantRateFactor,
186            RateControlMode::ConstantQuality,
187        ] {
188            assert!(!mode.description().is_empty());
189        }
190    }
191
192    // --- TargetBitrate ---
193
194    #[test]
195    fn test_cbr_all_equal() {
196        let tb = TargetBitrate::cbr(5000);
197        assert_eq!(tb.peak_kbps, 5000);
198        assert_eq!(tb.avg_kbps, 5000);
199        assert_eq!(tb.min_kbps, 5000);
200        assert!(!tb.is_vbr());
201    }
202
203    #[test]
204    fn test_with_peak_is_vbr() {
205        let tb = TargetBitrate::with_peak(8000, 5000);
206        assert_eq!(tb.peak_kbps, 8000);
207        assert_eq!(tb.avg_kbps, 5000);
208        assert!(tb.is_vbr());
209    }
210
211    #[test]
212    fn test_peak_to_avg_ratio() {
213        let tb = TargetBitrate::with_peak(10000, 5000);
214        assert!((tb.peak_to_avg_ratio() - 2.0).abs() < 1e-4);
215    }
216
217    #[test]
218    fn test_peak_to_avg_ratio_zero_avg() {
219        let tb = TargetBitrate {
220            peak_kbps: 100,
221            avg_kbps: 0,
222            min_kbps: 0,
223        };
224        assert!((tb.peak_to_avg_ratio() - 1.0).abs() < 1e-4);
225    }
226
227    // --- BitrateEstimator ---
228
229    #[test]
230    fn test_new_empty_history() {
231        let est = BitrateEstimator::new(RateControlMode::ConstantBitrate);
232        assert!(est.history.is_empty());
233    }
234
235    #[test]
236    fn test_current_kbps_no_frames() {
237        let est = BitrateEstimator::new(RateControlMode::ConstantBitrate);
238        assert_eq!(est.current_kbps(30.0), 0.0);
239    }
240
241    #[test]
242    fn test_current_kbps_single_frame() {
243        let mut est = BitrateEstimator::new(RateControlMode::ConstantBitrate);
244        // 125 bytes/frame at 30fps → 125 * 30 * 8 / 1000 = 30 kbps
245        est.add_frame_size_bytes(125);
246        assert!((est.current_kbps(30.0) - 30.0).abs() < 1e-3);
247    }
248
249    #[test]
250    fn test_current_kbps_zero_fps() {
251        let mut est = BitrateEstimator::new(RateControlMode::ConstantBitrate);
252        est.add_frame_size_bytes(1000);
253        assert_eq!(est.current_kbps(0.0), 0.0);
254    }
255
256    #[test]
257    fn test_variance_zero_single_frame() {
258        let mut est = BitrateEstimator::new(RateControlMode::VariableBitrate);
259        est.add_frame_size_bytes(500);
260        assert_eq!(est.variance(), 0.0);
261    }
262
263    #[test]
264    fn test_variance_uniform_frames() {
265        let mut est = BitrateEstimator::new(RateControlMode::VariableBitrate);
266        for _ in 0..10 {
267            est.add_frame_size_bytes(1000);
268        }
269        assert!((est.variance() - 0.0).abs() < 1e-3);
270    }
271
272    #[test]
273    fn test_variance_non_zero() {
274        let mut est = BitrateEstimator::new(RateControlMode::VariableBitrate);
275        est.add_frame_size_bytes(100);
276        est.add_frame_size_bytes(200);
277        // mean = 150, variance = ((50)^2 + (50)^2) / 2 = 2500
278        assert!((est.variance() - 2500.0).abs() < 1e-3);
279    }
280}