Backed out 3 changesets (bug 1876526) for causing crashes due to CubebDeviceEnumerato...
[gecko.git] / dom / media / webrtc / CubebDeviceEnumerator.cpp
blob625f5724b934a5afd7e96c8b151ab468e159eeed
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/. */
7 #include "CubebDeviceEnumerator.h"
9 #include "mozilla/ClearOnShutdown.h"
10 #include "mozilla/SchedulerGroup.h"
11 #include "mozilla/StaticMutex.h"
12 #include "mozilla/StaticPtr.h"
13 #include "mozilla/media/MediaUtils.h"
14 #include "nsThreadUtils.h"
15 #ifdef XP_WIN
16 # include "mozilla/mscom/EnsureMTA.h"
17 #endif
19 namespace mozilla {
21 using namespace CubebUtils;
22 using AudioDeviceSet = CubebDeviceEnumerator::AudioDeviceSet;
24 /* static */
25 static StaticRefPtr<CubebDeviceEnumerator> sInstance;
26 static StaticMutex sInstanceMutex MOZ_UNANNOTATED;
28 /* static */
29 CubebDeviceEnumerator* CubebDeviceEnumerator::GetInstance() {
30 StaticMutexAutoLock lock(sInstanceMutex);
31 if (!sInstance) {
32 sInstance = new CubebDeviceEnumerator();
33 static bool clearOnShutdownSetup = []() -> bool {
34 auto setClearOnShutdown = []() -> void {
35 ClearOnShutdown(&sInstance, ShutdownPhase::XPCOMShutdownThreads);
37 if (NS_IsMainThread()) {
38 setClearOnShutdown();
39 } else {
40 SchedulerGroup::Dispatch(
41 NS_NewRunnableFunction("CubebDeviceEnumerator::::GetInstance()",
42 std::move(setClearOnShutdown)));
44 return true;
45 }();
46 Unused << clearOnShutdownSetup;
48 return sInstance.get();
51 CubebDeviceEnumerator::CubebDeviceEnumerator()
52 : mMutex("CubebDeviceListMutex"),
53 mManualInputInvalidation(false),
54 mManualOutputInvalidation(false) {
55 #ifdef XP_WIN
56 // Ensure the MTA thread exists and gets instantiated before the
57 // CubebDeviceEnumerator so that this instance will always gets destructed
58 // before the MTA thread gets shutdown.
59 mozilla::mscom::EnsureMTA([&]() -> void {
60 #endif
61 int rv = cubeb_register_device_collection_changed(
62 GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT,
63 &OutputAudioDeviceListChanged_s, this);
64 if (rv != CUBEB_OK) {
65 NS_WARNING(
66 "Could not register the audio output"
67 " device collection changed callback.");
68 mManualOutputInvalidation = true;
70 rv = cubeb_register_device_collection_changed(
71 GetCubebContext(), CUBEB_DEVICE_TYPE_INPUT,
72 &InputAudioDeviceListChanged_s, this);
73 if (rv != CUBEB_OK) {
74 NS_WARNING(
75 "Could not register the audio input"
76 " device collection changed callback.");
77 mManualInputInvalidation = true;
79 #ifdef XP_WIN
80 });
81 #endif
84 /* static */
85 void CubebDeviceEnumerator::Shutdown() {
86 StaticMutexAutoLock lock(sInstanceMutex);
87 if (sInstance) {
88 sInstance = nullptr;
92 CubebDeviceEnumerator::~CubebDeviceEnumerator() {
93 #ifdef XP_WIN
94 mozilla::mscom::EnsureMTA([&]() -> void {
95 #endif
96 int rv = cubeb_register_device_collection_changed(
97 GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT, nullptr, this);
98 if (rv != CUBEB_OK) {
99 NS_WARNING(
100 "Could not unregister the audio output"
101 " device collection changed callback.");
103 rv = cubeb_register_device_collection_changed(
104 GetCubebContext(), CUBEB_DEVICE_TYPE_INPUT, nullptr, this);
105 if (rv != CUBEB_OK) {
106 NS_WARNING(
107 "Could not unregister the audio input"
108 " device collection changed callback.");
110 #ifdef XP_WIN
112 #endif
115 RefPtr<const AudioDeviceSet>
116 CubebDeviceEnumerator::EnumerateAudioInputDevices() {
117 return EnumerateAudioDevices(Side::INPUT);
120 RefPtr<const AudioDeviceSet>
121 CubebDeviceEnumerator::EnumerateAudioOutputDevices() {
122 return EnumerateAudioDevices(Side::OUTPUT);
125 #ifndef ANDROID
126 static uint16_t ConvertCubebType(cubeb_device_type aType) {
127 uint16_t map[] = {
128 nsIAudioDeviceInfo::TYPE_UNKNOWN, // CUBEB_DEVICE_TYPE_UNKNOWN
129 nsIAudioDeviceInfo::TYPE_INPUT, // CUBEB_DEVICE_TYPE_INPUT,
130 nsIAudioDeviceInfo::TYPE_OUTPUT // CUBEB_DEVICE_TYPE_OUTPUT
132 return map[aType];
135 static uint16_t ConvertCubebState(cubeb_device_state aState) {
136 uint16_t map[] = {
137 nsIAudioDeviceInfo::STATE_DISABLED, // CUBEB_DEVICE_STATE_DISABLED
138 nsIAudioDeviceInfo::STATE_UNPLUGGED, // CUBEB_DEVICE_STATE_UNPLUGGED
139 nsIAudioDeviceInfo::STATE_ENABLED // CUBEB_DEVICE_STATE_ENABLED
141 return map[aState];
144 static uint16_t ConvertCubebPreferred(cubeb_device_pref aPreferred) {
145 if (aPreferred == CUBEB_DEVICE_PREF_NONE) {
146 return nsIAudioDeviceInfo::PREF_NONE;
148 if (aPreferred == CUBEB_DEVICE_PREF_ALL) {
149 return nsIAudioDeviceInfo::PREF_ALL;
152 uint16_t preferred = 0;
153 if (aPreferred & CUBEB_DEVICE_PREF_MULTIMEDIA) {
154 preferred |= nsIAudioDeviceInfo::PREF_MULTIMEDIA;
156 if (aPreferred & CUBEB_DEVICE_PREF_VOICE) {
157 preferred |= nsIAudioDeviceInfo::PREF_VOICE;
159 if (aPreferred & CUBEB_DEVICE_PREF_NOTIFICATION) {
160 preferred |= nsIAudioDeviceInfo::PREF_NOTIFICATION;
162 return preferred;
165 static uint16_t ConvertCubebFormat(cubeb_device_fmt aFormat) {
166 uint16_t format = 0;
167 if (aFormat & CUBEB_DEVICE_FMT_S16LE) {
168 format |= nsIAudioDeviceInfo::FMT_S16LE;
170 if (aFormat & CUBEB_DEVICE_FMT_S16BE) {
171 format |= nsIAudioDeviceInfo::FMT_S16BE;
173 if (aFormat & CUBEB_DEVICE_FMT_F32LE) {
174 format |= nsIAudioDeviceInfo::FMT_F32LE;
176 if (aFormat & CUBEB_DEVICE_FMT_F32BE) {
177 format |= nsIAudioDeviceInfo::FMT_F32BE;
179 return format;
182 static RefPtr<AudioDeviceSet> GetDeviceCollection(Side aSide) {
183 RefPtr set = new AudioDeviceSet();
184 cubeb* context = GetCubebContext();
185 if (context) {
186 cubeb_device_collection collection = {nullptr, 0};
187 # ifdef XP_WIN
188 mozilla::mscom::EnsureMTA([&]() -> void {
189 # endif
190 if (cubeb_enumerate_devices(context,
191 aSide == Input ? CUBEB_DEVICE_TYPE_INPUT
192 : CUBEB_DEVICE_TYPE_OUTPUT,
193 &collection) == CUBEB_OK) {
194 for (unsigned int i = 0; i < collection.count; ++i) {
195 auto device = collection.device[i];
196 if (device.max_channels == 0) {
197 continue;
199 RefPtr<AudioDeviceInfo> info = new AudioDeviceInfo(
200 device.devid, NS_ConvertUTF8toUTF16(device.friendly_name),
201 NS_ConvertUTF8toUTF16(device.group_id),
202 NS_ConvertUTF8toUTF16(device.vendor_name),
203 ConvertCubebType(device.type), ConvertCubebState(device.state),
204 ConvertCubebPreferred(device.preferred),
205 ConvertCubebFormat(device.format),
206 ConvertCubebFormat(device.default_format), device.max_channels,
207 device.default_rate, device.max_rate, device.min_rate,
208 device.latency_hi, device.latency_lo);
209 set->AppendElement(std::move(info));
212 cubeb_device_collection_destroy(context, &collection);
213 # ifdef XP_WIN
215 # endif
217 return set;
219 #endif // non ANDROID
221 RefPtr<const AudioDeviceSet> CubebDeviceEnumerator::EnumerateAudioDevices(
222 CubebDeviceEnumerator::Side aSide) {
223 MOZ_ASSERT(aSide == Side::INPUT || aSide == Side::OUTPUT);
225 RefPtr<const AudioDeviceSet>* devicesCache;
226 bool manualInvalidation = true;
228 if (aSide == Side::INPUT) {
229 devicesCache = &mInputDevices;
230 manualInvalidation = mManualInputInvalidation;
231 } else {
232 MOZ_ASSERT(aSide == Side::OUTPUT);
233 devicesCache = &mOutputDevices;
234 manualInvalidation = mManualOutputInvalidation;
237 cubeb* context = GetCubebContext();
238 if (!context) {
239 return new AudioDeviceSet();
241 if (!manualInvalidation) {
242 MutexAutoLock lock(mMutex);
243 if (*devicesCache) {
244 return *devicesCache;
248 #ifdef ANDROID
249 cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
250 uint32_t channels = 0;
251 nsAutoString name;
252 if (aSide == Side::INPUT) {
253 type = CUBEB_DEVICE_TYPE_INPUT;
254 channels = 1;
255 name = u"Default audio input device"_ns;
256 } else {
257 MOZ_ASSERT(aSide == Side::OUTPUT);
258 type = CUBEB_DEVICE_TYPE_OUTPUT;
259 channels = 2;
260 name = u"Default audio output device"_ns;
262 RefPtr devices = new AudioDeviceSet();
263 // Bug 1473346: enumerating devices is not supported on Android in cubeb,
264 // simply state that there is a single sink, that it is the default, and has
265 // a single channel. All the other values are made up and are not to be used.
266 // Bug 1660391: we can't use fluent here yet to get localized strings, so
267 // those are hard-coded en_US strings for now.
268 RefPtr<AudioDeviceInfo> info = new AudioDeviceInfo(
269 nullptr, name, u""_ns, u""_ns, type, CUBEB_DEVICE_STATE_ENABLED,
270 CUBEB_DEVICE_PREF_ALL, CUBEB_DEVICE_FMT_ALL, CUBEB_DEVICE_FMT_S16NE,
271 channels, 44100, 44100, 44100, 441, 128);
272 devices->AppendElement(std::move(info));
273 #else
274 RefPtr devices = GetDeviceCollection(
275 (aSide == Side::INPUT) ? CubebUtils::Input : CubebUtils::Output);
276 #endif
278 MutexAutoLock lock(mMutex);
279 *devicesCache = devices;
281 return devices;
284 already_AddRefed<AudioDeviceInfo> CubebDeviceEnumerator::DeviceInfoFromName(
285 const nsString& aName, Side aSide) {
286 RefPtr devices = EnumerateAudioDevices(aSide);
287 for (const RefPtr<AudioDeviceInfo>& device : *devices) {
288 if (device->Name().Equals(aName)) {
289 RefPtr<AudioDeviceInfo> other = device;
290 return other.forget();
294 return nullptr;
297 RefPtr<AudioDeviceInfo> CubebDeviceEnumerator::DefaultDevice(Side aSide) {
298 RefPtr devices = EnumerateAudioDevices(aSide);
299 for (const RefPtr<AudioDeviceInfo>& device : *devices) {
300 if (device->Preferred()) {
301 RefPtr<AudioDeviceInfo> other = device;
302 return other.forget();
306 return nullptr;
309 void CubebDeviceEnumerator::InputAudioDeviceListChanged_s(cubeb* aContext,
310 void* aUser) {
311 CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);
312 self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::INPUT);
315 void CubebDeviceEnumerator::OutputAudioDeviceListChanged_s(cubeb* aContext,
316 void* aUser) {
317 CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);
318 self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::OUTPUT);
321 void CubebDeviceEnumerator::AudioDeviceListChanged(Side aSide) {
322 MutexAutoLock lock(mMutex);
323 if (aSide == Side::INPUT) {
324 mInputDevices = nullptr;
325 mOnInputDeviceListChange.Notify();
326 } else {
327 MOZ_ASSERT(aSide == Side::OUTPUT);
328 mOutputDevices = nullptr;
329 mOnOutputDeviceListChange.Notify();
333 } // namespace mozilla