diff options
Diffstat (limited to 'SDL-3.2.8/src/core/windows/SDL_immdevice.c')
| -rw-r--r-- | SDL-3.2.8/src/core/windows/SDL_immdevice.c | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/SDL-3.2.8/src/core/windows/SDL_immdevice.c b/SDL-3.2.8/src/core/windows/SDL_immdevice.c new file mode 100644 index 0000000..802a412 --- /dev/null +++ b/SDL-3.2.8/src/core/windows/SDL_immdevice.c | |||
| @@ -0,0 +1,434 @@ | |||
| 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 | #if defined(SDL_PLATFORM_WINDOWS) && defined(HAVE_MMDEVICEAPI_H) | ||
| 24 | |||
| 25 | #include "SDL_windows.h" | ||
| 26 | #include "SDL_immdevice.h" | ||
| 27 | #include "../../audio/SDL_sysaudio.h" | ||
| 28 | #include <objbase.h> // For CLSIDFromString | ||
| 29 | |||
| 30 | typedef struct SDL_IMMDevice_HandleData | ||
| 31 | { | ||
| 32 | LPWSTR immdevice_id; | ||
| 33 | GUID directsound_guid; | ||
| 34 | } SDL_IMMDevice_HandleData; | ||
| 35 | |||
| 36 | static const ERole SDL_IMMDevice_role = eConsole; // !!! FIXME: should this be eMultimedia? Should be a hint? | ||
| 37 | |||
| 38 | // This is global to the WASAPI target, to handle hotplug and default device lookup. | ||
| 39 | static IMMDeviceEnumerator *enumerator = NULL; | ||
| 40 | static SDL_IMMDevice_callbacks immcallbacks; | ||
| 41 | |||
| 42 | // PropVariantInit() is an inline function/macro in PropIdl.h that calls the C runtime's memset() directly. Use ours instead, to avoid dependency. | ||
| 43 | #ifdef PropVariantInit | ||
| 44 | #undef PropVariantInit | ||
| 45 | #endif | ||
| 46 | #define PropVariantInit(p) SDL_zerop(p) | ||
| 47 | |||
| 48 | // Some GUIDs we need to know without linking to libraries that aren't available before Vista. | ||
| 49 | /* *INDENT-OFF* */ // clang-format off | ||
| 50 | static const CLSID SDL_CLSID_MMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c,{ 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } }; | ||
| 51 | static const IID SDL_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35,{ 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } }; | ||
| 52 | static const IID SDL_IID_IMMNotificationClient = { 0x7991eec9, 0x7e89, 0x4d85,{ 0x83, 0x90, 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0 } }; | ||
| 53 | static const IID SDL_IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,{ 0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5 } }; | ||
| 54 | static const PROPERTYKEY SDL_PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd,{ 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, } }, 14 }; | ||
| 55 | static const PROPERTYKEY SDL_PKEY_AudioEngine_DeviceFormat = { { 0xf19f064d, 0x82c, 0x4e27,{ 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, } }, 0 }; | ||
| 56 | static const PROPERTYKEY SDL_PKEY_AudioEndpoint_GUID = { { 0x1da5d803, 0xd492, 0x4edd,{ 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e, } }, 4 }; | ||
| 57 | /* *INDENT-ON* */ // clang-format on | ||
| 58 | |||
| 59 | static bool FindByDevIDCallback(SDL_AudioDevice *device, void *userdata) | ||
| 60 | { | ||
| 61 | LPCWSTR devid = (LPCWSTR)userdata; | ||
| 62 | if (devid && device && device->handle) { | ||
| 63 | const SDL_IMMDevice_HandleData *handle = (const SDL_IMMDevice_HandleData *)device->handle; | ||
| 64 | if (handle->immdevice_id && SDL_wcscmp(handle->immdevice_id, devid) == 0) { | ||
| 65 | return true; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | return false; | ||
| 69 | } | ||
| 70 | |||
| 71 | static SDL_AudioDevice *SDL_IMMDevice_FindByDevID(LPCWSTR devid) | ||
| 72 | { | ||
| 73 | return SDL_FindPhysicalAudioDeviceByCallback(FindByDevIDCallback, (void *) devid); | ||
| 74 | } | ||
| 75 | |||
| 76 | LPGUID SDL_IMMDevice_GetDirectSoundGUID(SDL_AudioDevice *device) | ||
| 77 | { | ||
| 78 | return (device && device->handle) ? &(((SDL_IMMDevice_HandleData *) device->handle)->directsound_guid) : NULL; | ||
| 79 | } | ||
| 80 | |||
| 81 | LPCWSTR SDL_IMMDevice_GetDevID(SDL_AudioDevice *device) | ||
| 82 | { | ||
| 83 | return (device && device->handle) ? ((const SDL_IMMDevice_HandleData *) device->handle)->immdevice_id : NULL; | ||
| 84 | } | ||
| 85 | |||
| 86 | static void GetMMDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSIBLE *fmt, GUID *guid) | ||
| 87 | { | ||
| 88 | /* PKEY_Device_FriendlyName gives you "Speakers (SoundBlaster Pro)" which drives me nuts. I'd rather it be | ||
| 89 | "SoundBlaster Pro (Speakers)" but I guess that's developers vs users. Windows uses the FriendlyName in | ||
| 90 | its own UIs, like Volume Control, etc. */ | ||
| 91 | IPropertyStore *props = NULL; | ||
| 92 | *utf8dev = NULL; | ||
| 93 | SDL_zerop(fmt); | ||
| 94 | if (SUCCEEDED(IMMDevice_OpenPropertyStore(device, STGM_READ, &props))) { | ||
| 95 | PROPVARIANT var; | ||
| 96 | PropVariantInit(&var); | ||
| 97 | if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_Device_FriendlyName, &var))) { | ||
| 98 | *utf8dev = WIN_StringToUTF8W(var.pwszVal); | ||
| 99 | } | ||
| 100 | PropVariantClear(&var); | ||
| 101 | if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_AudioEngine_DeviceFormat, &var))) { | ||
| 102 | SDL_memcpy(fmt, var.blob.pBlobData, SDL_min(var.blob.cbSize, sizeof(WAVEFORMATEXTENSIBLE))); | ||
| 103 | } | ||
| 104 | PropVariantClear(&var); | ||
| 105 | if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_AudioEndpoint_GUID, &var))) { | ||
| 106 | (void)CLSIDFromString(var.pwszVal, guid); | ||
| 107 | } | ||
| 108 | PropVariantClear(&var); | ||
| 109 | IPropertyStore_Release(props); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device) | ||
| 114 | { | ||
| 115 | if (device && device->handle) { | ||
| 116 | SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *) device->handle; | ||
| 117 | SDL_free(handle->immdevice_id); | ||
| 118 | SDL_free(handle); | ||
| 119 | device->handle = NULL; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid) | ||
| 124 | { | ||
| 125 | /* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever). | ||
| 126 | In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for | ||
| 127 | phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be | ||
| 128 | available and switch automatically. (!!! FIXME...?) */ | ||
| 129 | |||
| 130 | if (!devname) { | ||
| 131 | return NULL; | ||
| 132 | } | ||
| 133 | |||
| 134 | // see if we already have this one first. | ||
| 135 | SDL_AudioDevice *device = SDL_IMMDevice_FindByDevID(devid); | ||
| 136 | if (device) { | ||
| 137 | if (SDL_GetAtomicInt(&device->zombie)) { | ||
| 138 | // whoa, it came back! This can happen if you unplug and replug USB headphones while we're still keeping the SDL object alive. | ||
| 139 | // Kill this device's IMMDevice id; the device will go away when the app closes it, or maybe a new default device is chosen | ||
| 140 | // (possibly this reconnected device), so we just want to make sure IMMDevice doesn't try to find the old device by the existing ID string. | ||
| 141 | SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *) device->handle; | ||
| 142 | SDL_free(handle->immdevice_id); | ||
| 143 | handle->immdevice_id = NULL; | ||
| 144 | device = NULL; // add a new device, below. | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | if (!device) { | ||
| 149 | // handle is freed by SDL_IMMDevice_FreeDeviceHandle! | ||
| 150 | SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *)SDL_malloc(sizeof(SDL_IMMDevice_HandleData)); | ||
| 151 | if (!handle) { | ||
| 152 | return NULL; | ||
| 153 | } | ||
| 154 | handle->immdevice_id = SDL_wcsdup(devid); | ||
| 155 | if (!handle->immdevice_id) { | ||
| 156 | SDL_free(handle); | ||
| 157 | return NULL; | ||
| 158 | } | ||
| 159 | SDL_memcpy(&handle->directsound_guid, dsoundguid, sizeof(GUID)); | ||
| 160 | |||
| 161 | SDL_AudioSpec spec; | ||
| 162 | SDL_zero(spec); | ||
| 163 | spec.channels = (Uint8)fmt->Format.nChannels; | ||
| 164 | spec.freq = fmt->Format.nSamplesPerSec; | ||
| 165 | spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt); | ||
| 166 | |||
| 167 | device = SDL_AddAudioDevice(recording, devname, &spec, handle); | ||
| 168 | if (!device) { | ||
| 169 | SDL_free(handle->immdevice_id); | ||
| 170 | SDL_free(handle); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | return device; | ||
| 175 | } | ||
| 176 | |||
| 177 | /* We need a COM subclass of IMMNotificationClient for hotplug support, which is | ||
| 178 | easy in C++, but we have to tapdance more to make work in C. | ||
| 179 | Thanks to this page for coaching on how to make this work: | ||
| 180 | https://www.codeproject.com/Articles/13601/COM-in-plain-C */ | ||
| 181 | |||
| 182 | typedef struct SDLMMNotificationClient | ||
| 183 | { | ||
| 184 | const IMMNotificationClientVtbl *lpVtbl; | ||
| 185 | SDL_AtomicInt refcount; | ||
| 186 | } SDLMMNotificationClient; | ||
| 187 | |||
| 188 | static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *client, REFIID iid, void **ppv) | ||
| 189 | { | ||
| 190 | if ((WIN_IsEqualIID(iid, &IID_IUnknown)) || (WIN_IsEqualIID(iid, &SDL_IID_IMMNotificationClient))) { | ||
| 191 | *ppv = client; | ||
| 192 | client->lpVtbl->AddRef(client); | ||
| 193 | return S_OK; | ||
| 194 | } | ||
| 195 | |||
| 196 | *ppv = NULL; | ||
| 197 | return E_NOINTERFACE; | ||
| 198 | } | ||
| 199 | |||
| 200 | static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_AddRef(IMMNotificationClient *iclient) | ||
| 201 | { | ||
| 202 | SDLMMNotificationClient *client = (SDLMMNotificationClient *)iclient; | ||
| 203 | return (ULONG)(SDL_AtomicIncRef(&client->refcount) + 1); | ||
| 204 | } | ||
| 205 | |||
| 206 | static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_Release(IMMNotificationClient *iclient) | ||
| 207 | { | ||
| 208 | // client is a static object; we don't ever free it. | ||
| 209 | SDLMMNotificationClient *client = (SDLMMNotificationClient *)iclient; | ||
| 210 | const ULONG rc = SDL_AtomicDecRef(&client->refcount); | ||
| 211 | if (rc == 0) { | ||
| 212 | SDL_SetAtomicInt(&client->refcount, 0); // uhh... | ||
| 213 | return 0; | ||
| 214 | } | ||
| 215 | return rc - 1; | ||
| 216 | } | ||
| 217 | |||
| 218 | // These are the entry points called when WASAPI device endpoints change. | ||
| 219 | static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDefaultDeviceChanged(IMMNotificationClient *iclient, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) | ||
| 220 | { | ||
| 221 | if (role == SDL_IMMDevice_role) { | ||
| 222 | immcallbacks.default_audio_device_changed(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); | ||
| 223 | } | ||
| 224 | return S_OK; | ||
| 225 | } | ||
| 226 | |||
| 227 | static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceAdded(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId) | ||
| 228 | { | ||
| 229 | /* we ignore this; devices added here then progress to ACTIVE, if appropriate, in | ||
| 230 | OnDeviceStateChange, making that a better place to deal with device adds. More | ||
| 231 | importantly: the first time you plug in a USB audio device, this callback will | ||
| 232 | fire, but when you unplug it, it isn't removed (it's state changes to NOTPRESENT). | ||
| 233 | Plugging it back in won't fire this callback again. */ | ||
| 234 | return S_OK; | ||
| 235 | } | ||
| 236 | |||
| 237 | static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceRemoved(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId) | ||
| 238 | { | ||
| 239 | return S_OK; // See notes in OnDeviceAdded handler about why we ignore this. | ||
| 240 | } | ||
| 241 | |||
| 242 | static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId, DWORD dwNewState) | ||
| 243 | { | ||
| 244 | IMMDevice *device = NULL; | ||
| 245 | |||
| 246 | if (SUCCEEDED(IMMDeviceEnumerator_GetDevice(enumerator, pwstrDeviceId, &device))) { | ||
| 247 | IMMEndpoint *endpoint = NULL; | ||
| 248 | if (SUCCEEDED(IMMDevice_QueryInterface(device, &SDL_IID_IMMEndpoint, (void **)&endpoint))) { | ||
| 249 | EDataFlow flow; | ||
| 250 | if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) { | ||
| 251 | const bool recording = (flow == eCapture); | ||
| 252 | if (dwNewState == DEVICE_STATE_ACTIVE) { | ||
| 253 | char *utf8dev; | ||
| 254 | WAVEFORMATEXTENSIBLE fmt; | ||
| 255 | GUID dsoundguid; | ||
| 256 | GetMMDeviceInfo(device, &utf8dev, &fmt, &dsoundguid); | ||
| 257 | if (utf8dev) { | ||
| 258 | SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid); | ||
| 259 | SDL_free(utf8dev); | ||
| 260 | } | ||
| 261 | } else { | ||
| 262 | immcallbacks.audio_device_disconnected(SDL_IMMDevice_FindByDevID(pwstrDeviceId)); | ||
| 263 | } | ||
| 264 | } | ||
| 265 | IMMEndpoint_Release(endpoint); | ||
| 266 | } | ||
| 267 | IMMDevice_Release(device); | ||
| 268 | } | ||
| 269 | |||
| 270 | return S_OK; | ||
| 271 | } | ||
| 272 | |||
| 273 | static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *client, LPCWSTR pwstrDeviceId, const PROPERTYKEY key) | ||
| 274 | { | ||
| 275 | return S_OK; // we don't care about these. | ||
| 276 | } | ||
| 277 | |||
| 278 | static const IMMNotificationClientVtbl notification_client_vtbl = { | ||
| 279 | SDLMMNotificationClient_QueryInterface, | ||
| 280 | SDLMMNotificationClient_AddRef, | ||
| 281 | SDLMMNotificationClient_Release, | ||
| 282 | SDLMMNotificationClient_OnDeviceStateChanged, | ||
| 283 | SDLMMNotificationClient_OnDeviceAdded, | ||
| 284 | SDLMMNotificationClient_OnDeviceRemoved, | ||
| 285 | SDLMMNotificationClient_OnDefaultDeviceChanged, | ||
| 286 | SDLMMNotificationClient_OnPropertyValueChanged | ||
| 287 | }; | ||
| 288 | |||
| 289 | static SDLMMNotificationClient notification_client = { ¬ification_client_vtbl, { 1 } }; | ||
| 290 | |||
| 291 | bool SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks) | ||
| 292 | { | ||
| 293 | HRESULT ret; | ||
| 294 | |||
| 295 | // just skip the discussion with COM here. | ||
| 296 | if (!WIN_IsWindowsVistaOrGreater()) { | ||
| 297 | return SDL_SetError("IMMDevice support requires Windows Vista or later"); | ||
| 298 | } | ||
| 299 | |||
| 300 | if (FAILED(WIN_CoInitialize())) { | ||
| 301 | return SDL_SetError("IMMDevice: CoInitialize() failed"); | ||
| 302 | } | ||
| 303 | |||
| 304 | ret = CoCreateInstance(&SDL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IMMDeviceEnumerator, (LPVOID *)&enumerator); | ||
| 305 | if (FAILED(ret)) { | ||
| 306 | WIN_CoUninitialize(); | ||
| 307 | return WIN_SetErrorFromHRESULT("IMMDevice CoCreateInstance(MMDeviceEnumerator)", ret); | ||
| 308 | } | ||
| 309 | |||
| 310 | if (callbacks) { | ||
| 311 | SDL_copyp(&immcallbacks, callbacks); | ||
| 312 | } else { | ||
| 313 | SDL_zero(immcallbacks); | ||
| 314 | } | ||
| 315 | |||
| 316 | if (!immcallbacks.audio_device_disconnected) { | ||
| 317 | immcallbacks.audio_device_disconnected = SDL_AudioDeviceDisconnected; | ||
| 318 | } | ||
| 319 | if (!immcallbacks.default_audio_device_changed) { | ||
| 320 | immcallbacks.default_audio_device_changed = SDL_DefaultAudioDeviceChanged; | ||
| 321 | } | ||
| 322 | |||
| 323 | return true; | ||
| 324 | } | ||
| 325 | |||
| 326 | void SDL_IMMDevice_Quit(void) | ||
| 327 | { | ||
| 328 | if (enumerator) { | ||
| 329 | IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); | ||
| 330 | IMMDeviceEnumerator_Release(enumerator); | ||
| 331 | enumerator = NULL; | ||
| 332 | } | ||
| 333 | |||
| 334 | SDL_zero(immcallbacks); | ||
| 335 | |||
| 336 | WIN_CoUninitialize(); | ||
| 337 | } | ||
| 338 | |||
| 339 | bool SDL_IMMDevice_Get(SDL_AudioDevice *device, IMMDevice **immdevice, bool recording) | ||
| 340 | { | ||
| 341 | const Uint64 timeout = SDL_GetTicks() + 8000; // intel's audio drivers can fail for up to EIGHT SECONDS after a device is connected or we wake from sleep. | ||
| 342 | |||
| 343 | SDL_assert(device != NULL); | ||
| 344 | SDL_assert(immdevice != NULL); | ||
| 345 | |||
| 346 | LPCWSTR devid = SDL_IMMDevice_GetDevID(device); | ||
| 347 | SDL_assert(devid != NULL); | ||
| 348 | |||
| 349 | HRESULT ret; | ||
| 350 | while ((ret = IMMDeviceEnumerator_GetDevice(enumerator, devid, immdevice)) == E_NOTFOUND) { | ||
| 351 | const Uint64 now = SDL_GetTicks(); | ||
| 352 | if (timeout > now) { | ||
| 353 | const Uint64 ticksleft = timeout - now; | ||
| 354 | SDL_Delay((Uint32)SDL_min(ticksleft, 300)); // wait awhile and try again. | ||
| 355 | continue; | ||
| 356 | } | ||
| 357 | break; | ||
| 358 | } | ||
| 359 | |||
| 360 | if (!SUCCEEDED(ret)) { | ||
| 361 | return WIN_SetErrorFromHRESULT("WASAPI can't find requested audio endpoint", ret); | ||
| 362 | } | ||
| 363 | return true; | ||
| 364 | } | ||
| 365 | |||
| 366 | static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **default_device) | ||
| 367 | { | ||
| 368 | /* Note that WASAPI separates "adapter devices" from "audio endpoint devices" | ||
| 369 | ...one adapter device ("SoundBlaster Pro") might have multiple endpoint devices ("Speakers", "Line-Out"). */ | ||
| 370 | |||
| 371 | IMMDeviceCollection *collection = NULL; | ||
| 372 | if (FAILED(IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, recording ? eCapture : eRender, DEVICE_STATE_ACTIVE, &collection))) { | ||
| 373 | return; | ||
| 374 | } | ||
| 375 | |||
| 376 | UINT total = 0; | ||
| 377 | if (FAILED(IMMDeviceCollection_GetCount(collection, &total))) { | ||
| 378 | IMMDeviceCollection_Release(collection); | ||
| 379 | return; | ||
| 380 | } | ||
| 381 | |||
| 382 | LPWSTR default_devid = NULL; | ||
| 383 | if (default_device) { | ||
| 384 | IMMDevice *default_immdevice = NULL; | ||
| 385 | const EDataFlow dataflow = recording ? eCapture : eRender; | ||
| 386 | if (SUCCEEDED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_IMMDevice_role, &default_immdevice))) { | ||
| 387 | LPWSTR devid = NULL; | ||
| 388 | if (SUCCEEDED(IMMDevice_GetId(default_immdevice, &devid))) { | ||
| 389 | default_devid = SDL_wcsdup(devid); // if this fails, oh well. | ||
| 390 | CoTaskMemFree(devid); | ||
| 391 | } | ||
| 392 | IMMDevice_Release(default_immdevice); | ||
| 393 | } | ||
| 394 | } | ||
| 395 | |||
| 396 | for (UINT i = 0; i < total; i++) { | ||
| 397 | IMMDevice *immdevice = NULL; | ||
| 398 | if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &immdevice))) { | ||
| 399 | LPWSTR devid = NULL; | ||
| 400 | if (SUCCEEDED(IMMDevice_GetId(immdevice, &devid))) { | ||
| 401 | char *devname = NULL; | ||
| 402 | WAVEFORMATEXTENSIBLE fmt; | ||
| 403 | GUID dsoundguid; | ||
| 404 | SDL_zero(fmt); | ||
| 405 | SDL_zero(dsoundguid); | ||
| 406 | GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid); | ||
| 407 | if (devname) { | ||
| 408 | SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(recording, devname, &fmt, devid, &dsoundguid); | ||
| 409 | if (default_device && default_devid && SDL_wcscmp(default_devid, devid) == 0) { | ||
| 410 | *default_device = sdldevice; | ||
| 411 | } | ||
| 412 | SDL_free(devname); | ||
| 413 | } | ||
| 414 | CoTaskMemFree(devid); | ||
| 415 | } | ||
| 416 | IMMDevice_Release(immdevice); | ||
| 417 | } | ||
| 418 | } | ||
| 419 | |||
| 420 | SDL_free(default_devid); | ||
| 421 | |||
| 422 | IMMDeviceCollection_Release(collection); | ||
| 423 | } | ||
| 424 | |||
| 425 | void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording) | ||
| 426 | { | ||
| 427 | EnumerateEndpointsForFlow(false, default_playback); | ||
| 428 | EnumerateEndpointsForFlow(true, default_recording); | ||
| 429 | |||
| 430 | // if this fails, we just won't get hotplug events. Carry on anyhow. | ||
| 431 | IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)¬ification_client); | ||
| 432 | } | ||
| 433 | |||
| 434 | #endif // defined(SDL_PLATFORM_WINDOWS) && defined(HAVE_MMDEVICEAPI_H) | ||
