Skip to main content

oximedia_codec/multipass/
mod.rs

1//! Multi-pass encoding with look-ahead for OxiMedia codecs.
2//!
3//! This module provides a comprehensive multi-pass encoding system that includes:
4//! - Two-pass encoding with statistics collection
5//! - Look-ahead buffer for analyzing future frames
6//! - Adaptive quantization based on frame complexity
7//! - VBV-compliant bitrate allocation
8//! - Scene change detection
9//!
10//! # Architecture
11//!
12//! ```text
13//! ┌─────────────────────────────────────────────────────────────┐
14//! │                   MultiPassEncoder                          │
15//! │  ┌──────────────────────────────────────────────────────┐   │
16//! │  │               First Pass                             │   │
17//! │  │  ┌──────────┐  ┌────────────┐  ┌─────────────────┐  │   │
18//! │  │  │ Analyze  │→ │ Collect    │→ │ Save Statistics │  │   │
19//! │  │  │ Frames   │  │ Statistics │  │ to File         │  │   │
20//! │  │  └──────────┘  └────────────┘  └─────────────────┘  │   │
21//! │  └──────────────────────────────────────────────────────┘   │
22//! │  ┌──────────────────────────────────────────────────────┐   │
23//! │  │               Second Pass                            │   │
24//! │  │  ┌──────────┐  ┌────────────┐  ┌─────────────────┐  │   │
25//! │  │  │ Load     │→ │ Allocate   │→ │ Encode with     │  │   │
26//! │  │  │ Stats    │  │ Bitrate    │  │ Optimal QP      │  │   │
27//! │  │  └──────────┘  └────────────┘  └─────────────────┘  │   │
28//! │  └──────────────────────────────────────────────────────┘   │
29//! │  ┌──────────────────────────────────────────────────────┐   │
30//! │  │          Single-Pass with Look-ahead                 │   │
31//! │  │  ┌──────────┐  ┌────────────┐  ┌─────────────────┐  │   │
32//! │  │  │ Analyze  │→ │ Allocate   │→ │ Encode          │  │   │
33//! │  │  │ Window   │  │ Adaptive   │  │                 │  │   │
34//! │  │  └──────────┘  └────────────┘  └─────────────────┘  │   │
35//! │  └──────────────────────────────────────────────────────┘   │
36//! └─────────────────────────────────────────────────────────────┘
37//! ```
38//!
39//! # Example: Two-Pass Encoding
40//!
41//! ```ignore
42//! use oximedia_codec::multipass::{MultiPassEncoder, PassType, EncoderConfig};
43//!
44//! // First pass
45//! let config = EncoderConfig::new(1920, 1080)
46//!     .with_pass(PassType::FirstPass)
47//!     .with_stats_file("pass1.stats");
48//!
49//! let mut encoder = MultiPassEncoder::new(config)?;
50//!
51//! for frame in frames {
52//!     encoder.encode_frame(&frame)?;
53//! }
54//!
55//! encoder.save_stats("pass1.stats")?;
56//!
57//! // Second pass
58//! let config = EncoderConfig::new(1920, 1080)
59//!     .with_pass(PassType::SecondPass)
60//!     .with_stats_file("pass1.stats")
61//!     .with_target_bitrate(5_000_000);
62//!
63//! let mut encoder = MultiPassEncoder::new(config)?;
64//! encoder.load_stats("pass1.stats")?;
65//!
66//! for frame in frames {
67//!     let packet = encoder.encode_frame(&frame)?;
68//!     // Write packet...
69//! }
70//! ```
71//!
72//! # Example: Single-Pass with Look-ahead
73//!
74//! ```ignore
75//! use oximedia_codec::multipass::{MultiPassEncoder, PassType, EncoderConfig};
76//!
77//! let config = EncoderConfig::new(1920, 1080)
78//!     .with_pass(PassType::SinglePassLookahead)
79//!     .with_lookahead_frames(40);
80//!
81//! let mut encoder = MultiPassEncoder::new(config)?;
82//!
83//! for frame in frames {
84//!     let packet = encoder.encode_frame(&frame)?;
85//!     // Write packet...
86//! }
87//! ```
88
89#![forbid(unsafe_code)]
90#![allow(clippy::cast_precision_loss)]
91#![allow(clippy::cast_possible_truncation)]
92#![allow(clippy::cast_sign_loss)]
93#![allow(clippy::cast_lossless)]
94#![allow(clippy::module_name_repetitions)]
95
96pub mod allocation;
97pub mod complexity;
98pub mod examples;
99pub mod lookahead;
100pub mod stats;
101pub mod vbv;
102
103use crate::frame::{FrameType, VideoFrame};
104use crate::traits::EncodedPacket;
105use allocation::{AllocationConfig, AllocationStrategy, BitrateAllocator, VbvAwareAllocator};
106use complexity::ComplexityAnalyzer;
107use lookahead::{LookaheadBuffer, LookaheadConfig};
108use stats::{FrameStatistics, PassStatistics};
109use vbv::{VbvBuffer, VbvConfig};
110
111// Re-export main types
112pub use allocation::{FrameAllocation, VbvAwareAllocator as Allocator};
113pub use complexity::{ComplexityAnalyzer as Analyzer, FrameComplexity};
114pub use lookahead::{LookaheadAnalysis, LookaheadFrame, SceneChangeDetector};
115pub use stats::{ComplexityStats, FrameStatistics as Stats, PassStatistics as PassStats};
116pub use vbv::{VbvBuffer as Buffer, VbvConfig as BufferConfig, VbvStatistics};
117
118/// Pass type for multi-pass encoding.
119#[derive(Clone, Copy, Debug, PartialEq, Eq)]
120pub enum PassType {
121    /// First pass: collect statistics only.
122    FirstPass,
123    /// Second pass: encode with optimal bitrate allocation.
124    SecondPass,
125    /// Single pass with look-ahead (no statistics file).
126    SinglePassLookahead,
127}
128
129/// Multi-pass encoder configuration.
130#[derive(Clone, Debug)]
131pub struct EncoderConfig {
132    /// Video width in pixels.
133    pub width: u32,
134    /// Video height in pixels.
135    pub height: u32,
136    /// Pass type.
137    pub pass: PassType,
138    /// Number of lookahead frames (10-250).
139    pub lookahead_frames: usize,
140    /// Target bitrate in bits per second.
141    pub target_bitrate: u64,
142    /// Maximum bitrate (for VBV).
143    pub max_bitrate: Option<u64>,
144    /// VBV buffer size in bits.
145    pub vbv_buffer_size: Option<u64>,
146    /// Frame rate numerator.
147    pub framerate_num: u32,
148    /// Frame rate denominator.
149    pub framerate_den: u32,
150    /// Minimum keyframe interval.
151    pub min_keyint: u32,
152    /// Maximum keyframe interval.
153    pub max_keyint: u32,
154    /// Scene change threshold (0.0-1.0).
155    pub scene_change_threshold: f64,
156    /// Enable adaptive quantization.
157    pub enable_aq: bool,
158    /// Statistics file path (for two-pass).
159    pub stats_file: Option<String>,
160    /// Bitrate allocation strategy.
161    pub allocation_strategy: AllocationStrategy,
162}
163
164impl Default for EncoderConfig {
165    fn default() -> Self {
166        Self {
167            width: 1920,
168            height: 1080,
169            pass: PassType::SinglePassLookahead,
170            lookahead_frames: 40,
171            target_bitrate: 5_000_000,
172            max_bitrate: None,
173            vbv_buffer_size: None,
174            framerate_num: 30,
175            framerate_den: 1,
176            min_keyint: 10,
177            max_keyint: 250,
178            scene_change_threshold: 0.4,
179            enable_aq: true,
180            stats_file: None,
181            allocation_strategy: AllocationStrategy::Complexity,
182        }
183    }
184}
185
186impl EncoderConfig {
187    /// Create a new encoder configuration.
188    #[must_use]
189    pub fn new(width: u32, height: u32) -> Self {
190        Self {
191            width,
192            height,
193            ..Default::default()
194        }
195    }
196
197    /// Set pass type.
198    #[must_use]
199    pub fn with_pass(mut self, pass: PassType) -> Self {
200        self.pass = pass;
201        self
202    }
203
204    /// Set lookahead frames.
205    #[must_use]
206    pub fn with_lookahead_frames(mut self, frames: usize) -> Self {
207        self.lookahead_frames = frames.clamp(10, 250);
208        self
209    }
210
211    /// Set target bitrate.
212    #[must_use]
213    pub fn with_target_bitrate(mut self, bitrate: u64) -> Self {
214        self.target_bitrate = bitrate;
215        self
216    }
217
218    /// Set VBV parameters.
219    #[must_use]
220    pub fn with_vbv(mut self, buffer_size: u64, max_bitrate: u64) -> Self {
221        self.vbv_buffer_size = Some(buffer_size);
222        self.max_bitrate = Some(max_bitrate);
223        self
224    }
225
226    /// Set frame rate.
227    #[must_use]
228    pub fn with_framerate(mut self, num: u32, den: u32) -> Self {
229        self.framerate_num = num;
230        self.framerate_den = den;
231        self
232    }
233
234    /// Set keyframe interval range.
235    #[must_use]
236    pub fn with_keyint_range(mut self, min: u32, max: u32) -> Self {
237        self.min_keyint = min;
238        self.max_keyint = max;
239        self
240    }
241
242    /// Set statistics file path.
243    #[must_use]
244    pub fn with_stats_file(mut self, path: impl Into<String>) -> Self {
245        self.stats_file = Some(path.into());
246        self
247    }
248
249    /// Set allocation strategy.
250    #[must_use]
251    pub fn with_allocation_strategy(mut self, strategy: AllocationStrategy) -> Self {
252        self.allocation_strategy = strategy;
253        self
254    }
255}
256
257/// Multi-pass encoder state.
258pub struct MultiPassEncoder {
259    config: EncoderConfig,
260    pass: PassType,
261    lookahead_buffer: Option<LookaheadBuffer>,
262    complexity_analyzer: ComplexityAnalyzer,
263    bitrate_allocator: BitrateAllocator,
264    vbv_buffer: Option<VbvBuffer>,
265    pass_statistics: PassStatistics,
266    frame_count: u64,
267}
268
269impl MultiPassEncoder {
270    /// Create a new multi-pass encoder.
271    #[must_use]
272    pub fn new(config: EncoderConfig) -> Self {
273        let complexity_analyzer = ComplexityAnalyzer::new(config.width, config.height);
274
275        let lookahead_buffer = if config.pass == PassType::SinglePassLookahead
276            || config.pass == PassType::SecondPass
277        {
278            let lookahead_config = LookaheadConfig::new(config.lookahead_frames)
279                .with_keyint_range(config.min_keyint, config.max_keyint)
280                .with_scene_threshold(config.scene_change_threshold);
281
282            Some(LookaheadBuffer::new(
283                lookahead_config,
284                config.width,
285                config.height,
286            ))
287        } else {
288            None
289        };
290
291        let allocation_config =
292            AllocationConfig::new(config.allocation_strategy, config.target_bitrate)
293                .with_framerate(config.framerate_num, config.framerate_den);
294
295        let bitrate_allocator = BitrateAllocator::new(allocation_config);
296
297        let vbv_buffer = if let (Some(buffer_size), Some(max_bitrate)) =
298            (config.vbv_buffer_size, config.max_bitrate)
299        {
300            let vbv_config = VbvConfig::new(
301                buffer_size,
302                max_bitrate,
303                config.framerate_num,
304                config.framerate_den,
305            );
306            Some(VbvBuffer::new(vbv_config))
307        } else {
308            None
309        };
310
311        let pass_statistics = PassStatistics::new(
312            config.width,
313            config.height,
314            config.framerate_num,
315            config.framerate_den,
316        );
317
318        Self {
319            pass: config.pass,
320            config,
321            lookahead_buffer,
322            complexity_analyzer,
323            bitrate_allocator,
324            vbv_buffer,
325            pass_statistics,
326            frame_count: 0,
327        }
328    }
329
330    /// Encode a frame (behavior depends on pass type).
331    pub fn encode_frame(
332        &mut self,
333        frame: &VideoFrame,
334    ) -> Result<Option<EncodingResult>, EncoderError> {
335        match self.pass {
336            PassType::FirstPass => self.encode_first_pass(frame),
337            PassType::SecondPass => self.encode_second_pass(frame),
338            PassType::SinglePassLookahead => self.encode_single_pass(frame),
339        }
340    }
341
342    /// First pass: analyze and collect statistics.
343    fn encode_first_pass(
344        &mut self,
345        frame: &VideoFrame,
346    ) -> Result<Option<EncodingResult>, EncoderError> {
347        // Analyze frame complexity
348        let complexity = self.complexity_analyzer.analyze(frame, self.frame_count);
349
350        // Allocate bits (basic allocation for first pass)
351        let allocation = self.bitrate_allocator.allocate(
352            self.frame_count,
353            frame.frame_type,
354            complexity.combined_complexity,
355        );
356
357        // Store statistics
358        let frame_stats = FrameStatistics::new(
359            self.frame_count,
360            frame.frame_type,
361            28.0, // Dummy QP for first pass
362            allocation.target_bits,
363            complexity,
364        );
365
366        self.pass_statistics.add_frame(frame_stats);
367        self.frame_count += 1;
368
369        // First pass doesn't produce packets
370        Ok(None)
371    }
372
373    /// Second pass: encode with optimal bitrate allocation.
374    fn encode_second_pass(
375        &mut self,
376        frame: &VideoFrame,
377    ) -> Result<Option<EncodingResult>, EncoderError> {
378        // Use lookahead if available
379        if let Some(ref mut lookahead) = self.lookahead_buffer {
380            lookahead.add_frame(frame.clone());
381
382            // Wait until lookahead is full (not flushing in this implementation)
383            if !lookahead.is_full() {
384                return Ok(None);
385            }
386
387            // Get next frame from lookahead
388            if let Some(lookahead_frame) = lookahead.get_next_frame() {
389                let complexity = lookahead_frame.complexity.combined_complexity;
390                let allocation = self.bitrate_allocator.allocate(
391                    self.frame_count,
392                    lookahead_frame.assigned_type,
393                    complexity,
394                );
395
396                let result = self.create_encoding_result(
397                    &lookahead_frame.frame,
398                    lookahead_frame.assigned_type,
399                    allocation,
400                    lookahead_frame.qp_offset,
401                );
402
403                self.frame_count += 1;
404                return Ok(Some(result));
405            }
406        } else {
407            // No lookahead, encode directly
408            let complexity = self.complexity_analyzer.analyze(frame, self.frame_count);
409            let allocation = self.bitrate_allocator.allocate(
410                self.frame_count,
411                frame.frame_type,
412                complexity.combined_complexity,
413            );
414
415            let result = self.create_encoding_result(frame, frame.frame_type, allocation, 0);
416            self.frame_count += 1;
417            return Ok(Some(result));
418        }
419
420        Ok(None)
421    }
422
423    /// Single pass with lookahead.
424    fn encode_single_pass(
425        &mut self,
426        frame: &VideoFrame,
427    ) -> Result<Option<EncodingResult>, EncoderError> {
428        if let Some(ref mut lookahead) = self.lookahead_buffer {
429            lookahead.add_frame(frame.clone());
430
431            // Wait until lookahead is full (not flushing in this implementation)
432            if !lookahead.is_full() {
433                return Ok(None);
434            }
435
436            // Get next frame from lookahead
437            if let Some(lookahead_frame) = lookahead.get_next_frame() {
438                let complexity = lookahead_frame.complexity.combined_complexity;
439                let allocation = self.bitrate_allocator.allocate(
440                    self.frame_count,
441                    lookahead_frame.assigned_type,
442                    complexity,
443                );
444
445                // Apply VBV constraints if enabled
446                let target_bits = if let Some(ref vbv) = self.vbv_buffer {
447                    vbv.target_frame_size(
448                        lookahead_frame.assigned_type,
449                        allocation.target_bits as f64,
450                    )
451                } else {
452                    allocation.target_bits
453                };
454
455                let mut result = self.create_encoding_result(
456                    &lookahead_frame.frame,
457                    lookahead_frame.assigned_type,
458                    allocation,
459                    lookahead_frame.qp_offset,
460                );
461
462                result.target_bits = target_bits;
463
464                // Update VBV buffer
465                if let Some(ref mut vbv) = self.vbv_buffer {
466                    vbv.update(target_bits);
467                }
468
469                self.frame_count += 1;
470                return Ok(Some(result));
471            }
472        }
473
474        Ok(None)
475    }
476
477    /// Create encoding result from allocation.
478    fn create_encoding_result(
479        &self,
480        frame: &VideoFrame,
481        frame_type: FrameType,
482        allocation: allocation::FrameAllocation,
483        qp_offset: i32,
484    ) -> EncodingResult {
485        // Calculate QP from target bits (simplified model)
486        let base_qp = self.bits_to_qp(allocation.target_bits);
487        let adjusted_qp = (base_qp + allocation.qp_adjustment + qp_offset as f64).clamp(1.0, 63.0);
488
489        EncodingResult {
490            frame_index: self.frame_count,
491            frame_type,
492            target_bits: allocation.target_bits,
493            min_bits: allocation.min_bits,
494            max_bits: allocation.max_bits,
495            qp: adjusted_qp,
496            complexity: 0.5, // Placeholder
497        }
498    }
499
500    /// Convert target bits to QP (simplified logarithmic model).
501    fn bits_to_qp(&self, target_bits: u64) -> f64 {
502        let pixels = (self.config.width as u64) * (self.config.height as u64);
503        let bpp = target_bits as f64 / pixels as f64;
504
505        // Logarithmic model: QP ≈ 69.0 - 12.0 * log2(bpp)
506        if bpp > 0.0 {
507            (69.0 - 12.0 * bpp.log2()).clamp(1.0, 63.0)
508        } else {
509            51.0 // Default QP
510        }
511    }
512
513    /// Save first-pass statistics to a file.
514    pub fn save_stats(&self, path: &str) -> Result<(), EncoderError> {
515        self.pass_statistics
516            .save_to_file(path)
517            .map_err(|e| EncoderError::IoError(e.to_string()))
518    }
519
520    /// Load first-pass statistics from a file.
521    pub fn load_stats(&mut self, path: &str) -> Result<(), EncoderError> {
522        let stats = PassStatistics::load_from_file(path)
523            .map_err(|e| EncoderError::IoError(e.to_string()))?;
524
525        self.bitrate_allocator.set_first_pass_stats(stats);
526        Ok(())
527    }
528
529    /// Get encoding statistics.
530    #[must_use]
531    pub fn statistics(&self) -> &PassStatistics {
532        &self.pass_statistics
533    }
534
535    /// Get VBV statistics if available.
536    #[must_use]
537    pub fn vbv_statistics(&self) -> Option<VbvStatistics> {
538        self.vbv_buffer.as_ref().map(|vbv| vbv.statistics())
539    }
540
541    /// Get current frame count.
542    #[must_use]
543    pub fn frame_count(&self) -> u64 {
544        self.frame_count
545    }
546
547    /// Reset encoder state.
548    pub fn reset(&mut self) {
549        self.frame_count = 0;
550        self.complexity_analyzer.reset();
551        self.bitrate_allocator.reset();
552
553        if let Some(ref mut lookahead) = self.lookahead_buffer {
554            lookahead.reset();
555        }
556
557        if let Some(ref mut vbv) = self.vbv_buffer {
558            vbv.reset();
559        }
560
561        self.pass_statistics = PassStatistics::new(
562            self.config.width,
563            self.config.height,
564            self.config.framerate_num,
565            self.config.framerate_den,
566        );
567    }
568}
569
570/// Encoding result with bitrate allocation.
571#[derive(Clone, Debug)]
572pub struct EncodingResult {
573    /// Frame index.
574    pub frame_index: u64,
575    /// Frame type.
576    pub frame_type: FrameType,
577    /// Target bits for this frame.
578    pub target_bits: u64,
579    /// Minimum acceptable bits.
580    pub min_bits: u64,
581    /// Maximum allowed bits.
582    pub max_bits: u64,
583    /// Quantization parameter.
584    pub qp: f64,
585    /// Frame complexity.
586    pub complexity: f64,
587}
588
589/// Encoder error types.
590#[derive(Debug)]
591pub enum EncoderError {
592    /// I/O error.
593    IoError(String),
594    /// Configuration error.
595    ConfigError(String),
596    /// Encoding error.
597    EncodingError(String),
598}
599
600impl std::fmt::Display for EncoderError {
601    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
602        match self {
603            Self::IoError(msg) => write!(f, "I/O error: {}", msg),
604            Self::ConfigError(msg) => write!(f, "Configuration error: {}", msg),
605            Self::EncodingError(msg) => write!(f, "Encoding error: {}", msg),
606        }
607    }
608}
609
610impl std::error::Error for EncoderError {}
611
612#[cfg(test)]
613mod tests {
614    use super::*;
615    use crate::frame::Plane;
616    use oximedia_core::{PixelFormat, Rational, Timestamp};
617
618    fn create_test_frame(width: u32, height: u32) -> VideoFrame {
619        let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
620        let size = (width * height) as usize;
621        let data = vec![128u8; size];
622        frame.planes.push(Plane::new(data, width as usize));
623        frame.timestamp = Timestamp::new(0, Rational::new(1, 30));
624        frame
625    }
626
627    #[test]
628    fn test_encoder_config_new() {
629        let config = EncoderConfig::new(1920, 1080);
630        assert_eq!(config.width, 1920);
631        assert_eq!(config.height, 1080);
632        assert_eq!(config.pass, PassType::SinglePassLookahead);
633    }
634
635    #[test]
636    fn test_encoder_config_builder() {
637        let config = EncoderConfig::new(1920, 1080)
638            .with_pass(PassType::FirstPass)
639            .with_lookahead_frames(50)
640            .with_target_bitrate(10_000_000);
641
642        assert_eq!(config.pass, PassType::FirstPass);
643        assert_eq!(config.lookahead_frames, 50);
644        assert_eq!(config.target_bitrate, 10_000_000);
645    }
646
647    #[test]
648    fn test_multipass_encoder_new() {
649        let config = EncoderConfig::new(1920, 1080);
650        let encoder = MultiPassEncoder::new(config);
651        assert_eq!(encoder.frame_count(), 0);
652    }
653
654    #[test]
655    fn test_first_pass_encoding() {
656        let config = EncoderConfig::new(320, 240).with_pass(PassType::FirstPass);
657        let mut encoder = MultiPassEncoder::new(config);
658
659        let frame = create_test_frame(320, 240);
660        let result = encoder.encode_frame(&frame);
661
662        assert!(result.is_ok());
663        assert!(result.expect("should succeed").is_none()); // First pass returns no packets
664        assert_eq!(encoder.frame_count(), 1);
665    }
666
667    #[test]
668    fn test_single_pass_lookahead() {
669        let config = EncoderConfig::new(320, 240)
670            .with_pass(PassType::SinglePassLookahead)
671            .with_lookahead_frames(10);
672
673        let mut encoder = MultiPassEncoder::new(config);
674
675        // Add frames to fill lookahead buffer
676        for _ in 0..15 {
677            let frame = create_test_frame(320, 240);
678            let result = encoder.encode_frame(&frame);
679            assert!(result.is_ok());
680        }
681
682        assert!(encoder.frame_count() > 0);
683    }
684}