const VIDEO_FRAME_RATE: u64 = 30; const FRAME_PERIOD_MS: f64 = 1000.0 / VIDEO_FRAME_RATE as f64;
#[derive(Debug)]
pub struct JitterEstimator {
jitter: f64,
last_arrival_time_ms: u128,
last_sequence_number: u64,
is_first_frame: bool,
}
impl Default for JitterEstimator {
fn default() -> Self {
Self::new()
}
}
impl JitterEstimator {
pub fn new() -> Self {
Self {
jitter: 0.0,
last_arrival_time_ms: 0,
last_sequence_number: 0,
is_first_frame: true,
}
}
pub fn update_estimate(&mut self, sequence_number: u64, arrival_time_ms: u128) {
if self.is_first_frame {
self.last_arrival_time_ms = arrival_time_ms;
self.last_sequence_number = sequence_number;
self.is_first_frame = false;
return;
}
if sequence_number > self.last_sequence_number {
let arrival_diff = arrival_time_ms - self.last_arrival_time_ms;
let seq_diff = sequence_number - self.last_sequence_number;
let timestamp_diff = seq_diff as f64 * FRAME_PERIOD_MS;
let transit_diff = arrival_diff as f64 - timestamp_diff;
self.jitter += (transit_diff.abs() - self.jitter) / 16.0;
self.last_arrival_time_ms = arrival_time_ms;
self.last_sequence_number = sequence_number;
}
}
pub fn get_jitter_estimate_ms(&self) -> f64 {
self.jitter
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_estimator_has_zero_jitter() {
let estimator = JitterEstimator::new();
assert_eq!(estimator.get_jitter_estimate_ms(), 0.0);
}
#[test]
fn first_frame_sets_initial_state_but_does_not_change_jitter() {
let mut estimator = JitterEstimator::new();
estimator.update_estimate(1, 1000);
assert_eq!(estimator.get_jitter_estimate_ms(), 0.0);
assert_eq!(estimator.last_arrival_time_ms, 1000);
assert_eq!(estimator.last_sequence_number, 1);
}
#[test]
fn steady_arrival_produces_no_jitter() {
let mut estimator = JitterEstimator::new();
let mut time_ms = 1000;
let mut seq = 1;
estimator.update_estimate(seq, time_ms);
for i in 2..=100 {
seq = i;
time_ms += FRAME_PERIOD_MS.round() as u128;
estimator.update_estimate(seq, time_ms);
}
assert!(
estimator.get_jitter_estimate_ms() < 1.0,
"Jitter should be close to zero for steady arrival"
);
}
#[test]
fn late_arrival_increases_jitter() {
let mut estimator = JitterEstimator::new();
let mut time_ms: u128 = 1000;
estimator.update_estimate(1, time_ms);
time_ms += FRAME_PERIOD_MS.round() as u128;
estimator.update_estimate(2, time_ms);
assert!(estimator.get_jitter_estimate_ms() < 1.0);
time_ms += FRAME_PERIOD_MS.round() as u128 + 50;
estimator.update_estimate(3, time_ms);
assert!((estimator.get_jitter_estimate_ms() - 3.125).abs() < 0.1);
time_ms += FRAME_PERIOD_MS.round() as u128;
estimator.update_estimate(4, time_ms);
assert!((estimator.get_jitter_estimate_ms() - 2.93).abs() < 0.1);
}
}