Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/CubebUtils.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
6
7
#include "CubebUtils.h"
8
9
#include "MediaInfo.h"
10
#include "mozilla/AbstractThread.h"
11
#include "mozilla/dom/ContentChild.h"
12
#include "mozilla/dom/AudioDeviceInfo.h"
13
#include "mozilla/ipc/FileDescriptor.h"
14
#include "mozilla/Logging.h"
15
#include "mozilla/Preferences.h"
16
#include "mozilla/Services.h"
17
#include "mozilla/Sprintf.h"
18
#include "mozilla/StaticMutex.h"
19
#include "mozilla/StaticPtr.h"
20
#include "mozilla/Telemetry.h"
21
#include "nsAutoRef.h"
22
#include "nsDebug.h"
23
#include "nsIStringBundle.h"
24
#include "nsString.h"
25
#include "nsThreadUtils.h"
26
#include "prdtoa.h"
27
#include <algorithm>
28
#include <stdint.h>
29
#ifdef MOZ_WIDGET_ANDROID
30
#include "GeneratedJNIWrappers.h"
31
#endif
32
33
3
#define AUDIOIPC_POOL_SIZE_DEFAULT 2
34
3
#define AUDIOIPC_STACK_SIZE_DEFAULT (64*4096)
35
36
27
#define PREF_VOLUME_SCALE "media.volume_scale"
37
18
#define PREF_CUBEB_BACKEND "media.cubeb.backend"
38
15
#define PREF_CUBEB_OUTPUT_DEVICE "media.cubeb.output_device"
39
24
#define PREF_CUBEB_LATENCY_PLAYBACK "media.cubeb_latency_playback_ms"
40
21
#define PREF_CUBEB_LATENCY_MSG "media.cubeb_latency_msg_frames"
41
// Allows to get something non-default for the preferred sample-rate, to allow
42
// troubleshooting in the field and testing.
43
18
#define PREF_CUBEB_FORCE_SAMPLE_RATE "media.cubeb.force_sample_rate"
44
18
#define PREF_CUBEB_LOGGING_LEVEL "media.cubeb.logging_level"
45
// Hidden pref used by tests to force failure to obtain cubeb context
46
12
#define PREF_CUBEB_FORCE_NULL_CONTEXT "media.cubeb.force_null_context"
47
// Hidden pref to disable BMO 1427011 experiment; can be removed once proven.
48
9
#define PREF_CUBEB_DISABLE_DEVICE_SWITCHING "media.cubeb.disable_device_switching"
49
9
#define PREF_CUBEB_SANDBOX "media.cubeb.sandbox"
50
9
#define PREF_AUDIOIPC_POOL_SIZE "media.audioipc.pool_size"
51
6
#define PREF_AUDIOIPC_STACK_SIZE "media.audioipc.stack_size"
52
53
#if (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)) || defined(XP_MACOSX)
54
#define MOZ_CUBEB_REMOTING
55
#endif
56
57
extern "C" {
58
59
struct AudioIpcInitParams {
60
  int mServerConnection;
61
  size_t mPoolSize;
62
  size_t mStackSize;
63
  void (*mThreadCreateCallback)(const char*);
64
};
65
66
// These functions are provided by audioipc-server crate
67
extern void* audioipc_server_start();
68
extern mozilla::ipc::FileDescriptor::PlatformHandleType audioipc_server_new_client(void*);
69
extern void audioipc_server_stop(void*);
70
// These functions are provided by audioipc-client crate
71
extern int audioipc_client_init(cubeb**, const char*, const AudioIpcInitParams*);
72
}
73
74
namespace mozilla {
75
76
namespace {
77
78
#ifdef MOZ_CUBEB_REMOTING
79
////////////////////////////////////////////////////////////////////////////////
80
// Cubeb Sound Server Thread
81
void* sServerHandle = nullptr;
82
83
// Initialized during early startup, protected by sMutex.
84
StaticAutoPtr<ipc::FileDescriptor> sIPCConnection;
85
86
static bool
87
StartSoundServer()
88
3
{
89
3
  sServerHandle = audioipc_server_start();
90
3
  return sServerHandle != nullptr;
91
3
}
92
93
static void
94
ShutdownSoundServer()
95
0
{
96
0
  if (!sServerHandle)
97
0
    return;
98
0
99
0
  audioipc_server_stop(sServerHandle);
100
0
  sServerHandle = nullptr;
101
0
}
102
#endif // MOZ_CUBEB_REMOTING
103
104
////////////////////////////////////////////////////////////////////////////////
105
106
LazyLogModule gCubebLog("cubeb");
107
108
void CubebLogCallback(const char* aFmt, ...)
109
0
{
110
0
  char buffer[256];
111
0
112
0
  va_list arglist;
113
0
  va_start(arglist, aFmt);
114
0
  VsprintfLiteral (buffer, aFmt, arglist);
115
0
  MOZ_LOG(gCubebLog, LogLevel::Error, ("%s", buffer));
116
0
  va_end(arglist);
117
0
}
118
119
// This mutex protects the variables below.
120
StaticMutex sMutex;
121
enum class CubebState {
122
  Uninitialized = 0,
123
  Initialized,
124
  Shutdown
125
} sCubebState = CubebState::Uninitialized;
126
cubeb* sCubebContext;
127
double sVolumeScale = 1.0;
128
uint32_t sCubebPlaybackLatencyInMilliseconds = 100;
129
uint32_t sCubebMSGLatencyInFrames = 512;
130
// If sCubebForcedSampleRate is zero, PreferredSampleRate will return the
131
// preferred sample-rate for the audio backend in use. Otherwise, it will be
132
// used as the preferred sample-rate.
133
uint32_t sCubebForcedSampleRate = 0;
134
bool sCubebPlaybackLatencyPrefSet = false;
135
bool sCubebMSGLatencyPrefSet = false;
136
bool sAudioStreamInitEverSucceeded = false;
137
bool sCubebForceNullContext = false;
138
bool sCubebDisableDeviceSwitching = true;
139
#ifdef MOZ_CUBEB_REMOTING
140
bool sCubebSandbox = false;
141
size_t sAudioIPCPoolSize;
142
size_t sAudioIPCStackSize;
143
#endif
144
StaticAutoPtr<char> sBrandName;
145
StaticAutoPtr<char> sCubebBackendName;
146
StaticAutoPtr<char> sCubebOutputDeviceName;
147
148
const char kBrandBundleURL[]      = "chrome://branding/locale/brand.properties";
149
150
const char* AUDIOSTREAM_BACKEND_ID_STR[] = {
151
  "jack",
152
  "pulse",
153
  "alsa",
154
  "audiounit",
155
  "audioqueue",
156
  "wasapi",
157
  "winmm",
158
  "directsound",
159
  "sndio",
160
  "opensl",
161
  "audiotrack",
162
  "kai"
163
};
164
/* Index for failures to create an audio stream the first time. */
165
const int CUBEB_BACKEND_INIT_FAILURE_FIRST =
166
  ArrayLength(AUDIOSTREAM_BACKEND_ID_STR);
167
/* Index for failures to create an audio stream after the first time */
168
const int CUBEB_BACKEND_INIT_FAILURE_OTHER = CUBEB_BACKEND_INIT_FAILURE_FIRST + 1;
169
/* Index for an unknown backend. */
170
const int CUBEB_BACKEND_UNKNOWN = CUBEB_BACKEND_INIT_FAILURE_FIRST + 2;
171
172
// Prefered samplerate, in Hz (characteristic of the hardware, mixer, platform,
173
// and API used).
174
//
175
// sMutex protects *initialization* of this, which must be performed from each
176
// thread before fetching, after which it is safe to fetch without holding the
177
// mutex because it is only written once per process execution (by the first
178
// initialization to complete).  Since the init must have been called on a
179
// given thread before fetching the value, it's guaranteed (via the mutex) that
180
// sufficient memory barriers have occurred to ensure the correct value is
181
// visible on the querying thread/CPU.
182
uint32_t sPreferredSampleRate;
183
184
} // namespace
185
186
static const uint32_t CUBEB_NORMAL_LATENCY_MS = 100;
187
// Consevative default that can work on all platforms.
188
static const uint32_t CUBEB_NORMAL_LATENCY_FRAMES = 1024;
189
190
namespace CubebUtils {
191
cubeb* GetCubebContextUnlocked();
192
193
void GetPrefAndSetString(const char* aPref, StaticAutoPtr<char>& aStorage)
194
6
{
195
6
  nsAutoCString value;
196
6
  Preferences::GetCString(aPref, value);
197
6
  if (value.IsEmpty()) {
198
6
    aStorage = nullptr;
199
6
  } else {
200
0
    aStorage = new char[value.Length() + 1];
201
0
    PodCopy(aStorage.get(), value.get(), value.Length());
202
0
    aStorage[value.Length()] = 0;
203
0
  }
204
6
}
205
206
void PrefChanged(const char* aPref, void* aClosure)
207
27
{
208
27
  if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) {
209
3
    nsAutoCString value;
210
3
    Preferences::GetCString(aPref, value);
211
3
    StaticMutexAutoLock lock(sMutex);
212
3
    if (value.IsEmpty()) {
213
0
      sVolumeScale = 1.0;
214
3
    } else {
215
3
      sVolumeScale = std::max<double>(0, PR_strtod(value.get(), nullptr));
216
3
    }
217
24
  } else if (strcmp(aPref, PREF_CUBEB_LATENCY_PLAYBACK) == 0) {
218
3
    StaticMutexAutoLock lock(sMutex);
219
3
    // Arbitrary default stream latency of 100ms.  The higher this
220
3
    // value, the longer stream volume changes will take to become
221
3
    // audible.
222
3
    sCubebPlaybackLatencyPrefSet = Preferences::HasUserValue(aPref);
223
3
    uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_MS);
224
3
    sCubebPlaybackLatencyInMilliseconds = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000);
225
21
  } else if (strcmp(aPref, PREF_CUBEB_LATENCY_MSG) == 0) {
226
3
    StaticMutexAutoLock lock(sMutex);
227
3
    sCubebMSGLatencyPrefSet = Preferences::HasUserValue(aPref);
228
3
    uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_FRAMES);
