This exposes the actual devices (and virtual sinks/sources) as reported by PulseAudio to an application, allowing it to select the devices itself and, for example, record from (or render to) two devices at the same time. The "PulseAudio" device (which is movable) is still the default, as before, with the same GUID to preserve compatibility with existing setups.
Based on a patch by Mark Harmstone mark@harmstone.com, with changes by Sebastian Lackner sebastian@fds-team.de.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
This patchset has lived in wine-staging for a long time, but has been recently rebased to deal with unixlib separation.
dlls/winepulse.drv/mmdevdrv.c | 210 ++++++++++++++++++++++++++++------ dlls/winepulse.drv/pulse.c | 135 ++++++++++++++++++++-- dlls/winepulse.drv/unixlib.h | 11 ++ 3 files changed, 310 insertions(+), 46 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 35a66e1..844c14c 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -69,6 +69,8 @@ static GUID pulse_render_guid = static GUID pulse_capture_guid = { 0x25da76d0, 0x033c, 0x4235, { 0x90, 0x02, 0x19, 0xf4, 0x88, 0x94, 0xac, 0x6f } };
+static const WCHAR *drv_key_devicesW = L"Software\Wine\Drivers\winepulse.drv\devices"; + static CRITICAL_SECTION session_cs; static CRITICAL_SECTION_DEBUG session_cs_debug = { 0, 0, &session_cs, @@ -134,6 +136,7 @@ struct ACImpl { IUnknown *marshal; IMMDevice *parent; struct list entry; + char device[256]; float *vol;
LONG ref; @@ -147,8 +150,6 @@ struct ACImpl { AudioSessionWrapper *session_wrapper; };
-static const WCHAR defaultW[] = L"PulseAudio"; - static const IAudioClient3Vtbl AudioClient3_Vtbl; static const IAudioRenderClientVtbl AudioRenderClient_Vtbl; static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl; @@ -267,39 +268,114 @@ static void set_stream_volumes(ACImpl *This) pulse_call(set_volumes, ¶ms); }
-HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys, - UINT *num, UINT *def_index) +static void get_device_guid(HKEY drv_key, EDataFlow flow, const WCHAR *device, GUID *guid) { - WCHAR *id; + DWORD type, size = sizeof(*guid); + WCHAR key_name[258]; + LSTATUS status; + HKEY dev_key;
- TRACE("%d %p %p %p\n", flow, ids, num, def_index); + if (!device[0]) { + *guid = (flow == eRender) ? pulse_render_guid : pulse_capture_guid; + return; + }
- *num = 1; - *def_index = 0; + if (!drv_key) { + CoCreateGuid(guid); + return; + }
- *ids = HeapAlloc(GetProcessHeap(), 0, sizeof(**ids)); - *keys = NULL; - if (!*ids) - return E_OUTOFMEMORY; + key_name[0] = (flow == eRender) ? '0' : '1'; + key_name[1] = ','; + wcscpy(key_name + 2, device);
- (*ids)[0] = id = HeapAlloc(GetProcessHeap(), 0, sizeof(defaultW)); - *keys = HeapAlloc(GetProcessHeap(), 0, sizeof(**keys)); - if (!*keys || !id) { - HeapFree(GetProcessHeap(), 0, id); - HeapFree(GetProcessHeap(), 0, *keys); - HeapFree(GetProcessHeap(), 0, *ids); - *ids = NULL; - *keys = NULL; - return E_OUTOFMEMORY; + status = RegCreateKeyExW(drv_key, key_name, 0, NULL, 0, KEY_READ | KEY_WRITE | KEY_WOW64_64KEY, + NULL, &dev_key, NULL); + if (status != ERROR_SUCCESS) { + ERR("Failed to open registry key for device %s: %u\n", debugstr_w(device), status); + CoCreateGuid(guid); + return; } - memcpy(id, defaultW, sizeof(defaultW));
- if (flow == eRender) - (*keys)[0] = pulse_render_guid; - else - (*keys)[0] = pulse_capture_guid; + status = RegQueryValueExW(dev_key, L"guid", 0, &type, (BYTE*)guid, &size); + if (status != ERROR_SUCCESS || type != REG_BINARY || size != sizeof(*guid)) { + CoCreateGuid(guid); + status = RegSetValueExW(dev_key, L"guid", 0, REG_BINARY, (BYTE*)guid, sizeof(*guid)); + if (status != ERROR_SUCCESS) + ERR("Failed to store device GUID for %s to registry: %u\n", debugstr_w(device), status); + } + RegCloseKey(dev_key); +}
- return S_OK; +HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids_out, GUID **keys, + UINT *num, UINT *def_index) +{ + struct get_endpoint_ids_params params; + GUID *guids = NULL; + WCHAR **ids = NULL; + unsigned int i = 0; + LSTATUS status; + DWORD name_len; + HKEY drv_key; + WCHAR *p; + + TRACE("%d %p %p %p\n", flow, ids_out, num, def_index); + + params.flow = flow; + params.size = 1024; + params.devices = NULL; + do { + HeapFree(GetProcessHeap(), 0, params.devices); + params.devices = HeapAlloc(GetProcessHeap(), 0, params.size); + pulse_call(get_endpoint_ids, ¶ms); + } while(params.result == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + + if (FAILED(params.result)) + goto end; + + ids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*ids)); + guids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*guids)); + if (!ids || !guids) { + params.result = E_OUTOFMEMORY; + goto end; + } + + status = RegCreateKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, NULL, 0, + KEY_WRITE | KEY_WOW64_64KEY, NULL, &drv_key, NULL); + if (status != ERROR_SUCCESS) { + ERR("Failed to open devices registry key: %u\n", status); + drv_key = NULL; + } + + p = params.devices; + for (i = 0; i < params.num; i++) { + get_device_guid(drv_key, flow, p, &guids[i]); + p += wcslen(p) + 1; + + name_len = wcslen(p) + 1; + if (!(ids[i] = HeapAlloc(GetProcessHeap(), 0, name_len * sizeof(WCHAR)))) { + params.result = E_OUTOFMEMORY; + break; + } + memcpy(ids[i], p, name_len * sizeof(WCHAR)); + p += name_len; + } + if (drv_key) + RegCloseKey(drv_key); + +end: + HeapFree(GetProcessHeap(), 0, params.devices); + if (FAILED(params.result)) { + HeapFree(GetProcessHeap(), 0, guids); + while (i--) HeapFree(GetProcessHeap(), 0, ids[i]); + HeapFree(GetProcessHeap(), 0, ids); + } else { + *ids_out = ids; + *keys = guids; + *num = params.num; + *def_index = 0; + } + return params.result; }
int WINAPI AUDDRV_GetPriority(void) @@ -314,26 +390,87 @@ int WINAPI AUDDRV_GetPriority(void) return SUCCEEDED(params.result) ? Priority_Preferred : Priority_Unavailable; }
+static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow *flow) +{ + DWORD key_name_size; + WCHAR key_name[258]; + DWORD index = 0; + HKEY key; + + /* Return empty string for default PulseAudio device */ + name[0] = 0; + if (IsEqualGUID(guid, &pulse_render_guid)) { + *flow = eRender; + return TRUE; + } else if (IsEqualGUID(guid, &pulse_capture_guid)) { + *flow = eCapture; + return TRUE; + } + + if (RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ | KEY_WOW64_64KEY, &key) != ERROR_SUCCESS) { + WARN("No devices found in registry\n"); + return FALSE; + } + + for (;;) { + DWORD size, type; + LSTATUS status; + GUID reg_guid; + HKEY dev_key; + + key_name_size = ARRAY_SIZE(key_name); + if (RegEnumKeyExW(key, index++, key_name, &key_name_size, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) + break; + + if (RegOpenKeyExW(key, key_name, 0, KEY_READ | KEY_WOW64_64KEY, &dev_key) != ERROR_SUCCESS) { + ERR("Couldn't open key: %s\n", wine_dbgstr_w(key_name)); + continue; + } + + size = sizeof(reg_guid); + status = RegQueryValueExW(dev_key, L"guid", 0, &type, (BYTE *)®_guid, &size); + RegCloseKey(dev_key); + + if (status == ERROR_SUCCESS && type == REG_BINARY && size == sizeof(reg_guid) && IsEqualGUID(®_guid, guid)) { + RegCloseKey(key); + + TRACE("Found matching device key: %s\n", wine_dbgstr_w(key_name)); + + if (key_name[0] == '0') + *flow = eRender; + else if (key_name[0] == '1') + *flow = eCapture; + else { + WARN("Unknown device type: %c\n", key_name[0]); + return FALSE; + } + + return WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, 256, NULL, NULL); + } + } + + RegCloseKey(key); + WARN("No matching device in registry for GUID %s\n", debugstr_guid(guid)); + return FALSE; +} + HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) { - ACImpl *This; + ACImpl *This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); EDataFlow dataflow; HRESULT hr;
TRACE("%s %p %p\n", debugstr_guid(guid), dev, out); - if (IsEqualGUID(guid, &pulse_render_guid)) - dataflow = eRender; - else if (IsEqualGUID(guid, &pulse_capture_guid)) - dataflow = eCapture; - else - return E_UNEXPECTED;
*out = NULL; - - This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); if (!This) return E_OUTOFMEMORY;
+ if (!get_pulse_name_by_guid(guid, This->device, &dataflow)) { + HeapFree(GetProcessHeap(), 0, This); + return AUDCLNT_E_DEVICE_INVALIDATED; + } + This->IAudioClient3_iface.lpVtbl = &AudioClient3_Vtbl; This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl; This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl; @@ -609,6 +746,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, }
params.name = name = get_application_name(); + params.device = This->device; params.dataflow = This->dataflow; params.mode = mode; params.flags = flags; diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 3e65936..d5f8edf 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -81,6 +81,12 @@ typedef struct _ACPacket UINT32 discont; } ACPacket;
+typedef struct _PhysDevice { + struct list entry; + WCHAR *name; + char device[0]; +} PhysDevice; + static pa_context *pulse_ctx; static pa_mainloop *pulse_ml;
@@ -89,6 +95,8 @@ static WAVEFORMATEXTENSIBLE pulse_fmt[2]; static REFERENCE_TIME pulse_min_period[2], pulse_def_period[2];
static UINT g_phys_speakers_mask = 0; +static struct list g_phys_speakers = LIST_INIT(g_phys_speakers); +static struct list g_phys_sources = LIST_INIT(g_phys_sources);
static const REFERENCE_TIME MinimumPeriod = 30000; static const REFERENCE_TIME DefaultPeriod = 100000; @@ -128,6 +136,20 @@ static void dump_attr(const pa_buffer_attr *attr) TRACE("prebuf: %u\n", attr->prebuf); }
+static void free_phys_device_lists(void) +{ + static struct list *const lists[] = { &g_phys_speakers, &g_phys_sources, NULL }; + struct list *const *list = lists; + PhysDevice *dev, *dev_next; + + do { + LIST_FOR_EACH_ENTRY_SAFE(dev, dev_next, *list, PhysDevice, entry) { + free(dev->name); + free(dev); + } + } while (*(++list)); +} + /* copied from kernelbase */ static int muldiv(int a, int b, int c) { @@ -190,6 +212,7 @@ static NTSTATUS pulse_process_attach(void *args)
static NTSTATUS pulse_process_detach(void *args) { + free_phys_device_lists(); if (pulse_ctx) { pa_context_disconnect(pulse_ctx); @@ -215,6 +238,39 @@ static NTSTATUS pulse_main_loop(void *args) return STATUS_SUCCESS; }
+static NTSTATUS pulse_get_endpoint_ids(void *args) +{ + struct get_endpoint_ids_params *params = args; + struct list *list = (params->flow == eRender) ? &g_phys_speakers : &g_phys_sources; + DWORD len, name_len, needed = 0; + WCHAR *p = params->devices; + WCHAR device[256]; + PhysDevice *dev; + + params->num = 0; + LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) { + if (!(len = ntdll_umbstowcs(dev->device, strlen(dev->device) + 1, device, ARRAY_SIZE(device)))) + continue; + name_len = lstrlenW(dev->name) + 1; + + needed += (len + name_len) * sizeof(WCHAR); + if (needed <= params->size) { + memcpy(p, device, len * sizeof(WCHAR)); + p += len; + memcpy(p, dev->name, name_len * sizeof(WCHAR)); + p += name_len; + } + params->num++; + } + + if (needed > params->size) { + params->size = needed; + params->result = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } else + params->result = S_OK; + return STATUS_SUCCESS; +} + static void pulse_contextcallback(pa_context *c, void *userdata) { switch (pa_context_get_state(c)) { @@ -357,12 +413,49 @@ static DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) return mask; }
-/* For default PulseAudio render device, OR together all of the - * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ +static void pulse_add_device(struct list *list, const char *device, const char *name) +{ + DWORD len = strlen(device), name_len = strlen(name); + PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, device[len + 1])); + WCHAR *wname; + + if (!dev) + return; + + if (!(wname = malloc((name_len + 1) * sizeof(WCHAR)))) { + free(dev); + return; + } + + if (!(name_len = ntdll_umbstowcs(name, name_len, wname, name_len)) || + !(dev->name = realloc(wname, (name_len + 1) * sizeof(WCHAR)))) { + free(wname); + free(dev); + return; + } + dev->name[name_len] = 0; + memcpy(dev->device, device, len + 1); + + list_add_tail(list, &dev->entry); +} + static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { - if (i) - g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map); + if (!i || !i->name || !i->name[0]) + return; + + /* For default PulseAudio render device, OR together all of the + * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ + g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map); + + pulse_add_device(&g_phys_speakers, i->name, i->description); +} + +static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) +{ + if (!i || !i->name || !i->name[0]) + return; + pulse_add_device(&g_phys_sources, i->name, i->description); }
/* For most hardware on Windows, users must choose a configuration with an even @@ -579,7 +672,14 @@ static NTSTATUS pulse_test_connect(void *args) pulse_probe_settings(1, &pulse_fmt[0]); pulse_probe_settings(0, &pulse_fmt[1]);
+ free_phys_device_lists(); + list_init(&g_phys_speakers); + list_init(&g_phys_sources); g_phys_speakers_mask = 0; + + pulse_add_device(&g_phys_speakers, "", "PulseAudio"); + pulse_add_device(&g_phys_sources, "", "PulseAudio"); + o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && @@ -588,6 +688,14 @@ static NTSTATUS pulse_test_connect(void *args) pa_operation_unref(o); }
+ o = pa_context_get_source_info_list(pulse_ctx, &pulse_phys_sources_cb, NULL); + if (o) { + while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && + pa_operation_get_state(o) == PA_OPERATION_RUNNING) + {} + pa_operation_unref(o); + } + pa_context_unref(pulse_ctx); pulse_ctx = NULL; pa_mainloop_free(pulse_ml); @@ -771,8 +879,9 @@ static HRESULT pulse_spec_from_waveformat(struct pulse_stream *stream, const WAV return S_OK; }
-static HRESULT pulse_stream_connect(struct pulse_stream *stream, UINT32 period_bytes) +static HRESULT pulse_stream_connect(struct pulse_stream *stream, const char *device, UINT32 period_bytes) { + pa_stream_flags_t flags = PA_STREAM_START_CORKED | PA_STREAM_START_UNMUTED | PA_STREAM_ADJUST_LATENCY; int ret; char buffer[64]; static LONG number; @@ -797,12 +906,17 @@ static HRESULT pulse_stream_connect(struct pulse_stream *stream, UINT32 period_b attr.maxlength = stream->bufsize_frames * pa_frame_size(&stream->ss); attr.prebuf = pa_frame_size(&stream->ss); dump_attr(&attr); + + /* If device name is given, use exactly the specified device */ + if (device[0]) + flags |= PA_STREAM_DONT_MOVE; + else + device = NULL; /* use default */ + if (stream->dataflow == eRender) - ret = pa_stream_connect_playback(stream->stream, NULL, &attr, - PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY, NULL, NULL); + ret = pa_stream_connect_playback(stream->stream, device, &attr, flags, NULL, NULL); else - ret = pa_stream_connect_record(stream->stream, NULL, &attr, - PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY); + ret = pa_stream_connect_record(stream->stream, device, &attr, flags); if (ret < 0) { WARN("Returns %i\n", ret); return AUDCLNT_E_ENDPOINT_CREATE_FAILED; @@ -864,7 +978,7 @@ static NTSTATUS pulse_create_stream(void *args)
stream->share = params->mode; stream->flags = params->flags; - hr = pulse_stream_connect(stream, stream->period_bytes); + hr = pulse_stream_connect(stream, params->device, stream->period_bytes); if (SUCCEEDED(hr)) { UINT32 unalign; const pa_buffer_attr *attr = pa_stream_get_buffer_attr(stream->stream); @@ -1967,6 +2081,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_process_attach, pulse_process_detach, pulse_main_loop, + pulse_get_endpoint_ids, pulse_create_stream, pulse_release_stream, pulse_start, diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index d28a73c..c4d274c 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -37,9 +37,19 @@ struct main_loop_params HANDLE event; };
+struct get_endpoint_ids_params +{ + EDataFlow flow; + DWORD size; + WCHAR *devices; + HRESULT result; + unsigned int num; +}; + struct create_stream_params { const char *name; + const char *device; EDataFlow dataflow; AUDCLNT_SHAREMODE mode; DWORD flags; @@ -191,6 +201,7 @@ enum unix_funcs process_attach, process_detach, main_loop, + get_endpoint_ids, create_stream, release_stream, start,
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
Will be useful for next patch since we'll have to look it up on each prop call.
dlls/winepulse.drv/mmdevdrv.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 844c14c..438d42d 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -63,6 +63,14 @@ static struct pulse_config pulse_config;
static HANDLE pulse_thread; static struct list g_sessions = LIST_INIT(g_sessions); +static struct list g_devices_cache = LIST_INIT(g_devices_cache); + +struct device_cache { + struct list entry; + GUID guid; + EDataFlow dataflow; + char device[0]; +};
static GUID pulse_render_guid = { 0xfd47d9cc, 0x4218, 0x4135, { 0x9c, 0xe2, 0x0c, 0x19, 0x5c, 0x87, 0x40, 0x5b } }; @@ -90,6 +98,10 @@ BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved) if (__wine_unix_call(pulse_handle, process_attach, NULL)) return FALSE; } else if (reason == DLL_PROCESS_DETACH) { + struct device_cache *device, *device_next; + + LIST_FOR_EACH_ENTRY_SAFE(device, device_next, &g_devices_cache, struct device_cache, entry) + free(device); __wine_unix_call(pulse_handle, process_detach, NULL); if (pulse_thread) { WaitForSingleObject(pulse_thread, INFINITE); @@ -392,6 +404,7 @@ int WINAPI AUDDRV_GetPriority(void)
static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow *flow) { + struct device_cache *device; DWORD key_name_size; WCHAR key_name[258]; DWORD index = 0; @@ -407,6 +420,15 @@ static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow * return TRUE; }
+ /* Check the cache first */ + LIST_FOR_EACH_ENTRY(device, &g_devices_cache, struct device_cache, entry) { + if (!IsEqualGUID(guid, &device->guid)) + continue; + *flow = device->dataflow; + strcpy(name, device->device); + return TRUE; + } + if (RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ | KEY_WOW64_64KEY, &key) != ERROR_SUCCESS) { WARN("No devices found in registry\n"); return FALSE; @@ -417,6 +439,7 @@ static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow * LSTATUS status; GUID reg_guid; HKEY dev_key; + int len;
key_name_size = ARRAY_SIZE(key_name); if (RegEnumKeyExW(key, index++, key_name, &key_name_size, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) @@ -445,7 +468,16 @@ static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow * return FALSE; }
- return WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, 256, NULL, NULL); + if (!(len = WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, 256, NULL, NULL))) + return FALSE; + + if ((device = malloc(FIELD_OFFSET(struct device_cache, device[len])))) { + device->guid = reg_guid; + device->dataflow = *flow; + strcpy(device->device, name); + list_add_tail(&g_devices_cache, &device->entry); + } + return TRUE; } }
Based on a patch by Mark Harmstone mark@harmstone.com.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/winepulse.drv/mmdevdrv.c | 32 +++++++++- dlls/winepulse.drv/pulse.c | 114 ++++++++++++++++++++++++++++++++-- dlls/winepulse.drv/unixlib.h | 22 +++++++ 3 files changed, 162 insertions(+), 6 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 438d42d..d7434ba 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -2544,6 +2544,10 @@ HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device,
HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARIANT *out) { + struct get_prop_value_params params; + char device[256]; + DWORD size; + TRACE("%s, (%s,%u), %p\n", wine_dbgstr_guid(guid), wine_dbgstr_guid(&prop->fmtid), prop->pid, out);
if (IsEqualGUID(guid, &pulse_render_guid) && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { @@ -2553,5 +2557,31 @@ HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARI return out->ulVal ? S_OK : E_FAIL; }
- return E_NOTIMPL; + if (!get_pulse_name_by_guid(guid, device, ¶ms.flow)) + return E_FAIL; + + params.device = device; + params.guid = guid; + params.prop = prop; + pulse_call(get_prop_value, ¶ms); + + if (params.result != S_OK) + return params.result; + + switch (params.vt) { + case VT_LPWSTR: + size = (wcslen(params.wstr) + 1) * sizeof(WCHAR); + if (!(out->pwszVal = CoTaskMemAlloc(size))) + return E_OUTOFMEMORY; + memcpy(out->pwszVal, params.wstr, size); + break; + case VT_UI4: + out->ulVal = params.ulVal; + break; + default: + assert(0); + } + out->vt = params.vt; + + return S_OK; } diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index d5f8edf..8729cac 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -84,6 +84,10 @@ typedef struct _ACPacket typedef struct _PhysDevice { struct list entry; WCHAR *name; + enum phys_device_bus_type bus_type; + USHORT vendor_id, product_id; + EndpointFormFactor form; + UINT index; char device[0]; } PhysDevice;
@@ -413,7 +417,33 @@ static DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) return mask; }
-static void pulse_add_device(struct list *list, const char *device, const char *name) +static void fill_device_info(PhysDevice *dev, pa_proplist *p) +{ + const char *buffer; + + dev->bus_type = phys_device_bus_invalid; + dev->vendor_id = 0; + dev->product_id = 0; + + if (!p) + return; + + if ((buffer = pa_proplist_gets(p, PA_PROP_DEVICE_BUS))) { + if (!strcmp(buffer, "usb")) + dev->bus_type = phys_device_bus_usb; + else if (!strcmp(buffer, "pci")) + dev->bus_type = phys_device_bus_pci; + } + + if ((buffer = pa_proplist_gets(p, PA_PROP_DEVICE_VENDOR_ID))) + dev->vendor_id = strtol(buffer, NULL, 16); + + if ((buffer = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_ID))) + dev->product_id = strtol(buffer, NULL, 16); +} + +static void pulse_add_device(struct list *list, pa_proplist *proplist, int index, EndpointFormFactor form, + const char *device, const char *name) { DWORD len = strlen(device), name_len = strlen(name); PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, device[len + 1])); @@ -434,6 +464,9 @@ static void pulse_add_device(struct list *list, const char *device, const char * return; } dev->name[name_len] = 0; + dev->form = form; + dev->index = index; + fill_device_info(dev, proplist); memcpy(dev->device, device, len + 1);
list_add_tail(list, &dev->entry); @@ -448,14 +481,15 @@ static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map);
- pulse_add_device(&g_phys_speakers, i->name, i->description); + pulse_add_device(&g_phys_speakers, i->proplist, i->index, Speakers, i->name, i->description); }
static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) { if (!i || !i->name || !i->name[0]) return; - pulse_add_device(&g_phys_sources, i->name, i->description); + pulse_add_device(&g_phys_sources, i->proplist, i->index, + (i->monitor_of_sink == PA_INVALID_INDEX) ? Microphone : LineLevel, i->name, i->description); }
/* For most hardware on Windows, users must choose a configuration with an even @@ -677,8 +711,8 @@ static NTSTATUS pulse_test_connect(void *args) list_init(&g_phys_sources); g_phys_speakers_mask = 0;
- pulse_add_device(&g_phys_speakers, "", "PulseAudio"); - pulse_add_device(&g_phys_sources, "", "PulseAudio"); + pulse_add_device(&g_phys_speakers, NULL, 0, Speakers, "", "PulseAudio"); + pulse_add_device(&g_phys_sources, NULL, 0, Microphone, "", "PulseAudio");
o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { @@ -2076,6 +2110,75 @@ static NTSTATUS pulse_is_started(void *args) return STATUS_SUCCESS; }
+static BOOL get_device_path(PhysDevice *dev, struct get_prop_value_params *params) +{ + const GUID *guid = params->guid; + UINT serial_number; + const char *fmt; + char path[128]; + int len; + + switch (dev->bus_type) { + case phys_device_bus_pci: + fmt = "{1}.HDAUDIO\FUNC_01&VEN_%04X&DEV_%04X\%u&%08X"; + break; + case phys_device_bus_usb: + fmt = "{1}.USB\VID_%04X&PID_%04X\%u&%08X"; + break; + default: + return FALSE; + } + + /* As hardly any audio devices have serial numbers, Windows instead + appears to use a persistent random number. We emulate this here + by instead using the last 8 hex digits of the GUID. */ + serial_number = (guid->Data4[4] << 24) | (guid->Data4[5] << 16) | (guid->Data4[6] << 8) | guid->Data4[7]; + + len = sprintf(path, fmt, dev->vendor_id, dev->product_id, dev->index, serial_number); + ntdll_umbstowcs(path, len + 1, params->wstr, ARRAY_SIZE(params->wstr)); + + params->vt = VT_LPWSTR; + return TRUE; +} + +static NTSTATUS pulse_get_prop_value(void *args) +{ + static const GUID PKEY_AudioEndpoint_GUID = { + 0x1da5d803, 0xd492, 0x4edd, {0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e} + }; + static const PROPERTYKEY devicepath_key = { /* undocumented? - {b3f8fa53-0004-438e-9003-51a46e139bfc},2 */ + {0xb3f8fa53, 0x0004, 0x438e, {0x90, 0x03, 0x51, 0xa4, 0x6e, 0x13, 0x9b, 0xfc}}, 2 + }; + struct get_prop_value_params *params = args; + struct list *list = (params->flow == eRender) ? &g_phys_speakers : &g_phys_sources; + PhysDevice *dev; + + params->result = S_OK; + LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) { + if (strcmp(params->device, dev->device)) + continue; + if (IsEqualPropertyKey(*params->prop, devicepath_key)) { + if (!get_device_path(dev, params)) + break; + return STATUS_SUCCESS; + } else if (IsEqualGUID(¶ms->prop->fmtid, &PKEY_AudioEndpoint_GUID)) { + switch (params->prop->pid) { + case 0: /* FormFactor */ + params->vt = VT_UI4; + params->ulVal = dev->form; + return STATUS_SUCCESS; + default: + break; + } + } + params->result = E_NOTIMPL; + return STATUS_SUCCESS; + } + + params->result = E_FAIL; + return STATUS_SUCCESS; +} + const unixlib_entry_t __wine_unix_call_funcs[] = { pulse_process_attach, @@ -2102,4 +2205,5 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_set_event_handle, pulse_test_connect, pulse_is_started, + pulse_get_prop_value, }; diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index c4d274c..bd09566 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -21,6 +21,12 @@
struct pulse_stream;
+enum phys_device_bus_type { + phys_device_bus_invalid = -1, + phys_device_bus_pci, + phys_device_bus_usb +}; + struct pulse_config { struct @@ -196,6 +202,21 @@ struct is_started_params BOOL started; };
+struct get_prop_value_params +{ + const char *device; + const GUID *guid; + const PROPERTYKEY *prop; + EDataFlow flow; + HRESULT result; + VARTYPE vt; + union + { + WCHAR wstr[128]; + ULONG ulVal; + }; +}; + enum unix_funcs { process_attach, @@ -222,4 +243,5 @@ enum unix_funcs set_event_handle, test_connect, is_started, + get_prop_value, };
From: Mark Harmstone mark@harmstone.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/winepulse.drv/Makefile.in | 2 +- dlls/winepulse.drv/mmdevdrv.c | 106 +++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 5 deletions(-)
diff --git a/dlls/winepulse.drv/Makefile.in b/dlls/winepulse.drv/Makefile.in index c71b283..d4b40e6 100644 --- a/dlls/winepulse.drv/Makefile.in +++ b/dlls/winepulse.drv/Makefile.in @@ -1,7 +1,7 @@ EXTRADEFS = -DWINE_NO_LONG_TYPES MODULE = winepulse.drv UNIXLIB = winepulse.so -IMPORTS = dxguid uuid winmm user32 advapi32 ole32 +IMPORTS = dxguid uuid winmm user32 advapi32 ole32 version EXTRALIBS = $(PULSE_LIBS) $(PTHREAD_LIBS) -lm EXTRAINCL = $(PULSE_CFLAGS)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index d7434ba..88924d1 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -30,6 +30,7 @@ #include "wine/list.h"
#include "ole2.h" +#include "mimeole.h" #include "dshow.h" #include "dsound.h" #include "propsys.h" @@ -242,13 +243,110 @@ static DWORD CALLBACK pulse_mainloop_thread(void *event) return 0; }
-static char *get_application_name(void) +typedef struct tagLANGANDCODEPAGE +{ + WORD wLanguage; + WORD wCodePage; +} LANGANDCODEPAGE; + +static BOOL query_productname(void *data, LANGANDCODEPAGE *lang, LPVOID *buffer, DWORD *len) +{ + WCHAR pn[37]; + swprintf(pn, ARRAY_SIZE(pn), L"\StringFileInfo\%04x%04x\ProductName", lang->wLanguage, lang->wCodePage); + return VerQueryValueW(data, pn, buffer, len) && *len; +} + +static char *get_application_name(BOOL query_app_name) { WCHAR path[MAX_PATH], *name; + char *str = NULL; size_t len; - char *str;
GetModuleFileNameW(NULL, path, ARRAY_SIZE(path)); + + if (query_app_name) + { + UINT translate_size, productname_size; + LANGANDCODEPAGE *translate; + LPVOID productname; + BOOL found = FALSE; + void *data = NULL; + unsigned int i; + LCID locale; + DWORD size; + + size = GetFileVersionInfoSizeW(path, NULL); + if (!size) + goto skip; + + data = malloc(size); + if (!data) + goto skip; + + if (!GetFileVersionInfoW(path, 0, size, data)) + goto skip; + + if (!VerQueryValueW(data, L"\VarFileInfo\Translation", (LPVOID *)&translate, &translate_size)) + goto skip; + + /* no translations found */ + if (translate_size < sizeof(LANGANDCODEPAGE)) + goto skip; + + /* The following code will try to find the best translation. We first search for an + * exact match of the language, then a match of the language PRIMARYLANGID, then we + * search for a LANG_NEUTRAL match, and if that still doesn't work we pick the + * first entry which contains a proper productname. */ + locale = GetThreadLocale(); + + for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { + if (translate[i].wLanguage == locale && + query_productname(data, &translate[i], &productname, &productname_size)) { + found = TRUE; + break; + } + } + + if (!found) { + for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { + if (PRIMARYLANGID(translate[i].wLanguage) == PRIMARYLANGID(locale) && + query_productname(data, &translate[i], &productname, &productname_size)) { + found = TRUE; + break; + } + } + } + + if (!found) { + for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { + if (PRIMARYLANGID(translate[i].wLanguage) == LANG_NEUTRAL && + query_productname(data, &translate[i], &productname, &productname_size)) { + found = TRUE; + break; + } + } + } + + if (!found) { + for (i = 0; i < translate_size / sizeof(LANGANDCODEPAGE); i++) { + if (query_productname(data, &translate[i], &productname, &productname_size)) { + found = TRUE; + break; + } + } + } + + if (found) { + len = WideCharToMultiByte(CP_UTF8, 0, productname, -1, NULL, 0, NULL, NULL); + str = malloc(len); + if (str) WideCharToMultiByte(CP_UTF8, 0, productname, -1, str, len, NULL, NULL); + } + + skip: + free(data); + if (str) return str; + } + name = wcsrchr(path, '\'); if (!name) name = path; @@ -395,7 +493,7 @@ int WINAPI AUDDRV_GetPriority(void) struct test_connect_params params; char *name;
- params.name = name = get_application_name(); + params.name = name = get_application_name(FALSE); params.config = &pulse_config; pulse_call(test_connect, ¶ms); free(name); @@ -777,7 +875,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, CloseHandle(event); }
- params.name = name = get_application_name(); + params.name = name = get_application_name(TRUE); params.device = This->device; params.dataflow = This->dataflow; params.mode = mode;
On Thu, Feb 10, 2022 at 06:07:47PM +0200, Gabriel Ivăncescu wrote:
@@ -395,7 +493,7 @@ int WINAPI AUDDRV_GetPriority(void) struct test_connect_params params; char *name;
- params.name = name = get_application_name();
- params.name = name = get_application_name(FALSE);
Do you know why we want FALSE here?
Andrew
On 15/02/2022 17:20, Andrew Eikum wrote:
On Thu, Feb 10, 2022 at 06:07:47PM +0200, Gabriel Ivăncescu wrote:
@@ -395,7 +493,7 @@ int WINAPI AUDDRV_GetPriority(void) struct test_connect_params params; char *name;
- params.name = name = get_application_name();
- params.name = name = get_application_name(FALSE);
Do you know why we want FALSE here?
Andrew
I didn't want to change existing behavior (since it's only used for querying). The comment says some broken apps call it during DllMain. Are those APIs (when querying app name) safe to call from it?
On Tue, Feb 15, 2022 at 08:51:01PM +0200, Gabriel Ivăncescu wrote:
On 15/02/2022 17:20, Andrew Eikum wrote:
On Thu, Feb 10, 2022 at 06:07:47PM +0200, Gabriel Ivăncescu wrote:
@@ -395,7 +493,7 @@ int WINAPI AUDDRV_GetPriority(void) struct test_connect_params params; char *name;
- params.name = name = get_application_name();
- params.name = name = get_application_name(FALSE);
Do you know why we want FALSE here?
I didn't want to change existing behavior (since it's only used for querying). The comment says some broken apps call it during DllMain. Are those APIs (when querying app name) safe to call from it?
Oh, that makes sense. It's fine like this then, thanks.
Andrew
From: Mark Harmstone mark@harmstone.com
And get rid of config->speakers_mask and g_phys_speakers_mask since they are no longer needed.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/winepulse.drv/mmdevdrv.c | 7 ------- dlls/winepulse.drv/pulse.c | 30 +++++++++++++++++++++--------- dlls/winepulse.drv/unixlib.h | 1 - 3 files changed, 21 insertions(+), 17 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 88924d1..1ec67a5 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -2648,13 +2648,6 @@ HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARI
TRACE("%s, (%s,%u), %p\n", wine_dbgstr_guid(guid), wine_dbgstr_guid(&prop->fmtid), prop->pid, out);
- if (IsEqualGUID(guid, &pulse_render_guid) && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { - out->vt = VT_UI4; - out->ulVal = pulse_config.speakers_mask; - - return out->ulVal ? S_OK : E_FAIL; - } - if (!get_pulse_name_by_guid(guid, device, ¶ms.flow)) return E_FAIL;
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 8729cac..a71541c 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -87,6 +87,7 @@ typedef struct _PhysDevice { enum phys_device_bus_type bus_type; USHORT vendor_id, product_id; EndpointFormFactor form; + DWORD channel_mask; UINT index; char device[0]; } PhysDevice; @@ -98,7 +99,6 @@ static pa_mainloop *pulse_ml; static WAVEFORMATEXTENSIBLE pulse_fmt[2]; static REFERENCE_TIME pulse_min_period[2], pulse_def_period[2];
-static UINT g_phys_speakers_mask = 0; static struct list g_phys_speakers = LIST_INIT(g_phys_speakers); static struct list g_phys_sources = LIST_INIT(g_phys_sources);
@@ -443,7 +443,7 @@ static void fill_device_info(PhysDevice *dev, pa_proplist *p) }
static void pulse_add_device(struct list *list, pa_proplist *proplist, int index, EndpointFormFactor form, - const char *device, const char *name) + DWORD channel_mask, const char *device, const char *name) { DWORD len = strlen(device), name_len = strlen(name); PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, device[len + 1])); @@ -466,6 +466,7 @@ static void pulse_add_device(struct list *list, pa_proplist *proplist, int index dev->name[name_len] = 0; dev->form = form; dev->index = index; + dev->channel_mask = channel_mask; fill_device_info(dev, proplist); memcpy(dev->device, device, len + 1);
@@ -474,14 +475,20 @@ static void pulse_add_device(struct list *list, pa_proplist *proplist, int index
static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) { + struct list *speaker; + DWORD channel_mask; + if (!i || !i->name || !i->name[0]) return; + channel_mask = pulse_channel_map_to_channel_mask(&i->channel_map);
/* For default PulseAudio render device, OR together all of the * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ - g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map); + speaker = list_head(&g_phys_speakers); + if (speaker) + LIST_ENTRY(speaker, PhysDevice, entry)->channel_mask |= channel_mask;
- pulse_add_device(&g_phys_speakers, i->proplist, i->index, Speakers, i->name, i->description); + pulse_add_device(&g_phys_speakers, i->proplist, i->index, Speakers, channel_mask, i->name, i->description); }
static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) @@ -489,7 +496,7 @@ static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eo if (!i || !i->name || !i->name[0]) return; pulse_add_device(&g_phys_sources, i->proplist, i->index, - (i->monitor_of_sink == PA_INVALID_INDEX) ? Microphone : LineLevel, i->name, i->description); + (i->monitor_of_sink == PA_INVALID_INDEX) ? Microphone : LineLevel, 0, i->name, i->description); }
/* For most hardware on Windows, users must choose a configuration with an even @@ -709,10 +716,9 @@ static NTSTATUS pulse_test_connect(void *args) free_phys_device_lists(); list_init(&g_phys_speakers); list_init(&g_phys_sources); - g_phys_speakers_mask = 0;
- pulse_add_device(&g_phys_speakers, NULL, 0, Speakers, "", "PulseAudio"); - pulse_add_device(&g_phys_sources, NULL, 0, Microphone, "", "PulseAudio"); + pulse_add_device(&g_phys_speakers, NULL, 0, Speakers, 0, "", "PulseAudio"); + pulse_add_device(&g_phys_sources, NULL, 0, Microphone, 0, "", "PulseAudio");
o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { @@ -735,7 +741,6 @@ static NTSTATUS pulse_test_connect(void *args) pa_mainloop_free(pulse_ml); pulse_ml = NULL;
- config->speakers_mask = g_phys_speakers_mask; config->modes[0].format = pulse_fmt[0]; config->modes[0].def_period = pulse_def_period[0]; config->modes[0].min_period = pulse_min_period[0]; @@ -2167,6 +2172,12 @@ static NTSTATUS pulse_get_prop_value(void *args) params->vt = VT_UI4; params->ulVal = dev->form; return STATUS_SUCCESS; + case 3: /* PhysicalSpeakers */ + if (!dev->channel_mask) + goto fail; + params->vt = VT_UI4; + params->ulVal = dev->channel_mask; + return STATUS_SUCCESS; default: break; } @@ -2175,6 +2186,7 @@ static NTSTATUS pulse_get_prop_value(void *args) return STATUS_SUCCESS; }
+fail: params->result = E_FAIL; return STATUS_SUCCESS; } diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index bd09566..ba5422b 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -35,7 +35,6 @@ struct pulse_config REFERENCE_TIME def_period; REFERENCE_TIME min_period; } modes[2]; - unsigned int speakers_mask; };
struct main_loop_params
On Thu, Feb 10, 2022 at 06:07:44PM +0200, Gabriel Ivăncescu wrote:
This exposes the actual devices (and virtual sinks/sources) as reported by PulseAudio to an application, allowing it to select the devices itself and, for example, record from (or render to) two devices at the same time. The "PulseAudio" device (which is movable) is still the default, as before, with the same GUID to preserve compatibility with existing setups.
Based on a patch by Mark Harmstone mark@harmstone.com, with changes by Sebastian Lackner sebastian@fds-team.de.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
This patchset has lived in wine-staging for a long time, but has been recently rebased to deal with unixlib separation.
dlls/winepulse.drv/mmdevdrv.c | 210 ++++++++++++++++++++++++++++------ dlls/winepulse.drv/pulse.c | 135 ++++++++++++++++++++-- dlls/winepulse.drv/unixlib.h | 11 ++ 3 files changed, 310 insertions(+), 46 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 35a66e1..844c14c 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -69,6 +69,8 @@ static GUID pulse_render_guid = static GUID pulse_capture_guid = { 0x25da76d0, 0x033c, 0x4235, { 0x90, 0x02, 0x19, 0xf4, 0x88, 0x94, 0xac, 0x6f } };
+static const WCHAR *drv_key_devicesW = L"Software\Wine\Drivers\winepulse.drv\devices";
static CRITICAL_SECTION session_cs; static CRITICAL_SECTION_DEBUG session_cs_debug = { 0, 0, &session_cs, @@ -134,6 +136,7 @@ struct ACImpl { IUnknown *marshal; IMMDevice *parent; struct list entry;
- char device[256];
These magic numbers could be replaced by a symbol. I noticed a bunch in this patch (256 and 256+2), and one more in the GetPropValue patch.
float *vol; LONG ref;
@@ -147,8 +150,6 @@ struct ACImpl { AudioSessionWrapper *session_wrapper; };
-static const WCHAR defaultW[] = L"PulseAudio";
static const IAudioClient3Vtbl AudioClient3_Vtbl; static const IAudioRenderClientVtbl AudioRenderClient_Vtbl; static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl; @@ -267,39 +268,114 @@ static void set_stream_volumes(ACImpl *This) pulse_call(set_volumes, ¶ms); }
-HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys,
UINT *num, UINT *def_index)
+static void get_device_guid(HKEY drv_key, EDataFlow flow, const WCHAR *device, GUID *guid) {
- WCHAR *id;
- DWORD type, size = sizeof(*guid);
- WCHAR key_name[258];
- LSTATUS status;
- HKEY dev_key;
- TRACE("%d %p %p %p\n", flow, ids, num, def_index);
- if (!device[0]) {
*guid = (flow == eRender) ? pulse_render_guid : pulse_capture_guid;
return;
- }
- *num = 1;
- *def_index = 0;
- if (!drv_key) {
CoCreateGuid(guid);
return;
- }
- *ids = HeapAlloc(GetProcessHeap(), 0, sizeof(**ids));
- *keys = NULL;
- if (!*ids)
return E_OUTOFMEMORY;
- key_name[0] = (flow == eRender) ? '0' : '1';
- key_name[1] = ',';
- wcscpy(key_name + 2, device);
- (*ids)[0] = id = HeapAlloc(GetProcessHeap(), 0, sizeof(defaultW));
- *keys = HeapAlloc(GetProcessHeap(), 0, sizeof(**keys));
- if (!*keys || !id) {
HeapFree(GetProcessHeap(), 0, id);
HeapFree(GetProcessHeap(), 0, *keys);
HeapFree(GetProcessHeap(), 0, *ids);
*ids = NULL;
*keys = NULL;
return E_OUTOFMEMORY;
- status = RegCreateKeyExW(drv_key, key_name, 0, NULL, 0, KEY_READ | KEY_WRITE | KEY_WOW64_64KEY,
NULL, &dev_key, NULL);
- if (status != ERROR_SUCCESS) {
ERR("Failed to open registry key for device %s: %u\n", debugstr_w(device), status);
CoCreateGuid(guid);
}return;
memcpy(id, defaultW, sizeof(defaultW));
if (flow == eRender)
(*keys)[0] = pulse_render_guid;
else
(*keys)[0] = pulse_capture_guid;
- status = RegQueryValueExW(dev_key, L"guid", 0, &type, (BYTE*)guid, &size);
- if (status != ERROR_SUCCESS || type != REG_BINARY || size != sizeof(*guid)) {
CoCreateGuid(guid);
status = RegSetValueExW(dev_key, L"guid", 0, REG_BINARY, (BYTE*)guid, sizeof(*guid));
if (status != ERROR_SUCCESS)
ERR("Failed to store device GUID for %s to registry: %u\n", debugstr_w(device), status);
- }
- RegCloseKey(dev_key);
+}
- return S_OK;
+HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids_out, GUID **keys,
UINT *num, UINT *def_index)
+{
- struct get_endpoint_ids_params params;
- GUID *guids = NULL;
- WCHAR **ids = NULL;
- unsigned int i = 0;
- LSTATUS status;
- DWORD name_len;
- HKEY drv_key;
- WCHAR *p;
- TRACE("%d %p %p %p\n", flow, ids_out, num, def_index);
- params.flow = flow;
- params.size = 1024;
Another magic number here. Maybe instead, max_name_len * 4?
- params.devices = NULL;
- do {
HeapFree(GetProcessHeap(), 0, params.devices);
params.devices = HeapAlloc(GetProcessHeap(), 0, params.size);
pulse_call(get_endpoint_ids, ¶ms);
- } while(params.result == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
- if (FAILED(params.result))
goto end;
- ids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*ids));
- guids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*guids));
- if (!ids || !guids) {
params.result = E_OUTOFMEMORY;
goto end;
- }
- status = RegCreateKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, NULL, 0,
KEY_WRITE | KEY_WOW64_64KEY, NULL, &drv_key, NULL);
- if (status != ERROR_SUCCESS) {
ERR("Failed to open devices registry key: %u\n", status);
drv_key = NULL;
- }
- p = params.devices;
- for (i = 0; i < params.num; i++) {
get_device_guid(drv_key, flow, p, &guids[i]);
p += wcslen(p) + 1;
name_len = wcslen(p) + 1;
if (!(ids[i] = HeapAlloc(GetProcessHeap(), 0, name_len * sizeof(WCHAR)))) {
params.result = E_OUTOFMEMORY;
break;
}
memcpy(ids[i], p, name_len * sizeof(WCHAR));
p += name_len;
- }
- if (drv_key)
RegCloseKey(drv_key);
+end:
- HeapFree(GetProcessHeap(), 0, params.devices);
- if (FAILED(params.result)) {
HeapFree(GetProcessHeap(), 0, guids);
while (i--) HeapFree(GetProcessHeap(), 0, ids[i]);
HeapFree(GetProcessHeap(), 0, ids);
- } else {
*ids_out = ids;
*keys = guids;
*num = params.num;
*def_index = 0;
- }
- return params.result;
}
int WINAPI AUDDRV_GetPriority(void) @@ -314,26 +390,87 @@ int WINAPI AUDDRV_GetPriority(void) return SUCCEEDED(params.result) ? Priority_Preferred : Priority_Unavailable; }
+static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow *flow) +{
- DWORD key_name_size;
- WCHAR key_name[258];
- DWORD index = 0;
- HKEY key;
- /* Return empty string for default PulseAudio device */
- name[0] = 0;
- if (IsEqualGUID(guid, &pulse_render_guid)) {
*flow = eRender;
return TRUE;
- } else if (IsEqualGUID(guid, &pulse_capture_guid)) {
*flow = eCapture;
return TRUE;
- }
- if (RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ | KEY_WOW64_64KEY, &key) != ERROR_SUCCESS) {
WARN("No devices found in registry\n");
return FALSE;
- }
- for (;;) {
DWORD size, type;
LSTATUS status;
GUID reg_guid;
HKEY dev_key;
key_name_size = ARRAY_SIZE(key_name);
if (RegEnumKeyExW(key, index++, key_name, &key_name_size, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
break;
if (RegOpenKeyExW(key, key_name, 0, KEY_READ | KEY_WOW64_64KEY, &dev_key) != ERROR_SUCCESS) {
ERR("Couldn't open key: %s\n", wine_dbgstr_w(key_name));
continue;
}
size = sizeof(reg_guid);
status = RegQueryValueExW(dev_key, L"guid", 0, &type, (BYTE *)®_guid, &size);
RegCloseKey(dev_key);
if (status == ERROR_SUCCESS && type == REG_BINARY && size == sizeof(reg_guid) && IsEqualGUID(®_guid, guid)) {
RegCloseKey(key);
TRACE("Found matching device key: %s\n", wine_dbgstr_w(key_name));
if (key_name[0] == '0')
*flow = eRender;
else if (key_name[0] == '1')
*flow = eCapture;
else {
WARN("Unknown device type: %c\n", key_name[0]);
return FALSE;
}
return WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, 256, NULL, NULL);
}
- }
- RegCloseKey(key);
- WARN("No matching device in registry for GUID %s\n", debugstr_guid(guid));
- return FALSE;
+}
HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) {
- ACImpl *This;
ACImpl *This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); EDataFlow dataflow; HRESULT hr;
TRACE("%s %p %p\n", debugstr_guid(guid), dev, out);
if (IsEqualGUID(guid, &pulse_render_guid))
dataflow = eRender;
else if (IsEqualGUID(guid, &pulse_capture_guid))
dataflow = eCapture;
else
return E_UNEXPECTED;
*out = NULL;
This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); if (!This) return E_OUTOFMEMORY;
- if (!get_pulse_name_by_guid(guid, This->device, &dataflow)) {
HeapFree(GetProcessHeap(), 0, This);
return AUDCLNT_E_DEVICE_INVALIDATED;
- }
- This->IAudioClient3_iface.lpVtbl = &AudioClient3_Vtbl; This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl; This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl;
@@ -609,6 +746,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, }
params.name = name = get_application_name();
- params.device = This->device; params.dataflow = This->dataflow; params.mode = mode; params.flags = flags;
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 3e65936..d5f8edf 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -81,6 +81,12 @@ typedef struct _ACPacket UINT32 discont; } ACPacket;
+typedef struct _PhysDevice {
- struct list entry;
- WCHAR *name;
- char device[0];
+} PhysDevice;
static pa_context *pulse_ctx; static pa_mainloop *pulse_ml;
@@ -89,6 +95,8 @@ static WAVEFORMATEXTENSIBLE pulse_fmt[2]; static REFERENCE_TIME pulse_min_period[2], pulse_def_period[2];
static UINT g_phys_speakers_mask = 0; +static struct list g_phys_speakers = LIST_INIT(g_phys_speakers); +static struct list g_phys_sources = LIST_INIT(g_phys_sources);
static const REFERENCE_TIME MinimumPeriod = 30000; static const REFERENCE_TIME DefaultPeriod = 100000; @@ -128,6 +136,20 @@ static void dump_attr(const pa_buffer_attr *attr) TRACE("prebuf: %u\n", attr->prebuf); }
+static void free_phys_device_lists(void) +{
- static struct list *const lists[] = { &g_phys_speakers, &g_phys_sources, NULL };
- struct list *const *list = lists;
- PhysDevice *dev, *dev_next;
- do {
LIST_FOR_EACH_ENTRY_SAFE(dev, dev_next, *list, PhysDevice, entry) {
free(dev->name);
free(dev);
}
- } while (*(++list));
+}
/* copied from kernelbase */ static int muldiv(int a, int b, int c) { @@ -190,6 +212,7 @@ static NTSTATUS pulse_process_attach(void *args)
static NTSTATUS pulse_process_detach(void *args) {
- free_phys_device_lists(); if (pulse_ctx) { pa_context_disconnect(pulse_ctx);
@@ -215,6 +238,39 @@ static NTSTATUS pulse_main_loop(void *args) return STATUS_SUCCESS; }
+static NTSTATUS pulse_get_endpoint_ids(void *args) +{
- struct get_endpoint_ids_params *params = args;
- struct list *list = (params->flow == eRender) ? &g_phys_speakers : &g_phys_sources;
- DWORD len, name_len, needed = 0;
- WCHAR *p = params->devices;
- WCHAR device[256];
- PhysDevice *dev;
- params->num = 0;
- LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) {
if (!(len = ntdll_umbstowcs(dev->device, strlen(dev->device) + 1, device, ARRAY_SIZE(device))))
continue;
name_len = lstrlenW(dev->name) + 1;
needed += (len + name_len) * sizeof(WCHAR);
if (needed <= params->size) {
memcpy(p, device, len * sizeof(WCHAR));
p += len;
memcpy(p, dev->name, name_len * sizeof(WCHAR));
p += name_len;
}
params->num++;
- }
I find this API difficult to understand. At least a comment describing what 'num' and 'devices' will contain on output would be nice.
If I understand it right, 'devices' will contain 2x'num' null-terminated strings, right? Another way to represent that is with a single array of null-terminated strings, with the end-of-array sentinel being a zero-length string. That eliminates one of the param fields, and simplifies the array creation and also the iteration side.
You could also split the 'device' and 'name' strings each into their own arrays, to make the code easier to read.
- if (needed > params->size) {
params->size = needed;
params->result = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
- } else
params->result = S_OK;
- return STATUS_SUCCESS;
+}
static void pulse_contextcallback(pa_context *c, void *userdata) { switch (pa_context_get_state(c)) { @@ -357,12 +413,49 @@ static DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) return mask; }
-/* For default PulseAudio render device, OR together all of the
- PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */
+static void pulse_add_device(struct list *list, const char *device, const char *name) ...
- pulse_add_device(&g_phys_speakers, i->name, i->description);
The usage of "device" and "name" is very confusing in this patch. E.g. note the 'device' parameter is called with a 'name' argument here, and the 'name' param is called with 'description'.
Later, in AUDDRV_GetEndpointIDs, we're handling ids and devices and names. It's pretty mixed up.
Could you use more descriptive terms, more consistently across the entire codebase? E.g. you could use 'id' for an internal representation, and 'friendly' for something user-visible?
Andrew
+{
- DWORD len = strlen(device), name_len = strlen(name);
- PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, device[len + 1]));
- WCHAR *wname;
- if (!dev)
return;
- if (!(wname = malloc((name_len + 1) * sizeof(WCHAR)))) {
free(dev);
return;
- }
- if (!(name_len = ntdll_umbstowcs(name, name_len, wname, name_len)) ||
!(dev->name = realloc(wname, (name_len + 1) * sizeof(WCHAR)))) {
free(wname);
free(dev);
return;
- }
- dev->name[name_len] = 0;
- memcpy(dev->device, device, len + 1);
- list_add_tail(list, &dev->entry);
+}
static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
- if (i)
g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map);
- if (!i || !i->name || !i->name[0])
return;
- /* For default PulseAudio render device, OR together all of the
* PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */
- g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map);
+}
+static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) +{
- if (!i || !i->name || !i->name[0])
return;
- pulse_add_device(&g_phys_sources, i->name, i->description);
}
/* For most hardware on Windows, users must choose a configuration with an even @@ -579,7 +672,14 @@ static NTSTATUS pulse_test_connect(void *args) pulse_probe_settings(1, &pulse_fmt[0]); pulse_probe_settings(0, &pulse_fmt[1]);
- free_phys_device_lists();
- list_init(&g_phys_speakers);
- list_init(&g_phys_sources); g_phys_speakers_mask = 0;
- pulse_add_device(&g_phys_speakers, "", "PulseAudio");
- pulse_add_device(&g_phys_sources, "", "PulseAudio");
- o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 &&
@@ -588,6 +688,14 @@ static NTSTATUS pulse_test_connect(void *args) pa_operation_unref(o); }
- o = pa_context_get_source_info_list(pulse_ctx, &pulse_phys_sources_cb, NULL);
- if (o) {
while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 &&
pa_operation_get_state(o) == PA_OPERATION_RUNNING)
{}
pa_operation_unref(o);
- }
- pa_context_unref(pulse_ctx); pulse_ctx = NULL; pa_mainloop_free(pulse_ml);
@@ -771,8 +879,9 @@ static HRESULT pulse_spec_from_waveformat(struct pulse_stream *stream, const WAV return S_OK; }
-static HRESULT pulse_stream_connect(struct pulse_stream *stream, UINT32 period_bytes) +static HRESULT pulse_stream_connect(struct pulse_stream *stream, const char *device, UINT32 period_bytes) {
- pa_stream_flags_t flags = PA_STREAM_START_CORKED | PA_STREAM_START_UNMUTED | PA_STREAM_ADJUST_LATENCY; int ret; char buffer[64]; static LONG number;
@@ -797,12 +906,17 @@ static HRESULT pulse_stream_connect(struct pulse_stream *stream, UINT32 period_b attr.maxlength = stream->bufsize_frames * pa_frame_size(&stream->ss); attr.prebuf = pa_frame_size(&stream->ss); dump_attr(&attr);
- /* If device name is given, use exactly the specified device */
- if (device[0])
flags |= PA_STREAM_DONT_MOVE;
- else
device = NULL; /* use default */
- if (stream->dataflow == eRender)
ret = pa_stream_connect_playback(stream->stream, NULL, &attr,
PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY, NULL, NULL);
elseret = pa_stream_connect_playback(stream->stream, device, &attr, flags, NULL, NULL);
ret = pa_stream_connect_record(stream->stream, NULL, &attr,
PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY);
if (ret < 0) { WARN("Returns %i\n", ret); return AUDCLNT_E_ENDPOINT_CREATE_FAILED;ret = pa_stream_connect_record(stream->stream, device, &attr, flags);
@@ -864,7 +978,7 @@ static NTSTATUS pulse_create_stream(void *args)
stream->share = params->mode; stream->flags = params->flags;
- hr = pulse_stream_connect(stream, stream->period_bytes);
- hr = pulse_stream_connect(stream, params->device, stream->period_bytes); if (SUCCEEDED(hr)) { UINT32 unalign; const pa_buffer_attr *attr = pa_stream_get_buffer_attr(stream->stream);
@@ -1967,6 +2081,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_process_attach, pulse_process_detach, pulse_main_loop,
- pulse_get_endpoint_ids, pulse_create_stream, pulse_release_stream, pulse_start,
diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index d28a73c..c4d274c 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -37,9 +37,19 @@ struct main_loop_params HANDLE event; };
+struct get_endpoint_ids_params +{
- EDataFlow flow;
- DWORD size;
- WCHAR *devices;
- HRESULT result;
- unsigned int num;
+};
struct create_stream_params { const char *name;
- const char *device; EDataFlow dataflow; AUDCLNT_SHAREMODE mode; DWORD flags;
@@ -191,6 +201,7 @@ enum unix_funcs process_attach, process_detach, main_loop,
- get_endpoint_ids, create_stream, release_stream, start,
-- 2.34.1
On 15/02/2022 17:16, Andrew Eikum wrote:
On Thu, Feb 10, 2022 at 06:07:44PM +0200, Gabriel Ivăncescu wrote:
This exposes the actual devices (and virtual sinks/sources) as reported by PulseAudio to an application, allowing it to select the devices itself and, for example, record from (or render to) two devices at the same time. The "PulseAudio" device (which is movable) is still the default, as before, with the same GUID to preserve compatibility with existing setups.
Based on a patch by Mark Harmstone mark@harmstone.com, with changes by Sebastian Lackner sebastian@fds-team.de.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
This patchset has lived in wine-staging for a long time, but has been recently rebased to deal with unixlib separation.
dlls/winepulse.drv/mmdevdrv.c | 210 ++++++++++++++++++++++++++++------ dlls/winepulse.drv/pulse.c | 135 ++++++++++++++++++++-- dlls/winepulse.drv/unixlib.h | 11 ++ 3 files changed, 310 insertions(+), 46 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 35a66e1..844c14c 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -69,6 +69,8 @@ static GUID pulse_render_guid = static GUID pulse_capture_guid = { 0x25da76d0, 0x033c, 0x4235, { 0x90, 0x02, 0x19, 0xf4, 0x88, 0x94, 0xac, 0x6f } };
+static const WCHAR *drv_key_devicesW = L"Software\Wine\Drivers\winepulse.drv\devices";
- static CRITICAL_SECTION session_cs; static CRITICAL_SECTION_DEBUG session_cs_debug = { 0, 0, &session_cs,
@@ -134,6 +136,7 @@ struct ACImpl { IUnknown *marshal; IMMDevice *parent; struct list entry;
- char device[256];
These magic numbers could be replaced by a symbol. I noticed a bunch in this patch (256 and 256+2), and one more in the GetPropValue patch.
Oh, since the original patch was using 256 for the device name everywhere, I was under the impression it was some max value in PA. But yeah it sounds good to use a symbol.
float *vol; LONG ref;
@@ -147,8 +150,6 @@ struct ACImpl { AudioSessionWrapper *session_wrapper; };
-static const WCHAR defaultW[] = L"PulseAudio";
- static const IAudioClient3Vtbl AudioClient3_Vtbl; static const IAudioRenderClientVtbl AudioRenderClient_Vtbl; static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl;
@@ -267,39 +268,114 @@ static void set_stream_volumes(ACImpl *This) pulse_call(set_volumes, ¶ms); }
-HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys,
UINT *num, UINT *def_index)
+static void get_device_guid(HKEY drv_key, EDataFlow flow, const WCHAR *device, GUID *guid) {
- WCHAR *id;
- DWORD type, size = sizeof(*guid);
- WCHAR key_name[258];
- LSTATUS status;
- HKEY dev_key;
- TRACE("%d %p %p %p\n", flow, ids, num, def_index);
- if (!device[0]) {
*guid = (flow == eRender) ? pulse_render_guid : pulse_capture_guid;
return;
- }
- *num = 1;
- *def_index = 0;
- if (!drv_key) {
CoCreateGuid(guid);
return;
- }
- *ids = HeapAlloc(GetProcessHeap(), 0, sizeof(**ids));
- *keys = NULL;
- if (!*ids)
return E_OUTOFMEMORY;
- key_name[0] = (flow == eRender) ? '0' : '1';
- key_name[1] = ',';
- wcscpy(key_name + 2, device);
- (*ids)[0] = id = HeapAlloc(GetProcessHeap(), 0, sizeof(defaultW));
- *keys = HeapAlloc(GetProcessHeap(), 0, sizeof(**keys));
- if (!*keys || !id) {
HeapFree(GetProcessHeap(), 0, id);
HeapFree(GetProcessHeap(), 0, *keys);
HeapFree(GetProcessHeap(), 0, *ids);
*ids = NULL;
*keys = NULL;
return E_OUTOFMEMORY;
- status = RegCreateKeyExW(drv_key, key_name, 0, NULL, 0, KEY_READ | KEY_WRITE | KEY_WOW64_64KEY,
NULL, &dev_key, NULL);
- if (status != ERROR_SUCCESS) {
ERR("Failed to open registry key for device %s: %u\n", debugstr_w(device), status);
CoCreateGuid(guid);
return; }
memcpy(id, defaultW, sizeof(defaultW));
if (flow == eRender)
(*keys)[0] = pulse_render_guid;
else
(*keys)[0] = pulse_capture_guid;
- status = RegQueryValueExW(dev_key, L"guid", 0, &type, (BYTE*)guid, &size);
- if (status != ERROR_SUCCESS || type != REG_BINARY || size != sizeof(*guid)) {
CoCreateGuid(guid);
status = RegSetValueExW(dev_key, L"guid", 0, REG_BINARY, (BYTE*)guid, sizeof(*guid));
if (status != ERROR_SUCCESS)
ERR("Failed to store device GUID for %s to registry: %u\n", debugstr_w(device), status);
- }
- RegCloseKey(dev_key);
+}
- return S_OK;
+HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids_out, GUID **keys,
UINT *num, UINT *def_index)
+{
- struct get_endpoint_ids_params params;
- GUID *guids = NULL;
- WCHAR **ids = NULL;
- unsigned int i = 0;
- LSTATUS status;
- DWORD name_len;
- HKEY drv_key;
- WCHAR *p;
- TRACE("%d %p %p %p\n", flow, ids_out, num, def_index);
- params.flow = flow;
- params.size = 1024;
Another magic number here. Maybe instead, max_name_len * 4?
- params.devices = NULL;
- do {
HeapFree(GetProcessHeap(), 0, params.devices);
params.devices = HeapAlloc(GetProcessHeap(), 0, params.size);
pulse_call(get_endpoint_ids, ¶ms);
- } while(params.result == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER));
- if (FAILED(params.result))
goto end;
- ids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*ids));
- guids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*guids));
- if (!ids || !guids) {
params.result = E_OUTOFMEMORY;
goto end;
- }
- status = RegCreateKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, NULL, 0,
KEY_WRITE | KEY_WOW64_64KEY, NULL, &drv_key, NULL);
- if (status != ERROR_SUCCESS) {
ERR("Failed to open devices registry key: %u\n", status);
drv_key = NULL;
- }
- p = params.devices;
- for (i = 0; i < params.num; i++) {
get_device_guid(drv_key, flow, p, &guids[i]);
p += wcslen(p) + 1;
name_len = wcslen(p) + 1;
if (!(ids[i] = HeapAlloc(GetProcessHeap(), 0, name_len * sizeof(WCHAR)))) {
params.result = E_OUTOFMEMORY;
break;
}
memcpy(ids[i], p, name_len * sizeof(WCHAR));
p += name_len;
- }
- if (drv_key)
RegCloseKey(drv_key);
+end:
HeapFree(GetProcessHeap(), 0, params.devices);
if (FAILED(params.result)) {
HeapFree(GetProcessHeap(), 0, guids);
while (i--) HeapFree(GetProcessHeap(), 0, ids[i]);
HeapFree(GetProcessHeap(), 0, ids);
} else {
*ids_out = ids;
*keys = guids;
*num = params.num;
*def_index = 0;
}
return params.result; }
int WINAPI AUDDRV_GetPriority(void)
@@ -314,26 +390,87 @@ int WINAPI AUDDRV_GetPriority(void) return SUCCEEDED(params.result) ? Priority_Preferred : Priority_Unavailable; }
+static BOOL get_pulse_name_by_guid(const GUID *guid, char name[256], EDataFlow *flow) +{
- DWORD key_name_size;
- WCHAR key_name[258];
- DWORD index = 0;
- HKEY key;
- /* Return empty string for default PulseAudio device */
- name[0] = 0;
- if (IsEqualGUID(guid, &pulse_render_guid)) {
*flow = eRender;
return TRUE;
- } else if (IsEqualGUID(guid, &pulse_capture_guid)) {
*flow = eCapture;
return TRUE;
- }
- if (RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ | KEY_WOW64_64KEY, &key) != ERROR_SUCCESS) {
WARN("No devices found in registry\n");
return FALSE;
- }
- for (;;) {
DWORD size, type;
LSTATUS status;
GUID reg_guid;
HKEY dev_key;
key_name_size = ARRAY_SIZE(key_name);
if (RegEnumKeyExW(key, index++, key_name, &key_name_size, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
break;
if (RegOpenKeyExW(key, key_name, 0, KEY_READ | KEY_WOW64_64KEY, &dev_key) != ERROR_SUCCESS) {
ERR("Couldn't open key: %s\n", wine_dbgstr_w(key_name));
continue;
}
size = sizeof(reg_guid);
status = RegQueryValueExW(dev_key, L"guid", 0, &type, (BYTE *)®_guid, &size);
RegCloseKey(dev_key);
if (status == ERROR_SUCCESS && type == REG_BINARY && size == sizeof(reg_guid) && IsEqualGUID(®_guid, guid)) {
RegCloseKey(key);
TRACE("Found matching device key: %s\n", wine_dbgstr_w(key_name));
if (key_name[0] == '0')
*flow = eRender;
else if (key_name[0] == '1')
*flow = eCapture;
else {
WARN("Unknown device type: %c\n", key_name[0]);
return FALSE;
}
return WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, 256, NULL, NULL);
}
- }
- RegCloseKey(key);
- WARN("No matching device in registry for GUID %s\n", debugstr_guid(guid));
- return FALSE;
+}
- HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) {
- ACImpl *This;
ACImpl *This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); EDataFlow dataflow; HRESULT hr;
TRACE("%s %p %p\n", debugstr_guid(guid), dev, out);
if (IsEqualGUID(guid, &pulse_render_guid))
dataflow = eRender;
else if (IsEqualGUID(guid, &pulse_capture_guid))
dataflow = eCapture;
else
return E_UNEXPECTED; *out = NULL;
This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); if (!This) return E_OUTOFMEMORY;
- if (!get_pulse_name_by_guid(guid, This->device, &dataflow)) {
HeapFree(GetProcessHeap(), 0, This);
return AUDCLNT_E_DEVICE_INVALIDATED;
- }
This->IAudioClient3_iface.lpVtbl = &AudioClient3_Vtbl; This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl; This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl;
@@ -609,6 +746,7 @@ static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, }
params.name = name = get_application_name();
- params.device = This->device; params.dataflow = This->dataflow; params.mode = mode; params.flags = flags;
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 3e65936..d5f8edf 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -81,6 +81,12 @@ typedef struct _ACPacket UINT32 discont; } ACPacket;
+typedef struct _PhysDevice {
- struct list entry;
- WCHAR *name;
- char device[0];
+} PhysDevice;
- static pa_context *pulse_ctx; static pa_mainloop *pulse_ml;
@@ -89,6 +95,8 @@ static WAVEFORMATEXTENSIBLE pulse_fmt[2]; static REFERENCE_TIME pulse_min_period[2], pulse_def_period[2];
static UINT g_phys_speakers_mask = 0; +static struct list g_phys_speakers = LIST_INIT(g_phys_speakers); +static struct list g_phys_sources = LIST_INIT(g_phys_sources);
static const REFERENCE_TIME MinimumPeriod = 30000; static const REFERENCE_TIME DefaultPeriod = 100000; @@ -128,6 +136,20 @@ static void dump_attr(const pa_buffer_attr *attr) TRACE("prebuf: %u\n", attr->prebuf); }
+static void free_phys_device_lists(void) +{
- static struct list *const lists[] = { &g_phys_speakers, &g_phys_sources, NULL };
- struct list *const *list = lists;
- PhysDevice *dev, *dev_next;
- do {
LIST_FOR_EACH_ENTRY_SAFE(dev, dev_next, *list, PhysDevice, entry) {
free(dev->name);
free(dev);
}
- } while (*(++list));
+}
- /* copied from kernelbase */ static int muldiv(int a, int b, int c) {
@@ -190,6 +212,7 @@ static NTSTATUS pulse_process_attach(void *args)
static NTSTATUS pulse_process_detach(void *args) {
- free_phys_device_lists(); if (pulse_ctx) { pa_context_disconnect(pulse_ctx);
@@ -215,6 +238,39 @@ static NTSTATUS pulse_main_loop(void *args) return STATUS_SUCCESS; }
+static NTSTATUS pulse_get_endpoint_ids(void *args) +{
- struct get_endpoint_ids_params *params = args;
- struct list *list = (params->flow == eRender) ? &g_phys_speakers : &g_phys_sources;
- DWORD len, name_len, needed = 0;
- WCHAR *p = params->devices;
- WCHAR device[256];
- PhysDevice *dev;
- params->num = 0;
- LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) {
if (!(len = ntdll_umbstowcs(dev->device, strlen(dev->device) + 1, device, ARRAY_SIZE(device))))
continue;
name_len = lstrlenW(dev->name) + 1;
needed += (len + name_len) * sizeof(WCHAR);
if (needed <= params->size) {
memcpy(p, device, len * sizeof(WCHAR));
p += len;
memcpy(p, dev->name, name_len * sizeof(WCHAR));
p += name_len;
}
params->num++;
- }
I find this API difficult to understand. At least a comment describing what 'num' and 'devices' will contain on output would be nice.
If I understand it right, 'devices' will contain 2x'num' null-terminated strings, right? Another way to represent that is with a single array of null-terminated strings, with the end-of-array sentinel being a zero-length string. That eliminates one of the param fields, and simplifies the array creation and also the iteration side.
You could also split the 'device' and 'name' strings each into their own arrays, to make the code easier to read.
Yeah, you got it right. Unfortunately we need 'num' so we can allocate the output arrays on PE side first before filling them, else we'd have to scan it twice.
The output WCHAR buffer contains a sequence of "device name" "friendly name" for each device (num devices), each separated by NUL terminator. I'll add a comment for it.
I also can't have it terminated by double NULs because the default PA devices have empty device names; this is nice because they don't require any special casing when looking them up (later).
- if (needed > params->size) {
params->size = needed;
params->result = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
- } else
params->result = S_OK;
- return STATUS_SUCCESS;
+}
- static void pulse_contextcallback(pa_context *c, void *userdata) { switch (pa_context_get_state(c)) {
@@ -357,12 +413,49 @@ static DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) return mask; }
-/* For default PulseAudio render device, OR together all of the
- PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */
+static void pulse_add_device(struct list *list, const char *device, const char *name) ...
- pulse_add_device(&g_phys_speakers, i->name, i->description);
The usage of "device" and "name" is very confusing in this patch. E.g. note the 'device' parameter is called with a 'name' argument here, and the 'name' param is called with 'description'.
Later, in AUDDRV_GetEndpointIDs, we're handling ids and devices and names. It's pretty mixed up.
Could you use more descriptive terms, more consistently across the entire codebase? E.g. you could use 'id' for an internal representation, and 'friendly' for something user-visible?
Andrew
The i->name and i->description are from the PA library. 'name' is the device name that is internal to PA (we store it in the registry key), and 'description' is the name we present to the Windows API and expose it to apps, i.e. our 'name'. The 'name' is never exposed since it's internal to PA. I don't think 'description' is appropriate for our context, but ofc we can't change the library's fields.
'device' typically refers to the internal PA device name, because that's what it is used. I can change it to 'id', although for me 'id' typically means some sort of number, not string, but I admit 'device' is not very understandable either...
'pulse_name' is probably the best bet, I should go with that, since it's not ambiguous at all.
+{
- DWORD len = strlen(device), name_len = strlen(name);
- PhysDevice *dev = malloc(FIELD_OFFSET(PhysDevice, device[len + 1]));
- WCHAR *wname;
- if (!dev)
return;
- if (!(wname = malloc((name_len + 1) * sizeof(WCHAR)))) {
free(dev);
return;
- }
- if (!(name_len = ntdll_umbstowcs(name, name_len, wname, name_len)) ||
!(dev->name = realloc(wname, (name_len + 1) * sizeof(WCHAR)))) {
free(wname);
free(dev);
return;
- }
- dev->name[name_len] = 0;
- memcpy(dev->device, device, len + 1);
- list_add_tail(list, &dev->entry);
+}
- static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
- if (i)
g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map);
- if (!i || !i->name || !i->name[0])
return;
- /* For default PulseAudio render device, OR together all of the
* PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */
- g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map);
+}
+static void pulse_phys_sources_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) +{
if (!i || !i->name || !i->name[0])
return;
pulse_add_device(&g_phys_sources, i->name, i->description); }
/* For most hardware on Windows, users must choose a configuration with an even
@@ -579,7 +672,14 @@ static NTSTATUS pulse_test_connect(void *args) pulse_probe_settings(1, &pulse_fmt[0]); pulse_probe_settings(0, &pulse_fmt[1]);
- free_phys_device_lists();
- list_init(&g_phys_speakers);
- list_init(&g_phys_sources); g_phys_speakers_mask = 0;
- pulse_add_device(&g_phys_speakers, "", "PulseAudio");
- pulse_add_device(&g_phys_sources, "", "PulseAudio");
o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); if (o) { while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 &&
@@ -588,6 +688,14 @@ static NTSTATUS pulse_test_connect(void *args) pa_operation_unref(o); }
- o = pa_context_get_source_info_list(pulse_ctx, &pulse_phys_sources_cb, NULL);
- if (o) {
while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 &&
pa_operation_get_state(o) == PA_OPERATION_RUNNING)
{}
pa_operation_unref(o);
- }
pa_context_unref(pulse_ctx); pulse_ctx = NULL; pa_mainloop_free(pulse_ml);
@@ -771,8 +879,9 @@ static HRESULT pulse_spec_from_waveformat(struct pulse_stream *stream, const WAV return S_OK; }
-static HRESULT pulse_stream_connect(struct pulse_stream *stream, UINT32 period_bytes) +static HRESULT pulse_stream_connect(struct pulse_stream *stream, const char *device, UINT32 period_bytes) {
- pa_stream_flags_t flags = PA_STREAM_START_CORKED | PA_STREAM_START_UNMUTED | PA_STREAM_ADJUST_LATENCY; int ret; char buffer[64]; static LONG number;
@@ -797,12 +906,17 @@ static HRESULT pulse_stream_connect(struct pulse_stream *stream, UINT32 period_b attr.maxlength = stream->bufsize_frames * pa_frame_size(&stream->ss); attr.prebuf = pa_frame_size(&stream->ss); dump_attr(&attr);
- /* If device name is given, use exactly the specified device */
- if (device[0])
flags |= PA_STREAM_DONT_MOVE;
- else
device = NULL; /* use default */
if (stream->dataflow == eRender)
ret = pa_stream_connect_playback(stream->stream, NULL, &attr,
PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY, NULL, NULL);
ret = pa_stream_connect_playback(stream->stream, device, &attr, flags, NULL, NULL); else
ret = pa_stream_connect_record(stream->stream, NULL, &attr,
PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_ADJUST_LATENCY);
ret = pa_stream_connect_record(stream->stream, device, &attr, flags); if (ret < 0) { WARN("Returns %i\n", ret); return AUDCLNT_E_ENDPOINT_CREATE_FAILED;
@@ -864,7 +978,7 @@ static NTSTATUS pulse_create_stream(void *args)
stream->share = params->mode; stream->flags = params->flags;
- hr = pulse_stream_connect(stream, stream->period_bytes);
- hr = pulse_stream_connect(stream, params->device, stream->period_bytes); if (SUCCEEDED(hr)) { UINT32 unalign; const pa_buffer_attr *attr = pa_stream_get_buffer_attr(stream->stream);
@@ -1967,6 +2081,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_process_attach, pulse_process_detach, pulse_main_loop,
- pulse_get_endpoint_ids, pulse_create_stream, pulse_release_stream, pulse_start,
diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index d28a73c..c4d274c 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -37,9 +37,19 @@ struct main_loop_params HANDLE event; };
+struct get_endpoint_ids_params +{
- EDataFlow flow;
- DWORD size;
- WCHAR *devices;
- HRESULT result;
- unsigned int num;
+};
- struct create_stream_params { const char *name;
- const char *device; EDataFlow dataflow; AUDCLNT_SHAREMODE mode; DWORD flags;
@@ -191,6 +201,7 @@ enum unix_funcs process_attach, process_detach, main_loop,
- get_endpoint_ids, create_stream, release_stream, start,
-- 2.34.1
Thanks for the review!