Hard Core Audio
[ardour.git] / libs / backends / coreaudio / coreaudio_pcmio_aggregate.cc
1 /*
2  * Copyright (C) 2015 Robin Gareus <robin@gareus.org>
3  * Copyright (C) 2004-2008 Grame
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19
20 #include <vector>
21
22 void
23 CoreAudioPCM::destroy_aggregate_device ()
24 {
25         if (_aggregate_plugin_id == 0) {
26                 return;
27         }
28
29         OSStatus err;
30
31         AudioObjectPropertyAddress property_address;
32         property_address.mSelector = kAudioPlugInDestroyAggregateDevice;
33         property_address.mScope = kAudioObjectPropertyScopeGlobal;
34         property_address.mElement = kAudioObjectPropertyElementMaster;
35         UInt32 outDataSize;
36
37         err = AudioObjectGetPropertyDataSize(_aggregate_plugin_id, &property_address, 0, NULL, &outDataSize);
38         if (err != noErr) {
39                 fprintf(stderr, "DestroyAggregateDevice : AudioObjectGetPropertyDataSize error\n");
40                 return;
41         }
42
43         err = AudioObjectGetPropertyData(_aggregate_plugin_id, &property_address, 0, NULL, &outDataSize, &_aggregate_device_id);
44         if (err != noErr) {
45                 fprintf(stderr, "DestroyAggregateDevice : AudioObjectGetPropertyData error\n");
46                 return;
47         }
48 #ifndef NDEBUG
49         printf("DestroyAggregateDevice : OK (plugin: %u device:%u)\n", _aggregate_plugin_id, _aggregate_device_id);
50 #endif
51 }
52
53 int
54 CoreAudioPCM::create_aggregate_device (
55                 AudioDeviceID input_device_id,
56                 AudioDeviceID output_device_id,
57                 uint32_t sample_rate,
58                 AudioDeviceID* created_device)
59 {
60         OSStatus err;
61         AudioObjectID sub_device[32];
62         UInt32 size = sizeof(sub_device);
63
64         /* look up sub-devices */
65         err = GetPropertyWrapper (input_device_id, 0, 0, kAudioAggregateDevicePropertyActiveSubDeviceList, &size, sub_device);
66         std::vector<AudioDeviceID> input_device_ids;
67
68         if (err != noErr) {
69                 input_device_ids.push_back(input_device_id);
70         } else {
71                 uint32_t num_devices = size / sizeof(AudioObjectID);
72                 for (uint32_t i = 0; i < num_devices; ++i) {
73                         input_device_ids.push_back(sub_device[i]);
74                 }
75         }
76
77         size = sizeof(sub_device);
78         err = GetPropertyWrapper (output_device_id, 0, 0, kAudioAggregateDevicePropertyActiveSubDeviceList, &size, sub_device);
79         std::vector<AudioDeviceID> output_device_ids;
80
81         if (err != noErr) {
82                 output_device_ids.push_back(output_device_id);
83         } else {
84                 uint32_t num_devices = size / sizeof(AudioObjectID);
85                 for (uint32_t i = 0; i < num_devices; ++i) {
86                         output_device_ids.push_back(sub_device[i]);
87                 }
88         }
89
90         //---------------------------------------------------------------------------
91         // Setup SR of both devices otherwise creating AD may fail...
92         //---------------------------------------------------------------------------
93         UInt32 keptclockdomain = 0;
94         UInt32 clockdomain = 0;
95         size = sizeof(UInt32);
96         bool need_clock_drift_compensation = false;
97
98         for (size_t i = 0; i < input_device_ids.size(); ++i) {
99                 set_device_sample_rate_id(input_device_ids[i], sample_rate, true);
100
101                 // Check clock domain
102                 err = GetPropertyWrapper (input_device_ids[i], 0, 0, kAudioDevicePropertyClockDomain, &size, &clockdomain);
103                 if (err == noErr) {
104                         keptclockdomain = (keptclockdomain == 0) ? clockdomain : keptclockdomain;
105                         if (clockdomain != 0 && clockdomain != keptclockdomain) {
106 #ifndef NDEBUG
107                                 printf("AggregateDevice: devices do not share the same clock.\n");
108 #endif
109                                 need_clock_drift_compensation = true;
110                         }
111                 }
112         }
113
114         for (UInt32 i = 0; i < output_device_ids.size(); i++) {
115                 set_device_sample_rate_id(output_device_ids[i], sample_rate, true);
116
117                 // Check clock domain
118                 err = GetPropertyWrapper (output_device_ids[i], 0, 0, kAudioDevicePropertyClockDomain, &size, &clockdomain);
119                 if (err == noErr) {
120                         keptclockdomain = (keptclockdomain == 0) ? clockdomain : keptclockdomain;
121                         if (clockdomain != 0 && clockdomain != keptclockdomain) {
122 #ifndef NDEBUG
123                                 printf("AggregateDevice: devices do not share the same clock.\n");
124 #endif
125                                 need_clock_drift_compensation = true;
126                         }
127                 }
128         }
129
130         // If no valid clock domain was found, then assume we have to compensate...
131         if (keptclockdomain == 0) {
132                 need_clock_drift_compensation = true;
133         }
134
135         //---------------------------------------------------------------------------
136         // Start to create a new aggregate by getting the base audio hardware plugin
137         //---------------------------------------------------------------------------
138
139 #ifndef NDEBUG
140         char device_name[256];
141         for (size_t i = 0; i < input_device_ids.size(); ++i) {
142                 GetDeviceNameFromID(input_device_ids[i], device_name);
143                 printf("Separated input = '%s'\n", device_name);
144         }
145
146         for (size_t i = 0; i < output_device_ids.size(); ++i) {
147                 GetDeviceNameFromID(output_device_ids[i], device_name);
148                 printf("Separated output = '%s'\n", device_name);
149         }
150 #endif
151
152         err = GetHardwarePropertyInfoWrapper (kAudioHardwarePropertyPlugInForBundleID, &size);
153         if (err != noErr) {
154                 fprintf(stderr, "AggregateDevice: AudioHardwareGetPropertyInfo kAudioHardwarePropertyPlugInForBundleID error\n");
155                 return -1;
156         }
157
158         AudioValueTranslation pluginAVT;
159
160         CFStringRef inBundleRef = CFSTR("com.apple.audio.CoreAudio");
161
162         pluginAVT.mInputData = &inBundleRef;
163         pluginAVT.mInputDataSize = sizeof(inBundleRef);
164         pluginAVT.mOutputData = &_aggregate_plugin_id;
165         pluginAVT.mOutputDataSize = sizeof(AudioDeviceID);
166
167         err = GetHardwarePropertyWrapper (kAudioHardwarePropertyPlugInForBundleID, &size, &pluginAVT);
168         if (err != noErr) {
169                 fprintf(stderr, "AggregateDevice: AudioHardwareGetProperty kAudioHardwarePropertyPlugInForBundleID error\n");
170                 return -1;
171         }
172
173         //-------------------------------------------------
174         // Create a CFDictionary for our aggregate device
175         //-------------------------------------------------
176
177         CFMutableDictionaryRef aggDeviceDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
178
179         CFStringRef AggregateDeviceNameRef = CFSTR("ArdourDuplex");
180         CFStringRef AggregateDeviceUIDRef = CFSTR("com.ardour.CoreAudio");
181         CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceNameKey), AggregateDeviceNameRef);
182         CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceUIDKey), AggregateDeviceUIDRef);
183
184         // hide from list
185         int value = 1;
186         CFNumberRef AggregateDeviceNumberRef = CFNumberCreate(NULL, kCFNumberIntType, &value);
187         CFDictionaryAddValue(aggDeviceDict, CFSTR(kAudioAggregateDeviceIsPrivateKey), AggregateDeviceNumberRef);
188
189         //-------------------------------------------------
190         // Create a CFMutableArray for our sub-device list
191         //-------------------------------------------------
192
193         // we need to append the UID for each device to a CFMutableArray, so create one here
194         CFMutableArrayRef subDevicesArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
195
196         std::vector<CFStringRef> captureDeviceUID;
197         for (UInt32 i = 0; i < input_device_ids.size(); i++) {
198                 CFStringRef ref = GetDeviceName(input_device_ids[i]);
199                 if (ref == NULL) {
200                         return -1;
201                 }
202                 captureDeviceUID.push_back(ref);
203                 CFArrayAppendValue(subDevicesArray, ref);
204         }
205
206         std::vector<CFStringRef> playbackDeviceUID;
207         for (UInt32 i = 0; i < output_device_ids.size(); i++) {
208                 CFStringRef ref = GetDeviceName(output_device_ids[i]);
209                 if (ref == NULL) {
210                         return -1;
211                 }
212                 playbackDeviceUID.push_back(ref);
213                 CFArrayAppendValue(subDevicesArray, ref);
214         }
215
216         //-----------------------------------------------------------------------
217         // Feed the dictionary to the plugin, to create a blank aggregate device
218         //-----------------------------------------------------------------------
219
220         AudioObjectPropertyAddress pluginAOPA;
221         pluginAOPA.mSelector = kAudioPlugInCreateAggregateDevice;
222         pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal;
223         pluginAOPA.mElement = kAudioObjectPropertyElementMaster;
224         UInt32 outDataSize;
225
226         err = AudioObjectGetPropertyDataSize(_aggregate_plugin_id, &pluginAOPA, 0, NULL, &outDataSize);
227         if (err != noErr) {
228                 fprintf(stderr, "AggregateDevice: AudioObjectGetPropertyDataSize error\n");
229                 goto error;
230         }
231
232         err = AudioObjectGetPropertyData(_aggregate_plugin_id, &pluginAOPA, sizeof(aggDeviceDict), &aggDeviceDict, &outDataSize, created_device);
233         if (err != noErr) {
234                 fprintf(stderr, "AggregateDevice: AudioObjectGetPropertyData error\n");
235                 goto error;
236         }
237
238         // pause for a bit to make sure that everything completed correctly
239         // this is to work around a bug in the HAL where a new aggregate device seems to disappear briefly after it is created
240         CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
241
242         //-------------------------
243         // Set the sub-device list
244         //-------------------------
245
246         pluginAOPA.mSelector = kAudioAggregateDevicePropertyFullSubDeviceList;
247         pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal;
248         pluginAOPA.mElement = kAudioObjectPropertyElementMaster;
249         outDataSize = sizeof(CFMutableArrayRef);
250         err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &subDevicesArray);
251         if (err != noErr) {
252                 fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for sub-device list error\n");
253                 goto error;
254         }
255
256         // pause again to give the changes time to take effect
257         CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
258
259         //-----------------------
260         // Set the master device
261         //-----------------------
262
263         // set the master device manually (this is the device which will act as the master clock for the aggregate device)
264         // pass in the UID of the device you want to use
265         pluginAOPA.mSelector = kAudioAggregateDevicePropertyMasterSubDevice;
266         pluginAOPA.mScope = kAudioObjectPropertyScopeGlobal;
267         pluginAOPA.mElement = kAudioObjectPropertyElementMaster;
268         outDataSize = sizeof(CFStringRef);
269         err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &captureDeviceUID[0]);
270         if (err != noErr) {
271                 fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for playback-master device error\n");
272                 // try playback
273                 err = AudioObjectSetPropertyData(*created_device, &pluginAOPA, 0, NULL, outDataSize, &playbackDeviceUID[0]);
274         }
275         if (err != noErr) {
276                 fprintf(stderr, "AggregateDevice: AudioObjectSetPropertyData for capture-master device error\n");
277                 goto error;
278         }
279
280         // pause again to give the changes time to take effect
281         CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
282
283         // Prepare sub-devices for clock drift compensation
284         // Workaround for bug in the HAL : until 10.6.2
285         if (need_clock_drift_compensation) {
286
287                 AudioObjectPropertyAddress theAddressOwned = { kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
288                 AudioObjectPropertyAddress theAddressDrift = { kAudioSubDevicePropertyDriftCompensation, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
289                 UInt32 theQualifierDataSize = sizeof(AudioObjectID);
290                 AudioClassID inClass = kAudioSubDeviceClassID;
291                 void* theQualifierData = &inClass;
292                 UInt32 subDevicesNum = 0;
293
294 #ifndef NDEBUG
295                 printf("Clock drift compensation activated...\n");
296 #endif
297
298                 // Get the property data size
299                 err = AudioObjectGetPropertyDataSize(*created_device, &theAddressOwned, theQualifierDataSize, theQualifierData, &size);
300                 if (err != noErr) {
301                         fprintf(stderr, "AggregateDevice: kAudioObjectPropertyOwnedObjects error\n");
302                 }
303
304                 //      Calculate the number of object IDs
305                 subDevicesNum = size / sizeof(AudioObjectID);
306 #ifndef NDEBUG
307                 printf("AggregateDevice: clock drift compensation, number of sub-devices = %d\n", subDevicesNum);
308 #endif
309                 AudioObjectID subDevices[subDevicesNum];
310                 size = sizeof(subDevices);
311
312                 err = AudioObjectGetPropertyData(*created_device, &theAddressOwned, theQualifierDataSize, theQualifierData, &size, subDevices);
313                 if (err != noErr) {
314                         fprintf(stderr, "AggregateDevice: kAudioObjectPropertyOwnedObjects error\n");
315                 }
316
317                 // Set kAudioSubDevicePropertyDriftCompensation property...
318                 for (UInt32 index = 0; index < subDevicesNum; ++index) {
319                         UInt32 theDriftCompensationValue = 1;
320                         err = AudioObjectSetPropertyData(subDevices[index], &theAddressDrift, 0, NULL, sizeof(UInt32), &theDriftCompensationValue);
321                         if (err != noErr) {
322                                 fprintf(stderr, "AggregateDevice: kAudioSubDevicePropertyDriftCompensation error\n");
323                         }
324                 }
325         }
326
327         // pause again to give the changes time to take effect
328         CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
329
330         //----------
331         // Clean up
332         //----------
333
334         // release the private AD key
335         CFRelease(AggregateDeviceNumberRef);
336
337         // release the CF objects we have created - we don't need them any more
338         CFRelease(aggDeviceDict);
339         CFRelease(subDevicesArray);
340
341         // release the device UID
342         for (size_t i = 0; i < captureDeviceUID.size(); ++i) {
343                 CFRelease(captureDeviceUID[i]);
344         }
345
346         for (size_t i = 0; i < playbackDeviceUID.size(); ++i) {
347                 CFRelease(playbackDeviceUID[i]);
348         }
349
350 #ifndef NDEBUG
351         printf("AggregateDevice: new aggregate device %u\n", *created_device);
352 #endif
353         return 0;
354
355 error:
356         destroy_aggregate_device();
357         return -1;
358 }