1#![allow(clippy::cast_lossless)]
56#![allow(clippy::cast_precision_loss)]
57#![allow(clippy::cast_possible_truncation)]
58#![allow(clippy::cast_sign_loss)]
59#![allow(clippy::cast_possible_wrap)]
60#![allow(clippy::too_many_arguments)]
61#![allow(clippy::struct_excessive_bools)]
62#![allow(dead_code)]
63#![forbid(unsafe_code)]
64
65use crate::frame::FrameType;
66
67use super::types::{FrameStats, GopStats, RcConfig, RcOutput};
68
69#[derive(Clone, Debug)]
71pub struct AbrController {
72 target_bitrate: u64,
74 target_file_size: Option<u64>,
76 total_frames: Option<u64>,
78 pass: u8,
80 framerate: f64,
82 current_qp: f32,
84 min_qp: u8,
86 max_qp: u8,
87 i_qp_offset: i8,
89 b_qp_offset: i8,
90 frame_count: u64,
92 total_bits: u64,
94 first_pass_stats: Option<FirstPassStats>,
96 allocator: MultiPassAllocator,
98 lookahead: LookaheadAnalyzer,
100 rc_model: RateControlModel,
102 curve_state: CurveCompressionState,
104 current_gop: GopStats,
106 gop_history: Vec<GopStats>,
107}
108
109#[derive(Clone, Debug, Default)]
111pub struct FirstPassStats {
112 pub frames: Vec<FramePassStats>,
114 pub total_complexity: f64,
116 pub frame_count: u64,
118 pub gop_complexities: Vec<f64>,
120 pub scene_changes: Vec<u64>,
122 pub avg_complexity: f64,
124 pub complexity_variance: f64,
126}
127
128#[derive(Clone, Debug, Default)]
130pub struct FramePassStats {
131 pub frame_index: u64,
133 pub frame_type: FrameType,
135 pub spatial_complexity: f32,
137 pub temporal_complexity: f32,
139 pub combined_complexity: f32,
141 pub is_scene_change: bool,
143 pub intra_blocks: u32,
145 pub inter_blocks: u32,
147 pub avg_motion: f32,
149 pub predicted_bits: u64,
151 pub allocated_bits: u64,
153}
154
155impl FirstPassStats {
156 pub fn add_frame(&mut self, stats: FramePassStats) {
158 self.total_complexity += stats.combined_complexity as f64;
159 self.frame_count += 1;
160 if stats.is_scene_change {
161 self.scene_changes.push(stats.frame_index);
162 }
163 self.frames.push(stats);
164 }
165
166 pub fn finalize(&mut self) {
168 if self.frame_count == 0 {
169 return;
170 }
171
172 self.avg_complexity = self.total_complexity / self.frame_count as f64;
173
174 let mut variance_sum = 0.0;
176 for frame in &self.frames {
177 let diff = frame.combined_complexity as f64 - self.avg_complexity;
178 variance_sum += diff * diff;
179 }
180 self.complexity_variance = variance_sum / self.frame_count as f64;
181 }
182
183 pub fn get_frame(&self, index: u64) -> Option<&FramePassStats> {
185 self.frames.get(index as usize)
186 }
187}
188
189#[derive(Clone, Debug)]
191struct MultiPassAllocator {
192 total_bits_budget: u64,
194 bits_used: u64,
196 frames_remaining: u64,
198 complexity_based: bool,
200 i_frame_ratio: f64,
202 p_frame_ratio: f64,
203 b_frame_ratio: f64,
204 adaptation_strength: f32,
206}
207
208impl Default for MultiPassAllocator {
209 fn default() -> Self {
210 Self {
211 total_bits_budget: 0,
212 bits_used: 0,
213 frames_remaining: 0,
214 complexity_based: true,
215 i_frame_ratio: 3.0,
216 p_frame_ratio: 1.0,
217 b_frame_ratio: 0.5,
218 adaptation_strength: 1.0,
219 }
220 }
221}
222
223impl MultiPassAllocator {
224 fn initialize(&mut self, total_bits: u64, total_frames: u64) {
226 self.total_bits_budget = total_bits;
227 self.frames_remaining = total_frames;
228 self.bits_used = 0;
229 }
230
231 fn allocate_frame_bits(
233 &mut self,
234 frame_type: FrameType,
235 complexity: f32,
236 avg_complexity: f32,
237 first_pass: &FirstPassStats,
238 ) -> u64 {
239 if self.frames_remaining == 0 {
240 return 0;
241 }
242
243 let bits_remaining = self.total_bits_budget.saturating_sub(self.bits_used);
244 let base_allocation = bits_remaining / self.frames_remaining;
245
246 let type_mult = match frame_type {
248 FrameType::Key => self.i_frame_ratio,
249 FrameType::Inter => self.p_frame_ratio,
250 FrameType::BiDir => self.b_frame_ratio,
251 FrameType::Switch => 1.5,
252 };
253
254 let complexity_mult = if self.complexity_based && avg_complexity > 0.0 {
256 (complexity / avg_complexity).clamp(0.5, 2.0)
257 } else {
258 1.0
259 };
260
261 let lookahead_mult = self.calculate_lookahead_multiplier(first_pass);
263
264 let allocated =
265 (base_allocation as f64 * type_mult * complexity_mult as f64 * lookahead_mult)
266 .max(base_allocation as f64 * 0.25) as u64;
267
268 self.bits_used += allocated;
269 self.frames_remaining = self.frames_remaining.saturating_sub(1);
270
271 allocated
272 }
273
274 fn calculate_lookahead_multiplier(&self, first_pass: &FirstPassStats) -> f64 {
276 let skip_count =
278 (first_pass.frame_count as usize).saturating_sub(self.frames_remaining as usize);
279 let remaining_complexity: f64 = first_pass
280 .frames
281 .iter()
282 .skip(skip_count)
283 .map(|f| f.combined_complexity as f64)
284 .sum();
285
286 if remaining_complexity > 0.0 && self.frames_remaining > 0 {
287 let avg_remaining = remaining_complexity / self.frames_remaining as f64;
288 let ratio = avg_remaining / first_pass.avg_complexity;
289
290 if ratio > 1.2 {
292 0.95 } else if ratio < 0.8 {
294 1.05 } else {
296 1.0
297 }
298 } else {
299 1.0
300 }
301 }
302
303 fn update(&mut self, actual_bits: u64) {
305 let _ = actual_bits;
307 }
308}
309
310#[derive(Clone, Debug)]
312struct LookaheadAnalyzer {
313 depth: usize,
315 complexity_buffer: Vec<f32>,
317 enable_me_lookahead: bool,
319 enable_scene_lookahead: bool,
321 scene_threshold: f32,
323}
324
325impl Default for LookaheadAnalyzer {
326 fn default() -> Self {
327 Self {
328 depth: 40,
329 complexity_buffer: Vec::new(),
330 enable_me_lookahead: true,
331 enable_scene_lookahead: true,
332 scene_threshold: 0.4,
333 }
334 }
335}
336
337impl LookaheadAnalyzer {
338 fn push_complexity(&mut self, complexity: f32) {
340 self.complexity_buffer.push(complexity);
341 if self.complexity_buffer.len() > self.depth {
342 self.complexity_buffer.remove(0);
343 }
344 }
345
346 fn analyze(&self) -> LookaheadAnalysis {
348 if self.complexity_buffer.is_empty() {
349 return LookaheadAnalysis::default();
350 }
351
352 let avg_complexity: f32 =
353 self.complexity_buffer.iter().sum::<f32>() / self.complexity_buffer.len() as f32;
354
355 let current_complexity = self.complexity_buffer[0];
356
357 let has_scene_cut = if self.enable_scene_lookahead && self.complexity_buffer.len() > 1 {
359 self.detect_scene_cut()
360 } else {
361 false
362 };
363
364 let trend = if self.complexity_buffer.len() >= 10 {
366 let first_half: f32 = self.complexity_buffer[..5].iter().sum::<f32>() / 5.0;
367 let second_half: f32 = self.complexity_buffer[5..10].iter().sum::<f32>() / 5.0;
368 (second_half - first_half) / first_half
369 } else {
370 0.0
371 };
372
373 LookaheadAnalysis {
374 avg_complexity,
375 current_complexity,
376 has_scene_cut,
377 complexity_trend: trend,
378 recommend_bits_multiplier: self.calculate_multiplier(
379 current_complexity,
380 avg_complexity,
381 trend,
382 ),
383 }
384 }
385
386 fn detect_scene_cut(&self) -> bool {
388 if self.complexity_buffer.len() < 2 {
389 return false;
390 }
391
392 let window = self.complexity_buffer.len().min(self.depth);
393 for i in 1..window {
394 let prev = self.complexity_buffer[i - 1];
395 let curr = self.complexity_buffer[i];
396 if prev > 0.0 && (curr / prev) > (1.0 + self.scene_threshold) {
397 return true;
398 }
399 }
400 false
401 }
402
403 fn calculate_multiplier(&self, current: f32, avg: f32, trend: f32) -> f64 {
405 let base_mult = if avg > 0.0 {
406 (current / avg).clamp(0.7, 1.5)
407 } else {
408 1.0
409 };
410
411 let trend_adj = if trend > 0.2 {
413 0.95 } else if trend < -0.2 {
415 1.05 } else {
417 1.0
418 };
419
420 base_mult as f64 * trend_adj
421 }
422}
423
424#[derive(Clone, Debug, Default)]
426struct LookaheadAnalysis {
427 avg_complexity: f32,
429 current_complexity: f32,
431 has_scene_cut: bool,
433 complexity_trend: f32,
435 recommend_bits_multiplier: f64,
437}
438
439#[derive(Clone, Debug)]
441struct RateControlModel {
442 model_type: u8,
444 linear_params: LinearModel,
446 quad_params: QuadraticModel,
448 power_params: PowerModel,
450 auto_select: bool,
452}
453
454impl Default for RateControlModel {
455 fn default() -> Self {
456 Self {
457 model_type: 0,
458 linear_params: LinearModel::default(),
459 quad_params: QuadraticModel::default(),
460 power_params: PowerModel::default(),
461 auto_select: true,
462 }
463 }
464}
465
466impl RateControlModel {
467 fn predict(&self, complexity: f32, qp: f32) -> u64 {
469 let prediction = match self.model_type {
470 0 => self.linear_params.predict(complexity, qp),
471 1 => self.quad_params.predict(complexity, qp),
472 2 => self.power_params.predict(complexity, qp),
473 _ => self.linear_params.predict(complexity, qp),
474 };
475
476 prediction.max(100.0) as u64
477 }
478
479 fn fit(&mut self, first_pass: &FirstPassStats) {
481 if first_pass.frames.len() < 10 {
482 return;
483 }
484
485 self.linear_params.fit(first_pass);
486 self.quad_params.fit(first_pass);
487 self.power_params.fit(first_pass);
488
489 if self.auto_select {
490 self.select_best_model(first_pass);
491 }
492 }
493
494 fn select_best_model(&mut self, first_pass: &FirstPassStats) {
496 let mut errors = [0.0_f64; 3];
497
498 for frame in &first_pass.frames {
499 if frame.predicted_bits == 0 {
500 continue;
501 }
502
503 let actual = frame.predicted_bits as f64;
504 let qp = 28.0; errors[0] += (actual - self.linear_params.predict(frame.combined_complexity, qp)).abs();
507 errors[1] += (actual - self.quad_params.predict(frame.combined_complexity, qp)).abs();
508 errors[2] += (actual - self.power_params.predict(frame.combined_complexity, qp)).abs();
509 }
510
511 self.model_type = if errors[0] <= errors[1] && errors[0] <= errors[2] {
513 0
514 } else if errors[1] <= errors[2] {
515 1
516 } else {
517 2
518 };
519 }
520}
521
522#[derive(Clone, Debug)]
524struct LinearModel {
525 a: f64,
526 b: f64,
527 c: f64,
528}
529
530impl Default for LinearModel {
531 fn default() -> Self {
532 Self {
533 a: 100_000.0,
534 b: -5_000.0,
535 c: 50_000.0,
536 }
537 }
538}
539
540impl LinearModel {
541 fn predict(&self, complexity: f32, qp: f32) -> f64 {
542 self.a * complexity as f64 + self.b * qp as f64 + self.c
543 }
544
545 fn fit(&mut self, first_pass: &FirstPassStats) {
546 let n = first_pass.frames.len() as f64;
548 if n < 3.0 {
549 return;
550 }
551
552 let mut sum_c = 0.0;
553 let mut sum_bits = 0.0;
554
555 for frame in &first_pass.frames {
556 if frame.predicted_bits > 0 {
557 sum_c += frame.combined_complexity as f64;
558 sum_bits += frame.predicted_bits as f64;
559 }
560 }
561
562 let avg_c = sum_c / n;
563 let avg_bits = sum_bits / n;
564
565 if avg_c > 0.0 {
567 self.a = avg_bits / avg_c;
568 self.c = avg_bits * 0.2;
569 }
570 }
571}
572
573#[derive(Clone, Debug)]
575struct QuadraticModel {
576 a: f64,
577 b: f64,
578 c: f64,
579 d: f64,
580}
581
582impl Default for QuadraticModel {
583 fn default() -> Self {
584 Self {
585 a: 10_000.0,
586 b: 80_000.0,
587 c: -5_000.0,
588 d: 50_000.0,
589 }
590 }
591}
592
593impl QuadraticModel {
594 fn predict(&self, complexity: f32, qp: f32) -> f64 {
595 let c = complexity as f64;
596 self.a * c * c + self.b * c + self.c * qp as f64 + self.d
597 }
598
599 fn fit(&mut self, first_pass: &FirstPassStats) {
600 let _ = first_pass;
602 }
604}
605
606#[derive(Clone, Debug)]
608struct PowerModel {
609 a: f64,
610 b: f64,
611 c: f64,
612}
613
614impl Default for PowerModel {
615 fn default() -> Self {
616 Self {
617 a: 150_000.0,
618 b: 1.1,
619 c: 0.8,
620 }
621 }
622}
623
624impl PowerModel {
625 fn predict(&self, complexity: f32, qp: f32) -> f64 {
626 if complexity <= 0.0 || qp <= 0.0 {
627 return 50_000.0;
628 }
629 self.a * (complexity as f64).powf(self.b) / (qp as f64).powf(self.c)
630 }
631
632 fn fit(&mut self, first_pass: &FirstPassStats) {
633 let _ = first_pass;
635 }
637}
638
639#[derive(Clone, Debug)]
641struct CurveCompressionState {
642 enabled: bool,
644 ratio: f32,
646 adjustment_factor: f32,
648}
649
650impl Default for CurveCompressionState {
651 fn default() -> Self {
652 Self {
653 enabled: true,
654 ratio: 0.6,
655 adjustment_factor: 1.0,
656 }
657 }
658}
659
660impl CurveCompressionState {
661 fn compress(&self, complexity: f32, avg_complexity: f32) -> f32 {
663 if !self.enabled || avg_complexity <= 0.0 {
664 return complexity;
665 }
666
667 let ratio = complexity / avg_complexity;
668 let compressed_ratio = if ratio > 1.0 {
669 1.0 + (ratio - 1.0) * self.ratio
671 } else {
672 1.0 - (1.0 - ratio) * self.ratio
674 };
675
676 avg_complexity * compressed_ratio * self.adjustment_factor
677 }
678
679 fn update(&mut self, target_bitrate: f64, actual_bitrate: f64) {
681 if target_bitrate <= 0.0 {
682 return;
683 }
684
685 let error = (actual_bitrate - target_bitrate) / target_bitrate;
686
687 let adjustment = 1.0 + (error * 0.05) as f32;
689 self.adjustment_factor = (self.adjustment_factor * adjustment).clamp(0.5, 2.0);
690 }
691}
692
693impl AbrController {
694 #[must_use]
696 pub fn new(config: &RcConfig) -> Self {
697 Self {
698 target_bitrate: config.target_bitrate,
699 target_file_size: None,
700 total_frames: None,
701 pass: 1,
702 framerate: config.framerate(),
703 current_qp: config.initial_qp as f32,
704 min_qp: config.min_qp,
705 max_qp: config.max_qp,
706 i_qp_offset: config.i_qp_offset,
707 b_qp_offset: config.b_qp_offset,
708 frame_count: 0,
709 total_bits: 0,
710 first_pass_stats: None,
711 allocator: MultiPassAllocator::default(),
712 lookahead: LookaheadAnalyzer::default(),
713 rc_model: RateControlModel::default(),
714 curve_state: CurveCompressionState::default(),
715 current_gop: GopStats::new(0, 0),
716 gop_history: Vec::new(),
717 }
718 }
719
720 pub fn set_target_file_size(&mut self, size_bytes: u64, duration_seconds: f64) {
722 self.target_file_size = Some(size_bytes);
723 if duration_seconds > 0.0 {
724 self.target_bitrate = ((size_bytes * 8) as f64 / duration_seconds) as u64;
726 self.total_frames = Some((duration_seconds * self.framerate) as u64);
727 }
728 }
729
730 pub fn set_pass(&mut self, pass: u8) {
732 self.pass = pass.clamp(1, 2);
733 if pass == 1 {
734 self.first_pass_stats = Some(FirstPassStats::default());
735 }
736 }
737
738 pub fn set_total_frames(&mut self, count: u64) {
740 self.total_frames = Some(count);
741 }
742
743 pub fn set_first_pass_stats(&mut self, stats: FirstPassStats) {
745 self.rc_model.fit(&stats);
747
748 if let Some(total_frames) = self.total_frames {
750 let duration = total_frames as f64 / self.framerate;
751 let total_bits = (self.target_bitrate as f64 * duration) as u64;
752 self.allocator.initialize(total_bits, total_frames);
753 }
754
755 self.first_pass_stats = Some(stats);
756 }
757
758 pub fn push_lookahead(&mut self, complexity: f32) {
760 self.lookahead.push_complexity(complexity);
761 }
762
763 #[must_use]
765 pub fn get_rc(&mut self, frame_type: FrameType, complexity: f32) -> RcOutput {
766 let target_bits = if self.pass == 2 && self.first_pass_stats.is_some() {
767 self.calculate_second_pass_bits(frame_type, complexity)
769 } else {
770 self.calculate_first_pass_bits(frame_type, complexity)
772 };
773
774 let qp = self.calculate_qp(frame_type, complexity, target_bits);
776
777 let lambda = self.calculate_lambda(qp, frame_type);
779 let lambda_me = lambda.sqrt();
780
781 RcOutput {
782 qp,
783 qp_f: qp as f32,
784 target_bits,
785 min_bits: target_bits / 4,
786 max_bits: target_bits * 4,
787 lambda,
788 lambda_me,
789 ..Default::default()
790 }
791 }
792
793 fn calculate_first_pass_bits(&self, frame_type: FrameType, complexity: f32) -> u64 {
795 let base_bits = if self.framerate > 0.0 {
796 (self.target_bitrate as f64 / self.framerate) as u64
797 } else {
798 100_000
799 };
800
801 let type_mult = match frame_type {
802 FrameType::Key => 3.0,
803 FrameType::Inter => 1.0,
804 FrameType::BiDir => 0.5,
805 FrameType::Switch => 1.5,
806 };
807
808 let complexity_mult = if complexity > 0.5 {
809 complexity.clamp(0.5, 2.0)
810 } else {
811 1.0
812 };
813
814 (base_bits as f64 * type_mult * complexity_mult as f64) as u64
815 }
816
817 fn calculate_second_pass_bits(&mut self, frame_type: FrameType, complexity: f32) -> u64 {
819 if let Some(ref stats) = self.first_pass_stats {
820 if let Some(frame_stats) = stats.get_frame(self.frame_count) {
822 if frame_stats.allocated_bits > 0 {
824 return frame_stats.allocated_bits;
825 }
826 }
827
828 let avg_complexity = stats.avg_complexity as f32;
830 let compressed_complexity = self.curve_state.compress(complexity, avg_complexity);
831
832 self.allocator.allocate_frame_bits(
833 frame_type,
834 compressed_complexity,
835 avg_complexity,
836 stats,
837 )
838 } else {
839 self.calculate_first_pass_bits(frame_type, complexity)
840 }
841 }
842
843 fn calculate_qp(&self, frame_type: FrameType, complexity: f32, target_bits: u64) -> u8 {
845 let base_qp = if target_bits > 0 {
847 let model_prediction = self.rc_model.predict(complexity, 28.0_f32) as f64;
849 let ratio = target_bits as f64 / model_prediction;
850
851 if ratio > 1.0 {
853 28.0_f64 - (ratio.ln() * 3.0)
855 } else {
856 28.0_f64 + ((1.0_f64 / ratio).ln() * 3.0)
858 }
859 } else {
860 28.0_f64
861 };
862
863 let offset = match frame_type {
865 FrameType::Key => self.i_qp_offset,
866 FrameType::BiDir => self.b_qp_offset,
867 _ => 0,
868 };
869
870 let final_qp = (base_qp + offset as f64).clamp(self.min_qp as f64, self.max_qp as f64);
871 final_qp as u8
872 }
873
874 fn calculate_lambda(&self, qp: u8, frame_type: FrameType) -> f64 {
876 let base = 0.85 * 2.0_f64.powf((f64::from(qp) - 12.0) / 3.0);
877
878 let multiplier = match frame_type {
879 FrameType::Key => 0.6,
880 FrameType::BiDir => 1.4,
881 _ => 1.0,
882 };
883
884 base * multiplier
885 }
886
887 pub fn update(&mut self, stats: &FrameStats) {
889 self.frame_count += 1;
890 self.total_bits += stats.bits;
891
892 if self.pass == 2 {
894 self.allocator.update(stats.bits);
895 }
896
897 if self.frame_count > 10 {
899 let elapsed = self.frame_count as f64 / self.framerate;
900 let actual_bitrate = self.total_bits as f64 / elapsed;
901 self.curve_state
902 .update(self.target_bitrate as f64, actual_bitrate);
903 }
904
905 self.current_gop.add_frame(stats.clone());
907 if stats.frame_type == FrameType::Key && self.current_gop.frame_count > 1 {
908 self.gop_history.push(self.current_gop.clone());
909 let next_idx = self.current_gop.gop_index + 1;
910 self.current_gop = GopStats::new(next_idx, self.frame_count);
911 }
912
913 if self.pass == 1 {
915 if let Some(ref mut pass_stats) = self.first_pass_stats {
916 let frame_stats = FramePassStats {
917 frame_index: stats.frame_num,
918 frame_type: stats.frame_type,
919 spatial_complexity: stats.spatial_complexity,
920 temporal_complexity: stats.temporal_complexity,
921 combined_complexity: stats.complexity,
922 is_scene_change: stats.scene_cut,
923 intra_blocks: 0,
924 inter_blocks: 0,
925 avg_motion: 0.0,
926 predicted_bits: stats.bits,
927 allocated_bits: 0,
928 };
929 pass_stats.add_frame(frame_stats);
930 }
931 }
932 }
933
934 #[must_use]
936 pub fn finalize_first_pass(&mut self) -> Option<FirstPassStats> {
937 if self.pass != 1 {
938 return None;
939 }
940
941 if let Some(ref mut stats) = self.first_pass_stats {
942 stats.finalize();
943
944 if let Some(total_frames) = self.total_frames {
946 let duration = total_frames as f64 / self.framerate;
947 let total_bits = (self.target_bitrate as f64 * duration) as u64;
948
949 for frame in &mut stats.frames {
950 let complexity_ratio = if stats.total_complexity > 0.0 {
952 frame.combined_complexity as f64 / stats.avg_complexity
953 } else {
954 1.0
955 };
956
957 frame.allocated_bits =
958 (total_bits as f64 / total_frames as f64 * complexity_ratio) as u64;
959 }
960 }
961 }
962
963 self.first_pass_stats.take()
964 }
965
966 #[must_use]
968 pub fn current_bitrate(&self) -> f64 {
969 if self.frame_count == 0 || self.framerate <= 0.0 {
970 return 0.0;
971 }
972 let elapsed = self.frame_count as f64 / self.framerate;
973 self.total_bits as f64 / elapsed
974 }
975
976 #[must_use]
978 pub fn target_bitrate(&self) -> u64 {
979 self.target_bitrate
980 }
981
982 #[must_use]
984 pub fn frame_count(&self) -> u64 {
985 self.frame_count
986 }
987
988 #[must_use]
990 pub fn total_bits(&self) -> u64 {
991 self.total_bits
992 }
993
994 #[must_use]
996 pub fn current_qp(&self) -> f32 {
997 self.current_qp
998 }
999
1000 #[must_use]
1002 pub fn projected_file_size(&self) -> Option<u64> {
1003 if let Some(total_frames) = self.total_frames {
1004 if self.frame_count > 0 && self.frame_count < total_frames {
1005 let bits_per_frame = self.total_bits as f64 / self.frame_count as f64;
1006 let projected_bits = bits_per_frame * total_frames as f64;
1007 Some((projected_bits / 8.0) as u64)
1008 } else {
1009 Some(self.total_bits / 8)
1010 }
1011 } else {
1012 None
1013 }
1014 }
1015
1016 #[must_use]
1018 pub fn bitrate_deviation(&self) -> f64 {
1019 let current = self.current_bitrate();
1020 if self.target_bitrate > 0 {
1021 (current - self.target_bitrate as f64) / self.target_bitrate as f64
1022 } else {
1023 0.0
1024 }
1025 }
1026
1027 pub fn reset(&mut self) {
1029 self.frame_count = 0;
1030 self.total_bits = 0;
1031 self.current_gop = GopStats::new(0, 0);
1032 self.gop_history.clear();
1033 self.lookahead.complexity_buffer.clear();
1034 }
1035}
1036
1037impl Default for AbrController {
1038 fn default() -> Self {
1039 Self {
1040 target_bitrate: 5_000_000,
1041 target_file_size: None,
1042 total_frames: None,
1043 pass: 1,
1044 framerate: 30.0,
1045 current_qp: 28.0,
1046 min_qp: 1,
1047 max_qp: 63,
1048 i_qp_offset: -2,
1049 b_qp_offset: 2,
1050 frame_count: 0,
1051 total_bits: 0,
1052 first_pass_stats: None,
1053 allocator: MultiPassAllocator::default(),
1054 lookahead: LookaheadAnalyzer::default(),
1055 rc_model: RateControlModel::default(),
1056 curve_state: CurveCompressionState::default(),
1057 current_gop: GopStats::new(0, 0),
1058 gop_history: Vec::new(),
1059 }
1060 }
1061}
1062
1063#[cfg(test)]
1064mod tests {
1065 use super::*;
1066
1067 #[test]
1068 fn test_abr_creation() {
1069 let config = RcConfig::default();
1070 let controller = AbrController::new(&config);
1071 assert_eq!(controller.target_bitrate(), 5_000_000);
1072 assert_eq!(controller.frame_count(), 0);
1073 }
1074
1075 #[test]
1076 fn test_first_pass_collection() {
1077 let config = RcConfig::default();
1078 let mut controller = AbrController::new(&config);
1079 controller.set_pass(1);
1080
1081 for i in 0..30 {
1082 let frame_type = if i % 10 == 0 {
1083 FrameType::Key
1084 } else {
1085 FrameType::Inter
1086 };
1087
1088 let output = controller.get_rc(frame_type, 1.0);
1089
1090 let mut stats = FrameStats::new(i, frame_type);
1091 stats.bits = output.target_bits;
1092 stats.complexity = 1.0;
1093 controller.update(&stats);
1094 }
1095
1096 let first_pass = controller.finalize_first_pass().expect("should succeed");
1097 assert_eq!(first_pass.frame_count, 30);
1098 }
1099
1100 #[test]
1101 fn test_two_pass_encoding() {
1102 let config = RcConfig::default();
1103 let mut controller = AbrController::new(&config);
1104 controller.set_total_frames(60);
1105 controller.set_pass(1);
1106
1107 for i in 0..30 {
1109 let frame_type = if i % 10 == 0 {
1110 FrameType::Key
1111 } else {
1112 FrameType::Inter
1113 };
1114 let _output = controller.get_rc(frame_type, 1.0 + (i as f32 % 3.0) * 0.1);
1115
1116 let mut stats = FrameStats::new(i, frame_type);
1117 stats.bits = 100_000;
1118 stats.complexity = 1.0;
1119 controller.update(&stats);
1120 }
1121
1122 let first_pass_stats = controller.finalize_first_pass().expect("should succeed");
1123
1124 let mut controller2 = AbrController::new(&config);
1126 controller2.set_total_frames(60);
1127 controller2.set_pass(2);
1128 controller2.set_first_pass_stats(first_pass_stats);
1129
1130 let output = controller2.get_rc(FrameType::Key, 1.0);
1131 assert!(output.target_bits > 0);
1132 }
1133
1134 #[test]
1135 fn test_target_file_size() {
1136 let config = RcConfig::default();
1137 let mut controller = AbrController::new(&config);
1138
1139 let target_size = 10_000_000; let duration = 60.0; controller.set_target_file_size(target_size, duration);
1142
1143 assert!(controller.target_bitrate() > 0);
1145 }
1146
1147 #[test]
1148 fn test_bit_allocation() {
1149 let mut allocator = MultiPassAllocator::default();
1150 allocator.initialize(10_000_000, 100);
1151
1152 let mut stats = FirstPassStats::default();
1153 for i in 0..10 {
1154 let frame_stats = FramePassStats {
1155 frame_index: i,
1156 combined_complexity: 1.0,
1157 ..Default::default()
1158 };
1159 stats.add_frame(frame_stats);
1160 }
1161 stats.finalize();
1162
1163 let bits = allocator.allocate_frame_bits(FrameType::Key, 1.5, 1.0, &stats);
1164 assert!(bits > 0);
1165 }
1166
1167 #[test]
1168 fn test_lookahead_analysis() {
1169 let mut lookahead = LookaheadAnalyzer::default();
1170
1171 for i in 0..20 {
1172 lookahead.push_complexity(1.0 + (i as f32 % 5.0) * 0.1);
1173 }
1174
1175 let analysis = lookahead.analyze();
1176 assert!(analysis.avg_complexity > 0.0);
1177 assert!(analysis.recommend_bits_multiplier > 0.0);
1178 }
1179
1180 #[test]
1181 fn test_scene_cut_detection() {
1182 let mut lookahead = LookaheadAnalyzer::default();
1183
1184 for _ in 0..5 {
1186 lookahead.push_complexity(1.0);
1187 }
1188
1189 lookahead.push_complexity(3.0);
1191
1192 let analysis = lookahead.analyze();
1193 assert!(analysis.has_scene_cut);
1194 }
1195
1196 #[test]
1197 fn test_curve_compression() {
1198 let curve = CurveCompressionState::default();
1199
1200 let high_complexity = 2.0;
1201 let low_complexity = 0.5;
1202 let avg = 1.0;
1203
1204 let compressed_high = curve.compress(high_complexity, avg);
1205 let compressed_low = curve.compress(low_complexity, avg);
1206
1207 assert!(compressed_high < high_complexity * avg);
1209 assert!(compressed_low > low_complexity * avg);
1211 }
1212
1213 #[test]
1214 fn test_rate_model_prediction() {
1215 let model = RateControlModel::default();
1216
1217 let bits = model.predict(1.5, 28.0);
1218 assert!(bits > 0);
1219
1220 let bits_high = model.predict(2.0, 28.0);
1222 assert!(bits_high > bits);
1223 }
1224
1225 #[test]
1226 fn test_linear_model() {
1227 let model = LinearModel::default();
1228
1229 let bits1 = model.predict(1.0, 28.0);
1230 let bits2 = model.predict(2.0, 28.0);
1231
1232 assert!(bits2 > bits1);
1234 }
1235
1236 #[test]
1237 fn test_quadratic_model() {
1238 let model = QuadraticModel::default();
1239
1240 let bits1 = model.predict(1.0, 28.0);
1241 let bits2 = model.predict(2.0, 28.0);
1242
1243 assert!(bits2 > bits1);
1244 }
1245
1246 #[test]
1247 fn test_power_model() {
1248 let model = PowerModel::default();
1249
1250 let bits1 = model.predict(1.0, 28.0);
1251 let bits2 = model.predict(2.0, 28.0);
1252
1253 assert!(bits2 > bits1);
1254 }
1255
1256 #[test]
1257 fn test_projected_file_size() {
1258 let config = RcConfig::default();
1259 let mut controller = AbrController::new(&config);
1260 controller.set_total_frames(100);
1261
1262 for i in 0..25 {
1264 let mut stats = FrameStats::new(i, FrameType::Inter);
1265 stats.bits = 100_000;
1266 controller.update(&stats);
1267 }
1268
1269 let projected = controller.projected_file_size();
1270 assert!(projected.is_some());
1271 assert!(projected.expect("should succeed") > 0);
1272 }
1273
1274 #[test]
1275 fn test_bitrate_deviation() {
1276 let config = RcConfig::default();
1277 let mut controller = AbrController::new(&config);
1278
1279 for i in 0..30 {
1280 let mut stats = FrameStats::new(i, FrameType::Inter);
1281 stats.bits = 200_000; controller.update(&stats);
1283 }
1284
1285 let deviation = controller.bitrate_deviation();
1286 assert!(deviation > 0.0);
1288 }
1289
1290 #[test]
1291 fn test_reset() {
1292 let config = RcConfig::default();
1293 let mut controller = AbrController::new(&config);
1294
1295 for i in 0..10 {
1296 let mut stats = FrameStats::new(i, FrameType::Inter);
1297 stats.bits = 100_000;
1298 controller.update(&stats);
1299 }
1300
1301 controller.reset();
1302 assert_eq!(controller.frame_count(), 0);
1303 assert_eq!(controller.total_bits(), 0);
1304 }
1305}