Cleanup RtpToNtpEstimator

- Use NtpTime instead of pair of uint32_t to represent ntp time
- Increase precision estimate with NtpTime precision instead of ms precision
- Hide helper structs as private types
- Modernize interface to prefer return values over output parameters
- embed LinearRegression helper into the only user: UpdateParameters

Bug: webrtc:13757
Change-Id: I0a62a03e2869b2ae1eacaa15253accc43ba0a598
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/254780
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36232}
diff --git a/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc b/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc
index 723064e..b1602e5d 100644
--- a/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc
+++ b/modules/rtp_rtcp/source/remote_ntp_time_estimator.cc
@@ -40,21 +40,21 @@
                                                  uint32_t ntp_secs,
                                                  uint32_t ntp_frac,
                                                  uint32_t rtp_timestamp) {
-  bool new_rtcp_sr = false;
-  if (!rtp_to_ntp_.UpdateMeasurements(ntp_secs, ntp_frac, rtp_timestamp,
-                                      &new_rtcp_sr)) {
-    return false;
-  }
-  if (!new_rtcp_sr) {
-    // No new RTCP SR since last time this function was called.
-    return true;
+  NtpTime sender_send_time(ntp_secs, ntp_frac);
+  switch (rtp_to_ntp_.UpdateMeasurements(sender_send_time, rtp_timestamp)) {
+    case RtpToNtpEstimator::kInvalidMeasurement:
+      return false;
+    case RtpToNtpEstimator::kSameMeasurement:
+      // No new RTCP SR since last time this function was called.
+      return true;
+    case RtpToNtpEstimator::kNewMeasurement:
+      break;
   }
 
   // Update extrapolator with the new arrival time.
   // The extrapolator assumes the ntp time.
   int64_t receiver_arrival_time_ms = clock_->CurrentNtpInMilliseconds();
-  int64_t sender_send_time_ms = NtpTime(ntp_secs, ntp_frac).ToMs();
-  int64_t sender_arrival_time_ms = sender_send_time_ms + rtt / 2;
+  int64_t sender_arrival_time_ms = sender_send_time.ToMs() + rtt / 2;
   int64_t remote_to_local_clocks_offset =
       receiver_arrival_time_ms - sender_arrival_time_ms;
   ntp_clocks_offset_estimator_.Insert(remote_to_local_clocks_offset);
@@ -62,10 +62,11 @@
 }
 
 int64_t RemoteNtpTimeEstimator::Estimate(uint32_t rtp_timestamp) {
-  int64_t sender_capture_ntp_ms = 0;
-  if (!rtp_to_ntp_.Estimate(rtp_timestamp, &sender_capture_ntp_ms)) {
+  NtpTime sender_capture = rtp_to_ntp_.Estimate(rtp_timestamp);
+  if (!sender_capture.Valid()) {
     return -1;
   }
+  int64_t sender_capture_ntp_ms = sender_capture.ToMs();
 
   int64_t remote_to_local_clocks_offset =
       ntp_clocks_offset_estimator_.GetFilteredValue();
diff --git a/system_wrappers/include/rtp_to_ntp_estimator.h b/system_wrappers/include/rtp_to_ntp_estimator.h
index 1750633..3b62b78 100644
--- a/system_wrappers/include/rtp_to_ntp_estimator.h
+++ b/system_wrappers/include/rtp_to_ntp_estimator.h
@@ -18,60 +18,51 @@
 #include "absl/types/optional.h"
 #include "modules/include/module_common_types_public.h"
 #include "rtc_base/checks.h"
-#include "rtc_base/numerics/moving_median_filter.h"
 #include "system_wrappers/include/ntp_time.h"
 
 namespace webrtc {
-// Class for converting an RTP timestamp to the NTP domain in milliseconds.
+
+// Converts an RTP timestamp to the NTP domain.
 // The class needs to be trained with (at least 2) RTP/NTP timestamp pairs from
 // RTCP sender reports before the convertion can be done.
 class RtpToNtpEstimator {
  public:
-  RtpToNtpEstimator();
-  ~RtpToNtpEstimator();
+  static constexpr int kMaxInvalidSamples = 3;
+
+  RtpToNtpEstimator() = default;
+  RtpToNtpEstimator(const RtpToNtpEstimator&) = delete;
+  RtpToNtpEstimator& operator=(const RtpToNtpEstimator&) = delete;
+  ~RtpToNtpEstimator() = default;
+
+  enum UpdateResult { kInvalidMeasurement, kSameMeasurement, kNewMeasurement };
+  // Updates measurements with RTP/NTP timestamp pair from a RTCP sender report.
+  UpdateResult UpdateMeasurements(NtpTime ntp, uint32_t rtp_timestamp);
+
+  // Converts an RTP timestamp to the NTP domain.
+  // Returns invalid NtpTime (i.e. NtpTime(0)) on failure.
+  NtpTime Estimate(uint32_t rtp_timestamp) const;
+
+  // Returns estimated rtp_timestamp frequency, or 0 on failure.
+  double EstimatedFrequencyKhz() const;
+
+ private:
+  // Estimated parameters from RTP and NTP timestamp pairs in `measurements_`.
+  // Defines linear estimation: NtpTime (in units of 1s/2^32) =
+  //   `Parameters::slope` * rtp_timestamp + `Parameters::offset`.
+  struct Parameters {
+    double slope;
+    double offset;
+  };
 
   // RTP and NTP timestamp pair from a RTCP SR report.
   struct RtcpMeasurement {
-    RtcpMeasurement(uint32_t ntp_secs,
-                    uint32_t ntp_frac,
-                    int64_t unwrapped_timestamp);
-    bool IsEqual(const RtcpMeasurement& other) const;
-
     NtpTime ntp_time;
     int64_t unwrapped_rtp_timestamp;
   };
 
-  // Estimated parameters from RTP and NTP timestamp pairs in `measurements_`.
-  struct Parameters {
-    Parameters() : frequency_khz(0.0), offset_ms(0.0) {}
-
-    Parameters(double frequency_khz, double offset_ms)
-        : frequency_khz(frequency_khz), offset_ms(offset_ms) {}
-
-    double frequency_khz;
-    double offset_ms;
-  };
-
-  // Updates measurements with RTP/NTP timestamp pair from a RTCP sender report.
-  // `new_rtcp_sr` is set to true if a new report is added.
-  bool UpdateMeasurements(uint32_t ntp_secs,
-                          uint32_t ntp_frac,
-                          uint32_t rtp_timestamp,
-                          bool* new_rtcp_sr);
-
-  // Converts an RTP timestamp to the NTP domain in milliseconds.
-  // Returns true on success, false otherwise.
-  bool Estimate(int64_t rtp_timestamp, int64_t* ntp_timestamp_ms) const;
-
-  // Returns estimated rtp to ntp linear transform parameters.
-  const absl::optional<Parameters> params() const;
-
-  static const int kMaxInvalidSamples = 3;
-
- private:
   void UpdateParameters();
 
-  int consecutive_invalid_samples_;
+  int consecutive_invalid_samples_ = 0;
   std::list<RtcpMeasurement> measurements_;
   absl::optional<Parameters> params_;
   mutable TimestampUnwrapper unwrapper_;
diff --git a/system_wrappers/source/rtp_to_ntp_estimator.cc b/system_wrappers/source/rtp_to_ntp_estimator.cc
index d0b0ad4..ef5d9a7 100644
--- a/system_wrappers/source/rtp_to_ntp_estimator.cc
+++ b/system_wrappers/source/rtp_to_ntp_estimator.cc
@@ -18,132 +18,85 @@
 #include "api/array_view.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/numerics/safe_conversions.h"
 
 namespace webrtc {
 namespace {
 // Maximum number of RTCP SR reports to use to map between RTP and NTP.
-const size_t kNumRtcpReportsToUse = 20;
+constexpr size_t kNumRtcpReportsToUse = 20;
 // Don't allow NTP timestamps to jump more than 1 hour. Chosen arbitrary as big
 // enough to not affect normal use-cases. Yet it is smaller than RTP wrap-around
 // half-period (90khz RTP clock wrap-arounds every 13.25 hours). After half of
 // wrap-around period it is impossible to unwrap RTP timestamps correctly.
-const int kMaxAllowedRtcpNtpIntervalMs = 60 * 60 * 1000;
+constexpr uint64_t kMaxAllowedRtcpNtpInterval = uint64_t{60 * 60} << 32;
+}  // namespace
 
-bool Contains(const std::list<RtpToNtpEstimator::RtcpMeasurement>& measurements,
-              const RtpToNtpEstimator::RtcpMeasurement& other) {
-  for (const auto& measurement : measurements) {
-    if (measurement.IsEqual(other))
-      return true;
-  }
-  return false;
-}
-
-// Given x[] and y[] writes out such k and b that line y=k*x+b approximates
-// given points in the best way (Least Squares Method).
-bool LinearRegression(rtc::ArrayView<const double> x,
-                      rtc::ArrayView<const double> y,
-                      double* k,
-                      double* b) {
-  size_t n = x.size();
+void RtpToNtpEstimator::UpdateParameters() {
+  size_t n = measurements_.size();
   if (n < 2)
-    return false;
+    return;
 
-  if (y.size() != n)
-    return false;
+  // Run linear regression:
+  // Given x[] and y[] writes out such k and b that line y=k*x+b approximates
+  // given points in the best way (Least Squares Method).
+  auto x = [](const RtcpMeasurement& m) {
+    return static_cast<double>(m.unwrapped_rtp_timestamp);
+  };
+  auto y = [](const RtcpMeasurement& m) {
+    return static_cast<double>(static_cast<uint64_t>(m.ntp_time));
+  };
 
   double avg_x = 0;
   double avg_y = 0;
-  for (size_t i = 0; i < n; ++i) {
-    avg_x += x[i];
-    avg_y += y[i];
+  for (const RtcpMeasurement& m : measurements_) {
+    avg_x += x(m);
+    avg_y += y(m);
   }
   avg_x /= n;
   avg_y /= n;
 
   double variance_x = 0;
   double covariance_xy = 0;
-  for (size_t i = 0; i < n; ++i) {
-    double normalized_x = x[i] - avg_x;
-    double normalized_y = y[i] - avg_y;
+  for (const RtcpMeasurement& m : measurements_) {
+    double normalized_x = x(m) - avg_x;
+    double normalized_y = y(m) - avg_y;
     variance_x += normalized_x * normalized_x;
     covariance_xy += normalized_x * normalized_y;
   }
 
   if (std::fabs(variance_x) < 1e-8)
-    return false;
-
-  *k = static_cast<double>(covariance_xy / variance_x);
-  *b = static_cast<double>(avg_y - (*k) * avg_x);
-  return true;
-}
-
-}  // namespace
-
-RtpToNtpEstimator::RtcpMeasurement::RtcpMeasurement(uint32_t ntp_secs,
-                                                    uint32_t ntp_frac,
-                                                    int64_t unwrapped_timestamp)
-    : ntp_time(ntp_secs, ntp_frac),
-      unwrapped_rtp_timestamp(unwrapped_timestamp) {}
-
-bool RtpToNtpEstimator::RtcpMeasurement::IsEqual(
-    const RtcpMeasurement& other) const {
-  // Use || since two equal timestamps will result in zero frequency and in
-  // RtpToNtpMs, `rtp_timestamp_ms` is estimated by dividing by the frequency.
-  return (ntp_time == other.ntp_time) ||
-         (unwrapped_rtp_timestamp == other.unwrapped_rtp_timestamp);
-}
-
-// Class for converting an RTP timestamp to the NTP domain.
-RtpToNtpEstimator::RtpToNtpEstimator() : consecutive_invalid_samples_(0) {}
-
-RtpToNtpEstimator::~RtpToNtpEstimator() {}
-
-void RtpToNtpEstimator::UpdateParameters() {
-  if (measurements_.size() < 2)
     return;
 
-  std::vector<double> x;
-  std::vector<double> y;
-  x.reserve(measurements_.size());
-  y.reserve(measurements_.size());
-  for (auto it = measurements_.begin(); it != measurements_.end(); ++it) {
-    x.push_back(it->unwrapped_rtp_timestamp);
-    y.push_back(it->ntp_time.ToMs());
-  }
-  double slope, offset;
-
-  if (!LinearRegression(x, y, &slope, &offset)) {
-    return;
-  }
-
-  params_.emplace(1 / slope, offset);
+  double k = covariance_xy / variance_x;
+  double b = avg_y - k * avg_x;
+  params_ = {{.slope = k, .offset = b}};
 }
 
-bool RtpToNtpEstimator::UpdateMeasurements(uint32_t ntp_secs,
-                                           uint32_t ntp_frac,
-                                           uint32_t rtp_timestamp,
-                                           bool* new_rtcp_sr) {
-  *new_rtcp_sr = false;
-
+RtpToNtpEstimator::UpdateResult RtpToNtpEstimator::UpdateMeasurements(
+    NtpTime ntp,
+    uint32_t rtp_timestamp) {
   int64_t unwrapped_rtp_timestamp = unwrapper_.Unwrap(rtp_timestamp);
 
-  RtcpMeasurement new_measurement(ntp_secs, ntp_frac, unwrapped_rtp_timestamp);
+  RtcpMeasurement new_measurement = {
+      .ntp_time = ntp, .unwrapped_rtp_timestamp = unwrapped_rtp_timestamp};
 
-  if (Contains(measurements_, new_measurement)) {
-    // RTCP SR report already added.
-    return true;
+  for (const RtcpMeasurement& measurement : measurements_) {
+    // Use || since two equal timestamps will result in zero frequency.
+    if (measurement.ntp_time == ntp ||
+        measurement.unwrapped_rtp_timestamp == unwrapped_rtp_timestamp) {
+      return kSameMeasurement;
+    }
   }
 
   if (!new_measurement.ntp_time.Valid())
-    return false;
+    return kInvalidMeasurement;
 
-  int64_t ntp_ms_new = new_measurement.ntp_time.ToMs();
+  uint64_t ntp_new = static_cast<uint64_t>(new_measurement.ntp_time);
   bool invalid_sample = false;
   if (!measurements_.empty()) {
     int64_t old_rtp_timestamp = measurements_.front().unwrapped_rtp_timestamp;
-    int64_t old_ntp_ms = measurements_.front().ntp_time.ToMs();
-    if (ntp_ms_new <= old_ntp_ms ||
-        ntp_ms_new > old_ntp_ms + kMaxAllowedRtcpNtpIntervalMs) {
+    uint64_t old_ntp = static_cast<uint64_t>(measurements_.front().ntp_time);
+    if (ntp_new <= old_ntp || ntp_new > old_ntp + kMaxAllowedRtcpNtpInterval) {
       invalid_sample = true;
     } else if (unwrapped_rtp_timestamp <= old_rtp_timestamp) {
       RTC_LOG(LS_WARNING)
@@ -158,7 +111,7 @@
   if (invalid_sample) {
     ++consecutive_invalid_samples_;
     if (consecutive_invalid_samples_ < kMaxInvalidSamples) {
-      return false;
+      return kInvalidMeasurement;
     }
     RTC_LOG(LS_WARNING) << "Multiple consecutively invalid RTCP SR reports, "
                            "clearing measurements.";
@@ -172,37 +125,29 @@
     measurements_.pop_back();
 
   measurements_.push_front(new_measurement);
-  *new_rtcp_sr = true;
 
   // List updated, calculate new parameters.
   UpdateParameters();
-  return true;
+  return kNewMeasurement;
 }
 
-bool RtpToNtpEstimator::Estimate(int64_t rtp_timestamp,
-                                 int64_t* ntp_timestamp_ms) const {
+NtpTime RtpToNtpEstimator::Estimate(uint32_t rtp_timestamp) const {
   if (!params_)
-    return false;
+    return NtpTime();
 
-  int64_t rtp_timestamp_unwrapped = unwrapper_.Unwrap(rtp_timestamp);
+  double estimated =
+      static_cast<double>(unwrapper_.Unwrap(rtp_timestamp)) * params_->slope +
+      params_->offset + 0.5f;
 
-  // params_calculated_ should not be true unless ms params.frequency_khz has
-  // been calculated to something non zero.
-  RTC_DCHECK_NE(params_->frequency_khz, 0.0);
-  double rtp_ms =
-      static_cast<double>(rtp_timestamp_unwrapped) / params_->frequency_khz +
-      params_->offset_ms + 0.5f;
-
-  if (rtp_ms < 0)
-    return false;
-
-  *ntp_timestamp_ms = rtp_ms;
-
-  return true;
+  return NtpTime(rtc::saturated_cast<uint64_t>(estimated));
 }
 
-const absl::optional<RtpToNtpEstimator::Parameters> RtpToNtpEstimator::params()
-    const {
-  return params_;
+double RtpToNtpEstimator::EstimatedFrequencyKhz() const {
+  if (!params_.has_value()) {
+    return 0.0;
+  }
+  static constexpr double kNtpUnitPerMs = 4.294967296E6;  // 2^32 / 1000.
+  return kNtpUnitPerMs / params_->slope;
 }
+
 }  // namespace webrtc
diff --git a/system_wrappers/source/rtp_to_ntp_estimator_unittest.cc b/system_wrappers/source/rtp_to_ntp_estimator_unittest.cc
index 14bc6e0..29de761 100644
--- a/system_wrappers/source/rtp_to_ntp_estimator_unittest.cc
+++ b/system_wrappers/source/rtp_to_ntp_estimator_unittest.cc
@@ -17,332 +17,249 @@
 
 namespace webrtc {
 namespace {
-const uint32_t kOneMsInNtpFrac = 4294967;
-const uint32_t kOneHourInNtpSec = 60 * 60;
-const uint32_t kTimestampTicksPerMs = 90;
+constexpr uint64_t kOneMsInNtp = 4294967;
+constexpr uint64_t kOneHourInNtp = uint64_t{60 * 60} << 32;
+constexpr uint32_t kTimestampTicksPerMs = 90;
 }  // namespace
 
 TEST(WrapAroundTests, OldRtcpWrapped_OldRtpTimestamp) {
   RtpToNtpEstimator estimator;
-  bool new_sr;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 1;
-  uint32_t timestamp = 0;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp -= kTimestampTicksPerMs;
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(kOneMsInNtp), 0),
+            RtpToNtpEstimator::kNewMeasurement);
   // No wraparound will be detected, since we are not allowed to wrap below 0,
   // but there will be huge rtp timestamp jump, e.g. old_timestamp = 0,
   // new_timestamp = 4294967295, which should be detected.
-  EXPECT_FALSE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2 * kOneMsInNtp),
+                                         -kTimestampTicksPerMs),
+            RtpToNtpEstimator::kInvalidMeasurement);
 }
 
 TEST(WrapAroundTests, OldRtcpWrapped_OldRtpTimestamp_Wraparound_Detected) {
   RtpToNtpEstimator estimator;
-  bool new_sr;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 1;
-  uint32_t timestamp = 0xFFFFFFFE;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += 2 * kOneMsInNtpFrac;
-  timestamp += 2 * kTimestampTicksPerMs;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp -= kTimestampTicksPerMs;
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFE),
+            RtpToNtpEstimator::kNewMeasurement);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + 2 * kOneMsInNtp),
+                                         0xFFFFFFFE + 2 * kTimestampTicksPerMs),
+            RtpToNtpEstimator::kNewMeasurement);
   // Expected to fail since the older RTCP has a smaller RTP timestamp than the
   // newer (old:10, new:4294967206).
-  EXPECT_FALSE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + 3 * kOneMsInNtp),
+                                         0xFFFFFFFE + kTimestampTicksPerMs),
+            RtpToNtpEstimator::kInvalidMeasurement);
 }
 
 TEST(WrapAroundTests, NewRtcpWrapped) {
   RtpToNtpEstimator estimator;
-  bool new_sr;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 1;
-  uint32_t timestamp = 0xFFFFFFFF;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp += kTimestampTicksPerMs;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  int64_t timestamp_ms = -1;
-  EXPECT_TRUE(estimator.Estimate(0xFFFFFFFF, &timestamp_ms));
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
+            RtpToNtpEstimator::kNewMeasurement);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
+                                         0xFFFFFFFF + kTimestampTicksPerMs),
+            RtpToNtpEstimator::kNewMeasurement);
   // Since this RTP packet has the same timestamp as the RTCP packet constructed
   // at time 0 it should be mapped to 0 as well.
-  EXPECT_EQ(0, timestamp_ms);
+  EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1));
 }
 
 TEST(WrapAroundTests, RtpWrapped) {
   RtpToNtpEstimator estimator;
-  bool new_sr;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 1;
-  uint32_t timestamp = 0xFFFFFFFF - 2 * kTimestampTicksPerMs;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp += kTimestampTicksPerMs;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1),
+                                         0xFFFFFFFF - 2 * kTimestampTicksPerMs),
+            RtpToNtpEstimator::kNewMeasurement);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
+                                         0xFFFFFFFF - kTimestampTicksPerMs),
+            RtpToNtpEstimator::kNewMeasurement);
 
