Browse Source

audio: Binding an SDL_AudioStream will set missing formats.

It _must_ have the format set for the opposite side from the device (so
playback needs the src format set, and recording needs the dst format set),
since the stream gets mangled by the device thread if not. So if it has never
been set (stream created with NULL audiospec), just set it to match the device.
If the stream is just meant to buffer and not convert, this is desired
behavior, even if it didn't also fix a bug.

Binding the audio stream will always set the device side's format, as usual;
this does not need to be set by the caller at all.

Fixes #13363.
Ryan C. Gordon 3 weeks ago
parent
commit
f2ae6503c0
2 changed files with 17 additions and 5 deletions
  1. 4 1
      include/SDL3/SDL_audio.h
  2. 13 4
      src/audio/SDL_audio.c

+ 4 - 1
include/SDL3/SDL_audio.h

@@ -942,7 +942,10 @@ extern SDL_DECLSPEC void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID devid);
  * Binding a stream to a device will set its output format for playback
  * devices, and its input format for recording devices, so they match the
  * device's settings. The caller is welcome to change the other end of the
- * stream's format at any time with SDL_SetAudioStreamFormat().
+ * stream's format at any time with SDL_SetAudioStreamFormat(). If the other
+ * end of the stream's format has never been set (the audio stream was created
+ * with a NULL audio spec), this function will set it to match the device
+ * end's format.
  *
  * \param devid an audio device to bind a stream to.
  * \param streams an array of audio streams to bind.

+ 13 - 4
src/audio/SDL_audio.c

@@ -1165,6 +1165,8 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device)
             // We should have updated this elsewhere if the format changed!
             SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &device->spec, NULL, NULL));
 
+            SDL_assert(stream->src_spec.format != SDL_AUDIO_UNKNOWN);
+
             int br = 0;
 
             if (!SDL_GetAtomicInt(&logdev->paused)) {
@@ -1224,6 +1226,8 @@ bool SDL_PlaybackAudioThreadIterate(SDL_AudioDevice *device)
                     // We should have updated this elsewhere if the format changed!
                     SDL_assert(SDL_AudioSpecsEqual(&stream->dst_spec, &outspec, NULL, NULL));
 
+                    SDL_assert(stream->src_spec.format != SDL_AUDIO_UNKNOWN);
+
                     /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams
                        for iterating here because the binding linked list can only change while the device lock is held.
                        (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind
@@ -1363,6 +1367,7 @@ bool SDL_RecordingAudioThreadIterate(SDL_AudioDevice *device)
                     SDL_assert(stream->src_spec.format == ((logdev->postmix || (logdev->gain != 1.0f)) ? SDL_AUDIO_F32 : device->spec.format));
                     SDL_assert(stream->src_spec.channels == device->spec.channels);
                     SDL_assert(stream->src_spec.freq == device->spec.freq);
+                    SDL_assert(stream->dst_spec.format != SDL_AUDIO_UNKNOWN);
 
                     void *final_buf = output_buffer;
 
@@ -2024,10 +2029,6 @@ bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *stre
     } else if (logdev->simplified) {
         result = SDL_SetError("Cannot change stream bindings on device opened with SDL_OpenAudioDeviceStream");
     } else {
-
-        // !!! FIXME: We'll set the device's side's format below, but maybe we should refuse to bind a stream if the app's side doesn't have a format set yet.
-        // !!! FIXME: Actually, why do we allow there to be an invalid format, again?
-
         // make sure start of list is sane.
         SDL_assert(!logdev->bound_streams || (logdev->bound_streams->prev_binding == NULL));
 
@@ -2062,9 +2063,17 @@ bool SDL_BindAudioStreams(SDL_AudioDeviceID devid, SDL_AudioStream * const *stre
 
     if (result) {
         // Now that everything is verified, chain everything together.
+        const bool recording = device->recording;
         for (int i = 0; i < num_streams; i++) {
             SDL_AudioStream *stream = streams[i];
             if (stream) {  // shouldn't be NULL, but just in case...
+                // if the stream never had its non-device-end format set, just set it to the device end's format.
+                if (recording && (stream->dst_spec.format == SDL_AUDIO_UNKNOWN)) {
+                    SDL_copyp(&stream->dst_spec, &device->spec);
+                } else if (!recording && (stream->src_spec.format == SDL_AUDIO_UNKNOWN)) {
+                    SDL_copyp(&stream->src_spec, &device->spec);
+                }
+
                 stream->bound_device = logdev;
                 stream->prev_binding = NULL;
                 stream->next_binding = logdev->bound_streams;