Bug 1404977 - Add a pref to force the output device by name, for testing. draft
authorPaul Adenot <paul@paul.cx>
Tue, 17 Apr 2018 16:45:33 +0200
changeset 783642 cc6af7857d4fca721d520e547f4b97c54b757751
parent 783549 f94b64e0020225c71701930f193bd96c3ad1d150
child 783643 77a7ebc74c4bfd5a99415301eab7d66d7cbcf779
push id106751
push userpaul@paul.cx
push dateTue, 17 Apr 2018 15:22:51 +0000
bugs1404977
milestone61.0a1
Bug 1404977 - Add a pref to force the output device by name, for testing. MozReview-Commit-ID: 6fgDqbf2Lnk
dom/media/CubebUtils.cpp
dom/media/CubebUtils.h
dom/media/GraphDriver.cpp
dom/media/tests/mochitest/test_getUserMedia_basicAudio_loopback.html
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -29,16 +29,17 @@
 #include "GeneratedJNIWrappers.h"
 #endif
 
 #define AUDIOIPC_POOL_SIZE_DEFAULT 1
 #define AUDIOIPC_STACK_SIZE_DEFAULT (64*1024)
 
 #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"
@@ -132,16 +133,17 @@ bool sAudioStreamInitEverSucceeded = fal
 bool sCubebForceNullContext = false;
 #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",
@@ -178,16 +180,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;
@@ -224,25 +239,21 @@ void PrefChanged(const char* aPref, void
     } else if (value.EqualsLiteral("normal")) {
       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) {
-    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;
-    }
+    StaticMutexAutoLock lock(sMutex);
+    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"));
   }
 #ifdef MOZ_CUBEB_REMOTING
   else if (strcmp(aPref, PREF_CUBEB_SANDBOX) == 0) {
@@ -512,16 +523,17 @@ uint32_t GetCubebMSGLatencyInFrames(cube
   }
   return latency_frames;
 #endif
 }
 
 void InitLibrary()
 {
   Preferences::RegisterCallbackAndCall(PrefChanged, PREF_VOLUME_SCALE);
+  Preferences::RegisterCallbackAndCall(PrefChanged, PREF_CUBEB_OUTPUT_DEVICE);
   Preferences::RegisterCallbackAndCall(PrefChanged, PREF_CUBEB_LATENCY_PLAYBACK);
   Preferences::RegisterCallbackAndCall(PrefChanged, PREF_CUBEB_LATENCY_MSG);
   Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_FORCE_SAMPLE_RATE);
   Preferences::RegisterCallbackAndCall(PrefChanged, PREF_CUBEB_BACKEND);
   Preferences::RegisterCallbackAndCall(PrefChanged, PREF_CUBEB_FORCE_NULL_CONTEXT);
   Preferences::RegisterCallbackAndCall(PrefChanged, PREF_CUBEB_SANDBOX);
   Preferences::RegisterCallbackAndCall(PrefChanged, PREF_AUDIOIPC_POOL_SIZE);
   Preferences::RegisterCallbackAndCall(PrefChanged, PREF_AUDIOIPC_STACK_SIZE);
@@ -544,16 +556,17 @@ void InitLibrary()
     InitAudioIPCConnection();
   }
 #endif
 }
 
 void ShutdownLibrary()
 {
   Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE);
+  Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_OUTPUT_DEVICE);
   Preferences::UnregisterCallback(PrefChanged, PREF_AUDIOIPC_STACK_SIZE);
   Preferences::UnregisterCallback(PrefChanged, PREF_AUDIOIPC_POOL_SIZE);
   Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_SANDBOX);
   Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_BACKEND);
   Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY_PLAYBACK);
   Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_FORCE_SAMPLE_RATE);
   Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY_MSG);
   Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LOGGING_LEVEL);
@@ -596,16 +609,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
@@ -41,17 +41,17 @@ cubeb* GetCubebContext();
 void ReportCubebStreamInitFailure(bool aIsFirstStream);
 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);
-
+char* GetForcedOutputDevice();
 #ifdef MOZ_WIDGET_ANDROID
 uint32_t AndroidGetAudioOutputSampleRate();
 uint32_t AndroidGetAudioOutputFramesPerBuffer();
 #endif
 } // namespace CubebUtils
 } // namespace mozilla
 
 #endif // CubebUtils_h_
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -597,16 +597,31 @@ AudioCallbackDriver::Init()
     if (!mFromFallback) {
       CubebUtils::ReportCubebStreamInitFailure(true);
     }
     MonitorAutoLock lock(GraphImpl()->GetMonitor());
     FallbackToSystemClockDriver();
     return true;
   }
 
+  cubeb_devid forcedOutputDeviceId  = nullptr;
+  char* forcedOutputDeviceName = CubebUtils::GetForcedOutputDevice();
+  if (forcedOutputDeviceName) {
+    nsTArray<RefPtr<AudioDeviceInfo>> deviceInfos;
+    GetDeviceCollection(deviceInfos, CubebUtils::Output);
+    for (auto device : deviceInfos) {
+      const nsString& friendlyName = device->FriendlyName();
+      if (friendlyName.Equals(NS_ConvertUTF8toUTF16(forcedOutputDeviceName))) {
+        if (device->GetDeviceID().isSome()) {
+          forcedOutputDeviceId = device->GetDeviceID().ref();
+        }
+      }
+    }
+  }
+
   cubeb_stream_params output;
   cubeb_stream_params input;
   bool firstStream = CubebUtils::GetFirstStream();
 
   MOZ_ASSERT(!NS_IsMainThread(),
       "This is blocking and should never run on the main thread.");
 
   mSampleRate = output.rate = mGraphImpl->GraphRate();
--- a/dom/media/tests/mochitest/test_getUserMedia_basicAudio_loopback.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_basicAudio_loopback.html
@@ -16,17 +16,19 @@
    * Run a test to verify the use of LoopbackTone as audio input.
    */
   scriptsReady.then(() => runTestWhenReady(async () => {
     let audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev", "");
     if (!audioDevice) {
       todo(false, "No loopback device set by framework. Try --use-test-media-devices");
       return Promise.resolve();
     }
-
+    await SpecialPowers.pushPrefEnv({"set": [
+      ["media.cubeb.output_device", audioDevice]
+    ]});
     // At this point DefaultLoopbackTone has been instantiated
     // automatically on frequency TEST_AUDIO_FREQ (440 Hz). Verify
     // that a tone is detected on that frequency.
     info("Capturing at default frequency");
     let stream = await getUserMedia({audio: true});
 
     let audioContext = new AudioContext();
     let analyser = new AudioStreamAnalyser(audioContext, stream);
@@ -81,14 +83,13 @@
       const freg_50Hz   = array[analyser.binIndexForFrequency(50)];
       const freq        = array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)];
       const freq_2000Hz = array[analyser.binIndexForFrequency(2000)];
 
       info("Analysing audio frequency - low:target:high = "
               + freg_50Hz + ':' + freq + ':' + freq_2000Hz);
       return freg_50Hz < 50 && freq < 50 && freq_2000Hz < 50;
     })
-  }))
-  .then(() => finish())
+  }).then(() => finish()))
 </script>
 </pre>
 </body>
 </html>