Bug 742154 - Work around media crashtest shutdown hang in cubeb_winmm. r=cpearce

This commit is contained in:
Matthew Gregan 2012-04-16 15:00:40 +12:00
parent 71f3aee6c9
commit 2152e9f321
4 changed files with 151 additions and 77 deletions

View File

@ -332,7 +332,7 @@ static int PrefChanged(const char* aPref, void* aClosure)
gVolumeScale = NS_MAX<double>(0, PR_strtod(utf8.get(), nsnull)); gVolumeScale = NS_MAX<double>(0, PR_strtod(utf8.get(), nsnull));
} }
} else if (strcmp(aPref, PREF_USE_CUBEB) == 0) { } else if (strcmp(aPref, PREF_USE_CUBEB) == 0) {
bool value = Preferences::GetBool(aPref, false); bool value = Preferences::GetBool(aPref, true);
mozilla::MutexAutoLock lock(*gAudioPrefsLock); mozilla::MutexAutoLock lock(*gAudioPrefsLock);
gUseCubeb = value; gUseCubeb = value;
} }
@ -878,7 +878,8 @@ private:
// once the remaining contents of mBuffer are requested by // once the remaining contents of mBuffer are requested by
// cubeb, after which StateCallback will indicate drain // cubeb, after which StateCallback will indicate drain
// completion. // 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; StreamState mState;
@ -986,7 +987,7 @@ nsresult
nsBufferedAudioStream::Write(const void* aBuf, PRUint32 aFrames) nsBufferedAudioStream::Write(const void* aBuf, PRUint32 aFrames)
{ {
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
if (!mCubebStream) { if (!mCubebStream || mState == ERRORED) {
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
NS_ASSERTION(mState == INITIALIZED || mState == STARTED, "Stream write in unexpected state."); NS_ASSERTION(mState == INITIALIZED || mState == STARTED, "Stream write in unexpected state.");
@ -1008,9 +1009,13 @@ nsBufferedAudioStream::Write(const void* aBuf, PRUint32 aFrames)
mState = STARTED; mState = STARTED;
} }
if (bytesToCopy > 0) { if (mState == STARTED && bytesToCopy > 0) {
mon.Wait(); mon.Wait();
} }
if (mState != STARTED) {
return NS_ERROR_FAILURE;
}
} }
return NS_OK; return NS_OK;
@ -1046,7 +1051,7 @@ nsBufferedAudioStream::Drain()
return; return;
} }
mState = DRAINING; mState = DRAINING;
while (mState != DRAINED) { while (mState == DRAINING) {
mon.Wait(); mon.Wait();
} }
} }
@ -1099,7 +1104,7 @@ nsBufferedAudioStream::GetPositionInFramesUnlocked()
{ {
mMonitor.AssertCurrentThreadOwns(); mMonitor.AssertCurrentThreadOwns();
if (!mCubebStream) { if (!mCubebStream || mState == ERRORED) {
return -1; return -1;
} }
@ -1186,6 +1191,10 @@ nsBufferedAudioStream::StateCallback(cubeb_state aState)
MonitorAutoLock mon(mMonitor); MonitorAutoLock mon(mMonitor);
mState = DRAINED; mState = DRAINED;
mon.NotifyAll(); mon.NotifyAll();
} else if (aState == CUBEB_STATE_ERROR) {
MonitorAutoLock mon(mMonitor);
mState = ERRORED;
mon.NotifyAll();
} }
return CUBEB_OK; return CUBEB_OK;
} }

View File

@ -5,4 +5,4 @@ Makefile.in build files for the Mozilla build system.
The cubeb git repository is: git://github.com/kinetiknz/cubeb.git 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.

View File