-  int64_t timestamp_ms = -1;
-  EXPECT_TRUE(
-      estimator.Estimate(0xFFFFFFFF - 2 * kTimestampTicksPerMs, &timestamp_ms));
   // Since this RTP packet has the same timestamp as the RTCP packet constructed
   // at time 0 it should be mapped to 0 as well.
-  EXPECT_EQ(0, timestamp_ms);
+  EXPECT_EQ(estimator.Estimate(0xFFFFFFFF - 2 * kTimestampTicksPerMs),
+            NtpTime(1));
   // Two kTimestampTicksPerMs advanced.
-  timestamp += kTimestampTicksPerMs;
-  EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
-  EXPECT_EQ(2, timestamp_ms);
+  EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1 + 2 * kOneMsInNtp));
   // Wrapped rtp.
-  timestamp += kTimestampTicksPerMs;
-  EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
-  EXPECT_EQ(3, timestamp_ms);
+  EXPECT_EQ(estimator.Estimate(0xFFFFFFFF + kTimestampTicksPerMs),
+            NtpTime(1 + 3 * kOneMsInNtp));
 }
 
 TEST(WrapAroundTests, OldRtp_RtcpsWrapped) {
   RtpToNtpEstimator estimator;
-  bool new_sr;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 1;
-  uint32_t timestamp = 0xFFFFFFFF;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp += kTimestampTicksPerMs;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  timestamp -= 2 * kTimestampTicksPerMs;
-  int64_t timestamp_ms = 0xFFFFFFFF;
-  EXPECT_FALSE(estimator.Estimate(timestamp, &timestamp_ms));
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
+            RtpToNtpEstimator::kNewMeasurement);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
+                                         0xFFFFFFFF + kTimestampTicksPerMs),
+            RtpToNtpEstimator::kNewMeasurement);
+
+  EXPECT_FALSE(estimator.Estimate(0xFFFFFFFF - kTimestampTicksPerMs).Valid());
 }
 
 TEST(WrapAroundTests, OldRtp_NewRtcpWrapped) {
   RtpToNtpEstimator estimator;
-  bool new_sr;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 1;
-  uint32_t timestamp = 0xFFFFFFFF;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp += kTimestampTicksPerMs;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  timestamp -= kTimestampTicksPerMs;
-  int64_t timestamp_ms = -1;
-  EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
+            RtpToNtpEstimator::kNewMeasurement);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
+                                         0xFFFFFFFF + kTimestampTicksPerMs),
+            RtpToNtpEstimator::kNewMeasurement);
+
   // Constructed at the same time as the first RTCP and should therefore be
   // mapped to zero.
