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}