oximedia_codec/multipass/
allocation.rs1#![forbid(unsafe_code)]
8#![allow(clippy::cast_precision_loss)]
9#![allow(clippy::cast_possible_truncation)]
10#![allow(clippy::cast_sign_loss)]
11#![allow(clippy::cast_lossless)]
12#![allow(clippy::too_many_arguments)]
13
14use crate::frame::FrameType;
15use crate::multipass::stats::{FrameStatistics, PassStatistics};
16use crate::multipass::vbv::VbvBuffer;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum AllocationStrategy {
21 Uniform,
23 Complexity,
25 Perceptual,
27 TwoPass,
29}
30
31#[derive(Clone, Debug)]
33pub struct AllocationConfig {
34 pub strategy: AllocationStrategy,
36 pub target_bitrate: u64,
38 pub framerate_num: u32,
40 pub framerate_den: u32,
42 pub i_frame_boost: f64,
44 pub p_frame_factor: f64,
46 pub b_frame_factor: f64,
48 pub complexity_weight: f64,
50 pub temporal_weight: f64,
52}
53
54impl Default for AllocationConfig {
55 fn default() -> Self {
56 Self {
57 strategy: AllocationStrategy::Complexity,
58 target_bitrate: 5_000_000,
59 framerate_num: 30,
60 framerate_den: 1,
61 i_frame_boost: 3.0,
62 p_frame_factor: 1.0,
63 b_frame_factor: 0.5,
64 complexity_weight: 0.7,
65 temporal_weight: 0.3,
66 }
67 }
68}
69
70impl AllocationConfig {
71 #[must_use]
73 pub fn new(strategy: AllocationStrategy, target_bitrate: u64) -> Self {
74 Self {
75 strategy,
76 target_bitrate,
77 ..Default::default()
78 }
79 }
80
81 #[must_use]
83 pub fn with_framerate(mut self, num: u32, den: u32) -> Self {
84 self.framerate_num = num;
85 self.framerate_den = den;
86 self
87 }
88
89 #[must_use]
91 pub fn bits_per_frame(&self) -> f64 {
92 let fps = self.framerate_num as f64 / self.framerate_den as f64;
93 self.target_bitrate as f64 / fps
94 }
95}
96
97pub struct BitrateAllocator {
99 config: AllocationConfig,
100 first_pass_stats: Option<PassStatistics>,
101 total_allocated: u64,
102 frames_allocated: u64,
103}
104
105impl BitrateAllocator {
106 #[must_use]
108 pub fn new(config: AllocationConfig) -> Self {
109 Self {
110 config,
111 first_pass_stats: None,
112 total_allocated: 0,
113 frames_allocated: 0,
114 }
115 }
116
117 pub fn set_first_pass_stats(&mut self, stats: PassStatistics) {
119 self.first_pass_stats = Some(stats);
120 }
121
122 #[must_use]
124 pub fn allocate(
125 &mut self,
126 frame_index: u64,
127 frame_type: FrameType,
128 complexity: f64,
129 ) -> FrameAllocation {
130 let allocation = match self.config.strategy {
131 AllocationStrategy::Uniform => self.allocate_uniform(frame_type),
132 AllocationStrategy::Complexity => self.allocate_complexity(frame_type, complexity),
133 AllocationStrategy::Perceptual => self.allocate_perceptual(frame_type, complexity),
134 AllocationStrategy::TwoPass => {
135 if let Some(ref stats) = self.first_pass_stats {
136 self.allocate_two_pass(frame_index, frame_type, stats)
137 } else {
138 self.allocate_complexity(frame_type, complexity)
139 }
140 }
141 };
142
143 self.total_allocated += allocation.target_bits;
144 self.frames_allocated += 1;
145
146 allocation
147 }
148
149 fn allocate_uniform(&self, frame_type: FrameType) -> FrameAllocation {
151 let base_bits = self.config.bits_per_frame();
152 let type_factor = self.get_frame_type_factor(frame_type);
153 let target_bits = (base_bits * type_factor) as u64;
154
155 FrameAllocation {
156 target_bits,
157 min_bits: (target_bits as f64 * 0.5) as u64,
158 max_bits: (target_bits as f64 * 2.0) as u64,
159 qp_adjustment: 0.0,
160 }
161 }
162
163 fn allocate_complexity(&self, frame_type: FrameType, complexity: f64) -> FrameAllocation {
165 let base_bits = self.config.bits_per_frame();
166 let type_factor = self.get_frame_type_factor(frame_type);
167
168 let complexity_factor = 0.5 + complexity;
170 let target_bits = (base_bits * type_factor * complexity_factor) as u64;
171
172 FrameAllocation {
173 target_bits,
174 min_bits: (target_bits as f64 * 0.5) as u64,
175 max_bits: (target_bits as f64 * 2.5) as u64,
176 qp_adjustment: -((complexity - 0.5) * 10.0), }
178 }
179
180 fn allocate_perceptual(&self, frame_type: FrameType, complexity: f64) -> FrameAllocation {
182 let base_bits = self.config.bits_per_frame();
183 let type_factor = self.get_frame_type_factor(frame_type);
184
185 let perceptual_importance = match frame_type {
187 FrameType::Key => 2.0,
188 FrameType::Inter => 1.0 + complexity * 0.5,
189 FrameType::BiDir => 0.7,
190 FrameType::Switch => 1.5,
191 };
192
193 let target_bits = (base_bits * type_factor * perceptual_importance) as u64;
194
195 FrameAllocation {
196 target_bits,
197 min_bits: (target_bits as f64 * 0.6) as u64,
198 max_bits: (target_bits as f64 * 2.0) as u64,
199 qp_adjustment: -(perceptual_importance - 1.0) * 5.0,
200 }
201 }
202
203 fn allocate_two_pass(
205 &self,
206 frame_index: u64,
207 frame_type: FrameType,
208 stats: &PassStatistics,
209 ) -> FrameAllocation {
210 let frame_stats = stats.get_frame(frame_index);
212
213 if let Some(frame_stats) = frame_stats {
214 let total_bits_available = self.calculate_remaining_budget(stats);
216 let frames_remaining = (stats.total_frames - frame_index) as f64;
217
218 let weight = self.calculate_frame_weight(frame_stats, stats);
220 let total_weight = self.calculate_total_remaining_weight(frame_index, stats);
221
222 let target_bits = if total_weight > 0.0 {
223 ((total_bits_available as f64) * weight / total_weight) as u64
224 } else {
225 (total_bits_available as f64 / frames_remaining) as u64
226 };
227
228 let qp_adjustment = self.calculate_qp_adjustment(frame_stats, stats);
230
231 FrameAllocation {
232 target_bits,
233 min_bits: (target_bits as f64 * 0.5) as u64,
234 max_bits: (target_bits as f64 * 3.0) as u64,
235 qp_adjustment,
236 }
237 } else {
238 self.allocate_complexity(frame_type, 0.5)
240 }
241 }
242
243 fn calculate_remaining_budget(&self, stats: &PassStatistics) -> u64 {
245 let total_duration = stats.total_frames as f64
246 * (self.config.framerate_den as f64 / self.config.framerate_num as f64);
247 let total_bits = (self.config.target_bitrate as f64 * total_duration) as u64;
248
249 total_bits.saturating_sub(self.total_allocated)
250 }
251
252 fn calculate_frame_weight(&self, frame_stats: &FrameStatistics, stats: &PassStatistics) -> f64 {
254 let complexity_dist = stats.complexity_distribution();
256 let normalized_complexity = if complexity_dist.mean > 0.0 {
257 frame_stats.complexity.combined_complexity / complexity_dist.mean
258 } else {
259 1.0
260 };
261
262 let type_weight = match frame_stats.frame_type {
264 FrameType::Key => self.config.i_frame_boost,
265 FrameType::Inter => self.config.p_frame_factor,
266 FrameType::BiDir => self.config.b_frame_factor,
267 FrameType::Switch => 1.5,
268 };
269
270 self.config.complexity_weight * normalized_complexity
272 + (1.0 - self.config.complexity_weight) * type_weight
273 }
274
275 fn calculate_total_remaining_weight(&self, current_index: u64, stats: &PassStatistics) -> f64 {
277 stats
278 .frames
279 .iter()
280 .filter(|f| f.frame_index >= current_index)
281 .map(|f| self.calculate_frame_weight(f, stats))
282 .sum()
283 }
284
285 fn calculate_qp_adjustment(
287 &self,
288 frame_stats: &FrameStatistics,
289 stats: &PassStatistics,
290 ) -> f64 {
291 let avg_qp = stats.avg_qp;
292 let frame_qp = frame_stats.qp;
293
294 (avg_qp - frame_qp) * 0.5 }
297
298 fn get_frame_type_factor(&self, frame_type: FrameType) -> f64 {
300 match frame_type {
301 FrameType::Key => self.config.i_frame_boost,
302 FrameType::Inter => self.config.p_frame_factor,
303 FrameType::BiDir => self.config.b_frame_factor,
304 FrameType::Switch => 1.5,
305 }
306 }
307
308 #[must_use]
310 pub fn total_allocated(&self) -> u64 {
311 self.total_allocated
312 }
313
314 #[must_use]
316 pub fn frames_allocated(&self) -> u64 {
317 self.frames_allocated
318 }
319
320 pub fn reset(&mut self) {
322 self.total_allocated = 0;
323 self.frames_allocated = 0;
324 }
325}
326
327#[derive(Clone, Debug)]
329pub struct FrameAllocation {
330 pub target_bits: u64,
332 pub min_bits: u64,
334 pub max_bits: u64,
336 pub qp_adjustment: f64,
338}
339
340impl FrameAllocation {
341 #[must_use]
343 pub fn clamp_bits(&self, actual_bits: u64) -> u64 {
344 actual_bits.clamp(self.min_bits, self.max_bits)
345 }
346
347 #[must_use]
349 pub fn is_within_range(&self, actual_bits: u64) -> bool {
350 actual_bits >= self.min_bits && actual_bits <= self.max_bits
351 }
352
353 #[must_use]
355 pub fn error(&self, actual_bits: u64) -> i64 {
356 actual_bits as i64 - self.target_bits as i64
357 }
358}
359
360pub struct VbvAwareAllocator {
362 allocator: BitrateAllocator,
363 vbv_buffer: Option<VbvBuffer>,
364}
365
366impl VbvAwareAllocator {
367 #[must_use]
369 pub fn new(config: AllocationConfig) -> Self {
370 Self {
371 allocator: BitrateAllocator::new(config),
372 vbv_buffer: None,
373 }
374 }
375
376 pub fn set_vbv_buffer(&mut self, vbv_buffer: VbvBuffer) {
378 self.vbv_buffer = Some(vbv_buffer);
379 }
380
381 #[must_use]
383 pub fn allocate(
384 &mut self,
385 frame_index: u64,
386 frame_type: FrameType,
387 complexity: f64,
388 ) -> FrameAllocation {
389 let mut allocation = self.allocator.allocate(frame_index, frame_type, complexity);
390
391 if let Some(ref vbv) = self.vbv_buffer {
393 let max_allowed = vbv.max_frame_size();
394 allocation.max_bits = allocation.max_bits.min(max_allowed);
395 allocation.target_bits = allocation.target_bits.min(max_allowed);
396 allocation.min_bits = allocation.min_bits.min(allocation.target_bits);
397 }
398
399 allocation
400 }
401
402 pub fn update_vbv(&mut self, frame_bits: u64) {
404 if let Some(ref mut vbv) = self.vbv_buffer {
405 vbv.update(frame_bits);
406 }
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[test]
415 fn test_allocation_config_new() {
416 let config = AllocationConfig::new(AllocationStrategy::Complexity, 5_000_000);
417 assert_eq!(config.strategy, AllocationStrategy::Complexity);
418 assert_eq!(config.target_bitrate, 5_000_000);
419 }
420
421 #[test]
422 fn test_allocation_config_bits_per_frame() {
423 let config =
424 AllocationConfig::new(AllocationStrategy::Uniform, 3_000_000).with_framerate(30, 1);
425 let bpf = config.bits_per_frame();
426 assert!((bpf - 100_000.0).abs() < 1.0); }
428
429 #[test]
430 fn test_allocator_uniform() {
431 let config =
432 AllocationConfig::new(AllocationStrategy::Uniform, 3_000_000).with_framerate(30, 1);
433 let mut allocator = BitrateAllocator::new(config);
434
435 let alloc = allocator.allocate(0, FrameType::Inter, 0.5);
436 assert!(alloc.target_bits > 0);
437 assert!(alloc.min_bits < alloc.target_bits);
438 assert!(alloc.max_bits > alloc.target_bits);
439 }
440
441 #[test]
442 fn test_allocator_complexity() {
443 let config =
444 AllocationConfig::new(AllocationStrategy::Complexity, 3_000_000).with_framerate(30, 1);
445 let mut allocator = BitrateAllocator::new(config);
446
447 let low_complexity = allocator.allocate(0, FrameType::Inter, 0.2);
448 let high_complexity = allocator.allocate(1, FrameType::Inter, 0.8);
449
450 assert!(high_complexity.target_bits > low_complexity.target_bits);
452 }
453
454 #[test]
455 fn test_allocator_keyframe_boost() {
456 let config =
457 AllocationConfig::new(AllocationStrategy::Uniform, 3_000_000).with_framerate(30, 1);
458 let mut allocator = BitrateAllocator::new(config);
459
460 let keyframe_alloc = allocator.allocate(0, FrameType::Key, 0.5);
461 let inter_alloc = allocator.allocate(1, FrameType::Inter, 0.5);
462
463 assert!(keyframe_alloc.target_bits > inter_alloc.target_bits);
465 }
466
467 #[test]
468 fn test_frame_allocation_clamp() {
469 let alloc = FrameAllocation {
470 target_bits: 100_000,
471 min_bits: 50_000,
472 max_bits: 200_000,
473 qp_adjustment: 0.0,
474 };
475
476 assert_eq!(alloc.clamp_bits(30_000), 50_000); assert_eq!(alloc.clamp_bits(100_000), 100_000); assert_eq!(alloc.clamp_bits(250_000), 200_000); }
480
481 #[test]
482 fn test_frame_allocation_within_range() {
483 let alloc = FrameAllocation {
484 target_bits: 100_000,
485 min_bits: 50_000,
486 max_bits: 200_000,
487 qp_adjustment: 0.0,
488 };
489
490 assert!(!alloc.is_within_range(30_000));
491 assert!(alloc.is_within_range(100_000));
492 assert!(!alloc.is_within_range(250_000));
493 }
494}