Index: media/audio/mac/audio_output_mac.cc |
=================================================================== |
--- media/audio/mac/audio_output_mac.cc (revision 14284) |
+++ media/audio/mac/audio_output_mac.cc (working copy) |
@@ -8,6 +8,28 @@ |
#include "base/basictypes.h" |
#include "base/logging.h" |
+// Overview of operation: |
John Grabowski
2009/04/30 23:16:24
Thanks for doing this.
|
+// 1) An object of PCMQueueOutAudioOutputStream is created by the AudioManager |
+// factory: audio_man->MakeAudioStream(). This just fills some structure. |
+// 2) Next some thread will call Open(), at that point the underliying OS |
+// queue is created and the audio buffers allocated. |
+// 3) Then some thread will call Start(source) At this point the source will be |
+// called to fill the initial buffers in the context of that same thread. |
+// Then the OS queue is started which will create its own thread which |
+// periodically will call the source for more data as buffers are being |
+// consumed. |
+// 4) At some point some thread will call Stop(), which we handle by directly |
+// stoping the OS queue. |
+// 5) One more callback to the source could be delivered in in the context of |
+// the queue's own thread. Data, if any will be discared. |
+// 6) The same thread that called stop will call Close() where we cleanup |
+// and notifiy the audio manager, which likley will destroy this object. |
+ |
+// TODO(cpu): Remove the constant for this error when snow leopard arrives. |
John Grabowski
2009/04/30 23:16:24
More correctly, "when we switch to the Snow Leopar
|
+enum { |
+ kAudioQueueErr_EnqueueDuringReset = -66632 |
+}; |
+ |
PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream( |
AudioManagerMac* manager, int channels, int sampling_rate, |
char bits_per_sample) |
@@ -26,18 +48,22 @@ |
format_.mFormatFlags = kLinearPCMFormatFlagIsPacked | |
kLinearPCMFormatFlagIsSignedInteger; |
format_.mBitsPerChannel = bits_per_sample; |
- format_.mBytesPerFrame = format_.mBytesPerPacket; |
format_.mChannelsPerFrame = channels; |
format_.mFramesPerPacket = 1; |
format_.mBytesPerPacket = (format_.mBitsPerChannel * channels) / 8; |
+ format_.mBytesPerFrame = format_.mBytesPerPacket; |
} |
PCMQueueOutAudioOutputStream::~PCMQueueOutAudioOutputStream() { |
} |
void PCMQueueOutAudioOutputStream::HandleError(OSStatus err) { |
- if (source_) |
- source_->OnError(this, static_cast<int>(err)); |
+ // source_ can be set to NULL from another thread. We need to cache its |
+ // pointer while we operate here. Note that does not mean that the source |
+ // has been destroyed. |
+ AudioSourceCallback* source = source_; |
+ if (source) |
+ source->OnError(this, static_cast<int>(err)); |
NOTREACHED() << "error code " << err; |
} |
@@ -79,16 +105,15 @@ |
for (size_t ix = 0; ix != kNumBuffers; ++ix) { |
if (buffer_[ix]) { |
err = AudioQueueFreeBuffer(audio_queue_, buffer_[ix]); |
- if (err) { |
+ if (err != noErr) { |
HandleError(err); |
break; |
} |
} |
} |
err = AudioQueueDispose(audio_queue_, true); |
- if (err) { |
+ if (err != noErr) |
HandleError(err); |
- } |
} |
// Inform the audio manager that we have been closed. This can cause our |
// destruction. |
@@ -96,7 +121,18 @@ |
} |
void PCMQueueOutAudioOutputStream::Stop() { |
- // TODO(cpu): Implement. |
+ // We request a synchronous stop, so the next call can take some time. In |
+ // the windows implementation we block here as well. |
+ source_ = NULL; |
+ // We set the source to null to signal to the data queueing thread it can stop |
+ // queueing data, however at most one callback might still be in flight which |
+ // could attempt to enqueue right after the next call. Rather that trying to |
+ // use a lock we rely on the internal Mac queue lock so the enqueue might |
+ // succeed or might fail but it won't crash or leave the queue itself in an |
+ // inconsistent state. |
+ OSStatus err = AudioQueueStop(audio_queue_, true); |
+ if (err != noErr) |
+ HandleError(err); |
} |
void PCMQueueOutAudioOutputStream::SetVolume(double left_level, |
@@ -113,33 +149,63 @@ |
return kNumBuffers; |
} |
+// Note to future hackers of this function: Do not add locks here because we |
+// call out to third party source that might do crazy things including adquire |
+// external locks or somehow re-enter here because its legal for them to call |
+// some audio functions. |
void PCMQueueOutAudioOutputStream::RenderCallback(void* p_this, |
AudioQueueRef queue, |
AudioQueueBufferRef buffer) { |
PCMQueueOutAudioOutputStream* audio_stream = |
static_cast<PCMQueueOutAudioOutputStream*>(p_this); |
- // Call the audio source to fill the free buffer with data. |
+ // Call the audio source to fill the free buffer with data. Not having a |
+ // source means that the queue has been closed. This is not an error. |
+ AudioSourceCallback* source = audio_stream->source_; |
+ if (!source) |
+ return; |
size_t capacity = buffer->mAudioDataBytesCapacity; |
- size_t filled = audio_stream->source_->OnMoreData(audio_stream, |
- buffer->mAudioData, |
- capacity); |
+ size_t filled = source->OnMoreData(audio_stream, buffer->mAudioData, capacity); |
if (filled > capacity) { |
// User probably overran our buffer. |
audio_stream->HandleError(0); |
return; |
} |
+ buffer->mAudioDataByteSize = filled; |
+ if (NULL == queue) |
+ return; |
// Queue the audio data to the audio driver. |
- buffer->mAudioDataByteSize = filled; |
OSStatus err = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL); |
- if (err != noErr) |
+ if (err != noErr) { |
+ if (err == kAudioQueueErr_EnqueueDuringReset) { |
+ // This is the error you get if you try to enqueue a buffer and the |
+ // queue has been closed. Not really a problem if indeed the queue |
+ // has been closed. |
+ if (!audio_stream->source_) |
+ return; |
+ } |
audio_stream->HandleError(err); |
+ } |
} |
void PCMQueueOutAudioOutputStream::Start(AudioSourceCallback* callback) { |
+ DCHECK(callback); |
OSStatus err = AudioQueueStart(audio_queue_, NULL); |
if (err != noErr) { |
HandleError(err); |
return; |
} |
- // TODO(cpu) : Prefill the avaiable buffers. |
+ source_ = callback; |
+ // Ask the source to pre-fill all our buffers before playing. |
+ for(size_t ix = 0; ix != kNumBuffers; ++ix) { |
+ RenderCallback(this, NULL, buffer_[ix]); |
+ } |
+ // Queue the buffers to the audio driver, sounds starts now. |
+ for(size_t ix = 0; ix != kNumBuffers; ++ix) { |
+ err = AudioQueueEnqueueBuffer(audio_queue_, buffer_[ix], 0, NULL); |
+ if (err != noErr) { |
+ HandleError(err); |
+ return; |
+ } |
+ } |
} |
+ |