use crate::api::units::{DataRate, DataSize, TimeDelta, Timestamp};
#[derive(Debug, Clone)]
pub struct BitrateEstimatorConfig {
pub initial_window_ms: i64,
pub window_ms: i64,
pub scale: f64,
pub scale_alr: f64,
pub scale_small: f64,
pub small_thresh: DataSize,
pub symmetry_cap: DataRate,
pub floor: DataRate,
}
impl BitrateEstimatorConfig {
const INITIAL_RATE_WINDOW_MS: i64 = 500;
const RATE_WINDOW_MS: i64 = 150;
const MIN_RATE_WINDOW_MS: i64 = 150;
const MAX_RATE_WINDOW_MS: i64 = 1000;
}
impl Default for BitrateEstimatorConfig {
fn default() -> Self {
Self {
initial_window_ms: Self::INITIAL_RATE_WINDOW_MS,
window_ms: Self::RATE_WINDOW_MS,
scale: 10.0,
scale_alr: 10.0, scale_small: 10.0, small_thresh: DataSize::zero(),
symmetry_cap: DataRate::zero(),
floor: DataRate::zero(),
}
}
}
impl BitrateEstimatorConfig {
pub fn validate(&mut self) {
self.initial_window_ms = self
.initial_window_ms
.clamp(Self::MAX_RATE_WINDOW_MS, Self::MAX_RATE_WINDOW_MS);
self.window_ms = self
.window_ms
.clamp(Self::MIN_RATE_WINDOW_MS, Self::MAX_RATE_WINDOW_MS);
}
}
pub struct BitrateEstimator {
sum: i64,
initial_window_ms: i64,
noninitial_window_ms: i64,
uncertainty_scale: f64,
uncertainty_scale_in_alr: f64,
small_sample_uncertainty_scale: f64,
small_sample_threshold: DataSize,
uncertainty_symmetry_cap: DataRate,
estimate_floor: DataRate,
current_window_ms: i64,
prev_time_ms: i64,
bitrate_estimate_kbps: f64,
bitrate_estimate_var: f64,
}
impl Default for BitrateEstimator {
fn default() -> Self {
Self::new(BitrateEstimatorConfig::default())
}
}
impl BitrateEstimator {
pub fn new(mut config: BitrateEstimatorConfig) -> Self {
config.validate();
Self {
sum: 0,
initial_window_ms: config.initial_window_ms,
noninitial_window_ms: config.window_ms,
uncertainty_scale: config.scale,
uncertainty_scale_in_alr: config.scale_alr,
small_sample_uncertainty_scale: config.scale_small,
small_sample_threshold: config.small_thresh,
uncertainty_symmetry_cap: config.symmetry_cap,
estimate_floor: config.floor,
current_window_ms: 0,
prev_time_ms: -1,
bitrate_estimate_kbps: -1.0,
bitrate_estimate_var: 50.0,
}
}
pub fn update(&mut self, at_time: Timestamp, amount: DataSize, in_alr: bool) {
let mut rate_window_ms: i64 = self.noninitial_window_ms;
if self.bitrate_estimate_kbps < 0.0 {
rate_window_ms = self.initial_window_ms;
}
let mut is_small_sample: bool = false;
let bitrate_sample_kbps: f64 = self.update_window(
at_time.ms(),
amount.bytes(),
rate_window_ms,
&mut is_small_sample,
);
if bitrate_sample_kbps < 0.0 {
return;
}
if self.bitrate_estimate_kbps < 0.0 {
self.bitrate_estimate_kbps = bitrate_sample_kbps;
return;
}
let mut scale: f64 = self.uncertainty_scale;
if is_small_sample && bitrate_sample_kbps < self.bitrate_estimate_kbps {
scale = self.small_sample_uncertainty_scale;
} else if in_alr && bitrate_sample_kbps < self.bitrate_estimate_kbps {
scale = self.uncertainty_scale_in_alr;
}
let sample_uncertainty: f64 = scale
* (self.bitrate_estimate_kbps - bitrate_sample_kbps).abs()
/ (self.bitrate_estimate_kbps
+ bitrate_sample_kbps.max(self.uncertainty_symmetry_cap.kbps_float()));
let sample_var: f64 = sample_uncertainty * sample_uncertainty;
let pred_bitrate_estimate_var: f64 = self.bitrate_estimate_var + 5.0;
self.bitrate_estimate_kbps = (sample_var * self.bitrate_estimate_kbps
+ pred_bitrate_estimate_var * bitrate_sample_kbps)
/ (sample_var + pred_bitrate_estimate_var);
self.bitrate_estimate_kbps = self
.bitrate_estimate_kbps
.max(self.estimate_floor.kbps_float());
self.bitrate_estimate_var =
sample_var * pred_bitrate_estimate_var / (sample_var + pred_bitrate_estimate_var);
}
pub fn bitrate(&self) -> Option<DataRate> {
if self.bitrate_estimate_kbps < 0.0 {
return None;
}
Some(DataRate::from_kilobits_per_sec_float(
self.bitrate_estimate_kbps,
))
}
pub fn peek_rate(&self) -> Option<DataRate> {
if self.current_window_ms > 0 {
return Some(
DataSize::from_bytes(self.sum) / TimeDelta::from_millis(self.current_window_ms),
);
}
None
}
pub fn expect_fast_rate_change(&mut self) {
self.bitrate_estimate_var += 200.0;
}
fn update_window(
&mut self,
now_ms: i64,
bytes: i64,
rate_window_ms: i64,
is_small_sample: &mut bool,
) -> f64 {
if now_ms < self.prev_time_ms {
self.prev_time_ms = -1;
self.sum = 0;
self.current_window_ms = 0;
}
if self.prev_time_ms >= 0 {
self.current_window_ms += now_ms - self.prev_time_ms;
if now_ms - self.prev_time_ms > rate_window_ms {
self.sum = 0;
self.current_window_ms %= rate_window_ms;
}
}
self.prev_time_ms = now_ms;
let mut bitrate_sample: f64 = -1.0;
if self.current_window_ms >= rate_window_ms {
*is_small_sample = self.sum < self.small_sample_threshold.bytes();
bitrate_sample = 8.0 * self.sum as f64 / (rate_window_ms) as f64;
self.current_window_ms -= rate_window_ms;
self.sum = 0;
}
self.sum += bytes;
bitrate_sample
}
}