-  EXPECT_EQ(0, timestamp_ms);
+  EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1));
 }
 
 TEST(WrapAroundTests, GracefullyHandleRtpJump) {
   RtpToNtpEstimator estimator;
-  bool new_sr;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 1;
-  uint32_t timestamp = 0;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp += kTimestampTicksPerMs;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp -= kTimestampTicksPerMs;
-  int64_t timestamp_ms = -1;
-  EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
+            RtpToNtpEstimator::kNewMeasurement);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
+                                         0xFFFFFFFF + kTimestampTicksPerMs),
+            RtpToNtpEstimator::kNewMeasurement);
+
   // Constructed at the same time as the first RTCP and should therefore be
   // mapped to zero.
-  EXPECT_EQ(0, timestamp_ms);
+  EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1));
 
-  timestamp -= 0xFFFFF;
+  uint32_t timestamp = 0xFFFFFFFF - 0xFFFFF;
+  uint64_t ntp_raw = 1 + 2 * kOneMsInNtp;
   for (int i = 0; i < RtpToNtpEstimator::kMaxInvalidSamples - 1; ++i) {
-    EXPECT_FALSE(
-        estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-    ntp_frac += kOneMsInNtpFrac;
+    EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
+              RtpToNtpEstimator::kInvalidMeasurement);
+    ntp_raw += kOneMsInNtp;
     timestamp += kTimestampTicksPerMs;
   }
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += kOneMsInNtpFrac;
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
+            RtpToNtpEstimator::kNewMeasurement);
+  ntp_raw += kOneMsInNtp;
   timestamp += kTimestampTicksPerMs;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp += kTimestampTicksPerMs;
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
+            RtpToNtpEstimator::kNewMeasurement);
 
