SDL: audio: Readd SDL_AudioSpec, but just with format/channels/freq fields.

From 26525f5fd39da742250326e1541f520a81c33e2f Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Tue, 30 May 2023 01:08:22 -0400
Subject: [PATCH] audio: Readd SDL_AudioSpec, but just with
 format/channels/freq fields.

---
 docs/README-migration.md              |   2 +-
 include/SDL3/SDL_audio.h              | 109 ++++++-----------
 src/audio/SDL_audio.c                 | 129 +++++++++----------
 src/audio/SDL_audiocvt.c              | 170 ++++++++++++++------------
 src/audio/SDL_sysaudio.h              |  21 +---
 src/audio/SDL_wave.c                  |  29 ++---
 src/audio/disk/SDL_diskaudio.c        |   6 +-
 src/audio/dummy/SDL_dummyaudio.c      |   2 +-
 src/audio/pulseaudio/SDL_pulseaudio.c |  28 +++--
 src/dynapi/SDL_dynapi_procs.h         |  22 ++--
 src/test/SDL_test_common.c            |   3 +-
 test/loopwave.c                       |  10 +-
 12 files changed, 244 insertions(+), 287 deletions(-)

diff --git a/docs/README-migration.md b/docs/README-migration.md
index 1e8265c528fb..ffa2a385475b 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -152,7 +152,7 @@ SDL_QueueAudio(), SDL_DequeueAudio, and SDL_ClearQueuedAudio and SDL_GetQueuedAu
 
 APIs that use channel counts used to use a Uint8 for the channel; now they use int.
 
-SDL_AudioSpec has been removed; things that used it have simply started taking separate arguments for format, channel, and sample rate. SDL_GetSilenceValueForFormat() can provide the information from the SDL_AudioSpec's `silence` field. The other SDL_AudioSpec fields aren't relevant anymore.
+SDL_AudioSpec has been reduced; now it only holds format, channel, and sample rate. SDL_GetSilenceValueForFormat() can provide the information from the SDL_AudioSpec's `silence` field. The other SDL2 SDL_AudioSpec fields aren't relevant anymore.
 
 SDL_GetAudioDeviceSpec() is removed; use SDL_GetAudioDeviceFormat() instead.
 
diff --git a/include/SDL3/SDL_audio.h b/include/SDL3/SDL_audio.h
index 9c0491bd8122..3e7468cf3e27 100644
--- a/include/SDL3/SDL_audio.h
+++ b/include/SDL3/SDL_audio.h
@@ -141,6 +141,13 @@ typedef Uint16 SDL_AudioFormat;
 
 /* @} *//* Audio flags */
 
+typedef struct SDL_AudioSpec
+{
+    SDL_AudioFormat format;     /**< Audio data format */
+    int channels;               /**< Number of channels: 1 mono, 2 stereo, etc */
+    int freq;                   /**< sample rate: sample frames per second */
+} SDL_AudioSpec;
+
 /* SDL_AudioStream is an audio conversion interface.
     - It can handle resampling data in chunks without generating
       artifacts, when it doesn't have the complete buffer available.
@@ -305,9 +312,7 @@ extern DECLSPEC char *SDLCALL SDL_GetAudioDeviceName(SDL_AudioDeviceID devid);
  * can't be determined).
  *
  * \param devid the instance ID of the device to query.
- * \param fmt On return, will be set to the device's data format. Can be NULL.
- * \param channels On return, will be set to the device's channel count. Can be NULL.
- * \param freq On return, will be set to the device's sample rate. Can be NULL.
+ * \param spec On return, will be filled with device details.
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
  *
@@ -315,7 +320,7 @@ extern DECLSPEC char *SDLCALL SDL_GetAudioDeviceName(SDL_AudioDeviceID devid);
  *
  * \since This function is available since SDL 3.0.0.
  */
-extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioFormat *fmt, int *channels, int *freq);
+extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec);
 
 
 /**
@@ -362,9 +367,7 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD
  *
  * \param devid the device instance id to open. 0 requests the most
  *              reasonable default device.
- * \param fmt the requested device format (`SDL_AUDIO_S16`, etc)
- * \param channels the requested device channels (1==mono, 2==stereo, etc).
- * \param freq the requested device frequency in sample-frames-per-second (Hz)
+ * \param spec the requested device configuration
  * \returns The device ID on success, 0 on error; call SDL_GetError() for more information.
  *
  * \since This function is available since SDL 3.0.0.
@@ -374,7 +377,7 @@ extern DECLSPEC int SDLCALL SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SD
  * \sa SDL_CloseAudioDevice
  * \sa SDL_GetAudioDeviceFormat
  */
-extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq);
+extern DECLSPEC SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec);
 
 
 /**
@@ -499,12 +502,8 @@ extern DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream);
 /**
  * Create a new audio stream.
  *
- * \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 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 src_spec The format details of the input audio
+ * \param dst_spec The format details of the output audio
  * \returns 0 on success, or -1 on error.
  *
  * \threadsafety It is safe to call this function from any thread.
@@ -519,26 +518,15 @@ extern DECLSPEC void SDLCALL SDL_UnbindAudioStream(SDL_AudioStream *stream);
  * \sa SDL_ChangeAudioStreamOutput
  * \sa SDL_DestroyAudioStream
  */
-extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAudioStream(SDL_AudioFormat src_format,
-                                                            int src_channels,
-                                                            int src_rate,
-                                                            SDL_AudioFormat dst_format,
-                                                            int dst_channels,
-                                                            int dst_rate);
+extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec);
 
 
 /**
  * Query the current format of an audio stream.
  *
  * \param stream the SDL_AudioStream to query.
- * \param src_format Where to store the input audio format; ignored if NULL.
- * \param src_channels Where to store the input channel count; ignored if
- *                     NULL.
- * \param src_rate Where to store the input sample rate; ignored if NULL.
- * \param dst_format Where to store the output audio format; ignored if NULL.
- * \param dst_channels Where to store the output channel count; ignored if
- *                     NULL.
- * \param dst_rate Where to store the output sample rate; ignored if NULL.
+ * \param src_spec Where to store the input audio format; ignored if NULL.
+ * \param dst_spec Where to store the output audio format; ignored if NULL.
  * \returns 0 on success, or -1 on error.
  *
  * \threadsafety It is safe to call this function from any thread, as it holds
@@ -547,12 +535,8 @@ extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAudioStream(SDL_AudioFormat s
  * \since This function is available since SDL 3.0.0.
  */
 extern DECLSPEC int SDLCALL SDL_GetAudioStreamFormat(SDL_AudioStream *stream,
-                                                     SDL_AudioFormat *src_format,
-                                                     int *src_channels,
-                                                     int *src_rate,
-                                                     SDL_AudioFormat *dst_format,
-                                                     int *dst_channels,
-                                                     int *dst_rate);
+                                                     SDL_AudioSpec *src_spec,
+                                                     SDL_AudioSpec *dst_spec);
 
 /**
  * Change the input and output formats of an audio stream.
@@ -562,12 +546,8 @@ extern DECLSPEC int SDLCALL SDL_GetAudioStreamFormat(SDL_AudioStream *stream,
  * must provide data in the new input formats.
  *
  * \param stream The stream the format is being changed
- * \param src_format The format of the audio input
- * \param src_channels The number of channels of the audio input
- * \param src_rate The sampling rate of the audio input
- * \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 src_spec The new format of the audio input; if NULL, it is not changed.
+ * \param dst_spec The new format of the audio output; if NULL, it is not changed.
  * \returns 0 on success, or -1 on error.
  *
  * \threadsafety It is safe to call this function from any thread, as it holds
@@ -581,12 +561,8 @@ extern DECLSPEC int SDLCALL SDL_GetAudioStreamFormat(SDL_AudioStream *stream,
  * \sa SDL_GetAudioStreamAvailable
  */
 extern DECLSPEC int SDLCALL SDL_SetAudioStreamFormat(SDL_AudioStream *stream,
-                                                     SDL_AudioFormat src_format,
-                                                     int src_channels,
-                                                     int src_rate,
-                                                     SDL_AudioFormat dst_format,
-                                                     int dst_channels,
-                                                     int dst_rate);
+                                                     const SDL_AudioSpec *src_spec,
+                                                     const SDL_AudioSpec *dst_spec);
 
 /**
  * Add data to be converted/resampled to the stream.
@@ -902,15 +878,13 @@ extern DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream);
  * correctly to match both the app and the audio device's needs. This is
  * optional, but slightly less cumbersome to set up for a common use case.
  *
- * The format parameters (`fmt`, `channels`, `freq`) represent the app's side
- * of the audio stream. That is, for recording audio, this will be the output
- * format, and for playing audio, this will be the input format. This function
- * will set the other side of the audio stream to the device's format.
+ * The `spec` parameter represents the app's side of the audio stream. That
+ * is, for recording audio, this will be the output format, and for playing
+ * audio, this will be the input format. This function will set the other
+ * side of the audio stream to the device's format.
  *
  * \param devid an audio device to bind a stream to. This must be an opened device, and can not be zero.
- * \param fmt the audio stream's format (`SDL_AUDIO_S16`, etc)
- * \param channels the audio stream's channel count (1==mono, 2==stereo, etc).
- * \param freq the audio stream's frequency in sample-frames-per-second (Hz)
+ * \param spec the audio stream's input format
  * \returns a bound audio stream on success, ready to use. NULL on error; call SDL_GetError() for more information.
  *
  * \since This function is available since SDL 3.0.0.
@@ -921,7 +895,7 @@ extern DECLSPEC void SDLCALL SDL_DestroyAudioStream(SDL_AudioStream *stream);
  * \sa SDL_UnbindAudioStreams
  * \sa SDL_UnbindAudioStream
  */
-extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq);
+extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec);
 
 
 /**
@@ -981,12 +955,8 @@ extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioD
  *
  * \param src The data source for the WAVE data
  * \param freesrc If non-zero, SDL will _always_ free the data source
- * \param fmt A pointer to an SDL_AudioFormat that will be set to the
- *            WAVE data's format on successful return.
- * \param channels A pointer to an int that will be set to the
- *            WAVE data's channel count on successful return.
- * \param freq A pointer to an int that will be set to the
- *             WAVE data's sample rate in Hz on successful return.
+ * \param spec A pointer to an SDL_AudioSpec that will be set to the
+ *             WAVE data's format details on successful return.
  * \param audio_buf A pointer filled with the audio data, allocated by the
  *                  function.
  * \param audio_len A pointer filled with the length of the audio data buffer
@@ -1009,8 +979,7 @@ extern DECLSPEC SDL_AudioStream *SDLCALL SDL_CreateAndBindAudioStream(SDL_AudioD
  * \sa SDL_LoadWAV
  */
 extern DECLSPEC int SDLCALL SDL_LoadWAV_RW(SDL_RWops * src, int freesrc,
-                                           SDL_AudioFormat *fmt, int *channels, int *freq,
-                                           Uint8 ** audio_buf,
+                                           SDL_AudioSpec * spec, Uint8 ** audio_buf,
                                            Uint32 * audio_len);
 
 /**
@@ -1074,14 +1043,10 @@ extern DECLSPEC int SDLCALL SDL_MixAudioFormat(Uint8 * dst,
  * use, so it's also less efficient than using one directly, if you need to
  * convert multiple times.
  *
- * \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_spec The format details of the input audio
  * \param src_data The audio data to be converted
  * \param src_len The len of src_data
- * \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_spec The format details of the output audio
  * \param dst_data Will be filled with a pointer to converted audio data,
  *                 which should be freed with SDL_free(). On error, it will be
  *                 NULL.
@@ -1093,14 +1058,10 @@ extern DECLSPEC int SDLCALL SDL_MixAudioFormat(Uint8 * dst,
  *
  * \sa SDL_CreateAudioStream
  */
-extern DECLSPEC int SDLCALL SDL_ConvertAudioSamples(SDL_AudioFormat src_format,
-                                                    int src_channels,
-                                                    int src_rate,
+extern DECLSPEC int SDLCALL SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec,
                                                     const Uint8 *src_data,
                                                     int src_len,
-                                                    SDL_AudioFormat dst_format,
-                                                    int dst_channels,
-                                                    int dst_rate,
+                                                    const SDL_AudioSpec *dst_spec,
                                                     Uint8 **dst_data,
                                                     int *dst_len);
 
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index 05d6e8321046..060980ee77db 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -169,7 +169,7 @@ static void DestroyAudioDevice(SDL_AudioDevice *device)
     SDL_free(device);
 }
 
