1#![allow(clippy::cast_lossless)]
16#![allow(clippy::cast_precision_loss)]
17#![allow(clippy::cast_possible_truncation)]
18#![allow(clippy::cast_sign_loss)]
19
20use std::collections::VecDeque;
21
22#[derive(Debug, Clone)]
31pub enum SimpleRateControlMode {
32 ConstantQuality {
37 crf: u8,
39 },
40
41 AverageBitrate {
46 target_kbps: u32,
48 },
49
50 ConstantBitrate {
55 target_kbps: u32,
57 vbv_size_kb: u32,
59 },
60
61 VariableBitrate {
65 min_kbps: u32,
67 max_kbps: u32,
69 target_kbps: u32,
71 },
72
73 TwoPass {
80 target_kbps: u32,
82 first_pass_stats: Option<String>,
84 },
85}
86
87#[derive(Debug, Clone)]
93pub struct SimpleRateControlConfig {
94 pub mode: SimpleRateControlMode,
96 pub fps: f32,
98 pub resolution: (u32, u32),
100 pub keyframe_interval: u32,
102 pub b_frames: u8,
104 pub scene_change_sensitivity: u8,
106}
107
108impl SimpleRateControlConfig {
109 pub fn crf(crf: u8, fps: f32, resolution: (u32, u32)) -> Self {
111 Self {
112 mode: SimpleRateControlMode::ConstantQuality { crf },
113 fps,
114 resolution,
115 keyframe_interval: 120,
116 b_frames: 2,
117 scene_change_sensitivity: 40,
118 }
119 }
120
121 pub fn abr(target_kbps: u32, fps: f32, resolution: (u32, u32)) -> Self {
123 Self {
124 mode: SimpleRateControlMode::AverageBitrate { target_kbps },
125 fps,
126 resolution,
127 keyframe_interval: 120,
128 b_frames: 2,
129 scene_change_sensitivity: 40,
130 }
131 }
132
133 pub fn cbr(target_kbps: u32, vbv_size_kb: u32, fps: f32, resolution: (u32, u32)) -> Self {
135 Self {
136 mode: SimpleRateControlMode::ConstantBitrate {
137 target_kbps,
138 vbv_size_kb,
139 },
140 fps,
141 resolution,
142 keyframe_interval: 120,
143 b_frames: 0,
144 scene_change_sensitivity: 20,
145 }
146 }
147}
148
149#[derive(Debug, Clone)]
155pub struct SimpleRateControlStats {
156 pub frames_encoded: u64,
158 pub total_bits: u64,
161 pub avg_bits_per_frame: f64,
163 pub avg_complexity: f32,
165 pub vbv_fullness: f64,
167 pub target_bitrate_kbps: u32,
169 pub actual_bitrate_kbps: f64,
171}
172
173const COMPLEXITY_HISTORY_LEN: usize = 64;
179
180pub struct SimpleRateController {
200 config: SimpleRateControlConfig,
202 frame_count: u64,
204 bits_spent: u64,
206 target_bits: u64,
208 vbv_fullness: f64,
210 complexity_history: VecDeque<f32>,
212 first_pass_complexities: Vec<f32>,
215 first_pass_mean: f32,
217}
218
219impl SimpleRateController {
220 pub fn new(config: SimpleRateControlConfig) -> Self {
225 let target_bits = compute_target_bits_per_frame(&config);
226 let vbv_fullness = match &config.mode {
227 SimpleRateControlMode::ConstantBitrate { .. } => 0.5,
228 _ => 0.0,
229 };
230
231 let (first_pass_complexities, first_pass_mean) = parse_first_pass_stats(&config.mode);
232
233 Self {
234 config,
235 frame_count: 0,
236 bits_spent: 0,
237 target_bits,
238 vbv_fullness,
239 complexity_history: VecDeque::with_capacity(COMPLEXITY_HISTORY_LEN),
240 first_pass_complexities,
241 first_pass_mean,
242 }
243 }
244
245 fn avg_complexity(&self) -> f32 {
251 if self.complexity_history.is_empty() {
252 return 1.0;
253 }
254 let sum: f32 = self.complexity_history.iter().sum();
255 sum / self.complexity_history.len() as f32
256 }
257
258 pub fn target_bits_per_frame(&self) -> u64 {
265 self.target_bits
266 }
267
268 pub fn allocate_frame_bits(&mut self, complexity: f32) -> u32 {
274 let complexity = complexity.max(0.0001_f32);
275 match &self.config.mode.clone() {
276 SimpleRateControlMode::ConstantQuality { crf } => {
277 let (w, h) = self.config.resolution;
278 let qp = (*crf as f32).max(1.0);
279 let bits = (w as f64 * h as f64 / (qp * qp) as f64) as u64;
280 bits.min(u32::MAX as u64) as u32
281 }
282
283 SimpleRateControlMode::AverageBitrate { .. } => {
284 let avg = self.avg_complexity();
285 let ratio = (complexity / avg) as f64;
286 let allocated = (self.target_bits as f64 * ratio).round() as u64;
287 allocated.clamp(1, self.target_bits.saturating_mul(4)) as u32
289 }
290
291 SimpleRateControlMode::VariableBitrate {
292 min_kbps, max_kbps, ..
293 } => {
294 let min_bits = (*min_kbps as f64 * 1000.0 / self.config.fps as f64) as u64;
295 let max_bits = (*max_kbps as f64 * 1000.0 / self.config.fps as f64) as u64;
296 let avg = self.avg_complexity();
297 let ratio = (complexity / avg) as f64;
298 let allocated = (self.target_bits as f64 * ratio).round() as u64;
299 allocated.clamp(min_bits.max(1), max_bits.max(1)) as u32
300 }
301
302 SimpleRateControlMode::ConstantBitrate { vbv_size_kb, .. } => {
303 let avg = self.avg_complexity();
304 let ratio = (complexity / avg) as f64;
305 let mut allocated = (self.target_bits as f64 * ratio).round() as u64;
306
307 let vbv_bits = (*vbv_size_kb as u64).saturating_mul(1000);
310 if self.vbv_fullness > 0.9 {
311 let cap = (self.target_bits as f64 * 0.5).round() as u64;
313 allocated = allocated.min(cap);
314 } else if self.vbv_fullness < 0.3 {
315 let floor = (self.target_bits as f64 * 1.5).round() as u64;
317 allocated = allocated.max(floor);
318 }
319
320 allocated.clamp(1, vbv_bits.max(1)) as u32
322 }
323
324 SimpleRateControlMode::TwoPass { .. } => {
325 let idx = self.frame_count as usize;
326 if !self.first_pass_complexities.is_empty() && self.first_pass_mean > 0.0 {
327 let frame_cplx = if idx < self.first_pass_complexities.len() {
328 self.first_pass_complexities[idx]
329 } else {
330 self.first_pass_mean
331 };
332 let ratio = (frame_cplx / self.first_pass_mean) as f64;
333 let allocated = (self.target_bits as f64 * ratio).round() as u64;
334 allocated.clamp(1, self.target_bits.saturating_mul(4)) as u32
335 } else {
336 self.target_bits.clamp(1, u32::MAX as u64) as u32
338 }
339 }
340 }
341 }
342
343 pub fn record_frame(&mut self, actual_bits: u32, complexity: f32) {
352 self.bits_spent = self.bits_spent.saturating_add(actual_bits as u64);
353 self.frame_count = self.frame_count.saturating_add(1);
354
355 if self.complexity_history.len() >= COMPLEXITY_HISTORY_LEN {
357 self.complexity_history.pop_front();
358 }
359 self.complexity_history.push_back(complexity.max(0.0));
360
361 if let SimpleRateControlMode::ConstantBitrate {
363 target_kbps,
364 vbv_size_kb,
365 } = &self.config.mode
366 {
367 let vbv_capacity = (*vbv_size_kb as f64) * 1000.0;
368 if vbv_capacity > 0.0 {
369 let drained = actual_bits as f64;
370 let refilled = (*target_kbps as f64 * 1000.0) / self.config.fps as f64;
371 let delta = (refilled - drained) / vbv_capacity;
373 self.vbv_fullness = (self.vbv_fullness + delta).clamp(0.0, 1.0);
374 }
375 }
376 }
377
378 pub fn vbv_status(&self) -> f64 {
382 self.vbv_fullness
383 }
384
385 pub fn is_keyframe(&self) -> bool {
390 let interval = self.config.keyframe_interval.max(1) as u64;
391 self.frame_count % interval == 0
392 }
393
394 pub fn stats(&self) -> SimpleRateControlStats {
396 let target_bitrate_kbps = extract_target_kbps(&self.config.mode);
397 let avg_bits_per_frame = if self.frame_count == 0 {
398 0.0
399 } else {
400 self.bits_spent as f64 / self.frame_count as f64
401 };
402 let actual_bitrate_kbps = avg_bits_per_frame * self.config.fps as f64 / 1000.0;
403
404 SimpleRateControlStats {
405 frames_encoded: self.frame_count,
406 total_bits: self.bits_spent,
407 avg_bits_per_frame,
408 avg_complexity: self.avg_complexity(),
409 vbv_fullness: self.vbv_fullness,
410 target_bitrate_kbps,
411 actual_bitrate_kbps,
412 }
413 }
414}
415
416fn compute_target_bits_per_frame(config: &SimpleRateControlConfig) -> u64 {
424 let fps = config.fps.max(0.001_f32) as f64;
425 match &config.mode {
426 SimpleRateControlMode::ConstantQuality { .. } => 0,
427 SimpleRateControlMode::AverageBitrate { target_kbps }
428 | SimpleRateControlMode::ConstantBitrate { target_kbps, .. }
429 | SimpleRateControlMode::VariableBitrate { target_kbps, .. }
430 | SimpleRateControlMode::TwoPass { target_kbps, .. } => {
431 (*target_kbps as f64 * 1000.0 / fps).round() as u64
432 }
433 }
434}
435
436fn extract_target_kbps(mode: &SimpleRateControlMode) -> u32 {
440 match mode {
441 SimpleRateControlMode::ConstantQuality { .. } => 0,
442 SimpleRateControlMode::AverageBitrate { target_kbps }
443 | SimpleRateControlMode::ConstantBitrate { target_kbps, .. }
444 | SimpleRateControlMode::VariableBitrate { target_kbps, .. }
445 | SimpleRateControlMode::TwoPass { target_kbps, .. } => *target_kbps,
446 }
447}
448
449fn parse_first_pass_stats(mode: &SimpleRateControlMode) -> (Vec<f32>, f32) {
454 if let SimpleRateControlMode::TwoPass {
455 first_pass_stats: Some(stats),
456 ..
457 } = mode
458 {
459 let values: Vec<f32> = stats
460 .lines()
461 .filter_map(|l| l.trim().parse::<f32>().ok())
462 .collect();
463 let mean = if values.is_empty() {
464 0.0
465 } else {
466 values.iter().sum::<f32>() / values.len() as f32
467 };
468 return (values, mean);
469 }
470 (Vec::new(), 0.0)
471}
472
473#[cfg(test)]
478mod tests {
479 use super::*;
480
481 fn make_crf(crf: u8) -> SimpleRateController {
482 SimpleRateController::new(SimpleRateControlConfig::crf(crf, 30.0, (1920, 1080)))
483 }
484
485 fn make_abr(kbps: u32) -> SimpleRateController {
486 SimpleRateController::new(SimpleRateControlConfig::abr(kbps, 30.0, (1920, 1080)))
487 }
488
489 fn make_cbr(kbps: u32, vbv_kb: u32) -> SimpleRateController {
490 SimpleRateController::new(SimpleRateControlConfig::cbr(
491 kbps,
492 vbv_kb,
493 30.0,
494 (1920, 1080),
495 ))
496 }
497
498 #[test]
501 fn crf_allocation_nonzero() {
502 let mut rc = make_crf(23);
503 let bits = rc.allocate_frame_bits(1.0);
504 assert!(bits > 0, "CRF allocation must be positive");
505 }
506
507 #[test]
510 fn crf_lower_crf_more_bits() {
511 let mut hi_q = make_crf(10);
512 let mut lo_q = make_crf(40);
513 let hi = hi_q.allocate_frame_bits(1.0);
514 let lo = lo_q.allocate_frame_bits(1.0);
515 assert!(hi > lo, "crf=10 should yield more bits than crf=40");
516 }
517
518 #[test]
521 fn abr_target_bits_per_frame() {
522 let rc = make_abr(4000);
523 let expected = (4_000_000_u64 / 30) as f64;
525 let got = rc.target_bits_per_frame() as f64;
526 assert!(
527 (got - expected).abs() / expected < 0.01,
528 "ABR target bits/frame off: got {got}, expected ~{expected}"
529 );
530 }
531
532 #[test]
535 fn crf_target_bits_zero() {
536 let rc = make_crf(28);
537 assert_eq!(rc.target_bits_per_frame(), 0);
538 }
539
540 #[test]
543 fn abr_high_complexity_more_bits() {
544 let mut rc = make_abr(4000);
545 for _ in 0..16 {
547 rc.record_frame(100_000, 1.0);
548 }
549 let low = rc.allocate_frame_bits(0.5);
550 let high = rc.allocate_frame_bits(2.0);
551 assert!(
552 high > low,
553 "higher complexity should yield more bits in ABR"
554 );
555 }
556
557 #[test]
560 fn record_frame_increments_count() {
561 let mut rc = make_abr(2000);
562 for _ in 0..10 {
563 rc.record_frame(50_000, 1.0);
564 }
565 assert_eq!(rc.stats().frames_encoded, 10);
566 }
567
568 #[test]
571 fn record_frame_accumulates_bits() {
572 let mut rc = make_abr(2000);
573 for _ in 0..5 {
574 rc.record_frame(10_000, 1.0);
575 }
576 assert_eq!(rc.stats().total_bits, 50_000);
577 }
578
579 #[test]
582 fn cbr_vbv_fullness_in_range() {
583 let mut rc = make_cbr(4000, 8000);
584 for i in 0..60 {
585 let bits = if i % 5 == 0 { 1_000_000 } else { 50_000 };
586 rc.record_frame(bits, 1.0);
587 let f = rc.vbv_status();
588 assert!((0.0..=1.0).contains(&f), "VBV fullness out of range: {f}");
589 }
590 }
591
592 #[test]
595 fn cbr_vbv_reduces_when_full() {
596 let mut rc = make_cbr(4000, 1000); for _ in 0..100 {
599 rc.record_frame(0, 1.0); }
601 let bits_full = rc.allocate_frame_bits(1.0);
602 let target = rc.target_bits_per_frame() as u32;
603 assert!(
605 bits_full <= target.saturating_mul(5) / 10 + 1,
606 "CBR should reduce bits when VBV is full; got {bits_full}, target {target}"
607 );
608 }
609
610 #[test]
613 fn is_keyframe_at_correct_intervals() {
614 let cfg = SimpleRateControlConfig {
615 mode: SimpleRateControlMode::AverageBitrate { target_kbps: 2000 },
616 fps: 30.0,
617 resolution: (1280, 720),
618 keyframe_interval: 10,
619 b_frames: 0,
620 scene_change_sensitivity: 0,
621 };
622 let mut rc = SimpleRateController::new(cfg);
623 assert!(rc.is_keyframe(), "frame_count=0 should be keyframe");
625 for i in 1..10u64 {
627 rc.record_frame(10_000, 1.0); assert_eq!(rc.frame_count, i);
629 assert!(!rc.is_keyframe(), "frame_count={i} should NOT be keyframe");
630 }
631 rc.record_frame(10_000, 1.0);
633 assert_eq!(rc.frame_count, 10);
634 assert!(rc.is_keyframe(), "frame_count=10 should be keyframe");
635 }
636
637 #[test]
640 fn stats_avg_bits_per_frame() {
641 let mut rc = make_abr(2000);
642 rc.record_frame(200_000, 1.0);
643 rc.record_frame(100_000, 1.0);
644 let s = rc.stats();
645 assert!((s.avg_bits_per_frame - 150_000.0).abs() < 1.0);
646 }
647
648 #[test]
651 fn vbr_allocation_clamped() {
652 let cfg = SimpleRateControlConfig {
653 mode: SimpleRateControlMode::VariableBitrate {
654 min_kbps: 1000,
655 max_kbps: 8000,
656 target_kbps: 4000,
657 },
658 fps: 30.0,
659 resolution: (1920, 1080),
660 keyframe_interval: 120,
661 b_frames: 2,
662 scene_change_sensitivity: 40,
663 };
664 let mut rc = SimpleRateController::new(cfg);
665 for _ in 0..16 {
667 rc.record_frame(130_000, 1.0);
668 }
669 let bits_low = rc.allocate_frame_bits(0.01); let bits_high = rc.allocate_frame_bits(100.0); let min_bits = (1000_u64 * 1000 / 30) as u32;
672 let max_bits = (8000_u64 * 1000 / 30) as u32;
673 assert!(
674 bits_low >= min_bits.saturating_sub(1),
675 "VBR low allocation {bits_low} below min {min_bits}"
676 );
677 assert!(
678 bits_high <= max_bits + 1,
679 "VBR high allocation {bits_high} above max {max_bits}"
680 );
681 }
682
683 #[test]
686 fn two_pass_uses_first_pass_stats() {
687 let stats = "0.5\n1.0\n2.0\n0.5\n1.0";
688 let cfg = SimpleRateControlConfig {
689 mode: SimpleRateControlMode::TwoPass {
690 target_kbps: 4000,
691 first_pass_stats: Some(stats.to_owned()),
692 },
693 fps: 30.0,
694 resolution: (1920, 1080),
695 keyframe_interval: 120,
696 b_frames: 2,
697 scene_change_sensitivity: 40,
698 };
699 let mut rc = SimpleRateController::new(cfg);
700
701 let bits_easy = rc.allocate_frame_bits(0.5);
703 rc.record_frame(bits_easy, 0.5);
704
705 let bits_avg = rc.allocate_frame_bits(1.0);
707 rc.record_frame(bits_avg, 1.0);
708
709 let bits_hard = rc.allocate_frame_bits(2.0);
711
712 assert!(
713 bits_hard > bits_easy,
714 "Two-pass: complex frame should get more bits than easy frame"
715 );
716 }
717
718 #[test]
721 fn stats_actual_bitrate_nonzero() {
722 let mut rc = make_abr(4000);
723 for _ in 0..30 {
724 rc.record_frame(133_333, 1.0);
725 }
726 let s = rc.stats();
727 assert!(
728 s.actual_bitrate_kbps > 0.0,
729 "actual bitrate should be positive"
730 );
731 }
732
733 #[test]
736 fn two_pass_no_stats_flat_fallback() {
737 let cfg = SimpleRateControlConfig {
738 mode: SimpleRateControlMode::TwoPass {
739 target_kbps: 2000,
740 first_pass_stats: None,
741 },
742 fps: 25.0,
743 resolution: (1280, 720),
744 keyframe_interval: 50,
745 b_frames: 2,
746 scene_change_sensitivity: 40,
747 };
748 let mut rc = SimpleRateController::new(cfg);
749 let bits = rc.allocate_frame_bits(1.0);
750 assert!(bits > 0, "fallback two-pass allocation must be positive");
751 let target = rc.target_bits_per_frame() as u32;
753 assert_eq!(
754 bits, target,
755 "no-stats two-pass should produce flat allocation equal to target"
756 );
757 }
758}