-  timestamp_ms = -1;
-  EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
-  // 6 milliseconds has passed since the start of the test.
-  EXPECT_EQ(6, timestamp_ms);
+  EXPECT_EQ(estimator.Estimate(timestamp), NtpTime(ntp_raw));
 }
 
 TEST(UpdateRtcpMeasurementTests, FailsForZeroNtp) {
   RtpToNtpEstimator estimator;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 0;
-  uint32_t timestamp = 0x12345678;
-  bool new_sr;
-  EXPECT_FALSE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_FALSE(new_sr);
+
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(0), 0x12345678),
+            RtpToNtpEstimator::kInvalidMeasurement);
 }
 
 TEST(UpdateRtcpMeasurementTests, FailsForEqualNtp) {
   RtpToNtpEstimator estimator;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 699925050;
+  NtpTime ntp(0, 699925050);
   uint32_t timestamp = 0x12345678;
-  bool new_sr;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_TRUE(new_sr);
+
+  EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp),
+            RtpToNtpEstimator::kNewMeasurement);
   // Ntp time already added, list not updated.
-  ++timestamp;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_FALSE(new_sr);
+  EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp + 1),
+            RtpToNtpEstimator::kSameMeasurement);
 }
 
 TEST(UpdateRtcpMeasurementTests, FailsForOldNtp) {
   RtpToNtpEstimator estimator;
-  uint32_t ntp_sec = 1;
-  uint32_t ntp_frac = 699925050;
+  uint64_t ntp_raw = 699925050;
+  NtpTime ntp(ntp_raw);
   uint32_t timestamp = 0x12345678;
-  bool new_sr;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_TRUE(new_sr);
+  EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp),
+            RtpToNtpEstimator::kNewMeasurement);
+
   // Old ntp time, list not updated.
