Bug 1480036 - Allow forcing a specific audio output device from a test. r=pehrsons
authorPaul Adenot <paul@paul.cx>
Wed, 01 Aug 2018 14:16:30 +0200
changeset 487679 3f99a091261fb67d798c7f16808d5a19efc7364b
parent 487678 91baa8e095b50463d3c523ce580233cced01a02e
child 487680 352e47aeafac716617392486f88c20107dce2a00
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspehrsons
bugs1480036
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1480036 - Allow forcing a specific audio output device from a test. r=pehrsons Differential Revision: https://phabricator.services.mozilla.com/D3507
dom/media/CubebUtils.cpp
dom/media/CubebUtils.h
dom/media/GraphDriver.cpp
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -30,16 +30,17 @@
 #include "GeneratedJNIWrappers.h"
 #endif
 
 #define AUDIOIPC_POOL_SIZE_DEFAULT 2
 #define AUDIOIPC_STACK_SIZE_DEFAULT (64*4096)
 
 #define PREF_VOLUME_SCALE "media.volume_scale"
 #define PREF_CUBEB_BACKEND "media.cubeb.backend"
+#define PREF_CUBEB_OUTPUT_DEVICE "media.cubeb.output_device"
 #define PREF_CUBEB_LATENCY_PLAYBACK "media.cubeb_latency_playback_ms"
 #define PREF_CUBEB_LATENCY_MSG "media.cubeb_latency_msg_frames"
 // Allows to get something non-default for the preferred sample-rate, to allow
 // troubleshooting in the field and testing.
 #define PREF_CUBEB_FORCE_SAMPLE_RATE "media.cubeb.force_sample_rate"
 #define PREF_CUBEB_LOGGING_LEVEL "media.cubeb.logging_level"
 // Hidden pref used by tests to force failure to obtain cubeb context
 #define PREF_CUBEB_FORCE_NULL_CONTEXT "media.cubeb.force_null_context"
@@ -137,16 +138,17 @@ bool sCubebForceNullContext = false;
 bool sCubebDisableDeviceSwitching = true;
 #ifdef MOZ_CUBEB_REMOTING
 bool sCubebSandbox = false;
 size_t sAudioIPCPoolSize;
 size_t sAudioIPCStackSize;
 #endif
 StaticAutoPtr<char> sBrandName;
 StaticAutoPtr<char> sCubebBackendName;
