Bug 1628779 - Add a function to get an estimation of the roundtrip latency of the default devices. r=achronop
authorPaul Adenot <paul@paul.cx>
Thu, 28 May 2020 09:50:55 +0000
changeset 532740 99be85a6ec5c09982fbda237c9c5c9d80889473d
parent 532739 61336bfc758449a130882874551df6c358ecd0ae
child 532741 eb232dca19b05733a3103e1689acad7c028ef063
push id117348
push userpadenot@mozilla.com
push dateThu, 28 May 2020 09:58:56 +0000
treeherderautoland@5e969a4bab42 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersachronop
bugs1628779
milestone78.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 1628779 - Add a function to get an estimation of the roundtrip latency of the default devices. r=achronop This is blocking, and will be used off-main-thread. Differential Revision: https://phabricator.services.mozilla.com/D75332
dom/media/CubebUtils.cpp
dom/media/CubebUtils.h
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -32,16 +32,18 @@
 #ifdef MOZ_WIDGET_ANDROID
 #  include "mozilla/java/GeckoAppShellWrappers.h"
 #endif
 #ifdef XP_WIN
 #  include "mozilla/mscom/EnsureMTA.h"
 #endif
 #include "audioipc_server_ffi_generated.h"
 #include "audioipc_client_ffi_generated.h"
+#include <cmath>
+#include <thread>
 
 #define AUDIOIPC_POOL_SIZE_DEFAULT 1
 #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"
@@ -685,16 +687,116 @@ cubeb_stream_prefs GetDefaultStreamPrefs
     return CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING;
   }
 #endif
   return CUBEB_STREAM_PREF_NONE;
 }
 
 bool RouteOutputAsVoice() { return sRouteOutputAsVoice; }
 
+long datacb(cubeb_stream*, void*, const void*, void*, long nframes) {
+  return nframes;
+}
+
+void statecb(cubeb_stream*, void*, cubeb_state) {}
+
+bool EstimatedRoundTripLatencyDefaultDevices(double* aMean, double* aStdDev) {
+  nsTArray<double> roundtripLatencies;
+  // Create a cubeb stream with the correct latency and default input/output
+  // devices (mono/stereo channels). Wait for two seconds, get the latency a few
+  // times.
+  int rv;
+  uint32_t rate;
+  uint32_t latencyFrames;
+  rv = cubeb_get_preferred_sample_rate(GetCubebContext(), &rate);
+  if (rv != CUBEB_OK) {
+    MOZ_LOG(gCubebLog, LogLevel::Error, ("Could not get preferred rate"));
+    return false;
+  }
+
+  cubeb_stream_params output_params;
+  output_params.format = CUBEB_SAMPLE_FLOAT32NE;
+  output_params.rate = rate;
+  output_params.channels = 2;
+  output_params.layout = CUBEB_LAYOUT_UNDEFINED;
+  output_params.prefs = GetDefaultStreamPrefs();
+
+  latencyFrames = GetCubebMTGLatencyInFrames(&output_params);
+
+  cubeb_stream_params input_params;
+  input_params.format = CUBEB_SAMPLE_FLOAT32NE;
+  input_params.rate = rate;
+  input_params.channels = 1;
+  input_params.layout = CUBEB_LAYOUT_UNDEFINED;
+  input_params.prefs = GetDefaultStreamPrefs();
+
+  cubeb_stream* stm;
+  rv = cubeb_stream_init(GetCubebContext(), &stm,
+                         "about:support latency estimation", NULL,
+                         &input_params, NULL, &output_params, latencyFrames,
+                         datacb, statecb, NULL);
+  if (rv != CUBEB_OK) {
+    MOZ_LOG(gCubebLog, LogLevel::Error, ("Could not get init stream"));
+    return false;
+  }
+
+  rv = cubeb_stream_start(stm);
+  if (rv != CUBEB_OK) {
+    MOZ_LOG(gCubebLog, LogLevel::Error, ("Could not start stream"));
+    return false;
+  }
+  // +-2s
+  for (uint32_t i = 0; i < 40; i++) {
+    std::this_thread::sleep_for(std::chrono::milliseconds(50));
+    uint32_t inputLatency, outputLatency, rvIn, rvOut;
+    rvOut = cubeb_stream_get_latency(stm, &outputLatency);
+    if (rvOut) {
+      MOZ_LOG(gCubebLog, LogLevel::Error, ("Could not get output latency"));
+    }
+    rvIn = cubeb_stream_get_input_latency(stm, &inputLatency);
+    if (rvIn) {
+      MOZ_LOG(gCubebLog, LogLevel::Error, ("Could not get input latency"));
+    }
+    if (rvIn != CUBEB_OK || rvOut != CUBEB_OK) {
+      continue;
+    }
+
+    double roundTrip = static_cast<double>(outputLatency + inputLatency) / rate;
+    roundtripLatencies.AppendElement(roundTrip);
+  }
+  rv = cubeb_stream_stop(stm);
+  if (rv != CUBEB_OK) {
+    MOZ_LOG(gCubebLog, LogLevel::Error, ("Could not stop the stream"));
+  }
+
+  *aMean = 0.0;
+  *aStdDev = 0.0;
+  double variance = 0.0;
+  for (uint32_t i = 0; i < roundtripLatencies.Length(); i++) {
+    *aMean += roundtripLatencies[i];
+  }
+
+  *aMean /= roundtripLatencies.Length();
+
+  for (uint32_t i = 0; i < roundtripLatencies.Length(); i++) {
+    variance += pow(roundtripLatencies[i] - *aMean, 2.);
+  }
+  variance /= roundtripLatencies.Length();
+
+  *aStdDev = sqrt(variance);
+
+  MOZ_LOG(gCubebLog, LogLevel::Debug,
+          ("Default device roundtrip latency in seconds %lf (stddev: %lf)",
+           *aMean, *aStdDev));
+
+  cubeb_stream_destroy(stm);
+
+  return true;
+}
+
 #ifdef MOZ_WIDGET_ANDROID
 uint32_t AndroidGetAudioOutputSampleRate() {
   int32_t sample_rate = java::GeckoAppShell::GetAudioOutputSampleRate();
   MOZ_ASSERT(sample_rate > 0);
   return sample_rate;
 }
 uint32_t AndroidGetAudioOutputFramesPerBuffer() {
   int32_t frames = java::GeckoAppShell::GetAudioOutputFramesPerBuffer();
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -50,16 +50,21 @@ char* GetForcedOutputDevice();
 // No-op on all platforms but Android, where it tells the device's AudioManager
 // to switch to "communication mode", which might change audio routing,
 // bluetooth communication type, etc.
 void SetInCommunication(bool aInCommunication);
 // Returns true if the output streams should be routed like a stream containing
 // voice data, and not generic audio. This can influence audio processing and
 // device selection.
 bool RouteOutputAsVoice();
+// Returns, in seconds, the roundtrip latency Gecko thinks there is between the
+// default input and output devices. This is for diagnosing purposes, the
+// latency figures are best used directly from the cubeb streams themselves, as
+// the devices being used matter. This is blocking.
+bool EstimatedRoundTripLatencyDefaultDevices(double* aMean, double* aStdDev);
 
 #  ifdef MOZ_WIDGET_ANDROID
 uint32_t AndroidGetAudioOutputSampleRate();
 uint32_t AndroidGetAudioOutputFramesPerBuffer();
 #  endif
 
 #  ifdef ENABLE_SET_CUBEB_BACKEND
 void ForceSetCubebContext(cubeb* aCubebContext);