Bug 1301648 - Add a pref to be able to control the AudioCallbackDriver requested latency. r?kinetik draft
authorPaul Adenot <paul@paul.cx>
Fri, 09 Sep 2016 15:21:50 +0200
changeset 412181 1fd9f3006c79c281aeb8ad0edbc0110c567274ba
parent 412180 157c939907af3019c92eb36fbe7599a2a1013bf4
child 412209 42025be4d74fa4195f2b36b71c4635fc7ea44d15
push id29064
push userpaul@paul.cx
push dateFri, 09 Sep 2016 13:22:15 +0000
reviewerskinetik
bugs1301648
milestone51.0a1
Bug 1301648 - Add a pref to be able to control the AudioCallbackDriver requested latency. r?kinetik MozReview-Commit-ID: 19BZUjyXFlq
dom/media/AudioStream.cpp
dom/media/CubebUtils.cpp
dom/media/CubebUtils.h
dom/media/GraphDriver.cpp
--- a/dom/media/AudioStream.cpp
+++ b/dom/media/AudioStream.cpp
@@ -358,17 +358,17 @@ AudioStream::OpenCubeb(cubeb_stream_para
   cubeb* cubebContext = CubebUtils::GetCubebContext();
   if (!cubebContext) {
     NS_WARNING("Can't get cubeb context!");
     return NS_ERROR_FAILURE;
   }
 
   cubeb_stream* stream = nullptr;
   /* Convert from milliseconds to frames. */
-  uint32_t latency_frames = CubebUtils::GetCubebLatency() * aParams.rate / 1000;
+  uint32_t latency_frames = CubebUtils::GetCubebPlaybackLatencyInMilliseconds() * aParams.rate / 1000;
   if (cubeb_stream_init(cubebContext, &stream, "AudioStream",
                         nullptr, nullptr, nullptr, &aParams,
                         latency_frames,
                         DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
     mCubebStream.reset(stream);
     CubebUtils::ReportCubebBackendUsed();
   } else {
     NS_WARNING(nsPrintfCString("AudioStream::OpenCubeb() %p failed to init cubeb", this).get());
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -14,34 +14,37 @@
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
 #include "nsThreadUtils.h"
 #include "CubebUtils.h"
 #include "nsAutoRef.h"
 #include "prdtoa.h"
 
 #define PREF_VOLUME_SCALE "media.volume_scale"
-#define PREF_CUBEB_LATENCY "media.cubeb_latency_ms"
+#define PREF_CUBEB_LATENCY_PLAYBACK "media.cubeb_latency_playback_ms"
+#define PREF_CUBEB_LATENCY_MSG "media.cubeb_latency_msg_frames"
 
 namespace mozilla {
 
 namespace {
 
 // This mutex protects the variables below.
 StaticMutex sMutex;
 enum class CubebState {
   Uninitialized = 0,
   Initialized,
   Error,
   Shutdown
 } sCubebState = CubebState::Uninitialized;
 cubeb* sCubebContext;
 double sVolumeScale;
-uint32_t sCubebLatency;
-bool sCubebLatencyPrefSet;
+uint32_t sCubebPlaybackLatencyInMilliseconds;
+uint32_t sCubebMSGLatencyInFrames;
+bool sCubebPlaybackLatencyPrefSet;
+bool sCubebMSGLatencyPrefSet;
 bool sAudioStreamInitEverSucceeded = false;
 StaticAutoPtr<char> sBrandName;
 
 const char kBrandBundleURL[]      = "chrome://branding/locale/brand.properties";
 
 const char* AUDIOSTREAM_BACKEND_ID_STR[] = {
   "jack",
   "pulse",
@@ -77,38 +80,49 @@ const int CUBEB_BACKEND_UNKNOWN = CUBEB_
 // visible on the querying thread/CPU.
 uint32_t sPreferredSampleRate;
 
 } // namespace
 
 extern LazyLogModule gAudioStreamLog;
 
 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 {
 
 void PrefChanged(const char* aPref, void* aClosure)
 {
   if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) {
     nsAdoptingString value = Preferences::GetString(aPref);
     StaticMutexAutoLock lock(sMutex);
     if (value.IsEmpty()) {
       sVolumeScale = 1.0;
     } else {
       NS_ConvertUTF16toUTF8 utf8(value);
       sVolumeScale = std::max<double>(0, PR_strtod(utf8.get(), nullptr));
     }
-  } else if (strcmp(aPref, PREF_CUBEB_LATENCY) == 0) {
+  } else if (strcmp(aPref, PREF_CUBEB_LATENCY_PLAYBACK) == 0) {
     // Arbitrary default stream latency of 100ms.  The higher this
     // value, the longer stream volume changes will take to become
     // audible.
-    sCubebLatencyPrefSet = Preferences::HasUserValue(aPref);
+    sCubebPlaybackLatencyPrefSet = Preferences::HasUserValue(aPref);
     uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_MS);
     StaticMutexAutoLock lock(sMutex);
-    sCubebLatency = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000);
+    sCubebPlaybackLatencyInMilliseconds = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000);
+  } else if (strcmp(aPref, PREF_CUBEB_LATENCY_MSG) == 0) {
+    sCubebMSGLatencyPrefSet = Preferences::HasUserValue(aPref);
+    uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_FRAMES);
+    StaticMutexAutoLock lock(sMutex);
+    // 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);
   }
 }
 
 bool GetFirstStream()
 {
   static bool sFirstStream = true;
 
   StaticMutexAutoLock lock(sMutex);
@@ -221,43 +235,62 @@ void ReportCubebStreamInitFailure(bool a
     // failures to open multiple streams in a process over time.
     return;
   }
   Telemetry::Accumulate(Telemetry::AUDIOSTREAM_BACKEND_USED,
                         aIsFirst ? CUBEB_BACKEND_INIT_FAILURE_FIRST
                                  : CUBEB_BACKEND_INIT_FAILURE_OTHER);
 }
 
-uint32_t GetCubebLatency()
+uint32_t GetCubebPlaybackLatencyInMilliseconds()
+{
+  StaticMutexAutoLock lock(sMutex);
+  return sCubebPlaybackLatencyInMilliseconds;
+}
+
+bool CubebPlaybackLatencyPrefSet()
 {
   StaticMutexAutoLock lock(sMutex);
-  return sCubebLatency;
+  return sCubebPlaybackLatencyPrefSet;
+}
+
+bool CubebMSGLatencyPrefSet()
+{
+  StaticMutexAutoLock lock(sMutex);
+  return sCubebMSGLatencyPrefSet;
 }
 
-bool CubebLatencyPrefSet()
+Maybe<uint32_t> GetCubebMSGLatencyInFrames()
 {
   StaticMutexAutoLock lock(sMutex);
-  return sCubebLatencyPrefSet;
+  if (!sCubebMSGLatencyPrefSet) {
+    return Maybe<uint32_t>();
+  }
+  MOZ_ASSERT(sCubebMSGLatencyInFrames > 0);
+  return Some(sCubebMSGLatencyInFrames);
 }
 
 void InitLibrary()
 {
   PrefChanged(PREF_VOLUME_SCALE, nullptr);
   Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE);
-  PrefChanged(PREF_CUBEB_LATENCY, nullptr);
-  Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
+  PrefChanged(PREF_CUBEB_LATENCY_PLAYBACK, nullptr);
+  PrefChanged(PREF_CUBEB_LATENCY_MSG, nullptr);
+  Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY_PLAYBACK);
+  Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY_MSG);
 #ifndef MOZ_WIDGET_ANDROID
   NS_DispatchToMainThread(NS_NewRunnableFunction(&InitBrandName));
 #endif
 }
 
 void ShutdownLibrary()
 {
   Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE);
