| | |
| | | #include "../SDL_audio_c.h" |
| | | #include "../SDL_sysaudio.h" |
| | | #include "SDL_coreaudio.h" |
| | | #include "SDL_assert.h" |
| | | #include "../../thread/SDL_systhread.h" |
| | | |
| | | #define DEBUG_COREAUDIO 0 |
| | | |
| | | #define CHECK_RESULT(msg) \ |
| | | if (result != noErr) { \ |
| | | SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \ |
| | | return 0; \ |
| | | } |
| | | #if DEBUG_COREAUDIO |
| | | #define CHECK_RESULT(msg) \ |
| | | if (result != noErr) { \ |
| | | printf("COREAUDIO: Got error %d from '%s'!\n", (int) result, msg); \ |
| | | SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \ |
| | | return 0; \ |
| | | } |
| | | #else |
| | | #define CHECK_RESULT(msg) \ |
| | | if (result != noErr) { \ |
| | | SDL_SetError("CoreAudio error (%s): %d", msg, (int) result); \ |
| | | return 0; \ |
| | | } |
| | | #endif |
| | | |
| | | |
| | | #if MACOSX_COREAUDIO |
| | | static const AudioObjectPropertyAddress devlist_address = { |
| | |
| | | #endif |
| | | |
| | | |
| | | static int open_playback_devices = 0; |
| | | static int open_capture_devices = 0; |
| | | static int open_playback_devices; |
| | | static int open_capture_devices; |
| | | static int num_open_devices; |
| | | static SDL_AudioDevice **open_devices; |
| | | |
| | | #if !MACOSX_COREAUDIO |
| | | |
| | | static BOOL session_active = NO; |
| | | |
| | | static void pause_audio_devices() |
| | | { |
| | | int i; |
| | | |
| | | if (!open_devices) { |
| | | return; |
| | | } |
| | | |
| | | for (i = 0; i < num_open_devices; ++i) { |
| | | SDL_AudioDevice *device = open_devices[i]; |
| | | if (device->hidden->audioQueue && !device->hidden->interrupted) { |
| | | AudioQueuePause(device->hidden->audioQueue); |
| | | } |
| | | } |
| | | } |
| | | |
| | | static void resume_audio_devices() |
| | | { |
| | | int i; |
| | | |
| | | if (!open_devices) { |
| | | return; |
| | | } |
| | | |
| | | for (i = 0; i < num_open_devices; ++i) { |
| | | SDL_AudioDevice *device = open_devices[i]; |
| | | if (device->hidden->audioQueue && !device->hidden->interrupted) { |
| | | AudioQueueStart(device->hidden->audioQueue, NULL); |
| | | } |
| | | } |
| | | } |
| | | |
| | | static void interruption_begin(_THIS) |
| | | { |
| | |
| | | |
| | | @end |
| | | |
| | | static BOOL update_audio_session(_THIS, SDL_bool open) |
| | | static BOOL update_audio_session(_THIS, SDL_bool open, SDL_bool allow_playandrecord) |
| | | { |
| | | @autoreleasepool { |
| | | AVAudioSession *session = [AVAudioSession sharedInstance]; |
| | | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; |
| | | |
| | | /* Set category to ambient by default so that other music continues playing. */ |
| | | NSString *category = AVAudioSessionCategoryAmbient; |
| | | NSString *category = AVAudioSessionCategoryPlayback; |
| | | NSString *mode = AVAudioSessionModeDefault; |
| | | NSUInteger options = 0; |
| | | NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers; |
| | | NSError *err = nil; |
| | | const char *hint; |
| | | |
| | | if (open_playback_devices && open_capture_devices) { |
| | | hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY); |
| | | if (hint) { |
| | | if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) { |
| | | category = AVAudioSessionCategoryAmbient; |
| | | } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) { |
| | | category = AVAudioSessionCategorySoloAmbient; |
| | | options &= ~AVAudioSessionCategoryOptionMixWithOthers; |
| | | } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 || |
| | | SDL_strcasecmp(hint, "playback") == 0) { |
| | | category = AVAudioSessionCategoryPlayback; |
| | | options &= ~AVAudioSessionCategoryOptionMixWithOthers; |
| | | } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayAndRecord") == 0 || |
| | | SDL_strcasecmp(hint, "playandrecord") == 0) { |
| | | if (allow_playandrecord) { |
| | | category = AVAudioSessionCategoryPlayAndRecord; |
| | | } |
| | | } |
| | | } else if (open_playback_devices && open_capture_devices) { |
| | | category = AVAudioSessionCategoryPlayAndRecord; |
| | | #if !TARGET_OS_TV |
| | | options = AVAudioSessionCategoryOptionDefaultToSpeaker; |
| | | #endif |
| | | } else if (open_capture_devices) { |
| | | category = AVAudioSessionCategoryRecord; |
| | | } |
| | | |
| | | #if !TARGET_OS_TV |
| | | if (category == AVAudioSessionCategoryPlayAndRecord) { |
| | | options |= AVAudioSessionCategoryOptionDefaultToSpeaker; |
| | | } |
| | | #endif |
| | | if (category == AVAudioSessionCategoryRecord || |
| | | category == AVAudioSessionCategoryPlayAndRecord) { |
| | | /* AVAudioSessionCategoryOptionAllowBluetooth isn't available in the SDK for |
| | | Apple TV but is still needed in order to output to Bluetooth devices. |
| | | */ |
| | | options |= 0x4; /* AVAudioSessionCategoryOptionAllowBluetooth; */ |
| | | } |
| | | if (category == AVAudioSessionCategoryPlayAndRecord) { |
| | | options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP | |
| | | AVAudioSessionCategoryOptionAllowAirPlay; |
| | | } |
| | | if (category == AVAudioSessionCategoryPlayback || |
| | | category == AVAudioSessionCategoryPlayAndRecord) { |
| | | options |= AVAudioSessionCategoryOptionDuckOthers; |
| | | } |
| | | |
| | | if ([session respondsToSelector:@selector(setCategory:mode:options:error:)]) { |
| | | if (![session.category isEqualToString:category] || session.categoryOptions != options) { |
| | | /* Stop the current session so we don't interrupt other application audio */ |
| | | pause_audio_devices(); |
| | | [session setActive:NO error:nil]; |
| | | session_active = NO; |
| | | |
| | | if (![session setCategory:category mode:mode options:options error:&err]) { |
| | | NSString *desc = err.description; |
| | | SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); |
| | | return NO; |
| | | } |
| | | } |
| | | } else { |
| | | const char *hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY); |
| | | if (hint) { |
| | | if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) { |
| | | category = AVAudioSessionCategoryAmbient; |
| | | } else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) { |
| | | category = AVAudioSessionCategorySoloAmbient; |
| | | } else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 || |
| | | SDL_strcasecmp(hint, "playback") == 0) { |
| | | category = AVAudioSessionCategoryPlayback; |
| | | if (![session.category isEqualToString:category]) { |
| | | /* Stop the current session so we don't interrupt other application audio */ |
| | | pause_audio_devices(); |
| | | [session setActive:NO error:nil]; |
| | | session_active = NO; |
| | | |
| | | if (![session setCategory:category error:&err]) { |
| | | NSString *desc = err.description; |
| | | SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); |
| | | return NO; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if ([session respondsToSelector:@selector(setCategory:mode:options:error:)]) { |
| | | if (![session setCategory:category mode:mode options:options error:&err]) { |
| | | NSString *desc = err.description; |
| | | SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); |
| | | return NO; |
| | | } |
| | | } else { |
| | | if (![session setCategory:category error:&err]) { |
| | | NSString *desc = err.description; |
| | | SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String); |
| | | return NO; |
| | | } |
| | | } |
| | | |
| | | if (open && (open_playback_devices + open_capture_devices) == 1) { |
| | | if ((open_playback_devices || open_capture_devices) && !session_active) { |
| | | if (![session setActive:YES error:&err]) { |
| | | if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable && |
| | | category == AVAudioSessionCategoryPlayAndRecord) { |
| | | return update_audio_session(this, open, SDL_FALSE); |
| | | } |
| | | |
| | | NSString *desc = err.description; |
| | | SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String); |
| | | return NO; |
| | | } |
| | | } else if (!open_playback_devices && !open_capture_devices) { |
| | | session_active = YES; |
| | | resume_audio_devices(); |
| | | } else if (!open_playback_devices && !open_capture_devices && session_active) { |
| | | pause_audio_devices(); |
| | | [session setActive:NO error:nil]; |
| | | session_active = NO; |
| | | } |
| | | |
| | | if (open) { |
| | |
| | | |
| | | this->hidden->interruption_listener = CFBridgingRetain(listener); |
| | | } else { |
| | | if (this->hidden->interruption_listener != NULL) { |
| | | SDLInterruptionListener *listener = nil; |
| | | listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener); |
| | | [center removeObserver:listener]; |
| | | @synchronized (listener) { |
| | | listener.device = NULL; |
| | | } |
| | | SDLInterruptionListener *listener = nil; |
| | | listener = (SDLInterruptionListener *) CFBridgingRelease(this->hidden->interruption_listener); |
| | | [center removeObserver:listener]; |
| | | @synchronized (listener) { |
| | | listener.device = NULL; |
| | | } |
| | | } |
| | | } |
| | |
| | | if (!SDL_AtomicGet(&this->enabled) || SDL_AtomicGet(&this->paused)) { |
| | | /* Supply silence if audio is not enabled or paused */ |
| | | SDL_memset(inBuffer->mAudioData, this->spec.silence, inBuffer->mAudioDataBytesCapacity); |
| | | } else if (this->stream ) { |
| | | } else if (this->stream) { |
| | | UInt32 remaining = inBuffer->mAudioDataBytesCapacity; |
| | | Uint8 *ptr = (Uint8 *) inBuffer->mAudioData; |
| | | |
| | | while (remaining > 0) { |
| | | if ( SDL_AudioStreamAvailable(this->stream) == 0 ) { |
| | | if (SDL_AudioStreamAvailable(this->stream) == 0) { |
| | | /* Generate the data */ |
| | | SDL_LockMutex(this->mixer_lock); |
| | | (*this->callbackspec.callback)(this->callbackspec.userdata, |
| | |
| | | this->hidden->bufferOffset = 0; |
| | | SDL_AudioStreamPut(this->stream, this->hidden->buffer, this->hidden->bufferSize); |
| | | } |
| | | if ( SDL_AudioStreamAvailable(this->stream) > 0 ) { |
| | | if (SDL_AudioStreamAvailable(this->stream) > 0) { |
| | | int got; |
| | | UInt32 len = SDL_AudioStreamAvailable(this->stream); |
| | | if ( len > remaining ) |
| | | if (len > remaining) |
| | | len = remaining; |
| | | got = SDL_AudioStreamGet(this->stream, ptr, len); |
| | | SDL_assert((got < 0) || (got == len)); |
| | |
| | | static void |
| | | inputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, |
| | | const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions, |
| | | const AudioStreamPacketDescription *inPacketDescs ) |
| | | const AudioStreamPacketDescription *inPacketDescs) |
| | | { |
| | | SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; |
| | | |
| | |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | /* macOS calls this when the default device changed (if we have a default device open). */ |
| | | static OSStatus |
| | | default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData) |
| | | { |
| | | SDL_AudioDevice *this = (SDL_AudioDevice *) inUserData; |
| | | #if DEBUG_COREAUDIO |
| | | printf("COREAUDIO: default device changed for SDL audio device %p!\n", this); |
| | | #endif |
| | | SDL_AtomicSet(&this->hidden->device_change_flag, 1); /* let the audioqueue thread pick up on this when safe to do so. */ |
| | | return noErr; |
| | | } |
| | | #endif |
| | | |
| | | static void |
| | | COREAUDIO_CloseDevice(_THIS) |
| | | { |
| | | const SDL_bool iscapture = this->iscapture; |
| | | int i; |
| | | |
| | | /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ |
| | | /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ |
| | | #if MACOSX_COREAUDIO |
| | | /* Fire a callback if the device stops being "alive" (disconnected, etc). */ |
| | | AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); |
| | | if (this->handle != NULL) { /* we don't register this listener for default devices. */ |
| | | AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); |
| | | } |
| | | #endif |
| | | |
| | | if (iscapture) { |
| | |
| | | } |
| | | |
| | | #if !MACOSX_COREAUDIO |
| | | update_audio_session(this, SDL_FALSE); |
| | | update_audio_session(this, SDL_FALSE, SDL_TRUE); |
| | | #endif |
| | | |
| | | for (i = 0; i < num_open_devices; ++i) { |
| | | if (open_devices[i] == this) { |
| | | --num_open_devices; |
| | | if (i < num_open_devices) { |
| | | SDL_memmove(&open_devices[i], &open_devices[i+1], sizeof(open_devices[i])*(num_open_devices - i)); |
| | | } |
| | | break; |
| | | } |
| | | } |
| | | if (num_open_devices == 0) { |
| | | SDL_free(open_devices); |
| | | open_devices = NULL; |
| | | } |
| | | |
| | | /* if callback fires again, feed silence; don't call into the app. */ |
| | | SDL_AtomicSet(&this->paused, 1); |
| | |
| | | this->hidden->deviceID = devid; |
| | | return 1; |
| | | } |
| | | |
| | | static int |
| | | assign_device_to_audioqueue(_THIS) |
| | | { |
| | | const AudioObjectPropertyAddress prop = { |
| | | kAudioDevicePropertyDeviceUID, |
| | | this->iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, |
| | | kAudioObjectPropertyElementMaster |
| | | }; |
| | | |
| | | OSStatus result; |
| | | CFStringRef devuid; |
| | | UInt32 devuidsize = sizeof (devuid); |
| | | result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid); |
| | | CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)"); |
| | | result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); |
| | | CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)"); |
| | | |
| | | return 1; |
| | | } |
| | | #endif |
| | | |
| | | static int |
| | |
| | | CHECK_RESULT("AudioQueueNewOutput"); |
| | | } |
| | | |
| | | #if MACOSX_COREAUDIO |
| | | { |
| | | const AudioObjectPropertyAddress prop = { |
| | | kAudioDevicePropertyDeviceUID, |
| | | iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, |
| | | kAudioObjectPropertyElementMaster |
| | | }; |
| | | CFStringRef devuid; |
| | | UInt32 devuidsize = sizeof (devuid); |
| | | result = AudioObjectGetPropertyData(this->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid); |
| | | CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)"); |
| | | result = AudioQueueSetProperty(this->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize); |
| | | CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)"); |
| | | #if MACOSX_COREAUDIO |
| | | if (!assign_device_to_audioqueue(this)) { |
| | | return 0; |
| | | } |
| | | |
| | | /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ |
| | | /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ |
| | | /* Fire a callback if the device stops being "alive" (disconnected, etc). */ |
| | | AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); |
| | | } |
| | | #endif |
| | | /* only listen for unplugging on specific devices, not the default device, as that should |
| | | switch to a different device (or hang out silently if there _is_ no other device). */ |
| | | if (this->handle != NULL) { |
| | | /* !!! FIXME: what does iOS do when a bluetooth audio device vanishes? Headphones unplugged? */ |
| | | /* !!! FIXME: (we only do a "default" device on iOS right now...can we do more?) */ |
| | | /* Fire a callback if the device stops being "alive" (disconnected, etc). */ |
| | | /* If this fails, oh well, we won't notice a device had an extraordinary event take place. */ |
| | | AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this); |
| | | } |
| | | #endif |
| | | |
| | | /* Calculate the final parameters for this audio specification */ |
| | | SDL_CalculateAudioSpec(&this->spec); |
| | |
| | | numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2); |
| | | } |
| | | |
| | | this->hidden->numAudioBuffers = numAudioBuffers; |
| | | this->hidden->audioBuffer = SDL_calloc(1, sizeof (AudioQueueBufferRef) * numAudioBuffers); |
| | | if (this->hidden->audioBuffer == NULL) { |
| | | SDL_OutOfMemory(); |
| | |
| | | CHECK_RESULT("AudioQueueAllocateBuffer"); |
| | | SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity); |
| | | this->hidden->audioBuffer[i]->mAudioDataByteSize = this->hidden->audioBuffer[i]->mAudioDataBytesCapacity; |
| | | /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */ |
| | | result = AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL); |
| | | CHECK_RESULT("AudioQueueEnqueueBuffer"); |
| | | } |
| | |
| | | audioqueue_thread(void *arg) |
| | | { |
| | | SDL_AudioDevice *this = (SDL_AudioDevice *) arg; |
| | | |
| | | #if MACOSX_COREAUDIO |
| | | const AudioObjectPropertyAddress default_device_address = { |
| | | this->iscapture ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice, |
| | | kAudioObjectPropertyScopeGlobal, |
| | | kAudioObjectPropertyElementMaster |
| | | }; |
| | | |
| | | if (this->handle == NULL) { /* opened the default device? Register to know if the user picks a new default. */ |
| | | /* we don't care if this fails; we just won't change to new default devices, but we still otherwise function in this case. */ |
| | | AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, this); |
| | | } |
| | | #endif |
| | | |
| | | const int rc = prepare_audioqueue(this); |
| | | if (!rc) { |
| | | this->hidden->thread_error = SDL_strdup(SDL_GetError()); |
| | |
| | | |
| | | /* init was successful, alert parent thread and start running... */ |
| | | SDL_SemPost(this->hidden->ready_semaphore); |
| | | |
| | | while (!SDL_AtomicGet(&this->hidden->shutdown)) { |
| | | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1); |
| | | |
| | | #if MACOSX_COREAUDIO |
| | | if ((this->handle == NULL) && SDL_AtomicGet(&this->hidden->device_change_flag)) { |
| | | SDL_AtomicSet(&this->hidden->device_change_flag, 0); |
| | | |
| | | #if DEBUG_COREAUDIO |
| | | printf("COREAUDIO: audioqueue_thread is trying to switch to new default device!\n"); |
| | | #endif |
| | | |
| | | /* if any of this fails, there's not much to do but wait to see if the user gives up |
| | | and quits (flagging the audioqueue for shutdown), or toggles to some other system |
| | | output device (in which case we'll try again). */ |
| | | const AudioDeviceID prev_devid = this->hidden->deviceID; |
| | | if (prepare_device(this, this->handle, this->iscapture) && (prev_devid != this->hidden->deviceID)) { |
| | | AudioQueueStop(this->hidden->audioQueue, 1); |
| | | if (assign_device_to_audioqueue(this)) { |
| | | int i; |
| | | for (i = 0; i < this->hidden->numAudioBuffers; i++) { |
| | | SDL_memset(this->hidden->audioBuffer[i]->mAudioData, this->spec.silence, this->hidden->audioBuffer[i]->mAudioDataBytesCapacity); |
| | | /* !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data? */ |
| | | AudioQueueEnqueueBuffer(this->hidden->audioQueue, this->hidden->audioBuffer[i], 0, NULL); |
| | | } |
| | | AudioQueueStart(this->hidden->audioQueue, NULL); |
| | | } |
| | | } |
| | | } |
| | | #endif |
| | | } |
| | | |
| | | if (!this->iscapture) { /* Drain off any pending playback. */ |
| | | const CFTimeInterval secs = (((this->spec.size / (SDL_AUDIO_BITSIZE(this->spec.format) / 8)) / this->spec.channels) / ((CFTimeInterval) this->spec.freq)) * 2.0; |
| | | CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0); |
| | | } |
| | | |
| | | #if MACOSX_COREAUDIO |
| | | if (this->handle == NULL) { |
| | | /* we don't care if this fails; we just won't change to new default devices, but we still otherwise function in this case. */ |
| | | AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_device_address, default_device_changed, this); |
| | | } |
| | | #endif |
| | | |
| | | return 0; |
| | | } |
| | |
| | | AudioStreamBasicDescription *strdesc; |
| | | SDL_AudioFormat test_format = SDL_FirstAudioFormat(this->spec.format); |
| | | int valid_datatype = 0; |
| | | SDL_AudioDevice **new_open_devices; |
| | | |
| | | /* Initialize all variables that we clean on shutdown */ |
| | | this->hidden = (struct SDL_PrivateAudioData *) |
| | |
| | | open_playback_devices++; |
| | | } |
| | | |
| | | new_open_devices = (SDL_AudioDevice **)SDL_realloc(open_devices, sizeof(open_devices[0]) * (num_open_devices + 1)); |
| | | if (new_open_devices) { |
| | | open_devices = new_open_devices; |
| | | open_devices[num_open_devices++] = this; |
| | | } |
| | | |
| | | #if !MACOSX_COREAUDIO |
| | | if (!update_audio_session(this, SDL_TRUE)) { |
| | | if (!update_audio_session(this, SDL_TRUE, SDL_TRUE)) { |
| | | return -1; |
| | | } |
| | | |
| | |
| | | this->spec.channels = session.preferredOutputNumberOfChannels; |
| | | } |
| | | #else |
| | | /* Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS */ |
| | | /* Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS */ |
| | | #endif /* TARGET_OS_TV */ |
| | | } |
| | | #endif |