oximedia_codec/multipass/
lookahead.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, VideoFrame};
15use crate::multipass::complexity::{ComplexityAnalyzer, FrameComplexity};
16use std::collections::VecDeque;
17
18#[derive(Clone, Debug)]
20pub struct LookaheadConfig {
21 pub window_size: usize,
23 pub min_keyint: u32,
25 pub max_keyint: u32,
27 pub scene_change_threshold: f64,
29 pub enable_aq: bool,
31}
32
33impl Default for LookaheadConfig {
34 fn default() -> Self {
35 Self {
36 window_size: 40,
37 min_keyint: 10,
38 max_keyint: 250,
39 scene_change_threshold: 0.4,
40 enable_aq: true,
41 }
42 }
43}
44
45impl LookaheadConfig {
46 #[must_use]
48 pub fn new(window_size: usize) -> Self {
49 Self {
50 window_size: window_size.clamp(10, 250),
51 ..Default::default()
52 }
53 }
54
55 #[must_use]
57 pub fn with_keyint_range(mut self, min: u32, max: u32) -> Self {
58 self.min_keyint = min;
59 self.max_keyint = max;
60 self
61 }
62
63 #[must_use]
65 pub fn with_scene_threshold(mut self, threshold: f64) -> Self {
66 self.scene_change_threshold = threshold.clamp(0.0, 1.0);
67 self
68 }
69}
70
71#[derive(Clone, Debug)]
73pub struct LookaheadFrame {
74 pub frame: VideoFrame,
76 pub complexity: FrameComplexity,
78 pub assigned_type: FrameType,
80 pub qp_offset: i32,
82}
83
84impl LookaheadFrame {
85 #[must_use]
87 pub fn new(frame: VideoFrame, complexity: FrameComplexity) -> Self {
88 Self {
89 assigned_type: frame.frame_type,
90 frame,
91 complexity,
92 qp_offset: 0,
93 }
94 }
95}
96
97pub struct LookaheadBuffer {
99 config: LookaheadConfig,
100 buffer: VecDeque<LookaheadFrame>,
101 complexity_analyzer: ComplexityAnalyzer,
102 frames_since_keyframe: u32,
103 total_frames_analyzed: u64,
104}
105
106impl LookaheadBuffer {
107 #[must_use]
109 pub fn new(config: LookaheadConfig, width: u32, height: u32) -> Self {
110 let mut analyzer = ComplexityAnalyzer::new(width, height);
111 analyzer.set_scene_change_threshold(config.scene_change_threshold);
112
113 let window_size = config.window_size;
114
115 Self {
116 config,
117 buffer: VecDeque::with_capacity(window_size),
118 complexity_analyzer: analyzer,
119 frames_since_keyframe: 0,
120 total_frames_analyzed: 0,
121 }
122 }
123
124 pub fn add_frame(&mut self, frame: VideoFrame) {
126 let complexity = self
127 .complexity_analyzer
128 .analyze(&frame, self.total_frames_analyzed);
129 let mut lookahead_frame = LookaheadFrame::new(frame, complexity);
130
131 if lookahead_frame.complexity.is_scene_change
133 && self.frames_since_keyframe >= self.config.min_keyint
134 {
135 lookahead_frame.assigned_type = FrameType::Key;
136 self.frames_since_keyframe = 0;
137 } else if self.frames_since_keyframe >= self.config.max_keyint {
138 lookahead_frame.assigned_type = FrameType::Key;
140 self.frames_since_keyframe = 0;
141 } else {
142 lookahead_frame.assigned_type = FrameType::Inter;
143 self.frames_since_keyframe += 1;
144 }
145
146 if self.config.enable_aq {
148 lookahead_frame.qp_offset = self.calculate_aq_offset(&lookahead_frame);
149 }
150
151 self.buffer.push_back(lookahead_frame);
152 self.total_frames_analyzed += 1;
153
154 while self.buffer.len() > self.config.window_size {
156 self.buffer.pop_front();
157 }
158 }
159
160 pub fn get_next_frame(&mut self) -> Option<LookaheadFrame> {
162 self.buffer.pop_front()
163 }
164
165 #[must_use]
167 pub fn peek_next(&self) -> Option<&LookaheadFrame> {
168 self.buffer.front()
169 }
170
171 #[must_use]
173 pub fn buffer_size(&self) -> usize {
174 self.buffer.len()
175 }
176
177 #[must_use]
179 pub fn is_full(&self) -> bool {
180 self.buffer.len() >= self.config.window_size
181 }
182
183 #[must_use]
185 pub fn is_empty(&self) -> bool {
186 self.buffer.is_empty()
187 }
188
189 #[must_use]
191 pub fn analyze_window(&self) -> LookaheadAnalysis {
192 if self.buffer.is_empty() {
193 return LookaheadAnalysis::default();
194 }
195
196 let mut analysis = LookaheadAnalysis::default();
197 analysis.total_frames = self.buffer.len();
198
199 let complexities: Vec<f64> = self
201 .buffer
202 .iter()
203 .map(|f| f.complexity.combined_complexity)
204 .collect();
205
206 analysis.avg_complexity = complexities.iter().sum::<f64>() / complexities.len() as f64;
207
208 analysis.min_complexity = complexities
209 .iter()
210 .copied()
211 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
212 .unwrap_or(0.0);
213
214 analysis.max_complexity = complexities
215 .iter()
216 .copied()
217 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
218 .unwrap_or(0.0);
219
220 analysis.scene_changes = self
222 .buffer
223 .iter()
224 .filter(|f| f.complexity.is_scene_change)
225 .count();
226
227 analysis.keyframes = self
229 .buffer
230 .iter()
231 .filter(|f| f.assigned_type == FrameType::Key)
232 .count();
233
234 analysis.next_scene_change = self
236 .buffer
237 .iter()
238 .position(|f| f.complexity.is_scene_change)
239 .map(|pos| pos as u32);
240
241 let variance: f64 = complexities
243 .iter()
244 .map(|c| (c - analysis.avg_complexity).powi(2))
245 .sum::<f64>()
246 / complexities.len() as f64;
247 analysis.complexity_variance = variance;
248
249 analysis
250 }
251
252 fn calculate_aq_offset(&self, frame: &LookaheadFrame) -> i32 {
254 let relative_complexity = if self.buffer.is_empty() {
256 1.0
257 } else {
258 let avg_complexity: f64 = self
259 .buffer
260 .iter()
261 .map(|f| f.complexity.combined_complexity)
262 .sum::<f64>()
263 / self.buffer.len() as f64;
264
265 if avg_complexity > 0.01 {
266 frame.complexity.combined_complexity / avg_complexity
267 } else {
268 1.0
269 }
270 };
271
272 let offset = (10.0 * (1.0 - relative_complexity)).clamp(-10.0, 10.0);
276 offset as i32
277 }
278
279 #[must_use]
281 pub fn config(&self) -> &LookaheadConfig {
282 &self.config
283 }
284
285 pub fn reset(&mut self) {
287 self.buffer.clear();
288 self.complexity_analyzer.reset();
289 self.frames_since_keyframe = 0;
290 self.total_frames_analyzed = 0;
291 }
292
293 pub fn flush(&mut self) -> Vec<LookaheadFrame> {
295 self.buffer.drain(..).collect()
296 }
297}
298
299#[derive(Clone, Debug, Default)]
301pub struct LookaheadAnalysis {
302 pub total_frames: usize,
304 pub avg_complexity: f64,
306 pub min_complexity: f64,
308 pub max_complexity: f64,
310 pub complexity_variance: f64,
312 pub scene_changes: usize,
314 pub keyframes: usize,
316 pub next_scene_change: Option<u32>,
318}
319
320impl LookaheadAnalysis {
321 #[must_use]
323 pub fn is_stable(&self) -> bool {
324 self.complexity_variance < 0.1
325 }
326
327 #[must_use]
329 pub fn complexity_range(&self) -> f64 {
330 self.max_complexity - self.min_complexity
331 }
332}
333
334pub struct SceneChangeDetector {
336 threshold: f64,
337 width: u32,
338 height: u32,
339 prev_frame: Option<Vec<u8>>,
340}
341
342impl SceneChangeDetector {
343 #[must_use]
345 pub fn new(width: u32, height: u32, threshold: f64) -> Self {
346 Self {
347 threshold: threshold.clamp(0.0, 1.0),
348 width,
349 height,
350 prev_frame: None,
351 }
352 }
353
354 #[must_use]
356 pub fn detect(&mut self, frame: &VideoFrame) -> bool {
357 if let Some(luma_plane) = frame.planes.first() {
358 let luma_data = luma_plane.data.as_ref();
359
360 if let Some(prev) = &self.prev_frame {
361 let sad = self.compute_sad(luma_data, prev);
362 let pixels = (self.width as u64) * (self.height as u64);
363 let avg_sad = if pixels > 0 {
364 sad as f64 / pixels as f64
365 } else {
366 0.0
367 };
368
369 let normalized_sad = (avg_sad / 50.0).min(1.0);
370 let is_scene_change = normalized_sad > self.threshold;
371
372 self.prev_frame = Some(luma_data.to_vec());
373 is_scene_change
374 } else {
375 self.prev_frame = Some(luma_data.to_vec());
376 true }
378 } else {
379 false
380 }
381 }
382
383 fn compute_sad(&self, current: &[u8], previous: &[u8]) -> u64 {
385 let mut sad = 0u64;
386 let len = current.len().min(previous.len());
387
388 for i in 0..len {
389 let diff = (current[i] as i32 - previous[i] as i32).abs();
390 sad += diff as u64;
391 }
392
393 sad
394 }
395
396 pub fn reset(&mut self) {
398 self.prev_frame = None;
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405 use crate::frame::Plane;
406 use oximedia_core::{PixelFormat, Rational, Timestamp};
407
408 fn create_test_frame(width: u32, height: u32, value: u8) -> VideoFrame {
409 let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
410 let size = (width * height) as usize;
411 let data = vec![value; size];
412 frame.planes.push(Plane::new(data, width as usize));
413 frame.timestamp = Timestamp::new(0, Rational::new(1, 30));
414 frame
415 }
416
417 #[test]
418 fn test_lookahead_config_new() {
419 let config = LookaheadConfig::new(50);
420 assert_eq!(config.window_size, 50);
421 assert_eq!(config.max_keyint, 250);
422 }
423
424 #[test]
425 fn test_lookahead_config_clamp() {
426 let config = LookaheadConfig::new(5); assert_eq!(config.window_size, 10); let config2 = LookaheadConfig::new(300); assert_eq!(config2.window_size, 250); }
432
433 #[test]
434 fn test_lookahead_buffer_new() {
435 let config = LookaheadConfig::new(40);
436 let buffer = LookaheadBuffer::new(config, 1920, 1080);
437 assert_eq!(buffer.buffer_size(), 0);
438 assert!(buffer.is_empty());
439 }
440
441 #[test]
442 fn test_lookahead_add_frame() {
443 let config = LookaheadConfig::new(40);
444 let mut buffer = LookaheadBuffer::new(config, 320, 240);
445
446 let frame = create_test_frame(320, 240, 128);
447 buffer.add_frame(frame);
448
449 assert_eq!(buffer.buffer_size(), 1);
450 assert!(!buffer.is_empty());
451 }
452
453 #[test]
454 fn test_lookahead_get_next_frame() {
455 let config = LookaheadConfig::new(40);
456 let mut buffer = LookaheadBuffer::new(config, 320, 240);
457
458 let frame = create_test_frame(320, 240, 128);
459 buffer.add_frame(frame);
460
461 let next = buffer.get_next_frame();
462 assert!(next.is_some());
463 assert!(buffer.is_empty());
464 }
465
466 #[test]
467 fn test_lookahead_buffer_full() {
468 let config = LookaheadConfig::new(10);
469 let mut buffer = LookaheadBuffer::new(config, 320, 240);
470
471 for i in 0..15 {
472 let frame = create_test_frame(320, 240, i as u8);
473 buffer.add_frame(frame);
474 }
475
476 assert_eq!(buffer.buffer_size(), 10);
478 assert!(buffer.is_full());
479 }
480
481 #[test]
482 fn test_lookahead_analyze_window() {
483 let config = LookaheadConfig::new(40);
484 let mut buffer = LookaheadBuffer::new(config, 320, 240);
485
486 for i in 0..10 {
487 let frame = create_test_frame(320, 240, i as u8);
488 buffer.add_frame(frame);
489 }
490
491 let analysis = buffer.analyze_window();
492 assert_eq!(analysis.total_frames, 10);
493 assert!(analysis.avg_complexity >= 0.0);
494 }
495
496 #[test]
497 fn test_scene_change_detector() {
498 let mut detector = SceneChangeDetector::new(320, 240, 0.4);
499
500 let frame1 = create_test_frame(320, 240, 0);
501 assert!(detector.detect(&frame1)); let frame2 = create_test_frame(320, 240, 255);
504 assert!(detector.detect(&frame2)); let frame3 = create_test_frame(320, 240, 250);
507 assert!(!detector.detect(&frame3)); }
509}