get started on coreaudio/midi backend
[ardour.git] / libs / backends / coreaudio / coreaudio_pcmio.cc
1 /*
2  * Copyright (C) 2015 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include "coreaudio_pcmio.h"
20 #include <string>
21
22
23 static OSStatus hardwarePropertyChangeCallback (AudioHardwarePropertyID inPropertyID, void* arg) {
24         if (inPropertyID == kAudioHardwarePropertyDevices) {
25                 CoreAudioPCM * self = static_cast<CoreAudioPCM*>(arg);
26                 self->hwPropertyChange();
27         }
28         return noErr;
29 }
30
31 CoreAudioPCM::CoreAudioPCM ()
32         : _auhal (0)
33         , _deviceIDs (0)
34         , _inputAudioBufferList (0)
35         , _state (-1)
36         , _capture_channels (0)
37         , _playback_channels (0)
38         , _in_process (false)
39         , _numDevices (0)
40         , _process_callback (0)
41         , _error_callback (0)
42         , _device_ins (0)
43         , _device_outs (0)
44 {
45 #ifdef COREAUDIO_108 // TODO
46         CFRunLoopRef theRunLoop = NULL;
47         AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, kAudioHardwarePropertyDevices };
48         AudioObjectSetPropertyData (kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
49 #endif
50         AudioHardwareAddPropertyListener (kAudioHardwarePropertyDevices, hardwarePropertyChangeCallback, this);
51 }
52
53 CoreAudioPCM::~CoreAudioPCM ()
54 {
55         if (_state == 0) {
56                 pcm_stop();
57         }
58         delete _deviceIDs;
59         free(_device_ins);
60         free(_device_outs);
61         AudioHardwareRemovePropertyListener(kAudioHardwarePropertyDevices, hardwarePropertyChangeCallback);
62         free(_inputAudioBufferList);
63 }
64
65
66 void
67 CoreAudioPCM::hwPropertyChange() {
68         printf("hardwarePropertyChangeCallback\n");
69         discover();
70 }
71
72 void
73 CoreAudioPCM::discover() {
74         OSStatus err;
75         UInt32 propSize = 0;
76
77         // TODO trymutex lock.
78
79         if (_deviceIDs) {
80                 delete _deviceIDs; _deviceIDs = 0;
81                 free(_device_ins); _device_ins = 0;
82                 free(_device_outs); _device_outs = 0;
83         }
84         _devices.clear();
85
86 #ifdef COREAUDIO_108
87         AudioObjectPropertyAddress propertyAddress;
88         propertyAddress.mSelector = kAudioHardwarePropertyDevices;
89         propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
90         propertyAddress.mElement = kAudioObjectPropertyElementMaster;
91         err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propSize);
92 #else
93         err = AudioHardwareGetPropertyInfo (kAudioHardwarePropertyDevices, &propSize, NULL);
94 #endif
95
96         _numDevices = propSize / sizeof (AudioDeviceID);
97         propSize = _numDevices * sizeof (AudioDeviceID);
98
99         _deviceIDs = new AudioDeviceID[_numDevices];
100         _device_ins = (uint32_t*) calloc(_numDevices, sizeof(uint32_t));
101         _device_outs = (uint32_t*) calloc(_numDevices, sizeof(uint32_t));
102
103 #ifdef COREAUDIO_108
104         propertyAddress.mSelector = kAudioHardwarePropertyDevices;
105         err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propSize, _deviceIDs);
106 #else
107         err = AudioHardwareGetProperty (kAudioHardwarePropertyDevices, &propSize, _deviceIDs);
108 #endif
109
110         for (size_t deviceIndex = 0; deviceIndex < _numDevices; deviceIndex++) {
111                 propSize = 64;
112                 char deviceName[64];
113 #ifdef COREAUDIO_108
114                 propertyAddress.mSelector = kAudioDevicePropertyDeviceName;
115                 propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
116                 err = AudioObjectGetPropertyData(_deviceIDs[deviceIndex], &propertyAddress, 0, NULL, &propSize, deviceName);
117 #else
118                 err = AudioDeviceGetProperty(_deviceIDs[deviceIndex], 0, 0, kAudioDevicePropertyDeviceName, &propSize, deviceName);
119 #endif
120
121                 if (kAudioHardwareNoError != err) {
122                         fprintf(stderr, "device name query failed: %i\n", err);
123                         continue;
124                 }
125
126                 UInt32 size;
127                 UInt32 outputChannelCount = 0;
128                 UInt32 inputChannelCount = 0;
129                 AudioBufferList *bufferList = NULL;
130
131                 /* query number of inputs */
132 #ifdef COREAUDIO_108
133                 size = 0;
134                 propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
135                 propertyAddress.mScope = kAudioDevicePropertyScopeOutput;
136                 err = AudioObjectGetPropertyDataSize(_deviceIDs[deviceIndex], &propertyAddress, 0, NULL, &size);
137                 if (kAudioHardwareNoError != err) {
138                         fprintf(stderr, "kAudioDevicePropertyStreamConfiguration failed: %i\n", err);
139                         continue;
140                 }
141
142                 bufferList = (AudioBufferList *)(malloc(size));
143                 assert(bufferList);
144                 if (!bufferList) { fprintf(stderr, "OUT OF MEMORY\n"); break; }
145
146                 err = AudioObjectGetPropertyData(_deviceIDs[deviceIndex], &propertyAddress, 0, NULL, &size, bufferList);
147
148 #else
149                 err = AudioDeviceGetPropertyInfo (_deviceIDs[deviceIndex], 0, AUHAL_OUTPUT_ELEMENT, kAudioDevicePropertyStreamConfiguration, &propSize, NULL);
150                 if (kAudioHardwareNoError != err) {
151                         fprintf(stderr, "kAudioDevicePropertyStreamConfiguration failed: %i\n", err);
152                         continue;
153                 }
154                 bufferList = (AudioBufferList *)(malloc(size));
155                 assert(bufferList);
156                 if (!bufferList) { fprintf(stderr, "OUT OF MEMORY\n"); break; }
157
158                 bufferList->mNumberBuffers = 0;
159                 err = AudioDeviceGetProperty(_deviceIDs[deviceIndex], 0, AUHAL_OUTPUT_ELEMENT, kAudioDevicePropertyStreamConfiguration, &size, bufferList);
160 #endif
161                 if(kAudioHardwareNoError != err) {
162                         fprintf(stderr, "kAudioDevicePropertyStreamConfiguration failed: %i\n", err);
163                         free(bufferList);
164                         continue;
165                 }
166
167                 for(UInt32 j = 0; j < bufferList->mNumberBuffers; ++j) {
168                         outputChannelCount += bufferList->mBuffers[j].mNumberChannels;
169                 }
170                 free(bufferList);
171
172
173                 /* query number of inputs */
174 #ifdef COREAUDIO_108
175                 size = 0;
176                 propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
177                 propertyAddress.mScope = kAudioDevicePropertyScopeInput;
178                 err = AudioObjectGetPropertyDataSize(_deviceIDs[deviceIndex], &propertyAddress, 0, NULL, &size);
179                 if (kAudioHardwareNoError != err) {
180                         fprintf(stderr, "kAudioDevicePropertyStreamConfiguration failed: %i\n", err);
181                         continue;
182                 }
183
184                 bufferList = (AudioBufferList *)(malloc(size));
185                 assert(bufferList);
186                 if (!bufferList) { fprintf(stderr, "OUT OF MEMORY\n"); break; }
187
188                 err = AudioObjectGetPropertyData(_deviceIDs[deviceIndex], &propertyAddress, 0, NULL, &size, bufferList);
189 #else
190                 err = AudioDeviceGetPropertyInfo (_deviceIDs[deviceIndex], 0, AUHAL_INPUT_ELEMENT, kAudioDevicePropertyStreamConfiguration, &propSize, NULL);
191                 if (kAudioHardwareNoError != err) {
192                         fprintf(stderr, "kAudioDevicePropertyStreamConfiguration failed: %i\n", err);
193                         continue;
194                 }
195                 bufferList = (AudioBufferList *)(malloc(size));
196                 assert(bufferList);
197                 if (!bufferList) { fprintf(stderr, "OUT OF MEMORY\n"); break; }
198
199                 bufferList->mNumberBuffers = 0;
200                 err = AudioDeviceGetProperty(_deviceIDs[deviceIndex], 0, AUHAL_INPUT_ELEMENT, kAudioDevicePropertyStreamConfiguration, &size, bufferList);
201 #endif
202                 if(kAudioHardwareNoError != err) {
203                         fprintf(stderr, "kAudioDevicePropertyStreamConfiguration failed: %i\n", err);
204                         free(bufferList);
205                         continue;
206                 }
207
208                 for(UInt32 j = 0; j < bufferList->mNumberBuffers; ++j) {
209                         inputChannelCount += bufferList->mBuffers[j].mNumberChannels;
210                 }
211                 free(bufferList);
212
213
214
215                 {
216                         std::string dn = deviceName;
217                         _device_ins[deviceIndex] = inputChannelCount;
218                         _device_outs[deviceIndex] = outputChannelCount;
219                         printf("CoreAudio Device: #%ld '%s' in:%d out:%d\n", deviceIndex, deviceName, inputChannelCount, outputChannelCount);
220                         if (outputChannelCount > 0 && inputChannelCount > 0) {
221                                 _devices.insert (std::pair<size_t, std::string> (deviceIndex, dn));
222                         }
223                 }
224         }
225 }
226
227 void
228 CoreAudioPCM::pcm_stop ()
229 {
230         printf("CoreAudioPCM::pcm_stop\n");
231         if (!_auhal) return;
232
233         AudioOutputUnitStop(_auhal);
234         AudioUnitUninitialize(_auhal);
235 #ifdef COREAUDIO_108
236         AudioComponentInstanceDispose(_auhal);
237 #else
238         CloseComponent(_auhal);
239 #endif
240         _auhal = 0;
241         _state = -1;
242         _capture_channels = 0;
243         _playback_channels = 0;
244
245         free(_inputAudioBufferList);
246         _inputAudioBufferList = 0;
247
248         _error_callback = 0;
249         _process_callback = 0;
250 }
251
252 #ifndef NDEBUG
253 static void PrintStreamDesc (AudioStreamBasicDescription *inDesc)
254 {
255     printf ("- - - - - - - - - - - - - - - - - - - -\n");
256     printf ("  Sample Rate:%f", inDesc->mSampleRate);
257     printf ("  Format ID:%.*s\n", (int)sizeof(inDesc->mFormatID), (char*)&inDesc->mFormatID);
258     printf ("  Format Flags:%X\n", inDesc->mFormatFlags);
259     printf ("  Bytes per Packet:%d\n", inDesc->mBytesPerPacket);
260     printf ("  Frames per Packet:%d\n", inDesc->mFramesPerPacket);
261     printf ("  Bytes per Frame:%d\n", inDesc->mBytesPerFrame);
262     printf ("  Channels per Frame:%d\n", inDesc->mChannelsPerFrame);
263     printf ("  Bits per Channel:%d\n", inDesc->mBitsPerChannel);
264     printf ("- - - - - - - - - - - - - - - - - - - -\n");
265 }
266 #endif
267
268 static OSStatus render_callback_ptr (
269                 void* inRefCon,
270                 AudioUnitRenderActionFlags* ioActionFlags,
271                 const AudioTimeStamp* inTimeStamp,
272                 UInt32 inBusNumber,
273                 UInt32 inNumberFrames,
274                 AudioBufferList* ioData)
275 {
276         CoreAudioPCM * d = static_cast<CoreAudioPCM*> (inRefCon);
277         return d->render_callback(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData);
278 }
279
280
281
282 int
283 CoreAudioPCM::pcm_start (
284                 uint32_t device_id_in, uint32_t device_id_out,
285                 uint32_t sample_rate, uint32_t samples_per_period,
286                 int (process_callback (void*)), void *process_arg)
287 {
288
289         assert(_deviceIDs);
290         _state = -2;
291
292         if (device_id_out >= _numDevices || device_id_in >= _numDevices) {
293                 return -1;
294         }
295
296         _process_callback = process_callback;
297         _process_arg = process_arg;
298         _max_samples_per_period = samples_per_period;
299         _cur_samples_per_period = 0;
300
301         ComponentResult err;
302         UInt32 enableIO;
303         AudioStreamBasicDescription srcFormat, dstFormat;
304         
305         AudioComponentDescription cd = {kAudioUnitType_Output, kAudioUnitSubType_HALOutput, kAudioUnitManufacturer_Apple, 0, 0};
306         AudioComponent HALOutput = AudioComponentFindNext(NULL, &cd);
307         if (!HALOutput) { goto error; }
308
309         err = AudioComponentInstanceNew(HALOutput, &_auhal);
310         if (err != noErr) { goto error; }
311
312         err = AudioUnitInitialize(_auhal);
313         if (err != noErr) { goto error; }
314
315         enableIO = 1;
316         err = AudioUnitSetProperty(_auhal, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, AUHAL_INPUT_ELEMENT, &enableIO, sizeof(enableIO));
317         if (err != noErr) { goto error; }
318         enableIO = 1;
319         err = AudioUnitSetProperty(_auhal, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, AUHAL_OUTPUT_ELEMENT, &enableIO, sizeof(enableIO));
320         if (err != noErr) { goto error; }
321
322         err = AudioUnitSetProperty(_auhal, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, AUHAL_OUTPUT_ELEMENT, &_deviceIDs[device_id_out], sizeof(AudioDeviceID));
323         if (err != noErr) { goto error; }
324
325         err = AudioUnitSetProperty(_auhal, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, AUHAL_INPUT_ELEMENT, &_deviceIDs[device_id_in], sizeof(AudioDeviceID));
326         if (err != noErr) { goto error; }
327
328         // Set buffer size
329         err = AudioUnitSetProperty(_auhal, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, AUHAL_INPUT_ELEMENT, (UInt32*)&_max_samples_per_period, sizeof(UInt32));
330         if (err != noErr) { goto error; }
331         err = AudioUnitSetProperty(_auhal, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, AUHAL_OUTPUT_ELEMENT, (UInt32*)&_max_samples_per_period, sizeof(UInt32));
332         if (err != noErr) { goto error; }
333
334
335         // set sample format
336         srcFormat.mSampleRate = sample_rate;
337         srcFormat.mFormatID = kAudioFormatLinearPCM;
338         srcFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kLinearPCMFormatFlagIsNonInterleaved;
339         srcFormat.mBytesPerPacket = sizeof(float);
340         srcFormat.mFramesPerPacket = 1;
341         srcFormat.mBytesPerFrame = sizeof(float);
342         srcFormat.mChannelsPerFrame = _device_ins[device_id_in];
343         srcFormat.mBitsPerChannel = 32;
344
345         err = AudioUnitSetProperty(_auhal, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, AUHAL_INPUT_ELEMENT, &srcFormat, sizeof(AudioStreamBasicDescription));
346         if (err != noErr) { goto error; }
347
348         dstFormat.mSampleRate = sample_rate;
349         dstFormat.mFormatID = kAudioFormatLinearPCM;
350         dstFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kLinearPCMFormatFlagIsNonInterleaved;
351         dstFormat.mBytesPerPacket = sizeof(float);
352         dstFormat.mFramesPerPacket = 1;
353         dstFormat.mBytesPerFrame = sizeof(float);
354         dstFormat.mChannelsPerFrame = _device_outs[device_id_out];
355         dstFormat.mBitsPerChannel = 32;
356
357         err = AudioUnitSetProperty(_auhal, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, AUHAL_OUTPUT_ELEMENT, &dstFormat, sizeof(AudioStreamBasicDescription));
358         if (err != noErr) { goto error; }
359
360         UInt32 size;
361         size = sizeof(AudioStreamBasicDescription);
362         err = AudioUnitGetProperty(_auhal, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, AUHAL_INPUT_ELEMENT, &srcFormat, &size);
363         if (err != noErr) { goto error; }
364         _capture_channels = srcFormat.mChannelsPerFrame;
365 #ifndef NDEBUG
366         PrintStreamDesc(&srcFormat);
367 #endif
368
369         size = sizeof(AudioStreamBasicDescription);
370         err = AudioUnitGetProperty(_auhal, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, AUHAL_OUTPUT_ELEMENT, &dstFormat, &size);
371         if (err != noErr) { goto error; }
372         _playback_channels = dstFormat.mChannelsPerFrame;
373
374 #ifndef NDEBUG
375         PrintStreamDesc(&dstFormat);
376 #endif
377
378         _inputAudioBufferList = (AudioBufferList*)malloc(sizeof(UInt32) + _capture_channels * sizeof(AudioBuffer));
379
380         // Setup callbacks
381         AURenderCallbackStruct renderCallback;
382         memset (&renderCallback, 0, sizeof (renderCallback));
383         renderCallback.inputProc = render_callback_ptr;
384         renderCallback.inputProcRefCon = this;
385         err = AudioUnitSetProperty(_auhal,
386                         kAudioUnitProperty_SetRenderCallback,
387                         kAudioUnitScope_Output, AUHAL_OUTPUT_ELEMENT,
388                         &renderCallback, sizeof (renderCallback));
389         if (err != noErr) { goto error; }
390
391         printf("SETUP OK..\n");
392
393         if (AudioOutputUnitStart(_auhal) == noErr) {
394                 printf("Coreaudio Started..\n");
395                 _state = 0;
396                 return 0;
397         }
398
399 error:
400         pcm_stop();
401         _state = -3;
402         return -1;
403 }
404
405
406 OSStatus
407 CoreAudioPCM::render_callback (
408                 AudioUnitRenderActionFlags* ioActionFlags,
409                 const AudioTimeStamp* inTimeStamp,
410                 UInt32 inBusNumber,
411                 UInt32 inNumberFrames,
412                 AudioBufferList* ioData)
413 {
414         OSStatus retVal = 0;
415
416         assert(_max_samples_per_period >= inNumberFrames);
417         assert(ioData->mNumberBuffers = _playback_channels);
418
419         _cur_samples_per_period = inNumberFrames;
420
421
422         _inputAudioBufferList->mNumberBuffers = _capture_channels;
423         for (int i = 0; i < _capture_channels; ++i) {
424                 _inputAudioBufferList->mBuffers[i].mNumberChannels = 1;
425                 _inputAudioBufferList->mBuffers[i].mDataByteSize = inNumberFrames * sizeof(float);
426                 _inputAudioBufferList->mBuffers[i].mData = NULL;
427         }
428
429         retVal = AudioUnitRender(_auhal, ioActionFlags, inTimeStamp, AUHAL_INPUT_ELEMENT, inNumberFrames, _inputAudioBufferList);
430
431         if (retVal != kAudioHardwareNoError) {
432                 char *rv = (char*)&retVal;
433                 printf("ERR %c%c%c%c\n", rv[0], rv[1], rv[2], rv[3]);
434                 if (_error_callback) {
435                         _error_callback(_error_arg);
436                 }
437                 return retVal;
438         }
439
440         _outputAudioBufferList = ioData;
441
442         _in_process = true;
443
444         int rv = -1;
445
446         if (_process_callback) {
447                 rv = _process_callback(_process_arg);
448         }
449
450         _in_process = false;
451
452         if (rv != 0) {
453                 // clear output
454                 for (int i = 0; i < ioData->mNumberBuffers; ++i) {
455                         float* ob = (float*) ioData->mBuffers[i].mData;
456                         memset(ob, 0, sizeof(float) * inNumberFrames);
457                 }
458         }
459         return noErr;
460 }
461
462 int
463 CoreAudioPCM::get_capture_channel (uint32_t chn, float *input, uint32_t n_samples)
464 {
465         if (!_in_process || chn > _capture_channels || n_samples > _cur_samples_per_period) {
466                 return -1;
467         }
468         assert(_inputAudioBufferList->mNumberBuffers > chn);
469         memcpy((void*)input, (void*)_inputAudioBufferList->mBuffers[chn].mData, sizeof(float) * n_samples);
470         return 0;
471
472 }
473 int
474 CoreAudioPCM::set_playback_channel (uint32_t chn, const float *output, uint32_t n_samples)
475 {
476         if (!_in_process || chn > _playback_channels || n_samples > _cur_samples_per_period) {
477                 return -1;
478         }
479
480         assert(_outputAudioBufferList->mNumberBuffers > chn);
481         memcpy((void*)_outputAudioBufferList->mBuffers[chn].mData, (void*)output, sizeof(float) * n_samples);
482         return 0;
483 }