Migrate RemoteNtpTimeEstimator to more precise time representations

Reland of https://webrtc-review.googlesource.com/c/src/+/261311

Bug: webrtc:13757
Change-Id: I34a58100b8fadfe3dbea9ffce71829b7670daad8
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/261726
Reviewed-by: Jakob Ivarsson‎ <jakobi@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36838}
diff --git a/audio/channel_receive.cc b/audio/channel_receive.cc
index 93f534e..d00a9a9 100644
--- a/audio/channel_receive.cc
+++ b/audio/channel_receive.cc
@@ -748,12 +748,13 @@
 
   {
     MutexLock lock(&ts_stats_lock_);
-    ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp);
-    absl::optional<int64_t> remote_to_local_clock_offset_ms =
-        ntp_estimator_.EstimateRemoteToLocalClockOffsetMs();
-    if (remote_to_local_clock_offset_ms.has_value()) {
+    ntp_estimator_.UpdateRtcpTimestamp(
+        TimeDelta::Millis(rtt), NtpTime(ntp_secs, ntp_frac), rtp_timestamp);
+    absl::optional<int64_t> remote_to_local_clock_offset =
+        ntp_estimator_.EstimateRemoteToLocalClockOffset();
+    if (remote_to_local_clock_offset.has_value()) {
       capture_clock_offset_updater_.SetRemoteToLocalClockOffset(
-          Int64MsToQ32x32(*remote_to_local_clock_offset_ms));
+          *remote_to_local_clock_offset);
     }
   }
 }
diff --git a/audio/voip/audio_ingress.cc b/audio/voip/audio_ingress.cc
index 8aa552b..71026e8 100644
--- a/audio/voip/audio_ingress.cc
+++ b/audio/voip/audio_ingress.cc
@@ -226,7 +226,8 @@
 
   {
     MutexLock lock(&lock_);
-    ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp);
+    ntp_estimator_.UpdateRtcpTimestamp(
+        TimeDelta::Millis(rtt), NtpTime(ntp_secs, ntp_frac), rtp_timestamp);
   }
 }
 
diff --git a/modules/rtp_rtcp/include/remote_ntp_time_estimator.h b/modules/rtp_rtcp/include/remote_ntp_time_estimator.h
index f31503d..9abad38 100644
--- a/modules/rtp_rtcp/include/remote_ntp_time_estimator.h
+++ b/modules/rtp_rtcp/include/remote_ntp_time_estimator.h
@@ -13,7 +13,10 @@
 
 #include <stdint.h>
 
+#include "absl/base/attributes.h"
 #include "absl/types/optional.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
 #include "rtc_base/numerics/moving_median_filter.h"
 #include "system_wrappers/include/rtp_to_ntp_estimator.h"
 
