author | Matthew Gregan <kinetik@flim.org> |
Mon, 16 Apr 2012 15:00:40 +1200 | |
changeset 91737 | 0e390be0b88fcdc3fd7b40ff3e3820f506e4bd98 |
parent 91736 | b99da5d23b1bb4383c41770aac25098b93782932 |
child 91738 | 2110c3be7d531f32fa0358578f01ce55167c2dd9 |
push id | 22472 |
push user | eakhgari@mozilla.com |
push date | Mon, 16 Apr 2012 15:03:21 +0000 |
treeherder | mozilla-central@0066df252596 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | cpearce |
bugs | 742154 |
milestone | 14.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
|
--- 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; }