blob: 64b0c5d63e45cd35ecf474fbc9123b0c626135ca [file] [log] [blame]
// Copyright 2018 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 <fuchsia/media/cpp/fidl.h>
#include <lib/gtest/real_loop_fixture.h>
#include <cmath>
#include "garnet/bin/media/audio_core/test/audio_fidl_tests_shared.h"
#include "lib/component/cpp/environment_services_helper.h"
#include "lib/fxl/logging.h"
namespace media::audio::test {
//
// AudioBase
//
class AudioBase : public gtest::RealLoopFixture {
protected:
void SetUp() override;
void TearDown() override;
bool ReceiveNoDisconnectCallback();
std::shared_ptr<component::Services> environment_services_;
fuchsia::media::AudioPtr audio_;
fuchsia::media::AudioRendererPtr audio_renderer_;
fuchsia::media::AudioCapturerPtr audio_capturer_;
bool error_occurred_ = false;
};
//
// AudioTest
//
class AudioTest : public AudioBase {
protected:
void SetUp() override;
};
//
// SystemGainMuteTest class
//
class SystemGainMuteTest : public AudioBase {
protected:
void SetUp() override;
void PresetSystemGainMute();
void SetSystemGain(float gain_db);
void SetSystemMute(bool mute);
bool ReceiveGainCallback(float gain_db, bool mute);
bool ReceiveNoGainCallback();
float received_gain_db_;
bool received_mute_;
bool received_gain_callback_ = false;
};
//
// AudioBase implementation
//
// Connect to Audio interface and set an error handler
void AudioBase::SetUp() {
::gtest::RealLoopFixture::SetUp();
environment_services_ = component::GetEnvironmentServices();
environment_services_->ConnectToService(audio_.NewRequest());
audio_.set_error_handler(
[this](zx_status_t error) { error_occurred_ = true; });
}
void AudioBase::TearDown() {
ASSERT_FALSE(error_occurred_);
::gtest::RealLoopFixture::TearDown();
}
// Expecting NOT to receive a disconnect. Wait, then check for errors.
bool AudioBase::ReceiveNoDisconnectCallback() {
bool timed_out = !RunLoopWithTimeoutOrUntil(
[this]() { return error_occurred_; }, kDurationTimeoutExpected);
EXPECT_FALSE(error_occurred_);
EXPECT_TRUE(timed_out) << kNoTimeoutErr;
return !error_occurred_ && timed_out;
}
//
// AudioTest implementation
//
void AudioTest::SetUp() {
AudioBase::SetUp();
ASSERT_TRUE(RunLoopWithTimeout(kDurationTimeoutExpected)) << kConnectionErr;
ASSERT_TRUE(audio_.is_bound());
}
//
// SystemGainMuteTest implementation
//
// Register for notification of SystemGainMute changes; receive initial values
// and set the system to a known baseline for gain/mute testing.
void SystemGainMuteTest::SetUp() {
AudioBase::SetUp();
audio_.events().SystemGainMuteChanged = [this](float gain_db, bool muted) {
received_gain_db_ = gain_db;
received_mute_ = muted;
received_gain_callback_ = true;
};
// When clients connects to Audio, the system enqueues an action to send the
// newly-connected client a callback with the systemwide Gain|Mute settings.
// The system executes this action after the client's currently executing task
// completes. This means that if a client establishes a connection and then
// registers a SystemGainMuteChanged callback BEFORE returning, this client
// will subsequently (once the system gets a chance to run) receive an initial
// notification of Gain|Mute settings at the time of connection. Conversely,
// if a client DOES return before registering, even after subsequently
// registering for the event the client has no way of learning the current
// Gain|Mute settings until they are changed. Wait for this callback now.
bool timed_out = !RunLoopWithTimeoutOrUntil(
[this]() { return error_occurred_ || received_gain_callback_; },
kDurationResponseExpected, kDurationGranularity);
ASSERT_TRUE(audio_.is_bound());
// Bail before the actual test cases, if we have no connection to service.
ASSERT_FALSE(error_occurred_) << kConnectionErr;
ASSERT_FALSE(timed_out) << kTimeoutErr;
ASSERT_TRUE(received_gain_callback_);
PresetSystemGainMute();
}
// Put system into a known state (unity-gain unmuted), only changing if needed.
void SystemGainMuteTest::PresetSystemGainMute() {
if (received_gain_db_ != kUnityGainDb) {
SetSystemGain(kUnityGainDb);
EXPECT_TRUE(ReceiveGainCallback(kUnityGainDb, received_mute_));
}
if (received_mute_) {
SetSystemMute(false);
EXPECT_TRUE(ReceiveGainCallback(kUnityGainDb, false));
}
// Once these callbacks arrive, we are primed and ready to test gain|mute.
}
// Set Gain, first resetting state so error can be detected.
void SystemGainMuteTest::SetSystemGain(float gain_db) {
received_gain_callback_ = false;
audio_->SetSystemGain(gain_db);
}
// Set Mute, first resetting state variable so error can be detected.
void SystemGainMuteTest::SetSystemMute(bool mute) {
received_gain_callback_ = false;
audio_->SetSystemMute(mute);
}
// Expecting to receive a callback, wait for it and check for errors.
bool SystemGainMuteTest::ReceiveGainCallback(float gain_db, bool mute) {
bool timed_out = !RunLoopWithTimeoutOrUntil(
[this, &gain_db, &mute]() {
return error_occurred_ ||
(received_gain_db_ == gain_db && received_mute_ == mute);
},
kDurationResponseExpected, kDurationGranularity);
EXPECT_FALSE(error_occurred_) << kConnectionErr;
EXPECT_FALSE(timed_out) << kTimeoutErr;
EXPECT_TRUE(received_gain_callback_);
EXPECT_EQ(received_gain_db_, gain_db);
EXPECT_EQ(received_mute_, mute);
bool return_val = !error_occurred_ && !timed_out && received_gain_callback_ &&
(received_gain_db_ == gain_db) && (received_mute_ == mute);
received_gain_callback_ = false;
return return_val;
}
// Expecting to NOT receive a callback, wait for it and check for errors.
bool SystemGainMuteTest::ReceiveNoGainCallback() {
bool return_val = ReceiveNoDisconnectCallback();
EXPECT_FALSE(received_gain_callback_);
return_val = return_val && (!received_gain_callback_);
received_gain_callback_ = false;
return return_val;
}
//
// Audio validation
// Tests of the asynchronous Audio interface.
//
// In some tests below, we run the message loop, so that any channel-disconnect
// from error -- with subsequent reset of the interface ptr -- can take effect.
//
// Test creation and interface independence of AudioRenderer.
// The following 3 conditions are validated:
// 1. Audio can create AudioRenderer.
// 2. Audio persists after created AudioRenderer is destroyed.
// 3. AudioRenderer persists after Audio is destroyed.
// 4. Asynchronous Audio can create synchronous AudioCapturers, too.
TEST_F(AudioTest, CreateAudioRenderer) {
auto err_handler = [this](zx_status_t error) { error_occurred_ = true; };
fuchsia::media::AudioPtr audio_2;
fuchsia::media::AudioPtr audio_3;
fuchsia::media::AudioPtr audio_4;
environment_services_->ConnectToService(audio_2.NewRequest());
environment_services_->ConnectToService(audio_3.NewRequest());
environment_services_->ConnectToService(audio_4.NewRequest());
audio_2.set_error_handler(err_handler);
audio_3.set_error_handler(err_handler);
audio_4.set_error_handler(err_handler);
fuchsia::media::AudioRendererPtr audio_renderer_2;
fuchsia::media::AudioRendererPtr audio_renderer_3;
fuchsia::media::AudioRendererSyncPtr audio_renderer_sync;
audio_->CreateAudioRenderer(audio_renderer_.NewRequest());
audio_2->CreateAudioRenderer(audio_renderer_2.NewRequest());
audio_3->CreateAudioRenderer(audio_renderer_3.NewRequest());
audio_4->CreateAudioRenderer(audio_renderer_sync.NewRequest());
audio_renderer_.set_error_handler(err_handler);
audio_renderer_2.set_error_handler(err_handler);
audio_renderer_3.set_error_handler(err_handler);
audio_renderer_2.Unbind();
audio_3.Unbind();
// Give time for Disconnect to occur, if it must.
ASSERT_TRUE(ReceiveNoDisconnectCallback()) << kConnectionErr;
// Validate Audio can create AudioRenderer interface.
EXPECT_TRUE(audio_.is_bound());
EXPECT_TRUE(audio_renderer_.is_bound());
// Validate that Audio2 persists without AudioRenderer2.
EXPECT_TRUE(audio_2.is_bound());
EXPECT_FALSE(audio_renderer_2.is_bound());
// Validate AudioRenderer3 persists after Audio3 is unbound.
EXPECT_FALSE(audio_3.is_bound());
EXPECT_TRUE(audio_renderer_3.is_bound());
// Validate AudioRendererSync was successfully created.
EXPECT_TRUE(audio_4.is_bound());
EXPECT_TRUE(audio_renderer_sync.is_bound());
}
// Test behavior of null or bad parameters. Both cases should cleanly fail
// without causing the Audio FIDL channel to disconnect.
TEST_F(AudioTest, CreateBadAudioRenderer) {
// Passing in a null request should have no effect.
audio_->CreateAudioRenderer(nullptr);
// Malformed request should not affect audio2
auto err_handler = [this](zx_status_t error) { error_occurred_ = true; };
fuchsia::media::AudioPtr audio_2;
environment_services_->ConnectToService(audio_2.NewRequest());
audio_2.set_error_handler(err_handler);
// Corrupt the contents of this request.
fidl::InterfaceRequest<fuchsia::media::AudioRenderer> bad_request;
uint32_t garbage = 0xF0B4783C;
memmove(&bad_request, &garbage, sizeof(uint32_t));
audio_->CreateAudioRenderer(std::move(bad_request));
// Give time for Disconnect to occur, if it must.
EXPECT_TRUE(ReceiveNoDisconnectCallback()) << kConnectionErr;
EXPECT_TRUE(audio_.is_bound());
EXPECT_TRUE(audio_2.is_bound());
// TODO(mpuryear): test cases where inner contents of request are corrupt.
}
// Test creation and interface independence of AudioCapturer.
// The following 3 conditions are validated:
// 1. Audio can create AudioCapturer.
// 2. Audio persists after created AudioCapturer is destroyed.
// 3. AudioCapturer persists after Audio is destroyed.
// 4. Asynchronous Audio can create synchronous AudioCapturers, too.
TEST_F(AudioTest, CreateAudioCapturer) {
auto err_handler = [this](zx_status_t error) { error_occurred_ = true; };
fuchsia::media::AudioPtr audio_2;
fuchsia::media::AudioPtr audio_3;
fuchsia::media::AudioPtr audio_4;
environment_services_->ConnectToService(audio_2.NewRequest());
environment_services_->ConnectToService(audio_3.NewRequest());
environment_services_->ConnectToService(audio_4.NewRequest());
audio_2.set_error_handler(err_handler);
audio_3.set_error_handler(err_handler);
audio_4.set_error_handler(err_handler);
fuchsia::media::AudioCapturerPtr audio_capturer_2;
fuchsia::media::AudioCapturerPtr audio_capturer_3;
fuchsia::media::AudioCapturerSyncPtr audio_capturer_sync;
audio_->CreateAudioCapturer(audio_capturer_.NewRequest(), false);
audio_2->CreateAudioCapturer(audio_capturer_2.NewRequest(), false);
audio_3->CreateAudioCapturer(audio_capturer_3.NewRequest(), true);
audio_4->CreateAudioCapturer(audio_capturer_sync.NewRequest(), false);
audio_capturer_.set_error_handler(err_handler);
audio_capturer_2.set_error_handler(err_handler);
audio_capturer_3.set_error_handler(err_handler);
audio_capturer_2.Unbind();
audio_3.Unbind();
// Give time for Disconnect to occur, if it must.
ASSERT_TRUE(ReceiveNoDisconnectCallback()) << kConnectionErr;
// Validate Audio can create AudioCapturer interfaces.
EXPECT_TRUE(audio_.is_bound());
EXPECT_TRUE(audio_capturer_.is_bound());
// Validate that Audio2 persists without AudioCapturer2.
EXPECT_TRUE(audio_2.is_bound());
EXPECT_FALSE(audio_capturer_2.is_bound());
// Validate AudioCapturer3 persists after Audio3 is unbound.
EXPECT_FALSE(audio_3.is_bound());
EXPECT_TRUE(audio_capturer_3.is_bound());
// Validate AudioCapturerSync was successfully created.
EXPECT_TRUE(audio_4.is_bound());
EXPECT_TRUE(audio_capturer_sync.is_bound());
}
// Test behavior of null or bad parameters. Both cases should cleanly fail
// without causing the Audio FIDL channel to disconnect.
TEST_F(AudioTest, CreateBadAudioCapturer) {
// Passing in a null request should have no effect.
audio_->CreateAudioCapturer(nullptr, false);
// Malformed request should not affect audio2
auto err_handler = [this](zx_status_t error) { error_occurred_ = true; };
fuchsia::media::AudioPtr audio_2;
environment_services_->ConnectToService(audio_2.NewRequest());
audio_2.set_error_handler(err_handler);
// Corrupt the contents of this request.
fidl::InterfaceRequest<fuchsia::media::AudioCapturer> bad_request;
uint32_t garbage = 0xF0B4783C;
memmove(&bad_request, &garbage, sizeof(uint32_t));
audio_2->CreateAudioCapturer(std::move(bad_request), true);
// Give time for Disconnect to occur, if it must.
EXPECT_TRUE(ReceiveNoDisconnectCallback()) << kConnectionErr;
EXPECT_TRUE(audio_.is_bound());
EXPECT_TRUE(audio_2.is_bound());
// TODO(mpuryear): test cases where inner contents of request are corrupt.
}
// Test setting (and re-setting) the audio output routing policy.
TEST_F(AudioTest, SetRoutingPolicy) {
audio_->SetRoutingPolicy(
fuchsia::media::AudioOutputRoutingPolicy::ALL_PLUGGED_OUTPUTS);
// Setting policy again should have no effect.
audio_->SetRoutingPolicy(
fuchsia::media::AudioOutputRoutingPolicy::ALL_PLUGGED_OUTPUTS);
// Out-of-range enum should cause debug message, but no disconnect.
audio_->SetRoutingPolicy(
static_cast<fuchsia::media::AudioOutputRoutingPolicy>(-1u));
// Setting policy to different mode.
audio_->SetRoutingPolicy(
fuchsia::media::AudioOutputRoutingPolicy::LAST_PLUGGED_OUTPUT);
EXPECT_TRUE(ReceiveNoDisconnectCallback());
EXPECT_TRUE(audio_.is_bound());
}
//
// Validation of System Gain and Mute
//
// Test setting the systemwide Mute. Initial SystemMute state is false.
TEST_F(SystemGainMuteTest, SetSystemMute_Basic) {
SetSystemMute(true);
EXPECT_TRUE(ReceiveGainCallback(kUnityGainDb, true));
SetSystemMute(false);
EXPECT_TRUE(ReceiveGainCallback(kUnityGainDb, false));
}
// Test setting the systemwide Gain. Initial SystemGain state is unity.
TEST_F(SystemGainMuteTest, SetSystemGain_Basic) {
constexpr float expected_gain_db = kUnityGainDb - 13.5f;
SetSystemGain(expected_gain_db);
EXPECT_TRUE(ReceiveGainCallback(expected_gain_db, false));
SetSystemGain(kUnityGainDb);
EXPECT_TRUE(ReceiveGainCallback(kUnityGainDb, false));
}
// Test independence of systemwide Gain and Mute. Systemwide Mute should not
// affect systemwide Gain (should not become MUTED_GAIN_DB when Mute is true).
TEST_F(SystemGainMuteTest, SystemMuteDoesntAffectSystemGain) {
constexpr float expected_gain_db = kUnityGainDb - 0.75f;
SetSystemGain(expected_gain_db);
EXPECT_TRUE(ReceiveGainCallback(expected_gain_db, false));
SetSystemMute(true);
EXPECT_TRUE(ReceiveGainCallback(expected_gain_db, true));
SetSystemGain(kUnityGainDb);
EXPECT_TRUE(ReceiveGainCallback(kUnityGainDb, true));
SetSystemGain(expected_gain_db);
EXPECT_TRUE(ReceiveGainCallback(expected_gain_db, true));
SetSystemMute(false);
EXPECT_TRUE(ReceiveGainCallback(expected_gain_db, false));
SetSystemMute(true);
EXPECT_TRUE(ReceiveGainCallback(expected_gain_db, true));
}
// Test independence of systemwide Gain/Mute. System Gain should not affect
// systemwide Mute (Mute should not become true when Gain is MUTED_GAIN_DB).
TEST_F(SystemGainMuteTest, SystemGainDoesntAffectSystemMute) {
SetSystemGain(fuchsia::media::MUTED_GAIN_DB);
EXPECT_TRUE(ReceiveGainCallback(fuchsia::media::MUTED_GAIN_DB, false));
SetSystemMute(true);
EXPECT_TRUE(ReceiveGainCallback(fuchsia::media::MUTED_GAIN_DB, true));
SetSystemMute(false);
EXPECT_TRUE(ReceiveGainCallback(fuchsia::media::MUTED_GAIN_DB, false));
SetSystemMute(true);
EXPECT_TRUE(ReceiveGainCallback(fuchsia::media::MUTED_GAIN_DB, true));
constexpr float expected_gain_db = -42.0f;
SetSystemGain(expected_gain_db);
EXPECT_TRUE(ReceiveGainCallback(expected_gain_db, true));
}
// Test setting the systemwide Mute to the already-set value.
// In these cases, we should receive no mute callback (should timeout).
// Verify this with permutations that include Mute=true and Gain=MUTED_GAIN_DB.
// 'No callback if no change in Mute' should be the case REGARDLESS of Gain.
// This test relies upon Gain-Mute independence verified by previous test.
TEST_F(SystemGainMuteTest, SystemMuteNoChangeEmitsNoCallback) {
SetSystemMute(true);
EXPECT_TRUE(ReceiveGainCallback(kUnityGainDb, true));
SetSystemMute(true);
EXPECT_TRUE(ReceiveNoGainCallback());
SetSystemGain(fuchsia::media::MUTED_GAIN_DB);
EXPECT_TRUE(ReceiveGainCallback(fuchsia::media::MUTED_GAIN_DB, true));
SetSystemMute(false);
EXPECT_TRUE(ReceiveGainCallback(fuchsia::media::MUTED_GAIN_DB, false));
SetSystemMute(false);
EXPECT_TRUE(ReceiveNoGainCallback());
}
// Test setting the systemwide Gain to the already-set value.
// In these cases, we should receive no gain callback (should timeout).
// Verify this with permutations that include Mute=true and Gain=MUTED_GAIN_DB.
// 'No callback if no change in Gain' should be the case REGARDLESS of Mute.
// This test relies upon Gain-Mute independence verified by previous test.
TEST_F(SystemGainMuteTest, SystemGainNoChangeEmitsNoCallback) {
SetSystemMute(true);
EXPECT_TRUE(ReceiveGainCallback(kUnityGainDb, true));
SetSystemGain(kUnityGainDb);
EXPECT_TRUE(ReceiveNoGainCallback());
SetSystemGain(fuchsia::media::MUTED_GAIN_DB);
EXPECT_TRUE(ReceiveGainCallback(fuchsia::media::MUTED_GAIN_DB, true));
SetSystemMute(false);
EXPECT_TRUE(ReceiveGainCallback(fuchsia::media::MUTED_GAIN_DB, false));
SetSystemGain(fuchsia::media::MUTED_GAIN_DB);
EXPECT_TRUE(ReceiveNoGainCallback());
}
// Set System Gain above allowed range, after setting to low value.
// Initial state of system gain is unity, which is the maximum value.
TEST_F(SystemGainMuteTest, SystemGainTooHighIsClampedToMaximum) {
SetSystemGain(fuchsia::media::MUTED_GAIN_DB);
EXPECT_TRUE(ReceiveGainCallback(fuchsia::media::MUTED_GAIN_DB, false));
SetSystemGain(kTooHighGainDb);
EXPECT_TRUE(ReceiveGainCallback(kUnityGainDb, false));
}
// Set System Gain below allowed range. Should clamp "up" to the minimum val.
TEST_F(SystemGainMuteTest, SystemGainTooLowIsClampedToMinimum) {
SetSystemGain(kTooLowGainDb);
EXPECT_TRUE(ReceiveGainCallback(fuchsia::media::MUTED_GAIN_DB, false));
}
// Set System Gain to malformed float. Should cause no change, nor disconnect.
TEST_F(SystemGainMuteTest, SystemGainNanHasNoEffect) {
SetSystemGain(NAN);
EXPECT_TRUE(ReceiveNoGainCallback());
}
} // namespace media::audio::test