-  ntp_frac -= kOneMsInNtpFrac;
-  timestamp += kTimestampTicksPerMs;
-  EXPECT_FALSE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw - kOneMsInNtp),
+                                         timestamp + kTimestampTicksPerMs),
+            RtpToNtpEstimator::kInvalidMeasurement);
 }
 
 TEST(UpdateRtcpMeasurementTests, FailsForTooNewNtp) {
   RtpToNtpEstimator estimator;
-  uint32_t ntp_sec = 1;
-  uint32_t ntp_frac = 699925050;
+
+  uint64_t ntp_raw = 699925050;
   uint32_t timestamp = 0x12345678;
-  bool new_sr;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_TRUE(new_sr);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
+            RtpToNtpEstimator::kNewMeasurement);
+
   // Ntp time from far future, list not updated.
-  ntp_sec += kOneHourInNtpSec * 2;
-  timestamp += kTimestampTicksPerMs * 10;
-  EXPECT_FALSE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw + 2 * kOneHourInNtp),
+                                         timestamp + 10 * kTimestampTicksPerMs),
+            RtpToNtpEstimator::kInvalidMeasurement);
 }
 
 TEST(UpdateRtcpMeasurementTests, FailsForEqualTimestamp) {
   RtpToNtpEstimator estimator;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 2;
+
   uint32_t timestamp = 0x12345678;
-  bool new_sr;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_TRUE(new_sr);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2), timestamp),
+            RtpToNtpEstimator::kNewMeasurement);
   // Timestamp already added, list not updated.