229
3
    // 128 is the block size for the Web Audio API, which limits how low the
230
3
    // latency can be here.
231
3
    // We don't want to limit the upper limit too much, so that people can
232
3
    // experiment.
233
3
    sCubebMSGLatencyInFrames = std::min<uint32_t>(std::max<uint32_t>(value, 128), 1e6);
234
18
  } else if (strcmp(aPref, PREF_CUBEB_FORCE_SAMPLE_RATE) == 0) {
235
0
    StaticMutexAutoLock lock(sMutex);
236
0
    sCubebForcedSampleRate = Preferences::GetUint(aPref);
237
18
  } else if (strcmp(aPref, PREF_CUBEB_LOGGING_LEVEL) == 0) {
238
0
    nsAutoCString value;
239
0
    Preferences::GetCString(aPref, value);
240
0
    LogModule* cubebLog = LogModule::Get("cubeb");
241
0
    if (value.EqualsLiteral("verbose")) {
242
0
      cubeb_set_log_callback(CUBEB_LOG_VERBOSE, CubebLogCallback);
243
0
      cubebLog->SetLevel(LogLevel::Verbose);
244
0
    } else if (value.EqualsLiteral("normal")) {
245
0
      cubeb_set_log_callback(CUBEB_LOG_NORMAL, CubebLogCallback);
246
0
      cubebLog->SetLevel(LogLevel::Error);
247
0
    } else if (value.IsEmpty()) {
248
0
      cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr);
249
0
      cubebLog->SetLevel(LogLevel::Disabled);
250
0
    }
