Skip to main content

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}