summaryrefslogtreecommitdiffstats
path: root/media/audio
diff options
context:
space:
mode:
authorjoth@chromium.org <joth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-27 10:41:22 +0000
committerjoth@chromium.org <joth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-27 10:41:22 +0000
commit6169835f4688ecd97f24f10262f59851796c241a (patch)
treedddbcbcc40805dabdd8700db956c1d4d9f5199e7 /media/audio
parent842ec80d46141284857933372d823ad89cdba6a3 (diff)
downloadchromium_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.cc182
-rw-r--r--media/audio/mac/audio_input_mac.h75
-rw-r--r--media/audio/mac/audio_input_mac_unittest.cc103
-rw-r--r--media/audio/mac/audio_manager_mac.cc38
-rw-r--r--media/audio/mac/audio_manager_mac.h7
-rw-r--r--media/audio/mac/audio_output_mac.h4
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