2 Simple DirectMedia Layer
3 Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
22 #include "../../SDL_internal.h"
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. */
30 #if SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__)
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"
41 #include <mmdeviceapi.h>
42 #include <audioclient.h>
44 #include "SDL_wasapi.h"
46 static const ERole SDL_WASAPI_role = eConsole; /* !!! FIXME: should this be eMultimedia? Should be a hint? */
48 /* This is global to the WASAPI target, to handle hotplug and default device lookup. */
49 static IMMDeviceEnumerator *enumerator = NULL;
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
55 #define PropVariantInit(p) SDL_zerop(p)
57 /* handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency). */
58 static HMODULE libavrt = NULL;
59 typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPWSTR, LPDWORD);
60 typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
61 static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
62 static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
64 /* Some GUIDs we need to know without linking to libraries that aren't available before Vista. */
65 static const CLSID SDL_CLSID_MMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c,{ 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } };
66 static const IID SDL_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35,{ 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } };
67 static const IID SDL_IID_IMMNotificationClient = { 0x7991eec9, 0x7e89, 0x4d85,{ 0x83, 0x90, 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0 } };
68 static const IID SDL_IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,{ 0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5 } };
69 static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32,{ 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
70 static const PROPERTYKEY SDL_PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd,{ 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, } }, 14 };
74 GetWasapiDeviceName(IMMDevice *device)
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. */
80 IPropertyStore *props = NULL;
81 if (SUCCEEDED(IMMDevice_OpenPropertyStore(device, STGM_READ, &props))) {
83 PropVariantInit(&var);
84 if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_Device_FriendlyName, &var))) {
85 utf8dev = WIN_StringToUTF8(var.pwszVal);
87 PropVariantClear(&var);
88 IPropertyStore_Release(props);
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 */
99 typedef struct SDLMMNotificationClient
101 const IMMNotificationClientVtbl *lpVtbl;
102 SDL_atomic_t refcount;
103 } SDLMMNotificationClient;
105 static HRESULT STDMETHODCALLTYPE
106 SDLMMNotificationClient_QueryInterface(IMMNotificationClient *this, REFIID iid, void **ppv)
108 if ((WIN_IsEqualIID(iid, &IID_IUnknown)) || (WIN_IsEqualIID(iid, &SDL_IID_IMMNotificationClient)))
111 this->lpVtbl->AddRef(this);
116 return E_NOINTERFACE;
119 static ULONG STDMETHODCALLTYPE
120 SDLMMNotificationClient_AddRef(IMMNotificationClient *ithis)
122 SDLMMNotificationClient *this = (SDLMMNotificationClient *) ithis;
123 return (ULONG) (SDL_AtomicIncRef(&this->refcount) + 1);
126 static ULONG STDMETHODCALLTYPE
127 SDLMMNotificationClient_Release(IMMNotificationClient *ithis)
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);
133 SDL_AtomicSet(&this->refcount, 0); /* uhh... */
139 /* These are the entry points called when WASAPI device endpoints change. */
140 static HRESULT STDMETHODCALLTYPE
141 SDLMMNotificationClient_OnDefaultDeviceChanged(IMMNotificationClient *ithis, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId)
143 if (role != SDL_WASAPI_role) {
144 return S_OK; /* ignore it. */
147 /* Increment the "generation," so opened devices will pick this up in their threads. */
150 SDL_AtomicAdd(&WASAPI_DefaultPlaybackGeneration, 1);
154 SDL_AtomicAdd(&WASAPI_DefaultCaptureGeneration, 1);
158 SDL_AtomicAdd(&WASAPI_DefaultPlaybackGeneration, 1);
159 SDL_AtomicAdd(&WASAPI_DefaultCaptureGeneration, 1);
163 SDL_assert(!"uhoh, unexpected OnDefaultDeviceChange flow!");
170 static HRESULT STDMETHODCALLTYPE
171 SDLMMNotificationClient_OnDeviceAdded(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId)
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. */
181 static HRESULT STDMETHODCALLTYPE
182 SDLMMNotificationClient_OnDeviceRemoved(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId)
184 /* See notes in OnDeviceAdded handler about why we ignore this. */
188 static HRESULT STDMETHODCALLTYPE
189 SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *ithis, LPCWSTR pwstrDeviceId, DWORD dwNewState)
191 IMMDevice *device = NULL;
193 if (SUCCEEDED(IMMDeviceEnumerator_GetDevice(enumerator, pwstrDeviceId, &device))) {
194 IMMEndpoint *endpoint = NULL;
195 if (SUCCEEDED(IMMDevice_QueryInterface(device, &SDL_IID_IMMEndpoint, (void **) &endpoint))) {
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);
202 WASAPI_AddDevice(iscapture, utf8dev, pwstrDeviceId);
206 WASAPI_RemoveDevice(iscapture, pwstrDeviceId);
209 IMMEndpoint_Release(endpoint);
211 IMMDevice_Release(device);
217 static HRESULT STDMETHODCALLTYPE
218 SDLMMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *this, LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
220 return S_OK; /* we don't care about these. */
223 static 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
234 static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 } };
238 WASAPI_PlatformInit(void)
242 /* just skip the discussion with COM here. */
243 if (!WIN_IsWindowsVistaOrGreater()) {
244 return SDL_SetError("WASAPI support requires Windows Vista or later");
247 if (FAILED(WIN_CoInitialize())) {
248 return SDL_SetError("WASAPI: CoInitialize() failed");
251 ret = CoCreateInstance(&SDL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IMMDeviceEnumerator, (LPVOID) &enumerator);
253 WIN_CoUninitialize();
254 return WIN_SetErrorFromHRESULT("WASAPI CoCreateInstance(MMDeviceEnumerator)", ret);
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! */
259 pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW) GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
260 pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics) GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics");
267 WASAPI_PlatformDeinit(void)
270 IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *) ¬ification_client);
271 IMMDeviceEnumerator_Release(enumerator);
276 FreeLibrary(libavrt);
280 pAvSetMmThreadCharacteristicsW = NULL;
281 pAvRevertMmThreadCharacteristics = NULL;
283 WIN_CoUninitialize();
287 WASAPI_PlatformThreadInit(_THIS)
289 /* this thread uses COM. */
290 if (SUCCEEDED(WIN_CoInitialize())) { /* can't report errors, hope it worked! */
291 this->hidden->coinitialized = SDL_TRUE;
294 /* Set this thread to very high "Pro Audio" priority. */
295 if (pAvSetMmThreadCharacteristicsW) {
297 this->hidden->task = pAvSetMmThreadCharacteristicsW(TEXT("Pro Audio"), &idx);
302 WASAPI_PlatformThreadDeinit(_THIS)
304 /* Set this thread back to normal priority. */
305 if (this->hidden->task && pAvRevertMmThreadCharacteristics) {
306 pAvRevertMmThreadCharacteristics(this->hidden->task);
307 this->hidden->task = NULL;
310 if (this->hidden->coinitialized) {
311 WIN_CoUninitialize();
312 this->hidden->coinitialized = SDL_FALSE;
317 WASAPI_ActivateDevice(_THIS, const SDL_bool isrecovery)
319 LPCWSTR devid = this->hidden->devid;
320 IMMDevice *device = NULL;
324 const EDataFlow dataflow = this->iscapture ? eCapture : eRender;
325 ret = IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_WASAPI_role, &device);
327 ret = IMMDeviceEnumerator_GetDevice(enumerator, devid, &device);
331 SDL_assert(device == NULL);
332 this->hidden->client = NULL;
333 return WIN_SetErrorFromHRESULT("WASAPI can't find requested audio endpoint", ret);
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);
341 SDL_assert(this->hidden->client == NULL);
342 return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
345 SDL_assert(this->hidden->client != NULL);
346 if (WASAPI_PrepDevice(this, isrecovery) == -1) { /* not async, fire it right away. */
350 return 0; /* good to go. */
355 WASAPI_EnumerateEndpointsForFlow(const SDL_bool iscapture)
357 IMMDeviceCollection *collection = NULL;
360 /* Note that WASAPI separates "adapter devices" from "audio endpoint devices"
361 ...one adapter device ("SoundBlaster Pro") might have multiple endpoint devices ("Speakers", "Line-Out"). */
363 if (FAILED(IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, iscapture ? eCapture : eRender, DEVICE_STATE_ACTIVE, &collection))) {
367 if (FAILED(IMMDeviceCollection_GetCount(collection, &total))) {
368 IMMDeviceCollection_Release(collection);
372 for (i = 0; i < total; i++) {
373 IMMDevice *device = NULL;
374 if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &device))) {
376 if (SUCCEEDED(IMMDevice_GetId(device, &devid))) {
377 char *devname = GetWasapiDeviceName(device);
379 WASAPI_AddDevice(iscapture, devname, devid);
382 CoTaskMemFree(devid);
384 IMMDevice_Release(device);
388 IMMDeviceCollection_Release(collection);
392 WASAPI_EnumerateEndpoints(void)
394 WASAPI_EnumerateEndpointsForFlow(SDL_FALSE); /* playback */
395 WASAPI_EnumerateEndpointsForFlow(SDL_TRUE); /* capture */
397 /* if this fails, we just won't get hotplug events. Carry on anyhow. */
398 IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *) ¬ification_client);
402 WASAPI_PlatformDeleteActivationHandler(void *handler)
404 /* not asynchronous. */
405 SDL_assert(!"This function should have only been called on WinRT.");
409 WASAPI_BeginLoopIteration(_THIS)
414 #endif /* SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__) */
416 /* vi: set ts=4 sw=4 expandtab: */