SDL: coreaudio: Add support for SDL_GetDefaultAudioInfo (#6277)

From 40893821f2b9e9826ec12e7c879cfb2d14ac5068 Mon Sep 17 00:00:00 2001
From: Jarod Hillman <[EMAIL REDACTED]>
Date: Thu, 29 Sep 2022 10:33:07 -0400
Subject: [PATCH] coreaudio: Add support for SDL_GetDefaultAudioInfo (#6277)

Co-authored-by: Ethan Lee <flibitijibibo@gmail.com>
Co-authored-by: Ozkan Sezer <sezeroz@gmail.com>
---
 src/audio/coreaudio/SDL_coreaudio.m | 136 ++++++++++++++++++++++++++++
 test/testaudioinfo.c                |  23 +++++
 2 files changed, 159 insertions(+)

diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m
index de8f72106190..1a6871904d8f 100644
--- a/src/audio/coreaudio/SDL_coreaudio.m
+++ b/src/audio/coreaudio/SDL_coreaudio.m
@@ -1151,6 +1151,141 @@ output device (in which case we'll try again). */
     return (this->hidden->thread != NULL) ? 0 : -1;
 }
 
+#if !MACOSX_COREAUDIO
+static int
+COREAUDIO_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture)
+{
+    AVAudioSession* session = [AVAudioSession sharedInstance];
+
+    if (name != NULL) {
+        *name = NULL;
+    }
+    SDL_zerop(spec);
+    spec->freq = [session sampleRate];
+    spec->channels = [session outputNumberOfChannels];
+    return 0;
+}
+#else /* MACOSX_COREAUDIO */
+static int
+COREAUDIO_GetDefaultAudioInfo(char **name, SDL_AudioSpec *spec, int iscapture)
+{
+    AudioDeviceID devid;
+    AudioBufferList *buflist;
+    OSStatus result;
+    UInt32 size;
+    CFStringRef cfstr;
+    char *devname;
+    int usable;
+    double sampleRate;
+
+    AudioObjectPropertyAddress addr = {
+        iscapture ? kAudioHardwarePropertyDefaultInputDevice
+                  : kAudioHardwarePropertyDefaultOutputDevice,
+        iscapture ? kAudioDevicePropertyScopeInput
+                  : kAudioDevicePropertyScopeOutput,
+        kAudioObjectPropertyElementMaster
+    };
+    AudioObjectPropertyAddress nameaddr = {
+        kAudioObjectPropertyName,
+        iscapture ? kAudioDevicePropertyScopeInput
+                  : kAudioDevicePropertyScopeOutput,
+        kAudioObjectPropertyElementMaster
+    };
+    AudioObjectPropertyAddress freqaddr = {
+        kAudioDevicePropertyNominalSampleRate,
+        iscapture ? kAudioDevicePropertyScopeInput
+                  : kAudioDevicePropertyScopeOutput,
+        kAudioObjectPropertyElementMaster
+    };
+    AudioObjectPropertyAddress bufaddr = {
+        kAudioDevicePropertyStreamConfiguration,
+        iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+        kAudioObjectPropertyElementMaster
+    };
+
+    /* Get the Device ID */
+    cfstr = NULL;
+    size = sizeof (AudioDeviceID);
+    result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
+                                        0, NULL, &size, &devid);
+
+    if (result != noErr) {
+        return SDL_SetError("%s: Default Device ID not found", "coreaudio");
+    }
+
+    if (name != NULL) {
+        /* Use the Device ID to get the name */
+        size = sizeof (CFStringRef);
+        result = AudioObjectGetPropertyData(devid, &nameaddr, 0, NULL, &size, &cfstr);
+
+        if (result != noErr) {
+            return SDL_SetError("%s: Default Device Name not found", "coreaudio");
+        }
+
+        CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
+                                                        kCFStringEncodingUTF8);
+        devname = (char *) SDL_malloc(len + 1);
+        usable = ((devname != NULL) &&
+                  (CFStringGetCString(cfstr, devname, len + 1, kCFStringEncodingUTF8)));
+        CFRelease(cfstr);
+
+        if (usable) {
+            usable = 0;
+            len = strlen(devname);
+            /* Some devices have whitespace at the end...trim it. */
+            while ((len > 0) && (devname[len - 1] == ' ')) {
+                len--;
+                usable = len;
+            }
+        }
+
+        if (usable) {
+            devname[len] = '\0';
+        }
+        *name = devname;
+    }
+
+    /* Uses the Device ID to get the spec */
+    SDL_zerop(spec);
+
+    sampleRate = 0;
+    size = sizeof(sampleRate);
+    result = AudioObjectGetPropertyData(devid, &freqaddr, 0, NULL, &size, &sampleRate);
+
+    if (result != noErr) {
+        return SDL_SetError("%s: Default Device Sample Rate not found", "coreaudio");
+    }
+
+    spec->freq = (int) sampleRate;
+
+    result = AudioObjectGetPropertyDataSize(devid, &bufaddr, 0, NULL, &size);
+    if (result != noErr)
+        return SDL_SetError("%s: Default Device Data Size not found", "coreaudio");
+
+    buflist = (AudioBufferList *) SDL_malloc(size);
+    if (buflist == NULL)
+        return SDL_SetError("%s: Default Device Buffer List not found", "coreaudio");
+
+    result = AudioObjectGetPropertyData(devid, &bufaddr, 0, NULL,
+                                        &size, buflist);
+
+    if (result == noErr) {
+        UInt32 j;
+        for (j = 0; j < buflist->mNumberBuffers; j++) {
+            spec->channels += buflist->mBuffers[j].mNumberChannels;
+        }
+    }
+
+    SDL_free(buflist);
+
+    if (spec->channels == 0) {
+        return SDL_SetError("%s: Default Device has no channels!", "coreaudio");
+    }
+
+    return 0;
+}
+#endif /* MACOSX_COREAUDIO */
+
 static void
 COREAUDIO_Deinitialize(void)
 {
@@ -1168,6 +1303,7 @@ output device (in which case we'll try again). */
     impl->OpenDevice = COREAUDIO_OpenDevice;
     impl->CloseDevice = COREAUDIO_CloseDevice;
     impl->Deinitialize = COREAUDIO_Deinitialize;
+    impl->GetDefaultAudioInfo = COREAUDIO_GetDefaultAudioInfo;
 
 #if MACOSX_COREAUDIO
     impl->DetectDevices = COREAUDIO_DetectDevices;
diff --git a/test/testaudioinfo.c b/test/testaudioinfo.c
index d22aed270285..6f1e0c75dbf8 100644
--- a/test/testaudioinfo.c
+++ b/test/testaudioinfo.c
@@ -40,6 +40,8 @@ print_devices(int iscapture)
 int
 main(int argc, char **argv)
 {
+    char *deviceName;
+    SDL_AudioSpec spec;
     int n;
 
     /* Enable standard application logging */
@@ -69,6 +71,27 @@ main(int argc, char **argv)
     print_devices(0);
     print_devices(1);
 
+    if (SDL_GetDefaultAudioInfo(&deviceName, &spec, 0) < 0) {
+        SDL_Log("Error when calling SDL_GetDefaultAudioInfo: %s\n", SDL_GetError());
+    } else {
+        SDL_Log("Default Output Name: %s\n", deviceName != NULL ? deviceName : "unknown");
+        SDL_free(deviceName);
+        SDL_Log("Sampling Rate: %d\n", spec.freq);
+        SDL_Log("Number of Channels: %d\n", spec.channels);
+        SDL_Log("Audio Format: %d\n", spec.format);
+    }
+
+    if (SDL_GetDefaultAudioInfo(&deviceName, &spec, 1) < 0) {
+        SDL_Log("Error when calling SDL_GetDefaultAudioInfo: %s\n", SDL_GetError());
+    } else {
+        SDL_Log("Default Capture Name: %s\n", deviceName != NULL ? deviceName : "unknown");
+        SDL_free(deviceName);
+        SDL_Log("Sampling Rate: %d\n", spec.freq);
+        SDL_Log("Number of Channels: %d\n", spec.channels);
+        SDL_Log("Audio Format: %d\n", spec.format);
+    }
+
+
     SDL_Quit();
     return 0;
 }