251
18
  } else if (strcmp(aPref, PREF_CUBEB_BACKEND) == 0) {
252
3
    StaticMutexAutoLock lock(sMutex);
253
3
    GetPrefAndSetString(aPref, sCubebBackendName);
254
15
  } else if (strcmp(aPref, PREF_CUBEB_OUTPUT_DEVICE) == 0) {
255
3
    StaticMutexAutoLock lock(sMutex);
256
3
    GetPrefAndSetString(aPref, sCubebOutputDeviceName);
257
12
  } else if (strcmp(aPref, PREF_CUBEB_FORCE_NULL_CONTEXT) == 0) {
258
3
    StaticMutexAutoLock lock(sMutex);
259
3
    sCubebForceNullContext = Preferences::GetBool(aPref, false);
260
3
    MOZ_LOG(gCubebLog, LogLevel::Verbose,
261
3
            ("%s: %s", PREF_CUBEB_FORCE_NULL_CONTEXT, sCubebForceNullContext ? "true" : "false"));
262
9
  } else if (strcmp(aPref, PREF_CUBEB_DISABLE_DEVICE_SWITCHING) == 0) {
263
0
    StaticMutexAutoLock lock(sMutex);
264
0
    sCubebDisableDeviceSwitching = Preferences::GetBool(aPref, true);
265
0
    MOZ_LOG(gCubebLog, LogLevel::Verbose,
266
0
            ("%s: %s", PREF_CUBEB_DISABLE_DEVICE_SWITCHING, sCubebDisableDeviceSwitching ? "true" : "false"));
267
0
 }
268
9
#ifdef MOZ_CUBEB_REMOTING
269
9
  else if (strcmp(aPref, PREF_CUBEB_SANDBOX) == 0) {
270
3
    StaticMutexAutoLock lock(sMutex);
271
3
    sCubebSandbox = Preferences::GetBool(aPref);
272
3
    MOZ_LOG(gCubebLog, LogLevel::Verbose, ("%s: %s", PREF_CUBEB_SANDBOX, sCubebSandbox ? "true" : "false"));
273
3
274
3
    if (sCubebSandbox && !sServerHandle && XRE_IsParentProcess()) {
275
3
      MOZ_LOG(gCubebLog, LogLevel::Debug, ("Starting cubeb server..."));
276
3
      StartSoundServer();
277
3
    }
278
3
  }
