// 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::ScopedCFTypeRef; namespace media { AggregateDeviceManager::AggregateDeviceManager() : plugin_id_(kAudioObjectUnknown), input_device_(kAudioDeviceUnknown), output_device_(kAudioDeviceUnknown), aggregate_device_(kAudioObjectUnknown) { } AggregateDeviceManager::~AggregateDeviceManager() { DestroyAggregateDevice(); } AudioDeviceID AggregateDeviceManager::GetDefaultAggregateDevice() { AudioDeviceID current_input_device; AudioDeviceID current_output_device; AudioManagerMac::GetDefaultInputDevice(¤t_input_device); AudioManagerMac::GetDefaultOutputDevice(¤t_output_device); if (AudioManagerMac::HardwareSampleRateForDevice(current_input_device) != AudioManagerMac::HardwareSampleRateForDevice(current_output_device)) { // TODO(crogers): with some extra work we can make aggregate devices work // if the clock domain is the same but the sample-rate differ. // For now we fallback to the synchronized path. return kAudioDeviceUnknown; } // 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. 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; } input_device_ = current_input_device; output_device_ = current_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