Skip to main content

Crate rtp_packet

Crate rtp_packet 

Source
Expand description

RTP fixed header + CSRC list + generic header extension — RFC 3550 §5.1 / §5.3.1, spec-complete (not just a happy-path subset).

This crate implements exactly the wire structures described in the curated spec transcription at rtp-packet/docs/rtp-header.md (fetched directly from RFC 3550) — cite that file, not this doc comment, as the field-semantics oracle.

  • RtpPacket — the §5.1 fixed header (version/padding/extension bit/ CSRC-count/marker/payload-type/sequence-number/timestamp/SSRC), the CSRC identifier list (0–15 entries), the optional §5.3.1 header extension, an optional trailing padding region, and the payload.
  • HeaderExtension — the §5.3.1 generic header extension: a 16-bit profile-specific identifier + opaque profile-specific data.

version/P/X/CC are never stored as independent fields that could disagree with the typed data: version is fixed at 2 by the spec (checked on parse, always written on serialize), and P/X/CC are derived from padding.is_some() / extension.is_some() / csrc.len() respectively — see RtpPacket’s doc for the reasoning.

Depends only on broadcast-common. #![no_std] (+ alloc) when the std feature is disabled.

The optional rfc8285 feature adds rfc8285, a decoder for RFC 8285’s one-byte/two-byte multiplexed extension elements that a profile may pack into HeaderExtension::data — see rtp-packet/docs/rfc8285_header_ext.md for the curated transcription. It is additive and off by default: most RTP consumers only need the RFC 3550 fixed header.

§Examples

Build a simple packet (no padding/CSRC/extension) and round-trip it:

use broadcast_common::{Parse, Serialize};
use rtp_packet::RtpPacket;

let pkt = RtpPacket {
    marker: true,
    payload_type: 96,
    sequence_number: 1,
    timestamp: 3600,
    ssrc: 0x1234_5678,
    csrc: vec![],
    extension: None,
    padding: None,
    payload: &[0xDE, 0xAD, 0xBE, 0xEF],
};
let mut bytes = vec![0u8; pkt.serialized_len()];
pkt.serialize_into(&mut bytes).unwrap();
assert_eq!(RtpPacket::parse(&bytes).unwrap(), pkt);

§Runnable examples

Run with cargo run -p rtp-packet --example <name>.

§build_packet

//! Build an RTP packet from typed fields (fixed header + CSRC list + header
//! extension) and serialize it to wire bytes — RFC 3550 §5.1 / §5.3.1.
//!
//! Run with `cargo run -p rtp-packet --example build_packet`.

use broadcast_common::Serialize;
use rtp_packet::{HeaderExtension, RtpPacket};

fn main() {
    let extension_data = [0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02];
    let pkt = RtpPacket {
        marker: true,
        payload_type: 96, // dynamic payload type, e.g. H.264 (RFC 6184)
        sequence_number: 42,
        timestamp: 90_000,
        ssrc: 0xCAFEBABE,
        csrc: vec![0x1111_1111, 0x2222_2222],
        extension: Some(HeaderExtension {
            profile_id: 0xBEDE, // example one-byte header extension profile id
            data: &extension_data,
        }),
        padding: None,
        payload: b"example RTP payload bytes",
    };

    let mut bytes = vec![0u8; pkt.serialized_len()];
    pkt.serialize_into(&mut bytes).expect("serialize");

    println!("serialized {} bytes:", bytes.len());
    println!(
        "{}",
        bytes
            .iter()
            .map(|b| format!("{b:02x}"))
            .collect::<Vec<_>>()
            .join(" ")
    );
    println!(
        "V={} P={} X={} CC={}",
        bytes[0] >> 6,
        (bytes[0] >> 5) & 1,
        (bytes[0] >> 4) & 1,
        bytes[0] & 0x0F
    );
}

§parse_packet

//! Parse the committed real-capture fixture (`tests/fixtures/rtp_simple.bin`)
//! and print its decoded RTP fields, then round-trip it back to bytes and
//! confirm the output is byte-identical — RFC 3550 §5.1.
//!
//! Run with `cargo run -p rtp-packet --example parse_packet`.

use broadcast_common::{Parse, Serialize};
use rtp_packet::RtpPacket;

fn main() {
    let path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/rtp_simple.bin");
    let bytes = std::fs::read(path).expect("fixture must exist");

    let pkt = RtpPacket::parse(&bytes).expect("parse RTP fixed header");
    println!("marker:          {}", pkt.marker);
    println!("payload_type:    {}", pkt.payload_type);
    println!("sequence_number: {}", pkt.sequence_number);
    println!("timestamp:       {}", pkt.timestamp);
    println!("ssrc:            {:#010x}", pkt.ssrc);
    println!("csrc count:      {}", pkt.csrc_count());
    println!("extension:       {}", pkt.extension.is_some());
    println!("padding:         {}", pkt.padding.is_some());
    println!("payload len:     {}", pkt.payload.len());

    let mut out = vec![0u8; pkt.serialized_len()];
    pkt.serialize_into(&mut out).expect("serialize");
    assert_eq!(out, bytes, "byte-identical round trip");
    println!("round trip byte-identical: OK ({} bytes)", out.len());
}

