Integrate `RttSimulator` into video timing simulator

In this change, the previously landed `RttSimulator` is integrated into
the video timing simulator. The `RtcEventLogDriver` receives the
smoothed session-level RTT estimates, and distributes them to the
streams. The streams use the RTTs to adapt the NACK module (inside the
`Assembler`) and the jitter estimator (inside the `RenderingTracker`).

Bug: b/423646186
Change-Id: I792fcfea7ac7f4bf8d97a73f674a7d5ca8c25727
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/479821
Commit-Queue: Rasmus Brandt <brandtr@webrtc.org>
Reviewed-by: Åsa Persson <asapersson@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47960}
diff --git a/video/timing/simulator/assembler.cc b/video/timing/simulator/assembler.cc
index af881d0..93d4baa 100644
--- a/video/timing/simulator/assembler.cc
+++ b/video/timing/simulator/assembler.cc
@@ -19,6 +19,7 @@
 #include "api/rtp_parameters.h"
 #include "api/sequence_checker.h"
 #include "api/task_queue/task_queue_base.h"
+#include "api/units/time_delta.h"
 #include "api/video/encoded_frame.h"
 #include "api/video/video_codec_type.h"
 #include "call/video_receive_stream.h"
@@ -96,6 +97,11 @@
   rtp_video_stream_receiver2_.FrameDecoded(frame_id);
 }
 
+void Assembler::UpdateMaxRtt(TimeDelta max_rtt) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  rtp_video_stream_receiver2_.UpdateRtt(max_rtt.ms());
+}
+
 void Assembler::OnCompleteFrame(std::unique_ptr<EncodedFrame> encoded_frame) {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
   // We rename this callback from `OnCompleteFrame` to `OnAssembledFrame`, since
diff --git a/video/timing/simulator/assembler.h b/video/timing/simulator/assembler.h
index 2e3fe11..f85c230 100644
--- a/video/timing/simulator/assembler.h
+++ b/video/timing/simulator/assembler.h
@@ -20,6 +20,7 @@
 #include "api/call/transport.h"
 #include "api/environment/environment.h"
 #include "api/sequence_checker.h"
+#include "api/units/time_delta.h"
 #include "api/video/encoded_frame.h"
 #include "call/video_receive_stream.h"
 #include "modules/rtp_rtcp/include/receive_statistics.h"
@@ -80,6 +81,8 @@
   // so that it can be flushed from the `PacketBuffer`.
   void OnDecodedFrameId(int64_t frame_id) override;
 
+  void UpdateMaxRtt(TimeDelta max_rtt);
+
  private:
   // Trivially implements `Transport`.
   // We need to implement this due to an RTC_DCHECK in rtcp_sender.cc.
diff --git a/video/timing/simulator/decodability_simulator.cc b/video/timing/simulator/decodability_simulator.cc
index acabf2e..d6d8b51 100644
--- a/video/timing/simulator/decodability_simulator.cc
+++ b/video/timing/simulator/decodability_simulator.cc
@@ -20,6 +20,7 @@
 #include "api/environment/environment.h"
 #include "api/sequence_checker.h"
 #include "api/units/data_size.h"
+#include "api/units/time_delta.h"
 #include "api/units/timestamp.h"
 #include "api/video/encoded_frame.h"
 #include "logging/rtc_event_log/rtc_event_log_parser.h"
@@ -150,6 +151,11 @@
     receiver_.InsertSimulatedPacket(simulated_packet);
   }
 
+  void UpdateMaxRtt(TimeDelta max_rtt) override {
+    RTC_DCHECK_RUN_ON(&sequence_checker_);
+    assembler_.UpdateMaxRtt(max_rtt);
+  }
+
   void Close() override {
     RTC_DCHECK_RUN_ON(&sequence_checker_);
     DecodabilitySimulator::Stream stream = collector_.GetStream();
diff --git a/video/timing/simulator/rendering_simulator.cc b/video/timing/simulator/rendering_simulator.cc
index 9bb5527..6ebe186 100644
--- a/video/timing/simulator/rendering_simulator.cc
+++ b/video/timing/simulator/rendering_simulator.cc
@@ -207,6 +207,12 @@
     receiver_.InsertSimulatedPacket(simulated_packet);
   }
 
