use crate::error::CodecResult;
use std::collections::VecDeque;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RateControlMode {
ConstantQuality,
ConstantBitrate,
VariableBitrate,
TwoPass,
}
pub struct RateController {
mode: RateControlMode,
target_bitrate: u64,
target_quality: u8,
framerate: f64,
buffer_size: u64,
buffer_fullness: i64,
frame_history: VecDeque<FrameStats>,
history_size: usize,
frame_number: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct FrameStats {
pub frame_number: u64,
pub is_keyframe: bool,
pub size_bits: u32,
pub quality: u8,
pub complexity: f64,
}
impl RateController {
#[must_use]
pub fn new(
mode: RateControlMode,
target_bitrate: u64,
target_quality: u8,
framerate: f64,
) -> Self {
let buffer_size = target_bitrate * 2;
Self {
mode,
target_bitrate,
target_quality: target_quality.min(63),
framerate,
buffer_size,
buffer_fullness: (buffer_size / 2) as i64,
frame_history: VecDeque::new(),
history_size: 100,
frame_number: 0,
}
}
#[must_use]
pub fn get_frame_quality(&mut self, is_keyframe: bool, complexity: f64) -> u8 {
match self.mode {
RateControlMode::ConstantQuality => self.target_quality,
RateControlMode::ConstantBitrate => self.calculate_cbr_quality(is_keyframe, complexity),
RateControlMode::VariableBitrate => self.calculate_vbr_quality(is_keyframe, complexity),
RateControlMode::TwoPass => self.calculate_twopass_quality(is_keyframe),
}
}
pub fn update_stats(
&mut self,
is_keyframe: bool,
size_bits: u32,
quality: u8,
complexity: f64,
) {
let stats = FrameStats {
frame_number: self.frame_number,
is_keyframe,
size_bits,
quality,
complexity,
};
self.frame_history.push_back(stats);
if self.frame_history.len() > self.history_size {
self.frame_history.pop_front();
}
if self.mode == RateControlMode::ConstantBitrate {
let target_frame_bits = (self.target_bitrate as f64 / self.framerate) as i64;
self.buffer_fullness += target_frame_bits - i64::from(size_bits);
self.buffer_fullness = self.buffer_fullness.clamp(0, self.buffer_size as i64);
}
self.frame_number += 1;
}
fn calculate_cbr_quality(&self, is_keyframe: bool, complexity: f64) -> u8 {
let target_frame_bits = self.target_bitrate as f64 / self.framerate;
let buffer_ratio = self.buffer_fullness as f64 / self.buffer_size as f64;
let buffer_adjustment = if buffer_ratio > 0.75 {
5
} else if buffer_ratio < 0.25 {
-5i8
} else {
0
};
let avg_complexity = self.get_average_complexity();
let complexity_ratio = if avg_complexity > 0.0 {
complexity / avg_complexity
} else {
1.0
};
let complexity_adjustment = if complexity_ratio > 1.5 {
5 } else if complexity_ratio < 0.5 {
-5i8 } else {
0
};
let keyframe_adjustment = if is_keyframe { 5i8 } else { 0 };
let quality = self.target_quality as i8
+ buffer_adjustment
+ complexity_adjustment
+ keyframe_adjustment;
quality.clamp(0, 63) as u8
}
fn calculate_vbr_quality(&self, is_keyframe: bool, complexity: f64) -> u8 {
let avg_complexity = self.get_average_complexity();
let complexity_ratio = if avg_complexity > 0.0 {
complexity / avg_complexity
} else {
1.0
};
let adjustment = if complexity_ratio > 1.5 {
3 } else if complexity_ratio < 0.5 {
-3i8 } else {
0
};
let keyframe_adjustment = if is_keyframe { 3i8 } else { 0 };
let quality = self.target_quality as i8 + adjustment + keyframe_adjustment;
quality.clamp(0, 63) as u8
}
fn calculate_twopass_quality(&self, _is_keyframe: bool) -> u8 {
self.target_quality
}
fn get_average_complexity(&self) -> f64 {
if self.frame_history.is_empty() {
return 1.0;
}
let sum: f64 = self.frame_history.iter().map(|s| s.complexity).sum();
sum / self.frame_history.len() as f64
}
#[must_use]
pub fn get_average_bitrate(&self) -> f64 {
if self.frame_history.is_empty() {
return 0.0;
}
let total_bits: u64 = self
.frame_history
.iter()
.map(|s| u64::from(s.size_bits))
.sum();
let duration = self.frame_history.len() as f64 / self.framerate;
if duration > 0.0 {
total_bits as f64 / duration
} else {
0.0
}
}
#[must_use]
pub fn buffer_fullness_ratio(&self) -> f64 {
self.buffer_fullness as f64 / self.buffer_size as f64
}
pub fn reset(&mut self) {
self.frame_history.clear();
self.buffer_fullness = (self.buffer_size / 2) as i64;
self.frame_number = 0;
}
}
#[must_use]
pub fn estimate_frame_complexity(
y_plane: &[u8],
width: usize,
height: usize,
stride: usize,
) -> f64 {
if width == 0 || height == 0 {
return 0.0;
}
let mut variance_sum = 0u64;
let block_size = 16;
for by in (0..height).step_by(block_size) {
for bx in (0..width).step_by(block_size) {
let variance = calculate_block_variance(y_plane, stride, bx, by, block_size);
variance_sum += u64::from(variance);
}
}
let num_blocks =
((width + block_size - 1) / block_size) * ((height + block_size - 1) / block_size);
if num_blocks > 0 {
(variance_sum / num_blocks as u64) as f64
} else {
0.0
}
}
fn calculate_block_variance(
plane: &[u8],
stride: usize,
x: usize,
y: usize,
block_size: usize,
) -> u32 {
let mut sum = 0u32;
let mut sum_sq = 0u32;
let mut count = 0u32;
for dy in 0..block_size {
if y + dy >= plane.len() / stride {
break;
}
for dx in 0..block_size {
if x + dx >= stride {
break;
}
let offset = (y + dy) * stride + x + dx;
if offset >= plane.len() {
break;
}
let pixel = u32::from(plane[offset]);
sum += pixel;
sum_sq += pixel * pixel;
count += 1;
}
}
if count == 0 {
return 0;
}
let mean = sum / count;
let variance = (sum_sq / count).saturating_sub(mean * mean);
variance
}
pub struct QuantMapper {
quality_to_qp: [u8; 64],
}
impl QuantMapper {
#[must_use]
pub fn new() -> Self {
let mut quality_to_qp = [0u8; 64];
for q in 0..64 {
let qp = if q < 20 {
10 + q / 2
} else if q < 40 {
20 + (q - 20)
} else {
40 + (q - 40) * 2
};
quality_to_qp[q] = qp.min(255) as u8;
}
Self { quality_to_qp }
}
#[must_use]
pub fn quality_to_qp(&self, quality: u8) -> u8 {
let quality = quality.min(63);
self.quality_to_qp[quality as usize]
}
#[must_use]
pub fn qp_to_quality(&self, qp: u8) -> u8 {
for q in 0..64 {
if self.quality_to_qp[q] >= qp {
return q as u8;
}
}
63
}
}
impl Default for QuantMapper {
fn default() -> Self {
Self::new()
}
}
pub struct AdaptiveQuantization {
base_quality: u8,
strength: f32,
}
impl AdaptiveQuantization {
#[must_use]
pub const fn new(base_quality: u8, strength: f32) -> Self {
Self {
base_quality,
strength,
}
}
#[must_use]
pub fn get_mb_quality(&self, plane: &[u8], stride: usize, mb_x: usize, mb_y: usize) -> u8 {
let x = mb_x * 16;
let y = mb_y * 16;
let variance = calculate_block_variance(plane, stride, x, y, 16);
let adjustment = if variance < 100 {
-(self.strength * 5.0) as i8
} else if variance > 1000 {
(self.strength * 5.0) as i8
} else {
0
};
let quality = self.base_quality as i8 + adjustment;
quality.clamp(0, 63) as u8
}
}
#[derive(Debug, Clone, Copy)]
pub struct BitrateAllocation {
pub intra_factor: f32,
pub inter_factor: f32,
}
impl Default for BitrateAllocation {
fn default() -> Self {
Self {
intra_factor: 3.0, inter_factor: 1.0,
}
}
}
impl BitrateAllocation {
#[must_use]
pub fn get_target_bits(&self, base_bits: f64, is_keyframe: bool) -> u32 {
let factor = if is_keyframe {
self.intra_factor
} else {
self.inter_factor
};
(base_bits * f64::from(factor)) as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rate_controller_creation() {
let rc = RateController::new(RateControlMode::ConstantBitrate, 2_000_000, 30, 30.0);
assert_eq!(rc.target_bitrate, 2_000_000);
assert_eq!(rc.target_quality, 30);
}
#[test]
fn test_constant_quality_mode() {
let mut rc = RateController::new(RateControlMode::ConstantQuality, 2_000_000, 30, 30.0);
let quality = rc.get_frame_quality(false, 100.0);
assert_eq!(quality, 30);
}
#[test]
fn test_frame_stats_update() {
let mut rc = RateController::new(RateControlMode::ConstantBitrate, 2_000_000, 30, 30.0);
rc.update_stats(true, 10000, 30, 100.0);
assert_eq!(rc.frame_history.len(), 1);
assert_eq!(rc.frame_number, 1);
}
#[test]
fn test_complexity_estimation() {
let plane = vec![128u8; 640 * 480];
let complexity = estimate_frame_complexity(&plane, 640, 480, 640);
assert_eq!(complexity, 0.0); }
#[test]
fn test_quant_mapper() {
let mapper = QuantMapper::new();
let qp = mapper.quality_to_qp(30);
let quality = mapper.qp_to_quality(qp);
assert!((quality as i16 - 30).abs() <= 2); }
#[test]
fn test_adaptive_quantization() {
let aq = AdaptiveQuantization::new(30, 0.5);
let plane = vec![128u8; 640 * 480];
let quality = aq.get_mb_quality(&plane, 640, 0, 0);
assert!(quality < 30); }
#[test]
fn test_bitrate_allocation() {
let alloc = BitrateAllocation::default();
let base_bits = 10000.0;
let intra_bits = alloc.get_target_bits(base_bits, true);
let inter_bits = alloc.get_target_bits(base_bits, false);
assert!(intra_bits > inter_bits);
}
}