blob: d81bd9f200464cf5aacfe3842d4e6d2b1790fb81 [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.
#ifndef SRC_MEDIA_AUDIO_AUDIO_CORE_AUDIO_CLOCK_H_
#define SRC_MEDIA_AUDIO_AUDIO_CORE_AUDIO_CLOCK_H_
#include <fuchsia/hardware/audio/cpp/fidl.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/clock.h>
#include <string>
#include "src/media/audio/audio_core/mixer/mixer.h"
#include "src/media/audio/lib/clock/pid_control.h"
#include "src/media/audio/lib/clock/utils.h"
#include "src/media/audio/lib/format/frames.h"
#include "src/media/audio/lib/timeline/timeline_function.h"
namespace media::audio {
class AudioClock;
namespace audio_clock_helper {
const zx::clock& get_underlying_zx_clock(const AudioClock&);
}
class AudioClockTest;
class AudioClock {
public:
// There are two kinds of clocks: Client clocks (zx::clocks that clients read) and Device clocks
// (actual clock hardware related to an audio device).
//
// Clock rates can change at any time. Client clock rates are changed by calls to zx_clock_update.
// Device clock rates change intentionally (by writes to hardware controls) or unintentionally (if
// clock hardware drifts). If AudioCore can control a clock's rate, the clock is Adjustable;
// otherwise it is NotAdjustable.
//
// We describe clocks by a pair (Source, Adjustable). Source is one of {Client, Device}
// and Adjustable is a boolean. The default constructor creates an Invalid clock, while static
// Create methods create Client and Device clocks.
//
// Clock Synchronization
// When two clocks run at slightly different rates, we error-correct to keep them synchronized.
// This is implemented in SynchronizeClocks().
//
// Clock domains
// A clock domain represents a set of clocks that always progress at the same rate (they may
// have offsets). Adjusting a clock causes all others in the same domain to respond as one.
// By definition, an adjustable device clock cannot be in the same clock domain as the local
// monotonic clock (CLOCK_DOMAIN_MONOTONIC, defined in fuchsia.hardware.audio/stream.fidl),
// because it is not strictly rate-locked to CLOCK_MONOTONIC.
//
// Domain is distinct from adjustability: a non-adjustable clock in a non-monotonic domain might
// still drift relative to the local monotonic clock, even though it is not rate-adjustable.
// AudioCore addresses hardware clock drift like any other clock misalignment (details below).
//
// Feedback control
// With any clock adjustment, we cannot set the exact instant for that rate change. Adjustments
// might overshoot or undershoot. Thus we must track POSITION (not just rate), and eliminate error
// over time with a feedback control loop.
static constexpr uint32_t kMonotonicDomain = fuchsia::hardware::audio::CLOCK_DOMAIN_MONOTONIC;
static constexpr uint32_t kInvalidDomain = 0xFFFFFFFE;
static AudioClock ClientAdjustable(zx::clock clock);
static AudioClock ClientFixed(zx::clock clock);
static AudioClock DeviceAdjustable(zx::clock clock, uint32_t domain);
static AudioClock DeviceFixed(zx::clock clock, uint32_t domain);
static Mixer::Resampler UpgradeResamplerIfNeeded(Mixer::Resampler resampler_hint,
AudioClock& source_clock,
AudioClock& dest_clock);
// No copy
AudioClock(const AudioClock&) = delete;
AudioClock& operator=(const AudioClock&) = delete;
// Move is allowed
AudioClock(AudioClock&& moved_clock) = default;
AudioClock& operator=(AudioClock&& moved_clock) = default;
// Returns true iff both AudioClocks refer to the same underlying zx::clock.
bool operator==(const AudioClock& comparable) const {
return (audio::clock::GetKoid(clock_) == audio::clock::GetKoid(comparable.clock_));
}
bool operator!=(const AudioClock& comparable) const { return !(*this == comparable); }
bool is_client_clock() const { return (source_ == Source::Client); }
bool is_device_clock() const { return (source_ == Source::Device); }
bool is_adjustable() const { return is_adjustable_; }
uint32_t domain() const { return domain_; }
// Return a transform based on a snapshot of the underlying zx::clock
TimelineFunction ref_clock_to_clock_mono() const;
zx::time ReferenceTimeFromMonotonicTime(zx::time mono_time) const;
zx::time MonotonicTimeFromReferenceTime(zx::time ref_time) const;
zx::clock DuplicateClock() const;
zx::time Read() const;
// We synchronize audio clocks so that positions (not just rates) align, reconciling differences
// using feedback controls. Given position error at a monotonic time, we tune src_pos_error to 0.
//
// The return value is the PPM value of any micro-SRC that should subsequently be applied.
static int32_t SynchronizeClocks(AudioClock& source_clock, AudioClock& dest_clock,
zx::time monotonic_time, zx::duration src_pos_error);
// For debugging purposes, dump the sync mode and current clock/micro-src rates.
static void DisplaySyncInfo(AudioClock& source_clock, AudioClock& dest_clock);
// Clear internal running state and restart the feedback loop at the given time.
void ResetRateAdjustment(zx::time reset_time);
// Directly incorporate a position error when recovering a device clock.
int32_t TuneForError(zx::time monotonic_time, zx::duration src_pos_error);
private:
friend const zx::clock& audio_clock_helper::get_underlying_zx_clock(const AudioClock&);
friend class AudioClockTest;
enum class Source { Client, Device };
enum class SyncMode {
// If two clocks are identical or in the same clock domain, no synchronization is needed.
None = 0,
// Immediately return an adjustable clock to monotonic rate (its sync target is now monotonic)
ResetSourceClock,
ResetDestClock,
// We rate-adjust client clocks if they permit us, to minimize cost.
// We also recover clocks, from devices running in non-MONOTONIC domains.
AdjustSourceClock,
AdjustDestClock,
// If neither clock is adjustable, we error-correct by slightly adjusting the sample-rate
// conversion ratio (referred to as "micro-SRC").
MicroSrc,
};
static SyncMode SyncModeForClocks(AudioClock& source_clock, AudioClock& dest_clock);
static std::string SyncModeToString(SyncMode mode);
static constexpr int32_t kMicroSrcAdjustmentPpmMax = 2500;
AudioClock(zx::clock clock, Source source, bool adjustable)
: AudioClock(std::move(clock), source, adjustable, kInvalidDomain) {}
AudioClock(zx::clock clock, Source source, bool adjustable, uint32_t domain);
int32_t ClampPpm(int32_t parts_per_million);
void AdjustClock(int32_t rate_adjust_ppm);
zx::clock clock_;
Source source_;
bool is_adjustable_;
uint32_t domain_;
audio::clock::PidControl feedback_control_;
// State used to avoid repeated redundant zx::clock syscalls.
int32_t previous_adjustment_ppm_ = 0;
};
} // namespace media::audio
#endif // SRC_MEDIA_AUDIO_AUDIO_CORE_AUDIO_CLOCK_H_