tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TestAudioDeviceEnumerator.cpp (16817B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "AudioDeviceInfo.h"
      8 #include "CubebDeviceEnumerator.h"
      9 #include "MockCubeb.h"
     10 #include "gmock/gmock.h"
     11 #include "gtest/gtest-printers.h"
     12 #include "gtest/gtest.h"
     13 #include "mozilla/media/MediaUtils.h"
     14 #include "nsTArray.h"
     15 
     16 using namespace mozilla;
     17 using AudioDeviceSet = CubebDeviceEnumerator::AudioDeviceSet;
     18 using ::testing::ElementsAre;
     19 using ::testing::Eq;
     20 using ::testing::Pointwise;
     21 
     22 // Custom matcher for comparing AudioDeviceInfo by device ID
     23 MATCHER(DeviceIdEq, "") {
     24  const auto& [actual, expected_devid] = arg;
     25  return actual->DeviceID() == expected_devid;
     26 }
     27 
     28 // Custom matcher for checking device preferences
     29 MATCHER(PreferredEq, "") {
     30  const auto& [actual, expected_pref] = arg;
     31  uint16_t pref;
     32  nsresult rv = actual->GetPreferred(&pref);
     33  return NS_SUCCEEDED(rv) && pref == expected_pref;
     34 }
     35 
     36 const bool DEBUG_PRINTS = false;
     37 
     38 enum DeviceOperation { ADD, REMOVE };
     39 
     40 void TestEnumeration(MockCubeb* aMock, uint32_t aExpectedDeviceCount,
     41                     DeviceOperation aOperation, cubeb_device_type aType) {
     42  RefPtr<CubebDeviceEnumerator> enumerator =
     43      CubebDeviceEnumerator::GetInstance();
     44 
     45  RefPtr<const AudioDeviceSet> devices;
     46 
     47  if (aType == CUBEB_DEVICE_TYPE_INPUT) {
     48    devices = enumerator->EnumerateAudioInputDevices();
     49  }
     50 
     51  if (aType == CUBEB_DEVICE_TYPE_OUTPUT) {
     52    devices = enumerator->EnumerateAudioOutputDevices();
     53  }
     54 
     55  EXPECT_EQ(devices->Length(), aExpectedDeviceCount)
     56      << "Device count is correct when enumerating";
     57 
     58  if (DEBUG_PRINTS) {
     59    for (const auto& deviceInfo : *devices) {
     60      printf("=== Before removal\n");
     61      PrintDevice(deviceInfo);
     62    }
     63  }
     64 
     65  if (aOperation == DeviceOperation::REMOVE) {
     66    aMock->RemoveDevice(reinterpret_cast<cubeb_devid>(1));
     67  } else {
     68    aMock->AddDevice(DeviceTemplate(reinterpret_cast<cubeb_devid>(123), aType));
     69  }
     70 
     71  if (aType == CUBEB_DEVICE_TYPE_INPUT) {
     72    devices = enumerator->EnumerateAudioInputDevices();
     73  }
     74 
     75  if (aType == CUBEB_DEVICE_TYPE_OUTPUT) {
     76    devices = enumerator->EnumerateAudioOutputDevices();
     77  }
     78 
     79  uint32_t newExpectedDeviceCount = aOperation == DeviceOperation::REMOVE
     80                                        ? aExpectedDeviceCount - 1
     81                                        : aExpectedDeviceCount + 1;
     82 
     83  EXPECT_EQ(devices->Length(), newExpectedDeviceCount)
     84      << "Device count is correct when enumerating after operation";
     85 
     86  if (DEBUG_PRINTS) {
     87    for (const auto& deviceInfo : *devices) {
     88      printf("=== After removal\n");
     89      PrintDevice(deviceInfo);
     90    }
     91  }
     92 }
     93 
     94 #ifndef ANDROID
     95 TEST(CubebDeviceEnumerator, EnumerateSimple)
     96 {
     97  // It looks like we're leaking this object, but in fact it will be owned by
     98  // CubebUtils: `cubeb_destroy()` is called when `ForceSetCubebContext()` is
     99  // called again or when layout statics are shutdown, and we cast back to a
    100  // MockCubeb* and call the dtor.
    101  MockCubeb* mock = new MockCubeb();
    102  mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext());
    103 
    104  // We want to test whether CubebDeviceEnumerator works with and without a
    105  // backend that can notify of a device collection change via callback.
    106  // Additionally, we're testing that both adding and removing a device
    107  // invalidates the list correctly.
    108  bool supportsDeviceChangeCallback[2] = {true, false};
    109  DeviceOperation operations[2] = {DeviceOperation::ADD,
    110                                   DeviceOperation::REMOVE};
    111 
    112  for (bool supports : supportsDeviceChangeCallback) {
    113    // Shutdown for `supports` to take effect
    114    CubebDeviceEnumerator::Shutdown();
    115    mock->SetSupportDeviceChangeCallback(supports);
    116    for (DeviceOperation op : operations) {
    117      uint32_t device_count = 4;
    118 
    119      cubeb_device_type deviceType = CUBEB_DEVICE_TYPE_INPUT;
    120      AddDevices(mock, device_count, deviceType);
    121      TestEnumeration(mock, device_count, op, deviceType);
    122 
    123      deviceType = CUBEB_DEVICE_TYPE_OUTPUT;
    124      AddDevices(mock, device_count, deviceType);
    125      TestEnumeration(mock, device_count, op, deviceType);
    126    }
    127  }
    128  // Shutdown to clean up the last `supports` effect
    129  CubebDeviceEnumerator::Shutdown();
    130 }
    131 
    132 TEST(CubebDeviceEnumerator, ZeroChannelDevices)
    133 {
    134  MockCubeb* mock = new MockCubeb();
    135  mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext());
    136 
    137  // Create devices with different channel count, including 0-channel
    138 
    139  cubeb_device_info dev1 = DeviceTemplate(reinterpret_cast<cubeb_devid>(1),
    140                                          CUBEB_DEVICE_TYPE_INPUT, "dev 1");
    141  dev1.max_channels = 1;
    142  mock->AddDevice(dev1);
    143 
    144  cubeb_device_info dev2 = DeviceTemplate(reinterpret_cast<cubeb_devid>(2),
    145                                          CUBEB_DEVICE_TYPE_INPUT, "dev 2");
    146  dev2.max_channels = 0;
    147  mock->AddDevice(dev2);
    148 
    149  cubeb_device_info dev3 = DeviceTemplate(reinterpret_cast<cubeb_devid>(3),
    150                                          CUBEB_DEVICE_TYPE_OUTPUT, "dev 3");
    151  dev3.max_channels = 2;
    152  mock->AddDevice(dev3);
    153 
    154  cubeb_device_info dev4 = DeviceTemplate(reinterpret_cast<cubeb_devid>(4),
    155                                          CUBEB_DEVICE_TYPE_OUTPUT, "dev 4");
    156  dev4.max_channels = 0;
    157  mock->AddDevice(dev4);
    158 
    159  // Make sure the devices are added to cubeb.
    160 
    161  cubeb_device_collection inputCollection = {nullptr, 0};
    162  mock->EnumerateDevices(CUBEB_DEVICE_TYPE_INPUT, &inputCollection);
    163  EXPECT_EQ(inputCollection.count, 2U);
    164  EXPECT_EQ(inputCollection.device[0].devid, dev1.devid);
    165  EXPECT_EQ(inputCollection.device[1].devid, dev2.devid);
    166  mock->DestroyDeviceCollection(&inputCollection);
    167  EXPECT_EQ(inputCollection.count, 0U);
    168 
    169  cubeb_device_collection outputCollection = {nullptr, 0};
    170  mock->EnumerateDevices(CUBEB_DEVICE_TYPE_OUTPUT, &outputCollection);
    171  EXPECT_EQ(outputCollection.count, 2U);
    172  EXPECT_EQ(outputCollection.device[0].devid, dev3.devid);
    173  EXPECT_EQ(outputCollection.device[1].devid, dev4.devid);
    174  mock->DestroyDeviceCollection(&outputCollection);
    175  EXPECT_EQ(outputCollection.count, 0U);
    176 
    177  // Enumerate the devices. The result should exclude the 0-channel devices.
    178 
    179  RefPtr<CubebDeviceEnumerator> enumerator =
    180      CubebDeviceEnumerator::GetInstance();
    181 
    182  RefPtr<const AudioDeviceSet> inputDevices =
    183      enumerator->EnumerateAudioInputDevices();
    184  EXPECT_EQ(inputDevices->Length(), 1U);
    185  EXPECT_EQ(inputDevices->ElementAt(0)->DeviceID(), dev1.devid);
    186  EXPECT_EQ(inputDevices->ElementAt(0)->MaxChannels(), dev1.max_channels);
    187 
    188  RefPtr<const AudioDeviceSet> outputDevices =
    189      enumerator->EnumerateAudioOutputDevices();
    190  EXPECT_EQ(outputDevices->Length(), 1U);
    191  EXPECT_EQ(outputDevices->ElementAt(0)->DeviceID(), dev3.devid);
    192  EXPECT_EQ(outputDevices->ElementAt(0)->MaxChannels(), dev3.max_channels);
    193 }
    194 
    195 #else  // building for Android, which has no device enumeration support
    196 TEST(CubebDeviceEnumerator, EnumerateAndroid)
    197 {
    198  MockCubeb* mock = new MockCubeb();
    199  mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext());
    200 
    201  RefPtr<CubebDeviceEnumerator> enumerator =
    202      CubebDeviceEnumerator::GetInstance();
    203 
    204  RefPtr<const AudioDeviceSet> inputDevices =
    205      enumerator->EnumerateAudioInputDevices();
    206  EXPECT_EQ(inputDevices->Length(), 1u)
    207      << "Android always exposes a single input device.";
    208  EXPECT_EQ((*inputDevices)[0]->MaxChannels(), 1u) << "With a single channel.";
    209  EXPECT_EQ((*inputDevices)[0]->DeviceID(), nullptr)
    210      << "It's always the default input device.";
    211  EXPECT_TRUE((*inputDevices)[0]->Preferred())
    212      << "it's always the prefered input device.";
    213 
    214  RefPtr<const AudioDeviceSet> outputDevices =
    215      enumerator->EnumerateAudioOutputDevices();
    216  EXPECT_EQ(outputDevices->Length(), 1u)
    217      << "Android always exposes a single output device.";
    218  EXPECT_EQ((*outputDevices)[0]->MaxChannels(), 2u) << "With stereo channels.";
    219  EXPECT_EQ((*outputDevices)[0]->DeviceID(), nullptr)
    220      << "It's always the default output device.";
    221  EXPECT_TRUE((*outputDevices)[0]->Preferred())
    222      << "it's always the prefered output device.";
    223 }
    224 #endif
    225 
    226 TEST(CubebDeviceEnumerator, ForceNullCubebContext)
    227 {
    228  mozilla::CubebUtils::ForceSetCubebContext(nullptr);
    229  RefPtr<CubebDeviceEnumerator> enumerator =
    230      CubebDeviceEnumerator::GetInstance();
    231 
    232  RefPtr inputDevices = enumerator->EnumerateAudioInputDevices();
    233  EXPECT_EQ(inputDevices->Length(), 0u)
    234      << "Enumeration must fail, input device list must be empty.";
    235 
    236  RefPtr outputDevices = enumerator->EnumerateAudioOutputDevices();
    237  EXPECT_EQ(outputDevices->Length(), 0u)
    238      << "Enumeration must fail, output device list must be empty.";
    239 
    240  // Shutdown to clean up the null context effect
    241  CubebDeviceEnumerator::Shutdown();
    242 }
    243 
    244 TEST(CubebDeviceEnumerator, DeviceInfoFromName)
    245 {
    246  MockCubeb* mock = new MockCubeb();
    247  mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext());
    248 
    249  cubeb_device_type deviceTypes[2] = {CUBEB_DEVICE_TYPE_INPUT,
    250                                      CUBEB_DEVICE_TYPE_OUTPUT};
    251 
    252  bool supportsDeviceChangeCallback[2] = {true, false};
    253  for (bool supports : supportsDeviceChangeCallback) {
    254    // Shutdown for `supports` to take effect
    255    CubebDeviceEnumerator::Shutdown();
    256    mock->SetSupportDeviceChangeCallback(supports);
    257    for (cubeb_device_type& deviceType : deviceTypes) {
    258      cubeb_devid id_1 = reinterpret_cast<cubeb_devid>(1);
    259      mock->AddDevice(DeviceTemplate(id_1, deviceType, "device name 1"));
    260      cubeb_devid id_2 = reinterpret_cast<cubeb_devid>(2);
    261      nsCString device_name = "device name 2"_ns;
    262      mock->AddDevice(DeviceTemplate(id_2, deviceType, device_name.get()));
    263      cubeb_devid id_3 = reinterpret_cast<cubeb_devid>(3);
    264      mock->AddDevice(DeviceTemplate(id_3, deviceType, "device name 3"));
    265 
    266      RefPtr<CubebDeviceEnumerator> enumerator =
    267          CubebDeviceEnumerator::GetInstance();
    268 
    269      EnumeratorSide side = (deviceType == CUBEB_DEVICE_TYPE_INPUT)
    270                                ? EnumeratorSide::INPUT
    271                                : EnumeratorSide::OUTPUT;
    272      RefPtr<AudioDeviceInfo> devInfo = enumerator->DeviceInfoFromName(
    273          NS_ConvertUTF8toUTF16(device_name), side);
    274      EXPECT_TRUE(devInfo) << "the device exist";
    275      EXPECT_EQ(devInfo->Name(), NS_ConvertUTF8toUTF16(device_name))
    276          << "verify the device";
    277 
    278      mock->RemoveDevice(id_2);
    279 
    280      devInfo = enumerator->DeviceInfoFromName(
    281          NS_ConvertUTF8toUTF16(device_name), side);
    282      EXPECT_FALSE(devInfo) << "the device does not exist any more";
    283    }
    284  }
    285  // Shutdown for `supports` to take effect
    286  CubebDeviceEnumerator::Shutdown();
    287 }
    288 
    289 TEST(CubebDeviceEnumerator, PreferredDeviceFirst)
    290 {
    291  MockCubeb* mock = new MockCubeb();
    292  mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext());
    293 
    294  // Add multiple devices where the preferred one is not first
    295  cubeb_device_info dev1 = DeviceTemplate(reinterpret_cast<cubeb_devid>(1),
    296                                          CUBEB_DEVICE_TYPE_INPUT, "dev 1");
    297  dev1.preferred = CUBEB_DEVICE_PREF_NONE;
    298  mock->AddDevice(dev1);
    299 
    300  cubeb_device_info dev2 = DeviceTemplate(reinterpret_cast<cubeb_devid>(2),
    301                                          CUBEB_DEVICE_TYPE_INPUT, "dev 2");
    302  dev2.preferred = CUBEB_DEVICE_PREF_NONE;
    303  mock->AddDevice(dev2);
    304 
    305  cubeb_device_info dev3 = DeviceTemplate(reinterpret_cast<cubeb_devid>(3),
    306                                          CUBEB_DEVICE_TYPE_INPUT, "dev 3");
    307  dev3.preferred = CUBEB_DEVICE_PREF_ALL;
    308  mock->AddDevice(dev3);
    309 
    310  RefPtr<CubebDeviceEnumerator> enumerator =
    311      CubebDeviceEnumerator::GetInstance();
    312 
    313  RefPtr<const AudioDeviceSet> inputDevices =
    314      enumerator->EnumerateAudioInputDevices();
    315  EXPECT_EQ(inputDevices->Length(), 3U) << "Should have 3 devices";
    316 
    317  EXPECT_TRUE((*inputDevices)[0]->Preferred())
    318      << "First device should be the default device";
    319  EXPECT_EQ((*inputDevices)[0]->DeviceID(), dev3.devid)
    320      << "First device should be dev3";
    321 
    322  EXPECT_FALSE((*inputDevices)[1]->Preferred())
    323      << "Second device should not be marked as default";
    324  EXPECT_FALSE((*inputDevices)[2]->Preferred())
    325      << "Third device should not be marked as default";
    326 
    327  CubebDeviceEnumerator::Shutdown();
    328 }
    329 
    330 TEST(CubebDeviceEnumerator, MultiplePreferredDeviceOrdering)
    331 {
    332  MockCubeb* mock = new MockCubeb();
    333  mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext());
    334 
    335  // Add devices in mixed order to test proper sorting
    336  cubeb_device_info dev1 =
    337      DeviceTemplate(reinterpret_cast<cubeb_devid>(1), CUBEB_DEVICE_TYPE_OUTPUT,
    338                     "non-preferred");
    339  dev1.preferred = CUBEB_DEVICE_PREF_NONE;
    340  mock->AddDevice(dev1);
    341 
    342  cubeb_device_info dev2 =
    343      DeviceTemplate(reinterpret_cast<cubeb_devid>(2), CUBEB_DEVICE_TYPE_OUTPUT,
    344                     "notification");
    345  dev2.preferred = CUBEB_DEVICE_PREF_NOTIFICATION;
    346  mock->AddDevice(dev2);
    347 
    348  cubeb_device_info dev3 = DeviceTemplate(
    349      reinterpret_cast<cubeb_devid>(3), CUBEB_DEVICE_TYPE_OUTPUT, "multimedia");
    350  dev3.preferred = CUBEB_DEVICE_PREF_MULTIMEDIA;
    351  mock->AddDevice(dev3);
    352 
    353  cubeb_device_info dev4 = DeviceTemplate(reinterpret_cast<cubeb_devid>(4),
    354                                          CUBEB_DEVICE_TYPE_OUTPUT, "voice");
    355  dev4.preferred = CUBEB_DEVICE_PREF_VOICE;
    356  mock->AddDevice(dev4);
    357 
    358  cubeb_device_info dev5 =
    359      DeviceTemplate(reinterpret_cast<cubeb_devid>(5), CUBEB_DEVICE_TYPE_OUTPUT,
    360                     "non-preferred2");
    361  dev5.preferred = CUBEB_DEVICE_PREF_NONE;
    362  mock->AddDevice(dev5);
    363 
    364  RefPtr<CubebDeviceEnumerator> enumerator =
    365      CubebDeviceEnumerator::GetInstance();
    366 
    367  RefPtr<const AudioDeviceSet> outputDevices =
    368      enumerator->EnumerateAudioOutputDevices();
    369 
    370  // Casting to the non-refcountable type for the pointwise assertion below,
    371  // that matches on nsTArray
    372  const nsTArray<RefPtr<AudioDeviceInfo>>& outputDevicesCasted = *outputDevices;
    373 
    374  // We expect multimedia -> voice -> notification -> non-preferred devices
    375  EXPECT_THAT(outputDevicesCasted,
    376              Pointwise(DeviceIdEq(), {dev3.devid, dev4.devid, dev2.devid,
    377                                       dev1.devid, dev5.devid}));
    378 
    379  EXPECT_THAT(outputDevicesCasted,
    380              Pointwise(PreferredEq(), {nsIAudioDeviceInfo::PREF_MULTIMEDIA,
    381                                        nsIAudioDeviceInfo::PREF_VOICE,
    382                                        nsIAudioDeviceInfo::PREF_NOTIFICATION,
    383                                        nsIAudioDeviceInfo::PREF_NONE,
    384                                        nsIAudioDeviceInfo::PREF_NONE}));
    385 
    386  CubebDeviceEnumerator::Shutdown();
    387 }
    388 
    389 TEST(CubebDeviceEnumerator, MultiplePreferencesPerDevice)
    390 {
    391  MockCubeb* mock = new MockCubeb();
    392  mozilla::CubebUtils::ForceSetCubebContext(mock->AsCubebContext());
    393 
    394  // Add a device with multiple preferences
    395  cubeb_device_info dev1 =
    396      DeviceTemplate(reinterpret_cast<cubeb_devid>(1), CUBEB_DEVICE_TYPE_OUTPUT,
    397                     "multimedia-notification");
    398  dev1.preferred = static_cast<cubeb_device_pref>(
    399      CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION);
    400  mock->AddDevice(dev1);
    401 
    402  // Add a device with single preference
    403  cubeb_device_info dev2 = DeviceTemplate(reinterpret_cast<cubeb_devid>(2),
    404                                          CUBEB_DEVICE_TYPE_OUTPUT, "voice");
    405  dev2.preferred = CUBEB_DEVICE_PREF_VOICE;
    406  mock->AddDevice(dev2);
    407 
    408  // Add a non-preferred device
    409  cubeb_device_info dev3 =
    410      DeviceTemplate(reinterpret_cast<cubeb_devid>(3), CUBEB_DEVICE_TYPE_OUTPUT,
    411                     "non-preferred");
    412  dev3.preferred = CUBEB_DEVICE_PREF_NONE;
    413  mock->AddDevice(dev3);
    414 
    415  RefPtr<CubebDeviceEnumerator> enumerator =
    416      CubebDeviceEnumerator::GetInstance();
    417 
    418  RefPtr<const AudioDeviceSet> outputDevices =
    419      enumerator->EnumerateAudioOutputDevices();
    420 
    421  // Casting to the non-refcountable type for the pointwise assertion below,
    422  // that matches on nsTArray
    423  const nsTArray<RefPtr<AudioDeviceInfo>>& outputDevicesCasted = *outputDevices;
    424 
    425  // Device with multimedia preference should come first
    426  EXPECT_THAT(outputDevicesCasted,
    427              Pointwise(DeviceIdEq(), {dev1.devid, dev2.devid, dev3.devid}));
    428 
    429  // Check device preferences - dev1 has both multimedia and notification
    430  EXPECT_THAT(outputDevicesCasted,
    431              Pointwise(PreferredEq(),
    432                        (uint16_t[]){nsIAudioDeviceInfo::PREF_MULTIMEDIA |
    433                                         nsIAudioDeviceInfo::PREF_NOTIFICATION,
    434                                     nsIAudioDeviceInfo::PREF_VOICE, 0}));
    435 
    436  CubebDeviceEnumerator::Shutdown();
    437 }
    438 #undef ENABLE_SET_CUBEB_BACKEND