Migrate VCMInterFrameDelay to use Time units

Additionally,
* Moved to its own GN target.
* Added unittests.
* Removed unused variable `_zeroWallClock`.
* Renamed variables to match style guide.
* Moved fields _dTS and _wrapArounds to variables.

Change-Id: I7aa8b8dec55abab49ceabe838dabf2a7e13d685d
Bug: webrtc:13756
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/253580
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Evan Shrubsole <eshr@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36147}
diff --git a/api/video/encoded_frame.cc b/api/video/encoded_frame.cc
index 56d199f..c5e2abb 100644
--- a/api/video/encoded_frame.cc
+++ b/api/video/encoded_frame.cc
@@ -14,6 +14,12 @@
 
 namespace webrtc {
 
+absl::optional<Timestamp> EncodedFrame::ReceivedTimestamp() const {
+  return ReceivedTime() >= 0
+             ? absl::make_optional(Timestamp::Millis(ReceivedTime()))
+             : absl::nullopt;
+}
+
 absl::optional<Timestamp> EncodedFrame::RenderTimestamp() const {
   return RenderTimeMs() >= 0
              ? absl::make_optional(Timestamp::Millis(RenderTimeMs()))
diff --git a/api/video/encoded_frame.h b/api/video/encoded_frame.h
index 610c988..66aee22 100644
--- a/api/video/encoded_frame.h
+++ b/api/video/encoded_frame.h
@@ -34,6 +34,9 @@
   // When this frame was received.
   // TODO(bugs.webrtc.org/13756): Use Timestamp instead of int.
   virtual int64_t ReceivedTime() const = 0;
+  // Returns a Timestamp from `ReceivedTime`, or nullopt if there is no receive
+  // time.
+  absl::optional<webrtc::Timestamp> ReceivedTimestamp() const;
 
   // When this frame should be rendered.
   // TODO(bugs.webrtc.org/13756): Use Timestamp instead of int.
diff --git a/modules/video_coding/BUILD.gn b/modules/video_coding/BUILD.gn
index a1b2d07..7d6b5da 100644
--- a/modules/video_coding/BUILD.gn
+++ b/modules/video_coding/BUILD.gn
@@ -234,6 +234,20 @@
   absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
 }
 
+rtc_library("inter_frame_delay") {
+  sources = [
+    "inter_frame_delay.cc",
+    "inter_frame_delay.h",
+  ]
+  deps = [
+    "..:module_api_public",
+    "../../api/units:frequency",
+    "../../api/units:time_delta",
+    "../../api/units:timestamp",
+  ]
+  absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
+}
+
 rtc_library("video_coding") {
   visibility = [ "*" ]
   sources = [
@@ -253,8 +267,6 @@
     "h264_sps_pps_tracker.cc",
     "h264_sps_pps_tracker.h",
     "include/video_codec_initializer.h",
-    "inter_frame_delay.cc",
-    "inter_frame_delay.h",
     "internal_defines.h",
     "loss_notification_controller.cc",
     "loss_notification_controller.h",
@@ -286,8 +298,8 @@
     ":encoded_frame",
     ":frame_buffer",
     ":frame_helpers",
+    ":inter_frame_delay",
     ":jitter_estimator",
-    ":packet_buffer",
     ":rtt_filter",
     ":timing",
     ":video_codec_interface",
@@ -402,6 +414,7 @@
   deps = [
     ":codec_globals_headers",
     ":encoded_frame",
+    ":inter_frame_delay",
     ":jitter_estimator",
     ":timing",
     ":video_codec_interface",
@@ -1107,6 +1120,7 @@
       "h264_sprop_parameter_sets_unittest.cc",
       "h264_sps_pps_tracker_unittest.cc",
       "histogram_unittest.cc",
+      "inter_frame_delay_unittest.cc",
       "jitter_buffer_unittest.cc",
       "jitter_estimator_tests.cc",
       "loss_notification_controller_unittest.cc",
@@ -1151,6 +1165,7 @@
       ":frame_buffer",
       ":frame_dependencies_calculator",
       ":h264_packet_buffer",
+      ":inter_frame_delay",
       ":jitter_estimator",
       ":nack_requester",
       ":packet_buffer",
diff --git a/modules/video_coding/frame_buffer2.cc b/modules/video_coding/frame_buffer2.cc
index d7944db..de23981 100644
--- a/modules/video_coding/frame_buffer2.cc
+++ b/modules/video_coding/frame_buffer2.cc
@@ -64,7 +64,6 @@
       callback_queue_(nullptr),
       jitter_estimator_(clock),
       timing_(timing),
-      inter_frame_delay_(clock_->TimeInMilliseconds()),
       stopped_(false),
       protection_mode_(kProtectionNack),
       stats_callback_(stats_callback),
@@ -295,12 +294,11 @@
   }
 
   if (!superframe_delayed_by_retransmission) {
-    int64_t frame_delay;
+    auto frame_delay = inter_frame_delay_.CalculateDelay(
+        first_frame.Timestamp(), Timestamp::Millis(receive_time_ms));
 
-    if (inter_frame_delay_.CalculateDelay(first_frame.Timestamp(), &frame_delay,
-                                          receive_time_ms)) {
-      jitter_estimator_.UpdateEstimate(TimeDelta::Millis(frame_delay),
-                                       superframe_size);
+    if (frame_delay) {
+      jitter_estimator_.UpdateEstimate(*frame_delay, superframe_size);
     }
 
     float rtt_mult = protection_mode_ == kProtectionNackFEC ? 0.0 : 1.0;
diff --git a/modules/video_coding/inter_frame_delay.cc b/modules/video_coding/inter_frame_delay.cc
index d0c21aa..8cdb664 100644
--- a/modules/video_coding/inter_frame_delay.cc
+++ b/modules/video_coding/inter_frame_delay.cc
@@ -10,85 +10,62 @@
 
 #include "modules/video_coding/inter_frame_delay.h"
 
+#include "absl/types/optional.h"
+#include "api/units/frequency.h"
+#include "api/units/time_delta.h"
+#include "modules/include/module_common_types_public.h"
+
 namespace webrtc {
 
-VCMInterFrameDelay::VCMInterFrameDelay(int64_t currentWallClock) {
-  Reset(currentWallClock);
+namespace {
+constexpr Frequency k90kHz = Frequency::KiloHertz(90);
+}
+
+VCMInterFrameDelay::VCMInterFrameDelay() {
+  Reset();
 }
 
 // Resets the delay estimate.
-void VCMInterFrameDelay::Reset(int64_t currentWallClock) {
-  _zeroWallClock = currentWallClock;
-  _wrapArounds = 0;
-  _prevWallClock = 0;
-  _prevTimestamp = 0;
-  _dTS = 0;
+void VCMInterFrameDelay::Reset() {
+  prev_wall_clock_ = absl::nullopt;
+  prev_rtp_timestamp_unwrapped_ = 0;
 }
 
 // Calculates the delay of a frame with the given timestamp.
 // This method is called when the frame is complete.
-bool VCMInterFrameDelay::CalculateDelay(uint32_t timestamp,
-                                        int64_t* delay,
-                                        int64_t currentWallClock) {
-  if (_prevWallClock == 0) {
+absl::optional<TimeDelta> VCMInterFrameDelay::CalculateDelay(
+    uint32_t rtp_timestamp,
+    Timestamp now) {
+  int64_t rtp_timestamp_unwrapped = unwrapper_.Unwrap(rtp_timestamp);
+  if (!prev_wall_clock_) {
     // First set of data, initialization, wait for next frame.
-    _prevWallClock = currentWallClock;
-    _prevTimestamp = timestamp;
-    *delay = 0;
-    return true;
+    prev_wall_clock_ = now;
+    prev_rtp_timestamp_unwrapped_ = rtp_timestamp_unwrapped;
+    return TimeDelta::Zero();
   }
 
-  int32_t prevWrapArounds = _wrapArounds;
-  CheckForWrapArounds(timestamp);
-
-  // This will be -1 for backward wrap arounds and +1 for forward wrap arounds.
-  int32_t wrapAroundsSincePrev = _wrapArounds - prevWrapArounds;
-
   // Account for reordering in jitter variance estimate in the future?
   // Note that this also captures incomplete frames which are grabbed for
   // decoding after a later frame has been complete, i.e. real packet losses.
-  if ((wrapAroundsSincePrev == 0 && timestamp < _prevTimestamp) ||
-      wrapAroundsSincePrev < 0) {
-    *delay = 0;
-    return false;
+  uint32_t cropped_last = static_cast<uint32_t>(prev_rtp_timestamp_unwrapped_);
+  if (rtp_timestamp_unwrapped < prev_rtp_timestamp_unwrapped_ ||
+      !IsNewerTimestamp(rtp_timestamp, cropped_last)) {
+    return absl::nullopt;
   }
 
-  // Compute the compensated timestamp difference and convert it to ms and round
-  // it to closest integer.
-  _dTS = static_cast<int64_t>(
-      (timestamp + wrapAroundsSincePrev * (static_cast<int64_t>(1) << 32) -
-       _prevTimestamp) /
-          90.0 +
-      0.5);
+  // Compute the compensated timestamp difference.
+  int64_t d_rtp_ticks = rtp_timestamp_unwrapped - prev_rtp_timestamp_unwrapped_;
+  TimeDelta dts = d_rtp_ticks / k90kHz;
+  TimeDelta dt = now - *prev_wall_clock_;
 
   // frameDelay is the difference of dT and dTS -- i.e. the difference of the
   // wall clock time difference and the timestamp difference between two
   // following frames.
-  *delay = static_cast<int64_t>(currentWallClock - _prevWallClock - _dTS);
+  TimeDelta delay = dt - dts;
 
-  _prevTimestamp = timestamp;
-  _prevWallClock = currentWallClock;
-
-  return true;
+  prev_rtp_timestamp_unwrapped_ = rtp_timestamp_unwrapped;
+  prev_wall_clock_ = now;
+  return delay;
 }
 
-// Investigates if the timestamp clock has overflowed since the last timestamp
-// and keeps track of the number of wrap arounds since reset.
-void VCMInterFrameDelay::CheckForWrapArounds(uint32_t timestamp) {
-  if (timestamp < _prevTimestamp) {
-    // This difference will probably be less than -2^31 if we have had a wrap
-    // around (e.g. timestamp = 1, _prevTimestamp = 2^32 - 1). Since it is cast
-    // to a int32_t, it should be positive.
-    if (static_cast<int32_t>(timestamp - _prevTimestamp) > 0) {
-      // Forward wrap around.
-      _wrapArounds++;
-    }
-    // This difference will probably be less than -2^31 if we have had a
-    // backward wrap around. Since it is cast to a int32_t, it should be
-    // positive.
-  } else if (static_cast<int32_t>(_prevTimestamp - timestamp) > 0) {
-    // Backward wrap around.
-    _wrapArounds--;
-  }
-}
 }  // namespace webrtc
diff --git a/modules/video_coding/inter_frame_delay.h b/modules/video_coding/inter_frame_delay.h
index f121c61..e0fee6b 100644
--- a/modules/video_coding/inter_frame_delay.h
+++ b/modules/video_coding/inter_frame_delay.h
@@ -13,46 +13,32 @@
 
 #include <stdint.h>
 
+#include "absl/types/optional.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "modules/include/module_common_types_public.h"
+
 namespace webrtc {
 
 class VCMInterFrameDelay {
  public:
-  explicit VCMInterFrameDelay(int64_t currentWallClock);
+  VCMInterFrameDelay();
 
   // Resets the estimate. Zeros are given as parameters.
-  void Reset(int64_t currentWallClock);
+  void Reset();
 
   // Calculates the delay of a frame with the given timestamp.
   // This method is called when the frame is complete.
-  //
-  // Input:
-  //          - timestamp         : RTP timestamp of a received frame.
-  //          - *delay            : Pointer to memory where the result should be
-  //                                stored.
-  //          - currentWallClock  : The current time in milliseconds.
-  //                                Should be -1 for normal operation, only used
-  //                                for testing.
-  // Return value                 : true if OK, false when reordered timestamps.
-  bool CalculateDelay(uint32_t timestamp,
-                      int64_t* delay,
-                      int64_t currentWallClock);
+  absl::optional<TimeDelta> CalculateDelay(uint32_t rtp_timestamp,
+                                           Timestamp now);
 
  private:
-  // Controls if the RTP timestamp counter has had a wrap around between the
-  // current and the previously received frame.
-  //
-  // Input:
-  //          - timestamp         : RTP timestamp of the current frame.
-  void CheckForWrapArounds(uint32_t timestamp);
+  // The previous rtp timestamp passed to the delay estimate
+  int64_t prev_rtp_timestamp_unwrapped_;
+  TimestampUnwrapper unwrapper_;
 
-  int64_t _zeroWallClock;  // Local timestamp of the first video packet received
-  int32_t _wrapArounds;    // Number of wrapArounds detected
-  // The previous timestamp passed to the delay estimate
-  uint32_t _prevTimestamp;
   // The previous wall clock timestamp used by the delay estimate
-  int64_t _prevWallClock;
-  // Wrap-around compensated difference between incoming timestamps
-  int64_t _dTS;
+  absl::optional<Timestamp> prev_wall_clock_;
 };
 
 }  // namespace webrtc
diff --git a/modules/video_coding/inter_frame_delay_unittest.cc b/modules/video_coding/inter_frame_delay_unittest.cc
new file mode 100644
index 0000000..a338ba9
--- /dev/null
+++ b/modules/video_coding/inter_frame_delay_unittest.cc
@@ -0,0 +1,190 @@
+/*
+ *  Copyright (c) 2022 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 "modules/video_coding/inter_frame_delay.h"
+
+#include <limits>
+
+#include "absl/types/optional.h"
+#include "api/units/frequency.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+#include "system_wrappers/include/clock.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+namespace {
+
+// Test is for frames at 30fps. At 30fps, RTP timestamps will increase by
+// 90000 / 30 = 3000 ticks per frame.
+constexpr Frequency k30Fps = Frequency::Hertz(30);
+constexpr TimeDelta kFrameDelay = 1 / k30Fps;
+constexpr uint32_t kRtpTicksPerFrame = Frequency::KiloHertz(90) / k30Fps;
+constexpr Timestamp kStartTime = Timestamp::Millis(1337);
+
+}  // namespace
+
+using ::testing::Eq;
+using ::testing::Optional;
+
+TEST(InterFrameDelayTest, OldRtpTimestamp) {
+  VCMInterFrameDelay inter_frame_delay;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(180000, kStartTime),
+              Optional(TimeDelta::Zero()));
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(90000, kStartTime),
+              Eq(absl::nullopt));
+}
+
+TEST(InterFrameDelayTest, NegativeWrapAroundIsSameAsOldRtpTimestamp) {
+  VCMInterFrameDelay inter_frame_delay;
+  uint32_t rtp = 1500;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, kStartTime),
+              Optional(TimeDelta::Zero()));
+  // RTP has wrapped around backwards.
+  rtp -= 3000;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, kStartTime),
+              Eq(absl::nullopt));
+}
+
+TEST(InterFrameDelayTest, CorrectDelayForFrames) {
+  VCMInterFrameDelay inter_frame_delay;
+  // Use a fake clock to simplify time keeping.
+  SimulatedClock clock(kStartTime);
+
+  // First frame is always delay 0.
+  uint32_t rtp = 90000;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Zero()));
+
+  // Perfectly timed frame has 0 delay.
+  clock.AdvanceTime(kFrameDelay);
+  rtp += kRtpTicksPerFrame;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Zero()));
+
+  // Slightly early frame will have a negative delay.
+  clock.AdvanceTime(kFrameDelay - TimeDelta::Millis(3));
+  rtp += kRtpTicksPerFrame;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(-TimeDelta::Millis(3)));
+
+  // Slightly late frame will have positive delay.
+  clock.AdvanceTime(kFrameDelay + TimeDelta::Micros(5125));
+  rtp += kRtpTicksPerFrame;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Micros(5125)));
+
+  // Simulate faster frame RTP at the same clock delay. The frame arrives late,
+  // since the RTP timestamp is faster than the delay, and thus is positive.
+  clock.AdvanceTime(kFrameDelay);
+  rtp += kRtpTicksPerFrame / 2;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(kFrameDelay / 2.0));
+
+  // Simulate slower frame RTP at the same clock delay. The frame is early,
+  // since the RTP timestamp advanced more than the delay, and thus is negative.
+  clock.AdvanceTime(kFrameDelay);
+  rtp += 1.5 * kRtpTicksPerFrame;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(-kFrameDelay / 2.0));
+}
+
+TEST(InterFrameDelayTest, PositiveWrapAround) {
+  VCMInterFrameDelay inter_frame_delay;
+  // Use a fake clock to simplify time keeping.
+  SimulatedClock clock(kStartTime);
+
+  // First frame is behind the max RTP by 1500.
+  uint32_t rtp = std::numeric_limits<uint32_t>::max() - 1500;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Zero()));
+
+  // Rtp wraps around, now 1499.
+  rtp += kRtpTicksPerFrame;
+
+  // Frame delay should be as normal, in this case simulated as 1ms late.
+  clock.AdvanceTime(kFrameDelay + TimeDelta::Millis(1));
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Millis(1)));
+}
+
+TEST(InterFrameDelayTest, MultipleWrapArounds) {
+  // Simulate a long pauses which cause wrap arounds multiple times.
+  constexpr Frequency k90Khz = Frequency::KiloHertz(90);
+  constexpr uint32_t kHalfRtp = std::numeric_limits<uint32_t>::max() / 2;
+  constexpr TimeDelta kWrapAroundDelay = kHalfRtp / k90Khz;
+
+  VCMInterFrameDelay inter_frame_delay;
+  // Use a fake clock to simplify time keeping.
+  SimulatedClock clock(kStartTime);
+  uint32_t rtp = 0;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Zero()));
+
+  rtp += kHalfRtp;
+  clock.AdvanceTime(kWrapAroundDelay);
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Zero()));
+  // 1st wrap around.
+  rtp += kHalfRtp + 1;
+  clock.AdvanceTime(kWrapAroundDelay + TimeDelta::Millis(1));
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Millis(1) - (1 / k90Khz)));
+
+  rtp += kHalfRtp;
+  clock.AdvanceTime(kWrapAroundDelay);
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Zero()));
+  // 2nd wrap arounds.
+  rtp += kHalfRtp + 1;
+  clock.AdvanceTime(kWrapAroundDelay - TimeDelta::Millis(1));
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(-TimeDelta::Millis(1) - (1 / k90Khz)));
+
+  // Ensure short delay (large RTP delay) between wrap-arounds has correct
+  // jitter.
+  rtp += kHalfRtp;
+  clock.AdvanceTime(TimeDelta::Millis(10));
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(-(kWrapAroundDelay - TimeDelta::Millis(10))));
+  // 3nd wrap arounds, this time with large RTP delay.
+  rtp += kHalfRtp + 1;
+  clock.AdvanceTime(TimeDelta::Millis(10));
+  EXPECT_THAT(
+      inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+      Optional(-(kWrapAroundDelay - TimeDelta::Millis(10) + (1 / k90Khz))));
+}
+
+TEST(InterFrameDelayTest, NegativeWrapAroundAfterPositiveWrapAround) {
+  VCMInterFrameDelay inter_frame_delay;
+  // Use a fake clock to simplify time keeping.
+  SimulatedClock clock(kStartTime);
+  uint32_t rtp = std::numeric_limits<uint32_t>::max() - 1500;
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Zero()));
+
+  // Rtp wraps around, now 1499.
+  rtp += kRtpTicksPerFrame;
+  // Frame delay should be as normal, in this case simulated as 1ms late.
+  clock.AdvanceTime(kFrameDelay);
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Optional(TimeDelta::Zero()));
+
+  // Wrap back.
+  rtp -= kRtpTicksPerFrame;
+  // Frame delay should be as normal, in this case simulated as 1ms late.
+  clock.AdvanceTime(kFrameDelay);
+  EXPECT_THAT(inter_frame_delay.CalculateDelay(rtp, clock.CurrentTime()),
+              Eq(absl::nullopt));
+}
+
+}  // namespace webrtc
diff --git a/modules/video_coding/jitter_buffer.cc b/modules/video_coding/jitter_buffer.cc
index a49ee69..f51b6ec 100644
--- a/modules/video_coding/jitter_buffer.cc
+++ b/modules/video_coding/jitter_buffer.cc
@@ -9,11 +9,11 @@
  */
 #include "modules/video_coding/jitter_buffer.h"
 
-
 #include <algorithm>
 #include <limits>
 #include <utility>
 
+#include "api/units/timestamp.h"
 #include "modules/video_coding/frame_buffer.h"
 #include "modules/video_coding/include/video_coding.h"
 #include "modules/video_coding/inter_frame_delay.h"
@@ -123,7 +123,6 @@
       num_packets_(0),
       num_duplicated_packets_(0),
       jitter_estimate_(clock),
-      inter_frame_delay_(clock_->TimeInMilliseconds()),
       missing_sequence_numbers_(SequenceNumberLessThan()),
       latest_received_sequence_number_(0),
       max_nack_list_size_(0),
@@ -192,7 +191,7 @@
   num_consecutive_old_packets_ = 0;
   // Also reset the jitter and delay estimates
   jitter_estimate_.Reset();
-  inter_frame_delay_.Reset(clock_->TimeInMilliseconds());
+  inter_frame_delay_.Reset();
   waiting_for_completion_.frame_size = 0;
   waiting_for_completion_.timestamp = 0;
   waiting_for_completion_.latest_packet_time = -1;
@@ -392,13 +391,13 @@
   if (error != kNoError)
     return error;
 
-  int64_t now_ms = clock_->TimeInMilliseconds();
+  Timestamp now = clock_->CurrentTime();
   // We are keeping track of the first and latest seq numbers, and
   // the number of wraps to be able to calculate how many packets we expect.
   if (first_packet_since_reset_) {
     // Now it's time to start estimating jitter
     // reset the delay estimate.
-    inter_frame_delay_.Reset(now_ms);
+    inter_frame_delay_.Reset();
   }
 
   // Empty packets may bias the jitter estimate (lacking size component),
@@ -408,9 +407,9 @@
       // This can get bad if we have a lot of duplicate packets,
       // we will then count some packet multiple times.
       waiting_for_completion_.frame_size += packet.sizeBytes;
-      waiting_for_completion_.latest_packet_time = now_ms;
+      waiting_for_completion_.latest_packet_time = now.ms();
     } else if (waiting_for_completion_.latest_packet_time >= 0 &&
-               waiting_for_completion_.latest_packet_time + 2000 <= now_ms) {
+               waiting_for_completion_.latest_packet_time + 2000 <= now.ms()) {
       // A packet should never be more than two seconds late
       UpdateJitterEstimate(waiting_for_completion_, true);
       waiting_for_completion_.latest_packet_time = -1;
@@ -425,7 +424,7 @@
   frame_data.rtt_ms = kDefaultRtt;
   frame_data.rolling_average_packets_per_frame = average_packets_per_frame_;
   VCMFrameBufferEnum buffer_state =
-      frame->InsertPacket(packet, now_ms, frame_data);
+      frame->InsertPacket(packet, now.ms(), frame_data);
 
   if (buffer_state > 0) {
     if (first_packet_since_reset_) {
@@ -873,14 +872,14 @@
   if (latest_packet_time_ms == -1) {
     return;
   }
-  int64_t frame_delay;
-  bool not_reordered = inter_frame_delay_.CalculateDelay(
-      timestamp, &frame_delay, latest_packet_time_ms);
+  auto frame_delay = inter_frame_delay_.CalculateDelay(
+      timestamp, Timestamp::Millis(latest_packet_time_ms));
+
+  bool not_reordered = frame_delay.has_value();
   // Filter out frames which have been reordered in time by the network
   if (not_reordered) {
     // Update the jitter estimate with the new samples
-    jitter_estimate_.UpdateEstimate(TimeDelta::Millis(frame_delay),
-                                    DataSize::Bytes(frame_size),
+    jitter_estimate_.UpdateEstimate(*frame_delay, DataSize::Bytes(frame_size),
                                     incomplete_frame);
   }
 }
diff --git a/video/BUILD.gn b/video/BUILD.gn
index e5ba48b..8dcef1a 100644
--- a/video/BUILD.gn
+++ b/video/BUILD.gn
@@ -100,6 +100,7 @@
     "../modules/video_coding:codec_globals_headers",
     "../modules/video_coding:frame_buffer",
     "../modules/video_coding:frame_helpers",
+    "../modules/video_coding:inter_frame_delay",
     "../modules/video_coding:jitter_estimator",
     "../modules/video_coding:nack_requester",
     "../modules/video_coding:packet_buffer",
diff --git a/video/frame_buffer_proxy.cc b/video/frame_buffer_proxy.cc
index c7fce7a..2ec3ac4 100644
--- a/video/frame_buffer_proxy.cc
+++ b/video/frame_buffer_proxy.cc
@@ -149,7 +149,7 @@
         contentType(frame.contentType()),
         delayed_by_retransmission(frame.delayed_by_retransmission()),
         rtp_timestamp(frame.Timestamp()),
-        receive_time_ms(frame.ReceivedTime()) {}
+        receive_time(frame.ReceivedTimestamp()) {}
 
   const bool is_last_spatial_layer;
   const bool is_keyframe;
@@ -157,9 +157,15 @@
   const VideoContentType contentType;
   const bool delayed_by_retransmission;
   const uint32_t rtp_timestamp;
-  const int64_t receive_time_ms;
+  const absl::optional<Timestamp> receive_time;
 };
 
+Timestamp ReceiveTime(const EncodedFrame& frame) {
+  absl::optional<Timestamp> ts = frame.ReceivedTimestamp();
+  RTC_DCHECK(ts.has_value()) << "Received frame must have a timestamp set!";
+  return *ts;
+}
+
 // Encapsulates use of the new frame buffer for use in VideoReceiveStream. This
 // behaves the same as the FrameBuffer2Proxy but uses frame_buffer3 instead.
 // Responsibilities from frame_buffer2, like stats, jitter and frame timing
@@ -186,7 +192,6 @@
         timing_(timing),
         frame_decode_scheduler_(std::move(frame_decode_scheduler)),
         jitter_estimator_(clock_),
-        inter_frame_delay_(clock_->TimeInMilliseconds()),
         buffer_(std::make_unique<FrameBuffer>(kMaxFramesBuffered,
                                               kMaxFramesHistory)),
         decode_timing_(clock_, timing_),
@@ -248,12 +253,10 @@
     if (complete_units < buffer_->GetTotalNumberOfContinuousTemporalUnits()) {
       stats_proxy_->OnCompleteFrame(metadata.is_keyframe, metadata.size,
                                     metadata.contentType);
-      RTC_DCHECK_GE(metadata.receive_time_ms, 0)
-          << "Frame receive time must be positive for received frames, was "
-          << metadata.receive_time_ms << ".";
-      if (!metadata.delayed_by_retransmission && metadata.receive_time_ms >= 0)
+      RTC_DCHECK(metadata.receive_time) << "Frame receive time must be set!";
+      if (!metadata.delayed_by_retransmission && metadata.receive_time)
         timing_->IncomingTimestamp(metadata.rtp_timestamp,
-                                   Timestamp::Millis(metadata.receive_time_ms));
+                                   *metadata.receive_time);
       MaybeScheduleFrameForRelease();
     }
 
@@ -301,7 +304,7 @@
     bool superframe_delayed_by_retransmission = false;
     DataSize superframe_size = DataSize::Zero();
     const EncodedFrame& first_frame = *frames.front();
-    int64_t receive_time_ms = first_frame.ReceivedTime();
+    Timestamp receive_time = ReceiveTime(first_frame);
 
     if (first_frame.is_keyframe())
       keyframe_required_ = false;
@@ -319,17 +322,15 @@
 
       superframe_delayed_by_retransmission |=
           frame->delayed_by_retransmission();
-      receive_time_ms = std::max(receive_time_ms, frame->ReceivedTime());
+      receive_time = std::max(receive_time, ReceiveTime(*frame));
       superframe_size += DataSize::Bytes(frame->size());
     }
 
     if (!superframe_delayed_by_retransmission) {
-      int64_t frame_delay;
-
-      if (inter_frame_delay_.CalculateDelay(first_frame.Timestamp(),
-                                            &frame_delay, receive_time_ms)) {
-        jitter_estimator_.UpdateEstimate(TimeDelta::Millis(frame_delay),
-                                         superframe_size);
+      auto frame_delay = inter_frame_delay_.CalculateDelay(
+          first_frame.Timestamp(), receive_time);
+      if (frame_delay) {
+        jitter_estimator_.UpdateEstimate(*frame_delay, superframe_size);
       }
 
       float rtt_mult = protection_mode_ == kProtectionNackFEC ? 0.0 : 1.0;