tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit e1a8fd0ff8ed27f57d1b280dba50cd0479a0bc33
parent cce0baff53452f64206424f9533fff2acc03fa24
Author: Michael Froman <mfroman@mozilla.com>
Date:   Wed,  8 Oct 2025 18:44:16 -0500

Bug 1993083 - Vendor libwebrtc from 6eae1ac476

Upstream commit: https://webrtc.googlesource.com/src/+/6eae1ac476b62f1f83def66dbb5cab1bbb568aa2
    Add hard outlier rejection to `TimestampExtrapolator`

    This change adds field trials for a hard outlier rejection mode. It also
    adds a hard reset on RTP timestamp jumps, which might be required in the
    case of remote clock source replacements (which otherwise would be
    detected as outliers only).

    Bug: b/422493053, b/428657776
    Change-Id: I219daf7cc2550d710a2bee453f6467700ff0e340
    Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/398740
    Reviewed-by: Åsa Persson <asapersson@webrtc.org>
    Commit-Queue: Rasmus Brandt <brandtr@google.com>
    Cr-Commit-Position: refs/heads/main@{#45076}

Diffstat:
Mthird_party/libwebrtc/README.mozilla.last-vendor | 4++--
Mthird_party/libwebrtc/modules/video_coding/timing/timestamp_extrapolator.cc | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mthird_party/libwebrtc/modules/video_coding/timing/timestamp_extrapolator.h | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mthird_party/libwebrtc/modules/video_coding/timing/timestamp_extrapolator_unittest.cc | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mthird_party/libwebrtc/moz-patch-stack/s0061.patch | 4++--
5 files changed, 393 insertions(+), 21 deletions(-)

diff --git a/third_party/libwebrtc/README.mozilla.last-vendor b/third_party/libwebrtc/README.mozilla.last-vendor @@ -1,4 +1,4 @@ # ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc -libwebrtc updated from /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-08T23:43:01.892200+00:00. +libwebrtc updated from /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-08T23:44:06.475027+00:00. # base of lastest vendoring -8fc86b7e46 +6eae1ac476 diff --git a/third_party/libwebrtc/modules/video_coding/timing/timestamp_extrapolator.cc b/third_party/libwebrtc/modules/video_coding/timing/timestamp_extrapolator.cc @@ -11,6 +11,7 @@ #include "modules/video_coding/timing/timestamp_extrapolator.h" #include <algorithm> +#include <cmath> #include <cstdint> #include <cstdlib> #include <optional> @@ -32,6 +33,7 @@ constexpr double kLambda = 1; constexpr int kStartUpFilterDelayInPackets = 2; constexpr double kP00 = 1.0; constexpr double kP11 = 1e10; +constexpr double kStartResidualVariance = 3000 * 3000; } // namespace @@ -48,6 +50,39 @@ TimestampExtrapolator::Config TimestampExtrapolator::Config::ParseAndValidate( << config.hard_reset_timeout; config.hard_reset_timeout = defaults.hard_reset_timeout; } + if (config.hard_reset_rtp_timestamp_jump_threshold <= 0) { + RTC_LOG(LS_WARNING) + << "Skipping invalid hard_reset_rtp_timestamp_jump_threshold=" + << config.hard_reset_rtp_timestamp_jump_threshold; + config.hard_reset_rtp_timestamp_jump_threshold = + defaults.hard_reset_rtp_timestamp_jump_threshold; + } + if (config.outlier_rejection_startup_delay < 0) { + RTC_LOG(LS_WARNING) << "Skipping invalid outlier_rejection_startup_delay=" + << config.outlier_rejection_startup_delay; + config.outlier_rejection_startup_delay = + defaults.outlier_rejection_startup_delay; + } + if (config.outlier_rejection_max_consecutive <= 0) { + RTC_LOG(LS_WARNING) << "Skipping invalid outlier_rejection_max_consecutive=" + << config.outlier_rejection_max_consecutive; + config.outlier_rejection_max_consecutive = + defaults.outlier_rejection_max_consecutive; + } + if (config.outlier_rejection_forgetting_factor < 0 || + config.outlier_rejection_forgetting_factor >= 1) { + RTC_LOG(LS_WARNING) + << "Skipping invalid outlier_rejection_forgetting_factor=" + << config.outlier_rejection_forgetting_factor; + config.outlier_rejection_forgetting_factor = + defaults.outlier_rejection_forgetting_factor; + } + if (config.outlier_rejection_stddev.has_value() && + *config.outlier_rejection_stddev <= 0) { + RTC_LOG(LS_WARNING) << "Skipping invalid outlier_rejection_stddev=" + << *config.outlier_rejection_stddev; + config.outlier_rejection_stddev = defaults.outlier_rejection_stddev; + } if (config.alarm_threshold <= 0) { RTC_LOG(LS_WARNING) << "Skipping invalid alarm_threshold=" << config.alarm_threshold; @@ -73,6 +108,9 @@ TimestampExtrapolator::TimestampExtrapolator( start_(Timestamp::Zero()), prev_(Timestamp::Zero()), packet_count_(0), + residual_mean_(0), + residual_variance_(kStartResidualVariance), + outliers_consecutive_count_(0), detector_accumulator_pos_(0), detector_accumulator_neg_(0) { Reset(start); @@ -99,24 +137,48 @@ void TimestampExtrapolator::Reset(Timestamp start) { p_[0][1] = p_[1][0] = 0; unwrapper_ = RtpTimestampUnwrapper(); packet_count_ = 0; + // Hard outlier rejection. + residual_mean_ = 0.0; + residual_variance_ = kStartResidualVariance; + outliers_consecutive_count_ = 0; + // Soft outlier attenuation. detector_accumulator_pos_ = 0; detector_accumulator_neg_ = 0; } void TimestampExtrapolator::Update(Timestamp now, uint32_t ts90khz) { + // Hard reset based local clock timeouts. if (now - prev_ > config_.hard_reset_timeout) { - // No complete frame within the timeout. Reset(now); } else { prev_ = now; } + const int64_t unwrapped_ts90khz = unwrapper_.Unwrap(ts90khz); + + // Hard reset based on large RTP timestamp jumps. This is only enabled if + // outlier rejection is enabled, since that feature would by itself + // consistently block any long-term static offset changes due to, e.g., + // remote clock source replacements. + if (config_.OutlierRejectionEnabled() && prev_unwrapped_timestamp_) { + // Predict the expected RTP timestamp change based on elapsed wall clock + // time. + int64_t expected_rtp_diff = + static_cast<int64_t>((now - prev_).ms() * w_[0]); + int64_t actual_rtp_diff = unwrapped_ts90khz - *prev_unwrapped_timestamp_; + int64_t rtp_jump = actual_rtp_diff - expected_rtp_diff; + if (std::abs(rtp_jump) > config_.hard_reset_rtp_timestamp_jump_threshold) { + RTC_LOG(LS_WARNING) << "Large jump in RTP timestamp detected. " + "Difference between actual and expected change: " + << rtp_jump << " ticks. Resetting filter."; + Reset(now); + } + } + // Remove offset to prevent badly scaled matrices const TimeDelta offset = now - start_; double t_ms = offset.ms(); - int64_t unwrapped_ts90khz = unwrapper_.Unwrap(ts90khz); - if (!first_unwrapped_timestamp_) { // Make an initial guess of the offset, // should be almost correct since t_ms - start @@ -128,23 +190,46 @@ void TimestampExtrapolator::Update(Timestamp now, uint32_t ts90khz) { double residual = (static_cast<double>(unwrapped_ts90khz) - *first_unwrapped_timestamp_) - t_ms * w_[0] - w_[1]; + + // Hard outlier rejection: reject outliers and avoid updating the filter state + // for frames whose residuals are too large. + if (config_.OutlierRejectionEnabled() && OutlierDetection(residual)) { + ++outliers_consecutive_count_; + if (outliers_consecutive_count_ <= + config_.outlier_rejection_max_consecutive) { + // This appears to be a transient spike. Reject it. + return; + } else { + // This appears to be a persistent delay change. Force the filter to + // adapt. + SoftReset(); + } + } + // Frame is an inlier, or we have reached `outlier_rejection_max_consecutive`. + outliers_consecutive_count_ = 0; + + // Soft outlier attenuation: boost the filter's uncertainty if the integrated + // delay has changed too much. + // TODO(brandtr): Move the packet count check into DelayChangeDetection. if (DelayChangeDetection(residual) && packet_count_ >= kStartUpFilterDelayInPackets) { // Force the filter to adjust its offset parameter by changing // the uncertainties. Don't do this during startup. - if (config_.reset_full_cov_on_alarm) { - p_[0][0] = kP00; - p_[0][1] = p_[1][0] = 0; - } - p_[1][1] = kP11; + SoftReset(); } - if (prev_unwrapped_timestamp_ && - unwrapped_ts90khz < prev_unwrapped_timestamp_) { + // If hard outlier rejection is enabled, we handle large RTP timestamp jumps + // above. + if (!config_.OutlierRejectionEnabled() && + (prev_unwrapped_timestamp_ && + unwrapped_ts90khz < prev_unwrapped_timestamp_)) { // Drop reordered frames. return; } + // Update recursive least squares filter. + // TODO(b/428657776): Document better. + // T = [t(k) 1]'; // that = T'*w; // K = P*T/(lambda + T'*P*T); @@ -210,15 +295,55 @@ std::optional<Timestamp> TimestampExtrapolator::ExtrapolateLocalTime( return start_ + diff; } -bool TimestampExtrapolator::DelayChangeDetection(double error) { +void TimestampExtrapolator::SoftReset() { + if (config_.reset_full_cov_on_alarm) { + p_[0][0] = kP00; + p_[0][1] = p_[1][0] = 0; + } + p_[1][1] = kP11; +} + +bool TimestampExtrapolator::OutlierDetection(double residual) { + if (!config_.outlier_rejection_stddev.has_value()) { + return false; + } + + if (packet_count_ >= config_.outlier_rejection_startup_delay) { + double threshold = + *config_.outlier_rejection_stddev * std::sqrt(residual_variance_); + // Outlier frames trigger the alarm. + // We intentionally use a symmetric detection here, meaning that + // significantly early frames are also alarmed on. The main reason is to + // ensure a symmetric update to the running statistics below. + if (std::abs(residual - residual_mean_) > threshold) { + // Alarm. + return true; + } + } + + // Update residual statistics only with inliers. + double forgetting_factor = config_.outlier_rejection_forgetting_factor; + residual_mean_ = + forgetting_factor * residual_mean_ + (1.0 - forgetting_factor) * residual; + double residual_deviation = residual - residual_mean_; + double squared_residual_deviation = residual_deviation * residual_deviation; + residual_variance_ = + std::max(forgetting_factor * residual_variance_ + + (1.0 - forgetting_factor) * squared_residual_deviation, + 1.0); + + return false; +} + +bool TimestampExtrapolator::DelayChangeDetection(double residual) { // CUSUM detection of sudden delay changes double acc_max_error = static_cast<double>(config_.acc_max_error); - error = (error > 0) ? std::min(error, acc_max_error) - : std::max(error, -acc_max_error); + residual = (residual > 0) ? std::min(residual, acc_max_error) + : std::max(residual, -acc_max_error); detector_accumulator_pos_ = std::max( - detector_accumulator_pos_ + error - config_.acc_drift, double{0}); + detector_accumulator_pos_ + residual - config_.acc_drift, double{0}); detector_accumulator_neg_ = std::min( - detector_accumulator_neg_ + error + config_.acc_drift, double{0}); + detector_accumulator_neg_ + residual + config_.acc_drift, double{0}); if (detector_accumulator_pos_ > config_.alarm_threshold || detector_accumulator_neg_ < -config_.alarm_threshold) { // Alarm diff --git a/third_party/libwebrtc/modules/video_coding/timing/timestamp_extrapolator.h b/third_party/libwebrtc/modules/video_coding/timing/timestamp_extrapolator.h @@ -25,6 +25,22 @@ namespace webrtc { +// The `TimestampExtrapolator` is an adaptive filter that estimates the +// local clock time of incoming RTP timestamps. It's main purpose is to handle +// clock drift and clock offset, not to model network behaviour. +// +// The mechanisms applied for this are: +// * Recursive least-squares filter for estimating clock skew and clock offset. +// * Hard reset on wall clock timeout. +// * Hard reset on large incoming RTP timestamp jumps. +// * Hard outlier rejection based on the difference between the predicted and +// the actual wall clock time for an RTP timestamp. +// * Soft outlier attenuation based on the integrated (CUSUM style) difference +// between predicted and actual wall clock time for an RTP timestamp. +// +// Not all of the mechanisms are enabled by default. Use the field trial string +// to experiment with different settings. +// // Not thread safe. class TimestampExtrapolator { public: @@ -42,6 +58,13 @@ class TimestampExtrapolator { // clang-format off return StructParametersParser::Create( "hard_reset_timeout", &hard_reset_timeout, + "hard_reset_rtp_timestamp_jump_threshold", + &hard_reset_rtp_timestamp_jump_threshold, + "outlier_rejection_startup_delay", &outlier_rejection_startup_delay, + "outlier_rejection_max_consecutive", &outlier_rejection_max_consecutive, + "outlier_rejection_forgetting_factor", + &outlier_rejection_forgetting_factor, + "outlier_rejection_stddev", &outlier_rejection_stddev, "alarm_threshold", &alarm_threshold, "acc_drift", &acc_drift, "acc_max_error", &acc_max_error, @@ -49,9 +72,44 @@ class TimestampExtrapolator { // clang-format on } + bool OutlierRejectionEnabled() const { + return outlier_rejection_stddev.has_value(); + } + + // -- Hard reset behaviour -- + // If a frame has not been received within this timeout, do a full reset. TimeDelta hard_reset_timeout = TimeDelta::Seconds(10); + // A jump in the RTP timestamp of this magnitude, not accounted for by the + // passage of time, is considered a source clock replacement and will + // trigger a filter reset. 900000 ticks = 10 seconds. + // (Only enabled if hard outlier rejection is enabled.) + int hard_reset_rtp_timestamp_jump_threshold = 900'000; + + // -- Hard outlier rejection -- + + // Number of frames to wait before starting to update the residual + // statistics. 300 frames = 10 seconds@30fps. + int outlier_rejection_startup_delay = 300; + + // Number of consecutive frames that are allowed to be treated as outliers. + // If more frames than these are outliers, hard outlier rejection stops + // and soft outlier attentuation starts. + // 150 frames = 5 seconds@30fps. + int outlier_rejection_max_consecutive = 150; + + // Smoothing factor for the residual statistics. + // Half-life is log(0.5)/log(0.999) ~= 693 frames ~= 23 seconds@30fps. + double outlier_rejection_forgetting_factor = 0.999; + + // If set, will reject outliers based on this number of standard deviations + // of the filtered residuals. + // Setting this field to non-nullopt enables hard outlier rejection. + std::optional<double> outlier_rejection_stddev = std::nullopt; + + // -- Soft outlier attenuation -- + // Alarm on sudden delay change if the (filtered; see below) accumulated // residuals are larger than this number of RTP ticks. After the // startup period, an alarm will result in a full or partial reset of the @@ -74,15 +132,21 @@ class TimestampExtrapolator { TimestampExtrapolator(Timestamp start, const FieldTrialsView& field_trials); ~TimestampExtrapolator(); + + // Update the filter with a new incoming local timestamp/RTP timestamp pair. void Update(Timestamp now, uint32_t ts90khz); + + // Return the expected local timestamp for an RTP timestamp. std::optional<Timestamp> ExtrapolateLocalTime(uint32_t timestamp90khz) const; + void Reset(Timestamp start); Config GetConfigForTest() const { return config_; } private: - void CheckForWrapArounds(uint32_t ts90khz); - bool DelayChangeDetection(double error); + void SoftReset(); + bool OutlierDetection(double residual); + bool DelayChangeDetection(double residual); const Config config_; @@ -94,6 +158,13 @@ class TimestampExtrapolator { RtpTimestampUnwrapper unwrapper_; std::optional<int64_t> prev_unwrapped_timestamp_; int packet_count_; + + // Running residual statistics for the hard outlier rejection. + double residual_mean_; + double residual_variance_; + int outliers_consecutive_count_; + + // Integrated residual statistics for the soft outlier attenuation. double detector_accumulator_pos_; double detector_accumulator_neg_; }; diff --git a/third_party/libwebrtc/modules/video_coding/timing/timestamp_extrapolator_unittest.cc b/third_party/libwebrtc/modules/video_coding/timing/timestamp_extrapolator_unittest.cc @@ -340,6 +340,11 @@ TEST(TimestampExtrapolatorTest, SetsValidConfig) { clock.CurrentTime(), CreateTestFieldTrials( "WebRTC-TimestampExtrapolatorConfig/" "hard_reset_timeout:1s," + "hard_reset_rtp_timestamp_jump_threshold:45000," + "outlier_rejection_startup_delay:123," + "outlier_rejection_max_consecutive:456," + "outlier_rejection_forgetting_factor:0.987," + "outlier_rejection_stddev:3.5," "alarm_threshold:123," "acc_drift:456," "acc_max_error:789," @@ -347,7 +352,13 @@ TEST(TimestampExtrapolatorTest, SetsValidConfig) { // clang-format on TimestampExtrapolator::Config config = ts_extrapolator.GetConfigForTest(); + EXPECT_TRUE(config.OutlierRejectionEnabled()); EXPECT_EQ(config.hard_reset_timeout, TimeDelta::Seconds(1)); + EXPECT_EQ(config.hard_reset_rtp_timestamp_jump_threshold, 45000); + EXPECT_EQ(config.outlier_rejection_startup_delay, 123); + EXPECT_EQ(config.outlier_rejection_max_consecutive, 456); + EXPECT_EQ(config.outlier_rejection_forgetting_factor, 0.987); + EXPECT_EQ(config.outlier_rejection_stddev, 3.5); EXPECT_EQ(config.alarm_threshold, 123); EXPECT_EQ(config.acc_drift, 456); EXPECT_EQ(config.acc_max_error, 789); @@ -361,16 +372,181 @@ TEST(TimestampExtrapolatorTest, DoesNotSetInvalidConfig) { clock.CurrentTime(), CreateTestFieldTrials( "WebRTC-TimestampExtrapolatorConfig/" "hard_reset_timeout:-1s," + "hard_reset_rtp_timestamp_jump_threshold:-1," + "outlier_rejection_startup_delay:-1," + "outlier_rejection_max_consecutive:0," + "outlier_rejection_forgetting_factor:1.1," + "outlier_rejection_stddev:-1," "alarm_threshold:-123," "acc_drift:-456," "acc_max_error:-789/")); // clang-format on TimestampExtrapolator::Config config = ts_extrapolator.GetConfigForTest(); + EXPECT_FALSE(config.OutlierRejectionEnabled()); EXPECT_EQ(config.hard_reset_timeout, TimeDelta::Seconds(10)); + EXPECT_EQ(config.hard_reset_rtp_timestamp_jump_threshold, 900000); + EXPECT_EQ(config.outlier_rejection_startup_delay, 300); + EXPECT_EQ(config.outlier_rejection_max_consecutive, 150); + EXPECT_EQ(config.outlier_rejection_forgetting_factor, 0.999); + EXPECT_FALSE(config.outlier_rejection_stddev.has_value()); EXPECT_EQ(config.alarm_threshold, 60000); EXPECT_EQ(config.acc_drift, 6600); EXPECT_EQ(config.acc_max_error, 7000); } +TEST(TimestampExtrapolatorTest, ExtrapolationNotAffectedByRtpTimestampJump) { + SimulatedClock clock(Timestamp::Millis(1337)); + TimestampExtrapolator extrapolator( + clock.CurrentTime(), + CreateTestFieldTrials("WebRTC-TimestampExtrapolatorConfig/" + "outlier_rejection_stddev:3,hard_reset_rtp_" + "timestamp_jump_threshold:900000/")); + + // Stabilize filter. + uint32_t rtp = 0; + for (int i = 0; i < 2000; ++i) { + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + } + + // Last frame before jump is expected on time. + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime()); + + // Next frame arrives on time, but with a 20 second RTP timestamp jump. + rtp += 2 * 900000; // 20 seconds. + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime()); + + // First frame after jump is expected on time. + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime()); +} + +TEST(TimestampExtrapolatorTest, ExtrapolationNotAffectedByFrameOutliers) { + SimulatedClock clock(Timestamp::Millis(1337)); + TimestampExtrapolator extrapolator( + clock.CurrentTime(), + CreateTestFieldTrials( + "WebRTC-TimestampExtrapolatorConfig/outlier_rejection_stddev:3/")); + + // Stabilize filter. + uint32_t rtp = 0; + for (int i = 0; i < 2000; ++i) { + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + } + + // Last frame before outlier arrives on time. + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime()); + + // Outlier frame arrives 1000ms late, but is expected on time. + rtp += kRtpHz / k25Fps; + Timestamp expected = clock.CurrentTime() + k25FpsDelay; + clock.AdvanceTime(TimeDelta::Millis(1000)); + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), expected); + + // Congested frames arrive back-to-back, but are expected on time. + for (int i = 0; i < 24; ++i) { + rtp += kRtpHz / k25Fps; + expected += k25FpsDelay; + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), expected); + } + + // Regular frame after outliers arrives on time. + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime()); +} + +TEST(TimestampExtrapolatorTest, + ExtrapolationAffectedByFrameOutliersAfterRejectionPeriod) { + SimulatedClock clock(Timestamp::Millis(1337)); + TimestampExtrapolator extrapolator( + clock.CurrentTime(), + CreateTestFieldTrials( + "WebRTC-TimestampExtrapolatorConfig/" + "outlier_rejection_stddev:3,outlier_rejection_max_consecutive:20/")); + + // Stabilize filter. + uint32_t rtp = 0; + for (int i = 0; i < 2000; ++i) { + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + } + + // Last frame before outlier arrives on time. + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime()); + + // Outlier frame arrives 1000ms late, but is expected on time. + rtp += kRtpHz / k25Fps; + Timestamp expected = clock.CurrentTime() + k25FpsDelay; + clock.AdvanceTime(TimeDelta::Millis(1000)); + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), expected); + + // Congested frames arrive back-to-back. The first 19 are expected on time. + for (int i = 0; i < 19; ++i) { + rtp += kRtpHz / k25Fps; + expected += k25FpsDelay; + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), expected); + } + + // After the 20 consecutive outlier frames, the filter soft resets and starts + // expecting frames on the new baseline, which is partially congested. + rtp += kRtpHz / k25Fps; + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime()); + for (int i = 0; i < 4; ++i) { + rtp += kRtpHz / k25Fps; + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), + clock.CurrentTime() + (i + 1) * k25FpsDelay); + } + + // Now we have caught up with realtime, but since the soft reset happened + // 4 frames too early, the new baseline is 4 * 1000/25 = 160ms off. + for (int i = 0; i < 10; ++i) { + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), + clock.CurrentTime() + 4 * k25FpsDelay); + } + + // Let the filter stabilize at a realtime rate again. + for (int i = 0; i < 2000; ++i) { + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + } + + // After the stabilization, the 160ms congestion offset has been canceled. + for (int i = 0; i < 10; ++i) { + rtp += kRtpHz / k25Fps; + clock.AdvanceTime(k25FpsDelay); + extrapolator.Update(clock.CurrentTime(), rtp); + EXPECT_EQ(extrapolator.ExtrapolateLocalTime(rtp), clock.CurrentTime()); + } +} + } // namespace webrtc diff --git a/third_party/libwebrtc/moz-patch-stack/s0061.patch b/third_party/libwebrtc/moz-patch-stack/s0061.patch @@ -10,10 +10,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/c5df7f40392464ffc 1 file changed, 1 insertion(+) diff --git a/modules/video_coding/timing/timestamp_extrapolator.cc b/modules/video_coding/timing/timestamp_extrapolator.cc -index 6f1be747eb..d46e961670 100644 +index 776d984dc2..177a382d6b 100644 --- a/modules/video_coding/timing/timestamp_extrapolator.cc +++ b/modules/video_coding/timing/timestamp_extrapolator.cc -@@ -179,6 +179,7 @@ void TimestampExtrapolator::Update(Timestamp now, uint32_t ts90khz) { +@@ -264,6 +264,7 @@ void TimestampExtrapolator::Update(Timestamp now, uint32_t ts90khz) { std::optional<Timestamp> TimestampExtrapolator::ExtrapolateLocalTime( uint32_t timestamp90khz) const { int64_t unwrapped_ts90khz = unwrapper_.PeekUnwrap(timestamp90khz);