+  void UpdateMaxRtt(TimeDelta max_rtt) override {
+    RTC_DCHECK_RUN_ON(&sequence_checker_);
+    assembler_.UpdateMaxRtt(max_rtt);
+    tracker_.UpdateMaxRtt(max_rtt);
+  }
+
   void Close() override {
     RTC_DCHECK_RUN_ON(&sequence_checker_);
     RenderingSimulator::Stream stream = collector_.GetStream();
diff --git a/video/timing/simulator/rendering_simulator_unittest.cc b/video/timing/simulator/rendering_simulator_unittest.cc
index b7e813e..eef19e1 100644
--- a/video/timing/simulator/rendering_simulator_unittest.cc
+++ b/video/timing/simulator/rendering_simulator_unittest.cc
@@ -479,7 +479,7 @@
                                [](const auto& e) {
                                  return e.ArrivalTimestamp().IsFinite();
                                }),
-              Eq(960));
+              Eq(987));
 
   // Find the last rendered frame.
   EXPECT_TRUE(absl::c_is_sorted(stream.frames,
@@ -511,15 +511,14 @@
            .last_packet_arrival_timestamp = Timestamp::Millis(821457158),
            // Frame timestamps.
            .assembled_timestamp = Timestamp::Millis(821457158),
-           .render_timestamp = Timestamp::Millis(821457178),
-           .decoded_timestamp = Timestamp::Micros(821457168065),
-           .rendered_timestamp = Timestamp::Micros(821457168065),
+           .render_timestamp = Timestamp::Millis(821457378),
+           .decoded_timestamp = Timestamp::Micros(821457368242),
+           .rendered_timestamp = Timestamp::Micros(821457368242),
            // Jitter buffer information.
            .frames_dropped = 0,
-           // The value below is unreasonably low, we should fix this.
-           .jitter_buffer_minimum_delay = TimeDelta::Micros(13085),
-           .jitter_buffer_target_delay = TimeDelta::Micros(13085),
-           .jitter_buffer_delay = TimeDelta::Micros(21065)}));
+           .jitter_buffer_minimum_delay = TimeDelta::Micros(213371),
+           .jitter_buffer_target_delay = TimeDelta::Micros(213371),
+           .jitter_buffer_delay = TimeDelta::Micros(221242)}));
 }
 
 }  // namespace
diff --git a/video/timing/simulator/rendering_tracker.cc b/video/timing/simulator/rendering_tracker.cc
index 0fc6eb3..88fcbcd 100644
--- a/video/timing/simulator/rendering_tracker.cc
+++ b/video/timing/simulator/rendering_tracker.cc
@@ -116,6 +116,11 @@
   }
 }
 
