1#![allow(clippy::cast_lossless)]
24#![allow(clippy::cast_precision_loss)]
25#![allow(clippy::cast_possible_truncation)]
26#![allow(clippy::cast_sign_loss)]
27#![allow(clippy::similar_names)]
28#![allow(clippy::too_many_arguments)]
29#![allow(clippy::struct_excessive_bools)]
30#![forbid(unsafe_code)]
31
32use crate::frame::FrameType;
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum AllocationStrategy {
37 Uniform,
39 ComplexityBased,
41 MultiPass,
43 Hierarchical,
45 Constrained,
47 Adaptive,
49}
50
51impl Default for AllocationStrategy {
52 fn default() -> Self {
53 Self::ComplexityBased
54 }
55}
56
57#[derive(Clone, Debug)]
59pub struct BitrateAllocator {
60 strategy: AllocationStrategy,
62 target_bitrate: u64,
64 framerate: f64,
66 gop_length: u32,
68 i_p_ratio: f32,
70 p_b_ratio: f32,
72 complexity_weight: f32,
74 complexity_history: Vec<f32>,
76 bit_usage_history: Vec<u64>,
78 max_history: usize,
80 vbv_buffer_size: Option<u64>,
82 bit_reservoir: i64,
84 max_reservoir: i64,
86 current_gop: u32,
88 frames_in_gop: u32,
90 bits_used_in_gop: u64,
92 gop_target_bits: u64,
94}
95
96impl BitrateAllocator {
97 #[must_use]
99 pub fn new(target_bitrate: u64, framerate: f64, gop_length: u32) -> Self {
100 let max_reservoir = (target_bitrate as i64) * 2;
101
102 Self {
103 strategy: AllocationStrategy::default(),
104 target_bitrate,
105 framerate,
106 gop_length,
107 i_p_ratio: 3.0,
108 p_b_ratio: 2.0,
109 complexity_weight: 1.0,
110 complexity_history: Vec::new(),
111 bit_usage_history: Vec::new(),
112 max_history: 100,
113 vbv_buffer_size: None,
114 bit_reservoir: 0,
115 max_reservoir,
116 current_gop: 0,
117 frames_in_gop: 0,
118 bits_used_in_gop: 0,
119 gop_target_bits: 0,
120 }
121 }
122
123 pub fn set_strategy(&mut self, strategy: AllocationStrategy) {
125 self.strategy = strategy;
126 }
127
128 pub fn set_i_p_ratio(&mut self, ratio: f32) {
130 self.i_p_ratio = ratio.max(1.0);
131 }
132
133 pub fn set_p_b_ratio(&mut self, ratio: f32) {
135 self.p_b_ratio = ratio.max(1.0);
136 }
137
138 pub fn set_complexity_weight(&mut self, weight: f32) {
140 self.complexity_weight = weight.clamp(0.0, 2.0);
141 }
142
143 pub fn set_vbv_buffer_size(&mut self, size: u64) {
145 self.vbv_buffer_size = Some(size);
146 if self.strategy == AllocationStrategy::Uniform {
147 self.strategy = AllocationStrategy::Constrained;
148 }
149 }
150
151 #[must_use]
153 pub fn allocate_frame_bits(
154 &self,
155 frame_type: FrameType,
156 complexity: f32,
157 frame_in_gop: u32,
158 ) -> AllocationResult {
159 let base_bits = self.calculate_base_allocation();
160
161 let allocated_bits = match self.strategy {
162 AllocationStrategy::Uniform => self.allocate_uniform(base_bits, frame_type),
163 AllocationStrategy::ComplexityBased => {
164 self.allocate_complexity_based(base_bits, frame_type, complexity)
165 }
166 AllocationStrategy::MultiPass => {
167 self.allocate_multipass(base_bits, frame_type, complexity)
168 }
169 AllocationStrategy::Hierarchical => {
170 self.allocate_hierarchical(base_bits, frame_type, frame_in_gop)
171 }
172 AllocationStrategy::Constrained => {
173 self.allocate_constrained(base_bits, frame_type, complexity)
174 }
175 AllocationStrategy::Adaptive => {
176 self.allocate_adaptive(base_bits, frame_type, complexity)
177 }
178 };
179
180 let mut max_bits = allocated_bits * 4;
181
182 if let Some(vbv_size) = self.vbv_buffer_size {
184 max_bits = max_bits.min(vbv_size);
185 }
186
187 AllocationResult {
188 target_bits: allocated_bits,
189 min_bits: allocated_bits / 4,
190 max_bits,
191 frame_type,
192 complexity_factor: complexity / self.average_complexity(),
193 reservoir_adjustment: self.calculate_reservoir_adjustment(allocated_bits),
194 }
195 }
196
197 fn calculate_base_allocation(&self) -> u64 {
199 if self.framerate <= 0.0 {
200 return 0;
201 }
202 (self.target_bitrate as f64 / self.framerate) as u64
203 }
204
205 fn allocate_uniform(&self, base_bits: u64, frame_type: FrameType) -> u64 {
207 match frame_type {
208 FrameType::Key => (base_bits as f32 * self.i_p_ratio) as u64,
209 FrameType::Inter => base_bits,
210 FrameType::BiDir => (base_bits as f32 / self.p_b_ratio) as u64,
211 FrameType::Switch => (base_bits as f32 * 1.5) as u64,
212 }
213 }
214
215 fn allocate_complexity_based(
217 &self,
218 base_bits: u64,
219 frame_type: FrameType,
220 complexity: f32,
221 ) -> u64 {
222 let type_bits = self.allocate_uniform(base_bits, frame_type);
224
225 let avg_complexity = self.average_complexity();
227 if avg_complexity <= 0.0 {
228 return type_bits;
229 }
230
231 let complexity_ratio = complexity / avg_complexity;
232 let complexity_multiplier = 1.0 + (complexity_ratio - 1.0) * self.complexity_weight;
233
234 (type_bits as f32 * complexity_multiplier.clamp(0.5, 2.0)) as u64
235 }
236
237 fn allocate_multipass(&self, base_bits: u64, frame_type: FrameType, complexity: f32) -> u64 {
239 if self.complexity_history.is_empty() {
241 return self.allocate_complexity_based(base_bits, frame_type, complexity);
242 }
243
244 let total_complexity: f32 = self.complexity_history.iter().sum();
246 if total_complexity <= 0.0 {
247 return self.allocate_complexity_based(base_bits, frame_type, complexity);
248 }
249
250 let gop_bits = base_bits * self.gop_length as u64;
252
253 let frame_proportion = complexity / total_complexity;
255 let mut allocated = (gop_bits as f32 * frame_proportion) as u64;
256
257 allocated = match frame_type {
259 FrameType::Key => (allocated as f32 * self.i_p_ratio) as u64,
260 FrameType::BiDir => (allocated as f32 / self.p_b_ratio) as u64,
261 _ => allocated,
262 };
263
264 allocated.max(base_bits / 4)
265 }
266
267 fn allocate_hierarchical(
269 &self,
270 base_bits: u64,
271 frame_type: FrameType,
272 frame_in_gop: u32,
273 ) -> u64 {
274 let type_bits = self.allocate_uniform(base_bits, frame_type);
275
276 match frame_type {
277 FrameType::BiDir => {
278 let pyramid_level = self.calculate_pyramid_level(frame_in_gop);
280 let level_multiplier = 1.0 + (pyramid_level as f32 * 0.2);
281 (type_bits as f32 * level_multiplier) as u64
282 }
283 _ => type_bits,
284 }
285 }
286
287 fn calculate_pyramid_level(&self, frame_in_gop: u32) -> u32 {
289 let mut level = 0;
293 let mut pos = frame_in_gop;
294
295 while pos % 2 == 0 && pos > 0 {
296 level += 1;
297 pos /= 2;
298 }
299
300 level
301 }
302
303 fn allocate_constrained(&self, base_bits: u64, frame_type: FrameType, complexity: f32) -> u64 {
305 let mut allocated = self.allocate_complexity_based(base_bits, frame_type, complexity);
306
307 if let Some(vbv_size) = self.vbv_buffer_size {
309 let buffer_level = self.estimate_buffer_level();
311
312 let fullness_ratio = buffer_level as f32 / vbv_size as f32;
314 if fullness_ratio > 0.8 {
315 let reduction = (fullness_ratio - 0.8) * 5.0; allocated = (allocated as f32 * (1.0 - reduction).max(0.5)) as u64;
317 }
318
319 allocated = allocated.min(vbv_size);
321 }
322
323 allocated
324 }
325
326 fn allocate_adaptive(&self, base_bits: u64, frame_type: FrameType, complexity: f32) -> u64 {
328 let mut allocated = self.allocate_complexity_based(base_bits, frame_type, complexity);
329
330 if !self.bit_usage_history.is_empty() {
332 let recent_usage: u64 = self.bit_usage_history.iter().rev().take(10).sum();
333 let recent_target = base_bits * 10.min(self.bit_usage_history.len()) as u64;
334
335 if recent_target > 0 {
336 let usage_ratio = recent_usage as f32 / recent_target as f32;
337
338 if usage_ratio > 1.2 {
341 allocated = (allocated as f32 * 0.9) as u64;
342 } else if usage_ratio < 0.8 {
343 allocated = (allocated as f32 * 1.1) as u64;
344 }
345 }
346 }
347
348 let reservoir_adjustment = self.calculate_reservoir_adjustment(allocated);
350 ((allocated as i64) + reservoir_adjustment).max(base_bits as i64 / 4) as u64
351 }
352
353 fn calculate_reservoir_adjustment(&self, target: u64) -> i64 {
355 if self.bit_reservoir == 0 {
356 return 0;
357 }
358
359 let reservoir_ratio = self.bit_reservoir as f32 / self.max_reservoir as f32;
360
361 let adjustment = (target as f32 * reservoir_ratio * 0.1) as i64;
363
364 adjustment.clamp(-(target as i64 / 4), target as i64 / 4)
366 }
367
368 fn estimate_buffer_level(&self) -> u64 {
370 if let Some(vbv_size) = self.vbv_buffer_size {
372 let bits_per_frame = self.calculate_base_allocation();
373 let recent_frames = 10.min(self.bit_usage_history.len());
374
375 if recent_frames == 0 {
376 return vbv_size / 2; }
378
379 let recent_bits: u64 = self
380 .bit_usage_history
381 .iter()
382 .rev()
383 .take(recent_frames)
384 .sum();
385 let target_bits = bits_per_frame * recent_frames as u64;
386
387 if recent_bits < target_bits {
389 let saved = target_bits - recent_bits;
390 (vbv_size / 2 + saved).min(vbv_size)
391 } else {
392 let overage = recent_bits - target_bits;
393 (vbv_size / 2).saturating_sub(overage)
394 }
395 } else {
396 0
397 }
398 }
399
400 fn average_complexity(&self) -> f32 {
402 if self.complexity_history.is_empty() {
403 return 1.0;
404 }
405
406 let sum: f32 = self.complexity_history.iter().sum();
407 (sum / self.complexity_history.len() as f32).max(0.01)
408 }
409
410 pub fn update(&mut self, complexity: f32, bits_used: u64) {
412 self.complexity_history.push(complexity);
414 if self.complexity_history.len() > self.max_history {
415 self.complexity_history.remove(0);
416 }
417
418 self.bit_usage_history.push(bits_used);
420 if self.bit_usage_history.len() > self.max_history {
421 self.bit_usage_history.remove(0);
422 }
423
424 let target_per_frame = self.calculate_base_allocation();
426 self.bit_reservoir += target_per_frame as i64 - bits_used as i64;
427 self.bit_reservoir = self
428 .bit_reservoir
429 .clamp(-self.max_reservoir, self.max_reservoir);
430
431 self.frames_in_gop += 1;
433 self.bits_used_in_gop += bits_used;
434 }
435
436 pub fn start_new_gop(&mut self) {
438 self.current_gop += 1;
439 self.frames_in_gop = 0;
440 self.bits_used_in_gop = 0;
441 self.gop_target_bits = self.calculate_base_allocation() * self.gop_length as u64;
442 }
443
444 #[must_use]
446 pub fn gop_status(&self) -> GopAllocationStatus {
447 let remaining_frames = self.gop_length.saturating_sub(self.frames_in_gop);
448 let remaining_bits = self.gop_target_bits.saturating_sub(self.bits_used_in_gop);
449
450 GopAllocationStatus {
451 gop_index: self.current_gop,
452 frames_encoded: self.frames_in_gop,
453 frames_remaining: remaining_frames,
454 bits_used: self.bits_used_in_gop,
455 bits_remaining: remaining_bits,
456 target_bits: self.gop_target_bits,
457 on_target: self.is_gop_on_target(),
458 }
459 }
460
461 fn is_gop_on_target(&self) -> bool {
463 if self.frames_in_gop == 0 {
464 return true;
465 }
466
467 let expected_bits = (self.gop_target_bits as f32
468 * (self.frames_in_gop as f32 / self.gop_length as f32))
469 as u64;
470
471 let accuracy = self.bits_used_in_gop as f32 / expected_bits as f32;
472 accuracy > 0.8 && accuracy < 1.2
473 }
474
475 pub fn reset(&mut self) {
477 self.complexity_history.clear();
478 self.bit_usage_history.clear();
479 self.bit_reservoir = 0;
480 self.current_gop = 0;
481 self.frames_in_gop = 0;
482 self.bits_used_in_gop = 0;
483 self.gop_target_bits = 0;
484 }
485}
486
487#[derive(Clone, Debug)]
489pub struct AllocationResult {
490 pub target_bits: u64,
492 pub min_bits: u64,
494 pub max_bits: u64,
496 pub frame_type: FrameType,
498 pub complexity_factor: f32,
500 pub reservoir_adjustment: i64,
502}
503
504impl AllocationResult {
505 #[must_use]
507 pub fn is_within_range(&self, actual_bits: u64) -> bool {
508 actual_bits >= self.min_bits && actual_bits <= self.max_bits
509 }
510
511 #[must_use]
513 pub fn accuracy(&self, actual_bits: u64) -> f32 {
514 if self.target_bits == 0 {
515 return 1.0;
516 }
517 actual_bits as f32 / self.target_bits as f32
518 }
519}
520
521#[derive(Clone, Debug)]
523pub struct GopAllocationStatus {
524 pub gop_index: u32,
526 pub frames_encoded: u32,
528 pub frames_remaining: u32,
530 pub bits_used: u64,
532 pub bits_remaining: u64,
534 pub target_bits: u64,
536 pub on_target: bool,
538}
539
540impl GopAllocationStatus {
541 #[must_use]
543 pub fn average_bits_per_frame(&self) -> u64 {
544 if self.frames_encoded == 0 {
545 return 0;
546 }
547 self.bits_used / self.frames_encoded as u64
548 }
549
550 #[must_use]
552 pub fn recommended_bits_per_frame(&self) -> u64 {
553 if self.frames_remaining == 0 {
554 return 0;
555 }
556 self.bits_remaining / self.frames_remaining as u64
557 }
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563
564 #[test]
565 fn test_allocator_creation() {
566 let allocator = BitrateAllocator::new(5_000_000, 30.0, 250);
567 assert_eq!(allocator.target_bitrate, 5_000_000);
568 assert_eq!(allocator.gop_length, 250);
569 }
570
571 #[test]
572 fn test_base_allocation() {
573 let allocator = BitrateAllocator::new(3_000_000, 30.0, 250);
574 let base = allocator.calculate_base_allocation();
575 assert_eq!(base, 100_000); }
577
578 #[test]
579 fn test_uniform_allocation() {
580 let allocator = BitrateAllocator::new(3_000_000, 30.0, 250);
581 let base = 100_000;
582
583 let i_bits = allocator.allocate_uniform(base, FrameType::Key);
584 let p_bits = allocator.allocate_uniform(base, FrameType::Inter);
585 let b_bits = allocator.allocate_uniform(base, FrameType::BiDir);
586
587 assert!(i_bits > p_bits); assert!(p_bits > b_bits); }
590
591 #[test]
592 fn test_complexity_based_allocation() {
593 let mut allocator = BitrateAllocator::new(3_000_000, 30.0, 250);
594
595 allocator.update(1.0, 100_000);
597 allocator.update(2.0, 150_000);
598 allocator.update(1.5, 125_000);
599
600 let result = allocator.allocate_frame_bits(FrameType::Inter, 2.0, 0);
601 let low_complexity_result = allocator.allocate_frame_bits(FrameType::Inter, 0.5, 0);
602
603 assert!(result.target_bits > low_complexity_result.target_bits);
604 }
605
606 #[test]
607 fn test_hierarchical_allocation() {
608 let allocator = BitrateAllocator::new(3_000_000, 30.0, 250);
609 let base = 100_000;
610
611 let level0 = allocator.allocate_hierarchical(base, FrameType::BiDir, 2);
613 let level1 = allocator.allocate_hierarchical(base, FrameType::BiDir, 4);
614
615 assert!(level1 >= level0);
617 }
618
619 #[test]
620 fn test_reservoir_management() {
621 let mut allocator = BitrateAllocator::new(3_000_000, 30.0, 250);
622
623 allocator.update(1.0, 80_000); assert!(allocator.bit_reservoir > 0);
626
627 allocator.update(1.0, 120_000);
629 assert!(allocator.bit_reservoir < 20_000);
630 }
631
632 #[test]
633 fn test_vbv_constraint() {
634 let mut allocator = BitrateAllocator::new(3_000_000, 30.0, 250);
635 allocator.set_vbv_buffer_size(1_000_000);
636 allocator.set_strategy(AllocationStrategy::Constrained);
637
638 let result = allocator.allocate_frame_bits(FrameType::Key, 5.0, 0);
639
640 assert!(result.max_bits <= 1_000_000);
642 }
643
644 #[test]
645 fn test_adaptive_allocation() {
646 let mut allocator = BitrateAllocator::new(3_000_000, 30.0, 250);
647 allocator.set_strategy(AllocationStrategy::Adaptive);
648
649 for _ in 0..10 {
651 allocator.update(1.0, 130_000); }
653
654 let result = allocator.allocate_frame_bits(FrameType::Inter, 1.0, 0);
655
656 assert!(result.target_bits < 100_000);
658 }
659
660 #[test]
661 fn test_gop_tracking() {
662 let mut allocator = BitrateAllocator::new(3_000_000, 30.0, 10);
663 allocator.start_new_gop();
664
665 for i in 0..5 {
666 allocator.update(1.0, 100_000);
667 let status = allocator.gop_status();
668 assert_eq!(status.frames_encoded, i + 1);
669 assert_eq!(status.frames_remaining, 10 - (i + 1));
670 }
671
672 let status = allocator.gop_status();
673 assert_eq!(status.frames_encoded, 5);
674 assert_eq!(status.bits_used, 500_000);
675 }
676
677 #[test]
678 fn test_allocation_result() {
679 let result = AllocationResult {
680 target_bits: 100_000,
681 min_bits: 25_000,
682 max_bits: 400_000,
683 frame_type: FrameType::Inter,
684 complexity_factor: 1.5,
685 reservoir_adjustment: 0,
686 };
687
688 assert!(result.is_within_range(100_000));
689 assert!(result.is_within_range(50_000));
690 assert!(!result.is_within_range(500_000));
691 assert!(!result.is_within_range(10_000));
692
693 let accuracy = result.accuracy(110_000);
694 assert!((accuracy - 1.1).abs() < 0.01);
695 }
696
697 #[test]
698 fn test_gop_status() {
699 let status = GopAllocationStatus {
700 gop_index: 1,
701 frames_encoded: 5,
702 frames_remaining: 5,
703 bits_used: 500_000,
704 bits_remaining: 500_000,
705 target_bits: 1_000_000,
706 on_target: true,
707 };
708
709 assert_eq!(status.average_bits_per_frame(), 100_000);
710 assert_eq!(status.recommended_bits_per_frame(), 100_000);
711 }
712
713 #[test]
714 fn test_strategy_switching() {
715 let mut allocator = BitrateAllocator::new(3_000_000, 30.0, 250);
716
717 allocator.set_strategy(AllocationStrategy::Uniform);
718 let uniform_result = allocator.allocate_frame_bits(FrameType::Inter, 2.0, 0);
719
720 allocator.set_strategy(AllocationStrategy::ComplexityBased);
721 allocator.update(1.0, 100_000);
722 let complexity_result = allocator.allocate_frame_bits(FrameType::Inter, 2.0, 0);
723
724 assert_ne!(uniform_result.target_bits, complexity_result.target_bits);
726 }
727
728 #[test]
729 fn test_reset() {
730 let mut allocator = BitrateAllocator::new(3_000_000, 30.0, 250);
731
732 allocator.update(1.0, 100_000);
733 allocator.update(2.0, 150_000);
734 assert!(!allocator.complexity_history.is_empty());
735
736 allocator.reset();
737
738 assert!(allocator.complexity_history.is_empty());
739 assert!(allocator.bit_usage_history.is_empty());
740 assert_eq!(allocator.bit_reservoir, 0);
741 }
742}