diff options
author | joth@chromium.org <joth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-27 10:41:22 +0000 |
---|---|---|
committer | joth@chromium.org <joth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-27 10:41:22 +0000 |
commit | 6169835f4688ecd97f24f10262f59851796c241a (patch) | |
tree | dddbcbcc40805dabdd8700db956c1d4d9f5199e7 /media/audio | |
parent | 842ec80d46141284857933372d823ad89cdba6a3 (diff) | |
download | chromium_src-6169835f4688ecd97f24f10262f59851796c241a.zip chromium_src-6169835f4688ecd97f24f10262f59851796c241a.tar.gz chromium_src-6169835f4688ecd97f24f10262f59851796c241a.tar.bz2 |
Mac audio input implementation
Required for speech input feature
BUG=none
TEST=Added AudioInputStreamMacTest.* unit tests
Review URL: http://codereview.chromium.org/3181041
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@57660 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-rw-r--r-- | media/audio/mac/audio_input_mac.cc | 182 | ||||
-rw-r--r-- | media/audio/mac/audio_input_mac.h | 75 | ||||
-rw-r--r-- | media/audio/mac/audio_input_mac_unittest.cc | 103 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.cc | 38 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.h | 7 | ||||
-rw-r--r-- | media/audio/mac/audio_output_mac.h | 4 |
6 files changed, 392 insertions, 17 deletions
diff --git a/media/audio/mac/audio_input_mac.cc b/media/audio/mac/audio_input_mac.cc new file mode 100644 index 0000000..5203632 --- /dev/null +++ b/media/audio/mac/audio_input_mac.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/audio/mac/audio_input_mac.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "media/audio/audio_util.h" +#include "media/audio/mac/audio_manager_mac.h" + +#if !defined(MAC_OS_X_VERSION_10_6) || + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 +enum { + kAudioQueueErr_EnqueueDuringReset = -66632 +}; +#endif + +PCMQueueInAudioInputStream::PCMQueueInAudioInputStream( + AudioManagerMac* manager, + int channels, + int sampling_rate, + char bits_per_sample, + uint32 samples_per_buffer) + : manager_(manager), + callback_(NULL), + audio_queue_(NULL), + buffer_size_bytes_(0) { + // We must have a manager. + DCHECK(manager_); + // A frame is one sample across all channels. In interleaved audio the per + // frame fields identify the set of n |channels|. In uncompressed audio, a + // packet is always one frame. + format_.mSampleRate = sampling_rate; + format_.mFormatID = kAudioFormatLinearPCM; + format_.mFormatFlags = kLinearPCMFormatFlagIsPacked | + kLinearPCMFormatFlagIsSignedInteger; + format_.mBitsPerChannel = bits_per_sample; + format_.mChannelsPerFrame = channels; + format_.mFramesPerPacket = 1; + format_.mBytesPerPacket = (bits_per_sample * channels) / 8; + format_.mBytesPerFrame = format_.mBytesPerPacket; + + buffer_size_bytes_ = format_.mBytesPerFrame * samples_per_buffer; +} + +PCMQueueInAudioInputStream::~PCMQueueInAudioInputStream() { + DCHECK(!callback_); + DCHECK(!audio_queue_); +} + +bool PCMQueueInAudioInputStream::Open() { + OSStatus err = AudioQueueNewInput(&format_, + &HandleInputBufferStatic, + this, + NULL, // Use OS CFRunLoop for |callback| + kCFRunLoopCommonModes, + 0, // Reserved + &audio_queue_); + if (err != noErr) { + HandleError(err); + return false; + } + return SetupBuffers(); +} + +void PCMQueueInAudioInputStream::Start(AudioInputCallback* callback) { + DCHECK(callback); + DCHECK(audio_queue_) << "Must call Open() first"; + if (callback_) + return; + callback_ = callback; + OSStatus err = AudioQueueStart(audio_queue_, NULL); + if (err != noErr) + HandleError(err); +} + +void PCMQueueInAudioInputStream::Stop() { + if (!audio_queue_) + return; + // We request a synchronous stop, so the next call can take some time. In + // the windows implementation we block here as well. + OSStatus err = AudioQueueStop(audio_queue_, true); + if (err != noErr) + HandleError(err); +} + +void PCMQueueInAudioInputStream::Close() { + // It is valid to call Close() before calling Open() or Start(), thus + // |audio_queue_| and |callback_| might be NULL. + if (audio_queue_) { + OSStatus err = AudioQueueDispose(audio_queue_, true); + audio_queue_ = NULL; + if (err != noErr) + HandleError(err); + } + if (callback_) { + callback_->OnClose(this); + callback_ = NULL; + } + manager_->ReleaseInputStream(this); + // CARE: This object may now be destroyed. +} + +void PCMQueueInAudioInputStream::HandleError(OSStatus err) { + if (callback_) + callback_->OnError(this, static_cast<int>(err)); + NOTREACHED() << "error code " << err; +} + +bool PCMQueueInAudioInputStream::SetupBuffers() { + DCHECK(buffer_size_bytes_); + for (int i = 0; i < kNumberBuffers; ++i) { + AudioQueueBufferRef buffer; + OSStatus err = AudioQueueAllocateBuffer(audio_queue_, + buffer_size_bytes_, + &buffer); + if (err == noErr) + err = QueueNextBuffer(buffer); + if (err != noErr) { + HandleError(err); + return false; + } + // |buffer| will automatically be freed when |audio_queue_| is released. + } + return true; +} + +OSStatus PCMQueueInAudioInputStream::QueueNextBuffer( + AudioQueueBufferRef audio_buffer) { + // Only the first 2 params are needed for recording. + return AudioQueueEnqueueBuffer(audio_queue_, audio_buffer, 0, NULL); +} + +// static +void PCMQueueInAudioInputStream::HandleInputBufferStatic( + void* data, + AudioQueueRef audio_queue, + AudioQueueBufferRef audio_buffer, + const AudioTimeStamp* start_time, + UInt32 num_packets, + const AudioStreamPacketDescription* desc) { + reinterpret_cast<PCMQueueInAudioInputStream*>(data)-> + HandleInputBuffer(audio_queue, audio_buffer, start_time, + num_packets, desc); +} + +void PCMQueueInAudioInputStream::HandleInputBuffer( + AudioQueueRef audio_queue, + AudioQueueBufferRef audio_buffer, + const AudioTimeStamp* start_time, + UInt32 num_packets, + const AudioStreamPacketDescription* packet_desc) { + DCHECK_EQ(audio_queue_, audio_queue); + DCHECK(audio_buffer->mAudioData); + if (!callback_) { + // This can happen if Stop() was called without start. + DCHECK_EQ(0U, audio_buffer->mAudioDataByteSize); + return; + } + + if (audio_buffer->mAudioDataByteSize) + callback_->OnData(this, + reinterpret_cast<const uint8*>(audio_buffer->mAudioData), + audio_buffer->mAudioDataByteSize); + // Recycle the buffer. + OSStatus err = QueueNextBuffer(audio_buffer); + 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. + // TODO(joth): PCMQueueOutAudioOutputStream uses callback_ to provide an + // extra guard for this situation, but it seems to introduce more + // complications than it solves (memory barrier issues accessing it from + // multiple threads, looses the means to indicate OnClosed to client). + // Should determine if we need to do something equivalent here. + return; + } + HandleError(err); + } +} diff --git a/media/audio/mac/audio_input_mac.h b/media/audio/mac/audio_input_mac.h new file mode 100644 index 0000000..9615d18 --- /dev/null +++ b/media/audio/mac/audio_input_mac.h @@ -0,0 +1,75 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_AUDIO_MAC_AUDIO_INPUT_MAC_H_ +#define MEDIA_AUDIO_MAC_AUDIO_INPUT_MAC_H_ + +#include <AudioToolbox/AudioQueue.h> +#include <AudioToolbox/AudioFormat.h> + +#include "media/audio/audio_io.h" + +class AudioManagerMac; + +// Implementation of AudioInputStream for Mac OS X using the audio queue service +// present in OS 10.5 and later. Design reflects PCMQueueOutAudioOutputStream. +class PCMQueueInAudioInputStream : public AudioInputStream { + public: + // Parameters as per AudioManager::MakeAudioInputStream. + PCMQueueInAudioInputStream(AudioManagerMac* manager, + int channels, + int sampling_rate, + char bits_per_sample, + uint32 samples_per_packet); + virtual ~PCMQueueInAudioInputStream(); + + // Implementation of AudioInputStream. + virtual bool Open(); + virtual void Start(AudioInputCallback* callback); + virtual void Stop(); + virtual void Close(); + + private: + // Issue the OnError to |callback_|; + void HandleError(OSStatus err); + + // Allocates and prepares the memory that will be used for recording. + bool SetupBuffers(); + + // Sends a buffer to the audio driver for recording. + OSStatus QueueNextBuffer(AudioQueueBufferRef audio_buffer); + + // Callback from OS, delegates to non-static version below. + static void HandleInputBufferStatic( + void* data, + AudioQueueRef audio_queue, + AudioQueueBufferRef audio_buffer, + const AudioTimeStamp* start_time, + UInt32 num_packets, + const AudioStreamPacketDescription* desc); + + // Handles callback from OS. Will be called on OS internal thread. + void HandleInputBuffer(AudioQueueRef audio_queue, + AudioQueueBufferRef audio_buffer, + const AudioTimeStamp* start_time, + UInt32 num_packets, + const AudioStreamPacketDescription* packet_desc); + + static const int kNumberBuffers = 3; + + // Manager that owns this stream, used for closing down. + AudioManagerMac* manager_; + // We use the callback mostly to periodically supply the recorded audio data. + AudioInputCallback* callback_; + // Structure that holds the stream format details such as bitrate. + AudioStreamBasicDescription format_; + // Handle to the OS audio queue object. + AudioQueueRef audio_queue_; + // Size of each of the buffers in |audio_buffers_| + uint32 buffer_size_bytes_; + + DISALLOW_COPY_AND_ASSIGN(PCMQueueInAudioInputStream); +}; + +#endif // MEDIA_AUDIO_MAC_AUDIO_INPUT_MAC_H_ diff --git a/media/audio/mac/audio_input_mac_unittest.cc b/media/audio/mac/audio_input_mac_unittest.cc new file mode 100644 index 0000000..fe6b9a4 --- /dev/null +++ b/media/audio/mac/audio_input_mac_unittest.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Tests the mac audio input stream implementation. +// TODO(joth): See if we can generalize this for all platforms? + +#include "base/time.h" +#include "media/audio/audio_io.h" +#include "media/audio/audio_manager.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AllOf; +using ::testing::AtLeast; +using ::testing::Gt; +using ::testing::Le; +using ::testing::NotNull; +using ::testing::StrictMock; + +class MockAudioInputCallback : public AudioInputStream::AudioInputCallback { + public: + MOCK_METHOD3(OnData, void(AudioInputStream* stream, const uint8* src, + uint32 size)); + MOCK_METHOD1(OnClose, void(AudioInputStream* stream)); + MOCK_METHOD2(OnError, void(AudioInputStream* stream, int code)); +}; + +// Test fixture. +class AudioInputStreamMacTest : public testing::Test { + protected: + // From testing::Test. + virtual void SetUp() { + ias_ = NULL; + AudioManager* audio_man = AudioManager::GetAudioManager(); + ASSERT_TRUE(NULL != audio_man); + if (audio_man->HasAudioInputDevices()) + ias_ = audio_man->MakeAudioInputStream(AudioManager::AUDIO_PCM_LINEAR, 2, + kSampleRate, 16, kSamplesPerCall); + } + virtual void TearDown() { + ias_->Close(); + } + + bool TestEnabled() const { + return NULL != ias_; + } + + static const int kSampleRate = 8000; + static const int kSamplesPerCall = 3000; + // This is the default callback implementation; it will assert the expected + // calls were received when it is destroyed. + StrictMock<MockAudioInputCallback> client_; + // The audio input stream under test. + AudioInputStream* ias_; +}; + +// Test that can it be created and closed. +TEST_F(AudioInputStreamMacTest, PCMInputStreamCreateAndClose) { + if (!TestEnabled()) + return; +} + +// Test that it can be opened and closed. +TEST_F(AudioInputStreamMacTest, PCMInputStreamOpenAndClose) { + if (!TestEnabled()) + return; + EXPECT_TRUE(ias_->Open()); +} + +// Test we handle a open then stop OK. +TEST_F(AudioInputStreamMacTest, PCMInputStreamOpenTheStop) { + if (!TestEnabled()) + return; + EXPECT_TRUE(ias_->Open()); + ias_->Stop(); +} + +// Record for a short time then stop. Make sure we get the callbacks. +TEST_F(AudioInputStreamMacTest, PCMInputStreamRecordCoupleSeconds) { + if (!TestEnabled()) + return; + ASSERT_TRUE(ias_->Open()); + // For a given sample rate, number of samples per callback, and recording time + // we can estimate the number of callbacks we should get. We underestimate + // this slightly, as the callback thread could be slow. + static const double kRecordPeriodSecs = 1.5; + static const int kMinExpectedCalls = + (kRecordPeriodSecs * kSampleRate / kSamplesPerCall) - 1; + // Check this is in reasonable bounds. + EXPECT_GT(kMinExpectedCalls, 1); + EXPECT_LT(kMinExpectedCalls, 10); + + static const uint kMaxBytesPerCall = 2 * 2 * kSamplesPerCall; + EXPECT_CALL(client_, OnData(ias_, NotNull(), + AllOf(Gt(0u), Le(kMaxBytesPerCall)))) + .Times(AtLeast(kMinExpectedCalls)); + EXPECT_CALL(client_, OnClose(ias_)); + + ias_->Start(&client_); + usleep(kRecordPeriodSecs * base::Time::kMicrosecondsPerSecond); + ias_->Stop(); +} diff --git a/media/audio/mac/audio_manager_mac.cc b/media/audio/mac/audio_manager_mac.cc index 528fd6e..a82af0b 100644 --- a/media/audio/mac/audio_manager_mac.cc +++ b/media/audio/mac/audio_manager_mac.cc @@ -6,13 +6,15 @@ #include "media/audio/fake_audio_input_stream.h" #include "media/audio/fake_audio_output_stream.h" +#include "media/audio/mac/audio_input_mac.h" #include "media/audio/mac/audio_manager_mac.h" #include "media/audio/mac/audio_output_mac.h" -bool AudioManagerMac::HasAudioOutputDevices() { +namespace { +bool HasAudioHardware(AudioObjectPropertySelector selector) { AudioDeviceID output_device_id = kAudioObjectUnknown; - AudioObjectPropertyAddress property_address = { - kAudioHardwarePropertyDefaultOutputDevice, // mSelector + const AudioObjectPropertyAddress property_address = { + selector, kAudioObjectPropertyScopeGlobal, // mScope kAudioObjectPropertyElementMaster // mElement }; @@ -24,12 +26,16 @@ bool AudioManagerMac::HasAudioOutputDevices() { &output_device_id_size, &output_device_id); return err == kAudioHardwareNoError && - output_device_id != kAudioObjectUnknown; + output_device_id != kAudioObjectUnknown; +} +} // namespace + +bool AudioManagerMac::HasAudioOutputDevices() { + return HasAudioHardware(kAudioHardwarePropertyDefaultOutputDevice); } bool AudioManagerMac::HasAudioInputDevices() { - // TODO(satish): implement. - return false; + return HasAudioHardware(kAudioHardwarePropertyDefaultInputDevice); } AudioInputStream* AudioManagerMac::MakeAudioInputStream( @@ -42,8 +48,10 @@ AudioInputStream* AudioManagerMac::MakeAudioInputStream( return FakeAudioInputStream::MakeFakeStream(channels, bits_per_sample, sample_rate, samples_per_packet); + } else if (format == AUDIO_PCM_LINEAR) { + return new PCMQueueInAudioInputStream(this, channels, sample_rate, + bits_per_sample, samples_per_packet); } - // TODO(satish): implement. return NULL; } @@ -52,12 +60,13 @@ AudioOutputStream* AudioManagerMac::MakeAudioOutputStream( int channels, int sample_rate, char bits_per_sample) { - if (format == AUDIO_MOCK) + if (format == AUDIO_MOCK) { return FakeAudioOutputStream::MakeFakeStream(); - else if (format != AUDIO_PCM_LINEAR) - return NULL; - return new PCMQueueOutAudioOutputStream(this, channels, sample_rate, - bits_per_sample); + } else if (format == AUDIO_PCM_LINEAR) { + return new PCMQueueOutAudioOutputStream(this, channels, sample_rate, + bits_per_sample); + } + return NULL; } void AudioManagerMac::MuteAll() { @@ -74,6 +83,11 @@ void AudioManagerMac::ReleaseOutputStream( delete stream; } +// Called by the stream when it has been released by calling Close(). +void AudioManagerMac::ReleaseInputStream(PCMQueueInAudioInputStream* stream) { + delete stream; +} + // static AudioManager* AudioManager::CreateAudioManager() { return new AudioManagerMac(); diff --git a/media/audio/mac/audio_manager_mac.h b/media/audio/mac/audio_manager_mac.h index fa25ece..3d720c5 100644 --- a/media/audio/mac/audio_manager_mac.h +++ b/media/audio/mac/audio_manager_mac.h @@ -8,6 +8,7 @@ #include "base/basictypes.h" #include "media/audio/audio_manager_base.h" +class PCMQueueInAudioInputStream; class PCMQueueOutAudioOutputStream; // Mac OS X implementation of the AudioManager singleton. This class is internal @@ -30,9 +31,11 @@ class AudioManagerMac : public AudioManagerBase { virtual void MuteAll(); virtual void UnMuteAll(); - // Mac-only method to free a stream created in MakeAudioStream. - // It is called internally by the audio stream when it has been closed. + // Mac-only method to free the streams created by above facoty methods. + // They are called internally by the respective audio stream when it has + // been closed. void ReleaseOutputStream(PCMQueueOutAudioOutputStream* stream); + void ReleaseInputStream(PCMQueueInAudioInputStream* stream); private: friend void DestroyAudioManagerMac(void*); diff --git a/media/audio/mac/audio_output_mac.h b/media/audio/mac/audio_output_mac.h index 7e86107..636e63b 100644 --- a/media/audio/mac/audio_output_mac.h +++ b/media/audio/mac/audio_output_mac.h @@ -5,13 +5,11 @@ #ifndef MEDIA_AUDIO_MAC_AUDIO_OUTPUT_MAC_H_ #define MEDIA_AUDIO_MAC_AUDIO_OUTPUT_MAC_H_ -#include <AudioToolbox/AudioQueue.h> #include <AudioToolbox/AudioFormat.h> +#include <AudioToolbox/AudioQueue.h> #include "media/audio/audio_io.h" -#include "base/basictypes.h" - class AudioManagerMac; // Implementation of AudioOuputStream for Mac OS X using the audio queue service |