use crate::picture::SourceFormat;
pub const fn picture_bits_cap(fmt: SourceFormat) -> u32 {
match fmt {
SourceFormat::Qcif => 64 * 1024,
SourceFormat::Cif => 256 * 1024,
}
}
pub const FRAME_RATE_TIMES_10000: u32 = 299_700;
pub fn frame_rate_hz() -> f64 {
FRAME_RATE_TIMES_10000 as f64 / 10_000.0
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HrdParams {
pub r_max_bps: u32,
pub b_bits: u64,
}
impl HrdParams {
pub fn new(r_max_bps: u32) -> Self {
let b_bits = (4u64 * r_max_bps as u64 * 10_000) / FRAME_RATE_TIMES_10000 as u64;
Self { r_max_bps, b_bits }
}
pub fn rx_buffer_bits(&self) -> u64 {
self.b_bits + 256 * 1024
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PictureCapStatus {
Ok,
Overflow {
actual_bits: u32,
cap_bits: u32,
},
}
pub fn check_picture_cap(coded_bits: u32, fmt: SourceFormat) -> PictureCapStatus {
let cap = picture_bits_cap(fmt);
if coded_bits <= cap {
PictureCapStatus::Ok
} else {
PictureCapStatus::Overflow {
actual_bits: coded_bits,
cap_bits: cap,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HrdTrace {
pub buffer_bits_after_arrival: Vec<u64>,
pub first_underflow: Option<usize>,
}
pub fn walk_buffer(pictures: &[u32], pictures_per_skip: u32, params: HrdParams) -> HrdTrace {
assert!(pictures_per_skip >= 1, "skip factor must be ≥ 1");
let mut trace = HrdTrace {
buffer_bits_after_arrival: Vec::with_capacity(pictures.len()),
first_underflow: None,
};
let arrived_per_interval: u64 = (params.r_max_bps as u64 * pictures_per_skip as u64 * 10_000)
/ FRAME_RATE_TIMES_10000 as u64;
let mut occupancy: u64 = 0;
for (i, &d) in pictures.iter().enumerate() {
occupancy += arrived_per_interval;
if (d as u64) > occupancy {
if trace.first_underflow.is_none() {
trace.first_underflow = Some(i);
}
occupancy = 0;
} else {
occupancy -= d as u64;
}
trace.buffer_bits_after_arrival.push(occupancy);
}
trace
}
pub fn check_overflow(
pictures: &[u32],
pictures_per_skip: u32,
params: HrdParams,
) -> Option<usize> {
let arrived_per_interval: u64 = (params.r_max_bps as u64 * pictures_per_skip as u64 * 10_000)
/ FRAME_RATE_TIMES_10000 as u64;
let cap = params.rx_buffer_bits();
let mut occupancy: u64 = 0;
for (i, &d) in pictures.iter().enumerate() {
occupancy = occupancy.saturating_add(arrived_per_interval);
if occupancy > cap {
return Some(i);
}
occupancy = occupancy.saturating_sub(d as u64);
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn per_picture_cap_qcif_64k() {
assert_eq!(picture_bits_cap(SourceFormat::Qcif), 64 * 1024);
assert_eq!(picture_bits_cap(SourceFormat::Cif), 256 * 1024);
}
#[test]
fn check_picture_cap_below_limit_is_ok() {
assert_eq!(
check_picture_cap(50_000, SourceFormat::Qcif),
PictureCapStatus::Ok
);
assert_eq!(
check_picture_cap(200_000, SourceFormat::Cif),
PictureCapStatus::Ok
);
}
#[test]
fn check_picture_cap_at_limit_is_ok() {
assert_eq!(
check_picture_cap(64 * 1024, SourceFormat::Qcif),
PictureCapStatus::Ok
);
assert_eq!(
check_picture_cap(256 * 1024, SourceFormat::Cif),
PictureCapStatus::Ok
);
}
#[test]
fn check_picture_cap_over_limit_flags_overflow() {
let r = check_picture_cap(70_000, SourceFormat::Qcif);
match r {
PictureCapStatus::Overflow {
actual_bits,
cap_bits,
} => {
assert_eq!(actual_bits, 70_000);
assert_eq!(cap_bits, 64 * 1024);
}
PictureCapStatus::Ok => panic!("expected Overflow"),
}
}
#[test]
fn hrd_params_derive_b_for_64kbit_channel() {
let p = HrdParams::new(64_000);
assert_eq!(p.b_bits, 8541);
assert_eq!(p.rx_buffer_bits(), 8541 + 256 * 1024);
}
#[test]
fn hrd_params_derive_b_for_2mbit_channel() {
let p = HrdParams::new(2_048_000);
assert_eq!(p.b_bits, 273_340);
}
#[test]
fn walk_buffer_constant_rate_constant_picture_holds_steady() {
let params = HrdParams::new(64_000);
let per_interval: u64 = (64_000u64 * 10_000) / FRAME_RATE_TIMES_10000 as u64;
assert_eq!(per_interval, 2135);
let pics = vec![per_interval as u32; 10];
let trace = walk_buffer(&pics, 1, params);
assert_eq!(trace.first_underflow, None);
for &b in &trace.buffer_bits_after_arrival {
assert_eq!(b, 0, "buffer should drain to exactly 0 each interval");
}
}
#[test]
fn walk_buffer_smaller_pictures_accumulate_into_buffer() {
let params = HrdParams::new(64_000);
let per_interval: u64 = (64_000u64 * 10_000) / FRAME_RATE_TIMES_10000 as u64;
let half = (per_interval / 2) as u32;
let pics = vec![half; 5];
let trace = walk_buffer(&pics, 1, params);
assert_eq!(trace.first_underflow, None);
let drift_per_step = per_interval - half as u64;
for (i, &b) in trace.buffer_bits_after_arrival.iter().enumerate() {
let expected = ((i + 1) as u64) * drift_per_step;
assert_eq!(b, expected, "step {i}: drift expected {expected}, got {b}");
}
}
#[test]
fn walk_buffer_oversized_picture_triggers_underflow() {
let params = HrdParams::new(64_000);
let pics = vec![100_000, 1000, 1000];
let trace = walk_buffer(&pics, 1, params);
assert_eq!(trace.first_underflow, Some(0));
}
#[test]
fn walk_buffer_skip_factor_doubles_arrival_per_interval() {
let params = HrdParams::new(64_000);
let per_interval_n1: u64 = (64_000u64 * 10_000) / FRAME_RATE_TIMES_10000 as u64;
let pics = vec![per_interval_n1 as u32 * 2; 5];
let trace = walk_buffer(&pics, 2, params);
assert_eq!(trace.first_underflow, None);
for &b in &trace.buffer_bits_after_arrival {
assert_eq!(b, 0);
}
}
#[test]
fn check_overflow_does_not_trip_under_normal_drain() {
let params = HrdParams::new(64_000);
let per_interval: u64 = (64_000u64 * 10_000) / FRAME_RATE_TIMES_10000 as u64;
let pics = vec![per_interval as u32; 100];
assert_eq!(check_overflow(&pics, 1, params), None);
}
#[test]
fn check_overflow_trips_when_pictures_are_tiny() {
let params = HrdParams::new(64_000);
let pics = vec![0u32; 200];
let idx = check_overflow(&pics, 1, params).expect("should overflow");
assert_eq!(idx, 126);
}
#[test]
fn frame_rate_is_29_97() {
assert!((frame_rate_hz() - 29.97).abs() < 1e-9);
}
}