mod error;
pub mod packet;
pub mod session;
pub mod srtp;
pub mod stats;
pub mod time;
pub mod traits;
pub mod transport;
pub mod api;
pub mod buffer;
pub mod csrc;
pub mod dtls;
pub mod events;
pub mod feedback;
pub mod network;
pub mod rtcp;
pub mod security;
pub mod sync;
pub const DEFAULT_MAX_PACKET_SIZE: usize = 1500;
pub type RtpTimestamp = u32;
pub type RtpSequenceNumber = u16;
pub type RtpSsrc = u32;
pub type RtpCsrc = u32;
pub type Result<T> = std::result::Result<T, Error>;
pub use error::Error;
pub use packet::extension::{
ids::AUDIO_LEVEL, ids::FRAME_MARKING, ids::SDES, ids::TRANSPORT_CC, ids::VIDEO_ORIENTATION,
uris::ABS_SEND_TIME, uris::MID, uris::REPAIR_RTP_STREAM_ID, uris::STREAM_ID,
uris::VIDEO_CONTENT_TYPE, ExtensionElement, RtpHeaderExtensions,
};
pub use packet::header::RtpHeader;
pub use packet::rtcp::{
NtpTimestamp, RtcpApplicationDefined, RtcpCompoundPacket, RtcpExtendedReport, RtcpGoodbye,
RtcpPacket, RtcpReceiverReport, RtcpReportBlock, RtcpSenderReport, RtcpSourceDescription,
RtcpXrBlock, VoipMetricsBlock,
};
pub use packet::rtp::RtpPacket;
pub use session::{
RtpSendHandle, RtpSession, RtpSessionBufferConfig, RtpSessionConfig, RtpSessionEvent,
RtpSessionStats, RtpStream, RtpStreamStats,
};
pub use transport::{RtpTransport, RtpTransportBufferConfig, RtpTransportConfig, UdpRtpTransport};
pub use traits::media_transport::RtpMediaTransport;
pub use traits::{MediaTransport, RtpEvent};
pub use csrc::{CsrcManager, CsrcMapping, MAX_CSRC_COUNT};
pub use sync::clock::MediaClock;
pub use sync::mapping::TimestampMapper;
pub use sync::MediaSync;
pub use feedback::packets::{
FeedbackPacket, FirPacket, PliPacket, RembPacket, SliPacket, TransportCcPacket, TstoPacket,
RTCP_HEADER_SIZE,
};
pub use feedback::{
CongestionState, FeedbackConfig, FeedbackContext, FeedbackDecision, FeedbackGenerator,
FeedbackGeneratorFactory, FeedbackPriority, QualityDegradation,
};
pub use api::client::{ClientConfig, ClientConfigBuilder, ClientFactory, MediaTransportClient};
pub use api::common::buffer::{BufferStats, MediaBuffer, MediaBufferConfig};
pub use api::common::config::{
BaseTransportConfig, NetworkPreset, SecurityInfo, SecurityMode, SrtpProfile,
};
pub use api::common::error::{BufferError, MediaTransportError, SecurityError, StatsError};
pub use api::common::events::{MediaEventCallback, MediaTransportEvent};
pub use api::common::frame::{MediaFrame, MediaFrameType};
pub use api::common::stats::{MediaStats, QualityLevel};
pub use api::server::{
ClientInfo, MediaTransportServer, ServerConfig, ServerConfigBuilder, ServerFactory,
};
pub mod prelude {
pub use crate::{
Error, Result, RtpCsrc, RtpHeader, RtpPacket, RtpSequenceNumber, RtpSession,
RtpSessionConfig, RtpSsrc, RtpTimestamp,
};
pub use crate::packet::rtcp::{
NtpTimestamp, RtcpPacket, RtcpReceiverReport, RtcpReportBlock, RtcpSenderReport,
};
pub use crate::traits::{MediaTransport, RtpMediaTransport};
pub use crate::api::client::{ClientConfig, ClientFactory, MediaTransportClient};
pub use crate::api::common::events::{MediaEventCallback, MediaTransportEvent};
pub use crate::api::common::frame::{MediaFrame, MediaFrameType};
pub use crate::api::server::{ClientInfo, MediaTransportServer, ServerConfig, ServerFactory};
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
use packet::{hex_dump, RtpHeader};
use tracing::debug;
fn init_test_logging() {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.try_init();
}
#[test]
fn test_rtp_header_serialize_parse() {
init_test_logging();
let original_header = RtpHeader::new(96, 1000, 0x12345678, 0xabcdef01);
debug!("Original header: PT={}", original_header.payload_type);
let mut buf = bytes::BytesMut::with_capacity(12);
original_header.serialize(&mut buf).unwrap();
debug!("Serialized header bytes: [{}]", hex_dump(&buf));
let buf = buf.freeze();
let mut reader = buf.clone();
let parsed_header = RtpHeader::parse(&mut reader).unwrap();
debug!("Parsed header: PT={}", parsed_header.payload_type);
assert_eq!(parsed_header.version, 2);
assert_eq!(
parsed_header.payload_type, 96,
"Payload type mismatch: expected 96, got {}",
parsed_header.payload_type
);
assert_eq!(parsed_header.sequence_number, 1000);
assert_eq!(parsed_header.timestamp, 0x12345678);
assert_eq!(parsed_header.ssrc, 0xabcdef01);
assert_eq!(parsed_header.padding, false);
assert_eq!(parsed_header.extension, false);
assert_eq!(parsed_header.cc, 0);
assert_eq!(parsed_header.marker, false);
}
#[test]
fn test_rtp_packet_serialize_parse() {
let payload_data = b"test payload data";
let payload = Bytes::copy_from_slice(payload_data);
let original_packet = RtpPacket::new_with_payload(
96, 1000, 0x12345678, 0xabcdef01, payload.clone(),
);
let serialized = original_packet.serialize().unwrap();
let parsed_packet = RtpPacket::parse(&serialized).unwrap();
assert_eq!(parsed_packet.header.version, 2);
assert_eq!(parsed_packet.header.payload_type, 96);
assert_eq!(parsed_packet.header.sequence_number, 1000);
assert_eq!(parsed_packet.header.timestamp, 0x12345678);
assert_eq!(parsed_packet.header.ssrc, 0xabcdef01);
assert_eq!(parsed_packet.payload, payload);
}
#[test]
fn test_rtp_header_with_csrc() {
let mut header = RtpHeader::new(96, 1000, 0x12345678, 0xabcdef01);
header.csrc = vec![0x11111111, 0x22222222];
header.cc = 2;
let mut buf = bytes::BytesMut::with_capacity(20);
header.serialize(&mut buf).unwrap();
let mut reader = buf.freeze();
let parsed_header = RtpHeader::parse(&mut reader).unwrap();
assert_eq!(parsed_header.cc, 2);
assert_eq!(parsed_header.csrc.len(), 2);
assert_eq!(parsed_header.csrc[0], 0x11111111);
assert_eq!(parsed_header.csrc[1], 0x22222222);
}
#[test]
fn test_rtp_header_with_extension() {
init_test_logging();
let mut header = RtpHeader::new(96, 1000, 0x12345678, 0xabcdef01);
header.extension = true;
let mut ext = RtpHeaderExtensions::new_legacy(0x1234);
ext.elements.push(ExtensionElement {
id: 1, data: Bytes::from_static(b"extension data"),
});
header.extensions = Some(ext);
debug!(
"Original header with extension: ext={}, format={:?}, data_len={:?}",
header.extension,
header.extensions.as_ref().map(|e| e.format),
header.extensions.as_ref().map(|e| e
.elements
.iter()
.map(|el| el.data.len())
.sum::<usize>())
);
let mut buf = bytes::BytesMut::with_capacity(40);
header.serialize(&mut buf).unwrap();
debug!(
"Serialized extension header (size={}): [{}]",
buf.len(),
hex_dump(&buf)
);
let first_byte = buf[0];
debug!(
"First byte: 0x{:02x}, extension bit set: {}",
first_byte,
((first_byte >> 4) & 0x01) == 1
);
let version = (first_byte >> 6) & 0x03;
let padding = ((first_byte >> 5) & 0x01) == 1;
let extension = ((first_byte >> 4) & 0x01) == 1;
let cc = first_byte & 0x0F;
debug!(
"Manual parse of first byte 0x{:02x}: V={}, P={}, X={}, CC={}",
first_byte, version, padding, extension, cc
);
let mut reader = buf.freeze();
debug!("Buffer size for parsing: {}", reader.len());
let parse_result = RtpHeader::parse(&mut reader);
if let Err(ref e) = parse_result {
debug!("Parse error: {:?}", e);
}
let parsed_header = parse_result.unwrap();
debug!("Remaining bytes after parse: {}", reader.len());
assert_eq!(parsed_header.extension, true);
assert!(parsed_header.extensions.is_some());
let parsed_extensions = parsed_header.extensions.unwrap();
assert_eq!(parsed_extensions.profile_id, 0x1234);
assert!(!parsed_extensions.elements.is_empty());
let parsed_data = &parsed_extensions.elements[0].data;
let original_data = b"extension data";
assert!(
parsed_data.starts_with(original_data),
"Extension data doesn't match. Expected to start with: {:?}, got: {:?}",
original_data,
parsed_data
);
}
#[test]
fn test_parse_real_world_packet() {
init_test_logging();
let packet_data = [
0x80, 0x00, 0xfd, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x65,
0x73, 0x74,
];
debug!("Test packet data: [{}]", hex_dump(&packet_data));
let mut buf = Bytes::copy_from_slice(&packet_data);
let header_result = packet::RtpHeader::parse(&mut buf);
if let Err(ref e) = header_result {
debug!("RTP header parse failed: {:?}", e);
} else {
debug!("RTP header parse succeeded, remaining bytes: {}", buf.len());
}
assert!(
header_result.is_ok(),
"Failed to parse RTP header: {:?}",
header_result.err()
);
let packet_result = RtpPacket::parse(&packet_data);
if let Err(ref e) = packet_result {
debug!("RTP packet parse failed: {:?}", e);
} else {
debug!("RTP packet parse succeeded");
}
assert!(
packet_result.is_ok(),
"Failed to parse RTP packet: {:?}",
packet_result.err()
);
let parsed = packet_result.unwrap();
assert_eq!(parsed.header.version, 2); assert_eq!(parsed.header.payload_type, 0); assert_eq!(parsed.header.cc, 0); assert_eq!(parsed.header.sequence_number, 0xfd70); assert_eq!(parsed.header.timestamp, 0); assert_eq!(parsed.header.ssrc, 0);
assert_eq!(parsed.payload.len(), 4);
assert_eq!(parsed.payload.as_ref(), &b"Test"[..]);
}
#[test]
fn test_serialize_rtp_packet_with_extension() {
let mut header = RtpHeader::new(96, 1000, 12345, 0xABCDEF01);
header.extension = true;
let mut ext = RtpHeaderExtensions::new_legacy(0x1234);
ext.elements.push(ExtensionElement {
id: 1, data: Bytes::from_static(b"extension data"),
});
header.extensions = Some(ext);
println!(
"Extension: {}, profile ID: {}, Data length: {}",
header.extension,
header
.extensions
.as_ref()
.map(|e| e.profile_id)
.unwrap_or(0),
header
.extensions
.as_ref()
.map(|e| e.elements.iter().map(|el| el.data.len()).sum::<usize>())
.unwrap_or(0)
);
let payload = Bytes::from_static(b"test payload");
let packet = RtpPacket::new(header, payload);
let bytes = packet.serialize().unwrap();
assert_eq!(bytes[0] & 0x10, 0x10);
let ext_header_offset = 12;
let ext_id =
((bytes[ext_header_offset] as u16) << 8) | (bytes[ext_header_offset + 1] as u16);
let ext_len_words =
((bytes[ext_header_offset + 2] as u16) << 8) | (bytes[ext_header_offset + 3] as u16);
assert_eq!(ext_id, 0x1234);
assert_eq!(ext_len_words * 4, 16);
let ext_data_offset = ext_header_offset + 4;
let ext_data = &bytes[ext_data_offset..ext_data_offset + 14];
let expected_data = b"extension data";
for i in 0..expected_data.len() {
assert_eq!(ext_data[i], expected_data[i]);
}
}
#[test]
fn test_parse_rtp_packet_with_extension() {
let mut header = RtpHeader::new(96, 1000, 12345, 0xABCDEF01);
header.extension = true;
let mut ext = RtpHeaderExtensions::new_legacy(0x1234);
ext.elements.push(ExtensionElement {
id: 1, data: Bytes::from_static(b"extension data"),
});
header.extensions = Some(ext);
let payload = Bytes::from_static(b"test payload");
let packet = RtpPacket::new(header, payload);
let bytes = packet.serialize().unwrap();
let parsed_packet = RtpPacket::parse(&bytes).unwrap();
let parsed_header = parsed_packet.header;
assert_eq!(parsed_header.extension, true);
assert!(parsed_header.extensions.is_some());
let parsed_extensions = parsed_header.extensions.unwrap();
assert_eq!(parsed_extensions.profile_id, 0x1234);
assert!(!parsed_extensions.elements.is_empty());
let parsed_data = &parsed_extensions.elements[0].data;
let original_data = b"extension data";
assert!(
parsed_data.starts_with(original_data),
"Extension data doesn't match original. Expected to start with: {:?}, got: {:?}",
original_data,
parsed_data
);
assert_eq!(parsed_packet.payload.as_ref(), b"test payload" as &[u8]);
}
}