summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/audio/wasapi
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/audio/wasapi')
-rw-r--r--contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.c963
-rw-r--r--contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.h61
2 files changed, 1024 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.c b/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.c
new file mode 100644
index 0000000..db0974b
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.c
@@ -0,0 +1,963 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_AUDIO_DRIVER_WASAPI
24
25#include "../../core/windows/SDL_windows.h"
26#include "../../core/windows/SDL_immdevice.h"
27#include "../../thread/SDL_systhread.h"
28#include "../SDL_sysaudio.h"
29
30#define COBJMACROS
31#include <audioclient.h>
32
33#include "SDL_wasapi.h"
34
35// These constants aren't available in older SDKs
36#ifndef AUDCLNT_STREAMFLAGS_RATEADJUST
37#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
38#endif
39#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
40#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
41#endif
42#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
43#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
44#endif
45
46// handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency).
47static HMODULE libavrt = NULL;
48typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
49typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
50static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
51static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
52
53// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
54static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
55static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
56static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
57#ifdef __IAudioClient3_INTERFACE_DEFINED__
58static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } };
59#endif //
60
61static bool immdevice_initialized = false;
62
63// WASAPI is _really_ particular about various things happening on the same thread, for COM and such,
64// so we proxy various stuff to a single background thread to manage.
65
66typedef struct ManagementThreadPendingTask
67{
68 ManagementThreadTask fn;
69 void *userdata;
70 bool result;
71 SDL_Semaphore *task_complete_sem;
72 char *errorstr;
73 struct ManagementThreadPendingTask *next;
74} ManagementThreadPendingTask;
75
76static SDL_Thread *ManagementThread = NULL;
77static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL;
78static SDL_Mutex *ManagementThreadLock = NULL;
79static SDL_Condition *ManagementThreadCondition = NULL;
80static SDL_AtomicInt ManagementThreadShutdown;
81
82static void ManagementThreadMainloop(void)
83{
84 SDL_LockMutex(ManagementThreadLock);
85 ManagementThreadPendingTask *task;
86 while (((task = (ManagementThreadPendingTask *)SDL_GetAtomicPointer((void **)&ManagementThreadPendingTasks)) != NULL) || !SDL_GetAtomicInt(&ManagementThreadShutdown)) {
87 if (!task) {
88 SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do.
89 } else {
90 SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, task->next); // take task off the pending list.
91 SDL_UnlockMutex(ManagementThreadLock); // let other things add to the list while we chew on this task.
92 task->result = task->fn(task->userdata); // run this task.
93 if (task->task_complete_sem) { // something waiting on result?
94 task->errorstr = SDL_strdup(SDL_GetError());
95 SDL_SignalSemaphore(task->task_complete_sem);
96 } else { // nothing waiting, we're done, free it.
97 SDL_free(task);
98 }
99 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.
100 }
101 }
102 SDL_UnlockMutex(ManagementThreadLock); // told to shut down and out of tasks, let go of the lock and return.
103}
104
105bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *wait_on_result)
106{
107 // 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.
108 if ((wait_on_result) && (SDL_GetCurrentThreadID() == SDL_GetThreadID(ManagementThread))) {
109 *wait_on_result = task(userdata);
110 return true; // completed!
111 }
112
113 if (SDL_GetAtomicInt(&ManagementThreadShutdown)) {
114 return SDL_SetError("Can't add task, we're shutting down");
115 }
116
117 ManagementThreadPendingTask *pending = (ManagementThreadPendingTask *)SDL_calloc(1, sizeof(ManagementThreadPendingTask));
118 if (!pending) {
119 return false;
120 }
121
122 pending->fn = task;
123 pending->userdata = userdata;
124
125 if (wait_on_result) {
126 pending->task_complete_sem = SDL_CreateSemaphore(0);
127 if (!pending->task_complete_sem) {
128 SDL_free(pending);
129 return false;
130 }
131 }
132
133 pending->next = NULL;
134
135 SDL_LockMutex(ManagementThreadLock);
136
137 // add to end of task list.
138 ManagementThreadPendingTask *prev = NULL;
139 for (ManagementThreadPendingTask *i = (ManagementThreadPendingTask *)SDL_GetAtomicPointer((void **)&ManagementThreadPendingTasks); i; i = i->next) {
140 prev = i;
141 }
142
143 if (prev) {
144 prev->next = pending;
145 } else {
146 SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, pending);
147 }
148
149 // task is added to the end of the pending list, let management thread rip!
150 SDL_SignalCondition(ManagementThreadCondition);
151 SDL_UnlockMutex(ManagementThreadLock);
152
153 if (wait_on_result) {
154 SDL_WaitSemaphore(pending->task_complete_sem);
155 SDL_DestroySemaphore(pending->task_complete_sem);
156 *wait_on_result = pending->result;
157 if (pending->errorstr) {
158 SDL_SetError("%s", pending->errorstr);
159 SDL_free(pending->errorstr);
160 }
161 SDL_free(pending);
162 }
163
164 return true; // successfully added (and possibly executed)!
165}
166
167static bool mgmtthrtask_AudioDeviceDisconnected(void *userdata)
168{
169 SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
170 SDL_AudioDeviceDisconnected(device);
171 UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
172 return true;
173}
174
175static void AudioDeviceDisconnected(SDL_AudioDevice *device)
176{
177 // don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
178 if (device) {
179 RefPhysicalAudioDevice(device); // make sure this lives until the task completes.
180 WASAPI_ProxyToManagementThread(mgmtthrtask_AudioDeviceDisconnected, device, NULL);
181 }
182}
183
184static bool mgmtthrtask_DefaultAudioDeviceChanged(void *userdata)
185{
186 SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
187 SDL_DefaultAudioDeviceChanged(device);
188 UnrefPhysicalAudioDevice(device); // make sure this lived until the task completes.
189 return true;
190}
191
192static void DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
193{
194 // don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
195 if (new_default_device) {
196 RefPhysicalAudioDevice(new_default_device); // make sure this lives until the task completes.
197 WASAPI_ProxyToManagementThread(mgmtthrtask_DefaultAudioDeviceChanged, new_default_device, NULL);
198 }
199}
200
201static void StopWasapiHotplug(void)
202{
203 if (immdevice_initialized) {
204 SDL_IMMDevice_Quit();
205 immdevice_initialized = false;
206 }
207}
208
209static void Deinit(void)
210{
211 if (libavrt) {
212 FreeLibrary(libavrt);
213 libavrt = NULL;
214 }
215
216 pAvSetMmThreadCharacteristicsW = NULL;
217 pAvRevertMmThreadCharacteristics = NULL;
218
219 StopWasapiHotplug();
220
221 WIN_CoUninitialize();
222}
223
224static bool ManagementThreadPrepare(void)
225{
226 const SDL_IMMDevice_callbacks callbacks = { AudioDeviceDisconnected, DefaultAudioDeviceChanged };
227 if (FAILED(WIN_CoInitialize())) {
228 return SDL_SetError("CoInitialize() failed");
229 } else if (!SDL_IMMDevice_Init(&callbacks)) {
230 return false; // Error string is set by SDL_IMMDevice_Init
231 }
232
233 immdevice_initialized = true;
234
235 libavrt = LoadLibrary(TEXT("avrt.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
236 if (libavrt) {
237 pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW)GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
238 pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics)GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics");
239 }
240
241 ManagementThreadLock = SDL_CreateMutex();
242 if (!ManagementThreadLock) {
243 Deinit();
244 return false;
245 }
246
247 ManagementThreadCondition = SDL_CreateCondition();
248 if (!ManagementThreadCondition) {
249 SDL_DestroyMutex(ManagementThreadLock);
250 ManagementThreadLock = NULL;
251 Deinit();
252 return false;
253 }
254
255 return true;
256}
257
258typedef struct
259{
260 char *errorstr;
261 SDL_Semaphore *ready_sem;
262} ManagementThreadEntryData;
263
264static int ManagementThreadEntry(void *userdata)
265{
266 ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata;
267
268 if (!ManagementThreadPrepare()) {
269 data->errorstr = SDL_strdup(SDL_GetError());
270 SDL_SignalSemaphore(data->ready_sem); // unblock calling thread.
271 return 0;
272 }
273
274 SDL_SignalSemaphore(data->ready_sem); // unblock calling thread.
275 ManagementThreadMainloop();
276
277 Deinit();
278 return 0;
279}
280
281static bool InitManagementThread(void)
282{
283 ManagementThreadEntryData mgmtdata;
284 SDL_zero(mgmtdata);
285 mgmtdata.ready_sem = SDL_CreateSemaphore(0);
286 if (!mgmtdata.ready_sem) {
287 return false;
288 }
289
290 SDL_SetAtomicPointer((void **) &ManagementThreadPendingTasks, NULL);
291 SDL_SetAtomicInt(&ManagementThreadShutdown, 0);
292 ManagementThread = SDL_CreateThreadWithStackSize(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size?
293 if (!ManagementThread) {
294 return false;
295 }
296
297 SDL_WaitSemaphore(mgmtdata.ready_sem);
298 SDL_DestroySemaphore(mgmtdata.ready_sem);
299
300 if (mgmtdata.errorstr) {
301 SDL_WaitThread(ManagementThread, NULL);
302 ManagementThread = NULL;
303 SDL_SetError("%s", mgmtdata.errorstr);
304 SDL_free(mgmtdata.errorstr);
305 return false;
306 }
307
308 return true;
309}
310
311static void DeinitManagementThread(void)
312{
313 if (ManagementThread) {
314 SDL_SetAtomicInt(&ManagementThreadShutdown, 1);
315 SDL_LockMutex(ManagementThreadLock);
316 SDL_SignalCondition(ManagementThreadCondition);
317 SDL_UnlockMutex(ManagementThreadLock);
318 SDL_WaitThread(ManagementThread, NULL);
319 ManagementThread = NULL;
320 }
321
322 SDL_assert(SDL_GetAtomicPointer((void **) &ManagementThreadPendingTasks) == NULL);
323
324 SDL_DestroyCondition(ManagementThreadCondition);
325 SDL_DestroyMutex(ManagementThreadLock);
326 ManagementThreadCondition = NULL;
327 ManagementThreadLock = NULL;
328 SDL_SetAtomicInt(&ManagementThreadShutdown, 0);
329}
330
331typedef struct
332{
333 SDL_AudioDevice **default_playback;
334 SDL_AudioDevice **default_recording;
335} mgmtthrtask_DetectDevicesData;
336
337static bool mgmtthrtask_DetectDevices(void *userdata)
338{
339 mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
340 SDL_IMMDevice_EnumerateEndpoints(data->default_playback, data->default_recording);
341 return true;
342}
343
344static void WASAPI_DetectDevices(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
345{
346 bool rc;
347 // this blocks because it needs to finish before the audio subsystem inits
348 mgmtthrtask_DetectDevicesData data;
349 data.default_playback = default_playback;
350 data.default_recording = default_recording;
351 WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
352}
353
354static bool mgmtthrtask_DisconnectDevice(void *userdata)
355{
356 SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
357 SDL_AudioDeviceDisconnected(device);
358 UnrefPhysicalAudioDevice(device);
359 return true;
360}
361
362void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
363{
364 if (SDL_CompareAndSwapAtomicInt(&device->hidden->device_disconnecting, 0, 1)) {
365 RefPhysicalAudioDevice(device); // will unref when the task ends.
366 WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, NULL);
367 }
368}
369
370static bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err)
371{
372 if (err == S_OK) {
373 return false;
374 } else if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
375 device->hidden->device_lost = true;
376 } else {
377 device->hidden->device_dead = true;
378 }
379
380 return true;
381}
382
383static bool mgmtthrtask_StopAndReleaseClient(void *userdata)
384{
385 IAudioClient *client = (IAudioClient *) userdata;
386 IAudioClient_Stop(client);
387 IAudioClient_Release(client);
388 return true;
389}
390
391static bool mgmtthrtask_ReleaseCaptureClient(void *userdata)
392{
393 IAudioCaptureClient_Release((IAudioCaptureClient *)userdata);
394 return true;
395}
396
397static bool mgmtthrtask_ReleaseRenderClient(void *userdata)
398{
399 IAudioRenderClient_Release((IAudioRenderClient *)userdata);
400 return true;
401}
402
403static bool mgmtthrtask_CoTaskMemFree(void *userdata)
404{
405 CoTaskMemFree(userdata);
406 return true;
407}
408
409static bool mgmtthrtask_CloseHandle(void *userdata)
410{
411 CloseHandle((HANDLE) userdata);
412 return true;
413}
414
415static void ResetWasapiDevice(SDL_AudioDevice *device)
416{
417 if (!device || !device->hidden) {
418 return;
419 }
420
421 // just queue up all the tasks in the management thread and don't block.
422 // We don't care when any of these actually get free'd.
423
424 if (device->hidden->client) {
425 IAudioClient *client = device->hidden->client;
426 device->hidden->client = NULL;
427 WASAPI_ProxyToManagementThread(mgmtthrtask_StopAndReleaseClient, client, NULL);
428 }
429
430 if (device->hidden->render) {
431 IAudioRenderClient *render = device->hidden->render;
432 device->hidden->render = NULL;
433 WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, render, NULL);
434 }
435
436 if (device->hidden->capture) {
437 IAudioCaptureClient *capture = device->hidden->capture;
438 device->hidden->capture = NULL;
439 WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, capture, NULL);
440 }
441
442 if (device->hidden->waveformat) {
443 void *ptr = device->hidden->waveformat;
444 device->hidden->waveformat = NULL;
445 WASAPI_ProxyToManagementThread(mgmtthrtask_CoTaskMemFree, ptr, NULL);
446 }
447
448 if (device->hidden->event) {
449 HANDLE event = device->hidden->event;
450 device->hidden->event = NULL;
451 WASAPI_ProxyToManagementThread(mgmtthrtask_CloseHandle, (void *) event, NULL);
452 }
453}
454
455static bool mgmtthrtask_ActivateDevice(void *userdata)
456{
457 SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
458
459 IMMDevice *immdevice = NULL;
460 if (!SDL_IMMDevice_Get(device, &immdevice, device->recording)) {
461 device->hidden->client = NULL;
462 return false; // This is already set by SDL_IMMDevice_Get
463 }
464
465 // this is _not_ async in standard win32, yay!
466 HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client);
467 IMMDevice_Release(immdevice);
468
469 if (FAILED(ret)) {
470 SDL_assert(device->hidden->client == NULL);
471 return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
472 }
473
474 SDL_assert(device->hidden->client != NULL);
475 if (!WASAPI_PrepDevice(device)) { // not async, fire it right away.
476 return false;
477 }
478
479 return true; // good to go.
480}
481
482static bool ActivateWasapiDevice(SDL_AudioDevice *device)
483{
484 // this blocks because we're either being notified from a background thread or we're running during device open,
485 // both of which won't deadlock vs the device thread.
486 bool rc = false;
487 return (WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) && rc);
488}
489
490// do not call when holding the device lock!
491static bool RecoverWasapiDevice(SDL_AudioDevice *device)
492{
493 ResetWasapiDevice(device); // dump the lost device's handles.
494
495 // This handles a non-default device that simply had its format changed in the Windows Control Panel.
496 if (!ActivateWasapiDevice(device)) {
497 WASAPI_DisconnectDevice(device);
498 return false;
499 }
500
501 device->hidden->device_lost = false;
502
503 return true; // okay, carry on with new device details!
504}
505
506// do not call when holding the device lock!
507static bool RecoverWasapiIfLost(SDL_AudioDevice *device)
508{
509 if (SDL_GetAtomicInt(&device->shutdown)) {
510 return false; // closing, stop trying.
511 } else if (SDL_GetAtomicInt(&device->hidden->device_disconnecting)) {
512 return false; // failing via the WASAPI management thread, stop trying.
513 } else if (device->hidden->device_dead) { // had a fatal error elsewhere, clean up and quit
514 IAudioClient_Stop(device->hidden->client);
515 WASAPI_DisconnectDevice(device);
516 SDL_assert(SDL_GetAtomicInt(&device->shutdown)); // so we don't come back through here.
517 return false; // already failed.
518 } else if (SDL_GetAtomicInt(&device->zombie)) {
519 return false; // we're already dead, so just leave and let the Zombie implementations take over.
520 } else if (!device->hidden->client) {
521 return true; // still waiting for activation.
522 }
523
524 return device->hidden->device_lost ? RecoverWasapiDevice(device) : true;
525}
526
527static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
528{
529 // get an endpoint buffer from WASAPI.
530 BYTE *buffer = NULL;
531
532 if (device->hidden->render) {
533 const HRESULT ret = IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer);
534 if (ret == AUDCLNT_E_BUFFER_TOO_LARGE) {
535 SDL_assert(buffer == NULL);
536 *buffer_size = 0; // just go back to WaitDevice and try again after the hardware has consumed some more data.
537 } else if (WasapiFailed(device, ret)) {
538 SDL_assert(buffer == NULL);
539 if (device->hidden->device_lost) { // just use an available buffer, we won't be playing it anyhow.
540 *buffer_size = 0; // we'll recover during WaitDevice and try again.
541 }
542 }
543 }
544
545 return (Uint8 *)buffer;
546}
547
548static bool WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
549{
550 if (device->hidden->render && !SDL_GetAtomicInt(&device->hidden->device_disconnecting)) { // definitely activated?
551 // WasapiFailed() will mark the device for reacquisition or removal elsewhere.
552 WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0));
553 }
554 return true;
555}
556
557static bool WASAPI_WaitDevice(SDL_AudioDevice *device)
558{
559 // WaitDevice does not hold the device lock, so check for recovery/disconnect details here.
560 while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) {
561 if (device->recording) {
562 // Recording devices should return immediately if there is any data available
563 UINT32 padding = 0;
564 if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
565 //SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);
566 if (padding > 0) {
567 break;
568 }
569 }
570
571 switch (WaitForSingleObjectEx(device->hidden->event, 200, FALSE)) {
572 case WAIT_OBJECT_0:
573 case WAIT_TIMEOUT:
574 break;
575
576 default:
577 //SDL_Log("WASAPI FAILED EVENT!");
578 IAudioClient_Stop(device->hidden->client);
579 return false;
580 }
581 } else {
582 DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE);
583 if (waitResult == WAIT_OBJECT_0) {
584 UINT32 padding = 0;
585 if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
586 //SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);
587 if (padding <= (UINT32)device->sample_frames) {
588 break;
589 }
590 }
591 } else if (waitResult != WAIT_TIMEOUT) {
592 //SDL_Log("WASAPI FAILED EVENT!");*/
593 IAudioClient_Stop(device->hidden->client);
594 return false;
595 }
596 }
597 }
598
599 return true;
600}
601
602static int WASAPI_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
603{
604 BYTE *ptr = NULL;
605 UINT32 frames = 0;
606 DWORD flags = 0;
607
608 while (device->hidden->capture) {
609 const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
610 if (ret == AUDCLNT_S_BUFFER_EMPTY) {
611 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 SDL3.
612 }
613
614 WasapiFailed(device, ret); // mark device lost/failed if necessary.
615
616 if (ret == S_OK) {
617 const int total = ((int)frames) * device->hidden->framesize;
618 const int cpy = SDL_min(buflen, total);
619 const int leftover = total - cpy;
620 const bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? true : false;
621
622 SDL_assert(leftover == 0); // according to MSDN, this isn't everything available, just one "packet" of data per-GetBuffer call.
623
624 if (silent) {
625 SDL_memset(buffer, device->silence_value, cpy);
626 } else {
627 SDL_memcpy(buffer, ptr, cpy);
628 }
629
630 WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames));
631
632 return cpy;
633 }
634 }
635
636 return -1; // unrecoverable error.
637}
638
639static void WASAPI_FlushRecording(SDL_AudioDevice *device)
640{
641 BYTE *ptr = NULL;
642 UINT32 frames = 0;
643 DWORD flags = 0;
644
645 // just read until we stop getting packets, throwing them away.
646 while (!SDL_GetAtomicInt(&device->shutdown) && device->hidden->capture) {
647 const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
648 if (ret == AUDCLNT_S_BUFFER_EMPTY) {
649 break; // no more buffered data; we're done.
650 } else if (WasapiFailed(device, ret)) {
651 break; // failed for some other reason, abort.
652 } else if (WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames))) {
653 break; // something broke.
654 }
655 }
656}
657
658static void WASAPI_CloseDevice(SDL_AudioDevice *device)
659{
660 if (device->hidden) {
661 ResetWasapiDevice(device);
662 SDL_free(device->hidden->devid);
663 SDL_free(device->hidden);
664 device->hidden = NULL;
665 }
666}
667
668static bool mgmtthrtask_PrepDevice(void *userdata)
669{
670 SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
671
672 /* !!! FIXME: we could request an exclusive mode stream, which is lower latency;
673 !!! it will write into the kernel's audio buffer directly instead of
674 !!! shared memory that a user-mode mixer then writes to the kernel with
675 !!! everything else. Doing this means any other sound using this device will
676 !!! stop playing, including the user's MP3 player and system notification
677 !!! sounds. You'd probably need to release the device when the app isn't in
678 !!! the foreground, to be a good citizen of the system. It's doable, but it's
679 !!! more work and causes some annoyances, and I don't know what the latency
680 !!! wins actually look like. Maybe add a hint to force exclusive mode at
681 !!! some point. To be sure, defaulting to shared mode is the right thing to
682 !!! do in any case. */
683 const AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED;
684
685 IAudioClient *client = device->hidden->client;
686 SDL_assert(client != NULL);
687
688 device->hidden->event = CreateEvent(NULL, FALSE, FALSE, NULL);
689 if (!device->hidden->event) {
690 return WIN_SetError("WASAPI can't create an event handle");
691 }
692
693 HRESULT ret;
694
695 WAVEFORMATEX *waveformat = NULL;
696 ret = IAudioClient_GetMixFormat(client, &waveformat);
697 if (FAILED(ret)) {
698 return WIN_SetErrorFromHRESULT("WASAPI can't determine mix format", ret);
699 }
700 SDL_assert(waveformat != NULL);
701 device->hidden->waveformat = waveformat;
702
703 SDL_AudioSpec newspec;
704 newspec.channels = (Uint8)waveformat->nChannels;
705
706 // Make sure we have a valid format that we can convert to whatever WASAPI wants.
707 const SDL_AudioFormat wasapi_format = SDL_WaveFormatExToSDLFormat(waveformat);
708
709 SDL_AudioFormat test_format;
710 const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
711 while ((test_format = *(closefmts++)) != 0) {
712 if (test_format == wasapi_format) {
713 newspec.format = test_format;
714 break;
715 }
716 }
717
718 if (!test_format) {
719 return SDL_SetError("%s: Unsupported audio format", "wasapi");
720 }
721
722 REFERENCE_TIME default_period = 0;
723 ret = IAudioClient_GetDevicePeriod(client, &default_period, NULL);
724 if (FAILED(ret)) {
725 return WIN_SetErrorFromHRESULT("WASAPI can't determine minimum device period", ret);
726 }
727
728 DWORD streamflags = 0;
729
730 /* we've gotten reports that WASAPI's resampler introduces distortions, but in the short term
731 it fixes some other WASAPI-specific quirks we haven't quite tracked down.
732 Refer to bug #6326 for the immediate concern. */
733#if 1
734 // favor WASAPI's resampler over our own
735 if ((DWORD)device->spec.freq != waveformat->nSamplesPerSec) {
736 streamflags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY);
737 waveformat->nSamplesPerSec = device->spec.freq;
738 waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nChannels * (waveformat->wBitsPerSample / 8);
739 }
740#endif
741
742 newspec.freq = waveformat->nSamplesPerSec;
743
744 streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
745
746 int new_sample_frames = 0;
747 bool iaudioclient3_initialized = false;
748
749#ifdef __IAudioClient3_INTERFACE_DEFINED__
750 // Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED
751 if (sharemode == AUDCLNT_SHAREMODE_SHARED) {
752 IAudioClient3 *client3 = NULL;
753 ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient3, (void**)&client3);
754 if (SUCCEEDED(ret)) {
755 UINT32 default_period_in_frames = 0;
756 UINT32 fundamental_period_in_frames = 0;
757 UINT32 min_period_in_frames = 0;
758 UINT32 max_period_in_frames = 0;
759 ret = IAudioClient3_GetSharedModeEnginePeriod(client3, waveformat,
760 &default_period_in_frames, &fundamental_period_in_frames, &min_period_in_frames, &max_period_in_frames);
761 if (SUCCEEDED(ret)) {
762 // IAudioClient3_InitializeSharedAudioStream only accepts the integral multiple of fundamental_period_in_frames
763 UINT32 period_in_frames = fundamental_period_in_frames * (UINT32)SDL_round((double)device->sample_frames / fundamental_period_in_frames);
764 period_in_frames = SDL_clamp(period_in_frames, min_period_in_frames, max_period_in_frames);
765
766 ret = IAudioClient3_InitializeSharedAudioStream(client3, streamflags, period_in_frames, waveformat, NULL);
767 if (SUCCEEDED(ret)) {
768 new_sample_frames = (int)period_in_frames;
769 iaudioclient3_initialized = true;
770 }
771 }
772
773 IAudioClient3_Release(client3);
774 }
775 }
776#endif
777
778 if (!iaudioclient3_initialized)
779 ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL);
780
781 if (FAILED(ret)) {
782 return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret);
783 }
784
785 ret = IAudioClient_SetEventHandle(client, device->hidden->event);
786 if (FAILED(ret)) {
787 return WIN_SetErrorFromHRESULT("WASAPI can't set event handle", ret);
788 }
789
790 UINT32 bufsize = 0; // this is in sample frames, not samples, not bytes.
791 ret = IAudioClient_GetBufferSize(client, &bufsize);
792 if (FAILED(ret)) {
793 return WIN_SetErrorFromHRESULT("WASAPI can't determine buffer size", ret);
794 }
795
796 // Match the callback size to the period size to cut down on the number of
797 // interrupts waited for in each call to WaitDevice
798 if (new_sample_frames <= 0) {
799 const float period_millis = default_period / 10000.0f;
800 const float period_frames = period_millis * newspec.freq / 1000.0f;
801 new_sample_frames = (int) SDL_ceilf(period_frames);
802 }
803
804 // regardless of what we calculated for the period size, clamp it to the expected hardware buffer size.
805 if (new_sample_frames > (int) bufsize) {
806 new_sample_frames = (int) bufsize;
807 }
808
809 // Update the fragment size as size in bytes
810 if (!SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames)) {
811 return false;
812 }
813
814 device->hidden->framesize = SDL_AUDIO_FRAMESIZE(device->spec);
815
816 if (device->recording) {
817 IAudioCaptureClient *capture = NULL;
818 ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void **)&capture);
819 if (FAILED(ret)) {
820 return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret);
821 }
822
823 SDL_assert(capture != NULL);
824 device->hidden->capture = capture;
825 ret = IAudioClient_Start(client);
826 if (FAILED(ret)) {
827 return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
828 }
829
830 WASAPI_FlushRecording(device); // MSDN says you should flush the recording endpoint right after startup.
831 } else {
832 IAudioRenderClient *render = NULL;
833 ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void **)&render);
834 if (FAILED(ret)) {
835 return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret);
836 }
837
838 SDL_assert(render != NULL);
839 device->hidden->render = render;
840 ret = IAudioClient_Start(client);
841 if (FAILED(ret)) {
842 return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
843 }
844 }
845
846 return true; // good to go.
847}
848
849// This is called once a device is activated, possibly asynchronously.
850bool WASAPI_PrepDevice(SDL_AudioDevice *device)
851{
852 bool rc = true;
853 return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) && rc);
854}
855
856static bool WASAPI_OpenDevice(SDL_AudioDevice *device)
857{
858 // Initialize all variables that we clean on shutdown
859 device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
860 if (!device->hidden) {
861 return false;
862 } else if (!ActivateWasapiDevice(device)) {
863 return false; // already set error.
864 }
865
866 /* Ready, but possibly waiting for async device activation.
867 Until activation is successful, we will report silence from recording
868 devices and ignore data on playback devices. Upon activation, we'll make
869 sure any bound audio streams are adjusted for the final device format. */
870
871 return true;
872}
873
874static void WASAPI_ThreadInit(SDL_AudioDevice *device)
875{
876 // this thread uses COM.
877 if (SUCCEEDED(WIN_CoInitialize())) { // can't report errors, hope it worked!
878 device->hidden->coinitialized = true;
879 }
880
881 // Set this thread to very high "Pro Audio" priority.
882 if (pAvSetMmThreadCharacteristicsW) {
883 DWORD idx = 0;
884 device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx);
885 } else {
886 SDL_SetCurrentThreadPriority(device->recording ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
887 }
888}
889
890static void WASAPI_ThreadDeinit(SDL_AudioDevice *device)
891{
892 // Set this thread back to normal priority.
893 if (device->hidden->task && pAvRevertMmThreadCharacteristics) {
894 pAvRevertMmThreadCharacteristics(device->hidden->task);
895 device->hidden->task = NULL;
896 }
897
898 if (device->hidden->coinitialized) {
899 WIN_CoUninitialize();
900 device->hidden->coinitialized = false;
901 }
902}
903
904static bool mgmtthrtask_FreeDeviceHandle(void *userdata)
905{
906 SDL_IMMDevice_FreeDeviceHandle((SDL_AudioDevice *) userdata);
907 return true;
908}
909
910static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device)
911{
912 bool rc;
913 WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc);
914}
915
916static bool mgmtthrtask_DeinitializeStart(void *userdata)
917{
918 StopWasapiHotplug();
919 return true;
920}
921
922static void WASAPI_DeinitializeStart(void)
923{
924 bool rc;
925 WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc);
926}
927
928static void WASAPI_Deinitialize(void)
929{
930 DeinitManagementThread();
931}
932
933static bool WASAPI_Init(SDL_AudioDriverImpl *impl)
934{
935 if (!InitManagementThread()) {
936 return false;
937 }
938
939 impl->DetectDevices = WASAPI_DetectDevices;
940 impl->ThreadInit = WASAPI_ThreadInit;
941 impl->ThreadDeinit = WASAPI_ThreadDeinit;
942 impl->OpenDevice = WASAPI_OpenDevice;
943 impl->PlayDevice = WASAPI_PlayDevice;
944 impl->WaitDevice = WASAPI_WaitDevice;
945 impl->GetDeviceBuf = WASAPI_GetDeviceBuf;
946 impl->WaitRecordingDevice = WASAPI_WaitDevice;
947 impl->RecordDevice = WASAPI_RecordDevice;
948 impl->FlushRecording = WASAPI_FlushRecording;
949 impl->CloseDevice = WASAPI_CloseDevice;
950 impl->DeinitializeStart = WASAPI_DeinitializeStart;
951 impl->Deinitialize = WASAPI_Deinitialize;
952 impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
953
954 impl->HasRecordingSupport = true;
955
956 return true;
957}
958
959AudioBootStrap WASAPI_bootstrap = {
960 "wasapi", "WASAPI", WASAPI_Init, false, false
961};
962
963#endif // SDL_AUDIO_DRIVER_WASAPI
diff --git a/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.h b/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.h
new file mode 100644
index 0000000..5e528dc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/audio/wasapi/SDL_wasapi.h
@@ -0,0 +1,61 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_wasapi_h_
24#define SDL_wasapi_h_
25
26#ifdef __cplusplus
27extern "C" {
28#endif
29
30#include "../SDL_sysaudio.h"
31
32struct SDL_PrivateAudioData
33{
34 WCHAR *devid;
35 WAVEFORMATEX *waveformat;
36 IAudioClient *client;
37 IAudioRenderClient *render;
38 IAudioCaptureClient *capture;
39 HANDLE event;
40 HANDLE task;
41 bool coinitialized;
42 int framesize;
43 SDL_AtomicInt device_disconnecting;
44 bool device_lost;
45 bool device_dead;
46};
47
48// win32 implementation calls into these.
49bool WASAPI_PrepDevice(SDL_AudioDevice *device);
50void WASAPI_DisconnectDevice(SDL_AudioDevice *device); // don't hold the device lock when calling this!
51
52
53// BE CAREFUL: if you are holding the device lock and proxy to the management thread with wait_until_complete, and grab the lock again, you will deadlock.
54typedef bool (*ManagementThreadTask)(void *userdata);
55bool WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, bool *wait_until_complete);
56
57#ifdef __cplusplus
58}
59#endif
60
61#endif // SDL_wasapi_h_