+void RenderingTracker::UpdateMaxRtt(TimeDelta max_rtt) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  video_stream_buffer_controller_.UpdateRtt(max_rtt.ms());
+}
+
 void RenderingTracker::OnEncodedFrame(
     std::unique_ptr<EncodedFrame> encoded_frame) {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
diff --git a/video/timing/simulator/rendering_tracker.h b/video/timing/simulator/rendering_tracker.h
index 0e0dc75..7acaf48 100644
--- a/video/timing/simulator/rendering_tracker.h
+++ b/video/timing/simulator/rendering_tracker.h
@@ -80,6 +80,8 @@
   // any rendered frames to the `observer_`.
   void OnAssembledFrame(std::unique_ptr<EncodedFrame> assembled_frame) override;
 
+  void UpdateMaxRtt(TimeDelta max_rtt);
+
  private:
   struct VideoStreamBufferControllerObserverDecodableStats {
     TimeDelta jitter_buffer_delay = TimeDelta::Zero();
diff --git a/video/timing/simulator/rtc_event_log_driver.cc b/video/timing/simulator/rtc_event_log_driver.cc
index 3ca305a..c51a766 100644
--- a/video/timing/simulator/rtc_event_log_driver.cc
+++ b/video/timing/simulator/rtc_event_log_driver.cc
@@ -13,9 +13,12 @@
 #include <cstdint>
 #include <memory>
 #include <optional>
+#include <set>
 #include <utility>
 
 #include "absl/base/nullability.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
 #include "absl/functional/any_invocable.h"
 #include "absl/strings/string_view.h"
 #include "api/environment/environment_factory.h"
@@ -32,6 +35,7 @@
 #include "rtc_base/logging.h"
 #include "test/time_controller/simulated_time_task_queue_controller.h"
 #include "video/timing/simulator/rtp_packet_simulator.h"
+#include "video/timing/simulator/rtt_simulator.h"
 
 namespace webrtc::video_timing_simulator {
 
@@ -52,15 +56,26 @@
       simulator_queue_(time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
           "simulator_queue",
           TaskQueueFactory::Priority::kNormal)),
-      packet_simulator_(env_) {
+      packet_simulator_(env_),
+      rtt_callback_adapter_(this) {
   RTC_DCHECK(stream_factory_) << "stream_factory must be provided";
 
+  bool done = false;
+  simulator_queue_->PostTask([this, &done]() {
+    RTC_DCHECK_RUN_ON(simulator_queue_.get());
+    rtt_simulator_ = std::make_unique<RttSimulator>(
+        env_, simulator_queue_.get(), &rtt_callback_adapter_);
+    done = true;
+  });
+  time_controller_.AdvanceTime(TimeDelta::Zero());
+  RTC_DCHECK(done);
+
   // Config events.
   processor_.AddEvents(
       parsed_log_.video_recv_configs(),
-      [&](const auto& config) { OnLoggedVideoRecvConfig(config); });
+      [this](const auto& config) { OnLoggedVideoRecvConfig(config); });
 
-  // Video packet events (media + RTX).
+  // RTP video packet events (media + RTX).
   for (const auto& stream : parsed_log_.incoming_rtp_packets_by_ssrc()) {
     bool is_video = parsed_log_.GetMediaType(
                         stream.ssrc, PacketDirection::kIncomingPacket) ==
@@ -68,10 +83,44 @@
     if (!is_video) {
       continue;
     }
-    processor_.AddEvents(stream.incoming_packets, [&](const auto& packet) {
+    processor_.AddEvents(stream.incoming_packets, [this](const auto& packet) {
       OnLoggedRtpPacketIncoming(packet);
     });
   }
+
+  // RTCP packet events (outgoing).
+  processor_.AddEvents(
+      parsed_log_.sender_reports(PacketDirection::kOutgoingPacket),
+      [this](const auto& packet) {
+        OnLoggedRtcpPacketSenderReportOutgoing(packet);
+      },
+      PacketDirection::kOutgoingPacket);
+  processor_.AddEvents(
+      parsed_log_.extended_reports(PacketDirection::kOutgoingPacket),
+      [this](const auto& packet) {
+        OnLoggedRtcpPacketExtendedReportsOutgoing(packet);
+      },
+      PacketDirection::kOutgoingPacket);
+
+  // RTCP packet events (incoming).
+  processor_.AddEvents(
+      parsed_log_.sender_reports(PacketDirection::kIncomingPacket),
+      [this](const auto& packet) {
+        OnLoggedRtcpPacketSenderReportIncoming(packet);
+      },
+      PacketDirection::kIncomingPacket);
+  processor_.AddEvents(
+      parsed_log_.receiver_reports(PacketDirection::kIncomingPacket),
+      [this](const auto& packet) {
+        OnLoggedRtcpPacketReceiverReportIncoming(packet);
+      },
+      PacketDirection::kIncomingPacket);
+  processor_.AddEvents(
+      parsed_log_.extended_reports(PacketDirection::kIncomingPacket),
+      [this](const auto& packet) {
+        OnLoggedRtcpPacketExtendedReportsIncoming(packet);
+      },
+      PacketDirection::kIncomingPacket);
 }
 
 RtcEventLogDriver::~RtcEventLogDriver() = default;
@@ -200,13 +249,62 @@
   });
 }
 
+void RtcEventLogDriver::OnLoggedRtcpPacketSenderReportOutgoing(
+    const LoggedRtcpPacketSenderReport& packet) {
+  HandleEvent(packet.log_time(), [this, packet]() {
+    RTC_DCHECK_RUN_ON(simulator_queue_.get());
+    rtt_simulator_->OnOutgoingSenderReport(packet);
+  });
+}
+
+void RtcEventLogDriver::OnLoggedRtcpPacketExtendedReportsOutgoing(
+    const LoggedRtcpPacketExtendedReports& packet) {
+  HandleEvent(packet.log_time(), [this, packet]() {
+    RTC_DCHECK_RUN_ON(simulator_queue_.get());
+    rtt_simulator_->OnOutgoingExtendedReports(packet);
+  });
+}
+
+void RtcEventLogDriver::OnLoggedRtcpPacketSenderReportIncoming(
+    const LoggedRtcpPacketSenderReport& packet) {
+  HandleEvent(packet.log_time(), [this, packet]() {
+    RTC_DCHECK_RUN_ON(simulator_queue_.get());
+    rtt_simulator_->OnIncomingSenderReport(packet);
+  });
+}
+
+void RtcEventLogDriver::OnLoggedRtcpPacketReceiverReportIncoming(
+    const LoggedRtcpPacketReceiverReport& packet) {
+  HandleEvent(packet.log_time(), [this, packet]() {
+    RTC_DCHECK_RUN_ON(simulator_queue_.get());
+    rtt_simulator_->OnIncomingReceiverReport(packet);
+  });
+}
+
+void RtcEventLogDriver::OnLoggedRtcpPacketExtendedReportsIncoming(
+    const LoggedRtcpPacketExtendedReports& packet) {
+  HandleEvent(packet.log_time(), [this, packet]() {
+    RTC_DCHECK_RUN_ON(simulator_queue_.get());
+    rtt_simulator_->OnIncomingExtendedReports(packet);
+  });
+}
+
+void RtcEventLogDriver::UpdateMaxRtt(TimeDelta max_rtt) {
+  RTC_DCHECK_RUN_ON(simulator_queue_.get());
+  for (auto& stream : streams_) {
+    stream.second->UpdateMaxRtt(max_rtt);
+  }
+}
+
 void RtcEventLogDriver::TeardownOnQueue() {
   RTC_DCHECK_RUN_ON(simulator_queue_.get());
   for (auto& stream : streams_) {
     stream.second->Close();
   }
+  all_known_ssrcs_.clear();
   receiving_streams_.clear();
   streams_.clear();
+  rtt_simulator_.reset();
 }
 
 }  // namespace webrtc::video_timing_simulator
