Skip to main content

rtc_interceptor/pacing/
sender.rs

1//! Smooths outgoing packets to a target rate.
2
3use super::pacer::Pacer as LeakyBucket;
4use crate::Interceptor;
5use crate::StreamInfo;
6use crate::{Attribute, Packet, TaggedPacket};
7use sansio::Protocol;
8use shared::error::Error;
9use shared::marshal::MarshalSize;
10use std::collections::VecDeque;
11use std::time::Instant;
12
13/// Rate used when none is configured: 1 Mb/s.
14pub const DEFAULT_BITRATE: f64 = 1_000_000.0;
15
16/// Packets held before new ones are refused.
17pub const DEFAULT_QUEUE_LIMIT: usize = 4096;
18
19/// Builder for [`PacerInterceptor`].
20///
21/// # Example
22///
23/// ```
24/// use rtc_interceptor::{Slot, PacerBuilder, Registry};
25///
26/// let chain = Registry::new()
27///     .with(Slot::Pacer, PacerBuilder::new().with_target_bitrate(2_000_000.0).build())
28///     .build();
29/// ```
30pub struct PacerBuilder {
31    bitrate: f64,
32    burst_bits: Option<f64>,
33    queue_limit: usize,
34}
35
36impl Default for PacerBuilder {
37    fn default() -> Self {
38        Self {
39            bitrate: DEFAULT_BITRATE,
40            burst_bits: None,
41            queue_limit: DEFAULT_QUEUE_LIMIT,
42        }
43    }
44}
45
46impl PacerBuilder {
47    /// Create a builder with the default rate and queue limit.
48    pub fn new() -> Self {
49        Self::default()
50    }
51
52    /// The rate to pace at, in bits per second.
53    pub fn with_target_bitrate(mut self, bits_per_second: f64) -> Self {
54        self.bitrate = bits_per_second;
55        self
56    }
57
58    /// How much may be released at once, in bits.
59    ///
60    /// Larger bursts smooth less but cost fewer wake-ups.
61    pub fn with_burst_bits(mut self, burst_bits: f64) -> Self {
62        self.burst_bits = Some(burst_bits);
63        self
64    }
65
66    /// How many packets may be queued before new ones are refused.
67    pub fn with_queue_limit(mut self, queue_limit: usize) -> Self {
68        self.queue_limit = queue_limit;
69        self
70    }
71
72    /// Build the interceptor.
73    pub fn build(self) -> PacerInterceptor {
74        let bucket = match self.burst_bits {
75            Some(burst_bits) => LeakyBucket::new(self.bitrate).with_burst_bits(burst_bits),
76            None => LeakyBucket::new(self.bitrate),
77        };
78        PacerInterceptor::new(bucket, self.queue_limit)
79    }
80}
81
82/// Releases queued packets at a target rate rather than as fast as they arrive.
83///
84/// # Where this belongs in the chain
85///
86/// **Close to the wire, with every generator on the application side of it.** Everything after it
87/// on the write path observes the *release* instant, so a send history goes there and records what
88/// actually left; placed the other way round it would record the enqueue instant and charge this
89/// queueing delay to the network.
90///
91/// Retransmissions, FEC repair packets and generated RTCP are all produced further out and reach
92/// the pacer on the belt, so they are metered along with everything else. Under the nested chain
93/// they bypassed it entirely.
94///
95/// # Differences from upstream
96///
97/// - **No ticker and no goroutine.** Upstream runs a loop on a 5 ms `time.Ticker`; here the
98///   budget is a pure function of the instants handed in, so a release schedule is reproducible
99///   rather than merely eventually-correct.
100/// - **Idle means idle.** `poll_timeout` returns `None` with nothing queued, so an idle
101///   connection does not wake the whole chain at the pacing interval.
102/// - **The deadline is when the head can afford to go**, not `now + interval`, so it always
103///   advances — a deadline at or before the `now` just handed to `handle_timeout` is the
104///   webrtc#862 busy-loop.
105pub struct PacerInterceptor {
106    pacer: LeakyBucket,
107    queue: VecDeque<TaggedPacket>,
108    queue_limit: usize,
109    /// Packets refused because the queue was full.
110    dropped: u64,
111    /// Packets the budget has released, waiting to join the belt.
112    released: VecDeque<TaggedPacket>,
113    /// Inbound packets ready for the next interceptor.
114    read_queue: VecDeque<TaggedPacket>,
115    /// Outbound packets ready for the next interceptor: what passed through, plus
116    /// anything this one generated.
117    write_queue: VecDeque<TaggedPacket>,
118}
119
120impl PacerInterceptor {
121    fn new(pacer: LeakyBucket, queue_limit: usize) -> Self {
122        Self {
123            read_queue: VecDeque::new(),
124            write_queue: VecDeque::new(),
125            released: VecDeque::new(),
126            pacer,
127            queue: VecDeque::new(),
128            queue_limit: queue_limit.max(1),
129            dropped: 0,
130        }
131    }
132
133    /// The pacing bucket, for a bandwidth estimator to drive.
134    pub fn pacer(&self) -> &LeakyBucket {
135        &self.pacer
136    }
137
138    /// The pacing bucket, mutably — this is where `set_target_bitrate` is reached.
139    pub fn pacer_mut(&mut self) -> &mut LeakyBucket {
140        &mut self.pacer
141    }
142
143    /// How many packets are waiting to be released.
144    pub fn queued(&self) -> usize {
145        self.queue.len()
146    }
147
148    /// How many packets have been refused because the queue was full.
149    pub fn dropped(&self) -> u64 {
150        self.dropped
151    }
152
153    /// The size of a packet on the wire, in bits — the unit the budget is kept in.
154    fn bits_of(packet: &TaggedPacket) -> f64 {
155        match &packet.message.packet {
156            Packet::Rtp(rtp) => (rtp.marshal_size() * 8) as f64,
157            Packet::Rtcp(_) => 0.0,
158        }
159    }
160
161    /// The instant the head of the queue can next be released.
162    fn next_release(&self) -> Option<Instant> {
163        let head = self.queue.front()?;
164        self.pacer.releasable_at(Self::bits_of(head))
165    }
166}
167
168impl Protocol<TaggedPacket, TaggedPacket, ()> for PacerInterceptor {
169    type Rout = TaggedPacket;
170    type Wout = TaggedPacket;
171    type Eout = ();
172    type Error = Error;
173    type Time = Instant;
174
175    fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
176        // Follow the congestion controller's estimate.
177        //
178        // It arrives on the **read** leg because that is the only one it can cross on. The
179        // controller is wire-most, so on the write leg it is the last interceptor to see a packet
180        // and anything it attached would already have gone past here. On the read leg it is the
181        // first, and this is downstream of it — so the feedback packet that produced the estimate
182        // carries it here on its way to the application.
183        //
184        // Observed, not consumed: the packet carries on with the attribute attached, so anything
185        // further along reads the same number.
186        if let Some(Attribute::TargetBitrateChanged { bits_per_second }) =
187            msg.message.get(&Attribute::TargetBitrateChanged {
188                bits_per_second: 0.0,
189            })
190        {
191            self.pacer.set_target_bitrate(*bits_per_second);
192        }
193
194        self.read_queue.push_back(msg);
195        Ok(())
196    }
197
198    fn poll_read(&mut self) -> Option<Self::Rout> {
199        self.read_queue.pop_front()
200    }
201
202    fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
203        // No estimate is read here. It was, once — but the congestion controller sits wire-*ward*
204        // of the pacer, so on the write leg it never sees a packet before this interceptor does and
205        // the branch had no possible producer. If an application-set target is ever wanted it comes
206        // back with its own `RTCEvent` variant, and a test.
207
208        // RTCP is control traffic and mostly time-sensitive — feedback is only useful while it is
209        // fresh — so it is not paced.
210        if matches!(msg.message.packet, Packet::Rtcp(_)) {
211            self.write_queue.push_back(msg);
212            return Ok(());
213        }
214
215        // Keeping the budget current at enqueue time is what lets `poll_timeout` compute a
216        // deadline that is never in the past, even before the first `handle_timeout`.
217        self.pacer.refill(msg.now);
218
219        if self.queue.len() >= self.queue_limit {
220            // Refuse the arrival rather than evicting something already queued: the queued
221            // packets are older, and dropping one of those would put a hole in the middle of a
222            // stream that the receiver would have to recover from.
223            self.dropped += 1;
224            // Refused, so it never leaves.
225            return Ok(());
226        }
227
228        // Held: it leaves later, from `poll_write`, when the budget allows.
229        self.queue.push_back(msg);
230        Ok(())
231    }
232
233    fn poll_write(&mut self) -> Option<TaggedPacket> {
234        // Unpaced traffic goes first — RTCP is only useful while it is fresh, and holding it
235        // behind the budget would be pacing it after all.
236        self.write_queue
237            .pop_front()
238            .or_else(|| self.released.pop_front())
239    }
240
241    fn handle_timeout(&mut self, now: Instant) -> Result<(), Error> {
242        self.pacer.refill(now);
243
244        while let Some(head) = self.queue.front() {
245            let bits = Self::bits_of(head);
246            if !self.pacer.can_release(bits) {
247                break;
248            }
249
250            let mut packet = self.queue.pop_front().expect("front just checked");
251            self.pacer.consume(bits);
252            // Rule 3: the packet departs now. Anything below recording departure — the send
253            // history congestion control reads — must see the release instant, not the enqueue
254            // instant, or this queueing delay is charged to the network.
255            packet.now = now;
256            // Queued for `poll_write`, which puts it back on the belt so it traverses every
257            // interceptor ahead — numbering, and the send history reading the release instant just set.
258            self.released.push_back(packet);
259        }
260        Ok(())
261    }
262
263    fn poll_timeout(&mut self) -> Option<Instant> {
264        self.next_release()
265    }
266}
267
268impl Interceptor for PacerInterceptor {
269    fn bind_local_stream(&mut self, _info: &StreamInfo) {}
270
271    fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
272
273    fn bind_remote_stream(&mut self, _info: &StreamInfo) {}
274
275    fn unbind_remote_stream(&mut self, _info: &StreamInfo) {}
276}