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_secs,
uint32_t ntp_frac, uint32_t ntp_frac,
uint32_t rtp_timestamp) { uint32_t rtp_timestamp) {
bool new_rtcp_sr = false; NtpTime sender_send_time(ntp_secs, ntp_frac);
if (!rtp_to_ntp_.UpdateMeasurements(ntp_secs, ntp_frac, rtp_timestamp, switch (rtp_to_ntp_.UpdateMeasurements(sender_send_time, rtp_timestamp)) {
&new_rtcp_sr)) { case RtpToNtpEstimator::kInvalidMeasurement:
return false; return false;
} case RtpToNtpEstimator::kSameMeasurement:
if (!new_rtcp_sr) { // No new RTCP SR since last time this function was called.
// No new RTCP SR since last time this function was called. return true;
return true; case RtpToNtpEstimator::kNewMeasurement:
break;
} }
// Update extrapolator with the new arrival time. // Update extrapolator with the new arrival time.
// The extrapolator assumes the ntp time. // The extrapolator assumes the ntp time.
int64_t receiver_arrival_time_ms = clock_->CurrentNtpInMilliseconds(); 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.ToMs() + rtt / 2;
int64_t sender_arrival_time_ms = sender_send_time_ms + rtt / 2;
int64_t remote_to_local_clocks_offset = int64_t remote_to_local_clocks_offset =
receiver_arrival_time_ms - sender_arrival_time_ms; receiver_arrival_time_ms - sender_arrival_time_ms;
ntp_clocks_offset_estimator_.Insert(remote_to_local_clocks_offset); 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 RemoteNtpTimeEstimator::Estimate(uint32_t rtp_timestamp) {
int64_t sender_capture_ntp_ms = 0; NtpTime sender_capture = rtp_to_ntp_.Estimate(rtp_timestamp);
if (!rtp_to_ntp_.Estimate(rtp_timestamp, &sender_capture_ntp_ms)) { if (!sender_capture.Valid()) {
return -1; return -1;
} }
int64_t sender_capture_ntp_ms = sender_capture.ToMs();
int64_t remote_to_local_clocks_offset = int64_t remote_to_local_clocks_offset =
ntp_clocks_offset_estimator_.GetFilteredValue(); ntp_clocks_offset_estimator_.GetFilteredValue();

View file

@ -18,60 +18,51 @@
#include "absl/types/optional.h" #include "absl/types/optional.h"
#include "modules/include/module_common_types_public.h" #include "modules/include/module_common_types_public.h"
#include "rtc_base/checks.h" #include "rtc_base/checks.h"
#include "rtc_base/numerics/moving_median_filter.h"
#include "system_wrappers/include/ntp_time.h" #include "system_wrappers/include/ntp_time.h"
namespace webrtc { 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 // The class needs to be trained with (at least 2) RTP/NTP timestamp pairs from
// RTCP sender reports before the convertion can be done. // RTCP sender reports before the convertion can be done.
class RtpToNtpEstimator { class RtpToNtpEstimator {
public: public:
RtpToNtpEstimator(); static constexpr int kMaxInvalidSamples = 3;
~RtpToNtpEstimator();
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. // RTP and NTP timestamp pair from a RTCP SR report.
struct RtcpMeasurement { struct RtcpMeasurement {
RtcpMeasurement(uint32_t ntp_secs,
uint32_t ntp_frac,
int64_t unwrapped_timestamp);
bool IsEqual(const RtcpMeasurement& other) const;
NtpTime ntp_time; NtpTime ntp_time;
int64_t unwrapped_rtp_timestamp; 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(); void UpdateParameters();
int consecutive_invalid_samples_; int consecutive_invalid_samples_ = 0;
std::list<RtcpMeasurement> measurements_; std::list<RtcpMeasurement> measurements_;
absl::optional<Parameters> params_; absl::optional<Parameters> params_;
mutable TimestampUnwrapper unwrapper_; mutable TimestampUnwrapper unwrapper_;

View file

@ -18,132 +18,85 @@
#include "api/array_view.h" #include "api/array_view.h"
#include "rtc_base/checks.h" #include "rtc_base/checks.h"
#include "rtc_base/logging.h" #include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc { namespace webrtc {
namespace { namespace {
// Maximum number of RTCP SR reports to use to map between RTP and NTP. // 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 // 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 // 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 // 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. // 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, void RtpToNtpEstimator::UpdateParameters() {
const RtpToNtpEstimator::RtcpMeasurement& other) { size_t n = measurements_.size();
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();
if (n < 2) if (n < 2)
return false; return;
if (y.size() != n) // Run linear regression:
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).
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_x = 0;
double avg_y = 0; double avg_y = 0;
for (size_t i = 0; i < n; ++i) { for (const RtcpMeasurement& m : measurements_) {
avg_x += x[i]; avg_x += x(m);
avg_y += y[i]; avg_y += y(m);
} }
avg_x /= n; avg_x /= n;
avg_y /= n; avg_y /= n;
double variance_x = 0; double variance_x = 0;
double covariance_xy = 0; double covariance_xy = 0;
for (size_t i = 0; i < n; ++i) { for (const RtcpMeasurement& m : measurements_) {
double normalized_x = x[i] - avg_x; double normalized_x = x(m) - avg_x;
double normalized_y = y[i] - avg_y; double normalized_y = y(m) - avg_y;
variance_x += normalized_x * normalized_x; variance_x += normalized_x * normalized_x;
covariance_xy += normalized_x * normalized_y; covariance_xy += normalized_x * normalized_y;
} }
if (std::fabs(variance_x) < 1e-8) 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; return;
std::vector<double> x; double k = covariance_xy / variance_x;
std::vector<double> y; double b = avg_y - k * avg_x;
x.reserve(measurements_.size()); params_ = {{.slope = k, .offset = b}};
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);
} }
bool RtpToNtpEstimator::UpdateMeasurements(uint32_t ntp_secs, RtpToNtpEstimator::UpdateResult RtpToNtpEstimator::UpdateMeasurements(
uint32_t ntp_frac, NtpTime ntp,
uint32_t rtp_timestamp, uint32_t rtp_timestamp) {
bool* new_rtcp_sr) {
*new_rtcp_sr = false;
int64_t unwrapped_rtp_timestamp = unwrapper_.Unwrap(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)) { for (const RtcpMeasurement& measurement : measurements_) {
// RTCP SR report already added. // Use || since two equal timestamps will result in zero frequency.
return true; if (measurement.ntp_time == ntp ||
measurement.unwrapped_rtp_timestamp == unwrapped_rtp_timestamp) {
return kSameMeasurement;
}
} }
if (!new_measurement.ntp_time.Valid()) 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; bool invalid_sample = false;
if (!measurements_.empty()) { if (!measurements_.empty()) {
int64_t old_rtp_timestamp = measurements_.front().unwrapped_rtp_timestamp; int64_t old_rtp_timestamp = measurements_.front().unwrapped_rtp_timestamp;
int64_t old_ntp_ms = measurements_.front().ntp_time.ToMs(); uint64_t old_ntp = static_cast<uint64_t>(measurements_.front().ntp_time);
if (ntp_ms_new <= old_ntp_ms || if (ntp_new <= old_ntp || ntp_new > old_ntp + kMaxAllowedRtcpNtpInterval) {
ntp_ms_new > old_ntp_ms + kMaxAllowedRtcpNtpIntervalMs) {
invalid_sample = true; invalid_sample = true;
} else if (unwrapped_rtp_timestamp <= old_rtp_timestamp) { } else if (unwrapped_rtp_timestamp <= old_rtp_timestamp) {
RTC_LOG(LS_WARNING) RTC_LOG(LS_WARNING)
@ -158,7 +111,7 @@ bool RtpToNtpEstimator::UpdateMeasurements(uint32_t ntp_secs,
if (invalid_sample) { if (invalid_sample) {
++consecutive_invalid_samples_; ++consecutive_invalid_samples_;
if (consecutive_invalid_samples_ < kMaxInvalidSamples) { if (consecutive_invalid_samples_ < kMaxInvalidSamples) {
return false; return kInvalidMeasurement;
} }
RTC_LOG(LS_WARNING) << "Multiple consecutively invalid RTCP SR reports, " RTC_LOG(LS_WARNING) << "Multiple consecutively invalid RTCP SR reports, "
"clearing measurements."; "clearing measurements.";
@ -172,37 +125,29 @@ bool RtpToNtpEstimator::UpdateMeasurements(uint32_t ntp_secs,
measurements_.pop_back(); measurements_.pop_back();
measurements_.push_front(new_measurement); measurements_.push_front(new_measurement);
*new_rtcp_sr = true;
// List updated, calculate new parameters. // List updated, calculate new parameters.
UpdateParameters(); UpdateParameters();
return true; return kNewMeasurement;
} }
bool RtpToNtpEstimator::Estimate(int64_t rtp_timestamp, NtpTime RtpToNtpEstimator::Estimate(uint32_t rtp_timestamp) const {
int64_t* ntp_timestamp_ms) const {
if (!params_) 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 return NtpTime(rtc::saturated_cast<uint64_t>(estimated));
// 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;
} }
const absl::optional<RtpToNtpEstimator::Parameters> RtpToNtpEstimator::params() double RtpToNtpEstimator::EstimatedFrequencyKhz() const {
const { if (!params_.has_value()) {
return params_; return 0.0;
}
static constexpr double kNtpUnitPerMs = 4.294967296E6; // 2^32 / 1000.
return kNtpUnitPerMs / params_->slope;
} }
} // namespace webrtc } // namespace webrtc

View file

@ -17,332 +17,249 @@
namespace webrtc { namespace webrtc {
namespace { namespace {
const uint32_t kOneMsInNtpFrac = 4294967; constexpr uint64_t kOneMsInNtp = 4294967;
const uint32_t kOneHourInNtpSec = 60 * 60; constexpr uint64_t kOneHourInNtp = uint64_t{60 * 60} << 32;
const uint32_t kTimestampTicksPerMs = 90; constexpr uint32_t kTimestampTicksPerMs = 90;
} // namespace } // namespace
TEST(WrapAroundTests, OldRtcpWrapped_OldRtpTimestamp) { TEST(WrapAroundTests, OldRtcpWrapped_OldRtpTimestamp) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
bool new_sr; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(kOneMsInNtp), 0),
uint32_t ntp_sec = 0; RtpToNtpEstimator::kNewMeasurement);
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;
// No wraparound will be detected, since we are not allowed to wrap below 0, // 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, // but there will be huge rtp timestamp jump, e.g. old_timestamp = 0,
// new_timestamp = 4294967295, which should be detected. // new_timestamp = 4294967295, which should be detected.
EXPECT_FALSE( EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2 * kOneMsInNtp),
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr)); -kTimestampTicksPerMs),
RtpToNtpEstimator::kInvalidMeasurement);
} }
TEST(WrapAroundTests, OldRtcpWrapped_OldRtpTimestamp_Wraparound_Detected) { TEST(WrapAroundTests, OldRtcpWrapped_OldRtpTimestamp_Wraparound_Detected) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
bool new_sr; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFE),
uint32_t ntp_sec = 0; RtpToNtpEstimator::kNewMeasurement);
uint32_t ntp_frac = 1; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + 2 * kOneMsInNtp),
uint32_t timestamp = 0xFFFFFFFE; 0xFFFFFFFE + 2 * kTimestampTicksPerMs),
EXPECT_TRUE( RtpToNtpEstimator::kNewMeasurement);
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;
// Expected to fail since the older RTCP has a smaller RTP timestamp than the // Expected to fail since the older RTCP has a smaller RTP timestamp than the
// newer (old:10, new:4294967206). // newer (old:10, new:4294967206).
EXPECT_FALSE( EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + 3 * kOneMsInNtp),
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr)); 0xFFFFFFFE + kTimestampTicksPerMs),
RtpToNtpEstimator::kInvalidMeasurement);
} }
TEST(WrapAroundTests, NewRtcpWrapped) { TEST(WrapAroundTests, NewRtcpWrapped) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
bool new_sr; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
uint32_t ntp_sec = 0; RtpToNtpEstimator::kNewMeasurement);
uint32_t ntp_frac = 1; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
uint32_t timestamp = 0xFFFFFFFF; 0xFFFFFFFF + kTimestampTicksPerMs),
EXPECT_TRUE( RtpToNtpEstimator::kNewMeasurement);
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));
// Since this RTP packet has the same timestamp as the RTCP packet constructed // Since this RTP packet has the same timestamp as the RTCP packet constructed
// at time 0 it should be mapped to 0 as well. // 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) { TEST(WrapAroundTests, RtpWrapped) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
bool new_sr; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1),
uint32_t ntp_sec = 0; 0xFFFFFFFF - 2 * kTimestampTicksPerMs),
uint32_t ntp_frac = 1; RtpToNtpEstimator::kNewMeasurement);
uint32_t timestamp = 0xFFFFFFFF - 2 * kTimestampTicksPerMs; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
EXPECT_TRUE( 0xFFFFFFFF - kTimestampTicksPerMs),
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
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 - 2 * kTimestampTicksPerMs, &timestamp_ms));
// Since this RTP packet has the same timestamp as the RTCP packet constructed // Since this RTP packet has the same timestamp as the RTCP packet constructed
// at time 0 it should be mapped to 0 as well. // 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. // Two kTimestampTicksPerMs advanced.
timestamp += kTimestampTicksPerMs; EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1 + 2 * kOneMsInNtp));
EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
EXPECT_EQ(2, timestamp_ms);
// Wrapped rtp. // Wrapped rtp.
timestamp += kTimestampTicksPerMs; EXPECT_EQ(estimator.Estimate(0xFFFFFFFF + kTimestampTicksPerMs),
EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms)); NtpTime(1 + 3 * kOneMsInNtp));
EXPECT_EQ(3, timestamp_ms);
} }
TEST(WrapAroundTests, OldRtp_RtcpsWrapped) { TEST(WrapAroundTests, OldRtp_RtcpsWrapped) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
bool new_sr; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
uint32_t ntp_sec = 0; RtpToNtpEstimator::kNewMeasurement);
uint32_t ntp_frac = 1; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
uint32_t timestamp = 0xFFFFFFFF; 0xFFFFFFFF + kTimestampTicksPerMs),
EXPECT_TRUE( RtpToNtpEstimator::kNewMeasurement);
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac; EXPECT_FALSE(estimator.Estimate(0xFFFFFFFF - kTimestampTicksPerMs).Valid());
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));
} }
TEST(WrapAroundTests, OldRtp_NewRtcpWrapped) { TEST(WrapAroundTests, OldRtp_NewRtcpWrapped) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
bool new_sr; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
uint32_t ntp_sec = 0; RtpToNtpEstimator::kNewMeasurement);
uint32_t ntp_frac = 1; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
uint32_t timestamp = 0xFFFFFFFF; 0xFFFFFFFF + kTimestampTicksPerMs),
EXPECT_TRUE( RtpToNtpEstimator::kNewMeasurement);
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));
// Constructed at the same time as the first RTCP and should therefore be // Constructed at the same time as the first RTCP and should therefore be
// mapped to zero. // mapped to zero.
EXPECT_EQ(0, timestamp_ms); EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1));
} }
TEST(WrapAroundTests, GracefullyHandleRtpJump) { TEST(WrapAroundTests, GracefullyHandleRtpJump) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
bool new_sr; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
uint32_t ntp_sec = 0; RtpToNtpEstimator::kNewMeasurement);
uint32_t ntp_frac = 1; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
uint32_t timestamp = 0; 0xFFFFFFFF + kTimestampTicksPerMs),
EXPECT_TRUE( RtpToNtpEstimator::kNewMeasurement);
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));
// Constructed at the same time as the first RTCP and should therefore be // Constructed at the same time as the first RTCP and should therefore be
// mapped to zero. // 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) { for (int i = 0; i < RtpToNtpEstimator::kMaxInvalidSamples - 1; ++i) {
EXPECT_FALSE( EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr)); RtpToNtpEstimator::kInvalidMeasurement);
ntp_frac += kOneMsInNtpFrac; ntp_raw += kOneMsInNtp;
timestamp += kTimestampTicksPerMs; timestamp += kTimestampTicksPerMs;
} }
EXPECT_TRUE( EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
ntp_frac += kOneMsInNtpFrac; ntp_raw += kOneMsInNtp;
timestamp += kTimestampTicksPerMs;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
timestamp += kTimestampTicksPerMs; timestamp += kTimestampTicksPerMs;
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
RtpToNtpEstimator::kNewMeasurement);
timestamp_ms = -1; EXPECT_EQ(estimator.Estimate(timestamp), NtpTime(ntp_raw));
EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
// 6 milliseconds has passed since the start of the test.
EXPECT_EQ(6, timestamp_ms);
} }
TEST(UpdateRtcpMeasurementTests, FailsForZeroNtp) { TEST(UpdateRtcpMeasurementTests, FailsForZeroNtp) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 0; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(0), 0x12345678),
uint32_t timestamp = 0x12345678; RtpToNtpEstimator::kInvalidMeasurement);
bool new_sr;
EXPECT_FALSE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_FALSE(new_sr);
} }
TEST(UpdateRtcpMeasurementTests, FailsForEqualNtp) { TEST(UpdateRtcpMeasurementTests, FailsForEqualNtp) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
uint32_t ntp_sec = 0; NtpTime ntp(0, 699925050);
uint32_t ntp_frac = 699925050;
uint32_t timestamp = 0x12345678; uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE( EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp),
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
EXPECT_TRUE(new_sr);
// Ntp time already added, list not updated. // Ntp time already added, list not updated.
++timestamp; EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp + 1),
EXPECT_TRUE( RtpToNtpEstimator::kSameMeasurement);
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_FALSE(new_sr);
} }
TEST(UpdateRtcpMeasurementTests, FailsForOldNtp) { TEST(UpdateRtcpMeasurementTests, FailsForOldNtp) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
uint32_t ntp_sec = 1; uint64_t ntp_raw = 699925050;
uint32_t ntp_frac = 699925050; NtpTime ntp(ntp_raw);
uint32_t timestamp = 0x12345678; uint32_t timestamp = 0x12345678;
bool new_sr; EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp),
EXPECT_TRUE( RtpToNtpEstimator::kNewMeasurement);
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
// Old ntp time, list not updated. // Old ntp time, list not updated.
ntp_frac -= kOneMsInNtpFrac; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw - kOneMsInNtp),
timestamp += kTimestampTicksPerMs; timestamp + kTimestampTicksPerMs),
EXPECT_FALSE( RtpToNtpEstimator::kInvalidMeasurement);
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
} }
TEST(UpdateRtcpMeasurementTests, FailsForTooNewNtp) { TEST(UpdateRtcpMeasurementTests, FailsForTooNewNtp) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
uint32_t ntp_sec = 1;
uint32_t ntp_frac = 699925050; uint64_t ntp_raw = 699925050;
uint32_t timestamp = 0x12345678; uint32_t timestamp = 0x12345678;
bool new_sr; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
EXPECT_TRUE( RtpToNtpEstimator::kNewMeasurement);
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
// Ntp time from far future, list not updated. // Ntp time from far future, list not updated.
ntp_sec += kOneHourInNtpSec * 2; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw + 2 * kOneHourInNtp),
timestamp += kTimestampTicksPerMs * 10; timestamp + 10 * kTimestampTicksPerMs),
EXPECT_FALSE( RtpToNtpEstimator::kInvalidMeasurement);
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
} }
TEST(UpdateRtcpMeasurementTests, FailsForEqualTimestamp) { TEST(UpdateRtcpMeasurementTests, FailsForEqualTimestamp) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 2;
uint32_t timestamp = 0x12345678; uint32_t timestamp = 0x12345678;
bool new_sr; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2), timestamp),
EXPECT_TRUE( RtpToNtpEstimator::kNewMeasurement);
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
// Timestamp already added, list not updated. // Timestamp already added, list not updated.
++ntp_frac; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(3), timestamp),
EXPECT_TRUE( RtpToNtpEstimator::kSameMeasurement);
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_FALSE(new_sr);
} }
TEST(UpdateRtcpMeasurementTests, FailsForOldRtpTimestamp) { TEST(UpdateRtcpMeasurementTests, FailsForOldRtpTimestamp) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 2;
uint32_t timestamp = 0x12345678; uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE( EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2), timestamp),
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
EXPECT_TRUE(new_sr);
// Old timestamp, list not updated. // Old timestamp, list not updated.
ntp_frac += kOneMsInNtpFrac; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2 + kOneMsInNtp),
timestamp -= kTimestampTicksPerMs; timestamp - kTimestampTicksPerMs),
EXPECT_FALSE( RtpToNtpEstimator::kInvalidMeasurement);
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_FALSE(new_sr);
} }
TEST(UpdateRtcpMeasurementTests, VerifyParameters) { TEST(UpdateRtcpMeasurementTests, VerifyParameters) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
uint32_t ntp_sec = 1;
uint32_t ntp_frac = 2;
uint32_t timestamp = 0x12345678; uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE( EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(kOneMsInNtp), timestamp),
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
EXPECT_TRUE(new_sr);
EXPECT_FALSE(estimator.params()); EXPECT_DOUBLE_EQ(estimator.EstimatedFrequencyKhz(), 0.0);
// Add second report, parameters should be calculated. // Add second report, parameters should be calculated.
ntp_frac += kOneMsInNtpFrac; EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2 * kOneMsInNtp),
timestamp += kTimestampTicksPerMs; timestamp + kTimestampTicksPerMs),
EXPECT_TRUE( RtpToNtpEstimator::kNewMeasurement);
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(estimator.params()); EXPECT_NEAR(estimator.EstimatedFrequencyKhz(), kTimestampTicksPerMs, 0.01);
EXPECT_DOUBLE_EQ(90.0, estimator.params()->frequency_khz);
EXPECT_NE(0.0, estimator.params()->offset_ms);
} }
TEST(RtpToNtpTests, FailsForNoParameters) { TEST(RtpToNtpTests, FailsForNoParameters) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
uint32_t ntp_sec = 1;
uint32_t ntp_frac = 2;
uint32_t timestamp = 0x12345678; uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE( EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), timestamp),
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
EXPECT_TRUE(new_sr);
// Parameters are not calculated, conversion of RTP to NTP time should fail. // Parameters are not calculated, conversion of RTP to NTP time should fail.
EXPECT_FALSE(estimator.params()); EXPECT_DOUBLE_EQ(estimator.EstimatedFrequencyKhz(), 0.0);
int64_t timestamp_ms = -1; EXPECT_FALSE(estimator.Estimate(timestamp).Valid());
EXPECT_FALSE(estimator.Estimate(timestamp, &timestamp_ms));
} }
TEST(RtpToNtpTests, AveragesErrorOut) { TEST(RtpToNtpTests, AveragesErrorOut) {
RtpToNtpEstimator estimator; RtpToNtpEstimator estimator;
uint32_t ntp_sec = 1; uint64_t ntp_raw = 90000000; // More than 1 ms.
uint32_t ntp_frac = 90000000; // More than 1 ms. ASSERT_GT(ntp_raw, kOneMsInNtp);
uint32_t timestamp = 0x12345678; uint32_t timestamp = 0x12345678;
const int kNtpSecStep = 1; // 1 second. constexpr uint64_t kNtpSecStep = uint64_t{1} << 32; // 1 second.
const int kRtpTicksPerMs = 90; constexpr int kRtpTicksPerMs = 90;
const int kRtpStep = kRtpTicksPerMs * 1000; constexpr int kRtpStep = kRtpTicksPerMs * 1000;
bool new_sr;
EXPECT_TRUE( EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
EXPECT_TRUE(new_sr);
Random rand(1123536L); Random rand(1123536L);
for (size_t i = 0; i < 1000; i++) { for (size_t i = 0; i < 1000; i++) {
// Advance both timestamps by exactly 1 second. // Advance both timestamps by exactly 1 second.
ntp_sec += kNtpSecStep; ntp_raw += kNtpSecStep;
timestamp += kRtpStep; timestamp += kRtpStep;
// Add upto 1ms of errors to NTP and RTP timestamps passed to estimator. // Add upto 1ms of errors to NTP and RTP timestamps passed to estimator.
EXPECT_TRUE(estimator.UpdateMeasurements( EXPECT_EQ(
ntp_sec, estimator.UpdateMeasurements(
ntp_frac + rand.Rand(-static_cast<int>(kOneMsInNtpFrac), NtpTime(ntp_raw + rand.Rand(-int{kOneMsInNtp}, int{kOneMsInNtp})),
static_cast<int>(kOneMsInNtpFrac)), timestamp + rand.Rand(-kRtpTicksPerMs, kRtpTicksPerMs)),
timestamp + rand.Rand(-kRtpTicksPerMs, kRtpTicksPerMs), &new_sr)); RtpToNtpEstimator::kNewMeasurement);
EXPECT_TRUE(new_sr);
int64_t estimated_ntp_ms; NtpTime estimated_ntp = estimator.Estimate(timestamp);
EXPECT_TRUE(estimator.Estimate(timestamp, &estimated_ntp_ms)); EXPECT_TRUE(estimated_ntp.Valid());
// Allow upto 2 ms of error. // 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) { const Syncable::Info& info) {
stream->latest_timestamp = info.latest_received_capture_timestamp; stream->latest_timestamp = info.latest_received_capture_timestamp;
stream->latest_receive_time_ms = info.latest_receive_time_ms; stream->latest_receive_time_ms = info.latest_receive_time_ms;
bool new_rtcp_sr = false;
return stream->rtp_to_ntp.UpdateMeasurements( return stream->rtp_to_ntp.UpdateMeasurements(
info.capture_time_ntp_secs, info.capture_time_ntp_frac, NtpTime(info.capture_time_ntp_secs, info.capture_time_ntp_frac),
info.capture_time_source_clock, &new_rtcp_sr); info.capture_time_source_clock) !=
RtpToNtpEstimator::kInvalidMeasurement;
} }
} // namespace } // namespace
@ -183,32 +183,35 @@ bool RtpStreamsSynchronizer::GetStreamSyncOffsetInMs(
return false; return false;
} }
int64_t latest_audio_ntp; NtpTime latest_audio_ntp =
if (!audio_measurement_.rtp_to_ntp.Estimate(audio_rtp_timestamp, audio_measurement_.rtp_to_ntp.Estimate(audio_rtp_timestamp);
&latest_audio_ntp)) { if (!latest_audio_ntp.Valid()) {
return false; 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; NtpTime latest_video_ntp =
if (!video_measurement_.rtp_to_ntp.Estimate(rtp_timestamp, video_measurement_.rtp_to_ntp.Estimate(rtp_timestamp);
&latest_video_ntp)) { if (!latest_video_ntp.Valid()) {
return false; return false;
} }
int64_t latest_video_ntp_ms = latest_video_ntp.ToMs();
// Current audio ntp. // Current audio ntp.
int64_t now_ms = rtc::TimeMillis(); 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. // Remove video playout delay.
int64_t time_to_render_ms = render_time_ms - now_ms; int64_t time_to_render_ms = render_time_ms - now_ms;
if (time_to_render_ms > 0) 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; *video_playout_ntp_ms = latest_video_ntp_ms;
*stream_offset_ms = latest_audio_ntp - latest_video_ntp; *stream_offset_ms = latest_audio_ntp_ms - latest_video_ntp_ms;
*estimated_freq_khz = video_measurement_.rtp_to_ntp.params()->frequency_khz; *estimated_freq_khz = video_measurement_.rtp_to_ntp.EstimatedFrequencyKhz();
return true; return true;
} }

View file

@ -35,19 +35,19 @@ bool StreamSynchronization::ComputeRelativeDelay(
const Measurements& audio_measurement, const Measurements& audio_measurement,
const Measurements& video_measurement, const Measurements& video_measurement,
int* relative_delay_ms) { int* relative_delay_ms) {
int64_t audio_last_capture_time_ms; NtpTime audio_last_capture_time =
if (!audio_measurement.rtp_to_ntp.Estimate(audio_measurement.latest_timestamp, audio_measurement.rtp_to_ntp.Estimate(audio_measurement.latest_timestamp);
&audio_last_capture_time_ms)) { if (!audio_last_capture_time.Valid()) {
return false; return false;
} }
int64_t video_last_capture_time_ms; NtpTime video_last_capture_time =
if (!video_measurement.rtp_to_ntp.Estimate(video_measurement.latest_timestamp, video_measurement.rtp_to_ntp.Estimate(video_measurement.latest_timestamp);
&video_last_capture_time_ms)) { if (!video_last_capture_time.Valid()) {
return false;
}
if (video_last_capture_time_ms < 0) {
return false; 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. // Positive diff means that video_measurement is behind audio_measurement.
*relative_delay_ms = *relative_delay_ms =
video_measurement.latest_receive_time_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); static_cast<int>(kDefaultVideoFrequency * video_clock_drift_ + 0.5);
// Generate NTP/RTP timestamp pair for both streams corresponding to RTCP. // Generate NTP/RTP timestamp pair for both streams corresponding to RTCP.
bool new_sr;
StreamSynchronization::Measurements audio; StreamSynchronization::Measurements audio;
StreamSynchronization::Measurements video; StreamSynchronization::Measurements video;
NtpTime ntp_time = clock_sender_.CurrentNtpTime(); NtpTime ntp_time = clock_sender_.CurrentNtpTime();
uint32_t rtp_timestamp = uint32_t rtp_timestamp =
clock_sender_.CurrentTime().ms() * audio_frequency / 1000; clock_sender_.CurrentTime().ms() * audio_frequency / 1000;
EXPECT_TRUE(audio.rtp_to_ntp.UpdateMeasurements( EXPECT_EQ(audio.rtp_to_ntp.UpdateMeasurements(ntp_time, rtp_timestamp),
ntp_time.seconds(), ntp_time.fractions(), rtp_timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
clock_sender_.AdvanceTimeMilliseconds(100); clock_sender_.AdvanceTimeMilliseconds(100);
clock_receiver_.AdvanceTimeMilliseconds(100); clock_receiver_.AdvanceTimeMilliseconds(100);
ntp_time = clock_sender_.CurrentNtpTime(); ntp_time = clock_sender_.CurrentNtpTime();
rtp_timestamp = clock_sender_.CurrentTime().ms() * video_frequency / 1000; rtp_timestamp = clock_sender_.CurrentTime().ms() * video_frequency / 1000;
EXPECT_TRUE(video.rtp_to_ntp.UpdateMeasurements( EXPECT_EQ(video.rtp_to_ntp.UpdateMeasurements(ntp_time, rtp_timestamp),
ntp_time.seconds(), ntp_time.fractions(), rtp_timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
clock_sender_.AdvanceTimeMilliseconds(900); clock_sender_.AdvanceTimeMilliseconds(900);
clock_receiver_.AdvanceTimeMilliseconds(900); clock_receiver_.AdvanceTimeMilliseconds(900);
ntp_time = clock_sender_.CurrentNtpTime(); ntp_time = clock_sender_.CurrentNtpTime();
rtp_timestamp = clock_sender_.CurrentTime().ms() * audio_frequency / 1000; rtp_timestamp = clock_sender_.CurrentTime().ms() * audio_frequency / 1000;
EXPECT_TRUE(audio.rtp_to_ntp.UpdateMeasurements( EXPECT_EQ(audio.rtp_to_ntp.UpdateMeasurements(ntp_time, rtp_timestamp),
ntp_time.seconds(), ntp_time.fractions(), rtp_timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
clock_sender_.AdvanceTimeMilliseconds(100); clock_sender_.AdvanceTimeMilliseconds(100);
clock_receiver_.AdvanceTimeMilliseconds(100); clock_receiver_.AdvanceTimeMilliseconds(100);
ntp_time = clock_sender_.CurrentNtpTime(); ntp_time = clock_sender_.CurrentNtpTime();
rtp_timestamp = clock_sender_.CurrentTime().ms() * video_frequency / 1000; rtp_timestamp = clock_sender_.CurrentTime().ms() * video_frequency / 1000;
EXPECT_TRUE(video.rtp_to_ntp.UpdateMeasurements( EXPECT_EQ(video.rtp_to_ntp.UpdateMeasurements(ntp_time, rtp_timestamp),
ntp_time.seconds(), ntp_time.fractions(), rtp_timestamp, &new_sr)); RtpToNtpEstimator::kNewMeasurement);
clock_sender_.AdvanceTimeMilliseconds(900); clock_sender_.AdvanceTimeMilliseconds(900);
clock_receiver_.AdvanceTimeMilliseconds(900); clock_receiver_.AdvanceTimeMilliseconds(900);