1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
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 "CubebUtils.h"
10 # include "CubebDeviceEnumerator.h"
12 #include "MediaInfo.h"
13 #include "mozilla/AbstractThread.h"
14 #include "mozilla/dom/ContentChild.h"
15 #include "mozilla/dom/AudioDeviceInfo.h"
16 #include "mozilla/ipc/FileDescriptor.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/Preferences.h"
19 #include "mozilla/Services.h"
20 #include "mozilla/Sprintf.h"
21 #include "mozilla/StaticMutex.h"
22 #include "mozilla/StaticPtr.h"
23 #include "mozilla/Telemetry.h"
24 #include "nsAutoRef.h"
26 #include "nsIStringBundle.h"
28 #include "nsThreadUtils.h"
32 #ifdef MOZ_WIDGET_ANDROID
33 # include "GeneratedJNIWrappers.h"
36 # include "mozilla/mscom/EnsureMTA.h"
39 #define AUDIOIPC_POOL_SIZE_DEFAULT 2
40 #define AUDIOIPC_STACK_SIZE_DEFAULT (64 * 4096)
42 #define PREF_VOLUME_SCALE "media.volume_scale"
43 #define PREF_CUBEB_BACKEND "media.cubeb.backend"
44 #define PREF_CUBEB_OUTPUT_DEVICE "media.cubeb.output_device"
45 #define PREF_CUBEB_LATENCY_PLAYBACK "media.cubeb_latency_playback_ms"
46 #define PREF_CUBEB_LATENCY_MSG "media.cubeb_latency_msg_frames"
47 // Allows to get something non-default for the preferred sample-rate, to allow
48 // troubleshooting in the field and testing.
49 #define PREF_CUBEB_FORCE_SAMPLE_RATE "media.cubeb.force_sample_rate"
50 #define PREF_CUBEB_LOGGING_LEVEL "media.cubeb.logging_level"
51 // Hidden pref used by tests to force failure to obtain cubeb context
52 #define PREF_CUBEB_FORCE_NULL_CONTEXT "media.cubeb.force_null_context"
53 // Hidden pref to disable BMO 1427011 experiment; can be removed once proven.
54 #define PREF_CUBEB_DISABLE_DEVICE_SWITCHING \
55 "media.cubeb.disable_device_switching"
56 #define PREF_CUBEB_SANDBOX "media.cubeb.sandbox"
57 #define PREF_AUDIOIPC_POOL_SIZE "media.audioipc.pool_size"
58 #define PREF_AUDIOIPC_STACK_SIZE "media.audioipc.stack_size"
60 #if (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)) || \
61 defined(XP_MACOSX) || (defined(XP_WIN) && !defined(_ARM64_))
62 # define MOZ_CUBEB_REMOTING
67 // This must match AudioIpcInitParams in media/audioipc/client/src/lib.rs.
68 // TODO: Generate this from the Rust definition rather than duplicating it.
69 struct AudioIpcInitParams
{
70 mozilla::ipc::FileDescriptor::PlatformHandleType mServerConnection
;
73 void (*mThreadCreateCallback
)(const char*);
76 // These functions are provided by audioipc-server crate
77 extern void* audioipc_server_start(const char*, const char*);
78 extern mozilla::ipc::FileDescriptor::PlatformHandleType
79 audioipc_server_new_client(void*);
80 extern void audioipc_server_stop(void*);
81 // These functions are provided by audioipc-client crate
82 extern int audioipc_client_init(cubeb
**, const char*,
83 const AudioIpcInitParams
*);
85 extern void audioipc_init_threads(const AudioIpcInitParams
*);
93 LazyLogModule
gCubebLog("cubeb");
95 void CubebLogCallback(const char* aFmt
, ...) {
99 va_start(arglist
, aFmt
);
100 VsprintfLiteral(buffer
, aFmt
, arglist
);
101 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("%s", buffer
));
105 // This mutex protects the variables below.
107 enum class CubebState
{
111 } sCubebState
= CubebState::Uninitialized
;
112 cubeb
* sCubebContext
;
113 double sVolumeScale
= 1.0;
114 uint32_t sCubebPlaybackLatencyInMilliseconds
= 100;
115 uint32_t sCubebMSGLatencyInFrames
= 512;
116 // If sCubebForcedSampleRate is zero, PreferredSampleRate will return the
117 // preferred sample-rate for the audio backend in use. Otherwise, it will be
118 // used as the preferred sample-rate.
119 uint32_t sCubebForcedSampleRate
= 0;
120 bool sCubebPlaybackLatencyPrefSet
= false;
121 bool sCubebMSGLatencyPrefSet
= false;
122 bool sAudioStreamInitEverSucceeded
= false;
123 bool sCubebForceNullContext
= false;
124 bool sCubebDisableDeviceSwitching
= true;
125 #ifdef MOZ_CUBEB_REMOTING
126 bool sCubebSandbox
= false;
127 size_t sAudioIPCPoolSize
;
128 size_t sAudioIPCStackSize
;
130 StaticAutoPtr
<char> sBrandName
;
131 StaticAutoPtr
<char> sCubebBackendName
;
132 StaticAutoPtr
<char> sCubebOutputDeviceName
;
133 #ifdef MOZ_WIDGET_ANDROID
134 // Counts the number of time a request for switching to global "communication
135 // mode" has been received. If this is > 0, global communication mode is to be
136 // enabled. If it is 0, the global communication mode is to be disabled.
137 // This allows to correctly track the global behaviour to adopt accross
138 // asynchronous GraphDriver changes, on Android.
139 int sInCommunicationCount
= 0;
142 const char kBrandBundleURL
[] = "chrome://branding/locale/brand.properties";
144 const char* AUDIOSTREAM_BACKEND_ID_STR
[] = {
145 "jack", "pulse", "alsa", "audiounit", "audioqueue", "wasapi",
146 "winmm", "directsound", "sndio", "opensl", "audiotrack", "kai"};
147 /* Index for failures to create an audio stream the first time. */
148 const int CUBEB_BACKEND_INIT_FAILURE_FIRST
=
149 ArrayLength(AUDIOSTREAM_BACKEND_ID_STR
);
150 /* Index for failures to create an audio stream after the first time */
151 const int CUBEB_BACKEND_INIT_FAILURE_OTHER
=
152 CUBEB_BACKEND_INIT_FAILURE_FIRST
+ 1;
153 /* Index for an unknown backend. */
154 const int CUBEB_BACKEND_UNKNOWN
= CUBEB_BACKEND_INIT_FAILURE_FIRST
+ 2;
156 // Prefered samplerate, in Hz (characteristic of the hardware, mixer, platform,
159 // sMutex protects *initialization* of this, which must be performed from each
160 // thread before fetching, after which it is safe to fetch without holding the
161 // mutex because it is only written once per process execution (by the first
162 // initialization to complete). Since the init must have been called on a
163 // given thread before fetching the value, it's guaranteed (via the mutex) that
164 // sufficient memory barriers have occurred to ensure the correct value is
165 // visible on the querying thread/CPU.
166 uint32_t sPreferredSampleRate
;
168 #ifdef MOZ_CUBEB_REMOTING
169 // AudioIPC server handle
170 void* sServerHandle
= nullptr;
172 // Initialized during early startup, protected by sMutex.
173 StaticAutoPtr
<ipc::FileDescriptor
> sIPCConnection
;
175 static bool StartAudioIPCServer() {
176 sServerHandle
= audioipc_server_start(sBrandName
, sCubebBackendName
);
177 return sServerHandle
!= nullptr;
180 static void ShutdownAudioIPCServer() {
181 if (!sServerHandle
) {
185 audioipc_server_stop(sServerHandle
);
186 sServerHandle
= nullptr;
188 #endif // MOZ_CUBEB_REMOTING
191 static const uint32_t CUBEB_NORMAL_LATENCY_MS
= 100;
192 // Consevative default that can work on all platforms.
193 static const uint32_t CUBEB_NORMAL_LATENCY_FRAMES
= 1024;
195 namespace CubebUtils
{
196 cubeb
* GetCubebContextUnlocked();
198 void GetPrefAndSetString(const char* aPref
, StaticAutoPtr
<char>& aStorage
) {
200 Preferences::GetCString(aPref
, value
);
201 if (value
.IsEmpty()) {
204 aStorage
= new char[value
.Length() + 1];
205 PodCopy(aStorage
.get(), value
.get(), value
.Length());
206 aStorage
[value
.Length()] = 0;
210 void PrefChanged(const char* aPref
, void* aClosure
) {
211 if (strcmp(aPref
, PREF_VOLUME_SCALE
) == 0) {
213 Preferences::GetCString(aPref
, value
);
214 StaticMutexAutoLock
lock(sMutex
);
215 if (value
.IsEmpty()) {
218 sVolumeScale
= std::max
<double>(0, PR_strtod(value
.get(), nullptr));
220 } else if (strcmp(aPref
, PREF_CUBEB_LATENCY_PLAYBACK
) == 0) {
221 StaticMutexAutoLock
lock(sMutex
);
222 // Arbitrary default stream latency of 100ms. The higher this
223 // value, the longer stream volume changes will take to become
225 sCubebPlaybackLatencyPrefSet
= Preferences::HasUserValue(aPref
);
226 uint32_t value
= Preferences::GetUint(aPref
, CUBEB_NORMAL_LATENCY_MS
);
227 sCubebPlaybackLatencyInMilliseconds
=
228 std::min
<uint32_t>(std::max
<uint32_t>(value
, 1), 1000);
229 } else if (strcmp(aPref
, PREF_CUBEB_LATENCY_MSG
) == 0) {
230 StaticMutexAutoLock
lock(sMutex
);
231 sCubebMSGLatencyPrefSet
= Preferences::HasUserValue(aPref
);
232 uint32_t value
= Preferences::GetUint(aPref
, CUBEB_NORMAL_LATENCY_FRAMES
);
233 // 128 is the block size for the Web Audio API, which limits how low the
234 // latency can be here.
235 // We don't want to limit the upper limit too much, so that people can
237 sCubebMSGLatencyInFrames
=
238 std::min
<uint32_t>(std::max
<uint32_t>(value
, 128), 1e6
);
239 } else if (strcmp(aPref
, PREF_CUBEB_FORCE_SAMPLE_RATE
) == 0) {
240 StaticMutexAutoLock
lock(sMutex
);
241 sCubebForcedSampleRate
= Preferences::GetUint(aPref
);
242 } else if (strcmp(aPref
, PREF_CUBEB_LOGGING_LEVEL
) == 0) {
244 Preferences::GetCString(aPref
, value
);
245 LogModule
* cubebLog
= LogModule::Get("cubeb");
246 if (value
.EqualsLiteral("verbose")) {
247 cubeb_set_log_callback(CUBEB_LOG_VERBOSE
, CubebLogCallback
);
248 cubebLog
->SetLevel(LogLevel::Verbose
);
249 } else if (value
.EqualsLiteral("normal")) {
250 cubeb_set_log_callback(CUBEB_LOG_NORMAL
, CubebLogCallback
);
251 cubebLog
->SetLevel(LogLevel::Error
);
252 } else if (value
.IsEmpty()) {
253 cubeb_set_log_callback(CUBEB_LOG_DISABLED
, nullptr);
254 cubebLog
->SetLevel(LogLevel::Disabled
);
256 } else if (strcmp(aPref
, PREF_CUBEB_BACKEND
) == 0) {
257 StaticMutexAutoLock
lock(sMutex
);
258 GetPrefAndSetString(aPref
, sCubebBackendName
);
259 } else if (strcmp(aPref
, PREF_CUBEB_OUTPUT_DEVICE
) == 0) {
260 StaticMutexAutoLock
lock(sMutex
);
261 GetPrefAndSetString(aPref
, sCubebOutputDeviceName
);
262 } else if (strcmp(aPref
, PREF_CUBEB_FORCE_NULL_CONTEXT
) == 0) {
263 StaticMutexAutoLock
lock(sMutex
);
264 sCubebForceNullContext
= Preferences::GetBool(aPref
, false);
265 MOZ_LOG(gCubebLog
, LogLevel::Verbose
,
266 ("%s: %s", PREF_CUBEB_FORCE_NULL_CONTEXT
,
267 sCubebForceNullContext
? "true" : "false"));
268 } else if (strcmp(aPref
, PREF_CUBEB_DISABLE_DEVICE_SWITCHING
) == 0) {
269 StaticMutexAutoLock
lock(sMutex
);
270 sCubebDisableDeviceSwitching
= Preferences::GetBool(aPref
, true);
271 MOZ_LOG(gCubebLog
, LogLevel::Verbose
,
272 ("%s: %s", PREF_CUBEB_DISABLE_DEVICE_SWITCHING
,
273 sCubebDisableDeviceSwitching
? "true" : "false"));
275 #ifdef MOZ_CUBEB_REMOTING
276 else if (strcmp(aPref
, PREF_CUBEB_SANDBOX
) == 0) {
277 StaticMutexAutoLock
lock(sMutex
);
278 sCubebSandbox
= Preferences::GetBool(aPref
);
279 MOZ_LOG(gCubebLog
, LogLevel::Verbose
,
280 ("%s: %s", PREF_CUBEB_SANDBOX
, sCubebSandbox
? "true" : "false"));
281 } else if (strcmp(aPref
, PREF_AUDIOIPC_POOL_SIZE
) == 0) {
282 StaticMutexAutoLock
lock(sMutex
);
283 sAudioIPCPoolSize
= Preferences::GetUint(PREF_AUDIOIPC_POOL_SIZE
,
284 AUDIOIPC_POOL_SIZE_DEFAULT
);
285 } else if (strcmp(aPref
, PREF_AUDIOIPC_STACK_SIZE
) == 0) {
286 StaticMutexAutoLock
lock(sMutex
);
287 sAudioIPCStackSize
= Preferences::GetUint(PREF_AUDIOIPC_STACK_SIZE
,
288 AUDIOIPC_STACK_SIZE_DEFAULT
);
293 bool GetFirstStream() {
294 static bool sFirstStream
= true;
296 StaticMutexAutoLock
lock(sMutex
);
297 bool result
= sFirstStream
;
298 sFirstStream
= false;
302 double GetVolumeScale() {
303 StaticMutexAutoLock
lock(sMutex
);
307 cubeb
* GetCubebContext() {
308 StaticMutexAutoLock
lock(sMutex
);
309 return GetCubebContextUnlocked();
312 // This is only exported when running tests.
313 void ForceSetCubebContext(cubeb
* aCubebContext
) {
314 StaticMutexAutoLock
lock(sMutex
);
315 sCubebContext
= aCubebContext
;
316 sCubebState
= CubebState::Initialized
;
319 void SetInCommunication(bool aInCommunication
) {
320 #ifdef MOZ_WIDGET_ANDROID
321 StaticMutexAutoLock
lock(sMutex
);
322 if (aInCommunication
) {
323 sInCommunicationCount
++;
325 MOZ_ASSERT(sInCommunicationCount
> 0);
326 sInCommunicationCount
--;
329 if (sInCommunicationCount
== 1) {
330 java::GeckoAppShell::SetCommunicationAudioModeOn(true);
331 } else if (sInCommunicationCount
== 0) {
332 java::GeckoAppShell::SetCommunicationAudioModeOn(false);
337 bool InitPreferredSampleRate() {
338 StaticMutexAutoLock
lock(sMutex
);
339 if (sPreferredSampleRate
!= 0) {
342 #ifdef MOZ_WIDGET_ANDROID
343 sPreferredSampleRate
= AndroidGetAudioOutputSampleRate();
345 cubeb
* context
= GetCubebContextUnlocked();
349 if (cubeb_get_preferred_sample_rate(context
, &sPreferredSampleRate
) !=
354 MOZ_ASSERT(sPreferredSampleRate
);
358 uint32_t PreferredSampleRate() {
359 if (sCubebForcedSampleRate
) {
360 return sCubebForcedSampleRate
;
362 if (!InitPreferredSampleRate()) {
365 MOZ_ASSERT(sPreferredSampleRate
);
366 return sPreferredSampleRate
;
369 void InitBrandName() {
373 nsAutoString brandName
;
374 nsCOMPtr
<nsIStringBundleService
> stringBundleService
=
375 mozilla::services::GetStringBundleService();
376 if (stringBundleService
) {
377 nsCOMPtr
<nsIStringBundle
> brandBundle
;
378 nsresult rv
= stringBundleService
->CreateBundle(
379 kBrandBundleURL
, getter_AddRefs(brandBundle
));
380 if (NS_SUCCEEDED(rv
)) {
381 rv
= brandBundle
->GetStringFromName("brandShortName", brandName
);
382 NS_WARNING_ASSERTION(
384 "Could not get the program name for a cubeb stream.");
387 NS_LossyConvertUTF16toASCII
ascii(brandName
);
388 sBrandName
= new char[ascii
.Length() + 1];
389 PodCopy(sBrandName
.get(), ascii
.get(), ascii
.Length());
390 sBrandName
[ascii
.Length()] = 0;
393 #ifdef MOZ_CUBEB_REMOTING
394 void InitAudioIPCConnection() {
395 MOZ_ASSERT(NS_IsMainThread());
396 auto contentChild
= dom::ContentChild::GetSingleton();
397 auto promise
= contentChild
->SendCreateAudioIPCConnection();
399 AbstractThread::MainThread(), __func__
,
400 [](dom::FileDescOrError
&& aFD
) {
401 StaticMutexAutoLock
lock(sMutex
);
402 MOZ_ASSERT(!sIPCConnection
);
403 if (aFD
.type() == dom::FileDescOrError::Type::TFileDescriptor
) {
404 sIPCConnection
= new ipc::FileDescriptor(std::move(aFD
));
406 MOZ_LOG(gCubebLog
, LogLevel::Error
,
407 ("SendCreateAudioIPCConnection failed: invalid FD"));
410 [](mozilla::ipc::ResponseRejectReason
&& aReason
) {
411 MOZ_LOG(gCubebLog
, LogLevel::Error
,
412 ("SendCreateAudioIPCConnection rejected: %d", int(aReason
)));
417 ipc::FileDescriptor
CreateAudioIPCConnection() {
418 #ifdef MOZ_CUBEB_REMOTING
419 MOZ_ASSERT(sCubebSandbox
&& XRE_IsParentProcess());
420 if (!sServerHandle
) {
421 MOZ_LOG(gCubebLog
, LogLevel::Debug
, ("Starting cubeb server..."));
422 if (!StartAudioIPCServer()) {
423 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("audioipc_server_start failed"));
424 return ipc::FileDescriptor();
427 MOZ_ASSERT(sServerHandle
);
428 ipc::FileDescriptor::PlatformHandleType rawFD
=
429 audioipc_server_new_client(sServerHandle
);
430 ipc::FileDescriptor
fd(rawFD
);
432 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("audioipc_server_new_client failed"));
433 return ipc::FileDescriptor();
435 // Close rawFD since FileDescriptor's ctor cloned it.
436 // TODO: Find cleaner cross-platform way to close rawFD.
444 return ipc::FileDescriptor();
448 #if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
449 void InitAudioThreads() {
450 AudioIpcInitParams initParams
;
451 initParams
.mPoolSize
= sAudioIPCPoolSize
;
452 initParams
.mStackSize
= sAudioIPCStackSize
;
453 initParams
.mThreadCreateCallback
= [](const char* aName
) {
454 PROFILER_REGISTER_THREAD(aName
);
456 audioipc_init_threads(&initParams
);
460 cubeb
* GetCubebContextUnlocked() {
461 sMutex
.AssertCurrentThreadOwns();
462 if (sCubebForceNullContext
) {
463 // Pref set such that we should return a null context
464 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
465 ("%s: returning null context due to %s!", __func__
,
466 PREF_CUBEB_FORCE_NULL_CONTEXT
));
469 if (recordreplay::IsRecordingOrReplaying()) {
470 // Media is not supported when recording or replaying. See bug 1304146.
473 if (sCubebState
!= CubebState::Uninitialized
) {
474 // If we have already passed the initialization point (below), just return
475 // the current context, which may be null (e.g., after error or shutdown.)
476 return sCubebContext
;
479 if (!sBrandName
&& NS_IsMainThread()) {
482 NS_WARNING_ASSERTION(
484 "Did not initialize sbrandName, and not on the main thread?");
487 int rv
= CUBEB_ERROR
;
488 #ifdef MOZ_CUBEB_REMOTING
489 MOZ_LOG(gCubebLog
, LogLevel::Info
,
490 ("%s: %s", PREF_CUBEB_SANDBOX
, sCubebSandbox
? "true" : "false"));
493 if (XRE_IsParentProcess() && !sIPCConnection
) {
494 // TODO: Don't use audio IPC when within the same process.
495 auto fd
= CreateAudioIPCConnection();
497 sIPCConnection
= new ipc::FileDescriptor(fd
);
500 if (NS_WARN_IF(!sIPCConnection
)) {
501 // Either the IPC connection failed to init or we're still waiting for
502 // InitAudioIPCConnection to complete (bug 1454782).
506 AudioIpcInitParams initParams
;
507 initParams
.mPoolSize
= sAudioIPCPoolSize
;
508 initParams
.mStackSize
= sAudioIPCStackSize
;
509 initParams
.mServerConnection
=
510 sIPCConnection
->ClonePlatformHandle().release();
511 initParams
.mThreadCreateCallback
= [](const char* aName
) {
512 PROFILER_REGISTER_THREAD(aName
);
515 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
516 ("%s: %d", PREF_AUDIOIPC_POOL_SIZE
, (int)initParams
.mPoolSize
));
517 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
518 ("%s: %d", PREF_AUDIOIPC_STACK_SIZE
, (int)initParams
.mStackSize
));
520 rv
= audioipc_client_init(&sCubebContext
, sBrandName
, &initParams
);
522 #endif // MOZ_CUBEB_REMOTING
524 mozilla::mscom::EnsureMTA([&]() -> void {
526 rv
= cubeb_init(&sCubebContext
, sBrandName
, sCubebBackendName
);
530 #ifdef MOZ_CUBEB_REMOTING
532 sIPCConnection
= nullptr;
533 #endif // MOZ_CUBEB_REMOTING
534 NS_WARNING_ASSERTION(rv
== CUBEB_OK
, "Could not get a cubeb context.");
536 (rv
== CUBEB_OK
) ? CubebState::Initialized
: CubebState::Uninitialized
;
538 return sCubebContext
;
541 void ReportCubebBackendUsed() {
542 StaticMutexAutoLock
lock(sMutex
);
544 sAudioStreamInitEverSucceeded
= true;
546 bool foundBackend
= false;
547 for (uint32_t i
= 0; i
< ArrayLength(AUDIOSTREAM_BACKEND_ID_STR
); i
++) {
548 if (!strcmp(cubeb_get_backend_id(sCubebContext
),
549 AUDIOSTREAM_BACKEND_ID_STR
[i
])) {
550 Telemetry::Accumulate(Telemetry::AUDIOSTREAM_BACKEND_USED
, i
);
555 Telemetry::Accumulate(Telemetry::AUDIOSTREAM_BACKEND_USED
,
556 CUBEB_BACKEND_UNKNOWN
);
560 void ReportCubebStreamInitFailure(bool aIsFirst
) {
561 StaticMutexAutoLock
lock(sMutex
);
562 if (!aIsFirst
&& !sAudioStreamInitEverSucceeded
) {
563 // This machine has no audio hardware, or it's in really bad shape, don't
564 // send this info, since we want CUBEB_BACKEND_INIT_FAILURE_OTHER to detect
565 // failures to open multiple streams in a process over time.
568 Telemetry::Accumulate(Telemetry::AUDIOSTREAM_BACKEND_USED
,
569 aIsFirst
? CUBEB_BACKEND_INIT_FAILURE_FIRST
570 : CUBEB_BACKEND_INIT_FAILURE_OTHER
);
573 uint32_t GetCubebPlaybackLatencyInMilliseconds() {
574 StaticMutexAutoLock
lock(sMutex
);
575 return sCubebPlaybackLatencyInMilliseconds
;
578 bool CubebPlaybackLatencyPrefSet() {
579 StaticMutexAutoLock
lock(sMutex
);
580 return sCubebPlaybackLatencyPrefSet
;
583 bool CubebMSGLatencyPrefSet() {
584 StaticMutexAutoLock
lock(sMutex
);
585 return sCubebMSGLatencyPrefSet
;
588 uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params
* params
) {
589 StaticMutexAutoLock
lock(sMutex
);
590 if (sCubebMSGLatencyPrefSet
) {
591 MOZ_ASSERT(sCubebMSGLatencyInFrames
> 0);
592 return sCubebMSGLatencyInFrames
;
595 #ifdef MOZ_WIDGET_ANDROID
596 return AndroidGetAudioOutputFramesPerBuffer();
598 cubeb
* context
= GetCubebContextUnlocked();
600 return sCubebMSGLatencyInFrames
; // default 512
602 uint32_t latency_frames
= 0;
603 if (cubeb_get_min_latency(context
, params
, &latency_frames
) != CUBEB_OK
) {
604 NS_WARNING("Could not get minimal latency from cubeb.");
605 return sCubebMSGLatencyInFrames
; // default 512
607 return latency_frames
;
611 static const char* gInitCallbackPrefs
[] = {
612 PREF_VOLUME_SCALE
, PREF_CUBEB_OUTPUT_DEVICE
,
613 PREF_CUBEB_LATENCY_PLAYBACK
, PREF_CUBEB_LATENCY_MSG
,
614 PREF_CUBEB_BACKEND
, PREF_CUBEB_FORCE_NULL_CONTEXT
,
615 PREF_CUBEB_SANDBOX
, PREF_AUDIOIPC_POOL_SIZE
,
616 PREF_AUDIOIPC_STACK_SIZE
, nullptr,
618 static const char* gCallbackPrefs
[] = {
619 PREF_CUBEB_FORCE_SAMPLE_RATE
,
620 // We don't want to call the callback on startup, because the pref is the
621 // empty string by default ("", which means "logging disabled"). Because the
622 // logging can be enabled via environment variables (MOZ_LOG="module:5"),
623 // calling this callback on init would immediately re-disable the logging.
624 PREF_CUBEB_LOGGING_LEVEL
,
629 Preferences::RegisterCallbacksAndCall(PrefChanged
, gInitCallbackPrefs
);
630 Preferences::RegisterCallbacks(PrefChanged
, gCallbackPrefs
);
632 if (MOZ_LOG_TEST(gCubebLog
, LogLevel::Verbose
)) {
633 cubeb_set_log_callback(CUBEB_LOG_VERBOSE
, CubebLogCallback
);
634 } else if (MOZ_LOG_TEST(gCubebLog
, LogLevel::Error
)) {
635 cubeb_set_log_callback(CUBEB_LOG_NORMAL
, CubebLogCallback
);
638 #ifndef MOZ_WIDGET_ANDROID
639 AbstractThread::MainThread()->Dispatch(
640 NS_NewRunnableFunction("CubebUtils::InitLibrary", &InitBrandName
));
642 #ifdef MOZ_CUBEB_REMOTING
643 if (sCubebSandbox
&& XRE_IsContentProcess() && !recordreplay::IsMiddleman()) {
644 InitAudioIPCConnection();
649 void ShutdownLibrary() {
650 Preferences::UnregisterCallbacks(PrefChanged
, gInitCallbackPrefs
);
651 Preferences::UnregisterCallbacks(PrefChanged
, gCallbackPrefs
);
654 // This must be done before cubeb destroy.
655 CubebDeviceEnumerator::Shutdown();
658 StaticMutexAutoLock
lock(sMutex
);
660 cubeb_destroy(sCubebContext
);
661 sCubebContext
= nullptr;
663 sBrandName
= nullptr;
664 sCubebBackendName
= nullptr;
665 // This will ensure we don't try to re-create a context.
666 sCubebState
= CubebState::Shutdown
;
668 #ifdef MOZ_CUBEB_REMOTING
669 sIPCConnection
= nullptr;
670 ShutdownAudioIPCServer();
674 uint32_t MaxNumberOfChannels() {
675 cubeb
* cubebContext
= GetCubebContext();
676 uint32_t maxNumberOfChannels
;
677 if (cubebContext
&& cubeb_get_max_channel_count(
678 cubebContext
, &maxNumberOfChannels
) == CUBEB_OK
) {
679 return maxNumberOfChannels
;
685 void GetCurrentBackend(nsAString
& aBackend
) {
686 cubeb
* cubebContext
= GetCubebContext();
688 const char* backend
= cubeb_get_backend_id(cubebContext
);
690 aBackend
.AssignASCII(backend
);
694 aBackend
.AssignLiteral("unknown");
697 char* GetForcedOutputDevice() {
698 StaticMutexAutoLock
lock(sMutex
);
699 return sCubebOutputDeviceName
;
702 cubeb_stream_prefs
GetDefaultStreamPrefs() {
704 // Investigation for bug 1427011 - if we're in E10S mode, rely on the
705 // AudioNotification IPC to detect device changes.
706 if (sCubebDisableDeviceSwitching
&&
707 (XRE_IsE10sParentProcess() || XRE_IsContentProcess())) {
708 return CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING
;
711 return CUBEB_STREAM_PREF_NONE
;
714 #ifdef MOZ_WIDGET_ANDROID
715 uint32_t AndroidGetAudioOutputSampleRate() {
716 int32_t sample_rate
= java::GeckoAppShell::GetAudioOutputSampleRate();
717 MOZ_ASSERT(sample_rate
> 0);
720 uint32_t AndroidGetAudioOutputFramesPerBuffer() {
721 int32_t frames
= java::GeckoAppShell::GetAudioOutputFramesPerBuffer();
722 MOZ_ASSERT(frames
> 0);
727 } // namespace CubebUtils
728 } // namespace mozilla