Skip to main content

rtc_interceptor/twcc/
receiver.rs

1//! TWCC Receiver Interceptor - tracks incoming packets and generates feedback.
2
3use super::recorder::Recorder;
4use super::stream_supports_twcc;
5use crate::Interceptor;
6use crate::stream_info::StreamInfo;
7use crate::{AttributedPacket, Packet, TaggedPacket};
8use sansio::Protocol;
9use shared::TransportContext;
10use shared::error::Error;
11use shared::marshal::Unmarshal;
12use std::collections::{HashMap, VecDeque};
13use std::time::{Duration, Instant};
14
15/// Default interval for sending TWCC feedback.
16const DEFAULT_INTERVAL: Duration = Duration::from_millis(100);
17
18/// Builder for the TwccReceiverInterceptor.
19///
20/// # Example
21///
22/// ```
23/// use rtc_interceptor::{Registry, TwccReceiverBuilder};
24/// use std::time::Duration;
25///
26/// let chain = Registry::new()
27///     .with(TwccReceiverBuilder::new()
28///         .with_interval(Duration::from_millis(100))
29///         .build())
30///     .build();
31/// ```
32pub struct TwccReceiverBuilder {
33    /// Interval between feedback reports.
34    interval: Duration,
35}
36
37impl Default for TwccReceiverBuilder {
38    fn default() -> Self {
39        Self {
40            interval: DEFAULT_INTERVAL,
41        }
42    }
43}
44
45impl TwccReceiverBuilder {
46    /// Create a new builder with default settings.
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    /// Set the interval between feedback reports.
52    pub fn with_interval(mut self, interval: Duration) -> Self {
53        self.interval = interval;
54        self
55    }
56
57    /// Build the interceptor.
58    pub fn build(self) -> TwccReceiverInterceptor {
59        TwccReceiverInterceptor::new(self.interval)
60    }
61}
62
63/// Per-stream state for the receiver.
64struct RemoteStream {
65    /// Header extension ID for transport-wide CC.
66    hdr_ext_id: u8,
67}
68
69/// Interceptor that tracks incoming RTP packets and generates TWCC feedback.
70///
71/// This interceptor examines incoming RTP packets for transport-wide CC sequence
72/// numbers and periodically generates TransportLayerCC feedback packets.
73pub struct TwccReceiverInterceptor {
74    /// Configuration
75    interval: Duration,
76
77    /// Start time for calculating arrival times.
78    start_time: Option<Instant>,
79
80    /// TWCC recorder for building feedback.
81    recorder: Option<Recorder>,
82
83    /// Remote stream state per SSRC.
84    streams: HashMap<u32, RemoteStream>,
85
86    /// Queue for feedback packets.
87    write_queue: VecDeque<TaggedPacket>,
88
89    /// Next timeout for sending feedback.
90    next_timeout: Option<Instant>,
91    /// Inbound packets ready for the next interceptor.
92    read_queue: VecDeque<TaggedPacket>,
93}
94
95impl TwccReceiverInterceptor {
96    fn new(interval: Duration) -> Self {
97        Self {
98            read_queue: VecDeque::new(),
99            interval,
100            start_time: None,
101            recorder: None,
102            streams: HashMap::new(),
103            write_queue: VecDeque::new(),
104            next_timeout: None,
105        }
106    }
107
108    fn generate_feedback(&mut self, now: Instant) {
109        let Some(recorder) = self.recorder.as_mut() else {
110            return;
111        };
112
113        let packets = recorder.build_feedback_packet();
114        for pkt in packets {
115            self.write_queue.push_back(TaggedPacket {
116                now,
117                transport: TransportContext::default(),
118                message: AttributedPacket::new(Packet::Rtcp(vec![pkt])),
119            });
120        }
121    }
122}
123
124impl Protocol<TaggedPacket, TaggedPacket, ()> for TwccReceiverInterceptor {
125    type Rout = TaggedPacket;
126    type Wout = TaggedPacket;
127    type Eout = ();
128    type Error = Error;
129    type Time = Instant;
130
131    fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
132        // Process incoming RTP packets with TWCC extension
133        if let Packet::Rtp(ref rtp_packet) = msg.message.packet
134            && let Some(stream) = self.streams.get(&rtp_packet.header.ssrc)
135        {
136            // Initialize recorder on first packet
137            if self.recorder.is_none() {
138                // Use a random sender SSRC for feedback
139                self.recorder = Some(Recorder::new(rand::random()));
140                self.start_time = Some(msg.now);
141                self.next_timeout = Some(msg.now + self.interval);
142            }
143
144            // Extract transport CC sequence number
145            if let Some(ext_data) = rtp_packet.header.get_extension(stream.hdr_ext_id)
146                && let Ok(tcc) =
147                    rtp::extension::transport_cc_extension::TransportCcExtension::unmarshal(
148                        &mut ext_data.as_ref(),
149                    )
150            {
151                // Calculate arrival time in microseconds since start
152                let arrival_time = self
153                    .start_time
154                    .map(|start| msg.now.duration_since(start).as_micros() as i64)
155                    .unwrap_or(0);
156
157                if let Some(recorder) = self.recorder.as_mut() {
158                    recorder.record(rtp_packet.header.ssrc, tcc.transport_sequence, arrival_time);
159                }
160            }
161        }
162
163        self.read_queue.push_back(msg);
164
165        Ok(())
166    }
167
168    fn poll_read(&mut self) -> Option<Self::Rout> {
169        self.read_queue.pop_front()
170    }
171
172    fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
173        self.write_queue.push_back(msg);
174        Ok(())
175    }
176
177    fn poll_write(&mut self) -> Option<TaggedPacket> {
178        // First drain feedback packets
179        if let Some(pkt) = self.write_queue.pop_front() {
180            return Some(pkt);
181        }
182        None
183    }
184
185    fn handle_timeout(&mut self, now: Instant) -> Result<(), Error> {
186        // Check if we need to send feedback
187        if let Some(timeout) = self.next_timeout
188            && now >= timeout
189        {
190            self.generate_feedback(now);
191            self.next_timeout = Some(now + self.interval);
192        }
193        Ok(())
194    }
195
196    fn poll_timeout(&mut self) -> Option<Instant> {
197        self.next_timeout
198    }
199}
200
201impl Interceptor for TwccReceiverInterceptor {
202    fn bind_remote_stream(&mut self, info: &StreamInfo) {
203        if let Some(hdr_ext_id) = stream_supports_twcc(info) {
204            // Don't track if ID is 0 (invalid)
205            if hdr_ext_id != 0 {
206                self.streams.insert(info.ssrc, RemoteStream { hdr_ext_id });
207            }
208        }
209    }
210
211    fn unbind_remote_stream(&mut self, info: &StreamInfo) {
212        self.streams.remove(&info.ssrc);
213    }
214
215    fn bind_local_stream(&mut self, _info: &StreamInfo) {}
216
217    fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
218}