diff --git a/video/timing/simulator/rtc_event_log_driver.h b/video/timing/simulator/rtc_event_log_driver.h
index 48a8eee..ab311e9 100644
--- a/video/timing/simulator/rtc_event_log_driver.h
+++ b/video/timing/simulator/rtc_event_log_driver.h
@@ -33,6 +33,7 @@
 #include "rtc_base/thread_annotations.h"
 #include "test/time_controller/simulated_time_task_queue_controller.h"
 #include "video/timing/simulator/rtp_packet_simulator.h"
+#include "video/timing/simulator/rtt_simulator.h"
 
 namespace webrtc::video_timing_simulator {
 
@@ -49,9 +50,12 @@
 // running on the provided task queue(s).
 //
 // TODO: b/423646186 - Improvements:
+//  * Consider splitting the `RtcEventLogDriver` class into two:
+//    - One for orchestration (queue, event processor,
+//      event handler registration, ...).
+//    - One for media simulation (RTTs, streams, ...). Running on the queue.
 //  * Handle `LogSegment`s.
 //  * Handle stop events.
-//  * Parse RTT updates from RTCPs.
 class RtcEventLogDriver {
  public:
   // Configuration for the `RtcEventLogDriver` itself.
@@ -73,6 +77,8 @@
     // Insert `simulated_packet` into the stream.
     virtual void InsertSimulatedPacket(
         const RtpPacketSimulator::SimulatedPacket& simulated_packet) = 0;
+    // Propagate an RTT update to the stream components.
+    virtual void UpdateMaxRtt(TimeDelta max_rtt) = 0;
     // Notify the stream that no more packets will be inserted.
     virtual void Close() = 0;
   };
@@ -104,6 +110,19 @@
   }
 
  private:
