diff options
author | cpu@chromium.org <cpu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-30 23:29:53 +0000 |
---|---|---|
committer | cpu@chromium.org <cpu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-30 23:29:53 +0000 |
commit | 558b6591982ff3f66538d1c5388cf4b84ccaad98 (patch) | |
tree | a7d9b1ee3671225c072453fc4ad7eef4b142884a /media/audio | |
parent | 7122ffd5e564f28392194dad993b853242c50262 (diff) | |
download | chromium_src-558b6591982ff3f66538d1c5388cf4b84ccaad98.zip chromium_src-558b6591982ff3f66538d1c5388cf4b84ccaad98.tar.gz chromium_src-558b6591982ff3f66538d1c5388cf4b84ccaad98.tar.bz2 |
Third installement of low level audio for mac
- Finally audio playback wired
- Takes into account initial buffer fill change of last week
- Two 'can you hear this?' unit tests added
Review URL: http://codereview.chromium.org/92131
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15017 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-rw-r--r-- | media/audio/mac/audio_output_mac.cc | 94 | ||||
-rw-r--r-- | media/audio/mac/audio_output_mac_unittest.cc | 50 |
2 files changed, 130 insertions, 14 deletions
diff --git a/media/audio/mac/audio_output_mac.cc b/media/audio/mac/audio_output_mac.cc index 019baac..0b90d2c 100644 --- a/media/audio/mac/audio_output_mac.cc +++ b/media/audio/mac/audio_output_mac.cc @@ -8,6 +8,28 @@ #include "base/basictypes.h" #include "base/logging.h" +// Overview of operation: +// 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. +enum { + kAudioQueueErr_EnqueueDuringReset = -66632 +}; + PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream( AudioManagerMac* manager, int channels, int sampling_rate, char bits_per_sample) @@ -26,18 +48,22 @@ PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream( 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 @@ void PCMQueueOutAudioOutputStream::Close() { 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::Close() { } 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 @@ size_t PCMQueueOutAudioOutputStream::GetNumBuffers() { 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; } - // Queue the audio data to the audio driver. buffer->mAudioDataByteSize = filled; + if (NULL == queue) + return; + // Queue the audio data to the audio driver. 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; + } + } } + diff --git a/media/audio/mac/audio_output_mac_unittest.cc b/media/audio/mac/audio_output_mac_unittest.cc index 6b357e7..c0f5348 100644 --- a/media/audio/mac/audio_output_mac_unittest.cc +++ b/media/audio/mac/audio_output_mac_unittest.cc @@ -54,3 +54,53 @@ TEST(MacAudioTest, PCMWaveStreamOpenAndClose) { EXPECT_TRUE(oas->Open(1024)); oas->Close(); } + +// This test produces actual audio for 1.5 seconds on the default wave device at +// 44.1K s/sec. Parameters have been chosen carefully so you should not hear +// pops or noises while the sound is playing. The sound must also be identical +// to the sound of PCMWaveStreamPlay200HzTone22KssMono test. +TEST(MacAudioTest, PCMWaveStreamPlay200HzTone44KssMono) { + AudioManager* audio_man = AudioManager::GetAudioManager(); + ASSERT_TRUE(NULL != audio_man); + if (!audio_man->HasAudioDevices()) + return; + AudioOutputStream* oas = + audio_man->MakeAudioStream(AudioManager::AUDIO_PCM_LINEAR, 1, + AudioManager::kAudioCDSampleRate, 16); + ASSERT_TRUE(NULL != oas); + + SineWaveAudioSource source(SineWaveAudioSource::FORMAT_16BIT_LINEAR_PCM, 1, + 200.0, AudioManager::kAudioCDSampleRate); + size_t bytes_100_ms = (AudioManager::kAudioCDSampleRate / 10) * 2; + + EXPECT_TRUE(oas->Open(bytes_100_ms)); + oas->Start(&source); + usleep(1500000); + oas->Stop(); + oas->Close(); +} + +// This test produces actual audio for 1.5 seconds on the default wave device at +// 22K s/sec. Parameters have been chosen carefully so you should not hear pops +// or noises while the sound is playing. The sound must also be identical to the +// sound of PCMWaveStreamPlay200HzTone44KssMono test. +TEST(MacAudioTest, PCMWaveStreamPlay200HzTone22KssMono) { + AudioManager* audio_man = AudioManager::GetAudioManager(); + ASSERT_TRUE(NULL != audio_man); + if (!audio_man->HasAudioDevices()) + return; + AudioOutputStream* oas = + audio_man->MakeAudioStream(AudioManager::AUDIO_PCM_LINEAR, 1, + AudioManager::kAudioCDSampleRate/2, 16); + ASSERT_TRUE(NULL != oas); + + SineWaveAudioSource source(SineWaveAudioSource::FORMAT_16BIT_LINEAR_PCM, 1, + 200.0, AudioManager::kAudioCDSampleRate/2); + size_t bytes_100_ms = (AudioManager::kAudioCDSampleRate / 20) * 2; + + EXPECT_TRUE(oas->Open(bytes_100_ms)); + oas->Start(&source); + usleep(1500000); + oas->Stop(); + oas->Close(); +} |