#![allow(clippy::cast_lossless)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
#![forbid(unsafe_code)]
use crate::frame::FrameType;
use super::types::{FrameStats, RcConfig, RcOutput};
#[derive(Clone, Debug)]
pub struct CrfController {
crf: f32,
current_qp: f32,
min_qp: u8,
max_qp: u8,
i_qp_offset: i8,
b_qp_offset: i8,
enable_aq: bool,
aq_strength: f32,
frame_count: u64,
total_bits: u64,
average_complexity: f32,
complexity_weight: f32,
qp_history: Vec<f32>,
max_qp_history: usize,
}
impl CrfController {
#[must_use]
pub fn new(config: &RcConfig) -> Self {
Self {
crf: config.crf.clamp(0.0, 63.0),
current_qp: config.crf,
min_qp: config.min_qp,
max_qp: config.max_qp,
i_qp_offset: config.i_qp_offset,
b_qp_offset: config.b_qp_offset,
enable_aq: config.enable_aq,
aq_strength: config.aq_strength,
frame_count: 0,
total_bits: 0,
average_complexity: 1.0,
complexity_weight: 0.1,
qp_history: Vec::with_capacity(30),
max_qp_history: 30,
}
}
#[must_use]
pub fn with_crf(crf: f32) -> Self {
Self {
crf: crf.clamp(0.0, 63.0),
current_qp: crf,
..Default::default()
}
}
pub fn set_crf(&mut self, crf: f32) {
self.crf = crf.clamp(0.0, 63.0);
}
pub fn set_enable_aq(&mut self, enable: bool) {
self.enable_aq = enable;
}
pub fn set_aq_strength(&mut self, strength: f32) {
self.aq_strength = strength.clamp(0.0, 2.0);
}
#[must_use]
pub fn get_rc(&self, frame_type: FrameType, complexity: f32) -> RcOutput {
let base_qp = self.crf_to_qp(self.crf);
let complexity_adjustment = self.calculate_complexity_adjustment(complexity);
let offset = match frame_type {
FrameType::Key => self.i_qp_offset,
FrameType::BiDir => self.b_qp_offset,
FrameType::Inter | FrameType::Switch => 0,
};
let final_qp = (base_qp + complexity_adjustment + offset as f32)
.clamp(self.min_qp as f32, self.max_qp as f32);
let qp = final_qp.round() as u8;
let mut output = RcOutput {
qp,
qp_f: final_qp,
..Default::default()
};
output.compute_lambda();
output
}
fn crf_to_qp(&self, crf: f32) -> f32 {
crf
}
#[must_use]
pub fn qp_to_crf(&self, qp: f32) -> f32 {
qp
}
fn calculate_complexity_adjustment(&self, complexity: f32) -> f32 {
if complexity <= 0.0 || self.average_complexity <= 0.0 {
return 0.0;
}
let ratio = complexity / self.average_complexity;
let log_ratio = ratio.ln();
let adjustment = log_ratio * 2.0 * self.aq_strength;
adjustment.clamp(-4.0, 4.0)
}
pub fn update(&mut self, stats: &FrameStats) {
self.frame_count += 1;
self.total_bits += stats.bits;
if stats.complexity > 0.0 {
self.average_complexity = self.average_complexity * (1.0 - self.complexity_weight)
+ stats.complexity * self.complexity_weight;
}
self.qp_history.push(stats.qp_f);
if self.qp_history.len() > self.max_qp_history {
self.qp_history.remove(0);
}
self.current_qp = stats.qp_f;
}
#[must_use]
pub fn crf(&self) -> f32 {
self.crf
}
#[must_use]
pub fn current_qp(&self) -> f32 {
self.current_qp
}
#[must_use]
pub fn average_qp(&self) -> f32 {
if self.qp_history.is_empty() {
return self.crf;
}
self.qp_history.iter().sum::<f32>() / self.qp_history.len() as f32
}
#[must_use]
pub fn average_complexity(&self) -> f32 {
self.average_complexity
}
#[must_use]
pub fn frame_count(&self) -> u64 {
self.frame_count
}
#[must_use]
pub fn total_bits(&self) -> u64 {
self.total_bits
}
#[must_use]
pub fn estimated_bitrate(&self, fps: f64) -> f64 {
if self.frame_count == 0 || fps <= 0.0 {
return 0.0;
}
let avg_bits_per_frame = self.total_bits as f64 / self.frame_count as f64;
avg_bits_per_frame * fps
}
#[must_use]
pub fn qp_variance(&self) -> f32 {
if self.qp_history.len() < 2 {
return 0.0;
}
let avg = self.average_qp();
let variance: f32 = self
.qp_history
.iter()
.map(|qp| (qp - avg).powi(2))
.sum::<f32>()
/ self.qp_history.len() as f32;
variance.sqrt()
}
pub fn reset(&mut self) {
self.frame_count = 0;
self.total_bits = 0;
self.current_qp = self.crf;
self.average_complexity = 1.0;
self.qp_history.clear();
}
#[must_use]
pub fn crf_to_lambda(&self) -> f64 {
RcOutput::qp_to_lambda(self.crf)
}
}
impl Default for CrfController {
fn default() -> Self {
Self {
crf: 23.0,
current_qp: 23.0,
min_qp: 1,
max_qp: 63,
i_qp_offset: -2,
b_qp_offset: 2,
enable_aq: true,
aq_strength: 1.0,
frame_count: 0,
total_bits: 0,
average_complexity: 1.0,
complexity_weight: 0.1,
qp_history: Vec::with_capacity(30),
max_qp_history: 30,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum QualityPreset {
Lossless,
VisuallyLossless,
High,
Good,
Medium,
Low,
Minimum,
}
impl QualityPreset {
#[must_use]
pub fn to_crf(self) -> f32 {
match self {
Self::Lossless => 0.0,
Self::VisuallyLossless => 18.0,
Self::High => 20.0,
Self::Good => 23.0,
Self::Medium => 28.0,
Self::Low => 35.0,
Self::Minimum => 51.0,
}
}
#[must_use]
pub fn to_controller(self) -> CrfController {
CrfController::with_crf(self.to_crf())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crf_creation() {
let config = RcConfig::crf(23.0);
let controller = CrfController::new(&config);
assert!((controller.crf() - 23.0).abs() < f32::EPSILON);
}
#[test]
fn test_crf_with_value() {
let controller = CrfController::with_crf(28.0);
assert!((controller.crf() - 28.0).abs() < f32::EPSILON);
}
#[test]
fn test_get_rc_i_frame() {
let controller = CrfController::default();
let output = controller.get_rc(FrameType::Key, 1.0);
assert!(!output.drop_frame);
assert!(output.qp > 0);
}
#[test]
fn test_frame_type_qp_offsets() {
let mut controller = CrfController::default();
controller.i_qp_offset = -4;
controller.b_qp_offset = 4;
let i_output = controller.get_rc(FrameType::Key, 1.0);
let p_output = controller.get_rc(FrameType::Inter, 1.0);
let b_output = controller.get_rc(FrameType::BiDir, 1.0);
assert!(i_output.qp < p_output.qp);
assert!(b_output.qp > p_output.qp);
}
#[test]
fn test_complexity_adjustment() {
let controller = CrfController::default();
let low_complexity = controller.get_rc(FrameType::Inter, 0.5);
let high_complexity = controller.get_rc(FrameType::Inter, 2.0);
assert!(high_complexity.qp_f > low_complexity.qp_f);
}
#[test]
fn test_statistics_update() {
let mut controller = CrfController::default();
let mut stats = FrameStats::new(0, FrameType::Key);
stats.bits = 100_000;
stats.qp_f = 24.0;
stats.complexity = 1.2;
controller.update(&stats);
assert_eq!(controller.frame_count(), 1);
assert_eq!(controller.total_bits(), 100_000);
}
#[test]
fn test_average_qp() {
let mut controller = CrfController::default();
for i in 0..10 {
let mut stats = FrameStats::new(i, FrameType::Inter);
stats.qp_f = 24.0 + (i as f32 * 0.5);
controller.update(&stats);
}
let avg = controller.average_qp();
assert!((avg - 26.25).abs() < 0.1);
}
#[test]
fn test_qp_variance() {
let mut controller = CrfController::default();
for i in 0..10 {
let mut stats = FrameStats::new(i, FrameType::Inter);
stats.qp_f = 24.0;
controller.update(&stats);
}
assert!(controller.qp_variance() < 0.01);
controller.reset();
for i in 0..10 {
let mut stats = FrameStats::new(i, FrameType::Inter);
stats.qp_f = 20.0 + (i as f32 * 2.0);
controller.update(&stats);
}
assert!(controller.qp_variance() > 1.0);
}
#[test]
fn test_quality_presets() {
assert!((QualityPreset::Lossless.to_crf() - 0.0).abs() < f32::EPSILON);
assert!((QualityPreset::Good.to_crf() - 23.0).abs() < f32::EPSILON);
assert!((QualityPreset::Minimum.to_crf() - 51.0).abs() < f32::EPSILON);
let controller = QualityPreset::High.to_controller();
assert!((controller.crf() - 20.0).abs() < f32::EPSILON);
}
#[test]
fn test_lambda_calculation() {
let controller = CrfController::default();
let lambda = controller.crf_to_lambda();
assert!(lambda > 0.0);
}
#[test]
fn test_reset() {
let mut controller = CrfController::default();
for i in 0..10 {
let mut stats = FrameStats::new(i, FrameType::Inter);
stats.bits = 100_000;
stats.qp_f = 24.0;
stats.complexity = 1.0;
controller.update(&stats);
}
controller.reset();
assert_eq!(controller.frame_count(), 0);
assert_eq!(controller.total_bits(), 0);
assert!(controller.qp_history.is_empty());
}
#[test]
fn test_estimated_bitrate() {
let mut controller = CrfController::default();
for i in 0..30 {
let mut stats = FrameStats::new(i, FrameType::Inter);
stats.bits = 100_000; controller.update(&stats);
}
let bitrate = controller.estimated_bitrate(30.0);
assert!((bitrate - 3_000_000.0).abs() < 1.0);
}
}