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.

§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, TwccReceiverBuilder, TwccSenderBuilder,
};
use std::time::Duration;

// Listed wire-to-application, which is the order they run in on the read path and the
// reverse of the order they run in on the write path.
let chain = Registry::new()
    .with(TwccSenderBuilder::new().build())
    .with(NackResponderBuilder::new().build())
    .with(NackGeneratorBuilder::new().build())
    .with(TwccReceiverBuilder::new().build())
    .with(ReceiverReportBuilder::new().build())
    .with(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.

§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::{NackGeneratorBuilder, Registry, SenderReportBuilder};

let chain = if nack_enabled {
    Registry::new().with(NackGeneratorBuilder::new().build()).build()
} else {
    Registry::new().with(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::{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::{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(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.
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.
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.
History
Records outgoing packets and matches incoming feedback against them.
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.
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.
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).
ReceiverReportBuilder
Builder for the ReceiverReportInterceptor.
ReceiverReportInterceptor
Interceptor that generates RTCP Receiver Reports.
Registry
Collects interceptors and assembles them into an [InterceptorChain].
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.
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
Rejected
Why a packet was not stored.

Constants§

DEFAULT_NUM_FEC_PACKETS
Repair packets produced per block.
DEFAULT_NUM_MEDIA_PACKETS
Media packets gathered before a repair block is produced.
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§

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.