+  class RttCallbackAdapter : public SimulatedRttCallback {
+   public:
+    explicit RttCallbackAdapter(RtcEventLogDriver* absl_nonnull driver)
+        : driver_(*driver) {}
+    ~RttCallbackAdapter() override = default;
+    void OnMaxRttUpdate(TimeDelta max_rtt) override {
+      driver_.UpdateMaxRtt(max_rtt);
+    }
+
+   private:
+    RtcEventLogDriver& driver_;
+  };
+
   // Simulation.
   // Sets the `time_controller_` simulated time to `log_timestamp`, thus
   // executing all relevant tasks on the `simulator_queue_`.
@@ -117,6 +136,18 @@
   // RtcEventProcessor callbacks (running on main thread).
   void OnLoggedVideoRecvConfig(const LoggedVideoRecvConfig& config);
   void OnLoggedRtpPacketIncoming(const LoggedRtpPacketIncoming& packet);
+  void OnLoggedRtcpPacketSenderReportOutgoing(
+      const LoggedRtcpPacketSenderReport& packet);
+  void OnLoggedRtcpPacketExtendedReportsOutgoing(
+      const LoggedRtcpPacketExtendedReports& packet);
+  void OnLoggedRtcpPacketSenderReportIncoming(
+      const LoggedRtcpPacketSenderReport& packet);
+  void OnLoggedRtcpPacketReceiverReportIncoming(
+      const LoggedRtcpPacketReceiverReport& packet);
+  void OnLoggedRtcpPacketExtendedReportsIncoming(
+      const LoggedRtcpPacketExtendedReports& packet);
+
+  void UpdateMaxRtt(TimeDelta max_rtt);
 
   // Destroy owned objects on `simulator_queue_`.
   void TeardownOnQueue() RTC_RUN_ON(simulator_queue_);
@@ -135,6 +166,8 @@
   std::optional<Timestamp> prev_log_timestamp_;
   std::unique_ptr<TaskQueueBase, TaskQueueDeleter> simulator_queue_;
   RtpPacketSimulator packet_simulator_ RTC_GUARDED_BY(simulator_queue_);
+  RttCallbackAdapter rtt_callback_adapter_ RTC_GUARDED_BY(simulator_queue_);
+  std::unique_ptr<RttSimulator> rtt_simulator_ RTC_GUARDED_BY(simulator_queue_);
   // Owned streams. Keyed by `ssrc`, so that they can be replaced if needed.
   absl::flat_hash_map<uint32_t, std::unique_ptr<StreamInterface>> streams_
       RTC_GUARDED_BY(simulator_queue_);
diff --git a/video/timing/simulator/rtc_event_log_driver_unittest.cc b/video/timing/simulator/rtc_event_log_driver_unittest.cc
index 50ac1f2..9922fc6 100644
--- a/video/timing/simulator/rtc_event_log_driver_unittest.cc
+++ b/video/timing/simulator/rtc_event_log_driver_unittest.cc
@@ -17,8 +17,17 @@
 #include "absl/strings/string_view.h"
 #include "api/environment/environment.h"
 #include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
 #include "logging/rtc_event_log/rtc_event_log_parser.h"
+#include "modules/rtp_rtcp/source/ntp_time_util.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/report_block.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/rrtr.h"
+#include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h"
 #include "rtc_base/checks.h"
+#include "system_wrappers/include/ntp_time.h"
 #include "test/gmock.h"
 #include "test/gtest.h"
 #include "video/timing/simulator/rtp_packet_simulator.h"
@@ -39,12 +48,16 @@
 
 constexpr uint16_t kRtxOsn = 823;
 
+constexpr uint32_t kSenderSsrc = 123456;
+constexpr uint32_t kReceiverSsrc = 987654;
+
 class MockRtcEventLogDriverStream : public RtcEventLogDriver::StreamInterface {
  public:
   MOCK_METHOD(void,
               InsertSimulatedPacket,
               (const RtpPacketSimulator::SimulatedPacket& simulated_packet),
               (override));
+  MOCK_METHOD(void, UpdateMaxRtt, (TimeDelta max_rtt), (override));
   MOCK_METHOD(void, Close, (), (override));
 };
 
