#![forbid(unsafe_code)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_lossless)]
#![allow(clippy::module_name_repetitions)]
pub mod allocation;
pub mod complexity;
pub mod examples;
pub mod lookahead;
pub mod stats;
pub mod vbv;
use crate::frame::{FrameType, VideoFrame};
use crate::traits::EncodedPacket;
use allocation::{AllocationConfig, AllocationStrategy, BitrateAllocator, VbvAwareAllocator};
use complexity::ComplexityAnalyzer;
use lookahead::{LookaheadBuffer, LookaheadConfig};
use stats::{FrameStatistics, PassStatistics};
use vbv::{VbvBuffer, VbvConfig};
pub use allocation::{FrameAllocation, VbvAwareAllocator as Allocator};
pub use complexity::{ComplexityAnalyzer as Analyzer, FrameComplexity};
pub use lookahead::{LookaheadAnalysis, LookaheadFrame, SceneChangeDetector};
pub use stats::{ComplexityStats, FrameStatistics as Stats, PassStatistics as PassStats};
pub use vbv::{VbvBuffer as Buffer, VbvConfig as BufferConfig, VbvStatistics};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PassType {
FirstPass,
SecondPass,
SinglePassLookahead,
}
#[derive(Clone, Debug)]
pub struct EncoderConfig {
pub width: u32,
pub height: u32,
pub pass: PassType,
pub lookahead_frames: usize,
pub target_bitrate: u64,
pub max_bitrate: Option<u64>,
pub vbv_buffer_size: Option<u64>,
pub framerate_num: u32,
pub framerate_den: u32,
pub min_keyint: u32,
pub max_keyint: u32,
pub scene_change_threshold: f64,
pub enable_aq: bool,
pub stats_file: Option<String>,
pub allocation_strategy: AllocationStrategy,
}
impl Default for EncoderConfig {
fn default() -> Self {
Self {
width: 1920,
height: 1080,
pass: PassType::SinglePassLookahead,
lookahead_frames: 40,
target_bitrate: 5_000_000,
max_bitrate: None,
vbv_buffer_size: None,
framerate_num: 30,
framerate_den: 1,
min_keyint: 10,
max_keyint: 250,
scene_change_threshold: 0.4,
enable_aq: true,
stats_file: None,
allocation_strategy: AllocationStrategy::Complexity,
}
}
}
impl EncoderConfig {
#[must_use]
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
..Default::default()
}
}
#[must_use]
pub fn with_pass(mut self, pass: PassType) -> Self {
self.pass = pass;
self
}
#[must_use]
pub fn with_lookahead_frames(mut self, frames: usize) -> Self {
self.lookahead_frames = frames.clamp(10, 250);
self
}
#[must_use]
pub fn with_target_bitrate(mut self, bitrate: u64) -> Self {
self.target_bitrate = bitrate;
self
}
#[must_use]
pub fn with_vbv(mut self, buffer_size: u64, max_bitrate: u64) -> Self {
self.vbv_buffer_size = Some(buffer_size);
self.max_bitrate = Some(max_bitrate);
self
}
#[must_use]
pub fn with_framerate(mut self, num: u32, den: u32) -> Self {
self.framerate_num = num;
self.framerate_den = den;
self
}
#[must_use]
pub fn with_keyint_range(mut self, min: u32, max: u32) -> Self {
self.min_keyint = min;
self.max_keyint = max;
self
}
#[must_use]
pub fn with_stats_file(mut self, path: impl Into<String>) -> Self {
self.stats_file = Some(path.into());
self
}
#[must_use]
pub fn with_allocation_strategy(mut self, strategy: AllocationStrategy) -> Self {
self.allocation_strategy = strategy;
self
}
}
pub struct MultiPassEncoder {
config: EncoderConfig,
pass: PassType,
lookahead_buffer: Option<LookaheadBuffer>,
complexity_analyzer: ComplexityAnalyzer,
bitrate_allocator: BitrateAllocator,
vbv_buffer: Option<VbvBuffer>,
pass_statistics: PassStatistics,
frame_count: u64,
}
impl MultiPassEncoder {
#[must_use]
pub fn new(config: EncoderConfig) -> Self {
let complexity_analyzer = ComplexityAnalyzer::new(config.width, config.height);
let lookahead_buffer = if config.pass == PassType::SinglePassLookahead
|| config.pass == PassType::SecondPass
{
let lookahead_config = LookaheadConfig::new(config.lookahead_frames)
.with_keyint_range(config.min_keyint, config.max_keyint)
.with_scene_threshold(config.scene_change_threshold);
Some(LookaheadBuffer::new(
lookahead_config,
config.width,
config.height,
))
} else {
None
};
let allocation_config =
AllocationConfig::new(config.allocation_strategy, config.target_bitrate)
.with_framerate(config.framerate_num, config.framerate_den);
let bitrate_allocator = BitrateAllocator::new(allocation_config);
let vbv_buffer = if let (Some(buffer_size), Some(max_bitrate)) =
(config.vbv_buffer_size, config.max_bitrate)
{
let vbv_config = VbvConfig::new(
buffer_size,
max_bitrate,
config.framerate_num,
config.framerate_den,
);
Some(VbvBuffer::new(vbv_config))
} else {
None
};
let pass_statistics = PassStatistics::new(
config.width,
config.height,
config.framerate_num,
config.framerate_den,
);
Self {
pass: config.pass,
config,
lookahead_buffer,
complexity_analyzer,
bitrate_allocator,
vbv_buffer,
pass_statistics,
frame_count: 0,
}
}
pub fn encode_frame(
&mut self,
frame: &VideoFrame,
) -> Result<Option<EncodingResult>, EncoderError> {
match self.pass {
PassType::FirstPass => self.encode_first_pass(frame),
PassType::SecondPass => self.encode_second_pass(frame),
PassType::SinglePassLookahead => self.encode_single_pass(frame),
}
}
fn encode_first_pass(
&mut self,
frame: &VideoFrame,
) -> Result<Option<EncodingResult>, EncoderError> {
let complexity = self.complexity_analyzer.analyze(frame, self.frame_count);
let allocation = self.bitrate_allocator.allocate(
self.frame_count,
frame.frame_type,
complexity.combined_complexity,
);
let frame_stats = FrameStatistics::new(
self.frame_count,
frame.frame_type,
28.0, allocation.target_bits,
complexity,
);
self.pass_statistics.add_frame(frame_stats);
self.frame_count += 1;
Ok(None)
}
fn encode_second_pass(
&mut self,
frame: &VideoFrame,
) -> Result<Option<EncodingResult>, EncoderError> {
if let Some(ref mut lookahead) = self.lookahead_buffer {
lookahead.add_frame(frame.clone());
if !lookahead.is_full() {
return Ok(None);
}
if let Some(lookahead_frame) = lookahead.get_next_frame() {
let complexity = lookahead_frame.complexity.combined_complexity;
let allocation = self.bitrate_allocator.allocate(
self.frame_count,
lookahead_frame.assigned_type,
complexity,
);
let result = self.create_encoding_result(
&lookahead_frame.frame,
lookahead_frame.assigned_type,
allocation,
lookahead_frame.qp_offset,
);
self.frame_count += 1;
return Ok(Some(result));
}
} else {
let complexity = self.complexity_analyzer.analyze(frame, self.frame_count);
let allocation = self.bitrate_allocator.allocate(
self.frame_count,
frame.frame_type,
complexity.combined_complexity,
);
let result = self.create_encoding_result(frame, frame.frame_type, allocation, 0);
self.frame_count += 1;
return Ok(Some(result));
}
Ok(None)
}
fn encode_single_pass(
&mut self,
frame: &VideoFrame,
) -> Result<Option<EncodingResult>, EncoderError> {
if let Some(ref mut lookahead) = self.lookahead_buffer {
lookahead.add_frame(frame.clone());
if !lookahead.is_full() {
return Ok(None);
}
if let Some(lookahead_frame) = lookahead.get_next_frame() {
let complexity = lookahead_frame.complexity.combined_complexity;
let allocation = self.bitrate_allocator.allocate(
self.frame_count,
lookahead_frame.assigned_type,
complexity,
);
let target_bits = if let Some(ref vbv) = self.vbv_buffer {
vbv.target_frame_size(
lookahead_frame.assigned_type,
allocation.target_bits as f64,
)
} else {
allocation.target_bits
};
let mut result = self.create_encoding_result(
&lookahead_frame.frame,
lookahead_frame.assigned_type,
allocation,
lookahead_frame.qp_offset,
);
result.target_bits = target_bits;
if let Some(ref mut vbv) = self.vbv_buffer {
vbv.update(target_bits);
}
self.frame_count += 1;
return Ok(Some(result));
}
}
Ok(None)
}
fn create_encoding_result(
&self,
frame: &VideoFrame,
frame_type: FrameType,
allocation: allocation::FrameAllocation,
qp_offset: i32,
) -> EncodingResult {
let base_qp = self.bits_to_qp(allocation.target_bits);
let adjusted_qp = (base_qp + allocation.qp_adjustment + qp_offset as f64).clamp(1.0, 63.0);
EncodingResult {
frame_index: self.frame_count,
frame_type,
target_bits: allocation.target_bits,
min_bits: allocation.min_bits,
max_bits: allocation.max_bits,
qp: adjusted_qp,
complexity: 0.5, }
}
fn bits_to_qp(&self, target_bits: u64) -> f64 {
let pixels = (self.config.width as u64) * (self.config.height as u64);
let bpp = target_bits as f64 / pixels as f64;
if bpp > 0.0 {
(69.0 - 12.0 * bpp.log2()).clamp(1.0, 63.0)
} else {
51.0 }
}
pub fn save_stats(&self, path: &str) -> Result<(), EncoderError> {
self.pass_statistics
.save_to_file(path)
.map_err(|e| EncoderError::IoError(e.to_string()))
}
pub fn load_stats(&mut self, path: &str) -> Result<(), EncoderError> {
let stats = PassStatistics::load_from_file(path)
.map_err(|e| EncoderError::IoError(e.to_string()))?;
self.bitrate_allocator.set_first_pass_stats(stats);
Ok(())
}
#[must_use]
pub fn statistics(&self) -> &PassStatistics {
&self.pass_statistics
}
#[must_use]
pub fn vbv_statistics(&self) -> Option<VbvStatistics> {
self.vbv_buffer.as_ref().map(|vbv| vbv.statistics())
}
#[must_use]
pub fn frame_count(&self) -> u64 {
self.frame_count
}
pub fn reset(&mut self) {
self.frame_count = 0;
self.complexity_analyzer.reset();
self.bitrate_allocator.reset();
if let Some(ref mut lookahead) = self.lookahead_buffer {
lookahead.reset();
}
if let Some(ref mut vbv) = self.vbv_buffer {
vbv.reset();
}
self.pass_statistics = PassStatistics::new(
self.config.width,
self.config.height,
self.config.framerate_num,
self.config.framerate_den,
);
}
}
#[derive(Clone, Debug)]
pub struct EncodingResult {
pub frame_index: u64,
pub frame_type: FrameType,
pub target_bits: u64,
pub min_bits: u64,
pub max_bits: u64,
pub qp: f64,
pub complexity: f64,
}
#[derive(Debug)]
pub enum EncoderError {
IoError(String),
ConfigError(String),
EncodingError(String),
}
impl std::fmt::Display for EncoderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::IoError(msg) => write!(f, "I/O error: {}", msg),
Self::ConfigError(msg) => write!(f, "Configuration error: {}", msg),
Self::EncodingError(msg) => write!(f, "Encoding error: {}", msg),
}
}
}
impl std::error::Error for EncoderError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::Plane;
use oximedia_core::{PixelFormat, Rational, Timestamp};
fn create_test_frame(width: u32, height: u32) -> VideoFrame {
let mut frame = VideoFrame::new(PixelFormat::Yuv420p, width, height);
let size = (width * height) as usize;
let data = vec![128u8; size];
frame.planes.push(Plane::new(data, width as usize));
frame.timestamp = Timestamp::new(0, Rational::new(1, 30));
frame
}
#[test]
fn test_encoder_config_new() {
let config = EncoderConfig::new(1920, 1080);
assert_eq!(config.width, 1920);
assert_eq!(config.height, 1080);
assert_eq!(config.pass, PassType::SinglePassLookahead);
}
#[test]
fn test_encoder_config_builder() {
let config = EncoderConfig::new(1920, 1080)
.with_pass(PassType::FirstPass)
.with_lookahead_frames(50)
.with_target_bitrate(10_000_000);
assert_eq!(config.pass, PassType::FirstPass);
assert_eq!(config.lookahead_frames, 50);
assert_eq!(config.target_bitrate, 10_000_000);
}
#[test]
fn test_multipass_encoder_new() {
let config = EncoderConfig::new(1920, 1080);
let encoder = MultiPassEncoder::new(config);
assert_eq!(encoder.frame_count(), 0);
}
#[test]
fn test_first_pass_encoding() {
let config = EncoderConfig::new(320, 240).with_pass(PassType::FirstPass);
let mut encoder = MultiPassEncoder::new(config);
let frame = create_test_frame(320, 240);
let result = encoder.encode_frame(&frame);
assert!(result.is_ok());
assert!(result.expect("should succeed").is_none()); assert_eq!(encoder.frame_count(), 1);
}
#[test]
fn test_single_pass_lookahead() {
let config = EncoderConfig::new(320, 240)
.with_pass(PassType::SinglePassLookahead)
.with_lookahead_frames(10);
let mut encoder = MultiPassEncoder::new(config);
for _ in 0..15 {
let frame = create_test_frame(320, 240);
let result = encoder.encode_frame(&frame);
assert!(result.is_ok());
}
assert!(encoder.frame_count() > 0);
}
}