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() {}