blob: 4b2e52ba5a85aaca7febdfcd30365b9e0a40e8c0 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/media/audio/audio_core/audio_clock.h"
#include <zircon/syscalls.h>
#include <cmath>
#include <iomanip>
#include "src/media/audio/audio_core/audio_clock_coefficients.h"
#include "src/media/audio/lib/clock/pid_control.h"
#include "src/media/audio/lib/timeline/timeline_function.h"
namespace media::audio {
//
// static methods
//
AudioClock AudioClock::ClientAdjustable(zx::clock clock) {
return AudioClock(std::move(clock), Source::Client, true);
}
AudioClock AudioClock::ClientFixed(zx::clock clock) {
return AudioClock(std::move(clock), Source::Client, false);
}
AudioClock AudioClock::DeviceAdjustable(zx::clock clock, uint32_t domain) {
return AudioClock(std::move(clock), Source::Device, true, domain);
}
AudioClock AudioClock::DeviceFixed(zx::clock clock, uint32_t domain) {
return AudioClock(std::move(clock), Source::Device, false, domain);
}
//
// Policy-related static methods
Mixer::Resampler AudioClock::UpgradeResamplerIfNeeded(Mixer::Resampler initial_resampler_hint,
AudioClock& source_clock,
AudioClock& dest_clock) {
if (initial_resampler_hint == Mixer::Resampler::Default) {
auto mode = AudioClock::SyncModeForClocks(source_clock, dest_clock);
// If we might need micro-SRC for synchronization, use the higher quality resampler.
if (mode == AudioClock::SyncMode::MicroSrc ||
mode == AudioClock::SyncMode::AdjustSourceHardware ||
mode == AudioClock::SyncMode::AdjustDestHardware) {
return Mixer::Resampler::WindowedSinc;
}
}
return initial_resampler_hint;
}
AudioClock::SyncMode AudioClock::SyncModeForClocks(AudioClock& source_clock,
AudioClock& dest_clock) {
if (source_clock == dest_clock) {
return SyncMode::None;
}
if (source_clock.is_device_clock() && dest_clock.is_device_clock() &&
source_clock.domain() == dest_clock.domain()) {
return SyncMode::None;
}
if (source_clock.is_adjustable() && source_clock.is_client_clock()) {
return SyncMode::AdjustSourceClock;
}
if (dest_clock.is_adjustable() && dest_clock.is_client_clock()) {
return SyncMode::AdjustDestClock;
}
if (source_clock.is_adjustable() && dest_clock.controls_device_clock()) {
return SyncMode::AdjustSourceHardware;
}
if (dest_clock.is_adjustable() && source_clock.controls_device_clock()) {
return SyncMode::AdjustDestHardware;
}
return SyncMode::MicroSrc;
}
int32_t AudioClock::ClampPpm(SyncMode sync_mode, int32_t parts_per_million) {
if (sync_mode == SyncMode::MicroSrc) {
return std::clamp<int32_t>(parts_per_million, -kMicroSrcAdjustmentPpmMax,
kMicroSrcAdjustmentPpmMax);
}
return std::clamp<int32_t>(parts_per_million, ZX_CLOCK_UPDATE_MIN_RATE_ADJUST,
ZX_CLOCK_UPDATE_MAX_RATE_ADJUST);
}
//
// Based on policy separately defined above, synchronize two clocks. Returns the ppm value of any
// micro-SRC that is needed. Error factor is a delta in frac_src frames, time is dest ref time.
int32_t AudioClock::SynchronizeClocks(AudioClock& source_clock, AudioClock& dest_clock,
zx::duration src_pos_error, zx::time monotonic_time) {
// The two clocks determine the sync mode.
auto sync_mode = SyncModeForClocks(source_clock, dest_clock);
// From the sync mode, determine which clock to tune, and the appropriate PID.
AudioClock* clock;
audio::clock::PidControl* feedback_control;
switch (sync_mode) {
case SyncMode::None:
// Same clock, or device clocks in the same clock domain. No need to adjust anything.
return 0;
case SyncMode::AdjustSourceClock:
case SyncMode::AdjustSourceHardware:
// We will adjust the source -- either its zx::clock, or the hardware it represents.
clock = &source_clock;
feedback_control = &(clock->adjustable_feedback_control_.value());
break;
case SyncMode::AdjustDestClock:
case SyncMode::AdjustDestHardware:
// We will adjust the dest -- either its zx::clock, or the hardware it represents.
clock = &dest_clock;
feedback_control = &(clock->adjustable_feedback_control_.value());
break;
case SyncMode::MicroSrc:
// No clock is adjustable; use micro-SRC. Either can do the accounting; we choose source.
clock = &source_clock;
feedback_control = &clock->microsrc_feedback_control_;
break;
}
feedback_control->TuneForError(monotonic_time.get(), src_pos_error.get());
// 'adjustment' is a zero-centric adjustment factor, relative to current rate.
auto adjustment = feedback_control->Read();
auto adjust_ppm = ClampPpm(sync_mode, round(adjustment * 1'000'000));
if (adjust_ppm != clock->adjustment_ppm_) {
FX_LOGS(TRACE) << "For sync_mode " << static_cast<uint32_t>(sync_mode)
<< ", changed from (ppm) " << std::setw(5) << clock->adjustment_ppm_ << " to "
<< std::setw(5) << adjust_ppm << "; src_pos_err " << std::setw(6)
<< src_pos_error.get();
clock->adjustment_ppm_ = adjust_ppm;
} else {
FX_LOGS(TRACE) << "For sync_mode " << static_cast<uint32_t>(sync_mode)
<< ", adjustment_ppm_ still (ppm) " << std::setw(5) << clock->adjustment_ppm_
<< "; src_pos_err " << std::setw(6) << src_pos_error.get();
}
return (sync_mode == SyncMode::MicroSrc ? adjust_ppm : 0);
}
//
// instance methods
//
AudioClock::AudioClock(zx::clock clock, Source source, bool adjustable, uint32_t domain)
: clock_(std::move(clock)),
source_(source),
is_adjustable_(adjustable),
domain_(domain),
adjustment_ppm_(0),
controls_device_clock_(false) {
zx_info_handle_basic_t info;
auto status = zx_object_get_info(clock_.get_handle(), ZX_INFO_HANDLE_BASIC, &info, sizeof(info),
nullptr, nullptr);
FX_CHECK(status == ZX_OK) << "Failed to to fetch clock rights";
const auto kRequiredRights = ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER | ZX_RIGHT_READ |
(is_adjustable_ ? ZX_RIGHT_WRITE : 0);
auto rights = info.rights & kRequiredRights;
FX_CHECK(rights == kRequiredRights)
<< "Rights: actual 0x" << std::hex << rights << ", expected 0x" << kRequiredRights;
// If we can read the clock now, we will always be able to. This check covers all error modes
// except actual adjustment (bad handle, wrong object type, no RIGHT_READ, clock not running).
zx_time_t now_unused;
FX_CHECK(clock_.read(&now_unused) == ZX_OK) << "Submitted zx::clock could not be read";
// Set feedback controls (including PID coefficients) for synchronizing this clock.
if (is_adjustable()) {
switch (source_) {
case Source::Client:
adjustable_feedback_control_ = audio::clock::PidControl(kPidFactorsAdjustClientClock);
break;
case Source::Device:
adjustable_feedback_control_ = audio::clock::PidControl(kPidFactorsAdjustHardwareClock);
break;
} // no default, to catch logic errors if an enum is added
}
microsrc_feedback_control_ = audio::clock::PidControl(kPidFactorsMicroSrc);
}
void AudioClock::set_controls_device_clock(bool should_control_device_clock) {
if (is_client_clock() && !is_adjustable_) {
controls_device_clock_ = should_control_device_clock;
}
}
// We pre-qualify the clock, so the following methods should never fail.
TimelineFunction AudioClock::ref_clock_to_clock_mono() const {
return audio::clock::SnapshotClock(clock_).take_value().reference_to_monotonic;
}
zx::time AudioClock::ReferenceTimeFromMonotonicTime(zx::time mono_time) const {
return audio::clock::ReferenceTimeFromMonotonicTime(clock_, mono_time).take_value();
}
zx::time AudioClock::MonotonicTimeFromReferenceTime(zx::time ref_time) const {
return audio::clock::MonotonicTimeFromReferenceTime(clock_, ref_time).take_value();
}
zx::clock AudioClock::DuplicateClock() const {
return audio::clock::DuplicateClock(clock_).take_value();
}
zx::time AudioClock::Read() const {
zx::time ref_now;
clock_.read(ref_now.get_address());
return ref_now;
}
void AudioClock::ResetRateAdjustment(zx::time reset_time) {
microsrc_feedback_control_.Start(reset_time.get());
if (adjustable_feedback_control_.has_value()) {
adjustable_feedback_control_->Start(reset_time.get());
}
}
} // namespace media::audio