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"
9 #include "audio_thread_priority.h"
10 #include "mozilla/AbstractThread.h"
11 #include "mozilla/dom/ContentChild.h"
12 #include "mozilla/ipc/FileDescriptor.h"
13 #include "mozilla/Logging.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/Components.h"
16 #include "mozilla/Sprintf.h"
17 #include "mozilla/StaticMutex.h"
18 #include "mozilla/StaticPtr.h"
19 #include "mozilla/Telemetry.h"
20 #include "mozilla/UnderrunHandler.h"
21 #include "nsContentUtils.h"
23 #include "nsIStringBundle.h"
25 #include "nsThreadUtils.h"
29 #ifdef MOZ_WIDGET_ANDROID
30 # include "mozilla/java/GeckoAppShellWrappers.h"
33 # include "mozilla/mscom/EnsureMTA.h"
35 #include "audioipc2_server_ffi_generated.h"
36 #include "audioipc2_client_ffi_generated.h"
39 #include "CallbackThreadRegistry.h"
40 #include "mozilla/StaticPrefs_media.h"
42 #define AUDIOIPC_STACK_SIZE_DEFAULT (64 * 4096)
44 #define PREF_VOLUME_SCALE "media.volume_scale"
45 #define PREF_CUBEB_BACKEND "media.cubeb.backend"
46 #define PREF_CUBEB_OUTPUT_DEVICE "media.cubeb.output_device"
47 #define PREF_CUBEB_LATENCY_PLAYBACK "media.cubeb_latency_playback_ms"
48 #define PREF_CUBEB_LATENCY_MTG "media.cubeb_latency_mtg_frames"
49 // Allows to get something non-default for the preferred sample-rate, to allow
50 // troubleshooting in the field and testing.
51 #define PREF_CUBEB_FORCE_SAMPLE_RATE "media.cubeb.force_sample_rate"
52 #define PREF_CUBEB_LOGGING_LEVEL "logging.cubeb"
53 // Hidden pref used by tests to force failure to obtain cubeb context
54 #define PREF_CUBEB_FORCE_NULL_CONTEXT "media.cubeb.force_null_context"
55 #define PREF_CUBEB_OUTPUT_VOICE_ROUTING "media.cubeb.output_voice_routing"
56 #define PREF_CUBEB_SANDBOX "media.cubeb.sandbox"
57 #define PREF_AUDIOIPC_STACK_SIZE "media.audioipc.stack_size"
58 #define PREF_AUDIOIPC_SHM_AREA_SIZE "media.audioipc.shm_area_size"
60 #if defined(XP_LINUX) || defined(XP_MACOSX) || defined(XP_WIN)
61 # define MOZ_CUBEB_REMOTING
68 using Telemetry::LABELS_MEDIA_AUDIO_BACKEND
;
69 using Telemetry::LABELS_MEDIA_AUDIO_INIT_FAILURE
;
71 LazyLogModule
gCubebLog("cubeb");
73 void CubebLogCallback(const char* aFmt
, ...) {
77 va_start(arglist
, aFmt
);
78 VsprintfLiteral(buffer
, aFmt
, arglist
);
79 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("%s", buffer
));
83 // This mutex protects the variables below.
85 enum class CubebState
{
89 } sCubebState
= CubebState::Uninitialized
;
91 double sVolumeScale
= 1.0;
92 uint32_t sCubebPlaybackLatencyInMilliseconds
= 100;
93 uint32_t sCubebMTGLatencyInFrames
= 512;
94 // If sCubebForcedSampleRate is zero, PreferredSampleRate will return the
95 // preferred sample-rate for the audio backend in use. Otherwise, it will be
96 // used as the preferred sample-rate.
97 Atomic
<uint32_t> sCubebForcedSampleRate
{0};
98 bool sCubebPlaybackLatencyPrefSet
= false;
99 bool sCubebMTGLatencyPrefSet
= false;
100 bool sAudioStreamInitEverSucceeded
= false;
101 bool sCubebForceNullContext
= false;
102 bool sRouteOutputAsVoice
= false;
103 #ifdef MOZ_CUBEB_REMOTING
104 bool sCubebSandbox
= false;
105 size_t sAudioIPCStackSize
;
106 size_t sAudioIPCShmAreaSize
;
108 StaticAutoPtr
<char> sBrandName
;
109 StaticAutoPtr
<char> sCubebBackendName
;
110 StaticAutoPtr
<char> sCubebOutputDeviceName
;
111 #ifdef MOZ_WIDGET_ANDROID
112 // Counts the number of time a request for switching to global "communication
113 // mode" has been received. If this is > 0, global communication mode is to be
114 // enabled. If it is 0, the global communication mode is to be disabled.
115 // This allows to correctly track the global behaviour to adopt accross
116 // asynchronous GraphDriver changes, on Android.
117 int sInCommunicationCount
= 0;
120 const char kBrandBundleURL
[] = "chrome://branding/locale/brand.properties";
122 std::unordered_map
<std::string
, LABELS_MEDIA_AUDIO_BACKEND
>
123 kTelemetryBackendLabel
= {
124 {"audiounit", LABELS_MEDIA_AUDIO_BACKEND::audiounit
},
125 {"audiounit-rust", LABELS_MEDIA_AUDIO_BACKEND::audiounit_rust
},
126 {"aaudio", LABELS_MEDIA_AUDIO_BACKEND::aaudio
},
127 {"opensl", LABELS_MEDIA_AUDIO_BACKEND::opensl
},
128 {"wasapi", LABELS_MEDIA_AUDIO_BACKEND::wasapi
},
129 {"winmm", LABELS_MEDIA_AUDIO_BACKEND::winmm
},
130 {"alsa", LABELS_MEDIA_AUDIO_BACKEND::alsa
},
131 {"jack", LABELS_MEDIA_AUDIO_BACKEND::jack
},
132 {"oss", LABELS_MEDIA_AUDIO_BACKEND::oss
},
133 {"pulse", LABELS_MEDIA_AUDIO_BACKEND::pulse
},
134 {"pulse-rust", LABELS_MEDIA_AUDIO_BACKEND::pulse_rust
},
135 {"sndio", LABELS_MEDIA_AUDIO_BACKEND::sndio
},
136 {"sun", LABELS_MEDIA_AUDIO_BACKEND::sunaudio
},
139 // Prefered samplerate, in Hz (characteristic of the hardware, mixer, platform,
142 // sMutex protects *initialization* of this, which must be performed from each
143 // thread before fetching, after which it is safe to fetch without holding the
144 // mutex because it is only written once per process execution (by the first
145 // initialization to complete). Since the init must have been called on a
146 // given thread before fetching the value, it's guaranteed (via the mutex) that
147 // sufficient memory barriers have occurred to ensure the correct value is
148 // visible on the querying thread/CPU.
149 static Atomic
<uint32_t> sPreferredSampleRate
{0};
151 #ifdef MOZ_CUBEB_REMOTING
152 // AudioIPC server handle
153 void* sServerHandle
= nullptr;
155 // Initialized during early startup, protected by sMutex.
156 StaticAutoPtr
<ipc::FileDescriptor
> sIPCConnection
;
158 static bool StartAudioIPCServer() {
160 audioipc2::AudioIpcServerInitParams initParams
{};
161 initParams
.mThreadCreateCallback
= [](const char* aName
) {
162 PROFILER_REGISTER_THREAD(aName
);
164 initParams
.mThreadDestroyCallback
= []() { PROFILER_UNREGISTER_THREAD(); };
166 sServerHandle
= audioipc2::audioipc2_server_start(
167 sBrandName
, sCubebBackendName
, &initParams
);
169 return sServerHandle
!= nullptr;
172 static void ShutdownAudioIPCServer() {
173 if (!sServerHandle
) {
177 audioipc2::audioipc2_server_stop(sServerHandle
);
178 sServerHandle
= nullptr;
180 #endif // MOZ_CUBEB_REMOTING
183 static const uint32_t CUBEB_NORMAL_LATENCY_MS
= 100;
184 // Consevative default that can work on all platforms.
185 static const uint32_t CUBEB_NORMAL_LATENCY_FRAMES
= 1024;
187 namespace CubebUtils
{
188 cubeb
* GetCubebContextUnlocked();
190 void GetPrefAndSetString(const char* aPref
, StaticAutoPtr
<char>& aStorage
) {
192 Preferences::GetCString(aPref
, value
);
193 if (value
.IsEmpty()) {
196 aStorage
= new char[value
.Length() + 1];
197 PodCopy(aStorage
.get(), value
.get(), value
.Length());
198 aStorage
[value
.Length()] = 0;
202 void PrefChanged(const char* aPref
, void* aClosure
) {
203 if (strcmp(aPref
, PREF_VOLUME_SCALE
) == 0) {
205 Preferences::GetCString(aPref
, value
);
206 StaticMutexAutoLock
lock(sMutex
);
207 if (value
.IsEmpty()) {
210 sVolumeScale
= std::max
<double>(0, PR_strtod(value
.get(), nullptr));
212 } else if (strcmp(aPref
, PREF_CUBEB_LATENCY_PLAYBACK
) == 0) {
213 StaticMutexAutoLock
lock(sMutex
);
214 // Arbitrary default stream latency of 100ms. The higher this
215 // value, the longer stream volume changes will take to become
217 sCubebPlaybackLatencyPrefSet
= Preferences::HasUserValue(aPref
);
218 uint32_t value
= Preferences::GetUint(aPref
, CUBEB_NORMAL_LATENCY_MS
);
219 sCubebPlaybackLatencyInMilliseconds
=
220 std::min
<uint32_t>(std::max
<uint32_t>(value
, 1), 1000);
221 } else if (strcmp(aPref
, PREF_CUBEB_LATENCY_MTG
) == 0) {
222 StaticMutexAutoLock
lock(sMutex
);
223 sCubebMTGLatencyPrefSet
= Preferences::HasUserValue(aPref
);
224 uint32_t value
= Preferences::GetUint(aPref
, CUBEB_NORMAL_LATENCY_FRAMES
);
225 // 128 is the block size for the Web Audio API, which limits how low the
226 // latency can be here.
227 // We don't want to limit the upper limit too much, so that people can
229 sCubebMTGLatencyInFrames
=
230 std::min
<uint32_t>(std::max
<uint32_t>(value
, 128), 1e6
);
231 } else if (strcmp(aPref
, PREF_CUBEB_FORCE_SAMPLE_RATE
) == 0) {
232 StaticMutexAutoLock
lock(sMutex
);
233 sCubebForcedSampleRate
= Preferences::GetUint(aPref
);
234 } else if (strcmp(aPref
, PREF_CUBEB_LOGGING_LEVEL
) == 0) {
236 ToLogLevel(Preferences::GetInt(aPref
, 0 /* LogLevel::Disabled */));
237 if (value
== LogLevel::Verbose
) {
238 cubeb_set_log_callback(CUBEB_LOG_VERBOSE
, CubebLogCallback
);
239 } else if (value
== LogLevel::Debug
) {
240 cubeb_set_log_callback(CUBEB_LOG_NORMAL
, CubebLogCallback
);
241 } else if (value
== LogLevel::Disabled
) {
242 cubeb_set_log_callback(CUBEB_LOG_DISABLED
, nullptr);
244 } else if (strcmp(aPref
, PREF_CUBEB_BACKEND
) == 0) {
245 StaticMutexAutoLock
lock(sMutex
);
246 GetPrefAndSetString(aPref
, sCubebBackendName
);
247 } else if (strcmp(aPref
, PREF_CUBEB_OUTPUT_DEVICE
) == 0) {
248 StaticMutexAutoLock
lock(sMutex
);
249 GetPrefAndSetString(aPref
, sCubebOutputDeviceName
);
250 } else if (strcmp(aPref
, PREF_CUBEB_FORCE_NULL_CONTEXT
) == 0) {
251 StaticMutexAutoLock
lock(sMutex
);
252 sCubebForceNullContext
= Preferences::GetBool(aPref
, false);
253 MOZ_LOG(gCubebLog
, LogLevel::Verbose
,
254 ("%s: %s", PREF_CUBEB_FORCE_NULL_CONTEXT
,
255 sCubebForceNullContext
? "true" : "false"));
257 #ifdef MOZ_CUBEB_REMOTING
258 else if (strcmp(aPref
, PREF_CUBEB_SANDBOX
) == 0) {
259 StaticMutexAutoLock
lock(sMutex
);
260 sCubebSandbox
= Preferences::GetBool(aPref
);
261 MOZ_LOG(gCubebLog
, LogLevel::Verbose
,
262 ("%s: %s", PREF_CUBEB_SANDBOX
, sCubebSandbox
? "true" : "false"));
263 } else if (strcmp(aPref
, PREF_AUDIOIPC_STACK_SIZE
) == 0) {
264 StaticMutexAutoLock
lock(sMutex
);
265 sAudioIPCStackSize
= Preferences::GetUint(PREF_AUDIOIPC_STACK_SIZE
,
266 AUDIOIPC_STACK_SIZE_DEFAULT
);
267 } else if (strcmp(aPref
, PREF_AUDIOIPC_SHM_AREA_SIZE
) == 0) {
268 StaticMutexAutoLock
lock(sMutex
);
269 sAudioIPCShmAreaSize
= Preferences::GetUint(PREF_AUDIOIPC_SHM_AREA_SIZE
);
272 else if (strcmp(aPref
, PREF_CUBEB_OUTPUT_VOICE_ROUTING
) == 0) {
273 StaticMutexAutoLock
lock(sMutex
);
274 sRouteOutputAsVoice
= Preferences::GetBool(aPref
);
275 MOZ_LOG(gCubebLog
, LogLevel::Verbose
,
276 ("%s: %s", PREF_CUBEB_OUTPUT_VOICE_ROUTING
,
277 sRouteOutputAsVoice
? "true" : "false"));
281 bool GetFirstStream() {
282 static bool sFirstStream
= true;
284 StaticMutexAutoLock
lock(sMutex
);
285 bool result
= sFirstStream
;
286 sFirstStream
= false;
290 double GetVolumeScale() {
291 StaticMutexAutoLock
lock(sMutex
);
295 cubeb
* GetCubebContext() {
296 StaticMutexAutoLock
lock(sMutex
);
297 return GetCubebContextUnlocked();
300 // This is only exported when running tests.
301 void ForceSetCubebContext(cubeb
* aCubebContext
) {
302 StaticMutexAutoLock
lock(sMutex
);
304 cubeb_destroy(sCubebContext
);
306 sCubebContext
= aCubebContext
;
307 sCubebState
= CubebState::Initialized
;
310 void SetInCommunication(bool aInCommunication
) {
311 #ifdef MOZ_WIDGET_ANDROID
312 StaticMutexAutoLock
lock(sMutex
);
313 if (aInCommunication
) {
314 sInCommunicationCount
++;
316 MOZ_ASSERT(sInCommunicationCount
> 0);
317 sInCommunicationCount
--;
320 if (sInCommunicationCount
== 1) {
321 java::GeckoAppShell::SetCommunicationAudioModeOn(true);
322 } else if (sInCommunicationCount
== 0) {
323 java::GeckoAppShell::SetCommunicationAudioModeOn(false);
328 bool InitPreferredSampleRate() {
329 StaticMutexAutoLock
lock(sMutex
);
330 if (sPreferredSampleRate
!= 0) {
333 #ifdef MOZ_WIDGET_ANDROID
334 int rate
= AndroidGetAudioOutputSampleRate();
336 sPreferredSampleRate
= rate
;
342 cubeb
* context
= GetCubebContextUnlocked();
347 if (cubeb_get_preferred_sample_rate(context
, &rate
) != CUBEB_OK
) {
350 sPreferredSampleRate
= rate
;
352 MOZ_ASSERT(sPreferredSampleRate
);
356 uint32_t PreferredSampleRate(bool aShouldResistFingerprinting
) {
357 if (sCubebForcedSampleRate
) {
358 return sCubebForcedSampleRate
;
360 if (aShouldResistFingerprinting
) {
363 if (!InitPreferredSampleRate()) {
366 MOZ_ASSERT(sPreferredSampleRate
);
367 return sPreferredSampleRate
;
370 int CubebStreamInit(cubeb
* context
, cubeb_stream
** stream
,
371 char const* stream_name
, cubeb_devid input_device
,
372 cubeb_stream_params
* input_stream_params
,
373 cubeb_devid output_device
,
374 cubeb_stream_params
* output_stream_params
,
375 uint32_t latency_frames
, cubeb_data_callback data_callback
,
376 cubeb_state_callback state_callback
, void* user_ptr
) {
377 uint32_t ms
= StaticPrefs::media_cubeb_slow_stream_init_ms();
379 std::this_thread::sleep_for(std::chrono::milliseconds(ms
));
381 return cubeb_stream_init(context
, stream
, stream_name
, input_device
,
382 input_stream_params
, output_device
,
383 output_stream_params
, latency_frames
, data_callback
,
384 state_callback
, user_ptr
);
387 void InitBrandName() {
391 nsAutoString brandName
;
392 nsCOMPtr
<nsIStringBundleService
> stringBundleService
=
393 mozilla::components::StringBundle::Service();
394 if (stringBundleService
) {
395 nsCOMPtr
<nsIStringBundle
> brandBundle
;
396 nsresult rv
= stringBundleService
->CreateBundle(
397 kBrandBundleURL
, getter_AddRefs(brandBundle
));
398 if (NS_SUCCEEDED(rv
)) {
399 rv
= brandBundle
->GetStringFromName("brandShortName", brandName
);
400 NS_WARNING_ASSERTION(
402 "Could not get the program name for a cubeb stream.");
405 NS_LossyConvertUTF16toASCII
ascii(brandName
);
406 sBrandName
= new char[ascii
.Length() + 1];
407 PodCopy(sBrandName
.get(), ascii
.get(), ascii
.Length());
408 sBrandName
[ascii
.Length()] = 0;
411 #ifdef MOZ_CUBEB_REMOTING
412 void InitAudioIPCConnection() {
413 MOZ_ASSERT(NS_IsMainThread());
414 auto contentChild
= dom::ContentChild::GetSingleton();
415 auto promise
= contentChild
->SendCreateAudioIPCConnection();
417 AbstractThread::MainThread(), __func__
,
418 [](dom::FileDescOrError
&& aFD
) {
419 StaticMutexAutoLock
lock(sMutex
);
420 MOZ_ASSERT(!sIPCConnection
);
421 if (aFD
.type() == dom::FileDescOrError::Type::TFileDescriptor
) {
422 sIPCConnection
= new ipc::FileDescriptor(std::move(aFD
));
424 MOZ_LOG(gCubebLog
, LogLevel::Error
,
425 ("SendCreateAudioIPCConnection failed: invalid FD"));
428 [](mozilla::ipc::ResponseRejectReason
&& aReason
) {
429 MOZ_LOG(gCubebLog
, LogLevel::Error
,
430 ("SendCreateAudioIPCConnection rejected: %d", int(aReason
)));
435 #ifdef MOZ_CUBEB_REMOTING
436 ipc::FileDescriptor
CreateAudioIPCConnectionUnlocked() {
437 MOZ_ASSERT(sCubebSandbox
&& XRE_IsParentProcess());
438 if (!sServerHandle
) {
439 MOZ_LOG(gCubebLog
, LogLevel::Debug
, ("Starting cubeb server..."));
440 if (!StartAudioIPCServer()) {
441 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("audioipc_server_start failed"));
442 return ipc::FileDescriptor();
445 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
446 ("%s: %d", PREF_AUDIOIPC_SHM_AREA_SIZE
, (int)sAudioIPCShmAreaSize
));
447 MOZ_ASSERT(sServerHandle
);
448 ipc::FileDescriptor::PlatformHandleType rawFD
;
449 rawFD
= audioipc2::audioipc2_server_new_client(sServerHandle
,
450 sAudioIPCShmAreaSize
);
451 ipc::FileDescriptor
fd(rawFD
);
453 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("audioipc_server_new_client failed"));
454 return ipc::FileDescriptor();
456 // Close rawFD since FileDescriptor's ctor cloned it.
457 // TODO: Find cleaner cross-platform way to close rawFD.
467 ipc::FileDescriptor
CreateAudioIPCConnection() {
468 #ifdef MOZ_CUBEB_REMOTING
469 StaticMutexAutoLock
lock(sMutex
);
470 return CreateAudioIPCConnectionUnlocked();
472 return ipc::FileDescriptor();
476 cubeb
* GetCubebContextUnlocked() {
477 sMutex
.AssertCurrentThreadOwns();
478 if (sCubebForceNullContext
) {
479 // Pref set such that we should return a null context
480 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
481 ("%s: returning null context due to %s!", __func__
,
482 PREF_CUBEB_FORCE_NULL_CONTEXT
));
485 if (sCubebState
!= CubebState::Uninitialized
) {
486 // If we have already passed the initialization point (below), just return
487 // the current context, which may be null (e.g., after error or shutdown.)
488 return sCubebContext
;
491 if (!sBrandName
&& NS_IsMainThread()) {
494 NS_WARNING_ASSERTION(
496 "Did not initialize sbrandName, and not on the main thread?");
499 int rv
= CUBEB_ERROR
;
500 #ifdef MOZ_CUBEB_REMOTING
501 MOZ_LOG(gCubebLog
, LogLevel::Info
,
502 ("%s: %s", PREF_CUBEB_SANDBOX
, sCubebSandbox
? "true" : "false"));
505 if (XRE_IsParentProcess() && !sIPCConnection
) {
506 // TODO: Don't use audio IPC when within the same process.
507 auto fd
= CreateAudioIPCConnectionUnlocked();
509 sIPCConnection
= new ipc::FileDescriptor(fd
);
512 if (NS_WARN_IF(!sIPCConnection
)) {
513 // Either the IPC connection failed to init or we're still waiting for
514 // InitAudioIPCConnection to complete (bug 1454782).
518 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
519 ("%s: %d", PREF_AUDIOIPC_STACK_SIZE
, (int)sAudioIPCStackSize
));
521 audioipc2::AudioIpcInitParams initParams
{};
522 initParams
.mStackSize
= sAudioIPCStackSize
;
523 initParams
.mServerConnection
=
524 sIPCConnection
->ClonePlatformHandle().release();
525 initParams
.mThreadCreateCallback
= [](const char* aName
) {
526 PROFILER_REGISTER_THREAD(aName
);
528 initParams
.mThreadDestroyCallback
= []() { PROFILER_UNREGISTER_THREAD(); };
530 rv
= audioipc2::audioipc2_client_init(&sCubebContext
, sBrandName
,
533 #endif // MOZ_CUBEB_REMOTING
535 mozilla::mscom::EnsureMTA([&]() -> void {
537 rv
= cubeb_init(&sCubebContext
, sBrandName
, sCubebBackendName
);
541 #ifdef MOZ_CUBEB_REMOTING
543 sIPCConnection
= nullptr;
544 #endif // MOZ_CUBEB_REMOTING
545 NS_WARNING_ASSERTION(rv
== CUBEB_OK
, "Could not get a cubeb context.");
547 (rv
== CUBEB_OK
) ? CubebState::Initialized
: CubebState::Uninitialized
;
549 return sCubebContext
;
552 void ReportCubebBackendUsed() {
553 StaticMutexAutoLock
lock(sMutex
);
555 sAudioStreamInitEverSucceeded
= true;
557 LABELS_MEDIA_AUDIO_BACKEND label
= LABELS_MEDIA_AUDIO_BACKEND::unknown
;
559 kTelemetryBackendLabel
.find(cubeb_get_backend_id(sCubebContext
));
560 if (backend
!= kTelemetryBackendLabel
.end()) {
561 label
= backend
->second
;
563 AccumulateCategorical(label
);
566 void ReportCubebStreamInitFailure(bool aIsFirst
) {
567 StaticMutexAutoLock
lock(sMutex
);
568 if (!aIsFirst
&& !sAudioStreamInitEverSucceeded
) {
569 // This machine has no audio hardware, or it's in really bad shape, don't
570 // send this info, since we want CUBEB_BACKEND_INIT_FAILURE_OTHER to detect
571 // failures to open multiple streams in a process over time.
574 AccumulateCategorical(aIsFirst
? LABELS_MEDIA_AUDIO_INIT_FAILURE::first
575 : LABELS_MEDIA_AUDIO_INIT_FAILURE::other
);
578 uint32_t GetCubebPlaybackLatencyInMilliseconds() {
579 StaticMutexAutoLock
lock(sMutex
);
580 return sCubebPlaybackLatencyInMilliseconds
;
583 bool CubebPlaybackLatencyPrefSet() {
584 StaticMutexAutoLock
lock(sMutex
);
585 return sCubebPlaybackLatencyPrefSet
;
588 bool CubebMTGLatencyPrefSet() {
589 StaticMutexAutoLock
lock(sMutex
);
590 return sCubebMTGLatencyPrefSet
;
593 uint32_t GetCubebMTGLatencyInFrames(cubeb_stream_params
* params
) {
594 StaticMutexAutoLock
lock(sMutex
);
595 if (sCubebMTGLatencyPrefSet
) {
596 MOZ_ASSERT(sCubebMTGLatencyInFrames
> 0);
597 return sCubebMTGLatencyInFrames
;
600 #ifdef MOZ_WIDGET_ANDROID
601 int frames
= AndroidGetAudioOutputFramesPerBuffer();
608 cubeb
* context
= GetCubebContextUnlocked();
610 return sCubebMTGLatencyInFrames
; // default 512
612 uint32_t latency_frames
= 0;
613 if (cubeb_get_min_latency(context
, params
, &latency_frames
) != CUBEB_OK
) {
614 NS_WARNING("Could not get minimal latency from cubeb.");
615 return sCubebMTGLatencyInFrames
; // default 512
617 return latency_frames
;
621 static const char* gInitCallbackPrefs
[] = {
622 PREF_VOLUME_SCALE
, PREF_CUBEB_OUTPUT_DEVICE
,
623 PREF_CUBEB_LATENCY_PLAYBACK
, PREF_CUBEB_LATENCY_MTG
,
624 PREF_CUBEB_BACKEND
, PREF_CUBEB_FORCE_NULL_CONTEXT
,
625 PREF_CUBEB_SANDBOX
, PREF_AUDIOIPC_STACK_SIZE
,
626 PREF_AUDIOIPC_SHM_AREA_SIZE
, nullptr,
629 static const char* gCallbackPrefs
[] = {
630 PREF_CUBEB_FORCE_SAMPLE_RATE
,
631 // We don't want to call the callback on startup, because the pref is the
632 // empty string by default ("", which means "logging disabled"). Because the
633 // logging can be enabled via environment variables (MOZ_LOG="module:5"),
634 // calling this callback on init would immediately re-disable the logging.
635 PREF_CUBEB_LOGGING_LEVEL
,
640 Preferences::RegisterCallbacksAndCall(PrefChanged
, gInitCallbackPrefs
);
641 Preferences::RegisterCallbacks(PrefChanged
, gCallbackPrefs
);
643 if (MOZ_LOG_TEST(gCubebLog
, LogLevel::Verbose
)) {
644 cubeb_set_log_callback(CUBEB_LOG_VERBOSE
, CubebLogCallback
);
645 } else if (MOZ_LOG_TEST(gCubebLog
, LogLevel::Error
)) {
646 cubeb_set_log_callback(CUBEB_LOG_NORMAL
, CubebLogCallback
);
649 #ifndef MOZ_WIDGET_ANDROID
650 NS_DispatchToMainThread(
651 NS_NewRunnableFunction("CubebUtils::InitLibrary", &InitBrandName
));
653 #ifdef MOZ_CUBEB_REMOTING
654 if (sCubebSandbox
&& XRE_IsContentProcess()) {
655 # if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
656 if (atp_set_real_time_limit(0, 48000)) {
657 NS_WARNING("could not set real-time limit in CubebUtils::InitLibrary");
659 InstallSoftRealTimeLimitHandler();
661 InitAudioIPCConnection();
665 // Ensure the CallbackThreadRegistry is not created in an audio callback by
667 Unused
<< CallbackThreadRegistry::Get();
670 void ShutdownLibrary() {
671 Preferences::UnregisterCallbacks(PrefChanged
, gInitCallbackPrefs
);
672 Preferences::UnregisterCallbacks(PrefChanged
, gCallbackPrefs
);
674 StaticMutexAutoLock
lock(sMutex
);
675 cubeb_set_log_callback(CUBEB_LOG_DISABLED
, nullptr);
677 cubeb_destroy(sCubebContext
);
678 sCubebContext
= nullptr;
680 sBrandName
= nullptr;
681 sCubebBackendName
= nullptr;
682 // This will ensure we don't try to re-create a context.
683 sCubebState
= CubebState::Shutdown
;
685 #ifdef MOZ_CUBEB_REMOTING
686 sIPCConnection
= nullptr;
687 ShutdownAudioIPCServer();
691 bool SandboxEnabled() {
692 #ifdef MOZ_CUBEB_REMOTING
693 StaticMutexAutoLock
lock(sMutex
);
694 return !!sCubebSandbox
;
700 uint32_t MaxNumberOfChannels() {
701 cubeb
* cubebContext
= GetCubebContext();
702 uint32_t maxNumberOfChannels
;
703 if (cubebContext
&& cubeb_get_max_channel_count(
704 cubebContext
, &maxNumberOfChannels
) == CUBEB_OK
) {
705 return maxNumberOfChannels
;
711 void GetCurrentBackend(nsAString
& aBackend
) {
712 cubeb
* cubebContext
= GetCubebContext();
714 const char* backend
= cubeb_get_backend_id(cubebContext
);
716 aBackend
.AssignASCII(backend
);
720 aBackend
.AssignLiteral("unknown");
723 char* GetForcedOutputDevice() {
724 StaticMutexAutoLock
lock(sMutex
);
725 return sCubebOutputDeviceName
;
728 cubeb_stream_prefs
GetDefaultStreamPrefs(cubeb_device_type aType
) {
729 cubeb_stream_prefs prefs
= CUBEB_STREAM_PREF_NONE
;
731 if (StaticPrefs::media_cubeb_wasapi_raw() & static_cast<uint32_t>(aType
)) {
732 prefs
|= CUBEB_STREAM_PREF_RAW
;
738 bool RouteOutputAsVoice() { return sRouteOutputAsVoice
; }
740 long datacb(cubeb_stream
*, void*, const void*, void* out_buffer
, long nframes
) {
741 PodZero(static_cast<float*>(out_buffer
), nframes
* 2);
745 void statecb(cubeb_stream
*, void*, cubeb_state
) {}
747 bool EstimatedRoundTripLatencyDefaultDevices(double* aMean
, double* aStdDev
) {
748 nsTArray
<double> roundtripLatencies
;
749 // Create a cubeb stream with the correct latency and default input/output
750 // devices (mono/stereo channels). Wait for two seconds, get the latency a few
754 uint32_t latencyFrames
;
755 rv
= cubeb_get_preferred_sample_rate(GetCubebContext(), &rate
);
756 if (rv
!= CUBEB_OK
) {
757 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not get preferred rate"));
761 cubeb_stream_params output_params
;
762 output_params
.format
= CUBEB_SAMPLE_FLOAT32NE
;
763 output_params
.rate
= rate
;
764 output_params
.channels
= 2;
765 output_params
.layout
= CUBEB_LAYOUT_UNDEFINED
;
766 output_params
.prefs
= GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_OUTPUT
);
768 latencyFrames
= GetCubebMTGLatencyInFrames(&output_params
);
770 cubeb_stream_params input_params
;
771 input_params
.format
= CUBEB_SAMPLE_FLOAT32NE
;
772 input_params
.rate
= rate
;
773 input_params
.channels
= 1;
774 input_params
.layout
= CUBEB_LAYOUT_UNDEFINED
;
775 input_params
.prefs
= GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_INPUT
);
778 rv
= cubeb_stream_init(GetCubebContext(), &stm
,
779 "about:support latency estimation", NULL
,
780 &input_params
, NULL
, &output_params
, latencyFrames
,
781 datacb
, statecb
, NULL
);
782 if (rv
!= CUBEB_OK
) {
783 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not get init stream"));
787 rv
= cubeb_stream_start(stm
);
788 if (rv
!= CUBEB_OK
) {
789 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not start stream"));
793 for (uint32_t i
= 0; i
< 40; i
++) {
794 std::this_thread::sleep_for(std::chrono::milliseconds(50));
795 uint32_t inputLatency
, outputLatency
, rvIn
, rvOut
;
796 rvOut
= cubeb_stream_get_latency(stm
, &outputLatency
);
798 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not get output latency"));
800 rvIn
= cubeb_stream_get_input_latency(stm
, &inputLatency
);
802 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not get input latency"));
804 if (rvIn
!= CUBEB_OK
|| rvOut
!= CUBEB_OK
) {
808 double roundTrip
= static_cast<double>(outputLatency
+ inputLatency
) / rate
;
809 roundtripLatencies
.AppendElement(roundTrip
);
811 rv
= cubeb_stream_stop(stm
);
812 if (rv
!= CUBEB_OK
) {
813 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not stop the stream"));
818 double variance
= 0.0;
819 for (uint32_t i
= 0; i
< roundtripLatencies
.Length(); i
++) {
820 *aMean
+= roundtripLatencies
[i
];
823 *aMean
/= roundtripLatencies
.Length();
825 for (uint32_t i
= 0; i
< roundtripLatencies
.Length(); i
++) {
826 variance
+= pow(roundtripLatencies
[i
] - *aMean
, 2.);
828 variance
/= roundtripLatencies
.Length();
830 *aStdDev
= sqrt(variance
);
832 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
833 ("Default device roundtrip latency in seconds %lf (stddev: %lf)",
836 cubeb_stream_destroy(stm
);
841 #ifdef MOZ_WIDGET_ANDROID
842 int32_t AndroidGetAudioOutputSampleRate() {
843 # if defined(MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS)
844 return 44100; // TODO: Remote value; will be handled in following patch.
846 int32_t sample_rate
= java::GeckoAppShell::GetAudioOutputSampleRate();
850 int32_t AndroidGetAudioOutputFramesPerBuffer() {
851 # if defined(MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS)
852 return 512; // TODO: Remote value; will be handled in following patch.
854 int32_t frames
= java::GeckoAppShell::GetAudioOutputFramesPerBuffer();
860 } // namespace CubebUtils
861 } // namespace mozilla