SDL: SDL_audiocvt: Respct the SDL_HINT_AUDIO_RESAMPLING_MODE hint

From 22461383c69f750cd1f66769f88c9893d5466368 Mon Sep 17 00:00:00 2001
From: Daniel Bomar <[EMAIL REDACTED]>
Date: Sat, 15 Oct 2022 15:54:12 -0500
Subject: [PATCH] SDL_audiocvt: Respct the SDL_HINT_AUDIO_RESAMPLING_MODE hint

This implements using libsamplerate for the SDL_AudioCVT API.
This library was already being used for audio streams when this hint is
set.
---
 include/SDL_hints.h      |  5 +--
 src/audio/SDL_audio.c    |  5 ++-
 src/audio/SDL_audio_c.h  |  1 +
 src/audio/SDL_audiocvt.c | 69 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 75 insertions(+), 5 deletions(-)

diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index 4d445b35cee2..600989e58bb1 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -278,10 +278,7 @@ extern "C" {
  *  If this hint isn't specified to a valid setting, or libsamplerate isn't
  *  available, SDL will use the default, internal resampling algorithm.
  *
- *  Note that this is currently only applicable to resampling audio that is
- *  being written to a device for playback or audio being read from a device
- *  for capture. SDL_AudioCVT always uses the default resampler (although this
- *  might change for SDL 2.1).
+ *  As of SDL 2.26, SDL_AudioCVT now respects this hint.
  *
  *  This hint is currently only checked at audio subsystem initialization.
  *
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index 11700d497d41..08888782242f 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -144,6 +144,7 @@ int (*SRC_src_process)(SRC_STATE *state, SRC_DATA *data) = NULL;
 int (*SRC_src_reset)(SRC_STATE *state) = NULL;
 SRC_STATE* (*SRC_src_delete)(SRC_STATE *state) = NULL;
 const char* (*SRC_src_strerror)(int error) = NULL;
+int (*SRC_src_simple)(SRC_DATA *data, int converter_type, int channels) = NULL;
 
 static SDL_bool
 LoadLibSampleRate(void)
@@ -178,8 +179,9 @@ LoadLibSampleRate(void)
     SRC_src_reset = (int(*)(SRC_STATE *state))SDL_LoadFunction(SRC_lib, "src_reset");
     SRC_src_delete = (SRC_STATE* (*)(SRC_STATE *state))SDL_LoadFunction(SRC_lib, "src_delete");
     SRC_src_strerror = (const char* (*)(int error))SDL_LoadFunction(SRC_lib, "src_strerror");
+    SRC_src_simple = (int(*)(SRC_DATA *data, int converter_type, int channels))SDL_LoadFunction(SRC_lib, "src_simple");
 
-    if (!SRC_src_new || !SRC_src_process || !SRC_src_reset || !SRC_src_delete || !SRC_src_strerror) {
+    if (!SRC_src_new || !SRC_src_process || !SRC_src_reset || !SRC_src_delete || !SRC_src_strerror || !SRC_src_simple) {
         SDL_UnloadObject(SRC_lib);
         SRC_lib = NULL;
         return SDL_FALSE;
@@ -190,6 +192,7 @@ LoadLibSampleRate(void)
     SRC_src_reset = src_reset;
     SRC_src_delete = src_delete;
     SRC_src_strerror = src_strerror;
+    SRC_src_simple = src_simple;
 #endif
 
     SRC_available = SDL_TRUE;
diff --git a/src/audio/SDL_audio_c.h b/src/audio/SDL_audio_c.h
index a516c554a114..a976dfd09e61 100644
--- a/src/audio/SDL_audio_c.h
+++ b/src/audio/SDL_audio_c.h
@@ -45,6 +45,7 @@ extern int (*SRC_src_process)(SRC_STATE *state, SRC_DATA *data);
 extern int (*SRC_src_reset)(SRC_STATE *state);
 extern SRC_STATE* (*SRC_src_delete)(SRC_STATE *state);
 extern const char* (*SRC_src_strerror)(int error);
+extern int (*SRC_src_simple)(SRC_DATA *data, int converter_type, int channels);
 #endif
 
 /* Functions to get a list of "close" audio formats */
diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c
index 85faa4b0b75a..e79437e91d97 100644
--- a/src/audio/SDL_audiocvt.c
+++ b/src/audio/SDL_audiocvt.c
@@ -418,6 +418,48 @@ SDL_BuildAudioTypeCVTFromFloat(SDL_AudioCVT *cvt, const SDL_AudioFormat dst_fmt)
     return retval;
 }
 
+#ifdef HAVE_LIBSAMPLERATE_H
+
+static void
+SDL_ResampleCVT_SRC(SDL_AudioCVT *cvt, const int chans, const SDL_AudioFormat format)
+{
+    const float *src = (const float *) cvt->buf;
+    const int srclen = cvt->len_cvt;
+    float *dst = (float *) (cvt->buf + srclen);
+    const int dstlen = (cvt->len * cvt->len_mult) - srclen;
+    const int framelen = sizeof(float) * chans;
+    int result = 0;
+    SRC_DATA data;
+
+    SDL_zero(data);
+
+    data.data_in = (float *)src; /* Older versions of libsamplerate had a non-const pointer, but didn't write to it */
+    data.input_frames = srclen / framelen;
+
+    data.data_out = dst;
+    data.output_frames = dstlen / framelen;
+
+    data.src_ratio = cvt->rate_incr;
+
+    result = SRC_src_simple(&data, SRC_converter, chans); /* Simple API converts the whole buffer at once.  No need for initialization. */
+    /* !!! FIXME: Handle library failures? */
+    #ifdef DEBUG_CONVERT
+    if (result != 0) {
+        SDL_Log("src_simple() failed: %s", SRC_src_strerror(result));
+    }
+    #endif
+
+    cvt->len_cvt = data.output_frames_gen * framelen;
+
+    SDL_memmove(cvt->buf, dst, cvt->len_cvt);
+
+    if (cvt->filters[++cvt->filter_index]) {
+        cvt->filters[cvt->filter_index](cvt, format);
+    }
+}
+
+#endif /* HAVE_LIBSAMPLERATE_H */
+
 static void
 SDL_ResampleCVT(SDL_AudioCVT *cvt, const int chans, const SDL_AudioFormat format)
 {
@@ -478,9 +520,36 @@ RESAMPLER_FUNCS(6)
 RESAMPLER_FUNCS(8)
 #undef RESAMPLER_FUNCS
 
+#ifdef HAVE_LIBSAMPLERATE_H
+#define RESAMPLER_FUNCS(chans) \
+    static void SDLCALL \
+    SDL_ResampleCVT_SRC_c##chans(SDL_AudioCVT *cvt, SDL_AudioFormat format) { \
+        SDL_ResampleCVT_SRC(cvt, chans, format); \
+    }
+RESAMPLER_FUNCS(1)
+RESAMPLER_FUNCS(2)
+RESAMPLER_FUNCS(4)
+RESAMPLER_FUNCS(6)
+RESAMPLER_FUNCS(8)
+#undef RESAMPLER_FUNCS
+#endif /* HAVE_LIBSAMPLERATE_H */
+
 static SDL_AudioFilter
 ChooseCVTResampler(const int dst_channels)
 {
+    #ifdef HAVE_LIBSAMPLERATE_H
+    if (SRC_available) {
+        switch (dst_channels) {
+            case 1: return SDL_ResampleCVT_SRC_c1;
+            case 2: return SDL_ResampleCVT_SRC_c2;
+            case 4: return SDL_ResampleCVT_SRC_c4;
+            case 6: return SDL_ResampleCVT_SRC_c6;
+            case 8: return SDL_ResampleCVT_SRC_c8;
+            default: break;
+        }
+    }
+    #endif /* HAVE_LIBSAMPLERATE_H */
+
     switch (dst_channels) {
         case 1: return SDL_ResampleCVT_c1;
         case 2: return SDL_ResampleCVT_c2;