-static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture, SDL_AudioFormat fmt, int channels, int freq, void *handle, SDL_AudioDevice **devices, SDL_AtomicInt *device_count)
+static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture, const SDL_AudioSpec *spec, void *handle, SDL_AudioDevice **devices, SDL_AtomicInt *device_count)
 {
     SDL_AudioDevice *device;
 
@@ -202,10 +202,9 @@ static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture,
     SDL_AtomicSet(&device->shutdown, 0);
     SDL_AtomicSet(&device->condemned, 0);
     device->iscapture = iscapture;
-    device->format = device->default_format = fmt;
-    device->channels = device->default_channels = channels;
-    device->freq = device->default_freq = freq;
-    device->silence_value = SDL_GetSilenceValueForFormat(device->format);
+    SDL_memcpy(&device->spec, spec, sizeof (SDL_AudioSpec));
+    SDL_memcpy(&device->default_spec, spec, sizeof (SDL_AudioSpec));
+    device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format);
     device->handle = handle;
     device->prev = NULL;
 
@@ -225,33 +224,34 @@ static SDL_AudioDevice *CreateAudioDevice(const char *name, SDL_bool iscapture,
     return device;
 }
 
-static SDL_AudioDevice *CreateAudioCaptureDevice(const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle)
+static SDL_AudioDevice *CreateAudioCaptureDevice(const char *name, const SDL_AudioSpec *spec, void *handle)
 {
     SDL_assert(current_audio.impl.HasCaptureSupport);
-    return CreateAudioDevice(name, SDL_TRUE, fmt, channels, freq, handle, &current_audio.capture_devices, &current_audio.capture_device_count);
+    return CreateAudioDevice(name, SDL_TRUE, spec, handle, &current_audio.capture_devices, &current_audio.capture_device_count);
 }
 
-static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle)
+static SDL_AudioDevice *CreateAudioOutputDevice(const char *name, const SDL_AudioSpec *spec, void *handle)
 {
-    return CreateAudioDevice(name, SDL_FALSE, fmt, channels, freq, handle, &current_audio.output_devices, &current_audio.output_device_count);
+    return CreateAudioDevice(name, SDL_FALSE, spec, handle, &current_audio.output_devices, &current_audio.output_device_count);
 }
 
 /* The audio backends call this when a new device is plugged in. */
-SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, SDL_AudioFormat fmt, int channels, int freq, void *handle)
+SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *inspec, void *handle)
 {
     SDL_AudioDevice *device;
+    SDL_AudioSpec spec;
 
-    if (fmt == 0) {
-        fmt = DEFAULT_AUDIO_FORMAT;
-    }
-    if (channels == 0) {
-        channels = DEFAULT_AUDIO_CHANNELS;
-    }
-    if (freq == 0) {
-        freq = DEFAULT_AUDIO_FREQUENCY;
+    if (!inspec) {
+        spec.format = DEFAULT_AUDIO_FORMAT;
+        spec.channels = DEFAULT_AUDIO_CHANNELS;
+        spec.freq = DEFAULT_AUDIO_FREQUENCY;
+    } else {
+        spec.format = (inspec->format != 0) ? inspec->format : DEFAULT_AUDIO_FORMAT;
+        spec.channels = (inspec->channels != 0) ? inspec->channels : DEFAULT_AUDIO_CHANNELS;
+        spec.freq = (inspec->freq != 0) ? inspec->freq : DEFAULT_AUDIO_FREQUENCY;
     }
 
-    device = iscapture ? CreateAudioCaptureDevice(name, fmt, channels, freq, handle) : CreateAudioOutputDevice(name, fmt, channels, freq, handle);
+    device = iscapture ? CreateAudioCaptureDevice(name, &spec, handle) : CreateAudioOutputDevice(name, &spec, handle);
     if (device) {
         /* Post the event, if desired */
         if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_ADDED)) {
@@ -330,9 +330,9 @@ static void SDL_AudioDetectDevices_Default(void)
     SDL_assert(current_audio.impl.OnlyHasDefaultOutputDevice);
     SDL_assert(current_audio.impl.OnlyHasDefaultCaptureDevice || !current_audio.impl.HasCaptureSupport);
 
-    SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, 0, 0, 0, (void *)((size_t)0x1));
+    SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)((size_t)0x1));
     if (current_audio.impl.HasCaptureSupport) {
-        SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, 0, 0, 0, (void *)((size_t)0x2));
+        SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)((size_t)0x2));
     }
 }
 