@@ -28,32 +31,64 @@
 class RemoteNtpTimeEstimator {
  public:
   explicit RemoteNtpTimeEstimator(Clock* clock);
-
-  ~RemoteNtpTimeEstimator();
-
   RemoteNtpTimeEstimator(const RemoteNtpTimeEstimator&) = delete;
   RemoteNtpTimeEstimator& operator=(const RemoteNtpTimeEstimator&) = delete;
+  ~RemoteNtpTimeEstimator() = default;
 
   // Updates the estimator with round trip time `rtt`, NTP seconds `ntp_secs`,
   // NTP fraction `ntp_frac` and RTP timestamp `rtp_timestamp`.
+  ABSL_DEPRECATED(
+      "Use UpdateRtcpTimestamp with strict time types: TimeDelta and NtpTime.")
   bool UpdateRtcpTimestamp(int64_t rtt,
                            uint32_t ntp_secs,
                            uint32_t ntp_frac,
+                           uint32_t rtp_timestamp) {
+    return UpdateRtcpTimestamp(TimeDelta::Millis(rtt),
+                               NtpTime(ntp_secs, ntp_frac), rtp_timestamp);
+  }
+
+  bool UpdateRtcpTimestamp(TimeDelta rtt,
+                           NtpTime sender_send_time,
                            uint32_t rtp_timestamp);
 
   // Estimates the NTP timestamp in local timebase from `rtp_timestamp`.
   // Returns the NTP timestamp in ms when success. -1 if failed.
-  int64_t Estimate(uint32_t rtp_timestamp);
+  int64_t Estimate(uint32_t rtp_timestamp) {
+    NtpTime ntp_time = EstimateNtp(rtp_timestamp);
+    if (!ntp_time.Valid()) {
+      return -1;
+    }
+    return ntp_time.ToMs();
+  }
+
+  // Estimates the NTP timestamp in local timebase from `rtp_timestamp`.
+  // Returns invalid NtpTime (i.e. NtpTime(0)) on failure.
+  NtpTime EstimateNtp(uint32_t rtp_timestamp);
 
   // Estimates the offset, in milliseconds, between the remote clock and the
   // local one. This is equal to local NTP clock - remote NTP clock.
-  absl::optional<int64_t> EstimateRemoteToLocalClockOffsetMs();
+  ABSL_DEPRECATED("Use EstimateRemoteToLocalClockOffset.")
+  absl::optional<int64_t> EstimateRemoteToLocalClockOffsetMs() {
+    if (absl::optional<int64_t> offset = EstimateRemoteToLocalClockOffset()) {
+      return std::round(static_cast<double>(*offset) * 1'000 /
+                        (int64_t{1} << 32));
+    }
+    return absl::nullopt;
+  }
+
+  // Estimates the offset between the remote clock and the
+  // local one. This is equal to local NTP clock - remote NTP clock.
+  // The offset is returned in ntp time resolution, i.e. 1/2^32 sec ~= 0.2 ns.
+  // Returns nullopt on failure.
+  absl::optional<int64_t> EstimateRemoteToLocalClockOffset();
 
  private:
   Clock* clock_;
+  // Offset is measured with the same precision as NtpTime: in 1/2^32 seconds ~=
+  // 0.2 ns.
   MovingMedianFilter<int64_t> ntp_clocks_offset_estimator_;
   RtpToNtpEstimator rtp_to_ntp_;
-  int64_t last_timing_log_ms_;
+  Timestamp last_timing_log_ = Timestamp::MinusInfinity();
 };
 
 }  // namespace webrtc
diff --git a/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc b/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc
index b1602e5d..6f90cd1 100644
--- a/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc
+++ b/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc
@@ -22,25 +22,37 @@
 namespace {
 
 constexpr int kMinimumNumberOfSamples = 2;
-constexpr int kTimingLogIntervalMs = 10000;
+constexpr TimeDelta kTimingLogInterval = TimeDelta::Seconds(10);
 constexpr int kClocksOffsetSmoothingWindow = 100;
 
+// Subtracts two NtpTime values keeping maximum precision.
+int64_t Subtract(NtpTime minuend, NtpTime subtrahend) {
+  uint64_t a = static_cast<uint64_t>(minuend);
+  uint64_t b = static_cast<uint64_t>(subtrahend);
+  return a >= b ? static_cast<int64_t>(a - b) : -static_cast<int64_t>(b - a);
+}
+
+NtpTime Add(NtpTime lhs, int64_t rhs) {
+  uint64_t result = static_cast<uint64_t>(lhs);
+  if (rhs >= 0) {
+    result += static_cast<uint64_t>(rhs);
+  } else {
+    result -= static_cast<uint64_t>(-rhs);
+  }
+  return NtpTime(result);
+}
+
 }  // namespace
 
 // TODO(wu): Refactor this class so that it can be shared with
 // vie_sync_module.cc.
 RemoteNtpTimeEstimator::RemoteNtpTimeEstimator(Clock* clock)
     : clock_(clock),
-      ntp_clocks_offset_estimator_(kClocksOffsetSmoothingWindow),
-      last_timing_log_ms_(-1) {}
+      ntp_clocks_offset_estimator_(kClocksOffsetSmoothingWindow) {}
 
