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}
This commit is contained in:
Danil Chapovalov 2022-03-14 12:31:46 +01:00 committed by WebRTC LUCI CQ
parent 340cb5e46a
commit ae4fb618d7
7 changed files with 266 additions and 410 deletions

View file

@ -40,21 +40,21 @@ bool RemoteNtpTimeEstimator::UpdateRtcpTimestamp(int64_t rtt,
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 @@ bool RemoteNtpTimeEstimator::UpdateRtcpTimestamp(int64_t rtt,
}
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();

View file

@ -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_;

View file

@ -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 @@ bool RtpToNtpEstimator::UpdateMeasurements(uint32_t ntp_secs,
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 @@ bool RtpToNtpEstimator::UpdateMeasurements(uint32_t ntp_secs,
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

View file

@ -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;
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_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);
}
}

View file

@ -29,10 +29,10 @@ bool UpdateMeasurements(StreamSynchronization::Measurements* stream,
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 @@ bool RtpStreamsSynchronizer::GetStreamSyncOffsetInMs(
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;
}

View file

@ -35,19 +35,19 @@ bool StreamSynchronization::ComputeRelativeDelay(
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)) {
return false;
}
if (video_last_capture_time_ms < 0) {
NtpTime video_last_capture_time =
video_measurement.rtp_to_ntp.Estimate(video_measurement.latest_timestamp);
if (!video_last_capture_time.Valid()) {
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 -

View file

@ -47,32 +47,31 @@ class StreamSynchronizationTest : public ::testing::Test {
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);