-  ++ntp_frac;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_FALSE(new_sr);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(3), timestamp),
+            RtpToNtpEstimator::kSameMeasurement);
 }
 
 TEST(UpdateRtcpMeasurementTests, FailsForOldRtpTimestamp) {
   RtpToNtpEstimator estimator;
-  uint32_t ntp_sec = 0;
-  uint32_t ntp_frac = 2;
   uint32_t timestamp = 0x12345678;
-  bool new_sr;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_TRUE(new_sr);
+
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2), timestamp),
+            RtpToNtpEstimator::kNewMeasurement);
   // Old timestamp, list not updated.
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp -= kTimestampTicksPerMs;
-  EXPECT_FALSE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_FALSE(new_sr);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2 + kOneMsInNtp),
+                                         timestamp - kTimestampTicksPerMs),
+            RtpToNtpEstimator::kInvalidMeasurement);
 }
 
 TEST(UpdateRtcpMeasurementTests, VerifyParameters) {
   RtpToNtpEstimator estimator;
-  uint32_t ntp_sec = 1;
-  uint32_t ntp_frac = 2;
   uint32_t timestamp = 0x12345678;
-  bool new_sr;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_TRUE(new_sr);
-  EXPECT_FALSE(estimator.params());
+
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(kOneMsInNtp), timestamp),
+            RtpToNtpEstimator::kNewMeasurement);
+
+  EXPECT_DOUBLE_EQ(estimator.EstimatedFrequencyKhz(), 0.0);
+
   // Add second report, parameters should be calculated.
-  ntp_frac += kOneMsInNtpFrac;
-  timestamp += kTimestampTicksPerMs;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_TRUE(estimator.params());
-  EXPECT_DOUBLE_EQ(90.0, estimator.params()->frequency_khz);
-  EXPECT_NE(0.0, estimator.params()->offset_ms);
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2 * kOneMsInNtp),
+                                         timestamp + kTimestampTicksPerMs),
+            RtpToNtpEstimator::kNewMeasurement);
+
+  EXPECT_NEAR(estimator.EstimatedFrequencyKhz(), kTimestampTicksPerMs, 0.01);
 }
 
 TEST(RtpToNtpTests, FailsForNoParameters) {
   RtpToNtpEstimator estimator;
-  uint32_t ntp_sec = 1;
-  uint32_t ntp_frac = 2;
   uint32_t timestamp = 0x12345678;
-  bool new_sr;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_TRUE(new_sr);
+
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), timestamp),
+            RtpToNtpEstimator::kNewMeasurement);
   // Parameters are not calculated, conversion of RTP to NTP time should fail.
