Wire up non-sender RTT for audio, and implement related standardized stats.
The implemented stats are:
- https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime
- https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-totalroundtriptime
- https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptimemeasurements
Bug: webrtc:12951, webrtc:12714
Change-Id: Ia362d5c4b0456140e32da79d40edc06ab9ce2a2c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/226956
Commit-Queue: Ivo Creusen <ivoc@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34861}
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 8a6327e..849ef80 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -593,6 +593,9 @@
RTCStatsMember<std::string> local_id;
RTCStatsMember<double> remote_timestamp;
RTCStatsMember<uint64_t> reports_sent;
+ RTCStatsMember<double> round_trip_time;
+ RTCStatsMember<uint64_t> round_trip_time_measurements;
+ RTCStatsMember<double> total_round_trip_time;
};
// https://w3c.github.io/webrtc-stats/#dom-rtcmediasourcestats
diff --git a/audio/BUILD.gn b/audio/BUILD.gn
index 200f9f4..56b974d 100644
--- a/audio/BUILD.gn
+++ b/audio/BUILD.gn
@@ -141,6 +141,7 @@
"remix_resample_unittest.cc",
"test/audio_stats_test.cc",
"test/nack_test.cc",
+ "test/non_sender_rtt_test.cc",
]
deps = [
":audio",
diff --git a/audio/audio_receive_stream.cc b/audio/audio_receive_stream.cc
index f384381..6f24449 100644
--- a/audio/audio_receive_stream.cc
+++ b/audio/audio_receive_stream.cc
@@ -81,9 +81,10 @@
config.rtcp_send_transport, event_log, config.rtp.local_ssrc,
config.rtp.remote_ssrc, config.jitter_buffer_max_packets,
config.jitter_buffer_fast_accelerate, config.jitter_buffer_min_delay_ms,
- config.jitter_buffer_enable_rtx_handling, config.decoder_factory,
- config.codec_pair_id, std::move(config.frame_decryptor),
- config.crypto_options, std::move(config.frame_transformer));
+ config.jitter_buffer_enable_rtx_handling, config.enable_non_sender_rtt,
+ config.decoder_factory, config.codec_pair_id,
+ std::move(config.frame_decryptor), config.crypto_options,
+ std::move(config.frame_transformer));
}
} // namespace
@@ -242,6 +243,12 @@
}
}
+void AudioReceiveStream::SetNonSenderRttMeasurement(bool enabled) {
+ RTC_DCHECK_RUN_ON(&worker_thread_checker_);
+ config_.enable_non_sender_rtt = enabled;
+ channel_receive_->SetNonSenderRttMeasurement(enabled);
+}
+
void AudioReceiveStream::SetFrameDecryptor(
rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor) {
// TODO(bugs.webrtc.org/11993): This is called via WebRtcAudioReceiveStream,
@@ -347,6 +354,9 @@
stats.sender_reports_packets_sent = call_stats.sender_reports_packets_sent;
stats.sender_reports_bytes_sent = call_stats.sender_reports_bytes_sent;
stats.sender_reports_reports_count = call_stats.sender_reports_reports_count;
+ stats.round_trip_time = call_stats.round_trip_time;
+ stats.round_trip_time_measurements = call_stats.round_trip_time_measurements;
+ stats.total_round_trip_time = call_stats.total_round_trip_time;
return stats;
}
diff --git a/audio/audio_receive_stream.h b/audio/audio_receive_stream.h
index 61ebc27..444ec45 100644
--- a/audio/audio_receive_stream.h
+++ b/audio/audio_receive_stream.h
@@ -92,6 +92,7 @@
void SetDecoderMap(std::map<int, SdpAudioFormat> decoder_map) override;
void SetUseTransportCcAndNackHistory(bool use_transport_cc,
int history_ms) override;
+ void SetNonSenderRttMeasurement(bool enabled) override;
void SetFrameDecryptor(rtc::scoped_refptr<webrtc::FrameDecryptorInterface>
frame_decryptor) override;
void SetRtpExtensions(std::vector<RtpExtension> extensions) override;
diff --git a/audio/audio_send_stream_unittest.cc b/audio/audio_send_stream_unittest.cc
index db42efc..58db525 100644
--- a/audio/audio_send_stream_unittest.cc
+++ b/audio/audio_send_stream_unittest.cc
@@ -387,7 +387,8 @@
"min_bitrate_bps: 12000, max_bitrate_bps: 34000, has "
"audio_network_adaptor_config: false, has_dscp: true, "
"send_codec_spec: {nack_enabled: true, transport_cc_enabled: false, "
- "cng_payload_type: 42, red_payload_type: 43, payload_type: 103, "
+ "enable_non_sender_rtt: false, cng_payload_type: 42, "
+ "red_payload_type: 43, payload_type: 103, "
"format: {name: isac, clockrate_hz: 16000, num_channels: 1, "
"parameters: {}}}}",
config.ToString());
diff --git a/audio/channel_receive.cc b/audio/channel_receive.cc
index b741a8c..9c6f672 100644
--- a/audio/channel_receive.cc
+++ b/audio/channel_receive.cc
@@ -97,6 +97,7 @@
bool jitter_buffer_fast_playout,
int jitter_buffer_min_delay_ms,
bool jitter_buffer_enable_rtx_handling,
+ bool enable_non_sender_rtt,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
absl::optional<AudioCodecPairId> codec_pair_id,
rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor,
@@ -158,6 +159,7 @@
CallReceiveStatistics GetRTCPStatistics() const override;
void SetNACKStatus(bool enable, int maxNumberOfPackets) override;
+ void SetNonSenderRttMeasurement(bool enabled) override;
AudioMixer::Source::AudioFrameInfo GetAudioFrameWithInfo(
int sample_rate_hz,
@@ -524,6 +526,7 @@
bool jitter_buffer_fast_playout,
int jitter_buffer_min_delay_ms,
bool jitter_buffer_enable_rtx_handling,
+ bool enable_non_sender_rtt,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
absl::optional<AudioCodecPairId> codec_pair_id,
rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor,
@@ -573,6 +576,7 @@
configuration.event_log = event_log_;
configuration.local_media_ssrc = local_ssrc;
configuration.rtcp_packet_type_counter_observer = this;
+ configuration.non_sender_rtt_measurement = enable_non_sender_rtt;
if (frame_transformer)
InitFrameTransformerDelegate(std::move(frame_transformer));
@@ -856,6 +860,15 @@
stats.sender_reports_reports_count = rtcp_sr_stats->reports_count;
}
+ absl::optional<RtpRtcpInterface::NonSenderRttStats> non_sender_rtt_stats =
+ rtp_rtcp_->GetNonSenderRttStats();
+ if (non_sender_rtt_stats.has_value()) {
+ stats.round_trip_time = non_sender_rtt_stats->round_trip_time;
+ stats.round_trip_time_measurements =
+ non_sender_rtt_stats->round_trip_time_measurements;
+ stats.total_round_trip_time = non_sender_rtt_stats->total_round_trip_time;
+ }
+
return stats;
}
@@ -872,6 +885,11 @@
}
}
+void ChannelReceive::SetNonSenderRttMeasurement(bool enabled) {
+ RTC_DCHECK_RUN_ON(&worker_thread_checker_);
+ rtp_rtcp_->SetNonSenderRttMeasurement(enabled);
+}
+
// Called when we are missing one or more packets.
int ChannelReceive::ResendPackets(const uint16_t* sequence_numbers,
int length) {
@@ -1110,6 +1128,7 @@
bool jitter_buffer_fast_playout,
int jitter_buffer_min_delay_ms,
bool jitter_buffer_enable_rtx_handling,
+ bool enable_non_sender_rtt,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
absl::optional<AudioCodecPairId> codec_pair_id,
rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor,
@@ -1119,8 +1138,9 @@
clock, neteq_factory, audio_device_module, rtcp_send_transport,
rtc_event_log, local_ssrc, remote_ssrc, jitter_buffer_max_packets,
jitter_buffer_fast_playout, jitter_buffer_min_delay_ms,
- jitter_buffer_enable_rtx_handling, decoder_factory, codec_pair_id,
- std::move(frame_decryptor), crypto_options, std::move(frame_transformer));
+ jitter_buffer_enable_rtx_handling, enable_non_sender_rtt, decoder_factory,
+ codec_pair_id, std::move(frame_decryptor), crypto_options,
+ std::move(frame_transformer));
}
} // namespace voe
diff --git a/audio/channel_receive.h b/audio/channel_receive.h
index deec49f..d811e87 100644
--- a/audio/channel_receive.h
+++ b/audio/channel_receive.h
@@ -74,6 +74,9 @@
uint32_t sender_reports_packets_sent = 0;
uint64_t sender_reports_bytes_sent = 0;
uint64_t sender_reports_reports_count = 0;
+ absl::optional<TimeDelta> round_trip_time;
+ TimeDelta total_round_trip_time = TimeDelta::Zero();
+ int round_trip_time_measurements;
};
namespace voe {
@@ -138,6 +141,7 @@
virtual CallReceiveStatistics GetRTCPStatistics() const = 0;
virtual void SetNACKStatus(bool enable, int max_packets) = 0;
+ virtual void SetNonSenderRttMeasurement(bool enabled) = 0;
virtual AudioMixer::Source::AudioFrameInfo GetAudioFrameWithInfo(
int sample_rate_hz,
@@ -179,6 +183,7 @@
bool jitter_buffer_fast_playout,
int jitter_buffer_min_delay_ms,
bool jitter_buffer_enable_rtx_handling,
+ bool enable_non_sender_rtt,
rtc::scoped_refptr<AudioDecoderFactory> decoder_factory,
absl::optional<AudioCodecPairId> codec_pair_id,
rtc::scoped_refptr<FrameDecryptorInterface> frame_decryptor,
diff --git a/audio/mock_voe_channel_proxy.h b/audio/mock_voe_channel_proxy.h
index ea2a2ac..d445b51 100644
--- a/audio/mock_voe_channel_proxy.h
+++ b/audio/mock_voe_channel_proxy.h
@@ -30,6 +30,7 @@
class MockChannelReceive : public voe::ChannelReceiveInterface {
public:
MOCK_METHOD(void, SetNACKStatus, (bool enable, int max_packets), (override));
+ MOCK_METHOD(void, SetNonSenderRttMeasurement, (bool enabled), (override));
MOCK_METHOD(void,
RegisterReceiverCongestionControlObjects,
(PacketRouter*),
diff --git a/audio/test/non_sender_rtt_test.cc b/audio/test/non_sender_rtt_test.cc
new file mode 100644
index 0000000..5c5b15e
--- /dev/null
+++ b/audio/test/non_sender_rtt_test.cc
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "audio/test/audio_end_to_end_test.h"
+#include "system_wrappers/include/sleep.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace test {
+
+using NonSenderRttTest = CallTest;
+
+TEST_F(NonSenderRttTest, NonSenderRttStats) {
+ class NonSenderRttTest : public AudioEndToEndTest {
+ public:
+ const int kTestDurationMs = 10000;
+ const int64_t kRttMs = 30;
+
+ BuiltInNetworkBehaviorConfig GetNetworkPipeConfig() const override {
+ BuiltInNetworkBehaviorConfig pipe_config;
+ pipe_config.queue_delay_ms = kRttMs / 2;
+ return pipe_config;
+ }
+
+ void ModifyAudioConfigs(
+ AudioSendStream::Config* send_config,
+ std::vector<AudioReceiveStream::Config>* receive_configs) override {
+ ASSERT_EQ(receive_configs->size(), 1U);
+ (*receive_configs)[0].enable_non_sender_rtt = true;
+ AudioEndToEndTest::ModifyAudioConfigs(send_config, receive_configs);
+ send_config->send_codec_spec->enable_non_sender_rtt = true;
+ }
+
+ void PerformTest() override { SleepMs(kTestDurationMs); }
+
+ void OnStreamsStopped() override {
+ AudioReceiveStream::Stats recv_stats =
+ receive_stream()->GetStats(/*get_and_clear_legacy_stats=*/true);
+ EXPECT_GT(recv_stats.round_trip_time_measurements, 0);
+ ASSERT_TRUE(recv_stats.round_trip_time.has_value());
+ EXPECT_GT(recv_stats.round_trip_time->ms(), 0);
+ EXPECT_GE(recv_stats.total_round_trip_time.ms(),
+ recv_stats.round_trip_time->ms());
+ }
+ } test;
+
+ RunBaseTest(&test);
+}
+
+} // namespace test
+} // namespace webrtc
diff --git a/call/audio_receive_stream.h b/call/audio_receive_stream.h
index 182cd49..1c2d11b 100644
--- a/call/audio_receive_stream.h
+++ b/call/audio_receive_stream.h
@@ -96,6 +96,9 @@
uint32_t sender_reports_packets_sent = 0;
uint64_t sender_reports_bytes_sent = 0;
uint64_t sender_reports_reports_count = 0;
+ absl::optional<TimeDelta> round_trip_time;
+ TimeDelta total_round_trip_time = TimeDelta::Zero();
+ int round_trip_time_measurements;
};
struct Config {
@@ -115,6 +118,9 @@
NackConfig nack;
} rtp;
+ // Receive-side RTT.
+ bool enable_non_sender_rtt = false;
+
Transport* rtcp_send_transport = nullptr;
// NetEq settings.
@@ -158,6 +164,8 @@
virtual void SetDecoderMap(std::map<int, SdpAudioFormat> decoder_map) = 0;
virtual void SetUseTransportCcAndNackHistory(bool use_transport_cc,
int history_ms) = 0;
+ virtual void SetNonSenderRttMeasurement(bool enabled) = 0;
+
// Set/change the rtp header extensions. Must be called on the packet
// delivery thread.
virtual void SetRtpExtensions(std::vector<RtpExtension> extensions) = 0;
diff --git a/call/audio_send_stream.cc b/call/audio_send_stream.cc
index 916336b..a36050a 100644
--- a/call/audio_send_stream.cc
+++ b/call/audio_send_stream.cc
@@ -80,6 +80,8 @@
rtc::SimpleStringBuilder ss(buf);
ss << "{nack_enabled: " << (nack_enabled ? "true" : "false");
ss << ", transport_cc_enabled: " << (transport_cc_enabled ? "true" : "false");
+ ss << ", enable_non_sender_rtt: "
+ << (enable_non_sender_rtt ? "true" : "false");
ss << ", cng_payload_type: "
<< (cng_payload_type ? rtc::ToString(*cng_payload_type) : "<unset>");
ss << ", red_payload_type: "
@@ -94,6 +96,7 @@
const AudioSendStream::Config::SendCodecSpec& rhs) const {
if (nack_enabled == rhs.nack_enabled &&
transport_cc_enabled == rhs.transport_cc_enabled &&
+ enable_non_sender_rtt == rhs.enable_non_sender_rtt &&
cng_payload_type == rhs.cng_payload_type &&
red_payload_type == rhs.red_payload_type &&
payload_type == rhs.payload_type && format == rhs.format &&
diff --git a/call/audio_send_stream.h b/call/audio_send_stream.h
index e084d42..e38a47f 100644
--- a/call/audio_send_stream.h
+++ b/call/audio_send_stream.h
@@ -140,6 +140,7 @@
SdpAudioFormat format;
bool nack_enabled = false;
bool transport_cc_enabled = false;
+ bool enable_non_sender_rtt = false;
absl::optional<int> cng_payload_type;
absl::optional<int> red_payload_type;
// If unset, use the encoder's default target bitrate.
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 61a29f1..87d3672 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -85,6 +85,7 @@
"../api/transport:stun_types",
"../api/transport:webrtc_key_value_config",
"../api/transport/rtp:rtp_source",
+ "../api/units:time_delta",
"../api/video:video_bitrate_allocation",
"../api/video:video_bitrate_allocator_factory",
"../api/video:video_frame",
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 6467a44..2607a7a 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -28,6 +28,7 @@
#include "api/rtp_parameters.h"
#include "api/transport/data_channel_transport_interface.h"
#include "api/transport/rtp/rtp_source.h"
+#include "api/units/time_delta.h"
#include "api/video/video_content_type.h"
#include "api/video/video_sink_interface.h"
#include "api/video/video_source_interface.h"
@@ -531,6 +532,9 @@
uint32_t sender_reports_packets_sent = 0;
uint64_t sender_reports_bytes_sent = 0;
uint64_t sender_reports_reports_count = 0;
+ absl::optional<webrtc::TimeDelta> round_trip_time;
+ webrtc::TimeDelta total_round_trip_time = webrtc::TimeDelta::Zero();
+ int round_trip_time_measurements = 0;
};
struct VideoSenderInfo : public MediaSenderInfo {
diff --git a/media/engine/fake_webrtc_call.cc b/media/engine/fake_webrtc_call.cc
index 1340151..f61aab0 100644
--- a/media/engine/fake_webrtc_call.cc
+++ b/media/engine/fake_webrtc_call.cc
@@ -116,6 +116,10 @@
config_.rtp.nack.rtp_history_ms = history_ms;
}
+void FakeAudioReceiveStream::SetNonSenderRttMeasurement(bool enabled) {
+ config_.enable_non_sender_rtt = enabled;
+}
+
void FakeAudioReceiveStream::SetFrameDecryptor(
rtc::scoped_refptr<webrtc::FrameDecryptorInterface> frame_decryptor) {
config_.frame_decryptor = std::move(frame_decryptor);
diff --git a/media/engine/fake_webrtc_call.h b/media/engine/fake_webrtc_call.h
index aeef954..5f6b864 100644
--- a/media/engine/fake_webrtc_call.h
+++ b/media/engine/fake_webrtc_call.h
@@ -122,6 +122,7 @@
std::map<int, webrtc::SdpAudioFormat> decoder_map) override;
void SetUseTransportCcAndNackHistory(bool use_transport_cc,
int history_ms) override;
+ void SetNonSenderRttMeasurement(bool enabled) override;
void SetFrameDecryptor(rtc::scoped_refptr<webrtc::FrameDecryptorInterface>
frame_decryptor) override;
void SetRtpExtensions(std::vector<webrtc::RtpExtension> extensions) override;
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index 0cf17ed..75d1a5f 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -255,6 +255,7 @@
uint32_t local_ssrc,
bool use_transport_cc,
bool use_nack,
+ bool enable_non_sender_rtt,
const std::vector<std::string>& stream_ids,
const std::vector<webrtc::RtpExtension>& extensions,
webrtc::Transport* rtcp_send_transport,
@@ -278,6 +279,7 @@
}
config.rtp.extensions = extensions;
config.rtcp_send_transport = rtcp_send_transport;
+ config.enable_non_sender_rtt = enable_non_sender_rtt;
config.decoder_factory = decoder_factory;
config.decoder_map = decoder_map;
config.codec_pair_id = codec_pair_id;
@@ -1245,6 +1247,11 @@
use_nack ? kNackRtpHistoryMs : 0);
}
+ void SetNonSenderRttMeasurement(bool enabled) {
+ RTC_DCHECK_RUN_ON(&worker_thread_checker_);
+ stream_->SetNonSenderRttMeasurement(enabled);
+ }
+
void SetRtpExtensions(const std::vector<webrtc::RtpExtension>& extensions) {
RTC_DCHECK_RUN_ON(&worker_thread_checker_);
stream_->SetRtpExtensions(extensions);
@@ -1715,6 +1722,7 @@
}
send_codec_spec->transport_cc_enabled = HasTransportCc(voice_codec);
send_codec_spec->nack_enabled = HasNack(voice_codec);
+ send_codec_spec->enable_non_sender_rtt = HasRrtr(voice_codec);
bitrate_config = GetBitrateConfigForCodec(voice_codec);
break;
}
@@ -1790,16 +1798,27 @@
// preferred send codec, and in that case reconfigure all receive streams.
if (recv_transport_cc_enabled_ != send_codec_spec_->transport_cc_enabled ||
recv_nack_enabled_ != send_codec_spec_->nack_enabled) {
- RTC_LOG(LS_INFO) << "Recreate all the receive streams because the send "
- "codec has changed.";
+ RTC_LOG(LS_INFO) << "Changing transport cc and NACK status on receive "
+ "streams.";
recv_transport_cc_enabled_ = send_codec_spec_->transport_cc_enabled;
recv_nack_enabled_ = send_codec_spec_->nack_enabled;
+ enable_non_sender_rtt_ = send_codec_spec_->enable_non_sender_rtt;
for (auto& kv : recv_streams_) {
kv.second->SetUseTransportCc(recv_transport_cc_enabled_,
recv_nack_enabled_);
}
}
+ // Check if the receive-side RTT status has changed on the preferred send
+ // codec, in that case reconfigure all receive streams.
+ if (enable_non_sender_rtt_ != send_codec_spec_->enable_non_sender_rtt) {
+ RTC_LOG(LS_INFO) << "Changing receive-side RTT status on receive streams.";
+ enable_non_sender_rtt_ = send_codec_spec_->enable_non_sender_rtt;
+ for (auto& kv : recv_streams_) {
+ kv.second->SetNonSenderRttMeasurement(enable_non_sender_rtt_);
+ }
+ }
+
send_codecs_ = codecs;
return true;
}
@@ -1963,9 +1982,9 @@
// Create a new channel for receiving audio data.
auto config = BuildReceiveStreamConfig(
ssrc, receiver_reports_ssrc_, recv_transport_cc_enabled_,
- recv_nack_enabled_, sp.stream_ids(), recv_rtp_extensions_, this,
- engine()->decoder_factory_, decoder_map_, codec_pair_id_,
- engine()->audio_jitter_buffer_max_packets_,
+ recv_nack_enabled_, enable_non_sender_rtt_, sp.stream_ids(),
+ recv_rtp_extensions_, this, engine()->decoder_factory_, decoder_map_,
+ codec_pair_id_, engine()->audio_jitter_buffer_max_packets_,
engine()->audio_jitter_buffer_fast_accelerate_,
engine()->audio_jitter_buffer_min_delay_ms_,
engine()->audio_jitter_buffer_enable_rtx_handling_,
@@ -2424,6 +2443,9 @@
rinfo.sender_reports_packets_sent = stats.sender_reports_packets_sent;
rinfo.sender_reports_bytes_sent = stats.sender_reports_bytes_sent;
rinfo.sender_reports_reports_count = stats.sender_reports_reports_count;
+ rinfo.round_trip_time = stats.round_trip_time;
+ rinfo.round_trip_time_measurements = stats.round_trip_time_measurements;
+ rinfo.total_round_trip_time = stats.total_round_trip_time;
if (recv_nack_enabled_) {
rinfo.nacks_sent = stats.nacks_sent;
diff --git a/media/engine/webrtc_voice_engine.h b/media/engine/webrtc_voice_engine.h
index a7c2f39..a8eb61d 100644
--- a/media/engine/webrtc_voice_engine.h
+++ b/media/engine/webrtc_voice_engine.h
@@ -277,6 +277,7 @@
int dtmf_payload_freq_ = -1;
bool recv_transport_cc_enabled_ = false;
bool recv_nack_enabled_ = false;
+ bool enable_non_sender_rtt_ = false;
bool playout_ = false;
bool send_ = false;
webrtc::Call* const call_ = nullptr;
diff --git a/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h b/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h
index 33c5a9b..05e49fb 100644
--- a/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h
+++ b/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h
@@ -152,6 +152,10 @@
GetSenderReportStats,
(),
(const, override));
+ MOCK_METHOD(absl::optional<NonSenderRttStats>,
+ GetNonSenderRttStats,
+ (),
+ (const, override));
MOCK_METHOD(void,
SetRemb,
(int64_t bitrate, std::vector<uint32_t> ssrcs),
diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc
index 32f442a..e64b693 100644
--- a/modules/rtp_rtcp/source/rtcp_receiver.cc
+++ b/modules/rtp_rtcp/source/rtcp_receiver.cc
@@ -303,6 +303,15 @@
return 0;
}
+RTCPReceiver::NonSenderRttStats RTCPReceiver::GetNonSenderRTT() const {
+ MutexLock lock(&rtcp_receiver_lock_);
+ auto it = non_sender_rtts_.find(remote_ssrc_);
+ if (it == non_sender_rtts_.end()) {
+ return {};
+ }
+ return it->second;
+}
+
void RTCPReceiver::SetNonSenderRttMeasurement(bool enabled) {
MutexLock lock(&rtcp_receiver_lock_);
xr_rrtr_status_ = enabled;
@@ -435,6 +444,16 @@
MutexLock lock(&rtcp_receiver_lock_);
CommonHeader rtcp_block;
+ // If a sender report is received but no DLRR, we need to reset the
+ // roundTripTime stat according to the standard, see
+ // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime
+ struct RtcpReceivedBlock {
+ bool sender_report = false;
+ bool dlrr = false;
+ };
+ // For each remote SSRC we store if we've received a sender report or a DLRR
+ // block.
+ flat_map<uint32_t, RtcpReceivedBlock> received_blocks;
for (const uint8_t* next_block = packet.begin(); next_block != packet.end();
next_block = rtcp_block.NextPacket()) {
ptrdiff_t remaining_blocks_size = packet.end() - next_block;
@@ -455,6 +474,7 @@
switch (rtcp_block.type()) {
case rtcp::SenderReport::kPacketType:
HandleSenderReport(rtcp_block, packet_information);
+ received_blocks[packet_information->remote_ssrc].sender_report = true;
break;
case rtcp::ReceiverReport::kPacketType:
HandleReceiverReport(rtcp_block, packet_information);
@@ -463,7 +483,12 @@
HandleSdes(rtcp_block, packet_information);
break;
case rtcp::ExtendedReports::kPacketType:
- HandleXr(rtcp_block, packet_information);
+ bool contains_dlrr;
+ uint32_t ssrc;
+ HandleXr(rtcp_block, packet_information, contains_dlrr, ssrc);
+ if (contains_dlrr) {
+ received_blocks[ssrc].dlrr = true;
+ }
break;
case rtcp::Bye::kPacketType:
HandleBye(rtcp_block);
@@ -515,6 +540,15 @@
}
}
+ for (const auto& rb : received_blocks) {
+ if (rb.second.sender_report && !rb.second.dlrr) {
+ auto rtt_stats = non_sender_rtts_.find(rb.first);
+ if (rtt_stats != non_sender_rtts_.end()) {
+ rtt_stats->second.Invalidate();
+ }
+ }
+ }
+
if (packet_type_counter_observer_) {
packet_type_counter_observer_->RtcpPacketTypesCounterUpdated(
main_ssrc_, packet_type_counter_);
@@ -832,18 +866,22 @@
}
void RTCPReceiver::HandleXr(const CommonHeader& rtcp_block,
- PacketInformation* packet_information) {
+ PacketInformation* packet_information,
+ bool& contains_dlrr,
+ uint32_t& ssrc) {
rtcp::ExtendedReports xr;
if (!xr.Parse(rtcp_block)) {
++num_skipped_packets_;
return;
}
+ ssrc = xr.sender_ssrc();
+ contains_dlrr = !xr.dlrr().sub_blocks().empty();
if (xr.rrtr())
HandleXrReceiveReferenceTime(xr.sender_ssrc(), *xr.rrtr());
for (const rtcp::ReceiveTimeInfo& time_info : xr.dlrr().sub_blocks())
- HandleXrDlrrReportBlock(time_info);
+ HandleXrDlrrReportBlock(xr.sender_ssrc(), time_info);
if (xr.target_bitrate()) {
HandleXrTargetBitrate(xr.sender_ssrc(), *xr.target_bitrate(),
@@ -872,7 +910,8 @@
}
}
-void RTCPReceiver::HandleXrDlrrReportBlock(const rtcp::ReceiveTimeInfo& rti) {
+void RTCPReceiver::HandleXrDlrrReportBlock(uint32_t sender_ssrc,
+ const rtcp::ReceiveTimeInfo& rti) {
if (!registered_ssrcs_.contains(rti.ssrc)) // Not to us.
return;
@@ -884,14 +923,21 @@
uint32_t send_time_ntp = rti.last_rr;
// RFC3611, section 4.5, LRR field discription states:
// If no such block has been received, the field is set to zero.
- if (send_time_ntp == 0)
+ if (send_time_ntp == 0) {
+ auto rtt_stats = non_sender_rtts_.find(sender_ssrc);
+ if (rtt_stats != non_sender_rtts_.end()) {
+ rtt_stats->second.Invalidate();
+ }
return;
+ }
uint32_t delay_ntp = rti.delay_since_last_rr;
uint32_t now_ntp = CompactNtp(clock_->CurrentNtpTime());
uint32_t rtt_ntp = now_ntp - delay_ntp - send_time_ntp;
xr_rr_rtt_ms_ = CompactNtpRttToMs(rtt_ntp);
+
+ non_sender_rtts_[sender_ssrc].Update(TimeDelta::Millis(xr_rr_rtt_ms_));
}
void RTCPReceiver::HandleXrTargetBitrate(
diff --git a/modules/rtp_rtcp/source/rtcp_receiver.h b/modules/rtp_rtcp/source/rtcp_receiver.h
index 206b63a..f45b783 100644
--- a/modules/rtp_rtcp/source/rtcp_receiver.h
+++ b/modules/rtp_rtcp/source/rtcp_receiver.h
@@ -16,8 +16,10 @@
#include <string>
#include <vector>
+#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/sequence_checker.h"
+#include "api/units/time_delta.h"
#include "modules/rtp_rtcp/include/report_block_data.h"
#include "modules/rtp_rtcp/include/rtcp_statistics.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
@@ -58,6 +60,35 @@
protected:
virtual ~ModuleRtpRtcp() = default;
};
+ // Standardized stats derived from the non-sender RTT.
+ class NonSenderRttStats {
+ public:
+ NonSenderRttStats() = default;
+ NonSenderRttStats(const NonSenderRttStats&) = default;
+ NonSenderRttStats& operator=(const NonSenderRttStats&) = default;
+ ~NonSenderRttStats() = default;
+ void Update(TimeDelta non_sender_rtt_seconds) {
+ round_trip_time_ = non_sender_rtt_seconds;
+ total_round_trip_time_ += non_sender_rtt_seconds;
+ round_trip_time_measurements_++;
+ }
+ void Invalidate() { round_trip_time_.reset(); }
+ // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime
+ absl::optional<TimeDelta> round_trip_time() const {
+ return round_trip_time_;
+ }
+ // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-totalroundtriptime
+ TimeDelta total_round_trip_time() const { return total_round_trip_time_; }
+ // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptimemeasurements
+ int round_trip_time_measurements() const {
+ return round_trip_time_measurements_;
+ }
+
+ private:
+ absl::optional<TimeDelta> round_trip_time_;
+ TimeDelta total_round_trip_time_ = TimeDelta::Zero();
+ int round_trip_time_measurements_ = 0;
+ };
RTCPReceiver(const RtpRtcpInterface::Configuration& config,
ModuleRtpRtcp* owner);
@@ -108,6 +139,9 @@
int64_t* min_rtt_ms,
int64_t* max_rtt_ms) const;
+ // Returns non-sender RTT metrics for the remote SSRC.
+ NonSenderRttStats GetNonSenderRTT() const;
+
void SetNonSenderRttMeasurement(bool enabled);
bool GetAndResetXrRrRtt(int64_t* rtt_ms);
@@ -277,14 +311,16 @@
RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_);
void HandleXr(const rtcp::CommonHeader& rtcp_block,
- PacketInformation* packet_information)
+ PacketInformation* packet_information,
+ bool& contains_dlrr,
+ uint32_t& ssrc)
RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_);
void HandleXrReceiveReferenceTime(uint32_t sender_ssrc,
const rtcp::Rrtr& rrtr)
RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_);
- void HandleXrDlrrReportBlock(const rtcp::ReceiveTimeInfo& rti)
+ void HandleXrDlrrReportBlock(uint32_t ssrc, const rtcp::ReceiveTimeInfo& rti)
RTC_EXCLUSIVE_LOCKS_REQUIRED(rtcp_receiver_lock_);
void HandleXrTargetBitrate(uint32_t ssrc,
@@ -382,6 +418,9 @@
// Round-Trip Time per remote sender ssrc.
flat_map<uint32_t, RttStats> rtts_ RTC_GUARDED_BY(rtcp_receiver_lock_);
+ // Non-sender Round-trip time per remote ssrc.
+ flat_map<uint32_t, NonSenderRttStats> non_sender_rtts_
+ RTC_GUARDED_BY(rtcp_receiver_lock_);
// Report blocks per local source ssrc.
flat_map<uint32_t, ReportBlockData> received_report_blocks_
diff --git a/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc b/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc
index 585d698..fa7d569 100644
--- a/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc
+++ b/modules/rtp_rtcp/source/rtcp_receiver_unittest.cc
@@ -802,6 +802,11 @@
int64_t rtt_ms = 0;
EXPECT_FALSE(receiver.GetAndResetXrRrRtt(&rtt_ms));
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_TRUE(non_sender_rtt_stats.total_round_trip_time().IsZero());
+ EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 0);
}
TEST(RtcpReceiverTest, InjectExtendedReportsDlrrPacketWithSubBlock) {
@@ -826,6 +831,11 @@
EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms));
uint32_t rtt_ntp = compact_ntp_now - kDelay - kLastRR;
EXPECT_NEAR(CompactNtpRttToMs(rtt_ntp), rtt_ms, 1);
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_GT(non_sender_rtt_stats.round_trip_time(), TimeDelta::Zero());
+ EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero());
+ EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0);
}
TEST(RtcpReceiverTest, InjectExtendedReportsDlrrPacketWithMultipleSubBlocks) {
@@ -851,6 +861,11 @@
EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms));
uint32_t rtt_ntp = compact_ntp_now - kDelay - kLastRR;
EXPECT_NEAR(CompactNtpRttToMs(rtt_ntp), rtt_ms, 1);
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_GT(non_sender_rtt_stats.round_trip_time(), TimeDelta::Zero());
+ EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero());
+ EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0);
}
TEST(RtcpReceiverTest, InjectExtendedReportsPacketWithMultipleReportBlocks) {
@@ -901,6 +916,11 @@
// Validate Dlrr report wasn't processed.
int64_t rtt_ms = 0;
EXPECT_FALSE(receiver.GetAndResetXrRrRtt(&rtt_ms));
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_TRUE(non_sender_rtt_stats.total_round_trip_time().IsZero());
+ EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 0);
}
TEST(RtcpReceiverTest, TestExtendedReportsRrRttInitiallyFalse) {
@@ -912,6 +932,11 @@
int64_t rtt_ms;
EXPECT_FALSE(receiver.GetAndResetXrRrRtt(&rtt_ms));
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_TRUE(non_sender_rtt_stats.total_round_trip_time().IsZero());
+ EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 0);
}
TEST(RtcpReceiverTest, RttCalculatedAfterExtendedReportsDlrr) {
@@ -938,6 +963,12 @@
int64_t rtt_ms = 0;
EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms));
EXPECT_NEAR(kRttMs, rtt_ms, 1);
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().value().IsZero());
+ EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero());
+ EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0);
}
// Same test as above but enables receive-side RTT using the setter instead of
@@ -967,6 +998,12 @@
int64_t rtt_ms = 0;
EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms));
EXPECT_NEAR(rtt_ms, kRttMs, 1);
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().value().IsZero());
+ EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero());
+ EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0);
}
// Same test as above but disables receive-side RTT using the setter instead of
@@ -996,6 +1033,11 @@
// We expect that no RTT is available (because receive-side RTT was disabled).
int64_t rtt_ms = 0;
EXPECT_FALSE(receiver.GetAndResetXrRrRtt(&rtt_ms));
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_TRUE(non_sender_rtt_stats.total_round_trip_time().IsZero());
+ EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 0);
}
TEST(RtcpReceiverTest, XrDlrrCalculatesNegativeRttAsOne) {
@@ -1022,6 +1064,205 @@
int64_t rtt_ms = 0;
EXPECT_TRUE(receiver.GetAndResetXrRrRtt(&rtt_ms));
EXPECT_EQ(1, rtt_ms);
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().value().IsZero());
+ EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero());
+ EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0);
+}
+
+// Test receiver RTT stats with multiple measurements.
+TEST(RtcpReceiverTest, ReceiverRttWithMultipleMeasurements) {
+ ReceiverMocks mocks;
+ auto config = DefaultConfiguration(&mocks);
+ config.non_sender_rtt_measurement = true;
+ RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl);
+ receiver.SetRemoteSSRC(kSenderSsrc);
+
+ Random rand(0x0123456789abcdef);
+ const int64_t kRttMs = rand.Rand(1, 9 * 3600 * 1000);
+ const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff);
+ const int64_t kDelayMs = CompactNtpRttToMs(kDelayNtp);
+ NtpTime now = mocks.clock.CurrentNtpTime();
+ uint32_t sent_ntp = CompactNtp(now);
+ mocks.clock.AdvanceTimeMilliseconds(kRttMs + kDelayMs);
+
+ rtcp::ExtendedReports xr;
+ xr.SetSenderSsrc(kSenderSsrc);
+ xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp));
+
+ receiver.IncomingPacket(xr.Build());
+
+ // Check that the non-sender RTT stats are valid and based on a single
+ // measurement.
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_NEAR(non_sender_rtt_stats.round_trip_time()->ms(), kRttMs, 1);
+ EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 1);
+ EXPECT_EQ(non_sender_rtt_stats.total_round_trip_time().ms(),
+ non_sender_rtt_stats.round_trip_time()->ms());
+
+ // Generate another XR report with the same RTT and delay.
+ NtpTime now2 = mocks.clock.CurrentNtpTime();
+ uint32_t sent_ntp2 = CompactNtp(now2);
+ mocks.clock.AdvanceTimeMilliseconds(kRttMs + kDelayMs);
+
+ rtcp::ExtendedReports xr2;
+ xr2.SetSenderSsrc(kSenderSsrc);
+ xr2.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp2, kDelayNtp));
+
+ receiver.IncomingPacket(xr2.Build());
+
+ // Check that the non-sender RTT stats are based on 2 measurements, and that
+ // the values are as expected.
+ non_sender_rtt_stats = receiver.GetNonSenderRTT();
+ EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_NEAR(non_sender_rtt_stats.round_trip_time()->ms(), kRttMs, 1);
+ EXPECT_EQ(non_sender_rtt_stats.round_trip_time_measurements(), 2);
+ EXPECT_NEAR(non_sender_rtt_stats.total_round_trip_time().ms(), 2 * kRttMs, 2);
+}
+
+// Test that the receiver RTT stat resets when receiving a SR without XR. This
+// behavior is described in the standard, see
+// https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime.
+TEST(RtcpReceiverTest, ReceiverRttResetOnSrWithoutXr) {
+ ReceiverMocks mocks;
+ auto config = DefaultConfiguration(&mocks);
+ config.non_sender_rtt_measurement = true;
+ RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl);
+ receiver.SetRemoteSSRC(kSenderSsrc);
+
+ Random rand(0x0123456789abcdef);
+ const int64_t kRttMs = rand.Rand(1, 9 * 3600 * 1000);
+ const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff);
+ const int64_t kDelayMs = CompactNtpRttToMs(kDelayNtp);
+ NtpTime now = mocks.clock.CurrentNtpTime();
+ uint32_t sent_ntp = CompactNtp(now);
+ mocks.clock.AdvanceTimeMilliseconds(kRttMs + kDelayMs);
+
+ rtcp::ExtendedReports xr;
+ xr.SetSenderSsrc(kSenderSsrc);
+ xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp));
+
+ receiver.IncomingPacket(xr.Build());
+
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_NEAR(non_sender_rtt_stats.round_trip_time()->ms(), kRttMs, 1);
+
+ // Generate a SR without XR.
+ rtcp::ReportBlock rb;
+ rb.SetMediaSsrc(kReceiverMainSsrc);
+ rtcp::SenderReport sr;
+ sr.SetSenderSsrc(kSenderSsrc);
+ sr.AddReportBlock(rb);
+ EXPECT_CALL(mocks.rtp_rtcp_impl, OnReceivedRtcpReportBlocks);
+ EXPECT_CALL(mocks.bandwidth_observer, OnReceivedRtcpReceiverReport);
+
+ receiver.IncomingPacket(sr.Build());
+
+ // Check that the non-sender RTT stat is not set.
+ non_sender_rtt_stats = receiver.GetNonSenderRTT();
+ EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value());
+}
+
+// Test that the receiver RTT stat resets when receiving a DLRR with a timestamp
+// of zero. This behavior is described in the standard, see
+// https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime.
+TEST(RtcpReceiverTest, ReceiverRttResetOnDlrrWithZeroTimestamp) {
+ ReceiverMocks mocks;
+ auto config = DefaultConfiguration(&mocks);
+ config.non_sender_rtt_measurement = true;
+ RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl);
+ receiver.SetRemoteSSRC(kSenderSsrc);
+
+ Random rand(0x0123456789abcdef);
+ const int64_t kRttMs = rand.Rand(1, 9 * 3600 * 1000);
+ const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff);
+ const int64_t kDelayMs = CompactNtpRttToMs(kDelayNtp);
+ NtpTime now = mocks.clock.CurrentNtpTime();
+ uint32_t sent_ntp = CompactNtp(now);
+ mocks.clock.AdvanceTimeMilliseconds(kRttMs + kDelayMs);
+
+ rtcp::ExtendedReports xr;
+ xr.SetSenderSsrc(kSenderSsrc);
+ xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp));
+
+ receiver.IncomingPacket(xr.Build());
+
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_NEAR(non_sender_rtt_stats.round_trip_time()->ms(), kRttMs, 1);
+
+ // Generate an XR+DLRR with zero timestamp.
+ rtcp::ExtendedReports xr2;
+ xr2.SetSenderSsrc(kSenderSsrc);
+ xr2.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, 0, kDelayMs));
+
+ receiver.IncomingPacket(xr2.Build());
+
+ // Check that the non-sender RTT stat is not set.
+ non_sender_rtt_stats = receiver.GetNonSenderRTT();
+ EXPECT_FALSE(non_sender_rtt_stats.round_trip_time().has_value());
+}
+
+// Check that the receiver RTT works correctly when the remote SSRC changes.
+TEST(RtcpReceiverTest, ReceiverRttWithMultipleRemoteSsrcs) {
+ ReceiverMocks mocks;
+ auto config = DefaultConfiguration(&mocks);
+ config.non_sender_rtt_measurement = false;
+ RTCPReceiver receiver(config, &mocks.rtp_rtcp_impl);
+ receiver.SetRemoteSSRC(kSenderSsrc);
+ receiver.SetNonSenderRttMeasurement(true);
+
+ Random rand(0x0123456789abcdef);
+ const int64_t kRttMs = rand.Rand(1, 9 * 3600 * 1000);
+ const uint32_t kDelayNtp = rand.Rand(0, 0x7fffffff);
+ const int64_t kDelayMs = CompactNtpRttToMs(kDelayNtp);
+ NtpTime now = mocks.clock.CurrentNtpTime();
+ uint32_t sent_ntp = CompactNtp(now);
+ mocks.clock.AdvanceTimeMilliseconds(kRttMs + kDelayMs);
+
+ rtcp::ExtendedReports xr;
+ xr.SetSenderSsrc(kSenderSsrc);
+ xr.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp, kDelayNtp));
+
+ receiver.IncomingPacket(xr.Build());
+
+ // Generate an XR report for another SSRC.
+ const int64_t kRttMs2 = rand.Rand(1, 9 * 3600 * 1000);
+ const uint32_t kDelayNtp2 = rand.Rand(0, 0x7fffffff);
+ const int64_t kDelayMs2 = CompactNtpRttToMs(kDelayNtp2);
+ NtpTime now2 = mocks.clock.CurrentNtpTime();
+ uint32_t sent_ntp2 = CompactNtp(now2);
+ mocks.clock.AdvanceTimeMilliseconds(kRttMs2 + kDelayMs2);
+
+ rtcp::ExtendedReports xr2;
+ xr2.SetSenderSsrc(kSenderSsrc + 1);
+ xr2.AddDlrrItem(ReceiveTimeInfo(kReceiverMainSsrc, sent_ntp2, kDelayNtp2));
+
+ receiver.IncomingPacket(xr2.Build());
+
+ // Check that the non-sender RTT stats match the first XR.
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ receiver.GetNonSenderRTT();
+ EXPECT_TRUE(non_sender_rtt_stats.round_trip_time().has_value());
+ EXPECT_NEAR(non_sender_rtt_stats.round_trip_time()->ms(), kRttMs, 1);
+ EXPECT_FALSE(non_sender_rtt_stats.total_round_trip_time().IsZero());
+ EXPECT_GT(non_sender_rtt_stats.round_trip_time_measurements(), 0);
+
+ // Change the remote SSRC and check that the stats match the second XR.
+ receiver.SetRemoteSSRC(kSenderSsrc + 1);
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats2 =
+ receiver.GetNonSenderRTT();
+ EXPECT_TRUE(non_sender_rtt_stats2.round_trip_time().has_value());
+ EXPECT_NEAR(non_sender_rtt_stats2.round_trip_time()->ms(), kRttMs2, 1);
+ EXPECT_FALSE(non_sender_rtt_stats2.total_round_trip_time().IsZero());
+ EXPECT_GT(non_sender_rtt_stats2.round_trip_time_measurements(), 0);
}
TEST(RtcpReceiverTest, ConsumeReceivedXrReferenceTimeInfoInitiallyEmpty) {
diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
index a9bd671..3dda8c6 100644
--- a/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
+++ b/modules/rtp_rtcp/source/rtp_rtcp_impl.cc
@@ -603,6 +603,12 @@
return absl::nullopt;
}
+absl::optional<RtpRtcpInterface::NonSenderRttStats>
+ModuleRtpRtcpImpl::GetNonSenderRttStats() const {
+ // This is not implemented for this legacy class.
+ return absl::nullopt;
+}
+
// (REMB) Receiver Estimated Max Bitrate.
void ModuleRtpRtcpImpl::SetRemb(int64_t bitrate_bps,
std::vector<uint32_t> ssrcs) {
diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/modules/rtp_rtcp/source/rtp_rtcp_impl.h
index 2ffe013..655691c 100644
--- a/modules/rtp_rtcp/source/rtp_rtcp_impl.h
+++ b/modules/rtp_rtcp/source/rtp_rtcp_impl.h
@@ -200,6 +200,9 @@
// which is the SSRC of the corresponding outbound RTP stream, is unique.
std::vector<ReportBlockData> GetLatestReportBlockData() const override;
absl::optional<SenderReportStats> GetSenderReportStats() const override;
+ // Round trip time statistics computed from the XR block contained in the last
+ // report.
+ absl::optional<NonSenderRttStats> GetNonSenderRttStats() const override;
// (REMB) Receiver Estimated Max Bitrate.
void SetRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs) override;
diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc b/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc
index 88de8db..73ae138 100644
--- a/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc
+++ b/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc
@@ -552,6 +552,17 @@
return absl::nullopt;
}
+absl::optional<RtpRtcpInterface::NonSenderRttStats>
+ModuleRtpRtcpImpl2::GetNonSenderRttStats() const {
+ RTCPReceiver::NonSenderRttStats non_sender_rtt_stats =
+ rtcp_receiver_.GetNonSenderRTT();
+ return {{
+ non_sender_rtt_stats.round_trip_time(),
+ non_sender_rtt_stats.total_round_trip_time(),
+ non_sender_rtt_stats.round_trip_time_measurements(),
+ }};
+}
+
// (REMB) Receiver Estimated Max Bitrate.
void ModuleRtpRtcpImpl2::SetRemb(int64_t bitrate_bps,
std::vector<uint32_t> ssrcs) {
diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl2.h b/modules/rtp_rtcp/source/rtp_rtcp_impl2.h
index a503895..57a3fd1 100644
--- a/modules/rtp_rtcp/source/rtp_rtcp_impl2.h
+++ b/modules/rtp_rtcp/source/rtp_rtcp_impl2.h
@@ -213,6 +213,7 @@
// which is the SSRC of the corresponding outbound RTP stream, is unique.
std::vector<ReportBlockData> GetLatestReportBlockData() const override;
absl::optional<SenderReportStats> GetSenderReportStats() const override;
+ absl::optional<NonSenderRttStats> GetNonSenderRttStats() const override;
// (REMB) Receiver Estimated Max Bitrate.
void SetRemb(int64_t bitrate_bps, std::vector<uint32_t> ssrcs) override;
diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc b/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc
index 70c9a1c..57b564a 100644
--- a/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc
+++ b/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc
@@ -616,12 +616,12 @@
/*is_last=*/1)));
}
-// Checks that the sender report stats are not available if no RTCP SR was sent.
+// Checks that the remote sender stats are not available if no RTCP SR was sent.
TEST_F(RtpRtcpImplTest, SenderReportStatsNotAvailable) {
EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Eq(absl::nullopt));
}
-// Checks that the sender report stats are available if an RTCP SR was sent.
+// Checks that the remote sender stats are available if an RTCP SR was sent.
TEST_F(RtpRtcpImplTest, SenderReportStatsAvailable) {
// Send a frame in order to send an SR.
SendFrame(&sender_, sender_video_.get(), kBaseLayerTid);
@@ -630,7 +630,7 @@
EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Not(Eq(absl::nullopt)));
}
-// Checks that the sender report stats are not available if an RTCP SR with an
+// Checks that the remote sender stats are not available if an RTCP SR with an
// unexpected SSRC is received.
TEST_F(RtpRtcpImplTest, SenderReportStatsNotUpdatedWithUnexpectedSsrc) {
constexpr uint32_t kUnexpectedSenderSsrc = 0x87654321;
@@ -670,7 +670,7 @@
Field(&SenderReportStats::bytes_sent, Eq(kOctetCount)))));
}
-// Checks that the sender report stats count equals the number of sent RTCP SRs.
+// Checks that the remote sender stats count equals the number of sent RTCP SRs.
TEST_F(RtpRtcpImplTest, SenderReportStatsCount) {
using SenderReportStats = RtpRtcpInterface::SenderReportStats;
// Send a frame in order to send an SR.
@@ -685,7 +685,7 @@
Optional(Field(&SenderReportStats::reports_count, Eq(2u))));
}
-// Checks that the sender report stats include a valid arrival time if an RTCP
+// Checks that the remote sender stats include a valid arrival time if an RTCP
// SR was sent.
TEST_F(RtpRtcpImplTest, SenderReportStatsArrivalTimestampSet) {
// Send a frame in order to send an SR.
diff --git a/modules/rtp_rtcp/source/rtp_rtcp_interface.h b/modules/rtp_rtcp/source/rtp_rtcp_interface.h
index df5593c..13ebb0a 100644
--- a/modules/rtp_rtcp/source/rtp_rtcp_interface.h
+++ b/modules/rtp_rtcp/source/rtp_rtcp_interface.h
@@ -153,7 +153,7 @@
// Stats for RTCP sender reports (SR) for a specific SSRC.
// Refer to https://tools.ietf.org/html/rfc3550#section-6.4.1.
struct SenderReportStats {
- // Arrival NPT timestamp for the last received RTCP SR.
+ // Arrival NTP timestamp for the last received RTCP SR.
NtpTime last_arrival_timestamp;
// Received (a.k.a., remote) NTP timestamp for the last received RTCP SR.
NtpTime last_remote_timestamp;
@@ -170,6 +170,16 @@
// https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-reportssent.
uint64_t reports_count;
};
+ // Stats about the non-sender SSRC, based on RTCP extended reports (XR).
+ // Refer to https://datatracker.ietf.org/doc/html/rfc3611#section-2.
+ struct NonSenderRttStats {
+ // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptime
+ absl::optional<TimeDelta> round_trip_time;
+ // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-totalroundtriptime
+ TimeDelta total_round_trip_time = TimeDelta::Zero();
+ // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-roundtriptimemeasurements
+ int round_trip_time_measurements = 0;
+ };
// **************************************************************************
// Receiver functions
@@ -403,6 +413,8 @@
virtual std::vector<ReportBlockData> GetLatestReportBlockData() const = 0;
// Returns stats based on the received RTCP SRs.
virtual absl::optional<SenderReportStats> GetSenderReportStats() const = 0;
+ // Returns non-sender RTT stats, based on DLRR.
+ virtual absl::optional<NonSenderRttStats> GetNonSenderRttStats() const = 0;
// (REMB) Receiver Estimated Max Bitrate.
// Schedules sending REMB on next and following sender/receiver reports.
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index c2b453e..1f8b28d 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -436,6 +436,14 @@
stats->remote_timestamp = static_cast<double>(
voice_receiver_info.last_sender_report_remote_timestamp_ms.value());
stats->reports_sent = voice_receiver_info.sender_reports_reports_count;
+ if (voice_receiver_info.round_trip_time) {
+ stats->round_trip_time =
+ voice_receiver_info.round_trip_time->seconds<double>();
+ }
+ stats->round_trip_time_measurements =
+ voice_receiver_info.round_trip_time_measurements;
+ stats->total_round_trip_time =
+ voice_receiver_info.total_round_trip_time.seconds<double>();
return stats;
}
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index 6af724e..762011d 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -936,7 +936,10 @@
"remote-outbound-rtp",
&local_id,
&remote_timestamp,
- &reports_sent)
+ &reports_sent,
+ &round_trip_time,
+ &round_trip_time_measurements,
+ &total_round_trip_time)
// clang-format on
RTCRemoteOutboundRtpStreamStats::RTCRemoteOutboundRtpStreamStats(
@@ -950,14 +953,20 @@
: RTCSentRtpStreamStats(std::move(id), timestamp_us),
local_id("localId"),
remote_timestamp("remoteTimestamp"),
- reports_sent("reportsSent") {}
+ reports_sent("reportsSent"),
+ round_trip_time("roundTripTime"),
+ round_trip_time_measurements("roundTripTimeMeasurements"),
+ total_round_trip_time("totalRoundTripTime") {}
RTCRemoteOutboundRtpStreamStats::RTCRemoteOutboundRtpStreamStats(
const RTCRemoteOutboundRtpStreamStats& other)
: RTCSentRtpStreamStats(other),
local_id(other.local_id),
remote_timestamp(other.remote_timestamp),
- reports_sent(other.reports_sent) {}
+ reports_sent(other.reports_sent),
+ round_trip_time(other.round_trip_time),
+ round_trip_time_measurements(other.round_trip_time_measurements),
+ total_round_trip_time(other.total_round_trip_time) {}
RTCRemoteOutboundRtpStreamStats::~RTCRemoteOutboundRtpStreamStats() {}