Migrate RemoteNtpTimeEstimator to more precise time representations

Bug: webrtc:13757
Change-Id: I880ab3cc6e4f72da587ae42ddca051332907c07f
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/261311
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Reviewed-by: Jakob Ivarsson‎ <jakobi@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Emil Lundmark <lndmrk@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36817}
This commit is contained in:
Danil Chapovalov
2022-05-09 15:11:14 +02:00
committed by WebRTC LUCI CQ
parent 14d01508be
commit a154a15c97
9 changed files with 171 additions and 117 deletions

View File

@ -748,12 +748,13 @@ void ChannelReceive::ReceivedRTCPPacket(const uint8_t* data, size_t length) {
{
MutexLock lock(&ts_stats_lock_);
ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp);
absl::optional<int64_t> remote_to_local_clock_offset_ms =
ntp_estimator_.EstimateRemoteToLocalClockOffsetMs();
if (remote_to_local_clock_offset_ms.has_value()) {
ntp_estimator_.UpdateRtcpTimestamp(
TimeDelta::Millis(rtt), NtpTime(ntp_secs, ntp_frac), rtp_timestamp);
absl::optional<int64_t> remote_to_local_clock_offset =
ntp_estimator_.EstimateRemoteToLocalClockOffset();
if (remote_to_local_clock_offset.has_value()) {
capture_clock_offset_updater_.SetRemoteToLocalClockOffset(
Int64MsToQ32x32(*remote_to_local_clock_offset_ms));
*remote_to_local_clock_offset);
}
}
}

View File

@ -226,7 +226,8 @@ void AudioIngress::ReceivedRTCPPacket(
{
MutexLock lock(&lock_);
ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp);
ntp_estimator_.UpdateRtcpTimestamp(
TimeDelta::Millis(rtt), NtpTime(ntp_secs, ntp_frac), rtp_timestamp);
}
}

View File

@ -13,7 +13,10 @@
#include <stdint.h>
#include "absl/base/attributes.h"
#include "absl/types/optional.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "rtc_base/numerics/moving_median_filter.h"
#include "system_wrappers/include/rtp_to_ntp_estimator.h"
@ -28,32 +31,63 @@ class Clock;
class RemoteNtpTimeEstimator {
public:
explicit RemoteNtpTimeEstimator(Clock* clock);
~RemoteNtpTimeEstimator();
RemoteNtpTimeEstimator(const RemoteNtpTimeEstimator&) = delete;
RemoteNtpTimeEstimator& operator=(const RemoteNtpTimeEstimator&) = delete;
~RemoteNtpTimeEstimator() = default;
// Updates the estimator with round trip time `rtt`, NTP seconds `ntp_secs`,
// NTP fraction `ntp_frac` and RTP timestamp `rtp_timestamp`.
ABSL_DEPRECATED(
"Use UpdateRtcpTimestamp with strict time types: TimeDelta and NtpTime.")
bool UpdateRtcpTimestamp(int64_t rtt,
uint32_t ntp_secs,
uint32_t ntp_frac,
uint32_t rtp_timestamp) {
return UpdateRtcpTimestamp(TimeDelta::Millis(rtt),
NtpTime(ntp_secs, ntp_frac), rtp_timestamp);
}
bool UpdateRtcpTimestamp(TimeDelta rtt,
NtpTime sender_send_time,
uint32_t rtp_timestamp);
// Estimates the NTP timestamp in local timebase from `rtp_timestamp`.
// Returns the NTP timestamp in ms when success. -1 if failed.
int64_t Estimate(uint32_t rtp_timestamp);
int64_t Estimate(uint32_t rtp_timestamp) {
NtpTime ntp_time = EstimateNtp(rtp_timestamp);
if (!ntp_time.Valid()) {
return -1;
}
return ntp_time.ToMs();
}
// Estimates the NTP timestamp in local timebase from `rtp_timestamp`.
// Returns invalid NtpTime (i.e. NtpTime(0)) on failure.
NtpTime EstimateNtp(uint32_t rtp_timestamp);
// Estimates the offset, in milliseconds, between the remote clock and the
// local one. This is equal to local NTP clock - remote NTP clock.
absl::optional<int64_t> EstimateRemoteToLocalClockOffsetMs();
ABSL_DEPRECATED("Use EstimateRemoteToLocalClockOffset.")
absl::optional<int64_t> EstimateRemoteToLocalClockOffsetMs() {
if (absl::optional<int64_t> offset = EstimateRemoteToLocalClockOffset()) {
return (*offset * 1'000) / (int64_t{1} << 32);
}
return absl::nullopt;
}
// Estimates the offset between the remote clock and the
// local one. This is equal to local NTP clock - remote NTP clock.
// The offset is returned in ntp time resolution, i.e. 1/2^32 sec ~= 0.2 ns.
// Returns nullopt on failure.
absl::optional<int64_t> EstimateRemoteToLocalClockOffset();
private:
Clock* clock_;
// Offset is measured with the same precision as NtpTime: in 1/2^32 seconds ~=
// 0.2 ns.
MovingMedianFilter<int64_t> ntp_clocks_offset_estimator_;
RtpToNtpEstimator rtp_to_ntp_;
int64_t last_timing_log_ms_;
Timestamp last_timing_log_ = Timestamp::MinusInfinity();
};
} // namespace webrtc

