1#![allow(dead_code)]
8
9#[derive(Debug, Clone)]
11pub struct TwoPassConfig {
12 pub target_bitrate_kbps: u32,
14 pub input_duration_ms: u64,
16 pub complexity_analysis: bool,
18}
19
20impl TwoPassConfig {
21 #[must_use]
23 pub fn new(target_bitrate_kbps: u32, input_duration_ms: u64) -> Self {
24 Self {
25 target_bitrate_kbps,
26 input_duration_ms,
27 complexity_analysis: true,
28 }
29 }
30
31 #[must_use]
33 pub fn total_bits(&self) -> u64 {
34 let seconds = self.input_duration_ms as f64 / 1000.0;
36 (f64::from(self.target_bitrate_kbps) * 1000.0 * seconds) as u64
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct PassOneResult {
43 pub complexity_map: Vec<f64>,
45 pub avg_complexity: f64,
47 pub peak_complexity: f64,
49 pub duration_analyzed_ms: u64,
51}
52
53impl PassOneResult {
54 fn from_complexities(complexities: Vec<f64>, duration_analyzed_ms: u64) -> Self {
56 let n = complexities.len();
57 let (avg, peak) = if n == 0 {
58 (0.0, 0.0)
59 } else {
60 let sum: f64 = complexities.iter().sum();
61 let peak = complexities
62 .iter()
63 .copied()
64 .fold(f64::NEG_INFINITY, f64::max);
65 (sum / n as f64, peak)
66 };
67 Self {
68 complexity_map: complexities,
69 avg_complexity: avg,
70 peak_complexity: peak,
71 duration_analyzed_ms,
72 }
73 }
74
75 #[must_use]
80 pub fn allocate_bits(&self, frame_idx: usize, total_bits: u64) -> u64 {
81 let n = self.complexity_map.len();
82 if n == 0 || frame_idx >= n {
83 return 0;
84 }
85
86 let sum: f64 = self.complexity_map.iter().sum();
87 if sum <= 0.0 {
88 return total_bits / n as u64;
90 }
91
92 let weight = self.complexity_map[frame_idx] / sum;
93 (total_bits as f64 * weight) as u64
94 }
95
96 #[must_use]
101 pub fn is_complex_region(&self, idx: usize) -> bool {
102 let n = self.complexity_map.len();
103 if n == 0 || idx >= n {
104 return false;
105 }
106
107 if n == 1 {
108 return self.complexity_map[0] > 0.5;
109 }
110
111 let mean = self.avg_complexity;
112 let variance: f64 = self
113 .complexity_map
114 .iter()
115 .map(|&c| (c - mean).powi(2))
116 .sum::<f64>()
117 / n as f64;
118 let std_dev = variance.sqrt();
119 self.complexity_map[idx] > mean + std_dev
120 }
121
122 #[must_use]
124 pub fn complex_region_fraction(&self) -> f64 {
125 let n = self.complexity_map.len();
126 if n == 0 {
127 return 0.0;
128 }
129 let count = (0..n).filter(|&i| self.is_complex_region(i)).count();
130 count as f64 / n as f64
131 }
132}
133
134pub struct TwoPassEncoder {
136 pub config: TwoPassConfig,
138 pub pass_one_result: Option<PassOneResult>,
140}
141
142impl TwoPassEncoder {
143 #[must_use]
145 pub fn new(config: TwoPassConfig) -> Self {
146 Self {
147 config,
148 pass_one_result: None,
149 }
150 }
151
152 pub fn analyze_pass_one(&mut self, complexities: Vec<f64>) -> &PassOneResult {
156 let duration = self.config.input_duration_ms;
157 self.pass_one_result
158 .insert(PassOneResult::from_complexities(complexities, duration))
159 }
160
161 #[must_use]
165 pub fn encode_bitrate_for_frame(&self, frame_idx: usize) -> u32 {
166 let Some(pass_one) = &self.pass_one_result else {
167 return self.config.target_bitrate_kbps;
168 };
169
170 let total_bits = self.config.total_bits();
171 let n = pass_one.complexity_map.len();
172 if n == 0 {
173 return self.config.target_bitrate_kbps;
174 }
175
176 let frame_bits = pass_one.allocate_bits(frame_idx, total_bits);
177
178 let duration_s = self.config.input_duration_ms as f64 / 1000.0;
181 if duration_s <= 0.0 {
182 return self.config.target_bitrate_kbps;
183 }
184 let avg_bits_per_frame = total_bits as f64 / n as f64;
185 let scale = if avg_bits_per_frame > 0.0 {
186 frame_bits as f64 / avg_bits_per_frame
187 } else {
188 1.0
189 };
190
191 let scaled = (f64::from(self.config.target_bitrate_kbps) * scale).clamp(
193 f64::from(self.config.target_bitrate_kbps) * 0.1,
194 f64::from(self.config.target_bitrate_kbps) * 5.0,
195 );
196 scaled as u32
197 }
198
199 #[must_use]
201 pub fn pass_one_complete(&self) -> bool {
202 self.pass_one_result.is_some()
203 }
204
205 #[must_use]
207 pub fn statistics(&self) -> Option<TwoPassStatistics> {
208 let pass_one = self.pass_one_result.as_ref()?;
209 let total_bits = self.config.total_bits();
210
211 Some(TwoPassStatistics::from_pass_one(
212 pass_one,
213 &self.config,
214 total_bits,
215 ))
216 }
217}
218
219#[derive(Debug, Clone)]
225pub struct TwoPassStatistics {
226 pub frame_count: usize,
228 pub mean_complexity: f64,
230 pub peak_complexity: f64,
232 pub min_complexity: f64,
234 pub complexity_std_dev: f64,
236 pub complex_region_fraction: f64,
238 pub total_bit_budget: u64,
240 pub avg_bits_per_frame: u64,
242 pub max_bits_per_frame: u64,
244 pub min_bits_per_frame: u64,
246 pub target_bitrate_kbps: u32,
248 pub duration_ms: u64,
250 pub complexity_histogram: [u32; 10],
252 pub scene_change_indices: Vec<usize>,
254}
255
256impl TwoPassStatistics {
257 fn from_pass_one(pass_one: &PassOneResult, config: &TwoPassConfig, total_bits: u64) -> Self {
259 let n = pass_one.complexity_map.len();
260 let (min_c, max_c, std_dev) = if n == 0 {
261 (0.0, 0.0, 0.0)
262 } else {
263 let min = pass_one
264 .complexity_map
265 .iter()
266 .copied()
267 .fold(f64::INFINITY, f64::min);
268 let max = pass_one.peak_complexity;
269 let mean = pass_one.avg_complexity;
270 let variance: f64 = pass_one
271 .complexity_map
272 .iter()
273 .map(|&c| (c - mean).powi(2))
274 .sum::<f64>()
275 / n as f64;
276 (min, max, variance.sqrt())
277 };
278
279 let mut max_bits: u64 = 0;
281 let mut min_bits: u64 = u64::MAX;
282 for i in 0..n {
283 let bits = pass_one.allocate_bits(i, total_bits);
284 if bits > max_bits {
285 max_bits = bits;
286 }
287 if bits < min_bits {
288 min_bits = bits;
289 }
290 }
291 if n == 0 {
292 min_bits = 0;
293 }
294
295 let avg_bits = if n > 0 { total_bits / n as u64 } else { 0 };
296
297 let mut histogram = [0u32; 10];
299 for &c in &pass_one.complexity_map {
300 let bin = ((c * 10.0).floor() as usize).min(9);
301 histogram[bin] += 1;
302 }
303
304 let scene_changes = Self::detect_scene_changes(&pass_one.complexity_map, std_dev);
307
308 Self {
309 frame_count: n,
310 mean_complexity: pass_one.avg_complexity,
311 peak_complexity: max_c,
312 min_complexity: min_c,
313 complexity_std_dev: std_dev,
314 complex_region_fraction: pass_one.complex_region_fraction(),
315 total_bit_budget: total_bits,
316 avg_bits_per_frame: avg_bits,
317 max_bits_per_frame: max_bits,
318 min_bits_per_frame: min_bits,
319 target_bitrate_kbps: config.target_bitrate_kbps,
320 duration_ms: config.input_duration_ms,
321 complexity_histogram: histogram,
322 scene_change_indices: scene_changes,
323 }
324 }
325
326 fn detect_scene_changes(complexities: &[f64], std_dev: f64) -> Vec<usize> {
329 let threshold = 2.0 * std_dev;
330 if threshold <= 0.0 || complexities.len() < 2 {
331 return Vec::new();
332 }
333
334 let mut changes = Vec::new();
335 for i in 1..complexities.len() {
336 let delta = (complexities[i] - complexities[i - 1]).abs();
337 if delta > threshold {
338 changes.push(i);
339 }
340 }
341 changes
342 }
343
344 #[must_use]
349 pub fn content_uniformity(&self) -> f64 {
350 if self.peak_complexity <= 0.0 {
351 return 1.0;
352 }
353 self.mean_complexity / self.peak_complexity
354 }
355
356 #[must_use]
360 pub fn bit_allocation_ratio(&self) -> f64 {
361 if self.avg_bits_per_frame == 0 {
362 return 1.0;
363 }
364 self.max_bits_per_frame as f64 / self.avg_bits_per_frame as f64
365 }
366
367 #[must_use]
370 pub fn benefits_from_two_pass(&self) -> bool {
371 if self.mean_complexity <= 0.0 {
373 return false;
374 }
375 let cv = self.complexity_std_dev / self.mean_complexity;
376 cv > 0.3 }
378
379 #[must_use]
381 pub fn scene_change_count(&self) -> usize {
382 self.scene_change_indices.len()
383 }
384
385 #[must_use]
387 pub fn summary(&self) -> String {
388 format!(
389 "Frames: {} | Complexity: mean={:.3}, peak={:.3}, std={:.3} | \
390 Bits: budget={}, avg/frame={}, max/frame={} | \
391 Scene changes: {} | Two-pass benefit: {}",
392 self.frame_count,
393 self.mean_complexity,
394 self.peak_complexity,
395 self.complexity_std_dev,
396 self.total_bit_budget,
397 self.avg_bits_per_frame,
398 self.max_bits_per_frame,
399 self.scene_change_count(),
400 if self.benefits_from_two_pass() {
401 "high"
402 } else {
403 "low"
404 },
405 )
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 #[test]
414 fn test_two_pass_config_total_bits() {
415 let cfg = TwoPassConfig::new(5000, 10_000); assert_eq!(cfg.total_bits(), 50_000_000);
417 }
418
419 #[test]
420 fn test_two_pass_config_zero_duration() {
421 let cfg = TwoPassConfig::new(5000, 0);
422 assert_eq!(cfg.total_bits(), 0);
423 }
424
425 #[test]
426 fn test_pass_one_result_avg_complexity() {
427 let result = PassOneResult::from_complexities(vec![0.2, 0.4, 0.6, 0.8], 4000);
428 assert!((result.avg_complexity - 0.5).abs() < 1e-9);
429 }
430
431 #[test]
432 fn test_pass_one_result_peak_complexity() {
433 let result = PassOneResult::from_complexities(vec![0.1, 0.9, 0.5], 3000);
434 assert!((result.peak_complexity - 0.9).abs() < 1e-9);
435 }
436
437 #[test]
438 fn test_pass_one_result_empty() {
439 let result = PassOneResult::from_complexities(vec![], 0);
440 assert_eq!(result.avg_complexity, 0.0);
441 assert_eq!(result.peak_complexity, 0.0);
442 }
443
444 #[test]
445 fn test_allocate_bits_proportional() {
446 let result = PassOneResult::from_complexities(vec![0.25, 0.75], 2000);
448 let total_bits = 10_000_000u64;
449 let bits_0 = result.allocate_bits(0, total_bits);
450 let bits_1 = result.allocate_bits(1, total_bits);
451 assert_eq!(bits_0 + bits_1, total_bits);
452 assert!(bits_1 > bits_0);
453 }
454
455 #[test]
456 fn test_allocate_bits_out_of_range() {
457 let result = PassOneResult::from_complexities(vec![0.5, 0.5], 2000);
458 assert_eq!(result.allocate_bits(99, 1_000_000), 0);
459 }
460
461 #[test]
462 fn test_is_complex_region_simple() {
463 let result = PassOneResult::from_complexities(vec![0.5, 0.5, 0.5, 0.5], 4000);
465 assert!(!result.is_complex_region(0));
466 }
467
468 #[test]
469 fn test_is_complex_region_clear_outlier() {
470 let result = PassOneResult::from_complexities(vec![0.1, 0.1, 0.1, 0.9], 4000);
472 assert!(result.is_complex_region(3));
473 assert!(!result.is_complex_region(0));
474 }
475
476 #[test]
477 fn test_two_pass_encoder_fallback_before_pass_one() {
478 let cfg = TwoPassConfig::new(4000, 5000);
479 let encoder = TwoPassEncoder::new(cfg);
480 assert!(!encoder.pass_one_complete());
481 assert_eq!(encoder.encode_bitrate_for_frame(0), 4000);
483 }
484
485 #[test]
486 fn test_two_pass_encoder_analyze_and_encode() {
487 let cfg = TwoPassConfig::new(4000, 10_000);
488 let mut encoder = TwoPassEncoder::new(cfg);
489 encoder.analyze_pass_one(vec![0.2, 0.2, 0.9, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2]);
490 assert!(encoder.pass_one_complete());
491 let complex_bitrate = encoder.encode_bitrate_for_frame(2);
493 let simple_bitrate = encoder.encode_bitrate_for_frame(0);
494 assert!(complex_bitrate > simple_bitrate);
495 }
496
497 #[test]
498 fn test_complex_region_fraction() {
499 let result = PassOneResult::from_complexities(vec![0.1, 0.1, 0.1, 0.9], 4000);
500 let fraction = result.complex_region_fraction();
501 assert!(fraction > 0.0);
502 assert!(fraction <= 1.0);
503 }
504
505 #[test]
506 fn test_is_complex_region_single_frame() {
507 let result = PassOneResult::from_complexities(vec![0.8], 1000);
508 assert!(result.is_complex_region(0));
509 let result_low = PassOneResult::from_complexities(vec![0.2], 1000);
510 assert!(!result_low.is_complex_region(0));
511 }
512
513 #[test]
516 fn test_statistics_basic() {
517 let cfg = TwoPassConfig::new(5000, 10_000);
518 let mut encoder = TwoPassEncoder::new(cfg);
519 encoder.analyze_pass_one(vec![0.2, 0.4, 0.6, 0.8]);
520 let stats = encoder
521 .statistics()
522 .expect("should have stats after pass one");
523 assert_eq!(stats.frame_count, 4);
524 assert!((stats.mean_complexity - 0.5).abs() < 1e-9);
525 assert!((stats.peak_complexity - 0.8).abs() < 1e-9);
526 assert!((stats.min_complexity - 0.2).abs() < 1e-9);
527 assert!(stats.complexity_std_dev > 0.0);
528 assert_eq!(stats.target_bitrate_kbps, 5000);
529 assert_eq!(stats.duration_ms, 10_000);
530 }
531
532 #[test]
533 fn test_statistics_bit_budget() {
534 let cfg = TwoPassConfig::new(5000, 10_000);
535 let mut encoder = TwoPassEncoder::new(cfg);
536 encoder.analyze_pass_one(vec![0.5, 0.5, 0.5, 0.5]);
537 let stats = encoder.statistics().expect("should have stats");
538 assert_eq!(stats.total_bit_budget, 50_000_000);
539 assert_eq!(stats.avg_bits_per_frame, 12_500_000);
540 }
541
542 #[test]
543 fn test_statistics_none_before_pass_one() {
544 let cfg = TwoPassConfig::new(4000, 5000);
545 let encoder = TwoPassEncoder::new(cfg);
546 assert!(encoder.statistics().is_none());
547 }
548
549 #[test]
550 fn test_statistics_content_uniformity_uniform() {
551 let cfg = TwoPassConfig::new(5000, 10_000);
552 let mut encoder = TwoPassEncoder::new(cfg);
553 encoder.analyze_pass_one(vec![0.5, 0.5, 0.5, 0.5]);
554 let stats = encoder.statistics().expect("should have stats");
555 assert!((stats.content_uniformity() - 1.0).abs() < 1e-9);
556 }
557
558 #[test]
559 fn test_statistics_content_uniformity_variable() {
560 let cfg = TwoPassConfig::new(5000, 10_000);
561 let mut encoder = TwoPassEncoder::new(cfg);
562 encoder.analyze_pass_one(vec![0.1, 0.1, 0.1, 0.9]);
563 let stats = encoder.statistics().expect("should have stats");
564 assert!(stats.content_uniformity() < 0.5);
565 }
566
567 #[test]
568 fn test_statistics_bit_allocation_ratio_uniform() {
569 let cfg = TwoPassConfig::new(5000, 10_000);
570 let mut encoder = TwoPassEncoder::new(cfg);
571 encoder.analyze_pass_one(vec![0.5, 0.5, 0.5, 0.5]);
572 let stats = encoder.statistics().expect("should have stats");
573 assert!((stats.bit_allocation_ratio() - 1.0).abs() < 0.01);
575 }
576
577 #[test]
578 fn test_statistics_bit_allocation_ratio_variable() {
579 let cfg = TwoPassConfig::new(5000, 10_000);
580 let mut encoder = TwoPassEncoder::new(cfg);
581 encoder.analyze_pass_one(vec![0.1, 0.1, 0.1, 0.9]);
582 let stats = encoder.statistics().expect("should have stats");
583 assert!(stats.bit_allocation_ratio() > 1.5);
585 }
586
587 #[test]
588 fn test_statistics_benefits_from_two_pass_variable() {
589 let cfg = TwoPassConfig::new(5000, 10_000);
590 let mut encoder = TwoPassEncoder::new(cfg);
591 encoder.analyze_pass_one(vec![0.1, 0.1, 0.1, 0.9, 0.1, 0.1, 0.1, 0.9]);
592 let stats = encoder.statistics().expect("should have stats");
593 assert!(stats.benefits_from_two_pass());
594 }
595
596 #[test]
597 fn test_statistics_not_benefits_from_two_pass_uniform() {
598 let cfg = TwoPassConfig::new(5000, 10_000);
599 let mut encoder = TwoPassEncoder::new(cfg);
600 encoder.analyze_pass_one(vec![0.5, 0.5, 0.5, 0.5, 0.5]);
601 let stats = encoder.statistics().expect("should have stats");
602 assert!(!stats.benefits_from_two_pass());
603 }
604
605 #[test]
606 fn test_statistics_complexity_histogram() {
607 let cfg = TwoPassConfig::new(5000, 10_000);
608 let mut encoder = TwoPassEncoder::new(cfg);
609 encoder.analyze_pass_one(vec![0.15, 0.15, 0.15, 0.15, 0.85]);
611 let stats = encoder.statistics().expect("should have stats");
612 assert_eq!(stats.complexity_histogram[1], 4);
614 assert_eq!(stats.complexity_histogram[8], 1);
616 }
617
618 #[test]
619 fn test_statistics_scene_changes() {
620 let cfg = TwoPassConfig::new(5000, 10_000);
621 let mut encoder = TwoPassEncoder::new(cfg);
622 encoder.analyze_pass_one(vec![0.1, 0.1, 0.1, 0.9, 0.9, 0.9, 0.1, 0.1]);
624 let stats = encoder.statistics().expect("should have stats");
625 assert!(stats.scene_change_count() > 0);
626 assert!(stats.scene_change_indices.contains(&3));
627 }
628
629 #[test]
630 fn test_statistics_no_scene_changes_uniform() {
631 let cfg = TwoPassConfig::new(5000, 10_000);
632 let mut encoder = TwoPassEncoder::new(cfg);
633 encoder.analyze_pass_one(vec![0.5, 0.5, 0.5, 0.5]);
634 let stats = encoder.statistics().expect("should have stats");
635 assert_eq!(stats.scene_change_count(), 0);
636 }
637
638 #[test]
639 fn test_statistics_summary_not_empty() {
640 let cfg = TwoPassConfig::new(5000, 10_000);
641 let mut encoder = TwoPassEncoder::new(cfg);
642 encoder.analyze_pass_one(vec![0.2, 0.4, 0.6, 0.8]);
643 let stats = encoder.statistics().expect("should have stats");
644 let summary = stats.summary();
645 assert!(!summary.is_empty());
646 assert!(summary.contains("Frames: 4"));
647 assert!(summary.contains("Scene changes:"));
648 }
649
650 #[test]
651 fn test_statistics_empty_complexities() {
652 let cfg = TwoPassConfig::new(5000, 10_000);
653 let mut encoder = TwoPassEncoder::new(cfg);
654 encoder.analyze_pass_one(vec![]);
655 let stats = encoder.statistics().expect("should have stats");
656 assert_eq!(stats.frame_count, 0);
657 assert_eq!(stats.avg_bits_per_frame, 0);
658 assert_eq!(stats.min_bits_per_frame, 0);
659 assert!((stats.content_uniformity() - 1.0).abs() < 1e-9);
660 assert!((stats.bit_allocation_ratio() - 1.0).abs() < 1e-9);
661 assert!(!stats.benefits_from_two_pass());
662 }
663}