| // Copyright 2014 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "cast/streaming/impl/clock_drift_smoother.h" | |
| #include <cmath> | |
| #include "util/chrono_helpers.h" | |
| #include "util/osp_logging.h" | |
| #include "util/saturate_cast.h" | |
| namespace openscreen::cast { | |
| namespace { | |
| constexpr Clock::time_point kNullTime = Clock::time_point::min(); | |
| } | |
| using clock_operators::operator<<; | |
| ClockDriftSmoother::ClockDriftSmoother(Clock::duration time_constant) | |
| : time_constant_(time_constant), | |
| last_update_time_(kNullTime), | |
| estimated_tick_offset_(0.0) { | |
| OSP_CHECK(time_constant_ > decltype(time_constant_)::zero()); | |
| } | |
| ClockDriftSmoother::~ClockDriftSmoother() = default; | |
| std::optional<Clock::duration> ClockDriftSmoother::Current() const { | |
| if (last_update_time_ == kNullTime) { | |
| return std::nullopt; | |
| } | |
| return Clock::duration( | |
| rounded_saturate_cast<Clock::duration::rep>(estimated_tick_offset_)); | |
| } | |
| void ClockDriftSmoother::Reset(Clock::time_point now, | |
| Clock::duration measured_offset) { | |
| OSP_CHECK_NE(now, kNullTime); | |
| last_update_time_ = now; | |
| estimated_tick_offset_ = static_cast<double>(measured_offset.count()); | |
| } | |
| void ClockDriftSmoother::Update(Clock::time_point now, | |
| Clock::duration measured_offset) { | |
| OSP_CHECK_NE(now, kNullTime); | |
| if (last_update_time_ == kNullTime) { | |
| Reset(now, measured_offset); | |
| return; | |
| } | |
| if (now < last_update_time_) { | |
| // `now` is not monotonically non-decreasing. | |
| OSP_NOTREACHED(); | |
| } | |
| const double elapsed_ticks = | |
| static_cast<double>((now - last_update_time_).count()); | |
| last_update_time_ = now; | |
| // This is a standard exponential moving average (EMA) filter. | |
| // The alpha value is calculated such that the filter has the desired time | |
| // constant. | |
| const double alpha = 1.0 - std::exp(-elapsed_ticks / time_constant_.count()); | |
| estimated_tick_offset_ = | |
| alpha * static_cast<double>(measured_offset.count()) + | |
| (1.0 - alpha) * estimated_tick_offset_; | |
| const auto current = Current(); | |
| OSP_VLOG << "Local clock is ahead of the remote clock by: measured = " | |
| << measured_offset << ", " | |
| << "filtered = " << (current ? ToString(*current) : "null") << "."; | |
| } | |
| // static | |
| constexpr std::chrono::seconds ClockDriftSmoother::kDefaultTimeConstant; | |
| } // namespace openscreen::cast |