const RTP_VERSION: u8 = 2;
pub const RTP_HEADER_LEN: usize = 12;
pub const PT_PCMU: u8 = 0;
pub const PT_PCMA: u8 = 8;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RtpPacket {
pub payload_type: u8,
pub seq: u16,
pub timestamp: u32,
pub ssrc: u32,
pub marker: bool,
pub payload: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RtpParseError {
TooShort,
BadVersion(u8),
Truncated,
}
impl std::fmt::Display for RtpParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RtpParseError::TooShort => write!(f, "RTP datagram shorter than 12-byte header"),
RtpParseError::BadVersion(v) => write!(f, "RTP version {v} != 2"),
RtpParseError::Truncated => write!(f, "RTP header claims more bytes than present"),
}
}
}
#[derive(Debug, Clone)]
pub struct RtpSender {
ssrc: u32,
payload_type: u8,
seq: u16,
timestamp: u32,
}
impl RtpSender {
pub fn new(payload_type: u8) -> Self {
Self::with_seeds(
payload_type,
rand::random::<u32>(),
rand::random::<u16>(),
rand::random::<u32>(),
)
}
pub fn with_seeds(payload_type: u8, ssrc: u32, seq: u16, timestamp: u32) -> Self {
Self {
ssrc,
payload_type,
seq,
timestamp,
}
}
pub fn ssrc(&self) -> u32 {
self.ssrc
}
pub fn packetize(&mut self, payload: &[u8]) -> Vec<u8> {
let mut pkt = Vec::with_capacity(RTP_HEADER_LEN + payload.len());
pkt.push(RTP_VERSION << 6);
pkt.push(self.payload_type & 0x7f);
pkt.extend_from_slice(&self.seq.to_be_bytes());
pkt.extend_from_slice(&self.timestamp.to_be_bytes());
pkt.extend_from_slice(&self.ssrc.to_be_bytes());
pkt.extend_from_slice(payload);
self.seq = self.seq.wrapping_add(1);
self.timestamp = self.timestamp.wrapping_add(payload.len() as u32);
pkt
}
}
pub fn depacketize(buf: &[u8]) -> Result<RtpPacket, RtpParseError> {
if buf.len() < RTP_HEADER_LEN {
return Err(RtpParseError::TooShort);
}
let b0 = buf[0];
let version = b0 >> 6;
if version != RTP_VERSION {
return Err(RtpParseError::BadVersion(version));
}
let has_extension = (b0 & 0x10) != 0;
let csrc_count = (b0 & 0x0f) as usize;
let b1 = buf[1];
let marker = (b1 & 0x80) != 0;
let payload_type = b1 & 0x7f;
let seq = u16::from_be_bytes([buf[2], buf[3]]);
let timestamp = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
let ssrc = u32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]);
let mut offset = RTP_HEADER_LEN + csrc_count * 4;
if buf.len() < offset {
return Err(RtpParseError::Truncated);
}
if has_extension {
if buf.len() < offset + 4 {
return Err(RtpParseError::Truncated);
}
let ext_words = u16::from_be_bytes([buf[offset + 2], buf[offset + 3]]) as usize;
offset += 4 + ext_words * 4;
if buf.len() < offset {
return Err(RtpParseError::Truncated);
}
}
Ok(RtpPacket {
payload_type,
seq,
timestamp,
ssrc,
marker,
payload: buf[offset..].to_vec(),
})
}
pub const JITTER_DEPTH: usize = 2;
#[derive(Debug)]
pub struct JitterBuffer {
buf: Vec<RtpPacket>,
depth: usize,
last_released: Option<u16>,
primed: bool,
}
impl JitterBuffer {
pub fn new() -> Self {
Self::with_depth(JITTER_DEPTH)
}
pub fn with_depth(depth: usize) -> Self {
Self {
buf: Vec::with_capacity(depth + 1),
depth: depth.max(1),
last_released: None,
primed: false,
}
}
pub fn reset(&mut self) {
self.buf.clear();
self.last_released = None;
self.primed = false;
}
fn seq_lt(seq: u16, reference: u16) -> bool {
seq != reference && seq.wrapping_sub(reference) > 0x8000
}
pub fn push(&mut self, pkt: RtpPacket) -> Vec<RtpPacket> {
if self.primed {
let last = self.last_released.unwrap();
if pkt.seq == last || Self::seq_lt(pkt.seq, last) {
return Vec::new();
}
}
if self.buf.iter().any(|p| p.seq == pkt.seq) {
return Vec::new();
}
let pos = self
.buf
.iter()
.position(|p| Self::seq_lt(pkt.seq, p.seq))
.unwrap_or(self.buf.len());
self.buf.insert(pos, pkt);
let mut out = Vec::new();
while self.buf.len() > self.depth {
let p = self.buf.remove(0);
self.last_released = Some(p.seq);
self.primed = true;
out.push(p);
}
out
}
pub fn drain(&mut self) -> Vec<RtpPacket> {
let out: Vec<RtpPacket> = self.buf.drain(..).collect();
if let Some(last) = out.last() {
self.last_released = Some(last.seq);
self.primed = true;
}
out
}
}
impl Default for JitterBuffer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pkt(seq: u16, payload: &[u8]) -> RtpPacket {
RtpPacket {
payload_type: PT_PCMU,
seq,
timestamp: seq as u32 * 160,
ssrc: 0xDEAD_BEEF,
marker: false,
payload: payload.to_vec(),
}
}
#[test]
fn packetize_writes_a_well_formed_12_byte_header() {
let mut s = RtpSender::with_seeds(PT_PCMU, 0x1122_3344, 0x0010, 0x0000_0100);
let payload: Vec<u8> = (0..160u16).map(|i| i as u8).collect();
let dgram = s.packetize(&payload);
assert_eq!(dgram.len(), RTP_HEADER_LEN + 160);
assert_eq!(dgram[0], 0x80);
assert_eq!(dgram[1], 0x00);
assert_eq!(u16::from_be_bytes([dgram[2], dgram[3]]), 0x0010);
assert_eq!(
u32::from_be_bytes([dgram[4], dgram[5], dgram[6], dgram[7]]),
0x0000_0100
);
assert_eq!(
u32::from_be_bytes([dgram[8], dgram[9], dgram[10], dgram[11]]),
0x1122_3344
);
assert_eq!(&dgram[RTP_HEADER_LEN..], &payload[..]);
}
#[test]
fn packetize_advances_seq_by_one_and_ts_by_sample_count() {
let mut s = RtpSender::with_seeds(PT_PCMA, 7, 100, 1000);
let a = depacketize(&s.packetize(&[0u8; 160])).unwrap();
let b = depacketize(&s.packetize(&[0u8; 160])).unwrap();
let c = depacketize(&s.packetize(&[0u8; 80])).unwrap(); assert_eq!((a.seq, a.timestamp), (100, 1000));
assert_eq!((b.seq, b.timestamp), (101, 1160)); assert_eq!((c.seq, c.timestamp), (102, 1320)); assert_eq!(a.payload_type, PT_PCMA);
assert_eq!(a.ssrc, 7);
}
#[test]
fn packetize_then_depacketize_preserves_all_fields_and_payload() {
let mut s = RtpSender::with_seeds(PT_PCMU, 0xCAFE_BABE, 65530, 4_000_000_000);
let payload: Vec<u8> = (0..160u32).map(|i| (i * 7) as u8).collect();
let dgram = s.packetize(&payload);
let p = depacketize(&dgram).unwrap();
assert_eq!(p.payload_type, PT_PCMU);
assert_eq!(p.seq, 65530);
assert_eq!(p.timestamp, 4_000_000_000);
assert_eq!(p.ssrc, 0xCAFE_BABE);
assert!(!p.marker);
assert_eq!(p.payload, payload);
}
#[test]
fn seq_and_timestamp_wrap_around() {
let mut s = RtpSender::with_seeds(PT_PCMU, 1, u16::MAX, u32::MAX - 100);
let a = depacketize(&s.packetize(&[0u8; 160])).unwrap();
let b = depacketize(&s.packetize(&[0u8; 160])).unwrap();
assert_eq!(a.seq, u16::MAX);
assert_eq!(b.seq, 0); assert_eq!(a.timestamp, u32::MAX - 100);
assert_eq!(b.timestamp, 59);
}
#[test]
fn depacketize_rejects_short_and_bad_version() {
assert_eq!(depacketize(&[0u8; 11]), Err(RtpParseError::TooShort));
let mut bad = vec![0x40u8];
bad.extend_from_slice(&[0u8; 11]);
assert_eq!(depacketize(&bad), Err(RtpParseError::BadVersion(1)));
}
#[test]
fn depacketize_skips_csrc_list() {
let mut dgram = vec![0x82u8, PT_PCMU];
dgram.extend_from_slice(&5u16.to_be_bytes()); dgram.extend_from_slice(&0u32.to_be_bytes()); dgram.extend_from_slice(&0u32.to_be_bytes()); dgram.extend_from_slice(&[0u8; 8]); dgram.extend_from_slice(&[0xAA, 0xBB]); let p = depacketize(&dgram).unwrap();
assert_eq!(p.seq, 5);
assert_eq!(p.payload, vec![0xAA, 0xBB]);
}
#[test]
fn depacketize_skips_header_extension() {
let mut dgram = vec![0x90u8, PT_PCMU];
dgram.extend_from_slice(&9u16.to_be_bytes()); dgram.extend_from_slice(&0u32.to_be_bytes()); dgram.extend_from_slice(&0u32.to_be_bytes()); dgram.extend_from_slice(&0xBEEFu16.to_be_bytes()); dgram.extend_from_slice(&1u16.to_be_bytes()); dgram.extend_from_slice(&[1, 2, 3, 4]); dgram.extend_from_slice(&[0x77]); let p = depacketize(&dgram).unwrap();
assert_eq!(p.seq, 9);
assert_eq!(p.payload, vec![0x77]);
}
fn run(jb: &mut JitterBuffer, seqs: &[u16]) -> Vec<u16> {
let mut out = Vec::new();
for &s in seqs {
for p in jb.push(pkt(s, &[s as u8])) {
out.push(p.seq);
}
}
out
}
#[test]
fn depacketize_exactly_12_bytes_is_empty_payload_no_panic() {
let mut d = vec![0x80u8, PT_PCMU];
d.extend_from_slice(&7u16.to_be_bytes());
d.extend_from_slice(&0u32.to_be_bytes());
d.extend_from_slice(&0u32.to_be_bytes());
let p = depacketize(&d).unwrap();
assert_eq!(p.seq, 7);
assert!(p.payload.is_empty());
}
#[test]
fn depacketize_rejects_truncated_csrc_list() {
let mut d = vec![0x82u8, PT_PCMU];
d.extend_from_slice(&1u16.to_be_bytes());
d.extend_from_slice(&0u32.to_be_bytes());
d.extend_from_slice(&0u32.to_be_bytes());
assert_eq!(depacketize(&d), Err(RtpParseError::Truncated));
}
#[test]
fn depacketize_rejects_truncated_header_extension() {
let mut d = vec![0x90u8, PT_PCMU];
d.extend_from_slice(&2u16.to_be_bytes());
d.extend_from_slice(&0u32.to_be_bytes());
d.extend_from_slice(&0u32.to_be_bytes());
d.extend_from_slice(&0xBEEFu16.to_be_bytes());
d.extend_from_slice(&4u16.to_be_bytes());
assert_eq!(depacketize(&d), Err(RtpParseError::Truncated));
}
#[test]
fn depacketize_parses_marker_bit_set() {
let mut d = vec![0x80u8, 0x80 | PT_PCMU];
d.extend_from_slice(&3u16.to_be_bytes());
d.extend_from_slice(&0u32.to_be_bytes());
d.extend_from_slice(&0u32.to_be_bytes());
d.push(0xAA);
let p = depacketize(&d).unwrap();
assert!(p.marker, "marker bit should parse as true");
assert_eq!(p.payload_type, PT_PCMU);
assert_eq!(p.payload, vec![0xAA]);
}
#[test]
fn depacketize_does_not_panic_on_arbitrary_short_buffers() {
for n in 0..RTP_HEADER_LEN {
assert_eq!(depacketize(&vec![0xFFu8; n]), Err(RtpParseError::TooShort));
}
}
#[test]
fn sender_seq_and_ts_wrap_independently() {
let mut s = RtpSender::with_seeds(PT_PCMU, 1, u16::MAX - 1, 0);
let seqs: Vec<u16> = (0..4)
.map(|_| depacketize(&s.packetize(&[0u8; 160])).unwrap().seq)
.collect();
assert_eq!(seqs, vec![u16::MAX - 1, u16::MAX, 0, 1]);
let mut s2 = RtpSender::with_seeds(PT_PCMU, 1, 0, u32::MAX - 160);
assert_eq!(
depacketize(&s2.packetize(&[0u8; 160])).unwrap().timestamp,
u32::MAX - 160
);
assert_eq!(
depacketize(&s2.packetize(&[0u8; 160])).unwrap().timestamp,
u32::MAX
);
assert_eq!(
depacketize(&s2.packetize(&[0u8; 160])).unwrap().timestamp,
159
); }
#[test]
fn jitter_depth_one_releases_immediately_in_order() {
let mut jb = JitterBuffer::with_depth(1);
assert_eq!(run(&mut jb, &[1, 2, 3, 4]), vec![1, 2, 3]);
let tail: Vec<u16> = jb.drain().into_iter().map(|p| p.seq).collect();
assert_eq!(tail, vec![4]);
}
#[test]
fn jitter_with_depth_zero_clamps_to_one() {
let mut jb = JitterBuffer::with_depth(0);
assert_eq!(run(&mut jb, &[5, 6]), vec![5]);
}
#[test]
fn jitter_lost_packet_does_not_stall_and_late_arrival_is_dropped() {
let mut jb = JitterBuffer::with_depth(2);
assert_eq!(run(&mut jb, &[1, 2, 4, 5, 6]), vec![1, 2, 4]);
assert_eq!(jb.push(pkt(3, &[3])), vec![], "late lost packet dropped");
}
#[test]
fn jitter_reset_clears_state_for_a_fresh_stream() {
let mut jb = JitterBuffer::with_depth(2);
let _ = run(&mut jb, &[40000, 40001, 40002, 40003]); jb.reset(); assert_eq!(run(&mut jb, &[100, 101, 102, 103]), vec![100, 101]);
}
#[test]
fn jitter_in_order_releases_in_order_after_priming() {
let mut jb = JitterBuffer::with_depth(4);
let released = run(&mut jb, &[1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(released, vec![1, 2, 3, 4]);
let tail: Vec<u16> = jb.drain().into_iter().map(|p| p.seq).collect();
assert_eq!(tail, vec![5, 6, 7, 8]);
}
#[test]
fn jitter_reorders_within_window() {
let mut jb = JitterBuffer::with_depth(4);
let released = run(&mut jb, &[1, 2, 4, 3, 5, 6, 7]);
assert_eq!(released, vec![1, 2, 3]); let tail: Vec<u16> = jb.drain().into_iter().map(|p| p.seq).collect();
assert_eq!(tail, vec![4, 5, 6, 7]);
}
#[test]
fn jitter_drops_duplicates() {
let mut jb = JitterBuffer::with_depth(4);
let released = run(&mut jb, &[1, 2, 2, 3, 4, 5, 6]);
assert_eq!(released, vec![1, 2]);
let tail: Vec<u16> = jb.drain().into_iter().map(|p| p.seq).collect();
assert_eq!(tail, vec![3, 4, 5, 6]);
}
#[test]
fn jitter_drops_late_packet_below_release_point() {
let mut jb = JitterBuffer::with_depth(2);
let released = run(&mut jb, &[10, 11, 12, 13, 14]);
assert_eq!(released, vec![10, 11, 12]); assert_eq!(jb.push(pkt(11, &[0])), vec![]);
assert_eq!(jb.push(pkt(12, &[0])), vec![]);
}
#[test]
fn jitter_handles_seq_wraparound() {
let mut jb = JitterBuffer::with_depth(2);
let released = run(&mut jb, &[65534, 65535, 0, 1, 2]);
assert_eq!(released, vec![65534, 65535, 0]);
let tail: Vec<u16> = jb.drain().into_iter().map(|p| p.seq).collect();
assert_eq!(tail, vec![1, 2]);
}
#[test]
fn jitter_reordered_across_wrap_is_sorted() {
let mut jb = JitterBuffer::with_depth(3);
let released = run(&mut jb, &[65534, 0, 65535, 1, 2, 3]);
assert_eq!(released, vec![65534, 65535, 0]);
let tail: Vec<u16> = jb.drain().into_iter().map(|p| p.seq).collect();
assert_eq!(tail, vec![1, 2, 3]);
}
}