diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h index d15bbaa6f5..ba50d0f3da 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 b27df57058..cb907807a0 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 e9cc39f892..7d4b9c23fb 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 cc28c71b32..5212a3b107 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 480b38d1c1..2deddd0213 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 8ff197492d..3b09c68ae2 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 @@ cleanup: /* ================= 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,