Expand description
RTC Interceptor - Sans-IO interceptor framework for RTP/RTCP processing.
This crate provides a composable interceptor framework built on top of the
sansio::Protocol trait. Interceptors can process, modify, or generate
RTP/RTCP packets as they flow through the pipeline.
§Available Interceptors
§RTCP Reports
| Interceptor | Description |
|---|---|
SenderReportInterceptor | Generates RTCP Sender Reports (SR) for local streams and filters hop-by-hop RTCP feedback |
ReceiverReportInterceptor | Generates RTCP Receiver Reports (RR) based on incoming RTP statistics |
§NACK (Negative Acknowledgement)
| Interceptor | Description |
|---|---|
NackGeneratorInterceptor | Detects missing RTP packets and generates NACK requests (RFC 4585) |
NackResponderInterceptor | Buffers sent packets and retransmits on NACK, with optional RTX support (RFC 4588) |
§TWCC (Transport Wide Congestion Control)
| Interceptor | Description |
|---|---|
TwccSenderInterceptor | Adds transport-wide sequence numbers to outgoing RTP packets |
TwccReceiverInterceptor | Tracks incoming packets and generates TransportLayerCC feedback |
§Congestion control
| Interceptor | Description |
|---|---|
PacerInterceptor | Releases outgoing packets at a target rate rather than in bursts |
Rfc8888Interceptor | Reports per-packet arrival times back to the sender (RFC 8888) |
§Utility
| Interceptor | Description |
|---|---|
NoopInterceptor | Ends the inbound RTCP path; the last interceptor in a chain |
§Design
A chain is a flat list of interceptors driven over a shared belt. Each one can:
- transform a packet passing through, or swallow it to drop or delay it
- emit packets it generated or was holding, which rejoin the belt and carry on
- act on timeouts, for periodic work like report generation
- track stream statistics and state
All interceptors work with TaggedPacket — an RTP or RTCP packet with transport metadata,
carrying Attributes that say what happened to it on the way. No interceptor holds a
reference to another; Registry assembles the list and walks it. Registry::build appends
NoopInterceptor last, so inbound RTCP stops before the application — control traffic the
interceptors act on is not media the caller asked for. An interceptor that wants a particular
packet delivered anyway attaches Attribute::DeliverToApplication to it.
§Direction
A chain is a flat list ordered by distance from the wire: the first interceptor is closest to the network, the last closest to the application. Direction is a property of the walk, not of the structure:
read (network → application) forward: first → … → last
write (application → network) reverse: last → … → firstEach interceptor is fed from a shared belt and its output is collected back onto it, so what a interceptor emits is seen by every interceptor still ahead of it in the walk. A retransmission emitted mid-chain still gets paced, numbered and recorded, because there is no way out of the chain except through the interceptors that follow.
One list serves both directions, so “closest to the wire” means one thing rather than opposite things per direction — which is why the send history and the FEC decoder sit next to each other, one being the last thing on the way out and the other the first on the way in.
§Quick Start
use rtc_interceptor::{
NackGeneratorBuilder, NackResponderBuilder, ReceiverReportBuilder,
Registry, SenderReportBuilder, Slot, TwccReceiverBuilder, TwccSenderBuilder,
};
use std::time::Duration;
// The slot decides the position, not the order of these calls; they are listed
// wire-to-application here only because that reads the way the chain runs — forwards on the
// read path, and in reverse on the write path.
let chain = Registry::new()
.with(Slot::TwccSender, TwccSenderBuilder::new().build())
.with(Slot::NackResponder, NackResponderBuilder::new().build())
.with(Slot::NackGenerator, NackGeneratorBuilder::new().build())
.with(Slot::TwccReceiver, TwccReceiverBuilder::new().build())
.with(Slot::ReceiverReport, ReceiverReportBuilder::new().build())
.with(Slot::SenderReport, SenderReportBuilder::new().with_interval(Duration::from_secs(1)).build())
.build();
// `build` appends [`NoopInterceptor`] last, so inbound RTCP — control traffic the interceptors
// above act on — stops there rather than arriving mixed in with the application's media. To
// receive some of it, add an interceptor that marks those packets `DeliverToApplication`.§One chain type
Registry::build returns a single concrete type whatever it was built from, so a struct can
hold one without a type parameter and two connections with different chains share a collection:
use rtc_interceptor::{Slot, NackGeneratorBuilder, Registry, SenderReportBuilder};
let chain = if nack_enabled {
Registry::new().with(Slot::NackGenerator, NackGeneratorBuilder::new().build()).build()
} else {
Registry::new().with(Slot::SenderReport, SenderReportBuilder::new().build()).build()
};The cost is one virtual call per interceptor per packet, which is nothing beside SRTP.
§Stream Binding
Before interceptors can process packets for a stream, the stream must be bound:
use rtc_interceptor::{Slot, Interceptor, RTCPFeedback, RTPHeaderExtension, Registry, StreamInfo};
let mut chain = Registry::new().build();
// Create stream info with NACK and TWCC support
let stream_info = StreamInfo {
ssrc: 0x12345678,
clock_rate: 90000,
mime_type: "video/VP8".to_string(),
payload_type: 96,
rtcp_feedback: vec![RTCPFeedback {
typ: "nack".to_string(),
parameter: String::new(),
}],
rtp_header_extensions: vec![RTPHeaderExtension {
uri: "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01".to_string(),
id: 5,
}],
..Default::default()
};
// Bind for outgoing streams (sender side)
chain.bind_local_stream(&stream_info);
// Bind for incoming streams (receiver side)
chain.bind_remote_stream(&stream_info);§Writing your own
Implement sansio::Protocol and Interceptor, then add it wherever it belongs in the
list. What handle_* takes in, poll_* gives back — so even a pass-through needs a queue,
because the queue is what the next interceptor is fed from:
use rtc_interceptor::{Slot, Interceptor, Registry, StreamInfo, TaggedPacket};
use sansio::Protocol;
use std::collections::VecDeque;
use std::time::Instant;
/// Counts packets on their way out.
#[derive(Default)]
struct Counter {
sent: u64,
read_queue: VecDeque<TaggedPacket>,
write_queue: VecDeque<TaggedPacket>,
}
impl Protocol<TaggedPacket, TaggedPacket, ()> for Counter {
type Rout = TaggedPacket;
type Wout = TaggedPacket;
type Eout = ();
type Error = shared::error::Error;
type Time = Instant;
fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
self.read_queue.push_back(msg);
Ok(())
}
fn poll_read(&mut self) -> Option<Self::Rout> {
self.read_queue.pop_front()
}
fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
self.sent += 1;
self.write_queue.push_back(msg); // queueing nothing would swallow it
Ok(())
}
fn poll_write(&mut self) -> Option<Self::Wout> {
self.write_queue.pop_front()
}
}
impl Interceptor for Counter {
fn bind_local_stream(&mut self, _info: &StreamInfo) {}
fn unbind_local_stream(&mut self, _info: &StreamInfo) {}
fn bind_remote_stream(&mut self, _info: &StreamInfo) {}
fn unbind_remote_stream(&mut self, _info: &StreamInfo) {}
}
let chain = Registry::new().with(Slot::NackGenerator, Counter::default()).build();Queue nothing to drop or delay a packet, and queue delayed or generated ones whenever they are
ready — from handle_timeout, say. They leave through
poll_* and continue through every interceptor ahead.
Structs§
- Acknowledgement
- One packet’s fate, as reported by the receiver.
- Adaptive
Threshold - A threshold that follows the trend it is judging.
- Arrival
Group - A run of packets that departed together, and when they turned up.
- Arrival
Group Accumulator - Collects acknowledgements into bursts and emits the gradient between consecutive bursts.
- Attributed
Packet - A packet together with what the interceptors have learned about it.
- BitArray
- A 128-bit mask, indexed from the most significant bit.
- CcFeedback
Recorder - Records packet arrivals per stream and builds feedback reports from them.
- Congestion
Control Builder - Builder for
CongestionControlInterceptor. - Congestion
Control Interceptor - Records every departing packet, resolves the remote’s feedback against it, and drives a
BandwidthEstimator. - Constant
Bitrate - An estimator that always says the same number.
- Delay
Trend - One filtered delay-gradient reading.
- Estimator
Stats - What an estimator is willing to say about itself.
- Flex
Fec03 Decoder - Recovers media packets lost from a stream protected by FlexFEC draft-03.
- Flex
Fec03 Encoder - Builds FlexFEC draft-03 repair packets.
- Flex
Fec03 Receive Builder - Builder for
FlexFec03ReceiveInterceptor. - Flex
Fec03 Receive Interceptor - Recovers media lost from streams protected by FlexFEC draft-03.
- Flex
Fec03 Send Builder - Builder for
FlexFec03SendInterceptor. - Flex
Fec03 Send Interceptor - Produces FlexFEC draft-03 repair packets for outgoing media.
- Gcc
- Google Congestion Control.
- History
- Records outgoing packets and matches incoming feedback against them.
- Inter
Group Delay - The delay gradient between two consecutive groups.
- Interval
PliInterceptor - Requests a keyframe from every bound remote stream on a fixed interval.
- Jitter
Buffer - A single stream’s packets, ordered by extended sequence number.
- Jitter
Buffer Builder - Builder for
JitterBufferInterceptor. - Jitter
Buffer Interceptor - Holds each stream’s packets for a fixed span of time, then releases them in order.
- Jitter
Buffer Stats - Counters describing what a buffer has had to cope with.
- Kalman
- Kalman filter over the one-way delay gradient, per draft-ietf-rmcat-gcc-02 §5.3.
- Loss
Controller - Loss-based congestion control, per draft-ietf-rmcat-gcc-02 §5.5.
- Nack
Generator Builder - Builder for the NackGeneratorInterceptor.
- Nack
Generator Interceptor - Interceptor that generates NACK requests for missing RTP packets.
- Nack
Responder Builder - Builder for the NackResponderInterceptor.
- Nack
Responder Interceptor - Interceptor that responds to NACK requests by retransmitting packets.
- Noop
Interceptor - Decides what becomes of inbound RTCP once the interceptors have had it, and passes everything else through.
- Overuse
Detector - Turns a filtered delay trend into a
Usage, with hysteresis. - Pacer
- A token bucket in bits, refilled from elapsed time.
- Pacer
Builder - Builder for
PacerInterceptor. - Pacer
Interceptor - Releases queued packets at a target rate rather than as fast as they arrive.
- Packet
Report - One outgoing packet, joined with whatever the receiver later said about it.
- Protection
Coverage - The assignment of media packets to repair packets.
- RTCP
Feedback - RTCP feedback mechanism negotiated for the stream.
- RTPHeader
Extension - RTP header extension as negotiated via SDP (RFC 5285).
- Rate
Calculator - The rate the far end is receiving, from acknowledged bytes over a sliding window.
- Rate
Controller - The AIMD controller: a usage signal and a received rate in, a target bitrate out.
- Receiver
Report Builder - Builder for the ReceiverReportInterceptor.
- Receiver
Report Interceptor - Interceptor that generates RTCP Receiver Reports.
- Registry
- Collects interceptors and assembles them into a chain.
- Report
- A batch of packet reports, with the round trip time they imply.
- Rfc8888
Builder - Builder for
Rfc8888Interceptor. - Rfc8888
Interceptor - Reports when each packet of each bound remote stream arrived (RFC 8888).
- Sender
Report Builder - Builder for the SenderReportInterceptor.
- Sender
Report Interceptor - Interceptor that filters hop-by-hop RTCP reports.
- Slope
Estimator - Grouping plus filtering: acknowledgements in, a delay trend out.
- Stream
Info - Stream context passed to interceptor bind/unbind callbacks.
- Twcc
Receiver Builder - Builder for the TwccReceiverInterceptor.
- Twcc
Receiver Interceptor - Interceptor that tracks incoming RTP packets and generates TWCC feedback.
- Twcc
Sender Builder - Builder for the
TwccSenderInterceptor. - Twcc
Sender Interceptor - Numbers every departing RTP packet so the remote can report on it
(
draft-holmer-rmcat-transport-wide-cc-extensions-01).
Enums§
- Attribute
- A fact about a packet, attached by one interceptor and readable by the rest.
- Flex
FecParse Error - Why a repair packet could not be used.
- Jitter
Buffer State - Whether the buffer is still filling or is handing packets out.
- Packet
- RTP/RTCP Packet
- Rate
Control State - What the rate controller should be doing.
- Rejected
- Why a packet was not stored.
- Slot
- Where an interceptor belongs in the chain, measured by distance from the wire.
- Usage
- What the delay signal currently says about the path.
Constants§
- CONGESTION_
CONTROL_ DEFAULT_ PRUNE_ HORIZON - How long an unacknowledged packet is kept before it is written off.
- DEFAULT_
NUM_ FEC_ PACKETS - Repair packets produced per block.
- DEFAULT_
NUM_ MEDIA_ PACKETS - Media packets gathered before a repair block is produced.
- GCC_
DECREASE_ FACTOR - Multiplier applied to the received rate when backing off.
- GCC_
DEFAULT_ BURST_ INTERVAL - Packets sent within this of each other are one burst.
- GCC_
DEFAULT_ OVERUSE_ TIME - How long the trend must stay outside the threshold before overuse is declared.
- GCC_
HIGH_ LOSS - Above this the path is considered congested and the rate must fall.
- GCC_
INCREASE_ FACTOR - Multiplicative growth per second while climbing well below the last known ceiling.
- GCC_
INITIAL_ BITRATE - Rate to start from when nothing is known yet.
- GCC_
LOSS_ INTERVAL - How long between changes, so each is observed before the next.
- GCC_
LOW_ LOSS - Below this loss fraction the path is considered healthy and the rate may climb.
- GCC_
MAX_ BITRATE - Ceiling.
- GCC_
MIN_ BITRATE - Floor. Below this a video call is not worth having.
- GCC_
RATE_ CONTROL_ INTERVAL - How long to wait between changes, so each one is observed before the next.
- GCC_
RATE_ WINDOW - How far back the received rate is measured over.
- GCC_
THRESHOLD_ INITIAL_ MS - Starting threshold, in milliseconds — draft-ietf-rmcat-gcc-02 §5.4.
- INTERVAL_
PLI_ DEFAULT_ INTERVAL - How often a keyframe is requested when no interval is configured.
- JITTER_
BUFFER_ DEFAULT_ CAPACITY - Default cap on packets held per stream, so a stalled or hostile stream cannot grow without bound while its deadline is still in the future.
- JITTER_
BUFFER_ DEFAULT_ DEPTH - Default playout depth: enough to absorb ordinary network jitter without adding audible delay.
- MAX_
FEC_ PACKETS - The most repair packets one block can produce.
- MAX_
MEDIA_ PACKETS - The most media packets one FEC block can protect.
- PACER_
DEFAULT_ BITRATE - Rate used when none is configured: 1 Mb/s.
- PACER_
DEFAULT_ QUEUE_ LIMIT - Packets held before new ones are refused.
- PACER_
MIN_ BURST_ BITS - Smallest burst any pacer allows, in bits.
- RFC8888_
DEFAULT_ INTERVAL - How often feedback is sent when no interval is configured.
- RFC8888_
DEFAULT_ MAX_ REPORT_ SIZE - Byte budget for one report, chosen to sit inside a conservative path MTU.
Traits§
- Bandwidth
Estimator - A congestion control algorithm: acknowledged packets in, a target bitrate out.
- Interceptor
- One interceptor of packet processing.
Functions§
- convert_
ccfb - Convert an RFC 8888 report into acknowledgements per media stream, with the delay the receiver added before sending it.
- convert_
twcc - Convert a TWCC feedback packet into one acknowledgement per reported packet.
Type Aliases§
- Boxed
Interceptor - An interceptor whose concrete type has been erased.
- Tagged
Packet - Tagged packet with transport metadata.