@ -114,14 +114,15 @@ typedef struct {
typedef enum { typedef enum {
CUBEB_STATE_STARTED, /**< Stream started. */ CUBEB_STATE_STARTED, /**< Stream started. */
CUBEB_STATE_STOPPED, /**< Stream stopped. */ 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; } cubeb_state;
/** Result code enumeration. */ /** Result code enumeration. */
enum { enum {
CUBEB_OK = 0, /**< Success. */ CUBEB_OK = 0, /**< Success. */
CUBEB_ERROR = -1, /**< Unclassified error. */ CUBEB_ERROR = -1, /**< Unclassified error. */
CUBEB_ERROR_INVALID_FORMAT /**< Unsupported #cubeb_stream_params requested. */ CUBEB_ERROR_INVALID_FORMAT = -2 /**< Unsupported #cubeb_stream_params requested. */
}; };
/** User supplied data callback. /** User supplied data callback.

View File

@ -13,8 +13,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include <stdio.h> #define CUBEB_STREAM_MAX 32
#define NBUFS 4 #define NBUFS 4
const GUID KSDATAFORMAT_SUBTYPE_PCM = const GUID KSDATAFORMAT_SUBTYPE_PCM =
@ -32,6 +31,8 @@ struct cubeb {
HANDLE thread; HANDLE thread;
int shutdown; int shutdown;
PSLIST_HEADER work; PSLIST_HEADER work;
CRITICAL_SECTION lock;
unsigned int active_streams;
}; };
struct cubeb_stream { struct cubeb_stream {
@ -41,6 +42,7 @@ struct cubeb_stream {
cubeb_state_callback state_callback; cubeb_state_callback state_callback;
void * user_ptr; void * user_ptr;
WAVEHDR buffers[NBUFS]; WAVEHDR buffers[NBUFS];
size_t buffer_size;
int next_buffer; int next_buffer;
int free_buffers; int free_buffers;
int shutdown; int shutdown;
@ -57,7 +59,7 @@ bytes_per_frame(cubeb_stream_params params)
switch (params.format) { switch (params.format) {
case CUBEB_SAMPLE_S16LE: case CUBEB_SAMPLE_S16LE:
bytes = sizeof(signed int); bytes = sizeof(signed short);
break; break;
case CUBEB_SAMPLE_FLOAT32LE: case CUBEB_SAMPLE_FLOAT32LE:
bytes = sizeof(float); bytes = sizeof(float);
@ -76,7 +78,7 @@ cubeb_get_next_buffer(cubeb_stream * stm)
assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS); assert(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
hdr = &stm->buffers[stm->next_buffer]; hdr = &stm->buffers[stm->next_buffer];
assert(hdr->dwFlags == 0 || assert(hdr->dwFlags & WHDR_PREPARED ||
(hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE))); (hdr->dwFlags & WHDR_DONE && !(hdr->dwFlags & WHDR_INQUEUE)));
stm->next_buffer = (stm->next_buffer + 1) % NBUFS; stm->next_buffer = (stm->next_buffer + 1) % NBUFS;
stm->free_buffers -= 1; stm->free_buffers -= 1;
@ -85,33 +87,62 @@ cubeb_get_next_buffer(cubeb_stream * stm)
} }
static void static void
cubeb_submit_buffer(cubeb_stream * stm, WAVEHDR * hdr) cubeb_refill_stream(cubeb_stream * stm)
{ {
WAVEHDR * hdr;
long got; long got;
long wanted;
MMRESULT r; MMRESULT r;
got = stm->data_callback(stm, stm->user_ptr, hdr->lpData, EnterCriticalSection(&stm->lock);
hdr->dwBufferLength / bytes_per_frame(stm->params)); 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) { if (got < 0) {
/* XXX handle this case */ /* XXX handle this case */
assert(0); assert(0);
return; return;
} else if ((DWORD) got < hdr->dwBufferLength / bytes_per_frame(stm->params)) { } else if (got < wanted) {
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);
stm->draining = 1; stm->draining = 1;
} }
assert(hdr->dwFlags & WHDR_PREPARED); 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)); 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 static unsigned __stdcall
@ -122,34 +153,14 @@ cubeb_buffer_thread(void * user_ptr)
for (;;) { for (;;) {
DWORD rv; DWORD rv;
struct cubeb_stream_item * item; PSLIST_ENTRY item;
rv = WaitForSingleObject(ctx->event, INFINITE); rv = WaitForSingleObject(ctx->event, INFINITE);
assert(rv == WAIT_OBJECT_0); assert(rv == WAIT_OBJECT_0);
item = (struct cubeb_stream_item *) InterlockedPopEntrySList(ctx->work); while ((item = InterlockedPopEntrySList(ctx->work)) != NULL) {
while (item) { cubeb_refill_stream(((struct cubeb_stream_item *) item)->stream);
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);
_aligned_free(item); _aligned_free(item);
item = (struct cubeb_stream_item *) InterlockedPopEntrySList(ctx->work);
} }
if (ctx->shutdown) { if (ctx->shutdown) {
@ -183,6 +194,9 @@ cubeb_init(cubeb ** context, char const * context_name)
{ {
cubeb * ctx; cubeb * ctx;
assert(context);
*context = NULL;
ctx = calloc(1, sizeof(*ctx)); ctx = calloc(1, sizeof(*ctx));
assert(ctx); assert(ctx);
@ -202,6 +216,9 @@ cubeb_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR; return CUBEB_ERROR;
} }
InitializeCriticalSection(&ctx->lock);
ctx->active_streams = 0;
*context = ctx; *context = ctx;
return CUBEB_OK; return CUBEB_OK;
@ -212,8 +229,11 @@ cubeb_destroy(cubeb * ctx)
{ {
DWORD rv; DWORD rv;
assert(ctx->active_streams == 0);
assert(!InterlockedPopEntrySList(ctx->work)); assert(!InterlockedPopEntrySList(ctx->work));
DeleteCriticalSection(&ctx->lock);
if (ctx->thread) { if (ctx->thread) {
ctx->shutdown = 1; ctx->shutdown = 1;
SetEvent(ctx->event); SetEvent(ctx->event);
@ -244,6 +264,11 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
int i; int i;
size_t bufsz; size_t bufsz;
assert(context);
assert(stream);
*stream = NULL;
if (stream_params.rate < 1 || stream_params.rate > 192000 || if (stream_params.rate < 1 || stream_params.rate > 192000 ||
stream_params.channels < 1 || stream_params.channels > 32 || stream_params.channels < 1 || stream_params.channels > 32 ||
latency < 1 || latency > 2000) { latency < 1 || latency > 2000) {
@ -286,6 +311,17 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
wfx.Samples.wSamplesPerBlock = 0; wfx.Samples.wSamplesPerBlock = 0;
wfx.Samples.wReserved = 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)); stm = calloc(1, sizeof(*stm));
assert(stm); assert(stm);
@ -303,12 +339,7 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
} }
assert(bufsz % bytes_per_frame(stm->params) == 0); assert(bufsz % bytes_per_frame(stm->params) == 0);
for (i = 0; i < NBUFS; ++i) { stm->buffer_size = bufsz;
stm->buffers[i].lpData = calloc(1, bufsz);
assert(stm->buffers[i].lpData);
stm->buffers[i].dwBufferLength = bufsz;
stm->buffers[i].dwFlags = 0;
}
InitializeCriticalSection(&stm->lock); InitializeCriticalSection(&stm->lock);
@ -318,8 +349,6 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
return CUBEB_ERROR; return CUBEB_ERROR;
} }
stm->free_buffers = NBUFS;
/* cubeb_buffer_callback will be called during waveOutOpen, so all /* cubeb_buffer_callback will be called during waveOutOpen, so all
other initialization must be complete before calling it. */ other initialization must be complete before calling it. */
r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format, r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
@ -329,18 +358,28 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
cubeb_stream_destroy(stm); cubeb_stream_destroy(stm);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
assert(r == MMSYSERR_NOERROR);
r = waveOutPause(stm->waveout); 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) { 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)); 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; *stream = stm;
@ -351,7 +390,6 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
void void
cubeb_stream_destroy(cubeb_stream * stm) cubeb_stream_destroy(cubeb_stream * stm)
{ {
MMRESULT r;
DWORD rv; DWORD rv;
int i; int i;
int enqueued; int enqueued;
@ -360,25 +398,32 @@ cubeb_stream_destroy(cubeb_stream * stm)
EnterCriticalSection(&stm->lock); EnterCriticalSection(&stm->lock);
stm->shutdown = 1; stm->shutdown = 1;
r = waveOutReset(stm->waveout); waveOutReset(stm->waveout);
assert(r == MMSYSERR_NOERROR);
enqueued = NBUFS - stm->free_buffers; enqueued = NBUFS - stm->free_buffers;
LeaveCriticalSection(&stm->lock); LeaveCriticalSection(&stm->lock);
/* wait for all blocks to complete */ /* Wait for all blocks to complete. */
if (enqueued > 0) { while (enqueued > 0) {
rv = WaitForSingleObject(stm->event, INFINITE); rv = WaitForSingleObject(stm->event, INFINITE);
assert(rv == WAIT_OBJECT_0); 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) { for (i = 0; i < NBUFS; ++i) {
r = waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i])); if (stm->buffers[i].dwFlags & WHDR_PREPARED) {
assert(r == MMSYSERR_NOERROR); waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i]));
}
} }
r = waveOutClose(stm->waveout); waveOutClose(stm->waveout);
assert(r == MMSYSERR_NOERROR);
LeaveCriticalSection(&stm->lock);
} }
if (stm->event) { if (stm->event) {
@ -391,6 +436,11 @@ cubeb_stream_destroy(cubeb_stream * stm)
free(stm->buffers[i].lpData); 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); free(stm);
} }
@ -399,8 +449,13 @@ cubeb_stream_start(cubeb_stream * stm)
{ {
MMRESULT r; MMRESULT r;
EnterCriticalSection(&stm->lock);
r = waveOutRestart(stm->waveout); 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); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
@ -412,8 +467,13 @@ cubeb_stream_stop(cubeb_stream * stm)
{ {
MMRESULT r; MMRESULT r;
EnterCriticalSection(&stm->lock);
r = waveOutPause(stm->waveout); 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); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
@ -426,10 +486,14 @@ cubeb_stream_get_position(cubeb_stream * stm, uint64_t * position)
MMRESULT r; MMRESULT r;
MMTIME time; MMTIME time;
EnterCriticalSection(&stm->lock);
time.wType = TIME_SAMPLES; time.wType = TIME_SAMPLES;
r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
assert(r == MMSYSERR_NOERROR); LeaveCriticalSection(&stm->lock);
assert(time.wType == TIME_SAMPLES);
if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) {
return CUBEB_ERROR;
}
*position = time.u.sample; *position = time.u.sample;