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

§Utility

InterceptorDescription
NoopInterceptorPass-through terminal for interceptor chains

§Design

Each interceptor wraps an inner Interceptor and can:

  • Process incoming/outgoing RTP/RTCP packets
  • Modify packet contents (headers, payloads)
  • Generate new packets (e.g., RTCP Sender/Receiver Reports)
  • Handle timeouts for periodic tasks (e.g., report generation)
  • Track stream statistics and state

All interceptors work with TaggedPacket (RTP or RTCP packets with transport metadata). The innermost interceptor is typically NoopInterceptor, which serves as the terminal.

§No Direction Concept

Important: Unlike PeerConnection’s pipeline where read and write have opposite processing direction orders, interceptors have no direction concept.

In PeerConnection’s pipeline:

Read:  Network → HandlerA → HandlerB → HandlerC → Application
Write: Application → HandlerC → HandlerB → HandlerA → Network
       (reversed order)

In Interceptor chains, all operations flow in the same direction:

handle_read:    Outer → Inner (A.handle_read calls B.handle_read calls C.handle_read)
handle_write:   Outer → Inner (A.handle_write calls B.handle_write calls C.handle_write)
handle_event:   Outer → Inner (A.handle_event calls B.handle_event calls C.handle_event)
handle_timeout: Outer → Inner (A.handle_timeout calls B.handle_timeout calls C.handle_timeout)

poll_read:    Outer → Inner (A.poll_read calls B.poll_read calls C.poll_read)
poll_write:   Outer → Inner (A.poll_write calls B.poll_write calls C.poll_write)
poll_event:   Outer → Inner (A.poll_event calls B.poll_event calls C.poll_event)
poll_timeout: Outer → Inner (A.poll_timeout calls B.poll_timeout calls C.poll_timeout)

This means interceptors are symmetric - they process read, write, and event in the same structural order. The distinction between “inbound” and “outbound” is semantic (based on message content), not structural (based on call order).

§Quick Start

use rtc_interceptor::{
    Registry, SenderReportBuilder, ReceiverReportBuilder,
    NackGeneratorBuilder, NackResponderBuilder,
    TwccSenderBuilder, TwccReceiverBuilder,
};
use std::time::Duration;

// Build a full-featured interceptor chain
let chain = Registry::new()
    // RTCP reports
    .with(SenderReportBuilder::new()
        .with_interval(Duration::from_secs(1))
        .build())
    .with(ReceiverReportBuilder::new()
        .with_interval(Duration::from_secs(1))
        .build())
    // NACK for packet loss recovery
    .with(NackGeneratorBuilder::new()
        .with_size(512)
        .with_interval(Duration::from_millis(100))
        .build())
    .with(NackResponderBuilder::new()
        .with_size(1024)
        .build())
    // TWCC for congestion control
    .with(TwccSenderBuilder::new().build())
    .with(TwccReceiverBuilder::new()
        .with_interval(Duration::from_millis(100))
        .build())
    .build();

§Type-Erasing a Chain

A chain’s type spells out its whole composition (TwccReceiverInterceptor<SenderReportInterceptor<…>>), and it propagates into every type that holds the peer connection built from it. That is fine when the chain is fixed at compile time, and a problem when it is chosen at runtime or has to live in your own structs.

Interceptor is object safe, so Registry::boxed can erase the chain to BoxedInterceptor — one concrete type, whatever it was built from:

use rtc_interceptor::{BoxedInterceptor, NackGeneratorBuilder, Registry, SenderReportBuilder};

// Two different chain types, unified by `.boxed()`.
let chain: BoxedInterceptor = if nack_enabled {
    Registry::new()
        .with(SenderReportBuilder::new().build())
        .with(NackGeneratorBuilder::new().build())
        .boxed()
        .build()
} else {
    Registry::new().with(SenderReportBuilder::new().build()).boxed().build()
};

The cost is one virtual call per chain entry point (handle_read, poll_write, handle_timeout, …); the layers inside still call each other through static dispatch and inline as before. Box<P> and &mut P both implement Interceptor, so a boxed or borrowed chain satisfies an I: Interceptor bound like any other.

§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);

§Creating Custom Interceptors

Use the derive macros to easily create custom interceptors:

use rtc_interceptor::{Interceptor, StreamInfo, TaggedPacket, interceptor};
use sansio::Protocol;
use shared::error::Error; // the generated `Protocol` impl names it
use std::collections::VecDeque;

#[derive(Interceptor)]
pub struct MyInterceptor<P: Interceptor> {
    #[next]
    next: P,  // The next interceptor in the chain (can use any field name)
    buffer: VecDeque<TaggedPacket>,
}

#[interceptor]
impl<P: Interceptor> MyInterceptor<P> {
    #[overrides]
    fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
        // Custom logic here
        self.next.handle_read(msg)
    }
}
  • #[derive(Interceptor)] - Marks a struct as an interceptor, requires #[next] field
  • #[interceptor] - Generates Protocol and Interceptor trait implementations
  • #[overrides] - Marks methods with custom implementations (non-marked methods delegate to next)

See the Interceptor trait documentation for more details.

Structs§

Acknowledgement
One packet’s fate, as reported by the receiver.
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.
IntervalPliBuilder
Builder for IntervalPliInterceptor.
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
A no-operation interceptor that simply queues messages for pass-through.
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
Registry for constructing interceptor chains.
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
Interceptor that adds transport-wide sequence numbers to outgoing RTP packets.

Enums§

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.
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
Trait for RTP/RTCP interceptors with fixed Protocol type parameters.

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
A type-erased interceptor chain.
TaggedPacket
Tagged packet with transport metadata.

Attribute Macros§

interceptor
Attribute macro for impl blocks to generate Protocol and Interceptor implementations.

Derive Macros§

Interceptor
Derive macro that marks a struct as an interceptor.