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