1#![deny(unsafe_code)]
68#![warn(missing_docs)]
69#![allow(clippy::too_many_arguments)]
70#![allow(clippy::cast_precision_loss)]
71#![allow(clippy::cast_possible_truncation)]
72#![allow(clippy::cast_sign_loss)]
73#![allow(clippy::module_name_repetitions)]
74
75pub mod adaptive_ladder;
76pub mod aq;
77pub mod benchmark;
78pub mod bitrate_controller;
79pub mod bitrate_optimizer;
80pub mod cache_opt;
81pub mod cache_optimizer;
82pub mod cache_strategy;
83pub mod complexity_analysis;
84pub mod crf_sweep;
85pub mod decision;
86pub mod encode_preset;
87pub mod encode_stats;
88pub mod entropy;
89pub mod examples;
90pub mod filter;
91pub mod frame_budget;
92pub mod gop_optimizer;
93pub mod intra;
94pub mod lookahead;
95pub mod media_optimize;
96pub mod motion;
97pub mod parallel_strategy;
98pub mod partition;
99pub mod perceptual_optimization;
100pub mod prefetch;
101pub mod presets;
102pub mod psycho;
103pub mod quality_ladder;
104pub mod quality_metric;
105pub mod quantizer_curve;
106pub mod rdo;
107pub mod reference;
108pub mod roi_encode;
109pub mod scene_encode;
110pub mod strategies;
111pub mod temporal_aq;
112pub mod transcode_optimizer;
113pub mod transform;
114pub mod two_pass;
115pub mod utils;
116pub mod vmaf_predict;
117
118#[allow(unsafe_code)]
120pub mod simd_metrics;
121
122use oximedia_core::OxiResult;
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
126pub enum OptimizationLevel {
127 Fast,
129 #[default]
131 Medium,
132 Slow,
134 Placebo,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
140pub enum ContentType {
141 Animation,
143 Film,
145 Screen,
147 #[default]
149 Generic,
150}
151
152#[derive(Debug, Clone)]
154pub struct OptimizerConfig {
155 pub level: OptimizationLevel,
157 pub enable_psychovisual: bool,
159 pub enable_aq: bool,
161 pub lookahead_frames: usize,
163 pub content_type: ContentType,
165 pub parallel_rdo: bool,
167 pub lambda_multiplier: f64,
169 pub enable_roi: bool,
171 pub enable_temporal_aq: bool,
173}
174
175impl Default for OptimizerConfig {
176 fn default() -> Self {
177 Self {
178 level: OptimizationLevel::default(),
179 enable_psychovisual: true,
180 enable_aq: true,
181 lookahead_frames: 20,
182 content_type: ContentType::default(),
183 parallel_rdo: true,
184 lambda_multiplier: 1.0,
185 enable_roi: false,
186 enable_temporal_aq: false,
187 }
188 }
189}
190
191pub struct Optimizer {
196 config: OptimizerConfig,
197 rdo_engine: rdo::RdoEngine,
198 psycho_analyzer: psycho::PsychoAnalyzer,
199 motion_optimizer: motion::MotionOptimizer,
200 aq_engine: aq::AqEngine,
201 roi_encoder: Option<roi_encode::RoiEncoder>,
202 temporal_aq_bridge: Option<temporal_aq::TemporalAqBridge>,
203}
204
205impl Optimizer {
206 pub fn new(config: OptimizerConfig) -> OxiResult<Self> {
208 let rdo_engine = rdo::RdoEngine::new(&config)?;
209 let psycho_analyzer = psycho::PsychoAnalyzer::new(&config)?;
210 let motion_optimizer = motion::MotionOptimizer::new(&config)?;
211 let aq_engine = aq::AqEngine::new(&config)?;
212
213 let roi_encoder = if config.enable_roi {
214 Some(roi_encode::RoiEncoder::new(
215 roi_encode::RoiEncoderConfig::default(),
216 ))
217 } else {
218 None
219 };
220
221 let temporal_aq_bridge = if config.enable_temporal_aq {
222 Some(temporal_aq::TemporalAqBridge::with_defaults())
223 } else {
224 None
225 };
226
227 Ok(Self {
228 config,
229 rdo_engine,
230 psycho_analyzer,
231 motion_optimizer,
232 aq_engine,
233 roi_encoder,
234 temporal_aq_bridge,
235 })
236 }
237
238 #[must_use]
240 pub fn config(&self) -> &OptimizerConfig {
241 &self.config
242 }
243
244 #[must_use]
246 pub fn rdo_engine(&self) -> &rdo::RdoEngine {
247 &self.rdo_engine
248 }
249
250 #[must_use]
252 pub fn psycho_analyzer(&self) -> &psycho::PsychoAnalyzer {
253 &self.psycho_analyzer
254 }
255
256 #[must_use]
258 pub fn motion_optimizer(&self) -> &motion::MotionOptimizer {
259 &self.motion_optimizer
260 }
261
262 #[must_use]
264 pub fn aq_engine(&self) -> &aq::AqEngine {
265 &self.aq_engine
266 }
267
268 pub fn aq_engine_mut(&mut self) -> &mut aq::AqEngine {
270 &mut self.aq_engine
271 }
272
273 #[must_use]
275 pub fn roi_encoder(&self) -> Option<&roi_encode::RoiEncoder> {
276 self.roi_encoder.as_ref()
277 }
278
279 pub fn roi_encoder_mut(&mut self) -> Option<&mut roi_encode::RoiEncoder> {
281 self.roi_encoder.as_mut()
282 }
283
284 #[must_use]
286 pub fn temporal_aq_bridge(&self) -> Option<&temporal_aq::TemporalAqBridge> {
287 self.temporal_aq_bridge.as_ref()
288 }
289
290 pub fn temporal_aq_bridge_mut(&mut self) -> Option<&mut temporal_aq::TemporalAqBridge> {
292 self.temporal_aq_bridge.as_mut()
293 }
294
295 pub fn process_temporal_aq(
300 &mut self,
301 activity: temporal_aq::TemporalActivity,
302 spatial_qp_offset: i8,
303 ) -> Option<temporal_aq::CombinedAqResult> {
304 if let Some(ref mut bridge) = self.temporal_aq_bridge {
305 bridge.update_temporal(activity);
306 Some(bridge.combine_with_spatial(spatial_qp_offset))
307 } else {
308 None
309 }
310 }
311
312 pub fn generate_roi_map(&self) -> Option<roi_encode::RoiOptimizeResult> {
316 self.roi_encoder.as_ref().map(|enc| enc.optimize_frame())
317 }
318
319 pub fn add_roi_region(&mut self, region: roi_encode::RoiRegion) -> bool {
323 if let Some(ref mut enc) = self.roi_encoder {
324 enc.add_region(region);
325 true
326 } else {
327 false
328 }
329 }
330
331 pub fn clear_roi_regions(&mut self) {
333 if let Some(ref mut enc) = self.roi_encoder {
334 enc.clear_regions();
335 }
336 }
337}
338
339pub use aq::{AqEngine, AqMode, AqResult};
341pub use benchmark::{BenchmarkConfig, BenchmarkResult, BenchmarkRunner, Profiler};
342pub use decision::{
343 DecisionContext, DecisionStrategy, ModeDecision, ReferenceDecision, SplitDecision,
344};
345pub use entropy::{ContextModel, ContextOptimizer, EntropyStats};
346pub use filter::{DeblockOptimizer, FilterDecision, SaoOptimizer};
347pub use gop_optimizer::{
348 ContentAdaptiveGop, ContentGopDecision, GopOptimizer, GopPattern, GopPlan,
349};
350pub use intra::{AngleOptimizer, IntraModeDecision, ModeOptimizer};
351pub use lookahead::{GopStructure, LookaheadAnalyzer, LookaheadFrame};
352pub use motion::{
353 BidirectionalOptimizer, MotionOptimizer, MotionSearchResult, MotionVector, MvPredictor,
354 SubpelOptimizer,
355};
356pub use partition::{ComplexityAnalyzer, PartitionDecision, SplitOptimizer};
357pub use presets::{OptimizationPresets, TunePresets};
358pub use psycho::{ContrastSensitivity, PsychoAnalyzer, VisualMasking};
359pub use rdo::{CostEstimate, LambdaCalculator, RdoEngine, RdoResult, RdoqOptimizer};
360pub use reference::{DpbOptimizer, ReferenceSelection};
361pub use roi_encode::{QpDeltaMap, RoiEncoder, RoiEncoderConfig, RoiOptimizeResult, RoiRegion};
362pub use scene_encode::{LookaheadSceneQp, SceneEncodeParams, SceneEncoder, SceneMetrics};
363pub use strategies::{
364 BitrateAllocator, ContentAdaptiveOptimizer, OptimizationStrategy, StrategySelector,
365 TemporalOptimizer,
366};
367pub use temporal_aq::{CombinedAqResult, TemporalActivity, TemporalAqBridge, TemporalAqEngine};
368pub use transform::{QuantizationOptimizer, TransformSelection};
369pub use utils::{BlockMetrics, FrameMetrics, OptimizationStats};
370pub use vmaf_predict::{
371 BatchVmafPredictor, VmafFeatures, VmafPrediction, VmafPredictor, VmafPredictorConfig,
372};
373
374#[cfg(test)]
375mod integration_tests {
376 use super::*;
377
378 #[test]
379 fn test_optimizer_with_roi() {
380 let config = OptimizerConfig {
381 enable_roi: true,
382 ..Default::default()
383 };
384 let mut optimizer = Optimizer::new(config).expect("Optimizer creation should succeed");
385
386 assert!(optimizer.roi_encoder().is_some());
387 assert!(
388 optimizer.add_roi_region(roi_encode::RoiRegion::with_priority(0, 0, 128, 128, 2.0),)
389 );
390
391 let map = optimizer.generate_roi_map();
392 assert!(map.is_some());
393 let result = map.expect("ROI map should exist");
394 assert!(result.has_active_regions);
395 }
396
397 #[test]
398 fn test_optimizer_without_roi() {
399 let config = OptimizerConfig::default();
400 let mut optimizer = Optimizer::new(config).expect("Optimizer creation should succeed");
401
402 assert!(optimizer.roi_encoder().is_none());
403 assert!(!optimizer.add_roi_region(roi_encode::RoiRegion::new(0, 0, 64, 64),));
404 assert!(optimizer.generate_roi_map().is_none());
405 }
406
407 #[test]
408 fn test_optimizer_with_temporal_aq() {
409 let config = OptimizerConfig {
410 enable_temporal_aq: true,
411 ..Default::default()
412 };
413 let mut optimizer = Optimizer::new(config).expect("Optimizer creation should succeed");
414
415 assert!(optimizer.temporal_aq_bridge().is_some());
416
417 let mut activity = temporal_aq::TemporalActivity::new(0);
418 activity.avg_motion_magnitude = 30.0;
419 activity.motion_coverage = 0.6;
420
421 let result = optimizer.process_temporal_aq(activity, -2);
422 assert!(result.is_some());
423 let combined = result.expect("Temporal AQ result should exist");
424 assert_ne!(combined.final_qp_delta, 0);
425 }
426
427 #[test]
428 fn test_optimizer_without_temporal_aq() {
429 let config = OptimizerConfig::default();
430 let mut optimizer = Optimizer::new(config).expect("Optimizer creation should succeed");
431
432 assert!(optimizer.temporal_aq_bridge().is_none());
433 let result = optimizer.process_temporal_aq(temporal_aq::TemporalActivity::new(0), 0);
434 assert!(result.is_none());
435 }
436
437 #[test]
438 fn test_optimizer_full_pipeline() {
439 let config = OptimizerConfig {
440 enable_roi: true,
441 enable_temporal_aq: true,
442 enable_aq: true,
443 enable_psychovisual: true,
444 content_type: ContentType::Film,
445 ..Default::default()
446 };
447 let mut optimizer = Optimizer::new(config).expect("Optimizer creation should succeed");
448
449 optimizer.add_roi_region(roi_encode::RoiRegion::with_priority(
451 100, 100, 200, 200, 1.5,
452 ));
453
454 let mut activity = temporal_aq::TemporalActivity::new(0);
456 activity.avg_motion_magnitude = 15.0;
457 activity.motion_coverage = 0.4;
458 let temporal_result = optimizer.process_temporal_aq(activity, -1);
459 assert!(temporal_result.is_some());
460
461 let roi_result = optimizer.generate_roi_map();
463 assert!(roi_result.is_some());
464
465 assert_eq!(optimizer.aq_engine().mode(), aq::AqMode::Combined);
467 }
468
469 #[test]
470 fn test_clear_roi_regions() {
471 let config = OptimizerConfig {
472 enable_roi: true,
473 ..Default::default()
474 };
475 let mut optimizer = Optimizer::new(config).expect("Optimizer creation should succeed");
476
477 optimizer.add_roi_region(roi_encode::RoiRegion::new(0, 0, 64, 64));
478 optimizer.clear_roi_regions();
479 let result = optimizer
480 .generate_roi_map()
481 .expect("ROI map should exist even without regions");
482 assert!(!result.has_active_regions);
483 }
484}