diff options
Diffstat (limited to 'dom/media/gtest/TestAudioDeviceEnumerator.cpp')
-rw-r--r-- | dom/media/gtest/TestAudioDeviceEnumerator.cpp | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/dom/media/gtest/TestAudioDeviceEnumerator.cpp b/dom/media/gtest/TestAudioDeviceEnumerator.cpp new file mode 100644 index 0000000000..7e0ffcc4af --- /dev/null +++ b/dom/media/gtest/TestAudioDeviceEnumerator.cpp @@ -0,0 +1,271 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define ENABLE_SET_CUBEB_BACKEND 1 +#include "CubebDeviceEnumerator.h" +#include "gtest/gtest-printers.h" +#include "gtest/gtest.h" +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/media/MediaUtils.h" +#include "nsTArray.h" + +#include "MockCubeb.h" + +using namespace mozilla; +using AudioDeviceSet = CubebDeviceEnumerator::AudioDeviceSet; + +const bool DEBUG_PRINTS = false; + +enum DeviceOperation { ADD, REMOVE }; + +void TestEnumeration(MockCubeb* aMock, uint32_t aExpectedDeviceCount, + DeviceOperation aOperation, cubeb_device_type aType) { + RefPtr<CubebDeviceEnumerator> enumerator = + CubebDeviceEnumerator::GetInstance(); + + RefPtr<const AudioDeviceSet> devices; + + if (aType == CUBEB_DEVICE_TYPE_INPUT) { + devices = enumerator->EnumerateAudioInputDevices(); + } + + if (aType == CUBEB_DEVICE_TYPE_OUTPUT) { + devices = enumerator->EnumerateAudioOutputDevices(); + } + + EXPECT_EQ(devices->Length(), aExpectedDeviceCount) + << "Device count is correct when enumerating"; + + if (DEBUG_PRINTS) { + for (const auto& deviceInfo : *devices) { + printf("=== Before removal\n"); + PrintDevice(deviceInfo); + } + } + + if (aOperation == DeviceOperation::REMOVE) { + aMock->RemoveDevice(reinterpret_cast<cubeb_devid>(1)); + } else { + aMock->AddDevice(DeviceTemplate(reinterpret_cast<cubeb_devid>(123), aType)); + } + + if (aType == CUBEB_DEVICE_TYPE_INPUT) { + devices = enumerator->EnumerateAudioInputDevices(); + } + + if (aType == CUBEB_DEVICE_TYPE_OUTPUT) { + devices = enumerator->EnumerateAudioOutputDevices(); + } + + uint32_t newExpectedDeviceCount = aOperation == DeviceOperation::REMOVE + ? aExpectedDeviceCount - 1 + : aExpectedDeviceCount + 1; + + EXPECT_EQ(devices->Length(), newExpectedDeviceCount) + << "Device count is correct when enumerating after operation"; + + if (DEBUG_PRINTS) { + for (const auto& deviceInfo : *devices) { + printf("=== After removal\n"); + PrintDevice(deviceInfo); + } + } +} + +#ifndef ANDROID +TEST(CubebDeviceEnumerator, EnumerateSimple) +{ + // It looks like we're leaking this object, but in fact it will be freed by + // gecko sometime later: `cubeb_destroy` is called when layout statics are + // shutdown and we cast back to a MockCubeb* and call the dtor. + MockCubeb* mock = new MockCubeb(); + mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext()); + + // We want to test whether CubebDeviceEnumerator works with and without a + // backend that can notify of a device collection change via callback. + // Additionally, we're testing that both adding and removing a device + // invalidates the list correctly. + bool supportsDeviceChangeCallback[2] = {true, false}; + DeviceOperation operations[2] = {DeviceOperation::ADD, + DeviceOperation::REMOVE}; + + for (bool supports : supportsDeviceChangeCallback) { + // Shutdown for `supports` to take effect + CubebDeviceEnumerator::Shutdown(); + mock->SetSupportDeviceChangeCallback(supports); + for (DeviceOperation op : operations) { + uint32_t device_count = 4; + + cubeb_device_type deviceType = CUBEB_DEVICE_TYPE_INPUT; + AddDevices(mock, device_count, deviceType); + TestEnumeration(mock, device_count, op, deviceType); + + deviceType = CUBEB_DEVICE_TYPE_OUTPUT; + AddDevices(mock, device_count, deviceType); + TestEnumeration(mock, device_count, op, deviceType); + } + } + // Shutdown to clean up the last `supports` effect + CubebDeviceEnumerator::Shutdown(); +} + +TEST(CubebDeviceEnumerator, ZeroChannelDevices) +{ + MockCubeb* mock = new MockCubeb(); + mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext()); + + // Create devices with different channel count, including 0-channel + + cubeb_device_info dev1 = DeviceTemplate(reinterpret_cast<cubeb_devid>(1), + CUBEB_DEVICE_TYPE_INPUT, "dev 1"); + dev1.max_channels = 1; + mock->AddDevice(dev1); + + cubeb_device_info dev2 = DeviceTemplate(reinterpret_cast<cubeb_devid>(2), + CUBEB_DEVICE_TYPE_INPUT, "dev 2"); + dev2.max_channels = 0; + mock->AddDevice(dev2); + + cubeb_device_info dev3 = DeviceTemplate(reinterpret_cast<cubeb_devid>(3), + CUBEB_DEVICE_TYPE_OUTPUT, "dev 3"); + dev3.max_channels = 2; + mock->AddDevice(dev3); + + cubeb_device_info dev4 = DeviceTemplate(reinterpret_cast<cubeb_devid>(4), + CUBEB_DEVICE_TYPE_OUTPUT, "dev 4"); + dev4.max_channels = 0; + mock->AddDevice(dev4); + + // Make sure the devices are added to cubeb. + + cubeb_device_collection inputCollection = {nullptr, 0}; + mock->EnumerateDevices(CUBEB_DEVICE_TYPE_INPUT, &inputCollection); + EXPECT_EQ(inputCollection.count, 2U); + EXPECT_EQ(inputCollection.device[0].devid, dev1.devid); + EXPECT_EQ(inputCollection.device[1].devid, dev2.devid); + mock->DestroyDeviceCollection(&inputCollection); + EXPECT_EQ(inputCollection.count, 0U); + + cubeb_device_collection outputCollection = {nullptr, 0}; + mock->EnumerateDevices(CUBEB_DEVICE_TYPE_OUTPUT, &outputCollection); + EXPECT_EQ(outputCollection.count, 2U); + EXPECT_EQ(outputCollection.device[0].devid, dev3.devid); + EXPECT_EQ(outputCollection.device[1].devid, dev4.devid); + mock->DestroyDeviceCollection(&outputCollection); + EXPECT_EQ(outputCollection.count, 0U); + + // Enumerate the devices. The result should exclude the 0-channel devices. + + RefPtr<CubebDeviceEnumerator> enumerator = + CubebDeviceEnumerator::GetInstance(); + + RefPtr<const AudioDeviceSet> inputDevices = + enumerator->EnumerateAudioInputDevices(); + EXPECT_EQ(inputDevices->Length(), 1U); + EXPECT_EQ(inputDevices->ElementAt(0)->DeviceID(), dev1.devid); + EXPECT_EQ(inputDevices->ElementAt(0)->MaxChannels(), dev1.max_channels); + + RefPtr<const AudioDeviceSet> outputDevices = + enumerator->EnumerateAudioOutputDevices(); + EXPECT_EQ(outputDevices->Length(), 1U); + EXPECT_EQ(outputDevices->ElementAt(0)->DeviceID(), dev3.devid); + EXPECT_EQ(outputDevices->ElementAt(0)->MaxChannels(), dev3.max_channels); +} + +#else // building for Android, which has no device enumeration support +TEST(CubebDeviceEnumerator, EnumerateAndroid) +{ + MockCubeb* mock = new MockCubeb(); + mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext()); + + RefPtr<CubebDeviceEnumerator> enumerator = + CubebDeviceEnumerator::GetInstance(); + + RefPtr<const AudioDeviceSet> inputDevices = + enumerator->EnumerateAudioInputDevices(); + EXPECT_EQ(inputDevices->Length(), 1u) + << "Android always exposes a single input device."; + EXPECT_EQ((*inputDevices)[0]->MaxChannels(), 1u) << "With a single channel."; + EXPECT_EQ((*inputDevices)[0]->DeviceID(), nullptr) + << "It's always the default input device."; + EXPECT_TRUE((*inputDevices)[0]->Preferred()) + << "it's always the prefered input device."; + + RefPtr<const AudioDeviceSet> outputDevices = + enumerator->EnumerateAudioOutputDevices(); + EXPECT_EQ(outputDevices->Length(), 1u) + << "Android always exposes a single output device."; + EXPECT_EQ((*outputDevices)[0]->MaxChannels(), 2u) << "With stereo channels."; + EXPECT_EQ((*outputDevices)[0]->DeviceID(), nullptr) + << "It's always the default output device."; + EXPECT_TRUE((*outputDevices)[0]->Preferred()) + << "it's always the prefered output device."; +} +#endif + +TEST(CubebDeviceEnumerator, ForceNullCubebContext) +{ + mozilla::CubebUtils::ForceSetCubebContext(nullptr); + RefPtr<CubebDeviceEnumerator> enumerator = + CubebDeviceEnumerator::GetInstance(); + + RefPtr inputDevices = enumerator->EnumerateAudioInputDevices(); + EXPECT_EQ(inputDevices->Length(), 0u) + << "Enumeration must fail, input device list must be empty."; + + RefPtr outputDevices = enumerator->EnumerateAudioOutputDevices(); + EXPECT_EQ(outputDevices->Length(), 0u) + << "Enumeration must fail, output device list must be empty."; + + // Shutdown to clean up the null context effect + CubebDeviceEnumerator::Shutdown(); +} + +TEST(CubebDeviceEnumerator, DeviceInfoFromName) +{ + MockCubeb* mock = new MockCubeb(); + mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext()); + + cubeb_device_type deviceTypes[2] = {CUBEB_DEVICE_TYPE_INPUT, + CUBEB_DEVICE_TYPE_OUTPUT}; + + bool supportsDeviceChangeCallback[2] = {true, false}; + for (bool supports : supportsDeviceChangeCallback) { + // Shutdown for `supports` to take effect + CubebDeviceEnumerator::Shutdown(); + mock->SetSupportDeviceChangeCallback(supports); + for (cubeb_device_type& deviceType : deviceTypes) { + cubeb_devid id_1 = reinterpret_cast<cubeb_devid>(1); + mock->AddDevice(DeviceTemplate(id_1, deviceType, "device name 1")); + cubeb_devid id_2 = reinterpret_cast<cubeb_devid>(2); + nsCString device_name = "device name 2"_ns; + mock->AddDevice(DeviceTemplate(id_2, deviceType, device_name.get())); + cubeb_devid id_3 = reinterpret_cast<cubeb_devid>(3); + mock->AddDevice(DeviceTemplate(id_3, deviceType, "device name 3")); + + RefPtr<CubebDeviceEnumerator> enumerator = + CubebDeviceEnumerator::GetInstance(); + + EnumeratorSide side = (deviceType == CUBEB_DEVICE_TYPE_INPUT) + ? EnumeratorSide::INPUT + : EnumeratorSide::OUTPUT; + RefPtr<AudioDeviceInfo> devInfo = enumerator->DeviceInfoFromName( + NS_ConvertUTF8toUTF16(device_name), side); + EXPECT_TRUE(devInfo) << "the device exist"; + EXPECT_EQ(devInfo->Name(), NS_ConvertUTF8toUTF16(device_name)) + << "verify the device"; + + mock->RemoveDevice(id_2); + + devInfo = enumerator->DeviceInfoFromName( + NS_ConvertUTF8toUTF16(device_name), side); + EXPECT_FALSE(devInfo) << "the device does not exist any more"; + } + } + // Shutdown for `supports` to take effect + CubebDeviceEnumerator::Shutdown(); +} +#undef ENABLE_SET_CUBEB_BACKEND |