blob: 8816800d7cdfe8ae30a8a42f6f75624508c059a8 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/mac/audio_device_listener_mac.h"
#include <CoreAudio/AudioHardware.h>
#include <memory>
#include <optional>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::Return;
namespace media {
class AudioDeviceListenerMacUnderTest final : public AudioDeviceListenerMac {
public:
AudioDeviceListenerMacUnderTest(base::RepeatingClosure listener_cb,
bool monitor_output_sample_rate_changes,
bool monitor_default_input,
bool monitor_addition_removal,
bool monitor_sources)
: AudioDeviceListenerMac(std::move(listener_cb),
monitor_output_sample_rate_changes,
monitor_default_input,
monitor_addition_removal,
monitor_sources) {}
~AudioDeviceListenerMacUnderTest() final = default;
MOCK_METHOD0(GetAllAudioDeviceIDs, std::vector<AudioObjectID>());
MOCK_METHOD1(IsOutputDevice, bool(AudioObjectID));
MOCK_METHOD2(GetDeviceSource, std::optional<uint32_t>(AudioObjectID, bool));
OSStatus AddPropertyListener(AudioObjectID inObjectID,
const AudioObjectPropertyAddress* inAddress,
AudioObjectPropertyListenerProc inListener,
void* inClientData) final {
return noErr;
}
OSStatus RemovePropertyListener(AudioObjectID inObjectID,
const AudioObjectPropertyAddress* inAddress,
AudioObjectPropertyListenerProc inListener,
void* inClientData) final {
return noErr;
}
};
class AudioDeviceListenerMacTest : public testing::Test {
public:
AudioDeviceListenerMacTest() = default;
AudioDeviceListenerMacTest(const AudioDeviceListenerMacTest&) = delete;
AudioDeviceListenerMacTest& operator=(const AudioDeviceListenerMacTest&) =
delete;
~AudioDeviceListenerMacTest() override = default;
static bool SimulateEvent(const AudioObjectPropertyAddress& address,
std::vector<void*>& contexts) {
// Include multiple addresses to ensure only a single device change event
// occurs.
const AudioObjectPropertyAddress addresses[] = {
address,
{kAudioHardwarePropertySleepingIsAllowed,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}};
for (void* context : contexts) {
OSStatus status = AudioDeviceListenerMac::SimulateEventForTesting(
kAudioObjectSystemObject, 2, addresses, context);
if (status != noErr)
return false;
}
return true;
}
static bool SimulateDeviceEvent(AudioObjectID id,
std::vector<void*>& contexts,
const AudioObjectPropertyAddress& address) {
const AudioObjectPropertyAddress addresses[] = {address};
for (void* context : contexts) {
OSStatus status = AudioDeviceListenerMac::SimulateEventForTesting(
id, 1, addresses, context);
if (status != noErr)
return false;
}
return true;
}
static bool SimulateSampleRateChange(AudioObjectID id,
std::vector<void*>& contexts) {
return SimulateDeviceEvent(
id, contexts, AudioDeviceListenerMac::kPropertyOutputSampleRateChanged);
}
static bool SimulateOutputSourceChange(AudioObjectID id,
std::vector<void*>& contexts) {
return SimulateDeviceEvent(
id, contexts, AudioDeviceListenerMac::kPropertyOutputSourceChanged);
}
static bool SimluateInputSourceChange(AudioObjectID id,
std::vector<void*>& contexts) {
return SimulateDeviceEvent(
id, contexts, AudioDeviceListenerMac::kPropertyInputSourceChanged);
}
static void CreatePropertyListeners(AudioDeviceListenerMac* device_listener) {
return device_listener->CreatePropertyListeners();
}
static std::vector<void*> GetPropertyListeners(
AudioDeviceListenerMac* device_listener) {
return device_listener->GetPropertyListenersForTesting();
}
static bool SimulateDefaultOutputDeviceChange(std::vector<void*>& contexts) {
return SimulateEvent(
AudioDeviceListenerMac::kDefaultOutputDeviceChangePropertyAddress,
contexts);
}
static bool SimulateDefaultInputDeviceChange(std::vector<void*>& contexts) {
return SimulateEvent(
AudioDeviceListenerMac::kDefaultInputDeviceChangePropertyAddress,
contexts);
}
static bool SimulateDeviceAdditionRemoval(std::vector<void*>& contexts) {
return SimulateEvent(AudioDeviceListenerMac::kDevicesPropertyAddress,
contexts);
}
MOCK_METHOD0(OnDeviceChange, void());
protected:
base::test::SingleThreadTaskEnvironment task_environment_;
std::unique_ptr<AudioDeviceListenerMac> device_listener_;
};
// Simulate a device change event and ensure we get the right callback.
TEST_F(AudioDeviceListenerMacTest, Events_DeviceMonitoring) {
auto device_listener = AudioDeviceListenerMac::Create(
base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
base::Unretained(this)),
/*monitor_output_sample_rate_changes=*/false,
/*monitor_default_input=*/true, /*monitor_addition_removal=*/true,
/*monitor_sources*/ false);
std::vector<void*> property_listeners =
GetPropertyListeners(device_listener.get());
// Default output, default input, addition-removal.
EXPECT_EQ(property_listeners.size(), 3u);
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
ASSERT_TRUE(SimulateDefaultOutputDeviceChange(property_listeners));
base::RunLoop().RunUntilIdle();
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
ASSERT_TRUE(SimulateDefaultInputDeviceChange(property_listeners));
base::RunLoop().RunUntilIdle();
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
base::RunLoop().RunUntilIdle();
device_listener.reset();
base::RunLoop().RunUntilIdle();
}
TEST_F(AudioDeviceListenerMacTest, Events_DefaultOutput) {
auto device_listener = AudioDeviceListenerMac::Create(
base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
base::Unretained(this)),
/*monitor_output_sample_rate_changes=*/false,
/*monitor_default_input=*/false, /*monitor_addition_removal=*/false,
/*monitor_sources*/ false);
std::vector<void*> property_listeners =
GetPropertyListeners(device_listener.get());
// Default output.
EXPECT_EQ(property_listeners.size(), 1u);
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
ASSERT_TRUE(SimulateDefaultOutputDeviceChange(property_listeners));
base::RunLoop().RunUntilIdle();
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnDeviceChange()).Times(0);
ASSERT_TRUE(SimulateDefaultInputDeviceChange(property_listeners));
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
base::RunLoop().RunUntilIdle();
}
TEST_F(AudioDeviceListenerMacTest, EventsNotProcessedAfterLisneterDeletion) {
auto device_listener = AudioDeviceListenerMac::Create(
base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
base::Unretained(this)),
/*monitor_output_sample_rate_changes=*/false,
/*monitor_default_input=*/true, /*monitor_addition_removal=*/true,
/*monitor_sources*/ false);
std::vector<void*> property_listeners =
GetPropertyListeners(device_listener.get());
// Default output, default input, addition-removal.
EXPECT_EQ(property_listeners.size(), 3u);
// AudioDeviceListenerMac is destroyed, but property listener destructions are
// delayed. Notifications on them should still work, but should not result
// in OnDeviceChange call.
device_listener.reset();
EXPECT_CALL(*this, OnDeviceChange()).Times(0);
ASSERT_TRUE(SimulateDefaultOutputDeviceChange(property_listeners));
ASSERT_TRUE(SimulateDefaultInputDeviceChange(property_listeners));
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
base::RunLoop().RunUntilIdle();
// Now all property listeners are destroyed.
}
TEST_F(AudioDeviceListenerMacTest, SampleRateChangeSubscription) {
auto device_listener = std::make_unique<AudioDeviceListenerMacUnderTest>(
base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
base::Unretained(this)),
/*monitor_output_sample_rate_changes=*/true,
/*monitor_default_input=*/false, /*monitor_addition_removal=*/false,
/*monitor_sources*/ false);
AudioDeviceListenerMacUnderTest& system_audio_mock = *device_listener.get();
EXPECT_CALL(system_audio_mock, GetAllAudioDeviceIDs())
.WillOnce(Return(std::vector<AudioObjectID>{1, 2, 3, 4}));
EXPECT_CALL(system_audio_mock, IsOutputDevice(1)).WillOnce(Return(true));
EXPECT_CALL(system_audio_mock, IsOutputDevice(2)).WillOnce(Return(false));
EXPECT_CALL(system_audio_mock, IsOutputDevice(3)).WillOnce(Return(true));
EXPECT_CALL(system_audio_mock, IsOutputDevice(4)).WillOnce(Return(false));
CreatePropertyListeners(device_listener.get());
std::vector<void*> property_listeners =
GetPropertyListeners(device_listener.get());
// Default output, addition-removal and two output devices
EXPECT_EQ(property_listeners.size(), 4u);
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
// Output.
SimulateSampleRateChange(1, property_listeners);
// Not output - no callback.
SimulateSampleRateChange(2, property_listeners);
device_listener.reset();
base::RunLoop().RunUntilIdle();
}
TEST_F(AudioDeviceListenerMacTest,
SampleRateChangeSubscriptionUpdatedWhenDevicesAddedRemoved) {
auto device_listener = std::make_unique<AudioDeviceListenerMacUnderTest>(
base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
base::Unretained(this)),
/*monitor_output_sample_rate_changes=*/true,
/*monitor_default_input=*/false, /*monitor_addition_removal=*/false,
/*monitor_sources*/ false);
AudioDeviceListenerMacUnderTest& system_audio_mock = *device_listener.get();
EXPECT_CALL(system_audio_mock, GetAllAudioDeviceIDs())
.WillOnce(Return(std::vector<AudioObjectID>{1}))
.WillOnce(Return(std::vector<AudioObjectID>{}))
.WillOnce(Return(std::vector<AudioObjectID>{1}))
.WillOnce(Return(std::vector<AudioObjectID>{1, 2}));
EXPECT_CALL(system_audio_mock, IsOutputDevice(1))
.Times(3)
.WillRepeatedly(Return(true));
EXPECT_CALL(system_audio_mock, IsOutputDevice(2)).WillOnce(Return(true));
// We only change the subscription, nothing notification-worthy.
EXPECT_CALL(*this, OnDeviceChange()).Times(0);
CreatePropertyListeners(device_listener.get());
std::vector<void*> property_listeners =
GetPropertyListeners(device_listener.get());
// Default output, addition-removal and one output device
EXPECT_EQ(property_listeners.size(), 3u);
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and no output device
EXPECT_EQ(property_listeners.size(), 2u);
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and one output device
EXPECT_EQ(property_listeners.size(), 3u);
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and two output device
EXPECT_EQ(property_listeners.size(), 4u);
device_listener.reset();
base::RunLoop().RunUntilIdle();
}
TEST_F(AudioDeviceListenerMacTest,
SampleRateChangeNotificationsForAddedDevices) {
auto device_listener = std::make_unique<AudioDeviceListenerMacUnderTest>(
base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
base::Unretained(this)),
/*monitor_output_sample_rate_changes=*/true,
/*monitor_default_input=*/false, /*monitor_addition_removal=*/false,
/*monitor_sources*/ false);
AudioDeviceListenerMacUnderTest& system_audio_mock = *device_listener.get();
EXPECT_CALL(system_audio_mock, GetAllAudioDeviceIDs())
.WillOnce(Return(std::vector<AudioObjectID>{}))
.WillOnce(Return(std::vector<AudioObjectID>{1}))
.WillOnce(Return(std::vector<AudioObjectID>{1, 2}));
EXPECT_CALL(system_audio_mock, IsOutputDevice(1))
.Times(2)
.WillRepeatedly(Return(true));
EXPECT_CALL(system_audio_mock, IsOutputDevice(2)).WillOnce(Return(true));
CreatePropertyListeners(device_listener.get());
std::vector<void*> property_listeners =
GetPropertyListeners(device_listener.get());
// Default output, addition-removal and none output device
EXPECT_EQ(property_listeners.size(), 2u);
{
EXPECT_CALL(*this, OnDeviceChange()).Times(0);
SimulateSampleRateChange(1, property_listeners);
SimulateSampleRateChange(2, property_listeners);
SimulateSampleRateChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
}
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and one output device
EXPECT_EQ(property_listeners.size(), 3u);
{
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
SimulateSampleRateChange(1, property_listeners);
SimulateSampleRateChange(2, property_listeners);
SimulateSampleRateChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
}
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and two output device
EXPECT_EQ(property_listeners.size(), 4u);
{
EXPECT_CALL(*this, OnDeviceChange()).Times(2);
SimulateSampleRateChange(1, property_listeners);
SimulateSampleRateChange(2, property_listeners);
SimulateSampleRateChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
}
device_listener.reset();
base::RunLoop().RunUntilIdle();
}
TEST_F(AudioDeviceListenerMacTest,
SourceChangeSubscriptionUpdatedWhenDevicesAddedRemoved) {
auto device_listener = std::make_unique<AudioDeviceListenerMacUnderTest>(
base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
base::Unretained(this)),
/*monitor_output_sample_rate_changes=*/false,
/*monitor_default_input=*/false, /*monitor_addition_removal=*/true,
/*monitor_sources*/ true);
AudioDeviceListenerMacUnderTest& system_audio_mock = *device_listener.get();
EXPECT_CALL(system_audio_mock, GetAllAudioDeviceIDs())
.WillOnce(Return(std::vector<AudioObjectID>{1}))
.WillOnce(Return(std::vector<AudioObjectID>{}))
.WillOnce(Return(std::vector<AudioObjectID>{1, 2}))
.WillOnce(Return(std::vector<AudioObjectID>{1, 3}));
// Device 1 is an input device.
EXPECT_CALL(system_audio_mock, GetDeviceSource(1, false))
.Times(3)
.WillRepeatedly(Return(std::optional<uint32_t>()));
EXPECT_CALL(system_audio_mock, GetDeviceSource(1, true))
.Times(3)
.WillRepeatedly(Return(123));
// Device 2 is an output device.
EXPECT_CALL(system_audio_mock, GetDeviceSource(2, false))
.WillOnce(Return(123));
EXPECT_CALL(system_audio_mock, GetDeviceSource(2, true))
.WillOnce(Return(std::optional<uint32_t>()));
// Device 3 is both an input and output device.
EXPECT_CALL(system_audio_mock, GetDeviceSource(3, false))
.WillOnce(Return(123));
EXPECT_CALL(system_audio_mock, GetDeviceSource(3, true))
.WillOnce(Return(123));
// We add or remove devices three times, expect a call for each of them.
EXPECT_CALL(*this, OnDeviceChange()).Times(3);
CreatePropertyListeners(device_listener.get());
std::vector<void*> property_listeners =
GetPropertyListeners(device_listener.get());
// Default output, addition-removal and one device source
EXPECT_EQ(property_listeners.size(), 3u);
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and no device source
EXPECT_EQ(property_listeners.size(), 2u);
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and two device sources
EXPECT_EQ(property_listeners.size(), 4u);
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and three device sources
EXPECT_EQ(property_listeners.size(), 5u);
device_listener.reset();
base::RunLoop().RunUntilIdle();
}
TEST_F(AudioDeviceListenerMacTest, SourceChangeNotifications) {
auto device_listener = std::make_unique<AudioDeviceListenerMacUnderTest>(
base::BindRepeating(&AudioDeviceListenerMacTest::OnDeviceChange,
base::Unretained(this)),
/*monitor_output_sample_rate_changes=*/false,
/*monitor_default_input=*/false, /*monitor_addition_removal=*/true,
/*monitor_sources*/ true);
AudioDeviceListenerMacUnderTest& system_audio_mock = *device_listener.get();
EXPECT_CALL(system_audio_mock, GetAllAudioDeviceIDs())
.WillOnce(Return(std::vector<AudioObjectID>{1}))
.WillOnce(Return(std::vector<AudioObjectID>{}))
.WillOnce(Return(std::vector<AudioObjectID>{1, 2}))
.WillOnce(Return(std::vector<AudioObjectID>{1, 3}));
// Device 1 is an input device.
EXPECT_CALL(system_audio_mock, GetDeviceSource(1, false))
.Times(3)
.WillRepeatedly(Return(std::optional<uint32_t>()));
EXPECT_CALL(system_audio_mock, GetDeviceSource(1, true))
.Times(3)
.WillRepeatedly(Return(123));
// Device 2 is an output device.
EXPECT_CALL(system_audio_mock, GetDeviceSource(2, false))
.WillOnce(Return(123));
EXPECT_CALL(system_audio_mock, GetDeviceSource(2, true))
.WillOnce(Return(std::optional<uint32_t>()));
// Device 3 is both an input and output device.
EXPECT_CALL(system_audio_mock, GetDeviceSource(3, false))
.WillOnce(Return(123));
EXPECT_CALL(system_audio_mock, GetDeviceSource(3, true))
.WillOnce(Return(123));
CreatePropertyListeners(device_listener.get());
std::vector<void*> property_listeners =
GetPropertyListeners(device_listener.get());
// Default output, addition-removal and one device source
EXPECT_EQ(property_listeners.size(), 3u);
{
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
SimluateInputSourceChange(1, property_listeners);
SimluateInputSourceChange(2, property_listeners);
SimluateInputSourceChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnDeviceChange()).Times(0);
SimulateOutputSourceChange(1, property_listeners);
SimulateOutputSourceChange(2, property_listeners);
SimulateOutputSourceChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
}
EXPECT_CALL(*this, OnDeviceChange());
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and no device source
EXPECT_EQ(property_listeners.size(), 2u);
{
EXPECT_CALL(*this, OnDeviceChange()).Times(0);
SimluateInputSourceChange(1, property_listeners);
SimluateInputSourceChange(2, property_listeners);
SimluateInputSourceChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnDeviceChange()).Times(0);
SimulateOutputSourceChange(1, property_listeners);
SimulateOutputSourceChange(2, property_listeners);
SimulateOutputSourceChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
}
EXPECT_CALL(*this, OnDeviceChange());
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and two device sources
EXPECT_EQ(property_listeners.size(), 4u);
{
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
SimluateInputSourceChange(1, property_listeners);
SimluateInputSourceChange(2, property_listeners);
SimluateInputSourceChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
SimulateOutputSourceChange(1, property_listeners);
SimulateOutputSourceChange(2, property_listeners);
SimulateOutputSourceChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
}
EXPECT_CALL(*this, OnDeviceChange());
ASSERT_TRUE(SimulateDeviceAdditionRemoval(property_listeners));
property_listeners = GetPropertyListeners(device_listener.get());
// Default output, addition-removal and three device sources
EXPECT_EQ(property_listeners.size(), 5u);
{
EXPECT_CALL(*this, OnDeviceChange()).Times(2);
SimluateInputSourceChange(1, property_listeners);
SimluateInputSourceChange(2, property_listeners);
SimluateInputSourceChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnDeviceChange()).Times(1);
SimulateOutputSourceChange(1, property_listeners);
SimulateOutputSourceChange(2, property_listeners);
SimulateOutputSourceChange(3, property_listeners);
testing::Mock::VerifyAndClearExpectations(this);
}
device_listener.reset();
base::RunLoop().RunUntilIdle();
}
} // namespace media