Bug 742154 - Work around media crashtest shutdown hang in cubeb_winmm. r=cpearce
authorMatthew Gregan <kinetik@flim.org>
Mon, 16 Apr 2012 15:00:40 +1200
changeset 91737 0e390be0b88fcdc3fd7b40ff3e3820f506e4bd98
parent 91736 b99da5d23b1bb4383c41770aac25098b93782932
child 91738 2110c3be7d531f32fa0358578f01ce55167c2dd9
push id22472
push usereakhgari@mozilla.com
push dateMon, 16 Apr 2012 15:03:21 +0000
treeherdermozilla-central@0066df252596 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs742154
milestone14.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 742154 - Work around media crashtest shutdown hang in cubeb_winmm. r=cpearce
content/media/nsAudioStream.cpp
media/libcubeb/README_MOZILLA
media/libcubeb/include/cubeb.h
media/libcubeb/src/cubeb_winmm.c
--- a/content/media/nsAudioStream.cpp
+++ b/content/media/nsAudioStream.cpp
@@ -327,17 +327,17 @@ static int PrefChanged(const char* aPref
     mozilla::MutexAutoLock lock(*gAudioPrefsLock);
     if (value.IsEmpty()) {
       gVolumeScale = 1.0;
     } else {
       NS_ConvertUTF16toUTF8 utf8(value);
       gVolumeScale = NS_MAX<double>(0, PR_strtod(utf8.get(), nsnull));
     }
   } else if (strcmp(aPref, PREF_USE_CUBEB) == 0) {
-    bool value = Preferences::GetBool(aPref, false);
+    bool value = Preferences::GetBool(aPref, true);
     mozilla::MutexAutoLock lock(*gAudioPrefsLock);
     gUseCubeb = value;
   }
   return 0;
 }
 
 static double GetVolumeScale()
 {
@@ -873,17 +873,18 @@ private:
   enum StreamState {
     INITIALIZED, // Initialized, playback has not begun.
     STARTED,     // Started by a call to Write() (iff INITIALIZED) or Resume().
     STOPPED,     // Stopped by a call to Pause().
     DRAINING,    // Drain requested.  DataCallback will indicate end of stream
                  // once the remaining contents of mBuffer are requested by
                  // cubeb, after which StateCallback will indicate drain
                  // completion.
-    DRAINED      // StateCallback has indicated that the drain is complete.
+    DRAINED,     // StateCallback has indicated that the drain is complete.
+    ERRORED      // Stream disabled due to an internal error.
   };
 
   StreamState mState;
 
   // Arbitrary default stream latency.  The higher this value, the longer stream
   // volume changes will take to become audible.
   static const unsigned int DEFAULT_LATENCY_MS = 100;
 };
