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/glean/GleanMetrics.h"
13 #include "mozilla/ipc/FileDescriptor.h"
14 #include "mozilla/Logging.h"
15 #include "mozilla/Preferences.h"
16 #include "mozilla/Components.h"
17 #include "mozilla/SharedThreadPool.h"
18 #include "mozilla/Sprintf.h"
19 #include "mozilla/StaticMutex.h"
20 #include "mozilla/StaticPtr.h"
21 #include "mozilla/Telemetry.h"
22 #include "mozilla/UnderrunHandler.h"
23 #if defined(MOZ_SANDBOX)
24 # include "mozilla/SandboxSettings.h"
26 #include "nsContentUtils.h"
28 #include "nsIStringBundle.h"
30 #include "nsThreadUtils.h"
34 #ifdef MOZ_WIDGET_ANDROID
35 # include "mozilla/java/GeckoAppShellWrappers.h"
38 # include "mozilla/mscom/EnsureMTA.h"
40 #include "audioipc2_server_ffi_generated.h"
41 #include "audioipc2_client_ffi_generated.h"
44 #include "CallbackThreadRegistry.h"
45 #include "mozilla/StaticPrefs_media.h"
47 #define AUDIOIPC_STACK_SIZE_DEFAULT (64 * 4096)
49 #define PREF_VOLUME_SCALE "media.volume_scale"
50 #define PREF_CUBEB_BACKEND "media.cubeb.backend"
51 #define PREF_CUBEB_OUTPUT_DEVICE "media.cubeb.output_device"
52 #define PREF_CUBEB_LATENCY_PLAYBACK "media.cubeb_latency_playback_ms"
53 #define PREF_CUBEB_LATENCY_MTG "media.cubeb_latency_mtg_frames"
54 // Allows to get something non-default for the preferred sample-rate, to allow
55 // troubleshooting in the field and testing.
56 #define PREF_CUBEB_FORCE_SAMPLE_RATE "media.cubeb.force_sample_rate"
57 #define PREF_CUBEB_LOGGING_LEVEL "logging.cubeb"
58 // Hidden pref used by tests to force failure to obtain cubeb context
59 #define PREF_CUBEB_FORCE_NULL_CONTEXT "media.cubeb.force_null_context"
60 #define PREF_CUBEB_OUTPUT_VOICE_ROUTING "media.cubeb.output_voice_routing"
61 #define PREF_CUBEB_SANDBOX "media.cubeb.sandbox"
62 #define PREF_AUDIOIPC_STACK_SIZE "media.audioipc.stack_size"
63 #define PREF_AUDIOIPC_SHM_AREA_SIZE "media.audioipc.shm_area_size"
65 #if defined(XP_LINUX) || defined(XP_MACOSX) || defined(XP_WIN)
66 # define MOZ_CUBEB_REMOTING
73 using Telemetry::LABELS_MEDIA_AUDIO_BACKEND
;
74 using Telemetry::LABELS_MEDIA_AUDIO_INIT_FAILURE
;
76 LazyLogModule
gCubebLog("cubeb");
78 void CubebLogCallback(const char* aFmt
, ...) {
82 va_start(arglist
, aFmt
);
83 VsprintfLiteral(buffer
, aFmt
, arglist
);
84 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("%s", buffer
));
88 // This mutex protects the variables below.
90 enum class CubebState
{
94 } sCubebState
= CubebState::Uninitialized
;
95 StaticRefPtr
<CubebUtils::CubebHandle
> sCubebHandle
;
96 double sVolumeScale
= 1.0;
97 uint32_t sCubebPlaybackLatencyInMilliseconds
= 100;
98 uint32_t sCubebMTGLatencyInFrames
= 512;
99 // If sCubebForcedSampleRate is zero, PreferredSampleRate will return the
100 // preferred sample-rate for the audio backend in use. Otherwise, it will be
101 // used as the preferred sample-rate.
102 Atomic
<uint32_t> sCubebForcedSampleRate
{0};
103 bool sCubebPlaybackLatencyPrefSet
= false;
104 bool sCubebMTGLatencyPrefSet
= false;
105 bool sAudioStreamInitEverSucceeded
= false;
106 bool sCubebForceNullContext
= false;
107 bool sRouteOutputAsVoice
= false;
108 #ifdef MOZ_CUBEB_REMOTING
109 bool sCubebSandbox
= false;
110 size_t sAudioIPCStackSize
;
111 size_t sAudioIPCShmAreaSize
;
113 StaticAutoPtr
<char> sBrandName
;
114 StaticAutoPtr
<char> sCubebBackendName
;
115 StaticAutoPtr
<char> sCubebOutputDeviceName
;
116 #ifdef MOZ_WIDGET_ANDROID
117 // Counts the number of time a request for switching to global "communication
118 // mode" has been received. If this is > 0, global communication mode is to be
119 // enabled. If it is 0, the global communication mode is to be disabled.
120 // This allows to correctly track the global behaviour to adopt accross
121 // asynchronous GraphDriver changes, on Android.
122 int sInCommunicationCount
= 0;
125 const char kBrandBundleURL
[] = "chrome://branding/locale/brand.properties";
127 MOZ_RUNINIT
std::unordered_map
<std::string
, LABELS_MEDIA_AUDIO_BACKEND
>
128 kTelemetryBackendLabel
= {
129 {"audiounit", LABELS_MEDIA_AUDIO_BACKEND::audiounit
},
130 {"audiounit-rust", LABELS_MEDIA_AUDIO_BACKEND::audiounit_rust
},
131 {"aaudio", LABELS_MEDIA_AUDIO_BACKEND::aaudio
},
132 {"opensl", LABELS_MEDIA_AUDIO_BACKEND::opensl
},
133 {"wasapi", LABELS_MEDIA_AUDIO_BACKEND::wasapi
},
134 {"winmm", LABELS_MEDIA_AUDIO_BACKEND::winmm
},
135 {"alsa", LABELS_MEDIA_AUDIO_BACKEND::alsa
},
136 {"jack", LABELS_MEDIA_AUDIO_BACKEND::jack
},
137 {"oss", LABELS_MEDIA_AUDIO_BACKEND::oss
},
138 {"pulse", LABELS_MEDIA_AUDIO_BACKEND::pulse
},
139 {"pulse-rust", LABELS_MEDIA_AUDIO_BACKEND::pulse_rust
},
140 {"sndio", LABELS_MEDIA_AUDIO_BACKEND::sndio
},
141 {"sun", LABELS_MEDIA_AUDIO_BACKEND::sunaudio
},
144 // Prefered samplerate, in Hz (characteristic of the hardware, mixer, platform,
147 // sMutex protects *initialization* of this, which must be performed from each
148 // thread before fetching, after which it is safe to fetch without holding the
149 // mutex because it is only written once per process execution (by the first
150 // initialization to complete). Since the init must have been called on a
151 // given thread before fetching the value, it's guaranteed (via the mutex) that
152 // sufficient memory barriers have occurred to ensure the correct value is
153 // visible on the querying thread/CPU.
154 static Atomic
<uint32_t> sPreferredSampleRate
{0};
156 #ifdef MOZ_CUBEB_REMOTING
157 // AudioIPC server handle
158 void* sServerHandle
= nullptr;
160 // Initialized during early startup, protected by sMutex.
161 StaticAutoPtr
<ipc::FileDescriptor
> sIPCConnection
;
163 static bool StartAudioIPCServer() {
165 audioipc2::AudioIpcServerInitParams initParams
{};
166 initParams
.mThreadCreateCallback
= [](const char* aName
) {
167 PROFILER_REGISTER_THREAD(aName
);
169 initParams
.mThreadDestroyCallback
= []() { PROFILER_UNREGISTER_THREAD(); };
171 sServerHandle
= audioipc2::audioipc2_server_start(
172 sBrandName
, sCubebBackendName
, &initParams
);
174 return sServerHandle
!= nullptr;
177 static void ShutdownAudioIPCServer() {
178 if (!sServerHandle
) {
182 audioipc2::audioipc2_server_stop(sServerHandle
);
183 sServerHandle
= nullptr;
185 #endif // MOZ_CUBEB_REMOTING
188 static const uint32_t CUBEB_NORMAL_LATENCY_MS
= 100;
189 // Consevative default that can work on all platforms.
190 static const uint32_t CUBEB_NORMAL_LATENCY_FRAMES
= 1024;
192 namespace CubebUtils
{
193 nsCString
ProcessingParamsToString(cubeb_input_processing_params aParams
) {
194 if (aParams
== CUBEB_INPUT_PROCESSING_PARAM_NONE
) {
198 for (auto p
: {CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
,
199 CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL
,
200 CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION
,
201 CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION
}) {
202 if (!(aParams
& p
)) {
205 if (!str
.IsEmpty()) {
210 case CUBEB_INPUT_PROCESSING_PARAM_NONE
:
213 "NONE is the absence of a param, thus not for logging here.");
214 case CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
:
216 case CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL
:
218 case CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION
:
220 case CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION
:
223 MOZ_ASSERT_UNREACHABLE("Unexpected input processing param");
224 return "<Unknown input processing param>";
230 RefPtr
<CubebHandle
> GetCubebUnlocked();
232 void GetPrefAndSetString(const char* aPref
, StaticAutoPtr
<char>& aStorage
) {
234 Preferences::GetCString(aPref
, value
);
235 if (value
.IsEmpty()) {
238 aStorage
= new char[value
.Length() + 1];
239 PodCopy(aStorage
.get(), value
.get(), value
.Length());
240 aStorage
[value
.Length()] = 0;
244 void PrefChanged(const char* aPref
, void* aClosure
) {
245 if (strcmp(aPref
, PREF_VOLUME_SCALE
) == 0) {
247 Preferences::GetCString(aPref
, value
);
248 StaticMutexAutoLock
lock(sMutex
);
249 if (value
.IsEmpty()) {
252 sVolumeScale
= std::max
<double>(0, PR_strtod(value
.get(), nullptr));
254 } else if (strcmp(aPref
, PREF_CUBEB_LATENCY_PLAYBACK
) == 0) {
255 StaticMutexAutoLock
lock(sMutex
);
256 // Arbitrary default stream latency of 100ms. The higher this
257 // value, the longer stream volume changes will take to become
259 sCubebPlaybackLatencyPrefSet
= Preferences::HasUserValue(aPref
);
260 uint32_t value
= Preferences::GetUint(aPref
, CUBEB_NORMAL_LATENCY_MS
);
261 sCubebPlaybackLatencyInMilliseconds
=
262 std::min
<uint32_t>(std::max
<uint32_t>(value
, 1), 1000);
263 } else if (strcmp(aPref
, PREF_CUBEB_LATENCY_MTG
) == 0) {
264 StaticMutexAutoLock
lock(sMutex
);
265 sCubebMTGLatencyPrefSet
= Preferences::HasUserValue(aPref
);
266 uint32_t value
= Preferences::GetUint(aPref
, CUBEB_NORMAL_LATENCY_FRAMES
);
267 // 128 is the block size for the Web Audio API, which limits how low the
268 // latency can be here.
269 // We don't want to limit the upper limit too much, so that people can
271 sCubebMTGLatencyInFrames
=
272 std::min
<uint32_t>(std::max
<uint32_t>(value
, 128), 1e6
);
273 } else if (strcmp(aPref
, PREF_CUBEB_FORCE_SAMPLE_RATE
) == 0) {
274 StaticMutexAutoLock
lock(sMutex
);
275 sCubebForcedSampleRate
= Preferences::GetUint(aPref
);
276 } else if (strcmp(aPref
, PREF_CUBEB_LOGGING_LEVEL
) == 0) {
278 ToLogLevel(Preferences::GetInt(aPref
, 0 /* LogLevel::Disabled */));
279 if (value
== LogLevel::Verbose
) {
280 cubeb_set_log_callback(CUBEB_LOG_VERBOSE
, CubebLogCallback
);
281 } else if (value
== LogLevel::Debug
) {
282 cubeb_set_log_callback(CUBEB_LOG_NORMAL
, CubebLogCallback
);
283 } else if (value
== LogLevel::Disabled
) {
284 cubeb_set_log_callback(CUBEB_LOG_DISABLED
, nullptr);
286 } else if (strcmp(aPref
, PREF_CUBEB_BACKEND
) == 0) {
287 StaticMutexAutoLock
lock(sMutex
);
288 GetPrefAndSetString(aPref
, sCubebBackendName
);
289 } else if (strcmp(aPref
, PREF_CUBEB_OUTPUT_DEVICE
) == 0) {
290 StaticMutexAutoLock
lock(sMutex
);
291 GetPrefAndSetString(aPref
, sCubebOutputDeviceName
);
292 } else if (strcmp(aPref
, PREF_CUBEB_FORCE_NULL_CONTEXT
) == 0) {
293 StaticMutexAutoLock
lock(sMutex
);
294 sCubebForceNullContext
= Preferences::GetBool(aPref
, false);
295 MOZ_LOG(gCubebLog
, LogLevel::Verbose
,
296 ("%s: %s", PREF_CUBEB_FORCE_NULL_CONTEXT
,
297 sCubebForceNullContext
? "true" : "false"));
299 #ifdef MOZ_CUBEB_REMOTING
300 else if (strcmp(aPref
, PREF_CUBEB_SANDBOX
) == 0) {
301 StaticMutexAutoLock
lock(sMutex
);
302 sCubebSandbox
= Preferences::GetBool(aPref
);
303 MOZ_LOG(gCubebLog
, LogLevel::Verbose
,
304 ("%s: %s", PREF_CUBEB_SANDBOX
, sCubebSandbox
? "true" : "false"));
305 # if defined(MOZ_SANDBOX)
306 if (!sCubebSandbox
&& IsContentSandboxEnabled()) {
307 sCubebSandbox
= true;
308 MOZ_LOG(gCubebLog
, LogLevel::Error
,
309 ("%s: false, but content sandbox enabled - forcing true",
310 PREF_CUBEB_SANDBOX
));
313 } else if (strcmp(aPref
, PREF_AUDIOIPC_STACK_SIZE
) == 0) {
314 StaticMutexAutoLock
lock(sMutex
);
315 sAudioIPCStackSize
= Preferences::GetUint(PREF_AUDIOIPC_STACK_SIZE
,
316 AUDIOIPC_STACK_SIZE_DEFAULT
);
317 } else if (strcmp(aPref
, PREF_AUDIOIPC_SHM_AREA_SIZE
) == 0) {
318 StaticMutexAutoLock
lock(sMutex
);
319 sAudioIPCShmAreaSize
= Preferences::GetUint(PREF_AUDIOIPC_SHM_AREA_SIZE
);
322 else if (strcmp(aPref
, PREF_CUBEB_OUTPUT_VOICE_ROUTING
) == 0) {
323 StaticMutexAutoLock
lock(sMutex
);
324 sRouteOutputAsVoice
= Preferences::GetBool(aPref
);
325 MOZ_LOG(gCubebLog
, LogLevel::Verbose
,
326 ("%s: %s", PREF_CUBEB_OUTPUT_VOICE_ROUTING
,
327 sRouteOutputAsVoice
? "true" : "false"));
331 bool GetFirstStream() {
332 static bool sFirstStream
= true;
334 StaticMutexAutoLock
lock(sMutex
);
335 bool result
= sFirstStream
;
336 sFirstStream
= false;
340 double GetVolumeScale() {
341 StaticMutexAutoLock
lock(sMutex
);
345 RefPtr
<CubebHandle
> GetCubeb() {
346 StaticMutexAutoLock
lock(sMutex
);
347 return GetCubebUnlocked();
350 // This is only exported when running tests.
351 void ForceSetCubebContext(cubeb
* aCubebContext
) {
352 RefPtr
<CubebHandle
> oldHandle
; // For release without sMutex
353 StaticMutexAutoLock
lock(sMutex
);
354 oldHandle
= sCubebHandle
.forget();
355 sCubebHandle
= aCubebContext
? new CubebHandle(aCubebContext
) : nullptr;
356 sCubebState
= CubebState::Initialized
;
359 void SetInCommunication(bool aInCommunication
) {
360 #ifdef MOZ_WIDGET_ANDROID
361 StaticMutexAutoLock
lock(sMutex
);
362 if (aInCommunication
) {
363 sInCommunicationCount
++;
365 MOZ_ASSERT(sInCommunicationCount
> 0);
366 sInCommunicationCount
--;
369 if (sInCommunicationCount
== 1) {
370 java::GeckoAppShell::SetCommunicationAudioModeOn(true);
371 } else if (sInCommunicationCount
== 0) {
372 java::GeckoAppShell::SetCommunicationAudioModeOn(false);
377 bool InitPreferredSampleRate() MOZ_REQUIRES(sMutex
) {
378 sMutex
.AssertCurrentThreadOwns();
379 if (sPreferredSampleRate
!= 0) {
382 #ifdef MOZ_WIDGET_ANDROID
383 int rate
= AndroidGetAudioOutputSampleRate();
385 sPreferredSampleRate
= rate
;
391 RefPtr
<CubebHandle
> handle
= GetCubebUnlocked();
397 StaticMutexAutoUnlock
unlock(sMutex
);
398 if (cubeb_get_preferred_sample_rate(handle
->Context(), &rate
) != CUBEB_OK
) {
402 sPreferredSampleRate
= rate
;
404 MOZ_ASSERT(sPreferredSampleRate
);
408 uint32_t PreferredSampleRate(bool aShouldResistFingerprinting
) {
409 StaticMutexAutoLock
lock(sMutex
);
410 if (sCubebForcedSampleRate
) {
411 return sCubebForcedSampleRate
;
413 if (aShouldResistFingerprinting
) {
416 if (!InitPreferredSampleRate()) {
419 MOZ_ASSERT(sPreferredSampleRate
);
420 return sPreferredSampleRate
;
423 int CubebStreamInit(cubeb
* context
, cubeb_stream
** stream
,
424 char const* stream_name
, cubeb_devid input_device
,
425 cubeb_stream_params
* input_stream_params
,
426 cubeb_devid output_device
,
427 cubeb_stream_params
* output_stream_params
,
428 uint32_t latency_frames
, cubeb_data_callback data_callback
,
429 cubeb_state_callback state_callback
, void* user_ptr
) {
430 uint32_t ms
= StaticPrefs::media_cubeb_slow_stream_init_ms();
432 std::this_thread::sleep_for(std::chrono::milliseconds(ms
));
434 cubeb_stream_params inputParamData
;
435 cubeb_stream_params outputParamData
;
436 cubeb_stream_params
* inputParamPtr
= input_stream_params
;
437 cubeb_stream_params
* outputParamPtr
= output_stream_params
;
438 if (input_stream_params
&& !output_stream_params
) {
439 inputParamData
= *input_stream_params
;
440 inputParamData
.rate
= llround(
441 static_cast<double>(StaticPrefs::media_cubeb_input_drift_factor()) *
442 inputParamData
.rate
);
444 gCubebLog
, LogLevel::Info
,
445 ("CubebStreamInit input stream rate %" PRIu32
, inputParamData
.rate
));
446 inputParamPtr
= &inputParamData
;
447 } else if (output_stream_params
&& !input_stream_params
) {
448 outputParamData
= *output_stream_params
;
449 outputParamData
.rate
= llround(
450 static_cast<double>(StaticPrefs::media_cubeb_output_drift_factor()) *
451 outputParamData
.rate
);
453 gCubebLog
, LogLevel::Info
,
454 ("CubebStreamInit output stream rate %" PRIu32
, outputParamData
.rate
));
455 outputParamPtr
= &outputParamData
;
458 return cubeb_stream_init(
459 context
, stream
, stream_name
, input_device
, inputParamPtr
, output_device
,
460 outputParamPtr
, latency_frames
, data_callback
, state_callback
, user_ptr
);
463 void InitBrandName() {
467 nsAutoString brandName
;
468 nsCOMPtr
<nsIStringBundleService
> stringBundleService
=
469 mozilla::components::StringBundle::Service();
470 if (stringBundleService
) {
471 nsCOMPtr
<nsIStringBundle
> brandBundle
;
472 nsresult rv
= stringBundleService
->CreateBundle(
473 kBrandBundleURL
, getter_AddRefs(brandBundle
));
474 if (NS_SUCCEEDED(rv
)) {
475 rv
= brandBundle
->GetStringFromName("brandShortName", brandName
);
476 NS_WARNING_ASSERTION(
478 "Could not get the program name for a cubeb stream.");
481 NS_LossyConvertUTF16toASCII
ascii(brandName
);
482 sBrandName
= new char[ascii
.Length() + 1];
483 PodCopy(sBrandName
.get(), ascii
.get(), ascii
.Length());
484 sBrandName
[ascii
.Length()] = 0;
487 #ifdef MOZ_CUBEB_REMOTING
488 void InitAudioIPCConnection() {
489 MOZ_ASSERT(NS_IsMainThread());
490 auto contentChild
= dom::ContentChild::GetSingleton();
491 auto promise
= contentChild
->SendCreateAudioIPCConnection();
493 AbstractThread::MainThread(), __func__
,
494 [](dom::FileDescOrError
&& aFD
) {
495 StaticMutexAutoLock
lock(sMutex
);
496 MOZ_ASSERT(!sIPCConnection
);
497 if (aFD
.type() == dom::FileDescOrError::Type::TFileDescriptor
) {
498 sIPCConnection
= new ipc::FileDescriptor(std::move(aFD
));
500 MOZ_LOG(gCubebLog
, LogLevel::Error
,
501 ("SendCreateAudioIPCConnection failed: invalid FD"));
504 [](mozilla::ipc::ResponseRejectReason
&& aReason
) {
505 MOZ_LOG(gCubebLog
, LogLevel::Error
,
506 ("SendCreateAudioIPCConnection rejected: %d", int(aReason
)));
511 #ifdef MOZ_CUBEB_REMOTING
512 ipc::FileDescriptor
CreateAudioIPCConnectionUnlocked() {
513 MOZ_ASSERT(sCubebSandbox
&& XRE_IsParentProcess());
514 if (!sServerHandle
) {
515 MOZ_LOG(gCubebLog
, LogLevel::Debug
, ("Starting cubeb server..."));
516 if (!StartAudioIPCServer()) {
517 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("audioipc_server_start failed"));
518 return ipc::FileDescriptor();
521 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
522 ("%s: %d", PREF_AUDIOIPC_SHM_AREA_SIZE
, (int)sAudioIPCShmAreaSize
));
523 MOZ_ASSERT(sServerHandle
);
524 ipc::FileDescriptor::PlatformHandleType rawFD
;
525 rawFD
= audioipc2::audioipc2_server_new_client(sServerHandle
,
526 sAudioIPCShmAreaSize
);
527 ipc::FileDescriptor
fd(rawFD
);
529 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("audioipc_server_new_client failed"));
530 return ipc::FileDescriptor();
532 // Close rawFD since FileDescriptor's ctor cloned it.
533 // TODO: Find cleaner cross-platform way to close rawFD.
543 ipc::FileDescriptor
CreateAudioIPCConnection() {
544 #ifdef MOZ_CUBEB_REMOTING
545 StaticMutexAutoLock
lock(sMutex
);
546 return CreateAudioIPCConnectionUnlocked();
548 return ipc::FileDescriptor();
552 RefPtr
<CubebHandle
> GetCubebUnlocked() {
553 sMutex
.AssertCurrentThreadOwns();
554 if (sCubebForceNullContext
) {
555 // Pref set such that we should return a null context
556 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
557 ("%s: returning null context due to %s!", __func__
,
558 PREF_CUBEB_FORCE_NULL_CONTEXT
));
561 if (sCubebState
!= CubebState::Uninitialized
) {
562 // If we have already passed the initialization point (below), just return
563 // the current context, which may be null (e.g., after error or shutdown.)
567 if (!sBrandName
&& NS_IsMainThread()) {
570 NS_WARNING_ASSERTION(
572 "Did not initialize sbrandName, and not on the main thread?");
575 int rv
= CUBEB_ERROR
;
576 #ifdef MOZ_CUBEB_REMOTING
577 MOZ_LOG(gCubebLog
, LogLevel::Info
,
578 ("%s: %s", PREF_CUBEB_SANDBOX
, sCubebSandbox
? "true" : "false"));
581 if (XRE_IsParentProcess() && !sIPCConnection
) {
582 // TODO: Don't use audio IPC when within the same process.
583 auto fd
= CreateAudioIPCConnectionUnlocked();
585 sIPCConnection
= new ipc::FileDescriptor(fd
);
588 if (NS_WARN_IF(!sIPCConnection
)) {
589 // Either the IPC connection failed to init or we're still waiting for
590 // InitAudioIPCConnection to complete (bug 1454782).
594 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
595 ("%s: %d", PREF_AUDIOIPC_STACK_SIZE
, (int)sAudioIPCStackSize
));
597 audioipc2::AudioIpcInitParams initParams
{};
598 initParams
.mStackSize
= sAudioIPCStackSize
;
599 initParams
.mServerConnection
=
600 sIPCConnection
->ClonePlatformHandle().release();
601 initParams
.mThreadCreateCallback
= [](const char* aName
) {
602 PROFILER_REGISTER_THREAD(aName
);
604 initParams
.mThreadDestroyCallback
= []() { PROFILER_UNREGISTER_THREAD(); };
606 cubeb
* temp
= nullptr;
607 rv
= audioipc2::audioipc2_client_init(&temp
, sBrandName
, &initParams
);
609 sCubebHandle
= new CubebHandle(temp
);
612 #endif // MOZ_CUBEB_REMOTING
614 mozilla::mscom::EnsureMTA([&]() -> void {
616 cubeb
* temp
= nullptr;
617 rv
= cubeb_init(&temp
, sBrandName
, sCubebBackendName
);
619 sCubebHandle
= new CubebHandle(temp
);
624 #ifdef MOZ_CUBEB_REMOTING
626 sIPCConnection
= nullptr;
627 #endif // MOZ_CUBEB_REMOTING
628 NS_WARNING_ASSERTION(rv
== CUBEB_OK
, "Could not get a cubeb context.");
630 (rv
== CUBEB_OK
) ? CubebState::Initialized
: CubebState::Uninitialized
;
635 void ReportCubebBackendUsed() {
636 RefPtr
<CubebHandle
> handle
;
638 StaticMutexAutoLock
lock(sMutex
);
639 sAudioStreamInitEverSucceeded
= true;
640 handle
= sCubebHandle
;
643 MOZ_RELEASE_ASSERT(handle
.get());
645 LABELS_MEDIA_AUDIO_BACKEND label
= LABELS_MEDIA_AUDIO_BACKEND::unknown
;
647 kTelemetryBackendLabel
.find(cubeb_get_backend_id(handle
->Context()));
648 if (backend
!= kTelemetryBackendLabel
.end()) {
649 label
= backend
->second
;
651 AccumulateCategorical(label
);
653 mozilla::glean::media_audio::backend
654 .Get(backend
!= kTelemetryBackendLabel
.end()
655 ? nsDependentCString(cubeb_get_backend_id(handle
->Context()))
656 : nsCString("unknown"_ns
))
660 void ReportCubebStreamInitFailure(bool aIsFirst
) {
661 StaticMutexAutoLock
lock(sMutex
);
662 if (!aIsFirst
&& !sAudioStreamInitEverSucceeded
) {
663 // This machine has no audio hardware, or it's in really bad shape, don't
664 // send this info, since we want CUBEB_BACKEND_INIT_FAILURE_OTHER to detect
665 // failures to open multiple streams in a process over time.
668 AccumulateCategorical(aIsFirst
? LABELS_MEDIA_AUDIO_INIT_FAILURE::first
669 : LABELS_MEDIA_AUDIO_INIT_FAILURE::other
);
670 mozilla::glean::media_audio::init_failure
671 .EnumGet(aIsFirst
? mozilla::glean::media_audio::InitFailureLabel::eFirst
672 : mozilla::glean::media_audio::InitFailureLabel::eOther
)
676 uint32_t GetCubebPlaybackLatencyInMilliseconds() {
677 StaticMutexAutoLock
lock(sMutex
);
678 return sCubebPlaybackLatencyInMilliseconds
;
681 bool CubebPlaybackLatencyPrefSet() {
682 StaticMutexAutoLock
lock(sMutex
);
683 return sCubebPlaybackLatencyPrefSet
;
686 bool CubebMTGLatencyPrefSet() {
687 StaticMutexAutoLock
lock(sMutex
);
688 return sCubebMTGLatencyPrefSet
;
691 uint32_t GetCubebMTGLatencyInFrames(cubeb_stream_params
* params
) {
692 StaticMutexAutoLock
lock(sMutex
);
693 if (sCubebMTGLatencyPrefSet
) {
694 MOZ_ASSERT(sCubebMTGLatencyInFrames
> 0);
695 return sCubebMTGLatencyInFrames
;
698 #ifdef MOZ_WIDGET_ANDROID
699 int32_t frames
= AndroidGetAudioOutputFramesPerBuffer();
700 // Allow extra time until audioipc threads are scheduled with higher
701 // priority (bug 1931080). 768 was not sufficient on a Samsung SM-A528B
702 // when switching to the home screen.
703 return std::max(1024, frames
);
705 RefPtr
<CubebHandle
> handle
= GetCubebUnlocked();
707 return sCubebMTGLatencyInFrames
; // default 512
709 uint32_t latency_frames
= 0;
710 int cubeb_result
= CUBEB_OK
;
713 StaticMutexAutoUnlock
unlock(sMutex
);
715 cubeb_get_min_latency(handle
->Context(), params
, &latency_frames
);
718 if (cubeb_result
!= CUBEB_OK
) {
719 NS_WARNING("Could not get minimal latency from cubeb.");
720 return sCubebMTGLatencyInFrames
; // default 512
722 return latency_frames
;
726 static const char* gInitCallbackPrefs
[] = {
728 PREF_CUBEB_OUTPUT_DEVICE
,
729 PREF_CUBEB_LATENCY_PLAYBACK
,
730 PREF_CUBEB_LATENCY_MTG
,
732 PREF_CUBEB_FORCE_SAMPLE_RATE
,
733 PREF_CUBEB_FORCE_NULL_CONTEXT
,
735 PREF_AUDIOIPC_STACK_SIZE
,
736 PREF_AUDIOIPC_SHM_AREA_SIZE
,
740 static const char* gCallbackPrefs
[] = {
741 // We don't want to call the callback on startup, because the pref is the
742 // empty string by default ("", which means "logging disabled"). Because the
743 // logging can be enabled via environment variables (MOZ_LOG="module:5"),
744 // calling this callback on init would immediately re-disable the logging.
745 PREF_CUBEB_LOGGING_LEVEL
,
750 Preferences::RegisterCallbacksAndCall(PrefChanged
, gInitCallbackPrefs
);
751 Preferences::RegisterCallbacks(PrefChanged
, gCallbackPrefs
);
753 if (MOZ_LOG_TEST(gCubebLog
, LogLevel::Verbose
)) {
754 cubeb_set_log_callback(CUBEB_LOG_VERBOSE
, CubebLogCallback
);
755 } else if (MOZ_LOG_TEST(gCubebLog
, LogLevel::Error
)) {
756 cubeb_set_log_callback(CUBEB_LOG_NORMAL
, CubebLogCallback
);
759 #ifndef MOZ_WIDGET_ANDROID
760 NS_DispatchToMainThread(
761 NS_NewRunnableFunction("CubebUtils::InitLibrary", &InitBrandName
));
763 #ifdef MOZ_CUBEB_REMOTING
764 if (sCubebSandbox
&& XRE_IsContentProcess()) {
765 # if defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)
766 if (atp_set_real_time_limit(0, 48000)) {
767 NS_WARNING("could not set real-time limit in CubebUtils::InitLibrary");
769 InstallSoftRealTimeLimitHandler();
771 InitAudioIPCConnection();
775 // Ensure the CallbackThreadRegistry is not created in an audio callback by
777 Unused
<< CallbackThreadRegistry::Get();
780 void ShutdownLibrary() {
781 Preferences::UnregisterCallbacks(PrefChanged
, gInitCallbackPrefs
);
782 Preferences::UnregisterCallbacks(PrefChanged
, gCallbackPrefs
);
784 cubeb_set_log_callback(CUBEB_LOG_DISABLED
, nullptr);
785 RefPtr
<CubebHandle
> trash
;
786 StaticMutexAutoLock
lock(sMutex
);
787 trash
= sCubebHandle
.forget();
788 sBrandName
= nullptr;
789 sCubebBackendName
= nullptr;
790 // This will ensure we don't try to re-create a context.
791 sCubebState
= CubebState::Shutdown
;
794 StaticMutexAutoUnlock
unlock(sMutex
);
795 nsrefcnt count
= trash
.forget().take()->Release();
796 MOZ_RELEASE_ASSERT(!count
,
797 "ShutdownLibrary should be releasing the last reference "
798 "to the cubeb ctx!");
801 #ifdef MOZ_CUBEB_REMOTING
802 sIPCConnection
= nullptr;
803 ShutdownAudioIPCServer();
807 bool SandboxEnabled() {
808 #ifdef MOZ_CUBEB_REMOTING
809 StaticMutexAutoLock
lock(sMutex
);
810 return !!sCubebSandbox
;
816 already_AddRefed
<SharedThreadPool
> GetCubebOperationThread() {
817 RefPtr
<SharedThreadPool
> pool
= SharedThreadPool::Get("CubebOperation"_ns
, 1);
818 const uint32_t kIdleThreadTimeoutMs
= 2000;
819 pool
->SetIdleThreadMaximumTimeout(
820 PR_MillisecondsToInterval(kIdleThreadTimeoutMs
));
821 return pool
.forget();
824 uint32_t MaxNumberOfChannels() {
825 RefPtr
<CubebHandle
> handle
= GetCubeb();
826 uint32_t maxNumberOfChannels
;
827 if (handle
&& cubeb_get_max_channel_count(handle
->Context(),
828 &maxNumberOfChannels
) == CUBEB_OK
) {
829 return maxNumberOfChannels
;
835 void GetCurrentBackend(nsAString
& aBackend
) {
836 RefPtr
<CubebHandle
> handle
= GetCubeb();
838 const char* backend
= cubeb_get_backend_id(handle
->Context());
840 aBackend
.AssignASCII(backend
);
844 aBackend
.AssignLiteral("unknown");
847 char* GetForcedOutputDevice() {
848 StaticMutexAutoLock
lock(sMutex
);
849 return sCubebOutputDeviceName
;
852 cubeb_stream_prefs
GetDefaultStreamPrefs(cubeb_device_type aType
) {
853 cubeb_stream_prefs prefs
= CUBEB_STREAM_PREF_NONE
;
855 if (StaticPrefs::media_cubeb_wasapi_raw() & static_cast<uint32_t>(aType
)) {
856 prefs
|= CUBEB_STREAM_PREF_RAW
;
862 bool RouteOutputAsVoice() { return sRouteOutputAsVoice
; }
864 long datacb(cubeb_stream
*, void*, const void*, void* out_buffer
, long nframes
) {
865 PodZero(static_cast<float*>(out_buffer
), nframes
* 2);
869 void statecb(cubeb_stream
*, void*, cubeb_state
) {}
871 bool EstimatedLatencyDefaultDevices(double* aMean
, double* aStdDev
,
873 RefPtr
<CubebHandle
> handle
= GetCubeb();
875 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("No cubeb context, bailing."));
878 bool includeInput
= aSide
& Side::Input
;
879 bool includeOutput
= aSide
& Side::Output
;
880 nsTArray
<double> latencies
;
881 // Create a cubeb stream with the correct latency and default input/output
882 // devices (mono/stereo channels). Wait for two seconds, get the latency a few
886 uint32_t latencyFrames
;
887 rv
= cubeb_get_preferred_sample_rate(handle
->Context(), &rate
);
888 if (rv
!= CUBEB_OK
) {
889 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not get preferred rate"));
893 cubeb_stream_params output_params
;
894 output_params
.format
= CUBEB_SAMPLE_FLOAT32NE
;
895 output_params
.rate
= rate
;
896 output_params
.channels
= 2;
897 output_params
.layout
= CUBEB_LAYOUT_UNDEFINED
;
898 output_params
.prefs
= GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_OUTPUT
);
900 latencyFrames
= GetCubebMTGLatencyInFrames(&output_params
);
902 cubeb_stream_params input_params
;
903 input_params
.format
= CUBEB_SAMPLE_FLOAT32NE
;
904 input_params
.rate
= rate
;
905 input_params
.channels
= 1;
906 input_params
.layout
= CUBEB_LAYOUT_UNDEFINED
;
907 input_params
.prefs
= GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_INPUT
);
910 rv
= cubeb_stream_init(handle
->Context(), &stm
,
911 "about:support latency estimation", NULL
,
912 &input_params
, NULL
, &output_params
, latencyFrames
,
913 datacb
, statecb
, NULL
);
914 if (rv
!= CUBEB_OK
) {
915 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not get init stream"));
919 rv
= cubeb_stream_start(stm
);
920 if (rv
!= CUBEB_OK
) {
921 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not start stream"));
925 for (uint32_t i
= 0; i
< 40; i
++) {
926 std::this_thread::sleep_for(std::chrono::milliseconds(50));
927 uint32_t inputLatency
, outputLatency
, rvIn
, rvOut
;
928 rvOut
= cubeb_stream_get_latency(stm
, &outputLatency
);
930 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not get output latency"));
932 rvIn
= cubeb_stream_get_input_latency(stm
, &inputLatency
);
934 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not get input latency"));
936 if (rvIn
!= CUBEB_OK
|| rvOut
!= CUBEB_OK
) {
940 double latency
= static_cast<double>((includeInput
? inputLatency
: 0) +
941 (includeOutput
? outputLatency
: 0)) /
943 latencies
.AppendElement(latency
);
945 rv
= cubeb_stream_stop(stm
);
946 if (rv
!= CUBEB_OK
) {
947 MOZ_LOG(gCubebLog
, LogLevel::Error
, ("Could not stop the stream"));
952 double variance
= 0.0;
953 for (uint32_t i
= 0; i
< latencies
.Length(); i
++) {
954 *aMean
+= latencies
[i
];
957 *aMean
/= latencies
.Length();
959 for (uint32_t i
= 0; i
< latencies
.Length(); i
++) {
960 variance
+= pow(latencies
[i
] - *aMean
, 2.);
962 variance
/= latencies
.Length();
964 *aStdDev
= sqrt(variance
);
966 MOZ_LOG(gCubebLog
, LogLevel::Debug
,
967 ("Default devices latency in seconds %lf (stddev: %lf)", *aMean
,
970 cubeb_stream_destroy(stm
);
975 #ifdef MOZ_WIDGET_ANDROID
976 int32_t AndroidGetAudioOutputSampleRate() {
977 # if defined(MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS)
978 return 44100; // TODO: Remote value; will be handled in following patch.
980 int32_t sample_rate
= java::GeckoAppShell::GetAudioOutputSampleRate();
984 int32_t AndroidGetAudioOutputFramesPerBuffer() {
985 # if defined(MOZ_ANDROID_CONTENT_SERVICE_ISOLATED_PROCESS)
986 return 512; // TODO: Remote value; will be handled in following patch.
988 int32_t frames
= java::GeckoAppShell::GetAudioOutputFramesPerBuffer();
994 } // namespace CubebUtils
995 } // namespace mozilla