use super::stats::MediaStats;
use super::FRAME_MS;
use std::collections::BTreeMap;
use std::sync::atomic::Ordering;
pub const PREFILL: usize = 2;
pub const MAX_DEPTH: usize = 25;
pub const MAX_TARGET: usize = 20;
const PEAK_DECAY_MS: f32 = 0.4;
const SHED_AFTER_MS: u64 = 3000;
pub const MAX_PLC_RUN: u32 = 3;
pub enum Pop {
Frame(Vec<u8>),
Fec(Vec<u8>),
Muted,
Conceal,
Wait,
}
pub struct Buffered {
payload: Vec<u8>,
muted: bool,
}
#[derive(Default)]
pub struct Jitter {
frames: BTreeMap<u64, Buffered>,
next: Option<u64>,
newest: Option<u64>,
last_arrival: Option<(u64, u64)>,
jitter_ms: f32,
peak_ms: f32,
over_since: Option<u64>,
shed_after: Option<u64>,
}
impl Jitter {
pub fn buffered(&self) -> usize {
self.frames.len()
}
fn target_frames(&self) -> usize {
let need_ms = (self.jitter_ms * 3.0).max(self.peak_ms * 1.2);
let frames = (need_ms / FRAME_MS as f32).ceil() as usize;
(PREFILL + frames).clamp(PREFILL, MAX_TARGET)
}
fn unwrap_seq(&mut self, seq: u16) -> u64 {
let s = match self.newest {
None => seq as u64,
Some(newest) => {
let delta = seq.wrapping_sub(newest as u16) as i16 as i64;
(newest as i64 + delta).max(0) as u64
}
};
if self.newest.map_or(true, |n| s > n) {
self.newest = Some(s);
}
s
}
pub fn push(&mut self, seq: u16, muted: bool, payload: &[u8], now_ms: u64, stats: &MediaStats) {
let s = self.unwrap_seq(seq);
let now = now_ms;
if let Some((at, prev_seq)) = self.last_arrival {
let expected = (s as i64 - prev_seq as i64) as f32 * FRAME_MS as f32;
let actual = now.saturating_sub(at) as f32;
let d = (actual - expected).abs();
self.jitter_ms += (d - self.jitter_ms) / 16.0;
self.peak_ms = d.max(self.peak_ms - PEAK_DECAY_MS);
stats.jitter_ms.store(self.jitter_ms as u32, Ordering::Relaxed);
}
self.last_arrival = Some((now, s));
if let Some(next) = self.next {
if s < next {
stats.late.fetch_add(1, Ordering::Relaxed);
return;
}
}
self.frames.insert(s, Buffered { payload: payload.to_vec(), muted });
if self.frames.len() > MAX_DEPTH {
let keep_from = *self.frames.keys().nth(self.frames.len() - PREFILL).unwrap();
let dropped = self.frames.range(..keep_from).count() as u64;
self.frames = self.frames.split_off(&keep_from);
stats.lost.fetch_add(dropped, Ordering::Relaxed);
self.next = Some(keep_from);
}
}
pub fn pop(&mut self, plc_run: u32, now_ms: u64, stats: &MediaStats) -> Pop {
let next = match self.next {
Some(n) => n,
None => {
if self.frames.len() < PREFILL {
return Pop::Wait;
}
let first = *self.frames.keys().next().unwrap();
self.next = Some(first);
first
}
};
if self.frames.len() > self.target_frames() {
let now = now_ms;
let since = *self.over_since.get_or_insert(now);
if now.saturating_sub(since) >= self.shed_after.unwrap_or(SHED_AFTER_MS) {
if let Some(oldest) = self.frames.keys().next().copied() {
self.frames.remove(&oldest);
stats.shed.fetch_add(1, Ordering::Relaxed);
self.next = Some(oldest + 1);
self.over_since = Some(now);
return self.pop(plc_run, now_ms, stats);
}
}
} else {
self.over_since = None;
}
if let Some(f) = self.frames.remove(&next) {
self.next = Some(next + 1);
return if f.muted { Pop::Muted } else { Pop::Frame(f.payload) };
}
match self.frames.keys().next().copied() {
Some(min) if min == next + 1 => {
if plc_run == 0 && self.frames.len() < self.target_frames() {
return Pop::Conceal;
}
self.next = Some(min);
Pop::Fec(self.frames[&min].payload.clone())
}
Some(min) => {
if plc_run < 2 {
self.next = Some(next + 1);
Pop::Conceal
} else {
stats.lost.fetch_add(min - next, Ordering::Relaxed);
self.next = Some(min);
self.pop(0, now_ms, stats)
}
}
None => {
if plc_run < MAX_PLC_RUN {
Pop::Conceal
} else {
Pop::Wait
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn frame(n: u8) -> Vec<u8> {
vec![n]
}
#[test]
fn frames_play_in_order_after_prefill_and_a_gap_uses_fec() {
let stats = MediaStats::default();
let mut j = Jitter::default();
assert!(matches!(j.pop(0, 0, &stats), Pop::Wait));
j.push(0, false, &frame(0), 0, &stats);
assert!(matches!(j.pop(0, 0, &stats), Pop::Wait));
j.push(1, false, &frame(1), 0, &stats);
assert!(matches!(j.pop(0, 0, &stats), Pop::Frame(p) if p == frame(0)));
assert!(matches!(j.pop(0, 0, &stats), Pop::Frame(p) if p == frame(1)));
j.push(3, false, &frame(3), 0, &stats);
assert!(matches!(j.pop(0, 0, &stats), Pop::Conceal));
assert!(matches!(j.pop(1, 0, &stats), Pop::Fec(p) if p == frame(3)));
assert!(matches!(j.pop(0, 0, &stats), Pop::Frame(p) if p == frame(3)));
j.push(2, false, &frame(2), 0, &stats);
assert_eq!(stats.late.load(Ordering::Relaxed), 1);
assert!(matches!(j.pop(0, 0, &stats), Pop::Conceal));
assert_eq!(j.next, Some(4));
j.push(4, false, &frame(4), 0, &stats);
assert!(matches!(j.pop(1, 0, &stats), Pop::Frame(p) if p == frame(4)));
}
#[test]
fn a_stretched_buffer_keeps_its_depth_at_first_then_sheds_to_target() {
let stats = MediaStats::default();
let mut j = Jitter::default();
for s in 0..8u16 {
j.push(s, false, &frame(s as u8), 0, &stats);
}
j.jitter_ms = 0.0;
j.peak_ms = 0.0;
assert_eq!(j.target_frames(), PREFILL);
assert!(matches!(j.pop(0, 0, &stats), Pop::Frame(p) if p == frame(0)));
assert_eq!(stats.shed.load(Ordering::Relaxed), 0);
j.shed_after = Some(0);
assert!(matches!(j.pop(0, 0, &stats), Pop::Frame(p) if p == frame(6)));
assert_eq!(stats.shed.load(Ordering::Relaxed), 5);
assert_eq!(stats.lost.load(Ordering::Relaxed), 0);
assert_eq!(j.frames.len(), 1);
}
#[test]
fn the_target_grows_with_jitter_and_remembers_bursts() {
let mut j = Jitter::default();
j.jitter_ms = 43.0;
assert_eq!(j.target_frames(), PREFILL + 7);
j.peak_ms = 300.0;
assert_eq!(j.target_frames(), PREFILL + 18);
j.jitter_ms = 1000.0;
assert_eq!(j.target_frames(), MAX_TARGET);
}
#[test]
fn sequence_wraps_and_deep_buffers_jump_forward() {
let stats = MediaStats::default();
let mut j = Jitter::default();
j.push(65534, false, &frame(1), 0, &stats);
j.push(65535, false, &frame(2), 0, &stats);
j.push(0, false, &frame(3), 0, &stats);
assert!(matches!(j.pop(0, 0, &stats), Pop::Frame(p) if p == frame(1)));
assert_eq!(j.next, Some(65535));
for s in 1..=(MAX_DEPTH as u16 + 2) {
j.push(s, false, &frame(9), 0, &stats);
}
assert!(j.frames.len() <= MAX_DEPTH, "{}", j.frames.len());
assert!(stats.lost.load(Ordering::Relaxed) > 0);
assert!(j.next.unwrap() > 65537);
}
}