@@ -614,7 +614,7 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device)
                 retval = SDL_FALSE;
                 break;
             } else if (br > 0) {  /* it's okay if we get less than requested, we mix what we have. */
-                if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->format, br, SDL_MIX_MAXVOLUME) < 0) {  /* !!! FIXME: allow streams to specify gain? */
+                if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->spec.format, br, SDL_MIX_MAXVOLUME) < 0) {  /* !!! FIXME: allow streams to specify gain? */
                     SDL_assert(!"We probably ended up with some totally unexpected audio format here");
                     retval = SDL_FALSE;  /* uh...? */
                     break;
@@ -636,10 +636,10 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device)
 
 void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device)
 {
-    const int samples = (device->buffer_size / (SDL_AUDIO_BITSIZE(device->format) / 8)) / device->channels;
+    const int samples = (device->buffer_size / (SDL_AUDIO_BITSIZE(device->spec.format) / 8)) / device->spec.channels;
     SDL_assert(!device->iscapture);
     /* Wait for the audio to drain. */ /* !!! FIXME: don't bother waiting if device is lost. */
-    SDL_Delay(((samples * 1000) / device->freq) * 2);
+    SDL_Delay(((samples * 1000) / device->spec.freq) * 2);
     current_audio.impl.ThreadDeinit(device);
     if (SDL_AtomicGet(&device->condemned)) {
         SDL_DetachThread(device->thread);  /* no one is waiting for us, just detach ourselves. */
@@ -878,23 +878,18 @@ char *SDL_GetAudioDeviceName(SDL_AudioDeviceID devid)
     return retval;
 }
 
-int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioFormat *fmt, int *channels, int *freq)
+int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec)
 {
+    if (!spec) {
+        return SDL_InvalidParamError("spec");
+    }
+
     SDL_AudioDevice *device = ObtainAudioDevice(devid);
     if (!device) {
         return -1;
     }
 
-    if (fmt) {
-        *fmt = device->format;
-    }
-    if (channels) {
-        *channels = device->channels;
-    }
-    if (freq) {
-        *freq = device->freq;
-    }
-
+    SDL_memcpy(spec, &device->spec, sizeof (SDL_AudioSpec));
     SDL_UnlockMutex(device->lock);
 
     return 0;
@@ -917,11 +912,9 @@ static void CloseAudioDevice(SDL_AudioDevice *device)
         device->work_buffer = NULL;
     }
 
-    device->format = device->default_format;
-    device->channels = device->default_channels;
-    device->freq = device->default_freq;
+    SDL_memcpy(&device->spec, &device->default_spec, sizeof (SDL_AudioSpec));
     device->sample_frames = 0;
-    device->silence_value = SDL_GetSilenceValueForFormat(device->format);
+    device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format);
 }
 
 void SDL_CloseAudioDevice(SDL_AudioDeviceID devid)
@@ -965,35 +958,35 @@ static SDL_AudioFormat ParseAudioFormatString(const char *string)
     return 0;
 }
 
