SDL: hints: Added SDL_HINT_AUDIO_ENFORCE_MINIMUM_SPEC.

From b7c8b2f29acdcf5eaa14b549101e68e24bb70af8 Mon Sep 17 00:00:00 2001
From: unknown <[EMAIL REDACTED]>
Date: Sun, 30 Nov 2025 23:26:02 -0500
Subject: [PATCH] hints: Added SDL_HINT_AUDIO_ENFORCE_MINIMUM_SPEC.

Fixes #14426.
---
 include/SDL3/SDL_hints.h | 30 ++++++++++++++++++++++++++++++
 src/audio/SDL_audio.c    | 27 +++++++++++++++------------
 2 files changed, 45 insertions(+), 12 deletions(-)

diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 972e0b7e782a1..0001dfa8e5994 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -493,6 +493,36 @@ extern "C" {
  */
 #define SDL_HINT_AUDIO_DUMMY_TIMESCALE "SDL_AUDIO_DUMMY_TIMESCALE"
 
+/**
+ * A variable controlling whether SDL enforces a minimum audio device spec.
+ *
+ * By default, SDL will require devices to be opened at a minimum spec
+ * (at time of writing: 44100Hz, stereo, Sint16 format), so if something
+ * lower quality tries to open the device first, it doesn't ruin audio
+ * for the next thing that opens a device. For example, if a VoIP library
+ * wants Uint8, 8000Hz, mono output, it doesn't force the entire game to
+ * this state simply because it got there first.
+ *
+ * However, an app that knows it will definitely be the only thing opening
+ * a device, and wants to open it at a lower spec, might be able to avoid
+ * some otherwise-unnecessary conversions by bypassing this minimum
+ * requirement.
+ *
+ * Note that even without the minimum enforcement, the system might choose a
+ * different format/channels/frequency than requested by the app; this hint
+ * just removes SDL's minimum policy.
+ *
+ * The variable can be set to the following values:
+ *
+ * - "0": Audio device mimimum formats _are not_ enforced.
+ * - "1": Audio device minimum formats _are_ enforced. (default)
+ *
+ * This hint should be set before an audio device is opened.
+ *
+ * \since This hint is available since SDL 3.4.0.
+ */
+#define SDL_HINT_AUDIO_ENFORCE_MINIMUM_SPEC "SDL_AUDIO_ENFORCE_MINIMUM_SPEC"
+
 /**
  * A variable controlling the default audio format.
  *
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index e084f24cfc758..6f200132019ad 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -1800,18 +1800,21 @@ static bool OpenPhysicalAudioDevice(SDL_AudioDevice *device, const SDL_AudioSpec
     SDL_copyp(&spec, inspec ? inspec : &device->default_spec);
     PrepareAudioFormat(device->recording, &spec);
 
-    /* We impose a simple minimum on device formats. 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! */
-
-    const SDL_AudioFormat minimum_format = device->recording ? DEFAULT_AUDIO_RECORDING_FORMAT : DEFAULT_AUDIO_PLAYBACK_FORMAT;
-    const int minimum_channels = device->recording ? DEFAULT_AUDIO_RECORDING_CHANNELS : DEFAULT_AUDIO_PLAYBACK_CHANNELS;
-    const int minimum_freq = device->recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY;
-
-    device->spec.format = (SDL_AUDIO_BITSIZE(minimum_format) >= SDL_AUDIO_BITSIZE(spec.format)) ? minimum_format : spec.format;
-    device->spec.channels = SDL_max(minimum_channels, spec.channels);
-    device->spec.freq = SDL_max(minimum_freq, spec.freq);
+    if (!SDL_GetHintBoolean(SDL_HINT_AUDIO_ENFORCE_MINIMUM_SPEC, true)) {
+        SDL_copyp(&device->spec, &spec);
+    } else {
+        /* We impose a simple minimum on device formats. 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! */
+        const SDL_AudioFormat minimum_format = device->recording ? DEFAULT_AUDIO_RECORDING_FORMAT : DEFAULT_AUDIO_PLAYBACK_FORMAT;
+        const int minimum_channels = device->recording ? DEFAULT_AUDIO_RECORDING_CHANNELS : DEFAULT_AUDIO_PLAYBACK_CHANNELS;
+        const int minimum_freq = device->recording ? DEFAULT_AUDIO_RECORDING_FREQUENCY : DEFAULT_AUDIO_PLAYBACK_FREQUENCY;
+        device->spec.format = (SDL_AUDIO_BITSIZE(minimum_format) >= SDL_AUDIO_BITSIZE(spec.format)) ? minimum_format : spec.format;
+        device->spec.channels = SDL_max(minimum_channels, spec.channels);
+        device->spec.freq = SDL_max(minimum_freq, spec.freq);
+    }
+
     device->sample_frames = SDL_GetDefaultSampleFramesFromFreq(device->spec.freq);
     SDL_UpdatedAudioDeviceFormat(device);  // start this off sane.