-RemoteNtpTimeEstimator::~RemoteNtpTimeEstimator() {}
-
-bool RemoteNtpTimeEstimator::UpdateRtcpTimestamp(int64_t rtt,
-                                                 uint32_t ntp_secs,
-                                                 uint32_t ntp_frac,
+bool RemoteNtpTimeEstimator::UpdateRtcpTimestamp(TimeDelta rtt,
+                                                 NtpTime sender_send_time,
                                                  uint32_t rtp_timestamp) {
-  NtpTime sender_send_time(ntp_secs, ntp_frac);
   switch (rtp_to_ntp_.UpdateMeasurements(sender_send_time, rtp_timestamp)) {
     case RtpToNtpEstimator::kInvalidMeasurement:
       return false;
@@ -51,42 +63,42 @@
       break;
   }
 
+  // Assume connection is symmetric and thus time to deliver the packet is half
+  // the round trip time.
+  int64_t deliver_time_ntp = ToNtpUnits(rtt) / 2;
+
   // Update extrapolator with the new arrival time.
-  // The extrapolator assumes the ntp time.
-  int64_t receiver_arrival_time_ms = clock_->CurrentNtpInMilliseconds();
-  int64_t sender_arrival_time_ms = sender_send_time.ToMs() + rtt / 2;
+  NtpTime receiver_arrival_time = clock_->CurrentNtpTime();
   int64_t remote_to_local_clocks_offset =
-      receiver_arrival_time_ms - sender_arrival_time_ms;
+      Subtract(receiver_arrival_time, sender_send_time) - deliver_time_ntp;
   ntp_clocks_offset_estimator_.Insert(remote_to_local_clocks_offset);
   return true;
 }
 
-int64_t RemoteNtpTimeEstimator::Estimate(uint32_t rtp_timestamp) {
+NtpTime RemoteNtpTimeEstimator::EstimateNtp(uint32_t rtp_timestamp) {
   NtpTime sender_capture = rtp_to_ntp_.Estimate(rtp_timestamp);
   if (!sender_capture.Valid()) {
-    return -1;
+    return sender_capture;
   }
-  int64_t sender_capture_ntp_ms = sender_capture.ToMs();
 
   int64_t remote_to_local_clocks_offset =
       ntp_clocks_offset_estimator_.GetFilteredValue();
-  int64_t receiver_capture_ntp_ms =
-      sender_capture_ntp_ms + remote_to_local_clocks_offset;
+  NtpTime receiver_capture = Add(sender_capture, remote_to_local_clocks_offset);
 
-  int64_t now_ms = clock_->TimeInMilliseconds();
-  if (now_ms - last_timing_log_ms_ > kTimingLogIntervalMs) {
+  Timestamp now = clock_->CurrentTime();
+  if (now - last_timing_log_ > kTimingLogInterval) {
     RTC_LOG(LS_INFO) << "RTP timestamp: " << rtp_timestamp
-                     << " in NTP clock: " << sender_capture_ntp_ms
+                     << " in NTP clock: " << sender_capture.ToMs()
                      << " estimated time in receiver NTP clock: "
-                     << receiver_capture_ntp_ms;
-    last_timing_log_ms_ = now_ms;
+                     << receiver_capture.ToMs();
+    last_timing_log_ = now;
   }
 
-  return receiver_capture_ntp_ms;
+  return receiver_capture;
 }
 
 absl::optional<int64_t>
-RemoteNtpTimeEstimator::EstimateRemoteToLocalClockOffsetMs() {
+RemoteNtpTimeEstimator::EstimateRemoteToLocalClockOffset() {
   if (ntp_clocks_offset_estimator_.GetNumberOfSamplesStored() <
       kMinimumNumberOfSamples) {
     return absl::nullopt;
diff --git a/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc b/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc
index 73c3e9b..8dbfaec 100644
--- a/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc
+++ b/modules/rtp_rtcp/source/remote_ntp_time_estimator_unittest.cc
@@ -9,32 +9,29 @@
  */
 
 #include "modules/rtp_rtcp/include/remote_ntp_time_estimator.h"
+
 #include "absl/types/optional.h"
+#include "modules/rtp_rtcp/source/time_util.h"
 #include "system_wrappers/include/clock.h"
 #include "system_wrappers/include/ntp_time.h"
 #include "test/gmock.h"
 #include "test/gtest.h"
 
 namespace webrtc {
+namespace {
 
-constexpr int64_t kTestRtt = 10;
-constexpr int64_t kLocalClockInitialTimeMs = 123;
-constexpr int64_t kRemoteClockInitialTimeMs = 345;
+constexpr TimeDelta kTestRtt = TimeDelta::Millis(10);
+constexpr Timestamp kLocalClockInitialTime = Timestamp::Millis(123);
+constexpr Timestamp kRemoteClockInitialTime = Timestamp::Millis(373);
 constexpr uint32_t kTimestampOffset = 567;
-constexpr int64_t kRemoteToLocalClockOffsetMs =
-    kLocalClockInitialTimeMs - kRemoteClockInitialTimeMs;
+constexpr int64_t kRemoteToLocalClockOffsetNtp =
+    ToNtpUnits(kLocalClockInitialTime - kRemoteClockInitialTime);
 
 class RemoteNtpTimeEstimatorTest : public ::testing::Test {
  protected:
-  RemoteNtpTimeEstimatorTest()
-      : local_clock_(kLocalClockInitialTimeMs * 1000),
-        remote_clock_(kRemoteClockInitialTimeMs * 1000),
-        estimator_(new RemoteNtpTimeEstimator(&local_clock_)) {}
-  ~RemoteNtpTimeEstimatorTest() override = default;
-
-  void AdvanceTimeMilliseconds(int64_t ms) {
-    local_clock_.AdvanceTimeMilliseconds(ms);
-    remote_clock_.AdvanceTimeMilliseconds(ms);
+  void AdvanceTime(TimeDelta delta) {
+    local_clock_.AdvanceTime(delta);
+    remote_clock_.AdvanceTime(delta);
   }
 
   uint32_t GetRemoteTimestamp() {
@@ -42,107 +39,90 @@
            kTimestampOffset;
   }
 
-  NtpTime GetRemoteNtpTime() { return remote_clock_.CurrentNtpTime(); }
-
   void SendRtcpSr() {
     uint32_t rtcp_timestamp = GetRemoteTimestamp();
-    NtpTime ntp = GetRemoteNtpTime();
+    NtpTime ntp = remote_clock_.CurrentNtpTime();
 
-    AdvanceTimeMilliseconds(kTestRtt / 2);
-    ReceiveRtcpSr(kTestRtt, rtcp_timestamp, ntp.seconds(), ntp.fractions());
+    AdvanceTime(kTestRtt / 2);
+    RTC_DCHECK(estimator_.UpdateRtcpTimestamp(kTestRtt, ntp, rtcp_timestamp));
   }
 
-  void SendRtcpSrInaccurately(int64_t ntp_error_ms,
-                              int64_t networking_delay_ms) {
+  void SendRtcpSrInaccurately(TimeDelta ntp_error, TimeDelta networking_delay) {
     uint32_t rtcp_timestamp = GetRemoteTimestamp();
-    int64_t ntp_error_fractions =
-        ntp_error_ms * static_cast<int64_t>(NtpTime::kFractionsPerSecond) /
-        1000;
-    NtpTime ntp(static_cast<uint64_t>(GetRemoteNtpTime()) +
+    int64_t ntp_error_fractions = ToNtpUnits(ntp_error);
+    NtpTime ntp(static_cast<uint64_t>(remote_clock_.CurrentNtpTime()) +
                 ntp_error_fractions);
-    AdvanceTimeMilliseconds(kTestRtt / 2 + networking_delay_ms);
-    ReceiveRtcpSr(kTestRtt, rtcp_timestamp, ntp.seconds(), ntp.fractions());
+    AdvanceTime(kTestRtt / 2 + networking_delay);
+    RTC_DCHECK(estimator_.UpdateRtcpTimestamp(kTestRtt, ntp, rtcp_timestamp));
   }
 
-  void UpdateRtcpTimestamp(int64_t rtt,
-                           uint32_t ntp_secs,
-                           uint32_t ntp_frac,
-                           uint32_t rtp_timestamp,
-                           bool expected_result) {
-    EXPECT_EQ(expected_result, estimator_->UpdateRtcpTimestamp(
-                                   rtt, ntp_secs, ntp_frac, rtp_timestamp));
-  }
-
-  void ReceiveRtcpSr(int64_t rtt,
-                     uint32_t rtcp_timestamp,
-                     uint32_t ntp_seconds,
-                     uint32_t ntp_fractions) {
-    UpdateRtcpTimestamp(rtt, ntp_seconds, ntp_fractions, rtcp_timestamp, true);
-  }
-
-  SimulatedClock local_clock_;
-  SimulatedClock remote_clock_;
-  std::unique_ptr<RemoteNtpTimeEstimator> estimator_;
+  SimulatedClock local_clock_{kLocalClockInitialTime};
+  SimulatedClock remote_clock_{kRemoteClockInitialTime};
+  RemoteNtpTimeEstimator estimator_{&local_clock_};
 };
 
-TEST_F(RemoteNtpTimeEstimatorTest, Estimate) {
-  // Failed without valid NTP.
-  UpdateRtcpTimestamp(kTestRtt, 0, 0, 0, false);
+TEST_F(RemoteNtpTimeEstimatorTest, FailsWithoutValidNtpTime) {
+  EXPECT_FALSE(
+      estimator_.UpdateRtcpTimestamp(kTestRtt, NtpTime(), /*rtp_timestamp=*/0));
+}
 
-  AdvanceTimeMilliseconds(1000);
+TEST_F(RemoteNtpTimeEstimatorTest, Estimate) {
   // Remote peer sends first RTCP SR.
   SendRtcpSr();
 
   // Remote sends a RTP packet.
-  AdvanceTimeMilliseconds(15);
+  AdvanceTime(TimeDelta::Millis(15));
   uint32_t rtp_timestamp = GetRemoteTimestamp();
   int64_t capture_ntp_time_ms = local_clock_.CurrentNtpInMilliseconds();
 
   // Local peer needs at least 2 RTCP SR to calculate the capture time.
   const int64_t kNotEnoughRtcpSr = -1;
-  EXPECT_EQ(kNotEnoughRtcpSr, estimator_->Estimate(rtp_timestamp));
-  EXPECT_EQ(absl::nullopt, estimator_->EstimateRemoteToLocalClockOffsetMs());
+  EXPECT_EQ(kNotEnoughRtcpSr, estimator_.Estimate(rtp_timestamp));
+  EXPECT_EQ(estimator_.EstimateRemoteToLocalClockOffset(), absl::nullopt);
 
-  AdvanceTimeMilliseconds(800);
+  AdvanceTime(TimeDelta::Millis(800));
   // Remote sends second RTCP SR.
   SendRtcpSr();
 
   // Local peer gets enough RTCP SR to calculate the capture time.
-  EXPECT_EQ(capture_ntp_time_ms, estimator_->Estimate(rtp_timestamp));
-  EXPECT_EQ(kRemoteToLocalClockOffsetMs,
-            estimator_->EstimateRemoteToLocalClockOffsetMs());
+  EXPECT_EQ(capture_ntp_time_ms, estimator_.Estimate(rtp_timestamp));
+  EXPECT_EQ(estimator_.EstimateRemoteToLocalClockOffset(),
+            kRemoteToLocalClockOffsetNtp);
 }
 
 TEST_F(RemoteNtpTimeEstimatorTest, AveragesErrorsOut) {
   // Remote peer sends first 10 RTCP SR without errors.
   for (int i = 0; i < 10; ++i) {
-    AdvanceTimeMilliseconds(1000);
+    AdvanceTime(TimeDelta::Seconds(1));
     SendRtcpSr();
   }
 
-  AdvanceTimeMilliseconds(150);
+  AdvanceTime(TimeDelta::Millis(150));
   uint32_t rtp_timestamp = GetRemoteTimestamp();
   int64_t capture_ntp_time_ms = local_clock_.CurrentNtpInMilliseconds();
   // Local peer gets enough RTCP SR to calculate the capture time.
-  EXPECT_EQ(capture_ntp_time_ms, estimator_->Estimate(rtp_timestamp));
-  EXPECT_EQ(kRemoteToLocalClockOffsetMs,
-            estimator_->EstimateRemoteToLocalClockOffsetMs());
+  EXPECT_EQ(capture_ntp_time_ms, estimator_.Estimate(rtp_timestamp));
+  EXPECT_EQ(kRemoteToLocalClockOffsetNtp,
+            estimator_.EstimateRemoteToLocalClockOffset());
 
   // Remote sends corrupted RTCP SRs
-  AdvanceTimeMilliseconds(1000);
-  SendRtcpSrInaccurately(/*ntp_error_ms=*/2, /*networking_delay_ms=*/-1);
-  AdvanceTimeMilliseconds(1000);
-  SendRtcpSrInaccurately(/*ntp_error_ms=*/-2, /*networking_delay_ms=*/1);
+  AdvanceTime(TimeDelta::Seconds(1));
+  SendRtcpSrInaccurately(/*ntp_error=*/TimeDelta::Millis(2),
+                         /*networking_delay=*/TimeDelta::Millis(-1));
+  AdvanceTime(TimeDelta::Seconds(1));
+  SendRtcpSrInaccurately(/*ntp_error=*/TimeDelta::Millis(-2),
+                         /*networking_delay=*/TimeDelta::Millis(1));
 
   // New RTP packet to estimate timestamp.
-  AdvanceTimeMilliseconds(150);
+  AdvanceTime(TimeDelta::Millis(150));
   rtp_timestamp = GetRemoteTimestamp();
   capture_ntp_time_ms = local_clock_.CurrentNtpInMilliseconds();
 
   // Errors should be averaged out.
-  EXPECT_EQ(capture_ntp_time_ms, estimator_->Estimate(rtp_timestamp));
-  EXPECT_EQ(kRemoteToLocalClockOffsetMs,
-            estimator_->EstimateRemoteToLocalClockOffsetMs());
+  EXPECT_EQ(capture_ntp_time_ms, estimator_.Estimate(rtp_timestamp));
+  EXPECT_EQ(kRemoteToLocalClockOffsetNtp,
+            estimator_.EstimateRemoteToLocalClockOffset());
 }
 
+}  // namespace
 }  // namespace webrtc
diff --git a/modules/rtp_rtcp/source/time_util.h b/modules/rtp_rtcp/source/time_util.h
index fba2dd9..5e72bba 100644
--- a/modules/rtp_rtcp/source/time_util.h
+++ b/modules/rtp_rtcp/source/time_util.h
@@ -34,6 +34,14 @@
 // Negative values converted to 0, Overlarge values converted to max uint32_t.
 uint32_t SaturatedToCompactNtp(TimeDelta delta);
 
+// Convert interval to the NTP time resolution (1/2^32 seconds ~= 0.2 ns).
+inline constexpr int64_t ToNtpUnits(TimeDelta delta) {
+  // For better precision `delta` is taken with best TimeDelta precision (us),
+  // then multiplaction and conversion to seconds are swapped to avoid float
+  // arithmetic.
+  return (delta.us() * (int64_t{1} << 32)) / 1'000'000;
+}
+
 // Converts interval from compact ntp (1/2^16 seconds) resolution to TimeDelta.
 // This interval can be up to ~9.1 hours (2^15 seconds).
 // Values close to 2^16 seconds are considered negative and are converted to
diff --git a/modules/rtp_rtcp/source/time_util_unittest.cc b/modules/rtp_rtcp/source/time_util_unittest.cc
index 983155e..f77c3c6 100644
--- a/modules/rtp_rtcp/source/time_util_unittest.cc
+++ b/modules/rtp_rtcp/source/time_util_unittest.cc
@@ -95,4 +95,20 @@
       5'515, 16);
 }
 
+TEST(TimeUtilTest, ToNtpUnits) {
+  EXPECT_EQ(ToNtpUnits(TimeDelta::Zero()), 0);
+  EXPECT_EQ(ToNtpUnits(TimeDelta::Seconds(1)), int64_t{1} << 32);
+  EXPECT_EQ(ToNtpUnits(TimeDelta::Seconds(-1)), -(int64_t{1} << 32));
+
+  EXPECT_EQ(ToNtpUnits(TimeDelta::Millis(500)), int64_t{1} << 31);
+  EXPECT_EQ(ToNtpUnits(TimeDelta::Millis(-1'500)), -(int64_t{3} << 31));
+
+  // Smallest TimeDelta that can be converted without precision loss.
+  EXPECT_EQ(ToNtpUnits(TimeDelta::Micros(15'625)), int64_t{1} << 26);
+
+  // 1 us ~= 4'294.97 NTP units. ToNtpUnits makes no rounding promises.
+  EXPECT_GE(ToNtpUnits(TimeDelta::Micros(1)), 4'294);
+  EXPECT_LE(ToNtpUnits(TimeDelta::Micros(1)), 4'295);
+}
+
 }  // namespace webrtc
diff --git a/video/rtp_video_stream_receiver.cc b/video/rtp_video_stream_receiver.cc
index 04484b9..5a26839 100644
--- a/video/rtp_video_stream_receiver.cc
+++ b/video/rtp_video_stream_receiver.cc
@@ -1122,12 +1122,13 @@
       clock_->CurrentNtpInMilliseconds() - received_ntp.ToMs();
   // Don't use old SRs to estimate time.
   if (time_since_received <= 1) {
-    ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp);
-    absl::optional<int64_t> remote_to_local_clock_offset_ms =
-        ntp_estimator_.EstimateRemoteToLocalClockOffsetMs();
-    if (remote_to_local_clock_offset_ms.has_value()) {
+    ntp_estimator_.UpdateRtcpTimestamp(
+        TimeDelta::Millis(rtt), NtpTime(ntp_secs, ntp_frac), rtp_timestamp);
+    absl::optional<int64_t> remote_to_local_clock_offset =
+        ntp_estimator_.EstimateRemoteToLocalClockOffset();
+    if (remote_to_local_clock_offset.has_value()) {
       capture_clock_offset_updater_.SetRemoteToLocalClockOffset(
-          Int64MsToQ32x32(*remote_to_local_clock_offset_ms));
+          *remote_to_local_clock_offset);
     }
   }
 
diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc
index 9956760..46fa2d9 100644
--- a/video/rtp_video_stream_receiver2.cc
+++ b/video/rtp_video_stream_receiver2.cc
@@ -1042,12 +1042,13 @@
       clock_->CurrentNtpInMilliseconds() - received_ntp.ToMs();
   // Don't use old SRs to estimate time.
   if (time_since_received <= 1) {
-    ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp);
-    absl::optional<int64_t> remote_to_local_clock_offset_ms =
-        ntp_estimator_.EstimateRemoteToLocalClockOffsetMs();
-    if (remote_to_local_clock_offset_ms.has_value()) {
+    ntp_estimator_.UpdateRtcpTimestamp(
+        TimeDelta::Millis(rtt), NtpTime(ntp_secs, ntp_frac), rtp_timestamp);
+    absl::optional<int64_t> remote_to_local_clock_offset =
+        ntp_estimator_.EstimateRemoteToLocalClockOffset();
+    if (remote_to_local_clock_offset.has_value()) {
       capture_clock_offset_updater_.SetRemoteToLocalClockOffset(
-          Int64MsToQ32x32(*remote_to_local_clock_offset_ms));
+          *remote_to_local_clock_offset);
     }
   }