SDL: audio: PlayDevice() should return an error code.

From 4e0c7c91fc3553877fcee43a532d3e8bc524deac Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Wed, 30 Aug 2023 19:16:39 -0400
Subject: [PATCH] audio: PlayDevice() should return an error code.

Higher level code treats errors as fatal and disconnects the device.
---
 src/audio/SDL_audio.c                      |  8 +++++---
 src/audio/SDL_sysaudio.h                   |  2 +-
 src/audio/aaudio/SDL_aaudio.c              |  5 +++--
 src/audio/alsa/SDL_alsa_audio.c            |  7 ++++---
 src/audio/android/SDL_androidaudio.c       |  3 ++-
 src/audio/coreaudio/SDL_coreaudio.m        |  3 ++-
 src/audio/directsound/SDL_directsound.c    |  7 +++++--
 src/audio/disk/SDL_diskaudio.c             |  5 +++--
 src/audio/dsp/SDL_dspaudio.c               |  6 +++---
 src/audio/emscripten/SDL_emscriptenaudio.c |  3 ++-
 src/audio/haiku/SDL_haikuaudio.cc          |  3 ++-
 src/audio/jack/SDL_jackaudio.c             |  4 +++-
 src/audio/n3ds/SDL_n3dsaudio.c             |  4 +++-
 src/audio/netbsd/SDL_netbsdaudio.c         | 10 ++++------
 src/audio/openslES/SDL_openslES.c          |  4 +++-
 src/audio/pipewire/SDL_pipewire.c          |  4 +++-
 src/audio/ps2/SDL_ps2audio.c               |  5 +++--
 src/audio/psp/SDL_pspaudio.c               |  8 +++++---
 src/audio/pulseaudio/SDL_pulseaudio.c      |  6 +++---
 src/audio/qnx/SDL_qsa_audio.c              | 16 +++++++---------
 src/audio/sndio/SDL_sndioaudio.c           |  5 +++--
 src/audio/vita/SDL_vitaaudio.c             |  4 ++--
 src/audio/wasapi/SDL_wasapi.c              |  3 ++-
 23 files changed, 73 insertions(+), 52 deletions(-)

diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index 2f2116edc871..58f2877063a7 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -412,7 +412,7 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
 
 static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ }
 static void SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { /* no-op. */ }
-static void SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { /* no-op. */ }
+static int SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size) { return 0; /* no-op. */ }
 static void SDL_AudioWaitCaptureDevice_Default(SDL_AudioDevice *device) { /* no-op. */ }
 static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) { /* no-op. */ }
 static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ }
@@ -731,8 +731,10 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device)
             }
         }
 
-        // !!! FIXME: have PlayDevice return a value and do disconnects in here with it.
-        current_audio.impl.PlayDevice(device, mix_buffer, buffer_size);  // this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice!
+        // PlayDevice SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice instead!
+        if (current_audio.impl.PlayDevice(device, mix_buffer, buffer_size) < 0) {
+            retval = SDL_FALSE;
+        }
     }
 
     SDL_UnlockMutex(device->lock);
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 81104e594eb9..1b6938e4daa1 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -121,7 +121,7 @@ typedef struct SDL_AudioDriverImpl
     void (*ThreadInit)(SDL_AudioDevice *device);   // Called by audio thread at start
     void (*ThreadDeinit)(SDL_AudioDevice *device); // Called by audio thread at end
     void (*WaitDevice)(SDL_AudioDevice *device);
-    void (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen);  // buffer and buflen are always from GetDeviceBuf, passed here for convenience.
+    int (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen);  // buffer and buflen are always from GetDeviceBuf, passed here for convenience.
     Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
     void (*WaitCaptureDevice)(SDL_AudioDevice *device);
     int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c
index b843ba6c2d7f..5b12d6f522cc 100644
--- a/src/audio/aaudio/SDL_aaudio.c
+++ b/src/audio/aaudio/SDL_aaudio.c
@@ -111,13 +111,14 @@ static void AAUDIO_WaitDevice(SDL_AudioDevice *device)
     SDL_WaitSemaphore(device->hidden->semaphore);
 }
 
-static void AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     // AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
     if (SDL_AtomicGet(&device->hidden->error_callback_triggered)) {
         SDL_AtomicSet(&device->hidden->error_callback_triggered, 0);
-        SDL_AudioDeviceDisconnected(device);
+        return -1;
     }
+    return 0;
 }
 
 // no need for a FlushCapture implementation, just don't read mixbuf until the next iteration.
diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c
index 66a3b3b00433..816a0c21749c 100644
--- a/src/audio/alsa/SDL_alsa_audio.c
+++ b/src/audio/alsa/SDL_alsa_audio.c
@@ -351,7 +351,7 @@ static void ALSA_WaitDevice(SDL_AudioDevice *device)
     }
 }
 
-static void ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     SDL_assert(buffer == device->hidden->mixbuf);
     Uint8 *sample_buf = device->hidden->mixbuf;
@@ -378,8 +378,7 @@ static void ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int bu
                 SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
                              "ALSA write failed (unrecoverable): %s",
                              ALSA_snd_strerror(status));
-                SDL_AudioDeviceDisconnected(device);
-                return;
+                return -1;
             }
             continue;
         } else if (status == 0) {
@@ -391,6 +390,8 @@ static void ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int bu
         sample_buf += status * frame_size;
         frames_left -= status;
     }
+
+    return 0;
 }
 
 static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
diff --git a/src/audio/android/SDL_androidaudio.c b/src/audio/android/SDL_androidaudio.c
index 761ec5be3765..c0dade8acb6e 100644
--- a/src/audio/android/SDL_androidaudio.c
+++ b/src/audio/android/SDL_androidaudio.c
@@ -87,9 +87,10 @@ static int ANDROIDAUDIO_OpenDevice(SDL_AudioDevice *device)
 
 // !!! FIXME: this needs a WaitDevice implementation.
 
-static void ANDROIDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int ANDROIDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     Android_JNI_WriteAudioBuffer();
+    return 0;
 }
 
 static Uint8 *ANDROIDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m
index b96db86b9f71..31be6db0832f 100644
--- a/src/audio/coreaudio/SDL_coreaudio.m
+++ b/src/audio/coreaudio/SDL_coreaudio.m
@@ -525,7 +525,7 @@ static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_b
 #endif
 
 
-static void COREAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
+static int COREAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
 {
     AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
     SDL_assert(current_buffer != NULL);  // should have been called from OutputBufferReadyCallback
@@ -533,6 +533,7 @@ static void COREAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, i
     current_buffer->mAudioDataByteSize = current_buffer->mAudioDataBytesCapacity;
     device->hidden->current_buffer = NULL;
     AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL);
+    return 0;
 }
 
 static Uint8 *COREAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
diff --git a/src/audio/directsound/SDL_directsound.c b/src/audio/directsound/SDL_directsound.c
index 35c0f97f69d5..db68648d7e30 100644
--- a/src/audio/directsound/SDL_directsound.c
+++ b/src/audio/directsound/SDL_directsound.c
@@ -285,11 +285,14 @@ static void DSOUND_WaitDevice(SDL_AudioDevice *device)
     }
 }
 
-static void DSOUND_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int DSOUND_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     // Unlock the buffer, allowing it to play
     SDL_assert(buflen == device->buffer_size);
-    IDirectSoundBuffer_Unlock(device->hidden->mixbuf, (LPVOID) buffer, buflen, NULL, 0);
+    if (IDirectSoundBuffer_Unlock(device->hidden->mixbuf, (LPVOID) buffer, buflen, NULL, 0) != DS_OK) {
+        return -1;
+    }
+    return 0;
 }
 
 static Uint8 *DSOUND_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c
index e7830ccc4984..b4cf2beebb20 100644
--- a/src/audio/disk/SDL_diskaudio.c
+++ b/src/audio/disk/SDL_diskaudio.c
@@ -40,15 +40,16 @@ static void DISKAUDIO_WaitDevice(SDL_AudioDevice *device)
     SDL_Delay(device->hidden->io_delay);
 }
 
-static void DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
+static int DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
 {
     const int written = (int)SDL_RWwrite(device->hidden->io, buffer, (size_t)buffer_size);
     if (written != buffer_size) { // If we couldn't write, assume fatal error for now
-        SDL_AudioDeviceDisconnected(device);
+        return -1;
     }
 #ifdef DEBUG_AUDIO
     SDL_Log("DISKAUDIO: Wrote %d bytes of audio data", (int) written);
 #endif
+    return 0;
 }
 
 static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
diff --git a/src/audio/dsp/SDL_dspaudio.c b/src/audio/dsp/SDL_dspaudio.c
index 004acef98c69..a6550bf3b715 100644
--- a/src/audio/dsp/SDL_dspaudio.c
+++ b/src/audio/dsp/SDL_dspaudio.c
@@ -225,17 +225,17 @@ static void DSP_WaitDevice(SDL_AudioDevice *device)
     }
 }
 
