[PATCH v2 1/5] winepulse.drv: Expose audio devices to the application.

Gabriel Ivăncescu gabrielopcode at gmail.com
Tue Feb 15 12:47:12 CST 2022


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 at harmstone.com>, with changes by
>> Sebastian Lackner <sebastian at fds-team.de>.
>>
>> Signed-off-by: Gabriel Ivăncescu <gabrielopcode at 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, &params);
>>   }
>>   
>> -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, &params);
>> +    } 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 *)&reg_guid, &size);
>> +        RegCloseKey(dev_key);
>> +
>> +        if (status == ERROR_SUCCESS && type == REG_BINARY && size == sizeof(reg_guid) && IsEqualGUID(&reg_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!



More information about the wine-devel mailing list