Bug 1572460 - Refactor `selection` out of the `InspectorFront`. r=yulia
[gecko.git] / dom / media / CubebUtils.cpp
blob8bb23f390094f0c7ee6be82b7b108908d9446e10
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 #ifdef MOZ_WEBRTC
10 # include "CubebDeviceEnumerator.h"
11 #endif
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"
25 #include "nsDebug.h"
26 #include "nsIStringBundle.h"
27 #include "nsString.h"
28 #include "nsThreadUtils.h"
29 #include "prdtoa.h"
30 #include <algorithm>
31 #include <stdint.h>
32 #ifdef MOZ_WIDGET_ANDROID
33 # include "GeneratedJNIWrappers.h"
34 #endif
35 #ifdef XP_WIN
36 # include "mozilla/mscom/EnsureMTA.h"
37 #endif
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
63 #endif
65 extern "C" {
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;
71 size_t mPoolSize;
72 size_t mStackSize;
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*);
84 #ifdef XP_LINUX
85 extern void audioipc_init_threads(const AudioIpcInitParams*);
86 #endif
89 namespace mozilla {
91 namespace {
93 LazyLogModule gCubebLog("cubeb");
95 void CubebLogCallback(const char* aFmt, ...) {
96 char buffer[256];
98 va_list arglist;
99 va_start(arglist, aFmt);
100 VsprintfLiteral(buffer, aFmt, arglist);
101 MOZ_LOG(gCubebLog, LogLevel::Error, ("%s", buffer));
102 va_end(arglist);
105 // This mutex protects the variables below.
106 StaticMutex sMutex;
107 enum class CubebState {
108 Uninitialized = 0,
109 Initialized,
110 Shutdown
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;
129 #endif
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;
140 #endif
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,
157 // and API used).
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) {
182 return;
185 audioipc_server_stop(sServerHandle);
186 sServerHandle = nullptr;
188 #endif // MOZ_CUBEB_REMOTING
189 } // namespace
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) {
199 nsAutoCString value;
200 Preferences::GetCString(aPref, value);
201 if (value.IsEmpty()) {
202 aStorage = nullptr;
203 } else {
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) {
212 nsAutoCString value;
213 Preferences::GetCString(aPref, value);
214 StaticMutexAutoLock lock(sMutex);
215 if (value.IsEmpty()) {
216 sVolumeScale = 1.0;
217 } else {
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
224 // audible.
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
236 // experiment.
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) {
243 nsAutoCString value;
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);
290 #endif
293 bool GetFirstStream() {
294 static bool sFirstStream = true;
296 StaticMutexAutoLock lock(sMutex);
297 bool result = sFirstStream;
298 sFirstStream = false;
299 return result;
302 double GetVolumeScale() {
303 StaticMutexAutoLock lock(sMutex);
304 return sVolumeScale;
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++;
324 } else {
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);
334 #endif
337 bool InitPreferredSampleRate() {
338 StaticMutexAutoLock lock(sMutex);
339 if (sPreferredSampleRate != 0) {
340 return true;
342 #ifdef MOZ_WIDGET_ANDROID
343 sPreferredSampleRate = AndroidGetAudioOutputSampleRate();
344 #else
345 cubeb* context = GetCubebContextUnlocked();
346 if (!context) {
347 return false;
349 if (cubeb_get_preferred_sample_rate(context, &sPreferredSampleRate) !=
350 CUBEB_OK) {
351 return false;
353 #endif
354 MOZ_ASSERT(sPreferredSampleRate);
355 return true;
358 uint32_t PreferredSampleRate() {
359 if (sCubebForcedSampleRate) {
360 return sCubebForcedSampleRate;
362 if (!InitPreferredSampleRate()) {
363 return 44100;
365 MOZ_ASSERT(sPreferredSampleRate);
366 return sPreferredSampleRate;
369 void InitBrandName() {
370 if (sBrandName) {
371 return;
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(
383 NS_SUCCEEDED(rv),
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();
398 promise->Then(
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));
405 } else {
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)));
415 #endif
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);
431 if (!fd.IsValid()) {
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.
437 # ifdef XP_WIN
438 CloseHandle(rawFD);
439 # else
440 close(rawFD);
441 # endif
442 return fd;
443 #else
444 return ipc::FileDescriptor();
445 #endif
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);
458 #endif
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));
467 return nullptr;
469 if (recordreplay::IsRecordingOrReplaying()) {
470 // Media is not supported when recording or replaying. See bug 1304146.
471 return nullptr;
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()) {
480 InitBrandName();
481 } else {
482 NS_WARNING_ASSERTION(
483 sBrandName,
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"));
492 if (sCubebSandbox) {
493 if (XRE_IsParentProcess() && !sIPCConnection) {
494 // TODO: Don't use audio IPC when within the same process.
495 auto fd = CreateAudioIPCConnection();
496 if (fd.IsValid()) {
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).
503 return nullptr;
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);
521 } else {
522 #endif // MOZ_CUBEB_REMOTING
523 #ifdef XP_WIN
524 mozilla::mscom::EnsureMTA([&]() -> void {
525 #endif
526 rv = cubeb_init(&sCubebContext, sBrandName, sCubebBackendName);
527 #ifdef XP_WIN
529 #endif
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.");
535 sCubebState =
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);
551 foundBackend = true;
554 if (!foundBackend) {
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.
566 return;
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();
597 #else
598 cubeb* context = GetCubebContextUnlocked();
599 if (!context) {
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;
608 #endif
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,
625 nullptr,
628 void InitLibrary() {
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));
641 #endif
642 #ifdef MOZ_CUBEB_REMOTING
643 if (sCubebSandbox && XRE_IsContentProcess() && !recordreplay::IsMiddleman()) {
644 InitAudioIPCConnection();
646 #endif
649 void ShutdownLibrary() {
650 Preferences::UnregisterCallbacks(PrefChanged, gInitCallbackPrefs);
651 Preferences::UnregisterCallbacks(PrefChanged, gCallbackPrefs);
653 #ifdef MOZ_WEBRTC
654 // This must be done before cubeb destroy.
655 CubebDeviceEnumerator::Shutdown();
656 #endif
658 StaticMutexAutoLock lock(sMutex);
659 if (sCubebContext) {
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();
671 #endif
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;
682 return 0;
685 void GetCurrentBackend(nsAString& aBackend) {
686 cubeb* cubebContext = GetCubebContext();
687 if (cubebContext) {
688 const char* backend = cubeb_get_backend_id(cubebContext);
689 if (backend) {
690 aBackend.AssignASCII(backend);
691 return;
694 aBackend.AssignLiteral("unknown");
697 char* GetForcedOutputDevice() {
698 StaticMutexAutoLock lock(sMutex);
699 return sCubebOutputDeviceName;
702 cubeb_stream_prefs GetDefaultStreamPrefs() {
703 #ifdef XP_WIN
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;
710 #endif
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);
718 return sample_rate;
720 uint32_t AndroidGetAudioOutputFramesPerBuffer() {
721 int32_t frames = java::GeckoAppShell::GetAudioOutputFramesPerBuffer();
722 MOZ_ASSERT(frames > 0);
723 return frames;
725 #endif
727 } // namespace CubebUtils
728 } // namespace mozilla