#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct RtpHeader {
pub version: u8,
pub payload_type: u8,
pub sequence_number: u16,
pub timestamp: u32,
pub ssrc: u32,
}
pub fn is_rtp_like(payload: &[u8]) -> bool {
payload.len() >= 2 && (payload[0] >> 6) == 2
}
pub fn parse_rtp_header(payload: &[u8]) -> Option<RtpHeader> {
if payload.len() < 12 || (payload[0] >> 6) != 2 {
return None;
}
Some(RtpHeader {
version: 2,
payload_type: payload[1] & 0x7f,
sequence_number: u16::from_be_bytes([payload[2], payload[3]]),
timestamp: u32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]),
ssrc: u32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MediaKind {
Rtp,
Rtcp,
Other,
}
pub fn classify(payload: &[u8]) -> MediaKind {
if !is_rtp_like(payload) {
return MediaKind::Other;
}
let pt_byte = payload.get(1).copied().unwrap_or(0);
if (200..=207).contains(&pt_byte) {
MediaKind::Rtcp
} else {
MediaKind::Rtp
}
}
pub fn rtp_clock_rate_for_payload_type(payload_type: u8) -> u32 {
match payload_type {
0 | 8 | 9 | 18 => 8000,
96..=127 => 48000,
_ => 8000,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_rtp_vs_rtcp() {
let rtp = [0x80u8, 0x00, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1];
assert_eq!(classify(&rtp), MediaKind::Rtp);
let rtp_m = [0x80u8, 0x80, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1];
assert_eq!(classify(&rtp_m), MediaKind::Rtp);
let sr = [0x80u8, 200, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0];
assert_eq!(classify(&sr), MediaKind::Rtcp);
let rr = [0x80u8, 201, 0, 1, 0, 0, 0, 1];
assert_eq!(classify(&rr), MediaKind::Rtcp);
let sdes = [0x81u8, 202, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0];
assert_eq!(classify(&sdes), MediaKind::Rtcp);
let sr_p = [0xA0u8, 200, 0, 1, 0, 0, 0, 1];
assert_eq!(classify(&sr_p), MediaKind::Rtcp);
assert_eq!(classify(&[0x00, 0x00]), MediaKind::Other);
}
}