Commit 9df2d09c authored by crogers@google.com's avatar crogers@google.com

Add OSX aggregate audio device support for best performance.

Some audio hardware is presented as separate input and output devices
even though they are really the same physical hardware and
share the same "clock domain" at the lowest levels of the driver.
A common of example of this is the "built-in" audio hardware:
    "Built-in Line Input"
    "Built-in Output"
We would like to use an "aggregate" device for these situations, since
CoreAudio will make the most efficient use of the shared "clock domain"
so we get the lowest latency and use fewer threads.

BUG=none
TEST=extensive manual testing with built-in Mac hardware
http://chromium.googlecode.com/svn/trunk/samples/audio/visualizer-live.html

Review URL: https://chromiumcodereview.appspot.com/13403002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@194765 0039d316-1c4b-4281-b951-d872f2087c98
parent 3d69f33e
// 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/aggregate_device_manager.h"
#include <CoreAudio/AudioHardware.h>
#include <string>
#include "base/mac/mac_logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "media/audio/audio_parameters.h"
#include "media/audio/mac/audio_manager_mac.h"
using base::mac::ScopedCFTypeRef;
namespace media {
AggregateDeviceManager::AggregateDeviceManager()
: plugin_id_(kAudioObjectUnknown),
input_device_(kAudioDeviceUnknown),
output_device_(kAudioDeviceUnknown),
aggregate_device_(kAudioObjectUnknown) {
}
AggregateDeviceManager::~AggregateDeviceManager() {
DestroyAggregateDevice();
}
AudioDeviceID AggregateDeviceManager::GetDefaultAggregateDevice() {
// Use a lazily created aggregate device if it's already available
// and still appropriate.
if (aggregate_device_ != kAudioObjectUnknown) {
// TODO(crogers): handle default device changes for synchronized I/O.
// For now, we check to make sure the default devices haven't changed
// since we lazily created the aggregate device.
AudioDeviceID current_input_device;
AudioDeviceID current_output_device;
AudioManagerMac::GetDefaultInputDevice(&current_input_device);
AudioManagerMac::GetDefaultOutputDevice(&current_output_device);
if (current_input_device == input_device_ &&
current_output_device == output_device_)
return aggregate_device_;
// For now, once lazily created don't attempt to create another
// aggregate device.
return kAudioDeviceUnknown;
}
AudioManagerMac::GetDefaultInputDevice(&input_device_);
AudioManagerMac::GetDefaultOutputDevice(&output_device_);
// Only create an aggregrate device if the clock domains match.
UInt32 input_clockdomain = GetClockDomain(input_device_);
UInt32 output_clockdomain = GetClockDomain(output_device_);
DVLOG(1) << "input_clockdomain: " << input_clockdomain;
DVLOG(1) << "output_clockdomain: " << output_clockdomain;
if (input_clockdomain == 0 || input_clockdomain != output_clockdomain)
return kAudioDeviceUnknown;
OSStatus result = CreateAggregateDevice(
input_device_,
output_device_,
&aggregate_device_);
if (result != noErr)
DestroyAggregateDevice();
return aggregate_device_;
}
CFStringRef AggregateDeviceManager::GetDeviceUID(AudioDeviceID id) {
static const AudioObjectPropertyAddress kDeviceUIDAddress = {
kAudioDevicePropertyDeviceUID,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
// As stated in the CoreAudio header (AudioHardwareBase.h),
// the caller is responsible for releasing the device_UID.
CFStringRef device_UID;
UInt32 size = sizeof(device_UID);
OSStatus result = AudioObjectGetPropertyData(
id,
&kDeviceUIDAddress,
0,
0,
&size,
&device_UID);
return (result == noErr) ? device_UID : NULL;
}
void AggregateDeviceManager::GetDeviceName(
AudioDeviceID id, char* name, UInt32 size) {
static const AudioObjectPropertyAddress kDeviceNameAddress = {
kAudioDevicePropertyDeviceName,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
OSStatus result = AudioObjectGetPropertyData(
id,
&kDeviceNameAddress,
0,
0,
&size,
name);
if (result != noErr && size > 0)
name[0] = 0;
}
UInt32 AggregateDeviceManager::GetClockDomain(AudioDeviceID device_id) {
static const AudioObjectPropertyAddress kClockDomainAddress = {
kAudioDevicePropertyClockDomain,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 clockdomain = 0;
UInt32 size = sizeof(UInt32);
OSStatus result = AudioObjectGetPropertyData(
device_id,
&kClockDomainAddress,
0,
0,
&size,
&clockdomain);
return (result == noErr) ? clockdomain : 0;
}
OSStatus AggregateDeviceManager::GetPluginID(AudioObjectID* id) {
DCHECK(id);
// Get the audio hardware plugin.
CFStringRef bundle_name = CFSTR("com.apple.audio.CoreAudio");
AudioValueTranslation plugin_translation;
plugin_translation.mInputData = &bundle_name;
plugin_translation.mInputDataSize = sizeof(bundle_name);
plugin_translation.mOutputData = id;
plugin_translation.mOutputDataSize = sizeof(*id);
static const AudioObjectPropertyAddress kPlugInAddress = {
kAudioHardwarePropertyPlugInForBundleID,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 size = sizeof(plugin_translation);
OSStatus result = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
&kPlugInAddress,
0,
0,
&size,
&plugin_translation);
DVLOG(1) << "CoreAudio plugin ID: " << *id;
return result;
}
CFMutableDictionaryRef
AggregateDeviceManager::CreateAggregateDeviceDictionary(
AudioDeviceID input_id,
AudioDeviceID output_id) {
CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable(
NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!aggregate_device_dict)
return NULL;
const CFStringRef kAggregateDeviceName =
CFSTR("ChromeAggregateAudioDevice");
const CFStringRef kAggregateDeviceUID =
CFSTR("com.google.chrome.AggregateAudioDevice");
// Add name and UID of the device to the dictionary.
CFDictionaryAddValue(
aggregate_device_dict,
CFSTR(kAudioAggregateDeviceNameKey),
kAggregateDeviceName);
CFDictionaryAddValue(
aggregate_device_dict,
CFSTR(kAudioAggregateDeviceUIDKey),
kAggregateDeviceUID);
// Add a "private aggregate key" to the dictionary.
// The 1 value means that the created aggregate device will
// only be accessible from the process that created it, and
// won't be visible to outside processes.
int value = 1;
ScopedCFTypeRef<CFNumberRef> aggregate_device_number(CFNumberCreate(
NULL,
kCFNumberIntType,
&value));
CFDictionaryAddValue(
aggregate_device_dict,
CFSTR(kAudioAggregateDeviceIsPrivateKey),
aggregate_device_number);
return aggregate_device_dict;
}
CFMutableArrayRef
AggregateDeviceManager::CreateSubDeviceArray(
CFStringRef input_device_UID, CFStringRef output_device_UID) {
CFMutableArrayRef sub_devices_array = CFArrayCreateMutable(
NULL,
0,
&kCFTypeArrayCallBacks);
CFArrayAppendValue(sub_devices_array, input_device_UID);
CFArrayAppendValue(sub_devices_array, output_device_UID);
return sub_devices_array;
}
OSStatus AggregateDeviceManager::CreateAggregateDevice(
AudioDeviceID input_id,
AudioDeviceID output_id,
AudioDeviceID* aggregate_device) {
DCHECK(aggregate_device);
const size_t kMaxDeviceNameLength = 256;
scoped_ptr<char[]> input_device_name(new char[kMaxDeviceNameLength]);
GetDeviceName(
input_id,
input_device_name.get(),
sizeof(input_device_name));
DVLOG(1) << "Input device: \n" << input_device_name;
scoped_ptr<char[]> output_device_name(new char[kMaxDeviceNameLength]);
GetDeviceName(
output_id,
output_device_name.get(),
sizeof(output_device_name));
DVLOG(1) << "Output device: \n" << output_device_name;
OSStatus result = GetPluginID(&plugin_id_);
if (result != noErr)
return result;
// Create a dictionary for the aggregate device.
ScopedCFTypeRef<CFMutableDictionaryRef> aggregate_device_dict(
CreateAggregateDeviceDictionary(input_id, output_id));
if (!aggregate_device_dict)
return -1;
// Create the aggregate device.
static const AudioObjectPropertyAddress kCreateAggregateDeviceAddress = {
kAudioPlugInCreateAggregateDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 size = sizeof(*aggregate_device);
result = AudioObjectGetPropertyData(
plugin_id_,
&kCreateAggregateDeviceAddress,
sizeof(aggregate_device_dict),
&aggregate_device_dict,
&size,
aggregate_device);
if (result != noErr) {
DLOG(ERROR) << "Error creating aggregate audio device!";
return result;
}
// Set the sub-devices for the aggregate device.
// In this case we use two: the input and output devices.
ScopedCFTypeRef<CFStringRef> input_device_UID(GetDeviceUID(input_id));
ScopedCFTypeRef<CFStringRef> output_device_UID(GetDeviceUID(output_id));
if (!input_device_UID || !output_device_UID) {
DLOG(ERROR) << "Error getting audio device UID strings.";
return -1;
}
ScopedCFTypeRef<CFMutableArrayRef> sub_devices_array(
CreateSubDeviceArray(input_device_UID, output_device_UID));
if (sub_devices_array == NULL) {
DLOG(ERROR) << "Error creating sub-devices array.";
return -1;
}
static const AudioObjectPropertyAddress kSetSubDevicesAddress = {
kAudioAggregateDevicePropertyFullSubDeviceList,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
size = sizeof(CFMutableArrayRef);
result = AudioObjectSetPropertyData(
*aggregate_device,
&kSetSubDevicesAddress,
0,
NULL,
size,
&sub_devices_array);
if (result != noErr) {
DLOG(ERROR) << "Error setting aggregate audio device sub-devices!";
return result;
}
// Use the input device as the master device.
static const AudioObjectPropertyAddress kSetMasterDeviceAddress = {
kAudioAggregateDevicePropertyMasterSubDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
size = sizeof(CFStringRef);
result = AudioObjectSetPropertyData(
*aggregate_device,
&kSetMasterDeviceAddress,
0,
NULL,
size,
&input_device_UID);
if (result != noErr) {
DLOG(ERROR) << "Error setting aggregate audio device master device!";
return result;
}
DVLOG(1) << "New aggregate device: " << *aggregate_device;
return noErr;
}
void AggregateDeviceManager::DestroyAggregateDevice() {
if (aggregate_device_ == kAudioObjectUnknown)
return;
static const AudioObjectPropertyAddress kDestroyAddress = {
kAudioPlugInDestroyAggregateDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
UInt32 size = sizeof(aggregate_device_);
OSStatus result = AudioObjectGetPropertyData(
plugin_id_,
&kDestroyAddress,
0,
NULL,
&size,
&aggregate_device_);
if (result != noErr) {
DLOG(ERROR) << "Error destroying aggregate audio device!";
return;
}
aggregate_device_ = kAudioObjectUnknown;
}
} // namespace media
// 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.
#ifndef MEDIA_AUDIO_MAC_AGGREGATE_DEVICE_MANAGER_H_
#define MEDIA_AUDIO_MAC_AGGREGATE_DEVICE_MANAGER_H_
#include <CoreAudio/CoreAudio.h>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "media/base/media_export.h"
namespace media {
class MEDIA_EXPORT AggregateDeviceManager {
public:
AggregateDeviceManager();
~AggregateDeviceManager();
// Lazily creates an aggregate device based on the default
// input and output devices.
// It will either return a valid device or kAudioDeviceUnknown
// if the default devices are not suitable for aggregate devices.
AudioDeviceID GetDefaultAggregateDevice();
private:
// The caller is responsible for releasing the CFStringRef.
static CFStringRef GetDeviceUID(AudioDeviceID id);
static void GetDeviceName(AudioDeviceID id, char* name, UInt32 size);
static UInt32 GetClockDomain(AudioDeviceID device_id);
static OSStatus GetPluginID(AudioObjectID* id);
CFMutableDictionaryRef CreateAggregateDeviceDictionary(
AudioDeviceID input_id,
AudioDeviceID output_id);
CFMutableArrayRef CreateSubDeviceArray(CFStringRef input_device_UID,
CFStringRef output_device_UID);
OSStatus CreateAggregateDevice(AudioDeviceID input_id,
AudioDeviceID output_id,
AudioDeviceID* aggregate_device);
void DestroyAggregateDevice();
AudioObjectID plugin_id_;
AudioDeviceID input_device_;
AudioDeviceID output_device_;
AudioDeviceID aggregate_device_;
DISALLOW_COPY_AND_ASSIGN(AggregateDeviceManager);
};
} // namespace media
#endif // MEDIA_AUDIO_MAC_AGGREGATE_DEVICE_MANAGER_H_
......@@ -76,10 +76,12 @@ AUHALStream::AUHALStream(
// 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_;
VLOG(1) << "AUHALStream::AUHALStream()";
VLOG(1) << "Device: " << device;
VLOG(1) << "Input channels: " << input_channels_;
VLOG(1) << "Output channels: " << output_channels_;
VLOG(1) << "Sample rate: " << params_.sample_rate();
VLOG(1) << "Buffer size: " << number_of_frames_;
}
AUHALStream::~AUHALStream() {
......
......@@ -426,24 +426,55 @@ AudioOutputStream* AudioManagerMac::MakeLinearOutputStream(
AudioOutputStream* AudioManagerMac::MakeLowLatencyOutputStream(
const AudioParameters& params) {
// Handle basic output with no input channels.
if (params.input_channels() == 0) {
AudioDeviceID device = kAudioObjectUnknown;
GetDefaultOutputDevice(&device);
return new AUHALStream(this, params, device);
}
// TODO(crogers): support more than stereo input.
if (params.input_channels() == 2) {
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,
kAudioDeviceUnknown,
kAudioDeviceUnknown);
if (params.input_channels() != 2) {
// WebAudio is currently hard-coded to 2 channels so we should not
// see this case.
NOTREACHED() << "Only stereo input is currently supported!";
return NULL;
}
AudioDeviceID device = kAudioObjectUnknown;
GetDefaultOutputDevice(&device);
return new AUHALStream(this, params, device);
if (HasUnifiedDefaultIO()) {
// For I/O, the simplest case is when the default input and output
// devices are the same.
GetDefaultOutputDevice(&device);
LOG(INFO) << "UNIFIED: default input and output devices are identical";
} else {
// Some audio hardware is presented as separate input and output devices
// even though they are really the same physical hardware and
// share the same "clock domain" at the lowest levels of the driver.
// A common of example of this is the "built-in" audio hardware:
// "Built-in Line Input"
// "Built-in Output"
// We would like to use an "aggregate" device for these situations, since
// CoreAudio will make the most efficient use of the shared "clock domain"
// so we get the lowest latency and use fewer threads.
device = aggregate_device_manager_.GetDefaultAggregateDevice();
if (device != kAudioObjectUnknown)
LOG(INFO) << "Using AGGREGATE audio device";
}
if (device != kAudioObjectUnknown)
return new AUHALStream(this, params, device);
// Fallback to AudioSynchronizedStream which will handle completely
// different and arbitrary combinations of input and output devices
// even running at different sample-rates.
// kAudioDeviceUnknown translates to "use default" here.
// TODO(crogers): consider tracking UMA stats on AUHALStream
// versus AudioSynchronizedStream.
return new AudioSynchronizedStream(this,
params,
kAudioDeviceUnknown,
kAudioDeviceUnknown);
}
AudioInputStream* AudioManagerMac::MakeLinearInputStream(
......
......@@ -12,6 +12,7 @@
#include "base/compiler_specific.h"
#include "base/message_loop_proxy.h"
#include "media/audio/audio_manager_base.h"
#include "media/audio/mac/aggregate_device_manager.h"
#include "media/audio/mac/audio_device_listener_mac.h"
namespace media {
......@@ -75,6 +76,8 @@ class MEDIA_EXPORT AudioManagerMac : public AudioManagerBase {
int current_sample_rate_;
AudioDeviceID current_output_device_;
AggregateDeviceManager aggregate_device_manager_;
DISALLOW_COPY_AND_ASSIGN(AudioManagerMac);
};
......
......@@ -85,6 +85,7 @@ AudioSynchronizedStream::AudioSynchronizedStream(
is_running_(false),
hardware_buffer_size_(kHardwareBufferSize),
channels_(kChannels) {
VLOG(1) << "AudioSynchronizedStream::AudioSynchronizedStream()";
}
AudioSynchronizedStream::~AudioSynchronizedStream() {
......
......@@ -136,6 +136,8 @@
'audio/linux/alsa_wrapper.h',
'audio/linux/audio_manager_linux.cc',
'audio/linux/audio_manager_linux.h',
'audio/mac/aggregate_device_manager.cc',
'audio/mac/aggregate_device_manager.h',
'audio/mac/audio_auhal_mac.cc',
'audio/mac/audio_auhal_mac.h',
'audio/mac/audio_device_listener_mac.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment