#![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 CqpController {
base_qp: u8,
p_offset: i8,
b_offset: i8,
min_qp: u8,
max_qp: u8,
frame_count: u64,
total_bits: u64,
}
impl CqpController {
#[must_use]
pub fn new(config: &RcConfig) -> Self {
Self {
base_qp: config.initial_qp.clamp(config.min_qp, config.max_qp),
p_offset: 0,
b_offset: config.b_qp_offset,
min_qp: config.min_qp,
max_qp: config.max_qp,
frame_count: 0,
total_bits: 0,
}
}
#[must_use]
pub fn with_offsets(base_qp: u8, p_offset: i8, b_offset: i8) -> Self {
Self {
base_qp: base_qp.clamp(1, 63),
p_offset,
b_offset,
min_qp: 1,
max_qp: 63,
frame_count: 0,
total_bits: 0,
}
}
pub fn set_base_qp(&mut self, qp: u8) {
self.base_qp = qp.clamp(self.min_qp, self.max_qp);
}
pub fn set_offsets(&mut self, p_offset: i8, b_offset: i8) {
self.p_offset = p_offset;
self.b_offset = b_offset;
}
#[must_use]
pub fn get_qp(&self, frame_type: FrameType) -> RcOutput {
let offset = match frame_type {
FrameType::Key => 0,
FrameType::Inter | FrameType::Switch => self.p_offset,
FrameType::BiDir => self.b_offset,
};
let qp = self.apply_offset(self.base_qp, offset);
let qp_f = qp as f32;
let mut output = RcOutput {
qp,
qp_f,
..Default::default()
};
output.compute_lambda();
output
}
fn apply_offset(&self, qp: u8, offset: i8) -> u8 {
let result = i16::from(qp) + i16::from(offset);
result.clamp(i16::from(self.min_qp), i16::from(self.max_qp)) as u8
}
pub fn update(&mut self, stats: &FrameStats) {
self.frame_count += 1;
self.total_bits += stats.bits;
}
#[must_use]
pub fn base_qp(&self) -> u8 {
self.base_qp
}
#[must_use]
pub fn i_qp(&self) -> u8 {
self.base_qp
}
#[must_use]
pub fn p_qp(&self) -> u8 {
self.apply_offset(self.base_qp, self.p_offset)
}
#[must_use]
pub fn b_qp(&self) -> u8 {
self.apply_offset(self.base_qp, self.b_offset)
}
#[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 average_bits_per_frame(&self) -> f64 {
if self.frame_count == 0 {
0.0
} else {
self.total_bits as f64 / self.frame_count as f64
}
}
pub fn reset(&mut self) {
self.frame_count = 0;
self.total_bits = 0;
}
}
impl Default for CqpController {
fn default() -> Self {
Self {
base_qp: 28,
p_offset: 0,
b_offset: 2,
min_qp: 1,
max_qp: 63,
frame_count: 0,
total_bits: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cqp_creation() {
let config = RcConfig::cqp(28);
let controller = CqpController::new(&config);
assert_eq!(controller.base_qp(), 28);
}
#[test]
fn test_cqp_with_offsets() {
let controller = CqpController::with_offsets(28, 2, 4);
assert_eq!(controller.i_qp(), 28);
assert_eq!(controller.p_qp(), 30);
assert_eq!(controller.b_qp(), 32);
}
#[test]
fn test_get_qp_by_frame_type() {
let controller = CqpController::with_offsets(28, 2, 4);
let output = controller.get_qp(FrameType::Key);
assert_eq!(output.qp, 28);
let output = controller.get_qp(FrameType::Inter);
assert_eq!(output.qp, 30);
let output = controller.get_qp(FrameType::BiDir);
assert_eq!(output.qp, 32);
}
#[test]
fn test_qp_clamping() {
let mut controller = CqpController::with_offsets(5, -10, 10);
controller.min_qp = 1;
controller.max_qp = 10;
assert_eq!(controller.i_qp(), 5);
assert_eq!(controller.p_qp(), 1);
assert_eq!(controller.b_qp(), 10);
}
#[test]
fn test_statistics_tracking() {
let mut controller = CqpController::default();
let mut stats = FrameStats::new(0, FrameType::Key);
stats.bits = 100_000;
controller.update(&stats);
let mut stats = FrameStats::new(1, FrameType::Inter);
stats.bits = 50_000;
controller.update(&stats);
assert_eq!(controller.frame_count(), 2);
assert_eq!(controller.total_bits(), 150_000);
assert!((controller.average_bits_per_frame() - 75_000.0).abs() < f64::EPSILON);
}
#[test]
fn test_reset() {
let mut controller = CqpController::default();
let mut stats = FrameStats::new(0, FrameType::Key);
stats.bits = 100_000;
controller.update(&stats);
controller.reset();
assert_eq!(controller.frame_count(), 0);
assert_eq!(controller.total_bits(), 0);
}
#[test]
fn test_lambda_calculation() {
let controller = CqpController::default();
let output = controller.get_qp(FrameType::Key);
assert!(output.lambda > 0.0);
assert!(output.lambda_me > 0.0);
assert!((output.lambda_me - output.lambda.sqrt()).abs() < f64::EPSILON);
}
#[test]
fn test_set_base_qp() {
let mut controller = CqpController::default();
controller.set_base_qp(35);
assert_eq!(controller.base_qp(), 35);
controller.min_qp = 10;
controller.max_qp = 40;
controller.set_base_qp(5);
assert_eq!(controller.base_qp(), 10);
controller.set_base_qp(50);
assert_eq!(controller.base_qp(), 40);
}
}