@@ -272,5 +285,134 @@
   driver.Simulate();
 }
 
+rtcp::SenderReport CreateSenderReport(NtpTime ntp) {
+  rtcp::SenderReport sr;
+  sr.SetSenderSsrc(kSenderSsrc);
+  sr.SetNtp(ntp);
+  return sr;
+}
+
+rtcp::SenderReport CreateSenderReportWithReportBlock(
+    NtpTime ntp,
+    uint32_t last_sr,
+    uint32_t delay_since_last_sr) {
+  rtcp::SenderReport sr;
+  sr.SetSenderSsrc(kReceiverSsrc);
+  sr.SetNtp(ntp);
+  rtcp::ReportBlock block;
+  block.SetMediaSsrc(kSenderSsrc);
+  block.SetLastSr(last_sr);
+  block.SetDelayLastSr(delay_since_last_sr);
+  sr.AddReportBlock(block);
+  return sr;
+}
+
+rtcp::ReceiverReport CreateReceiverReport(uint32_t last_sr,
+                                          uint32_t delay_since_last_sr) {
+  rtcp::ReceiverReport rr;
+  rr.SetSenderSsrc(kReceiverSsrc);
+  rtcp::ReportBlock block;
+  block.SetMediaSsrc(kSenderSsrc);
+  block.SetLastSr(last_sr);
+  block.SetDelayLastSr(delay_since_last_sr);
+  rr.AddReportBlock(block);
+  return rr;
+}
+
+rtcp::ExtendedReports CreateExtendedReportsWithRrtr(NtpTime ntp) {
+  rtcp::ExtendedReports xr;
+  xr.SetSenderSsrc(kReceiverSsrc);
+  rtcp::Rrtr rrtr;
+  rrtr.SetNtp(ntp);
+  xr.SetRrtr(rrtr);
+  return xr;
+}
+
+rtcp::ExtendedReports CreateExtendedReportsWithDlrr(
+    uint32_t last_rr,
+    uint32_t delay_since_last_rr) {
+  rtcp::ExtendedReports xr;
+  xr.SetSenderSsrc(kSenderSsrc);
+  rtcp::ReceiveTimeInfo dlrr_block;
+  dlrr_block.ssrc = kReceiverSsrc;
+  dlrr_block.last_rr = last_rr;
+  dlrr_block.delay_since_last_rr = delay_since_last_rr;
+  xr.AddDlrrItem(dlrr_block);
+  return xr;
+}
+
+TEST_F(RtcEventLogDriverTest, SenderCalculatesRttFromIncomingRr) {
+  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
+
+  // Outgoing SR.
+  NtpTime ntp = parsed_log_builder_.CurrentNtpTime();
+  parsed_log_builder_.LogRtcpPacketOutgoing(CreateSenderReport(ntp));
+
+  // Incoming RR: 50ms delay, arrives after 150ms => RTT is 100ms.
+  parsed_log_builder_.AdvanceTime(TimeDelta::Millis(150));
+  uint32_t last_sr = CompactNtp(ntp);
+  uint32_t delay_since_last_sr = SaturatedToCompactNtp(TimeDelta::Millis(50));
+  parsed_log_builder_.LogRtcpPacketIncoming(
+      CreateReceiverReport(last_sr, delay_since_last_sr));
+
+  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();
+
+  EXPECT_CALL(*stream_factory_.stream1_ptr_,
+              UpdateMaxRtt(TimeDelta::Millis(100)));
+  EXPECT_CALL(*stream_factory_.stream1_ptr_, Close());
+  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
+                           kEmptyFieldTrialsString, BuildStreamFactory());
+  driver.Simulate();
+}
+
+TEST_F(RtcEventLogDriverTest, SenderCalculatesRttFromIncomingSr) {
+  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
+
+  // Outgoing SR.
+  NtpTime ntp = parsed_log_builder_.CurrentNtpTime();
+  parsed_log_builder_.LogRtcpPacketOutgoing(CreateSenderReport(ntp));
+
+  // Incoming SR: 50ms delay, arrives after 150ms => RTT is 100ms.
+  parsed_log_builder_.AdvanceTime(TimeDelta::Millis(150));
+  uint32_t last_sr = CompactNtp(ntp);
+  uint32_t delay_since_last_sr = SaturatedToCompactNtp(TimeDelta::Millis(50));
+  NtpTime ntp_incoming;
+  parsed_log_builder_.LogRtcpPacketIncoming(CreateSenderReportWithReportBlock(
+      ntp_incoming, last_sr, delay_since_last_sr));
+
+  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();
+
+  EXPECT_CALL(*stream_factory_.stream1_ptr_,
+              UpdateMaxRtt(TimeDelta::Millis(100)));
+  EXPECT_CALL(*stream_factory_.stream1_ptr_, Close());
+  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
+                           kEmptyFieldTrialsString, BuildStreamFactory());
+  driver.Simulate();
+}
+
+TEST_F(RtcEventLogDriverTest, ReceiverCalculatesRttFromIncomingXr) {
+  parsed_log_builder_.LogVideoRecvConfig(kSsrc1, kRtxSsrc1);
+
+  // Outgoing XR with RRTR.
+  NtpTime ntp = parsed_log_builder_.CurrentNtpTime();
+  parsed_log_builder_.LogRtcpPacketOutgoing(CreateExtendedReportsWithRrtr(ntp));
+
+  // Incoming XR with DLRR: 50ms delay, arrives after 150ms => RTT is 100ms.
+  parsed_log_builder_.AdvanceTime(TimeDelta::Millis(150));
+  uint32_t last_rr = CompactNtp(ntp);
+  uint32_t delay_since_last_rr = SaturatedToCompactNtp(TimeDelta::Millis(50));
+  parsed_log_builder_.LogRtcpPacketIncoming(
+      CreateExtendedReportsWithDlrr(last_rr, delay_since_last_rr));
+
+  std::unique_ptr<ParsedRtcEventLog> parsed_log = parsed_log_builder_.Build();
+
+  EXPECT_CALL(*stream_factory_.stream1_ptr_,
+              UpdateMaxRtt(TimeDelta::Millis(100)));
+  EXPECT_CALL(*stream_factory_.stream1_ptr_, Close());
+  RtcEventLogDriver driver(RtcEventLogDriver::Config(), parsed_log.get(),
+                           kEmptyFieldTrialsString, BuildStreamFactory());
+  driver.Simulate();
+}
+
 }  // namespace
 }  // namespace webrtc::video_timing_simulator