279
6
  else if (strcmp(aPref, PREF_AUDIOIPC_POOL_SIZE) == 0) {
280
3
    StaticMutexAutoLock lock(sMutex);
281
3
    sAudioIPCPoolSize =  Preferences::GetUint(PREF_AUDIOIPC_POOL_SIZE,
282
3
                                              AUDIOIPC_POOL_SIZE_DEFAULT);
283
3
  }
284
3
  else if (strcmp(aPref, PREF_AUDIOIPC_STACK_SIZE) == 0) {
285
3
    StaticMutexAutoLock lock(sMutex);
286
3
    sAudioIPCStackSize = Preferences::GetUint(PREF_AUDIOIPC_STACK_SIZE,
287
3
                                              AUDIOIPC_STACK_SIZE_DEFAULT);
288
3
  }
289
27
#endif
290
27
}
291
292
bool GetFirstStream()
293
0
{
294
0
  static bool sFirstStream = true;
295
0
296
0
  StaticMutexAutoLock lock(sMutex);
297
0
  bool result = sFirstStream;
298
0
  sFirstStream = false;
299
0
  return result;
300
0
}
301
302
double GetVolumeScale()
303
0
{
304
0
  StaticMutexAutoLock lock(sMutex);
305
0
  return sVolumeScale;
306
0
}
307
308
cubeb* GetCubebContext()
309
0
{
310
0
  StaticMutexAutoLock lock(sMutex);
311
0
  return GetCubebContextUnlocked();
312
0
}
313
314
// This is only exported when running tests.
315
void
316
ForceSetCubebContext(cubeb* aCubebContext)
317
0
{
318
0
  StaticMutexAutoLock lock(sMutex);
319
0
  sCubebContext = aCubebContext;
320
0
  sCubebState = CubebState::Initialized;
321
0
}
322
323
bool InitPreferredSampleRate()
324
0
{
325
0
  StaticMutexAutoLock lock(sMutex);
326
0
  if (sPreferredSampleRate != 0) {
327
0
    return true;
328
0
  }
329
#ifdef MOZ_WIDGET_ANDROID
330
  sPreferredSampleRate = AndroidGetAudioOutputSampleRate();
331
#else
332
0
  cubeb* context = GetCubebContextUnlocked();
333
0
  if (!context) {
334
0
    return false;
335
0
  }
336
0
  if (cubeb_get_preferred_sample_rate(context,
337
0
                                      &sPreferredSampleRate) != CUBEB_OK) {
338
0
339
0
    return false;
340
0
  }
341
0
#endif
342
0
  MOZ_ASSERT(sPreferredSampleRate);
343
0
  return true;
344
0
}
345
346
uint32_t PreferredSampleRate()
347
0
{
348
0
  if (sCubebForcedSampleRate) {
349
0
    return sCubebForcedSampleRate;
350
0
  }
351
0
  if (!InitPreferredSampleRate()) {
352
0
    return 44100;
353
0
  }
354
0
  MOZ_ASSERT(sPreferredSampleRate);
355
0
  return sPreferredSampleRate;
356
0
}
357
358
void InitBrandName()
359
0
{
360
0
  if (sBrandName) {
361
0
    return;
362
0
  }
363
0
  nsAutoString brandName;
364
0
  nsCOMPtr<nsIStringBundleService> stringBundleService =
365
0
    mozilla::services::GetStringBundleService();
366
0
  if (stringBundleService) {
367
0
    nsCOMPtr<nsIStringBundle> brandBundle;
368
0
    nsresult rv = stringBundleService->CreateBundle(kBrandBundleURL,
369
0
                                           getter_AddRefs(brandBundle));
370
0
    if (NS_SUCCEEDED(rv)) {
371
0
      rv = brandBundle->GetStringFromName("brandShortName", brandName);
372
0
      NS_WARNING_ASSERTION(
373
0
        NS_SUCCEEDED(rv), "Could not get the program name for a cubeb stream.");
374
0
    }
375
0
  }
376
0
  NS_LossyConvertUTF16toASCII ascii(brandName);
377
0
  sBrandName = new char[ascii.Length() + 1];
378
0
  PodCopy(sBrandName.get(), ascii.get(), ascii.Length());
379
0
  sBrandName[ascii.Length()] = 0;
380
0
}
381
382
#ifdef MOZ_CUBEB_REMOTING
383
void InitAudioIPCConnection()
384
0
{
385
0
  MOZ_ASSERT(NS_IsMainThread());
386
0
  auto contentChild = dom::ContentChild::GetSingleton();
387
0
  auto promise = contentChild->SendCreateAudioIPCConnection();
388
0
  promise->Then(AbstractThread::MainThread(),
389
0
                __func__,
390
0
                [](ipc::FileDescriptor aFD) {
391
0
                  StaticMutexAutoLock lock(sMutex);
392
0
                  MOZ_ASSERT(!sIPCConnection);
393
0
                  sIPCConnection = new ipc::FileDescriptor(aFD);
394
0
                },
395
0
                [](mozilla::ipc::ResponseRejectReason aReason) {
396
0
                  MOZ_LOG(gCubebLog, LogLevel::Error, ("SendCreateAudioIPCConnection failed: %d",
397
0
                                                       int(aReason)));
398
0
                });
399
0
}
400
#endif
401
402
ipc::FileDescriptor CreateAudioIPCConnection()
403
0
{
404
0
#ifdef MOZ_CUBEB_REMOTING
405
0
  MOZ_ASSERT(sServerHandle);
406
0
  int rawFD = audioipc_server_new_client(sServerHandle);
407
0
  ipc::FileDescriptor fd(rawFD);
408
0
  close(rawFD);
409
0
  return fd;
410
#else
411
  return ipc::FileDescriptor();
412
#endif
413
}
414
415
cubeb* GetCubebContextUnlocked()
416
0
{
417
0
  sMutex.AssertCurrentThreadOwns();
418
0
  if (sCubebForceNullContext) {
419
0
    // Pref set such that we should return a null context
420
0
    MOZ_LOG(gCubebLog, LogLevel::Debug,
421
0
            ("%s: returning null context due to %s!", __func__, PREF_CUBEB_FORCE_NULL_CONTEXT));
422
0
    return nullptr;
423
0
  }
424
0
  if (recordreplay::IsRecordingOrReplaying()) {
425
0
    // Media is not supported when recording or replaying. See bug 1304146.
426
0
    return nullptr;
427
0
  }
428
0
  if (sCubebState != CubebState::Uninitialized) {
429
0
    // If we have already passed the initialization point (below), just return
430
0
    // the current context, which may be null (e.g., after error or shutdown.)
431
0
    return sCubebContext;
432
0
  }
433
0
434
0
  if (!sBrandName && NS_IsMainThread()) {
435
0
    InitBrandName();
436
0
  } else {
437
0
    NS_WARNING_ASSERTION(
438
0
      sBrandName, "Did not initialize sbrandName, and not on the main thread?");
439
0
  }
440
0
441
0
#ifdef MOZ_CUBEB_REMOTING
442
0
  MOZ_LOG(gCubebLog, LogLevel::Info, ("%s: %s", PREF_CUBEB_SANDBOX, sCubebSandbox ? "true" : "false"));
443
0
444
0
  int rv = CUBEB_OK;
445
0
  if (sCubebSandbox) {
446
0
    if (XRE_IsParentProcess()) {
447
0
      // TODO: Don't use audio IPC when within the same process.
448
0
      MOZ_ASSERT(!sIPCConnection);
449
0
      sIPCConnection = new ipc::FileDescriptor(CreateAudioIPCConnection());
450
0
    } else {
451
0
      MOZ_DIAGNOSTIC_ASSERT(sIPCConnection);
452
0
    }
453
0
454
0
    AudioIpcInitParams initParams;
455
0
    initParams.mPoolSize = sAudioIPCPoolSize;
456
0
    initParams.mStackSize = sAudioIPCStackSize;
457
0
    initParams.mServerConnection = sIPCConnection->ClonePlatformHandle().release();
458
0
    initParams.mThreadCreateCallback = [](const char* aName) {
459
0
      PROFILER_REGISTER_THREAD(aName);
460
0
    };
461
0
462
0
    MOZ_LOG(gCubebLog, LogLevel::Debug, ("%s: %d", PREF_AUDIOIPC_POOL_SIZE, (int) initParams.mPoolSize));
463
0
    MOZ_LOG(gCubebLog, LogLevel::Debug, ("%s: %d", PREF_AUDIOIPC_STACK_SIZE, (int) initParams.mStackSize));
464
0
465
0
    rv = audioipc_client_init(&sCubebContext, sBrandName, &initParams);
466
0
  } else {
467
0
    rv = cubeb_init(&sCubebContext, sBrandName, sCubebBackendName.get());
468
0
  }
469
0
  sIPCConnection = nullptr;
470
#else // !MOZ_CUBEB_REMOTING
471
  int rv = cubeb_init(&sCubebContext, sBrandName, sCubebBackendName.get());
472
#endif // MOZ_CUBEB_REMOTING
473
0
  NS_WARNING_ASSERTION(rv == CUBEB_OK, "Could not get a cubeb context.");
474
0
  sCubebState = (rv == CUBEB_OK) ? CubebState::Initialized : CubebState::Uninitialized;
475
0
476
0
  return sCubebContext;
477
0
}
478
479
void ReportCubebBackendUsed()
480
0
{
481
0
  StaticMutexAutoLock lock(sMutex);
482
0
483
0
  sAudioStreamInitEverSucceeded = true;
484
0
485
0
  bool foundBackend = false;
486
0
  for (uint32_t i = 0; i < ArrayLength(AUDIOSTREAM_BACKEND_ID_STR); i++) {
487
0
    if (!strcmp(cubeb_get_backend_id(sCubebContext), AUDIOSTREAM_BACKEND_ID_STR[i])) {
488
0
      Telemetry::Accumulate(Telemetry::AUDIOSTREAM_BACKEND_USED, i);
489
0
      foundBackend = true;
490
0
    }
491
0
  }
492
0
  if (!foundBackend) {
493
0
    Telemetry::Accumulate(Telemetry::AUDIOSTREAM_BACKEND_USED,
494
0
                          CUBEB_BACKEND_UNKNOWN);
495
0
  }
496
0
}
497
498
void ReportCubebStreamInitFailure(bool aIsFirst)
499
0
{
500
0
  StaticMutexAutoLock lock(sMutex);
501
0
  if (!aIsFirst && !sAudioStreamInitEverSucceeded) {
502
0
    // This machine has no audio hardware, or it's in really bad shape, don't
503
0
    // send this info, since we want CUBEB_BACKEND_INIT_FAILURE_OTHER to detect
504
0
    // failures to open multiple streams in a process over time.
505
0
    return;
506
0
  }
507
0
  Telemetry::Accumulate(Telemetry::AUDIOSTREAM_BACKEND_USED,
508
0
                        aIsFirst ? CUBEB_BACKEND_INIT_FAILURE_FIRST
509
0
                                 : CUBEB_BACKEND_INIT_FAILURE_OTHER);
510
0
}
511
512
uint32_t GetCubebPlaybackLatencyInMilliseconds()
513
0
{
514
0
  StaticMutexAutoLock lock(sMutex);
515
0
  return sCubebPlaybackLatencyInMilliseconds;
516
0
}
517
518
bool CubebPlaybackLatencyPrefSet()
519
0
{
520
0
  StaticMutexAutoLock lock(sMutex);
521
0
  return sCubebPlaybackLatencyPrefSet;
522
0
}
523
524
bool CubebMSGLatencyPrefSet()
525
0
{
526
0
  StaticMutexAutoLock lock(sMutex);
527
0
  return sCubebMSGLatencyPrefSet;
528
0
}
529
530
uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params * params)
531
0
{
532
0
  StaticMutexAutoLock lock(sMutex);
533
0
  if (sCubebMSGLatencyPrefSet) {
534
0
    MOZ_ASSERT(sCubebMSGLatencyInFrames > 0);
535
0
    return sCubebMSGLatencyInFrames;
536
0
  }
537
0
538
#ifdef MOZ_WIDGET_ANDROID
539
  return AndroidGetAudioOutputFramesPerBuffer();
540
#else
541
0
  cubeb* context = GetCubebContextUnlocked();
542
0
  if (!context) {
543
0
    return sCubebMSGLatencyInFrames; // default 512
544
0
  }
545
0
  uint32_t latency_frames = 0;
546
0
  if (cubeb_get_min_latency(context, params, &latency_frames) != CUBEB_OK) {
547
0
    NS_WARNING("Could not get minimal latency from cubeb.");
548
0
    return sCubebMSGLatencyInFrames; // default 512
549
0
  }
550
0
  return latency_frames;
551
0
#endif
552
0
}
553
554
static const char* gInitCallbackPrefs[] = {
555
  PREF_VOLUME_SCALE,           PREF_CUBEB_OUTPUT_DEVICE,
556
  PREF_CUBEB_LATENCY_PLAYBACK, PREF_CUBEB_LATENCY_MSG,
557
  PREF_CUBEB_BACKEND,          PREF_CUBEB_FORCE_NULL_CONTEXT,
558
  PREF_CUBEB_SANDBOX,          PREF_AUDIOIPC_POOL_SIZE,
559
  PREF_AUDIOIPC_STACK_SIZE,    nullptr,
560
};
561
static const char* gCallbackPrefs[] = {
562
  PREF_CUBEB_FORCE_SAMPLE_RATE,
563
  // We don't want to call the callback on startup, because the pref is the
564
  // empty string by default ("", which means "logging disabled"). Because the
565
  // logging can be enabled via environment variables (MOZ_LOG="module:5"),
566
  // calling this callback on init would immediately re-disable the logging.
567
  PREF_CUBEB_LOGGING_LEVEL,
568
  nullptr,
569
};
570
571
void InitLibrary()
572
3
{
573
3
  Preferences::RegisterCallbacksAndCall(PrefChanged, gInitCallbackPrefs);
574
3
  Preferences::RegisterCallbacks(PrefChanged, gCallbackPrefs);
575
3
576
3
  if (MOZ_LOG_TEST(gCubebLog, LogLevel::Verbose)) {
577
0
    cubeb_set_log_callback(CUBEB_LOG_VERBOSE, CubebLogCallback);
578
3
  } else if (MOZ_LOG_TEST(gCubebLog, LogLevel::Error)) {
579
0
    cubeb_set_log_callback(CUBEB_LOG_NORMAL, CubebLogCallback);
580
0
  }
581
3
582
3
#ifndef MOZ_WIDGET_ANDROID
583
3
  AbstractThread::MainThread()->Dispatch(
584
3
    NS_NewRunnableFunction("CubebUtils::InitLibrary", &InitBrandName));
585
3
#endif
586
3
#ifdef MOZ_CUBEB_REMOTING
587
3
  if (sCubebSandbox && XRE_IsContentProcess() && !recordreplay::IsMiddleman()) {
588
0
    InitAudioIPCConnection();
589
0
  }
590
3
#endif
591
3
}
592
593
void ShutdownLibrary()
594
0
{
595
0
  Preferences::UnregisterCallbacks(PrefChanged, gInitCallbackPrefs);
596
0
  Preferences::UnregisterCallbacks(PrefChanged, gCallbackPrefs);
597
0
598
0
  StaticMutexAutoLock lock(sMutex);
599
0
  if (sCubebContext) {
600
0
    cubeb_destroy(sCubebContext);
601
0
    sCubebContext = nullptr;
602
0
  }
603
0
  sBrandName = nullptr;
604
0
  sCubebBackendName = nullptr;
605
0
  // This will ensure we don't try to re-create a context.
606
0
  sCubebState = CubebState::Shutdown;
607
0
608
0
#ifdef MOZ_CUBEB_REMOTING
609
0
  sIPCConnection = nullptr;
610
0
  ShutdownSoundServer();
611
0
#endif
612
0
}
613
614
uint32_t MaxNumberOfChannels()
615
0
{
616
0
  cubeb* cubebContext = GetCubebContext();
617
0
  uint32_t maxNumberOfChannels;
618
0
  if (cubebContext &&
619
0
      cubeb_get_max_channel_count(cubebContext,
620
0
                                  &maxNumberOfChannels) == CUBEB_OK) {
621
0
    return maxNumberOfChannels;
622
0
  }
623
0
624
0
  return 0;
625
0
}
626
627
void GetCurrentBackend(nsAString& aBackend)
628
0
{
629
0
  cubeb* cubebContext = GetCubebContext();
630
0
  if (cubebContext) {
631
0
    const char* backend = cubeb_get_backend_id(cubebContext);
632
0
    if (backend) {
633
0
      aBackend.AssignASCII(backend);
634
0
      return;
635
0
    }
636
0
  }
637
0
  aBackend.AssignLiteral("unknown");
638
0
}
639
640
char* GetForcedOutputDevice()
641
0
{
642
0
  StaticMutexAutoLock lock(sMutex);
643
0
  return sCubebOutputDeviceName;
644
0
}
645
646
uint16_t ConvertCubebType(cubeb_device_type aType)
647
0
{
648
0
  uint16_t map[] = {
649
0
    nsIAudioDeviceInfo::TYPE_UNKNOWN, // CUBEB_DEVICE_TYPE_UNKNOWN
650
0
    nsIAudioDeviceInfo::TYPE_INPUT,   // CUBEB_DEVICE_TYPE_INPUT,
651
0
    nsIAudioDeviceInfo::TYPE_OUTPUT   // CUBEB_DEVICE_TYPE_OUTPUT
652
0
  };
653
0
  return map[aType];
654
0
}
655
656
uint16_t ConvertCubebState(cubeb_device_state aState)
657
0
{
658
0
  uint16_t map[] = {
659
0
    nsIAudioDeviceInfo::STATE_DISABLED,   // CUBEB_DEVICE_STATE_DISABLED
660
0
    nsIAudioDeviceInfo::STATE_UNPLUGGED,  // CUBEB_DEVICE_STATE_UNPLUGGED
661
0
    nsIAudioDeviceInfo::STATE_ENABLED     // CUBEB_DEVICE_STATE_ENABLED
662
0
  };
663
0
  return map[aState];
664
0
}
665
666
uint16_t ConvertCubebPreferred(cubeb_device_pref aPreferred)
667
0
{
668
0
  if (aPreferred == CUBEB_DEVICE_PREF_NONE) {
669
0
    return nsIAudioDeviceInfo::PREF_NONE;
670
0
  } else if (aPreferred == CUBEB_DEVICE_PREF_ALL) {
671
0
    return nsIAudioDeviceInfo::PREF_ALL;
672
0
  }
673
0
674
0
  uint16_t preferred = 0;
675
0
  if (aPreferred & CUBEB_DEVICE_PREF_MULTIMEDIA) {
676
0
    preferred |= nsIAudioDeviceInfo::PREF_MULTIMEDIA;
677
0
  }
678
0
  if (aPreferred & CUBEB_DEVICE_PREF_VOICE) {
679
0
    preferred |= nsIAudioDeviceInfo::PREF_VOICE;
680
0
  }
681
0
  if (aPreferred & CUBEB_DEVICE_PREF_NOTIFICATION) {
682
0
    preferred |= nsIAudioDeviceInfo::PREF_NOTIFICATION;
683
0
  }
684
0
  return preferred;
685
0
}
686
687
uint16_t ConvertCubebFormat(cubeb_device_fmt aFormat)
688
0
{
689
0
  uint16_t format = 0;
690
0
  if (aFormat & CUBEB_DEVICE_FMT_S16LE) {
691
0
    format |= nsIAudioDeviceInfo::FMT_S16LE;
692
0
  }
693
0
  if (aFormat & CUBEB_DEVICE_FMT_S16BE) {
694
0
    format |= nsIAudioDeviceInfo::FMT_S16BE;
695
0
  }
696
0
  if (aFormat & CUBEB_DEVICE_FMT_F32LE) {
697
0
    format |= nsIAudioDeviceInfo::FMT_F32LE;
698
0
  }
699
0
  if (aFormat & CUBEB_DEVICE_FMT_F32BE) {
700
0
    format |= nsIAudioDeviceInfo::FMT_F32BE;
701
0
  }
702
0
  return format;
703
0
}
704
705
void GetDeviceCollection(nsTArray<RefPtr<AudioDeviceInfo>>& aDeviceInfos,
706
                         Side aSide)
