early-access version 1617

This commit is contained in:
pineappleEA
2021-04-20 21:40:33 +02:00
parent 242b6f6b49
commit f46563104f
510 changed files with 141726 additions and 62846 deletions

View File

@@ -31,7 +31,6 @@
#if MACOSX_COREAUDIO
#include <CoreAudio/CoreAudio.h>
#include <CoreServices/CoreServices.h>
#else
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIApplication.h>
@@ -47,6 +46,7 @@ struct SDL_PrivateAudioData
{
SDL_Thread *thread;
AudioQueueRef audioQueue;
int numAudioBuffers;
AudioQueueBufferRef *audioBuffer;
void *buffer;
UInt32 bufferOffset;
@@ -57,6 +57,7 @@ struct SDL_PrivateAudioData
SDL_atomic_t shutdown;
#if MACOSX_COREAUDIO
AudioDeviceID deviceID;
SDL_atomic_t device_change_flag;
#else
SDL_bool interrupted;
CFTypeRef interruption_listener;

View File

@@ -29,16 +29,25 @@
#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 = {
@@ -270,11 +279,47 @@ device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectP
#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)
{
if (this != NULL && this->hidden->audioQueue != NULL) {
@@ -321,61 +366,107 @@ static void interruption_end(_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) {
@@ -403,13 +494,11 @@ static BOOL update_audio_session(_THIS, SDL_bool 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;
}
}
}
@@ -431,12 +520,12 @@ outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffe
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,
@@ -445,10 +534,10 @@ outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffe
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));
@@ -494,7 +583,7 @@ outputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffe
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;
@@ -566,18 +655,32 @@ device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectProperty
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) {
@@ -587,9 +690,23 @@ COREAUDIO_CloseDevice(_THIS)
}
#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);
@@ -666,6 +783,26 @@ prepare_device(_THIS, void *handle, int iscapture)
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
@@ -686,26 +823,21 @@ prepare_audioqueue(_THIS)
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);
@@ -769,6 +901,7 @@ prepare_audioqueue(_THIS)
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();
@@ -784,6 +917,7 @@ prepare_audioqueue(_THIS)
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");
}
@@ -799,6 +933,20 @@ static int
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());
@@ -810,8 +958,36 @@ audioqueue_thread(void *arg)
/* 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. */
@@ -819,6 +995,13 @@ audioqueue_thread(void *arg)
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;
}
@@ -828,6 +1011,7 @@ COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
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 *)
@@ -845,8 +1029,14 @@ COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
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;
}
@@ -864,7 +1054,7 @@ COREAUDIO_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
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