Commit 558b6591 authored by cpu@chromium.org's avatar cpu@chromium.org

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
parent 7122ffd5
...@@ -8,6 +8,28 @@ ...@@ -8,6 +8,28 @@
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/logging.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( PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream(
AudioManagerMac* manager, int channels, int sampling_rate, AudioManagerMac* manager, int channels, int sampling_rate,
char bits_per_sample) char bits_per_sample)
...@@ -26,18 +48,22 @@ PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream( ...@@ -26,18 +48,22 @@ PCMQueueOutAudioOutputStream::PCMQueueOutAudioOutputStream(
format_.mFormatFlags = kLinearPCMFormatFlagIsPacked | format_.mFormatFlags = kLinearPCMFormatFlagIsPacked |
kLinearPCMFormatFlagIsSignedInteger; kLinearPCMFormatFlagIsSignedInteger;
format_.mBitsPerChannel = bits_per_sample; format_.mBitsPerChannel = bits_per_sample;
format_.mBytesPerFrame = format_.mBytesPerPacket;
format_.mChannelsPerFrame = channels; format_.mChannelsPerFrame = channels;
format_.mFramesPerPacket = 1; format_.mFramesPerPacket = 1;
format_.mBytesPerPacket = (format_.mBitsPerChannel * channels) / 8; format_.mBytesPerPacket = (format_.mBitsPerChannel * channels) / 8;
format_.mBytesPerFrame = format_.mBytesPerPacket;
} }
PCMQueueOutAudioOutputStream::~PCMQueueOutAudioOutputStream() { PCMQueueOutAudioOutputStream::~PCMQueueOutAudioOutputStream() {
} }
void PCMQueueOutAudioOutputStream::HandleError(OSStatus err) { void PCMQueueOutAudioOutputStream::HandleError(OSStatus err) {
if (source_) // source_ can be set to NULL from another thread. We need to cache its
source_->OnError(this, static_cast<int>(err)); // 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; NOTREACHED() << "error code " << err;
} }
...@@ -79,16 +105,15 @@ void PCMQueueOutAudioOutputStream::Close() { ...@@ -79,16 +105,15 @@ void PCMQueueOutAudioOutputStream::Close() {
for (size_t ix = 0; ix != kNumBuffers; ++ix) { for (size_t ix = 0; ix != kNumBuffers; ++ix) {
if (buffer_[ix]) { if (buffer_[ix]) {
err = AudioQueueFreeBuffer(audio_queue_, buffer_[ix]); err = AudioQueueFreeBuffer(audio_queue_, buffer_[ix]);
if (err) { if (err != noErr) {
HandleError(err); HandleError(err);
break; break;
} }
} }
} }
err = AudioQueueDispose(audio_queue_, true); err = AudioQueueDispose(audio_queue_, true);
if (err) { if (err != noErr)
HandleError(err); HandleError(err);
}
} }
// Inform the audio manager that we have been closed. This can cause our // Inform the audio manager that we have been closed. This can cause our
// destruction. // destruction.
...@@ -96,7 +121,18 @@ void PCMQueueOutAudioOutputStream::Close() { ...@@ -96,7 +121,18 @@ void PCMQueueOutAudioOutputStream::Close() {
} }
void PCMQueueOutAudioOutputStream::Stop() { 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, void PCMQueueOutAudioOutputStream::SetVolume(double left_level,
...@@ -113,33 +149,63 @@ size_t PCMQueueOutAudioOutputStream::GetNumBuffers() { ...@@ -113,33 +149,63 @@ size_t PCMQueueOutAudioOutputStream::GetNumBuffers() {
return kNumBuffers; 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, void PCMQueueOutAudioOutputStream::RenderCallback(void* p_this,
AudioQueueRef queue, AudioQueueRef queue,
AudioQueueBufferRef buffer) { AudioQueueBufferRef buffer) {
PCMQueueOutAudioOutputStream* audio_stream = PCMQueueOutAudioOutputStream* audio_stream =
static_cast<PCMQueueOutAudioOutputStream*>(p_this); 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 capacity = buffer->mAudioDataBytesCapacity;
size_t filled = audio_stream->source_->OnMoreData(audio_stream, size_t filled = source->OnMoreData(audio_stream, buffer->mAudioData, capacity);
buffer->mAudioData,
capacity);
if (filled > capacity) { if (filled > capacity) {
// User probably overran our buffer. // User probably overran our buffer.
audio_stream->HandleError(0); audio_stream->HandleError(0);
return; return;
} }
// Queue the audio data to the audio driver.
buffer->mAudioDataByteSize = filled; buffer->mAudioDataByteSize = filled;
if (NULL == queue)
return;
// Queue the audio data to the audio driver.
OSStatus err = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL); 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); audio_stream->HandleError(err);
}
} }
void PCMQueueOutAudioOutputStream::Start(AudioSourceCallback* callback) { void PCMQueueOutAudioOutputStream::Start(AudioSourceCallback* callback) {
DCHECK(callback);
OSStatus err = AudioQueueStart(audio_queue_, NULL); OSStatus err = AudioQueueStart(audio_queue_, NULL);
if (err != noErr) { if (err != noErr) {
HandleError(err); HandleError(err);
return; 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;
}
}
} }
...@@ -54,3 +54,53 @@ TEST(MacAudioTest, PCMWaveStreamOpenAndClose) { ...@@ -54,3 +54,53 @@ TEST(MacAudioTest, PCMWaveStreamOpenAndClose) {
EXPECT_TRUE(oas->Open(1024)); EXPECT_TRUE(oas->Open(1024));
oas->Close(); 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();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment