rtc_interceptor/flexfec/draft03/receiver.rs
1//! Receive-side FlexFEC draft-03: recovers lost media and keeps repair packets off the wire.
2//!
3//! # No upstream counterpart
4//!
5//! `pion/interceptor` has a full draft-03 decoder that nothing constructs: `ConfigureFlexFEC03`
6//! registers only the encoder, and `newFECDecoder` appears outside its own tests nowhere. So this
7//! interceptor is new work, and the decisions it makes are ones upstream never had to:
8//!
9//! - **Repair packets are consumed, not forwarded.** They are not media; the application must
10//! never see them, and neither should the interceptors below, which would treat their sequence
11//! numbers as a media stream's and report gaps that do not exist.
12//! - **Recovered packets re-enter through `inner`.** A recovered packet has to look to every
13//! layer below exactly like one that arrived normally.
14//! - **Memory is bounded** by the decoder's own retention limits; a receive path that keeps every
15//! packet it has ever seen in case a repair packet turns up later is a leak.
16
17use super::decoder::FlexFec03Decoder;
18use crate::stream_info::StreamInfo;
19use crate::{Interceptor, Packet, TaggedPacket, interceptor};
20use shared::error::Error;
21use std::collections::HashMap;
22use std::marker::PhantomData;
23
24/// Builder for [`FlexFec03ReceiveInterceptor`].
25///
26/// # Example
27///
28/// ```
29/// use rtc_interceptor::{FlexFec03ReceiveBuilder, Registry};
30///
31/// let chain = Registry::new()
32/// .with(FlexFec03ReceiveBuilder::new().build())
33/// .build();
34/// ```
35pub struct FlexFec03ReceiveBuilder<P> {
36 _phantom: PhantomData<P>,
37}
38
39impl<P> Default for FlexFec03ReceiveBuilder<P> {
40 fn default() -> Self {
41 Self {
42 _phantom: PhantomData,
43 }
44 }
45}
46
47impl<P> FlexFec03ReceiveBuilder<P> {
48 /// Create a builder.
49 pub fn new() -> Self {
50 Self::default()
51 }
52
53 /// Build the interceptor factory function.
54 pub fn build(self) -> impl FnOnce(P) -> FlexFec03ReceiveInterceptor<P> {
55 move |inner| FlexFec03ReceiveInterceptor::new(inner)
56 }
57}
58
59/// Recovers media lost from streams protected by FlexFEC draft-03.
60///
61/// Belongs **outermost on the read path**: a recovered packet must be indistinguishable from one
62/// that arrived, so recovery has to happen before anything below inspects sequence numbers — the
63/// NACK generator should not ask for a packet FEC is about to rebuild, and the jitter buffer
64/// should order the recovered packet along with the rest.
65#[derive(Interceptor)]
66pub struct FlexFec03ReceiveInterceptor<P> {
67 #[next]
68 inner: P,
69 /// Decoders keyed by the media SSRC they protect.
70 decoders: HashMap<u32, FlexFec03Decoder>,
71 /// Repair SSRC to the media SSRC it repairs, so a repair packet finds its decoder.
72 repair_to_media: HashMap<u32, u32>,
73}
74
75impl<P> FlexFec03ReceiveInterceptor<P> {
76 fn new(inner: P) -> Self {
77 Self {
78 inner,
79 decoders: HashMap::new(),
80 repair_to_media: HashMap::new(),
81 }
82 }
83
84 /// The media SSRCs currently protected.
85 pub fn protected_streams(&self) -> impl Iterator<Item = u32> + '_ {
86 self.decoders.keys().copied()
87 }
88}
89
90#[interceptor]
91impl<P: Interceptor> FlexFec03ReceiveInterceptor<P> {
92 #[overrides]
93 fn bind_remote_stream(&mut self, info: &StreamInfo) {
94 // Both halves, as everywhere else: without the repair SSRC there is nothing to route, and
95 // without the payload type the association was never negotiated.
96 if let (Some(ssrc_fec), Some(_)) = (info.ssrc_fec, info.payload_type_fec) {
97 self.decoders
98 .insert(info.ssrc, FlexFec03Decoder::new(ssrc_fec, info.ssrc));
99 self.repair_to_media.insert(ssrc_fec, info.ssrc);
100 }
101 self.inner.bind_remote_stream(info);
102 }
103
104 #[overrides]
105 fn unbind_remote_stream(&mut self, info: &StreamInfo) {
106 self.decoders.remove(&info.ssrc);
107 self.repair_to_media.retain(|_, media| *media != info.ssrc);
108 self.inner.unbind_remote_stream(info);
109 }
110
111 #[overrides]
112 fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
113 let Packet::Rtp(rtp_packet) = &msg.message else {
114 return self.inner.handle_read(msg);
115 };
116
117 let ssrc = rtp_packet.header.ssrc;
118 let now = msg.now;
119 let transport = msg.transport;
120
121 // A repair packet: hand it to the decoder and stop here. Forwarding it would put a
122 // non-media stream in front of every interceptor below.
123 if let Some(&media_ssrc) = self.repair_to_media.get(&ssrc) {
124 let recovered = match self.decoders.get_mut(&media_ssrc) {
125 Some(decoder) => decoder.decode(rtp_packet.clone()),
126 None => Vec::new(),
127 };
128 return self.forward_recovered(now, transport, recovered);
129 }
130
131 // A protected media packet: the decoder needs it in order to recover its neighbours, and
132 // it carries on downstream as usual.
133 let recovered = match self.decoders.get_mut(&ssrc) {
134 Some(decoder) => decoder.decode(rtp_packet.clone()),
135 None => return self.inner.handle_read(msg),
136 };
137
138 // The live packet arrived first, so it goes first; anything it made recoverable follows.
139 // Their order relative to each other is the jitter buffer's problem, not this one's.
140 self.inner.handle_read(msg)?;
141 self.forward_recovered(now, transport, recovered)
142 }
143}
144
145impl<P: Interceptor> FlexFec03ReceiveInterceptor<P> {
146 /// Re-inject recovered packets through `inner` (chain contract rule 2).
147 ///
148 /// Not a local `poll_read` queue: a recovered packet is a media packet the layers below have
149 /// never seen, and they all have work to do on it.
150 fn forward_recovered(
151 &mut self,
152 now: std::time::Instant,
153 transport: shared::TransportContext,
154 recovered: Vec<rtp::Packet>,
155 ) -> Result<(), Error> {
156 for packet in recovered {
157 self.inner.handle_read(TaggedPacket {
158 now,
159 transport,
160 message: Packet::Rtp(packet),
161 })?;
162 }
163 Ok(())
164 }
165}