Modernize TimestampExtrapolator to use correct units
* Add unit tests
* Use TimestampUnwrapper
* Follow style guide
Change-Id: I057b05faba0aeafb2830a45007474be0eca1c6e0
Bug: webrtc:13756
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/256261
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Commit-Queue: Evan Shrubsole <eshr@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36313}
diff --git a/BUILD.gn b/BUILD.gn
index aaa1260..52736b3 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -574,6 +574,7 @@
"rtc_base/task_utils:pending_task_safety_flag_unittests",
"rtc_base/task_utils:repeating_task_unittests",
"rtc_base/task_utils:to_queued_task_unittests",
+ "rtc_base/time:timestamp_extrapolator_unittests",
"rtc_base/units:units_unittests",
"sdk:sdk_tests",
"test:rtp_test_utils",
diff --git a/rtc_base/time/BUILD.gn b/rtc_base/time/BUILD.gn
index 890695d..092abd9 100644
--- a/rtc_base/time/BUILD.gn
+++ b/rtc_base/time/BUILD.gn
@@ -17,6 +17,26 @@
"timestamp_extrapolator.cc",
"timestamp_extrapolator.h",
]
- deps = [ "../../api/units:timestamp" ]
+ deps = [
+ "../../api/units:frequency",
+ "../../api/units:timestamp",
+ "../../modules:module_api_public",
+ ]
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
+
+if (rtc_include_tests) {
+ rtc_library("timestamp_extrapolator_unittests") {
+ testonly = true
+ sources = [ "timestamp_extrapolator_unittest.cc" ]
+ deps = [
+ ":timestamp_extrapolator",
+ "../../api/units:frequency",
+ "../../api/units:time_delta",
+ "../../api/units:timestamp",
+ "../../system_wrappers",
+ "../../test:test_support",
+ ]
+ absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
+ }
+}
diff --git a/rtc_base/time/timestamp_extrapolator.cc b/rtc_base/time/timestamp_extrapolator.cc
index 521b5ba..910db8a 100644
--- a/rtc_base/time/timestamp_extrapolator.cc
+++ b/rtc_base/time/timestamp_extrapolator.cc
@@ -13,87 +13,83 @@
#include <algorithm>
#include "absl/types/optional.h"
+#include "api/units/frequency.h"
+#include "modules/include/module_common_types_public.h"
namespace webrtc {
+namespace {
+
+constexpr double kLambda = 1;
+constexpr uint32_t kStartUpFilterDelayInPackets = 2;
+constexpr double kAlarmThreshold = 60e3;
+// in timestamp ticks, i.e. 15 ms
+constexpr double kAccDrift = 6600;
+constexpr double kAccMaxError = 7000;
+constexpr double kP11 = 1e10;
+
+} // namespace
+
TimestampExtrapolator::TimestampExtrapolator(Timestamp start)
- : _start(Timestamp::Zero()),
- _prev(Timestamp::Zero()),
- _firstTimestamp(0),
- _wrapArounds(0),
- _prevUnwrappedTimestamp(-1),
- _prevWrapTimestamp(-1),
- _lambda(1),
- _firstAfterReset(true),
- _packetCount(0),
- _startUpFilterDelayInPackets(2),
- _detectorAccumulatorPos(0),
- _detectorAccumulatorNeg(0),
- _alarmThreshold(60e3),
- _accDrift(6600), // in timestamp ticks, i.e. 15 ms
- _accMaxError(7000),
- _pP11(1e10) {
+ : start_(Timestamp::Zero()),
+ prev_(Timestamp::Zero()),
+ packet_count_(0),
+ detector_accumulator_pos_(0),
+ detector_accumulator_neg_(0) {
Reset(start);
}
void TimestampExtrapolator::Reset(Timestamp start) {
- _start = start;
- _prev = _start;
- _firstTimestamp = 0;
- _w[0] = 90.0;
- _w[1] = 0;
- _pP[0][0] = 1;
- _pP[1][1] = _pP11;
- _pP[0][1] = _pP[1][0] = 0;
- _firstAfterReset = true;
- _prevUnwrappedTimestamp = -1;
- _prevWrapTimestamp = -1;
- _wrapArounds = 0;
- _packetCount = 0;
- _detectorAccumulatorPos = 0;
- _detectorAccumulatorNeg = 0;
+ start_ = start;
+ prev_ = start_;
+ first_unwrapped_timestamp_ = absl::nullopt;
+ w_[0] = 90.0;
+ w_[1] = 0;
+ p_[0][0] = 1;
+ p_[1][1] = kP11;
+ p_[0][1] = p_[1][0] = 0;
+ unwrapper_ = TimestampUnwrapper();
+ packet_count_ = 0;
+ detector_accumulator_pos_ = 0;
+ detector_accumulator_neg_ = 0;
}
void TimestampExtrapolator::Update(Timestamp now, uint32_t ts90khz) {
- if (now - _prev > TimeDelta::Seconds(10)) {
+ if (now - prev_ > TimeDelta::Seconds(10)) {
// Ten seconds without a complete frame.
// Reset the extrapolator
Reset(now);
} else {
- _prev = now;
+ prev_ = now;
}
// Remove offset to prevent badly scaled matrices
- const TimeDelta offset = now - _start;
- double tMs = offset.ms();
+ const TimeDelta offset = now - start_;
+ double t_ms = offset.ms();
- CheckForWrapArounds(ts90khz);
+ int64_t unwrapped_ts90khz = unwrapper_.Unwrap(ts90khz);
- int64_t unwrapped_ts90khz =
- static_cast<int64_t>(ts90khz) +
- _wrapArounds * ((static_cast<int64_t>(1) << 32) - 1);
-
- if (_firstAfterReset) {
+ if (!first_unwrapped_timestamp_) {
// Make an initial guess of the offset,
- // should be almost correct since tMs - _startMs
+ // should be almost correct since t_ms - start
// should about zero at this time.
- _w[1] = -_w[0] * tMs;
- _firstTimestamp = unwrapped_ts90khz;
- _firstAfterReset = false;
+ w_[1] = -w_[0] * t_ms;
+ first_unwrapped_timestamp_ = unwrapped_ts90khz;
}
- double residual = (static_cast<double>(unwrapped_ts90khz) - _firstTimestamp) -
- tMs * _w[0] - _w[1];
+ double residual =
+ (static_cast<double>(unwrapped_ts90khz) - *first_unwrapped_timestamp_) -
+ t_ms * w_[0] - w_[1];
if (DelayChangeDetection(residual) &&
- _packetCount >= _startUpFilterDelayInPackets) {
+ packet_count_ >= kStartUpFilterDelayInPackets) {
// A sudden change of average network delay has been detected.
// Force the filter to adjust its offset parameter by changing
// the offset uncertainty. Don't do this during startup.
- _pP[1][1] = _pP11;
+ p_[1][1] = kP11;
}
- if (_prevUnwrappedTimestamp >= 0 &&
- unwrapped_ts90khz < _prevUnwrappedTimestamp) {
+ if (prev_unwrapped_timestamp_ &&
+ unwrapped_ts90khz < prev_unwrapped_timestamp_) {
// Drop reordered frames.
return;
}
@@ -102,94 +98,62 @@
// that = T'*w;
// K = P*T/(lambda + T'*P*T);
double K[2];
- K[0] = _pP[0][0] * tMs + _pP[0][1];
- K[1] = _pP[1][0] * tMs + _pP[1][1];
- double TPT = _lambda + tMs * K[0] + K[1];
+ K[0] = p_[0][0] * t_ms + p_[0][1];
+ K[1] = p_[1][0] * t_ms + p_[1][1];
+ double TPT = kLambda + t_ms * K[0] + K[1];
K[0] /= TPT;
K[1] /= TPT;
// w = w + K*(ts(k) - that);
- _w[0] = _w[0] + K[0] * residual;
- _w[1] = _w[1] + K[1] * residual;
+ w_[0] = w_[0] + K[0] * residual;
+ w_[1] = w_[1] + K[1] * residual;
// P = 1/lambda*(P - K*T'*P);
double p00 =
- 1 / _lambda * (_pP[0][0] - (K[0] * tMs * _pP[0][0] + K[0] * _pP[1][0]));
+ 1 / kLambda * (p_[0][0] - (K[0] * t_ms * p_[0][0] + K[0] * p_[1][0]));
double p01 =
- 1 / _lambda * (_pP[0][1] - (K[0] * tMs * _pP[0][1] + K[0] * _pP[1][1]));
- _pP[1][0] =
- 1 / _lambda * (_pP[1][0] - (K[1] * tMs * _pP[0][0] + K[1] * _pP[1][0]));
- _pP[1][1] =
- 1 / _lambda * (_pP[1][1] - (K[1] * tMs * _pP[0][1] + K[1] * _pP[1][1]));
- _pP[0][0] = p00;
- _pP[0][1] = p01;
- _prevUnwrappedTimestamp = unwrapped_ts90khz;
- if (_packetCount < _startUpFilterDelayInPackets) {
- _packetCount++;
+ 1 / kLambda * (p_[0][1] - (K[0] * t_ms * p_[0][1] + K[0] * p_[1][1]));
+ p_[1][0] =
+ 1 / kLambda * (p_[1][0] - (K[1] * t_ms * p_[0][0] + K[1] * p_[1][0]));
+ p_[1][1] =
+ 1 / kLambda * (p_[1][1] - (K[1] * t_ms * p_[0][1] + K[1] * p_[1][1]));
+ p_[0][0] = p00;
+ p_[0][1] = p01;
+ prev_unwrapped_timestamp_ = unwrapped_ts90khz;
+ if (packet_count_ < kStartUpFilterDelayInPackets) {
+ packet_count_++;
}
}
absl::optional<Timestamp> TimestampExtrapolator::ExtrapolateLocalTime(
- uint32_t timestamp90khz) {
- CheckForWrapArounds(timestamp90khz);
- double unwrapped_ts90khz =
- static_cast<double>(timestamp90khz) +
- _wrapArounds * ((static_cast<int64_t>(1) << 32) - 1);
- if (_packetCount == 0) {
- return absl::nullopt;
- } else if (_packetCount < _startUpFilterDelayInPackets) {
- auto diffMs = static_cast<int64_t>(
- static_cast<double>(unwrapped_ts90khz - _prevUnwrappedTimestamp) /
- 90.0 +
- 0.5);
- return _prev + TimeDelta::Millis(diffMs);
- } else if (_w[0] < 1e-3) {
- return _start;
- } else {
- double timestampDiff =
- unwrapped_ts90khz - static_cast<double>(_firstTimestamp);
- auto diffMs = static_cast<int64_t>((timestampDiff - _w[1]) / _w[0] + 0.5);
- return _start + TimeDelta::Millis(diffMs);
- }
-}
+ uint32_t timestamp90khz) const {
+ int64_t unwrapped_ts90khz = unwrapper_.UnwrapWithoutUpdate(timestamp90khz);
-// Investigates if the timestamp clock has overflowed since the last timestamp
-// and keeps track of the number of wrap arounds since reset.
-void TimestampExtrapolator::CheckForWrapArounds(uint32_t ts90khz) {
- if (_prevWrapTimestamp == -1) {
- _prevWrapTimestamp = ts90khz;
- return;
- }
- if (ts90khz < _prevWrapTimestamp) {
- // This difference will probably be less than -2^31 if we have had a wrap
- // around (e.g. timestamp = 1, _previousTimestamp = 2^32 - 1). Since it is
- // casted to a Word32, it should be positive.
- if (static_cast<int32_t>(ts90khz - _prevWrapTimestamp) > 0) {
- // Forward wrap around
- _wrapArounds++;
- }
+ if (!first_unwrapped_timestamp_) {
+ return absl::nullopt;
+ } else if (packet_count_ < kStartUpFilterDelayInPackets) {
+ constexpr Frequency k90KHz = Frequency::KiloHertz(90);
+ TimeDelta diff = (unwrapped_ts90khz - *prev_unwrapped_timestamp_) / k90KHz;
+ return prev_ + diff;
+ } else if (w_[0] < 1e-3) {
+ return start_;
} else {
- // This difference will probably be less than -2^31 if we have had a
- // backward wrap around. Since it is casted to a Word32, it should be
- // positive.
- if (static_cast<int32_t>(_prevWrapTimestamp - ts90khz) > 0) {
- // Backward wrap around
- _wrapArounds--;
- }
+ double timestampDiff = unwrapped_ts90khz - *first_unwrapped_timestamp_;
+ auto diff_ms = static_cast<int64_t>((timestampDiff - w_[1]) / w_[0] + 0.5);
+ return start_ + TimeDelta::Millis(diff_ms);
}
- _prevWrapTimestamp = ts90khz;
}
bool TimestampExtrapolator::DelayChangeDetection(double error) {
// CUSUM detection of sudden delay changes
- error = (error > 0) ? std::min(error, _accMaxError)
- : std::max(error, -_accMaxError);
- _detectorAccumulatorPos =
- std::max(_detectorAccumulatorPos + error - _accDrift, double{0});
- _detectorAccumulatorNeg =
- std::min(_detectorAccumulatorNeg + error + _accDrift, double{0});
- if (_detectorAccumulatorPos > _alarmThreshold ||
- _detectorAccumulatorNeg < -_alarmThreshold) {
+ error = (error > 0) ? std::min(error, kAccMaxError)
+ : std::max(error, -kAccMaxError);
+ detector_accumulator_pos_ =
+ std::max(detector_accumulator_pos_ + error - kAccDrift, double{0});
+ detector_accumulator_neg_ =
+ std::min(detector_accumulator_neg_ + error + kAccDrift, double{0});
+ if (detector_accumulator_pos_ > kAlarmThreshold ||
+ detector_accumulator_neg_ < -kAlarmThreshold) {
// Alarm
- _detectorAccumulatorPos = _detectorAccumulatorNeg = 0;
+ detector_accumulator_pos_ = detector_accumulator_neg_ = 0;
return true;
}
return false;
diff --git a/rtc_base/time/timestamp_extrapolator.h b/rtc_base/time/timestamp_extrapolator.h
index df56eea..23e7975 100644
--- a/rtc_base/time/timestamp_extrapolator.h
+++ b/rtc_base/time/timestamp_extrapolator.h
@@ -15,6 +15,7 @@
#include "absl/types/optional.h"
#include "api/units/timestamp.h"
+#include "modules/include/module_common_types_public.h"
namespace webrtc {
@@ -23,31 +24,23 @@
public:
explicit TimestampExtrapolator(Timestamp start);
void Update(Timestamp now, uint32_t ts90khz);
- absl::optional<Timestamp> ExtrapolateLocalTime(uint32_t timestamp90khz);
+ absl::optional<Timestamp> ExtrapolateLocalTime(uint32_t timestamp90khz) const;
void Reset(Timestamp start);
private:
void CheckForWrapArounds(uint32_t ts90khz);
bool DelayChangeDetection(double error);
- double _w[2];
- double _pP[2][2];
- Timestamp _start;
- Timestamp _prev;
- uint32_t _firstTimestamp;
- int32_t _wrapArounds;
- int64_t _prevUnwrappedTimestamp;
- int64_t _prevWrapTimestamp;
- const double _lambda;
- bool _firstAfterReset;
- uint32_t _packetCount;
- const uint32_t _startUpFilterDelayInPackets;
- double _detectorAccumulatorPos;
- double _detectorAccumulatorNeg;
- const double _alarmThreshold;
- const double _accDrift;
- const double _accMaxError;
- const double _pP11;
+ double w_[2];
+ double p_[2][2];
+ Timestamp start_;
+ Timestamp prev_;
+ absl::optional<int64_t> first_unwrapped_timestamp_;
+ TimestampUnwrapper unwrapper_;
+ absl::optional<int64_t> prev_unwrapped_timestamp_;
+ uint32_t packet_count_;
+ double detector_accumulator_pos_;
+ double detector_accumulator_neg_;
};
} // namespace webrtc
diff --git a/rtc_base/time/timestamp_extrapolator_unittest.cc b/rtc_base/time/timestamp_extrapolator_unittest.cc
new file mode 100644
index 0000000..b153d7a
--- /dev/null
+++ b/rtc_base/time/timestamp_extrapolator_unittest.cc
@@ -0,0 +1,208 @@
+/*
+ * 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 "rtc_base/time/timestamp_extrapolator.h"
+
+#include <stdint.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 {
+
+using ::testing::Eq;
+using ::testing::Optional;
+
+namespace {
+
+constexpr Frequency kRtpHz = Frequency::KiloHertz(90);
+constexpr Frequency k25Fps = Frequency::Hertz(25);
+constexpr TimeDelta k25FpsDelay = 1 / k25Fps;
+
+} // namespace
+
+TEST(TimestampExtrapolatorTest, ExtrapolationOccursAfter2Packets) {
+ SimulatedClock clock(Timestamp::Millis(1337));
+ TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
+
+ // No packets so no timestamp.
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(90000), Eq(absl::nullopt));
+
+ uint32_t rtp = 90000;
+ clock.AdvanceTime(k25FpsDelay);
+ // First result is a bit confusing since it is based off the "start" time,
+ // which is arbitrary.
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
+ Optional(clock.CurrentTime()));
+
+ rtp += kRtpHz / k25Fps;
+ clock.AdvanceTime(k25FpsDelay);
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
+ Optional(clock.CurrentTime()));
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp + 90000),
+ Optional(clock.CurrentTime() + TimeDelta::Seconds(1)));
+}
+
+TEST(TimestampExtrapolatorTest, ResetsAfter10SecondPause) {
+ SimulatedClock clock(Timestamp::Millis(1337));
+ TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
+
+ uint32_t rtp = 90000;
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
+ Optional(clock.CurrentTime()));
+
+ rtp += kRtpHz / k25Fps;
+ clock.AdvanceTime(k25FpsDelay);
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
+ Optional(clock.CurrentTime()));
+
+ rtp += 10 * kRtpHz.hertz();
+ clock.AdvanceTime(TimeDelta::Seconds(10) + TimeDelta::Micros(1));
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
+ Optional(clock.CurrentTime()));
+}
+
+TEST(TimestampExtrapolatorTest, TimestampExtrapolatesMultipleRtpWrapArounds) {
+ SimulatedClock clock(Timestamp::Millis(1337));
+ TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
+
+ uint32_t rtp = std::numeric_limits<uint32_t>::max();
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
+ Optional(clock.CurrentTime()));
+
+ // One overflow. Static cast to avoid undefined behaviour with +=.
+ rtp += static_cast<uint32_t>(kRtpHz / k25Fps);
+ clock.AdvanceTime(k25FpsDelay);
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
+ Optional(clock.CurrentTime()));
+
+ // Assert that extrapolation works across the boundary as expected.
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp + 90000),
+ Optional(clock.CurrentTime() + TimeDelta::Seconds(1)));
+ // This is not quite 1s since the math always rounds up.
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp - 90000),
+ Optional(clock.CurrentTime() - TimeDelta::Millis(999)));
+
+ // In order to avoid a wrap arounds reset, add a packet every 10s until we
+ // overflow twice.
+ constexpr TimeDelta kRtpOverflowDelay =
+ std::numeric_limits<uint32_t>::max() / kRtpHz;
+ const Timestamp overflow_time = clock.CurrentTime() + kRtpOverflowDelay * 2;
+
+ while (clock.CurrentTime() < overflow_time) {
+ clock.AdvanceTime(TimeDelta::Seconds(10));
+ // Static-cast before += to avoid undefined behaviour of overflow.
+ rtp += static_cast<uint32_t>(kRtpHz * TimeDelta::Seconds(10));
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
+ Optional(clock.CurrentTime()));
+ }
+}
+
+TEST(TimestampExtrapolatorTest, Slow90KHzClock) {
+ // This simulates a slow camera, which produces frames at 24Hz instead of
+ // 25Hz. The extrapolator should be able to resolve this with enough data.
+ SimulatedClock clock(Timestamp::Millis(1337));
+ TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
+
+ constexpr TimeDelta k24FpsDelay = 1 / Frequency::Hertz(24);
+ uint32_t rtp = 90000;
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+
+ // Slow camera will increment RTP at 25 FPS rate even though its producing at
+ // 24 FPS. After 25 frames the extrapolator should settle at this rate.
+ for (int i = 0; i < 25; ++i) {
+ rtp += kRtpHz / k25Fps;
+ clock.AdvanceTime(k24FpsDelay);
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ }
+
+ // The camera would normally produce 25 frames in 90K ticks, but is slow
+ // so takes 1s + k24FpsDelay for 90K ticks.
+ constexpr Frequency kSlowRtpHz = 90000 / (25 * k24FpsDelay);
+ // The extrapolator will be predicting that time at millisecond precision.
+ auto ts = ts_extrapolator.ExtrapolateLocalTime(rtp + kSlowRtpHz.hertz());
+ ASSERT_TRUE(ts.has_value());
+ EXPECT_EQ(ts->ms(), clock.TimeInMilliseconds() + 1000);
+}
+
+TEST(TimestampExtrapolatorTest, Fast90KHzClock) {
+ // This simulates a fast camera, which produces frames at 26Hz instead of
+ // 25Hz. The extrapolator should be able to resolve this with enough data.
+ SimulatedClock clock(Timestamp::Millis(1337));
+ TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
+
+ constexpr TimeDelta k26FpsDelay = 1 / Frequency::Hertz(26);
+ uint32_t rtp = 90000;
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+
+ // Fast camera will increment RTP at 25 FPS rate even though its producing at
+ // 26 FPS. After 25 frames the extrapolator should settle at this rate.
+ for (int i = 0; i < 25; ++i) {
+ rtp += kRtpHz / k25Fps;
+ clock.AdvanceTime(k26FpsDelay);
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ }
+
+ // The camera would normally produce 25 frames in 90K ticks, but is slow
+ // so takes 1s + k24FpsDelay for 90K ticks.
+ constexpr Frequency kSlowRtpHz = 90000 / (25 * k26FpsDelay);
+ // The extrapolator will be predicting that time at millisecond precision.
+ auto ts = ts_extrapolator.ExtrapolateLocalTime(rtp + kSlowRtpHz.hertz());
+ ASSERT_TRUE(ts.has_value());
+ EXPECT_EQ(ts->ms(), clock.TimeInMilliseconds() + 1000);
+}
+
+TEST(TimestampExtrapolatorTest, TimestampJump) {
+ // This simulates a jump in RTP timestamp, which could occur if a camera was
+ // swapped for example.
+ SimulatedClock clock(Timestamp::Millis(1337));
+ TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
+
+ uint32_t rtp = 90000;
+ clock.AdvanceTime(k25FpsDelay);
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ rtp += kRtpHz / k25Fps;
+ clock.AdvanceTime(k25FpsDelay);
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ rtp += kRtpHz / k25Fps;
+ clock.AdvanceTime(k25FpsDelay);
+ ts_extrapolator.Update(clock.CurrentTime(), rtp);
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
+ Optional(clock.CurrentTime()));
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp + 90000),
+ Optional(clock.CurrentTime() + TimeDelta::Seconds(1)));
+
+ // Jump RTP.
+ uint32_t new_rtp = 1337 * 90000;
+ clock.AdvanceTime(k25FpsDelay);
+ ts_extrapolator.Update(clock.CurrentTime(), new_rtp);
+ new_rtp += kRtpHz / k25Fps;
+ clock.AdvanceTime(k25FpsDelay);
+ ts_extrapolator.Update(clock.CurrentTime(), new_rtp);
+ EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(new_rtp),
+ Optional(clock.CurrentTime()));
+}
+
+} // namespace webrtc