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