SDL: Add SDL_ConvertAudioSamples() helper function

From 052b14eb654a18ed899325b03bc53fd1c62cde33 Mon Sep 17 00:00:00 2001
From: Sylvain <[EMAIL REDACTED]>
Date: Mon, 23 Jan 2023 08:24:49 +0100
Subject: [PATCH] Add SDL_ConvertAudioSamples() helper function

---
 WhatsNew.txt                      |  1 +
 docs/README-migration.md          | 27 ++++++------
 include/SDL3/SDL_audio.h          | 31 ++++++++++++++
 src/audio/SDL_audio.c             | 70 +++++++++++++++++++++++++++++++
 src/dynapi/SDL_dynapi.sym         |  1 +
 src/dynapi/SDL_dynapi_overrides.h |  1 +
 src/dynapi/SDL_dynapi_procs.h     |  1 +
 test/testresample.c               | 49 +++-------------------
 8 files changed, 124 insertions(+), 57 deletions(-)

diff --git a/WhatsNew.txt b/WhatsNew.txt
index 093928bbb2b3..08f6f323a671 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -24,3 +24,4 @@ General:
 * Added SDL_aligned_alloc() and SDL_aligned_free() to allocate and free memory with a given alignment
 * Added SDL_GetRenderVSync() to get vsync of the given renderer
 * Added SDL_PlayAudioDevice() to start audio playback
+* Added SDL_ConvertAudioSamples() to convert audio samples from one format to another
diff --git a/docs/README-migration.md b/docs/README-migration.md
index 0edbfb2fc424..519e1fad8758 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -54,29 +54,28 @@ SDL_PauseAudioDevice() is only used to pause audio playback. Use SDL_PlayAudioDe
 
 SDL_FreeWAV has been removed and calls can be replaced with SDL_free.
 
-SDL_AudioCVT interface is removed, SDL_AudioStream can be used instead.
+SDL_AudioCVT interface is removed, SDL_AudioStream interface or SDL_ConvertAudioSamples() helper function can be used.
 
 Code that used to look like this:
 ```c
     SDL_AudioCVT cvt;
-    SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq, spec.format, cvtchans, cvtfreq);
-    cvt.len = len;
-    cvt.buf = (Uint8 *) SDL_malloc(len * cvt.len_mult);
-    SDL_memcpy(cvt.buf, data, len);
+    SDL_BuildAudioCVT(&cvt, src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate);
+    cvt.len = src_len;
+    cvt.buf = (Uint8 *) SDL_malloc(src_len * cvt.len_mult);
+    SDL_memcpy(cvt.buf, src_data, src_len);
     SDL_ConvertAudio(&cvt);
     do_something(cvt.buf, cvt.len_cvt);

should be changed to:

-    SDL_AudioStream *stream = SDL_CreateAudioStream(spec.format, spec.channels, spec.freq, spec.format, cvtchans, cvtfreq);
-    int src_samplesize = (SDL_AUDIO_BITSIZE(spec.format) / 8) * spec.channels;
-    int src_len = len & ~(src_samplesize - 1); // need to be rounded to samplesize
-    SDL_PutAudioStreamData(stream, data, src_len);
-    SDL_FlushAudioStream(stream);
-    int dst_len = expected_dst_len & ~(dst_samplesize - 1); // need to be rounded to samplesize
-    Uint8 *dst_buf = (Uint8 *)SDL_malloc(dst_len);
-    int real_dst_len = SDL_GetAudioStreamData(stream, dst_buf, dst_len);
-    do_something(dst_buf, real_dst_len);
+    Uint8 *dst_data = NULL;
+    int dst_len = 0;
+    if (SDL_ConvertAudioSamples(src_format, src_channels, src_rate, src_len, src_data
+                                dst_format, dst_channels, dst_rate, &dst_len, &dst_data) < 0) {
+        /* error */
+    }
+    do_something(dst_data, dst_len);
+    SDL_free(dst_data);

diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h
index fe7bc7b61a22…28500ea16f26 100644
— a/include/SDL3/SDL_audio.h
+++ b/include/SDL3/SDL_audio.h
@@ -1113,6 +1113,37 @@ extern DECLSPEC void SDLCALL SDL_UnlockAudioDevice(SDL_AudioDeviceID dev);
*/
extern DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID dev);

+/**

    • Convert some audio data of one format to another format.
    • \param src_format The format of the source audio
    • \param src_channels The number of channels of the source audio
    • \param src_rate The sampling rate of the source audio
    • \param src_len The len of src_data
    • \param src_data The audio data to be converted
    • \param dst_format The format of the desired audio output
    • \param dst_channels The number of channels of the desired audio output
    • \param dst_rate The sampling rate of the desired audio output
    • \param dst_len Will be filled with the len of dst_data
    • \param dst_data Will be filled with a pointer to converted audio data, which should be freed with SDL_free().
    • \returns 0 on success or a negative error code on failure. On error, *dst_data will be NULL and so not allocated.
    • \since This function is available since SDL 3.0.0.
    • \sa SDL_CreateAudioStream
  • */
    +extern DECLSPEC int SDLCALL SDL_ConvertAudioSamples(SDL_AudioFormat src_format,
  •                                                Uint8 src_channels,
    
  •                                                int src_rate,
    
  •                                                int src_len,
    
  •                                                Uint8 *src_data,
    
  •                                                SDL_AudioFormat dst_format,
    
  •                                                Uint8 dst_channels,
    
  •                                                int dst_rate,
    
  •                                                int *dst_len,
    
  •                                                Uint8 **dst_data);
    

/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index 08a1160cbd1f…877895fc66a3 100644
— a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -1662,3 +1662,73 @@ void SDL_CalculateAudioSpec(SDL_AudioSpec *spec)
spec->size *= spec->samples;
}

+int SDL_ConvertAudioSamples(

  •    SDL_AudioFormat src_format, Uint8 src_channels, int src_rate, int src_len, Uint8 *src_data,
    
  •    SDL_AudioFormat dst_format, Uint8 dst_channels, int dst_rate, int *dst_len, Uint8 **dst_data)
    

+{

  • int ret = -1;
  • SDL_AudioStream *stream = NULL;
  • int src_samplesize, dst_samplesize;
  • int real_dst_len;
  • if (src_len < 0) {
  •    return SDL_InvalidParamError("src_len");
    
  • }
  • if (src_data == NULL) {
  •    return SDL_InvalidParamError("src_data");
    
  • }
  • if (dst_len == NULL) {
  •    return SDL_InvalidParamError("dst_len");
    
  • }
  • if (dst_data == NULL) {
  •    return SDL_InvalidParamError("dst_data");
    
  • }
  • *dst_len = 0;
  • *dst_data = NULL;
  • stream = SDL_CreateAudioStream(src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate);
  • if (stream == NULL) {
  •    goto end;
    
  • }
  • src_samplesize = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels;
  • dst_samplesize = (SDL_AUDIO_BITSIZE(dst_format) / 8) * dst_channels;
  • src_len &= ~(src_samplesize - 1);
  • *dst_len = dst_samplesize * (src_len / src_samplesize);
  • if (src_rate < dst_rate) {
  •    const double mult = ((double)dst_rate) / ((double)src_rate);
    
  •    *dst_len *= (int) SDL_ceil(mult);
    
  • }
  • *dst_len &= ~(dst_samplesize - 1);
  • *dst_data = (Uint8 *)SDL_malloc(*dst_len);
  • if (*dst_data == NULL) {
  •    goto end;
    
  • }
  • if (SDL_PutAudioStreamData(stream, src_data, src_len) < 0 ||
  •    SDL_FlushAudioStream(stream) < 0) {
    
  •    goto end;
    
  • }
  • real_dst_len = SDL_GetAudioStreamData(stream, *dst_data, *dst_len);
  • if (real_dst_len < 0) {
  •    goto end;
    
  • }
  • *dst_len = real_dst_len;
  • ret = 0;

+end:

  • if (ret != 0) {
  •    SDL_free(*dst_data);
    
  •    *dst_len = 0;
    
  •    *dst_data = NULL;
    
  • }
  • SDL_DestroyAudioStream(stream);
  • return ret;
    +}
    diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
    index b9d980ae2660…74e6d25ef24a 100644
    — a/src/dynapi/SDL_dynapi.sym
    +++ b/src/dynapi/SDL_dynapi.sym
    @@ -841,6 +841,7 @@ SDL3_0.0.0 {
    SDL_PlayAudioDevice;
    SDL_aligned_alloc;
    SDL_aligned_free;
  • SDL_ConvertAudioSamples;

    extra symbols go here (don’t modify this line)

local: *;
};
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index b74093cdef0e…5c82e562dafe 100644
— a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -868,3 +868,4 @@
#define SDL_PlayAudioDevice SDL_PlayAudioDevice_REAL
#define SDL_aligned_alloc SDL_aligned_alloc_REAL
#define SDL_aligned_free SDL_aligned_free_REAL
+#define SDL_ConvertAudioSamples SDL_ConvertAudioSamples_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 8e9b300e9ec8…2128ef2c3b00 100644
— a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -913,3 +913,4 @@ SDL_DYNAPI_PROC(int,SDL_GetRenderVSync,(SDL_Renderer *a, int b),(a,b),return)
SDL_DYNAPI_PROC(void,SDL_PlayAudioDevice,(SDL_AudioDeviceID a),(a),)
SDL_DYNAPI_PROC(void
,SDL_aligned_alloc,(size_t a, size_t b),(a,b),return)
SDL_DYNAPI_PROC(void,SDL_aligned_free,(void *a),(a),)
+SDL_DYNAPI_PROC(int,SDL_ConvertAudioSamples,(SDL_AudioFormat a, Uint8 b, int c, int d, Uint8 *e, SDL_AudioFormat f, Uint8 g, int h, int *i, Uint8 **j),(a,b,c,d,e,f,g,h,i,j),return)
diff --git a/test/testresample.c b/test/testresample.c
index 6772f26944f6…15eb76c4917f 100644
— a/test/testresample.c
+++ b/test/testresample.c
@@ -26,8 +26,7 @@ int main(int argc, char **argv)
int blockalign = 0;
int avgbytes = 0;
SDL_RWops *io = NULL;

  • int src_samplesize, dst_samplesize;
  • int src_len, dst_len, real_dst_len;
  • int dst_len;
    int ret = 0;

    /* Enable standard application logging */
    @@ -54,54 +53,18 @@ int main(int argc, char **argv)
    goto end;
    }

  • stream = SDL_CreateAudioStream(spec.format, spec.channels, spec.freq, spec.format, cvtchans, cvtfreq);
  • if (stream == NULL) {
  •    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "failed to build audio stream: %s\n", SDL_GetError());
    
  • if (SDL_ConvertAudioSamples(spec.format, spec.channels, spec.freq, len, data,
  •                       spec.format, cvtchans, cvtfreq, &dst_len, &dst_buf) < 0) {
    
  •    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "failed to convert samples: %s\n", SDL_GetError());
       ret = 4;
       goto end;
    
    }
  • src_samplesize = (SDL_AUDIO_BITSIZE(spec.format) / 8) * spec.channels;
  • dst_samplesize = (SDL_AUDIO_BITSIZE(spec.format) / 8) * cvtchans;
  • src_len = len & ~(src_samplesize - 1);
  • dst_len = dst_samplesize * (src_len / src_samplesize);
  • if (spec.freq < cvtfreq) {
  •    const double mult = ((double)cvtfreq) / ((double)spec.freq);
    
  •    dst_len *= (int) SDL_ceil(mult);
    
  • }
  • dst_len = dst_len & ~(dst_samplesize - 1);
  • dst_buf = (Uint8 *)SDL_malloc(dst_len);
  • if (dst_buf == NULL) {
  •    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory.\n");
    
  •    ret = 5;
    
  •    goto end;
    
  • }
  • /* Run the audio converter */
  • if (SDL_PutAudioStreamData(stream, data, src_len) < 0 ||
  •    SDL_FlushAudioStream(stream) < 0) {
    
  •    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Conversion failed: %s\n", SDL_GetError());
    
  •    ret = 6;
    
  •    goto end;
    
  • }
  • real_dst_len = SDL_GetAudioStreamData(stream, dst_buf, dst_len);
  • if (real_dst_len < 0) {
  •    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Conversion failed: %s\n", SDL_GetError());
    
  •    ret = 7;
    
  •    goto end;
    
  • }
  • dst_len = real_dst_len;
  • /* write out a WAV header… */
    io = SDL_RWFromFile(argv[2], “wb”);
    if (io == NULL) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, “fopen(‘%s’) failed: %s\n”, argv[2], SDL_GetError());
  •    ret = 8;
    
  •    ret = 5;
       goto end;
    
    }

@@ -126,7 +89,7 @@ int main(int argc, char **argv)

 if (SDL_RWclose(io) == -1) {
     SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "fclose('%s') failed: %s\n", argv[2], SDL_GetError());
  •    ret = 9;
    
  •    ret = 6;
       goto end;
    
    }