-  EXPECT_FALSE(estimator.params());
-  int64_t timestamp_ms = -1;
-  EXPECT_FALSE(estimator.Estimate(timestamp, &timestamp_ms));
+  EXPECT_DOUBLE_EQ(estimator.EstimatedFrequencyKhz(), 0.0);
+  EXPECT_FALSE(estimator.Estimate(timestamp).Valid());
 }
 
 TEST(RtpToNtpTests, AveragesErrorOut) {
   RtpToNtpEstimator estimator;
-  uint32_t ntp_sec = 1;
-  uint32_t ntp_frac = 90000000;  // More than 1 ms.
+  uint64_t ntp_raw = 90000000;  // More than 1 ms.
+  ASSERT_GT(ntp_raw, kOneMsInNtp);
   uint32_t timestamp = 0x12345678;
-  const int kNtpSecStep = 1;  // 1 second.
-  const int kRtpTicksPerMs = 90;
-  const int kRtpStep = kRtpTicksPerMs * 1000;
-  bool new_sr;
-  EXPECT_TRUE(
-      estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
-  EXPECT_TRUE(new_sr);
+  constexpr uint64_t kNtpSecStep = uint64_t{1} << 32;  // 1 second.
+  constexpr int kRtpTicksPerMs = 90;
+  constexpr int kRtpStep = kRtpTicksPerMs * 1000;
+
+  EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
+            RtpToNtpEstimator::kNewMeasurement);
 
   Random rand(1123536L);
   for (size_t i = 0; i < 1000; i++) {
     // Advance both timestamps by exactly 1 second.
-    ntp_sec += kNtpSecStep;
+    ntp_raw += kNtpSecStep;
     timestamp += kRtpStep;
     // Add upto 1ms of errors to NTP and RTP timestamps passed to estimator.
-    EXPECT_TRUE(estimator.UpdateMeasurements(
-        ntp_sec,
-        ntp_frac + rand.Rand(-static_cast<int>(kOneMsInNtpFrac),
-                             static_cast<int>(kOneMsInNtpFrac)),
-        timestamp + rand.Rand(-kRtpTicksPerMs, kRtpTicksPerMs), &new_sr));
-    EXPECT_TRUE(new_sr);
+    EXPECT_EQ(
+        estimator.UpdateMeasurements(
+            NtpTime(ntp_raw + rand.Rand(-int{kOneMsInNtp}, int{kOneMsInNtp})),
+            timestamp + rand.Rand(-kRtpTicksPerMs, kRtpTicksPerMs)),
+        RtpToNtpEstimator::kNewMeasurement);
 
-    int64_t estimated_ntp_ms;
-    EXPECT_TRUE(estimator.Estimate(timestamp, &estimated_ntp_ms));
+    NtpTime estimated_ntp = estimator.Estimate(timestamp);
+    EXPECT_TRUE(estimated_ntp.Valid());
     // Allow upto 2 ms of error.
-    EXPECT_NEAR(NtpTime(ntp_sec, ntp_frac).ToMs(), estimated_ntp_ms, 2);
+    EXPECT_NEAR(ntp_raw, static_cast<uint64_t>(estimated_ntp), 2 * kOneMsInNtp);
   }
 }
 
diff --git a/video/rtp_streams_synchronizer2.cc b/video/rtp_streams_synchronizer2.cc
index 4096fce..0fbb391 100644
--- a/video/rtp_streams_synchronizer2.cc
+++ b/video/rtp_streams_synchronizer2.cc
@@ -29,10 +29,10 @@
                         const Syncable::Info& info) {
   stream->latest_timestamp = info.latest_received_capture_timestamp;
   stream->latest_receive_time_ms = info.latest_receive_time_ms;
-  bool new_rtcp_sr = false;
   return stream->rtp_to_ntp.UpdateMeasurements(
-      info.capture_time_ntp_secs, info.capture_time_ntp_frac,
-      info.capture_time_source_clock, &new_rtcp_sr);
+             NtpTime(info.capture_time_ntp_secs, info.capture_time_ntp_frac),
+             info.capture_time_source_clock) !=
+         RtpToNtpEstimator::kInvalidMeasurement;
 }
 
 }  // namespace
@@ -183,32 +183,35 @@
     return false;
   }
 