§rfc8285_extensions (requires --features rfc8285)

//! Build an RTP packet carrying RFC 8285 one-byte-form multiplexed header-
//! extension elements, serialize it, then parse it back and decode the
//! extension elements — RFC 8285 §4.1.2 / §4.2.
//!
//! Run with `cargo run -p rtp-packet --example rfc8285_extensions --features rfc8285`.

use broadcast_common::{Parse, Serialize};
use rtp_packet::rfc8285::{
    ExtensionElements, OneByteElement, OneByteElements, OneByteId, parse_extensions,
};
use rtp_packet::{HeaderExtension, RtpPacket};

fn main() {
    // Two named extension elements: a 1-byte "audio level" (id=1) and a
    // 3-byte "MID" identifier (id=2) — the kind of thing WebRTC streams
    // multiplex via RFC 8285 in practice.
    let elements = OneByteElements(vec![
        OneByteElement {
            id: OneByteId::new(1).expect("1 is in the valid 1..=14 range"),
            data: &[0x2A], // audio level, one byte
        },
        OneByteElement {
            id: OneByteId::new(2).expect("2 is in the valid 1..=14 range"),
            data: b"mid", // 3-byte MID value
        },
    ]);

    // Serialize the elements to the padded byte sequence that becomes the
    // RFC 3550 §5.3.1 HeaderExtension's `data`.
    let mut ext_data = vec![0u8; elements.serialized_len()];
    elements
        .serialize_into(&mut ext_data)
        .expect("serialize extension elements");
    println!(
        "encoded {} extension-data bytes (word-aligned): {}",
        ext_data.len(),
        ext_data
            .iter()
            .map(|b| format!("{b:02x}"))
            .collect::<Vec<_>>()
            .join(" ")
    );

    let pkt = RtpPacket {
        marker: true,
        payload_type: 96,
        sequence_number: 1,
        timestamp: 3600,
        ssrc: 0x1234_5678,
        csrc: vec![],
        extension: Some(HeaderExtension {
            profile_id: rtp_packet::rfc8285::ONE_BYTE_PROFILE_ID,
            data: &ext_data,
        }),
        padding: None,
        payload: b"example payload",
    };

    let mut wire = vec![0u8; pkt.serialized_len()];
    pkt.serialize_into(&mut wire).expect("serialize RTP packet");
    println!("serialized RTP packet: {} bytes", wire.len());

    // Parse it back and decode the RFC 8285 elements from the extension.
    let reparsed = RtpPacket::parse(&wire).expect("parse RTP packet");
    let ext = reparsed.extension.expect("X=1");
    let decoded = parse_extensions(&ext).expect("recognized RFC 8285 profile_id");
    let ExtensionElements::OneByte(one_byte) = decoded else {
        panic!("expected the one-byte form (profile_id 0xBEDE)");
    };

    println!("decoded {} extension elements:", one_byte.elements().len());
    for e in one_byte.elements() {
        println!("  id={:>2} data={:02x?}", e.id.get(), e.data);
    }

    // Re-serializing the decoded elements reproduces the exact original
    // extension data byte-for-byte, including the zero-padding tail.
    let mut re_ext_data = vec![0u8; one_byte.serialized_len()];
    one_byte
        .serialize_into(&mut re_ext_data)
        .expect("serialize decoded elements");
    assert_eq!(re_ext_data, ext_data);
    println!(
        "round trip byte-identical: OK ({} bytes)",
        re_ext_data.len()
    );
}

Modules§

rfc8285rfc8285
RFC 8285 one-byte/two-byte RTP header-extension element multiplexing.

Structs§

HeaderExtension
The RTP generic header extension (§5.3.1): a 16-bit profile-specific identifier + opaque profile-specific data.
RtpPacket
A parsed (or to-be-serialized) RTP packet: the §5.1 fixed header, the CSRC list, the optional §5.3.1 header extension, an optional padding region, and the payload.

Enums§

Error
An RTP header parse / serialize error.

Constants§

FIXED_HEADER_LEN
Length of the fixed header (12 bytes: byte0 + byte1 + seq(2) + ts(4) + ssrc(4)) before any CSRC identifiers, per the §5.1 bit diagram.
MAX_CSRC_COUNT
Maximum CSRC count — the CC field is 4 bits (§5.1).
MAX_PADDING_COUNT
Maximum padding-octet count — the trailing count byte is 8 bits (§5.1: “the last octet of the padding contains a count of how many padding octets should be ignored, including itself”).
MAX_PAYLOAD_TYPE
Maximum payload type value — PT is a 7-bit field (§5.1).
RTP_VERSION
RTP version — “the version defined by this specification is two (2)” (docs/rtp-header.md §5.1, version (V), 2 bits).

Type Aliases§

Result
Result alias for rtp-packet parsing/serialization.