-static void DSP_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int DSP_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     struct SDL_PrivateAudioData *h = device->hidden;
     if (write(h->audio_fd, buffer, buflen) == -1) {
         perror("Audio write");
-        SDL_AudioDeviceDisconnected(device);
-        return;
+        return -1;
     }
 #ifdef DEBUG_AUDIO
     fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen);
 #endif
+    return 0;
 }
 
 static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
diff --git a/src/audio/emscripten/SDL_emscriptenaudio.c b/src/audio/emscripten/SDL_emscriptenaudio.c
index 408706978053..62780dcfcf22 100644
--- a/src/audio/emscripten/SDL_emscriptenaudio.c
+++ b/src/audio/emscripten/SDL_emscriptenaudio.c
@@ -36,7 +36,7 @@ static Uint8 *EMSCRIPTENAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_
     return device->hidden->mixbuf;
 }
 
-static void EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
+static int EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
 {
     const int framelen = (SDL_AUDIO_BITSIZE(device->spec.format) / 8) * device->spec.channels;
     MAIN_THREAD_EM_ASM({
@@ -53,6 +53,7 @@ static void EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buf
             }
         }
     }, buffer, buffer_size / framelen);
+    return 0;
 }
 
 
diff --git a/src/audio/haiku/SDL_haikuaudio.cc b/src/audio/haiku/SDL_haikuaudio.cc
index c34142f3468c..9ccaf3c43f9a 100644
--- a/src/audio/haiku/SDL_haikuaudio.cc
+++ b/src/audio/haiku/SDL_haikuaudio.cc
@@ -46,13 +46,14 @@ static Uint8 *HAIKUAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
     return device->hidden->current_buffer;
 }
 
-static void HAIKUAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
+static int HAIKUAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
 {
     // We already wrote our output right into the BSoundPlayer's callback's stream. Just clean up our stuff.
     SDL_assert(device->hidden->current_buffer != NULL);
     SDL_assert(device->hidden->current_buffer_len > 0);
     device->hidden->current_buffer = NULL;
     device->hidden->current_buffer_len = 0;
+    return 0;
 }
 
 // The Haiku callback for handling the audio buffer
diff --git a/src/audio/jack/SDL_jackaudio.c b/src/audio/jack/SDL_jackaudio.c
index 3e3ed20828d2..e6567d4c705f 100644
--- a/src/audio/jack/SDL_jackaudio.c
+++ b/src/audio/jack/SDL_jackaudio.c
@@ -149,7 +149,7 @@ static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
     return 0;
 }
 
-static void JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int buflen)
+static int JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int buflen)
 {
     const float *buffer = (float *) ui8buffer;
     jack_port_t **ports = device->hidden->sdlports;
@@ -167,6 +167,8 @@ static void JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int
             }
         }
     }
+
+    return 0;
 }
 
 static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
diff --git a/src/audio/n3ds/SDL_n3dsaudio.c b/src/audio/n3ds/SDL_n3dsaudio.c
index f17ac5601f97..e5f4bad8cf91 100644
--- a/src/audio/n3ds/SDL_n3dsaudio.c
+++ b/src/audio/n3ds/SDL_n3dsaudio.c
@@ -176,7 +176,7 @@ static int N3DSAUDIO_OpenDevice(SDL_AudioDevice *device)
     return 0;
 }
 
-static void N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     contextLock(device);
 
@@ -196,6 +196,8 @@ static void N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, i
     DSP_FlushDataCache(device->hidden->waveBuf[nextbuf].data_vaddr, buflen);
 
     ndspChnWaveBufAdd(0, &device->hidden->waveBuf[nextbuf]);
+
+    return 0;
 }
 
 static void N3DSAUDIO_WaitDevice(SDL_AudioDevice *device)
diff --git a/src/audio/netbsd/SDL_netbsdaudio.c b/src/audio/netbsd/SDL_netbsdaudio.c
index 4165b056df51..2d43a72b88c2 100644
--- a/src/audio/netbsd/SDL_netbsdaudio.c
+++ b/src/audio/netbsd/SDL_netbsdaudio.c
@@ -141,20 +141,18 @@ static void NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device)
     }
 }
 
-static void NETBSDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int NETBSDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     struct SDL_PrivateAudioData *h = device->hidden;
     const int written = write(h->audio_fd, buffer, buflen);
