summaryrefslogtreecommitdiffstats
path: root/media/audio
diff options
context:
space:
mode:
authorcpu@chromium.org <cpu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-30 23:29:53 +0000
committercpu@chromium.org <cpu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-04-30 23:29:53 +0000
commit558b6591982ff3f66538d1c5388cf4b84ccaad98 (patch)
treea7d9b1ee3671225c072453fc4ad7eef4b142884a /media/audio
parent7122ffd5e564f28392194dad993b853242c50262 (diff)
downloadchromium_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.cc94
-rw-r--r--media/audio/mac/audio_output_mac_unittest.cc50
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();
+}