| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromecast/media/audio/rate_adjuster.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <utility> |
| |
| #include "base/check.h" |
| |
| namespace chromecast { |
| namespace media { |
| |
| namespace { |
| constexpr auto kMaxRateChangeInterval = base::Minutes(5); |
| } // namespace |
| |
| RateAdjuster::RateAdjuster(const Config& config, |
| RateChangeCallback change_clock_rate, |
| double current_clock_rate) |
| : config_(config), |
| change_clock_rate_(std::move(change_clock_rate)), |
| linear_error_(config_.linear_regression_window.InMicroseconds()), |
| current_clock_rate_(current_clock_rate) { |
| DCHECK(change_clock_rate_); |
| } |
| |
| RateAdjuster::~RateAdjuster() = default; |
| |
| void RateAdjuster::Reserve(int count) { |
| linear_error_.Reserve(count); |
| } |
| |
| void RateAdjuster::Reset() { |
| linear_error_.Reset(); |
| initialized_ = false; |
| clock_rate_start_timestamp_ = 0; |
| initial_timestamp_ = 0; |
| clock_rate_error_base_ = 0.0; |
| } |
| |
| void RateAdjuster::AddError(int64_t error, int64_t timestamp) { |
| if (!initialized_) { |
| clock_rate_start_timestamp_ = timestamp; |
| clock_rate_error_base_ = 0.0; |
| initial_timestamp_ = timestamp; |
| initialized_ = true; |
| } |
| |
| int64_t x = timestamp - initial_timestamp_; |
| |
| // Error is positive if actions are happening too late. |
| // We perform |current_clock_rate_| seconds of actions per second of actual |
| // time. We want to run a linear regression on how the error is changing over |
| // time, if we ignore the effects of any previous clock rate changes. To do |
| // this, we correct the error value to what it would have been if we had never |
| // adjusted the clock rate. |
| // In the last N seconds, if the clock rate was 1.0 we would have performed |
| // (1.0 - clock_rate) * N more seconds of actions, so the current action would |
| // have occurred that much sooner (reducing its error by that amount). We |
| // also need to take into account the previous "expected error" (due to clock |
| // rate changes) at the point when we last changed the clock rate. The |
| // expected error now is the previous expected error, plus the change due to |
| // the clock rate of (1.0 - clock_rate) * N seconds. |
| int64_t time_at_current_clock_rate = timestamp - clock_rate_start_timestamp_; |
| double correction = clock_rate_error_base_ + |
| (1.0 - current_clock_rate_) * time_at_current_clock_rate; |
| int64_t corrected_error = error - correction; |
| linear_error_.AddSample(x, corrected_error, 1.0); |
| |
| if (time_at_current_clock_rate < |
| config_.rate_change_interval.InMicroseconds()) { |
| // Don't change clock rate too frequently. |
| return; |
| } |
| |
| int64_t offset; |
| double slope; |
| double e; |
| if (!linear_error_.EstimateY(x, &offset, &e) || |
| !linear_error_.EstimateSlope(&slope, &e)) { |
| return; |
| } |
| |
| // Get the smoothed error (linear regression estimate) at the current time, |
| // translated back into actual error. |
| int64_t smoothed_error = offset + correction; |
| |
| // If slope is positive, a clock rate of 1.0 is too slow (actions are |
| // occurring progressively later than desired). We wanted to do slope*N |
| // seconds actions during N seconds than would have been done at rate 1.0. |
| // Therefore the actual clock rate should be (1.0 + slope). |
| // However, we also want to correct for any existing offset. We correct so |
| // that the error should reduce to 0 by the next rate change interval; |
| // however the rate change is capped to prevent very fast slewing. |
| double offset_correction = |
| static_cast<double>(smoothed_error) / |
| (config_.rate_change_interval.InMicroseconds() * 2); |
| if (std::abs(smoothed_error) < config_.max_ignored_current_error) { |
| // Offset is small enough that we can ignore it, but still correct a little |
| // bit to avoid bouncing in and out of the ignored region. |
| offset_correction = offset_correction / 4; |
| } |
| offset_correction = |
| std::clamp(offset_correction, -config_.max_current_error_correction, |
| config_.max_current_error_correction); |
| double new_rate = (1.0 + slope) + offset_correction; |
| |
| // Only change the clock rate if the difference between the desired rate and |
| // the current rate is larger than the minimum change. |
| if (std::fabs(new_rate - current_clock_rate_) > config_.min_rate_change || |
| time_at_current_clock_rate > kMaxRateChangeInterval.InMicroseconds()) { |
| current_clock_rate_ = |
| change_clock_rate_.Run(new_rate, slope, smoothed_error); |
| clock_rate_start_timestamp_ = timestamp; |
| clock_rate_error_base_ = correction; |
| } |
| } |
| |
| } // namespace media |
| } // namespace chromecast |