-    if (written == -1) {
-        // Non recoverable error has occurred. It should be reported!!!
-        SDL_AudioDeviceDisconnected(device);
-        perror("audio");
-        return;
+    if (written != buflen) {  // Treat even partial writes as fatal errors.
+        return -1;
     }
 
 #ifdef DEBUG_AUDIO
     fprintf(stderr, "Wrote %d bytes of audio data\n", written);
 #endif
+    return 0;
 }
 
 static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
diff --git a/src/audio/openslES/SDL_openslES.c b/src/audio/openslES/SDL_openslES.c
index 37c7dc8cb338..3d29eb8f5bc8 100644
--- a/src/audio/openslES/SDL_openslES.c
+++ b/src/audio/openslES/SDL_openslES.c
@@ -638,7 +638,7 @@ static void openslES_WaitDevice(SDL_AudioDevice *device)
     SDL_WaitSemaphore(audiodata->playsem);
 }
 
-static void openslES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int openslES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     struct SDL_PrivateAudioData *audiodata = device->hidden;
 
@@ -657,6 +657,8 @@ static void openslES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, in
     if (SL_RESULT_SUCCESS != result) {
         SDL_PostSemaphore(audiodata->playsem);
     }
+
+    return 0;
 }
 
 ///           n   playn sem
diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c
index 419fbfcb8667..edd1c4a5c514 100644
--- a/src/audio/pipewire/SDL_pipewire.c
+++ b/src/audio/pipewire/SDL_pipewire.c
@@ -940,7 +940,7 @@ static Uint8 *PIPEWIRE_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
     return (Uint8 *) spa_buf->datas[0].data;
 }
 
-static void PIPEWIRE_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
+static int PIPEWIRE_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
 {
     struct pw_stream *stream = device->hidden->stream;
     struct pw_buffer *pw_buf = device->hidden->pw_buf;
@@ -951,6 +951,8 @@ static void PIPEWIRE_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, in
 
     PIPEWIRE_pw_stream_queue_buffer(stream, pw_buf);
     device->hidden->pw_buf = NULL;
+
+    return 0;
 }
 
 static void output_callback(void *data)
diff --git a/src/audio/ps2/SDL_ps2audio.c b/src/audio/ps2/SDL_ps2audio.c
index e50a497dffdd..2165194eb182 100644
--- a/src/audio/ps2/SDL_ps2audio.c
+++ b/src/audio/ps2/SDL_ps2audio.c
@@ -85,9 +85,10 @@ static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device)
     return 0;
 }
 
-static void PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
-    audsrv_play_audio((char *)buffer, buflen);
+    // this returns number of bytes accepted or a negative error. We assume anything other than buflen is a fatal error.
+    return (audsrv_play_audio((char *)buffer, buflen) != buflen) ? -1 : 0;
 }
 
 static void PS2AUDIO_WaitDevice(SDL_AudioDevice *device)
diff --git a/src/audio/psp/SDL_pspaudio.c b/src/audio/psp/SDL_pspaudio.c
index b5af4e8813ba..d4900572a9f5 100644
--- a/src/audio/psp/SDL_pspaudio.c
+++ b/src/audio/psp/SDL_pspaudio.c
@@ -106,14 +106,16 @@ static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device)
     return 0;
 }
 
-static void PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
+    int rc;
     if (!isBasicAudioConfig(&device->spec)) {
         SDL_assert(device->spec.channels == 2);
-        sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, (void *) buffer);
+        rc = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, (void *) buffer);
     } else {
-        sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer);
+        rc = sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer);
     }
+    return (rc == 0) ? 0 : -1;
 }
 
 static void PSPAUDIO_WaitDevice(SDL_AudioDevice *device)
diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c
index fb6c35182795..c3028c0a6813 100644
--- a/src/audio/pulseaudio/SDL_pulseaudio.c
+++ b/src/audio/pulseaudio/SDL_pulseaudio.c
@@ -388,7 +388,7 @@ static void PULSEAUDIO_WaitDevice(SDL_AudioDevice *device)
     PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
 }
 
-static void PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
+static int PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
 {
     struct SDL_PrivateAudioData *h = device->hidden;
 
@@ -401,14 +401,14 @@ static void PULSEAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer,
     PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
 
     if (rc < 0) {
-        SDL_AudioDeviceDisconnected(device);
-        return;
+        return -1;
     }
 
     /*printf("PULSEAUDIO FEED! nbytes=%d\n", buffer_size);*/
     h->bytes_requested -= buffer_size;
 
     /*printf("PULSEAUDIO PLAYDEVICE END! written=%d\n", written);*/
+    return 0;
 }
 
 static Uint8 *PULSEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
