https://github.com/libsdl-org/SDL/commit/c58d95c3431c028d59e2af3d0d4b6e045e3c4f22
From c58d95c3431c028d59e2af3d0d4b6e045e3c4f22 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Fri, 14 Jul 2023 19:55:30 -0400
Subject: [PATCH] wasapi: Reworked for new SDL3 audio API, other win32 fixes.
The WinRT code has _also_ be updated, but it has not been
tested or compiled, yet.
---
src/audio/SDL_audio.c | 44 +-
src/audio/SDL_audiocvt.c | 2 +-
src/audio/SDL_sysaudio.h | 6 +
src/audio/wasapi/SDL_wasapi.c | 764 +++++++++++++++-----------
src/audio/wasapi/SDL_wasapi.h | 25 +-
src/audio/wasapi/SDL_wasapi_win32.c | 58 +-
src/audio/wasapi/SDL_wasapi_winrt.cpp | 262 ++++-----
src/core/windows/SDL_immdevice.c | 7 +-
src/core/windows/SDL_immdevice.h | 2 +-
test/loopwave.c | 2 +-
10 files changed, 635 insertions(+), 537 deletions(-)
diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index 5224cb067ef1..9d770b817097 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -1082,7 +1082,7 @@ int SDL_GetAudioDeviceFormat(SDL_AudioDeviceID devid, SDL_AudioSpec *spec)
return 0;
}
-// this expects the device lock to be held.
+// this expects the device lock to be held. !!! FIXME: no it doesn't...?
static void ClosePhysicalAudioDevice(SDL_AudioDevice *device)
{
SDL_assert(current_audio.impl.ProvidesOwnCallbackThread || ((device->thread == NULL) == (SDL_AtomicGet(&device->thread_alive) == 0)));
@@ -1688,3 +1688,45 @@ void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
SDL_AudioDeviceDisconnected(current_default_device); // Call again, now that we're not the default; this will remove from device list, send removal events, and destroy the SDL_AudioDevice.
}
}
+
+int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
+{
+ SDL_bool kill_device = SDL_FALSE;
+
+ const int orig_buffer_size = device->buffer_size;
+ const SDL_bool iscapture = device->iscapture;
+
+ if ((device->spec.format != newspec->format) || (device->spec.channels != newspec->channels) || (device->spec.freq != newspec->freq)) {
+ SDL_memcpy(&device->spec, newspec, sizeof (newspec));
+ for (SDL_LogicalAudioDevice *logdev = device->logical_devices; !kill_device && (logdev != NULL); logdev = logdev->next) {
+ for (SDL_AudioStream *stream = logdev->bound_streams; !kill_device && (stream != NULL); stream = stream->next_binding) {
+ if (SDL_SetAudioStreamFormat(stream, iscapture ? &device->spec : NULL, iscapture ? NULL : &device->spec) == -1) {
+ kill_device = SDL_TRUE;
+ }
+ }
+ }
+ }
+
+ if (!kill_device) {
+ device->sample_frames = new_sample_frames;
+ SDL_UpdatedAudioDeviceFormat(device);
+ if (device->work_buffer && (device->buffer_size > orig_buffer_size)) {
+ SDL_aligned_free(device->work_buffer);
+ device->work_buffer = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), device->buffer_size);
+ if (!device->work_buffer) {
+ kill_device = SDL_TRUE;
+ }
+ }
+ }
+
+ return kill_device ? -1 : 0;
+}
+
+int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames)
+{
+ SDL_LockMutex(device->lock);
+ const int retval = SDL_AudioDeviceFormatChangedAlreadyLocked(device, newspec, new_sample_frames);
+ SDL_UnlockMutex(device->lock);
+ return retval;
+}
+
diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c
index effc3af25504..d7e2c54f412e 100644
--- a/src/audio/SDL_audiocvt.c
+++ b/src/audio/SDL_audiocvt.c
@@ -879,7 +879,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
// if they are upsampling and we end up needing less than a frame of input, we reject it because it would cause artifacts on future reads to eat a full input frame.
// however, if the stream is flushed, we would just be padding any remaining input with silence anyhow, so use it up.
if (stream->flushed) {
- SDL_assert(((input_frames * src_sample_frame_size) + future_buffer_filled_frames) <= stream->future_buffer_allocation);
+ SDL_assert(((size_t) ((input_frames * src_sample_frame_size) + future_buffer_filled_frames)) <= stream->future_buffer_allocation);
// leave input_frames alone; this will just shuffle what's available from the future buffer and pad with silence as appropriate, below.
} else {
return 0;
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 2a034b5c85ec..7e170330a234 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -85,6 +85,12 @@ extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device);
// Backends should call this if the system default device changes.
extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device);
+// Backends should call this if a device's format is changing (opened or not); SDL will update state and carry on with the new format.
+extern int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
+
+// Same as above, but assume the device is already locked.
+extern int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
+
// Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. DOES NOT LOCK THE DEVICE.
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle);
diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c
index d91d4239997a..2f2ad8ff3a1b 100644
--- a/src/audio/wasapi/SDL_wasapi.c
+++ b/src/audio/wasapi/SDL_wasapi.c
@@ -24,6 +24,7 @@
#include "../../core/windows/SDL_windows.h"
#include "../../core/windows/SDL_immdevice.h"
+#include "../../thread/SDL_systhread.h"
#include "../SDL_audio_c.h"
#include "../SDL_sysaudio.h"
@@ -32,7 +33,7 @@
#include "SDL_wasapi.h"
-/* These constants aren't available in older SDKs */
+// These constants aren't available in older SDKs
#ifndef AUDCLNT_STREAMFLAGS_RATEADJUST
#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
#endif
@@ -43,140 +44,367 @@
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
#endif
-/* Some GUIDs we need to know without linking to libraries that aren't available before Vista. */
+// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
-static void WASAPI_DetectDevices(void)
+// WASAPI is _really_ particular about various things happening on the same thread, for COM and such,
+// so we proxy various stuff to a single background thread to manage.
+
+typedef struct ManagementThreadPendingTask
+{
+ ManagementThreadTask fn;
+ void *userdata;
+ int result;
+ SDL_Semaphore *task_complete_sem;
+ char *errorstr;
+ struct ManagementThreadPendingTask *next;
+} ManagementThreadPendingTask;
+
+static SDL_Thread *ManagementThread = NULL;
+static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL;
+static SDL_Mutex *ManagementThreadLock = NULL;
+static SDL_Condition *ManagementThreadCondition = NULL;
+static SDL_AtomicInt ManagementThreadShutdown;
+
+static void ManagementThreadMainloop(void)
{
- WASAPI_EnumerateEndpoints();
+ SDL_LockMutex(ManagementThreadLock);
+ ManagementThreadPendingTask *task;
+ while (((task = SDL_AtomicGetPtr(&ManagementThreadPendingTasks)) != NULL) || !SDL_AtomicGet(&ManagementThreadShutdown)) {
+ if (!task) {
+ SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do.
+ } else {
+ SDL_AtomicSetPtr(&ManagementThreadPendingTasks, task->next); // take task off the pending list.
+ SDL_UnlockMutex(ManagementThreadLock); // let other things add to the list while we chew on this task.
+ task->result = task->fn(task->userdata); // run this task.
+ if (task->task_complete_sem) { // something waiting on result?
+ task->errorstr = SDL_strdup(SDL_GetError());
+ SDL_PostSemaphore(task->task_complete_sem);
+ } else { // nothing waiting, we're done, free it.
+ SDL_free(task);
+ }
+ SDL_LockMutex(ManagementThreadLock); // regrab the lock so we can get the next task; if nothing to do, we'll release the lock in SDL_WaitCondition.
+ }
+ }
+ SDL_UnlockMutex(ManagementThreadLock); // told to shut down and out of tasks, let go of the lock and return.
}
-static SDL_INLINE SDL_bool WasapiFailed(SDL_AudioDevice *_this, const HRESULT err)
+int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_on_result)
{
- if (err == S_OK) {
- return SDL_FALSE;
+ // We want to block for a result, but we are already running from the management thread! Just run the task now so we don't deadlock.
+ if ((wait_on_result != NULL) && (SDL_ThreadID() == SDL_GetThreadID(ManagementThread))) {
+ *wait_on_result = task(userdata);
+ return 0; // completed!
}
- if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
- _this->hidden->device_lost = SDL_TRUE;
- } else if (SDL_AtomicGet(&_this->enabled)) {
- IAudioClient_Stop(_this->hidden->client);
- SDL_OpenedAudioDeviceDisconnected(_this);
- SDL_assert(!SDL_AtomicGet(&_this->enabled));
+ if (SDL_AtomicGet(&ManagementThreadShutdown)) {
+ return SDL_SetError("Can't add task, we're shutting down");
}
- return SDL_TRUE;
-}
+ ManagementThreadPendingTask *pending = SDL_calloc(1, sizeof(ManagementThreadPendingTask));
+ if (!pending) {
+ return SDL_OutOfMemory();
+ }
-static int UpdateAudioStream(SDL_AudioDevice *_this, const SDL_AudioSpec *oldspec)
-{
- /* Since WASAPI requires us to handle all audio conversion, and our
- device format might have changed, we might have to add/remove/change
- the audio stream that the higher level uses to convert data, so
- SDL keeps firing the callback as if nothing happened here. */
-
- if ((_this->callbackspec.channels == _this->spec.channels) &&
- (_this->callbackspec.format == _this->spec.format) &&
- (_this->callbackspec.freq == _this->spec.freq) &&
- (_this->callbackspec.samples == _this->spec.samples)) {
- /* no need to buffer/convert in an AudioStream! */
- SDL_DestroyAudioStream(_this->stream);
- _this->stream = NULL;
- } else if ((oldspec->channels == _this->spec.channels) &&
- (oldspec->format == _this->spec.format) &&
- (oldspec->freq == _this->spec.freq)) {
- /* The existing audio stream is okay to keep using. */
- } else {
- /* replace the audiostream for new format */
- SDL_DestroyAudioStream(_this->stream);
- if (_this->iscapture) {
- _this->stream = SDL_CreateAudioStream(_this->spec.format,
- _this->spec.channels, _this->spec.freq,
- _this->callbackspec.format,
- _this->callbackspec.channels,
- _this->callbackspec.freq);
- } else {
- _this->stream = SDL_CreateAudioStream(_this->callbackspec.format,
- _this->callbackspec.channels,
- _this->callbackspec.freq, _this->spec.format,
- _this->spec.channels, _this->spec.freq);
- }
+ pending->fn = task;
+ pending->userdata = userdata;
- if (!_this->stream) {
- return -1; /* SDL_CreateAudioStream should have called SDL_SetError. */
+ if (wait_on_result) {
+ pending->task_complete_sem = SDL_CreateSemaphore(0);
+ if (!pending->task_complete_sem) {
+ SDL_free(pending);
+ return -1;
}
}
- /* make sure our scratch buffer can cover the new device spec. */
- if (_this->spec.size > _this->work_buffer_len) {
- Uint8 *ptr = (Uint8 *)SDL_realloc(_this->work_buffer, _this->spec.size);
- if (ptr == NULL) {
- return SDL_OutOfMemory();
+ pending->next = NULL;
+
+ SDL_LockMutex(ManagementThreadLock);
+
+ // add to end of task list.
+ ManagementThreadPendingTask *prev = NULL;
+ for (ManagementThreadPendingTask *i = SDL_AtomicGetPtr(&ManagementThreadPendingTasks); i != NULL; i = i->next) {
+ prev = i;
+ }
+
+ if (prev != NULL) {
+ prev->next = pending;
+ } else {
+ SDL_AtomicSetPtr(&ManagementThreadPendingTasks, pending);
+ }
+
+ // task is added to the end of the pending list, let management thread rip!
+ SDL_SignalCondition(ManagementThreadCondition);
+ SDL_UnlockMutex(ManagementThreadLock);
+
+ if (wait_on_result) {
+ SDL_WaitSemaphore(pending->task_complete_sem);
+ SDL_DestroySemaphore(pending->task_complete_sem);
+ *wait_on_result = pending->result;
+ if (pending->errorstr) {
+ SDL_SetError("%s", pending->errorstr);
+ SDL_free(pending->errorstr);
}
- _this->work_buffer = ptr;
- _this->work_buffer_len = _this->spec.size;
+ SDL_free(pending);
+ }
+
+ return 0; // successfully added (and possibly executed)!
+}
+
+static int ManagementThreadPrepare(void)
+{
+ if (WASAPI_PlatformInit() == -1) {
+ return -1;
+ }
+
+ ManagementThreadLock = SDL_CreateMutex();
+ if (!ManagementThreadLock) {
+ WASAPI_PlatformDeinit();
+ return -1;
+ }
+
+ ManagementThreadCondition = SDL_CreateCondition();
+ if (!ManagementThreadCondition) {
+ SDL_DestroyMutex(ManagementThreadLock);
+ ManagementThreadLock = NULL;
+ WASAPI_PlatformDeinit();
+ return -1;
}
return 0;
}
-static void ReleaseWasapiDevice(SDL_AudioDevice *_this);
+typedef struct
+{
+ char *errorstr;
+ SDL_Semaphore *ready_sem;
+} ManagementThreadEntryData;
+
+static int ManagementThreadEntry(void *userdata)
+{
+ ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata;
+
+ if (ManagementThreadPrepare() < 0) {
+ data->errorstr = SDL_strdup(SDL_GetError());
+ SDL_PostSemaphore(data->ready_sem); // unblock calling thread.
+ return 0;
+ }
+
+ SDL_PostSemaphore(data->ready_sem); // unblock calling thread.
+ ManagementThreadMainloop();
+
+ WASAPI_PlatformDeinit();
+ return 0;
+}
-static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *_this)
+static int InitManagementThread(void)
{
- ReleaseWasapiDevice(_this); /* dump the lost device's handles. */
+ ManagementThreadEntryData mgmtdata;
+ SDL_zero(mgmtdata);
+ mgmtdata.ready_sem = SDL_CreateSemaphore(0);
+ if (!mgmtdata.ready_sem) {
+ return -1;
+ }
+
+ SDL_AtomicSetPtr(&ManagementThreadPendingTasks, NULL);
+ SDL_AtomicSet(&ManagementThreadShutdown, 0);
+ ManagementThread = SDL_CreateThreadInternal(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size?
+ if (!ManagementThread) {
+ return -1;
+ }
+
+ SDL_WaitSemaphore(mgmtdata.ready_sem);
+ SDL_DestroySemaphore(mgmtdata.ready_sem);
- if (_this->hidden->default_device_generation) {
- _this->hidden->default_device_generation = SDL_AtomicGet(_this->iscapture ? &SDL_IMMDevice_DefaultCaptureGeneration : &SDL_IMMDevice_DefaultPlaybackGeneration);
+ if (mgmtdata.errorstr) {
+ SDL_WaitThread(ManagementThread, NULL);
+ ManagementThread = NULL;
+ SDL_SetError("%s", mgmtdata.errorstr);
+ SDL_free(mgmtdata.errorstr);
+ return -1;
}
- /* this can fail for lots of reasons, but the most likely is we had a
- non-default device that was disconnected, so we can't recover. Default
- devices try to reinitialize whatever the new default is, so it's more
- likely to carry on here, but this handles a non-default device that
- simply had its format changed in the Windows Control Panel. */
- if (WASAPI_ActivateDevice(_this, SDL_TRUE) == -1) {
- SDL_OpenedAudioDeviceDisconnected(_this);
+ return 0;
+}
+
+static void DeinitManagementThread(void)
+{
+ if (ManagementThread) {
+ SDL_AtomicSet(&ManagementThreadShutdown, 1);
+ SDL_LockMutex(ManagementThreadLock);
+ SDL_SignalCondition(ManagementThreadCondition);
+ SDL_UnlockMutex(ManagementThreadLock);
+ SDL_WaitThread(ManagementThread, NULL);
+ ManagementThread = NULL;
+ }
+
+ SDL_assert(SDL_AtomicGetPtr(&ManagementThreadPendingTasks) == NULL);
+
+ SDL_DestroyCondition(ManagementThreadCondition);
+ SDL_DestroyMutex(ManagementThreadLock);
+ ManagementThreadCondition = NULL;
+ ManagementThreadLock = NULL;
+ SDL_AtomicSet(&ManagementThreadShutdown, 0);
+}
+
+typedef struct
+{
+ SDL_AudioDevice **default_output;
+ SDL_AudioDevice **default_capture;
+} mgmtthrtask_DetectDevicesData;
+
+static int mgmtthrtask_DetectDevices(void *userdata)
+{
+ mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
+ WASAPI_EnumerateEndpoints(data->default_output, data->default_capture);
+ return 0;
+}
+
+static void WASAPI_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
+{
+ int rc;
+ mgmtthrtask_DetectDevicesData data = { default_output, default_capture };
+ WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
+}
+
+static int mgmtthrtask_DisconnectDevice(void *userdata)
+{
+ SDL_AudioDeviceDisconnected((SDL_AudioDevice *)userdata);
+ return 0;
+}
+
+void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
+{
+ // this runs async, so it can hold the device lock from the management thread.
+ WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL);
+}
+
+static SDL_bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err)
+{
+ if (err == S_OK) {
return SDL_FALSE;
}
- _this->hidden->device_lost = SDL_FALSE;
+ if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
+ device->hidden->device_lost = SDL_TRUE;
+ } else {
+ IAudioClient_Stop(device->hidden->client);
+ WASAPI_DisconnectDevice(device);
+ }
+
+ return SDL_TRUE;
+}
+
+static int mgmtthrtask_ReleaseCaptureClient(void *userdata)
+{
+ IAudioCaptureClient_Release((IAudioCaptureClient *)userdata);
+ return 0;
+}
- return SDL_TRUE; /* okay, carry on with new device details! */
+static int mgmtthrtask_ReleaseRenderClient(void *userdata)
+{
+ IAudioRenderClient_Release((IAudioRenderClient *)userdata);
+ return 0;
}
-static SDL_bool RecoverWasapiIfLost(SDL_AudioDevice *_this)
+static int mgmtthrtask_ResetWasapiDevice(void *userdata)
{
- const int generation = _this->hidden->default_device_generation;
- SDL_bool lost = _this->hidden->device_lost;
+ SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
- if (!SDL_AtomicGet(&_this->enabled)) {
- return SDL_FALSE; /* already failed. */
+ if (!device || !device->hidden) {
+ return 0;
}
- if (!_this->hidden->client) {
- return SDL_TRUE; /* still waiting for activation. */
+ if (device->hidden->client) {
+ IAudioClient_Stop(device->hidden->client);
+ IAudioClient_Release(device->hidden->client);
+ device->hidden->client = NULL;
}
- if (!lost && (generation > 0)) { /* is a default device? */
- const int newgen = SDL_AtomicGet(_this->iscapture ? &SDL_IMMDevice_DefaultCaptureGeneration : &SDL_IMMDevice_DefaultPlaybackGeneration);
- if (generation != newgen) { /* the desired default device was changed, jump over to it. */
- lost = SDL_TRUE;
- }
+ if (device->hidden->render) {
+ // this is silly, but this will block indefinitely if you call it from SDLMMNotificationClient_OnDefaultDeviceChanged, so
+ // proxy this to the management thread to be released later.
+ WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, device->hidden->render, NULL);
+ device->hidden->render = NULL;
}
- return lost ? RecoverWasapiDevice(_this) : SDL_TRUE;
+ if (device->hidden->capture) {
+ // this is silly, but this will block indefinitely if you call it from SDLMMNotificationClient_OnDefaultDeviceChanged, so
+ // proxy this to the management thread to be released later.
+ WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, device->hidden->capture, NULL);
+ device->hidden->capture = NULL;
+ }
+
+ if (device->hidden->waveformat) {
+ CoTaskMemFree(device->hidden->waveformat);
+ device->hidden->waveformat = NULL;
+ }
+
+ if (device->hidden->activation_handler) {
+ WASAPI_PlatformDeleteActivationHandler(device->hidden->activation_handler);
+ device->hidden->activation_handler = NULL;
+ }
+
+ if (device->hidden->event) {
+ CloseHandle(device->hidden->event);
+ device->hidden->event = NULL;
+ }
+
+ return 0;
+}
+
+static void ResetWasapiDevice(SDL_AudioDevice *device)
+{
+ int rc;
+ WASAPI_ProxyToManagementThread(mgmtthrtask_ResetWasapiDevice, device, &rc);
}
-static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *_this)
+static int mgmtthrtask_ActivateDevice(void *userdata)
{
- /* get an endpoint buffer from WASAPI. */
+ return WASAPI_ActivateDevice((SDL_AudioDevice *)userdata);
+}
+
+static int ActivateWasapiDevice(SDL_AudioDevice *device)
+{
+ int rc;
+ return ((WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) < 0) || (rc < 0)) ? -1 : 0;
+}
+
+static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *device)
+{
+ ResetWasapiDevice(device); // dump the lost device's handles.
+
+ // This handles a non-default device that simply had its format changed in the Windows Control Panel.
+ if (ActivateWasapiDevice(device) == -1) {
+ WASAPI_DisconnectDevice(device);
+ return SDL_FALSE;
+ }
+
+ device->hidden->device_lost = SDL_FALSE;
+
+ return SDL_TRUE; // okay, carry on with new device details!
+}
+
+static SDL_bool RecoverWasapiIfLost(SDL_AudioDevice *device)
+{
+ if (SDL_AtomicGet(&device->shutdown)) {
+ return SDL_FALSE; // already failed.
+ } else if (!device->hidden->client) {
+ return SDL_TRUE; // still waiting for activation.
+ }
+
+ return device->hidden->device_lost ? RecoverWasapiDevice(device) : SDL_TRUE;
+}
+
+static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
+{
+ // get an endpoint buffer from WASAPI.
BYTE *buffer = NULL;
- while (RecoverWasapiIfLost(_this) && _this->hidden->render) {
- if (!WasapiFailed(_this, IAudioRenderClient_GetBuffer(_this->hidden->render, _this->spec.samples, &buffer))) {
+ while (RecoverWasapiIfLost(device) && device->hidden->render) {
+ if (!WasapiFailed(device, IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer))) {
return (Uint8 *)buffer;
}
SDL_assert(buffer == NULL);
@@ -185,197 +413,108 @@ static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *_this)
return (Uint8 *)buffer;
}
-static void WASAPI_PlayDevice(SDL_AudioDevice *_this)
+static void WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
- if (_this->hidden->render != NULL) { /* definitely activated? */
- /* WasapiFailed() will mark the device for reacquisition or removal elsewhere. */
- WasapiFailed(_this, IAudioRenderClient_ReleaseBuffer(_this->hidden->render, _this->spec.samples, 0));
+ 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));
}
}
-static void WASAPI_WaitDevice(SDL_AudioDevice *_this)
+static void WASAPI_WaitDevice(SDL_AudioDevice *device)
{
- while (RecoverWasapiIfLost(_this) && _this->hidden->client && _this->hidden->event) {
- DWORD waitResult = WaitForSingleObjectEx(_this->hidden->event, 200, FALSE);
+ while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) {
+ DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE);
if (waitResult == WAIT_OBJECT_0) {
- const UINT32 maxpadding = _this->spec.samples;
+ const UINT32 maxpadding = device->sample_frames;
UINT32 padding = 0;
- if (!WasapiFailed(_this, IAudioClient_GetCurrentPadding(_this->hidden->client, &padding))) {
- /*SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);*/
- if (_this->iscapture) {
- if (padding > 0) {
- break;
- }
- } else {
- if (padding <= maxpadding) {
- break;
- }
+ if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
+ //SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);*/
+ if (device->iscapture && (padding > 0)) {
+ break;
+ } else if (!device->iscapture && (padding <= maxpadding)) {
+ break;
}
}
} else if (waitResult != WAIT_TIMEOUT) {
- /*SDL_Log("WASAPI FAILED EVENT!");*/
- IAudioClient_Stop(_this->hidden->client);
- SDL_OpenedAudioDeviceDisconnected(_this);
+ //SDL_Log("WASAPI FAILED EVENT!");*/
+ IAudioClient_Stop(device->hidden->client);
+ WASAPI_DisconnectDevice(device);
}
}
}
-static int WASAPI_CaptureFromDevice(SDL_AudioDevice *_this, void *buffer, int buflen)
+static int WASAPI_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
- SDL_AudioStream *stream = _this->hidden->capturestream;
- const int avail = SDL_GetAudioStreamAvailable(stream);
- if (avail > 0) {
- const int cpy = SDL_min(buflen, avail);
- SDL_GetAudioStreamData(stream, buffer, cpy);
- return cpy;
- }
-
- while (RecoverWasapiIfLost(_this)) {
- HRESULT ret;
- BYTE *ptr = NULL;
- UINT32 frames = 0;
- DWORD flags = 0;
+ BYTE *ptr = NULL;
+ UINT32 frames = 0;
+ DWORD flags = 0;
- /* uhoh, client isn't activated yet, just return silence. */
- if (!_this->hidden->capture) {
- /* Delay so we run at about the speed that audio would be arriving. */
- SDL_Delay(((_this->spec.samples * 1000) / _this->spec.freq));
- SDL_memset(buffer, _this->spec.silence, buflen);
- return buflen;
+ while (RecoverWasapiIfLost(device) && device->hidden->capture) {
+ HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
+ if (ret == AUDCLNT_S_BUFFER_EMPTY) {
+ return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL
}
- ret = IAudioCaptureClient_GetBuffer(_this->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
- if (ret != AUDCLNT_S_BUFFER_EMPTY) {
- WasapiFailed(_this, ret); /* mark device lost/failed if necessary. */
- }
+ WasapiFailed(device, ret); // mark device lost/failed if necessary.
- if ((ret == AUDCLNT_S_BUFFER_EMPTY) || !frames) {
- WASAPI_WaitDevice(_this);
- } else if (ret == S_OK) {
- const int total = ((int)frames) * _this->hidden->framesize;
+ if (ret == S_OK) {
+ const int total = ((int)frames) * device->hidden->framesize;
const int cpy = SDL_min(buflen, total);
const int leftover = total - cpy;
const SDL_bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? SDL_TRUE : SDL_FALSE;
+ SDL_assert(leftover == 0); // according to MSDN, this isn't everything available, just one "packet" of data per-GetBuffer call.
+
if (silent) {
- SDL_memset(buffer, _this->spec.silence, cpy);
+ SDL_memset(buffer, device->silence_value, cpy);
} else {
SDL_memcpy(buffer, ptr, cpy);
}
- if (leftover > 0) {
- ptr += cpy;
- if (silent) {
- SDL_memset(ptr, _this->spec.silence, leftover); /* I guess this is safe? */
- }
-
- if (SDL_PutAudioStreamData(stream, ptr, leftover) == -1) {
- return -1; /* uhoh, out of memory, etc. Kill device. :( */
- }
- }
-
- ret = IAudioCaptureClient_ReleaseBuffer(_this->hidden->capture, frames);
- WasapiFailed(_this, ret); /* mark device lost/failed if necessary. */
+ ret = IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames);
+ WasapiFailed(device, ret); // mark device lost/failed if necessary.
return cpy;
}
}
- return -1; /* unrecoverable error. */
+ return -1; // unrecoverable error.
}
-static void WASAPI_FlushCapture(SDL_AudioDevice *_this)
+static void WASAPI_FlushCapture(SDL_AudioDevice *device)
{
BYTE *ptr = NULL;
UINT32 frames = 0;
DWORD flags = 0;
- if (!_this->hidden->capture) {
- return; /* not activated yet? */
- }
-
- /* just read until we stop getting packets, throwing them away. */
- while (SDL_TRUE) {
- const HRESULT ret = IAudioCaptureClient_GetBuffer(_this->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
+ // just read until we stop getting packets, throwing them away.
+ while (!SDL_AtomicGet(&device->shutdown) && device->hidden->capture) {
+ const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
- break; /* no more buffered data; we're done. */
- } else if (WasapiFailed(_this, ret)) {
- break; /* failed for some other reason, abort. */
- } else if (WasapiFailed(_this, IAudioCaptureClient_ReleaseBuffer(_this->hidden->capture, frames))) {
- break; /* something broke. */
+ break; // no more buffered data; we're done.
+ } else if (WasapiFailed(device, ret)) {
+ break; // failed for some other reason, abort.
+ } else if (WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames))) {
+ break; // something broke.
}
}
- SDL_ClearAudioStream(_this->hidden->capturestream);
}
(Patch may be truncated, please check the link at the top of this post.)