#![forbid(unsafe_code)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_lossless)]
use crate::frame::FrameType;
#[derive(Clone, Debug)]
pub struct VbvConfig {
pub buffer_size: u64,
pub max_bitrate: u64,
pub initial_fullness: f64,
pub framerate_num: u32,
pub framerate_den: u32,
}
impl VbvConfig {
#[must_use]
pub fn new(buffer_size: u64, max_bitrate: u64, framerate_num: u32, framerate_den: u32) -> Self {
Self {
buffer_size,
max_bitrate,
initial_fullness: 0.75,
framerate_num,
framerate_den,
}
}
#[must_use]
pub fn with_initial_fullness(mut self, fullness: f64) -> Self {
self.initial_fullness = fullness.clamp(0.0, 1.0);
self
}
#[must_use]
pub fn frame_period(&self) -> f64 {
self.framerate_den as f64 / self.framerate_num as f64
}
#[must_use]
pub fn bits_per_frame(&self) -> f64 {
self.max_bitrate as f64 * self.frame_period()
}
}
pub struct VbvBuffer {
config: VbvConfig,
current_fullness: f64,
min_fullness: f64,
max_fullness: f64,
frame_count: u64,
underflow_count: u64,
overflow_count: u64,
}
impl VbvBuffer {
#[must_use]
pub fn new(config: VbvConfig) -> Self {
let current_fullness = config.buffer_size as f64 * config.initial_fullness;
Self {
config,
current_fullness,
min_fullness: current_fullness,
max_fullness: current_fullness,
frame_count: 0,
underflow_count: 0,
overflow_count: 0,
}
}
#[must_use]
pub fn can_send_frame(&self, frame_bits: u64) -> bool {
let new_fullness = self.current_fullness - frame_bits as f64;
new_fullness >= 0.0
}
#[must_use]
pub fn max_frame_size(&self) -> u64 {
self.current_fullness as u64
}
#[must_use]
pub fn min_frame_size(&self) -> u64 {
let bits_added = self.config.bits_per_frame();
let max_fullness = self.config.buffer_size as f64;
let available_after_refill = self.current_fullness + bits_added;
if available_after_refill > max_fullness {
(available_after_refill - max_fullness) as u64
} else {
0
}
}
pub fn update(&mut self, frame_bits: u64) -> VbvUpdateResult {
self.current_fullness -= frame_bits as f64;
let mut result = VbvUpdateResult {
underflow: false,
overflow: false,
fullness_ratio: 0.0,
available_bits: 0,
};
if self.current_fullness < 0.0 {
result.underflow = true;
self.underflow_count += 1;
self.current_fullness = 0.0;
}
let bits_added = self.config.bits_per_frame();
self.current_fullness += bits_added;
if self.current_fullness > self.config.buffer_size as f64 {
result.overflow = true;
self.overflow_count += 1;
self.current_fullness = self.config.buffer_size as f64;
}
self.min_fullness = self.min_fullness.min(self.current_fullness);
self.max_fullness = self.max_fullness.max(self.current_fullness);
result.fullness_ratio = self.current_fullness / self.config.buffer_size as f64;
result.available_bits = self.current_fullness as u64;
self.frame_count += 1;
result
}
#[must_use]
pub fn target_frame_size(&self, frame_type: FrameType, base_size: f64) -> u64 {
let fullness_ratio = self.current_fullness / self.config.buffer_size as f64;
let buffer_factor = if fullness_ratio > 0.75 {
1.2 } else if fullness_ratio > 0.5 {
1.0 } else if fullness_ratio > 0.25 {
0.8 } else {
0.6 };
let type_factor = match frame_type {
FrameType::Key => 3.0, FrameType::Inter => 1.0, FrameType::BiDir => 0.5, FrameType::Switch => 2.0, };
let target = base_size * buffer_factor * type_factor;
target.min(self.max_frame_size() as f64) as u64
}
#[must_use]
pub fn fullness_ratio(&self) -> f64 {
self.current_fullness / self.config.buffer_size as f64
}
#[must_use]
pub fn current_fullness(&self) -> u64 {
self.current_fullness as u64
}
#[must_use]
pub fn statistics(&self) -> VbvStatistics {
VbvStatistics {
frame_count: self.frame_count,
underflow_count: self.underflow_count,
overflow_count: self.overflow_count,
current_fullness: self.current_fullness,
min_fullness: self.min_fullness,
max_fullness: self.max_fullness,
buffer_size: self.config.buffer_size,
}
}
pub fn reset(&mut self) {
self.current_fullness = self.config.buffer_size as f64 * self.config.initial_fullness;
self.min_fullness = self.current_fullness;
self.max_fullness = self.current_fullness;
self.frame_count = 0;
self.underflow_count = 0;
self.overflow_count = 0;
}
}
#[derive(Clone, Debug)]
pub struct VbvUpdateResult {
pub underflow: bool,
pub overflow: bool,
pub fullness_ratio: f64,
pub available_bits: u64,
}
#[derive(Clone, Debug)]
pub struct VbvStatistics {
pub frame_count: u64,
pub underflow_count: u64,
pub overflow_count: u64,
pub current_fullness: f64,
pub min_fullness: f64,
pub max_fullness: f64,
pub buffer_size: u64,
}
impl VbvStatistics {
#[must_use]
pub fn is_compliant(&self) -> bool {
self.underflow_count == 0 && self.overflow_count == 0
}
#[must_use]
pub fn utilization(&self) -> BufferUtilization {
BufferUtilization {
min_ratio: self.min_fullness / self.buffer_size as f64,
max_ratio: self.max_fullness / self.buffer_size as f64,
current_ratio: self.current_fullness / self.buffer_size as f64,
}
}
}
#[derive(Clone, Debug)]
pub struct BufferUtilization {
pub min_ratio: f64,
pub max_ratio: f64,
pub current_ratio: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vbv_config_new() {
let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
assert_eq!(config.buffer_size, 1_000_000);
assert_eq!(config.max_bitrate, 5_000_000);
assert_eq!(config.initial_fullness, 0.75);
}
#[test]
fn test_vbv_config_frame_period() {
let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
let period = config.frame_period();
assert!((period - 1.0 / 30.0).abs() < 1e-6);
}
#[test]
fn test_vbv_buffer_new() {
let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
let buffer = VbvBuffer::new(config);
assert_eq!(buffer.current_fullness, 750_000.0);
}
#[test]
fn test_vbv_buffer_can_send() {
let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
let buffer = VbvBuffer::new(config);
assert!(buffer.can_send_frame(100_000));
assert!(buffer.can_send_frame(750_000));
assert!(!buffer.can_send_frame(800_000));
}
#[test]
fn test_vbv_buffer_update() {
let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
let mut buffer = VbvBuffer::new(config);
let result = buffer.update(100_000);
assert!(!result.underflow);
assert!(!result.overflow);
assert!(result.fullness_ratio > 0.0);
}
#[test]
fn test_vbv_buffer_underflow() {
let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
let mut buffer = VbvBuffer::new(config);
let result = buffer.update(1_000_000);
assert!(result.underflow);
assert_eq!(buffer.underflow_count, 1);
}
#[test]
fn test_vbv_target_frame_size() {
let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
let buffer = VbvBuffer::new(config);
let target = buffer.target_frame_size(FrameType::Inter, 50_000.0);
assert!(target > 0);
let key_target = buffer.target_frame_size(FrameType::Key, 50_000.0);
assert!(key_target > target); }
#[test]
fn test_vbv_statistics() {
let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
let mut buffer = VbvBuffer::new(config);
buffer.update(100_000);
buffer.update(100_000);
let stats = buffer.statistics();
assert_eq!(stats.frame_count, 2);
assert!(stats.is_compliant());
}
#[test]
fn test_vbv_reset() {
let config = VbvConfig::new(1_000_000, 5_000_000, 30, 1);
let mut buffer = VbvBuffer::new(config);
buffer.update(100_000);
assert_eq!(buffer.frame_count, 1);
buffer.reset();
assert_eq!(buffer.frame_count, 0);
assert_eq!(buffer.current_fullness, 750_000.0);
}
}