-  Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
+  Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY_PLAYBACK);
+  Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY_MSG);
 
   StaticMutexAutoLock lock(sMutex);
   if (sCubebContext) {
     cubeb_destroy(sCubebContext);
     sCubebContext = nullptr;
   }
   sBrandName = nullptr;
   // This will ensure we don't try to re-create a context.
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(CubebUtils_h_)
 #define CubebUtils_h_
 
 #include "cubeb/cubeb.h"
 #include "mozilla/dom/AudioChannelBinding.h"
+#include "mozilla/Maybe.h"
 
 namespace mozilla {
 namespace CubebUtils {
 
 typedef cubeb_devid AudioDeviceID;
 
 // Initialize Audio Library. Some Audio backends require initializing the
 // library before using it.
@@ -36,17 +37,18 @@ uint32_t PreferredSampleRate();
 
 void PrefChanged(const char* aPref, void* aClosure);
 double GetVolumeScale();
 bool GetFirstStream();
 cubeb* GetCubebContext();
 cubeb* GetCubebContextUnlocked();
 void ReportCubebStreamInitFailure(bool aIsFirstStream);
 void ReportCubebBackendUsed();
-uint32_t GetCubebLatency();
+uint32_t GetCubebPlaybackLatencyInMilliseconds();
+Maybe<uint32_t> GetCubebMSGLatencyInFrames();
 bool CubebLatencyPrefSet();
 #if defined(__ANDROID__) && defined(MOZ_B2G)
 cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannel aChannel);
 #endif
 void GetCurrentBackend(nsAString& aBackend);
 
 } // namespace CubebUtils
 } // namespace mozilla
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -620,19 +620,24 @@ AudioCallbackDriver::Init()
 
   output.channels = mGraphImpl->AudioChannelCount();
   if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
     output.format = CUBEB_SAMPLE_S16NE;
   } else {
     output.format = CUBEB_SAMPLE_FLOAT32NE;
   }
 
-  if (cubeb_get_min_latency(cubebContext, output, &latency_frames) != CUBEB_OK) {
-    NS_WARNING("Could not get minimal latency from cubeb.");
-    return;
+  Maybe<uint32_t> latencyPref = CubebUtils::GetCubebMSGLatencyInFrames();
+  if (latencyPref) {
+    latency_frames = latencyPref.value();
+  } else {
+    if (cubeb_get_min_latency(cubebContext, output, &latency_frames) != CUBEB_OK) {
+      NS_WARNING("Could not get minimal latency from cubeb.");
+      return;
+    }
   }
 
   input = output;
   input.channels = mInputChannels; // change to support optional stereo capture
 
   cubeb_stream* stream = nullptr;
   CubebUtils::AudioDeviceID input_id = nullptr, output_id = nullptr;
   // We have to translate the deviceID values to cubeb devid's since those can be