@@ -981,17 +982,17 @@ nsBufferedAudioStream::Shutdown()
     mCubebStream.reset();
   }
 }
 
 nsresult
 nsBufferedAudioStream::Write(const void* aBuf, PRUint32 aFrames)
 {
   MonitorAutoLock mon(mMonitor);
-  if (!mCubebStream) {
+  if (!mCubebStream || mState == ERRORED) {
     return NS_ERROR_FAILURE;
   }
   NS_ASSERTION(mState == INITIALIZED || mState == STARTED, "Stream write in unexpected state.");
 
   const PRUint8* src = static_cast<const PRUint8*>(aBuf);
   PRUint32 bytesToCopy = aFrames * mBytesPerFrame;
 
   while (bytesToCopy > 0) {
@@ -1003,19 +1004,23 @@ nsBufferedAudioStream::Write(const void*
     mBuffer.AppendElements(src, available);
     src += available;
     bytesToCopy -= available;
 
     if (mState != STARTED && cubeb_stream_start(mCubebStream) == CUBEB_OK) {
       mState = STARTED;
     }
 
-    if (bytesToCopy > 0) {
+    if (mState == STARTED && bytesToCopy > 0) {
       mon.Wait();
     }
+
+    if (mState != STARTED) {
+      return NS_ERROR_FAILURE;
+    }
   }
 
   return NS_OK;
 }
 
 PRUint32
 nsBufferedAudioStream::Available()
 {
@@ -1041,17 +1046,17 @@ nsBufferedAudioStream::SetVolume(double 
 void
 nsBufferedAudioStream::Drain()
 {
   MonitorAutoLock mon(mMonitor);
   if (mState != STARTED) {
     return;
   }
   mState = DRAINING;
-  while (mState != DRAINED) {
+  while (mState == DRAINING) {
     mon.Wait();
   }
 }
 
 void
 nsBufferedAudioStream::Pause()
 {
   MonitorAutoLock mon(mMonitor);
@@ -1094,17 +1099,17 @@ nsBufferedAudioStream::GetPositionInFram
   return GetPositionInFramesUnlocked();
 }
 
 PRInt64
 nsBufferedAudioStream::GetPositionInFramesUnlocked()
 {
   mMonitor.AssertCurrentThreadOwns();
 
-  if (!mCubebStream) {
+  if (!mCubebStream || mState == ERRORED) {
     return -1;
   }
 
   uint64_t position = 0;
   if (cubeb_stream_get_position(mCubebStream, &position) != CUBEB_OK) {
     return -1;
   }
 
@@ -1181,13 +1186,17 @@ nsBufferedAudioStream::DataCallback(void
 
 int
 nsBufferedAudioStream::StateCallback(cubeb_state aState)
 {
   if (aState == CUBEB_STATE_DRAINED) {
     MonitorAutoLock mon(mMonitor);
     mState = DRAINED;
     mon.NotifyAll();
+  } else if (aState == CUBEB_STATE_ERROR) {
+    MonitorAutoLock mon(mMonitor);
+    mState = ERRORED;
+    mon.NotifyAll();
   }
   return CUBEB_OK;
 }
 #endif
 
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb 
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
 
-The git commit ID used was ddfaaf39c1a15cfb1a04ce62d6bd253737fc764a-dirty.
+The git commit ID used was 3ef8175c72c40f02d68181d882a51d86d54eff6f.
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -109,24 +109,25 @@ typedef struct {
   unsigned int rate;          /**< Requested sample rate.  Valid range is [1, 192000]. */
   unsigned int channels;      /**< Requested channel count.  Valid range is [1, 32]. */
 } cubeb_stream_params;
 
 /** Stream states signaled via state_callback. */
 typedef enum {
   CUBEB_STATE_STARTED, /**< Stream started. */
   CUBEB_STATE_STOPPED, /**< Stream stopped. */
-  CUBEB_STATE_DRAINED /**< Stream drained. */
+  CUBEB_STATE_DRAINED, /**< Stream drained. */
+  CUBEB_STATE_ERROR    /**< Stream disabled due to error. */
 } cubeb_state;
 
 /** Result code enumeration. */
 enum {
-  CUBEB_OK = 0,              /**< Success. */
-  CUBEB_ERROR = -1,          /**< Unclassified error. */
-  CUBEB_ERROR_INVALID_FORMAT /**< Unsupported #cubeb_stream_params requested. */
+  CUBEB_OK = 0,                   /**< Success. */
+  CUBEB_ERROR = -1,               /**< Unclassified error. */
+  CUBEB_ERROR_INVALID_FORMAT = -2 /**< Unsupported #cubeb_stream_params requested. */
 };
 
 /** User supplied data callback.
     @param stream
     @param user_ptr
     @param buffer
     @param nframes
     @retval Number of frames written to buffer, which must equal nframes except at end of stream.
--- a/media/libcubeb/src/cubeb_winmm.c
+++ b/media/libcubeb/src/cubeb_winmm.c
@@ -8,18 +8,17 @@
 #include <assert.h>
 #include <windows.h>
 #include <mmreg.h>
 #include <mmsystem.h>
 #include <process.h>
 #include <stdlib.h>
 #include "cubeb/cubeb.h"
 
-#include <stdio.h>
-
+#define CUBEB_STREAM_MAX 32
 #define NBUFS 4
 
 const GUID KSDATAFORMAT_SUBTYPE_PCM =
 { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
 const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT =
 { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
 
 struct cubeb_stream_item {
@@ -27,42 +26,45 @@ struct cubeb_stream_item {
   cubeb_stream * stream;
 };
 
 struct cubeb {
   HANDLE event;
   HANDLE thread;
   int shutdown;
   PSLIST_HEADER work;
+  CRITICAL_SECTION lock;
+  unsigned int active_streams;
 };
 
 struct cubeb_stream {
   cubeb * context;
   cubeb_stream_params params;
   cubeb_data_callback data_callback;
   cubeb_state_callback state_callback;
   void * user_ptr;
   WAVEHDR buffers[NBUFS];
+  size_t buffer_size;
   int next_buffer;
   int free_buffers;
   int shutdown;
   int draining;
   HANDLE event;
   HWAVEOUT waveout;
   CRITICAL_SECTION lock;
 };
 
 static size_t
 bytes_per_frame(cubeb_stream_params params)
 {
   size_t bytes;
 
   switch (params.format) {
   case CUBEB_SAMPLE_S16LE:
-    bytes = sizeof(signed int);
+    bytes = sizeof(signed short);
     break;
   case CUBEB_SAMPLE_FLOAT32LE:
     bytes = sizeof(float);
     break;
   default:
     assert(0);
   }
 
@@ -71,90 +73,99 @@ bytes_per_frame(cubeb_stream_params para
 
 static WAVEHDR *
 cubeb_get_next_buffer(cubeb_stream * stm)
 {
   WAVEHDR * hdr = NULL;
 
   assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
   hdr = &stm->buffers[stm->next_buffer];
-  assert(hdr->dwFlags == 0 ||
+  assert(hdr->dwFlags & WHDR_PREPARED ||
          (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE)));
   stm->next_buffer = (stm->next_buffer + 1) % NBUFS;
   stm->free_buffers -= 1;
 
   return hdr;
 }
 
 static void
-cubeb_submit_buffer(cubeb_stream * stm, WAVEHDR * hdr)
+cubeb_refill_stream(cubeb_stream * stm)
 {
+  WAVEHDR * hdr;
   long got;
+  long wanted;
   MMRESULT r;
 
-  got = stm->data_callback(stm, stm->user_ptr, hdr->lpData,
-                           hdr->dwBufferLength / bytes_per_frame(stm->params));
+  EnterCriticalSection(&stm->lock);
+  stm->free_buffers += 1;
+  assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
+
+  if (stm->draining) {
+    LeaveCriticalSection(&stm->lock);
+    if (stm->free_buffers == NBUFS) {
+      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+    }
+    SetEvent(stm->event);
+    return;
+  }
+
+  if (stm->shutdown) {
+    LeaveCriticalSection(&stm->lock);
+    SetEvent(stm->event);
+    return;
+  }
+
+  hdr = cubeb_get_next_buffer(stm);
+
+  wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params);
+
+  /* It is assumed that the caller is holding this lock.  It must be dropped
+     during the callback to avoid deadlocks. */
+  LeaveCriticalSection(&stm->lock);
+  got = stm->data_callback(stm, stm->user_ptr, hdr->lpData, wanted);
+  EnterCriticalSection(&stm->lock);
   if (got < 0) {
     /* XXX handle this case */
     assert(0);
     return;
-  } else if ((DWORD) got < hdr->dwBufferLength / bytes_per_frame(stm->params)) {
-    r = waveOutUnprepareHeader(stm->waveout, hdr, sizeof(*hdr));
-    assert(r == MMSYSERR_NOERROR);
-
-    hdr->dwBufferLength = got * bytes_per_frame(stm->params);
-
-    r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr));
-    assert(r == MMSYSERR_NOERROR);
-
+  } else if (got < wanted) {
     stm->draining = 1;
   }
 
   assert(hdr->dwFlags & WHDR_PREPARED);
 
+  hdr->dwBufferLength = got * bytes_per_frame(stm->params);
+  assert(hdr->dwBufferLength <= stm->buffer_size);
+
   r = waveOutWrite(stm->waveout, hdr, sizeof(*hdr));
-  assert(r == MMSYSERR_NOERROR);
+  if (r != MMSYSERR_NOERROR) {
+    LeaveCriticalSection(&stm->lock);
+    stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
+    return;
+  }
+
+  LeaveCriticalSection(&stm->lock);
 }
 
 static unsigned __stdcall
 cubeb_buffer_thread(void * user_ptr)
 {
   cubeb * ctx = (cubeb *) user_ptr;
   assert(ctx);
 
   for (;;) {
     DWORD rv;
-    struct cubeb_stream_item * item;
+    PSLIST_ENTRY item;
 
     rv = WaitForSingleObject(ctx->event, INFINITE);
     assert(rv == WAIT_OBJECT_0);
 
-    item = (struct cubeb_stream_item *) InterlockedPopEntrySList(ctx->work);
-    while (item) {
-      cubeb_stream * stm = item->stream;
-
-      EnterCriticalSection(&stm->lock);
-      stm->free_buffers += 1;
-      assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
-
-      if (stm->draining || stm->shutdown) {
-        if (stm->free_buffers == NBUFS) {
-          if (stm->draining) {
-            stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
-          }
-          SetEvent(stm->event);
-        }
-      } else {
-        cubeb_submit_buffer(stm, cubeb_get_next_buffer(stm));
-      }
-      LeaveCriticalSection(&stm->lock);
-
+    while ((item = InterlockedPopEntrySList(ctx->work)) != NULL) {
+      cubeb_refill_stream(((struct cubeb_stream_item *) item)->stream);
       _aligned_free(item);
-
-      item = (struct cubeb_stream_item *) InterlockedPopEntrySList(ctx->work);
     }
 
     if (ctx->shutdown) {
       break;
     }
   }
 
   return 0;
@@ -178,16 +189,19 @@ cubeb_buffer_callback(HWAVEOUT waveout, 
   SetEvent(stm->context->event);
 }
 
 int
 cubeb_init(cubeb ** context, char const * context_name)
 {
   cubeb * ctx;
 
+  assert(context);
+  *context = NULL;
+
   ctx = calloc(1, sizeof(*ctx));
   assert(ctx);
 
   ctx->work = _aligned_malloc(sizeof(*ctx->work), MEMORY_ALLOCATION_ALIGNMENT);
   assert(ctx->work);
   InitializeSListHead(ctx->work);
 
   ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
@@ -197,28 +211,34 @@ cubeb_init(cubeb ** context, char const 
   }
 
   ctx->thread = (HANDLE) _beginthreadex(NULL, 64 * 1024, cubeb_buffer_thread, ctx, 0, NULL);
   if (!ctx->thread) {
     cubeb_destroy(ctx);
     return CUBEB_ERROR;
   }
 
+  InitializeCriticalSection(&ctx->lock);
+  ctx->active_streams = 0;
+
   *context = ctx;
 
   return CUBEB_OK;
 }
 
 void
 cubeb_destroy(cubeb * ctx)
 {
   DWORD rv;
 
+  assert(ctx->active_streams == 0);
   assert(!InterlockedPopEntrySList(ctx->work));
 
+  DeleteCriticalSection(&ctx->lock);
+
   if (ctx->thread) {
     ctx->shutdown = 1;
     SetEvent(ctx->event);
     rv = WaitForSingleObject(ctx->thread, INFINITE);
     assert(rv == WAIT_OBJECT_0);
     CloseHandle(ctx->thread);
   }
 
@@ -239,16 +259,21 @@ cubeb_stream_init(cubeb * context, cubeb
                   void * user_ptr)
 {
   MMRESULT r;
   WAVEFORMATEXTENSIBLE wfx;
   cubeb_stream * stm;
   int i;
   size_t bufsz;
 
+  assert(context);
+  assert(stream);
+
+  *stream = NULL;
+
   if (stream_params.rate < 1 || stream_params.rate > 192000 ||
       stream_params.channels < 1 || stream_params.channels > 32 ||
       latency < 1 || latency > 2000) {
     return CUBEB_ERROR_INVALID_FORMAT;
   }
 
   memset(&wfx, 0, sizeof(wfx));
   if (stream_params.channels > 2) {
@@ -281,16 +306,27 @@ cubeb_stream_init(cubeb * context, cubeb
   }
 
   wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
   wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
   wfx.Samples.wValidBitsPerSample = 0;
   wfx.Samples.wSamplesPerBlock = 0;
   wfx.Samples.wReserved = 0;
 
+  EnterCriticalSection(&context->lock);
+  /* CUBEB_STREAM_MAX is a horrible hack to avoid a situation where, when
+     many streams are active at once, a subset of them will not consume (via
+     playback) or release (via waveOutReset) their buffers. */
+  if (context->active_streams >= CUBEB_STREAM_MAX) {
+    LeaveCriticalSection(&context->lock);
+    return CUBEB_ERROR;
+  }
+  context->active_streams += 1;
+  LeaveCriticalSection(&context->lock);
+
   stm = calloc(1, sizeof(*stm));
   assert(stm);
 
   stm->context = context;
 
   stm->params = stream_params;
 
   stm->data_callback = data_callback;
@@ -298,141 +334,169 @@ cubeb_stream_init(cubeb * context, cubeb
   stm->user_ptr = user_ptr;
 
   bufsz = (size_t) (stm->params.rate / 1000.0 * latency * bytes_per_frame(stm->params) / NBUFS);
   if (bufsz % bytes_per_frame(stm->params) != 0) {
     bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params));
   }
   assert(bufsz % bytes_per_frame(stm->params) == 0);
 
-  for (i = 0; i < NBUFS; ++i) {
-    stm->buffers[i].lpData = calloc(1, bufsz);
-    assert(stm->buffers[i].lpData);
-    stm->buffers[i].dwBufferLength = bufsz;
-    stm->buffers[i].dwFlags = 0;
-  }
+  stm->buffer_size = bufsz;
 
   InitializeCriticalSection(&stm->lock);
 
   stm->event = CreateEvent(NULL, FALSE, FALSE, NULL);
   if (!stm->event) {
     cubeb_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
-  stm->free_buffers = NBUFS;
-
   /* cubeb_buffer_callback will be called during waveOutOpen, so all
      other initialization must be complete before calling it. */
   r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
                   (DWORD_PTR) cubeb_buffer_callback, (DWORD_PTR) stm,
                   CALLBACK_FUNCTION);
   if (r != MMSYSERR_NOERROR) {
     cubeb_stream_destroy(stm);
     return CUBEB_ERROR;
   }
-  assert(r == MMSYSERR_NOERROR);
 
   r = waveOutPause(stm->waveout);
-  assert(r == MMSYSERR_NOERROR);
+  if (r != MMSYSERR_NOERROR) {
+    cubeb_stream_destroy(stm);
+    return CUBEB_ERROR;
+  }
 
   for (i = 0; i < NBUFS; ++i) {
-    WAVEHDR * hdr = cubeb_get_next_buffer(stm);
+    WAVEHDR * hdr = &stm->buffers[i];
+
+    hdr->lpData = calloc(1, bufsz);
+    assert(hdr->lpData);
+    hdr->dwBufferLength = bufsz;
+    hdr->dwFlags = 0;
 
     r = waveOutPrepareHeader(stm->waveout, hdr, sizeof(*hdr));
-    assert(r == MMSYSERR_NOERROR);
+    if (r != MMSYSERR_NOERROR) {
+      cubeb_stream_destroy(stm);
+      return CUBEB_ERROR;
+    }
 
-    cubeb_submit_buffer(stm, hdr);
+    cubeb_refill_stream(stm);
   }
 
   *stream = stm;
 
   return CUBEB_OK;
 }
 
 void
 cubeb_stream_destroy(cubeb_stream * stm)
 {
-  MMRESULT r;
   DWORD rv;
   int i;
   int enqueued;
 
   if (stm->waveout) {
     EnterCriticalSection(&stm->lock);
     stm->shutdown = 1;
 
-    r = waveOutReset(stm->waveout);
-    assert(r == MMSYSERR_NOERROR);
+    waveOutReset(stm->waveout);
 
     enqueued = NBUFS - stm->free_buffers;
     LeaveCriticalSection(&stm->lock);
 
-    /* wait for all blocks to complete */
-    if (enqueued > 0) {
+    /* Wait for all blocks to complete. */
+    while (enqueued > 0) {
       rv = WaitForSingleObject(stm->event, INFINITE);
       assert(rv == WAIT_OBJECT_0);
+
+      EnterCriticalSection(&stm->lock);
+      enqueued = NBUFS - stm->free_buffers;
+      LeaveCriticalSection(&stm->lock);
     }
 
+    EnterCriticalSection(&stm->lock);
+
     for (i = 0; i < NBUFS; ++i) {
-      r = waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i]));
-      assert(r == MMSYSERR_NOERROR);
+      if (stm->buffers[i].dwFlags & WHDR_PREPARED) {
+        waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i]));
+      }
     }
 
-    r = waveOutClose(stm->waveout);
-    assert(r == MMSYSERR_NOERROR);
+    waveOutClose(stm->waveout);
+
+    LeaveCriticalSection(&stm->lock);
   }
 
   if (stm->event) {
     CloseHandle(stm->event);
   }
 
   DeleteCriticalSection(&stm->lock);
 
   for (i = 0; i < NBUFS; ++i) {
     free(stm->buffers[i].lpData);
   }
 
+  EnterCriticalSection(&stm->context->lock);
+  assert(stm->context->active_streams >= 1);
+  stm->context->active_streams -= 1;
+  LeaveCriticalSection(&stm->context->lock);
+
   free(stm);
 }
 
 int
 cubeb_stream_start(cubeb_stream * stm)
 {
   MMRESULT r;
 
+  EnterCriticalSection(&stm->lock);
   r = waveOutRestart(stm->waveout);
-  assert(r == MMSYSERR_NOERROR);
+  LeaveCriticalSection(&stm->lock);
+
+  if (r != MMSYSERR_NOERROR) {
+    return CUBEB_ERROR;
+  }
 
   stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
 
   return CUBEB_OK;
 }
 
 int
 cubeb_stream_stop(cubeb_stream * stm)
 {
   MMRESULT r;
 
+  EnterCriticalSection(&stm->lock);
   r = waveOutPause(stm->waveout);
-  assert(r == MMSYSERR_NOERROR);
+  LeaveCriticalSection(&stm->lock);
+
+  if (r != MMSYSERR_NOERROR) {
+    return CUBEB_ERROR;
+  }
 
   stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
 
   return CUBEB_OK;
 }
 
 int
 cubeb_stream_get_position(cubeb_stream * stm, uint64_t * position)
 {
   MMRESULT r;
   MMTIME time;
 
+  EnterCriticalSection(&stm->lock);
   time.wType = TIME_SAMPLES;
   r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
-  assert(r == MMSYSERR_NOERROR);
-  assert(time.wType == TIME_SAMPLES);
+  LeaveCriticalSection(&stm->lock);
+
+  if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
+    return CUBEB_ERROR;
+  }
 
   *position = time.u.sample;
 
   return CUBEB_OK;
 }