SDL  2.0
SDL_coreaudio.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2017 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 (this->hidden->thread) {
545  SDL_AtomicSet(&this->hidden->shutdown, 1);
546  SDL_WaitThread(this->hidden->thread, NULL);
547  }
548 
549  if (this->hidden->audioQueue) {
550  AudioQueueDispose(this->hidden->audioQueue, 1);
551  }
552 
553  if (this->hidden->ready_semaphore) {
554  SDL_DestroySemaphore(this->hidden->ready_semaphore);
555  }
556 
557  /* AudioQueueDispose() frees the actual buffer objects. */
558  SDL_free(this->hidden->audioBuffer);
559  SDL_free(this->hidden->thread_error);
560  SDL_free(this->hidden->buffer);
561  SDL_free(this->hidden);
562 
563  if (iscapture) {
564  open_capture_devices--;
565  } else {
566  open_playback_devices--;
567  }
568 }
569 
570 #if MACOSX_COREAUDIO
571 static int
572 prepare_device(_THIS, void *handle, int iscapture)
573 {
574  AudioDeviceID devid = (AudioDeviceID) ((size_t) handle);
575  OSStatus result = noErr;
576  UInt32 size = 0;
577  UInt32 alive = 0;
578  pid_t pid = 0;
579 
580  AudioObjectPropertyAddress addr = {
581  0,
582  kAudioObjectPropertyScopeGlobal,
583  kAudioObjectPropertyElementMaster
584  };
585 
586  if (handle == NULL) {
587  size = sizeof (AudioDeviceID);
588  addr.mSelector =
589  ((iscapture) ? kAudioHardwarePropertyDefaultInputDevice :
590  kAudioHardwarePropertyDefaultOutputDevice);
591  result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
592  0, NULL, &size, &devid);
593  CHECK_RESULT("AudioHardwareGetProperty (default device)");
594  }
595 
596  addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
597  addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
598  kAudioDevicePropertyScopeOutput;
599 
600  size = sizeof (alive);
601  result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
602  CHECK_RESULT
603  ("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
604 
605  if (!alive) {
606  SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
607  return 0;
608  }
609 
610  addr.mSelector = kAudioDevicePropertyHogMode;
611  size = sizeof (pid);
612  result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);
613 
614  /* some devices don't support this property, so errors are fine here. */
615  if ((result == noErr) && (pid != -1)) {
616  SDL_SetError("CoreAudio: requested device is being hogged.");
617  return 0;
618  }
619 
620  this->hidden->deviceID = devid;
621  return 1;
622 }
623 #endif
624 
625 static int
626 prepare_audioqueue(_THIS)
627 {
628  const AudioStreamBasicDescription *strdesc = &this->hidden->strdesc;
629  const int iscapture = this->iscapture;
630  OSStatus result;
631  int i;
632 
633  SDL_assert(CFRunLoopGetCurrent() != NULL);
634 
635  if (iscapture) {
636  result = AudioQueueNewInput(strdesc, inputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
637  CHECK_RESULT("AudioQueueNewInput");
638  } else {
639  result = AudioQueueNewOutput(strdesc, outputCallback, this, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &this->hidden->audioQueue);
640  CHECK_RESULT("AudioQueueNewOutput");
641  }
642 
643 #if MACOSX_COREAUDIO
644 {
645  const AudioObjectPropertyAddress prop = {
646  kAudioDevicePropertyDeviceUID,
647  iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
648  kAudioObjectPropertyElementMaster
649  };
650  CFStringRef devuid;
651  UInt32 devuidsize = sizeof (devuid);
652  result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid);
653  CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)");
654  result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
655  CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)");
656 
657  /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */
658  /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */
659  /* Fire a callback if the device stops being "alive" (disconnected, etc). */
660  AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
661 }
662 #endif
663 
664  /* Calculate the final parameters for this audio specification */
666 
667  /* Allocate a sample buffer */
668  this->hidden->bufferSize = this->spec.size;
669  this->hidden->bufferOffset = iscapture ? 0 : this->hidden->bufferSize;
670 
671  this->hidden->buffer = SDL_malloc(this->hidden->bufferSize);
672  if (this->hidden->buffer == NULL) {
673  SDL_OutOfMemory();
674  return 0;
675  }
676 
677  /* Make sure we can feed the device a minimum amount of time */
678  double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
679 #if defined(__IPHONEOS__)
680  if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
681  /* Older iOS hardware, use 40 ms as a minimum time */
682  MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0;
683  }
684 #endif
685  const double msecs = (this->spec.samples / ((double) this->spec.freq)) * 1000.0;
686  int numAudioBuffers = 2;
687  if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { /* use more buffers if we have a VERY small sample set. */
688  numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
689  }
690 
691  this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers);
692  if (this->hidden->audioBuffer == NULL) {
693  SDL_OutOfMemory();
694  return 0;
695  }
696 
697 #if DEBUG_COREAUDIO
698  printf("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers);
699 #endif
700 
701  for (i = 0; i < numAudioBuffers; i++) {
702  result = AudioQueueAllocateBuffer(this->hidden->audioQueue, this->spec.size, &this->hidden->audioBuffer[i]);
703  CHECK_RESULT("AudioQueueAllocateBuffer");
704  SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity);
705  this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity;
706  result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL);
707  CHECK_RESULT("AudioQueueEnqueueBuffer");
708  }
709 
710  result = AudioQueueStart(this->hidden->audioQueue, NULL);
711  CHECK_RESULT("AudioQueueStart");
712 
713  /* We're running! */
714  return 1;
715 }
716 
717 static int
718 audioqueue_thread(void *arg)
719 {
720  SDL_AudioDevice *this = (SDL_AudioDevice *) arg;
721  const int rc = prepare_audioqueue(this);
722  if (!rc) {
723  this->hidden->thread_error = SDL_strdup(SDL_GetError());
724  SDL_SemPost(this->hidden->ready_semaphore);
725  return 0;
726  }
727 
728  /* init was successful, alert parent thread and start running... */
729  SDL_SemPost(this->hidden->ready_semaphore);
730  while (!SDL_AtomicGet(&this->hidden->shutdown)) {
731  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
732  }
733 
734  if (this->iscapture) { /* just stop immediately for capture devices. */
735  AudioQueueStop(this->hidden->audioQueue, 1);
736  } else { /* Drain off any pending playback. */
737  const CFTimeInterval secs = (((this->spec.size / (SDL_AUDIO_BITSIZE(this->spec.format) / 8)) / this->spec.channels) / ((CFTimeInterval) this->spec.freq)) * 2.0;
738  CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0);
739  AudioQueueStop(this->hidden->audioQueue, 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:1577
#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:174
#define SDL_AUDIO_ISSIGNED(x)
Definition: SDL_audio.h:78
void SDL_OpenedAudioDeviceDisconnected(SDL_AudioDevice *device)
Definition: SDL_audio.c:446
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:370
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:868
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
void * SDL_calloc(size_t nmemb, size_t size)
EGLImageKHR EGLint EGLint * handle
Definition: eglext.h:937
void SDL_RemoveAudioDevice(const int iscapture, void *handle)
Definition: SDL_audio.c:487
Uint8 channels
Definition: SDL_audio.h:172
#define _THIS
uint8_t Uint8
An unsigned 8-bit integer type.
Definition: SDL_stdinc.h:153
void SDL_free(void *mem)
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:82
#define AUDIO_S32LSB
Definition: SDL_audio.h:103
int paused
Definition: testoverlay2.c:149
void SDL_CalculateAudioSpec(SDL_AudioSpec *spec)
Definition: SDL_audio.c:1598
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:176
#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:78
#define AUDIO_S16MSB
Definition: SDL_audio.h:94
#define SDL_strdup
SDL_AudioFormat format
Definition: SDL_audio.h:171
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 floor
Definition: math_private.h:37
#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:429