View File

@ -22,25 +22,37 @@ namespace webrtc {
namespace {
constexpr int kMinimumNumberOfSamples = 2;
constexpr int kTimingLogIntervalMs = 10000;
constexpr TimeDelta kTimingLogInterval = TimeDelta::Seconds(10);
constexpr int kClocksOffsetSmoothingWindow = 100;
// Subtracts two NtpTime values keeping maximum precision.
int64_t Subtract(NtpTime minuend, NtpTime subtrahend) {
uint64_t a = static_cast<uint64_t>(minuend);
uint64_t b = static_cast<uint64_t>(subtrahend);
return a >= b ? static_cast<int64_t>(a - b) : -static_cast<int64_t>(b - a);
}
NtpTime Add(NtpTime lhs, int64_t rhs) {
uint64_t result = static_cast<uint64_t>(lhs);
if (rhs >= 0) {
result += static_cast<uint64_t>(rhs);
} else {
result -= static_cast<uint64_t>(-rhs);
}
return NtpTime(result);
}
} // namespace
// TODO(wu): Refactor this class so that it can be shared with
// vie_sync_module.cc.
RemoteNtpTimeEstimator::RemoteNtpTimeEstimator(Clock* clock)
: clock_(clock),
ntp_clocks_offset_estimator_(kClocksOffsetSmoothingWindow),
last_timing_log_ms_(-1) {}
ntp_clocks_offset_estimator_(kClocksOffsetSmoothingWindow) {}
RemoteNtpTimeEstimator::~RemoteNtpTimeEstimator() {}
bool RemoteNtpTimeEstimator::UpdateRtcpTimestamp(int64_t rtt,
uint32_t ntp_secs,
uint32_t ntp_frac,
bool RemoteNtpTimeEstimator::UpdateRtcpTimestamp(TimeDelta rtt,
NtpTime sender_send_time,
uint32_t rtp_timestamp) {
NtpTime sender_send_time(ntp_secs, ntp_frac);
switch (rtp_to_ntp_.UpdateMeasurements(sender_send_time, rtp_timestamp)) {
case RtpToNtpEstimator::kInvalidMeasurement:
return false;
@ -51,42 +63,42 @@ bool RemoteNtpTimeEstimator::UpdateRtcpTimestamp(int64_t rtt,
break;
}
// Assume connection is symmetric and thus time to deliver the packet is half
// the round trip time.
int64_t deliver_time_ntp = ToNtpUnits(rtt) / 2;
// Update extrapolator with the new arrival time.
// The extrapolator assumes the ntp time.
int64_t receiver_arrival_time_ms = clock_->CurrentNtpInMilliseconds();
int64_t sender_arrival_time_ms = sender_send_time.ToMs() + rtt / 2;
NtpTime receiver_arrival_time = clock_->CurrentNtpTime();
int64_t remote_to_local_clocks_offset =
receiver_arrival_time_ms - sender_arrival_time_ms;
Subtract(receiver_arrival_time, sender_send_time) - deliver_time_ntp;
ntp_clocks_offset_estimator_.Insert(remote_to_local_clocks_offset);
return true;
}
int64_t RemoteNtpTimeEstimator::Estimate(uint32_t rtp_timestamp) {
NtpTime RemoteNtpTimeEstimator::EstimateNtp(uint32_t rtp_timestamp) {
NtpTime sender_capture = rtp_to_ntp_.Estimate(rtp_timestamp);
if (!sender_capture.Valid()) {
return -1;
return sender_capture;
}
int64_t sender_capture_ntp_ms = sender_capture.ToMs();
int64_t remote_to_local_clocks_offset =
ntp_clocks_offset_estimator_.GetFilteredValue();
int64_t receiver_capture_ntp_ms =
sender_capture_ntp_ms + remote_to_local_clocks_offset;
NtpTime receiver_capture = Add(sender_capture, remote_to_local_clocks_offset);
int64_t now_ms = clock_->TimeInMilliseconds();
if (now_ms - last_timing_log_ms_ > kTimingLogIntervalMs) {
Timestamp now = clock_->CurrentTime();
if (now - last_timing_log_ > kTimingLogInterval) {
RTC_LOG(LS_INFO) << "RTP timestamp: " << rtp_timestamp
<< " in NTP clock: " << sender_capture_ntp_ms
<< " in NTP clock: " << sender_capture.ToMs()
<< " estimated time in receiver NTP clock: "
<< receiver_capture_ntp_ms;
last_timing_log_ms_ = now_ms;
<< receiver_capture.ToMs();
last_timing_log_ = now;
}
return receiver_capture_ntp_ms;
return receiver_capture;
}
absl::optional<int64_t>
RemoteNtpTimeEstimator::EstimateRemoteToLocalClockOffsetMs() {
RemoteNtpTimeEstimator::EstimateRemoteToLocalClockOffset() {
if (ntp_clocks_offset_estimator_.GetNumberOfSamplesStored() <
kMinimumNumberOfSamples) {
return absl::nullopt;

View File

@ -9,32 +9,29 @@
*/
#include "modules/rtp_rtcp/include/remote_ntp_time_estimator.h"
#include "absl/types/optional.h"
#include "modules/rtp_rtcp/source/time_util.h"
#include "system_wrappers/include/clock.h"
#include "system_wrappers/include/ntp_time.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
constexpr int64_t kTestRtt = 10;
constexpr int64_t kLocalClockInitialTimeMs = 123;
constexpr int64_t kRemoteClockInitialTimeMs = 345;
constexpr TimeDelta kTestRtt = TimeDelta::Millis(10);
constexpr Timestamp kLocalClockInitialTime = Timestamp::Millis(123);
constexpr Timestamp kRemoteClockInitialTime = Timestamp::Millis(373);
constexpr uint32_t kTimestampOffset = 567;
constexpr int64_t kRemoteToLocalClockOffsetMs =
kLocalClockInitialTimeMs - kRemoteClockInitialTimeMs;
constexpr int64_t kRemoteToLocalClockOffsetNtp =
ToNtpUnits(kLocalClockInitialTime - kRemoteClockInitialTime);
class RemoteNtpTimeEstimatorTest : public ::testing::Test {
protected:
RemoteNtpTimeEstimatorTest()
: local_clock_(kLocalClockInitialTimeMs * 1000),
remote_clock_(kRemoteClockInitialTimeMs * 1000),
estimator_(new RemoteNtpTimeEstimator(&local_clock_)) {}
~RemoteNtpTimeEstimatorTest() override = default;
void AdvanceTimeMilliseconds(int64_t ms) {
local_clock_.AdvanceTimeMilliseconds(ms);
remote_clock_.AdvanceTimeMilliseconds(ms);
void AdvanceTime(TimeDelta delta) {
local_clock_.AdvanceTime(delta);
remote_clock_.AdvanceTime(delta);
}
uint32_t GetRemoteTimestamp() {
@ -42,107 +39,90 @@ class RemoteNtpTimeEstimatorTest : public ::testing::Test {
kTimestampOffset;
}
NtpTime GetRemoteNtpTime() { return remote_clock_.CurrentNtpTime(); }
void SendRtcpSr() {
uint32_t rtcp_timestamp = GetRemoteTimestamp();
NtpTime ntp = GetRemoteNtpTime();
NtpTime ntp = remote_clock_.CurrentNtpTime();
AdvanceTimeMilliseconds(kTestRtt / 2);
ReceiveRtcpSr(kTestRtt, rtcp_timestamp, ntp.seconds(), ntp.fractions());
AdvanceTime(kTestRtt / 2);
RTC_DCHECK(estimator_.UpdateRtcpTimestamp(kTestRtt, ntp, rtcp_timestamp));
}
void SendRtcpSrInaccurately(int64_t ntp_error_ms,
int64_t networking_delay_ms) {
void SendRtcpSrInaccurately(TimeDelta ntp_error, TimeDelta networking_delay) {
uint32_t rtcp_timestamp = GetRemoteTimestamp();
int64_t ntp_error_fractions =
ntp_error_ms * static_cast<int64_t>(NtpTime::kFractionsPerSecond) /
1000;
NtpTime ntp(static_cast<uint64_t>(GetRemoteNtpTime()) +
int64_t ntp_error_fractions = ToNtpUnits(ntp_error);
NtpTime ntp(static_cast<uint64_t>(remote_clock_.CurrentNtpTime()) +
ntp_error_fractions);
AdvanceTimeMilliseconds(kTestRtt / 2 + networking_delay_ms);
ReceiveRtcpSr(kTestRtt, rtcp_timestamp, ntp.seconds(), ntp.fractions());
AdvanceTime(kTestRtt / 2 + networking_delay);
RTC_DCHECK(estimator_.UpdateRtcpTimestamp(kTestRtt, ntp, rtcp_timestamp));
}
void UpdateRtcpTimestamp(int64_t rtt,
uint32_t ntp_secs,
uint32_t ntp_frac,
uint32_t rtp_timestamp,
bool expected_result) {
EXPECT_EQ(expected_result, estimator_->UpdateRtcpTimestamp(
rtt, ntp_secs, ntp_frac, rtp_timestamp));
}
void ReceiveRtcpSr(int64_t rtt,
uint32_t rtcp_timestamp,
uint32_t ntp_seconds,
uint32_t ntp_fractions) {
UpdateRtcpTimestamp(rtt, ntp_seconds, ntp_fractions, rtcp_timestamp, true);
}
SimulatedClock local_clock_;
SimulatedClock remote_clock_;
std::unique_ptr<RemoteNtpTimeEstimator> estimator_;
SimulatedClock local_clock_{kLocalClockInitialTime};
SimulatedClock remote_clock_{kRemoteClockInitialTime};
RemoteNtpTimeEstimator estimator_{&local_clock_};
};
TEST_F(RemoteNtpTimeEstimatorTest, Estimate) {
// Failed without valid NTP.
UpdateRtcpTimestamp(kTestRtt, 0, 0, 0, false);
TEST_F(RemoteNtpTimeEstimatorTest, FailsWithoutValidNtpTime) {
EXPECT_FALSE(
estimator_.UpdateRtcpTimestamp(kTestRtt, NtpTime(), /*rtp_timestamp=*/0));
}
AdvanceTimeMilliseconds(1000);
TEST_F(RemoteNtpTimeEstimatorTest, Estimate) {
// Remote peer sends first RTCP SR.
SendRtcpSr();
// Remote sends a RTP packet.
AdvanceTimeMilliseconds(15);
AdvanceTime(TimeDelta::Millis(15));
uint32_t rtp_timestamp = GetRemoteTimestamp();
int64_t capture_ntp_time_ms = local_clock_.CurrentNtpInMilliseconds();
// Local peer needs at least 2 RTCP SR to calculate the capture time.
const int64_t kNotEnoughRtcpSr = -1;
EXPECT_EQ(kNotEnoughRtcpSr, estimator_->Estimate(rtp_timestamp));
EXPECT_EQ(absl::nullopt, estimator_->EstimateRemoteToLocalClockOffsetMs());
EXPECT_EQ(kNotEnoughRtcpSr, estimator_.Estimate(rtp_timestamp));
EXPECT_EQ(estimator_.EstimateRemoteToLocalClockOffset(), absl::nullopt);
AdvanceTimeMilliseconds(800);
AdvanceTime(TimeDelta::Millis(800));
// Remote sends second RTCP SR.
SendRtcpSr();
// Local peer gets enough RTCP SR to calculate the capture time.
EXPECT_EQ(capture_ntp_time_ms, estimator_->Estimate(rtp_timestamp));
EXPECT_EQ(kRemoteToLocalClockOffsetMs,
estimator_->EstimateRemoteToLocalClockOffsetMs());
EXPECT_EQ(capture_ntp_time_ms, estimator_.Estimate(rtp_timestamp));
EXPECT_EQ(estimator_.EstimateRemoteToLocalClockOffset(),
kRemoteToLocalClockOffsetNtp);
}
TEST_F(RemoteNtpTimeEstimatorTest, AveragesErrorsOut) {
// Remote peer sends first 10 RTCP SR without errors.
for (int i = 0; i < 10; ++i) {
AdvanceTimeMilliseconds(1000);
AdvanceTime(TimeDelta::Seconds(1));
SendRtcpSr();
}
AdvanceTimeMilliseconds(150);
AdvanceTime(TimeDelta::Millis(150));
uint32_t rtp_timestamp = GetRemoteTimestamp();
int64_t capture_ntp_time_ms = local_clock_.CurrentNtpInMilliseconds();
// Local peer gets enough RTCP SR to calculate the capture time.
EXPECT_EQ(capture_ntp_time_ms, estimator_->Estimate(rtp_timestamp));
EXPECT_EQ(kRemoteToLocalClockOffsetMs,
estimator_->EstimateRemoteToLocalClockOffsetMs());
EXPECT_EQ(capture_ntp_time_ms, estimator_.Estimate(rtp_timestamp));
EXPECT_EQ(kRemoteToLocalClockOffsetNtp,
estimator_.EstimateRemoteToLocalClockOffset());
// Remote sends corrupted RTCP SRs
AdvanceTimeMilliseconds(1000);
SendRtcpSrInaccurately(/*ntp_error_ms=*/2, /*networking_delay_ms=*/-1);
AdvanceTimeMilliseconds(1000);
SendRtcpSrInaccurately(/*ntp_error_ms=*/-2, /*networking_delay_ms=*/1);
AdvanceTime(TimeDelta::Seconds(1));
SendRtcpSrInaccurately(/*ntp_error=*/TimeDelta::Millis(2),
/*networking_delay=*/TimeDelta::Millis(-1));
AdvanceTime(TimeDelta::Seconds(1));
SendRtcpSrInaccurately(/*ntp_error=*/TimeDelta::Millis(-2),
/*networking_delay=*/TimeDelta::Millis(1));
// New RTP packet to estimate timestamp.
AdvanceTimeMilliseconds(150);
AdvanceTime(TimeDelta::Millis(150));
rtp_timestamp = GetRemoteTimestamp();
capture_ntp_time_ms = local_clock_.CurrentNtpInMilliseconds();
// Errors should be averaged out.
EXPECT_EQ(capture_ntp_time_ms, estimator_->Estimate(rtp_timestamp));
EXPECT_EQ(kRemoteToLocalClockOffsetMs,
estimator_->EstimateRemoteToLocalClockOffsetMs());
EXPECT_EQ(capture_ntp_time_ms, estimator_.Estimate(rtp_timestamp));
EXPECT_EQ(kRemoteToLocalClockOffsetNtp,
estimator_.EstimateRemoteToLocalClockOffset());
}
} // namespace
} // namespace webrtc

View File

@ -34,6 +34,14 @@ inline uint32_t CompactNtp(NtpTime ntp) {
// Negative values converted to 0, Overlarge values converted to max uint32_t.
uint32_t SaturatedToCompactNtp(TimeDelta delta);
// Convert interval to the NTP time resolution (1/2^32 seconds ~= 0.2 ns).
inline constexpr int64_t ToNtpUnits(TimeDelta delta) {
// For better precision `delta` is taken with best TimeDelta precision (us),
// then multiplaction and conversion to seconds are swapped to avoid float
// arithmetic.
return (delta.us() * (int64_t{1} << 32)) / 1'000'000;
}
// Converts interval from compact ntp (1/2^16 seconds) resolution to TimeDelta.
// This interval can be up to ~9.1 hours (2^15 seconds).
// Values close to 2^16 seconds are considered negative and are converted to

View File

@ -95,4 +95,20 @@ TEST(TimeUtilTest, SaturatedToCompactNtp) {
5'515, 16);
}
TEST(TimeUtilTest, ToNtpUnits) {
EXPECT_EQ(ToNtpUnits(TimeDelta::Zero()), 0);
EXPECT_EQ(ToNtpUnits(TimeDelta::Seconds(1)), int64_t{1} << 32);
EXPECT_EQ(ToNtpUnits(TimeDelta::Seconds(-1)), -(int64_t{1} << 32));
EXPECT_EQ(ToNtpUnits(TimeDelta::Millis(500)), int64_t{1} << 31);
EXPECT_EQ(ToNtpUnits(TimeDelta::Millis(-1'500)), -(int64_t{3} << 31));
// Smallest TimeDelta that can be converted without precision loss.
EXPECT_EQ(ToNtpUnits(TimeDelta::Micros(15'625)), int64_t{1} << 26);
// 1 us ~= 4'294.97 NTP units. ToNtpUnits makes no rounding promises.
EXPECT_GE(ToNtpUnits(TimeDelta::Micros(1)), 4'294);
EXPECT_LE(ToNtpUnits(TimeDelta::Micros(1)), 4'295);
}
} // namespace webrtc

View File

@ -1122,12 +1122,13 @@ bool RtpVideoStreamReceiver::DeliverRtcp(const uint8_t* rtcp_packet,
clock_->CurrentNtpInMilliseconds() - received_ntp.ToMs();
// Don't use old SRs to estimate time.
if (time_since_received <= 1) {
ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp);
absl::optional<int64_t> remote_to_local_clock_offset_ms =
ntp_estimator_.EstimateRemoteToLocalClockOffsetMs();
if (remote_to_local_clock_offset_ms.has_value()) {
ntp_estimator_.UpdateRtcpTimestamp(
TimeDelta::Millis(rtt), NtpTime(ntp_secs, ntp_frac), rtp_timestamp);
absl::optional<int64_t> remote_to_local_clock_offset =
ntp_estimator_.EstimateRemoteToLocalClockOffset();
if (remote_to_local_clock_offset.has_value()) {
capture_clock_offset_updater_.SetRemoteToLocalClockOffset(
Int64MsToQ32x32(*remote_to_local_clock_offset_ms));
*remote_to_local_clock_offset);
}
}

View File

@ -1042,12 +1042,13 @@ bool RtpVideoStreamReceiver2::DeliverRtcp(const uint8_t* rtcp_packet,
clock_->CurrentNtpInMilliseconds() - received_ntp.ToMs();
// Don't use old SRs to estimate time.
if (time_since_received <= 1) {
ntp_estimator_.UpdateRtcpTimestamp(rtt, ntp_secs, ntp_frac, rtp_timestamp);
absl::optional<int64_t> remote_to_local_clock_offset_ms =
ntp_estimator_.EstimateRemoteToLocalClockOffsetMs();
if (remote_to_local_clock_offset_ms.has_value()) {
ntp_estimator_.UpdateRtcpTimestamp(
TimeDelta::Millis(rtt), NtpTime(ntp_secs, ntp_frac), rtp_timestamp);
absl::optional<int64_t> remote_to_local_clock_offset =
ntp_estimator_.EstimateRemoteToLocalClockOffset();
if (remote_to_local_clock_offset.has_value()) {
capture_clock_offset_updater_.SetRemoteToLocalClockOffset(
Int64MsToQ32x32(*remote_to_local_clock_offset_ms));
*remote_to_local_clock_offset);
}
}