comparison src/audio/macosx/SDL_coreaudio.c @ 3785:da2ea0694d11 SDL-ryan-multiple-audio-device

Ton of work on CoreAudio driver for new 1.3 features...most of the multi-device support is wired up, and the starts of capture support, too. All is still subject to change, and what's there is still a little flakey.
author Ryan C. Gordon <icculus@icculus.org>
date Tue, 03 Oct 2006 05:58:44 +0000
parents 37c9c4590689
children b70f4e9291bb
comparison
equal deleted inserted replaced
3784:37c9c4590689 3785:da2ea0694d11
19 Sam Lantinga 19 Sam Lantinga
20 slouken@libsdl.org 20 slouken@libsdl.org
21 */ 21 */
22 #include "SDL_config.h" 22 #include "SDL_config.h"
23 23
24 #include <CoreAudio/CoreAudio.h>
24 #include <AudioUnit/AudioUnit.h> 25 #include <AudioUnit/AudioUnit.h>
25 26
26 #include "SDL_audio.h" 27 #include "SDL_audio.h"
27 #include "../SDL_audio_c.h" 28 #include "../SDL_audio_c.h"
28 #include "../SDL_sysaudio.h" 29 #include "../SDL_sysaudio.h"
29 #include "SDL_coreaudio.h" 30 #include "SDL_coreaudio.h"
31
32
33 typedef struct COREAUDIO_DeviceList
34 {
35 AudioDeviceID id;
36 const char *name;
37 } COREAUDIO_DeviceList;
38
39 static COREAUDIO_DeviceList *inputDevices = NULL;
40 static int inputDeviceCount = 0;
41 static COREAUDIO_DeviceList *outputDevices = NULL;
42 static int outputDeviceCount = 0;
43
44 static void
45 free_device_list(COREAUDIO_DeviceList **devices, int *devCount)
46 {
47 if (*devices) {
48 int i = *devCount;
49 while (i--)
50 SDL_free((void *) (*devices)[i].name);
51 SDL_free(*devices);
52 *devices = NULL;
53 }
54 *devCount = 0;
55 }
56
57
58 static void
59 build_device_list(int iscapture, COREAUDIO_DeviceList **devices, int *devCount)
60 {
61 Boolean outWritable = 0;
62 OSStatus result = noErr;
63 UInt32 size = 0;
64 AudioDeviceID *devs = NULL;
65 UInt32 i = 0;
66 UInt32 max = 0;
67
68 free_device_list(devices, devCount);
69
70 result = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
71 &size, &outWritable);
72
73 if (result != kAudioHardwareNoError)
74 return;
75
76 devs = (AudioDeviceID *) alloca(size);
77 if (devs == NULL)
78 return;
79
80 max = size / sizeof (AudioDeviceID);
81 *devices = (COREAUDIO_DeviceList *) SDL_malloc(max * sizeof (**devices));
82 if (*devices == NULL)
83 return;
84
85 result = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
86 &size, devs);
87 if (result != kAudioHardwareNoError)
88 return;
89
90 for (i = 0; i < max; i++) {
91 char *ptr = NULL;
92 AudioDeviceID dev = devs[i];
93 AudioBufferList *buflist = NULL;
94 int usable = 0;
95
96 result = AudioDeviceGetPropertyInfo(dev, 0, iscapture,
97 kAudioDevicePropertyStreamConfiguration,
98 &size, &outWritable);
99 if (result != noErr)
100 continue;
101
102 buflist = (AudioBufferList *) SDL_malloc(size);
103 if (buflist == NULL)
104 continue;
105
106 result = AudioDeviceGetProperty(dev, 0, iscapture,
107 kAudioDevicePropertyStreamConfiguration,
108 &size, buflist);
109
110 if (result == noErr) {
111 UInt32 j;
112 for (j = 0; j < buflist->mNumberBuffers; j++) {
113 if (buflist->mBuffers[j].mNumberChannels > 0) {
114 usable = 1;
115 break;
116 }
117 }
118 }
119
120 SDL_free(buflist);
121
122 if (!usable)
123 continue;
124
125 /* !!! FIXME: use CFStrings, instead, and convert to UTF-8. */
126 result = AudioDeviceGetPropertyInfo(dev, 0, iscapture,
127 kAudioDevicePropertyDeviceName,
128 &size, &outWritable);
129
130 if (result != kAudioHardwareNoError)
131 continue;
132
133 ptr = (char *) SDL_malloc(size + 1);
134 if (ptr == NULL)
135 continue;
136
137 result = AudioDeviceGetProperty(dev, 0, iscapture,
138 kAudioDevicePropertyDeviceName,
139 &size, ptr);
140
141 if (result != kAudioHardwareNoError)
142 continue;
143
144 while ((size > 0) && (ptr[size-1] == ' '))
145 size--; /* I have a USB device with whitespace at the end... */
146
147 if (size == 0) {
148 SDL_free(ptr);
149 } else {
150 ptr[size] = '\0';
151 (*devices)[*devCount].id = dev;
152 (*devices)[*devCount].name = ptr;
153 (*devCount)++;
154 }
155 }
156 }
157
158 static int
159 find_device_id(const char *devname, int iscapture, AudioDeviceID *id)
160 {
161 int i = ((iscapture) ? inputDeviceCount : outputDeviceCount);
162 COREAUDIO_DeviceList *devs = ((iscapture) ? inputDevices : outputDevices);
163 while (i--) {
164 if (SDL_strcmp(devname, devs->name) == 0) {
165 *id = devs->id;
166 return 1;
167 }
168 devs++;
169 }
170
171 return 0;
172 }
30 173
31 174
32 /* Audio driver functions */ 175 /* Audio driver functions */
33 176
34 static int COREAUDIO_OpenAudio(_THIS, const char *devname, int iscapture); 177 static int COREAUDIO_OpenAudio(_THIS, const char *devname, int iscapture);
46 } 189 }
47 190
48 static int 191 static int
49 COREAUDIO_Init(SDL_AudioDriverImpl *impl) 192 COREAUDIO_Init(SDL_AudioDriverImpl *impl)
50 { 193 {
194 /* !!! FIXME: should these _really_ be static? */
195 build_device_list(0, &outputDevices, &outputDeviceCount);
196 build_device_list(1, &inputDevices, &inputDeviceCount);
197
51 /* Set the function pointers */ 198 /* Set the function pointers */
52 impl->OpenAudio = COREAUDIO_OpenAudio; 199 impl->OpenAudio = COREAUDIO_OpenAudio;
53 impl->WaitAudio = COREAUDIO_WaitAudio; 200 impl->WaitAudio = COREAUDIO_WaitAudio;
54 impl->PlayAudio = COREAUDIO_PlayAudio; 201 impl->PlayAudio = COREAUDIO_PlayAudio;
55 impl->GetAudioBuf = COREAUDIO_GetAudioBuf; 202 impl->GetAudioBuf = COREAUDIO_GetAudioBuf;
63 COREAUDIO_Available, COREAUDIO_Init 210 COREAUDIO_Available, COREAUDIO_Init
64 }; 211 };
65 212
66 /* The CoreAudio callback */ 213 /* The CoreAudio callback */
67 static OSStatus 214 static OSStatus
68 audioCallback(void *inRefCon, 215 outputCallback(void *inRefCon,
69 AudioUnitRenderActionFlags inActionFlags, 216 AudioUnitRenderActionFlags *ioActionFlags,
70 const AudioTimeStamp * inTimeStamp, 217 const AudioTimeStamp * inTimeStamp,
71 UInt32 inBusNumber, AudioBuffer * ioData) 218 UInt32 inBusNumber, UInt32 inNumberFrames,
219 AudioBufferList *ioDataList)
72 { 220 {
73 SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon; 221 SDL_AudioDevice *this = (SDL_AudioDevice *) inRefCon;
222 AudioBuffer *ioData = &ioDataList->mBuffers[0];
74 UInt32 remaining, len; 223 UInt32 remaining, len;
75 void *ptr; 224 void *ptr;
225
226 if (ioDataList->mNumberBuffers != 1) {
227 fprintf(stderr, "!!! FIXME SDL!\n");
228 return noErr;
229 }
76 230
77 /* Only do anything if audio is enabled and not paused */ 231 /* Only do anything if audio is enabled and not paused */
78 if (!this->enabled || this->paused) { 232 if (!this->enabled || this->paused) {
79 SDL_memset(ioData->mData, this->spec.silence, ioData->mDataByteSize); 233 SDL_memset(ioData->mData, this->spec.silence, ioData->mDataByteSize);
80 return 0; 234 return 0;
114 } 268 }
115 269
116 return 0; 270 return 0;
117 } 271 }
118 272
273 static OSStatus
274 inputCallback(void *inRefCon,
275 AudioUnitRenderActionFlags *ioActionFlags,
276 const AudioTimeStamp * inTimeStamp,
277 UInt32 inBusNumber, UInt32 inNumberFrames,
278 AudioBufferList *ioData)
279 {
280 //err = AudioUnitRender(afr->fAudioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, afr->fAudioBuffer);
281 // !!! FIXME: write me!
282 return noErr;
283 }
284
285
119 /* Dummy functions -- we don't use thread-based audio */ 286 /* Dummy functions -- we don't use thread-based audio */
120 void 287 void
121 COREAUDIO_WaitAudio(_THIS) 288 COREAUDIO_WaitAudio(_THIS)
122 { 289 {
123 return; 290 return;
136 } 303 }
137 304
138 void 305 void
139 COREAUDIO_CloseAudio(_THIS) 306 COREAUDIO_CloseAudio(_THIS)
140 { 307 {
141 OSStatus result; 308 if (this->hidden != NULL) {
142 struct AudioUnitInputCallback callback; 309 OSStatus result = noErr;
143 310 AURenderCallbackStruct callback;
144 if (this->hidden == NULL) { 311 const AudioUnitElement output_bus = 0;
145 return; 312 const AudioUnitElement input_bus = 1;
146 } 313 const int iscapture = this->hidden->isCapture;
147 314 const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus);
148 /* stop processing the audio unit */ 315 const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output :
149 result = AudioOutputUnitStop(this->hidden->outputAudioUnit); 316 kAudioUnitScope_Input);
150 if (result != noErr) { 317
151 SDL_SetError("COREAUDIO_CloseAudio: AudioOutputUnitStop"); 318 /* stop processing the audio unit */
152 return; 319 result = AudioOutputUnitStop(this->hidden->audioUnit);
153 } 320
154 321 /* Remove the input callback */
155 /* Remove the input callback */ 322 memset(&callback, '\0', sizeof (AURenderCallbackStruct));
156 callback.inputProc = 0; 323 result = AudioUnitSetProperty(this->hidden->audioUnit,
157 callback.inputProcRefCon = 0; 324 kAudioUnitProperty_SetRenderCallback,
158 result = AudioUnitSetProperty(this->hidden->outputAudioUnit, 325 scope, bus, &callback, sizeof (callback));
159 kAudioUnitProperty_SetInputCallback, 326
160 kAudioUnitScope_Input, 327 CloseComponent(this->hidden->audioUnit);
161 0, &callback, sizeof(callback)); 328
162 if (result != noErr) { 329 SDL_free(this->hidden->buffer);
163 SDL_SetError 330 SDL_free(this->hidden);
164 ("COREAUDIO_CloseAudio: AudioUnitSetProperty (kAudioUnitProperty_SetInputCallback)"); 331 this->hidden = NULL;
165 return; 332 }
166 } 333 }
167 334
168 result = CloseComponent(this->hidden->outputAudioUnit);
169 if (result != noErr) {
170 SDL_SetError("COREAUDIO_CloseAudio: CloseComponent");
171 return;
172 }
173
174 SDL_free(this->hidden->buffer);
175 SDL_free(this->hidden);
176 this->hidden = NULL;
177 }
178 335
179 #define CHECK_RESULT(msg) \ 336 #define CHECK_RESULT(msg) \
180 if (result != noErr) { \ 337 if (result != noErr) { \
181 COREAUDIO_CloseAudio(this); \ 338 COREAUDIO_CloseAudio(this); \
182 SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \ 339 SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
183 return -1; \ 340 return 0; \
184 } 341 }
342
343 static int
344 find_device_by_name(_THIS, const char *devname, int iscapture)
345 {
346 AudioDeviceID devid = 0;
347 OSStatus result = noErr;
348 UInt32 size = 0;
349 UInt32 alive = 0;
350 pid_t pid = 0;
351
352 if (devname == NULL) {
353 size = sizeof (AudioDeviceID);
354 const AudioHardwarePropertyID propid =
355 ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
356 kAudioHardwarePropertyDefaultOutputDevice);
357
358 result = AudioHardwareGetProperty(propid, &size, &devid);
359 CHECK_RESULT("AudioHardwareGetProperty (default device)");
360 } else {
361 if (!find_device_id(devname, iscapture, &devid)) {
362 SDL_SetError("CoreAudio: No such audio device.");
363 return 0;
364 }
365 }
366
367 size = sizeof (alive);
368 result = AudioDeviceGetProperty(devid, 0, iscapture,
369 kAudioDevicePropertyDeviceIsAlive,
370 &size, &alive);
371 CHECK_RESULT("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
372
373 if (!alive) {
374 SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
375 return 0;
376 }
377
378 size = sizeof (pid);
379 result = AudioDeviceGetProperty(devid, 0, iscapture,
380 kAudioDevicePropertyHogMode, &size, &pid);
381
382 /* some devices don't support this property, so errors are fine here. */
383 if ((result == noErr) && (pid != -1)) {
384 SDL_SetError("CoreAudio: requested device is being hogged.");
385 return 0;
386 }
387
388 this->hidden->deviceID = devid;
389 return 1;
390 }
391
392
393 static int
394 prepare_audiounit(_THIS, const char *devname, int iscapture,
395 const AudioStreamBasicDescription *strdesc)
396 {
397 OSStatus result = noErr;
398 AURenderCallbackStruct callback;
399 ComponentDescription desc;
400 Component comp = NULL;
401 int use_system_device = 0;
402 UInt32 enableIO = 0;
403 const AudioUnitElement output_bus = 0;
404 const AudioUnitElement input_bus = 1;
405 const AudioUnitElement bus = ((iscapture) ? input_bus : output_bus);
406 const AudioUnitScope scope = ((iscapture) ? kAudioUnitScope_Output :
407 kAudioUnitScope_Input);
408
409 /* !!! FIXME: move something like this to higher level. */
410 if ( (devname == NULL) && (SDL_getenv("SDL_AUDIO_DEVNAME")) )
411 devname = SDL_getenv("SDL_AUDIO_DEVNAME");
412
413 if (!find_device_by_name(this, devname, iscapture)) {
414 SDL_SetError("Couldn't find requested CoreAudio device");
415 return 0;
416 }
417
418 memset(&desc, '\0', sizeof(ComponentDescription));
419 desc.componentType = kAudioUnitType_Output;
420 desc.componentSubType = kAudioUnitSubType_HALOutput;
421 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
422
423 comp = FindNextComponent(NULL, &desc);
424 if (comp == NULL) {
425 SDL_SetError("Couldn't find requested CoreAudio component");
426 return 0;
427 }
428
429 /* Open & initialize the audio unit */
430 result = OpenAComponent(comp, &this->hidden->audioUnit);
431 CHECK_RESULT("OpenAComponent");
432
433 // !!! FIXME: this is wrong?
434 enableIO = ((iscapture) ? 1 : 0);
435 result = AudioUnitSetProperty(this->hidden->audioUnit,
436 kAudioOutputUnitProperty_EnableIO,
437 kAudioUnitScope_Input, input_bus,
438 &enableIO, sizeof (enableIO));
439 CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_EnableIO input)");
440
441 // !!! FIXME: this is wrong?
442 enableIO = ((iscapture) ? 0 : 1);
443 result = AudioUnitSetProperty(this->hidden->audioUnit,
444 kAudioOutputUnitProperty_EnableIO,
445 kAudioUnitScope_Output, output_bus,
446 &enableIO, sizeof (enableIO));
447 CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_EnableIO output)");
448
449 result = AudioUnitSetProperty(this->hidden->audioUnit,
450 kAudioOutputUnitProperty_CurrentDevice,
451 kAudioUnitScope_Global, 0,
452 &this->hidden->deviceID,
453 sizeof (AudioDeviceID));
454 CHECK_RESULT("AudioUnitSetProperty (kAudioOutputUnitProperty_CurrentDevice)");
455
456 /* Set the data format of the audio unit. */
457 result = AudioUnitSetProperty(this->hidden->audioUnit,
458 kAudioUnitProperty_StreamFormat,
459 scope, bus, strdesc, sizeof (*strdesc));
460 CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)");
461
462 /* Set the audio callback */
463 memset(&callback, '\0', sizeof (AURenderCallbackStruct));
464 callback.inputProc = ((iscapture) ? inputCallback : outputCallback);
465 callback.inputProcRefCon = this;
466 result = AudioUnitSetProperty(this->hidden->audioUnit,
467 kAudioUnitProperty_SetRenderCallback,
468 scope, bus, &callback, sizeof (callback));
469 CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_SetInputCallback)");
470
471 /* Calculate the final parameters for this audio specification */
472 SDL_CalculateAudioSpec(&this->spec);
473
474 /* Allocate a sample buffer */
475 this->hidden->bufferOffset = this->hidden->bufferSize = this->spec.size;
476 this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
477
478 result = AudioUnitInitialize(this->hidden->audioUnit);
479 CHECK_RESULT("AudioUnitInitialize");
480
481 /* Finally, start processing of the audio unit */
482 result = AudioOutputUnitStart(this->hidden->audioUnit);
483 CHECK_RESULT("AudioOutputUnitStart");
484
485 /* We're running! */
486 return 1;
487 }
185 488
186 489
187 int 490 int
188 COREAUDIO_OpenAudio(_THIS, const char *devname, int iscapture) 491 COREAUDIO_OpenAudio(_THIS, const char *devname, int iscapture)
189 { 492 {
190 OSStatus result = noErr;
191 Component comp;
192 ComponentDescription desc;
193 struct AudioUnitInputCallback callback;
194 AudioStreamBasicDescription strdesc; 493 AudioStreamBasicDescription strdesc;
195 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); 494 SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
196 int valid_datatype = 0; 495 int valid_datatype = 0;
197 496
198 /* Initialize all variables that we clean on shutdown */ 497 /* Initialize all variables that we clean on shutdown */
202 SDL_OutOfMemory(); 501 SDL_OutOfMemory();
203 return (0); 502 return (0);
204 } 503 }
205 SDL_memset(this->hidden, 0, (sizeof *this->hidden)); 504 SDL_memset(this->hidden, 0, (sizeof *this->hidden));
206 505
207 /* !!! FIXME: check devname and iscapture... */ 506 this->hidden->isCapture = iscapture;
208 507
209 /* Setup a AudioStreamBasicDescription with the requested format */ 508 /* Setup a AudioStreamBasicDescription with the requested format */
210 memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription)); 509 memset(&strdesc, '\0', sizeof(AudioStreamBasicDescription));
211 strdesc.mFormatID = kAudioFormatLinearPCM; 510 strdesc.mFormatID = kAudioFormatLinearPCM;
212 strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked; 511 strdesc.mFormatFlags = kLinearPCMFormatFlagIsPacked;
249 strdesc.mBytesPerFrame = 548 strdesc.mBytesPerFrame =
250 strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8; 549 strdesc.mBitsPerChannel * strdesc.mChannelsPerFrame / 8;
251 strdesc.mBytesPerPacket = 550 strdesc.mBytesPerPacket =
252 strdesc.mBytesPerFrame * strdesc.mFramesPerPacket; 551 strdesc.mBytesPerFrame * strdesc.mFramesPerPacket;
253 552
254 /* Locate the default output audio unit */ 553 if (!prepare_audiounit(this, devname, iscapture, &strdesc)) {
255 memset(&desc, '\0', sizeof(ComponentDescription)); 554 return 0; /* prepare_audiounit() will call SDL_SetError()... */
256 desc.componentType = kAudioUnitComponentType; 555 }
257 desc.componentSubType = kAudioUnitSubType_Output; 556
258 desc.componentManufacturer = kAudioUnitID_DefaultOutput; 557 return 1; /* good to go. */
259 desc.componentFlags = 0;
260 desc.componentFlagsMask = 0;
261
262 comp = FindNextComponent(NULL, &desc);
263 if (comp == NULL) {
264 COREAUDIO_CloseAudio(this);
265 SDL_SetError
266 ("Failed to start CoreAudio: FindNextComponent returned NULL");
267 return 0;
268 }
269
270 /* Open & initialize the default output audio unit */
271 result = OpenAComponent(comp, &this->hidden->outputAudioUnit);
272 CHECK_RESULT("OpenAComponent")
273 result = AudioUnitInitialize(this->hidden->outputAudioUnit);
274 CHECK_RESULT("AudioUnitInitialize")
275 /* Set the input format of the audio unit. */
276 result = AudioUnitSetProperty(this->hidden->outputAudioUnit,
277 kAudioUnitProperty_StreamFormat,
278 kAudioUnitScope_Input,
279 0, &strdesc, sizeof(strdesc));
280 CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_StreamFormat)")
281 /* Set the audio callback */
282 callback.inputProc = audioCallback;
283 callback.inputProcRefCon = this;
284 result = AudioUnitSetProperty(this->hidden->outputAudioUnit,
285 kAudioUnitProperty_SetInputCallback,
286 kAudioUnitScope_Input,
287 0, &callback, sizeof(callback));
288 CHECK_RESULT("AudioUnitSetProperty (kAudioUnitProperty_SetInputCallback)")
289 /* Calculate the final parameters for this audio specification */
290 SDL_CalculateAudioSpec(&this->spec);
291
292 /* Allocate a sample buffer */
293 this->hidden->bufferOffset = this->hidden->bufferSize = this->spec.size;
294 this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
295
296 /* Finally, start processing of the audio unit */
297 result = AudioOutputUnitStart(this->hidden->outputAudioUnit);
298 CHECK_RESULT("AudioOutputUnitStart")
299 /* We're running! */
300 return (1);
301 } 558 }
302 559
303 /* vi: set ts=4 sw=4 expandtab: */ 560 /* vi: set ts=4 sw=4 expandtab: */