@@ -313,32 +313,6 @@ static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_playback, SDL_Audi
313313
314314static bool session_active = false ;
315315
316- static bool PauseOneAudioDevice (SDL_AudioDevice *device, void *userdata)
317- {
318- if (device->hidden && device->hidden ->audioQueue && !device->hidden ->interrupted ) {
319- AudioQueuePause (device->hidden ->audioQueue );
320- }
321- return false ; // keep enumerating devices until we've paused them all.
322- }
323-
324- static void PauseAudioDevices (void )
325- {
326- (void ) SDL_FindPhysicalAudioDeviceByCallback (PauseOneAudioDevice, NULL );
327- }
328-
329- static bool ResumeOneAudioDevice (SDL_AudioDevice *device, void *userdata)
330- {
331- if (device->hidden && device->hidden ->audioQueue && !device->hidden ->interrupted ) {
332- AudioQueueStart (device->hidden ->audioQueue , NULL );
333- }
334- return false ; // keep enumerating devices until we've resumed them all.
335- }
336-
337- static void ResumeAudioDevices (void )
338- {
339- (void ) SDL_FindPhysicalAudioDeviceByCallback (ResumeOneAudioDevice, NULL );
340- }
341-
342316static void InterruptionBegin (SDL_AudioDevice *device)
343317{
344318 if (device != NULL && device->hidden != NULL && device->hidden ->audioQueue != NULL ) {
@@ -383,40 +357,22 @@ - (void)applicationBecameActive:(NSNotification *)note
383357
384358@end
385359
386- typedef struct
387- {
388- int playback;
389- int recording;
390- } CountOpenAudioDevicesData;
391360
392- static bool CountOpenAudioDevices (SDL_AudioDevice *device, void *userdata)
393- {
394- CountOpenAudioDevicesData *data = (CountOpenAudioDevicesData *) userdata;
395- if (device->hidden != NULL ) { // assume it's open if hidden != NULL
396- if (device->recording ) {
397- data->recording ++;
398- } else {
399- data->playback ++;
400- }
401- }
402- return false ; // keep enumerating until all devices have been checked.
403- }
361+ static SDL_AtomicInt open_playback_device_count;
362+ static SDL_AtomicInt open_recording_device_count;
404363
405- static bool UpdateAudioSession (SDL_AudioDevice *device, bool open, bool allow_playandrecord)
364+ static bool UpdateAudioSession (SDL_AudioDevice *device, bool allow_playandrecord)
406365{
407366 @autoreleasepool {
408367 AVAudioSession *session = [AVAudioSession sharedInstance ];
409- NSNotificationCenter *center = [NSNotificationCenter defaultCenter ];
410-
411368 NSString *category = AVAudioSessionCategoryPlayback;
412369 NSString *mode = AVAudioSessionModeDefault;
413370 NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers;
414371 NSError *err = nil ;
415372 const char *hint;
416373
417- CountOpenAudioDevicesData data;
418- SDL_zero (data);
419- (void ) SDL_FindPhysicalAudioDeviceByCallback (CountOpenAudioDevices, &data);
374+ const int opened_for_playback = SDL_GetAtomicInt (&open_playback_device_count);
375+ const int opened_for_recording = SDL_GetAtomicInt (&open_recording_device_count);
420376
421377 hint = SDL_GetHint (SDL_HINT_AUDIO_CATEGORY );
422378 if (hint) {
@@ -436,14 +392,14 @@ static bool UpdateAudioSession(SDL_AudioDevice *device, bool open, bool allow_pl
436392 category = AVAudioSessionCategoryPlayAndRecord;
437393 }
438394 }
439- } else if (data. playback && data. recording ) {
395+ } else if (opened_for_playback && opened_for_recording ) {
440396 if (allow_playandrecord) {
441397 category = AVAudioSessionCategoryPlayAndRecord;
442398 } else {
443399 // We already failed play and record with AVAudioSessionErrorCodeResourceNotAvailable
444400 return false ;
445401 }
446- } else if (data. recording ) {
402+ } else if (opened_for_recording ) {
447403 category = AVAudioSessionCategoryRecord;
448404 }
449405
@@ -469,72 +425,12 @@ static bool UpdateAudioSession(SDL_AudioDevice *device, bool open, bool allow_pl
469425 }
470426
471427 if (![session.category isEqualToString: category] || session.categoryOptions != options) {
472- // Stop the current session so we don't interrupt other application audio
473- PauseAudioDevices ();
474- [session setActive: NO error: nil ];
475- session_active = false ;
476-
477428 if (![session setCategory: category mode: mode options: options error: &err]) {
478429 NSString *desc = err.description ;
479430 SDL_SetError (" Could not set Audio Session category: %s " , desc.UTF8String );
480431 return false ;
481432 }
482433 }
483-
484- if ((data.playback || data.recording ) && !session_active) {
485- if (![session setActive: YES error: &err]) {
486- if ([err code ] == AVAudioSessionErrorCodeResourceNotAvailable &&
487- category == AVAudioSessionCategoryPlayAndRecord) {
488- if (UpdateAudioSession (device, open, false )) {
489- return true ;
490- } else {
491- return SDL_SetError (" Could not activate Audio Session: Resource not available" );
492- }
493- }
494-
495- NSString *desc = err.description ;
496- return SDL_SetError (" Could not activate Audio Session: %s " , desc.UTF8String );
497- }
498- session_active = true ;
499- ResumeAudioDevices ();
500- } else if (!data.playback && !data.recording && session_active) {
501- PauseAudioDevices ();
502- [session setActive: NO error: nil ];
503- session_active = false ;
504- }
505-
506- if (open) {
507- SDLInterruptionListener *listener = [SDLInterruptionListener new ];
508- listener.device = device;
509-
510- [center addObserver: listener
511- selector: @selector (audioSessionInterruption: )
512- name: AVAudioSessionInterruptionNotification
513- object: session];
514-
515- /* An interruption end notification is not guaranteed to be sent if
516- we were previously interrupted... resuming if needed when the app
517- becomes active seems to be the way to go. */
518- // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications.
519- [center addObserver: listener
520- selector: @selector (applicationBecameActive: )
521- name: UIApplicationDidBecomeActiveNotification
522- object: nil ];
523-
524- [center addObserver: listener
525- selector: @selector (applicationBecameActive: )
526- name: UIApplicationWillEnterForegroundNotification
527- object: nil ];
528-
529- device->hidden ->interruption_listener = CFBridgingRetain (listener);
530- } else {
531- SDLInterruptionListener *listener = nil ;
532- listener = (SDLInterruptionListener *)CFBridgingRelease (device->hidden ->interruption_listener );
533- [center removeObserver: listener];
534- @synchronized (listener) {
535- listener.device = NULL ;
536- }
537- }
538434 }
539435
540436 return true ;
@@ -640,7 +536,14 @@ static void COREAUDIO_CloseDevice(SDL_AudioDevice *device)
640536 }
641537
642538 #ifndef MACOSX_COREAUDIO
643- UpdateAudioSession (device, false , true );
539+ if (device->hidden ->interruption_listener ) {
540+ SDLInterruptionListener *listener = (SDLInterruptionListener *)CFBridgingRelease (device->hidden ->interruption_listener );
541+ device->hidden ->interruption_listener = NULL ;
542+ [[NSNotificationCenter defaultCenter ] removeObserver: listener];
543+ @synchronized (listener) {
544+ listener.device = NULL ;
545+ }
546+ }
644547 #endif
645548
646549 if (device->hidden ->ready_semaphore ) {
@@ -651,6 +554,18 @@ static void COREAUDIO_CloseDevice(SDL_AudioDevice *device)
651554 SDL_free (device->hidden ->audioBuffer );
652555 SDL_free (device->hidden ->thread_error );
653556 SDL_free (device->hidden );
557+
558+ #ifndef MACOSX_COREAUDIO
559+ SDL_AtomicDecRef (device->recording ? &open_recording_device_count : &open_playback_device_count);
560+
561+ SDL_assert (SDL_GetAtomicInt (&open_playback_device_count) >= 0 );
562+ SDL_assert (SDL_GetAtomicInt (&open_recording_device_count) >= 0 );
563+
564+ if (session_active && !SDL_GetAtomicInt (&open_playback_device_count) && !SDL_GetAtomicInt (&open_recording_device_count)) {
565+ [[AVAudioSession sharedInstance ] setActive: NO error: nil ];
566+ session_active = false ;
567+ }
568+ #endif
654569}
655570
656571#ifdef MACOSX_COREAUDIO
@@ -899,26 +814,33 @@ static bool COREAUDIO_OpenDevice(SDL_AudioDevice *device)
899814 }
900815
901816 #ifndef MACOSX_COREAUDIO
902- if (!UpdateAudioSession (device, true , true )) {
817+ SDL_AtomicIncRef (device->recording ? &open_recording_device_count : &open_playback_device_count);
818+ if (!UpdateAudioSession (device, true )) {
903819 return false ;
904820 }
905821
822+ AVAudioSession *session = [AVAudioSession sharedInstance ];
906823 // Stop CoreAudio from doing expensive audio rate conversion
907- @autoreleasepool {
908- AVAudioSession *session = [AVAudioSession sharedInstance ];
909- [session setPreferredSampleRate: device->spec.freq error: nil ];
910- device->spec .freq = (int )session.sampleRate ;
911- #ifdef SDL_PLATFORM_TVOS
912- if (device->recording ) {
913- [session setPreferredInputNumberOfChannels: device->spec.channels error: nil ];
914- device->spec .channels = (int )session.preferredInputNumberOfChannels ;
915- } else {
916- [session setPreferredOutputNumberOfChannels: device->spec.channels error: nil ];
917- device->spec .channels = (int )session.preferredOutputNumberOfChannels ;
824+ [session setPreferredSampleRate: device->spec.freq error: nil ];
825+ device->spec .freq = (int )session.sampleRate ;
826+ #ifdef SDL_PLATFORM_TVOS
827+ if (device->recording ) {
828+ [session setPreferredInputNumberOfChannels: device->spec.channels error: nil ];
829+ device->spec .channels = (int )session.preferredInputNumberOfChannels ;
830+ } else {
831+ [session setPreferredOutputNumberOfChannels: device->spec.channels error: nil ];
832+ device->spec .channels = (int )session.preferredOutputNumberOfChannels ;
833+ }
834+ #else
835+ // Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS
836+ #endif // SDL_PLATFORM_TVOS
837+
838+ if (!session_active) {
839+ NSError *err = nil ;
840+ if (![session setActive: YES error: &err]) {
841+ return SDL_SetError (" Could not activate Audio Session: %s " , err.description .UTF8String );
918842 }
919- #else
920- // Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS
921- #endif // SDL_PLATFORM_TVOS
843+ session_active = true ;
922844 }
923845 #endif
924846
@@ -992,13 +914,40 @@ static bool COREAUDIO_OpenDevice(SDL_AudioDevice *device)
992914 SDL_DestroySemaphore (device->hidden ->ready_semaphore );
993915 device->hidden ->ready_semaphore = NULL ;
994916
995- if (( device->hidden ->thread != NULL ) && (device-> hidden -> thread_error != NULL ) ) {
917+ if (device->hidden ->thread_error != NULL ) {
996918 SDL_WaitThread (device->hidden ->thread , NULL );
997919 device->hidden ->thread = NULL ;
998920 return SDL_SetError (" %s " , device->hidden ->thread_error );
999921 }
1000922
1001- return (device->hidden ->thread != NULL );
923+ #ifndef MACOSX_COREAUDIO
924+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter ];
925+ SDLInterruptionListener *listener = [SDLInterruptionListener new ];
926+ listener.device = device;
927+
928+ [center addObserver: listener
929+ selector: @selector (audioSessionInterruption: )
930+ name: AVAudioSessionInterruptionNotification
931+ object: session];
932+
933+ /* An interruption end notification is not guaranteed to be sent if
934+ we were previously interrupted... resuming if needed when the app
935+ becomes active seems to be the way to go. */
936+ // Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications.
937+ [center addObserver: listener
938+ selector: @selector (applicationBecameActive: )
939+ name: UIApplicationDidBecomeActiveNotification
940+ object: nil ];
941+
942+ [center addObserver: listener
943+ selector: @selector (applicationBecameActive: )
944+ name: UIApplicationWillEnterForegroundNotification
945+ object: nil ];
946+
947+ device->hidden ->interruption_listener = CFBridgingRetain (listener);
948+ #endif
949+
950+ return true ;
1002951}
1003952
1004953static void COREAUDIO_DeinitializeStart (void )
0 commit comments