summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorcrogers@google.com <crogers@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-26 08:25:35 +0000
committercrogers@google.com <crogers@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-26 08:25:35 +0000
commite8e49bf53386d069ea5097370f881c41a0d703f7 (patch)
treed93541d62f911cb7a0c48d0554438e41f4706900 /media
parentb78f96a67f2f6da212aa941d0105580ec970d67d (diff)
downloadchromium_src-e8e49bf53386d069ea5097370f881c41a0d703f7.zip
chromium_src-e8e49bf53386d069ea5097370f881c41a0d703f7.tar.gz
chromium_src-e8e49bf53386d069ea5097370f881c41a0d703f7.tar.bz2
Implement AUHALStream using an AUHAL with configurable device selection.
We currently have three audio output back-ends on OSX: 1) AUAudioOutputStream in audio_low_latency_mac 2) AudioSynchronizedStream in audio_synchronized_mac 3) AudioHardwareUnifiedStream in audio_unified_mac 1 and 3 can be replaced by a single back-end which uses an AUHAL Even (2) can probably use the AUHAL with an appropriately configured aggregate device. Along with other improvements and simplifications, this CL has a chance to improve http://crbug.com/158170. BUG=158170 TEST=audio_auhal_mac_unittest.cc (also manually verified that repeated device changes did not crash in gdb) Review URL: https://chromiumcodereview.appspot.com/12611022 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@190593 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/audio/mac/audio_auhal_mac.cc529
-rw-r--r--media/audio/mac/audio_auhal_mac.h163
-rw-r--r--media/audio/mac/audio_auhal_mac_unittest.cc218
-rw-r--r--media/audio/mac/audio_device_listener_mac.cc14
-rw-r--r--media/audio/mac/audio_device_listener_mac.h4
-rw-r--r--media/audio/mac/audio_device_listener_mac_unittest.cc2
-rw-r--r--media/audio/mac/audio_manager_mac.cc154
-rw-r--r--media/audio/mac/audio_manager_mac.h23
-rw-r--r--media/media.gyp3
9 files changed, 1025 insertions, 85 deletions
diff --git a/media/audio/mac/audio_auhal_mac.cc b/media/audio/mac/audio_auhal_mac.cc
new file mode 100644
index 0000000..20df6f5
--- /dev/null
+++ b/media/audio/mac/audio_auhal_mac.cc
@@ -0,0 +1,529 @@
+// Copyright 2013 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_auhal_mac.h"
+
+#include <CoreServices/CoreServices.h>
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/mac/mac_logging.h"
+#include "media/audio/audio_util.h"
+#include "media/audio/mac/audio_manager_mac.h"
+#include "media/base/media_switches.h"
+
+namespace media {
+
+static std::ostream& operator<<(std::ostream& os,
+ const AudioStreamBasicDescription& format) {
+ os << "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;
+ return os;
+}
+
+static void ZeroBufferList(AudioBufferList* buffer_list) {
+ for (size_t i = 0; i < buffer_list->mNumberBuffers; ++i) {
+ memset(buffer_list->mBuffers[i].mData,
+ 0,
+ buffer_list->mBuffers[i].mDataByteSize);
+ }
+}
+
+static void WrapBufferList(AudioBufferList* buffer_list,
+ AudioBus* bus,
+ int frames) {
+ DCHECK(buffer_list);
+ DCHECK(bus);
+ const int channels = bus->channels();
+ const int buffer_list_channels = buffer_list->mNumberBuffers;
+ DCHECK_EQ(channels, buffer_list_channels);
+
+ // Copy pointers from AudioBufferList.
+ for (int i = 0; i < channels; ++i) {
+ bus->SetChannelData(
+ i, static_cast<float*>(buffer_list->mBuffers[i].mData));
+ }
+
+ // Finally set the actual length.
+ bus->set_frames(frames);
+}
+
+AUHALStream::AUHALStream(
+ AudioManagerMac* manager,
+ const AudioParameters& params,
+ AudioDeviceID device)
+ : manager_(manager),
+ params_(params),
+ input_channels_(params_.input_channels()),
+ output_channels_(params_.channels()),
+ number_of_frames_(params_.frames_per_buffer()),
+ source_(NULL),
+ device_(device),
+ audio_unit_(0),
+ volume_(1),
+ hardware_latency_frames_(0),
+ stopped_(false),
+ input_buffer_list_(NULL) {
+ // We must have a manager.
+ DCHECK(manager_);
+
+ DVLOG(1) << "Input channels: " << input_channels_;
+ DVLOG(1) << "Output channels: " << output_channels_;
+ DVLOG(1) << "Sample rate: " << params_.sample_rate();
+ DVLOG(1) << "Buffer size: " << number_of_frames_;
+}
+
+AUHALStream::~AUHALStream() {
+}
+
+bool AUHALStream::Open() {
+ // Get the total number of input and output channels that the
+ // hardware supports.
+ int device_input_channels;
+ bool got_input_channels = AudioManagerMac::GetDeviceChannels(
+ device_,
+ kAudioDevicePropertyScopeInput,
+ &device_input_channels);
+
+ int device_output_channels;
+ bool got_output_channels = AudioManagerMac::GetDeviceChannels(
+ device_,
+ kAudioDevicePropertyScopeOutput,
+ &device_output_channels);
+
+ // Sanity check the requested I/O channels.
+ if (!got_input_channels ||
+ input_channels_ < 0 || input_channels_ > device_input_channels) {
+ LOG(ERROR) << "AudioDevice does not support requested input channels.";
+ return false;
+ }
+
+ if (!got_output_channels ||
+ output_channels_ <= 0 || output_channels_ > device_output_channels) {
+ LOG(ERROR) << "AudioDevice does not support requested output channels.";
+ return false;
+ }
+
+ // The requested sample-rate must match the hardware sample-rate.
+ int sample_rate = AudioManagerMac::HardwareSampleRateForDevice(device_);
+
+ if (sample_rate != params_.sample_rate()) {
+ LOG(ERROR) << "Requested sample-rate: " << params_.sample_rate()
+ << " must match the hardware sample-rate: " << sample_rate;
+ return false;
+ }
+
+ CreateIOBusses();
+
+ bool configured = ConfigureAUHAL();
+ if (configured)
+ hardware_latency_frames_ = GetHardwareLatency();
+
+ return configured;
+}
+
+void AUHALStream::Close() {
+ if (input_buffer_list_) {
+ input_buffer_list_storage_.reset();
+ input_buffer_list_ = NULL;
+ input_bus_.reset(NULL);
+ output_bus_.reset(NULL);
+ }
+
+ if (audio_unit_) {
+ AudioUnitUninitialize(audio_unit_);
+ AudioComponentInstanceDispose(audio_unit_);
+ }
+
+ // Inform the audio manager that we have been closed. This will cause our
+ // destruction.
+ manager_->ReleaseOutputStream(this);
+}
+
+void AUHALStream::Start(AudioSourceCallback* callback) {
+ DCHECK(callback);
+ if (!audio_unit_) {
+ DLOG(ERROR) << "Open() has not been called successfully";
+ return;
+ }
+
+ stopped_ = false;
+ {
+ base::AutoLock auto_lock(source_lock_);
+ source_ = callback;
+ }
+
+ AudioOutputUnitStart(audio_unit_);
+}
+
+void AUHALStream::Stop() {
+ if (stopped_)
+ return;
+
+ AudioOutputUnitStop(audio_unit_);
+
+ base::AutoLock auto_lock(source_lock_);
+ source_ = NULL;
+ stopped_ = true;
+}
+
+void AUHALStream::SetVolume(double volume) {
+ volume_ = static_cast<float>(volume);
+
+ // TODO(crogers): set volume property
+}
+
+void AUHALStream::GetVolume(double* volume) {
+ *volume = volume_;
+}
+
+// Pulls on our provider to get rendered audio stream.
+// Note to future hackers of this function: Do not add locks which can
+// be contended in the middle of stream processing here (starting and stopping
+// the stream are ok) because this is running on a real-time thread.
+OSStatus AUHALStream::Render(
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* output_time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data) {
+ TRACE_EVENT0("audio", "AUHALStream::Render");
+
+ if (number_of_frames != number_of_frames_) {
+ // This can happen if we've suddenly changed sample-rates.
+ // The stream should be stopping very soon.
+ //
+ // Unfortunately AUAudioInputStream and AUHALStream share the frame
+ // size set by kAudioDevicePropertyBufferFrameSize above on a per process
+ // basis. What this means is that the |number_of_frames| value may be
+ // larger or smaller than the value set during ConfigureAUHAL().
+ // In this case either audio input or audio output will be broken,
+ // so just output silence.
+ ZeroBufferList(io_data);
+ return noErr;
+ }
+
+ if (input_channels_ > 0 && input_buffer_list_) {
+ // Get the input data. |input_buffer_list_| is wrapped
+ // to point to the data allocated in |input_bus_|.
+ OSStatus result = AudioUnitRender(
+ audio_unit_,
+ flags,
+ output_time_stamp,
+ 1,
+ number_of_frames,
+ input_buffer_list_);
+ if (result != noErr)
+ ZeroBufferList(input_buffer_list_);
+ }
+
+ // Make |output_bus_| wrap the output AudioBufferList.
+ WrapBufferList(io_data, output_bus_.get(), number_of_frames);
+
+ // Update the playout latency.
+ double playout_latency_frames = GetPlayoutLatency(output_time_stamp);
+
+ uint32 hardware_pending_bytes = static_cast<uint32>
+ ((playout_latency_frames + 0.5) * output_format_.mBytesPerFrame);
+
+ {
+ // Render() shouldn't be called except between AudioOutputUnitStart() and
+ // AudioOutputUnitStop() calls, but crash reports have shown otherwise:
+ // http://crbug.com/178765. We use |source_lock_| to prevent races and
+ // crashes in Render() when |source_| is cleared.
+ base::AutoLock auto_lock(source_lock_);
+ if (!source_) {
+ ZeroBufferList(io_data);
+ return noErr;
+ }
+
+ // Supply the input data and render the output data.
+ source_->OnMoreIOData(
+ input_bus_.get(),
+ output_bus_.get(),
+ AudioBuffersState(0, hardware_pending_bytes));
+ }
+
+ return noErr;
+}
+
+// AUHAL callback.
+OSStatus AUHALStream::InputProc(
+ void* user_data,
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* output_time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data) {
+ // Dispatch to our class method.
+ AUHALStream* audio_output =
+ static_cast<AUHALStream*>(user_data);
+ if (!audio_output)
+ return -1;
+
+ return audio_output->Render(
+ flags,
+ output_time_stamp,
+ bus_number,
+ number_of_frames,
+ io_data);
+}
+
+double AUHALStream::GetHardwareLatency() {
+ if (!audio_unit_ || device_ == kAudioObjectUnknown) {
+ DLOG(WARNING) << "AudioUnit is NULL or device ID is unknown";
+ return 0.0;
+ }
+
+ // Get audio unit latency.
+ Float64 audio_unit_latency_sec = 0.0;
+ UInt32 size = sizeof(audio_unit_latency_sec);
+ OSStatus result = AudioUnitGetProperty(
+ audio_unit_,
+ kAudioUnitProperty_Latency,
+ kAudioUnitScope_Global,
+ 0,
+ &audio_unit_latency_sec,
+ &size);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result) << "Could not get AudioUnit latency";
+ return 0.0;
+ }
+
+ // Get output audio device latency.
+ static const AudioObjectPropertyAddress property_address = {
+ kAudioDevicePropertyLatency,
+ kAudioDevicePropertyScopeOutput,
+ kAudioObjectPropertyElementMaster
+ };
+
+ UInt32 device_latency_frames = 0;
+ size = sizeof(device_latency_frames);
+ result = AudioObjectGetPropertyData(
+ device_,
+ &property_address,
+ 0,
+ NULL,
+ &size,
+ &device_latency_frames);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result) << "Could not get audio device latency";
+ return 0.0;
+ }
+
+ return static_cast<double>((audio_unit_latency_sec *
+ output_format_.mSampleRate) + device_latency_frames);
+}
+
+double AUHALStream::GetPlayoutLatency(
+ const AudioTimeStamp* output_time_stamp) {
+ // Ensure mHostTime is valid.
+ if ((output_time_stamp->mFlags & kAudioTimeStampHostTimeValid) == 0)
+ return 0;
+
+ // Get the delay between the moment getting the callback and the scheduled
+ // time stamp that tells when the data is going to be played out.
+ UInt64 output_time_ns = AudioConvertHostTimeToNanos(
+ output_time_stamp->mHostTime);
+ UInt64 now_ns = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
+
+ // Prevent overflow leading to huge delay information; occurs regularly on
+ // the bots, probably less so in the wild.
+ if (now_ns > output_time_ns)
+ return 0;
+
+ double delay_frames = static_cast<double>
+ (1e-9 * (output_time_ns - now_ns) * output_format_.mSampleRate);
+
+ return (delay_frames + hardware_latency_frames_);
+}
+
+void AUHALStream::CreateIOBusses() {
+ if (input_channels_ > 0) {
+ // Allocate storage for the AudioBufferList used for the
+ // input data from the input AudioUnit.
+ // We allocate enough space for with one AudioBuffer per channel.
+ size_t buffer_list_size = offsetof(AudioBufferList, mBuffers[0]) +
+ (sizeof(AudioBuffer) * input_channels_);
+ input_buffer_list_storage_.reset(new uint8[buffer_list_size]);
+
+ input_buffer_list_ =
+ reinterpret_cast<AudioBufferList*>(input_buffer_list_storage_.get());
+ input_buffer_list_->mNumberBuffers = input_channels_;
+
+ // |input_bus_| allocates the storage for the PCM input data.
+ input_bus_ = AudioBus::Create(input_channels_, number_of_frames_);
+
+ // Make the AudioBufferList point to the memory in |input_bus_|.
+ UInt32 buffer_size_bytes = input_bus_->frames() * sizeof(Float32);
+ for (size_t i = 0; i < input_buffer_list_->mNumberBuffers; ++i) {
+ input_buffer_list_->mBuffers[i].mNumberChannels = 1;
+ input_buffer_list_->mBuffers[i].mDataByteSize = buffer_size_bytes;
+ input_buffer_list_->mBuffers[i].mData = input_bus_->channel(i);
+ }
+ }
+
+ // The output bus will wrap the AudioBufferList given to us in
+ // the Render() callback.
+ DCHECK_GT(output_channels_, 0);
+ output_bus_ = AudioBus::CreateWrapper(output_channels_);
+}
+
+bool AUHALStream::EnableIO(bool enable, UInt32 scope) {
+ // See Apple technote for details about the EnableIO property.
+ // Note that we use bus 1 for input and bus 0 for output:
+ // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
+ UInt32 enable_IO = enable ? 1 : 0;
+ OSStatus result = AudioUnitSetProperty(
+ audio_unit_,
+ kAudioOutputUnitProperty_EnableIO,
+ scope,
+ (scope == kAudioUnitScope_Input) ? 1 : 0,
+ &enable_IO,
+ sizeof(enable_IO));
+ return (result == noErr);
+}
+
+bool AUHALStream::SetStreamFormat(
+ AudioStreamBasicDescription* desc,
+ int channels,
+ UInt32 scope,
+ UInt32 element) {
+ DCHECK(desc);
+ AudioStreamBasicDescription& format = *desc;
+
+ format.mSampleRate = params_.sample_rate();
+ format.mFormatID = kAudioFormatLinearPCM;
+ format.mFormatFlags = kAudioFormatFlagsNativeFloatPacked |
+ kLinearPCMFormatFlagIsNonInterleaved;
+ format.mBytesPerPacket = sizeof(Float32);
+ format.mFramesPerPacket = 1;
+ format.mBytesPerFrame = sizeof(Float32);
+ format.mChannelsPerFrame = channels;
+ format.mBitsPerChannel = 32;
+ format.mReserved = 0;
+
+ OSStatus result = AudioUnitSetProperty(
+ audio_unit_,
+ kAudioUnitProperty_StreamFormat,
+ scope,
+ element,
+ &format,
+ sizeof(format));
+ return (result == noErr);
+}
+
+bool AUHALStream::ConfigureAUHAL() {
+ if (device_ == kAudioObjectUnknown ||
+ (input_channels_ == 0 && output_channels_ == 0))
+ return false;
+
+ AudioComponentDescription desc = {
+ kAudioUnitType_Output,
+ kAudioUnitSubType_HALOutput,
+ kAudioUnitManufacturer_Apple,
+ 0,
+ 0
+ };
+ AudioComponent comp = AudioComponentFindNext(0, &desc);
+ if (!comp)
+ return false;
+
+ OSStatus result = AudioComponentInstanceNew(comp, &audio_unit_);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result) << "AudioComponentInstanceNew() failed.";
+ return false;
+ }
+
+ result = AudioUnitInitialize(audio_unit_);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result) << "AudioUnitInitialize() failed.";
+ return false;
+ }
+
+ // Enable input and output as appropriate.
+ if (!EnableIO(input_channels_ > 0, kAudioUnitScope_Input))
+ return false;
+ if (!EnableIO(output_channels_ > 0, kAudioUnitScope_Output))
+ return false;
+
+ // Set the device to be used with the AUHAL AudioUnit.
+ result = AudioUnitSetProperty(
+ audio_unit_,
+ kAudioOutputUnitProperty_CurrentDevice,
+ kAudioUnitScope_Global,
+ 0,
+ &device_,
+ sizeof(AudioDeviceID));
+ if (result != noErr)
+ return false;
+
+ // Set stream formats.
+ // See Apple's tech note for details on the peculiar way that
+ // inputs and outputs are handled in the AUHAL concerning scope and bus
+ // (element) numbers:
+ // http://developer.apple.com/library/mac/#technotes/tn2091/_index.html
+
+ if (input_channels_ > 0) {
+ if (!SetStreamFormat(&input_format_,
+ input_channels_,
+ kAudioUnitScope_Output,
+ 1))
+ return false;
+ }
+
+ if (output_channels_ > 0) {
+ if (!SetStreamFormat(&output_format_,
+ output_channels_,
+ kAudioUnitScope_Input,
+ 0))
+ return false;
+ }
+
+ // Set the buffer frame size.
+ // WARNING: Setting this value changes the frame size for all audio units in
+ // the current process. It's imperative that the input and output frame sizes
+ // be the same as the frames_per_buffer() returned by
+ // GetDefaultOutputStreamParameters().
+ // See http://crbug.com/154352 for details.
+ UInt32 buffer_size = number_of_frames_;
+ result = AudioUnitSetProperty(
+ audio_unit_,
+ kAudioDevicePropertyBufferFrameSize,
+ kAudioUnitScope_Output,
+ 0,
+ &buffer_size,
+ sizeof(buffer_size));
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result)
+ << "AudioUnitSetProperty(kAudioDevicePropertyBufferFrameSize) failed.";
+ return false;
+ }
+
+ // Setup callback.
+ AURenderCallbackStruct callback;
+ callback.inputProc = InputProc;
+ callback.inputProcRefCon = this;
+ result = AudioUnitSetProperty(
+ audio_unit_,
+ kAudioUnitProperty_SetRenderCallback,
+ kAudioUnitScope_Input,
+ 0,
+ &callback,
+ sizeof(callback));
+ if (result != noErr)
+ return false;
+
+ return true;
+}
+
+} // namespace media
diff --git a/media/audio/mac/audio_auhal_mac.h b/media/audio/mac/audio_auhal_mac.h
new file mode 100644
index 0000000..bd88097
--- /dev/null
+++ b/media/audio/mac/audio_auhal_mac.h
@@ -0,0 +1,163 @@
+// Copyright 2013 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 notes:
+//
+// - It is recommended to first acquire the native sample rate of the default
+// output device and then use the same rate when creating this object.
+// Use AudioManagerMac::HardwareSampleRate() to retrieve the sample rate.
+// - Calling Close() also leads to self destruction.
+// - The latency consists of two parts:
+// 1) Hardware latency, which includes Audio Unit latency, audio device
+// latency;
+// 2) The delay between the moment getting the callback and the scheduled time
+// stamp that tells when the data is going to be played out.
+//
+#ifndef MEDIA_AUDIO_MAC_AUDIO_AUHAL_MAC_H_
+#define MEDIA_AUDIO_MAC_AUDIO_AUHAL_MAC_H_
+
+#include <AudioUnit/AudioUnit.h>
+#include <CoreAudio/CoreAudio.h>
+
+#include "base/compiler_specific.h"
+#include "base/synchronization/lock.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_parameters.h"
+
+namespace media {
+
+class AudioManagerMac;
+
+// Implementation of AudioOuputStream for Mac OS X using the
+// AUHAL Audio Unit present in OS 10.4 and later.
+// It is useful for low-latency output with optional synchronized
+// input.
+//
+// Overview of operation:
+// 1) An object of AUHALStream is created by the AudioManager
+// factory: audio_man->MakeAudioStream().
+// 2) Next some thread will call Open(), at that point the underlying
+// AUHAL Audio Unit is created and configured to use the |device|.
+// 3) Then some thread will call Start(source).
+// Then the AUHAL is started which creates its own thread which
+// periodically will call the source for more data as buffers are being
+// consumed.
+// 4) At some point some thread will call Stop(), which we handle by directly
+// stopping the default output Audio Unit.
+// 6) The same thread that called stop will call Close() where we cleanup
+// and notify the audio manager, which likely will destroy this object.
+
+class AUHALStream : public AudioOutputStream {
+ public:
+ // |manager| creates this object.
+ // |device| is the CoreAudio device to use for the stream.
+ // It will often be the default output device.
+ AUHALStream(AudioManagerMac* manager,
+ const AudioParameters& params,
+ AudioDeviceID device);
+ // The dtor is typically called by the AudioManager only and it is usually
+ // triggered by calling AudioOutputStream::Close().
+ virtual ~AUHALStream();
+
+ // Implementation of AudioOutputStream.
+ virtual bool Open() OVERRIDE;
+ virtual void Close() OVERRIDE;
+ virtual void Start(AudioSourceCallback* callback) OVERRIDE;
+ virtual void Stop() OVERRIDE;
+ virtual void SetVolume(double volume) OVERRIDE;
+ virtual void GetVolume(double* volume) OVERRIDE;
+
+ private:
+ // AUHAL callback.
+ static OSStatus InputProc(void* user_data,
+ AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data);
+
+ OSStatus Render(AudioUnitRenderActionFlags* flags,
+ const AudioTimeStamp* output_time_stamp,
+ UInt32 bus_number,
+ UInt32 number_of_frames,
+ AudioBufferList* io_data);
+
+ // Helper method to enable input and output.
+ bool EnableIO(bool enable, UInt32 scope);
+
+ // Sets the stream format on the AUHAL to PCM Float32 non-interleaved
+ // for the given number of channels on the given scope and element.
+ // The created stream description will be stored in |desc|.
+ bool SetStreamFormat(AudioStreamBasicDescription* desc,
+ int channels,
+ UInt32 scope,
+ UInt32 element);
+
+ // Creates the AUHAL, sets its stream format, buffer-size, etc.
+ bool ConfigureAUHAL();
+
+ // Creates the input and output busses.
+ void CreateIOBusses();
+
+ // Gets the fixed playout device hardware latency and stores it. Returns 0
+ // if not available.
+ double GetHardwareLatency();
+
+ // Gets the current playout latency value.
+ double GetPlayoutLatency(const AudioTimeStamp* output_time_stamp);
+
+ // Our creator, the audio manager needs to be notified when we close.
+ AudioManagerMac* manager_;
+
+ AudioParameters params_;
+ // For convenience - same as in params_.
+ int input_channels_;
+ int output_channels_;
+
+ // Buffer-size.
+ size_t number_of_frames_;
+
+ // Pointer to the object that will provide the audio samples.
+ AudioSourceCallback* source_;
+
+ // Protects |source_|. Necessary since Render() calls seem to be in flight
+ // when |audio_unit_| is supposedly stopped. See http://crbug.com/178765.
+ base::Lock source_lock_;
+
+ // Holds the stream format details such as bitrate.
+ AudioStreamBasicDescription input_format_;
+ AudioStreamBasicDescription output_format_;
+
+ // The audio device to use with the AUHAL.
+ // We can potentially handle both input and output with this device.
+ AudioDeviceID device_;
+
+ // The AUHAL Audio Unit which talks to |device_|.
+ AudioUnit audio_unit_;
+
+ // Volume level from 0 to 1.
+ float volume_;
+
+ // Fixed playout hardware latency in frames.
+ double hardware_latency_frames_;
+
+ // The flag used to stop the streaming.
+ bool stopped_;
+
+ // The input AudioUnit renders its data here.
+ scoped_array<uint8> input_buffer_list_storage_;
+ AudioBufferList* input_buffer_list_;
+
+ // Holds the actual data for |input_buffer_list_|.
+ scoped_ptr<AudioBus> input_bus_;
+
+ // Container for retrieving data from AudioSourceCallback::OnMoreIOData().
+ scoped_ptr<AudioBus> output_bus_;
+
+ DISALLOW_COPY_AND_ASSIGN(AUHALStream);
+};
+
+} // namespace media
+
+#endif // MEDIA_AUDIO_MAC_AUDIO_AUHAL_MAC_H_
diff --git a/media/audio/mac/audio_auhal_mac_unittest.cc b/media/audio/mac/audio_auhal_mac_unittest.cc
new file mode 100644
index 0000000..cab8c28
--- /dev/null
+++ b/media/audio/mac/audio_auhal_mac_unittest.cc
@@ -0,0 +1,218 @@
+// Copyright 2013 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/memory/scoped_ptr.h"
+#include "media/audio/audio_io.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/simple_sources.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::DoAll;
+using ::testing::Field;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::NiceMock;
+using ::testing::NotNull;
+using ::testing::Return;
+
+static const int kBitsPerSample = 16;
+
+// TODO(crogers): Most of these tests can be made platform agnostic.
+// http://crbug.com/223242
+
+namespace media {
+
+class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
+ public:
+ MOCK_METHOD2(OnMoreData, int(AudioBus* audio_bus,
+ AudioBuffersState buffers_state));
+ MOCK_METHOD3(OnMoreIOData, int(AudioBus* source,
+ AudioBus* dest,
+ AudioBuffersState buffers_state));
+ MOCK_METHOD1(OnError, void(AudioOutputStream* stream));
+};
+
+// Convenience method which creates a default AudioOutputStream object but
+// also allows the user to modify the default settings.
+class AudioOutputStreamWrapper {
+ public:
+ explicit AudioOutputStreamWrapper()
+ : audio_man_(AudioManager::Create()),
+ format_(AudioParameters::AUDIO_PCM_LOW_LATENCY),
+ bits_per_sample_(kBitsPerSample) {
+ AudioParameters preferred_params =
+ audio_man_->GetDefaultOutputStreamParameters();
+ channel_layout_ = preferred_params.channel_layout();
+ channels_ = preferred_params.channels();
+ sample_rate_ = preferred_params.sample_rate();
+ samples_per_packet_ = preferred_params.frames_per_buffer();
+ }
+
+ ~AudioOutputStreamWrapper() {}
+
+ // Creates AudioOutputStream object using default parameters.
+ AudioOutputStream* Create() {
+ return CreateOutputStream();
+ }
+
+ // Creates AudioOutputStream object using non-default parameters where the
+ // frame size is modified.
+ AudioOutputStream* Create(int samples_per_packet) {
+ samples_per_packet_ = samples_per_packet;
+ return CreateOutputStream();
+ }
+
+ // Creates AudioOutputStream object using non-default parameters where the
+ // sample rate is modified.
+ AudioOutputStream* CreateWithSampleRate(int sample_rate) {
+ sample_rate_ = sample_rate;
+ return CreateOutputStream();
+ }
+
+ // Creates AudioOutputStream object using non-default parameters where the
+ // channel layout is modified.
+ AudioOutputStream* CreateWithLayout(ChannelLayout layout) {
+ channel_layout_ = layout;
+ channels_ = ChannelLayoutToChannelCount(layout);
+ return CreateOutputStream();
+ }
+
+ AudioParameters::Format format() const { return format_; }
+ int channels() const { return ChannelLayoutToChannelCount(channel_layout_); }
+ int bits_per_sample() const { return bits_per_sample_; }
+ int sample_rate() const { return sample_rate_; }
+ int samples_per_packet() const { return samples_per_packet_; }
+
+ bool CanRunAudioTests() {
+ return audio_man_->HasAudioOutputDevices();
+ }
+
+ private:
+ AudioOutputStream* CreateOutputStream() {
+ AudioParameters params;
+ params.Reset(format_, channel_layout_,
+ channels_, 0,
+ sample_rate_, bits_per_sample_,
+ samples_per_packet_);
+
+ AudioOutputStream* aos = audio_man_->MakeAudioOutputStream(params);
+ EXPECT_TRUE(aos);
+ return aos;
+ }
+
+ scoped_ptr<AudioManager> audio_man_;
+
+ AudioParameters::Format format_;
+ ChannelLayout channel_layout_;
+ int channels_;
+ int bits_per_sample_;
+ int sample_rate_;
+ int samples_per_packet_;
+};
+
+// Test that we can get the hardware sample-rate.
+TEST(AUHALStreamTest, HardwareSampleRate) {
+ AudioOutputStreamWrapper aosw;
+ if (!aosw.CanRunAudioTests())
+ return;
+
+ int sample_rate = aosw.sample_rate();
+ EXPECT_GE(sample_rate, 16000);
+ EXPECT_LE(sample_rate, 192000);
+}
+
+// Test Create(), Close() calling sequence.
+TEST(AUHALStreamTest, CreateAndClose) {
+ AudioOutputStreamWrapper aosw;
+ if (!aosw.CanRunAudioTests())
+ return;
+
+ AudioOutputStream* aos = aosw.Create();
+ aos->Close();
+}
+
+// Test Open(), Close() calling sequence.
+TEST(AUHALStreamTest, OpenAndClose) {
+ AudioOutputStreamWrapper aosw;
+ if (!aosw.CanRunAudioTests())
+ return;
+
+ AudioOutputStream* aos = aosw.Create();
+ EXPECT_TRUE(aos->Open());
+ aos->Close();
+}
+
+// Test Open(), Start(), Close() calling sequence.
+TEST(AUHALStreamTest, OpenStartAndClose) {
+ AudioOutputStreamWrapper aosw;
+ if (!aosw.CanRunAudioTests())
+ return;
+
+ AudioOutputStream* aos = aosw.Create();
+ EXPECT_TRUE(aos->Open());
+ MockAudioSourceCallback source;
+ EXPECT_CALL(source, OnError(aos))
+ .Times(0);
+ aos->Start(&source);
+ aos->Close();
+}
+
+// Test Open(), Start(), Stop(), Close() calling sequence.
+TEST(AUHALStreamTest, OpenStartStopAndClose) {
+ AudioOutputStreamWrapper aosw;
+ if (!aosw.CanRunAudioTests())
+ return;
+
+ AudioOutputStream* aos = aosw.Create();
+ EXPECT_TRUE(aos->Open());
+ MockAudioSourceCallback source;
+ EXPECT_CALL(source, OnError(aos))
+ .Times(0);
+ aos->Start(&source);
+ aos->Stop();
+ aos->Close();
+}
+
+// This test produces actual audio for 0.5 seconds on the default audio device
+// at the hardware sample-rate (usually 44.1KHz).
+// Parameters have been chosen carefully so you should not hear
+// pops or noises while the sound is playing.
+TEST(AUHALStreamTest, AUHALStreamPlay200HzTone) {
+ AudioOutputStreamWrapper aosw;
+ if (!aosw.CanRunAudioTests())
+ return;
+
+ AudioOutputStream* aos = aosw.CreateWithLayout(CHANNEL_LAYOUT_MONO);
+
+ EXPECT_TRUE(aos->Open());
+
+ SineWaveAudioSource source(1, 200.0, aosw.sample_rate());
+ aos->Start(&source);
+ usleep(500000);
+
+ aos->Stop();
+ aos->Close();
+}
+
+// Test that Open() will fail with a sample-rate which isn't the hardware
+// sample-rate.
+TEST(AUHALStreamTest, AUHALStreamInvalidSampleRate) {
+ AudioOutputStreamWrapper aosw;
+ if (!aosw.CanRunAudioTests())
+ return;
+
+ int non_default_sample_rate = aosw.sample_rate() == 44100 ?
+ 48000 : 44100;
+ AudioOutputStream* aos = aosw.CreateWithSampleRate(non_default_sample_rate);
+
+ EXPECT_FALSE(aos->Open());
+
+ aos->Close();
+}
+
+} // namespace media
diff --git a/media/audio/mac/audio_device_listener_mac.cc b/media/audio/mac/audio_device_listener_mac.cc
index 15de47b..b6eaf78 100644
--- a/media/audio/mac/audio_device_listener_mac.cc
+++ b/media/audio/mac/audio_device_listener_mac.cc
@@ -105,16 +105,7 @@ OSStatus AudioDeviceListenerMac::OnDefaultDeviceChanged(
addresses[i].mScope == kDeviceChangePropertyAddress.mScope &&
addresses[i].mElement == kDeviceChangePropertyAddress.mElement &&
context) {
- AudioDeviceListenerMac* p_this =
- static_cast<AudioDeviceListenerMac*>(context);
- // Device changes on Mac are risky, the OSX API is not thread safe, so
- // only change devices if we have to. Again, see http://crbug.com/158170.
- // TODO(crogers): Remove this once the AUHAL output driver is in.
- int sample_rate = AUAudioOutputStream::HardwareSampleRate();
- if (p_this->current_sample_rate_ != sample_rate) {
- p_this->current_sample_rate_ = sample_rate;
- p_this->listener_cb_.Run();
- }
+ static_cast<AudioDeviceListenerMac*>(context)->listener_cb_.Run();
break;
}
}
@@ -125,8 +116,7 @@ OSStatus AudioDeviceListenerMac::OnDefaultDeviceChanged(
AudioDeviceListenerMac::AudioDeviceListenerMac(const base::Closure& listener_cb)
: listener_block_(NULL),
add_listener_block_func_(NULL),
- remove_listener_block_func_(NULL),
- current_sample_rate_(AUAudioOutputStream::HardwareSampleRate()) {
+ remove_listener_block_func_(NULL) {
// Device changes are hard, lets go shopping! Sadly OSX does not handle
// property listener callbacks in a thread safe manner. On 10.6 we can set
// kAudioHardwarePropertyRunLoop to account for this. On 10.7 this is broken
diff --git a/media/audio/mac/audio_device_listener_mac.h b/media/audio/mac/audio_device_listener_mac.h
index 24ecf26..d2d5870 100644
--- a/media/audio/mac/audio_device_listener_mac.h
+++ b/media/audio/mac/audio_device_listener_mac.h
@@ -68,10 +68,6 @@ class MEDIA_EXPORT AudioDeviceListenerMac {
// thread.
base::ThreadChecker thread_checker_;
- // If the sample rate hasn't changed, don't fire a device change. OSX will
- // handle the routing under the hood.
- int current_sample_rate_;
-
DISALLOW_COPY_AND_ASSIGN(AudioDeviceListenerMac);
};
diff --git a/media/audio/mac/audio_device_listener_mac_unittest.cc b/media/audio/mac/audio_device_listener_mac_unittest.cc
index c88c339..1f884bc 100644
--- a/media/audio/mac/audio_device_listener_mac_unittest.cc
+++ b/media/audio/mac/audio_device_listener_mac_unittest.cc
@@ -63,8 +63,6 @@ class AudioDeviceListenerMacTest : public testing::Test {
kAudioObjectPropertyElementMaster }
};
- // Force sample rate change so the listener fires.
- output_device_listener_->current_sample_rate_++;
return noErr == output_device_listener_->OnDefaultDeviceChanged(
kAudioObjectSystemObject, 1, addresses, output_device_listener_.get());
}
diff --git a/media/audio/mac/audio_manager_mac.cc b/media/audio/mac/audio_manager_mac.cc
index 93dbcc9..ff7e289 100644
--- a/media/audio/mac/audio_manager_mac.cc
+++ b/media/audio/mac/audio_manager_mac.cc
@@ -14,6 +14,7 @@
#include "base/sys_string_conversions.h"
#include "media/audio/audio_parameters.h"
#include "media/audio/audio_util.h"
+#include "media/audio/mac/audio_auhal_mac.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"
@@ -32,6 +33,9 @@ static const int kMaxOutputStreams = 50;
// Default buffer size in samples for low-latency input and output streams.
static const int kDefaultLowLatencyBufferSize = 128;
+// Default sample-rate on most Apple hardware.
+static const int kFallbackSampleRate = 44100;
+
static int ChooseBufferSize(int output_sample_rate) {
int buffer_size = kDefaultLowLatencyBufferSize;
const int user_buffer_size = GetUserBufferSize();
@@ -69,38 +73,9 @@ static bool HasAudioHardware(AudioObjectPropertySelector selector) {
// Returns true if the default input device is the same as
// the default output device.
-static bool HasUnifiedDefaultIO() {
+bool AudioManagerMac::HasUnifiedDefaultIO() {
AudioDeviceID input_id, output_id;
-
- AudioObjectPropertyAddress pa;
- pa.mSelector = kAudioHardwarePropertyDefaultInputDevice;
- pa.mScope = kAudioObjectPropertyScopeGlobal;
- pa.mElement = kAudioObjectPropertyElementMaster;
- UInt32 size = sizeof(input_id);
-
- // Get the default input.
- OSStatus result = AudioObjectGetPropertyData(
- kAudioObjectSystemObject,
- &pa,
- 0,
- 0,
- &size,
- &input_id);
-
- if (result != noErr)
- return false;
-
- // Get the default output.
- pa.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
- result = AudioObjectGetPropertyData(
- kAudioObjectSystemObject,
- &pa,
- 0,
- 0,
- &size,
- &output_id);
-
- if (result != noErr)
+ if (!GetDefaultInputDevice(&input_id) || !GetDefaultOutputDevice(&output_id))
return false;
return input_id == output_id;
@@ -254,7 +229,11 @@ static AudioDeviceID GetAudioDeviceIdByUId(bool is_input,
return audio_device_id;
}
-AudioManagerMac::AudioManagerMac() {
+AudioManagerMac::AudioManagerMac()
+ : current_sample_rate_(HardwareSampleRate()) {
+ if (!GetDefaultOutputDevice(&current_output_device_))
+ current_output_device_ = kAudioDeviceUnknown;
+
SetMaxOutputStreamsAllowed(kMaxOutputStreams);
// Task must be posted last to avoid races from handing out "this" to the
@@ -281,30 +260,40 @@ bool AudioManagerMac::HasAudioInputDevices() {
}
// TODO(crogers): There are several places on the OSX specific code which
-// could benefit from this helper function.
+// could benefit from these helper functions.
+bool AudioManagerMac::GetDefaultInputDevice(
+ AudioDeviceID* device) {
+ return GetDefaultDevice(device, true);
+}
+
bool AudioManagerMac::GetDefaultOutputDevice(
AudioDeviceID* device) {
+ return GetDefaultDevice(device, false);
+}
+
+bool AudioManagerMac::GetDefaultDevice(
+ AudioDeviceID* device, bool input) {
CHECK(device);
// Obtain the current output device selected by the user.
- static const AudioObjectPropertyAddress kAddress = {
- kAudioHardwarePropertyDefaultOutputDevice,
- kAudioObjectPropertyScopeGlobal,
- kAudioObjectPropertyElementMaster
- };
+ AudioObjectPropertyAddress pa;
+ pa.mSelector = input ? kAudioHardwarePropertyDefaultInputDevice :
+ kAudioHardwarePropertyDefaultOutputDevice;
+ pa.mScope = kAudioObjectPropertyScopeGlobal;
+ pa.mElement = kAudioObjectPropertyElementMaster;
UInt32 size = sizeof(*device);
OSStatus result = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
- &kAddress,
+ &pa,
0,
0,
&size,
device);
if ((result != kAudioHardwareNoError) || (*device == kAudioDeviceUnknown)) {
- DLOG(ERROR) << "Error getting default output AudioDevice.";
+ DLOG(ERROR) << "Error getting default AudioDevice.";
return false;
}
@@ -312,24 +301,21 @@ bool AudioManagerMac::GetDefaultOutputDevice(
}
bool AudioManagerMac::GetDefaultOutputChannels(
- int* channels, int* channels_per_frame) {
+ int* channels) {
AudioDeviceID device;
if (!GetDefaultOutputDevice(&device))
return false;
return GetDeviceChannels(device,
kAudioDevicePropertyScopeOutput,
- channels,
- channels_per_frame);
+ channels);
}
bool AudioManagerMac::GetDeviceChannels(
AudioDeviceID device,
AudioObjectPropertyScope scope,
- int* channels,
- int* channels_per_frame) {
+ int* channels) {
CHECK(channels);
- CHECK(channels_per_frame);
// Get stream configuration.
AudioObjectPropertyAddress pa;
@@ -358,19 +344,53 @@ bool AudioManagerMac::GetDeviceChannels(
return false;
// Determine number of input channels.
- *channels_per_frame = buffer_list.mNumberBuffers > 0 ?
+ int channels_per_frame = buffer_list.mNumberBuffers > 0 ?
buffer_list.mBuffers[0].mNumberChannels : 0;
- if (*channels_per_frame == 1 && buffer_list.mNumberBuffers > 1) {
+ if (channels_per_frame == 1 && buffer_list.mNumberBuffers > 1) {
// Non-interleaved.
*channels = buffer_list.mNumberBuffers;
} else {
// Interleaved.
- *channels = *channels_per_frame;
+ *channels = channels_per_frame;
}
return true;
}
+int AudioManagerMac::HardwareSampleRateForDevice(AudioDeviceID device_id) {
+ Float64 nominal_sample_rate;
+ UInt32 info_size = sizeof(nominal_sample_rate);
+
+ static const AudioObjectPropertyAddress kNominalSampleRateAddress = {
+ kAudioDevicePropertyNominalSampleRate,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+ OSStatus result = AudioObjectGetPropertyData(
+ device_id,
+ &kNominalSampleRateAddress,
+ 0,
+ 0,
+ &info_size,
+ &nominal_sample_rate);
+ if (result != noErr) {
+ OSSTATUS_DLOG(WARNING, result)
+ << "Could not get default sample rate for device: " << device_id;
+ return 0;
+ }
+
+ return static_cast<int>(nominal_sample_rate);
+}
+
+int AudioManagerMac::HardwareSampleRate() {
+ // Determine the default output device's sample-rate.
+ AudioDeviceID device_id = kAudioObjectUnknown;
+ if (!GetDefaultOutputDevice(&device_id))
+ return kFallbackSampleRate;
+
+ return HardwareSampleRateForDevice(device_id);
+}
+
void AudioManagerMac::GetAudioInputDeviceNames(
media::AudioDeviceNames* device_names) {
GetAudioDeviceInfo(true, device_names);
@@ -412,6 +432,9 @@ AudioOutputStream* AudioManagerMac::MakeLowLatencyOutputStream(
if (HasUnifiedDefaultIO())
return new AudioHardwareUnifiedStream(this, params);
+ // TODO(crogers): use aggregate devices along with AUHALStream
+ // to get better performance for built-in hardware.
+
// kAudioDeviceUnknown translates to "use default" here.
return new AudioSynchronizedStream(this,
params,
@@ -419,7 +442,9 @@ AudioOutputStream* AudioManagerMac::MakeLowLatencyOutputStream(
kAudioDeviceUnknown);
}
- return new AUAudioOutputStream(this, params);
+ AudioDeviceID device = kAudioObjectUnknown;
+ GetDefaultOutputDevice(&device);
+ return new AUHALStream(this, params, device);
}
AudioInputStream* AudioManagerMac::MakeLinearInputStream(
@@ -444,9 +469,7 @@ AudioInputStream* AudioManagerMac::MakeLowLatencyInputStream(
AudioParameters AudioManagerMac::GetPreferredOutputStreamParameters(
const AudioParameters& input_params) {
int hardware_channels = 2;
- int hardware_channels_per_frame = 1;
- if (!GetDefaultOutputChannels(&hardware_channels,
- &hardware_channels_per_frame)) {
+ if (!GetDefaultOutputChannels(&hardware_channels)) {
// Fallback to stereo.
hardware_channels = 2;
}
@@ -485,8 +508,10 @@ AudioParameters AudioManagerMac::GetPreferredOutputStreamParameters(
void AudioManagerMac::CreateDeviceListener() {
DCHECK(GetMessageLoop()->BelongsToCurrentThread());
- output_device_listener_.reset(new AudioDeviceListenerMac(base::Bind(
- &AudioManagerMac::DelayedDeviceChange, base::Unretained(this))));
+ output_device_listener_.reset(new AudioDeviceListenerMac(BindToLoop(
+ GetMessageLoop(), base::Bind(
+ &AudioManagerMac::HandleDeviceChanges,
+ base::Unretained(this)))));
}
void AudioManagerMac::DestroyDeviceListener() {
@@ -494,13 +519,18 @@ void AudioManagerMac::DestroyDeviceListener() {
output_device_listener_.reset();
}
-void AudioManagerMac::DelayedDeviceChange() {
- // TODO(dalecurtis): This is ridiculous, but we need to delay device changes
- // to workaround threading issues with OSX property listener callbacks. See
- // http://crbug.com/158170
- GetMessageLoop()->PostDelayedTask(FROM_HERE, base::Bind(
- &AudioManagerMac::NotifyAllOutputDeviceChangeListeners,
- base::Unretained(this)), base::TimeDelta::FromSeconds(2));
+void AudioManagerMac::HandleDeviceChanges() {
+ int new_sample_rate = HardwareSampleRate();
+ AudioDeviceID new_output_device;
+ GetDefaultOutputDevice(&new_output_device);
+
+ if (current_sample_rate_ == new_sample_rate &&
+ current_output_device_ == new_output_device)
+ return;
+
+ current_sample_rate_ = new_sample_rate;
+ current_output_device_ = new_output_device;
+ NotifyAllOutputDeviceChangeListeners();
}
AudioManager* CreateAudioManager() {
diff --git a/media/audio/mac/audio_manager_mac.h b/media/audio/mac/audio_manager_mac.h
index 6ebac06..8a93215 100644
--- a/media/audio/mac/audio_manager_mac.h
+++ b/media/audio/mac/audio_manager_mac.h
@@ -5,6 +5,9 @@
#ifndef MEDIA_AUDIO_MAC_AUDIO_MANAGER_MAC_H_
#define MEDIA_AUDIO_MAC_AUDIO_MANAGER_MAC_H_
+#include <CoreAudio/AudioHardware.h>
+#include <string>
+
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/message_loop_proxy.h"
@@ -38,15 +41,18 @@ class MEDIA_EXPORT AudioManagerMac : public AudioManagerBase {
virtual AudioInputStream* MakeLowLatencyInputStream(
const AudioParameters& params, const std::string& device_id) OVERRIDE;
+ static bool GetDefaultInputDevice(AudioDeviceID* device);
static bool GetDefaultOutputDevice(AudioDeviceID* device);
+ static bool GetDefaultDevice(AudioDeviceID* device, bool input);
- static bool GetDefaultOutputChannels(int* channels,
- int* channels_per_frame);
+ static bool GetDefaultOutputChannels(int* channels);
static bool GetDeviceChannels(AudioDeviceID device,
AudioObjectPropertyScope scope,
- int* channels,
- int* channels_per_frame);
+ int* channels);
+
+ static int HardwareSampleRateForDevice(AudioDeviceID device_id);
+ static int HardwareSampleRate();
protected:
virtual ~AudioManagerMac();
@@ -55,13 +61,20 @@ class MEDIA_EXPORT AudioManagerMac : public AudioManagerBase {
const AudioParameters& input_params) OVERRIDE;
private:
+ bool HasUnifiedDefaultIO();
+
// Helper methods for constructing AudioDeviceListenerMac on the audio thread.
void CreateDeviceListener();
void DestroyDeviceListener();
- void DelayedDeviceChange();
+ void HandleDeviceChanges();
scoped_ptr<AudioDeviceListenerMac> output_device_listener_;
+ // Track the output sample-rate and the default output device
+ // so we can intelligently handle device notifications only when necessary.
+ int current_sample_rate_;
+ AudioDeviceID current_output_device_;
+
DISALLOW_COPY_AND_ASSIGN(AudioManagerMac);
};
diff --git a/media/media.gyp b/media/media.gyp
index 97ef02b..705102e 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -130,6 +130,8 @@
'audio/linux/alsa_wrapper.h',
'audio/linux/audio_manager_linux.cc',
'audio/linux/audio_manager_linux.h',
+ 'audio/mac/audio_auhal_mac.cc',
+ 'audio/mac/audio_auhal_mac.h',
'audio/mac/audio_device_listener_mac.cc',
'audio/mac/audio_device_listener_mac.h',
'audio/mac/audio_input_mac.cc',
@@ -864,6 +866,7 @@
'audio/fake_audio_consumer_unittest.cc',
'audio/ios/audio_manager_ios_unittest.cc',
'audio/linux/alsa_output_unittest.cc',
+ 'audio/mac/audio_auhal_mac_unittest.cc',
'audio/mac/audio_device_listener_mac_unittest.cc',
'audio/mac/audio_low_latency_input_mac_unittest.cc',
'audio/simple_sources_unittest.cc',