rtc_interceptor/intervalpli/
generator.rs1use super::stream_supports_pli;
4use crate::Interceptor;
5use crate::stream_info::StreamInfo;
6use crate::{Attribute, AttributedPacket, Packet, TaggedPacket};
7use sansio::Protocol;
8use shared::TransportContext;
9use shared::error::Error;
10use std::collections::{BTreeSet, VecDeque};
11use std::time::{Duration, Instant};
12
13pub const DEFAULT_INTERVAL: Duration = Duration::from_secs(3);
15
16pub struct IntervalPliInterceptor {
35 interval: Duration,
36 streams: BTreeSet<u32>,
38 pending_immediate: BTreeSet<u32>,
43 next_timeout: Option<Instant>,
44 write_queue: VecDeque<TaggedPacket>,
45 read_queue: VecDeque<TaggedPacket>,
47}
48
49impl Default for IntervalPliInterceptor {
50 fn default() -> Self {
51 Self::new(DEFAULT_INTERVAL)
52 }
53}
54
55impl IntervalPliInterceptor {
56 pub fn new(interval: Duration) -> Self {
61 Self {
62 read_queue: VecDeque::new(),
63 interval,
64 streams: BTreeSet::new(),
65 pending_immediate: BTreeSet::new(),
66 next_timeout: None,
67 write_queue: VecDeque::new(),
68 }
69 }
70
71 pub fn bound_streams(&self) -> impl Iterator<Item = u32> + '_ {
73 self.streams.iter().copied()
74 }
75
76 fn queue_plis(&mut self, now: Instant, ssrcs: &[u32]) {
81 if ssrcs.is_empty() {
82 return;
83 }
84
85 for ssrc in ssrcs {
88 self.pending_immediate.remove(ssrc);
89 }
90
91 let plis: Vec<Box<dyn rtcp::Packet>> = ssrcs
92 .iter()
93 .map(|&ssrc| {
94 Box::new(
95 rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication {
96 sender_ssrc: 0,
97 media_ssrc: ssrc,
98 },
99 ) as Box<dyn rtcp::Packet>
100 })
101 .collect();
102
103 self.write_queue.push_back(TaggedPacket {
104 now,
105 transport: TransportContext::default(),
106 message: AttributedPacket::new(Packet::Rtcp(plis)),
107 });
108 }
109
110 fn observe(&mut self, now: Instant) {
113 if !self.pending_immediate.is_empty() {
114 let ssrcs: Vec<u32> = self.pending_immediate.iter().copied().collect();
115 self.queue_plis(now, &ssrcs);
116 }
117 self.arm(now);
118 }
119
120 fn arm(&mut self, now: Instant) {
122 if self.next_timeout.is_none() && self.is_periodic() && !self.streams.is_empty() {
123 self.next_timeout = Some(now + self.interval);
124 }
125 }
126
127 fn is_periodic(&self) -> bool {
128 !self.interval.is_zero()
129 }
130
131 fn targets(&self, requested: Option<&Vec<u32>>) -> Vec<u32> {
135 match requested {
136 None => self.streams.iter().copied().collect(),
137 Some(ssrcs) => ssrcs
138 .iter()
139 .copied()
140 .filter(|ssrc| self.streams.contains(ssrc))
141 .collect(),
142 }
143 }
144}
145
146impl Protocol<TaggedPacket, TaggedPacket, ()> for IntervalPliInterceptor {
147 type Rout = TaggedPacket;
148 type Wout = TaggedPacket;
149 type Eout = ();
150 type Error = Error;
151 type Time = Instant;
152
153 fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
154 self.observe(msg.now);
155
156 if let Some(Attribute::ForcePli { ssrcs }) =
159 msg.message.get(&Attribute::ForcePli { ssrcs: None })
160 {
161 let targets = self.targets(ssrcs.as_ref());
162 self.queue_plis(msg.now, &targets);
163 self.arm(msg.now);
164 }
165
166 self.read_queue.push_back(msg);
167 Ok(())
168 }
169
170 fn poll_read(&mut self) -> Option<Self::Rout> {
171 self.read_queue.pop_front()
172 }
173
174 fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
175 self.write_queue.push_back(msg);
176 Ok(())
177 }
178
179 fn poll_write(&mut self) -> Option<TaggedPacket> {
180 self.write_queue.pop_front()
181 }
182
183 fn handle_timeout(&mut self, now: Instant) -> Result<(), Error> {
184 self.observe(now);
185
186 if let Some(next_timeout) = self.next_timeout
187 && now >= next_timeout
188 {
189 self.next_timeout = Some(now + self.interval);
190 let ssrcs: Vec<u32> = self.streams.iter().copied().collect();
191 self.queue_plis(now, &ssrcs);
192 }
193 Ok(())
194 }
195
196 fn poll_timeout(&mut self) -> Option<Instant> {
197 self.next_timeout
198 }
199}
200
201impl Interceptor for IntervalPliInterceptor {
202 fn bind_remote_stream(&mut self, info: &StreamInfo) {
203 if stream_supports_pli(info) {
204 self.streams.insert(info.ssrc);
205 self.pending_immediate.insert(info.ssrc);
206 }
207 }
208
209 fn unbind_remote_stream(&mut self, info: &StreamInfo) {
210 self.streams.remove(&info.ssrc);
211 self.pending_immediate.remove(&info.ssrc);
212 if self.streams.is_empty() {
213 self.next_timeout = None;
215 }
216 }
217
218 fn bind_local_stream(&mut self, _info: &StreamInfo) {}
219
220 fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::chain::InterceptorChain;
227 use crate::stream_info::RTCPFeedback;
228 use sansio::Protocol;
229
230 fn stream_info(ssrc: u32) -> StreamInfo {
231 StreamInfo {
232 ssrc,
233 rtcp_feedback: vec![RTCPFeedback {
234 typ: "nack".to_owned(),
235 parameter: "pli".to_owned(),
236 }],
237 ..Default::default()
238 }
239 }
240
241 fn force_pli(now: Instant, ssrcs: Option<Vec<u32>>) -> TaggedPacket {
244 let mut msg = TaggedPacket {
245 now,
246 transport: Default::default(),
247 message: AttributedPacket::new(Packet::Rtp(rtp::Packet::default())),
248 };
249 msg.message.add(Attribute::ForcePli { ssrcs });
250 msg
251 }
252
253 fn plis(chain: &mut InterceptorChain) -> Vec<u32> {
254 let mut out = Vec::new();
255 while let Some(pkt) = chain.poll_write() {
256 if let Packet::Rtcp(packets) = &pkt.message.packet {
257 for p in packets {
258 if let Some(pli) = p
259 .as_any()
260 .downcast_ref::<rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication>(
261 ) {
262 out.push(pli.media_ssrc);
263 }
264 }
265 }
266 }
267 out
268 }
269
270 fn chain(interval: Duration) -> InterceptorChain {
271 InterceptorChain::new(vec![Box::new(IntervalPliInterceptor::new(interval))])
272 }
273
274 #[test]
275 fn a_bound_stream_is_asked_immediately() {
276 let now = Instant::now();
277 let mut chain = chain(Duration::from_secs(3));
278 chain.bind_remote_stream(&stream_info(7));
279
280 chain.handle_timeout(now).unwrap();
281 assert_eq!(vec![7], plis(&mut chain));
282 }
283
284 #[test]
285 fn requests_repeat_on_the_interval() {
286 let now = Instant::now();
287 let mut chain = chain(Duration::from_secs(1));
288 chain.bind_remote_stream(&stream_info(7));
289
290 chain.handle_timeout(now).unwrap();
291 assert_eq!(vec![7], plis(&mut chain), "the immediate one");
292
293 chain
294 .handle_timeout(now + Duration::from_millis(999))
295 .unwrap();
296 assert!(plis(&mut chain).is_empty(), "not due yet");
297
298 chain.handle_timeout(now + Duration::from_secs(1)).unwrap();
299 assert_eq!(vec![7], plis(&mut chain), "due");
300 }
301
302 #[test]
303 fn an_unbound_stream_stops_being_asked() {
304 let now = Instant::now();
305 let mut chain = chain(Duration::from_secs(1));
306 chain.bind_remote_stream(&stream_info(7));
307 chain.handle_timeout(now).unwrap();
308 let _ = plis(&mut chain);
309
310 chain.unbind_remote_stream(&stream_info(7));
311 chain.handle_timeout(now + Duration::from_secs(5)).unwrap();
312
313 assert!(plis(&mut chain).is_empty());
314 assert_eq!(None, chain.poll_timeout(), "and stops asking to be woken");
315 }
316
317 #[test]
324 fn force_pli_reaches_the_generator_through_the_chain() {
325 let now = Instant::now();
326 let mut chain = InterceptorChain::new(vec![
329 Box::new(IntervalPliInterceptor::new(Duration::ZERO)),
330 Box::new(crate::TwccSenderBuilder::new().build()),
331 ]);
332 chain.bind_remote_stream(&stream_info(7));
333 chain.handle_timeout(now).unwrap();
337 plis(&mut chain);
338
339 chain
340 .handle_read(force_pli(now, None))
341 .expect("handle_read");
342
343 assert_eq!(vec![7], plis(&mut chain));
344 }
345
346 #[test]
347 fn force_pli_can_name_specific_streams() {
348 let now = Instant::now();
349 let mut chain = chain(Duration::ZERO);
350 chain.bind_remote_stream(&stream_info(7));
351 chain.bind_remote_stream(&stream_info(8));
352 chain.handle_timeout(now).unwrap();
356 plis(&mut chain);
357
358 chain
359 .handle_read(force_pli(now, Some(vec![8])))
360 .expect("handle_read");
361
362 assert_eq!(vec![8], plis(&mut chain), "only the one named");
363 }
364
365 #[test]
366 fn force_pli_ignores_streams_that_are_not_bound() {
367 let now = Instant::now();
368 let mut chain = chain(Duration::ZERO);
369 chain.bind_remote_stream(&stream_info(7));
370 chain.handle_timeout(now).unwrap();
374 plis(&mut chain);
375
376 chain
377 .handle_read(force_pli(now, Some(vec![999])))
378 .expect("handle_read");
379
380 assert!(
381 plis(&mut chain).is_empty(),
382 "a PLI for a stream nobody receives has no destination"
383 );
384 }
385
386 #[test]
388 fn a_zero_interval_disables_only_the_periodic_requests() {
389 let now = Instant::now();
390 let mut chain = chain(Duration::ZERO);
391 chain.bind_remote_stream(&stream_info(7));
392
393 chain.handle_timeout(now).unwrap();
394 assert_eq!(vec![7], plis(&mut chain), "the immediate one still happens");
395 assert_eq!(None, chain.poll_timeout(), "but no interval is armed");
396
397 chain.handle_timeout(now + Duration::from_secs(60)).unwrap();
398 assert!(plis(&mut chain).is_empty());
399
400 chain
401 .handle_read(force_pli(now, None))
402 .expect("handle_read");
403 assert_eq!(vec![7], plis(&mut chain), "forcing still works");
404 }
405
406 #[test]
409 fn a_force_pli_attribute_is_not_consumed() {
410 let now = Instant::now();
411 let mut chain =
412 InterceptorChain::new(vec![Box::new(IntervalPliInterceptor::new(Duration::ZERO))]);
413 chain.bind_remote_stream(&stream_info(7));
414 chain.handle_timeout(now).unwrap();
418 plis(&mut chain);
419
420 let mut carrier = TaggedPacket {
421 now,
422 transport: Default::default(),
423 message: AttributedPacket::new(Packet::Rtp(rtp::Packet::default())),
424 };
425 carrier.message.add(Attribute::ForcePli { ssrcs: None });
426 chain.handle_read(carrier).expect("handle_read");
427
428 assert_eq!(vec![7], plis(&mut chain), "the request was acted on");
429 assert!(
430 chain.poll_read().is_some(),
431 "and the packet that carried it carried on"
432 );
433 }
434}