oximedia_transcode/
two_pass.rs1#![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
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_two_pass_config_total_bits() {
214 let cfg = TwoPassConfig::new(5000, 10_000); assert_eq!(cfg.total_bits(), 50_000_000);
216 }
217
218 #[test]
219 fn test_two_pass_config_zero_duration() {
220 let cfg = TwoPassConfig::new(5000, 0);
221 assert_eq!(cfg.total_bits(), 0);
222 }
223
224 #[test]
225 fn test_pass_one_result_avg_complexity() {
226 let result = PassOneResult::from_complexities(vec![0.2, 0.4, 0.6, 0.8], 4000);
227 assert!((result.avg_complexity - 0.5).abs() < 1e-9);
228 }
229
230 #[test]
231 fn test_pass_one_result_peak_complexity() {
232 let result = PassOneResult::from_complexities(vec![0.1, 0.9, 0.5], 3000);
233 assert!((result.peak_complexity - 0.9).abs() < 1e-9);
234 }
235
236 #[test]
237 fn test_pass_one_result_empty() {
238 let result = PassOneResult::from_complexities(vec![], 0);
239 assert_eq!(result.avg_complexity, 0.0);
240 assert_eq!(result.peak_complexity, 0.0);
241 }
242
243 #[test]
244 fn test_allocate_bits_proportional() {
245 let result = PassOneResult::from_complexities(vec![0.25, 0.75], 2000);
247 let total_bits = 10_000_000u64;
248 let bits_0 = result.allocate_bits(0, total_bits);
249 let bits_1 = result.allocate_bits(1, total_bits);
250 assert_eq!(bits_0 + bits_1, total_bits);
251 assert!(bits_1 > bits_0);
252 }
253
254 #[test]
255 fn test_allocate_bits_out_of_range() {
256 let result = PassOneResult::from_complexities(vec![0.5, 0.5], 2000);
257 assert_eq!(result.allocate_bits(99, 1_000_000), 0);
258 }
259
260 #[test]
261 fn test_is_complex_region_simple() {
262 let result = PassOneResult::from_complexities(vec![0.5, 0.5, 0.5, 0.5], 4000);
264 assert!(!result.is_complex_region(0));
265 }
266
267 #[test]
268 fn test_is_complex_region_clear_outlier() {
269 let result = PassOneResult::from_complexities(vec![0.1, 0.1, 0.1, 0.9], 4000);
271 assert!(result.is_complex_region(3));
272 assert!(!result.is_complex_region(0));
273 }
274
275 #[test]
276 fn test_two_pass_encoder_fallback_before_pass_one() {
277 let cfg = TwoPassConfig::new(4000, 5000);
278 let encoder = TwoPassEncoder::new(cfg);
279 assert!(!encoder.pass_one_complete());
280 assert_eq!(encoder.encode_bitrate_for_frame(0), 4000);
282 }
283
284 #[test]
285 fn test_two_pass_encoder_analyze_and_encode() {
286 let cfg = TwoPassConfig::new(4000, 10_000);
287 let mut encoder = TwoPassEncoder::new(cfg);
288 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]);
289 assert!(encoder.pass_one_complete());
290 let complex_bitrate = encoder.encode_bitrate_for_frame(2);
292 let simple_bitrate = encoder.encode_bitrate_for_frame(0);
293 assert!(complex_bitrate > simple_bitrate);
294 }
295
296 #[test]
297 fn test_complex_region_fraction() {
298 let result = PassOneResult::from_complexities(vec![0.1, 0.1, 0.1, 0.9], 4000);
299 let fraction = result.complex_region_fraction();
300 assert!(fraction > 0.0);
301 assert!(fraction <= 1.0);
302 }
303
304 #[test]
305 fn test_is_complex_region_single_frame() {
306 let result = PassOneResult::from_complexities(vec![0.8], 1000);
307 assert!(result.is_complex_region(0));
308 let result_low = PassOneResult::from_complexities(vec![0.2], 1000);
309 assert!(!result_low.is_complex_region(0));
310 }
311}