/* * Copyright (C) 2004-2008 Grame * Copyright (C) 2015-2019 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include void CoreAudioPCM::destroy_aggregate_device () { if (_aggregate_plugin_id == 0) { return; } OSStatus err; AudioObjectPropertyAddress property_address; property_address.mSelector = kAudioPlugInDestroyAggregateDevice; property_address.mScope = kAudioObjectPropertyScopeGlobal; property_address.mElement = kAudioObjectPropertyElementMaster; UInt32 outDataSize = 0; err = AudioObjectGetPropertyDataSize(_aggregate_plugin_id, &property_address, 0, NULL, &outDataSize); if (err != noErr) { fprintf(stderr, "DestroyAggregateDevice : AudioObjectGetPropertyDataSize error\n"); return; } err = AudioObjectGetPropertyData(_aggregate_plugin_id, &property_address, 0, NULL, &outDataSize, &_aggregate_device_id); if (err != noErr) { fprintf(stderr, "DestroyAggregateDevice : AudioObjectGetPropertyData error\n"); return; } #ifndef NDEBUG printf("DestroyAggregateDevice : OK (plugin: %u device:%u)\n", (unsigned int)_aggregate_plugin_id, (unsigned int)_aggregate_device_id); #endif } int CoreAudioPCM::create_aggregate_device ( AudioDeviceID input_device_id, AudioDeviceID output_device_id, uint32_t sample_rate, AudioDeviceID* created_device) { OSStatus err; AudioObjectID sub_device[32]; UInt32 size = sizeof(sub_device); /* look up sub-devices */ err = GetPropertyWrapper (input_device_id, 0, 0, kAudioAggregateDevicePropertyActiveSubDeviceList, &size, sub_device); std::vector input_device_ids; if (err != noErr) { input_device_ids.push_back(input_device_id); } else { uint32_t num_devices = size / sizeof(AudioObjectID); for (uint32_t i = 0; i < num_devices; ++i) { input_device_ids.push_back(sub_device[i]); } } size = sizeof(sub_device); err = GetPropertyWrapper (output_device_id, 0, 0, kAudioAggregateDevicePropertyActiveSubDeviceList, &size, sub_device); std::vector output_device_ids; if (err != noErr) { output_device_ids.push_back(output_device_id); } else { uint32_t num_devices = size / sizeof(AudioObjectID); for (uint32_t i = 0; i < num_devices; ++i) { output_device_ids.push_back(sub_device[i]); } } //--------------------------------------------------------------------------- // Setup SR of both devices otherwise creating AD may fail... //--------------------------------------------------------------------------- UInt32 keptclockdomain = 0; UInt32 clockdomain = 0; size = sizeof(UInt32); bool need_clock_drift_compensation = false; for (size_t i = 0; i < input_device_ids.size(); ++i) { set_device_sample_rate_id(input_device_ids[i], sample_rate, true); // Check clock domain err = GetPropertyWrapper (input_device_ids[i], 0, 0, kAudioDevicePropertyClockDomain, &size, &clockdomain); if (err == noErr) { keptclockdomain = (keptclockdomain == 0) ? clockdomain : keptclockdomain; if (clockdomain != 0 && clockdomain != keptclockdomain) { #ifndef NDEBUG printf("AggregateDevice: devices do not share the same clock.\n"); #endif need_clock_drift_compensation = true; } } } for (UInt32 i = 0; i < output_device_ids.size(); i++) { set_device_sample_rate_id(output_device_ids[i], sample_rate, true); // Check clock domain err = GetPropertyWrapper (output_device_ids[i], 0, 0, kAudioDevicePropertyClockDomain, &size, &clockdomain); if (err == noErr) { keptclockdomain = (keptclockdomain == 0) ? clockdomain : keptclockdomain; if (clockdomain != 0 && clockdomain != keptclockdomain) { #ifndef NDEBUG printf("AggregateDevice: devices do not share the same clock.\n"); #endif need_clock_drift_compensation = true; } } } // If no valid clock domain was found, then assume we have to compensate... if (keptclockdomain == 0) { need_clock_drift_compensation = true; } //--------------------------------------------------------------------------- // Start to create a new aggregate by getting the base audio hardware plugin //--------------------------------------------------------------------------- #ifndef NDEBUG char device_name[256]; for (size_t i = 0; i < input_device_ids.size(); ++i) { GetDeviceNameFromID(input_device_ids[i], device_name); printf("Separated input = '%s'\n", device_name); } for (size_t i = 0; i < output_device_ids.size(); ++i) { GetDeviceNameFromID(output_device_ids[i], device_name); printf("Separated output = '%s'\n", device_name); } #endif err = GetHardwarePropertyInfoWrapper (kAudioHardwarePropertyPlugInForBundleID, &size); if (err != noErr) { fprintf(stderr, "AggregateDevice: AudioHardwareGetPropertyInfo kAudioHardwarePropertyPlugInForBundleID error\n"); return -1; } AudioValueTranslation pluginAVT; CFStringRef inBundleRef = CFSTR("com.apple.audio.CoreAudio"); pluginAVT.mInputData = &inBundleRef; pluginAVT.mInputDataSize = sizeof(inBundleRef); pluginAVT.mOutputData = &_aggregate_plugin_id; pluginAVT.mOutputDataSize = sizeof(AudioDeviceID); err = GetHardwarePropertyWrapper (kAudioHardwarePropertyPlugInForBundleID, &size, &pluginAVT); if (err != noErr) { fprintf(stderr, "AggregateDevice: AudioHardwareGetProperty kAudioHardwarePropertyPlugInForBundleID error\n"); return -1; } //------------------------------------------------- // Create a CFDictionary for our aggregate device //------------------------------------------------- CFMutableDictionaryRef aggDeviceDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFStringRef AggregateDeviceNameRef = CFSTR("ArdourDuplex"); CFStringRef AggregateDeviceUIDRef = CFSTR("com.ardour.CoreAudio"); CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceNameKey), AggregateDeviceNameRef); CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceUIDKey), AggregateDeviceUIDRef); // hide from list int value = 1; CFNumberRef AggregateDeviceNumberRef = CFNumberCreate(NULL, kCFNumberIntType, &value); CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceIsPrivateKey), AggregateDeviceNumberRef); //------------------------------------------------- // Create a CFMutableArray for our sub-device list //------------------------------------------------- // we need to append the UID for each device to a CFMutableArray, so create one here CFMutableArrayRef subDevicesArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); std::vector captureDeviceUID; for (UInt32 i = 0; i < input_device_ids.size(); i++) { CFStringRef ref = GetDeviceName(input_device_ids[i]); if (ref == NULL) { return -1; } captureDeviceUID.push_back(ref); CFArrayAppendValue(subDevicesArray, ref); } std::vector playbackDeviceUID; for (UInt32 i = 0; i < output_device_ids.size(); i++) { CFStringRef ref = GetDeviceName(output_device_ids[i]); if (ref == NULL) { return -1; } playbackDeviceUID.push_back(ref); CFArrayAppendValue(subDevicesArray, ref); } //----------------------------------------------------------------------- // Feed the dictionary to the plugin, to create a blank aggregate device //----------------------------------------------------------------------- AudioObjectPropertyAddress pluginAOPA; pluginAOPA.mSelector = kAudioPlugInCreateAggregateDevice; pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal; pluginAOPA.mElement = kAudioObjectPropertyElementMaster; UInt32 outDataSize = 0; err = AudioObjectGetPropertyDataSize(_aggregate_plugin_id, &pluginAOPA, 0, NULL, &outDataSize); if (err != noErr) { char *rv = (char*)&err; fprintf(stderr, "AggregateDevice: AudioObjectGetPropertyDataSize error '%c%c%c%c' 0x%08x\n", rv[0], rv[1], rv[2], rv[3], err); goto error; } err = AudioObjectGetPropertyData(_aggregate_plugin_id, &pluginAOPA, sizeof(aggDeviceDict), &aggDeviceDict, &outDataSize, created_device); if (err != noErr) { char *rv = (char*)&err; fprintf(stderr, "AggregateDevice: AudioObjectGetPropertyData error '%c%c%c%c' 0x%08x\n", rv[0], rv[1], rv[2], rv[3], err); goto error; } // pause for a bit to make sure that everything completed correctly // this is to work around a bug in the HAL where a new aggregate device seems to disappear briefly after it is created CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false); //------------------------- // Set the sub-device list //------------------------- pluginAOPA.mSelector = kAudioAggregateDevicePropertyFullSubDeviceList; pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal; pluginAOPA.mElement = kAudioObjectPropertyElementMaster; outDataSize = sizeof(CFMutableArrayRef); err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &subDevicesArray); if (err != noErr) { fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for sub-device list error\n"); goto error; } // pause again to give the changes time to take effect CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false); //----------------------- // Set the master device //----------------------- // set the master device manually (this is the device which will act as the master clock for the aggregate device) // pass in the UID of the device you want to use pluginAOPA.mSelector = kAudioAggregateDevicePropertyMasterSubDevice; pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal; pluginAOPA.mElement = kAudioObjectPropertyElementMaster; outDataSize = sizeof(CFStringRef); err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &playbackDeviceUID[0]); if (err != noErr) { fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for playback-master device error\n"); // try playback err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &captureDeviceUID[0]); } if (err != noErr) { fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for capture-master device error\n"); goto error; } // pause again to give the changes time to take effect CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false); // Prepare sub-devices for clock drift compensation // Workaround for bug in the HAL : until 10.6.2 if (need_clock_drift_compensation) { AudioObjectPropertyAddress theAddressOwned = { kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; AudioObjectPropertyAddress theAddressDrift = { kAudioSubDevicePropertyDriftCompensation, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; UInt32 theQualifierDataSize = sizeof(AudioObjectID); AudioClassID inClass = kAudioSubDeviceClassID; void* theQualifierData = &inClass; UInt32 subDevicesNum = 0; #ifndef NDEBUG printf("Clock drift compensation activated...\n"); #endif // Get the property data size err = AudioObjectGetPropertyDataSize(*created_device, &theAddressOwned, theQualifierDataSize, theQualifierData, &size); if (err != noErr) { fprintf(stderr, "AggregateDevice: kAudioObjectPropertyOwnedObjects error\n"); } // Calculate the number of object IDs subDevicesNum = size / sizeof(AudioObjectID); #ifndef NDEBUG printf("AggregateDevice: clock drift compensation, number of sub-devices = %u\n", (unsigned int)subDevicesNum); #endif AudioObjectID subDevices[subDevicesNum]; size = sizeof(subDevices); err = AudioObjectGetPropertyData(*created_device, &theAddressOwned, theQualifierDataSize, theQualifierData, &size, subDevices); if (err != noErr) { fprintf(stderr, "AggregateDevice: kAudioObjectPropertyOwnedObjects error\n"); } // Set kAudioSubDevicePropertyDriftCompensation property... for (UInt32 index = 0; index < subDevicesNum; ++index) { UInt32 theDriftCompensationValue = 1; err = AudioObjectSetPropertyData(subDevices[index], &theAddressDrift, 0, NULL, sizeof(UInt32), &theDriftCompensationValue); if (err != noErr) { fprintf(stderr, "AggregateDevice: kAudioSubDevicePropertyDriftCompensation error\n"); } } } // pause again to give the changes time to take effect CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false); //---------- // Clean up //---------- // release the private AD key CFRelease(AggregateDeviceNumberRef); // release the CF objects we have created - we don't need them any more CFRelease(aggDeviceDict); CFRelease(subDevicesArray); // release the device UID for (size_t i = 0; i < captureDeviceUID.size(); ++i) { CFRelease(captureDeviceUID[i]); } for (size_t i = 0; i < playbackDeviceUID.size(); ++i) { CFRelease(playbackDeviceUID[i]); } #ifndef NDEBUG printf("AggregateDevice: new aggregate device %u\n", (unsigned int)*created_device); #endif return 0; error: destroy_aggregate_device(); return -1; }