2022-03-21 17:16:39 +01:00
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
|
2022-09-14 15:52:13 +02:00
|
|
|
#include "modules/video_coding/timing/timestamp_extrapolator.h"
|
2022-03-21 17:16:39 +01:00
|
|
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
|
|
#include <limits>
|
2024-08-29 13:00:40 +00:00
|
|
|
#include <optional>
|
2024-11-15 15:54:45 +01:00
|
|
|
#include <string>
|
2022-03-21 17:16:39 +01:00
|
|
|
|
|
|
|
|
#include "api/units/frequency.h"
|
|
|
|
|
#include "api/units/time_delta.h"
|
|
|
|
|
#include "api/units/timestamp.h"
|
|
|
|
|
#include "system_wrappers/include/clock.h"
|
2024-11-15 15:54:45 +01:00
|
|
|
#include "system_wrappers/include/metrics.h"
|
2022-03-21 17:16:39 +01:00
|
|
|
#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.
|
2024-08-29 13:00:40 +00:00
|
|
|
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(90000), Eq(std::nullopt));
|
2022-03-21 17:16:39 +01:00
|
|
|
|
|
|
|
|
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()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-09 14:47:17 +00:00
|
|
|
TEST(TimestampExtrapolatorTest, NegativeRtpTimestampWrapAround) {
|
|
|
|
|
SimulatedClock clock(Timestamp::Millis(1337));
|
|
|
|
|
TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
|
|
|
|
|
uint32_t rtp = 0;
|
|
|
|
|
ts_extrapolator.Update(clock.CurrentTime(), rtp);
|
|
|
|
|
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
|
|
|
|
|
Optional(clock.CurrentTime()));
|
2023-04-07 10:09:12 +02:00
|
|
|
// Go backwards! Static cast to avoid undefined behaviour with -=.
|
|
|
|
|
rtp -= static_cast<uint32_t>(kRtpHz.hertz());
|
2023-01-09 14:47:17 +00:00
|
|
|
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
|
|
|
|
|
Optional(clock.CurrentTime() - TimeDelta::Seconds(1)));
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-07 10:09:12 +02:00
|
|
|
TEST(TimestampExtrapolatorTest, NegativeRtpTimestampWrapAroundSecondScenario) {
|
|
|
|
|
SimulatedClock clock(Timestamp::Millis(1337));
|
|
|
|
|
TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
|
|
|
|
|
uint32_t rtp = 0;
|
|
|
|
|
ts_extrapolator.Update(clock.CurrentTime(), rtp);
|
|
|
|
|
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
|
|
|
|
|
Optional(clock.CurrentTime()));
|
|
|
|
|
// Go backwards! Static cast to avoid undefined behaviour with -=.
|
|
|
|
|
rtp -= static_cast<uint32_t>(kRtpHz * TimeDelta::Seconds(10));
|
|
|
|
|
ts_extrapolator.Update(clock.CurrentTime(), rtp);
|
2024-08-29 13:00:40 +00:00
|
|
|
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp), std::nullopt);
|
2023-04-07 10:09:12 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-21 17:16:39 +01:00
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2024-11-15 15:54:45 +01:00
|
|
|
ts_extrapolator.Update(clock.CurrentTime(), rtp);
|
2022-03-21 17:16:39 +01:00
|
|
|
rtp += kRtpHz / k25Fps;
|
|
|
|
|
clock.AdvanceTime(k24FpsDelay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2024-11-15 15:54:45 +01:00
|
|
|
ts_extrapolator.Update(clock.CurrentTime(), rtp);
|
2022-03-21 17:16:39 +01:00
|
|
|
rtp += kRtpHz / k25Fps;
|
|
|
|
|
clock.AdvanceTime(k26FpsDelay);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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()));
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-13 13:02:15 +01:00
|
|
|
TEST(TimestampExtrapolatorTest, GapInReceivedFrames) {
|
|
|
|
|
SimulatedClock clock(
|
|
|
|
|
Timestamp::Seconds(std::numeric_limits<uint32_t>::max() / 90000 - 31));
|
|
|
|
|
TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
|
|
|
|
|
|
|
|
|
|
uint32_t rtp = std::numeric_limits<uint32_t>::max();
|
|
|
|
|
clock.AdvanceTime(k25FpsDelay);
|
|
|
|
|
ts_extrapolator.Update(clock.CurrentTime(), rtp);
|
|
|
|
|
|
|
|
|
|
rtp += 30 * 90000;
|
|
|
|
|
clock.AdvanceTime(TimeDelta::Seconds(30));
|
|
|
|
|
ts_extrapolator.Update(clock.CurrentTime(), rtp);
|
|
|
|
|
EXPECT_THAT(ts_extrapolator.ExtrapolateLocalTime(rtp),
|
|
|
|
|
Optional(clock.CurrentTime()));
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-15 15:54:45 +01:00
|
|
|
TEST(TimestampExtrapolatorTest, EstimatedClockDriftHistogram) {
|
|
|
|
|
const std::string kHistogramName = "WebRTC.Video.EstimatedClockDrift_ppm";
|
|
|
|
|
constexpr int kPpmTolerance = 50;
|
|
|
|
|
constexpr int kToPpmFactor = 1e6;
|
|
|
|
|
constexpr int kMinimumSamples = 3000;
|
|
|
|
|
constexpr Frequency k24Fps = Frequency::Hertz(24);
|
|
|
|
|
constexpr TimeDelta k24FpsDelay = 1 / k24Fps;
|
|
|
|
|
|
|
|
|
|
// This simulates a remote clock without drift with frames produced at 25 fps.
|
|
|
|
|
// Local scope to trigger the destructor of TimestampExtrapolator.
|
|
|
|
|
{
|
|
|
|
|
// Clear all histogram data.
|
|
|
|
|
metrics::Reset();
|
|
|
|
|
SimulatedClock clock(Timestamp::Millis(1337));
|
|
|
|
|
TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
|
|
|
|
|
|
|
|
|
|
uint32_t rtp = 90000;
|
|
|
|
|
for (int i = 0; i < kMinimumSamples; ++i) {
|
|
|
|
|
ts_extrapolator.Update(clock.CurrentTime(), rtp);
|
|
|
|
|
rtp += kRtpHz / k25Fps;
|
|
|
|
|
clock.AdvanceTime(k25FpsDelay);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
EXPECT_EQ(metrics::NumSamples(kHistogramName), 1);
|
|
|
|
|
const int kExpectedIdealClockDriftPpm = 0;
|
|
|
|
|
EXPECT_NEAR(kExpectedIdealClockDriftPpm, metrics::MinSample(kHistogramName),
|
|
|
|
|
kPpmTolerance);
|
|
|
|
|
|
|
|
|
|
// This simulates a slow remote clock, where the RTP timestamps are
|
|
|
|
|
// incremented as if the camera was 25 fps even though frames arrive at 24
|
|
|
|
|
// fps. Local scope to trigger the destructor of TimestampExtrapolator.
|
|
|
|
|
{
|
|
|
|
|
// Clear all histogram data.
|
|
|
|
|
metrics::Reset();
|
|
|
|
|
SimulatedClock clock(Timestamp::Millis(1337));
|
|
|
|
|
TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
|
|
|
|
|
|
|
|
|
|
uint32_t rtp = 90000;
|
|
|
|
|
for (int i = 0; i < kMinimumSamples; ++i) {
|
|
|
|
|
ts_extrapolator.Update(clock.CurrentTime(), rtp);
|
|
|
|
|
rtp += kRtpHz / k25Fps;
|
|
|
|
|
clock.AdvanceTime(k24FpsDelay);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
EXPECT_EQ(metrics::NumSamples(kHistogramName), 1);
|
|
|
|
|
const int kExpectedSlowClockDriftPpm =
|
|
|
|
|
std::abs(k24Fps / k25Fps - 1.0) * kToPpmFactor;
|
|
|
|
|
EXPECT_NEAR(kExpectedSlowClockDriftPpm, metrics::MinSample(kHistogramName),
|
|
|
|
|
kPpmTolerance);
|
|
|
|
|
|
|
|
|
|
// This simulates a fast remote clock, where the RTP timestamps are
|
|
|
|
|
// incremented as if the camera was 24 fps even though frames arrive at 25
|
|
|
|
|
// fps. Local scope to trigger the destructor of TimestampExtrapolator.
|
|
|
|
|
{
|
|
|
|
|
// Clear all histogram data.
|
|
|
|
|
metrics::Reset();
|
|
|
|
|
SimulatedClock clock(Timestamp::Millis(1337));
|
|
|
|
|
TimestampExtrapolator ts_extrapolator(clock.CurrentTime());
|
|
|
|
|
|
|
|
|
|
uint32_t rtp = 90000;
|
|
|
|
|
for (int i = 0; i < kMinimumSamples; ++i) {
|
|
|
|
|
ts_extrapolator.Update(clock.CurrentTime(), rtp);
|
|
|
|
|
rtp += kRtpHz / k24Fps;
|
|
|
|
|
clock.AdvanceTime(k25FpsDelay);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
EXPECT_EQ(metrics::NumSamples(kHistogramName), 1);
|
|
|
|
|
const int kExpectedFastClockDriftPpm = (k25Fps / k24Fps - 1.0) * kToPpmFactor;
|
|
|
|
|
EXPECT_NEAR(kExpectedFastClockDriftPpm, metrics::MinSample(kHistogramName),
|
|
|
|
|
kPpmTolerance);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-21 17:16:39 +01:00
|
|
|
} // namespace webrtc
|