SDL 2.0
SDL_wasapi_win32.c
Go to the documentation of this file.
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2019 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
22#include "../../SDL_internal.h"
23
24/* This is code that Windows uses to talk to WASAPI-related system APIs.
25 This is for non-WinRT desktop apps. The C++/CX implementation of these
26 functions, exclusive to WinRT, are in SDL_wasapi_winrt.cpp.
27 The code in SDL_wasapi.c is used by both standard Windows and WinRT builds
28 to deal with audio and calls into these functions. */
29
30#if SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__)
31
32#include "../../core/windows/SDL_windows.h"
33#include "SDL_audio.h"
34#include "SDL_timer.h"
35#include "../SDL_audio_c.h"
36#include "../SDL_sysaudio.h"
37#include "SDL_assert.h"
38#include "SDL_log.h"
39
40#define COBJMACROS
41#include <mmdeviceapi.h>
42#include <audioclient.h>
43
44#include "SDL_wasapi.h"
45
46static const ERole SDL_WASAPI_role = eConsole; /* !!! FIXME: should this be eMultimedia? Should be a hint? */
47
48/* This is global to the WASAPI target, to handle hotplug and default device lookup. */
49static IMMDeviceEnumerator *enumerator = NULL;
50
51/* PropVariantInit() is an inline function/macro in PropIdl.h that calls the C runtime's memset() directly. Use ours instead, to avoid dependency. */
52#ifdef PropVariantInit
53#undef PropVariantInit
54#endif
55#define PropVariantInit(p) SDL_zerop(p)
56
57/* handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency). */
58static HMODULE libavrt = NULL;
59typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPWSTR, LPDWORD);
60typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
61static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
62static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
63
64/* Some GUIDs we need to know without linking to libraries that aren't available before Vista. */
65static const CLSID SDL_CLSID_MMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c,{ 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } };
66static const IID SDL_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35,{ 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } };
67static const IID SDL_IID_IMMNotificationClient = { 0x7991eec9, 0x7e89, 0x4d85,{ 0x83, 0x90, 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0 } };
68static const IID SDL_IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,{ 0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5 } };
69static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32,{ 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
70static const PROPERTYKEY SDL_PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd,{ 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, } }, 14 };
71
72
73static char *
74GetWasapiDeviceName(IMMDevice *device)
75{
76 /* PKEY_Device_FriendlyName gives you "Speakers (SoundBlaster Pro)" which drives me nuts. I'd rather it be
77 "SoundBlaster Pro (Speakers)" but I guess that's developers vs users. Windows uses the FriendlyName in
78 its own UIs, like Volume Control, etc. */
79 char *utf8dev = NULL;
80 IPropertyStore *props = NULL;
81 if (SUCCEEDED(IMMDevice_OpenPropertyStore(device, STGM_READ, &props))) {
82 PROPVARIANT var;
83 PropVariantInit(&var);
84 if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_Device_FriendlyName, &var))) {
85 utf8dev = WIN_StringToUTF8(var.pwszVal);
86 }
87 PropVariantClear(&var);
88 IPropertyStore_Release(props);
89 }
90 return utf8dev;
91}
92
93
94/* We need a COM subclass of IMMNotificationClient for hotplug support, which is
95 easy in C++, but we have to tapdance more to make work in C.
96 Thanks to this page for coaching on how to make this work:
97 https://www.codeproject.com/Articles/13601/COM-in-plain-C */
98
99typedef struct SDLMMNotificationClient
100{
101 const IMMNotificationClientVtbl *lpVtbl;
102 SDL_atomic_t refcount;
103} SDLMMNotificationClient;
104
105static HRESULT STDMETHODCALLTYPE
106SDLMMNotificationClient_QueryInterface(IMMNotificationClient *this, REFIID iid, void **ppv)
107{
108 if ((WIN_IsEqualIID(iid, &IID_IUnknown)) || (WIN_IsEqualIID(iid, &SDL_IID_IMMNotificationClient)))
109 {
110 *ppv = this;
111 this->lpVtbl->AddRef(this);
112 return S_OK;
113 }
114
115 *ppv = NULL;
116 return E_NOINTERFACE;
117}
118
119static ULONG STDMETHODCALLTYPE
120SDLMMNotificationClient_AddRef(IMMNotificationClient *ithis)
121{
122 SDLMMNotificationClient *this = (SDLMMNotificationClient *) ithis;
123 return (ULONG) (SDL_AtomicIncRef(&this->refcount) + 1);
124}
125
126static ULONG STDMETHODCALLTYPE
127SDLMMNotificationClient_Release(IMMNotificationClient *ithis)
128{
129 /* this is a static object; we don't ever free it. */
130 SDLMMNotificationClient *this = (SDLMMNotificationClient *) ithis;
131 const ULONG retval = SDL_AtomicDecRef(&this->refcount);
132 if (retval == 0) {
133 SDL_AtomicSet(&this->refcount, 0); /* uhh... */
134 return 0;
135 }
136 return retval - 1;
137}
138
139/* These are the entry points called when WASAPI device endpoints change. */
140static HRESULT STDMETHODCALLTYPE
141SDLMMNotificationClient_OnDefaultDeviceChanged(IMMNotificationClient *ithis, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId)
142{
143 if (role != SDL_WASAPI_role) {
144 return S_OK; /* ignore it. */
145 }
146
147 /* Increment the "generation," so opened devices will pick this up in their threads. */
148 switch (flow) {
149 case eRender:
151 break;
152
153 case eCapture:
155 break;
156
157 case eAll:
160 break;
161
162 default:
163 SDL_assert(!"uhoh, unexpected OnDefaultDeviceChange flow!");
164 break;
165 }
166
167 return S_OK;
168}
169
170static HRESULT STDMETHODCALLTYPE
171SDLMMNotificationClient_OnDeviceAdded(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId)
172{
173 /* we ignore this; devices added here then progress to ACTIVE, if appropriate, in
174 OnDeviceStateChange, making that a better place to deal with device adds. More
175 importantly: the first time you plug in a USB audio device, this callback will
176 fire, but when you unplug it, it isn't removed (it's state changes to NOTPRESENT).
177 Plugging it back in won't fire this callback again. */
178 return S_OK;
179}
180
181static HRESULT STDMETHODCALLTYPE
182SDLMMNotificationClient_OnDeviceRemoved(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId)
183{
184 /* See notes in OnDeviceAdded handler about why we ignore this. */
185 return S_OK;
186}
187
188static HRESULT STDMETHODCALLTYPE
189SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId, DWORD dwNewState)
190{
191 IMMDevice *device = NULL;
192
193 if (SUCCEEDED(IMMDeviceEnumerator_GetDevice(enumerator, pwstrDeviceId, &device))) {
194 IMMEndpoint *endpoint = NULL;
195 if (SUCCEEDED(IMMDevice_QueryInterface(device, &SDL_IID_IMMEndpoint, (void **) &endpoint))) {
196 EDataFlow flow;
197 if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) {
198 const SDL_bool iscapture = (flow == eCapture);
199 if (dwNewState == DEVICE_STATE_ACTIVE) {
200 char *utf8dev = GetWasapiDeviceName(device);
201 if (utf8dev) {
202 WASAPI_AddDevice(iscapture, utf8dev, pwstrDeviceId);
203 SDL_free(utf8dev);
204 }
205 } else {
206 WASAPI_RemoveDevice(iscapture, pwstrDeviceId);
207 }
208 }
209 IMMEndpoint_Release(endpoint);
210 }
211 IMMDevice_Release(device);
212 }
213
214 return S_OK;
215}
216
217static HRESULT STDMETHODCALLTYPE
218SDLMMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this, LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
219{
220 return S_OK; /* we don't care about these. */
221}
222
223static const IMMNotificationClientVtbl notification_client_vtbl = {
224 SDLMMNotificationClient_QueryInterface,
225 SDLMMNotificationClient_AddRef,
226 SDLMMNotificationClient_Release,
227 SDLMMNotificationClient_OnDeviceStateChanged,
228 SDLMMNotificationClient_OnDeviceAdded,
229 SDLMMNotificationClient_OnDeviceRemoved,
230 SDLMMNotificationClient_OnDefaultDeviceChanged,
231 SDLMMNotificationClient_OnPropertyValueChanged
232};
233
234static SDLMMNotificationClient notification_client = { &notification_client_vtbl, { 1 } };
235
236
237int
239{
240 HRESULT ret;
241
242 /* just skip the discussion with COM here. */
244 return SDL_SetError("WASAPI support requires Windows Vista or later");
245 }
246
247 if (FAILED(WIN_CoInitialize())) {
248 return SDL_SetError("WASAPI: CoInitialize() failed");
249 }
250
251 ret = CoCreateInstance(&SDL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IMMDeviceEnumerator, (LPVOID) &enumerator);
252 if (FAILED(ret)) {
254 return WIN_SetErrorFromHRESULT("WASAPI CoCreateInstance(MMDeviceEnumerator)", ret);
255 }
256
257 libavrt = LoadLibraryW(L"avrt.dll"); /* this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now! */
258 if (libavrt) {
259 pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW) GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
260 pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics) GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics");
261 }
262
263 return 0;
264}
265
266void
268{
269 if (enumerator) {
270 IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *) &notification_client);
271 IMMDeviceEnumerator_Release(enumerator);
272 enumerator = NULL;
273 }
274
275 if (libavrt) {
276 FreeLibrary(libavrt);
277 libavrt = NULL;
278 }
279
280 pAvSetMmThreadCharacteristicsW = NULL;
281 pAvRevertMmThreadCharacteristics = NULL;
282
284}
285
286void
288{
289 /* this thread uses COM. */
290 if (SUCCEEDED(WIN_CoInitialize())) { /* can't report errors, hope it worked! */
291 this->hidden->coinitialized = SDL_TRUE;
292 }
293
294 /* Set this thread to very high "Pro Audio" priority. */
295 if (pAvSetMmThreadCharacteristicsW) {
296 DWORD idx = 0;
297 this->hidden->task = pAvSetMmThreadCharacteristicsW(TEXT("Pro Audio"), &idx);
298 }
299}
300
301void
303{
304 /* Set this thread back to normal priority. */
305 if (this->hidden->task && pAvRevertMmThreadCharacteristics) {
306 pAvRevertMmThreadCharacteristics(this->hidden->task);
307 this->hidden->task = NULL;
308 }
309
310 if (this->hidden->coinitialized) {
312 this->hidden->coinitialized = SDL_FALSE;
313 }
314}
315
316int
317WASAPI_ActivateDevice(_THIS, const SDL_bool isrecovery)
318{
319 LPCWSTR devid = this->hidden->devid;
320 IMMDevice *device = NULL;
321 HRESULT ret;
322
323 if (devid == NULL) {
324 const EDataFlow dataflow = this->iscapture ? eCapture : eRender;
325 ret = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_WASAPI_role, &device);
326 } else {
327 ret = IMMDeviceEnumerator_GetDevice(enumerator, devid, &device);
328 }
329
330 if (FAILED(ret)) {
332 this->hidden->client = NULL;
333 return WIN_SetErrorFromHRESULT("WASAPI can't find requested audio endpoint", ret);
334 }
335
336 /* this is not async in standard win32, yay! */
337 ret = IMMDevice_Activate(device, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **) &this->hidden->client);
338 IMMDevice_Release(device);
339
340 if (FAILED(ret)) {
341 SDL_assert(this->hidden->client == NULL);
342 return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
343 }
344
345 SDL_assert(this->hidden->client != NULL);
346 if (WASAPI_PrepDevice(this, isrecovery) == -1) { /* not async, fire it right away. */
347 return -1;
348 }
349
350 return 0; /* good to go. */
351}
352
353
354typedef struct
355{
356 LPWSTR devid;
357 char *devname;
358} EndpointItem;
359
360static int sort_endpoints(const void *_a, const void *_b)
361{
362 LPWSTR a = ((const EndpointItem *) _a)->devid;
363 LPWSTR b = ((const EndpointItem *) _b)->devid;
364 if (!a && b) {
365 return -1;
366 } else if (a && !b) {
367 return 1;
368 }
369
370 while (SDL_TRUE) {
371 if (*a < *b) {
372 return -1;
373 } else if (*a > *b) {
374 return 1;
375 } else if (*a == 0) {
376 break;
377 }
378 a++;
379 b++;
380 }
381
382 return 0;
383}
384
385static void
386WASAPI_EnumerateEndpointsForFlow(const SDL_bool iscapture)
387{
388 IMMDeviceCollection *collection = NULL;
389 EndpointItem *items;
390 UINT i, total;
391
392 /* Note that WASAPI separates "adapter devices" from "audio endpoint devices"
393 ...one adapter device ("SoundBlaster Pro") might have multiple endpoint devices ("Speakers", "Line-Out"). */
394
395 if (FAILED(IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, iscapture ? eCapture : eRender, DEVICE_STATE_ACTIVE, &collection))) {
396 return;
397 }
398
399 if (FAILED(IMMDeviceCollection_GetCount(collection, &total))) {
400 IMMDeviceCollection_Release(collection);
401 return;
402 }
403
404 items = (EndpointItem *) SDL_calloc(total, sizeof (EndpointItem));
405 if (!items) {
406 return; /* oh well. */
407 }
408
409 for (i = 0; i < total; i++) {
410 EndpointItem *item = items + i;
411 IMMDevice *device = NULL;
412 if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &device))) {
413 if (SUCCEEDED(IMMDevice_GetId(device, &item->devid))) {
414 item->devname = GetWasapiDeviceName(device);
415 }
416 IMMDevice_Release(device);
417 }
418 }
419
420 /* sort the list of devices by their guid so list is consistent between runs */
421 SDL_qsort(items, total, sizeof (*items), sort_endpoints);
422
423 /* Send the sorted list on to the SDL's higher level. */
424 for (i = 0; i < total; i++) {
425 EndpointItem *item = items + i;
426 if ((item->devid) && (item->devname)) {
427 WASAPI_AddDevice(iscapture, item->devname, item->devid);
428 }
429 SDL_free(item->devname);
430 CoTaskMemFree(item->devid);
431 }
432
433 SDL_free(items);
434 IMMDeviceCollection_Release(collection);
435}
436
437void
439{
440 WASAPI_EnumerateEndpointsForFlow(SDL_FALSE); /* playback */
441 WASAPI_EnumerateEndpointsForFlow(SDL_TRUE); /* capture */
442
443 /* if this fails, we just won't get hotplug events. Carry on anyhow. */
444 IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *) &notification_client);
445}
446
447void
449{
450 /* not asynchronous. */
451 SDL_assert(!"This function should have only been called on WinRT.");
452}
453
454#endif /* SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__) */
455
456/* vi: set ts=4 sw=4 expandtab: */
457
#define _THIS
#define SDL_assert(condition)
Definition: SDL_assert.h:169
#define SDL_AtomicDecRef(a)
Decrement an atomic variable used as a reference count.
Definition: SDL_atomic.h:262
#define SDL_AtomicIncRef(a)
Increment an atomic variable used as a reference count.
Definition: SDL_atomic.h:252
#define SUCCEEDED(x)
Definition: SDL_directx.h:51
#define S_OK
Definition: SDL_directx.h:47
#define E_NOINTERFACE
Definition: SDL_directx.h:61
#define FAILED(x)
Definition: SDL_directx.h:54
#define SDL_AtomicSet
#define SDL_SetError
#define SDL_qsort
#define SDL_free
#define SDL_AtomicAdd
#define SDL_calloc
GLboolean GLboolean GLboolean b
GLenum GLuint GLsizei const GLenum * props
GLboolean GLboolean GLboolean GLboolean a
SDL_bool
Definition: SDL_stdinc.h:162
@ SDL_TRUE
Definition: SDL_stdinc.h:164
@ SDL_FALSE
Definition: SDL_stdinc.h:163
void WASAPI_PlatformDeleteActivationHandler(void *handler)
void WASAPI_RemoveDevice(const SDL_bool iscapture, LPCWSTR devid)
SDL_atomic_t WASAPI_DefaultPlaybackGeneration
void WASAPI_PlatformThreadDeinit(_THIS)
int WASAPI_ActivateDevice(_THIS, const SDL_bool isrecovery)
SDL_atomic_t WASAPI_DefaultCaptureGeneration
void WASAPI_PlatformThreadInit(_THIS)
int WASAPI_PlatformInit(void)
int WASAPI_PrepDevice(_THIS, const SDL_bool updatestream)
void WASAPI_EnumerateEndpoints(void)
void WASAPI_PlatformDeinit(void)
void WASAPI_AddDevice(const SDL_bool iscapture, const char *devname, LPCWSTR devid)
HRESULT WIN_CoInitialize(void)
void WIN_CoUninitialize(void)
BOOL WIN_IsWindowsVistaOrGreater(void)
BOOL WIN_IsEqualIID(REFIID a, REFIID b)
#define WIN_StringToUTF8(S)
Definition: SDL_windows.h:46
int WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr)
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
#define NULL
Definition: begin_code.h:167
GLuint64 key
Definition: gl2ext.h:2192
static SDL_AudioDeviceID device
Definition: loopwave.c:37
A type representing an atomic integer value. It is a struct so people don't accidentally use numeric ...
Definition: SDL_atomic.h:216
SDL_bool retval