Home | History | Annotate | Download | only in mac
      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(&current_input_device);
     34   AudioManagerMac::GetDefaultOutputDevice(&current_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