Chromium Code Reviews| Index: media/audio/mac/aggregate_device_manager.cc |
| =================================================================== |
| --- media/audio/mac/aggregate_device_manager.cc (revision 0) |
| +++ media/audio/mac/aggregate_device_manager.cc (revision 0) |
| @@ -0,0 +1,361 @@ |
| +// 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(¤t_input_device); |
| + AudioManagerMac::GetDefaultOutputDevice(¤t_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. |
|
DaleCurtis
2013/04/17 01:01:38
Should you destroy the old one at this point?
Chris Rogers
2013/04/17 20:42:31
I thought about this, but it would not be safe bec
|
| + 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_); |
| + VLOG(1) << "input_clockdomain: " << input_clockdomain; |
|
DaleCurtis
2013/04/17 01:01:38
DVLOG? Here and all other VLOG.
Chris Rogers
2013/04/17 20:42:31
Done.
|
| + VLOG(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; |
| +} |
| + |
| +OSStatus AggregateDeviceManager::GetDeviceName( |
|
no longer working on chromium
2013/04/17 09:16:22
nit, make it void since we don't care the return v
Chris Rogers
2013/04/17 20:42:31
Done.
|
| + AudioDeviceID id, char* name, UInt32 size) { |
| + static const AudioObjectPropertyAddress kDeviceNameAddress = { |
| + kAudioDevicePropertyDeviceName, |
| + kAudioObjectPropertyScopeGlobal, |
| + kAudioObjectPropertyElementMaster |
| + }; |
| + |
| + OSStatus result = AudioObjectGetPropertyData( |
| + id, |
| + &kDeviceNameAddress, |
| + 0, |
| + 0, |
| + &size, |
| + name); |
| + |
| + return result; |
| +} |
| + |
| +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); |
| + |
| + VLOG(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; |
| + |
| + CFStringRef kAggregateDeviceName = |
|
no longer working on chromium
2013/04/17 09:16:22
nit, should it be aggregate_device_name here, acco
Chris Rogers
2013/04/17 20:42:31
changed to "const"
|
| + CFSTR("ChromeAggregateAudioDevice"); |
| + 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; |
| + CFNumberRef kAggregateDeviceNumber = CFNumberCreate( |
|
no longer working on chromium
2013/04/17 09:16:22
is it a leak here? I guess you might need to call
Chris Rogers
2013/04/17 20:42:31
Yes, good catch - Fixed
|
| + NULL, |
| + kCFNumberIntType, |
| + &value); |
| + CFDictionaryAddValue( |
| + aggregate_device_dict, |
| + CFSTR(kAudioAggregateDeviceIsPrivateKey), |
| + kAggregateDeviceNumber); |
| + |
| + 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; |
| + char input_device_name_[kMaxDeviceNameLength]; |
|
DaleCurtis
2013/04/17 01:01:38
dynamically allocate instead?
Chris Rogers
2013/04/17 20:42:31
Done.
|
| + GetDeviceName( |
| + input_id, |
| + input_device_name_, |
| + sizeof(input_device_name_)); |
| + VLOG(1) << "Input device: \n" << input_device_name_; |
| + |
| + char output_device_name_[kMaxDeviceNameLength]; |
|
DaleCurtis
2013/04/17 01:01:38
Ditto.
Chris Rogers
2013/04/17 20:42:31
Done.
|
| + GetDeviceName( |
| + output_id, |
| + output_device_name_, |
| + sizeof(output_device_name_)); |
| + VLOG(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 == NULL) |
|
DaleCurtis
2013/04/17 01:01:38
!aggregate_device_dict
Chris Rogers
2013/04/17 20:42:31
Done.
|
| + 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 == NULL || output_device_UID == NULL) { |
|
DaleCurtis
2013/04/17 01:01:38
use ! instead.
Chris Rogers
2013/04/17 20:42:31
Done.
|
| + 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; |
| + } |
| + |
| + VLOG(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 |