Bug 1732479 - Update libcubeb to 8ef5a1ff. r=cubeb-reviewers,padenot
authorMatthew Gregan <kinetik@flim.org>
Tue, 12 Oct 2021 19:25:57 +0000 (2021-10-12)
changeset 595574 c7768090c505715c543b2a71e42b07fcb905d7a9
parent 595573 acc9a2f97b7e93cfcdfc36306d22f68d3f0f4964
child 595575 1f7cf38e9094ad0ba8f0a3b6506f765400fdc8c7
push id38872
push usermalexandru@mozilla.com
push dateWed, 13 Oct 2021 03:44:20 +0000 (2021-10-13)
treeherdermozilla-central@9b9f8bfe2625 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscubeb-reviewers, padenot
bugs1732479
milestone95.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 1732479 - Update libcubeb to 8ef5a1ff. r=cubeb-reviewers,padenot Differential Revision: https://phabricator.services.mozilla.com/D128111
media/libcubeb/moz.yaml
media/libcubeb/src/cubeb_wasapi.cpp
media/libcubeb/src/cubeb_winmm.c
media/libcubeb/update.sh
--- a/media/libcubeb/moz.yaml
+++ b/media/libcubeb/moz.yaml
@@ -14,10 +14,10 @@ bugzilla:
 origin:
   name: "cubeb"
   description: "Cross platform audio library"
 
   url: "https://github.com/kinetiknz/cubeb"
   license: "ISC"
 
   # update.sh will update this value
-  release: "f495dc982572a70701c5df930ef454b68bb6fe20 (2021-07-28 11:43:17 +1200)"
+  release: "ef5a1ffde99fe98ba0c28dab5128476e8aa384b8 (2021-10-12 08:09:34 +1300)"
 
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -2173,45 +2173,40 @@ setup_wasapi_stream_one_side(cubeb_strea
   REFERENCE_TIME minimum_period;
   REFERENCE_TIME default_period;
   hr = audio_client->GetDevicePeriod(&default_period, &minimum_period);
   if (FAILED(hr)) {
     LOG("Could not get device period: %lx", hr);
     return CUBEB_ERROR;
   }
 
-  REFERENCE_TIME latency_hns;
-
-  uint32_t latency_frames = stm->latency;
+  REFERENCE_TIME latency_hns = frames_to_hns(stream_params->rate, stm->latency);
+  stm->input_bluetooth_handsfree = false;
+
   cubeb_device_info device_info;
-  int rv = wasapi_create_device(stm->context, device_info,
-                                stm->device_enumerator.get(), device.get());
-  if (rv == CUBEB_OK) {
+  if (wasapi_create_device(stm->context, device_info,
+                           stm->device_enumerator.get(),
+                           device.get()) == CUBEB_OK) {
     const char * HANDSFREE_TAG = "BTHHFENUM";
     size_t len = sizeof(HANDSFREE_TAG);
     if (direction == eCapture) {
       uint32_t default_period_frames =
           hns_to_frames(device_info.default_rate, default_period);
       if (strlen(device_info.group_id) >= len &&
           strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) {
         stm->input_bluetooth_handsfree = true;
-      } else {
-        stm->input_bluetooth_handsfree = false;
       }
       // This multiplicator has been found empirically.
-      latency_frames = default_period_frames * 8;
+      uint32_t latency_frames = default_period_frames * 8;
       LOG("Input: latency increased to %u frames from a default of %u",
           latency_frames, default_period_frames);
+      latency_hns = frames_to_hns(device_info.default_rate, latency_frames);
     }
-    latency_hns = frames_to_hns(device_info.default_rate, latency_frames);
-
     wasapi_destroy_device(&device_info);
   } else {
-    stm->input_bluetooth_handsfree = false;
-    latency_hns = frames_to_hns(mix_params->rate, latency_frames);
     LOG("Could not get cubeb_device_info.");
   }
 
   if (stream_params->prefs & CUBEB_STREAM_PREF_RAW) {
     if (initialize_iaudioclient2(audio_client) != CUBEB_OK) {
       LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError());
       // This is not fatal.
     }
--- a/media/libcubeb/src/cubeb_winmm.c
+++ b/media/libcubeb/src/cubeb_winmm.c
@@ -116,16 +116,20 @@ struct cubeb_stream {
   int free_buffers;
   int shutdown;
   int draining;
   HANDLE event;
   HWAVEOUT waveout;
   CRITICAL_SECTION lock;
   uint64_t written;
   float soft_volume;
+  /* For position wrap-around handling: */
+  size_t frame_size;
+  DWORD prev_pos_lo_dword;
+  DWORD pos_hi_dword;
 };
 
 static size_t
 bytes_per_frame(cubeb_stream_params params)
 {
   size_t bytes;
 
   switch (params.format) {
@@ -552,16 +556,20 @@ winmm_stream_init(cubeb * context, cubeb
     if (r != MMSYSERR_NOERROR) {
       winmm_stream_destroy(stm);
       return CUBEB_ERROR;
     }
 
     winmm_refill_stream(stm);
   }
 
+  stm->frame_size = bytes_per_frame(stm->params);
+  stm->prev_pos_lo_dword = 0;
+  stm->pos_hi_dword = 0;
+
   *stream = stm;
 
   return CUBEB_OK;
 }
 
 static void
 winmm_stream_destroy(cubeb_stream * stm)
 {
@@ -704,55 +712,113 @@ winmm_stream_stop(cubeb_stream * stm)
     return CUBEB_ERROR;
   }
 
   stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
 
   return CUBEB_OK;
 }
 
+/*
+Microsoft wave audio docs say "samples are the preferred time format in which
+to represent the current position", but relying on this causes problems on
+Windows XP, the only OS cubeb_winmm is used on.
+
+While the wdmaud.sys driver internally tracks a 64-bit position and ensures no
+backward movement, the WinMM API limits the position returned from
+waveOutGetPosition() to a 32-bit DWORD (this applies equally to XP x64). The
+higher 32 bits are chopped off, and to an API consumer the position can appear
+to move backward.
+
+In theory, even a 32-bit TIME_SAMPLES position should provide plenty of
+playback time for typical use cases before this pseudo wrap-around, e.g:
+    (2^32 - 1)/48000 = ~24:51:18 for 48.0 kHz stereo;
+    (2^32 - 1)/44100 = ~27:03:12 for 44.1 kHz stereo.
+In reality, wdmaud.sys doesn't provide a TIME_SAMPLES position at all, only a
+32-bit TIME_BYTES position, from which wdmaud.drv derives TIME_SAMPLES:
+    SamplePos = (BytePos * 8) / BitsPerFrame,
+    where BitsPerFrame = Channels * BitsPerSample,
+Per dom\media\AudioSampleFormat.h, desktop builds always use 32-bit FLOAT32
+samples, so the maximum for TIME_SAMPLES should be:
+    (2^29 - 1)/48000 = ~03:06:25;
+    (2^29 - 1)/44100 = ~03:22:54.
+This might still be OK for typical browser usage, but there's also a bug in the
+formula above: BytePos * 8 (BytePos << 3) is done on a 32-bit BytePos, without
+first casting it to 64 bits, so the highest 3 bits, if set, would get shifted
+out, and the maximum possible TIME_SAMPLES drops unacceptably low:
+    (2^26 - 1)/48000 = ~00:23:18;
+    (2^26 - 1)/44100 = ~00:25:22.
+
+To work around these limitations, we just get the position in TIME_BYTES,
+recover the 64-bit value, and do our own conversion to samples.
+*/
+
+/* Convert chopped 32-bit waveOutGetPosition() into 64-bit true position. */
+static uint64_t
+update_64bit_position(cubeb_stream * stm, DWORD pos_lo_dword)
+{
+  /* Caller should be holding stm->lock. */
+  if (pos_lo_dword < stm->prev_pos_lo_dword) {
+    stm->pos_hi_dword++;
+    LOG("waveOutGetPosition() has wrapped around: %#lx -> %#lx",
+        stm->prev_pos_lo_dword, pos_lo_dword);
+    LOG("Wrap-around count = %#lx", stm->pos_hi_dword);
+    LOG("Current 64-bit position = %#llx",
+        (((uint64_t)stm->pos_hi_dword) << 32) | ((uint64_t)pos_lo_dword));
+  }
+  stm->prev_pos_lo_dword = pos_lo_dword;
+
+  return (((uint64_t)stm->pos_hi_dword) << 32) | ((uint64_t)pos_lo_dword);
+}
+
 static int
 winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
 {
   MMRESULT r;
   MMTIME time;
 
   EnterCriticalSection(&stm->lock);
-  time.wType = TIME_SAMPLES;
+  /* See the long comment above for why not just use TIME_SAMPLES here. */
+  time.wType = TIME_BYTES;
   r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
-  LeaveCriticalSection(&stm->lock);
 
-  if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
+  if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
+    LeaveCriticalSection(&stm->lock);
     return CUBEB_ERROR;
   }
 
-  *position = time.u.sample;
+  *position = update_64bit_position(stm, time.u.cb) / stm->frame_size;
+  LeaveCriticalSection(&stm->lock);
 
   return CUBEB_OK;
 }
 
 static int
 winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
 {
   MMRESULT r;
   MMTIME time;
-  uint64_t written;
+  uint64_t written, position;
 
   EnterCriticalSection(&stm->lock);
-  time.wType = TIME_SAMPLES;
+  /* See the long comment above for why not just use TIME_SAMPLES here. */
+  time.wType = TIME_BYTES;
   r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
+
+  if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
+    LeaveCriticalSection(&stm->lock);
+    return CUBEB_ERROR;
+  }
+
+  position = update_64bit_position(stm, time.u.cb);
   written = stm->written;
   LeaveCriticalSection(&stm->lock);
 
-  if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
-    return CUBEB_ERROR;
-  }
-
-  XASSERT(written - time.u.sample <= UINT32_MAX);
-  *latency = (uint32_t)(written - time.u.sample);
+  XASSERT((written - (position / stm->frame_size)) <= UINT32_MAX);
+  *latency = (uint32_t)(written - (position / stm->frame_size));
 
   return CUBEB_OK;
 }
 
 static int
 winmm_stream_set_volume(cubeb_stream * stm, float volume)
 {
   EnterCriticalSection(&stm->lock);
--- a/media/libcubeb/update.sh
+++ b/media/libcubeb/update.sh
@@ -76,13 +76,13 @@ if [ -n "$rev" ]; then
     version=$version-dirty
     echo "WARNING: updating from a dirty git repository."
   fi
   sed -i.bak -e "s/^ *release:.*/  release: \"$version ($date)\"/" moz.yaml
   if [[ ! "$( grep "$version" moz.yaml )" ]]; then
     echo "Updating moz.yaml failed."
     exit 1
   fi
-  rm moz.yaml.bak
+  rm -f moz.yaml.bak
   [[ -n "$commits" ]] && echo -e "Pick commits:\n$commits"
 else
   echo "Remember to update moz.yaml with the version details."
 fi