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::{PacerBuilder, Registry};
25///
26/// let chain = Registry::new()
27///     .with(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        self.read_queue.push_back(msg);
177        Ok(())
178    }
179
180    fn poll_read(&mut self) -> Option<Self::Rout> {
181        self.read_queue.pop_front()
182    }
183
184    fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
185        // Follow the congestion controller's estimate. It rides on an outgoing packet because
186        // the controller is application-ward of the pacer and nothing else connects the two;
187        // observed rather than consumed, so whatever picks the encoder bitrate reads the same
188        // number from the same packet.
189        if let Some(Attribute::TargetBitrateChanged { bits_per_second }) =
190            msg.message.get(&Attribute::TargetBitrateChanged {
191                bits_per_second: 0.0,
192            })
193        {
194            self.pacer.set_target_bitrate(*bits_per_second);
195        }
196
197        // RTCP is control traffic and mostly time-sensitive — feedback is only useful while it is
198        // fresh — so it is not paced.
199        if matches!(msg.message.packet, Packet::Rtcp(_)) {
200            self.write_queue.push_back(msg);
201            return Ok(());
202        }
203
204        // Keeping the budget current at enqueue time is what lets `poll_timeout` compute a
205        // deadline that is never in the past, even before the first `handle_timeout`.
206        self.pacer.refill(msg.now);
207
208        if self.queue.len() >= self.queue_limit {
209            // Refuse the arrival rather than evicting something already queued: the queued
210            // packets are older, and dropping one of those would put a hole in the middle of a
211            // stream that the receiver would have to recover from.
212            self.dropped += 1;
213            // Refused, so it never leaves.
214            return Ok(());
215        }
216
217        // Held: it leaves later, from `poll_write`, when the budget allows.
218        self.queue.push_back(msg);
219        Ok(())
220    }
221
222    fn poll_write(&mut self) -> Option<TaggedPacket> {
223        // Unpaced traffic goes first — RTCP is only useful while it is fresh, and holding it
224        // behind the budget would be pacing it after all.
225        self.write_queue
226            .pop_front()
227            .or_else(|| self.released.pop_front())
228    }
229
230    fn handle_timeout(&mut self, now: Instant) -> Result<(), Error> {
231        self.pacer.refill(now);
232
233        while let Some(head) = self.queue.front() {
234            let bits = Self::bits_of(head);
235            if !self.pacer.can_release(bits) {
236                break;
237            }
238
239            let mut packet = self.queue.pop_front().expect("front just checked");
240            self.pacer.consume(bits);
241            // Rule 3: the packet departs now. Anything below recording departure — the send
242            // history congestion control reads — must see the release instant, not the enqueue
243            // instant, or this queueing delay is charged to the network.
244            packet.now = now;
245            // Queued for `poll_write`, which puts it back on the belt so it traverses every
246            // interceptor ahead — numbering, and the send history reading the release instant just set.
247            self.released.push_back(packet);
248        }
249        Ok(())
250    }
251
252    fn poll_timeout(&mut self) -> Option<Instant> {
253        self.next_release()
254    }
255}
256
257impl Interceptor for PacerInterceptor {
258    fn bind_local_stream(&mut self, _info: &StreamInfo) {}
259
260    fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
261
262    fn bind_remote_stream(&mut self, _info: &StreamInfo) {}
263
264    fn unbind_remote_stream(&mut self, _info: &StreamInfo) {}
265}