|
|
@@ -54,6 +54,7 @@ static const IID SDL_IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,{ 0x85, 0x86
|
|
|
static const PROPERTYKEY SDL_PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd,{ 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, } }, 14 };
|
|
|
static const PROPERTYKEY SDL_PKEY_AudioEngine_DeviceFormat = { { 0xf19f064d, 0x82c, 0x4e27,{ 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, } }, 0 };
|
|
|
static const PROPERTYKEY SDL_PKEY_AudioEndpoint_GUID = { { 0x1da5d803, 0xd492, 0x4edd,{ 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e, } }, 4 };
|
|
|
+static const PROPERTYKEY SDL_PKEY_AudioEndpoint_StableId = { { 0x1da5d803, 0xd492, 0x4edd,{ 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e, } }, 12 };
|
|
|
/* *INDENT-ON* */ // clang-format on
|
|
|
|
|
|
static bool FindByDevIDCallback(SDL_AudioDevice *device, void *userdata)
|
|
|
@@ -83,13 +84,15 @@ LPCWSTR SDL_IMMDevice_GetDevID(SDL_AudioDevice *device)
|
|
|
return (device && device->handle) ? ((const SDL_IMMDevice_HandleData *) device->handle)->immdevice_id : NULL;
|
|
|
}
|
|
|
|
|
|
-static void GetMMDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSIBLE *fmt, GUID *guid)
|
|
|
+static void GetMMDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSIBLE *fmt, GUID *guid, char **unique_id)
|
|
|
{
|
|
|
/* PKEY_Device_FriendlyName gives you "Speakers (SoundBlaster Pro)" which drives me nuts. I'd rather it be
|
|
|
"SoundBlaster Pro (Speakers)" but I guess that's developers vs users. Windows uses the FriendlyName in
|
|
|
its own UIs, like Volume Control, etc. */
|
|
|
+ LPWSTR devid = NULL;
|
|
|
IPropertyStore *props = NULL;
|
|
|
*utf8dev = NULL;
|
|
|
+ *unique_id = NULL;
|
|
|
SDL_zerop(fmt);
|
|
|
if (SUCCEEDED(IMMDevice_OpenPropertyStore(device, STGM_READ, &props))) {
|
|
|
PROPVARIANT var;
|
|
|
@@ -105,6 +108,15 @@ static void GetMMDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSI
|
|
|
if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_AudioEndpoint_GUID, &var))) {
|
|
|
(void)CLSIDFromString(var.pwszVal, guid);
|
|
|
}
|
|
|
+
|
|
|
+ PropVariantClear(&var);
|
|
|
+ if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_AudioEndpoint_StableId, &var)) && var.pwszVal) { // this was introduced in Windows 11, with stronger promises than IMMDevice_GetId().
|
|
|
+ *unique_id = WIN_StringToUTF8W(var.pwszVal);
|
|
|
+ } else if (SUCCEEDED(IMMDevice_GetId(device, &devid))) {
|
|
|
+ *unique_id = WIN_StringToUTF8W(devid);
|
|
|
+ CoTaskMemFree(devid);
|
|
|
+ }
|
|
|
+
|
|
|
PropVariantClear(&var);
|
|
|
IPropertyStore_Release(props);
|
|
|
}
|
|
|
@@ -120,7 +132,7 @@ void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid, SDL_AudioFormat force_format, bool supports_recording_playback_devices)
|
|
|
+static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid, const char *unique_id, SDL_AudioFormat force_format, bool supports_recording_playback_devices)
|
|
|
{
|
|
|
/* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
|
|
|
In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
|
|
|
@@ -164,7 +176,7 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devn
|
|
|
spec.freq = fmt->Format.nSamplesPerSec;
|
|
|
spec.format = (force_format != SDL_AUDIO_UNKNOWN) ? force_format : SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt);
|
|
|
|
|
|
- device = SDL_AddAudioDevice(recording, devname, &spec, handle);
|
|
|
+ device = SDL_AddAudioDevice(recording, devname, unique_id, &spec, handle);
|
|
|
|
|
|
if (!recording && supports_recording_playback_devices) {
|
|
|
// handle is freed by SDL_IMMDevice_FreeDeviceHandle!
|
|
|
@@ -181,7 +193,7 @@ static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devn
|
|
|
|
|
|
SDL_copyp(&recording_handle->directsound_guid, dsoundguid);
|
|
|
|
|
|
- if (!SDL_AddAudioDevice(true, devname, &spec, recording_handle)) {
|
|
|
+ if (!SDL_AddAudioDevice(true, devname, unique_id, &spec, recording_handle)) {
|
|
|
SDL_free(recording_handle->immdevice_id);
|
|
|
SDL_free(recording_handle);
|
|
|
}
|
|
|
@@ -276,13 +288,15 @@ static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IM
|
|
|
const bool recording = (flow == eCapture);
|
|
|
if (dwNewState == DEVICE_STATE_ACTIVE) {
|
|
|
char *utf8dev;
|
|
|
+ char *unique_id;
|
|
|
WAVEFORMATEXTENSIBLE fmt;
|
|
|
GUID dsoundguid;
|
|
|
- GetMMDeviceInfo(device, &utf8dev, &fmt, &dsoundguid);
|
|
|
+ GetMMDeviceInfo(device, &utf8dev, &fmt, &dsoundguid, &unique_id);
|
|
|
if (utf8dev) {
|
|
|
- SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid, client->force_format, client->supports_recording_playback_devices);
|
|
|
+ SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid, unique_id, client->force_format, client->supports_recording_playback_devices);
|
|
|
SDL_free(utf8dev);
|
|
|
}
|
|
|
+ SDL_free(unique_id);
|
|
|
} else {
|
|
|
immcallbacks.audio_device_disconnected(SDL_IMMDevice_FindByDevID(pwstrDeviceId));
|
|
|
}
|
|
|
@@ -440,18 +454,20 @@ static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **de
|
|
|
LPWSTR devid = NULL;
|
|
|
if (SUCCEEDED(IMMDevice_GetId(immdevice, &devid))) {
|
|
|
char *devname = NULL;
|
|
|
+ char *unique_id = NULL;
|
|
|
WAVEFORMATEXTENSIBLE fmt;
|
|
|
GUID dsoundguid;
|
|
|
SDL_zero(fmt);
|
|
|
SDL_zero(dsoundguid);
|
|
|
- GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid);
|
|
|
+ GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid, &unique_id);
|
|
|
if (devname) {
|
|
|
- SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(recording, devname, &fmt, devid, &dsoundguid, force_format, supports_recording_playback_devices);
|
|
|
+ SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(recording, devname, &fmt, devid, &dsoundguid, unique_id, force_format, supports_recording_playback_devices);
|
|
|
if (default_device && default_devid && SDL_wcscmp(default_devid, devid) == 0) {
|
|
|
*default_device = sdldevice;
|
|
|
}
|
|
|
SDL_free(devname);
|
|
|
}
|
|
|
+ SDL_free(unique_id);
|
|
|
CoTaskMemFree(devid);
|
|
|
}
|
|
|
IMMDevice_Release(immdevice);
|