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