diff options
author | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-05 08:41:35 +0000 |
---|---|---|
committer | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-05 08:41:35 +0000 |
commit | 39d2c5cc9d1a8c7378d3edf1fd3b6a32847567ae (patch) | |
tree | ecad93ca6b4d4635a3b79214252d81c8464373a1 /media/audio | |
parent | b36980b1c9726783cca0118aae333f5cd7d0ef49 (diff) | |
download | chromium_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.h | 5 | ||||
-rw-r--r-- | media/audio/mac/audio_low_latency_input_mac.cc | 354 | ||||
-rw-r--r-- | media/audio/mac/audio_low_latency_input_mac.h | 112 | ||||
-rw-r--r-- | media/audio/mac/audio_low_latency_input_mac_unittest.cc | 289 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.cc | 5 | ||||
-rw-r--r-- | media/audio/mac/audio_manager_mac.h | 2 |
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(); |