Skip to main content

Crate rtc_interceptor

Crate rtc_interceptor 

Source
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

InterceptorDescription
SenderReportInterceptorGenerates RTCP Sender Reports (SR) for local streams and filters hop-by-hop RTCP feedback
ReceiverReportInterceptorGenerates RTCP Receiver Reports (RR) based on incoming RTP statistics

§NACK (Negative Acknowledgement)

InterceptorDescription
NackGeneratorInterceptorDetects missing RTP packets and generates NACK requests (RFC 4585)
NackResponderInterceptorBuffers sent packets and retransmits on NACK, with optional RTX support (RFC 4588)

§TWCC (Transport Wide Congestion Control)

InterceptorDescription
TwccSenderInterceptorAdds transport-wide sequence numbers to outgoing RTP packets
TwccReceiverInterceptorTracks incoming packets and generates TransportLayerCC feedback

§Congestion control

InterceptorDescription
PacerInterceptorReleases outgoing packets at a target rate rather than in bursts
Rfc8888InterceptorReports per-packet arrival times back to the sender (RFC 8888)

§Utility

InterceptorDescription
NoopInterceptorEnds 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  → … → first

Each 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.
AdaptiveThreshold
A threshold that follows the trend it is judging.
ArrivalGroup
A run of packets that departed together, and when they turned up.
ArrivalGroupAccumulator
Collects acknowledgements into bursts and emits the gradient between consecutive bursts.
AttributedPacket
A packet together with what the interceptors have learned about it.
BitArray
A 128-bit mask, indexed from the most significant bit.
CcFeedbackRecorder
Records packet arrivals per stream and builds feedback reports from them.
CongestionControlBuilder
Builder for CongestionControlInterceptor.
CongestionControlInterceptor
Records every departing packet, resolves the remote’s feedback against it, and drives a BandwidthEstimator.
ConstantBitrate
An estimator that always says the same number.
DelayTrend
One filtered delay-gradient reading.
EstimatorStats
What an estimator is willing to say about itself.
FlexFec03Decoder
Recovers media packets lost from a stream protected by FlexFEC draft-03.
FlexFec03Encoder
Builds FlexFEC draft-03 repair packets.
FlexFec03ReceiveBuilder
Builder for FlexFec03ReceiveInterceptor.
FlexFec03ReceiveInterceptor
Recovers media lost from streams protected by FlexFEC draft-03.
FlexFec03SendBuilder
Builder for FlexFec03SendInterceptor.
FlexFec03SendInterceptor
Produces FlexFEC draft-03 repair packets for outgoing media.
Gcc
Google Congestion Control.
History
Records outgoing packets and matches incoming feedback against them.
InterGroupDelay
The delay gradient between two consecutive groups.
IntervalPliInterceptor
Requests a keyframe from every bound remote stream on a fixed interval.
JitterBuffer
A single stream’s packets, ordered by extended sequence number.
JitterBufferBuilder
Builder for JitterBufferInterceptor.
JitterBufferInterceptor
Holds each stream’s packets for a fixed span of time, then releases them in order.
JitterBufferStats
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.
LossController
Loss-based congestion control, per draft-ietf-rmcat-gcc-02 §5.5.
NackGeneratorBuilder
Builder for the NackGeneratorInterceptor.
NackGeneratorInterceptor
Interceptor that generates NACK requests for missing RTP packets.
NackResponderBuilder
Builder for the NackResponderInterceptor.
NackResponderInterceptor
Interceptor that responds to NACK requests by retransmitting packets.
NoopInterceptor
Decides what becomes of inbound RTCP once the interceptors have had it, and passes everything else through.
OveruseDetector
Turns a filtered delay trend into a Usage, with hysteresis.
Pacer
A token bucket in bits, refilled from elapsed time.
PacerBuilder
Builder for PacerInterceptor.
PacerInterceptor
Releases queued packets at a target rate rather than as fast as they arrive.
PacketReport
One outgoing packet, joined with whatever the receiver later said about it.
ProtectionCoverage
The assignment of media packets to repair packets.
RTCPFeedback
RTCP feedback mechanism negotiated for the stream.
RTPHeaderExtension
RTP header extension as negotiated via SDP (RFC 5285).
RateCalculator
The rate the far end is receiving, from acknowledged bytes over a sliding window.
RateController
The AIMD controller: a usage signal and a received rate in, a target bitrate out.
ReceiverReportBuilder
Builder for the ReceiverReportInterceptor.
ReceiverReportInterceptor
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.
Rfc8888Builder
Builder for Rfc8888Interceptor.
Rfc8888Interceptor
Reports when each packet of each bound remote stream arrived (RFC 8888).
SenderReportBuilder
Builder for the SenderReportInterceptor.
SenderReportInterceptor
Interceptor that filters hop-by-hop RTCP reports.
SlopeEstimator
Grouping plus filtering: acknowledgements in, a delay trend out.
StreamInfo
Stream context passed to interceptor bind/unbind callbacks.
TwccReceiverBuilder
Builder for the TwccReceiverInterceptor.
TwccReceiverInterceptor
Interceptor that tracks incoming RTP packets and generates TWCC feedback.
TwccSenderBuilder
Builder for the TwccSenderInterceptor.
TwccSenderInterceptor
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.
FlexFecParseError
Why a repair packet could not be used.
JitterBufferState
Whether the buffer is still filling or is handing packets out.
Packet
RTP/RTCP Packet
RateControlState
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§

BandwidthEstimator
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§

BoxedInterceptor
An interceptor whose concrete type has been erased.
TaggedPacket
Tagged packet with transport metadata.