diff --git a/video/timing/simulator/rtt_simulator.h b/video/timing/simulator/rtt_simulator.h
index 11ef4b7..4599fef 100644
--- a/video/timing/simulator/rtt_simulator.h
+++ b/video/timing/simulator/rtt_simulator.h
@@ -68,7 +68,7 @@
 
   // Worker objects.
   RtcpRttCalculator rtt_calculator_ RTC_GUARDED_BY(sequence_checker_);
-  webrtc::internal::CallStats call_stats_ RTC_GUARDED_BY(sequence_checker_);
+  internal::CallStats call_stats_ RTC_GUARDED_BY(sequence_checker_);
 
   // Outputs.
   SimulatedRttCallback& simulated_rtt_cb_;
diff --git a/video/timing/simulator/rtt_simulator_unittest.cc b/video/timing/simulator/rtt_simulator_unittest.cc
index 31b2714..b7bfdf2 100644
--- a/video/timing/simulator/rtt_simulator_unittest.cc
+++ b/video/timing/simulator/rtt_simulator_unittest.cc
@@ -163,7 +163,7 @@
   Timestamp recv_timestamp = time_controller_.GetClock()->CurrentTime();
   uint32_t last_sr = CompactNtp(ntp);
   uint32_t delay_since_last_sr = SaturatedToCompactNtp(TimeDelta::Millis(50));
-  NtpTime ntp_incoming = env_.clock().ConvertTimestampToNtpTime(recv_timestamp);
+  NtpTime ntp_incoming;
   auto sr_incoming = CreateLoggedRtcpPacketSenderReportWithReportBlock(
       ntp_incoming, last_sr, delay_since_last_sr, recv_timestamp);
 
