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"
16 # include "mozilla/mscom/EnsureMTA.h"
21 using namespace CubebUtils
;
22 using AudioDeviceSet
= CubebDeviceEnumerator::AudioDeviceSet
;
25 static StaticRefPtr
<CubebDeviceEnumerator
> sInstance
;
26 static StaticMutex sInstanceMutex MOZ_UNANNOTATED
;
29 CubebDeviceEnumerator
* CubebDeviceEnumerator::GetInstance() {
30 StaticMutexAutoLock
lock(sInstanceMutex
);
32 sInstance
= new CubebDeviceEnumerator();
33 static bool clearOnShutdownSetup
= []() -> bool {
34 auto setClearOnShutdown
= []() -> void {
35 ClearOnShutdown(&sInstance
, ShutdownPhase::XPCOMShutdownThreads
);
37 if (NS_IsMainThread()) {
40 SchedulerGroup::Dispatch(
41 NS_NewRunnableFunction("CubebDeviceEnumerator::::GetInstance()",
42 std::move(setClearOnShutdown
)));
46 Unused
<< clearOnShutdownSetup
;
48 return sInstance
.get();
51 CubebDeviceEnumerator::CubebDeviceEnumerator()
52 : mMutex("CubebDeviceListMutex"),
53 mManualInputInvalidation(false),
54 mManualOutputInvalidation(false) {
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 {
61 int rv
= cubeb_register_device_collection_changed(
62 GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT
,
63 &OutputAudioDeviceListChanged_s
, this);
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);
75 "Could not register the audio input"
76 " device collection changed callback.");
77 mManualInputInvalidation
= true;
85 void CubebDeviceEnumerator::Shutdown() {
86 StaticMutexAutoLock
lock(sInstanceMutex
);
92 CubebDeviceEnumerator::~CubebDeviceEnumerator() {
94 mozilla::mscom::EnsureMTA([&]() -> void {
96 int rv
= cubeb_register_device_collection_changed(
97 GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT
, nullptr, this);
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
) {
107 "Could not unregister the audio input"
108 " device collection changed callback.");
115 RefPtr
<const AudioDeviceSet
>
116 CubebDeviceEnumerator::EnumerateAudioInputDevices() {
117 return EnumerateAudioDevices(Side::INPUT
);
120 RefPtr
<const AudioDeviceSet
>
121 CubebDeviceEnumerator::EnumerateAudioOutputDevices() {
122 return EnumerateAudioDevices(Side::OUTPUT
);
126 static uint16_t ConvertCubebType(cubeb_device_type aType
) {
128 nsIAudioDeviceInfo::TYPE_UNKNOWN
, // CUBEB_DEVICE_TYPE_UNKNOWN
129 nsIAudioDeviceInfo::TYPE_INPUT
, // CUBEB_DEVICE_TYPE_INPUT,
130 nsIAudioDeviceInfo::TYPE_OUTPUT
// CUBEB_DEVICE_TYPE_OUTPUT
135 static uint16_t ConvertCubebState(cubeb_device_state aState
) {
137 nsIAudioDeviceInfo::STATE_DISABLED
, // CUBEB_DEVICE_STATE_DISABLED
138 nsIAudioDeviceInfo::STATE_UNPLUGGED
, // CUBEB_DEVICE_STATE_UNPLUGGED
139 nsIAudioDeviceInfo::STATE_ENABLED
// CUBEB_DEVICE_STATE_ENABLED
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
;
165 static uint16_t ConvertCubebFormat(cubeb_device_fmt aFormat
) {
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
;
182 static RefPtr
<AudioDeviceSet
> GetDeviceCollection(Side aSide
) {
183 RefPtr set
= new AudioDeviceSet();
184 cubeb
* context
= GetCubebContext();
186 cubeb_device_collection collection
= {nullptr, 0};
188 mozilla::mscom::EnsureMTA([&]() -> void {
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) {
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
);
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
;
232 MOZ_ASSERT(aSide
== Side::OUTPUT
);
233 devicesCache
= &mOutputDevices
;
234 manualInvalidation
= mManualOutputInvalidation
;
237 cubeb
* context
= GetCubebContext();
239 return new AudioDeviceSet();
241 if (!manualInvalidation
) {
242 MutexAutoLock
lock(mMutex
);
244 return *devicesCache
;
249 cubeb_device_type type
= CUBEB_DEVICE_TYPE_UNKNOWN
;
250 uint32_t channels
= 0;
252 if (aSide
== Side::INPUT
) {
253 type
= CUBEB_DEVICE_TYPE_INPUT
;
255 name
= u
"Default audio input device"_ns
;
257 MOZ_ASSERT(aSide
== Side::OUTPUT
);
258 type
= CUBEB_DEVICE_TYPE_OUTPUT
;
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
));
274 RefPtr devices
= GetDeviceCollection(
275 (aSide
== Side::INPUT
) ? CubebUtils::Input
: CubebUtils::Output
);
278 MutexAutoLock
lock(mMutex
);
279 *devicesCache
= 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();
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();
309 void CubebDeviceEnumerator::InputAudioDeviceListChanged_s(cubeb
* aContext
,
311 CubebDeviceEnumerator
* self
= reinterpret_cast<CubebDeviceEnumerator
*>(aUser
);
312 self
->AudioDeviceListChanged(CubebDeviceEnumerator::Side::INPUT
);
315 void CubebDeviceEnumerator::OutputAudioDeviceListChanged_s(cubeb
* aContext
,
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();
327 MOZ_ASSERT(aSide
== Side::OUTPUT
);
328 mOutputDevices
= nullptr;
329 mOnOutputDeviceListChange
.Notify();
333 } // namespace mozilla