707
0
{
708
0
  cubeb* context = GetCubebContext();
709
0
  if (context) {
710
0
    cubeb_device_collection collection = { nullptr, 0 };
711
0
    if (cubeb_enumerate_devices(context,
712
0
                                aSide == Input ? CUBEB_DEVICE_TYPE_INPUT :
713
0
                                                 CUBEB_DEVICE_TYPE_OUTPUT,
714
0
                                &collection) == CUBEB_OK) {
715
0
      for (unsigned int i = 0; i < collection.count; ++i) {
716
0
        auto device = collection.device[i];
717
0
        RefPtr<AudioDeviceInfo> info =
718
0
          new AudioDeviceInfo(device.devid,
719
0
                              NS_ConvertUTF8toUTF16(device.friendly_name),
720
0
                              NS_ConvertUTF8toUTF16(device.group_id),
721
0
                              NS_ConvertUTF8toUTF16(device.vendor_name),
722
0
                              ConvertCubebType(device.type),
723
0
                              ConvertCubebState(device.state),
724
0
                              ConvertCubebPreferred(device.preferred),
725
0
                              ConvertCubebFormat(device.format),
726
0
                              ConvertCubebFormat(device.default_format),
727
0
                              device.max_channels,
728
0
                              device.default_rate,
729
0
                              device.max_rate,
730
0
                              device.min_rate,
731
0
                              device.latency_hi,
732
0
                              device.latency_lo);
733
0
        aDeviceInfos.AppendElement(info);
734
0
      }
735
0
    }
736
0
    cubeb_device_collection_destroy(context, &collection);
737
0
  }
738
0
}
739
740
cubeb_stream_prefs GetDefaultStreamPrefs()
741
0
{
742
#ifdef XP_WIN
743
  // Investigation for bug 1427011 - if we're in E10S mode, rely on the
744
  // AudioNotification IPC to detect device changes.
745
  if (sCubebDisableDeviceSwitching &&
746
      (XRE_IsE10sParentProcess() || XRE_IsContentProcess())) {
747
    return CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING;
748
  }
749
#endif
750
  return CUBEB_STREAM_PREF_NONE;
751
0
}
752
753
#ifdef MOZ_WIDGET_ANDROID
754
uint32_t AndroidGetAudioOutputSampleRate()
755
{
756
  int32_t sample_rate = java::GeckoAppShell::GetAudioOutputSampleRate();
757
  MOZ_ASSERT(sample_rate > 0);
758
  return sample_rate;
759
}
760
uint32_t AndroidGetAudioOutputFramesPerBuffer()
761
{
762
  int32_t frames = java::GeckoAppShell::GetAudioOutputFramesPerBuffer();
763
  MOZ_ASSERT(frames > 0);
764
  return frames;
765
}
766
#endif
767
768
} // namespace CubebUtils
769
} // namespace mozilla