diff --git a/src/audio/qnx/SDL_qsa_audio.c b/src/audio/qnx/SDL_qsa_audio.c
index 5c60e555cd0d..d2552ce854b7 100644
--- a/src/audio/qnx/SDL_qsa_audio.c
+++ b/src/audio/qnx/SDL_qsa_audio.c
@@ -110,10 +110,10 @@ static void QSA_WaitDevice(SDL_AudioDevice *device)
     }
 }
 
-static void QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     if (SDL_AtomicGet(&device->shutdown) || !device->hidden) {
-        return;
+        return 0;
     }
 
     int towrite = buflen;
@@ -125,7 +125,7 @@ static void QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buf
             // Check if samples playback got stuck somewhere in hardware or in the audio device driver
             if ((errno == EAGAIN) && (bw == 0)) {
                 if (device->hidden->timeout_on_wait) {
-                    return;  // oh well, try again next time.  !!! FIXME: Should we just disconnect the device in this case?
+                    return 0;  // oh well, try again next time.  !!! FIXME: Should we just disconnect the device in this case?
                 }
             }
 
@@ -145,17 +145,17 @@ static void QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buf
                 int status = snd_pcm_plugin_status(device->hidden->audio_handle, &cstatus);
                 if (status < 0) {
                     QSA_SetError("snd_pcm_plugin_status", status);
-                    return;  // !!! FIXME: disconnect the device?
+                    return -1;
                 } else if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) {
                     status = snd_pcm_plugin_prepare(device->hidden->audio_handle, device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
                     if (status < 0) {
                         QSA_SetError("snd_pcm_plugin_prepare", status);
-                        return;  // !!! FIXME: disconnect the device?
+                        return -1;
                     }
                 }
                 continue;
             } else {
-                return;  // !!! FIXME: disconnect the device?
+                return -1;
             }
         } else {
             // we wrote all remaining data
@@ -165,9 +165,7 @@ static void QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buf
     }
 
     // If we couldn't write, assume fatal error for now
-    if (towrite != 0) {
-        SDL_AudioDeviceDisconnected(device);
-    }
+    return (towrite != 0) ? -1 : 0;
 }
 
 static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
diff --git a/src/audio/sndio/SDL_sndioaudio.c b/src/audio/sndio/SDL_sndioaudio.c
index a7ac91ddf706..d1685a747168 100644
--- a/src/audio/sndio/SDL_sndioaudio.c
+++ b/src/audio/sndio/SDL_sndioaudio.c
@@ -175,16 +175,17 @@ static void SNDIO_WaitDevice(SDL_AudioDevice *device)
     }
 }
 
-static void SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     // !!! FIXME: this should be non-blocking so we can check device->shutdown.
     // this is set to blocking, because we _have_ to send the entire buffer down, but hopefully WaitDevice took most of the delay time.
     if (SNDIO_sio_write(device->hidden->dev, buffer, buflen) != buflen) {
-        SDL_AudioDeviceDisconnected(device);  // If we couldn't write, assume fatal error for now
+        return -1;  // If we couldn't write, assume fatal error for now
     }
 #ifdef DEBUG_AUDIO
     fprintf(stderr, "Wrote %d bytes of audio data\n", written);
 #endif
+    return 0;
 }
 
 static int SNDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
diff --git a/src/audio/vita/SDL_vitaaudio.c b/src/audio/vita/SDL_vitaaudio.c
index efe519552197..6e6190c9552a 100644
--- a/src/audio/vita/SDL_vitaaudio.c
+++ b/src/audio/vita/SDL_vitaaudio.c
@@ -130,9 +130,9 @@ static int VITAAUD_OpenDevice(SDL_AudioDevice *device)
     return 0;
 }
 
-static void VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
+static int VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
 {
-    sceAudioOutOutput(device->hidden->port, buffer);
+    return (sceAudioOutOutput(device->hidden->port, buffer) == 0) ? 0 : -1;
 }
 
 // This function waits until it is possible to write a full sound buffer
diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c
index 2041bc88b47e..a50228487b10 100644
--- a/src/audio/wasapi/SDL_wasapi.c
+++ b/src/audio/wasapi/SDL_wasapi.c
@@ -413,12 +413,13 @@ static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
     return (Uint8 *)buffer;
 }
 
-static void WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+static int WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
 {
     if (device->hidden->render != NULL) { // definitely activated?
         // WasapiFailed() will mark the device for reacquisition or removal elsewhere.
         WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0));
     }
+    return 0;
 }
 
 static void WASAPI_WaitDevice(SDL_AudioDevice *device)