-static void PrepareAudioFormat(SDL_AudioFormat *fmt, int *channels, int *freq)
+static void PrepareAudioFormat(SDL_AudioSpec *spec)
 {
     const char *env;
 
-    if (*freq == 0) {
-        *freq = DEFAULT_AUDIO_FREQUENCY;
-        env = SDL_getenv("SDL_AUDIO_FREQUENCY");
+    if (spec->freq == 0) {
+        spec->freq = DEFAULT_AUDIO_FREQUENCY;
+        env = SDL_getenv("SDL_AUDIO_FREQUENCY");  // !!! FIXME: should be a hint?
         if (env != NULL) {
             const int val = SDL_atoi(env);
             if (val > 0) {
-                *freq = val;
+                spec->freq = val;
             }
         }
     }
 
-    if (*channels == 0) {
-        *channels = DEFAULT_AUDIO_CHANNELS;
+    if (spec->channels == 0) {
+        spec->channels = DEFAULT_AUDIO_CHANNELS;
         env = SDL_getenv("SDL_AUDIO_CHANNELS");
         if (env != NULL) {
             const int val = SDL_atoi(env);
             if (val > 0) {
-                *channels = val;
+                spec->channels = val;
             }
         }
     }
 
-    if (*fmt == 0) {
+    if (spec->format == 0) {
         const SDL_AudioFormat val = ParseAudioFormatString(SDL_getenv("SDL_AUDIO_FORMAT"));
-        *fmt = (val != 0) ? val : DEFAULT_AUDIO_FORMAT;
+        spec->format = (val != 0) ? val : DEFAULT_AUDIO_FORMAT;
     }
 }
 
@@ -1010,25 +1003,27 @@ static int GetDefaultSampleFramesFromFreq(int freq)
 
 void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device)
 {
-    device->silence_value = SDL_GetSilenceValueForFormat(device->format);
-    device->buffer_size = device->sample_frames * (SDL_AUDIO_BITSIZE(device->format) / 8) * device->channels;
+    device->silence_value = SDL_GetSilenceValueForFormat(device->spec.format);
+    device->buffer_size = device->sample_frames * (SDL_AUDIO_BITSIZE(device->spec.format) / 8) * device->spec.channels;
 }
 
 /* this expects the device lock to be held. */
-static int OpenAudioDevice(SDL_AudioDevice *device, SDL_AudioFormat fmt, int channels, int freq)
+static int OpenAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec *inspec)
 {
     SDL_assert(SDL_AtomicGet(&device->refcount) == 1);
 
-    PrepareAudioFormat(&fmt, &channels, &freq);
+    SDL_AudioSpec spec;
+    SDL_memcpy(&spec, inspec, sizeof (SDL_AudioSpec));
+    PrepareAudioFormat(&spec);
 
     /* we allow the device format to change if it's better than the current settings (by various definitions of "better"). This prevents
        something low quality, like an old game using S8/8000Hz audio, from ruining a music thing playing at CD quality that tries to open later.
        (or some VoIP library that opens for mono output ruining your surround-sound game because it got there first). */
     /* These are just requests! The backend may change any of these values during OpenDevice method! */
-    device->format = (SDL_AUDIO_BITSIZE(device->default_format) >= SDL_AUDIO_BITSIZE(fmt)) ? device->default_format : fmt;
-    device->freq = SDL_max(device->default_freq, freq);
-    device->channels = SDL_max(device->default_channels, channels);
-    device->sample_frames = GetDefaultSampleFramesFromFreq(device->freq);
+    device->spec.format = (SDL_AUDIO_BITSIZE(device->default_spec.format) >= SDL_AUDIO_BITSIZE(spec.format)) ? device->default_spec.format : spec.format;
+    device->spec.freq = SDL_max(device->default_spec.freq, spec.freq);
+    device->spec.channels = SDL_max(device->default_spec.channels, spec.channels);
+    device->sample_frames = GetDefaultSampleFramesFromFreq(device->spec.freq);
     SDL_UpdatedAudioDeviceFormat(device);  /* start this off sane. */
 
     if (current_audio.impl.OpenDevice(device) < 0) {
@@ -1062,7 +1057,7 @@ static int OpenAudioDevice(SDL_AudioDevice *device, SDL_AudioFormat fmt, int cha
     return 0;
 }
 
-SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq)
+SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec)
 {
     SDL_AudioDevice *device = ObtainAudioDevice(devid);  /* !!! FIXME: need to choose default device for devid==0 */
     int retval = 0;
@@ -1070,7 +1065,7 @@ SDL_AudioDeviceID SDL_OpenAudioDevice(SDL_AudioDeviceID devid, SDL_AudioFormat f
     if (device) {
         retval = device->instance_id;
         if (SDL_AtomicIncRef(&device->refcount) == 0) {
-            if (OpenAudioDevice(device, fmt, channels, freq) == -1) {
+            if (OpenAudioDevice(device, spec) == -1) {
                 retval = 0;
             }
         }
@@ -1127,16 +1122,14 @@ int SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream **streams, int
         /* Now that everything is verified, chain everything together. */
         for (i = 0; i < num_streams; i++) {
             SDL_AudioStream *stream = streams[i];
-            SDL_AudioFormat src_format, dst_format;
-            int src_channels, dst_channels;
-            int src_rate, dst_rate;
+            SDL_AudioSpec src_spec, dst_spec;
 
             /* set the proper end of the stream to the device's format. */
-            SDL_GetAudioStreamFormat(stream, &src_format, &src_channels, &src_rate, &dst_format, &dst_channels, &dst_rate);
+            SDL_GetAudioStreamFormat(stream, &src_spec, &dst_spec);
             if (device->iscapture) {
-                SDL_SetAudioStreamFormat(stream, device->format, device->channels, device->freq, dst_format, dst_channels, dst_rate);
+                SDL_SetAudioStreamFormat(stream, &device->spec, &dst_spec);
             } else {
-                SDL_SetAudioStreamFormat(stream, src_format, src_channels, src_rate, device->format, device->channels, device->freq);
+                SDL_SetAudioStreamFormat(stream, &src_spec, &device->spec);
             }
 
             stream->bound_device = device;
@@ -1236,16 +1229,16 @@ void SDL_UnbindAudioStream(SDL_AudioStream *stream)
 }
 
 
-SDL_AudioStream *SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, SDL_AudioFormat fmt, int channels, int freq)
+SDL_AudioStream *SDL_CreateAndBindAudioStream(SDL_AudioDeviceID devid, const SDL_AudioSpec *spec)
 {
     SDL_AudioStream *stream = NULL;
     SDL_AudioDevice *device = ObtainAudioDevice(devid);
     if (device) {
         const SDL_bool iscapture = (devid & 1) ? SDL_FALSE : SDL_TRUE;   /* capture instance ids are even and output devices are odd */
         if (iscapture) {
-            stream = SDL_CreateAudioStream(device->format, device->channels, device->freq, fmt, channels, freq);
+            stream = SDL_CreateAudioStream(&device->spec, spec);
         } else {
-            stream = SDL_CreateAudioStream(fmt, channels, freq, device->format, device->channels, device->freq);
+            stream = SDL_CreateAudioStream(spec, &device->spec);
         }
 
         if (stream) {
diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c
index eec84107a4f0..d61aa75ca252 100644
--- a/src/audio/SDL_audiocvt.c
+++ b/src/audio/SDL_audiocvt.c
@@ -446,13 +446,20 @@ static int CalculateMaxSampleFrameSize(SDL_AudioFormat src_format, int src_chann
 }
 
 /* this assumes you're holding the stream's lock (or are still creating the stream). */
-static int SetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat src_format, int src_channels, int src_rate, SDL_AudioFormat dst_format, int dst_channels, int dst_rate)
+static int SetAudioStreamFormat(SDL_AudioStream *stream, const SDL_AudioSpec *src_spec, const SDL_AudioSpec *dst_spec)
 {
     /* If increasing channels, do it after resampling, since we'd just
        do more work to resample duplicate channels. If we're decreasing, do
        it first so we resample the interpolated data instead of interpolating
        the resampled data (!!! FIXME: decide if that works in practice, though!).
        This is decided in pre_resample_channels. */
+
+    const SDL_AudioFormat src_format = src_spec->format;
+    const int src_channels = src_spec->channels;
+    const int src_rate = src_spec->freq;
+    const SDL_AudioFormat dst_format = dst_spec->format;
+    const int dst_channels = dst_spec->channels;
+    const int dst_rate = dst_spec->freq;
     const int src_sample_frame_size = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels;
     const int dst_sample_frame_size = (SDL_AUDIO_BITSIZE(dst_format) / 8) * dst_channels;
     const int max_sample_frame_size = CalculateMaxSampleFrameSize(src_format, src_channels, dst_format, dst_channels);
@@ -515,16 +522,16 @@ static int SetAudioStreamFormat(SDL_AudioStream *stream, SDL_AudioFormat src_for
 
     /* copy to new buffers and/or convert data; ConvertAudio will do a simple memcpy if format matches, and nothing at all if the buffer hasn't changed */
     if (stream->future_buffer) {
-        ConvertAudio(stream->future_buffer_filled_frames, stream->future_buffer, stream->src_format, stream->src_channels, 

(Patch may be truncated, please check the link at the top of this post.)