In RtcpTransceiver implement handling incoming RRTR
Bug: webrtc:8239
Change-Id: I4a469b6a0c2e387e35262798f4686fbf310d00cd
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/251902
Reviewed-by: Emil Lundmark <lndmrk@webrtc.org>
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36037}
diff --git a/modules/rtp_rtcp/source/rtcp_packet/dlrr.h b/modules/rtp_rtcp/source/rtcp_packet/dlrr.h
index 6fe2099..ad91dfd 100644
--- a/modules/rtp_rtcp/source/rtcp_packet/dlrr.h
+++ b/modules/rtp_rtcp/source/rtcp_packet/dlrr.h
@@ -24,11 +24,21 @@
ReceiveTimeInfo() : ssrc(0), last_rr(0), delay_since_last_rr(0) {}
ReceiveTimeInfo(uint32_t ssrc, uint32_t last_rr, uint32_t delay)
: ssrc(ssrc), last_rr(last_rr), delay_since_last_rr(delay) {}
+
uint32_t ssrc;
uint32_t last_rr;
uint32_t delay_since_last_rr;
};
+inline bool operator==(const ReceiveTimeInfo& lhs, const ReceiveTimeInfo& rhs) {
+ return lhs.ssrc == rhs.ssrc && lhs.last_rr == rhs.last_rr &&
+ lhs.delay_since_last_rr == rhs.delay_since_last_rr;
+}
+
+inline bool operator!=(const ReceiveTimeInfo& lhs, const ReceiveTimeInfo& rhs) {
+ return !(lhs == rhs);
+}
+
// DLRR Report Block: Delay since the Last Receiver Report (RFC 3611).
class Dlrr {
public:
diff --git a/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc b/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc
index 7c50c01..3d9a2a3 100644
--- a/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc
+++ b/modules/rtp_rtcp/source/rtcp_packet/extended_reports_unittest.cc
@@ -24,19 +24,6 @@
using webrtc::rtcp::Rrtr;
namespace webrtc {
-// Define comparision operators that shouldn't be needed in production,
-// but make testing matches more clear.
-namespace rtcp {
-bool operator==(const Rrtr& rrtr1, const Rrtr& rrtr2) {
- return rrtr1.ntp() == rrtr2.ntp();
-}
-
-bool operator==(const ReceiveTimeInfo& time1, const ReceiveTimeInfo& time2) {
- return time1.ssrc == time2.ssrc && time1.last_rr == time2.last_rr &&
- time1.delay_since_last_rr == time2.delay_since_last_rr;
-}
-} // namespace rtcp
-
namespace {
constexpr uint32_t kSenderSsrc = 0x12345678;
constexpr uint8_t kEmptyPacket[] = {0x80, 207, 0x00, 0x01,
diff --git a/modules/rtp_rtcp/source/rtcp_packet/rrtr.h b/modules/rtp_rtcp/source/rtcp_packet/rrtr.h
index 8eb4ce6..827bd74 100644
--- a/modules/rtp_rtcp/source/rtcp_packet/rrtr.h
+++ b/modules/rtp_rtcp/source/rtcp_packet/rrtr.h
@@ -46,6 +46,14 @@
NtpTime ntp_;
};
+inline bool operator==(const Rrtr& rrtr1, const Rrtr& rrtr2) {
+ return rrtr1.ntp() == rrtr2.ntp();
+}
+
+inline bool operator!=(const Rrtr& rrtr1, const Rrtr& rrtr2) {
+ return !(rrtr1 == rrtr2);
+}
+
} // namespace rtcp
} // namespace webrtc
#endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_RRTR_H_
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_config.h b/modules/rtp_rtcp/source/rtcp_transceiver_config.h
index 0be633f..2400f9b 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_config.h
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_config.h
@@ -162,6 +162,13 @@
// https://tools.ietf.org/html/rfc3611#section-4.4 and #section-4.5
bool non_sender_rtt_measurement = false;
+ // Reply to incoming RRTR messages so that remote endpoint may estimate RTT as
+ // non-sender as described in https://tools.ietf.org/html/rfc3611#section-4.4
+ // and #section-4.5
+ // TODO(danilchap): Make it true by default after users got enough time to
+ // turn it off if not needed.
+ bool reply_to_non_sender_rtt_measurement = false;
+
// Allows a REMB message to be sent immediately when SetRemb is called without
// having to wait for the next compount message to be sent.
bool send_remb_on_change = false;
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc b/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc
index c075b2f..5909af9 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc
@@ -370,6 +370,14 @@
if (!extended_reports.Parse(rtcp_packet_header))
return;
+ if (config_.reply_to_non_sender_rtt_measurement && extended_reports.rrtr()) {
+ RrtrTimes& rrtr = received_rrtrs_[extended_reports.sender_ssrc()];
+ rrtr.received_remote_mid_ntp_time =
+ CompactNtp(extended_reports.rrtr()->ntp());
+ rrtr.local_receive_mid_ntp_time =
+ CompactNtp(config_.clock->ConvertTimestampToNtpTime(now));
+ }
+
if (extended_reports.dlrr())
HandleDlrr(extended_reports.dlrr(), now);
@@ -620,7 +628,29 @@
if (remb_.has_value()) {
reserved_bytes += remb_->BlockLength();
}
+ absl::optional<rtcp::ExtendedReports> xr;
+ if (!received_rrtrs_.empty()) {
+ RTC_DCHECK(config_.reply_to_non_sender_rtt_measurement);
+ xr.emplace();
+ uint32_t now_ntp =
+ CompactNtp(config_.clock->ConvertTimestampToNtpTime(now));
+ for (const auto& [ssrc, rrtr_info] : received_rrtrs_) {
+ rtcp::ReceiveTimeInfo reply;
+ reply.ssrc = ssrc;
+ reply.last_rr = rrtr_info.received_remote_mid_ntp_time;
+ reply.delay_since_last_rr =
+ now_ntp - rrtr_info.local_receive_mid_ntp_time;
+ xr->AddDlrrItem(reply);
+ }
+ reserved_bytes += xr->BlockLength();
+ }
if (config_.non_sender_rtt_measurement) {
+ // It looks like bytes for ExtendedReport header are reserved twice, but in
+ // practice the same RtcpTransceiver won't both produce RRTR (i.e. it is a
+ // receiver-only) and reply to RRTR (i.e. remote participant is a receiver
+ // only). If that happen, then `reserved_bytes` would be slightly larger
+ // than it should, which is not an issue.
+
// 4 bytes for common RTCP header + 4 bytes for the ExtenedReports header.
reserved_bytes += (4 + 4 + rtcp::Rrtr::kLength);
}
@@ -635,14 +665,16 @@
sender.AppendPacket(*remb_);
}
if (!result.has_sender_report && config_.non_sender_rtt_measurement) {
- rtcp::ExtendedReports xr;
- xr.SetSenderSsrc(result.sender_ssrc);
-
+ if (!xr.has_value()) {
+ xr.emplace();
+ }
rtcp::Rrtr rrtr;
rrtr.SetNtp(config_.clock->ConvertTimestampToNtpTime(now));
- xr.SetRrtr(rrtr);
-
- sender.AppendPacket(xr);
+ xr->SetRrtr(rrtr);
+ }
+ if (xr.has_value()) {
+ xr->SetSenderSsrc(result.sender_ssrc);
+ sender.AppendPacket(*xr);
}
}
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl.h b/modules/rtp_rtcp/source/rtcp_transceiver_impl.h
index ce25899..8dc50b8 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_impl.h
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl.h
@@ -82,6 +82,13 @@
class PacketSender;
struct RemoteSenderState;
struct LocalSenderState;
+ struct RrtrTimes {
+ // Received remote NTP timestamp in compact representation.
+ uint32_t received_remote_mid_ntp_time;
+
+ // Local NTP time when the report was received in compact representation.
+ uint32_t local_receive_mid_ntp_time;
+ };
void HandleReceivedPacket(const rtcp::CommonHeader& rtcp_packet_header,
Timestamp now,
@@ -144,6 +151,7 @@
std::list<LocalSenderState> local_senders_;
flat_map<uint32_t, std::list<LocalSenderState>::iterator>
local_senders_by_ssrc_;
+ flat_map<uint32_t, RrtrTimes> received_rrtrs_;
RepeatingTaskHandle periodic_task_handle_;
};
diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc b/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
index 417dcdd..82bfade 100644
--- a/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
+++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
@@ -45,6 +45,7 @@
using ::testing::Return;
using ::testing::SizeIs;
using ::testing::StrictMock;
+using ::testing::UnorderedElementsAre;
using ::testing::WithArg;
using ::webrtc::rtcp::Bye;
using ::webrtc::rtcp::CompoundPacket;
@@ -1208,6 +1209,43 @@
EXPECT_EQ(rtcp_parser.xr()->rrtr()->ntp(), ntp_time_now);
}
+TEST(RtcpTransceiverImplTest, RepliesToRrtrWhenEnabled) {
+ static constexpr uint32_t kSenderSsrc[] = {4321, 9876};
+ SimulatedClock clock(0);
+ RtcpTransceiverConfig config = DefaultTestConfig();
+ config.clock = &clock;
+ config.reply_to_non_sender_rtt_measurement = true;
+ RtcpPacketParser rtcp_parser;
+ RtcpParserTransport transport(&rtcp_parser);
+ config.outgoing_transport = &transport;
+ RtcpTransceiverImpl rtcp_transceiver(config);
+
+ rtcp::ExtendedReports xr;
+ rtcp::Rrtr rrtr;
+ rrtr.SetNtp(NtpTime(uint64_t{0x1111'2222'3333'4444}));
+ xr.SetRrtr(rrtr);
+ xr.SetSenderSsrc(kSenderSsrc[0]);
+ rtcp_transceiver.ReceivePacket(xr.Build(), clock.CurrentTime());
+ clock.AdvanceTime(TimeDelta::Millis(1'500));
+
+ rrtr.SetNtp(NtpTime(uint64_t{0x4444'5555'6666'7777}));
+ xr.SetRrtr(rrtr);
+ xr.SetSenderSsrc(kSenderSsrc[1]);
+ rtcp_transceiver.ReceivePacket(xr.Build(), clock.CurrentTime());
+ clock.AdvanceTime(TimeDelta::Millis(500));
+
+ rtcp_transceiver.SendCompoundPacket();
+
+ EXPECT_EQ(rtcp_parser.xr()->num_packets(), 1);
+ static constexpr uint32_t kComactNtpOneSecond = 0x0001'0000;
+ EXPECT_THAT(rtcp_parser.xr()->dlrr().sub_blocks(),
+ UnorderedElementsAre(
+ rtcp::ReceiveTimeInfo(kSenderSsrc[0], 0x2222'3333,
+ /*delay=*/2 * kComactNtpOneSecond),
+ rtcp::ReceiveTimeInfo(kSenderSsrc[1], 0x5555'6666,
+ /*delay=*/kComactNtpOneSecond / 2)));
+}
+
TEST(RtcpTransceiverImplTest, SendsNoXrRrtrWhenDisabled) {
SimulatedClock clock(0);
RtcpTransceiverConfig config;