blob: a40fce35ab116f3b1354e414afee98712a9d1b0a [file] [log] [blame]
// Copyright 2022 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/lib/clock/clock_synchronizer.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
#include <string>
#include <gtest/gtest.h>
#include "src/media/audio/lib/clock/synthetic_clock_realm.h"
namespace media_audio {
namespace {
using ::media::TimelineFunction;
constexpr uint32_t kMonotonicDomain = Clock::kMonotonicDomain;
constexpr uint32_t kExternalDomain = Clock::kExternalDomain;
constexpr uint32_t kCustomDomain = 42;
// Arbitrary initial `to_clock_mono` transform used by kCustomDomain to ensure
// that clocks in this domain don't match kMonotonicDomain.
const TimelineFunction kCustomDomainInitialToMono(0, 0, 100, 101);
const std::vector<ClockSynchronizer::Mode> kAllModes{
ClockSynchronizer::Mode::WithAdjustments,
ClockSynchronizer::Mode::WithMicroSRC,
};
std::string ModeToString(ClockSynchronizer::Mode mode) {
return mode == ClockSynchronizer::Mode::WithAdjustments ? "WithAdjustments" : "WithMicroSRC";
}
void TestNoChange(std::shared_ptr<ClockSynchronizer> sync, std::shared_ptr<Clock> follower) {
sync->Reset(zx::time(0));
auto follower_to_mono_before = follower->to_clock_mono();
// In all cases where we expect no change, `follower_pos_error` should be zero.
sync->Update(zx::time(10), zx::nsec(0));
EXPECT_EQ(sync->follower_adjustment_ppm(), 0);
auto follower_to_mono_after = follower->to_clock_mono();
EXPECT_EQ(follower_to_mono_before, follower_to_mono_after);
}
TEST(ClockSynchronizerTest, SelectModeBothNotAdjustable) {
// Neither is adjustable, so we must use MicroSRC.
auto realm = SyntheticClockRealm::Create();
auto source = realm->CreateClock("source", kExternalDomain, false);
auto dest = realm->CreateClock("dest", kExternalDomain, false);
auto sync = ClockSynchronizer::SelectModeAndCreate(source, dest);
EXPECT_EQ(sync->mode(), ClockSynchronizer::Mode::WithMicroSRC);
EXPECT_EQ(sync->leader(), dest);
EXPECT_EQ(sync->follower(), source);
}
TEST(ClockSynchronizerTest, SelectModeSourceAdjustable) {
// Neither is adjustable, so we must use MicroSRC.
auto realm = SyntheticClockRealm::Create();
auto source = realm->CreateClock("source", kExternalDomain, true);
auto dest = realm->CreateClock("dest", kExternalDomain, false);
auto sync = ClockSynchronizer::SelectModeAndCreate(source, dest);
EXPECT_EQ(sync->mode(), ClockSynchronizer::Mode::WithAdjustments);
EXPECT_EQ(sync->leader(), dest);
EXPECT_EQ(sync->follower(), source);
}
TEST(ClockSynchronizerTest, SelectModeDestAdjustable) {
// Neither is adjustable, so we must use MicroSRC.
auto realm = SyntheticClockRealm::Create();
auto source = realm->CreateClock("source", kExternalDomain, false);
auto dest = realm->CreateClock("dest", kExternalDomain, true);
auto sync = ClockSynchronizer::SelectModeAndCreate(source, dest);
EXPECT_EQ(sync->mode(), ClockSynchronizer::Mode::WithAdjustments);
EXPECT_EQ(sync->leader(), source);
EXPECT_EQ(sync->follower(), dest);
}
TEST(ClockSynchronizerTest, SameClocks) {
for (auto mode : kAllModes) {
SCOPED_TRACE(ModeToString(mode));
// Same clock for the leader and the follower.
auto realm = SyntheticClockRealm::Create();
auto clock = realm->CreateClock("clock", kExternalDomain, true);
auto sync = ClockSynchronizer::Create(clock, clock, mode);
TestNoChange(sync, clock);
}
}
TEST(ClockSynchronizerTest, SameDomain) {
for (auto mode : kAllModes) {
SCOPED_TRACE(ModeToString(mode));
// These two clocks are in the same domain (hence have the same rate) but are relatively offset.
auto realm = SyntheticClockRealm::Create();
auto leader = realm->CreateClock("lead", kCustomDomain, false, TimelineFunction(2, 0, 100, 99));
auto follower =
realm->CreateClock("follow", kCustomDomain, true, TimelineFunction(1, 0, 100, 99));
auto sync = ClockSynchronizer::Create(leader, follower, mode);
TestNoChange(sync, follower);
}
}
TEST(ClockSynchronizerTest, FollowerNotAdjustedYet) {
for (auto mode : kAllModes) {
SCOPED_TRACE(ModeToString(mode));
// Leader is in the monotonic domain.
// Follower starts identical to monotonic and hasn't been updated yet.
auto realm = SyntheticClockRealm::Create();
auto leader = realm->CreateClock("lead", kMonotonicDomain, false);
auto follower = realm->CreateClock("follow", kExternalDomain, true);
auto sync = ClockSynchronizer::Create(leader, follower, mode);
TestNoChange(sync, follower);
}
}
TEST(ClockSynchronizerTest, RevertToMonotonic) {
const auto kLargeError = zx::nsec(10000);
const auto kSmallError = zx::nsec(50);
const auto kVerySmallError = zx::nsec(5);
auto realm = SyntheticClockRealm::Create();
auto follower = realm->CreateClock("follower", kExternalDomain, true);
// Do one round synchronized to a clock in kCustomDomain.
{
auto leader = realm->CreateClock("leader0", kCustomDomain, false, kCustomDomainInitialToMono);
auto sync =
ClockSynchronizer::Create(leader, follower, ClockSynchronizer::Mode::WithAdjustments);
sync->Reset(realm->now());
// This error should result in significant upward adjustment of the client clock.
realm->AdvanceBy(zx::msec(10));
sync->Update(realm->now(), kLargeError);
auto mono_to_follower_ref = follower->to_clock_mono().Inverse();
EXPECT_GT(sync->follower_adjustment_ppm(), 0);
EXPECT_GT(mono_to_follower_ref.subject_delta(), mono_to_follower_ref.reference_delta())
<< "sub_delta " << mono_to_follower_ref.subject_delta() << ", ref_delta "
<< mono_to_follower_ref.reference_delta();
}
// Now synchronize to a clock in kMonotonicDomain.
auto leader = realm->CreateClock("leader0", kMonotonicDomain, false);
auto sync = ClockSynchronizer::Create(leader, follower, ClockSynchronizer::Mode::WithAdjustments);
// Syncing now to a MONOTONIC device clock, this error is still too large for us to fine-tune the
// follower toward perfect alignment, so PID-driven tuning continues.
sync->Reset(realm->now());
realm->AdvanceBy(zx::msec(10));
sync->Update(realm->now(), kLargeError);
auto mono_to_follower_ref = follower->to_clock_mono().Inverse();
EXPECT_GT(sync->follower_adjustment_ppm(), 0);
EXPECT_GT(mono_to_follower_ref.subject_delta(), mono_to_follower_ref.reference_delta())
<< "sub_delta " << mono_to_follower_ref.subject_delta() << ", ref_delta "
<< mono_to_follower_ref.reference_delta();
// The upward clock adjustment should be MUCH MORE than just 1 ppm.
EXPECT_GT(mono_to_follower_ref.rate().Scale(1'000'000), 1'000'001u);
// Once the error is small enough, follower-clock-tuning transitions to fine-tuning of +/- 1 ppm.
realm->AdvanceBy(zx::msec(10));
sync->Update(realm->now(), kSmallError);
mono_to_follower_ref = follower->to_clock_mono().Inverse();
EXPECT_GE(mono_to_follower_ref.rate().Scale(1'000'000), 1'000'001u);
EXPECT_GE(sync->follower_adjustment_ppm(), 1);
// And once error is very close to zero, follower should reset to no rate adjustment.
realm->AdvanceBy(zx::msec(10));
sync->Update(realm->now(), kVerySmallError);
mono_to_follower_ref = follower->to_clock_mono().Inverse();
EXPECT_EQ(mono_to_follower_ref.subject_delta(), mono_to_follower_ref.reference_delta());
EXPECT_EQ(sync->follower_adjustment_ppm(), 0);
realm->AdvanceBy(zx::msec(10));
sync->Update(realm->now(), zx::nsec(0) - kVerySmallError);
mono_to_follower_ref = follower->to_clock_mono().Inverse();
EXPECT_EQ(mono_to_follower_ref.subject_delta(), mono_to_follower_ref.reference_delta());
EXPECT_EQ(sync->follower_adjustment_ppm(), 0);
}
TEST(ClockSynchronizerTest, Update) {
for (auto mode : kAllModes) {
SCOPED_TRACE(ModeToString(mode));
// Follower is initially monotonic, running faster than the leader.
auto realm = SyntheticClockRealm::Create();
auto leader =
realm->CreateClock("lead", kExternalDomain, false, TimelineFunction(10, 0, 101, 100));
auto follower = realm->CreateClock("follow", kExternalDomain, true);
auto sync = ClockSynchronizer::Create(leader, follower, mode);
sync->Reset(realm->now());
// After 100ms, the follower is 1ms ahead.
realm->AdvanceBy(zx::msec(100));
sync->Update(realm->now(), zx::msec(1));
// Since the follower clock was ahead of the leader, it should have slowed down.
if (mode == ClockSynchronizer::Mode::WithAdjustments) {
// See audio_clock_coefficients.h for an explanation of why this branch is invered.
EXPECT_GT(sync->follower_adjustment_ppm(), 0);
} else {
EXPECT_LT(sync->follower_adjustment_ppm(), 0);
}
// In MicroSRC mode, the follower's clock should never change.
auto follower_to_mono = follower->to_clock_mono();
if (mode == ClockSynchronizer::Mode::WithMicroSRC) {
EXPECT_EQ(follower_to_mono.subject_delta(), follower_to_mono.reference_delta());
} else {
EXPECT_LT(follower_to_mono.subject_delta(), follower_to_mono.reference_delta());
}
}
}
} // namespace
} // namespace media_audio