Index: media/audio/win/waveout_output_win.cc |
=================================================================== |
--- media/audio/win/waveout_output_win.cc (revision 110309) |
+++ media/audio/win/waveout_output_win.cc (working copy) |
@@ -8,16 +8,19 @@ |
#include <mmsystem.h> |
#pragma comment(lib, "winmm.lib") |
+#include "base/at_exit.h" |
#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; |
+base::Lock PCMWaveOutAudioOutputStream::g_lock_; |
+volatile DWORD PCMWaveOutAudioOutputStream::g_ordinal_; |
+scoped_ptr<std::map<PCMWaveOutAudioOutputStream*, DWORD> > |
+ PCMWaveOutAudioOutputStream::g_streams_map_; |
tommi (sloooow) - chröme
2011/11/18 09:53:21
All these objects should belong to one singleton o
enal1
2011/11/19 00:59:19
Done.
|
// Some general thoughts about the waveOut API which is badly documented : |
// - We use CALLBACK_FUNCTION mode in which XP secretly creates two threads |
@@ -26,23 +29,14 @@ |
// - 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. |
+// - Stopping is tricky, see comment below. |
-// 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 |
@@ -88,12 +82,13 @@ |
waveout_(NULL), |
callback_(NULL), |
num_buffers_(num_buffers), |
- buffer_(NULL), |
+ buffers_(NULL), |
buffer_size_(params.GetPacketSize()), |
volume_(1), |
channels_(params.channels), |
- pending_bytes_(0) { |
- ::InitializeCriticalSectionAndSpinCount(&lock_, kSpinCount); |
+ pending_bytes_(0), |
+ ordinal_(0) { |
+ InitializeGlobalsIfNecessary(); |
format_.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; |
format_.Format.nChannels = params.channels; |
@@ -116,7 +111,6 @@ |
PCMWaveOutAudioOutputStream::~PCMWaveOutAudioOutputStream() { |
DCHECK(NULL == waveout_); |
- ::DeleteCriticalSection(&lock_); |
} |
bool PCMWaveOutAudioOutputStream::Open() { |
@@ -140,41 +134,30 @@ |
} |
void PCMWaveOutAudioOutputStream::SetupBuffers() { |
- WAVEHDR* last = NULL; |
- WAVEHDR* first = NULL; |
+ buffers_ = new char[CbBuffer() * 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->dwUser = 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; |
+ delete[] buffers_; |
tommi (sloooow) - chröme
2011/11/18 09:53:21
use scoped_array?
enal1
2011/11/19 00:59:19
Done.
|
} |
-// 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,33 @@ |
return; |
callback_ = callback; |
state_ = PCMA_PLAYING; |
+ |
+ // Place "this" into map. Do it under global lock. |
+ { |
+ base::AutoLock auto_lock(g_lock_); |
+ ordinal_ = g_ordinal_; |
+ ++g_ordinal_; |
+ DCHECK(g_streams_map_->find(this) == g_streams_map_->end()); |
+ (*g_streams_map_)[this] = ordinal_; |
+ } |
+ |
+ // 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); |
+ buffer->dwUser = ordinal_; |
// 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 +207,11 @@ |
// 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); |
} |
result = ::waveOutRestart(waveout_); |
if (result != MMSYSERR_NOERROR) { |
@@ -223,28 +220,45 @@ |
} |
} |
-// 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 |
+// * Here: |
+// (a) get object lock |
tommi (sloooow) - chröme
2011/11/18 09:53:21
why do we need the object lock while we hold the o
enal1
2011/11/19 00:59:19
Got rid of other lock. Done.
|
+// (b) get global lock |
+// (c) remove object from map |
+// (d) release both locks |
+// ...do real stopping... |
+// * In feeder thread: |
+// (e) get global lock |
+// (f) if object is not in the map, exit |
+// (g) try getting object lock; if fail, exit |
+// (h) release global lock |
+// ...do real feeding... |
+// There is no way for object to be alive in (f) but stopped/deleted in (g). |
+// For object to be stopped between (f) and (g), Stop() should acquire global |
+// lock, but it is already acquired by feeder. So it is safe to touch object |
+// lock at (g)... |
void PCMWaveOutAudioOutputStream::Stop() { |
if (state_ != PCMA_PLAYING) |
return; |
+ state_ = PCMA_STOPPING; |
+ MMRESULT res = MMSYSERR_NOERROR; |
+ { |
+ base::AutoLock auto_lock(lock_); |
- // 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_); |
+ // Remove us from the map. |
+ { |
+ base::AutoLock auto_lock(g_lock_); |
+ g_streams_map_->erase(this); |
+ } |
+ res = ::waveOutReset(waveout_); |
+ } |
if (res != MMSYSERR_NOERROR) { |
HandleError(res); |
return; |
} |
tommi (sloooow) - chröme
2011/11/18 09:53:21
it's ok to keep the empty line below this one imo.
enal1
2011/11/19 00:59:19
Done.
|
- |
// Don't use callback after Stop(). |
callback_ = NULL; |
- |
state_ = PCMA_READY; |
} |
@@ -316,40 +330,89 @@ |
buffer->dwFlags = WHDR_PREPARED; |
} |
+void PCMWaveOutAudioOutputStream::FeedBuffer( |
+ PCMWaveOutAudioOutputStream* stream, |
+ WAVEHDR* buffer, |
+ HWAVEOUT hwo, |
+ DWORD ordinal) { |
+ TRACE_EVENT0("audio", "PCMWaveOutAudioOutputStream::FeedBuffer"); |
+ // Is stream still alive and playing? Check is tricky. |
+ // See comment in PCMWaveOutAudioOutputStream::Stop() explaining what |
+ // exactly happens. |
tommi (sloooow) - chröme
2011/11/18 09:53:21
Are we doing this check to avoid touching an objec
enal1
2011/11/19 00:59:19
Yes, we had very complicated checking to avoid tou
|
+ bool stopped = true; |
+ { |
+ base::AutoLock auto_lock(g_lock_); |
+ std::map<PCMWaveOutAudioOutputStream*, DWORD>::iterator it = |
+ g_streams_map_->find(stream); |
+ stopped = (it == g_streams_map_->end() || |
+ it->second != ordinal || |
+ !stream->lock_.Try()); |
tommi (sloooow) - chröme
2011/11/18 09:53:21
Maybe add a comment that here is where you acquire
enal1
2011/11/19 00:59:19
Code totally rewritten.
|
+ } |
+ if (stopped) |
tommi (sloooow) - chröme
2011/11/18 09:53:21
what about checking the state_ variable for != PCM
enal1
2011/11/19 00:59:19
Again, could not do that because object could be d
|
+ 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); |
+ |
+ stream->pending_bytes_ += buffer->dwBufferLength; |
+ stream->lock_.Release(); |
+} |
+ |
// 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 therad. |
tommi (sloooow) - chröme
2011/11/18 09:53:21
therad -> thread
enal1
2011/11/19 00:59:19
Done.
|
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; |
- |
- 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); |
- |
- stream->pending_bytes_ += buffer->dwBufferLength; |
- ::LeaveCriticalSection(&stream->lock_); |
+ if (stream->state_ == PCMA_PLAYING) { |
+ stream->manager_->GetMessageLoop()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&PCMWaveOutAudioOutputStream::FeedBuffer, |
+ stream, |
+ buffer, |
+ hwo, |
+ buffer->dwUser)); |
} |
} |
} |
+// Initialize globals, if necessary. Use double-checked locking, |
+// it is safe on Windows as long as you are checking volatile variable. |
+// static |
+void PCMWaveOutAudioOutputStream::InitializeGlobalsIfNecessary() { |
+ if (g_ordinal_ == 0) { |
+ base::AutoLock auto_lock(g_lock_); |
+ if (g_ordinal_ == 0) { |
+ g_streams_map_.reset(new std::map<PCMWaveOutAudioOutputStream*, DWORD>); |
+ base::AtExitManager::RegisterCallback( |
+ &PCMWaveOutAudioOutputStream::CleanupGlobals, NULL); |
+ // Volatile stores have release semantics, no separate memory |
+ // barrier necessary. |
+ g_ordinal_ = 1; |
+ } |
+ } |
+} |
+ |
+// static |
+void PCMWaveOutAudioOutputStream::CleanupGlobals(void* not_used) { |
+ g_streams_map_.reset(NULL); |
+} |