Bug 1481152 - Restrict to a single input stream per process on Linux, when using PulseAudio and audio remoting is enabled. r=pehrsons
authorPaul Adenot <paul@paul.cx>
Tue, 25 Sep 2018 10:04:44 +0000
changeset 438097 ae4bb7377de29271d14f5ff56f08adec53f8fe0b
parent 438096 f0aeab777cf0ae2d7a2b06b48a0254ab10e3794e
child 438098 2eeb0b00d670738b3814068f10b3bfd21998475e
push id34710
push useraciure@mozilla.com
push dateTue, 25 Sep 2018 21:48:21 +0000
treeherdermozilla-central@17254f49a52e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspehrsons
bugs1481152
milestone64.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 1481152 - Restrict to a single input stream per process on Linux, when using PulseAudio and audio remoting is enabled. r=pehrsons Differential Revision: https://phabricator.services.mozilla.com/D5543
dom/media/CubebUtils.cpp
dom/media/CubebUtils.h
dom/media/tests/mochitest/head.js
dom/media/webrtc/MediaEngineWebRTCAudio.cpp
modules/libpref/init/all.js
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -33,16 +33,18 @@
 #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"
+// This only works when audio remoting is active, and pulseaudio is the backend.
+#define PREF_CUBEB_MAX_INPUT_STREAMS "media.cubeb_max_input_streams"
 // 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"
 // Hidden pref to disable BMO 1427011 experiment; can be removed once proven.
 #define PREF_CUBEB_DISABLE_DEVICE_SWITCHING "media.cubeb.disable_device_switching"
@@ -122,16 +124,19 @@ enum class CubebState {
   Uninitialized = 0,
   Initialized,
   Shutdown
 } sCubebState = CubebState::Uninitialized;
 cubeb* sCubebContext;
 double sVolumeScale = 1.0;
 uint32_t sCubebPlaybackLatencyInMilliseconds = 100;
 uint32_t sCubebMSGLatencyInFrames = 512;
+// Maximum number of audio input streams that can be open at once. This pref is
+// only used when remoting is on, and we're using PulseAudio as a backend.
+uint32_t sCubebMaxInputStreams = 1;
 // If sCubebForcedSampleRate is zero, PreferredSampleRate will return the
 // preferred sample-rate for the audio backend in use. Otherwise, it will be
 // used as the preferred sample-rate.
 uint32_t sCubebForcedSampleRate = 0;
 bool sCubebPlaybackLatencyPrefSet = false;
 bool sCubebMSGLatencyPrefSet = false;
 bool sAudioStreamInitEverSucceeded = false;
 bool sCubebForceNullContext = false;
@@ -226,16 +231,19 @@ void PrefChanged(const char* aPref, void
     StaticMutexAutoLock lock(sMutex);
     sCubebMSGLatencyPrefSet = Preferences::HasUserValue(aPref);
     uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_FRAMES);
     // 128 is the block size for the Web Audio API, which limits how low the
     // latency can be here.
     // We don't want to limit the upper limit too much, so that people can
     // experiment.
     sCubebMSGLatencyInFrames = std::min<uint32_t>(std::max<uint32_t>(value, 128), 1e6);
