SDL: Add macOS coreaudio support for SDL_GetDefaultAudioInfo

From 52534bb800bd726564c26fc3308634756a53dadf Mon Sep 17 00:00:00 2001
From: Jarod Hillman <[EMAIL REDACTED]>
Date: Fri, 23 Sep 2022 16:16:28 -0400
Subject: [PATCH] Add macOS coreaudio support for SDL_GetDefaultAudioInfo

---
 src/audio/coreaudio/SDL_coreaudio.m | 126 ++++++++++++++++++++++++++++
 test/testsurround.c                 |  24 ++++++
 2 files changed, 150 insertions(+)

diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m
index 4d35756579d4..34a0a18afb30 100644
--- a/src/audio/coreaudio/SDL_coreaudio.m
+++ b/src/audio/coreaudio/SDL_coreaudio.m
@@ -1145,6 +1145,131 @@ output device (in which case we'll try again). */
     return (this->hidden->thread != NULL) ? 0 : -1;
 }
 
+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");
+    }
+    
+    
+    // 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';
+    }
+    
+    if (name) {
+        *name = devname;
+    }
+    
+    // Uses the Device ID to get the spec
+    SDL_zero(*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;
+}
+
 static void
 COREAUDIO_Deinitialize(void)
 {
@@ -1162,6 +1287,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/testsurround.c b/test/testsurround.c
index 1537b35fe223..ff55af7b34ba 100644
--- a/test/testsurround.c
+++ b/test/testsurround.c
@@ -135,6 +135,8 @@ fill_buffer(void* unused, Uint8* stream, int len)
 int
 main(int argc, char *argv[])
 {
+    char *deviceName;
+    SDL_AudioSpec spec;
     int i;
 
     /* Enable standard application logging */
@@ -199,6 +201,28 @@ main(int argc, char *argv[])
         SDL_CloseAudioDevice(dev);
     }
 
+    if (SDL_GetDefaultAudioInfo(&deviceName, &spec, 0)) {
+        SDL_Log("Error when calling SDL_GetDefaultAudioInfo: %s\n", SDL_GetError());
+    } else {
+        SDL_Log("Default audio output info found!\n");
+        SDL_Log("Name: %s\n", 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_free(deviceName);
+    }
+    
+    if (SDL_GetDefaultAudioInfo(&deviceName, &spec, 1)) {
+        SDL_Log("Error when calling SDL_GetDefaultAudioInfo: %s\n", SDL_GetError());
+    } else {
+        SDL_Log("Default audio capture info found!\n");
+        SDL_Log("Name: %s\n", 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_free(deviceName);
+    }
+
     SDL_Quit();
     return 0;
 }