#![allow(clippy::cast_lossless)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::manual_clamp)]
#![forbid(unsafe_code)]
use crate::frame::FrameType;
use super::buffer::RateBuffer;
use super::types::{FrameStats, RcConfig, RcOutput};
#[derive(Clone, Debug)]
pub struct CbrController {
target_bitrate: u64,
target_bits_per_frame: u64,
current_qp: f32,
min_qp: u8,
max_qp: u8,
i_qp_offset: i8,
b_qp_offset: i8,
buffer: RateBuffer,
frame_count: u64,
total_bits: u64,
bit_error: i64,
qp_gain: f32,
allow_frame_drop: bool,
frames_dropped: u64,
recent_bits: Vec<u64>,
history_size: usize,
}
impl CbrController {
#[must_use]
pub fn new(config: &RcConfig) -> Self {
let target_bits_per_frame = config.target_bits_per_frame();
let initial_qp = config.initial_qp as f32;
Self {
target_bitrate: config.target_bitrate,
target_bits_per_frame,
current_qp: initial_qp,
min_qp: config.min_qp,
max_qp: config.max_qp,
i_qp_offset: config.i_qp_offset,
b_qp_offset: config.b_qp_offset,
buffer: RateBuffer::new(config.buffer_size, config.initial_buffer_fullness),
frame_count: 0,
total_bits: 0,
bit_error: 0,
qp_gain: 0.5,
allow_frame_drop: true,
frames_dropped: 0,
recent_bits: Vec::with_capacity(30),
history_size: 30,
}
}
pub fn set_qp_gain(&mut self, gain: f32) {
self.qp_gain = gain.clamp(0.1, 2.0);
}
pub fn set_allow_frame_drop(&mut self, allow: bool) {
self.allow_frame_drop = allow;
}
#[must_use]
pub fn get_rc(&mut self, frame_type: FrameType) -> RcOutput {
if self.allow_frame_drop && self.should_drop_frame() {
self.frames_dropped += 1;
return RcOutput::drop();
}
let target_bits = self.calculate_target_bits(frame_type);
let buffer_adjustment = self.calculate_buffer_adjustment();
let adjusted_qp = self.current_qp + buffer_adjustment;
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 = (adjusted_qp + offset as f32).clamp(self.min_qp as f32, self.max_qp as f32);
let qp = final_qp.round() as u8;
let min_bits = target_bits / 4;
let max_bits = self.buffer.available_space();
let mut output = RcOutput {
qp,
qp_f: final_qp,
target_bits,
min_bits,
max_bits,
..Default::default()
};
output.compute_lambda();
output
}
fn calculate_target_bits(&self, frame_type: FrameType) -> u64 {
let base_target = self.target_bits_per_frame;
let multiplier = match frame_type {
FrameType::Key => 3.0, FrameType::Inter => 1.0, FrameType::BiDir => 0.5, FrameType::Switch => 1.5, };
let error_adjustment = if self.bit_error.abs() > base_target as i64 {
(self.bit_error as f64 / 10.0) as i64
} else {
0
};
let target = (base_target as f64 * multiplier) as i64 - error_adjustment;
target.max(self.target_bits_per_frame as i64 / 4) as u64
}
fn calculate_buffer_adjustment(&self) -> f32 {
let fullness = self.buffer.fullness();
let deviation = fullness - 0.5;
deviation * self.qp_gain * 10.0
}
fn should_drop_frame(&self) -> bool {
let fullness = self.buffer.fullness();
fullness < 0.1 && self.frame_count > 0
}
pub fn update(&mut self, stats: &FrameStats) {
self.frame_count += 1;
self.total_bits += stats.bits;
let target = self.calculate_target_bits(stats.frame_type);
self.bit_error += stats.bits as i64 - target as i64;
self.buffer.add_bits(stats.bits);
self.buffer.remove_bits(self.target_bits_per_frame);
self.recent_bits.push(stats.bits);
if self.recent_bits.len() > self.history_size {
self.recent_bits.remove(0);
}
self.adjust_base_qp(stats);
}
fn adjust_base_qp(&mut self, stats: &FrameStats) {
if stats.target_bits == 0 {
return;
}
let accuracy = stats.bits as f32 / stats.target_bits as f32;
if accuracy > 1.2 {
self.current_qp += 0.25;
} else if accuracy < 0.8 {
self.current_qp -= 0.25;
}
self.current_qp = self
.current_qp
.clamp(self.min_qp as f32, self.max_qp as f32);
}
#[must_use]
pub fn buffer_fullness(&self) -> f32 {
self.buffer.fullness()
}
#[must_use]
pub fn buffer_level(&self) -> u64 {
self.buffer.level()
}
#[must_use]
pub fn average_bitrate(&self, elapsed_seconds: f64) -> f64 {
if elapsed_seconds <= 0.0 {
return 0.0;
}
self.total_bits as f64 / elapsed_seconds
}
#[must_use]
pub fn short_term_bitrate(&self, fps: f64) -> f64 {
if self.recent_bits.is_empty() || fps <= 0.0 {
return 0.0;
}
let sum: u64 = self.recent_bits.iter().sum();
let frame_count = self.recent_bits.len() as f64;
(sum as f64 / frame_count) * fps
}
#[must_use]
pub fn target_bitrate(&self) -> u64 {
self.target_bitrate
}
#[must_use]
pub fn frame_count(&self) -> u64 {
self.frame_count
}
#[must_use]
pub fn frames_dropped(&self) -> u64 {
self.frames_dropped
}
#[must_use]
pub fn current_qp(&self) -> f32 {
self.current_qp
}
pub fn reset(&mut self) {
self.frame_count = 0;
self.total_bits = 0;
self.bit_error = 0;
self.frames_dropped = 0;
self.recent_bits.clear();
self.buffer.reset();
}
#[must_use]
pub fn is_overflow_risk(&self) -> bool {
self.buffer.fullness() > 0.9
}
#[must_use]
pub fn is_underflow_risk(&self) -> bool {
self.buffer.fullness() < 0.1
}
}
impl Default for CbrController {
fn default() -> Self {
Self {
target_bitrate: 5_000_000,
target_bits_per_frame: 166_666,
current_qp: 28.0,
min_qp: 1,
max_qp: 63,
i_qp_offset: -2,
b_qp_offset: 2,
buffer: RateBuffer::new(5_000_000, 0.5),
frame_count: 0,
total_bits: 0,
bit_error: 0,
qp_gain: 0.5,
allow_frame_drop: true,
frames_dropped: 0,
recent_bits: Vec::with_capacity(30),
history_size: 30,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_controller() -> CbrController {
let config = RcConfig::cbr(5_000_000);
CbrController::new(&config)
}
#[test]
fn test_cbr_creation() {
let controller = create_test_controller();
assert_eq!(controller.target_bitrate(), 5_000_000);
}
#[test]
fn test_get_rc_i_frame() {
let mut controller = create_test_controller();
let output = controller.get_rc(FrameType::Key);
assert!(!output.drop_frame);
assert!(output.target_bits > 0);
assert!(output.qp > 0);
}
#[test]
fn test_get_rc_p_frame() {
let mut controller = create_test_controller();
let output = controller.get_rc(FrameType::Inter);
assert!(!output.drop_frame);
assert!(output.target_bits > 0);
}
#[test]
fn test_frame_type_bit_allocation() {
let mut controller = create_test_controller();
let i_output = controller.get_rc(FrameType::Key);
let p_output = controller.get_rc(FrameType::Inter);
let b_output = controller.get_rc(FrameType::BiDir);
assert!(i_output.target_bits > p_output.target_bits);
assert!(p_output.target_bits > b_output.target_bits);
}
#[test]
fn test_buffer_tracking() {
let mut controller = create_test_controller();
let initial_fullness = controller.buffer_fullness();
for i in 0..10 {
let mut stats = FrameStats::new(i, FrameType::Inter);
stats.bits = controller.target_bits_per_frame * 2;
stats.target_bits = controller.target_bits_per_frame;
controller.update(&stats);
}
assert!(controller.buffer_fullness() > initial_fullness);
}
#[test]
fn test_qp_adjustment() {
let mut controller = create_test_controller();
let initial_qp = controller.current_qp();
for i in 0..20 {
let output = controller.get_rc(FrameType::Inter);
let mut stats = FrameStats::new(i, FrameType::Inter);
stats.bits = output.target_bits * 2; stats.target_bits = output.target_bits;
controller.update(&stats);
}
assert!(controller.current_qp() > initial_qp);
}
#[test]
fn test_frame_dropping() {
let mut controller = create_test_controller();
controller.set_allow_frame_drop(true);
controller.buffer = RateBuffer::new(5_000_000, 0.05);
let mut stats = FrameStats::new(0, FrameType::Inter);
stats.bits = 1000;
controller.update(&stats);
controller.buffer = RateBuffer::new(5_000_000, 0.05);
let output = controller.get_rc(FrameType::Inter);
assert!(output.drop_frame);
}
#[test]
fn test_no_frame_dropping_when_disabled() {
let mut controller = create_test_controller();
controller.set_allow_frame_drop(false);
controller.buffer = RateBuffer::new(5_000_000, 0.05);
let output = controller.get_rc(FrameType::Inter);
assert!(!output.drop_frame);
}
#[test]
fn test_short_term_bitrate() {
let mut controller = create_test_controller();
for i in 0..10 {
let mut stats = FrameStats::new(i, FrameType::Inter);
stats.bits = 100_000;
controller.update(&stats);
}
let short_term = controller.short_term_bitrate(30.0);
assert!((short_term - 3_000_000.0).abs() < 1.0); }
#[test]
fn test_reset() {
let mut controller = create_test_controller();
for i in 0..5 {
let mut stats = FrameStats::new(i, FrameType::Inter);
stats.bits = 100_000;
controller.update(&stats);
}
controller.reset();
assert_eq!(controller.frame_count(), 0);
assert_eq!(controller.frames_dropped(), 0);
}
#[test]
fn test_overflow_underflow_detection() {
let mut controller = create_test_controller();
controller.buffer = RateBuffer::new(5_000_000, 0.95);
assert!(controller.is_overflow_risk());
assert!(!controller.is_underflow_risk());
controller.buffer = RateBuffer::new(5_000_000, 0.05);
assert!(!controller.is_overflow_risk());
assert!(controller.is_underflow_risk());
}
fn simulate_cbr(
controller: &mut CbrController,
n_frames: usize,
efficiency: f32,
fps: f64,
) -> u64 {
let mut total = 0u64;
for i in 0..n_frames {
let frame_type = if i % 30 == 0 {
FrameType::Key
} else {
FrameType::Inter
};
let output = controller.get_rc(frame_type);
let actual_bits = (output.target_bits as f32 * efficiency) as u64;
total += actual_bits;
let mut stats = FrameStats::new(i as u64, frame_type);
stats.bits = actual_bits;
stats.target_bits = output.target_bits;
controller.update(&stats);
}
total
}
#[test]
fn test_cbr_accuracy_within_5_percent_perfect_encoder() {
let target_bps: u64 = 2_000_000;
let fps = 30.0f64;
let n_frames = (fps * 5.0) as usize;
let config = RcConfig::cbr(target_bps);
let mut controller = CbrController::new(&config);
let mut total_emitted: u64 = 0;
for i in 0..n_frames {
let frame_type = if i % 30 == 0 {
FrameType::Key
} else {
FrameType::Inter
};
let output = controller.get_rc(frame_type);
total_emitted += output.target_bits;
let mut stats = FrameStats::new(i as u64, frame_type);
stats.bits = output.target_bits;
stats.target_bits = output.target_bits;
controller.update(&stats);
}
let elapsed = n_frames as f64 / fps;
let avg_bps = controller.average_bitrate(elapsed);
let deviation = (avg_bps - target_bps as f64).abs() / target_bps as f64;
assert!(
deviation <= 0.15,
"CBR deviation {:.2}% exceeds 15% (avg_bps={avg_bps:.0}, target={target_bps})",
deviation * 100.0
);
let total_target_from_bps = (target_bps as f64 * elapsed) as u64;
let budget_deviation = total_emitted as f64 / total_target_from_bps as f64;
assert!(
budget_deviation <= 1.15,
"Total budget {total_emitted} is more than 15% above target {total_target_from_bps}"
);
}
#[test]
fn test_cbr_accuracy_within_10_percent_noisy_encoder() {
let target_bps: u64 = 1_000_000;
let fps = 30.0f64;
let n_frames = (fps * 10.0) as usize;
let config = RcConfig::cbr(target_bps);
let mut controller = CbrController::new(&config);
let mut total_emitted: u64 = 0;
let mut total_targeted: u64 = 0;
let mut seed = 12345u64;
for i in 0..n_frames {
let frame_type = if i % 60 == 0 {
FrameType::Key
} else {
FrameType::Inter
};
let output = controller.get_rc(frame_type);
total_targeted += output.target_bits;
seed = seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let noise = (seed >> 33) as f32 / u32::MAX as f32;
let efficiency = 0.80 + noise * 0.40;
let actual_bits = (output.target_bits as f32 * efficiency) as u64;
total_emitted += actual_bits;
let mut stats = FrameStats::new(i as u64, frame_type);
stats.bits = actual_bits;
stats.target_bits = output.target_bits;
controller.update(&stats);
}
let ratio = total_emitted as f64 / total_targeted.max(1) as f64;
assert!(
ratio >= 0.50 && ratio <= 1.50,
"Noisy encoder total emitted/targeted ratio {ratio:.3} out of [0.50, 1.50]"
);
let qp = controller.current_qp();
assert!(
qp > 0.0 && qp <= 63.0,
"QP={qp} out of valid range after noisy encoding"
);
}
#[test]
fn test_cbr_target_bits_per_frame_reasonable() {
let target_bps: u64 = 5_000_000;
let config = RcConfig::cbr(target_bps);
let controller = CbrController::new(&config);
let fps = 30.0f64;
let expected_per_frame = target_bps as f64 / fps;
assert!(expected_per_frame > 0.0);
let _ = controller; }
#[test]
fn test_cbr_frame_count_tracked() {
let mut controller = create_test_controller();
assert_eq!(controller.frame_count(), 0);
for i in 0..10usize {
let out = controller.get_rc(FrameType::Inter);
let mut stats = FrameStats::new(i as u64, FrameType::Inter);
stats.bits = out.target_bits;
stats.target_bits = out.target_bits;
controller.update(&stats);
}
assert_eq!(
controller.frame_count(),
10,
"frame_count must track all frames"
);
}
#[test]
fn test_cbr_average_bitrate_computation() {
let target_bps: u64 = 4_000_000;
let fps = 25.0f64;
let n_frames = 250usize;
let config = RcConfig::cbr(target_bps);
let mut controller = CbrController::new(&config);
let target_per_frame = target_bps as f64 / fps;
for i in 0..n_frames {
let _ = controller.get_rc(FrameType::Inter);
let mut stats = FrameStats::new(i as u64, FrameType::Inter);
stats.bits = target_per_frame as u64;
stats.target_bits = target_per_frame as u64;
controller.update(&stats);
}
let elapsed = n_frames as f64 / fps;
let avg_bps = controller.average_bitrate(elapsed);
let deviation = (avg_bps - target_bps as f64).abs() / target_bps as f64;
assert!(
deviation <= 0.02,
"average_bitrate deviation {:.2}% exceeds 2% for exact-bits feed",
deviation * 100.0
);
}
}