+  } else if (strcmp(aPref, PREF_CUBEB_MAX_INPUT_STREAMS) == 0) {
+    StaticMutexAutoLock lock(sMutex);
+    sCubebMaxInputStreams = Preferences::GetUint(aPref);
   } else if (strcmp(aPref, PREF_CUBEB_FORCE_SAMPLE_RATE) == 0) {
     StaticMutexAutoLock lock(sMutex);
     sCubebForcedSampleRate = Preferences::GetUint(aPref);
   } else if (strcmp(aPref, PREF_CUBEB_LOGGING_LEVEL) == 0) {
     nsAutoCString value;
     Preferences::GetCString(aPref, value);
     LogModule* cubebLog = LogModule::Get("cubeb");
     if (value.EqualsLiteral("verbose")) {
@@ -546,22 +554,29 @@ uint32_t GetCubebMSGLatencyInFrames(cube
   if (cubeb_get_min_latency(context, params, &latency_frames) != CUBEB_OK) {
     NS_WARNING("Could not get minimal latency from cubeb.");
     return sCubebMSGLatencyInFrames; // default 512
   }
   return latency_frames;
 #endif
 }
 
+uint32_t GetMaxInputStreams()
+{
+  StaticMutexAutoLock lock(sMutex);
+  return sCubebMaxInputStreams;
+}
+
 static const char* gInitCallbackPrefs[] = {
   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,
+  PREF_CUBEB_MAX_INPUT_STREAMS, 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,
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -45,16 +45,17 @@ 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();
+uint32_t GetMaxInputStreams();
 
 #ifdef MOZ_WIDGET_ANDROID
 uint32_t AndroidGetAudioOutputSampleRate();
 uint32_t AndroidGetAudioOutputFramesPerBuffer();
 #endif
 
 #ifdef ENABLE_SET_CUBEB_BACKEND
 void
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -417,17 +417,18 @@ function setupEnvironment() {
       ['media.navigator.permission.disabled', true],
       // If either fake audio or video is desired we enable fake streams.
       // If loopback devices are set they will be chosen instead of fakes in gecko.
       ['media.navigator.streams.fake', WANT_FAKE_AUDIO || WANT_FAKE_VIDEO],
       ['media.getusermedia.audiocapture.enabled', true],
       ['media.getusermedia.screensharing.enabled', true],
       ['media.getusermedia.window.focus_source.enabled', false],
       ['media.recorder.audio_node.enabled', true],
-      ['media.webaudio.audiocontextoptions-samplerate.enabled', true]
+      ['media.webaudio.audiocontextoptions-samplerate.enabled', true],
+      ['media.cubeb_max_input_streams', 10000]
     ]
   };
 
   const isAndroid = !!navigator.userAgent.includes("Android");
 
   if (isAndroid) {
     defaultMochitestPrefs.set.push(
       ["media.navigator.video.default_width", 320],
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -31,16 +31,20 @@ using namespace webrtc;
 
 // These are restrictions from the webrtc.org code
 #define MAX_CHANNELS 2
 #define MAX_SAMPLING_FREQ 48000 // Hz - multiple of 100
 
 #define MAX_AEC_FIFO_DEPTH 200 // ms - multiple of 10
 static_assert(!(MAX_AEC_FIFO_DEPTH % 10), "Invalid MAX_AEC_FIFO_DEPTH");
 
+#ifdef MOZ_PULSEAUDIO
+static uint32_t sInputStreamsOpen = 0;
+#endif
+
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 LogModule* GetMediaManagerLog();
 #define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
@@ -756,16 +760,32 @@ MediaEngineWebRTCMicrophoneSource::Start
   CubebUtils::AudioDeviceID deviceID = mDeviceInfo->DeviceID();
   if (allocation.mStream->GraphImpl()->InputDeviceID() &&
       allocation.mStream->GraphImpl()->InputDeviceID() != deviceID) {
     // For now, we only allow opening a single audio input device per document,
     // because we can only have one MSG per document.
     return NS_ERROR_FAILURE;
   }
 
+  // On Linux with PulseAudio, we still only allow a certain number of audio
+  // input stream in each content process, because of issues related to audio
+  // remoting and PulseAudio.
+#ifdef MOZ_PULSEAUDIO
+  // When remoting, cubeb reports it's using the "remote" backend instead of the
+  // backend on the other side of the IPC.
+  const char* backend = cubeb_get_backend_id(CubebUtils::GetCubebContext());
+  if (strstr(backend, "remote") &&
+      sInputStreamsOpen == CubebUtils::GetMaxInputStreams()) {
+    LOG(("%p Already capturing audio in this process, aborting", this));
+    return NS_ERROR_FAILURE;
+  }
+
+  sInputStreamsOpen++;
+#endif
+
   MOZ_ASSERT(!allocation.mEnabled, "Source already started");
   {
     // This spans setting both the enabled state and mState.
     MutexAutoLock lock(mMutex);
     allocation.mEnabled = true;
 
 #ifdef DEBUG
     // Ensure that callback-tracking state is reset when callbacks start coming.
@@ -815,16 +835,20 @@ MediaEngineWebRTCMicrophoneSource::Stop(
     // This spans setting both the enabled state and mState.
     MutexAutoLock lock(mMutex);
     allocation.mEnabled = false;
 
     CubebUtils::AudioDeviceID deviceID = mDeviceInfo->DeviceID();
     Maybe<CubebUtils::AudioDeviceID> id = Some(deviceID);
     allocation.mStream->CloseAudioInput(id, mListener);
     mListener = nullptr;
+#ifdef MOZ_PULSEAUDIO
+    MOZ_ASSERT(sInputStreamsOpen > 0);
+    sInputStreamsOpen--;
+#endif
 
     if (HasEnabledTrack()) {
       // Another track is keeping us from stopping
       return NS_OK;
     }
 
     MOZ_ASSERT(mState == kStarted, "Should be started when stopping");
     mState = kStopped;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -617,16 +617,21 @@ pref("media.cubeb.logging_level", "");
 pref("media.cubeb.sandbox", true);
 pref("media.audioipc.pool_size", 2);
 // 64 * 4 kB stack per pool thread.
 pref("media.audioipc.stack_size", 262144);
 #else
 pref("media.cubeb.sandbox", false);
 #endif
 
+#ifdef XP_LINUX
+// Bug 1481152
+pref("media.cubeb_max_input_streams", 1);
+#endif
+
 #ifdef MOZ_AV1
 pref("media.av1.enabled", false);
 #endif
 
 pref("media.webaudio.audiocontextoptions-samplerate.enabled", true);
 
 // setSinkId expected to be unconditionally enabled in 63. Till then the
 // implementation will remain hidden behind this pref (Bug 1152401, Bug 934425).