`winecoreaudio` currently uses the `AudioDeviceID` as the device string (stored in the registry and used to correlate between Core Audio devices and generated GUIDs passed to Windows applications), but an `AudioDeviceID` may change between boots or even when a device is connected/disconnected.
Use the UID instead, which is persistent across boots and connects/disconnects. These are strings like `BuiltInSpeakerDevice` or `AppleUSBAudioEngine:Apple Inc.:Studio Display:00008030-000128469718180E:6,7`.
This only seems to be documented in the header files, from `AudioHardwareBase.h`: "`kAudioDevicePropertyDeviceUID`: A CFString that contains a persistent identifier for the AudioDevice. An AudioDevice's UID is persistent across boots. The content of the UID string is a black box and may contain information that is unique to a particular instance of an AudioDevice's hardware or unique to the CPU. Therefore they are not suitable for passing between CPUs or for identifying similar models of hardware."
From: Brendan Shanks bshanks@codeweavers.com
--- dlls/winecoreaudio.drv/coreaudio.c | 49 ++++++++++++++++++++++++++---- dlls/winecoreaudio.drv/mmdevdrv.c | 6 ++-- 2 files changed, 46 insertions(+), 9 deletions(-)
diff --git a/dlls/winecoreaudio.drv/coreaudio.c b/dlls/winecoreaudio.drv/coreaudio.c index 3299b84d489..d78744455a8 100644 --- a/dlls/winecoreaudio.drv/coreaudio.c +++ b/dlls/winecoreaudio.drv/coreaudio.c @@ -84,8 +84,6 @@ typedef OSSpinLock os_unfair_lock;
WINE_DEFAULT_DEBUG_CHANNEL(coreaudio);
-#define MAX_DEV_NAME_LEN 10 /* Max 32 bit digits */ - struct coreaudio_stream { os_unfair_lock lock; @@ -242,6 +240,7 @@ static NTSTATUS unix_get_endpoint_ids(void *args) struct endpoint_info { CFStringRef name; + CFStringRef uid; AudioDeviceID id; } *info; OSStatus sc; @@ -293,13 +292,13 @@ static NTSTATUS unix_get_endpoint_ids(void *args) return STATUS_SUCCESS; }
- addr.mSelector = kAudioObjectPropertyName; addr.mScope = get_scope(params->flow); addr.mElement = 0;
for(i = 0; i < num_devices; i++){ if(!device_has_channels(devices[i], params->flow)) continue;
+ addr.mSelector = kAudioObjectPropertyName; size = sizeof(CFStringRef); sc = AudioObjectGetPropertyData(devices[i], &addr, 0, NULL, &size, &info[params->num].name); if(sc != noErr){ @@ -307,6 +306,16 @@ static NTSTATUS unix_get_endpoint_ids(void *args) (unsigned int)devices[i], (int)sc); continue; } + + addr.mSelector = kAudioDevicePropertyDeviceUID; + size = sizeof(CFStringRef); + sc = AudioObjectGetPropertyData(devices[i], &addr, 0, NULL, &size, &info[params->num].uid); + if(sc != noErr){ + WARN("Unable to get UID property for device %u: %x\n", + (unsigned int)devices[i], (int)sc); + continue; + } + info[params->num++].id = devices[i]; } free(devices); @@ -316,7 +325,7 @@ static NTSTATUS unix_get_endpoint_ids(void *args)
for(i = 0; i < params->num; i++){ const SIZE_T name_len = CFStringGetLength(info[i].name) + 1; - const SIZE_T device_len = MAX_DEV_NAME_LEN + 1; + const SIZE_T device_len = CFStringGetLength(info[i].uid) + 1; needed += name_len * sizeof(WCHAR) + ((device_len + 1) & ~1);
if(needed <= params->size){ @@ -325,12 +334,16 @@ static NTSTATUS unix_get_endpoint_ids(void *args) CFStringGetCharacters(info[i].name, CFRangeMake(0, name_len - 1), ptr); ptr[name_len - 1] = 0; offset += name_len * sizeof(WCHAR); + endpoint->device = offset; - sprintf((char *)params->endpoints + offset, "%u", (unsigned int)info[i].id); + CFStringGetCString(info[i].uid, (char *)params->endpoints + offset, params->size - offset, kCFStringEncodingUTF8); + ((char *)params->endpoints)[offset + device_len - 1] = '\0'; offset += (device_len + 1) & ~1; + endpoint++; } CFRelease(info[i].name); + CFRelease(info[i].uid); if(info[i].id == default_id) params->default_idx = i; } free(info); @@ -669,7 +682,31 @@ static HRESULT ca_setup_audiounit(EDataFlow dataflow, AudioComponentInstance uni
static AudioDeviceID dev_id_from_device(const char *device) { - return strtoul(device, NULL, 10); + AudioDeviceID id; + CFStringRef uid; + UInt32 size; + OSStatus sc; + const AudioObjectPropertyAddress addr = + { + .mScope = kAudioObjectPropertyScopeGlobal, + .mElement = kAudioObjectPropertyElementMain, + .mSelector = kAudioHardwarePropertyTranslateUIDToDevice, + }; + + uid = CFStringCreateWithCStringNoCopy(NULL, device, kCFStringEncodingUTF8, kCFAllocatorNull); + + size = sizeof(id); + sc = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, sizeof(uid), &uid, &size, &id); + CFRelease(uid); + if(sc != noErr){ + WARN("Failed to get device ID for UID %s: %x\n", device, (int)sc); + return kAudioObjectUnknown; + } + + if (id == kAudioObjectUnknown) + WARN("Failed to get device ID for UID %s\n", device); + + return id; }
static NTSTATUS unix_create_stream(void *args) diff --git a/dlls/winecoreaudio.drv/mmdevdrv.c b/dlls/winecoreaudio.drv/mmdevdrv.c index 59bdb7b7488..65e965c5556 100644 --- a/dlls/winecoreaudio.drv/mmdevdrv.c +++ b/dlls/winecoreaudio.drv/mmdevdrv.c @@ -124,7 +124,7 @@ void WINAPI get_device_guid(EDataFlow flow, const char *dev, GUID *guid) key_name[0] = '0'; key_name[1] = ',';
- MultiByteToWideChar(CP_UNIXCP, 0, dev, -1, key_name + 2, ARRAY_SIZE(key_name) - 2); + MultiByteToWideChar(CP_UTF8, 0, dev, -1, key_name + 2, ARRAY_SIZE(key_name) - 2);
if(RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_WRITE|KEY_READ, &key) == ERROR_SUCCESS){ if(RegOpenKeyExW(key, key_name, 0, KEY_READ, &dev_key) == ERROR_SUCCESS){ @@ -195,13 +195,13 @@ BOOL WINAPI get_device_name_from_guid(const GUID *guid, char **name, EDataFlow * return FALSE; }
- if(!(size = WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, NULL, 0, NULL, NULL))) + if(!(size = WideCharToMultiByte(CP_UTF8, 0, key_name + 2, -1, NULL, 0, NULL, NULL))) return FALSE;
if(!(*name = malloc(size))) return FALSE;
- if(!WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, *name, size, NULL, NULL)){ + if(!WideCharToMultiByte(CP_UTF8, 0, key_name + 2, -1, *name, size, NULL, NULL)){ free(*name); return FALSE; }
This merge request was approved by Huw Davies.