#![allow(dead_code)]
#![allow(clippy::cast_precision_loss)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RcMode {
Cbr,
Vbr,
Crf,
Qp,
}
impl RcMode {
pub fn is_quality_based(self) -> bool {
matches!(self, RcMode::Crf | RcMode::Qp)
}
pub fn is_bitrate_constrained(self) -> bool {
!self.is_quality_based()
}
pub fn label(self) -> &'static str {
match self {
RcMode::Cbr => "CBR",
RcMode::Vbr => "VBR",
RcMode::Crf => "CRF",
RcMode::Qp => "QP",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BitrateTarget {
pub mode: RcMode,
pub target_kbps: u32,
pub min_kbps: u32,
pub max_kbps: u32,
pub quality_param: f32,
}
impl BitrateTarget {
pub fn cbr(kbps: u32) -> Self {
Self {
mode: RcMode::Cbr,
target_kbps: kbps,
min_kbps: kbps,
max_kbps: kbps,
quality_param: 0.0,
}
}
pub fn vbr(target_kbps: u32, min_kbps: u32, max_kbps: u32) -> Self {
Self {
mode: RcMode::Vbr,
target_kbps,
min_kbps,
max_kbps,
quality_param: 0.0,
}
}
pub fn crf(crf: f32) -> Self {
Self {
mode: RcMode::Crf,
target_kbps: 0,
min_kbps: 0,
max_kbps: 0,
quality_param: crf,
}
}
pub fn effective_kbps(&self) -> u32 {
if self.mode.is_quality_based() {
0
} else {
self.target_kbps
}
}
pub fn has_bitrate_budget(&self) -> bool {
self.effective_kbps() > 0
}
}
#[derive(Debug, Clone)]
pub struct BitrateModelEstimator {
bpp_coefficient: f64,
}
impl BitrateModelEstimator {
pub fn new() -> Self {
Self {
bpp_coefficient: 0.07,
}
}
pub fn with_coefficient(bpp_coefficient: f64) -> Self {
Self { bpp_coefficient }
}
#[allow(clippy::cast_precision_loss)]
pub fn estimate_for_resolution(&self, width: u32, height: u32, fps: f64) -> u32 {
let pixels = width as f64 * height as f64;
let bits_per_sec = pixels * fps * self.bpp_coefficient;
(bits_per_sec / 1000.0).round() as u32
}
pub fn estimate_for_preset(&self, preset: &str) -> Option<u32> {
match preset {
"720p30" => Some(self.estimate_for_resolution(1280, 720, 30.0)),
"720p60" => Some(self.estimate_for_resolution(1280, 720, 60.0)),
"1080p30" => Some(self.estimate_for_resolution(1920, 1080, 30.0)),
"1080p60" => Some(self.estimate_for_resolution(1920, 1080, 60.0)),
"4k30" => Some(self.estimate_for_resolution(3840, 2160, 30.0)),
"4k60" => Some(self.estimate_for_resolution(3840, 2160, 60.0)),
_ => None,
}
}
}
impl Default for BitrateModelEstimator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rc_mode_cbr_not_quality_based() {
assert!(!RcMode::Cbr.is_quality_based());
}
#[test]
fn test_rc_mode_crf_is_quality_based() {
assert!(RcMode::Crf.is_quality_based());
}
#[test]
fn test_rc_mode_qp_is_quality_based() {
assert!(RcMode::Qp.is_quality_based());
}
#[test]
fn test_rc_mode_vbr_is_bitrate_constrained() {
assert!(RcMode::Vbr.is_bitrate_constrained());
}
#[test]
fn test_rc_mode_labels() {
assert_eq!(RcMode::Cbr.label(), "CBR");
assert_eq!(RcMode::Crf.label(), "CRF");
assert_eq!(RcMode::Qp.label(), "QP");
}
#[test]
fn test_bitrate_target_cbr_effective_kbps() {
let t = BitrateTarget::cbr(5000);
assert_eq!(t.effective_kbps(), 5000);
assert!(t.has_bitrate_budget());
}
#[test]
fn test_bitrate_target_crf_effective_kbps_is_zero() {
let t = BitrateTarget::crf(23.0);
assert_eq!(t.effective_kbps(), 0);
assert!(!t.has_bitrate_budget());
}
#[test]
fn test_bitrate_target_vbr_bounds() {
let t = BitrateTarget::vbr(4000, 1000, 8000);
assert_eq!(t.min_kbps, 1000);
assert_eq!(t.max_kbps, 8000);
}
#[test]
fn test_estimator_1080p30_reasonable() {
let est = BitrateModelEstimator::new();
let kbps = est.estimate_for_resolution(1920, 1080, 30.0);
assert!(kbps > 3000 && kbps < 6000, "Unexpected kbps: {kbps}");
}
#[test]
fn test_estimator_4k_higher_than_1080p() {
let est = BitrateModelEstimator::new();
let kbps_1080 = est.estimate_for_resolution(1920, 1080, 30.0);
let kbps_4k = est.estimate_for_resolution(3840, 2160, 30.0);
assert!(kbps_4k > kbps_1080);
}
#[test]
fn test_estimator_preset_720p30() {
let est = BitrateModelEstimator::new();
let kbps = est.estimate_for_preset("720p30").expect("should succeed");
let manual = est.estimate_for_resolution(1280, 720, 30.0);
assert_eq!(kbps, manual);
}
#[test]
fn test_estimator_preset_unknown_returns_none() {
let est = BitrateModelEstimator::new();
assert!(est.estimate_for_preset("8k120").is_none());
}
#[test]
fn test_estimator_custom_coefficient() {
let est = BitrateModelEstimator::with_coefficient(0.14);
let kbps_default = BitrateModelEstimator::new().estimate_for_resolution(1920, 1080, 30.0);
let kbps_custom = est.estimate_for_resolution(1920, 1080, 30.0);
assert!(kbps_custom > kbps_default);
}
#[test]
fn test_estimator_default_impl() {
let _est: BitrateModelEstimator = Default::default();
}
}