Chromium Code Reviews| Index: media/audio/win/waveout_output_win.cc |
| =================================================================== |
| --- media/audio/win/waveout_output_win.cc (revision 110574) |
| +++ media/audio/win/waveout_output_win.cc (working copy) |
| @@ -9,16 +9,13 @@ |
| #pragma comment(lib, "winmm.lib") |
| #include "base/basictypes.h" |
| +#include "base/bind.h" |
| #include "base/debug/trace_event.h" |
| #include "base/logging.h" |
| #include "media/audio/audio_io.h" |
| #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_FUNCTION mode in which XP secretly creates two threads |
| // named _MixerCallbackThread and _waveThread which have real-time priority. |
| @@ -26,23 +23,13 @@ |
| // - 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 |
| +// - 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(). |
| +// non-instantaneous Stop() method. waveOutWrite() and waveOutPrepareHeader |
| +// seem to be in the same boat. |
| +// - We have to use separate "feeder" thread that calls waveOutWrite() to feed |
| +// buffers to driver, cannot do it from the callback. |
| -// 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 |
| @@ -79,6 +66,18 @@ |
| // 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); |
|
tommi (sloooow) - chröme
2011/11/19 17:23:27
nit: instead of 15 I find hex better to read for b
|
| +} |
| + |
| +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) |
| @@ -88,13 +87,10 @@ |
| 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; |
| @@ -116,7 +112,6 @@ |
| PCMWaveOutAudioOutputStream::~PCMWaveOutAudioOutputStream() { |
| DCHECK(NULL == waveout_); |
| - ::DeleteCriticalSection(&lock_); |
| } |
| bool PCMWaveOutAudioOutputStream::Open() { |
| @@ -140,41 +135,29 @@ |
| } |
| void PCMWaveOutAudioOutputStream::SetupBuffers() { |
| - WAVEHDR* last = NULL; |
| - WAVEHDR* first = NULL; |
| + buffers_.reset(new char[BufferSize() * num_buffers_]); |
| for (int ix = 0; ix != num_buffers_; ++ix) { |
| - 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_; |
| + 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; |
| // 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) { |
| - WAVEHDR* next = GetNextBuffer(current); |
| - ::waveOutUnprepareHeader(waveout_, current, sizeof(WAVEHDR)); |
| - delete[] reinterpret_cast<char*>(current); |
| - current = next; |
| + ::waveOutUnprepareHeader(waveout_, GetBuffer(ix), sizeof(WAVEHDR)); |
| } |
| - buffer_ = NULL; |
| + buffers_.reset(NULL); |
| } |
| -// Initially we ask the source to fill up both audio buffers. If we don't do |
| +// Initially we ask the source to fill up all 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) { |
| @@ -182,18 +165,25 @@ |
| return; |
| callback_ = callback; |
| state_ = PCMA_PLAYING; |
| + |
| + playing_object_ = new PlayingObject(this); |
| + |
| + // Queue the buffers. |
| + // TODO(enal): If there are more than 2, queue only first 2 and schedule |
| + // remaining to be filled in the "feeder" thread. Non-trivial because we have |
| + // to be sure that data is ready. Can be done by storing time of last |
| + // OnMoreData() call and scheduling delayed task in FeedBuffer() if data is |
| + // not ready yet. |
| 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, |
| @@ -209,12 +199,13 @@ |
| // 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_, buffer, sizeof(WAVEHDR)); |
| + result = ::waveOutWrite(waveout_, GetBuffer(ix), sizeof(WAVEHDR)); |
| if (result != MMSYSERR_NOERROR) { |
| HandleError(result); |
| break; |
| } |
| - buffer = GetNextBuffer(buffer); |
| + // Each successfully sent buffer references |playing_object_|. |
| + playing_object_->AddRef(); |
| } |
| result = ::waveOutRestart(waveout_); |
| if (result != MMSYSERR_NOERROR) { |
| @@ -223,25 +214,25 @@ |
| } |
| } |
| -// 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. |
| +// Stopping is tricky. We want to avoid stopping while feeder thread feeds |
| +// buffer to driver, so use the lock on the |playing_object_|. |
| void PCMWaveOutAudioOutputStream::Stop() { |
| if (state_ != PCMA_PLAYING) |
| return; |
| - |
| - // 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_); |
| + MMRESULT res = MMSYSERR_NOERROR; |
| + { |
| + base::AutoLock auto_lock(*playing_object_->lock()); |
| + playing_object_->stop_playing(); |
| + MemoryBarrier(); |
| + res = ::waveOutReset(waveout_); |
| + } |
| if (res != MMSYSERR_NOERROR) { |
| HandleError(res); |
| return; |
| } |
| + playing_object_ = NULL; |
|
tommi (sloooow) - chröme
2011/11/19 17:23:27
While this correctly releases the object, it also
|
| + |
| // Don't use callback after Stop(). |
| callback_ = NULL; |
| @@ -317,39 +308,66 @@ |
| } |
| // 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. |
| +// when it is done playing a buffer. Cannot feed freed buffer back to the system |
| +// from here, have to schedule it for the separate thread. |
|
cpu_(ooo_6.6-7.5)
2011/11/21 01:18:43
I don't agree with this approach. We have added an
tommi (sloooow) - chröme
2011/11/22 08:21:45
Hey Carlos - If we use the event model, won't the
|
| void PCMWaveOutAudioOutputStream::WaveCallback(HWAVEOUT hwo, UINT msg, |
| DWORD_PTR instance, |
| DWORD_PTR param1, DWORD_PTR) { |
| TRACE_EVENT0("audio", "PCMWaveOutAudioOutputStream::WaveCallback"); |
| 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. |
| + // WOM_DONE indicates that the driver is done with our buffer, |
| + // if still playing ask "feedef" thread to buffer more data. |
| WAVEHDR* buffer = reinterpret_cast<WAVEHDR*>(param1); |
| buffer->dwFlags = WHDR_DONE; |
| PCMWaveOutAudioOutputStream* stream = |
| reinterpret_cast<PCMWaveOutAudioOutputStream*>(instance); |
| - // 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; |
| + if (stream->playing_object_->playing()) { |
| + stream->manager_->GetMessageLoop()->PostTask( |
| + FROM_HERE, |
| + base::Bind( |
| + &PCMWaveOutAudioOutputStream::PlayingObject::FeedBuffer, |
| + stream->playing_object_.get(), |
| + buffer, |
| + hwo)); |
| + } |
| + stream->playing_object_->Release(); |
| + } |
| +} |
| - stream->QueueNextPacket(buffer); |
| +PCMWaveOutAudioOutputStream::PlayingObject::PlayingObject( |
| + PCMWaveOutAudioOutputStream *stream) : |
| + stream_(stream) { |
| + DCHECK(stream_ != NULL); |
| +} |
| - // 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(hwo, buffer, sizeof(WAVEHDR)); |
| - if (result != MMSYSERR_NOERROR) |
| - stream->HandleError(result); |
| +PCMWaveOutAudioOutputStream::PlayingObject::~PlayingObject() { |
| + DCHECK(stream_ == NULL); |
| +} |
| - stream->pending_bytes_ += buffer->dwBufferLength; |
| - ::LeaveCriticalSection(&stream->lock_); |
| - } |
| - } |
| +void PCMWaveOutAudioOutputStream::PlayingObject::FeedBuffer(WAVEHDR* buffer, |
| + HWAVEOUT hwo) { |
| + TRACE_EVENT0("audio", |
| + "PCMWaveOutAudioOutputStream::PlayingObject::FeedBuffer"); |
| + // Try to obtain the lock. Minor optimization: if lock is owned, return |
| + // immediately, not wait till PCMWaveOutAudioOutputStream::Stop() release |
| + // it and we will return anyways. |
| + if (!lock_.Try()) |
|
cpu_(ooo_6.6-7.5)
2011/11/21 01:18:43
I don't completely follow why we need to take this
|
| + return; |
| + if (!playing()) |
| + return; |
| + // 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); |
| + // 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(hwo, buffer, sizeof(WAVEHDR)); |
| + if (result != MMSYSERR_NOERROR) |
| + stream_->HandleError(result); |
| + AddRef(); |
|
tommi (sloooow) - chröme
2011/11/19 17:23:27
maybe a comment on where this reference is release
tommi (sloooow) - chröme
2011/11/19 21:58:42
Actually, I think this AddRef should not be bere.
|
| + stream_->pending_bytes_ += buffer->dwBufferLength; |
| + lock_.Release(); |
| } |