| Index: media/audio/win/waveout_output_win.cc
|
| ===================================================================
|
| --- media/audio/win/waveout_output_win.cc (revision 112232)
|
| +++ media/audio/win/waveout_output_win.cc (working copy)
|
| @@ -15,18 +15,34 @@
|
| #include "media/audio/audio_util.h"
|
| #include "media/audio/win/audio_manager_win.h"
|
|
|
| +// Number of times InitializeCriticalSectionAndSpinCount() spins
|
| +// before going to sleep.
|
| +const DWORD kSpinCount = 2000;
|
| +
|
| // Some general thoughts about the waveOut API which is badly documented :
|
| -// - We use CALLBACK_EVENT mode in which XP signals events such as buffer
|
| -// releases.
|
| -// - We use RegisterWaitForSingleObject() so one of threads in thread pool
|
| -// automatically calls our callback that feeds more data to Windows.
|
| +// - We use CALLBACK_FUNCTION mode in which XP secretly creates two threads
|
| +// named _MixerCallbackThread and _waveThread which have real-time priority.
|
| +// The callbacks occur in _waveThread.
|
| // - Windows does not provide a way to query if the device is playing or paused
|
| // thus it forces you to maintain state, which naturally is not exactly
|
| // synchronized to the actual device state.
|
| +// - Some functions, like waveOutReset cannot be called in the callback thread
|
| +// or called in any random state because they deadlock. This results in a
|
| +// non- instantaneous Stop() method. waveOutPrepareHeader seems to be in the
|
| +// same boat.
|
| +// - waveOutReset() will forcefully kill the _waveThread so it is important
|
| +// to make sure we are not executing inside the audio source's OnMoreData()
|
| +// or that we take locks inside WaveCallback() or QueueNextPacket().
|
|
|
| // Sixty four MB is the maximum buffer size per AudioOutputStream.
|
| static const uint32 kMaxOpenBufferSize = 1024 * 1024 * 64;
|
|
|
| +// Our sound buffers are allocated once and kept in a linked list using the
|
| +// the WAVEHDR::dwUser variable. The last buffer points to the first buffer.
|
| +static WAVEHDR* GetNextBuffer(WAVEHDR* current) {
|
| + return reinterpret_cast<WAVEHDR*>(current->dwUser);
|
| +}
|
| +
|
| // See Also
|
| // http://www.thx.com/consumer/home-entertainment/home-theater/surround-sound-speaker-set-up/
|
| // http://en.wikipedia.org/wiki/Surround_sound
|
| @@ -63,18 +79,6 @@
|
| // TODO(fbarchard): Add additional masks for 7.2 and beyond.
|
| };
|
|
|
| -inline size_t PCMWaveOutAudioOutputStream::BufferSize() const {
|
| - // Round size of buffer up to the nearest 16 bytes.
|
| - return (sizeof(WAVEHDR) + buffer_size_ + 15u) & static_cast<size_t>(~15);
|
| -}
|
| -
|
| -inline WAVEHDR* PCMWaveOutAudioOutputStream::GetBuffer(int n) const {
|
| - DCHECK_GE(n, 0);
|
| - DCHECK_LT(n, num_buffers_);
|
| - return reinterpret_cast<WAVEHDR*>(&buffers_[n * BufferSize()]);
|
| -}
|
| -
|
| -
|
| PCMWaveOutAudioOutputStream::PCMWaveOutAudioOutputStream(
|
| AudioManagerWin* manager, const AudioParameters& params, int num_buffers,
|
| UINT device_id)
|
| @@ -84,10 +88,13 @@
|
| waveout_(NULL),
|
| callback_(NULL),
|
| num_buffers_(num_buffers),
|
| + buffer_(NULL),
|
| buffer_size_(params.GetPacketSize()),
|
| volume_(1),
|
| channels_(params.channels),
|
| pending_bytes_(0) {
|
| + ::InitializeCriticalSectionAndSpinCount(&lock_, kSpinCount);
|
| +
|
| format_.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
| format_.Format.nChannels = params.channels;
|
| format_.Format.nSamplesPerSec = params.sample_rate;
|
| @@ -109,35 +116,21 @@
|
|
|
| PCMWaveOutAudioOutputStream::~PCMWaveOutAudioOutputStream() {
|
| DCHECK(NULL == waveout_);
|
| + ::DeleteCriticalSection(&lock_);
|
| }
|
|
|
| bool PCMWaveOutAudioOutputStream::Open() {
|
| if (state_ != PCMA_BRAND_NEW)
|
| return false;
|
| - if (BufferSize() * num_buffers_ > kMaxOpenBufferSize)
|
| - return false;
|
| if (num_buffers_ < 2 || num_buffers_ > 5)
|
| return false;
|
| -
|
| - // Create buffer event.
|
| - buffer_event_.Set(::CreateEvent(NULL, // Security attributes
|
| - FALSE, // It will auto-reset
|
| - FALSE, // Initial state
|
| - NULL // No name
|
| - ));
|
| - if (!buffer_event_.Get()) {
|
| - return false;
|
| - }
|
| -
|
| - // Open the device.
|
| - // We'll be getting buffer_event_ events when it's time to refill the buffer.
|
| - MMRESULT result = ::waveOutOpen(
|
| - &waveout_,
|
| - device_id_,
|
| - reinterpret_cast<LPCWAVEFORMATEX>(&format_),
|
| - reinterpret_cast<DWORD_PTR>(buffer_event_.Get()),
|
| - NULL,
|
| - CALLBACK_EVENT);
|
| + // Open the device. We'll be getting callback in WaveCallback function.
|
| + // They occur in a magic, time-critical thread that windows creates.
|
| + MMRESULT result = ::waveOutOpen(&waveout_, device_id_,
|
| + reinterpret_cast<LPCWAVEFORMATEX>(&format_),
|
| + reinterpret_cast<DWORD_PTR>(WaveCallback),
|
| + reinterpret_cast<DWORD_PTR>(this),
|
| + CALLBACK_FUNCTION);
|
| if (result != MMSYSERR_NOERROR)
|
| return false;
|
|
|
| @@ -147,65 +140,60 @@
|
| }
|
|
|
| void PCMWaveOutAudioOutputStream::SetupBuffers() {
|
| - buffers_.reset(new char[BufferSize() * num_buffers_]);
|
| + WAVEHDR* last = NULL;
|
| + WAVEHDR* first = NULL;
|
| for (int ix = 0; ix != num_buffers_; ++ix) {
|
| - WAVEHDR* buffer = GetBuffer(ix);
|
| - buffer->lpData = reinterpret_cast<char*>(buffer) + sizeof(WAVEHDR);
|
| - buffer->dwBufferLength = buffer_size_;
|
| - buffer->dwBytesRecorded = 0;
|
| - buffer->dwFlags = WHDR_DONE;
|
| - buffer->dwLoops = 0;
|
| + uint32 sz = sizeof(WAVEHDR) + buffer_size_;
|
| + buffer_ = reinterpret_cast<WAVEHDR*>(new char[sz]);
|
| + buffer_->lpData = reinterpret_cast<char*>(buffer_) + sizeof(WAVEHDR);
|
| + buffer_->dwBufferLength = buffer_size_;
|
| + buffer_->dwBytesRecorded = 0;
|
| + buffer_->dwUser = reinterpret_cast<DWORD_PTR>(last);
|
| + buffer_->dwFlags = WHDR_DONE;
|
| + buffer_->dwLoops = 0;
|
| + if (ix == 0)
|
| + first = buffer_;
|
| + last = buffer_;
|
| // Tell windows sound drivers about our buffers. Not documented what
|
| // this does but we can guess that causes the OS to keep a reference to
|
| // the memory pages so the driver can use them without worries.
|
| - ::waveOutPrepareHeader(waveout_, buffer, sizeof(WAVEHDR));
|
| + ::waveOutPrepareHeader(waveout_, buffer_, sizeof(WAVEHDR));
|
| }
|
| + // Fix the first buffer to point to the last one.
|
| + first->dwUser = reinterpret_cast<DWORD_PTR>(last);
|
| }
|
|
|
| void PCMWaveOutAudioOutputStream::FreeBuffers() {
|
| + WAVEHDR* current = buffer_;
|
| for (int ix = 0; ix != num_buffers_; ++ix) {
|
| - ::waveOutUnprepareHeader(waveout_, GetBuffer(ix), sizeof(WAVEHDR));
|
| + WAVEHDR* next = GetNextBuffer(current);
|
| + ::waveOutUnprepareHeader(waveout_, current, sizeof(WAVEHDR));
|
| + delete[] reinterpret_cast<char*>(current);
|
| + current = next;
|
| }
|
| - buffers_.reset(NULL);
|
| + buffer_ = NULL;
|
| }
|
|
|
| -// Initially we ask the source to fill up all audio buffers. If we don't do
|
| +// Initially we ask the source to fill up both audio buffers. If we don't do
|
| // this then we would always get the driver callback when it is about to run
|
| // samples and that would leave too little time to react.
|
| void PCMWaveOutAudioOutputStream::Start(AudioSourceCallback* callback) {
|
| if (state_ != PCMA_READY)
|
| return;
|
| callback_ = callback;
|
| -
|
| - // Start watching for buffer events.
|
| - {
|
| - HANDLE waiting_handle = NULL;
|
| - ::RegisterWaitForSingleObject(&waiting_handle,
|
| - buffer_event_.Get(),
|
| - &BufferCallback,
|
| - static_cast<void*>(this),
|
| - INFINITE,
|
| - WT_EXECUTEDEFAULT);
|
| - if (!waiting_handle) {
|
| - HandleError(MMSYSERR_ERROR);
|
| - return;
|
| - }
|
| - waiting_handle_.Set(waiting_handle);
|
| - }
|
| -
|
| state_ = PCMA_PLAYING;
|
| -
|
| - // Queue the buffers.
|
| pending_bytes_ = 0;
|
| + WAVEHDR* buffer = buffer_;
|
| for (int ix = 0; ix != num_buffers_; ++ix) {
|
| - WAVEHDR* buffer = GetBuffer(ix);
|
| // Caller waits for 1st packet to become available, but not for others,
|
| // so we wait for them here.
|
| if (ix != 0)
|
| callback_->WaitTillDataReady();
|
| QueueNextPacket(buffer); // Read more data.
|
| pending_bytes_ += buffer->dwBufferLength;
|
| + buffer = GetNextBuffer(buffer);
|
| }
|
| + buffer = buffer_;
|
|
|
| // From now on |pending_bytes_| would be accessed by callback thread.
|
| // Most likely waveOutPause() or waveOutRestart() has its own memory barrier,
|
| @@ -221,11 +209,12 @@
|
| // Send the buffers to the audio driver. Note that the device is paused
|
| // so we avoid entering the callback method while still here.
|
| for (int ix = 0; ix != num_buffers_; ++ix) {
|
| - result = ::waveOutWrite(waveout_, GetBuffer(ix), sizeof(WAVEHDR));
|
| + result = ::waveOutWrite(waveout_, buffer, sizeof(WAVEHDR));
|
| if (result != MMSYSERR_NOERROR) {
|
| HandleError(result);
|
| break;
|
| }
|
| + buffer = GetNextBuffer(buffer);
|
| }
|
| result = ::waveOutRestart(waveout_);
|
| if (result != MMSYSERR_NOERROR) {
|
| @@ -234,42 +223,25 @@
|
| }
|
| }
|
|
|
| -// Stopping is tricky if we want it be fast.
|
| -// For now just do it synchronously and avoid all the complexities.
|
| -// TODO(enal): if we want faster Stop() we can create singleton that keeps track
|
| -// of all currently playing streams. Then you don't have to wait
|
| -// till all callbacks are completed. Of course access to singleton
|
| -// should be under its own lock, and checking the liveness and
|
| -// acquiring the lock on stream should be done atomically.
|
| +// Stopping is tricky. First, no buffer should be locked by the audio driver
|
| +// or else the waveOutReset() will deadlock and secondly, the callback should
|
| +// not be inside the AudioSource's OnMoreData because waveOutReset() forcefully
|
| +// kills the callback thread after releasing all buffers.
|
| void PCMWaveOutAudioOutputStream::Stop() {
|
| if (state_ != PCMA_PLAYING)
|
| return;
|
| - state_ = PCMA_STOPPING;
|
| - MemoryBarrier();
|
|
|
| - // Stop playback.
|
| + // Enter into critical section and call ::waveOutReset(). The fact that we
|
| + // entered critical section means that callback is out of critical section and
|
| + // it is safe to reset.
|
| + ::EnterCriticalSection(&lock_);
|
| MMRESULT res = ::waveOutReset(waveout_);
|
| + ::LeaveCriticalSection(&lock_);
|
| if (res != MMSYSERR_NOERROR) {
|
| - state_ = PCMA_PLAYING;
|
| HandleError(res);
|
| return;
|
| }
|
|
|
| - // Stop watching for buffer event, wait till all the callbacks are complete.
|
| - BOOL unregister = UnregisterWaitEx(waiting_handle_.Take(),
|
| - INVALID_HANDLE_VALUE);
|
| - if (!unregister) {
|
| - state_ = PCMA_PLAYING;
|
| - HandleError(MMSYSERR_ERROR);
|
| - return;
|
| - }
|
| -
|
| - // waveOutReset() leaves buffers in the unpredictable state, causing
|
| - // problems if we want to release or reuse them. Fix the states.
|
| - for (int ix = 0; ix != num_buffers_; ++ix) {
|
| - GetBuffer(ix)->dwFlags = WHDR_PREPARED;
|
| - }
|
| -
|
| // Don't use callback after Stop().
|
| callback_ = NULL;
|
|
|
| @@ -279,8 +251,8 @@
|
| // We can Close in any state except that trying to close a stream that is
|
| // playing Windows generates an error, which we propagate to the source.
|
| void PCMWaveOutAudioOutputStream::Close() {
|
| - Stop(); // Just to be sure. No-op if not playing.
|
| if (waveout_) {
|
| + // waveOutClose generates a callback with WOM_CLOSE id in the same thread.
|
| MMRESULT res = ::waveOutClose(waveout_);
|
| if (res != MMSYSERR_NOERROR) {
|
| HandleError(res);
|
| @@ -344,48 +316,40 @@
|
| buffer->dwFlags = WHDR_PREPARED;
|
| }
|
|
|
| -// One of the threads in our thread pool asynchronously calls this function when
|
| -// buffer_event_ is signalled. Search through all the buffers looking for freed
|
| -// ones, fills them with data, and "feed" the Windows.
|
| -// Note: by searching through all the buffers we guarantee that we fill all the
|
| -// buffers, even when "event loss" happens, i.e. if Windows signals event
|
| -// when it did not flip into unsignaled state from the previous signal.
|
| -void NTAPI PCMWaveOutAudioOutputStream::BufferCallback(PVOID lpParameter,
|
| - BOOLEAN) {
|
| - TRACE_EVENT0("audio", "PCMWaveOutAudioOutputStream::BufferCallback");
|
| +// Windows call us back in this function when some events happen. Most notably
|
| +// when it is done playing a buffer. Since we use double buffering it is
|
| +// convenient to think of |buffer| as free and GetNextBuffer(buffer) as in
|
| +// use by the driver.
|
| +void PCMWaveOutAudioOutputStream::WaveCallback(HWAVEOUT hwo, UINT msg,
|
| + DWORD_PTR instance,
|
| + DWORD_PTR param1, DWORD_PTR) {
|
| + TRACE_EVENT0("audio", "PCMWaveOutAudioOutputStream::WaveCallback");
|
|
|
| - PCMWaveOutAudioOutputStream* stream =
|
| - reinterpret_cast<PCMWaveOutAudioOutputStream*>(lpParameter);
|
| + if (msg == WOM_DONE) {
|
| + // WOM_DONE indicates that the driver is done with our buffer, we can
|
| + // either ask the source for more data or check if we need to stop playing.
|
| + WAVEHDR* buffer = reinterpret_cast<WAVEHDR*>(param1);
|
| + buffer->dwFlags = WHDR_DONE;
|
|
|
| - // Lock the stream so 2 callbacks do not interfere with each other.
|
| - // Two callbacks can be called simultaneously by 2 different threads in the
|
| - // thread pool if one of the callbacks is slow, or system is very busy and
|
| - // one of scheduled callbacks is not called on time.
|
| - base::AutoLock auto_lock(stream->lock_);
|
| - if (stream->state_ != PCMA_PLAYING)
|
| - return;
|
| + PCMWaveOutAudioOutputStream* stream =
|
| + reinterpret_cast<PCMWaveOutAudioOutputStream*>(instance);
|
|
|
| - for (int ix = 0; ix != stream->num_buffers_; ++ix) {
|
| - WAVEHDR* buffer = stream->GetBuffer(ix);
|
| - if (buffer->dwFlags & WHDR_DONE) {
|
| + // Do real work only if main thread has not yet called waveOutReset().
|
| + if (::TryEnterCriticalSection(&stream->lock_)) {
|
| // Before we queue the next packet, we need to adjust the number of
|
| // pending bytes since the last write to hardware.
|
| stream->pending_bytes_ -= buffer->dwBufferLength;
|
| +
|
| stream->QueueNextPacket(buffer);
|
|
|
| - // QueueNextPacket() can take a long time, especially if several of them
|
| - // were called back-to-back. Check if we are stopping now.
|
| - if (stream->state_ != PCMA_PLAYING)
|
| - return;
|
| -
|
| // Time to send the buffer to the audio driver. Since we are reusing
|
| // the same buffers we can get away without calling waveOutPrepareHeader.
|
| - MMRESULT result = ::waveOutWrite(stream->waveout_,
|
| - buffer,
|
| - sizeof(WAVEHDR));
|
| + MMRESULT result = ::waveOutWrite(hwo, buffer, sizeof(WAVEHDR));
|
| if (result != MMSYSERR_NOERROR)
|
| stream->HandleError(result);
|
| +
|
| stream->pending_bytes_ += buffer->dwBufferLength;
|
| + ::LeaveCriticalSection(&stream->lock_);
|
| }
|
| }
|
| }
|
|
|