tor-browser

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

CubebDeviceEnumerator.cpp (12093B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=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 "CubebDeviceEnumerator.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/ClearOnShutdown.h"
     13 #include "mozilla/SchedulerGroup.h"
     14 #include "mozilla/StaticMutex.h"
     15 #include "mozilla/StaticPtr.h"
     16 #include "mozilla/media/MediaUtils.h"
     17 #include "nsThreadUtils.h"
     18 #ifdef XP_WIN
     19 #  include "mozilla/mscom/EnsureMTA.h"
     20 #endif
     21 
     22 namespace mozilla {
     23 
     24 using namespace CubebUtils;
     25 using AudioDeviceSet = CubebDeviceEnumerator::AudioDeviceSet;
     26 
     27 /* static */
     28 static StaticRefPtr<CubebDeviceEnumerator> sInstance;
     29 static StaticMutex sInstanceMutex MOZ_UNANNOTATED;
     30 
     31 /* static */
     32 CubebDeviceEnumerator* CubebDeviceEnumerator::GetInstance() {
     33  StaticMutexAutoLock lock(sInstanceMutex);
     34  if (!sInstance) {
     35    sInstance = new CubebDeviceEnumerator();
     36    static bool clearOnShutdownSetup = []() -> bool {
     37      auto setClearOnShutdown = []() -> void {
     38        ClearOnShutdown(&sInstance, ShutdownPhase::XPCOMShutdownThreads);
     39      };
     40      if (NS_IsMainThread()) {
     41        setClearOnShutdown();
     42      } else {
     43        SchedulerGroup::Dispatch(
     44            NS_NewRunnableFunction("CubebDeviceEnumerator::::GetInstance()",
     45                                   std::move(setClearOnShutdown)));
     46      }
     47      return true;
     48    }();
     49    (void)clearOnShutdownSetup;
     50  }
     51  return sInstance.get();
     52 }
     53 
     54 CubebDeviceEnumerator::CubebDeviceEnumerator()
     55    : mMutex("CubebDeviceListMutex"),
     56      mManualInputInvalidation(false),
     57      mManualOutputInvalidation(false) {
     58 #ifdef XP_WIN
     59  // Ensure the MTA thread exists and gets instantiated before the
     60  // CubebDeviceEnumerator so that this instance will always gets destructed
     61  // before the MTA thread gets shutdown.
     62  mozilla::mscom::EnsureMTA([&]() -> void {
     63 #endif
     64    RefPtr<CubebHandle> handle = GetCubeb();
     65    if (handle) {
     66      int rv = cubeb_register_device_collection_changed(
     67          handle->Context(), CUBEB_DEVICE_TYPE_OUTPUT,
     68          &OutputAudioDeviceListChanged_s, this);
     69      if (rv != CUBEB_OK) {
     70        NS_WARNING(
     71            "Could not register the audio output"
     72            " device collection changed callback.");
     73        mManualOutputInvalidation = true;
     74      }
     75      rv = cubeb_register_device_collection_changed(
     76          handle->Context(), CUBEB_DEVICE_TYPE_INPUT,
     77          &InputAudioDeviceListChanged_s, this);
     78      if (rv != CUBEB_OK) {
     79        NS_WARNING(
     80            "Could not register the audio input"
     81            " device collection changed callback.");
     82        mManualInputInvalidation = true;
     83      }
     84    }
     85 #ifdef XP_WIN
     86  });
     87 #endif
     88 }
     89 
     90 /* static */
     91 void CubebDeviceEnumerator::Shutdown() {
     92  StaticMutexAutoLock lock(sInstanceMutex);
     93  if (sInstance) {
     94    sInstance = nullptr;
     95  }
     96 }
     97 
     98 CubebDeviceEnumerator::~CubebDeviceEnumerator() {
     99 #ifdef XP_WIN
    100  mozilla::mscom::EnsureMTA([&]() -> void {
    101 #endif
    102    RefPtr<CubebHandle> handle = GetCubeb();
    103    if (handle) {
    104      int rv = cubeb_register_device_collection_changed(
    105          handle->Context(), CUBEB_DEVICE_TYPE_OUTPUT, nullptr, this);
    106      if (rv != CUBEB_OK) {
    107        NS_WARNING(
    108            "Could not unregister the audio output"
    109            " device collection changed callback.");
    110      }
    111      rv = cubeb_register_device_collection_changed(
    112          handle->Context(), CUBEB_DEVICE_TYPE_INPUT, nullptr, this);
    113      if (rv != CUBEB_OK) {
    114        NS_WARNING(
    115            "Could not unregister the audio input"
    116            " device collection changed callback.");
    117      }
    118    }
    119 #ifdef XP_WIN
    120  });
    121 #endif
    122 }
    123 
    124 RefPtr<const AudioDeviceSet>
    125 CubebDeviceEnumerator::EnumerateAudioInputDevices() {
    126  return EnumerateAudioDevices(Side::INPUT);
    127 }
    128 
    129 RefPtr<const AudioDeviceSet>
    130 CubebDeviceEnumerator::EnumerateAudioOutputDevices() {
    131  return EnumerateAudioDevices(Side::OUTPUT);
    132 }
    133 
    134 #ifndef ANDROID
    135 static uint16_t ConvertCubebType(cubeb_device_type aType) {
    136  uint16_t map[] = {
    137      nsIAudioDeviceInfo::TYPE_UNKNOWN,  // CUBEB_DEVICE_TYPE_UNKNOWN
    138      nsIAudioDeviceInfo::TYPE_INPUT,    // CUBEB_DEVICE_TYPE_INPUT,
    139      nsIAudioDeviceInfo::TYPE_OUTPUT    // CUBEB_DEVICE_TYPE_OUTPUT
    140  };
    141  return map[aType];
    142 }
    143 
    144 static uint16_t ConvertCubebState(cubeb_device_state aState) {
    145  uint16_t map[] = {
    146      nsIAudioDeviceInfo::STATE_DISABLED,   // CUBEB_DEVICE_STATE_DISABLED
    147      nsIAudioDeviceInfo::STATE_UNPLUGGED,  // CUBEB_DEVICE_STATE_UNPLUGGED
    148      nsIAudioDeviceInfo::STATE_ENABLED     // CUBEB_DEVICE_STATE_ENABLED
    149  };
    150  return map[aState];
    151 }
    152 
    153 static uint16_t ConvertCubebPreferred(cubeb_device_pref aPreferred) {
    154  if (aPreferred == CUBEB_DEVICE_PREF_NONE) {
    155    return nsIAudioDeviceInfo::PREF_NONE;
    156  }
    157  if (aPreferred == CUBEB_DEVICE_PREF_ALL) {
    158    return nsIAudioDeviceInfo::PREF_ALL;
    159  }
    160 
    161  uint16_t preferred = 0;
    162  if (aPreferred & CUBEB_DEVICE_PREF_MULTIMEDIA) {
    163    preferred |= nsIAudioDeviceInfo::PREF_MULTIMEDIA;
    164  }
    165  if (aPreferred & CUBEB_DEVICE_PREF_VOICE) {
    166    preferred |= nsIAudioDeviceInfo::PREF_VOICE;
    167  }
    168  if (aPreferred & CUBEB_DEVICE_PREF_NOTIFICATION) {
    169    preferred |= nsIAudioDeviceInfo::PREF_NOTIFICATION;
    170  }
    171  return preferred;
    172 }
    173 
    174 static uint16_t ConvertCubebFormat(cubeb_device_fmt aFormat) {
    175  uint16_t format = 0;
    176  if (aFormat & CUBEB_DEVICE_FMT_S16LE) {
    177    format |= nsIAudioDeviceInfo::FMT_S16LE;
    178  }
    179  if (aFormat & CUBEB_DEVICE_FMT_S16BE) {
    180    format |= nsIAudioDeviceInfo::FMT_S16BE;
    181  }
    182  if (aFormat & CUBEB_DEVICE_FMT_F32LE) {
    183    format |= nsIAudioDeviceInfo::FMT_F32LE;
    184  }
    185  if (aFormat & CUBEB_DEVICE_FMT_F32BE) {
    186    format |= nsIAudioDeviceInfo::FMT_F32BE;
    187  }
    188  return format;
    189 }
    190 
    191 static int GetDevicePriority(const RefPtr<AudioDeviceInfo>& device) {
    192  if (!device->Preferred()) {
    193    return 0;
    194  }
    195 
    196  uint16_t prefs = 0;
    197  device->GetPreferred(&prefs);
    198 
    199  // Priority order: multimedia (playback) -> voice (communication) ->
    200  // notification. PERF_ALL contains all flags, so is of the highest priority
    201  // and ends up sorted first.
    202  if (prefs & nsIAudioDeviceInfo::PREF_MULTIMEDIA) {
    203    return 3;
    204  }
    205  if (prefs & nsIAudioDeviceInfo::PREF_VOICE) {
    206    return 2;
    207  }
    208  if (prefs & nsIAudioDeviceInfo::PREF_NOTIFICATION) {
    209    return 1;
    210  }
    211  MOZ_ASSERT_UNREACHABLE("Unknown value in Preferred flag");
    212  return 0;
    213 }
    214 
    215 static RefPtr<AudioDeviceSet> GetDeviceCollection(Side aSide) {
    216  RefPtr set = new AudioDeviceSet();
    217  RefPtr<CubebHandle> handle = GetCubeb();
    218  if (handle) {
    219    cubeb_device_collection collection = {nullptr, 0};
    220 #  ifdef XP_WIN
    221    mozilla::mscom::EnsureMTA([&]() -> void {
    222 #  endif
    223      if (cubeb_enumerate_devices(handle->Context(),
    224                                  aSide == Input ? CUBEB_DEVICE_TYPE_INPUT
    225                                                 : CUBEB_DEVICE_TYPE_OUTPUT,
    226                                  &collection) == CUBEB_OK) {
    227        for (unsigned int i = 0; i < collection.count; ++i) {
    228          auto device = collection.device[i];
    229          if (device.max_channels == 0) {
    230            continue;
    231          }
    232          RefPtr<AudioDeviceInfo> info = new AudioDeviceInfo(
    233              device.devid, NS_ConvertUTF8toUTF16(device.friendly_name),
    234              NS_ConvertUTF8toUTF16(device.group_id),
    235              NS_ConvertUTF8toUTF16(device.vendor_name),
    236              ConvertCubebType(device.type), ConvertCubebState(device.state),
    237              ConvertCubebPreferred(device.preferred),
    238              ConvertCubebFormat(device.format),
    239              ConvertCubebFormat(device.default_format), device.max_channels,
    240              device.default_rate, device.max_rate, device.min_rate,
    241              device.latency_hi, device.latency_lo);
    242 
    243          set->AppendElement(std::move(info));
    244        }
    245      }
    246      cubeb_device_collection_destroy(handle->Context(), &collection);
    247 #  ifdef XP_WIN
    248    });
    249 #  endif
    250  }
    251 
    252  // On Windows, there is multiple kinds of default devices. This uses
    253  // this order: multimedia -> voice -> notification -> non-preferred.
    254  // This code works as expected on other OSes.
    255  std::stable_sort(
    256      set->begin(), set->end(),
    257      [](const RefPtr<AudioDeviceInfo>& a, const RefPtr<AudioDeviceInfo>& b) {
    258        return GetDevicePriority(a) > GetDevicePriority(b);
    259      });
    260 
    261  return set;
    262 }
    263 #endif  // non ANDROID
    264 
    265 RefPtr<const AudioDeviceSet> CubebDeviceEnumerator::EnumerateAudioDevices(
    266    CubebDeviceEnumerator::Side aSide) {
    267  MOZ_ASSERT(aSide == Side::INPUT || aSide == Side::OUTPUT);
    268 
    269  RefPtr<const AudioDeviceSet>* devicesCache;
    270  bool manualInvalidation = true;
    271 
    272  if (aSide == Side::INPUT) {
    273    devicesCache = &mInputDevices;
    274    manualInvalidation = mManualInputInvalidation;
    275  } else {
    276    MOZ_ASSERT(aSide == Side::OUTPUT);
    277    devicesCache = &mOutputDevices;
    278    manualInvalidation = mManualOutputInvalidation;
    279  }
    280 
    281  if (!GetCubeb()) {
    282    return new AudioDeviceSet();
    283  }
    284  if (!manualInvalidation) {
    285    MutexAutoLock lock(mMutex);
    286    if (*devicesCache) {
    287      return *devicesCache;
    288    }
    289  }
    290 
    291 #ifdef ANDROID
    292  cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
    293  uint32_t channels = 0;
    294  nsAutoString name;
    295  if (aSide == Side::INPUT) {
    296    type = CUBEB_DEVICE_TYPE_INPUT;
    297    channels = 1;
    298    name = u"Default audio input device"_ns;
    299  } else {
    300    MOZ_ASSERT(aSide == Side::OUTPUT);
    301    type = CUBEB_DEVICE_TYPE_OUTPUT;
    302    channels = 2;
    303    name = u"Default audio output device"_ns;
    304  }
    305  RefPtr devices = new AudioDeviceSet();
    306  // Bug 1473346: enumerating devices is not supported on Android in cubeb,
    307  // simply state that there is a single sink, that it is the default, and has
    308  // a single channel. All the other values are made up and are not to be used.
    309  // Bug 1660391: we can't use fluent here yet to get localized strings, so
    310  // those are hard-coded en_US strings for now.
    311  RefPtr<AudioDeviceInfo> info = new AudioDeviceInfo(
    312      nullptr, name, u""_ns, u""_ns, type, CUBEB_DEVICE_STATE_ENABLED,
    313      CUBEB_DEVICE_PREF_ALL, CUBEB_DEVICE_FMT_ALL, CUBEB_DEVICE_FMT_S16NE,
    314      channels, 44100, 44100, 44100, 441, 128);
    315  devices->AppendElement(std::move(info));
    316 #else
    317  RefPtr devices = GetDeviceCollection(
    318      (aSide == Side::INPUT) ? CubebUtils::Input : CubebUtils::Output);
    319 #endif
    320  {
    321    MutexAutoLock lock(mMutex);
    322    *devicesCache = devices;
    323  }
    324  return devices;
    325 }
    326 
    327 already_AddRefed<AudioDeviceInfo> CubebDeviceEnumerator::DeviceInfoFromName(
    328    const nsString& aName, Side aSide) {
    329  RefPtr devices = EnumerateAudioDevices(aSide);
    330  for (const RefPtr<AudioDeviceInfo>& device : *devices) {
    331    if (device->Name().Equals(aName)) {
    332      RefPtr<AudioDeviceInfo> other = device;
    333      return other.forget();
    334    }
    335  }
    336 
    337  return nullptr;
    338 }
    339 
    340 RefPtr<AudioDeviceInfo> CubebDeviceEnumerator::DefaultDevice(Side aSide) {
    341  RefPtr devices = EnumerateAudioDevices(aSide);
    342  for (const RefPtr<AudioDeviceInfo>& device : *devices) {
    343    if (device->Preferred()) {
    344      RefPtr<AudioDeviceInfo> other = device;
    345      return other.forget();
    346    }
    347  }
    348 
    349  return nullptr;
    350 }
    351 
    352 void CubebDeviceEnumerator::InputAudioDeviceListChanged_s(cubeb* aContext,
    353                                                          void* aUser) {
    354  CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);
    355  self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::INPUT);
    356 }
    357 
    358 void CubebDeviceEnumerator::OutputAudioDeviceListChanged_s(cubeb* aContext,
    359                                                           void* aUser) {
    360  CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);
    361  self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::OUTPUT);
    362 }
    363 
    364 void CubebDeviceEnumerator::AudioDeviceListChanged(Side aSide) {
    365  MutexAutoLock lock(mMutex);
    366  if (aSide == Side::INPUT) {
    367    mInputDevices = nullptr;
    368    mOnInputDeviceListChange.Notify();
    369  } else {
    370    MOZ_ASSERT(aSide == Side::OUTPUT);
    371    mOutputDevices = nullptr;
    372    mOnOutputDeviceListChange.Notify();
    373  }
    374 }
    375 
    376 }  // namespace mozilla