use bytes::{Buf, BufMut, BytesMut};
use super::NtpTimestamp;
use crate::error::Error;
use crate::{Result, RtpSsrc};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum RtcpXrBlockType {
LossRle = 1,
DuplicateRle = 2,
PacketReceiptTimes = 3,
ReceiverReferenceTimes = 4,
Dlrr = 5,
StatisticsSummary = 6,
VoipMetrics = 7,
}
impl TryFrom<u8> for RtcpXrBlockType {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
1 => Ok(RtcpXrBlockType::LossRle),
2 => Ok(RtcpXrBlockType::DuplicateRle),
3 => Ok(RtcpXrBlockType::PacketReceiptTimes),
4 => Ok(RtcpXrBlockType::ReceiverReferenceTimes),
5 => Ok(RtcpXrBlockType::Dlrr),
6 => Ok(RtcpXrBlockType::StatisticsSummary),
7 => Ok(RtcpXrBlockType::VoipMetrics),
_ => Err(Error::RtcpError(format!(
"Unknown XR block type: {}",
value
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RtcpExtendedReport {
pub ssrc: RtpSsrc,
pub blocks: Vec<RtcpXrBlock>,
}
impl RtcpExtendedReport {
pub fn new(ssrc: RtpSsrc) -> Self {
Self {
ssrc,
blocks: Vec::new(),
}
}
pub fn add_block(&mut self, block: RtcpXrBlock) {
self.blocks.push(block);
}
pub fn add_voip_metrics(&mut self, metrics: VoipMetricsBlock) {
self.blocks.push(RtcpXrBlock::VoipMetrics(metrics));
}
pub fn add_receiver_reference_time(&mut self, ntp: NtpTimestamp) {
self.blocks.push(RtcpXrBlock::ReceiverReferenceTimes(
ReceiverReferenceTimeBlock { ntp },
));
}
pub fn size(&self) -> usize {
4 + self.blocks.iter().map(|b| b.size()).sum::<usize>()
}
pub fn serialize(&self) -> Result<BytesMut> {
let total_size = self.size();
let mut buf = BytesMut::with_capacity(total_size);
buf.put_u32(self.ssrc);
for block in &self.blocks {
block.serialize(&mut buf)?;
}
Ok(buf)
}
}
pub fn parse_xr(buf: &mut impl Buf) -> Result<RtcpExtendedReport> {
if buf.remaining() < 4 {
return Err(Error::BufferTooSmall {
required: 4,
available: buf.remaining(),
});
}
let ssrc = buf.get_u32();
let mut xr = RtcpExtendedReport::new(ssrc);
while buf.has_remaining() {
if buf.remaining() < 4 {
return Err(Error::BufferTooSmall {
required: 4,
available: buf.remaining(),
});
}
let block_type_byte = buf.get_u8();
let block_type = RtcpXrBlockType::try_from(block_type_byte)?;
buf.advance(1);
let block_length = buf.get_u16() as usize * 4;
if buf.remaining() < block_length {
return Err(Error::BufferTooSmall {
required: block_length,
available: buf.remaining(),
});
}
let block = match block_type {
RtcpXrBlockType::LossRle => {
RtcpXrBlock::LossRle(parse_loss_rle_block(buf, block_length)?)
}
RtcpXrBlockType::DuplicateRle => {
RtcpXrBlock::DuplicateRle(parse_duplicate_rle_block(buf, block_length)?)
}
RtcpXrBlockType::PacketReceiptTimes => {
RtcpXrBlock::PacketReceiptTimes(parse_packet_receipt_times_block(
buf,
block_length,
)?)
}
RtcpXrBlockType::ReceiverReferenceTimes => {
RtcpXrBlock::ReceiverReferenceTimes(parse_receiver_reference_time_block(buf)?)
}
RtcpXrBlockType::Dlrr => {
RtcpXrBlock::Dlrr(parse_dlrr_block(buf, block_length)?)
}
RtcpXrBlockType::StatisticsSummary => {
RtcpXrBlock::StatisticsSummary(parse_statistics_summary_block(buf)?)
}
RtcpXrBlockType::VoipMetrics => {
RtcpXrBlock::VoipMetrics(parse_voip_metrics_block(buf)?)
}
};
xr.add_block(block);
}
Ok(xr)
}
fn parse_loss_rle_block(buf: &mut impl Buf, length: usize) -> Result<LossRleReportBlock> {
buf.advance(length);
Ok(LossRleReportBlock {
ssrc: 0,
begin_seq: 0,
end_seq: 0,
chunks: Vec::new(),
})
}
fn parse_duplicate_rle_block(buf: &mut impl Buf, length: usize) -> Result<DuplicateRleReportBlock> {
buf.advance(length);
Ok(DuplicateRleReportBlock {
ssrc: 0,
begin_seq: 0,
end_seq: 0,
chunks: Vec::new(),
})
}
fn parse_packet_receipt_times_block(
buf: &mut impl Buf,
length: usize,
) -> Result<PacketReceiptTimesBlock> {
buf.advance(length);
Ok(PacketReceiptTimesBlock {
ssrc: 0,
begin_seq: 0,
end_seq: 0,
receipt_times: Vec::new(),
})
}
fn parse_receiver_reference_time_block(buf: &mut impl Buf) -> Result<ReceiverReferenceTimeBlock> {
if buf.remaining() < 8 {
return Err(Error::BufferTooSmall {
required: 8,
available: buf.remaining(),
});
}
let ntp_sec = buf.get_u32();
let ntp_frac = buf.get_u32();
Ok(ReceiverReferenceTimeBlock {
ntp: NtpTimestamp {
seconds: ntp_sec,
fraction: ntp_frac,
},
})
}
fn parse_dlrr_block(buf: &mut impl Buf, length: usize) -> Result<DlrrBlock> {
if buf.remaining() < length {
return Err(Error::BufferTooSmall {
required: length,
available: buf.remaining(),
});
}
let mut sub_blocks = Vec::new();
let sub_block_len = 12;
let num_sub_blocks = length / sub_block_len;
for _ in 0..num_sub_blocks {
if buf.remaining() < sub_block_len {
break;
}
let ssrc = buf.get_u32();
let last_rr = buf.get_u32();
let delay = buf.get_u32();
sub_blocks.push(DlrrSubBlock {
ssrc,
last_rr,
delay,
});
}
Ok(DlrrBlock { sub_blocks })
}
fn parse_statistics_summary_block(buf: &mut impl Buf) -> Result<StatisticsSummaryBlock> {
if buf.remaining() < 16 {
return Err(Error::BufferTooSmall {
required: 16,
available: buf.remaining(),
});
}
let ssrc = buf.get_u32();
let flags = buf.get_u8();
buf.advance(1); let begin_seq = buf.get_u16();
let end_seq = buf.get_u16();
let lost_packets = buf.get_u32();
let dup_packets = buf.get_u32();
let loss_report = (flags & 0x01) != 0;
let duplicate_report = (flags & 0x02) != 0;
let jitter_report = (flags & 0x04) != 0;
let ttr_report = (flags & 0x08) != 0;
let min_jitter = if jitter_report && buf.remaining() >= 4 {
Some(buf.get_u32())
} else {
None
};
let max_jitter = if jitter_report && buf.remaining() >= 4 {
Some(buf.get_u32())
} else {
None
};
let mean_jitter = if jitter_report && buf.remaining() >= 4 {
Some(buf.get_u32())
} else {
None
};
let dev_jitter = if jitter_report && buf.remaining() >= 4 {
Some(buf.get_u32())
} else {
None
};
Ok(StatisticsSummaryBlock {
ssrc,
begin_seq,
end_seq,
lost_packets,
dup_packets,
loss_report,
duplicate_report,
jitter_report,
ttr_report,
min_jitter,
max_jitter,
mean_jitter,
dev_jitter,
})
}
fn parse_voip_metrics_block(buf: &mut impl Buf) -> Result<VoipMetricsBlock> {
if buf.remaining() < 24 {
return Err(Error::BufferTooSmall {
required: 24,
available: buf.remaining(),
});
}
let ssrc = buf.get_u32();
let loss_rate = buf.get_u8();
let discard_rate = buf.get_u8();
let burst_density = buf.get_u8();
let gap_density = buf.get_u8();
let burst_duration = buf.get_u16();
let gap_duration = buf.get_u16();
let round_trip_delay = buf.get_u16();
let end_system_delay = buf.get_u16();
let signal_level = buf.get_u8();
let noise_level = buf.get_u8();
let rerl = buf.get_u8();
let gmin = buf.get_u8();
let r_factor = buf.get_u8();
let ext_r_factor = buf.get_u8();
let mos_lq = buf.get_u8();
let mos_cq = buf.get_u8();
let rx_config = buf.get_u8();
buf.advance(1); let jb_nominal = buf.get_u16();
let jb_maximum = buf.get_u16();
let jb_abs_max = buf.get_u16();
Ok(VoipMetricsBlock {
ssrc,
loss_rate,
discard_rate,
burst_density,
gap_density,
burst_duration,
gap_duration,
round_trip_delay,
end_system_delay,
signal_level,
noise_level,
rerl,
gmin,
r_factor,
ext_r_factor,
mos_lq,
mos_cq,
rx_config,
jb_nominal,
jb_maximum,
jb_abs_max,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RtcpXrBlock {
LossRle(LossRleReportBlock),
DuplicateRle(DuplicateRleReportBlock),
PacketReceiptTimes(PacketReceiptTimesBlock),
ReceiverReferenceTimes(ReceiverReferenceTimeBlock),
Dlrr(DlrrBlock),
StatisticsSummary(StatisticsSummaryBlock),
VoipMetrics(VoipMetricsBlock),
}
impl RtcpXrBlock {
pub fn block_type(&self) -> RtcpXrBlockType {
match self {
RtcpXrBlock::LossRle(_) => RtcpXrBlockType::LossRle,
RtcpXrBlock::DuplicateRle(_) => RtcpXrBlockType::DuplicateRle,
RtcpXrBlock::PacketReceiptTimes(_) => RtcpXrBlockType::PacketReceiptTimes,
RtcpXrBlock::ReceiverReferenceTimes(_) => RtcpXrBlockType::ReceiverReferenceTimes,
RtcpXrBlock::Dlrr(_) => RtcpXrBlockType::Dlrr,
RtcpXrBlock::StatisticsSummary(_) => RtcpXrBlockType::StatisticsSummary,
RtcpXrBlock::VoipMetrics(_) => RtcpXrBlockType::VoipMetrics,
}
}
pub fn size(&self) -> usize {
match self {
RtcpXrBlock::LossRle(block) => 4 + block.size(),
RtcpXrBlock::DuplicateRle(block) => 4 + block.size(),
RtcpXrBlock::PacketReceiptTimes(block) => 4 + block.size(),
RtcpXrBlock::ReceiverReferenceTimes(_) => 4 + 8, RtcpXrBlock::Dlrr(block) => 4 + block.size(),
RtcpXrBlock::StatisticsSummary(_) => 4 + 16, RtcpXrBlock::VoipMetrics(_) => 4 + 24, }
}
pub fn serialize(&self, buf: &mut BytesMut) -> Result<()> {
buf.put_u8(self.block_type() as u8);
buf.put_u8(0);
let block_length = (self.size() - 4) / 4;
buf.put_u16(block_length as u16);
match self {
RtcpXrBlock::LossRle(block) => {
buf.put_u32(block.ssrc);
buf.put_u16(block.begin_seq);
buf.put_u16(block.end_seq);
}
RtcpXrBlock::DuplicateRle(block) => {
buf.put_u32(block.ssrc);
buf.put_u16(block.begin_seq);
buf.put_u16(block.end_seq);
}
RtcpXrBlock::PacketReceiptTimes(block) => {
buf.put_u32(block.ssrc);
buf.put_u16(block.begin_seq);
buf.put_u16(block.end_seq);
}
RtcpXrBlock::ReceiverReferenceTimes(block) => {
buf.put_u32(block.ntp.seconds);
buf.put_u32(block.ntp.fraction);
}
RtcpXrBlock::Dlrr(block) => {
for sub_block in &block.sub_blocks {
buf.put_u32(sub_block.ssrc);
buf.put_u32(sub_block.last_rr);
buf.put_u32(sub_block.delay);
}
}
RtcpXrBlock::StatisticsSummary(block) => {
buf.put_u32(block.ssrc);
let mut flags = 0u8;
if block.loss_report {
flags |= 0x01;
}
if block.duplicate_report {
flags |= 0x02;
}
if block.jitter_report {
flags |= 0x04;
}
if block.ttr_report {
flags |= 0x08;
}
buf.put_u8(flags);
buf.put_u8(0); buf.put_u16(block.begin_seq);
buf.put_u16(block.end_seq);
buf.put_u32(block.lost_packets);
buf.put_u32(block.dup_packets);
if block.jitter_report {
if let Some(min_jitter) = block.min_jitter {
buf.put_u32(min_jitter);
} else {
buf.put_u32(0);
}
if let Some(max_jitter) = block.max_jitter {
buf.put_u32(max_jitter);
} else {
buf.put_u32(0);
}
if let Some(mean_jitter) = block.mean_jitter {
buf.put_u32(mean_jitter);
} else {
buf.put_u32(0);
}
if let Some(dev_jitter) = block.dev_jitter {
buf.put_u32(dev_jitter);
} else {
buf.put_u32(0);
}
}
}
RtcpXrBlock::VoipMetrics(block) => {
buf.put_u32(block.ssrc);
buf.put_u8(block.loss_rate);
buf.put_u8(block.discard_rate);
buf.put_u8(block.burst_density);
buf.put_u8(block.gap_density);
buf.put_u16(block.burst_duration);
buf.put_u16(block.gap_duration);
buf.put_u16(block.round_trip_delay);
buf.put_u16(block.end_system_delay);
buf.put_u8(block.signal_level);
buf.put_u8(block.noise_level);
buf.put_u8(block.rerl);
buf.put_u8(block.gmin);
buf.put_u8(block.r_factor);
buf.put_u8(block.ext_r_factor);
buf.put_u8(block.mos_lq);
buf.put_u8(block.mos_cq);
buf.put_u8(block.rx_config);
buf.put_u8(0); buf.put_u16(block.jb_nominal);
buf.put_u16(block.jb_maximum);
buf.put_u16(block.jb_abs_max);
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LossRleReportBlock {
pub ssrc: RtpSsrc,
pub begin_seq: u16,
pub end_seq: u16,
pub chunks: Vec<RleChunk>,
}
impl LossRleReportBlock {
pub fn size(&self) -> usize {
8 + self.chunks.len() * 2 }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DuplicateRleReportBlock {
pub ssrc: RtpSsrc,
pub begin_seq: u16,
pub end_seq: u16,
pub chunks: Vec<RleChunk>,
}
impl DuplicateRleReportBlock {
pub fn size(&self) -> usize {
8 + self.chunks.len() * 2 }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RleChunk {
RunLength { run_type: bool, run_length: u16 },
BitVector(u16),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PacketReceiptTimesBlock {
pub ssrc: RtpSsrc,
pub begin_seq: u16,
pub end_seq: u16,
pub receipt_times: Vec<u32>,
}
impl PacketReceiptTimesBlock {
pub fn size(&self) -> usize {
8 + self.receipt_times.len() * 4 }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReceiverReferenceTimeBlock {
pub ntp: NtpTimestamp,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DlrrBlock {
pub sub_blocks: Vec<DlrrSubBlock>,
}
impl DlrrBlock {
pub fn size(&self) -> usize {
self.sub_blocks.len() * 12 }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DlrrSubBlock {
pub ssrc: RtpSsrc,
pub last_rr: u32,
pub delay: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StatisticsSummaryBlock {
pub ssrc: RtpSsrc,
pub begin_seq: u16,
pub end_seq: u16,
pub lost_packets: u32,
pub dup_packets: u32,
pub loss_report: bool,
pub duplicate_report: bool,
pub jitter_report: bool,
pub ttr_report: bool,
pub min_jitter: Option<u32>,
pub max_jitter: Option<u32>,
pub mean_jitter: Option<u32>,
pub dev_jitter: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VoipMetricsBlock {
pub ssrc: RtpSsrc,
pub loss_rate: u8,
pub discard_rate: u8,
pub burst_density: u8,
pub gap_density: u8,
pub burst_duration: u16,
pub gap_duration: u16,
pub round_trip_delay: u16,
pub end_system_delay: u16,
pub signal_level: u8,
pub noise_level: u8,
pub rerl: u8,
pub gmin: u8,
pub r_factor: u8,
pub ext_r_factor: u8,
pub mos_lq: u8,
pub mos_cq: u8,
pub rx_config: u8,
pub jb_nominal: u16,
pub jb_maximum: u16,
pub jb_abs_max: u16,
}
impl VoipMetricsBlock {
pub fn new(ssrc: RtpSsrc) -> Self {
Self {
ssrc,
loss_rate: 0,
discard_rate: 0,
burst_density: 0,
gap_density: 0,
burst_duration: 0,
gap_duration: 0,
round_trip_delay: 0,
end_system_delay: 0,
signal_level: 0,
noise_level: 0,
rerl: 0,
gmin: 16, r_factor: 0,
ext_r_factor: 0,
mos_lq: 0,
mos_cq: 0,
rx_config: 0,
jb_nominal: 0,
jb_maximum: 0,
jb_abs_max: 0,
}
}
pub fn calculate_r_factor(
&mut self,
packet_loss_percent: f32,
round_trip_ms: u16,
_jitter_ms: f32,
) {
let r0 = 93.2;
let is = 0.0;
let delay = round_trip_ms as f32 / 2.0; let id = if delay < 177.3 {
0.024 * delay
} else {
0.024 * delay + 0.11 * (delay - 177.3)
};
let ie = 10.0; let burst_r = 1.0; let ppl = packet_loss_percent;
let ie_eff = ie + (95.0 - ie) * ppl / (ppl + burst_r);
let a = 0.0;
let r = (r0 - is - id - ie_eff + a).clamp(0.0, 100.0);
self.r_factor = ((r * 255.0) / 100.0) as u8;
let mut mos_lq = 1.0 + 0.035 * r + r * (r - 60.0) * (100.0 - r) * 7.0e-6;
mos_lq = mos_lq.clamp(1.0, 4.5);
self.mos_lq = ((mos_lq * 254.0) / 4.5) as u8 + 1;
let mut mos_cq = mos_lq - 0.1 * (delay / 150.0).min(0.5);
mos_cq = mos_cq.clamp(1.0, 4.5);
self.mos_cq = ((mos_cq * 254.0) / 4.5) as u8 + 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_receiver_reference_time_block() {
let ntp = NtpTimestamp {
seconds: 0x12345678,
fraction: 0xabcdef01,
};
let block = ReceiverReferenceTimeBlock { ntp };
let xr_block = RtcpXrBlock::ReceiverReferenceTimes(block);
assert_eq!(
xr_block.block_type(),
RtcpXrBlockType::ReceiverReferenceTimes
);
assert_eq!(xr_block.size(), 12);
let mut buf = BytesMut::with_capacity(xr_block.size());
xr_block.serialize(&mut buf).unwrap();
assert_eq!(buf.len(), 12);
assert_eq!(buf[0], RtcpXrBlockType::ReceiverReferenceTimes as u8);
assert_eq!(buf[1], 0); assert_eq!(buf[2], 0); assert_eq!(buf[3], 2); }
#[test]
fn test_voip_metrics_block() {
let mut voip_metrics = VoipMetricsBlock::new(0x12345678);
voip_metrics.loss_rate = 5; voip_metrics.discard_rate = 2; voip_metrics.round_trip_delay = 150; voip_metrics.end_system_delay = 20;
voip_metrics.calculate_r_factor(5.0, 150, 30.0);
let xr_block = RtcpXrBlock::VoipMetrics(voip_metrics);
assert_eq!(xr_block.block_type(), RtcpXrBlockType::VoipMetrics);
let buf = BytesMut::with_capacity(100);
let mut buf_clone = buf.clone();
xr_block.serialize(&mut buf_clone).unwrap();
let serialized_size = buf_clone.len();
assert_eq!(serialized_size, 36);
assert_eq!(buf_clone[0], RtcpXrBlockType::VoipMetrics as u8);
assert_eq!(buf_clone[1], 0); assert_eq!(buf_clone[2], 0); assert_eq!(buf_clone[3], 6);
let mut read_buf = buf_clone.freeze();
let block_type = RtcpXrBlockType::try_from(read_buf[0]).unwrap();
read_buf.advance(4);
assert_eq!(block_type, RtcpXrBlockType::VoipMetrics);
let parsed_metrics = parse_voip_metrics_block(&mut read_buf).unwrap();
assert_eq!(parsed_metrics.ssrc, 0x12345678);
assert_eq!(parsed_metrics.loss_rate, 5);
assert_eq!(parsed_metrics.discard_rate, 2);
assert_eq!(parsed_metrics.round_trip_delay, 150);
assert_eq!(parsed_metrics.end_system_delay, 20);
assert!(parsed_metrics.r_factor > 0);
assert!(parsed_metrics.mos_lq > 0);
assert!(parsed_metrics.mos_cq > 0);
}
#[test]
fn test_xr_packet() {
let mut xr = RtcpExtendedReport::new(0x12345678);
let ntp = NtpTimestamp {
seconds: 0x12345678,
fraction: 0xabcdef01,
};
xr.add_block(RtcpXrBlock::ReceiverReferenceTimes(
ReceiverReferenceTimeBlock { ntp },
));
let mut voip_metrics = VoipMetricsBlock::new(0x87654321);
voip_metrics.loss_rate = 3;
voip_metrics.round_trip_delay = 120;
voip_metrics.calculate_r_factor(3.0, 120, 25.0);
xr.add_block(RtcpXrBlock::VoipMetrics(voip_metrics));
let buf = xr.serialize().unwrap();
let expected_size = 4 + xr.blocks.iter().map(|b| b.size()).sum::<usize>();
println!("XR packet blocks: {}", xr.blocks.len());
for (i, block) in xr.blocks.iter().enumerate() {
println!(
"Block {} type: {:?}, size: {}",
i,
block.block_type(),
block.size()
);
}
println!("XR packet expected size: {}", expected_size);
println!("XR packet buffer size: {}", buf.len());
assert_eq!(buf.len(), 52);
assert_eq!(&buf[0..4], &0x12345678u32.to_be_bytes());
let mut read_buf = buf.freeze();
let parsed_xr = parse_xr(&mut read_buf).unwrap();
assert_eq!(parsed_xr.ssrc, 0x12345678);
assert_eq!(parsed_xr.blocks.len(), 2);
match &parsed_xr.blocks[0] {
RtcpXrBlock::ReceiverReferenceTimes(block) => {
assert_eq!(block.ntp.seconds, 0x12345678);
assert_eq!(block.ntp.fraction, 0xabcdef01);
}
_ => panic!("Expected ReferenceTimeBlock"),
}
match &parsed_xr.blocks[1] {
RtcpXrBlock::VoipMetrics(block) => {
assert_eq!(block.ssrc, 0x87654321);
assert_eq!(block.loss_rate, 3);
assert_eq!(block.round_trip_delay, 120);
}
_ => panic!("Expected VoipMetricsBlock"),
}
}
}