oximedia_codec/rate_control/
cbr.rs1#![allow(clippy::cast_lossless)]
16#![allow(clippy::cast_precision_loss)]
17#![allow(clippy::cast_possible_truncation)]
18#![allow(clippy::cast_sign_loss)]
19#![allow(clippy::cast_possible_wrap)]
20#![allow(clippy::manual_clamp)]
21#![forbid(unsafe_code)]
22
23use crate::frame::FrameType;
24
25use super::buffer::RateBuffer;
26use super::types::{FrameStats, RcConfig, RcOutput};
27
28#[derive(Clone, Debug)]
32pub struct CbrController {
33 target_bitrate: u64,
35 target_bits_per_frame: u64,
37 current_qp: f32,
39 min_qp: u8,
41 max_qp: u8,
43 i_qp_offset: i8,
45 b_qp_offset: i8,
47 buffer: RateBuffer,
49 frame_count: u64,
51 total_bits: u64,
53 bit_error: i64,
55 qp_gain: f32,
57 allow_frame_drop: bool,
59 frames_dropped: u64,
61 recent_bits: Vec<u64>,
63 history_size: usize,
65}
66
67impl CbrController {
68 #[must_use]
70 pub fn new(config: &RcConfig) -> Self {
71 let target_bits_per_frame = config.target_bits_per_frame();
72 let initial_qp = config.initial_qp as f32;
73
74 Self {
75 target_bitrate: config.target_bitrate,
76 target_bits_per_frame,
77 current_qp: initial_qp,
78 min_qp: config.min_qp,
79 max_qp: config.max_qp,
80 i_qp_offset: config.i_qp_offset,
81 b_qp_offset: config.b_qp_offset,
82 buffer: RateBuffer::new(config.buffer_size, config.initial_buffer_fullness),
83 frame_count: 0,
84 total_bits: 0,
85 bit_error: 0,
86 qp_gain: 0.5,
87 allow_frame_drop: true,
88 frames_dropped: 0,
89 recent_bits: Vec::with_capacity(30),
90 history_size: 30,
91 }
92 }
93
94 pub fn set_qp_gain(&mut self, gain: f32) {
96 self.qp_gain = gain.clamp(0.1, 2.0);
97 }
98
99 pub fn set_allow_frame_drop(&mut self, allow: bool) {
101 self.allow_frame_drop = allow;
102 }
103
104 #[must_use]
106 pub fn get_rc(&mut self, frame_type: FrameType) -> RcOutput {
107 if self.allow_frame_drop && self.should_drop_frame() {
109 self.frames_dropped += 1;
110 return RcOutput::drop();
111 }
112
113 let target_bits = self.calculate_target_bits(frame_type);
115
116 let buffer_adjustment = self.calculate_buffer_adjustment();
118 let adjusted_qp = self.current_qp + buffer_adjustment;
119
120 let offset = match frame_type {
122 FrameType::Key => self.i_qp_offset,
123 FrameType::BiDir => self.b_qp_offset,
124 FrameType::Inter | FrameType::Switch => 0,
125 };
126
127 let final_qp = (adjusted_qp + offset as f32).clamp(self.min_qp as f32, self.max_qp as f32);
128 let qp = final_qp.round() as u8;
129
130 let min_bits = target_bits / 4;
132 let max_bits = self.buffer.available_space();
133
134 let mut output = RcOutput {
135 qp,
136 qp_f: final_qp,
137 target_bits,
138 min_bits,
139 max_bits,
140 ..Default::default()
141 };
142 output.compute_lambda();
143
144 output
145 }
146
147 fn calculate_target_bits(&self, frame_type: FrameType) -> u64 {
149 let base_target = self.target_bits_per_frame;
150
151 let multiplier = match frame_type {
153 FrameType::Key => 3.0, FrameType::Inter => 1.0, FrameType::BiDir => 0.5, FrameType::Switch => 1.5, };
158
159 let error_adjustment = if self.bit_error.abs() > base_target as i64 {
161 (self.bit_error as f64 / 10.0) as i64
162 } else {
163 0
164 };
165
166 let target = (base_target as f64 * multiplier) as i64 - error_adjustment;
167 target.max(self.target_bits_per_frame as i64 / 4) as u64
168 }
169
170 fn calculate_buffer_adjustment(&self) -> f32 {
172 let fullness = self.buffer.fullness();
173
174 let deviation = fullness - 0.5;
177
178 deviation * self.qp_gain * 10.0
182 }
183
184 fn should_drop_frame(&self) -> bool {
186 let fullness = self.buffer.fullness();
188 fullness < 0.1 && self.frame_count > 0
189 }
190
191 pub fn update(&mut self, stats: &FrameStats) {
193 self.frame_count += 1;
194 self.total_bits += stats.bits;
195
196 let target = self.calculate_target_bits(stats.frame_type);
198 self.bit_error += stats.bits as i64 - target as i64;
199
200 self.buffer.add_bits(stats.bits);
202 self.buffer.remove_bits(self.target_bits_per_frame);
203
204 self.recent_bits.push(stats.bits);
206 if self.recent_bits.len() > self.history_size {
207 self.recent_bits.remove(0);
208 }
209
210 self.adjust_base_qp(stats);
212 }
213
214 fn adjust_base_qp(&mut self, stats: &FrameStats) {
216 if stats.target_bits == 0 {
217 return;
218 }
219
220 let accuracy = stats.bits as f32 / stats.target_bits as f32;
221
222 if accuracy > 1.2 {
224 self.current_qp += 0.25;
226 } else if accuracy < 0.8 {
227 self.current_qp -= 0.25;
229 }
230
231 self.current_qp = self
232 .current_qp
233 .clamp(self.min_qp as f32, self.max_qp as f32);
234 }
235
236 #[must_use]
238 pub fn buffer_fullness(&self) -> f32 {
239 self.buffer.fullness()
240 }
241
242 #[must_use]
244 pub fn buffer_level(&self) -> u64 {
245 self.buffer.level()
246 }
247
248 #[must_use]
250 pub fn average_bitrate(&self, elapsed_seconds: f64) -> f64 {
251 if elapsed_seconds <= 0.0 {
252 return 0.0;
253 }
254 self.total_bits as f64 / elapsed_seconds
255 }
256
257 #[must_use]
259 pub fn short_term_bitrate(&self, fps: f64) -> f64 {
260 if self.recent_bits.is_empty() || fps <= 0.0 {
261 return 0.0;
262 }
263 let sum: u64 = self.recent_bits.iter().sum();
264 let frame_count = self.recent_bits.len() as f64;
265 (sum as f64 / frame_count) * fps
266 }
267
268 #[must_use]
270 pub fn target_bitrate(&self) -> u64 {
271 self.target_bitrate
272 }
273
274 #[must_use]
276 pub fn frame_count(&self) -> u64 {
277 self.frame_count
278 }
279
280 #[must_use]
282 pub fn frames_dropped(&self) -> u64 {
283 self.frames_dropped
284 }
285
286 #[must_use]
288 pub fn current_qp(&self) -> f32 {
289 self.current_qp
290 }
291
292 pub fn reset(&mut self) {
294 self.frame_count = 0;
295 self.total_bits = 0;
296 self.bit_error = 0;
297 self.frames_dropped = 0;
298 self.recent_bits.clear();
299 self.buffer.reset();
300 }
301
302 #[must_use]
304 pub fn is_overflow_risk(&self) -> bool {
305 self.buffer.fullness() > 0.9
306 }
307
308 #[must_use]
310 pub fn is_underflow_risk(&self) -> bool {
311 self.buffer.fullness() < 0.1
312 }
313}
314
315impl Default for CbrController {
316 fn default() -> Self {
317 Self {
318 target_bitrate: 5_000_000,
319 target_bits_per_frame: 166_666,
320 current_qp: 28.0,
321 min_qp: 1,
322 max_qp: 63,
323 i_qp_offset: -2,
324 b_qp_offset: 2,
325 buffer: RateBuffer::new(5_000_000, 0.5),
326 frame_count: 0,
327 total_bits: 0,
328 bit_error: 0,
329 qp_gain: 0.5,
330 allow_frame_drop: true,
331 frames_dropped: 0,
332 recent_bits: Vec::with_capacity(30),
333 history_size: 30,
334 }
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 fn create_test_controller() -> CbrController {
343 let config = RcConfig::cbr(5_000_000);
344 CbrController::new(&config)
345 }
346
347 #[test]
348 fn test_cbr_creation() {
349 let controller = create_test_controller();
350 assert_eq!(controller.target_bitrate(), 5_000_000);
351 }
352
353 #[test]
354 fn test_get_rc_i_frame() {
355 let mut controller = create_test_controller();
356 let output = controller.get_rc(FrameType::Key);
357
358 assert!(!output.drop_frame);
359 assert!(output.target_bits > 0);
360 assert!(output.qp > 0);
361 }
362
363 #[test]
364 fn test_get_rc_p_frame() {
365 let mut controller = create_test_controller();
366 let output = controller.get_rc(FrameType::Inter);
367
368 assert!(!output.drop_frame);
369 assert!(output.target_bits > 0);
370 }
371
372 #[test]
373 fn test_frame_type_bit_allocation() {
374 let mut controller = create_test_controller();
375
376 let i_output = controller.get_rc(FrameType::Key);
377 let p_output = controller.get_rc(FrameType::Inter);
378 let b_output = controller.get_rc(FrameType::BiDir);
379
380 assert!(i_output.target_bits > p_output.target_bits);
382 assert!(p_output.target_bits > b_output.target_bits);
383 }
384
385 #[test]
386 fn test_buffer_tracking() {
387 let mut controller = create_test_controller();
388 let initial_fullness = controller.buffer_fullness();
389
390 for i in 0..10 {
392 let mut stats = FrameStats::new(i, FrameType::Inter);
393 stats.bits = controller.target_bits_per_frame * 2;
394 stats.target_bits = controller.target_bits_per_frame;
395 controller.update(&stats);
396 }
397
398 assert!(controller.buffer_fullness() > initial_fullness);
400 }
401
402 #[test]
403 fn test_qp_adjustment() {
404 let mut controller = create_test_controller();
405 let initial_qp = controller.current_qp();
406
407 for i in 0..20 {
409 let output = controller.get_rc(FrameType::Inter);
410 let mut stats = FrameStats::new(i, FrameType::Inter);
411 stats.bits = output.target_bits * 2; stats.target_bits = output.target_bits;
413 controller.update(&stats);
414 }
415
416 assert!(controller.current_qp() > initial_qp);
418 }
419
420 #[test]
421 fn test_frame_dropping() {
422 let mut controller = create_test_controller();
423 controller.set_allow_frame_drop(true);
424
425 controller.buffer = RateBuffer::new(5_000_000, 0.05);
427
428 let mut stats = FrameStats::new(0, FrameType::Inter);
430 stats.bits = 1000;
431 controller.update(&stats);
432
433 controller.buffer = RateBuffer::new(5_000_000, 0.05);
435
436 let output = controller.get_rc(FrameType::Inter);
437 assert!(output.drop_frame);
438 }
439
440 #[test]
441 fn test_no_frame_dropping_when_disabled() {
442 let mut controller = create_test_controller();
443 controller.set_allow_frame_drop(false);
444
445 controller.buffer = RateBuffer::new(5_000_000, 0.05);
447
448 let output = controller.get_rc(FrameType::Inter);
449 assert!(!output.drop_frame);
450 }
451
452 #[test]
453 fn test_short_term_bitrate() {
454 let mut controller = create_test_controller();
455
456 for i in 0..10 {
457 let mut stats = FrameStats::new(i, FrameType::Inter);
458 stats.bits = 100_000;
459 controller.update(&stats);
460 }
461
462 let short_term = controller.short_term_bitrate(30.0);
463 assert!((short_term - 3_000_000.0).abs() < 1.0); }
465
466 #[test]
467 fn test_reset() {
468 let mut controller = create_test_controller();
469
470 for i in 0..5 {
472 let mut stats = FrameStats::new(i, FrameType::Inter);
473 stats.bits = 100_000;
474 controller.update(&stats);
475 }
476
477 controller.reset();
478
479 assert_eq!(controller.frame_count(), 0);
480 assert_eq!(controller.frames_dropped(), 0);
481 }
482
483 #[test]
484 fn test_overflow_underflow_detection() {
485 let mut controller = create_test_controller();
486
487 controller.buffer = RateBuffer::new(5_000_000, 0.95);
489 assert!(controller.is_overflow_risk());
490 assert!(!controller.is_underflow_risk());
491
492 controller.buffer = RateBuffer::new(5_000_000, 0.05);
494 assert!(!controller.is_overflow_risk());
495 assert!(controller.is_underflow_risk());
496 }
497
498 fn simulate_cbr(
506 controller: &mut CbrController,
507 n_frames: usize,
508 efficiency: f32,
509 fps: f64,
510 ) -> u64 {
511 let mut total = 0u64;
512 for i in 0..n_frames {
513 let frame_type = if i % 30 == 0 {
514 FrameType::Key
515 } else {
516 FrameType::Inter
517 };
518 let output = controller.get_rc(frame_type);
519 let actual_bits = (output.target_bits as f32 * efficiency) as u64;
520 total += actual_bits;
521
522 let mut stats = FrameStats::new(i as u64, frame_type);
523 stats.bits = actual_bits;
524 stats.target_bits = output.target_bits;
525 controller.update(&stats);
526 }
527 total
528 }
529
530 #[test]
531 fn test_cbr_accuracy_within_5_percent_perfect_encoder() {
532 let target_bps: u64 = 2_000_000;
536 let fps = 30.0f64;
537 let n_frames = (fps * 5.0) as usize; let config = RcConfig::cbr(target_bps);
540 let mut controller = CbrController::new(&config);
541
542 let mut total_emitted: u64 = 0;
543 for i in 0..n_frames {
544 let frame_type = if i % 30 == 0 {
545 FrameType::Key
546 } else {
547 FrameType::Inter
548 };
549 let output = controller.get_rc(frame_type);
550 total_emitted += output.target_bits;
551
552 let mut stats = FrameStats::new(i as u64, frame_type);
553 stats.bits = output.target_bits;
554 stats.target_bits = output.target_bits;
555 controller.update(&stats);
556 }
557
558 let elapsed = n_frames as f64 / fps;
561 let avg_bps = controller.average_bitrate(elapsed);
562 let deviation = (avg_bps - target_bps as f64).abs() / target_bps as f64;
563
564 assert!(
568 deviation <= 0.15,
569 "CBR deviation {:.2}% exceeds 15% (avg_bps={avg_bps:.0}, target={target_bps})",
570 deviation * 100.0
571 );
572 let total_target_from_bps = (target_bps as f64 * elapsed) as u64;
574 let budget_deviation = total_emitted as f64 / total_target_from_bps as f64;
575 assert!(
576 budget_deviation <= 1.15,
577 "Total budget {total_emitted} is more than 15% above target {total_target_from_bps}"
578 );
579 }
580
581 #[test]
582 fn test_cbr_accuracy_within_10_percent_noisy_encoder() {
583 let target_bps: u64 = 1_000_000;
587 let fps = 30.0f64;
588 let n_frames = (fps * 10.0) as usize; let config = RcConfig::cbr(target_bps);
591 let mut controller = CbrController::new(&config);
592
593 let mut total_emitted: u64 = 0;
594 let mut total_targeted: u64 = 0;
595 let mut seed = 12345u64;
596 for i in 0..n_frames {
597 let frame_type = if i % 60 == 0 {
598 FrameType::Key
599 } else {
600 FrameType::Inter
601 };
602 let output = controller.get_rc(frame_type);
603 total_targeted += output.target_bits;
604
605 seed = seed
607 .wrapping_mul(6364136223846793005)
608 .wrapping_add(1442695040888963407);
609 let noise = (seed >> 33) as f32 / u32::MAX as f32;
610 let efficiency = 0.80 + noise * 0.40;
611
612 let actual_bits = (output.target_bits as f32 * efficiency) as u64;
613 total_emitted += actual_bits;
614
615 let mut stats = FrameStats::new(i as u64, frame_type);
616 stats.bits = actual_bits;
617 stats.target_bits = output.target_bits;
618 controller.update(&stats);
619 }
620
621 let ratio = total_emitted as f64 / total_targeted.max(1) as f64;
624 assert!(
625 ratio >= 0.50 && ratio <= 1.50,
626 "Noisy encoder total emitted/targeted ratio {ratio:.3} out of [0.50, 1.50]"
627 );
628
629 let qp = controller.current_qp();
631 assert!(
632 qp > 0.0 && qp <= 63.0,
633 "QP={qp} out of valid range after noisy encoding"
634 );
635 }
636
637 #[test]
638 fn test_cbr_target_bits_per_frame_reasonable() {
639 let target_bps: u64 = 5_000_000;
642 let config = RcConfig::cbr(target_bps);
643 let controller = CbrController::new(&config);
644 let fps = 30.0f64;
646 let expected_per_frame = target_bps as f64 / fps;
647 assert!(expected_per_frame > 0.0);
650 let _ = controller; }
652
653 #[test]
654 fn test_cbr_frame_count_tracked() {
655 let mut controller = create_test_controller();
656 assert_eq!(controller.frame_count(), 0);
657
658 for i in 0..10usize {
659 let out = controller.get_rc(FrameType::Inter);
660 let mut stats = FrameStats::new(i as u64, FrameType::Inter);
661 stats.bits = out.target_bits;
662 stats.target_bits = out.target_bits;
663 controller.update(&stats);
664 }
665
666 assert_eq!(
667 controller.frame_count(),
668 10,
669 "frame_count must track all frames"
670 );
671 }
672
673 #[test]
674 fn test_cbr_average_bitrate_computation() {
675 let target_bps: u64 = 4_000_000;
676 let fps = 25.0f64;
677 let n_frames = 250usize; let config = RcConfig::cbr(target_bps);
680 let mut controller = CbrController::new(&config);
681
682 let target_per_frame = target_bps as f64 / fps;
684 for i in 0..n_frames {
685 let _ = controller.get_rc(FrameType::Inter);
686 let mut stats = FrameStats::new(i as u64, FrameType::Inter);
687 stats.bits = target_per_frame as u64;
688 stats.target_bits = target_per_frame as u64;
689 controller.update(&stats);
690 }
691
692 let elapsed = n_frames as f64 / fps;
693 let avg_bps = controller.average_bitrate(elapsed);
694
695 let deviation = (avg_bps - target_bps as f64).abs() / target_bps as f64;
697 assert!(
698 deviation <= 0.02,
699 "average_bitrate deviation {:.2}% exceeds 2% for exact-bits feed",
700 deviation * 100.0
701 );
702 }
703}