Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

* Respond to RRTR with DLRR #1022
* Use a local SSRC as the originator of outgoing RRTR reports #1027
* Add `DirectApi::send_pli_feedback` to send a PLI with a chosen sender SSRC #1025

# 0.22.0
Expand Down
6 changes: 3 additions & 3 deletions src/streams/receive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ impl StreamRx {
self.stats.jitter = report.jitter;
}

let xr = self.create_extended_receiver_report(now);
let xr = self.create_extended_receiver_report(now, sender_ssrc);

trace!(
"Created feedback RR/XR ({:?}): {:?} {:?}",
Expand Down Expand Up @@ -637,14 +637,14 @@ impl StreamRx {
}
}

fn create_extended_receiver_report(&self, now: Instant) -> ExtendedReport {
fn create_extended_receiver_report(&self, now: Instant, sender_ssrc: Ssrc) -> ExtendedReport {
// we only want to report our time to measure RTT,
// the source will answer with Dlrr feedback, allowing us to calculate RTT
let block = ReportBlock::Rrtr(Rrtr {
ntp_time: now.to_system_time(),
});
ExtendedReport {
ssrc: self.ssrc,
ssrc: sender_ssrc,
blocks: vec![block],
}
}
Expand Down
33 changes: 23 additions & 10 deletions tests/dlrr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,31 @@ pub fn dlrr_response_to_rrtr() -> Result<(), RtcError> {
}
}

// R should have sent at least one RRTR.
let rrtr_count = r
let media_ssrc = l
.events
.iter()
.filter(|(_, e)| {
matches!(
e.as_raw_packet(),
Some(RawPacket::RtcpTx(Rtcp::ExtendedReport(xr)))
if xr.blocks.iter().any(|b| matches!(b, ReportBlock::Rrtr(_)))
)
.find_map(|(_, e)| match e.as_raw_packet() {
Some(RawPacket::RtpTx(header, _)) => Some(header.ssrc),
_ => None,
})
.expect("L should have sent RTP");

// R should have sent at least one RRTR, using its local RTCP SSRC rather than
// the remote media SSRC it is reporting on.
let rrtr_ssrc = r
.events
.iter()
.find_map(|(_, e)| {
let Some(RawPacket::RtcpTx(Rtcp::ExtendedReport(xr))) = e.as_raw_packet() else {
return None;
};
xr.blocks
.iter()
.any(|b| matches!(b, ReportBlock::Rrtr(_)))
.then_some(xr.ssrc)
})
.count();
assert!(rrtr_count > 0, "R should have sent at least one RRTR");
.expect("R should have sent at least one RRTR");
assert_ne!(rrtr_ssrc, media_ssrc);

// L should have sent at least one DLRR in response.
let dlrr_reports: Vec<_> = l
Expand Down Expand Up @@ -107,6 +119,7 @@ pub fn dlrr_response_to_rrtr() -> Result<(), RtcError> {
.unwrap();
assert!(!dlrr.items.is_empty(), "DLRR should have at least one item");
let item = &dlrr.items[0];
assert_eq!(item.ssrc, rrtr_ssrc);

// The last_rr_time field must be the middle 32 bits of the RRTR's NTP timestamp.
// Verify it's non-zero and that R received the same value back (round-trip integrity).
Expand Down
Loading