blob: 816240de8967719b1957669b381bf814376c8b54 [file] [log] [blame]
// Copyright 2019 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 <fbl/algorithm.h>
#include "src/media/audio/audio_core/audio_device_manager.h"
#include "src/media/audio/audio_core/audio_driver.h"
#include "src/media/audio/audio_core/testing/audio_clock_helper.h"
#include "src/media/audio/audio_core/testing/fake_audio_device.h"
#include "src/media/audio/audio_core/testing/fake_audio_driver.h"
#include "src/media/audio/audio_core/testing/threading_model_fixture.h"
#include "src/media/audio/lib/clock/testing/clock_test.h"
namespace media::audio {
namespace {
// These tests are templated to run on both driver types
typedef ::testing::Types<testing::FakeAudioDriverV1, testing::FakeAudioDriverV2> DriverTypes;
// Enable gtest to pretty-print the driver type name
class DriverTypeNames {
public:
template <typename T>
static std::string GetName(int) {
if constexpr (std::is_same<T, testing::FakeAudioDriverV1>()) {
return "AudioDriverV1";
}
if constexpr (std::is_same<T, testing::FakeAudioDriverV2>()) {
return "AudioDriverV2";
}
}
};
TYPED_TEST_SUITE(AudioDriverClockTest, DriverTypes, DriverTypeNames);
// Configuration consts for these tests
const Format kFormat =
Format::Create<fuchsia::media::AudioSampleFormat::SIGNED_16>(2, 48000).value();
const auto kBytesPerFrame = kFormat.bytes_per_frame();
constexpr auto kRingBufferDuration = zx::msec(200);
constexpr size_t kRingBufferFrames = 9600; // 200 msec at 48k
const size_t kHalfRingBufferBytes = kRingBufferFrames * kBytesPerFrame / 2;
constexpr auto kStartTime = zx::time(0) + zx::msec(500);
constexpr auto kNotificationDuration = zx::msec(100);
constexpr uint32_t kNonMonotonicDomain = 42;
// Test class to verify AudioDriver's clock-related aspects (domain, notifications, clock recovery).
template <typename T>
class AudioDriverClockTest : public testing::ThreadingModelFixture {
protected:
// Initialize our remote driver after configuring its clock domain; retrieve basic driver info
void CreateDrivers(uint32_t clock_domain) {
driver_ = CreateAudioDriver<T>();
zx::channel for_remote, for_local;
EXPECT_EQ(ZX_OK, zx::channel::create(0, &for_remote, &for_local));
remote_driver_ = std::make_unique<T>(std::move(for_remote), dispatcher());
remote_driver_->set_clock_domain(clock_domain);
ASSERT_EQ(ZX_OK, driver_->Init(std::move(for_local)));
mapped_ring_buffer_ = remote_driver_->CreateRingBuffer(kRingBufferFrames * kBytesPerFrame);
remote_driver_->Start();
RunLoopUntilIdle();
ASSERT_EQ(driver_->GetDriverInfo(), ZX_OK);
RunLoopUntilIdle();
ASSERT_TRUE(device_->driver_info_fetched());
}
// Configure the driver, including establishment of format and ring buffer. Then start the ring
// buffer after advancing to the given time (so that ref_start_time is correct)
void ConfigureAndStartDriver() {
ASSERT_EQ(driver_->Configure(kFormat, kRingBufferDuration), ZX_OK);
RunLoopUntilIdle();
ASSERT_TRUE(device_->driver_config_complete());
RunLoopUntil(kStartTime);
ASSERT_EQ(driver_->Start(), ZX_OK);
RunLoopUntilIdle();
ASSERT_TRUE(device_->driver_start_complete());
EXPECT_TRUE(remote_driver_->is_running());
EXPECT_GT(remote_driver_->mono_start_time(), zx::time(0));
}
// Validation functions reused for parameterized testing
void ValidateClockDomainSet(uint32_t clock_domain) {
CreateDrivers(clock_domain);
EXPECT_EQ(driver_->clock_domain(), clock_domain);
}
// After GetDriverInfo, the clock should already be available and advancing at monotonic rate,
// regardless of its clock domain (thus regardless of whether it might subsequently diverge).
// Rate-adjustments occur based on position notifications, emitted after ring buffer is started.
void ValidateClockAdvancesAtClockMonotonicRate(uint32_t clock_domain) {
CreateDrivers(clock_domain);
audio_clock_helper::VerifyAdvances(driver_->reference_clock());
audio_clock_helper::VerifyIsSystemMonotonic(driver_->reference_clock());
}
// Verify that AudioDriver correctly uses driver position notifications to rate-adjust
// its AudioClock -- but only if the device is in a non-MONOTONIC domain.
void ValidateNotificationsTuneDriverClock(uint32_t clock_domain, uint32_t notification_position) {
CreateDrivers(clock_domain);
ConfigureAndStartDriver();
// Trigger the remote driver to emit an initial position notification.
remote_driver_->SendPositionNotification(remote_driver_->mono_start_time(), 0);
// If MONOTONIC, no position notifications should be delivered.
auto mono_to_ref = driver_->reference_clock().ref_clock_to_clock_mono().Inverse();
// First notification won't lead to adustment: clock still tracks MONOTONIC (numer == denom).
EXPECT_EQ(mono_to_ref.subject_delta(), mono_to_ref.reference_delta());
// Trigger a position notification. These values may indicate a rate divergence from MONOTONIC.
remote_driver_->SendPositionNotification(
remote_driver_->mono_start_time() + kNotificationDuration, notification_position);
RunLoopUntilIdle();
// When we return, if the driver zx::clock has been tuned, then the clock rate numer != denom.
}
// the actual object under test
std::unique_ptr<AudioDriver> driver_;
// simulates channel messages from the actual driver instance
std::unique_ptr<T> remote_driver_;
std::shared_ptr<testing::FakeAudioOutput> device_{testing::FakeAudioOutput::Create(
&threading_model(), &context().device_manager(), &context().link_matrix())};
fzl::VmoMapper mapped_ring_buffer_;
private:
template <typename U>
std::unique_ptr<AudioDriver> CreateAudioDriver() {}
template <>
std::unique_ptr<AudioDriver> CreateAudioDriver<testing::FakeAudioDriverV1>() {
return std::make_unique<AudioDriverV1>(device_.get(), [](zx::duration) {});
}
template <>
std::unique_ptr<AudioDriver> CreateAudioDriver<testing::FakeAudioDriverV2>() {
return std::make_unique<AudioDriverV2>(device_.get(), [](zx::duration) {});
}
};
// AudioDriver correctly retrieves and caches the clock domain provided by the driver
TYPED_TEST(AudioDriverClockTest, MonotonicClockDomain) {
this->ValidateClockDomainSet(AudioClock::kMonotonicDomain);
}
// AudioDriver correctly retrieves and caches the clock domain provided by the driver
TYPED_TEST(AudioDriverClockTest, NonMonotonicClockDomain) {
this->ValidateClockDomainSet(kNonMonotonicDomain);
}
// For devices in the MONOTONIC domain, the clock is available after GetDriverInfo.
TYPED_TEST(AudioDriverClockTest, DefaultRefClockAdvancesAtMonoRate) {
this->ValidateClockAdvancesAtClockMonotonicRate(AudioClock::kMonotonicDomain);
}
// We model clocks in a non-MONOTONIC domain as running at the MONOTONIC rate, until we start
// receiving the position notifications from which we recover its actual rate.
TYPED_TEST(AudioDriverClockTest, NonMonoClockAdvancesAtMonoRate) {
this->ValidateClockAdvancesAtClockMonotonicRate(kNonMonotonicDomain);
}
// Given notifications suggesting a device runs at monotonic rate, its clock should not be adjusted
TYPED_TEST(AudioDriverClockTest, NotificationsAdjustClockRateSame) {
this->ValidateNotificationsTuneDriverClock(kNonMonotonicDomain, kHalfRingBufferBytes);
auto mono_to_ref = this->driver_->reference_clock().ref_clock_to_clock_mono().Inverse();
EXPECT_EQ(mono_to_ref.subject_delta(), mono_to_ref.reference_delta());
}
// Given notifications suggesting a device runs slow, its clock should be rate-adjusted downward
TYPED_TEST(AudioDriverClockTest, NotificationsAdjustClockRateDown) {
// We should be at the ring buffer's halfway point, but we are not quite there yet
this->ValidateNotificationsTuneDriverClock(kNonMonotonicDomain, kHalfRingBufferBytes * 0.95);
auto mono_to_ref = this->driver_->reference_clock().ref_clock_to_clock_mono().Inverse();
EXPECT_LT(mono_to_ref.subject_delta(), mono_to_ref.reference_delta());
}
// Given notifications suggesting a device runs fast, its clock should be rate-adjusted upward
TYPED_TEST(AudioDriverClockTest, NotificationsAdjustClockRateUp) {
// We should be at the ring buffer's halfway point, but we are a little farther along
this->ValidateNotificationsTuneDriverClock(kNonMonotonicDomain, kHalfRingBufferBytes * 1.05);
auto mono_to_ref = this->driver_->reference_clock().ref_clock_to_clock_mono().Inverse();
EXPECT_GT(mono_to_ref.subject_delta(), mono_to_ref.reference_delta());
}
// In the MONOTONIC domain, no position notifications are sent; thus no rate-adjustment can occur.
// These values suggest the device runs fast, so if a notification is sent, the rate will change.
TYPED_TEST(AudioDriverClockTest, NotificationsDontAdjustMonotonicDomain) {
this->ValidateNotificationsTuneDriverClock(AudioClock::kMonotonicDomain,
kHalfRingBufferBytes * 1.05);
auto mono_to_ref = this->driver_->reference_clock().ref_clock_to_clock_mono().Inverse();
EXPECT_EQ(mono_to_ref.subject_delta(), mono_to_ref.reference_delta());
}
} // namespace
} // namespace media::audio