blob: 90cd10cd9011969ef10268604f18d1fdb7e1c015 [file] [log] [blame]
// 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