summaryrefslogtreecommitdiffstats
path: root/media/audio
diff options
context:
space:
mode:
authorhenrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-05 08:41:35 +0000
committerhenrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-05 08:41:35 +0000
commit39d2c5cc9d1a8c7378d3edf1fd3b6a32847567ae (patch)
treeecad93ca6b4d4635a3b79214252d81c8464373a1 /media/audio
parentb36980b1c9726783cca0118aae333f5cd7d0ef49 (diff)
downloadchromium_src-39d2c5cc9d1a8c7378d3edf1fd3b6a32847567ae.zip
chromium_src-39d2c5cc9d1a8c7378d3edf1fd3b6a32847567ae.tar.gz
chromium_src-39d2c5cc9d1a8c7378d3edf1fd3b6a32847567ae.tar.bz2
Implementation of AudioInputStream for Mac OS X using the special AUHAL input Audio Unit present in OS 10.4 and later.
The AUHAL input Audio Unit is for low-latency audio I/O. Review URL: http://codereview.chromium.org/7981022 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@104077 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-rw-r--r--media/audio/audio_io.h5
-rw-r--r--media/audio/mac/audio_low_latency_input_mac.cc354
-rw-r--r--media/audio/mac/audio_low_latency_input_mac.h112
-rw-r--r--media/audio/mac/audio_low_latency_input_mac_unittest.cc289
-rw-r--r--media/audio/mac/audio_manager_mac.cc5
-rw-r--r--media/audio/mac/audio_manager_mac.h2
6 files changed, 762 insertions, 5 deletions
diff --git a/media/audio/audio_io.h b/media/audio/audio_io.h
index 2550873..063cbc7 100644
--- a/media/audio/audio_io.h
+++ b/media/audio/audio_io.h
@@ -126,6 +126,8 @@ class AudioInputStream {
virtual void OnError(AudioInputStream* stream, int code) = 0;
};
+ virtual ~AudioInputStream() {}
+
// Open the stream and prepares it for recording. Call Start() to actually
// begin recording.
virtual bool Open() = 0;
@@ -142,9 +144,6 @@ class AudioInputStream {
// Close the stream. This also generates AudioInputCallback::OnClose(). This
// should be the last call made on this object.
virtual void Close() = 0;
-
- protected:
- virtual ~AudioInputStream() {}
};
#endif // MEDIA_AUDIO_AUDIO_IO_H_
diff --git a/media/audio/mac/audio_low_latency_input_mac.cc b/media/audio/mac/audio_low_latency_input_mac.cc
new file mode 100644
index 0000000..1c25f6c
--- /dev/null
+++ b/media/audio/mac/audio_low_latency_input_mac.cc
@@ -0,0 +1,354 @@
+// Copyright (c) 2011 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_low_latency_input_mac.h"
+
+#include <CoreServices/CoreServices.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "media/audio/audio_util.h"
+#include "media/audio/mac/audio_manager_mac.h"
+
+static void DLogFormat(const AudioStreamBasicDescription& format) {
+ DLOG(INFO) << " sample rate : " << format.mSampleRate << std::endl
+ << " format ID : " << format.mFormatID << std::endl
+ << " format flags : " << format.mFormatFlags << std::endl
+ << " bytes per packet : " << format.mBytesPerPacket << std::endl
+ << " frames per packet : " << format.mFramesPerPacket << std::endl
+ << " bytes per frame : " << format.mBytesPerFrame << std::endl
+ << " channels per frame: " << format.mChannelsPerFrame << std::endl
+ << " bits per channel : " << format.mBitsPerChannel;
+}
+
+// See "Technical Note TN2091 - Device input using the HAL Output Audio Unit"
+// http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
+// for more details and background regarding this implementation.
+
+AUAudioInputStream::AUAudioInputStream(
+ AudioManagerMac* manager, const AudioParameters& params)
+ : manager_(manager),
+ sink_(NULL),
+ audio_unit_(0),
+ started_(false) {
+ DCHECK(manager_);
+
+ // Set up the desired (output) format specified by the client.
+ format_.mSampleRate = params.sample_rate;
+ format_.mFormatID = kAudioFormatLinearPCM;
+ format_.mFormatFlags = kLinearPCMFormatFlagIsPacked |
+ kLinearPCMFormatFlagIsSignedInteger;
+ format_.mBitsPerChannel = params.bits_per_sample;
+ format_.mChannelsPerFrame = params.channels;
+ format_.mFramesPerPacket = 1; // uncompressed audio
+ format_.mBytesPerPacket = (format_.mBitsPerChannel *
+ params.channels) / 8;
+ format_.mBytesPerFrame = format_.mBytesPerPacket;
+ format_.mReserved = 0;
+
+ DLOG(INFO) << "Desired ouput format:";
+ DLogFormat(format_);
+
+ // Calculate the number of sample frames per callback.
+ number_of_frames_ = params.GetPacketSize() / format_.mBytesPerPacket;
+ DLOG(INFO) << "Number of frames per callback: " << number_of_frames_;
+
+ // Derive size (in bytes) of the buffers that we will render to.
+ UInt32 data_byte_size = number_of_frames_ * format_.mBytesPerFrame;
+ DLOG(INFO) << "Size of data buffer in bytes : " << data_byte_size;
+
+ // Allocate AudioBuffers to be used as storage for the received audio.
+ // The AudioBufferList structure works as a placeholder for the
+ // AudioBuffer structure, which holds a pointer to the actual data buffer.
+ audio_data_buffer_.reset(new uint8[data_byte_size]);
+ audio_buffer_list_.mNumberBuffers = 1;
+
+ AudioBuffer* audio_buffer = audio_buffer_list_.mBuffers;
+ audio_buffer->mNumberChannels = params.channels;
+ audio_buffer->mDataByteSize = data_byte_size;
+ audio_buffer->mData = audio_data_buffer_.get();
+}
+
+AUAudioInputStream::~AUAudioInputStream() {}
+
+// Obtain and open the AUHAL AudioOutputUnit for recording.
+bool AUAudioInputStream::Open() {
+ // Verify that we are not already opened.
+ if (audio_unit_)
+ return false;
+
+ // Start by obtaining an AudioOuputUnit using an AUHAL component description.
+
+ Component comp;
+ ComponentDescription desc;
+
+ // Description for the Audio Unit we want to use (AUHAL in this case).
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = kAudioUnitSubType_HALOutput;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+ comp = FindNextComponent(0, &desc);
+ DCHECK(comp);
+
+ // Get access to the service provided by the specified Audio Unit.
+ OSStatus result = OpenAComponent(comp, &audio_unit_);
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+
+ // Enable IO on the input scope of the Audio Unit.
+
+ // After creating the AUHAL object, we must enable IO on the input scope
+ // of the Audio Unit to obtain the device input. Input must be explicitly
+ // enabled with the kAudioOutputUnitProperty_EnableIO property on Element 1
+ // of the AUHAL. Beacause the AUHAL can be used for both input and output,
+ // we must also disable IO on the output scope.
+
+ UInt32 enableIO = 1;
+
+ // Enable input on the AUHAL.
+ result = AudioUnitSetProperty(audio_unit_,
+ kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Input,
+ 1, // input element 1
+ &enableIO, // enable
+ sizeof(enableIO));
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+
+ // Disable output on the AUHAL.
+ enableIO = 0;
+ result = AudioUnitSetProperty(audio_unit_,
+ kAudioOutputUnitProperty_EnableIO,
+ kAudioUnitScope_Output,
+ 0, // output element 0
+ &enableIO, // disable
+ sizeof(enableIO));
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+
+ // Set the current device of the AudioOuputUnit to default input device.
+
+ AudioDeviceID input_device;
+ UInt32 size = sizeof(input_device);
+
+ // First, obtain the current input device selected by the user.
+ result = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice,
+ &size,
+ &input_device);
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+
+ // Next, set the audio device to be the Audio Unit's current device.
+ // Note that, devices can only be set to the AUHAL after enabling IO.
+ result = AudioUnitSetProperty(audio_unit_,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &input_device,
+ sizeof(input_device));
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+
+ // Register the input procedure for the AUHAL.
+ // This procedure will be called when the AUHAL has received new data
+ // from the input device.
+ AURenderCallbackStruct callback;
+ callback.inputProc = InputProc;
+ callback.inputProcRefCon = this;
+ result = AudioUnitSetProperty(audio_unit_,
+ kAudioOutputUnitProperty_SetInputCallback,
+ kAudioUnitScope_Global,
+ 0,
+ &callback,
+ sizeof(callback));
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+
+ // Set up the the desired (output) format.
+ // For obtaining input from a device, the device format is always expressed
+ // on the output scope of the AUHAL's Element 1.
+ result = AudioUnitSetProperty(audio_unit_,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Output,
+ 1,
+ &format_,
+ sizeof(format_));
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+
+ // Set the desired number of frames in the IO buffer (output scope).
+ result = AudioUnitSetProperty(audio_unit_,
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output,
+ 1,
+ &number_of_frames_, // size is set in the ctor
+ sizeof(number_of_frames_));
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+
+ // Finally, initialize the audio unit and ensure that it is ready to render.
+ // Allocates memory according to the maximum number of audio frames
+ // it can produce in response to a single render call.
+ result = AudioUnitInitialize(audio_unit_);
+ if (result) {
+ HandleError(result);
+ return false;
+ }
+ return true;
+}
+
+void AUAudioInputStream::Start(AudioInputCallback* callback) {
+ DCHECK(callback);
+ if (started_)
+ return;
+ sink_ = callback;
+ OSStatus result = AudioOutputUnitStart(audio_unit_);
+ if (result == noErr) {
+ started_ = true;
+ }
+ DLOG_IF(ERROR, result != noErr) << "Failed to start acquiring data";
+}
+
+void AUAudioInputStream::Stop() {
+ if (!started_)
+ return;
+ OSStatus result;
+ result = AudioOutputUnitStop(audio_unit_);
+ if (result == noErr) {
+ started_ = false;
+ }
+ DLOG_IF(ERROR, result != noErr) << "Failed to stop acquiring data";
+}
+
+void AUAudioInputStream::Close() {
+ // It is valid to call Close() before calling open or Start().
+ // It is also valid to call Close() after Start() has been called.
+ if (started_) {
+ Stop();
+ }
+ if (audio_unit_) {
+ // Deallocate the audio unit’s resources.
+ AudioUnitUninitialize(audio_unit_);
+
+ // Terminates our connection to the AUHAL component.
+ CloseComponent(audio_unit_);
+ audio_unit_ = 0;
+ }
+ if (sink_) {
+ sink_->OnClose(this);
+ sink_ = NULL;
+ }
+
+ // Inform the audio manager that we have been closed. This can cause our
+ // destruction.
+ manager_->ReleaseInputStream(this);
+}
+
+// AUHAL AudioDeviceOutput unit callback
+OSStatus AUAudioInputStream::InputProc(void* user_data,
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data) {
+ // Verify that the correct bus is used (Input bus/Element 1)
+ DCHECK_EQ(bus_number, static_cast<UInt32>(1));
+ AUAudioInputStream* audio_input =
+ reinterpret_cast<AUAudioInputStream*>(user_data);
+ DCHECK(audio_input);
+ if (!audio_input)
+ return kAudioUnitErr_InvalidElement;
+
+ // Receive audio from the AUHAL from the output scope of the Audio Unit.
+ OSStatus result = AudioUnitRender(audio_input->audio_unit(),
+ flags,
+ time_stamp,
+ bus_number,
+ number_of_frames,
+ audio_input->audio_buffer_list());
+ if (result)
+ return result;
+
+ // Deliver recorded data to the consumer as a callback.
+ return audio_input->Provide(number_of_frames,
+ audio_input->audio_buffer_list());
+}
+
+OSStatus AUAudioInputStream::Provide(UInt32 number_of_frames,
+ AudioBufferList* io_data) {
+ AudioBuffer& buffer = io_data->mBuffers[0];
+ uint8* audio_data = reinterpret_cast<uint8*>(buffer.mData);
+ DCHECK(audio_data);
+ if (!audio_data)
+ return kAudioUnitErr_InvalidElement;
+
+ // TODO(henrika): improve delay estimation. Using buffer size for now.
+ sink_->OnData(this, audio_data, buffer.mDataByteSize, buffer.mDataByteSize);
+
+ return noErr;
+}
+
+double AUAudioInputStream::HardwareSampleRate() {
+ // Determine the default input device's sample-rate.
+ AudioDeviceID device_id = kAudioDeviceUnknown;
+ UInt32 info_size = sizeof(device_id);
+
+ AudioObjectPropertyAddress default_input_device_address = {
+ kAudioHardwarePropertyDefaultInputDevice,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+ OSStatus result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+ &default_input_device_address,
+ 0,
+ 0,
+ &info_size,
+ &device_id);
+ DCHECK_EQ(result, 0);
+ if (result)
+ return 0.0;
+
+ Float64 nominal_sample_rate;
+ info_size = sizeof(nominal_sample_rate);
+
+ AudioObjectPropertyAddress nominal_sample_rate_address = {
+ kAudioDevicePropertyNominalSampleRate,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+ result = AudioObjectGetPropertyData(device_id,
+ &nominal_sample_rate_address,
+ 0,
+ 0,
+ &info_size,
+ &nominal_sample_rate);
+ DCHECK_EQ(result, 0);
+ if (result)
+ return 0.0;
+
+ return nominal_sample_rate;
+}
+
+void AUAudioInputStream::HandleError(OSStatus err) {
+ NOTREACHED() << "error code: " << err;
+ if (sink_)
+ sink_->OnError(this, static_cast<int>(err));
+}
diff --git a/media/audio/mac/audio_low_latency_input_mac.h b/media/audio/mac/audio_low_latency_input_mac.h
new file mode 100644
index 0000000..8d1811c
--- /dev/null
+++ b/media/audio/mac/audio_low_latency_input_mac.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2011 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.
+//
+// Implementation of AudioInputStream for Mac OS X using the special AUHAL
+// input Audio Unit present in OS 10.4 and later.
+// The AUHAL input Audio Unit is for low-latency audio I/O.
+//
+// Overview of operation:
+//
+// - An object of AUAudioInputStream is created by the AudioManager
+// factory: audio_man->MakeAudioInputStream().
+// - Next some thread will call Open(), at that point the underlying
+// AUHAL output Audio Unit is created and configured.
+// - Then some thread will call Start(sink).
+// Then the Audio Unit is started which creates its own thread which
+// periodically will provide the sink with more data as buffers are being
+// produced/recorded.
+// - At some point some thread will call Stop(), which we handle by directly
+// stopping the AUHAL output Audio Unit.
+// - The same thread that called stop will call Close() where we cleanup
+// and notify the audio manager, which likely will destroy this object.
+//
+// Implementation notes:
+//
+// - It is recommended to first acquire the native sample rate of the default
+// input device and then use the same rate when creating this object.
+// Use AUAudioInputStream::HardwareSampleRate() to retrieve the sample rate.
+// - Calling Close() also leads to self destruction.
+//
+#ifndef MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_INPUT_MAC_H_
+#define MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_INPUT_MAC_H_
+
+#include <AudioUnit/AudioUnit.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_parameters.h"
+
+class AudioManagerMac;
+
+class AUAudioInputStream : public AudioInputStream {
+ public:
+ // The ctor takes all the usual parameters, plus |manager| which is the
+ // the audio manager who is creating this object.
+ AUAudioInputStream(AudioManagerMac* manager,
+ const AudioParameters& params);
+ // The dtor is typically called by the AudioManager only and it is usually
+ // triggered by calling AudioInputStream::Close().
+ virtual ~AUAudioInputStream();
+
+ // Implementation of AudioInputStream.
+ virtual bool Open() OVERRIDE;
+ virtual void Start(AudioInputCallback* callback) OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void Close() OVERRIDE;
+
+ // Returns the current hardware sample rate for the default input device.
+ static double HardwareSampleRate();
+
+ bool started() const { return started_; }
+ AudioUnit audio_unit() { return audio_unit_; }
+ AudioBufferList* audio_buffer_list() { return &audio_buffer_list_; }
+
+ private:
+ // AudioOutputUnit callback.
+ static OSStatus InputProc(void* user_data,
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data);
+
+ // Pushes recorded data to consumer of the input audio stream.
+ OSStatus Provide(UInt32 number_of_frames, AudioBufferList* io_data);
+
+ // Issues the OnError() callback to the |sink_|.
+ void HandleError(OSStatus err);
+
+ // Our creator, the audio manager needs to be notified when we close.
+ AudioManagerMac* manager_;
+
+ // Contains the desired number of audio frames in each callback.
+ size_t number_of_frames_;
+
+ // Pointer to the object that will receive the recorded audio samples.
+ AudioInputCallback* sink_;
+
+ // Structure that holds the desired output format of the stream.
+ // Note that, this format can differ from the device(=input) format.
+ AudioStreamBasicDescription format_;
+
+ // The special Audio Unit called AUHAL, which allows us to pass audio data
+ // directly from a microphone, through the HAL, and to our application.
+ // The AUHAL also enables selection of non default devices.
+ AudioUnit audio_unit_;
+
+ // Provides a mechanism for encapsulating one or more buffers of audio data.
+ AudioBufferList audio_buffer_list_;
+
+ // Temporary storage for recorded data. The InputProc() renders into this
+ // array as soon as a frame of the desired buffer size has been recorded.
+ scoped_array<uint8> audio_data_buffer_;
+
+ // True after successfull Start(), false after successful Stop().
+ bool started_;
+
+ DISALLOW_COPY_AND_ASSIGN(AUAudioInputStream);
+};
+
+#endif // MEDIA_AUDIO_MAC_AUDIO_LOW_LATENCY_INPUT_MAC_H_
diff --git a/media/audio/mac/audio_low_latency_input_mac_unittest.cc b/media/audio/mac/audio_low_latency_input_mac_unittest.cc
new file mode 100644
index 0000000..ea492a4
--- /dev/null
+++ b/media/audio/mac/audio_low_latency_input_mac_unittest.cc
@@ -0,0 +1,289 @@
+// Copyright (c) 2011 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 "base/basictypes.h"
+#include "base/environment.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/mac/audio_low_latency_input_mac.h"
+#include "media/base/seekable_buffer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::AnyNumber;
+using ::testing::Between;
+using ::testing::NotNull;
+
+class MockAudioInputCallback : public AudioInputStream::AudioInputCallback {
+ public:
+ MOCK_METHOD4(OnData, void(AudioInputStream* stream,
+ const uint8* src, uint32 size,
+ uint32 hardware_delay_bytes));
+ MOCK_METHOD1(OnClose, void(AudioInputStream* stream));
+ MOCK_METHOD2(OnError, void(AudioInputStream* stream, int code));
+};
+
+// This audio sink implementation should be used for manual tests only since
+// the recorded data is stored on a raw binary data file.
+// The last test (WriteToFileAudioSink) - which is disabled by default -
+// can use this audio sink to store the captured data on a file for offline
+// analysis.
+class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback {
+ public:
+ // Allocate space for ~10 seconds of data @ 48kHz in stereo:
+ // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes.
+ static const size_t kMaxBufferSize = 2 * 2 * 480 * 100 * 10;
+
+ explicit WriteToFileAudioSink(const char* file_name)
+ : buffer_(0, kMaxBufferSize),
+ file_(fopen(file_name, "wb")),
+ bytes_to_write_(0) {
+ }
+
+ virtual ~WriteToFileAudioSink() {
+ size_t bytes_written = 0;
+ while (bytes_written < bytes_to_write_) {
+ const uint8* chunk;
+ size_t chunk_size;
+
+ // Stop writing if no more data is available.
+ if (!buffer_.GetCurrentChunk(&chunk, &chunk_size))
+ break;
+
+ // Write recorded data chunk to the file and prepare for next chunk.
+ fwrite(chunk, 1, chunk_size, file_);
+ buffer_.Seek(chunk_size);
+ bytes_written += chunk_size;
+ }
+ fclose(file_);
+ }
+
+ // AudioInputStream::AudioInputCallback implementation.
+ virtual void OnData(AudioInputStream* stream,
+ const uint8* src, uint32 size,
+ uint32 hardware_delay_bytes) {
+ // Store data data in a temporary buffer to avoid making blocking
+ // fwrite() calls in the audio callback. The complete buffer will be
+ // written to file in the destructor.
+ if (buffer_.Append(src, size)) {
+ bytes_to_write_ += size;
+ }
+ }
+
+ virtual void OnClose(AudioInputStream* stream) {}
+ virtual void OnError(AudioInputStream* stream, int code) {}
+
+ private:
+ media::SeekableBuffer buffer_;
+ FILE* file_;
+ size_t bytes_to_write_;
+};
+
+// Convenience method which ensures that we are not running on the build
+// bots and that at least one valid input device can be found.
+static bool CanRunAudioTests() {
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ if (env->HasVar("CHROME_HEADLESS"))
+ return false;
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ if (NULL == audio_man)
+ return false;
+ return audio_man->HasAudioInputDevices();
+}
+
+// Convenience method which creates a default AudioInputStream object using
+// a 10ms frame size and a sample rate which is set to the hardware sample rate.
+static AudioInputStream* CreateDefaultAudioInputStream() {
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
+ int samples_per_packet = fs / 100;
+ AudioInputStream* ais = audio_man->MakeAudioInputStream(
+ AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ CHANNEL_LAYOUT_STEREO, fs, 16, samples_per_packet));
+ EXPECT_TRUE(ais);
+ return ais;
+}
+
+// Convenience method which creates an AudioInputStream object with a specified
+// channel layout.
+static AudioInputStream* CreateAudioInputStream(ChannelLayout channel_layout) {
+ AudioManager* audio_man = AudioManager::GetAudioManager();
+ int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
+ int samples_per_packet = fs / 100;
+ AudioInputStream* ais = audio_man->MakeAudioInputStream(
+ AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+ channel_layout, fs, 16, samples_per_packet));
+ EXPECT_TRUE(ais);
+ return ais;
+}
+
+
+// Test Create(), Close().
+TEST(MacAudioInputTest, AUAudioInputStreamCreateAndClose) {
+ if (!CanRunAudioTests())
+ return;
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ ais->Close();
+}
+
+// Test Open(), Close().
+TEST(MacAudioInputTest, AUAudioInputStreamOpenAndClose) {
+ if (!CanRunAudioTests())
+ return;
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ EXPECT_TRUE(ais->Open());
+ ais->Close();
+}
+
+// Test Open(), Start(), Close().
+TEST(MacAudioInputTest, AUAudioInputStreamOpenStartAndClose) {
+ if (!CanRunAudioTests())
+ return;
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ EXPECT_TRUE(ais->Open());
+ MockAudioInputCallback sink;
+ ais->Start(&sink);
+ EXPECT_CALL(sink, OnClose(ais))
+ .Times(1);
+ ais->Close();
+}
+
+// Test Open(), Start(), Stop(), Close().
+TEST(MacAudioInputTest, AUAudioInputStreamOpenStartStopAndClose) {
+ if (!CanRunAudioTests())
+ return;
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ EXPECT_TRUE(ais->Open());
+ MockAudioInputCallback sink;
+ ais->Start(&sink);
+ ais->Stop();
+ EXPECT_CALL(sink, OnClose(ais))
+ .Times(1);
+ ais->Close();
+}
+
+// Test some additional calling sequences.
+TEST(MacAudioInputTest, AUAudioInputStreamMiscCallingSequences) {
+ if (!CanRunAudioTests())
+ return;
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ AUAudioInputStream* auais = static_cast<AUAudioInputStream*>(ais);
+
+ // Open(), Open() should fail the second time.
+ EXPECT_TRUE(ais->Open());
+ EXPECT_FALSE(ais->Open());
+
+ MockAudioInputCallback sink;
+
+ // Start(), Start() is a valid calling sequence (second call does nothing).
+ ais->Start(&sink);
+ EXPECT_TRUE(auais->started());
+ ais->Start(&sink);
+ EXPECT_TRUE(auais->started());
+
+ // Stop(), Stop() is a valid calling sequence (second call does nothing).
+ ais->Stop();
+ EXPECT_FALSE(auais->started());
+ ais->Stop();
+ EXPECT_FALSE(auais->started());
+
+ EXPECT_CALL(sink, OnClose(ais))
+ .Times(1);
+ ais->Close();
+}
+
+// Verify that recording starts and stops correctly in mono using mocked sink.
+TEST(MacAudioInputTest, AUAudioInputStreamVerifyMonoRecording) {
+ if (!CanRunAudioTests())
+ return;
+
+ // Create an audio input stream which records in mono.
+ AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_MONO);
+ EXPECT_TRUE(ais->Open());
+
+ int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
+ int samples_per_packet = fs / 100;
+ int bits_per_sample = 16;
+ uint32 bytes_per_packet = samples_per_packet * (bits_per_sample / 8);
+
+ MockAudioInputCallback sink;
+
+ // We use 10ms packets and will run the test for ~100ms. Given that the
+ // startup sequence takes some time, it is reasonable to expect 5-10
+ // callbacks in this time period. All should contain valid packets of
+ // the same size.
+ EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, bytes_per_packet))
+ .Times(Between(5, 10));
+
+ ais->Start(&sink);
+ base::PlatformThread::Sleep(TestTimeouts::tiny_timeout_ms());
+ ais->Stop();
+
+ // Verify that the sink receieves OnClose() call when calling Close().
+ EXPECT_CALL(sink, OnClose(ais))
+ .Times(1);
+ ais->Close();
+}
+
+// Verify that recording starts and stops correctly in mono using mocked sink.
+TEST(MacAudioInputTest, AUAudioInputStreamVerifyStereoRecording) {
+ if (!CanRunAudioTests())
+ return;
+
+ // Create an audio input stream which records in stereo.
+ AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_STEREO);
+ EXPECT_TRUE(ais->Open());
+
+ int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
+ int samples_per_packet = fs / 100;
+ int bits_per_sample = 16;
+ uint32 bytes_per_packet = 2 * samples_per_packet * (bits_per_sample / 8);
+
+ MockAudioInputCallback sink;
+
+ // We use 10ms packets and will run the test for ~100ms. Given that the
+ // startup sequence takes some time, it is reasonable to expect 5-10
+ // callbacks in this time period. All should contain valid packets of
+ // the same size.
+ EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, bytes_per_packet))
+ .Times(Between(5, 10));
+
+ ais->Start(&sink);
+ base::PlatformThread::Sleep(TestTimeouts::tiny_timeout_ms());
+ ais->Stop();
+
+ // Verify that the sink receieves OnClose() call when calling Close().
+ EXPECT_CALL(sink, OnClose(ais))
+ .Times(1);
+ ais->Close();
+}
+
+// This test is intended for manual tests and should only be enabled
+// when it is required to store the captured data on a local file.
+// By default, GTest will print out YOU HAVE 1 DISABLED TEST.
+// To include disabled tests in test execution, just invoke the test program
+// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS
+// environment variable to a value greater than 0.
+TEST(MacAudioInputTest, DISABLED_AUAudioInputStreamRecordToFile) {
+ if (!CanRunAudioTests())
+ return;
+ const char* file_name = "out_stereo_10sec.pcm";
+
+ int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate());
+ AudioInputStream* ais = CreateDefaultAudioInputStream();
+ EXPECT_TRUE(ais->Open());
+
+ fprintf(stderr, " File name : %s\n", file_name);
+ fprintf(stderr, " Sample rate: %d\n", fs);
+ WriteToFileAudioSink file_sink(file_name);
+ fprintf(stderr, " >> Speak into the mic while recording...\n");
+ ais->Start(&file_sink);
+ base::PlatformThread::Sleep(TestTimeouts::action_timeout_ms());
+ ais->Stop();
+ fprintf(stderr, " >> Recording has stopped.\n");
+ ais->Close();
+}
+
diff --git a/media/audio/mac/audio_manager_mac.cc b/media/audio/mac/audio_manager_mac.cc
index ab2e18c..a4a6217 100644
--- a/media/audio/mac/audio_manager_mac.cc
+++ b/media/audio/mac/audio_manager_mac.cc
@@ -8,6 +8,7 @@
#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_low_latency_input_mac.h"
#include "media/audio/mac/audio_low_latency_output_mac.h"
#include "media/audio/mac/audio_manager_mac.h"
#include "media/audio/mac/audio_output_mac.h"
@@ -127,6 +128,8 @@ AudioInputStream* AudioManagerMac::MakeAudioInputStream(
return FakeAudioInputStream::MakeFakeStream(params);
} else if (params.format == AudioParameters::AUDIO_PCM_LINEAR) {
return new PCMQueueInAudioInputStream(this, params);
+ } else if (params.format == AudioParameters::AUDIO_PCM_LOW_LATENCY) {
+ return new AUAudioInputStream(this, params);
}
return NULL;
}
@@ -147,7 +150,7 @@ void AudioManagerMac::ReleaseOutputStream(AudioOutputStream* stream) {
}
// Called by the stream when it has been released by calling Close().
-void AudioManagerMac::ReleaseInputStream(PCMQueueInAudioInputStream* stream) {
+void AudioManagerMac::ReleaseInputStream(AudioInputStream* stream) {
delete stream;
}
diff --git a/media/audio/mac/audio_manager_mac.h b/media/audio/mac/audio_manager_mac.h
index b4ad7c6..a8ffced 100644
--- a/media/audio/mac/audio_manager_mac.h
+++ b/media/audio/mac/audio_manager_mac.h
@@ -33,7 +33,7 @@ class AudioManagerMac : public AudioManagerBase {
// They are called internally by the respective audio stream when it has
// been closed.
void ReleaseOutputStream(AudioOutputStream* stream);
- void ReleaseInputStream(PCMQueueInAudioInputStream* stream);
+ void ReleaseInputStream(AudioInputStream* stream);
private:
virtual ~AudioManagerMac();