1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "media/audio/mac/aggregate_device_manager.h" 6 7 #include <CoreAudio/AudioHardware.h> 8 #include <string> 9 10 #include "base/mac/mac_logging.h" 11 #include "base/mac/scoped_cftyperef.h" 12 #include "media/audio/audio_parameters.h" 13 #include "media/audio/mac/audio_manager_mac.h" 14 15 using base::ScopedCFTypeRef; 16 17 namespace media { 18 19 AggregateDeviceManager::AggregateDeviceManager() 20 : plugin_id_(kAudioObjectUnknown), 21 input_device_(kAudioDeviceUnknown), 22 output_device_(kAudioDeviceUnknown), 23 aggregate_device_(kAudioObjectUnknown) { 24 } 25 26 AggregateDeviceManager::~AggregateDeviceManager() { 27 DestroyAggregateDevice(); 28 } 29 30 AudioDeviceID AggregateDeviceManager::GetDefaultAggregateDevice() { 31 AudioDeviceID current_input_device; 32 AudioDeviceID current_output_device; 33 AudioManagerMac::GetDefaultInputDevice(¤t_input_device); 34 AudioManagerMac::GetDefaultOutputDevice(¤t_output_device); 35 36 if (AudioManagerMac::HardwareSampleRateForDevice(current_input_device) != 37 AudioManagerMac::HardwareSampleRateForDevice(current_output_device)) { 38 // TODO(crogers): with some extra work we can make aggregate devices work 39 // if the clock domain is the same but the sample-rate differ. 40 // For now we fallback to the synchronized path. 41 return kAudioDeviceUnknown; 42 } 43 44 // Use a lazily created aggregate device if it's already available 45 // and still appropriate. 46 if (aggregate_device_ != kAudioObjectUnknown) { 47 // TODO(crogers): handle default device changes for synchronized I/O. 48 // For now, we check to make sure the default devices haven't changed 49 // since we lazily created the aggregate device. 50 if (current_input_device == input_device_ && 51 current_output_device == output_device_) 52 return aggregate_device_; 53 54 // For now, once lazily created don't attempt to create another 55 // aggregate device. 56 return kAudioDeviceUnknown; 57 } 58 59 input_device_ = current_input_device; 60 output_device_ = current_output_device; 61 62 // Only create an aggregrate device if the clock domains match. 63 UInt32 input_clockdomain = GetClockDomain(input_device_); 64 UInt32 output_clockdomain = GetClockDomain(output_device_); 65 DVLOG(1) << "input_clockdomain: " << input_clockdomain; 66 DVLOG(1) << "output_clockdomain: " << output_clockdomain; 67 68 if (input_clockdomain == 0 || input_clockdomain != output_clockdomain) 69 return kAudioDeviceUnknown; 70 71 OSStatus result = CreateAggregateDevice( 72 input_device_, 73 output_device_, 74 &aggregate_device_); 75 if (result != noErr) 76 DestroyAggregateDevice(); 77 78 return aggregate_device_; 79 } 80 81 CFStringRef AggregateDeviceManager::GetDeviceUID(AudioDeviceID id) { 82 static const AudioObjectPropertyAddress kDeviceUIDAddress = { 83 kAudioDevicePropertyDeviceUID, 84 kAudioObjectPropertyScopeGlobal, 85 kAudioObjectPropertyElementMaster 86 }; 87 88 // As stated in the CoreAudio header (AudioHardwareBase.h), 89 // the caller is responsible for releasing the device_UID. 90 CFStringRef device_UID; 91 UInt32 size = sizeof(device_UID); 92 OSStatus result = AudioObjectGetPropertyData( 93 id, 94 &kDeviceUIDAddress, 95 0, 96 0, 97 &size, 98 &device_UID); 99 100 return (result == noErr) ? device_UID : NULL; 101 } 102 103 void AggregateDeviceManager::GetDeviceName( 104 AudioDeviceID id, char* name, UInt32 size) { 105 static const AudioObjectPropertyAddress kDeviceNameAddress = { 106 kAudioDevicePropertyDeviceName, 107 kAudioObjectPropertyScopeGlobal, 108 kAudioObjectPropertyElementMaster 109 }; 110 111 OSStatus result = AudioObjectGetPropertyData( 112 id, 113 &kDeviceNameAddress, 114 0, 115 0, 116 &size, 117 name); 118 119 if (result != noErr && size > 0) 120 name[0] = 0; 121 } 122 123 UInt32 AggregateDeviceManager::GetClockDomain(AudioDeviceID device_id) { 124 static const AudioObjectPropertyAddress kClockDomainAddress = { 125 kAudioDevicePropertyClockDomain, 126 kAudioObjectPropertyScopeGlobal, 127 kAudioObjectPropertyElementMaster 128 }; 129 130 UInt32 clockdomain = 0; 131 UInt32 size = sizeof(UInt32); 132 OSStatus result = AudioObjectGetPropertyData( 133 device_id, 134 &kClockDomainAddress, 135 0, 136 0, 137 &size, 138 &clockdomain); 139 140 return (result == noErr) ? clockdomain : 0; 141 } 142 143 OSStatus AggregateDeviceManager::GetPluginID(AudioObjectID* id) { 144 DCHECK(id); 145 146 // Get the audio hardware plugin. 147 CFStringRef bundle_name = CFSTR("com.apple.audio.CoreAudio"); 148 149 AudioValueTranslation plugin_translation; 150 plugin_translation.mInputData = &bundle_name; 151 plugin_translation.mInputDataSize = sizeof(bundle_name); 152 plugin_translation.mOutputData = id; 153 plugin_translation.mOutputDataSize = sizeof(*id); 154 155 static const AudioObjectPropertyAddress kPlugInAddress = { 156 kAudioHardwarePropertyPlugInForBundleID, 157 kAudioObjectPropertyScopeGlobal, 158 kAudioObjectPropertyElementMaster 159 }; 160 161 UInt32 size = sizeof(plugin_translation); 162 OSStatus result = AudioObjectGetPropertyData( 163 kAudioObjectSystemObject, 164 &kPlugInAddress, 165 0, 166 0, 167 &size, 168 &plugin_translation); 169 170 DVLOG(1) << "CoreAudio plugin ID: " << *id; 171 172 return result; 173 } 174 175 CFMutableDictionaryRef 176 AggregateDeviceManager::CreateAggregateDeviceDictionary( 177 AudioDeviceID input_id, 178 AudioDeviceID output_id) { 179 CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable( 180 NULL, 181 0, 182 &kCFTypeDictionaryKeyCallBacks, 183 &kCFTypeDictionaryValueCallBacks); 184 if (!aggregate_device_dict) 185 return NULL; 186 187 const CFStringRef kAggregateDeviceName = 188 CFSTR("ChromeAggregateAudioDevice"); 189 const CFStringRef kAggregateDeviceUID = 190 CFSTR("com.google.chrome.AggregateAudioDevice"); 191 192 // Add name and UID of the device to the dictionary. 193 CFDictionaryAddValue( 194 aggregate_device_dict, 195 CFSTR(kAudioAggregateDeviceNameKey), 196 kAggregateDeviceName); 197 CFDictionaryAddValue( 198 aggregate_device_dict, 199 CFSTR(kAudioAggregateDeviceUIDKey), 200 kAggregateDeviceUID); 201 202 // Add a "private aggregate key" to the dictionary. 203 // The 1 value means that the created aggregate device will 204 // only be accessible from the process that created it, and 205 // won't be visible to outside processes. 206 int value = 1; 207 ScopedCFTypeRef<CFNumberRef> aggregate_device_number(CFNumberCreate( 208 NULL, 209 kCFNumberIntType, 210 &value)); 211 CFDictionaryAddValue( 212 aggregate_device_dict, 213 CFSTR(kAudioAggregateDeviceIsPrivateKey), 214 aggregate_device_number); 215 216 return aggregate_device_dict; 217 } 218 219 CFMutableArrayRef 220 AggregateDeviceManager::CreateSubDeviceArray( 221 CFStringRef input_device_UID, CFStringRef output_device_UID) { 222 CFMutableArrayRef sub_devices_array = CFArrayCreateMutable( 223 NULL, 224 0, 225 &kCFTypeArrayCallBacks); 226 227 CFArrayAppendValue(sub_devices_array, input_device_UID); 228 CFArrayAppendValue(sub_devices_array, output_device_UID); 229 230 return sub_devices_array; 231 } 232 233 OSStatus AggregateDeviceManager::CreateAggregateDevice( 234 AudioDeviceID input_id, 235 AudioDeviceID output_id, 236 AudioDeviceID* aggregate_device) { 237 DCHECK(aggregate_device); 238 239 const size_t kMaxDeviceNameLength = 256; 240 241 scoped_ptr<char[]> input_device_name(new char[kMaxDeviceNameLength]); 242 GetDeviceName( 243 input_id, 244 input_device_name.get(), 245 sizeof(input_device_name)); 246 DVLOG(1) << "Input device: \n" << input_device_name; 247 248 scoped_ptr<char[]> output_device_name(new char[kMaxDeviceNameLength]); 249 GetDeviceName( 250 output_id, 251 output_device_name.get(), 252 sizeof(output_device_name)); 253 DVLOG(1) << "Output device: \n" << output_device_name; 254 255 OSStatus result = GetPluginID(&plugin_id_); 256 if (result != noErr) 257 return result; 258 259 // Create a dictionary for the aggregate device. 260 ScopedCFTypeRef<CFMutableDictionaryRef> aggregate_device_dict( 261 CreateAggregateDeviceDictionary(input_id, output_id)); 262 if (!aggregate_device_dict) 263 return -1; 264 265 // Create the aggregate device. 266 static const AudioObjectPropertyAddress kCreateAggregateDeviceAddress = { 267 kAudioPlugInCreateAggregateDevice, 268 kAudioObjectPropertyScopeGlobal, 269 kAudioObjectPropertyElementMaster 270 }; 271 272 UInt32 size = sizeof(*aggregate_device); 273 result = AudioObjectGetPropertyData( 274 plugin_id_, 275 &kCreateAggregateDeviceAddress, 276 sizeof(aggregate_device_dict), 277 &aggregate_device_dict, 278 &size, 279 aggregate_device); 280 if (result != noErr) { 281 DLOG(ERROR) << "Error creating aggregate audio device!"; 282 return result; 283 } 284 285 // Set the sub-devices for the aggregate device. 286 // In this case we use two: the input and output devices. 287 288 ScopedCFTypeRef<CFStringRef> input_device_UID(GetDeviceUID(input_id)); 289 ScopedCFTypeRef<CFStringRef> output_device_UID(GetDeviceUID(output_id)); 290 if (!input_device_UID || !output_device_UID) { 291 DLOG(ERROR) << "Error getting audio device UID strings."; 292 return -1; 293 } 294 295 ScopedCFTypeRef<CFMutableArrayRef> sub_devices_array( 296 CreateSubDeviceArray(input_device_UID, output_device_UID)); 297 if (sub_devices_array == NULL) { 298 DLOG(ERROR) << "Error creating sub-devices array."; 299 return -1; 300 } 301 302 static const AudioObjectPropertyAddress kSetSubDevicesAddress = { 303 kAudioAggregateDevicePropertyFullSubDeviceList, 304 kAudioObjectPropertyScopeGlobal, 305 kAudioObjectPropertyElementMaster 306 }; 307 308 size = sizeof(CFMutableArrayRef); 309 result = AudioObjectSetPropertyData( 310 *aggregate_device, 311 &kSetSubDevicesAddress, 312 0, 313 NULL, 314 size, 315 &sub_devices_array); 316 if (result != noErr) { 317 DLOG(ERROR) << "Error setting aggregate audio device sub-devices!"; 318 return result; 319 } 320 321 // Use the input device as the master device. 322 static const AudioObjectPropertyAddress kSetMasterDeviceAddress = { 323 kAudioAggregateDevicePropertyMasterSubDevice, 324 kAudioObjectPropertyScopeGlobal, 325 kAudioObjectPropertyElementMaster 326 }; 327 328 size = sizeof(CFStringRef); 329 result = AudioObjectSetPropertyData( 330 *aggregate_device, 331 &kSetMasterDeviceAddress, 332 0, 333 NULL, 334 size, 335 &input_device_UID); 336 if (result != noErr) { 337 DLOG(ERROR) << "Error setting aggregate audio device master device!"; 338 return result; 339 } 340 341 DVLOG(1) << "New aggregate device: " << *aggregate_device; 342 return noErr; 343 } 344 345 void AggregateDeviceManager::DestroyAggregateDevice() { 346 if (aggregate_device_ == kAudioObjectUnknown) 347 return; 348 349 static const AudioObjectPropertyAddress kDestroyAddress = { 350 kAudioPlugInDestroyAggregateDevice, 351 kAudioObjectPropertyScopeGlobal, 352 kAudioObjectPropertyElementMaster 353 }; 354 355 UInt32 size = sizeof(aggregate_device_); 356 OSStatus result = AudioObjectGetPropertyData( 357 plugin_id_, 358 &kDestroyAddress, 359 0, 360 NULL, 361 &size, 362 &aggregate_device_); 363 if (result != noErr) { 364 DLOG(ERROR) << "Error destroying aggregate audio device!"; 365 return; 366 } 367 368 aggregate_device_ = kAudioObjectUnknown; 369 } 370 371 } // namespace media 372