use std::time::{Duration, Instant};
const PROCESS_NOISE_VAR: f64 = 1e-4;
const MEASUREMENT_NOISE_VAR: f64 = 5e-3;
const OVERUSE_THRESHOLD_US: f64 = 12_500.0;
const DECREASE_FACTOR: f64 = 0.85;
const ADDITIVE_INCREASE_BPS: f64 = 8_000.0;
pub const MIN_BITRATE_BPS: f64 = 30_000.0;
pub const MAX_BITRATE_BPS: f64 = 50_000_000.0;
const DECREASE_COOLDOWN: Duration = Duration::from_millis(200);
#[derive(Debug)]
pub struct DelayEstimator {
gradient_us: f64,
gradient_var: f64,
bitrate_bps: f64,
last_decrease: Option<Instant>,
}
impl DelayEstimator {
pub fn new(initial_bitrate_bps: f64) -> Self {
Self {
gradient_us: 0.0,
gradient_var: 1.0,
bitrate_bps: initial_bitrate_bps.clamp(MIN_BITRATE_BPS, MAX_BITRATE_BPS),
last_decrease: None,
}
}
pub fn update_kalman(&mut self, gradient_us: f64) {
self.gradient_var += PROCESS_NOISE_VAR;
let gain = self.gradient_var / (self.gradient_var + MEASUREMENT_NOISE_VAR);
self.gradient_us += gain * (gradient_us - self.gradient_us);
self.gradient_var *= 1.0 - gain;
}
pub fn apply_rate_control(&mut self, now: Instant) {
if self.gradient_us > OVERUSE_THRESHOLD_US {
let can_decrease = self
.last_decrease
.map_or(true, |t| now.duration_since(t) >= DECREASE_COOLDOWN);
if can_decrease {
self.bitrate_bps = (self.bitrate_bps * DECREASE_FACTOR).max(MIN_BITRATE_BPS);
self.last_decrease = Some(now);
}
} else {
self.bitrate_bps = (self.bitrate_bps + ADDITIVE_INCREASE_BPS).min(MAX_BITRATE_BPS);
}
}
#[must_use]
pub fn bitrate_bps(&self) -> f64 {
self.bitrate_bps
}
#[must_use]
pub fn filtered_gradient_us(&self) -> f64 {
self.gradient_us
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Instant;
#[test]
fn kalman_converges_to_injected_gradient() {
let mut est = DelayEstimator::new(1_000_000.0);
for _ in 0..100 {
est.update_kalman(20_000.0);
}
let filtered = est.filtered_gradient_us();
assert!(
(filtered - 20_000.0).abs() < 3_000.0,
"Kalman did not converge: got {filtered}, expected ~20000"
);
}
#[test]
fn rate_decreases_on_overuse() {
let initial = 2_000_000.0;
let mut est = DelayEstimator::new(initial);
let now = Instant::now();
for _ in 0..20 {
est.update_kalman(50_000.0);
}
let before = est.bitrate_bps();
est.apply_rate_control(now);
assert!(
est.bitrate_bps() < before,
"rate should decrease on overuse: {} >= {}",
est.bitrate_bps(),
before
);
}
#[test]
fn rate_respects_floor_and_ceiling() {
let mut est = DelayEstimator::new(MIN_BITRATE_BPS);
let now = Instant::now();
for _ in 0..100 {
est.update_kalman(200_000.0);
}
for _ in 0..20 {
est.apply_rate_control(now);
}
assert!(
est.bitrate_bps() >= MIN_BITRATE_BPS,
"floor violated: {}",
est.bitrate_bps()
);
let mut est2 = DelayEstimator::new(MAX_BITRATE_BPS);
for _ in 0..100 {
est2.update_kalman(-1_000_000.0);
}
for _ in 0..1000 {
est2.apply_rate_control(now);
}
assert!(
est2.bitrate_bps() <= MAX_BITRATE_BPS,
"ceiling violated: {}",
est2.bitrate_bps()
);
}
}