sipx_rtp/jitter.rs
1//! A jitter buffer.
2//!
3//! The network delivers packets late, early, twice, or not at all. Audio needs them evenly
4//! spaced and in order. The buffer trades a fixed amount of latency for that, and the whole
5//! design question is how much.
6//!
7//! Two of them, and the fixed one is not a stepping stone that got left behind. It is the
8//! control: an adaptive buffer that cannot be shown to beat a constant on a bad network and to
9//! match it on a good one is just a constant with extra machinery and extra ways to be wrong.
10//!
11//! The asymmetry that shapes the adaptive policy: **being too shallow is audible, being too
12//! deep is not.** A packet that arrives after its slot was played is a gap in the audio; a
13//! buffer holding one packet more than it needs is 20 ms of latency nobody notices. So it grows
14//! at the first sign of trouble and shrinks only on sustained evidence that the trouble is
15//! over.
16//!
17//! Shrinking costs nothing here, which is worth being explicit about because it is the part
18//! people expect to be hard. At packet granularity, lowering the depth means the next packet is
19//! released one slot sooner — it is not dropped, and nothing is played faster. Time-scale
20//! modification of the audio belongs in the media layer; this layer removes latency simply by
21//! holding less of it.
22
23use std::collections::BTreeMap;
24
25use crate::packet::{Packet, sequence_is_newer};
26
27/// How long a stretch of clean network is needed before the buffer gives up a packet of depth.
28///
29/// 250 packets is five seconds at the usual 20 ms. Long because shrinking is a bet that the
30/// network has settled, and losing that bet is audible while winning it saves 20 ms nobody
31/// notices. A shorter window would have the buffer shrink in the quiet between two jitter
32/// spikes and be caught out by the second.
33const SHRINK_AFTER: u32 = 250;
34
35/// How much lateness a buffer of this depth can absorb, in timestamp units.
36///
37/// [`JitterBuffer::pop`] holds `depth` packets before releasing, so a packet arriving up to
38/// `depth - 1` intervals behind its neighbours still makes its slot. That relation is the whole
39/// basis for choosing a depth, and writing it once means the release rule and the sizing rule
40/// cannot drift apart.
41fn absorbable(depth: usize, interval: f64) -> f64 {
42 let intervals = u32::try_from(depth.saturating_sub(1)).unwrap_or(u32::MAX);
43 f64::from(intervals) * interval
44}
45
46/// How the buffer chooses its depth.
47#[derive(Debug, Clone, Copy)]
48enum Policy {
49 /// A constant, chosen by the caller.
50 Fixed,
51 /// Between two bounds, from observed jitter and lateness.
52 Adaptive {
53 /// Never shallower than this, however clean the network looks.
54 min: usize,
55 /// Never deeper, however bad it gets. A pathological network must not be able to drive
56 /// latency without limit: at some point a call with three seconds of delay is worse
57 /// than a call with gaps, and the caller is entitled to decide where that point is.
58 max: usize,
59 },
60}
61
62/// Buffers packets, reorders them, and reports what went missing.
63#[derive(Debug)]
64pub struct JitterBuffer {
65 /// How many packets to hold before releasing.
66 depth: usize,
67 packets: BTreeMap<u64, Packet>,
68 /// The extended sequence number of the last packet released.
69 last_released: Option<u64>,
70 /// The high 48 bits, tracking wraps of the 16-bit counter.
71 cycles: u64,
72 highest: Option<u16>,
73 received: u64,
74 lost: u64,
75 duplicates: u64,
76 late: u64,
77 policy: Policy,
78 /// Smoothed interarrival jitter, in timestamp units.
79 jitter: f64,
80 /// The previous packet's transit time, which is what jitter is the change in.
81 last_transit: Option<u32>,
82 /// The packetisation interval in timestamp units, learned from the stream rather than
83 /// assumed: 20 ms is usual, 30 ms is common, and a buffer that assumed one and got the
84 /// other would size itself half or twice as deep as it meant to.
85 interval: Option<u32>,
86 last_timestamp: Option<u32>,
87 /// How many packets in a row have wanted a shallower buffer than the one we have.
88 clean_run: u32,
89}
90
91impl JitterBuffer {
92 /// A buffer holding `depth` packets.
93 ///
94 /// Depth is in packets rather than milliseconds because the buffer does not know the
95 /// packetisation interval; at the usual 20 ms, a depth of 3 is 60 ms of added latency.
96 #[must_use]
97 pub fn new(depth: usize) -> Self {
98 Self {
99 depth: depth.max(1),
100 packets: BTreeMap::new(),
101 last_released: None,
102 cycles: 0,
103 highest: None,
104 received: 0,
105 lost: 0,
106 duplicates: 0,
107 late: 0,
108 policy: Policy::Fixed,
109 jitter: 0.0,
110 last_transit: None,
111 interval: None,
112 last_timestamp: None,
113 clean_run: 0,
114 }
115 }
116
117 /// A buffer that sizes itself, between `min` and `max` packets.
118 ///
119 /// It starts at `min` and stays there until it has evidence it needs more, so a clean
120 /// network pays exactly what the fixed buffer would. Feed it with [`Self::push_at`]:
121 /// [`Self::push`] carries no arrival time, and a buffer with no arrival times cannot
122 /// measure jitter and will never adapt.
123 #[must_use]
124 pub fn adaptive(min: usize, max: usize) -> Self {
125 let min = min.max(1);
126 let max = max.max(min);
127 Self {
128 policy: Policy::Adaptive { min, max },
129 ..Self::new(min)
130 }
131 }
132
133 /// Accept a packet, noting when it arrived.
134 ///
135 /// `arrival` is the local clock in the same units as the RTP timestamp — for G.711, 8000
136 /// per second. The same convention as [`crate::rtcp::StreamStats::on_packet`], and for the
137 /// same reason: mixing units is how a jitter estimate becomes a number that means nothing.
138 pub fn push_at(&mut self, packet: Packet, arrival: u32) -> bool {
139 let (timestamp, was_late) = (packet.timestamp, self.late);
140 self.observe(timestamp, arrival);
141 let kept = self.push(packet);
142 if self.late > was_late {
143 // The most direct evidence there is that the buffer is too shallow: a packet turned
144 // up after its slot had been played. Nothing else needs to be inferred.
145 self.deepen();
146 } else {
147 self.resize();
148 }
149 kept
150 }
151
152 /// How deep the buffer currently is, in packets.
153 #[must_use]
154 pub fn depth(&self) -> usize {
155 self.depth
156 }
157
158 /// Smoothed interarrival jitter, in timestamp units. Zero under a fixed policy.
159 #[must_use]
160 pub fn jitter(&self) -> f64 {
161 self.jitter
162 }
163
164 /// Update the jitter estimate and the packetisation interval from one arrival.
165 fn observe(&mut self, timestamp: u32, arrival: u32) {
166 if let Some(previous) = self.last_timestamp {
167 let delta = timestamp.wrapping_sub(previous);
168 // A plausible interval only. Zero is a second packet of the same frame (DTMF does
169 // this); a huge delta is the gap after a silence, and taking either for the
170 // packetisation interval would size the buffer from noise.
171 if delta > 0 && delta < 48_000 {
172 self.interval = Some(match self.interval {
173 // Smoothed, so one late-arriving reorder does not redefine the interval.
174 Some(current) => (current * 7 + delta) / 8,
175 None => delta,
176 });
177 }
178 }
179 self.last_timestamp = Some(timestamp);
180
181 // RFC 3550 §A.8, the same recurrence the RTCP statistics use. Modular arithmetic
182 // throughout: the timestamp starts at a random value, so either clock wrapping mid-call
183 // is ordinary, and widening the subtraction turns each wrap into phantom jitter.
184 let transit = arrival.wrapping_sub(timestamp);
185 if let Some(previous) = self.last_transit {
186 let difference = transit.wrapping_sub(previous).cast_signed().unsigned_abs();
187 self.jitter += (f64::from(difference) - self.jitter) / 16.0;
188 }
189 self.last_transit = Some(transit);
190 }
191
192 /// The depth the current estimate asks for.
193 fn wanted(&self) -> Option<usize> {
194 let Policy::Adaptive { min, max } = self.policy else {
195 return None;
196 };
197 let interval = self.interval?;
198 // Derived from the release rule rather than guessed at. `pop` holds `depth` packets, so
199 // a packet arriving up to `depth - 1` intervals late still makes its slot; turn that
200 // round and absorbing a deviation of `d` needs `d / interval + 1` packets.
201 //
202 // The deviation is `2 * jitter` because `jitter` is a smoothed *mean* deviation and not
203 // a maximum. Covering one mean would leave roughly half of a bad network's packets
204 // late, and covering the worst arrival ever seen would let one outlier set the latency
205 // for the rest of the call.
206 //
207 // Note that this floors at `min` on its own: with no jitter the expression is 1, and
208 // the clamp does the rest. An earlier version added the slack *to* `min`, which looked
209 // equivalent and was not — `ceil` of the floating-point residue left by the decaying
210 // average is 1, not 0, so the buffer sat permanently one packet deeper than the network
211 // ever asked for and never gave that packet back.
212 let deviation = 2.0 * self.jitter;
213 let interval = f64::from(interval);
214 // Searched rather than divided-and-rounded. The candidates are a handful of small
215 // integers, so a scan is cheap, and it keeps every number that ends up as a depth in
216 // `usize` from the start — no float-to-integer conversion whose behaviour at the ends
217 // has to be reasoned about, and no rounding rule to get subtly wrong.
218 Some(
219 (min..=max)
220 .find(|&depth| deviation <= absorbable(depth, interval))
221 .unwrap_or(max),
222 )
223 }
224
225 /// Grow now. Growing is never delayed: the evidence for it is already audible.
226 fn deepen(&mut self) {
227 if let Policy::Adaptive { max, .. } = self.policy {
228 self.depth = (self.depth + 1).min(max);
229 self.clean_run = 0;
230 }
231 }
232
233 /// Grow to what the estimate asks for, or shrink after a long enough clean stretch.
234 fn resize(&mut self) {
235 let Some(want) = self.wanted() else {
236 return;
237 };
238 if want > self.depth {
239 self.depth = want;
240 self.clean_run = 0;
241 return;
242 }
243 if want < self.depth {
244 self.clean_run += 1;
245 if self.clean_run >= SHRINK_AFTER {
246 self.depth -= 1;
247 self.clean_run = 0;
248 }
249 return;
250 }
251 self.clean_run = 0;
252 }
253
254 /// Accept a packet.
255 ///
256 /// Returns whether it was kept. A packet already released is refused: playing it would put
257 /// audio out of order, which is worse than the gap it was going to fill.
258 pub fn push(&mut self, packet: Packet) -> bool {
259 self.received += 1;
260 let extended = self.extend(packet.sequence);
261
262 if let Some(last) = self.last_released
263 && extended <= last
264 {
265 // It arrived after its slot had already been played.
266 self.late += 1;
267 return false;
268 }
269 if self.packets.contains_key(&extended) {
270 self.duplicates += 1;
271 return false;
272 }
273
274 self.packets.insert(extended, packet);
275 true
276 }
277
278 /// Take the next packet, if the buffer has filled enough to release one.
279 ///
280 /// Returns `None` while still filling — that is the latency being paid for, not an error.
281 pub fn pop(&mut self) -> Option<Packet> {
282 if self.packets.len() < self.depth {
283 return None;
284 }
285 let &next = self.packets.keys().next()?;
286
287 if let Some(last) = self.last_released {
288 let expected = last + 1;
289 if next > expected {
290 // The packet we wanted never came. Count it and move on: waiting longer only
291 // adds latency, since anything that late is useless anyway.
292 self.lost += next - expected;
293 }
294 }
295
296 let packet = self.packets.remove(&next)?;
297 self.last_released = Some(next);
298 Some(packet)
299 }
300
301 /// Release everything held, in order, regardless of depth.
302 pub fn drain(&mut self) -> Vec<Packet> {
303 let mut out = Vec::with_capacity(self.packets.len());
304 while let Some(&next) = self.packets.keys().next() {
305 if let Some(packet) = self.packets.remove(&next) {
306 self.last_released = Some(next);
307 out.push(packet);
308 }
309 }
310 out
311 }
312
313 /// How many packets are held.
314 #[must_use]
315 pub fn len(&self) -> usize {
316 self.packets.len()
317 }
318
319 /// Whether nothing is held.
320 #[must_use]
321 pub fn is_empty(&self) -> bool {
322 self.packets.is_empty()
323 }
324
325 /// How many packets arrived.
326 #[must_use]
327 pub fn received(&self) -> u64 {
328 self.received
329 }
330
331 /// How many never arrived, as counted at release time.
332 #[must_use]
333 pub fn lost(&self) -> u64 {
334 self.lost
335 }
336
337 /// How many arrived more than once.
338 #[must_use]
339 pub fn duplicates(&self) -> u64 {
340 self.duplicates
341 }
342
343 /// How many arrived after their slot had been played.
344 #[must_use]
345 pub fn late(&self) -> u64 {
346 self.late
347 }
348
349 /// Map a 16-bit sequence number onto a monotonic 64-bit one.
350 ///
351 /// This is what makes reordering across the wrap work: once numbers are extended, ordinary
352 /// comparison is correct again, and the buffer's `BTreeMap` sorts them properly.
353 fn extend(&mut self, sequence: u16) -> u64 {
354 match self.highest {
355 None => {
356 self.highest = Some(sequence);
357 // The origin starts one cycle up, not at zero. The stream begins at a
358 // random sequence (RFC 3550 §5.1), so the first arrival can be from just
359 // after a wrap — and a straggler from before it must extend *below* the
360 // base, which needs room underneath.
361 self.cycles = 1;
362 65_536 + u64::from(sequence)
363 }
364 Some(highest) => {
365 if sequence_is_newer(sequence, highest) {
366 // A newer number that is numerically smaller means the counter wrapped.
367 if sequence < highest {
368 self.cycles += 1;
369 }
370 self.highest = Some(sequence);
371 self.cycles * 65_536 + u64::from(sequence)
372 } else {
373 // Older, and possibly from before a wrap we have already counted.
374 if sequence > highest && self.cycles > 0 {
375 (self.cycles - 1) * 65_536 + u64::from(sequence)
376 } else {
377 self.cycles * 65_536 + u64::from(sequence)
378 }
379 }
380 }
381 }
382 }
383}
384
385#[cfg(test)]
386#[allow(
387 clippy::unwrap_used,
388 clippy::expect_used,
389 clippy::panic,
390 clippy::indexing_slicing
391)]
392mod tests {
393 use super::*;
394 use bytes::Bytes;
395
396 fn packet(sequence: u16) -> Packet {
397 Packet::new(
398 0,
399 sequence,
400 u32::from(sequence) * 160,
401 1,
402 Bytes::from(vec![u8::try_from(sequence % 256).unwrap_or(0); 160]),
403 )
404 }
405
406 fn sequences(packets: &[Packet]) -> Vec<u16> {
407 packets.iter().map(|p| p.sequence).collect()
408 }
409
410 /// Packets come out in order, and the buffer keeps `depth - 1` in hand. That reserve is
411 /// the whole point: it is the slack available to absorb the next late arrival, and a
412 /// buffer that drained itself completely would have none.
413 #[test]
414 fn packets_in_order_come_out_in_order_leaving_a_reserve() {
415 let mut buffer = JitterBuffer::new(3);
416 for sequence in 1..=6 {
417 buffer.push(packet(sequence));
418 }
419 let mut out = Vec::new();
420 while let Some(packet) = buffer.pop() {
421 out.push(packet);
422 }
423 assert_eq!(sequences(&out), vec![1, 2, 3, 4]);
424 assert_eq!(buffer.len(), 2, "depth - 1 stays held as slack");
425 assert_eq!(buffer.lost(), 0);
426
427 // And the reserve is released when the stream ends.
428 assert_eq!(sequences(&buffer.drain()), vec![5, 6]);
429 }
430
431 /// The buffer exists for this: packets arriving out of order are played in order.
432 #[test]
433 fn reordered_packets_are_played_in_order() {
434 let mut buffer = JitterBuffer::new(3);
435 for sequence in [3, 1, 2, 5, 4, 6] {
436 buffer.push(packet(sequence));
437 }
438 assert_eq!(sequences(&buffer.drain()), vec![1, 2, 3, 4, 5, 6]);
439 }
440
441 #[test]
442 fn nothing_is_released_until_the_buffer_has_filled() {
443 let mut buffer = JitterBuffer::new(3);
444 buffer.push(packet(1));
445 assert!(buffer.pop().is_none(), "still filling");
446 buffer.push(packet(2));
447 assert!(buffer.pop().is_none());
448 buffer.push(packet(3));
449 assert!(buffer.pop().is_some(), "now it releases");
450 }
451
452 #[test]
453 fn a_duplicate_is_counted_and_dropped() {
454 let mut buffer = JitterBuffer::new(2);
455 assert!(buffer.push(packet(1)));
456 assert!(!buffer.push(packet(1)), "the second copy is refused");
457 assert_eq!(buffer.duplicates(), 1);
458 assert_eq!(buffer.len(), 1);
459 }
460
461 /// A gap is counted at release time rather than waited for. Waiting only adds latency,
462 /// since a packet that late is useless anyway.
463 #[test]
464 fn a_missing_packet_is_counted_as_lost() {
465 let mut buffer = JitterBuffer::new(2);
466 for sequence in [1, 2, 4, 5] {
467 buffer.push(packet(sequence));
468 }
469 let out = buffer.drain();
470 assert_eq!(sequences(&out), vec![1, 2, 4, 5]);
471 // Draining does not diagnose gaps; popping does.
472 let mut buffer = JitterBuffer::new(1);
473 for sequence in [1, 2, 4] {
474 buffer.push(packet(sequence));
475 }
476 buffer.pop();
477 buffer.pop();
478 buffer.pop();
479 assert_eq!(buffer.lost(), 1, "3 never arrived");
480 }
481
482 /// A packet whose slot has already been played is refused. Playing it would put audio out
483 /// of order, which sounds worse than the gap it was going to fill.
484 #[test]
485 fn a_packet_that_arrives_too_late_is_refused() {
486 let mut buffer = JitterBuffer::new(1);
487 buffer.push(packet(1));
488 buffer.push(packet(2));
489 buffer.pop();
490 buffer.pop();
491
492 assert!(!buffer.push(packet(1)), "its slot has been played");
493 assert_eq!(buffer.late(), 1);
494 }
495
496 /// The counter wraps every ~22 minutes at 50 packets per second. A buffer that treats the
497 /// wrap as a jump backwards discards a minute of audio while it resynchronises.
498 #[test]
499 fn the_buffer_orders_correctly_across_a_sequence_wrap() {
500 let mut buffer = JitterBuffer::new(2);
501 for sequence in [65_533, 65_534, 65_535, 0, 1, 2] {
502 buffer.push(packet(sequence));
503 }
504 assert_eq!(
505 sequences(&buffer.drain()),
506 vec![65_533, 65_534, 65_535, 0, 1, 2],
507 "the wrap is a continuation, not a jump backwards"
508 );
509 assert_eq!(buffer.lost(), 0);
510 }
511
512 /// The sequence starts at a random value (RFC 3550 §5.1), so a stream can begin just
513 /// short of the 16-bit wrap — and the first packet to arrive can be from just *after*
514 /// it. A straggler from before the wrap must then sort before the base, not be mapped
515 /// ~65000 slots into the future.
516 #[test]
517 fn a_pre_wrap_straggler_at_stream_start_sorts_before_the_base() {
518 let mut buffer = JitterBuffer::new(2);
519 for sequence in [0, 65_535, 1, 2, 3] {
520 buffer.push(packet(sequence));
521 }
522 assert_eq!(sequences(&buffer.drain()), vec![65_535, 0, 1, 2, 3]);
523 }
524
525 /// The catastrophic form of the same mistake: the straggler's slot has already been
526 /// played, and mapping it into the future poisons `last_released` — after which every
527 /// genuine packet is refused as late and the stream is silent until the real wrap.
528 #[test]
529 fn a_pre_wrap_straggler_cannot_mute_the_stream() {
530 let mut buffer = JitterBuffer::new(1);
531 buffer.push(packet(0));
532 assert!(buffer.pop().is_some());
533
534 assert!(
535 !buffer.push(packet(65_535)),
536 "its slot is in the past, so it is late — not 65535 slots early"
537 );
538 assert_eq!(buffer.late(), 1);
539
540 // And the genuine stream keeps flowing.
541 for sequence in [1, 2, 3] {
542 assert!(buffer.push(packet(sequence)));
543 }
544 assert_eq!(sequences(&buffer.drain()), vec![1, 2, 3]);
545 }
546
547 /// Reordering *across* the wrap is the hard case: a packet from before the wrap arriving
548 /// after one from after it.
549 #[test]
550 fn reordering_across_the_wrap_still_sorts() {
551 let mut buffer = JitterBuffer::new(4);
552 for sequence in [65_535, 1, 0, 2] {
553 buffer.push(packet(sequence));
554 }
555 assert_eq!(sequences(&buffer.drain()), vec![65_535, 0, 1, 2]);
556 }
557
558 #[test]
559 fn statistics_add_up() {
560 let mut buffer = JitterBuffer::new(1);
561 for sequence in [1, 2, 2, 4] {
562 buffer.push(packet(sequence));
563 }
564 assert_eq!(buffer.received(), 4);
565 assert_eq!(buffer.duplicates(), 1);
566 while buffer.pop().is_some() {}
567 assert_eq!(buffer.lost(), 1);
568 }
569}