oximedia_transcode/
bitrate_control.rs1#![allow(dead_code)]
7#![allow(clippy::cast_precision_loss)]
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum RateControlMode {
12 ConstantBitrate,
14 VariableBitrate,
16 ConstantRateFactor,
18 ConstantQuality,
20}
21
22impl RateControlMode {
23 #[must_use]
25 pub fn is_quality_based(&self) -> bool {
26 matches!(self, Self::ConstantRateFactor | Self::ConstantQuality)
27 }
28
29 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct TargetBitrate {
44 pub peak_kbps: u32,
46 pub avg_kbps: u32,
48 pub min_kbps: u32,
50}
51
52impl TargetBitrate {
53 #[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 #[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 #[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 #[must_use]
88 pub fn is_vbr(&self) -> bool {
89 self.peak_kbps != self.avg_kbps
90 }
91}
92
93#[derive(Debug, Clone)]
95pub struct BitrateEstimator {
96 pub mode: RateControlMode,
98 pub history: Vec<u32>,
100}
101
102impl BitrateEstimator {
103 #[must_use]
105 pub fn new(mode: RateControlMode) -> Self {
106 Self {
107 mode,
108 history: Vec::new(),
109 }
110 }
111
112 pub fn add_frame_size_bytes(&mut self, size: u32) {
114 self.history.push(size);
115 }
116
117 #[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 avg_bytes * fps * 8.0 / 1000.0
129 }
130
131 #[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 #[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 #[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 #[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 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 assert!((est.variance() - 2500.0).abs() < 1e-3);
279 }
280}