-  int64_t latest_audio_ntp;
-  if (!audio_measurement_.rtp_to_ntp.Estimate(audio_rtp_timestamp,
-                                              &latest_audio_ntp)) {
+  NtpTime latest_audio_ntp =
+      audio_measurement_.rtp_to_ntp.Estimate(audio_rtp_timestamp);
+  if (!latest_audio_ntp.Valid()) {
     return false;
   }
+  int64_t latest_audio_ntp_ms = latest_audio_ntp.ToMs();
 
-  syncable_audio_->SetEstimatedPlayoutNtpTimestampMs(latest_audio_ntp, time_ms);
+  syncable_audio_->SetEstimatedPlayoutNtpTimestampMs(latest_audio_ntp_ms,
+                                                     time_ms);
 
-  int64_t latest_video_ntp;
-  if (!video_measurement_.rtp_to_ntp.Estimate(rtp_timestamp,
-                                              &latest_video_ntp)) {
+  NtpTime latest_video_ntp =
+      video_measurement_.rtp_to_ntp.Estimate(rtp_timestamp);
+  if (!latest_video_ntp.Valid()) {
     return false;
   }
+  int64_t latest_video_ntp_ms = latest_video_ntp.ToMs();
 
   // Current audio ntp.
   int64_t now_ms = rtc::TimeMillis();
-  latest_audio_ntp += (now_ms - time_ms);
+  latest_audio_ntp_ms += (now_ms - time_ms);
 
   // Remove video playout delay.
   int64_t time_to_render_ms = render_time_ms - now_ms;
   if (time_to_render_ms > 0)
-    latest_video_ntp -= time_to_render_ms;
+    latest_video_ntp_ms -= time_to_render_ms;
 
-  *video_playout_ntp_ms = latest_video_ntp;
-  *stream_offset_ms = latest_audio_ntp - latest_video_ntp;
-  *estimated_freq_khz = video_measurement_.rtp_to_ntp.params()->frequency_khz;
+  *video_playout_ntp_ms = latest_video_ntp_ms;
+  *stream_offset_ms = latest_audio_ntp_ms - latest_video_ntp_ms;
+  *estimated_freq_khz = video_measurement_.rtp_to_ntp.EstimatedFrequencyKhz();
   return true;
 }
 
diff --git a/video/stream_synchronization.cc b/video/stream_synchronization.cc
index d5c77c1..d86cc79 100644
--- a/video/stream_synchronization.cc
+++ b/video/stream_synchronization.cc
@@ -35,19 +35,19 @@
     const Measurements& audio_measurement,
     const Measurements& video_measurement,
     int* relative_delay_ms) {
-  int64_t audio_last_capture_time_ms;
-  if (!audio_measurement.rtp_to_ntp.Estimate(audio_measurement.latest_timestamp,
-                                             &audio_last_capture_time_ms)) {
+  NtpTime audio_last_capture_time =
+      audio_measurement.rtp_to_ntp.Estimate(audio_measurement.latest_timestamp);
+  if (!audio_last_capture_time.Valid()) {
     return false;
   }
-  int64_t video_last_capture_time_ms;
-  if (!video_measurement.rtp_to_ntp.Estimate(video_measurement.latest_timestamp,
-                                             &video_last_capture_time_ms)) {
+  NtpTime video_last_capture_time =
+      video_measurement.rtp_to_ntp.Estimate(video_measurement.latest_timestamp);
+  if (!video_last_capture_time.Valid()) {
     return false;
   }
-  if (video_last_capture_time_ms < 0) {
-    return false;
-  }
+  int64_t audio_last_capture_time_ms = audio_last_capture_time.ToMs();
+  int64_t video_last_capture_time_ms = video_last_capture_time.ToMs();
+
   // Positive diff means that video_measurement is behind audio_measurement.
   *relative_delay_ms =
       video_measurement.latest_receive_time_ms -
diff --git a/video/stream_synchronization_unittest.cc b/video/stream_synchronization_unittest.cc
index 5c6c79f..b733a1d 100644
--- a/video/stream_synchronization_unittest.cc
+++ b/video/stream_synchronization_unittest.cc
@@ -47,32 +47,31 @@
         static_cast<int>(kDefaultVideoFrequency * video_clock_drift_ + 0.5);
 
     // Generate NTP/RTP timestamp pair for both streams corresponding to RTCP.
-    bool new_sr;
     StreamSynchronization::Measurements audio;
     StreamSynchronization::Measurements video;
     NtpTime ntp_time = clock_sender_.CurrentNtpTime();
     uint32_t rtp_timestamp =
         clock_sender_.CurrentTime().ms() * audio_frequency / 1000;
-    EXPECT_TRUE(audio.rtp_to_ntp.UpdateMeasurements(
-        ntp_time.seconds(), ntp_time.fractions(), rtp_timestamp, &new_sr));
+    EXPECT_EQ(audio.rtp_to_ntp.UpdateMeasurements(ntp_time, rtp_timestamp),
+              RtpToNtpEstimator::kNewMeasurement);
     clock_sender_.AdvanceTimeMilliseconds(100);
     clock_receiver_.AdvanceTimeMilliseconds(100);
     ntp_time = clock_sender_.CurrentNtpTime();
     rtp_timestamp = clock_sender_.CurrentTime().ms() * video_frequency / 1000;
-    EXPECT_TRUE(video.rtp_to_ntp.UpdateMeasurements(
-        ntp_time.seconds(), ntp_time.fractions(), rtp_timestamp, &new_sr));
+    EXPECT_EQ(video.rtp_to_ntp.UpdateMeasurements(ntp_time, rtp_timestamp),
+              RtpToNtpEstimator::kNewMeasurement);
     clock_sender_.AdvanceTimeMilliseconds(900);
     clock_receiver_.AdvanceTimeMilliseconds(900);
     ntp_time = clock_sender_.CurrentNtpTime();
     rtp_timestamp = clock_sender_.CurrentTime().ms() * audio_frequency / 1000;
-    EXPECT_TRUE(audio.rtp_to_ntp.UpdateMeasurements(
-        ntp_time.seconds(), ntp_time.fractions(), rtp_timestamp, &new_sr));
+    EXPECT_EQ(audio.rtp_to_ntp.UpdateMeasurements(ntp_time, rtp_timestamp),
+              RtpToNtpEstimator::kNewMeasurement);
     clock_sender_.AdvanceTimeMilliseconds(100);
     clock_receiver_.AdvanceTimeMilliseconds(100);
     ntp_time = clock_sender_.CurrentNtpTime();
     rtp_timestamp = clock_sender_.CurrentTime().ms() * video_frequency / 1000;
-    EXPECT_TRUE(video.rtp_to_ntp.UpdateMeasurements(
-        ntp_time.seconds(), ntp_time.fractions(), rtp_timestamp, &new_sr));
+    EXPECT_EQ(video.rtp_to_ntp.UpdateMeasurements(ntp_time, rtp_timestamp),
+              RtpToNtpEstimator::kNewMeasurement);
     clock_sender_.AdvanceTimeMilliseconds(900);
     clock_receiver_.AdvanceTimeMilliseconds(900);