SDL: Added SDL_GetAudioFormatName()

From 4cc3410dce50cefce98d3cf3cf1bc8eca83b862a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 8 Aug 2024 14:36:44 -0700
Subject: [PATCH] Added SDL_GetAudioFormatName()

Fixes https://github.com/libsdl-org/SDL/issues/10489
---
 include/SDL3/SDL_audio.h          | 12 ++++++
 src/audio/SDL_audio.c             | 19 +++++++++
 src/dynapi/SDL_dynapi.sym         |  1 +
 src/dynapi/SDL_dynapi_overrides.h |  1 +
 src/dynapi/SDL_dynapi_procs.h     |  1 +
 test/testautomation_audio.c       | 64 +++++++++++++++++++++++++++++++
 6 files changed, 98 insertions(+)

diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h
index d15bbaa6f5145..ba50d0f3da919 100644
--- a/include/SDL3/SDL_audio.h
+++ b/include/SDL3/SDL_audio.h
@@ -1996,6 +1996,18 @@ extern SDL_DECLSPEC int SDLCALL SDL_ConvertAudioSamples(const SDL_AudioSpec *src
                                                     Uint8 **dst_data,
                                                     int *dst_len);
 
+/**
+ * Get the human readable name of an audio format.
+ *
+ * \param format the audio format to query.
+ * \returns the human readable name of the specified audio format or
+ *          "SDL_AUDIO_UNKNOWN" if the format isn't recognized.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern SDL_DECLSPEC const char * SDLCALL SDL_GetAudioFormatName(SDL_AudioFormat format);
 
 /**
  * Get the appropriate memset value for silencing an audio format.
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index b27df57058439..cb907807a0933 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -2173,6 +2173,25 @@ const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format)
     return &format_list[0][NUM_FORMATS]; // not found; return what looks like a list with only a zero in it.
 }
 
+const char *SDL_GetAudioFormatName(SDL_AudioFormat format)
+{
+    switch (format) {
+#define CASE(X) \
+    case X: return #X;
+    CASE(SDL_AUDIO_U8)
+    CASE(SDL_AUDIO_S8)
+    CASE(SDL_AUDIO_S16LE)
+    CASE(SDL_AUDIO_S16BE)
+    CASE(SDL_AUDIO_S32LE)
+    CASE(SDL_AUDIO_S32BE)
+    CASE(SDL_AUDIO_F32LE)
+    CASE(SDL_AUDIO_F32BE)
+#undef CASE
+    default:
+        return "SDL_AUDIO_UNKNOWN";
+    }
+}
+
 int SDL_GetSilenceValueForFormat(SDL_AudioFormat format)
 {
     return (format == SDL_AUDIO_U8) ? 0x80 : 0x00;
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index e9cc39f89228f..7d4b9c23fb508 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -167,6 +167,7 @@ SDL3_0.0.0 {
     SDL_GetAudioDeviceGain;
     SDL_GetAudioDeviceName;
     SDL_GetAudioDriver;
+    SDL_GetAudioFormatName;
     SDL_GetAudioPlaybackDevices;
     SDL_GetAudioRecordingDevices;
     SDL_GetAudioStreamAvailable;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index cc28c71b32761..5212a3b107c79 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -192,6 +192,7 @@
 #define SDL_GetAudioDeviceGain SDL_GetAudioDeviceGain_REAL
 #define SDL_GetAudioDeviceName SDL_GetAudioDeviceName_REAL
 #define SDL_GetAudioDriver SDL_GetAudioDriver_REAL
+#define SDL_GetAudioFormatName SDL_GetAudioFormatName_REAL
 #define SDL_GetAudioPlaybackDevices SDL_GetAudioPlaybackDevices_REAL
 #define SDL_GetAudioRecordingDevices SDL_GetAudioRecordingDevices_REAL
 #define SDL_GetAudioStreamAvailable SDL_GetAudioStreamAvailable_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 480b38d1c10d9..2deddd0213f27 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -212,6 +212,7 @@ SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceFormat,(SDL_AudioDeviceID a, SDL_AudioSpec
 SDL_DYNAPI_PROC(float,SDL_GetAudioDeviceGain,(SDL_AudioDeviceID a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetAudioDeviceName,(SDL_AudioDeviceID a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetAudioDriver,(int a),(a),return)
+SDL_DYNAPI_PROC(const char*,SDL_GetAudioFormatName,(SDL_AudioFormat a),(a),return)
 SDL_DYNAPI_PROC(SDL_AudioDeviceID*,SDL_GetAudioPlaybackDevices,(int *a),(a),return)
 SDL_DYNAPI_PROC(SDL_AudioDeviceID*,SDL_GetAudioRecordingDevices,(int *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetAudioStreamAvailable,(SDL_AudioStream *a),(a),return)
diff --git a/test/testautomation_audio.c b/test/testautomation_audio.c
index 8ff197492d7be..3b09c68ae2d5c 100644
--- a/test/testautomation_audio.c
+++ b/test/testautomation_audio.c
@@ -467,7 +467,14 @@ static const char *g_audioFormatsVerbose[] = {
     "SDL_AUDIO_S32LE", "SDL_AUDIO_S32BE",
     "SDL_AUDIO_F32LE", "SDL_AUDIO_F32BE"
 };
+static SDL_AudioFormat g_invalidAudioFormats[] = {
+    (SDL_AudioFormat)SDL_DEFINE_AUDIO_FORMAT(SDL_AUDIO_MASK_SIGNED, SDL_AUDIO_MASK_BIG_ENDIAN, SDL_AUDIO_MASK_FLOAT, SDL_AUDIO_MASK_BITSIZE)
+};
+static const char *g_invalidAudioFormatsVerbose[] = {
+    "SDL_AUDIO_UNKNOWN"
+};
 static const int g_numAudioFormats = SDL_arraysize(g_audioFormats);
+static const int g_numInvalidAudioFormats = SDL_arraysize(g_invalidAudioFormats);
 static Uint8 g_audioChannels[] = { 1, 2, 4, 6 };
 static const int g_numAudioChannels = SDL_arraysize(g_audioChannels);
 static int g_audioFrequencies[] = { 11025, 22050, 44100, 48000 };
@@ -483,6 +490,58 @@ SDL_COMPILE_TIME_ASSERT(SDL_AUDIO_S32BE_FORMAT, SDL_AUDIO_S32BE == (SDL_AUDIO_S3
 SDL_COMPILE_TIME_ASSERT(SDL_AUDIO_F32LE_FORMAT, SDL_AUDIO_F32LE == (SDL_AUDIO_BITSIZE(32) | SDL_AUDIO_MASK_FLOAT | SDL_AUDIO_MASK_SIGNED));
 SDL_COMPILE_TIME_ASSERT(SDL_AUDIO_F32BE_FORMAT, SDL_AUDIO_F32BE == (SDL_AUDIO_F32LE | SDL_AUDIO_MASK_BIG_ENDIAN));
 
+/**
+ * Call to SDL_GetAudioFormatName
+ *
+ * \sa SDL_GetAudioFormatName
+ */
+static int audio_getAudioFormatName(void *arg)
+{
+    const char *error;
+    int i;
+    SDL_AudioFormat format;
+    const char *result;
+
+    /* audio formats */
+    for (i = 0; i < g_numAudioFormats; i++) {
+        format = g_audioFormats[i];
+        SDLTest_Log("Audio Format: %s (%d)", g_audioFormatsVerbose[i], format);
+
+        /* Get name of format */
+        result = SDL_GetAudioFormatName(format);
+        SDLTest_AssertPass("Call to SDL_GetAudioFormatName()");
+        SDLTest_AssertCheck(result != NULL, "Verify result is not NULL");
+        if (result != NULL) {
+            SDLTest_AssertCheck(result[0] != '\0', "Verify result is non-empty");
+            SDLTest_AssertCheck(SDL_strcmp(result, g_audioFormatsVerbose[i]) == 0,
+                                "Verify result text; expected: %s, got %s", g_audioFormatsVerbose[i], result);
+        }
+    }
+
+    /* Negative cases */
+
+    /* Invalid Formats */
+    SDL_ClearError();
+    SDLTest_AssertPass("Call to SDL_ClearError()");
+    for (i = 0; i < g_numInvalidAudioFormats; i++) {
+        format = g_invalidAudioFormats[i];
+        result = SDL_GetAudioFormatName(format);
+        SDLTest_AssertPass("Call to SDL_GetAudioFormatName(%d)", format);
+        SDLTest_AssertCheck(result != NULL, "Verify result is not NULL");
+        if (result != NULL) {
+            SDLTest_AssertCheck(result[0] != '\0',
+                                "Verify result is non-empty; got: %s", result);
+            SDLTest_AssertCheck(SDL_strcmp(result, g_invalidAudioFormatsVerbose[i]) == 0,
+                                "Validate name is UNKNOWN, expected: '%s', got: '%s'", g_invalidAudioFormatsVerbose[i], result);
+        }
+        error = SDL_GetError();
+        SDLTest_AssertPass("Call to SDL_GetError()");
+        SDLTest_AssertCheck(error == NULL || error[0] == '\0', "Validate that error message is empty");
+    }
+
+    return TEST_COMPLETED;
+}
+
 /**
  * Builds various audio conversion structures
  *
@@ -1386,6 +1445,10 @@ static int audio_formatChange(void *arg)
 /* ================= Test Case References ================== */
 
 /* Audio test cases */
+static const SDLTest_TestCaseReference audioTestGetAudioFormatName = {
+    audio_getAudioFormatName, "audio_getAudioFormatName", "Call to SDL_GetAudioFormatName", TEST_ENABLED
+};
+
 static const SDLTest_TestCaseReference audioTest1 = {
     audio_enumerateAndNameAudioDevices, "audio_enumerateAndNameAudioDevices", "Enumerate and name available audio devices (playback and recording)", TEST_ENABLED
 };
@@ -1462,6 +1525,7 @@ static const SDLTest_TestCaseReference audioTest18 = {
 
 /* Sequence of Audio test cases */
 static const SDLTest_TestCaseReference *audioTests[] = {
+    &audioTestGetAudioFormatName,
     &audioTest1, &audioTest2, &audioTest3, &audioTest4, &audioTest5, &audioTest6,
     &audioTest7, &audioTest8, &audioTest9, &audioTest10, &audioTest11,
     &audioTest12, &audioTest13, &audioTest14, &audioTest15, &audioTest16,