Atlas - SDL_wasapi_win32.c
Home / ext / SDL2 / src / audio / wasapi Lines: 1 | Size: 14774 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2018 Sam Lantinga <[email protected]> 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: 150 SDL_AtomicAdd(&WASAPI_DefaultPlaybackGeneration, 1); 151 break; 152 153 case eCapture: 154 SDL_AtomicAdd(&WASAPI_DefaultCaptureGeneration, 1); 155 break; 156 157 case eAll: 158 SDL_AtomicAdd(&WASAPI_DefaultPlaybackGeneration, 1); 159 SDL_AtomicAdd(&WASAPI_DefaultCaptureGeneration, 1); 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 = { ¬ification_client_vtbl, { 1 } }; 235 236 237int 238WASAPI_PlatformInit(void) 239{ 240 HRESULT ret; 241 242 /* just skip the discussion with COM here. */ 243 if (!WIN_IsWindowsVistaOrGreater()) { 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)) { 253 WIN_CoUninitialize(); 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 267WASAPI_PlatformDeinit(void) 268{ 269 if (enumerator) { 270 IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *) ¬ification_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 283 WIN_CoUninitialize(); 284} 285 286void 287WASAPI_PlatformThreadInit(_THIS) 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 302WASAPI_PlatformThreadDeinit(_THIS) 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) { 311 WIN_CoUninitialize(); 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)) { 331 SDL_assert(device == NULL); 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 354static void 355WASAPI_EnumerateEndpointsForFlow(const SDL_bool iscapture) 356{ 357 IMMDeviceCollection *collection = NULL; 358 UINT i, total; 359 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"). */ 362 363 if (FAILED(IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, iscapture ? eCapture : eRender, DEVICE_STATE_ACTIVE, &collection))) { 364 return; 365 } 366 367 if (FAILED(IMMDeviceCollection_GetCount(collection, &total))) { 368 IMMDeviceCollection_Release(collection); 369 return; 370 } 371 372 for (i = 0; i < total; i++) { 373 IMMDevice *device = NULL; 374 if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &device))) { 375 LPWSTR devid = NULL; 376 if (SUCCEEDED(IMMDevice_GetId(device, &devid))) { 377 char *devname = GetWasapiDeviceName(device); 378 if (devname) { 379 WASAPI_AddDevice(iscapture, devname, devid); 380 SDL_free(devname); 381 } 382 CoTaskMemFree(devid); 383 } 384 IMMDevice_Release(device); 385 } 386 } 387 388 IMMDeviceCollection_Release(collection); 389} 390 391void 392WASAPI_EnumerateEndpoints(void) 393{ 394 WASAPI_EnumerateEndpointsForFlow(SDL_FALSE); /* playback */ 395 WASAPI_EnumerateEndpointsForFlow(SDL_TRUE); /* capture */ 396 397 /* if this fails, we just won't get hotplug events. Carry on anyhow. */ 398 IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *) ¬ification_client); 399} 400 401void 402WASAPI_PlatformDeleteActivationHandler(void *handler) 403{ 404 /* not asynchronous. */ 405 SDL_assert(!"This function should have only been called on WinRT."); 406} 407 408void 409WASAPI_BeginLoopIteration(_THIS) 410{ 411 /* no-op. */ 412} 413 414#endif /* SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__) */ 415 416/* vi: set ts=4 sw=4 expandtab: */ 417 418[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.