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 = Some(PassOneResult::from_complexities(complexities, duration));
158 self.pass_one_result
159 .as_ref()
160 .expect("invariant: pass_one_result set just above")
161 }
162
163 #[must_use]
167 pub fn encode_bitrate_for_frame(&self, frame_idx: usize) -> u32 {
168 let Some(pass_one) = &self.pass_one_result else {
169 return self.config.target_bitrate_kbps;
170 };
171
172 let total_bits = self.config.total_bits();
173 let n = pass_one.complexity_map.len();
174 if n == 0 {
175 return self.config.target_bitrate_kbps;
176 }
177
178 let frame_bits = pass_one.allocate_bits(frame_idx, total_bits);
179
180 let duration_s = self.config.input_duration_ms as f64 / 1000.0;
183 if duration_s <= 0.0 {
184 return self.config.target_bitrate_kbps;
185 }
186 let avg_bits_per_frame = total_bits as f64 / n as f64;
187 let scale = if avg_bits_per_frame > 0.0 {
188 frame_bits as f64 / avg_bits_per_frame
189 } else {
190 1.0
191 };
192
193 let scaled = (f64::from(self.config.target_bitrate_kbps) * scale).clamp(
195 f64::from(self.config.target_bitrate_kbps) * 0.1,
196 f64::from(self.config.target_bitrate_kbps) * 5.0,
197 );
198 scaled as u32
199 }
200
201 #[must_use]
203 pub fn pass_one_complete(&self) -> bool {
204 self.pass_one_result.is_some()
205 }
206
207 #[must_use]
209 pub fn statistics(&self) -> Option<TwoPassStatistics> {
210 let pass_one = self.pass_one_result.as_ref()?;
211 let total_bits = self.config.total_bits();
212
213 Some(TwoPassStatistics::from_pass_one(
214 pass_one,
215 &self.config,
216 total_bits,
217 ))
218 }
219}
220
221#[derive(Debug, Clone)]
227pub struct TwoPassStatistics {
228 pub frame_count: usize,
230 pub mean_complexity: f64,
232 pub peak_complexity: f64,
234 pub min_complexity: f64,
236 pub complexity_std_dev: f64,
238 pub complex_region_fraction: f64,
240 pub total_bit_budget: u64,
242 pub avg_bits_per_frame: u64,
244 pub max_bits_per_frame: u64,
246 pub min_bits_per_frame: u64,
248 pub target_bitrate_kbps: u32,
250 pub duration_ms: u64,
252 pub complexity_histogram: [u32; 10],
254 pub scene_change_indices: Vec<usize>,
256}
257
258impl TwoPassStatistics {
259 fn from_pass_one(pass_one: &PassOneResult, config: &TwoPassConfig, total_bits: u64) -> Self {
261 let n = pass_one.complexity_map.len();
262 let (min_c, max_c, std_dev) = if n == 0 {
263 (0.0, 0.0, 0.0)
264 } else {
265 let min = pass_one
266 .complexity_map
267 .iter()
268 .copied()
269 .fold(f64::INFINITY, f64::min);
270 let max = pass_one.peak_complexity;
271 let mean = pass_one.avg_complexity;
272 let variance: f64 = pass_one
273 .complexity_map
274 .iter()
275 .map(|&c| (c - mean).powi(2))
276 .sum::<f64>()
277 / n as f64;
278 (min, max, variance.sqrt())
279 };
280
281 let mut max_bits: u64 = 0;
283 let mut min_bits: u64 = u64::MAX;
284 for i in 0..n {
285 let bits = pass_one.allocate_bits(i, total_bits);
286 if bits > max_bits {
287 max_bits = bits;
288 }
289 if bits < min_bits {
290 min_bits = bits;
291 }
292 }
293 if n == 0 {
294 min_bits = 0;
295 }
296
297 let avg_bits = if n > 0 { total_bits / n as u64 } else { 0 };
298
299 let mut histogram = [0u32; 10];
301 for &c in &pass_one.complexity_map {
302 let bin = ((c * 10.0).floor() as usize).min(9);
303 histogram[bin] += 1;
304 }
305
306 let scene_changes = Self::detect_scene_changes(&pass_one.complexity_map, std_dev);
309
310 Self {
311 frame_count: n,
312 mean_complexity: pass_one.avg_complexity,
313 peak_complexity: max_c,
314 min_complexity: min_c,
315 complexity_std_dev: std_dev,
316 complex_region_fraction: pass_one.complex_region_fraction(),
317 total_bit_budget: total_bits,
318 avg_bits_per_frame: avg_bits,
319 max_bits_per_frame: max_bits,
320 min_bits_per_frame: min_bits,
321 target_bitrate_kbps: config.target_bitrate_kbps,
322 duration_ms: config.input_duration_ms,
323 complexity_histogram: histogram,
324 scene_change_indices: scene_changes,
325 }
326 }
327
328 fn detect_scene_changes(complexities: &[f64], std_dev: f64) -> Vec<usize> {
331 let threshold = 2.0 * std_dev;
332 if threshold <= 0.0 || complexities.len() < 2 {
333 return Vec::new();
334 }
335
336 let mut changes = Vec::new();
337 for i in 1..complexities.len() {
338 let delta = (complexities[i] - complexities[i - 1]).abs();
339 if delta > threshold {
340 changes.push(i);
341 }
342 }
343 changes
344 }
345
346 #[must_use]
351 pub fn content_uniformity(&self) -> f64 {
352 if self.peak_complexity <= 0.0 {
353 return 1.0;
354 }
355 self.mean_complexity / self.peak_complexity
356 }
357
358 #[must_use]
362 pub fn bit_allocation_ratio(&self) -> f64 {
363 if self.avg_bits_per_frame == 0 {
364 return 1.0;
365 }
366 self.max_bits_per_frame as f64 / self.avg_bits_per_frame as f64
367 }
368
369 #[must_use]
372 pub fn benefits_from_two_pass(&self) -> bool {
373 if self.mean_complexity <= 0.0 {
375 return false;
376 }
377 let cv = self.complexity_std_dev / self.mean_complexity;
378 cv > 0.3 }
380
381 #[must_use]
383 pub fn scene_change_count(&self) -> usize {
384 self.scene_change_indices.len()
385 }
386
387 #[must_use]
389 pub fn summary(&self) -> String {
390 format!(
391 "Frames: {} | Complexity: mean={:.3}, peak={:.3}, std={:.3} | \
392 Bits: budget={}, avg/frame={}, max/frame={} | \
393 Scene changes: {} | Two-pass benefit: {}",
394 self.frame_count,
395 self.mean_complexity,
396 self.peak_complexity,
397 self.complexity_std_dev,
398 self.total_bit_budget,
399 self.avg_bits_per_frame,
400 self.max_bits_per_frame,
401 self.scene_change_count(),
402 if self.benefits_from_two_pass() {
403 "high"
404 } else {
405 "low"
406 },
407 )
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn test_two_pass_config_total_bits() {
417 let cfg = TwoPassConfig::new(5000, 10_000); assert_eq!(cfg.total_bits(), 50_000_000);
419 }
420
421 #[test]
422 fn test_two_pass_config_zero_duration() {
423 let cfg = TwoPassConfig::new(5000, 0);
424 assert_eq!(cfg.total_bits(), 0);
425 }
426
427 #[test]
428 fn test_pass_one_result_avg_complexity() {
429 let result = PassOneResult::from_complexities(vec![0.2, 0.4, 0.6, 0.8], 4000);
430 assert!((result.avg_complexity - 0.5).abs() < 1e-9);
431 }
432
433 #[test]
434 fn test_pass_one_result_peak_complexity() {
435 let result = PassOneResult::from_complexities(vec![0.1, 0.9, 0.5], 3000);
436 assert!((result.peak_complexity - 0.9).abs() < 1e-9);
437 }
438
439 #[test]
440 fn test_pass_one_result_empty() {
441 let result = PassOneResult::from_complexities(vec![], 0);
442 assert_eq!(result.avg_complexity, 0.0);
443 assert_eq!(result.peak_complexity, 0.0);
444 }
445
446 #[test]
447 fn test_allocate_bits_proportional() {
448 let result = PassOneResult::from_complexities(vec![0.25, 0.75], 2000);
450 let total_bits = 10_000_000u64;
451 let bits_0 = result.allocate_bits(0, total_bits);
452 let bits_1 = result.allocate_bits(1, total_bits);
453 assert_eq!(bits_0 + bits_1, total_bits);
454 assert!(bits_1 > bits_0);
455 }
456
457 #[test]
458 fn test_allocate_bits_out_of_range() {
459 let result = PassOneResult::from_complexities(vec![0.5, 0.5], 2000);
460 assert_eq!(result.allocate_bits(99, 1_000_000), 0);
461 }
462
463 #[test]
464 fn test_is_complex_region_simple() {
465 let result = PassOneResult::from_complexities(vec![0.5, 0.5, 0.5, 0.5], 4000);
467 assert!(!result.is_complex_region(0));
468 }
469
470 #[test]
471 fn test_is_complex_region_clear_outlier() {
472 let result = PassOneResult::from_complexities(vec![0.1, 0.1, 0.1, 0.9], 4000);
474 assert!(result.is_complex_region(3));
475 assert!(!result.is_complex_region(0));
476 }
477
478 #[test]
479 fn test_two_pass_encoder_fallback_before_pass_one() {
480 let cfg = TwoPassConfig::new(4000, 5000);
481 let encoder = TwoPassEncoder::new(cfg);
482 assert!(!encoder.pass_one_complete());
483 assert_eq!(encoder.encode_bitrate_for_frame(0), 4000);
485 }
486
487 #[test]
488 fn test_two_pass_encoder_analyze_and_encode() {
489 let cfg = TwoPassConfig::new(4000, 10_000);
490 let mut encoder = TwoPassEncoder::new(cfg);
491 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]);
492 assert!(encoder.pass_one_complete());
493 let complex_bitrate = encoder.encode_bitrate_for_frame(2);
495 let simple_bitrate = encoder.encode_bitrate_for_frame(0);
496 assert!(complex_bitrate > simple_bitrate);
497 }
498
499 #[test]
500 fn test_complex_region_fraction() {
501 let result = PassOneResult::from_complexities(vec![0.1, 0.1, 0.1, 0.9], 4000);
502 let fraction = result.complex_region_fraction();
503 assert!(fraction > 0.0);
504 assert!(fraction <= 1.0);
505 }
506
507 #[test]
508 fn test_is_complex_region_single_frame() {
509 let result = PassOneResult::from_complexities(vec![0.8], 1000);
510 assert!(result.is_complex_region(0));
511 let result_low = PassOneResult::from_complexities(vec![0.2], 1000);
512 assert!(!result_low.is_complex_region(0));
513 }
514
515 #[test]
518 fn test_statistics_basic() {
519 let cfg = TwoPassConfig::new(5000, 10_000);
520 let mut encoder = TwoPassEncoder::new(cfg);
521 encoder.analyze_pass_one(vec![0.2, 0.4, 0.6, 0.8]);
522 let stats = encoder
523 .statistics()
524 .expect("should have stats after pass one");
525 assert_eq!(stats.frame_count, 4);
526 assert!((stats.mean_complexity - 0.5).abs() < 1e-9);
527 assert!((stats.peak_complexity - 0.8).abs() < 1e-9);
528 assert!((stats.min_complexity - 0.2).abs() < 1e-9);
529 assert!(stats.complexity_std_dev > 0.0);
530 assert_eq!(stats.target_bitrate_kbps, 5000);
531 assert_eq!(stats.duration_ms, 10_000);
532 }
533
534 #[test]
535 fn test_statistics_bit_budget() {
536 let cfg = TwoPassConfig::new(5000, 10_000);
537 let mut encoder = TwoPassEncoder::new(cfg);
538 encoder.analyze_pass_one(vec![0.5, 0.5, 0.5, 0.5]);
539 let stats = encoder.statistics().expect("should have stats");
540 assert_eq!(stats.total_bit_budget, 50_000_000);
541 assert_eq!(stats.avg_bits_per_frame, 12_500_000);
542 }
543
544 #[test]
545 fn test_statistics_none_before_pass_one() {
546 let cfg = TwoPassConfig::new(4000, 5000);
547 let encoder = TwoPassEncoder::new(cfg);
548 assert!(encoder.statistics().is_none());
549 }
550
551 #[test]
552 fn test_statistics_content_uniformity_uniform() {
553 let cfg = TwoPassConfig::new(5000, 10_000);
554 let mut encoder = TwoPassEncoder::new(cfg);
555 encoder.analyze_pass_one(vec![0.5, 0.5, 0.5, 0.5]);
556 let stats = encoder.statistics().expect("should have stats");
557 assert!((stats.content_uniformity() - 1.0).abs() < 1e-9);
558 }
559
560 #[test]
561 fn test_statistics_content_uniformity_variable() {
562 let cfg = TwoPassConfig::new(5000, 10_000);
563 let mut encoder = TwoPassEncoder::new(cfg);
564 encoder.analyze_pass_one(vec![0.1, 0.1, 0.1, 0.9]);
565 let stats = encoder.statistics().expect("should have stats");
566 assert!(stats.content_uniformity() < 0.5);
567 }
568
569 #[test]
570 fn test_statistics_bit_allocation_ratio_uniform() {
571 let cfg = TwoPassConfig::new(5000, 10_000);
572 let mut encoder = TwoPassEncoder::new(cfg);
573 encoder.analyze_pass_one(vec![0.5, 0.5, 0.5, 0.5]);
574 let stats = encoder.statistics().expect("should have stats");
575 assert!((stats.bit_allocation_ratio() - 1.0).abs() < 0.01);
577 }
578
579 #[test]
580 fn test_statistics_bit_allocation_ratio_variable() {
581 let cfg = TwoPassConfig::new(5000, 10_000);
582 let mut encoder = TwoPassEncoder::new(cfg);
583 encoder.analyze_pass_one(vec![0.1, 0.1, 0.1, 0.9]);
584 let stats = encoder.statistics().expect("should have stats");
585 assert!(stats.bit_allocation_ratio() > 1.5);
587 }
588
589 #[test]
590 fn test_statistics_benefits_from_two_pass_variable() {
591 let cfg = TwoPassConfig::new(5000, 10_000);
592 let mut encoder = TwoPassEncoder::new(cfg);
593 encoder.analyze_pass_one(vec![0.1, 0.1, 0.1, 0.9, 0.1, 0.1, 0.1, 0.9]);
594 let stats = encoder.statistics().expect("should have stats");
595 assert!(stats.benefits_from_two_pass());
596 }
597
598 #[test]
599 fn test_statistics_not_benefits_from_two_pass_uniform() {
600 let cfg = TwoPassConfig::new(5000, 10_000);
601 let mut encoder = TwoPassEncoder::new(cfg);
602 encoder.analyze_pass_one(vec![0.5, 0.5, 0.5, 0.5, 0.5]);
603 let stats = encoder.statistics().expect("should have stats");
604 assert!(!stats.benefits_from_two_pass());
605 }
606
607 #[test]
608 fn test_statistics_complexity_histogram() {
609 let cfg = TwoPassConfig::new(5000, 10_000);
610 let mut encoder = TwoPassEncoder::new(cfg);
611 encoder.analyze_pass_one(vec![0.15, 0.15, 0.15, 0.15, 0.85]);
613 let stats = encoder.statistics().expect("should have stats");
614 assert_eq!(stats.complexity_histogram[1], 4);
616 assert_eq!(stats.complexity_histogram[8], 1);
618 }
619
620 #[test]
621 fn test_statistics_scene_changes() {
622 let cfg = TwoPassConfig::new(5000, 10_000);
623 let mut encoder = TwoPassEncoder::new(cfg);
624 encoder.analyze_pass_one(vec![0.1, 0.1, 0.1, 0.9, 0.9, 0.9, 0.1, 0.1]);
626 let stats = encoder.statistics().expect("should have stats");
627 assert!(stats.scene_change_count() > 0);
628 assert!(stats.scene_change_indices.contains(&3));
629 }
630
631 #[test]
632 fn test_statistics_no_scene_changes_uniform() {
633 let cfg = TwoPassConfig::new(5000, 10_000);
634 let mut encoder = TwoPassEncoder::new(cfg);
635 encoder.analyze_pass_one(vec![0.5, 0.5, 0.5, 0.5]);
636 let stats = encoder.statistics().expect("should have stats");
637 assert_eq!(stats.scene_change_count(), 0);
638 }
639
640 #[test]
641 fn test_statistics_summary_not_empty() {
642 let cfg = TwoPassConfig::new(5000, 10_000);
643 let mut encoder = TwoPassEncoder::new(cfg);
644 encoder.analyze_pass_one(vec![0.2, 0.4, 0.6, 0.8]);
645 let stats = encoder.statistics().expect("should have stats");
646 let summary = stats.summary();
647 assert!(!summary.is_empty());
648 assert!(summary.contains("Frames: 4"));
649 assert!(summary.contains("Scene changes:"));
650 }
651
652 #[test]
653 fn test_statistics_empty_complexities() {
654 let cfg = TwoPassConfig::new(5000, 10_000);
655 let mut encoder = TwoPassEncoder::new(cfg);
656 encoder.analyze_pass_one(vec![]);
657 let stats = encoder.statistics().expect("should have stats");
658 assert_eq!(stats.frame_count, 0);
659 assert_eq!(stats.avg_bits_per_frame, 0);
660 assert_eq!(stats.min_bits_per_frame, 0);
661 assert!((stats.content_uniformity() - 1.0).abs() < 1e-9);
662 assert!((stats.bit_allocation_ratio() - 1.0).abs() < 1e-9);
663 assert!(!stats.benefits_from_two_pass());
664 }
665}