SDL  2.0
SDL_coreaudio.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
4 
5  This software is provided 'as-is', without any express or implied
6  warranty. In no event will the authors be held liable for any damages
7  arising from the use of this software.
8 
9  Permission is granted to anyone to use this software for any purpose,
10  including commercial applications, and to alter it and redistribute it
11  freely, subject to the following restrictions:
12 
13  1. The origin of this software must not be misrepresented; you must not
14  claim that you wrote the original software. If you use this software
15  in a product, an acknowledgment in the product documentation would be
16  appreciated but is not required.
17  2. Altered source versions must be plainly marked as such, and must not be
18  misrepresented as being the original software.
19  3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #if SDL_AUDIO_DRIVER_COREAUDIO
24 
25 /* !!! FIXME: clean out some of the macro salsa in here. */
26 
27 #include "SDL_audio.h"
28 #include "SDL_hints.h"
29 #include "SDL_timer.h"
30 #include "../SDL_audio_c.h"
31 #include "../SDL_sysaudio.h"
32 #include "SDL_coreaudio.h"
33 #include "SDL_assert.h"
34 #include "../../thread/SDL_systhread.h"
35 
36 #define DEBUG_COREAUDIO 0
37 
38 #define CHECK_RESULT(msg) \
39  if (result != noErr) { \
40  SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \
41  return 0; \
42  }
43 
44 #if MACOSX_COREAUDIO
45 static const AudioObjectPropertyAddress devlist_address = {
46  kAudioHardwarePropertyDevices,
47  kAudioObjectPropertyScopeGlobal,
48  kAudioObjectPropertyElementMaster
49 };
50 
51 typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data);
52 
53 typedef struct AudioDeviceList
54 {
55  AudioDeviceID devid;
57  struct AudioDeviceList *next;
58 } AudioDeviceList;
59 
60 static AudioDeviceList *output_devs = NULL;
61 static AudioDeviceList *capture_devs = NULL;
62 
63 static SDL_bool
64 add_to_internal_dev_list(const int iscapture, AudioDeviceID devId)
65 {
66  AudioDeviceList *item = (AudioDeviceList *) SDL_malloc(sizeof (AudioDeviceList));
67  if (item == NULL) {
68  return SDL_FALSE;
69  }
70  item->devid = devId;
71  item->alive = SDL_TRUE;
72  item->next = iscapture ? capture_devs : output_devs;
73  if (iscapture) {
74  capture_devs = item;
75  } else {
76  output_devs = item;
77  }
78 
79  return SDL_TRUE;
80 }
81 
82 static void
83 addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data)
84 {
85  if (add_to_internal_dev_list(iscapture, devId)) {
86  SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
87  }
88 }
89 
90 static void
91 build_device_list(int iscapture, addDevFn addfn, void *addfndata)
92 {
93  OSStatus result = noErr;
94  UInt32 size = 0;
95  AudioDeviceID *devs = NULL;
96  UInt32 i = 0;
97  UInt32 max = 0;
98 
99  result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
100  &devlist_address, 0, NULL, &size);
101  if (result != kAudioHardwareNoError)
102  return;
103 
104  devs = (AudioDeviceID *) alloca(size);
105  if (devs == NULL)
106  return;
107 
108  result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
109  &devlist_address, 0, NULL, &size, devs);
110  if (result != kAudioHardwareNoError)
111  return;
112 
113  max = size / sizeof (AudioDeviceID);
114  for (i = 0; i < max; i++) {
115  CFStringRef cfstr = NULL;
116  char *ptr = NULL;
117  AudioDeviceID dev = devs[i];
118  AudioBufferList *buflist = NULL;
119  int usable = 0;
120  CFIndex len = 0;
121  const AudioObjectPropertyAddress addr = {
122  kAudioDevicePropertyStreamConfiguration,
123  iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
124  kAudioObjectPropertyElementMaster
125  };
126 
127  const AudioObjectPropertyAddress nameaddr = {
128  kAudioObjectPropertyName,
129  iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
130  kAudioObjectPropertyElementMaster
131  };
132 
133  result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
134  if (result != noErr)
135  continue;
136 
137  buflist = (AudioBufferList *) SDL_malloc(size);
138  if (buflist == NULL)
139  continue;
140 
141  result = AudioObjectGetPropertyData(dev, &addr, 0, NULL,
142  &size, buflist);
143 
144  if (result == noErr) {
145  UInt32 j;
146  for (j = 0; j < buflist->mNumberBuffers; j++) {
147  if (buflist->mBuffers[j].mNumberChannels > 0) {
148  usable = 1;
149  break;
150  }
151  }
152  }
153 
154  SDL_free(buflist);
155 
156  if (!usable)
157  continue;
158 
159 
160  size = sizeof (CFStringRef);
161  result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
162  if (result != kAudioHardwareNoError)
163  continue;
164 
165  len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
166  kCFStringEncodingUTF8);
167 
168  ptr = (char *) SDL_malloc(len + 1);
169  usable = ((ptr != NULL) &&
170  (CFStringGetCString
171  (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));
172 
173  CFRelease(cfstr);
174 
175  if (usable) {
176  len = strlen(ptr);
177  /* Some devices have whitespace at the end...trim it. */
178  while ((len > 0) && (ptr[len - 1] == ' ')) {
179  len--;
180  }
181  usable = (len > 0);
182  }
183 
184  if (usable) {
185  ptr[len] = '\0';
186 
187 #if DEBUG_COREAUDIO
188  printf("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n",
189  ((iscapture) ? "capture" : "output"),
190  (int) i, ptr, (int) dev);
191 #endif
192  addfn(ptr, iscapture, dev, addfndata);
193  }
194  SDL_free(ptr); /* addfn() would have copied the string. */
195  }
196 }
197 
198 static void
199 free_audio_device_list(AudioDeviceList **list)
200 {
201  AudioDeviceList *item = *list;
202  while (item) {
203  AudioDeviceList *next = item->next;
204  SDL_free(item);
205  item = next;
206  }
207  *list = NULL;
208 }
209 
210 static void
211 COREAUDIO_DetectDevices(void)
212 {
213  build_device_list(SDL_TRUE, addToDevList, NULL);
214  build_device_list(SDL_FALSE, addToDevList, NULL);
215 }
216 
217 static void
218 build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data)
219 {
220  AudioDeviceList **list = (AudioDeviceList **) data;
221  AudioDeviceList *item;
222  for (item = *list; item != NULL; item = item->next) {
223  if (item->devid == devId) {
224  item->alive = SDL_TRUE;
225  return;
226  }
227  }
228 
229  add_to_internal_dev_list(iscapture, devId); /* new device, add it. */
230  SDL_AddAudioDevice(iscapture, name, (void *) ((size_t) devId));
231 }
232 
233 static void
234 reprocess_device_list(const int iscapture, AudioDeviceList **list)
235 {
236  AudioDeviceList *item;
237  AudioDeviceList *prev = NULL;
238  for (item = *list; item != NULL; item = item->next) {
239  item->alive = SDL_FALSE;
240  }
241 
242  build_device_list(iscapture, build_device_change_list, list);
243 
244  /* free items in the list that aren't still alive. */
245  item = *list;
246  while (item != NULL) {
247  AudioDeviceList *next = item->next;
248  if (item->alive) {
249  prev = item;
250  } else {
251  SDL_RemoveAudioDevice(iscapture, (void *) ((size_t) item->devid));
252  if (prev) {
253  prev->next = item->next;
254  } else {
255  *list = item->next;
256  }
257  SDL_free(item);
258  }
259  item = next;
260  }
261 }
262 
263 /* this is called when the system's list of available audio devices changes. */
264 static OSStatus
265 device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
266 {
267  reprocess_device_list(SDL_TRUE, &capture_devs);
268  reprocess_device_list(SDL_FALSE, &output_devs);
269  return 0;
270 }
271 #endif
272 
273 
274 static int open_playback_devices = 0;
275 static int open_capture_devices = 0;
276 
277 #if !MACOSX_COREAUDIO
278 
279 static void interruption_begin(_THIS)
280 {
281  if (this != NULL && this->hidden->audioQueue != NULL) {
282  this->hidden->interrupted = SDL_TRUE;
283  AudioQueuePause(this->hidden->audioQueue);
284  }
285 }
286 
287 static void interruption_end(_THIS)
288 {
289  if (this != NULL && this->hidden != NULL && this->hidden->audioQueue != NULL
290  && this->hidden->interrupted
291  && AudioQueueStart(this->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) {
292  this->hidden->interrupted = SDL_FALSE;
293  }
294 }
295 
296 @interface SDLInterruptionListener : NSObject
297 
298 @property (nonatomic, assign) SDL_AudioDevice *device;
299 
300 @end
301 
302 @implementation SDLInterruptionListener
303 
304 - (void)audioSessionInterruption:(NSNotification *)note
305 {
306  @synchronized (self) {
307  NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];
308  if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) {
309  interruption_begin(self.device);
310  } else {
311  interruption_end(self.device);
312  }
313  }
314 }
315 
316 - (void)applicationBecameActive:(NSNotification *)note
317 {
318  @synchronized (self) {
319  interruption_end(self.device);
320  }
321 }
322 
323 @end
324 
325 static BOOL update_audio_session(_THIS, SDL_bool open)
326 {
327  @autoreleasepool {
328  AVAudioSession *session = [AVAudioSession sharedInstance];
329  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
330  /* Set category to ambient by default so that other music continues playing. */
331  NSString *category = AVAudioSessionCategoryAmbient;
332  NSError *err = nil;
333 
334  if (open_playback_devices && open_capture_devices) {
335  category = AVAudioSessionCategoryPlayAndRecord;
336  } else if (open_capture_devices) {
337  category = AVAudioSessionCategoryRecord;
338  } else {
339  const char *hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY);
340  if (hint) {
341  if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) {
342  category = AVAudioSessionCategoryAmbient;
343  } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) {
344  category = AVAudioSessionCategorySoloAmbient;
345  } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 ||
346  SDL_strcasecmp(hint, "playback") == 0) {
347  category = AVAudioSessionCategoryPlayback;
348  }
349  }
350  }
351 
352  if (![session setCategory:category error:&err]) {
353  NSString *desc = err.description;
354  SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
355  return NO;
356  }
357 
358  if (open && (open_playback_devices + open_capture_devices) == 1) {
359  if (![session setActive:YES error:&err]) {
360  NSString *desc = err.description;
361  SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String);
362  return NO;
363  }
364  } else if (!open_playback_devices && !open_capture_devices) {
365  [session setActive:NO error:nil];
366  }
367 
368  if (open) {
369  SDLInterruptionListener *listener = [SDLInterruptionListener new];
370  listener.device = this;
371 
372  [center addObserver:listener
373  selector:@selector(audioSessionInterruption:)
374  name:AVAudioSessionInterruptionNotification
375  object:session];
376 
377  /* An interruption end notification is not guaranteed to be sent if
378  we were previously interrupted... resuming if needed when the app
379  becomes active seems to be the way to go. */
380  [center addObserver:listener
381  selector:@selector(applicationBecameActive:)
382  name:UIApplicationDidBecomeActiveNotification
383  object:session];
384 
385  [center addObserver:listener
386  selector:@selector(applicationBecameActive:)
387  name:UIApplicationWillEnterForegroundNotification
388  object:session];
389 
390  this->hidden->interruption_listener = CFBridgingRetain(listener);
391  } else {
392  if (this->hidden->interruption_listener != NULL) {
393  SDLInterruptionListener *listener = nil;
394  listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener);
395  [center removeObserver:listener];
396  @synchronized (listener) {
397  listener.device = NULL;
398  }
399  }
400  }
401  }
402 
403  return YES;
404 }
405 #endif
406 
407 
408 /* The AudioQueue callback */
409 static void
410 outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
411 {
412  SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
413  SDL_assert(inBuffer->mAudioDataBytesCapacity == this->hidden->bufferSize);
414  SDL_memcpy(inBuffer->mAudioData, this->hidden->buffer, this->hidden->bufferSize);
415  SDL_memset(this->hidden->buffer, '\0', this->hidden->bufferSize); /* zero out in case we have to fill again without new data. */
416  inBuffer->mAudioDataByteSize = this->hidden->bufferSize;
417  AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
418  this->hidden->refill = SDL_TRUE;
419 }
420 
421 static Uint8 *
422 COREAUDIO_GetDeviceBuf(_THIS)
423 {
424  return this->hidden->buffer;
425 }
426 
427 static void
428 COREAUDIO_WaitDevice(_THIS)
429 {
430  while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) {
431  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
432  }
433  this->hidden->refill = SDL_FALSE;
434 }
435 
436 static void
437 inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
438  const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions,
439  const AudioStreamPacketDescription *inPacketDescs )
440 {
441  SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData;
442  if (SDL_AtomicGet(&this->enabled)) {
443  SDL_AudioStream *stream = this->hidden->capturestream;
444  if (SDL_AudioStreamPut(stream, inBuffer->mAudioData, inBuffer->mAudioDataByteSize) == -1) {
445  /* yikes, out of memory or something. I guess drop the buffer. Our WASAPI target kills the device in this case, though */
446  }
447  AudioQueueEnqueueBuffer(this->hidden->audioQueue, inBuffer, 0, NULL);
448  this->hidden->refill = SDL_TRUE;
449  }
450 }
451 
452 static int
453 COREAUDIO_CaptureFromDevice(_THIS, void *buffer, int buflen)
454 {
455  SDL_AudioStream *stream = this->hidden->capturestream;
456  while (SDL_AtomicGet(&this->enabled)) {
457  const int avail = SDL_AudioStreamAvailable(stream);
458  if (avail > 0) {
459  const int cpy = SDL_min(buflen, avail);
460  SDL_AudioStreamGet(stream, buffer, cpy);
461  return cpy;
462  }
463 
464  /* wait for more data, try again. */
465  while (SDL_AtomicGet(&this->enabled) && !this->hidden->refill) {
466  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
467  }
468  this->hidden->refill = SDL_FALSE;
469  }
470 
471  return 0; /* not enabled, giving up. */
472 }
473 
474 static void
475 COREAUDIO_FlushCapture(_THIS)
476 {
477  while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 1) == kCFRunLoopRunHandledSource) {
478  /* spin. */
479  }
480  this->hidden->refill = SDL_FALSE;
481  SDL_AudioStreamClear(this->hidden->capturestream);
482 }
483 
484 
485 #if MACOSX_COREAUDIO
486 static const AudioObjectPropertyAddress alive_address =
487 {
488  kAudioDevicePropertyDeviceIsAlive,
489  kAudioObjectPropertyScopeGlobal,
490  kAudioObjectPropertyElementMaster
491 };
492 
493 static OSStatus
494 device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
495 {
496  SDL_AudioDevice *this = (SDL_AudioDevice *) data;
497  SDL_bool dead = SDL_FALSE;
498  UInt32 isAlive = 1;
499  UInt32 size = sizeof (isAlive);
500  OSStatus error;
501 
502  if (!SDL_AtomicGet(&this->enabled)) {
503  return 0; /* already known to be dead. */
504  }
505 
506  error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address,
507  0, NULL, &size, &isAlive);
508 
509  if (error == kAudioHardwareBadDeviceError) {
510  dead = SDL_TRUE; /* device was unplugged. */
511  } else if ((error == kAudioHardwareNoError) && (!isAlive)) {
512  dead = SDL_TRUE; /* device died in some other way. */
513  }
514 
515  if (dead) {
517  }
518 
519  return 0;
520 }
521 #endif
522 
523 static void
524 COREAUDIO_CloseDevice(_THIS)
525 {
526  const SDL_bool iscapture = this->iscapture;
527 
528 /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */
529 /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */
530 #if MACOSX_COREAUDIO
531  /* Fire a callback if the device stops being "alive" (disconnected, etc). */
532  AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
533 #endif
534 
535 #if !MACOSX_COREAUDIO
536  update_audio_session(this, SDL_FALSE);
537 #endif
538 
539  if (this->hidden->audioQueue) {
540  AudioQueueDispose(this->hidden->audioQueue, 1);
541  }
542 
543  if (this->hidden->capturestream) {
544  SDL_FreeAudioStream(this->hidden->capturestream);
545  }
546 
547  /* AudioQueueDispose() frees the actual buffer objects. */
548  SDL_free(this->hidden->audioBuffer);
549  SDL_free(this->hidden->buffer);
550  SDL_free(this->hidden);
551 
552  if (iscapture) {
553  open_capture_devices--;
554  } else {
555  open_playback_devices--;
556  }
557 }
558 
559 #if MACOSX_COREAUDIO
560 static int
561 prepare_device(_THIS, void *handle, int iscapture)
562 {
563  AudioDeviceID devid = (AudioDeviceID) ((size_t) handle);
564  OSStatus result = noErr;
565  UInt32 size = 0;
566  UInt32 alive = 0;
567  pid_t pid = 0;
568 
569  AudioObjectPropertyAddress addr = {
570  0,
571  kAudioObjectPropertyScopeGlobal,
572  kAudioObjectPropertyElementMaster
573  };
574 
575  if (handle == NULL) {
576  size = sizeof (AudioDeviceID);
577  addr.mSelector =
578  ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
579  kAudioHardwarePropertyDefaultOutputDevice);
580  result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
581  0, NULL, &size, &devid);
582  CHECK_RESULT("AudioHardwareGetProperty (default device)");
583  }
584 
585  addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
586  addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
587  kAudioDevicePropertyScopeOutput;
588 
589  size = sizeof (alive);
590  result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
591  CHECK_RESULT
592  ("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
593 
594  if (!alive) {
595  SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
596  return 0;
597  }
598 
599  addr.mSelector = kAudioDevicePropertyHogMode;
600  size = sizeof (pid);
601  result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);
602 
603  /* some devices don't support this property, so errors are fine here. */
604  if ((result == noErr) && (pid != -1)) {
605  SDL_SetError("CoreAudio: requested device is being hogged.");
606  return 0;
607  }
608 
609  this->hidden->deviceID = devid;
610  return 1;
611 }
612 #endif
613 
614 
615 /* this all happens in the audio thread, since it needs a separate runloop. */
616 static int
617 prepare_audioqueue(_THIS)
618 {
619  const AudioStreamBasicDescription *strdesc = &this->hidden->strdesc;
620  const int iscapture = this->iscapture;
621  OSStatus result;
622  int i;
623 
624  SDL_assert(CFRunLoopGetCurrent() != NULL);
625 
626  if (iscapture) {
627  result = AudioQueueNewInput(strdesc, inputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
628  CHECK_RESULT("AudioQueueNewInput");
629  } else {
630  result = AudioQueueNewOutput(strdesc, outputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
631  CHECK_RESULT("AudioQueueNewOutput");
632  }
633 
634 #if MACOSX_COREAUDIO
635 {
636  const AudioObjectPropertyAddress prop = {
637  kAudioDevicePropertyDeviceUID,
638  iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
639  kAudioObjectPropertyElementMaster
640  };
641  CFStringRef devuid;
642  UInt32 devuidsize = sizeof (devuid);
643  result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid);
644  CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)");
645  result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
646  CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)");
647 
648  /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */
649  /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */
650  /* Fire a callback if the device stops being "alive" (disconnected, etc). */
651  AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
652 }
653 #endif
654 
655  /* Make sure we can feed the device a minimum amount of time */
656  double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
657 #if defined(__IPHONEOS__)
658  if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
659  /* Older iOS hardware, use 40 ms as a minimum time */
660  MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0;
661  }
662 #endif
663  const double msecs = (this->spec.samples / ((double) this->spec.freq)) * 1000.0;
664  int numAudioBuffers = 2;
665  if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { /* use more buffers if we have a VERY small sample set. */
666  numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
667  }
668 
669  this->hidden->numAudioBuffers = numAudioBuffers;
670  this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers);
671  if (this->hidden->audioBuffer == NULL) {
672  SDL_OutOfMemory();
673  return 0;
674  }
675 
676 #if DEBUG_COREAUDIO
677  printf("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers);
678 #endif
679 
680  for (i = 0; i < numAudioBuffers; i++) {
681  result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[i]);
682  CHECK_RESULT("AudioQueueAllocateBuffer");
683  SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity);
684  this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity;
685  result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL);
686  CHECK_RESULT("AudioQueueEnqueueBuffer");
687  }
688 
689  result = AudioQueueStart(this->hidden->audioQueue, NULL);
690  CHECK_RESULT("AudioQueueStart");
691 
692  /* We're running! */
693  return 1;
694 }
695 
696 static void
697 COREAUDIO_ThreadInit(_THIS)
698 {
699  const int rc = prepare_audioqueue(this);
700  if (!rc) {
701  /* !!! FIXME: do this in RunAudio, and maybe block OpenDevice until ThreadInit finishes, too, to report an opening error */
702  SDL_OpenedAudioDeviceDisconnected(this); /* oh well. */
703  }
704 }
705 
706 static void
707 COREAUDIO_PrepareToClose(_THIS)
708 {
709  /* run long enough to queue some silence, so we know our actual audio
710  has been played */
711  CFRunLoopRunInMode(kCFRunLoopDefaultMode, (((this->spec.samples * 1000) / this->spec.freq) * 2) / 1000.0f, 0);
712  AudioQueueStop(this->hidden->audioQueue, 1);
713 }
714 
715 static int
716 COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
717 {
718  AudioStreamBasicDescription *strdesc;
719  SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format);
720  int valid_datatype = 0;
721 
722  /* Initialize all variables that we clean on shutdown */
723  this->hidden = (struct SDL_PrivateAudioData *)
724  SDL_malloc((sizeof *this->hidden));
725  if (this->hidden == NULL) {
726  return SDL_OutOfMemory();
727  }
728  SDL_zerop(this->hidden);
729 
730  strdesc = &this->hidden->strdesc;
731 
732  if (iscapture) {
733  open_capture_devices++;
734  } else {
735  open_playback_devices++;
736  }
737 
738 #if !MACOSX_COREAUDIO
739  if (!update_audio_session(this, SDL_TRUE)) {
740  return -1;
741  }
742 
743  /* Stop CoreAudio from doing expensive audio rate conversion */
744  @autoreleasepool {
745  AVAudioSession* session = [AVAudioSession sharedInstance];
746  [session setPreferredSampleRate:this->spec.freq error:nil];
747  this->spec.freq = (int)session.sampleRate;
748  }
749 #endif
750 
751  /* Setup a AudioStreamBasicDescription with the requested format */
752  SDL_zerop(strdesc);
753  strdesc->mFormatID = kAudioFormatLinearPCM;
754  strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
755  strdesc->mChannelsPerFrame = this->spec.channels;
756  strdesc->mSampleRate = this->spec.freq;
757  strdesc->mFramesPerPacket = 1;
758 
759  while ((!valid_datatype) && (test_format)) {
760  this->spec.format = test_format;
761  /* Just a list of valid SDL formats, so people don't pass junk here. */
762  switch (test_format) {
763  case AUDIO_U8:
764  case AUDIO_S8:
765  case AUDIO_U16LSB:
766  case AUDIO_S16LSB:
767  case AUDIO_U16MSB:
768  case AUDIO_S16MSB:
769  case AUDIO_S32LSB:
770  case AUDIO_S32MSB:
771  case AUDIO_F32LSB:
772  case AUDIO_F32MSB:
773  valid_datatype = 1;
774  strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(this->spec.format);
775  if (SDL_AUDIO_ISBIGENDIAN(this->spec.format))
776  strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
777 
778  if (SDL_AUDIO_ISFLOAT(this->spec.format))
779  strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat;
780  else if (SDL_AUDIO_ISSIGNED(this->spec.format))
781  strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
782  break;
783  }
784  }
785 
786  if (!valid_datatype) { /* shouldn't happen, but just in case... */
787  return SDL_SetError("Unsupported audio format");
788  }
789 
790  strdesc->mBytesPerFrame = strdesc->mBitsPerChannel * strdesc->mChannelsPerFrame / 8;
791  strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket;
792 
793 #if MACOSX_COREAUDIO
794  if (!prepare_device(this, handle, iscapture)) {
795  return -1;
796  }
797 #endif
798 
799  /* Calculate the final parameters for this audio specification */
801 
802  if (iscapture) {
803  this->hidden->capturestream = SDL_NewAudioStream(this->spec.format, this->spec.channels, this->spec.freq, this->spec.format, this->spec.channels, this->spec.freq);
804  if (!this->hidden->capturestream) {
805  return -1; /* already set SDL_Error */
806  }
807  } else {
808  this->hidden->bufferSize = this->spec.size;
809  this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
810  if (this->hidden->buffer == NULL) {
811  return SDL_OutOfMemory();
812  }
813  }
814 
815  return 0;
816 }
817 
818 static void
819 COREAUDIO_Deinitialize(void)
820 {
821 #if MACOSX_COREAUDIO
822  AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
823  free_audio_device_list(&capture_devs);
824  free_audio_device_list(&output_devs);
825 #endif
826 }
827 
828 static int
829 COREAUDIO_Init(SDL_AudioDriverImpl * impl)
830 {
831  /* Set the function pointers */
832  impl->OpenDevice = COREAUDIO_OpenDevice;
833  impl->CloseDevice = COREAUDIO_CloseDevice;
834  impl->Deinitialize = COREAUDIO_Deinitialize;
835  impl->ThreadInit = COREAUDIO_ThreadInit;
836  impl->WaitDevice = COREAUDIO_WaitDevice;
837  impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf;
838  impl->PrepareToClose = COREAUDIO_PrepareToClose;
839  impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice;
840  impl->FlushCapture = COREAUDIO_FlushCapture;
841 
842 #if MACOSX_COREAUDIO
843  impl->DetectDevices = COREAUDIO_DetectDevices;
844  AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
845 #else
846  impl->OnlyHasDefaultOutputDevice = 1;
847  impl->OnlyHasDefaultCaptureDevice = 1;
848 #endif
849 
850  impl->HasCaptureSupport = 1;
851 
852  return 1; /* this audio target is available. */
853 }
854 
856  "coreaudio", "CoreAudio", COREAUDIO_Init, 0
857 };
858 
859 #endif /* SDL_AUDIO_DRIVER_COREAUDIO */
860 
861 /* vi: set ts=4 sw=4 expandtab: */
int alive
Definition: testsem.c:24
void(* CloseDevice)(_THIS)
Definition: SDL_sysaudio.h:79
#define SDL_AudioStreamAvailable
#define SDL_min(x, y)
Definition: SDL_stdinc.h:406
SDL_AudioFormat SDL_FirstAudioFormat(SDL_AudioFormat format)
Definition: SDL_audio.c:1639
#define AUDIO_S32MSB
Definition: SDL_audio.h:104
#define SDL_ceil
GLuint64EXT * result
void(* WaitDevice)(_THIS)
Definition: SDL_sysaudio.h:72
#define SDL_AUDIO_ISBIGENDIAN(x)
Definition: SDL_audio.h:77
#define AUDIO_U16LSB
Definition: SDL_audio.h:91
#define SDL_AudioStreamGet
Uint8 *(* GetDeviceBuf)(_THIS)
Definition: SDL_sysaudio.h:75
void(* DetectDevices)(void)
Definition: SDL_sysaudio.h:67
#define SDL_GetHint
Uint16 samples
Definition: SDL_audio.h:184
#define SDL_AUDIO_ISSIGNED(x)
Definition: SDL_audio.h:78
void SDL_OpenedAudioDeviceDisconnected(SDL_AudioDevice *device)
Definition: SDL_audio.c:490
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
Uint16 SDL_AudioFormat
Audio format flags.
Definition: SDL_audio.h:64
AudioBootStrap COREAUDIO_bootstrap
#define SDL_strcasecmp
void(* PrepareToClose)(_THIS)
Definition: SDL_sysaudio.h:78
#define SDL_zerop(x)
Definition: SDL_stdinc.h:417
GLenum GLsizei len
GLuint const GLchar * name
#define AUDIO_F32MSB
Definition: SDL_audio.h:113
#define SDL_HINT_AUDIO_CATEGORY
A variable controlling the audio category on iOS and Mac OS X.
Definition: SDL_hints.h:1044
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
SDL_AudioSpec spec
Definition: loopwave.c:31
#define SDL_AUDIO_ISFLOAT(x)
Definition: SDL_audio.h:76
static SDL_AudioDeviceID device
Definition: loopwave.c:37
#define AUDIO_U8
Definition: SDL_audio.h:89
#define SDL_memcpy
EGLImageKHR EGLint EGLint * handle
Definition: eglext.h:937
GLuint GLuint stream
void SDL_RemoveAudioDevice(const int iscapture, void *handle)
Definition: SDL_audio.c:535
Uint8 channels
Definition: SDL_audio.h:182
#define _THIS
uint8_t Uint8
Definition: SDL_stdinc.h:179
#define SDL_free
GLenum const void * addr
#define SDL_AUDIO_BITSIZE(x)
Definition: SDL_audio.h:75
#define AUDIO_F32LSB
Definition: SDL_audio.h:112
#define SDL_AudioStreamPut
int(* CaptureFromDevice)(_THIS, void *buffer, int buflen)
Definition: SDL_sysaudio.h:76
#define AUDIO_S32LSB
Definition: SDL_audio.h:103
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int int in j)
Definition: SDL_x11sym.h:50
void SDL_CalculateAudioSpec(SDL_AudioSpec *spec)
Definition: SDL_audio.c:1660
void(* Deinitialize)(void)
Definition: SDL_sysaudio.h:83
double floor(double x)
Definition: s_floor.c:29
GLsizeiptr size
GLenum GLenum GLsizei const GLuint GLboolean enabled
Uint32 size
Definition: SDL_audio.h:186
#define SDL_assert(condition)
Definition: SDL_assert.h:169
#define NULL
Definition: begin_code.h:164
#define SDL_OutOfMemory()
Definition: SDL_error.h:52
SDL_bool
Definition: SDL_stdinc.h:161
GLuint buffer
#define SDL_SetError
#define SDL_calloc
void(* ThreadInit)(_THIS)
Definition: SDL_sysaudio.h:69
#define AUDIO_S16MSB
Definition: SDL_audio.h:94
SDL_AudioFormat format
Definition: SDL_audio.h:181
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 void
#define AUDIO_S16LSB
Definition: SDL_audio.h:92
#define SDL_NewAudioStream
#define SDL_AudioStreamClear
#define SDL_AtomicGet
GLuint GLuint GLsizei GLenum type
Definition: SDL_opengl.h:1571
#define SDL_malloc
#define SDL_FreeAudioStream
void(* FlushCapture)(_THIS)
Definition: SDL_sysaudio.h:77
#define AUDIO_S8
Definition: SDL_audio.h:90
#define SDL_memset
#define AUDIO_U16MSB
Definition: SDL_audio.h:93
int(* OpenDevice)(_THIS, void *handle, const char *devname, int iscapture)
Definition: SDL_sysaudio.h:68
void SDL_AddAudioDevice(const int iscapture, const char *name, void *handle)
Definition: SDL_audio.c:473