diff --git a/video/timing/simulator/test/parsed_rtc_event_log_builder.cc b/video/timing/simulator/test/parsed_rtc_event_log_builder.cc
index 8f2c558..4e23453 100644
--- a/video/timing/simulator/test/parsed_rtc_event_log_builder.cc
+++ b/video/timing/simulator/test/parsed_rtc_event_log_builder.cc
@@ -23,12 +23,16 @@
 #include "api/rtc_event_log_output.h"
 #include "api/units/time_delta.h"
 #include "api/units/timestamp.h"
+#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_incoming.h"
+#include "logging/rtc_event_log/events/rtc_event_rtcp_packet_outgoing.h"
 #include "logging/rtc_event_log/events/rtc_event_rtp_packet_incoming.h"
 #include "logging/rtc_event_log/events/rtc_event_video_receive_stream_config.h"
 #include "logging/rtc_event_log/rtc_event_log_parser.h"
 #include "logging/rtc_event_log/rtc_stream_config.h"
+#include "modules/rtp_rtcp/source/rtcp_packet.h"
 #include "modules/rtp_rtcp/source/rtp_packet_received.h"
 #include "rtc_base/checks.h"
+#include "system_wrappers/include/ntp_time.h"
 #include "test/create_test_environment.h"
 
 namespace webrtc::video_timing_simulator {
@@ -82,6 +86,10 @@
   return log_clock_.CurrentTime();
 }
 
+NtpTime ParsedRtcEventLogBuilder::CurrentNtpTime() {
+  return log_clock_.ConvertTimestampToNtpTime(log_clock_.CurrentTime());
+}
+
 void ParsedRtcEventLogBuilder::AdvanceTime(TimeDelta duration) {
   log_clock_.AdvanceTime(duration);
 }
@@ -103,6 +111,16 @@
       rtp_packet, rtx_original_sequence_number));
 }
 
+void ParsedRtcEventLogBuilder::LogRtcpPacketOutgoing(
+    const rtcp::RtcpPacket& rtcp_packet) {
+  Log(std::make_unique<RtcEventRtcpPacketOutgoing>(rtcp_packet.Build()));
+}
+
+void ParsedRtcEventLogBuilder::LogRtcpPacketIncoming(
+    const rtcp::RtcpPacket& rtcp_packet) {
+  Log(std::make_unique<RtcEventRtcpPacketIncoming>(rtcp_packet.Build()));
+}
+
 void ParsedRtcEventLogBuilder::Log(std::unique_ptr<RtcEvent> event) {
   RTC_DCHECK(log_);
   log_->Log(std::move(event));
diff --git a/video/timing/simulator/test/parsed_rtc_event_log_builder.h b/video/timing/simulator/test/parsed_rtc_event_log_builder.h
index 815e775..b54a1de 100644
--- a/video/timing/simulator/test/parsed_rtc_event_log_builder.h
+++ b/video/timing/simulator/test/parsed_rtc_event_log_builder.h
@@ -21,7 +21,9 @@
 #include "api/units/time_delta.h"
 #include "api/units/timestamp.h"
 #include "logging/rtc_event_log/rtc_event_log_parser.h"
+#include "modules/rtp_rtcp/source/rtcp_packet.h"
 #include "system_wrappers/include/clock.h"
+#include "system_wrappers/include/ntp_time.h"
 
 namespace webrtc::video_timing_simulator {
 
@@ -37,6 +39,7 @@
   // Interactions with the `log_clock_`.
   // Note that this clock is different from the simulation clock!
   Timestamp CurrentTime();
+  NtpTime CurrentNtpTime();
   void AdvanceTime(TimeDelta duration);
 
   // Log specific events to the log.
@@ -45,6 +48,8 @@
   void LogRtpPacketIncoming(
       uint32_t ssrc,
       std::optional<uint16_t> rtx_original_sequence_number = std::nullopt);
+  void LogRtcpPacketOutgoing(const rtcp::RtcpPacket& rtcp_packet);
+  void LogRtcpPacketIncoming(const rtcp::RtcpPacket& rtcp_packet);
 
   // Returns the parsed log. Should only be called once.
   std::unique_ptr<ParsedRtcEventLog> Build();