+StaticAutoPtr<char> sCubebOutputDeviceName;
 
 const char kBrandBundleURL[]      = "chrome://branding/locale/brand.properties";
 
 const char* AUDIOSTREAM_BACKEND_ID_STR[] = {
   "jack",
   "pulse",
   "alsa",
   "audiounit",
@@ -183,16 +185,29 @@ uint32_t sPreferredSampleRate;
 
 static const uint32_t CUBEB_NORMAL_LATENCY_MS = 100;
 // Consevative default that can work on all platforms.
 static const uint32_t CUBEB_NORMAL_LATENCY_FRAMES = 1024;
 
 namespace CubebUtils {
 cubeb* GetCubebContextUnlocked();
 
+void GetPrefAndSetString(const char* aPref, StaticAutoPtr<char>& aStorage)
+{
+  nsAutoCString value;
+  Preferences::GetCString(aPref, value);
+  if (value.IsEmpty()) {
+    aStorage = nullptr;
+  } else {
+    aStorage = new char[value.Length() + 1];
+    PodCopy(aStorage.get(), value.get(), value.Length());
+    aStorage[value.Length()] = 0;
+  }
+}
+
 void PrefChanged(const char* aPref, void* aClosure)
 {
   if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) {
     nsAutoCString value;
     Preferences::GetCString(aPref, value);
     StaticMutexAutoLock lock(sMutex);
     if (value.IsEmpty()) {
       sVolumeScale = 1.0;
@@ -230,25 +245,20 @@ void PrefChanged(const char* aPref, void
       cubeb_set_log_callback(CUBEB_LOG_NORMAL, CubebLogCallback);
       cubebLog->SetLevel(LogLevel::Error);
     } else if (value.IsEmpty()) {
       cubeb_set_log_callback(CUBEB_LOG_DISABLED, nullptr);
       cubebLog->SetLevel(LogLevel::Disabled);
     }
   } else if (strcmp(aPref, PREF_CUBEB_BACKEND) == 0) {
     StaticMutexAutoLock lock(sMutex);
-    nsAutoCString value;
-    Preferences::GetCString(aPref, value);
-    if (value.IsEmpty()) {
-      sCubebBackendName = nullptr;
-    } else {
-      sCubebBackendName = new char[value.Length() + 1];
-      PodCopy(sCubebBackendName.get(), value.get(), value.Length());
-      sCubebBackendName[value.Length()] = 0;
-    }
+    GetPrefAndSetString(aPref, sCubebBackendName);
+  } else if (strcmp(aPref, PREF_CUBEB_OUTPUT_DEVICE) == 0) {
+    StaticMutexAutoLock lock(sMutex);
+    GetPrefAndSetString(aPref, sCubebOutputDeviceName);
   } else if (strcmp(aPref, PREF_CUBEB_FORCE_NULL_CONTEXT) == 0) {
     StaticMutexAutoLock lock(sMutex);
     sCubebForceNullContext = Preferences::GetBool(aPref, false);
     MOZ_LOG(gCubebLog, LogLevel::Verbose,
             ("%s: %s", PREF_CUBEB_FORCE_NULL_CONTEXT, sCubebForceNullContext ? "true" : "false"));
   } else if (strcmp(aPref, PREF_CUBEB_DISABLE_DEVICE_SWITCHING) == 0) {
     StaticMutexAutoLock lock(sMutex);
     sCubebDisableDeviceSwitching = Preferences::GetBool(aPref, true);
@@ -533,25 +543,21 @@ uint32_t GetCubebMSGLatencyInFrames(cube
     NS_WARNING("Could not get minimal latency from cubeb.");
     return sCubebMSGLatencyInFrames; // default 512
   }
   return latency_frames;
 #endif
 }
 
 static const char* gInitCallbackPrefs[] = {
-  PREF_VOLUME_SCALE,
-  PREF_CUBEB_LATENCY_PLAYBACK,
-  PREF_CUBEB_LATENCY_MSG,
-  PREF_CUBEB_BACKEND,
-  PREF_CUBEB_FORCE_NULL_CONTEXT,
-  PREF_CUBEB_SANDBOX,
-  PREF_AUDIOIPC_POOL_SIZE,
-  PREF_AUDIOIPC_STACK_SIZE,
-  nullptr,
+  PREF_VOLUME_SCALE,           PREF_CUBEB_OUTPUT_DEVICE,
+  PREF_CUBEB_LATENCY_PLAYBACK, PREF_CUBEB_LATENCY_MSG,
+  PREF_CUBEB_BACKEND,          PREF_CUBEB_FORCE_NULL_CONTEXT,
+  PREF_CUBEB_SANDBOX,          PREF_AUDIOIPC_POOL_SIZE,
+  PREF_AUDIOIPC_STACK_SIZE,    nullptr,
 };
 static const char* gCallbackPrefs[] = {
   PREF_CUBEB_FORCE_SAMPLE_RATE,
   // We don't want to call the callback on startup, because the pref is the
   // empty string by default ("", which means "logging disabled"). Because the
   // logging can be enabled via environment variables (MOZ_LOG="module:5"),
   // calling this callback on init would immediately re-disable the logging.
   PREF_CUBEB_LOGGING_LEVEL,
@@ -622,16 +628,22 @@ void GetCurrentBackend(nsAString& aBacke
     if (backend) {
       aBackend.AssignASCII(backend);
       return;
     }
   }
   aBackend.AssignLiteral("unknown");
 }
 
+char* GetForcedOutputDevice()
+{
+  StaticMutexAutoLock lock(sMutex);
+  return sCubebOutputDeviceName;
+}
+
 uint16_t ConvertCubebType(cubeb_device_type aType)
 {
   uint16_t map[] = {
     nsIAudioDeviceInfo::TYPE_UNKNOWN, // CUBEB_DEVICE_TYPE_UNKNOWN
     nsIAudioDeviceInfo::TYPE_INPUT,   // CUBEB_DEVICE_TYPE_INPUT,
     nsIAudioDeviceInfo::TYPE_OUTPUT   // CUBEB_DEVICE_TYPE_OUTPUT
   };
   return map[aType];
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -44,16 +44,17 @@ void ReportCubebStreamInitFailure(bool a
 void ReportCubebBackendUsed();
 uint32_t GetCubebPlaybackLatencyInMilliseconds();
 uint32_t GetCubebMSGLatencyInFrames(cubeb_stream_params * params);
 bool CubebLatencyPrefSet();
 void GetCurrentBackend(nsAString& aBackend);
 void GetDeviceCollection(nsTArray<RefPtr<AudioDeviceInfo>>& aDeviceInfos,
                          Side aSide);
 cubeb_stream_prefs GetDefaultStreamPrefs();
+char* GetForcedOutputDevice();
 
 #ifdef MOZ_WIDGET_ANDROID
 uint32_t AndroidGetAudioOutputSampleRate();
 uint32_t AndroidGetAudioOutputFramesPerBuffer();
 #endif
 
 #ifdef ENABLE_SET_CUBEB_BACKEND
 void
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -654,16 +654,32 @@ AudioCallbackDriver::Init()
   mOutputChannels = GraphImpl()->AudioOutputChannelCount();
   if (!mOutputChannels) {
     LOG(LogLevel::Warning, ("Output number of channels is 0."));
     MonitorAutoLock lock(GraphImpl()->GetMonitor());
     FallbackToSystemClockDriver();
     return true;
   }
 
+  CubebUtils::AudioDeviceID forcedOutputDeviceId = nullptr;
+
+  char* forcedOutputDeviceName = CubebUtils::GetForcedOutputDevice();
+  if (forcedOutputDeviceName) {
+    nsTArray<RefPtr<AudioDeviceInfo>> deviceInfos;
+    GetDeviceCollection(deviceInfos, CubebUtils::Output);
+    for (const auto& device : deviceInfos) {
+      const nsString& name = device->Name();
+      if (name.Equals(NS_ConvertUTF8toUTF16(forcedOutputDeviceName))) {
+        if (device->DeviceID()) {
+          forcedOutputDeviceId = device->DeviceID();
+        }
+      }
+    }
+  }
+
   mBuffer = AudioCallbackBufferWrapper<AudioDataValue>(mOutputChannels);
   mScratchBuffer = SpillBuffer<AudioDataValue, WEBAUDIO_BLOCK_SIZE * 2>(mOutputChannels);
 
   output.channels = mOutputChannels;
   output.layout = CUBEB_LAYOUT_UNDEFINED;
   output.prefs = CubebUtils::GetDefaultStreamPrefs();
 
   uint32_t latency_frames = CubebUtils::GetCubebMSGLatencyInFrames(&output);
@@ -685,17 +701,17 @@ AudioCallbackDriver::Init()
 
   // XXX Only pass input input if we have an input listener.  Always
   // set up output because it's easier, and it will just get silence.
   if (cubeb_stream_init(cubebContext,
                         &stream,
                         "AudioCallbackDriver",
                         input_id,
                         inputWanted ? &input : nullptr,
-                        output_id,
+                        forcedOutputDeviceId ? forcedOutputDeviceId : output_id,
                         &output,
                         latency_frames,
                         DataCallback_s,
                         StateCallback_s,
                         this) == CUBEB_OK) {
     mAudioStream.own(stream);
     DebugOnly<int> rv =
       cubeb_stream_set_volume(mAudioStream, CubebUtils::GetVolumeScale());