| // 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 "garnet/bin/media/audio_core/test/audio_device_test.h" |
| |
| #include <fuchsia/media/cpp/fidl.h> |
| #include <lib/gtest/real_loop_fixture.h> |
| #include <cmath> |
| #include <cstring> |
| |
| #include "garnet/bin/media/audio_core/test/audio_tests_shared.h" |
| #include "gtest/gtest.h" |
| #include "lib/component/cpp/environment_services_helper.h" |
| #include "lib/fxl/logging.h" |
| |
| namespace media::audio::test { |
| |
| // |
| // AudioDeviceTest static variables |
| // |
| std::shared_ptr<const ::component::Services> |
| AudioDeviceTest::environment_services_; |
| |
| uint16_t AudioDeviceTest::initial_input_device_count_ = kInvalidDeviceCount; |
| uint16_t AudioDeviceTest::initial_output_device_count_ = kInvalidDeviceCount; |
| uint64_t AudioDeviceTest::initial_input_default_ = ZX_KOID_INVALID; |
| uint64_t AudioDeviceTest::initial_output_default_ = ZX_KOID_INVALID; |
| float AudioDeviceTest::initial_input_gain_db_ = NAN; |
| float AudioDeviceTest::initial_output_gain_db_ = NAN; |
| uint32_t AudioDeviceTest::initial_input_gain_flags_ = 0; |
| uint32_t AudioDeviceTest::initial_output_gain_flags_ = 0; |
| |
| // |
| // AudioDeviceTest implementation |
| // |
| void AudioDeviceTest::SetUp() { |
| ::gtest::RealLoopFixture::SetUp(); |
| |
| auto err_handler = [this](zx_status_t error) { error_occurred_ = true; }; |
| |
| environment_services_->ConnectToService(audio_dev_enum_.NewRequest()); |
| audio_dev_enum_.set_error_handler(err_handler); |
| } |
| |
| void AudioDeviceTest::TearDown() { |
| EXPECT_FALSE(error_occurred_); |
| EXPECT_TRUE(audio_dev_enum_.is_bound()); |
| |
| ::gtest::RealLoopFixture::TearDown(); |
| } |
| |
| bool AudioDeviceTest::ExpectCallback() { |
| received_callback_ = false; |
| received_device_ = kInvalidDeviceInfo; |
| received_removed_token_ = kInvalidDeviceToken; |
| received_default_token_ = received_old_token_ = kInvalidDeviceToken; |
| received_gain_token_ = kInvalidDeviceToken; |
| received_gain_info_ = kInvalidGainInfo; |
| |
| bool timed_out = !RunLoopWithTimeoutOrUntil( |
| [this]() { return error_occurred_ || received_callback_; }, |
| kDurationResponseExpected, kDurationGranularity); |
| |
| EXPECT_FALSE(error_occurred_); |
| EXPECT_TRUE(audio_dev_enum_.is_bound()); |
| |
| EXPECT_FALSE(timed_out); |
| |
| EXPECT_TRUE(received_callback_); |
| |
| bool return_val = !error_occurred_ && !timed_out; |
| |
| return return_val; |
| } |
| |
| // TODO(mpuryear): Refactor tests to eliminate "wait for nothing bad to happen". |
| bool AudioDeviceTest::ExpectTimeout() { |
| received_callback_ = false; |
| received_device_ = kInvalidDeviceInfo; |
| received_removed_token_ = kInvalidDeviceToken; |
| received_default_token_ = received_old_token_ = kInvalidDeviceToken; |
| received_gain_token_ = kInvalidDeviceToken; |
| received_gain_info_ = kInvalidGainInfo; |
| |
| bool timed_out = !RunLoopWithTimeoutOrUntil( |
| [this]() { return error_occurred_ || received_callback_; }, |
| kDurationTimeoutExpected); |
| |
| EXPECT_FALSE(error_occurred_); |
| EXPECT_TRUE(audio_dev_enum_.is_bound()); |
| |
| EXPECT_TRUE(timed_out); |
| |
| EXPECT_FALSE(received_callback_); |
| if (received_callback_) { |
| EXPECT_EQ(received_device_.token_id, kInvalidDeviceToken) |
| << "Received Add event"; |
| EXPECT_EQ(received_removed_token_, kInvalidDeviceToken) |
| << "Received Remove event"; |
| EXPECT_EQ(received_default_token_, kInvalidDeviceToken) |
| << "Received Default event"; |
| EXPECT_EQ(received_old_token_, kInvalidDeviceToken) |
| << "Received Default event"; |
| EXPECT_EQ(received_gain_token_, kInvalidDeviceToken) |
| << "Received Gain event"; |
| } |
| |
| bool return_val = !error_occurred_ && !received_callback_; |
| |
| return return_val; |
| } |
| |
| void AudioDeviceTest::SetOnDeviceAddedEvent() { |
| audio_dev_enum_.events().OnDeviceAdded = |
| [this](fuchsia::media::AudioDeviceInfo dev) { |
| received_callback_ = true; |
| received_device_ = dev; |
| }; |
| } |
| |
| void AudioDeviceTest::SetOnDeviceRemovedEvent() { |
| audio_dev_enum_.events().OnDeviceRemoved = [this](uint64_t token_id) { |
| received_callback_ = true; |
| received_removed_token_ = token_id; |
| }; |
| } |
| |
| void AudioDeviceTest::SetOnDeviceGainChangedEvent() { |
| audio_dev_enum_.events().OnDeviceGainChanged = |
| [this](uint64_t dev_token, fuchsia::media::AudioGainInfo dev_gain_info) { |
| received_callback_ = true; |
| received_gain_token_ = dev_token; |
| received_gain_info_ = dev_gain_info; |
| }; |
| } |
| |
| void AudioDeviceTest::SetOnDefaultDeviceChangedEvent() { |
| audio_dev_enum_.events().OnDefaultDeviceChanged = |
| [this](uint64_t old_default_token, uint64_t new_default_token) { |
| received_callback_ = true; |
| received_default_token_ = new_default_token; |
| received_old_token_ = old_default_token; |
| }; |
| } |
| |
| uint32_t AudioDeviceTest::GainFlagsFromBools(bool can_mute, bool cur_mute, |
| bool can_agc, bool cur_agc) { |
| return ((can_mute && cur_mute) ? fuchsia::media::AudioGainInfoFlag_Mute |
| : 0u) | |
| (can_agc ? fuchsia::media::AudioGainInfoFlag_AgcSupported : 0u) | |
| ((can_agc && cur_agc) ? fuchsia::media::AudioGainInfoFlag_AgcEnabled |
| : 0u); |
| } |
| |
| uint32_t AudioDeviceTest::SetFlagsFromBools(bool set_gain, bool set_mute, |
| bool set_agc) { |
| return (set_gain ? fuchsia::media::SetAudioGainFlag_GainValid : 0u) | |
| (set_mute ? fuchsia::media::SetAudioGainFlag_MuteValid : 0u) | |
| (set_agc ? fuchsia::media::SetAudioGainFlag_AgcValid : 0u); |
| } |
| |
| void AudioDeviceTest::RetrieveDefaultDevInfoUsingGetDevices(bool get_input) { |
| audio_dev_enum_->GetDevices( |
| [this, get_input](std::vector<fuchsia::media::AudioDeviceInfo> devices) { |
| received_callback_ = true; |
| |
| for (auto& dev : devices) { |
| if (dev.is_default && (dev.is_input == get_input)) { |
| received_device_ = dev; |
| } |
| } |
| }); |
| |
| EXPECT_TRUE(ExpectCallback()); |
| } |
| |
| bool AudioDeviceTest::RetrieveGainInfoUsingGetDevices(uint64_t token) { |
| audio_dev_enum_->GetDevices( |
| [this, token](std::vector<fuchsia::media::AudioDeviceInfo> devices) { |
| received_callback_ = true; |
| |
| for (auto& dev : devices) { |
| if (dev.token_id == token) { |
| received_gain_info_ = dev.gain_info; |
| } |
| } |
| }); |
| |
| return ExpectCallback(); |
| } |
| |
| void AudioDeviceTest::RetrieveGainInfoUsingGetDeviceGain(uint64_t token, |
| bool valid_token) { |
| audio_dev_enum_->GetDeviceGain( |
| token, |
| [this](uint64_t dev_token, fuchsia::media::AudioGainInfo dev_gain_info) { |
| received_callback_ = true; |
| received_gain_token_ = dev_token; |
| received_gain_info_ = dev_gain_info; |
| }); |
| |
| EXPECT_TRUE(ExpectCallback()); |
| EXPECT_EQ(received_gain_token_, (valid_token ? token : ZX_KOID_INVALID)); |
| } |
| |
| void AudioDeviceTest::RetrieveTokenUsingGetDefault(bool is_input) { |
| auto get_default_handler = [this](uint64_t device_token) { |
| received_callback_ = true; |
| received_default_token_ = device_token; |
| }; |
| |
| if (is_input) { |
| audio_dev_enum_->GetDefaultInputDevice(get_default_handler); |
| } else { |
| audio_dev_enum_->GetDefaultOutputDevice(get_default_handler); |
| } |
| |
| EXPECT_TRUE(ExpectCallback()); |
| } |
| |
| void AudioDeviceTest::RetrievePreExistingDevices() { |
| if (AudioDeviceTest::initial_input_device_count_ != kInvalidDeviceCount && |
| AudioDeviceTest::initial_output_device_count_ != kInvalidDeviceCount) { |
| return; |
| } |
| |
| // RunLoopWithTimeout(kDurationGranularity); |
| |
| EXPECT_FALSE(error_occurred_); |
| EXPECT_TRUE(audio_dev_enum_.is_bound()); |
| |
| audio_dev_enum_->GetDevices( |
| [this](std::vector<fuchsia::media::AudioDeviceInfo> devices) { |
| received_callback_ = true; |
| AudioDeviceTest::initial_input_device_count_ = 0; |
| AudioDeviceTest::initial_output_device_count_ = 0; |
| |
| for (auto& dev : devices) { |
| if (dev.is_input) { |
| ++AudioDeviceTest::initial_input_device_count_; |
| if (dev.is_default) { |
| AudioDeviceTest::initial_input_default_ = dev.token_id; |
| AudioDeviceTest::initial_input_gain_db_ = dev.gain_info.gain_db; |
| AudioDeviceTest::initial_input_gain_flags_ = dev.gain_info.flags; |
| } |
| } else { |
| ++AudioDeviceTest::initial_output_device_count_; |
| if (dev.is_default) { |
| AudioDeviceTest::initial_output_default_ = dev.token_id; |
| AudioDeviceTest::initial_output_gain_db_ = dev.gain_info.gain_db; |
| AudioDeviceTest::initial_output_gain_flags_ = dev.gain_info.flags; |
| } |
| } |
| } |
| }); |
| |
| EXPECT_TRUE(ExpectCallback()); |
| } |
| |
| bool AudioDeviceTest::HasPreExistingDevices() { |
| RetrievePreExistingDevices(); |
| |
| EXPECT_NE(AudioDeviceTest::initial_input_device_count_, kInvalidDeviceCount); |
| EXPECT_NE(AudioDeviceTest::initial_output_device_count_, kInvalidDeviceCount); |
| |
| return ((AudioDeviceTest::initial_input_device_count_ + |
| AudioDeviceTest::initial_output_device_count_) > 0); |
| } |
| |
| // |
| // AudioDeviceTest test cases |
| // |
| |
| // Basic validation: we don't disconnect and callback is delivered. |
| // Later tests use RetrievePreExistingDevices which further validates |
| // GetDevices(). |
| TEST_F(AudioDeviceTest, ReceivesGetDevicesCallback) { |
| audio_dev_enum_->GetDevices( |
| [this](std::vector<fuchsia::media::AudioDeviceInfo> devices) { |
| received_callback_ = true; |
| }); |
| |
| EXPECT_TRUE(ExpectCallback()); |
| } |
| |
| TEST_F(AudioDeviceTest, GetDevicesHandlesLackOfDevices) { |
| if (HasPreExistingDevices()) { |
| FXL_LOG(INFO) << "Test case requires an environment with no audio devices"; |
| return; |
| } |
| |
| uint16_t num_devs = kInvalidDeviceCount; |
| audio_dev_enum_->GetDevices( |
| [this, &num_devs](std::vector<fuchsia::media::AudioDeviceInfo> devices) { |
| received_callback_ = true; |
| num_devs = devices.size(); |
| }); |
| |
| EXPECT_TRUE(ExpectCallback()); |
| EXPECT_EQ(num_devs, 0u); |
| } |
| |
| // Validate callback is received and default is same as from GetDevices. |
| TEST_F(AudioDeviceTest, GetDefaultInputDeviceMatchesGetDevicesBaseline) { |
| RetrievePreExistingDevices(); |
| if (!AudioDeviceTest::initial_input_device_count_) { |
| FXL_LOG(INFO) << "Test case requires an environment with audio devices"; |
| return; |
| } |
| |
| RetrieveTokenUsingGetDefault(true); |
| EXPECT_EQ(received_default_token_, AudioDeviceTest::initial_input_default_); |
| } |
| |
| // Validate callback is received and default is same as from GetDevices. |
| TEST_F(AudioDeviceTest, GetDefaultOutputDeviceMatchesGetDevicesBaseline) { |
| RetrievePreExistingDevices(); |
| if (!AudioDeviceTest::initial_output_device_count_) { |
| FXL_LOG(INFO) << "Test case requires an environment with audio devices"; |
| return; |
| } |
| |
| RetrieveTokenUsingGetDefault(false); |
| EXPECT_EQ(received_default_token_, AudioDeviceTest::initial_output_default_); |
| } |
| |
| TEST_F(AudioDeviceTest, GetDefaultInputDeviceHandlesLackOfDevices) { |
| if (HasPreExistingDevices()) { |
| FXL_LOG(INFO) << "Test case requires an environment with no audio devices"; |
| return; |
| } |
| RetrieveTokenUsingGetDefault(true); |
| EXPECT_EQ(received_default_token_, ZX_KOID_INVALID); |
| } |
| |
| TEST_F(AudioDeviceTest, GetDefaultOutputDeviceHandlesLackOfDevices) { |
| if (HasPreExistingDevices()) { |
| FXL_LOG(INFO) << "Test case requires an environment with no audio devices"; |
| return; |
| } |
| RetrieveTokenUsingGetDefault(false); |
| EXPECT_EQ(received_default_token_, ZX_KOID_INVALID); |
| } |
| |
| // Using preexisting devices (if any), validate GetDeviceGain callbacks |
| // received, tokens match the request, and only valid gain_flag bits are set. |
| // TODO(mpuryear): ensure >0-device scenario |
| TEST_F(AudioDeviceTest, GetDeviceGainMatchesGetDevicesInputBaseline) { |
| if (!HasPreExistingDevices()) { |
| FXL_LOG(INFO) << "Test case requires an environment with audio devices"; |
| return; |
| } |
| |
| RetrieveGainInfoUsingGetDeviceGain(AudioDeviceTest::initial_input_default_); |
| EXPECT_EQ(received_gain_info_.gain_db, |
| AudioDeviceTest::initial_input_gain_db_); |
| EXPECT_EQ(received_gain_info_.flags, |
| AudioDeviceTest::initial_input_gain_flags_); |
| } |
| |
| // Using preexisting devices (if any), validate GetDeviceGain callbacks |
| // received, tokens match the request, and only valid gain_flag bits are set. |
| // TODO(mpuryear): ensure >0-device scenario |
| TEST_F(AudioDeviceTest, GetDeviceGainMatchesGetDevicesOutputBaseline) { |
| if (!HasPreExistingDevices()) { |
| FXL_LOG(INFO) << "Test case requires an environment with audio devices"; |
| return; |
| } |
| |
| RetrieveGainInfoUsingGetDeviceGain(AudioDeviceTest::initial_output_default_); |
| EXPECT_EQ(received_gain_info_.gain_db, |
| AudioDeviceTest::initial_output_gain_db_); |
| EXPECT_EQ(received_gain_info_.flags, |
| AudioDeviceTest::initial_output_gain_flags_); |
| } |
| |
| // Given invalid token to GetDeviceGain, callback should be received with |
| // ZX_KOID_INVALID device; FIDL interface should not disconnect. |
| TEST_F(AudioDeviceTest, GetDeviceGainHandlesNullToken) { |
| RetrieveGainInfoUsingGetDeviceGain(ZX_KOID_INVALID); |
| } |
| |
| // Given invalid token to GetDeviceGain, callback should be received with |
| // ZX_KOID_INVALID device; FIDL interface should not disconnect. |
| TEST_F(AudioDeviceTest, GetDeviceGainHandlesBadToken) { |
| RetrieveGainInfoUsingGetDeviceGain(kInvalidDeviceToken, false); |
| } |
| |
| // Given null token to GetDeviceGain, FIDL interface should not disconnect. |
| TEST_F(AudioDeviceTest, SetDeviceGainHandlesNullToken) { |
| audio_dev_enum_->SetDeviceGain(ZX_KOID_INVALID, |
| {.gain_db = 0.0f, .flags = 0u}, |
| fuchsia::media::SetAudioGainFlag_GainValid); |
| EXPECT_TRUE(ExpectTimeout()); |
| } |
| |
| // Given invalid token to SetDeviceGain, FIDL interface should not disconnect. |
| TEST_F(AudioDeviceTest, SetDeviceGainHandlesBadToken) { |
| audio_dev_enum_->SetDeviceGain(kInvalidDeviceToken, |
| {.gain_db = 0.0f, .flags = 0u}, |
| fuchsia::media::SetAudioGainFlag_GainValid); |
| EXPECT_TRUE(ExpectTimeout()); |
| } |
| |
| // Given invalid token to GetDeviceGain, callback should be received with |
| // ZX_KOID_INVALID device; FIDL interface should not disconnect. |
| TEST_F(AudioDeviceTest, OnDeviceGainChangedIgnoresSetDeviceGainNullToken) { |
| SetOnDeviceGainChangedEvent(); |
| |
| audio_dev_enum_->SetDeviceGain(ZX_KOID_INVALID, |
| {.gain_db = 0.0f, .flags = 0u}, |
| fuchsia::media::SetAudioGainFlag_GainValid); |
| EXPECT_TRUE(ExpectTimeout()); |
| } |
| |
| TEST_F(AudioDeviceTest, OnDeviceGainChangedIgnoresSetDeviceGainBadToken) { |
| SetOnDeviceGainChangedEvent(); |
| |
| audio_dev_enum_->SetDeviceGain(kInvalidDeviceToken, |
| {.gain_db = 0.0f, .flags = 0u}, |
| fuchsia::media::SetAudioGainFlag_GainValid); |
| EXPECT_TRUE(ExpectTimeout()); |
| } |
| |
| } // namespace media::audio::test |