When `GetMixFormat` or `GetDevicePeriod` are used, return values specific to the selected device instead of those of the default one. This is especially useful when the audio devices features less channels than one specifically selected by the application.
This patch is part of an attempt to get Sony's DualSense controllers to work with Windows games through Wine. This is not enough by itself, but getting the proper mix format is required for most of those games to correctly use the haptic feedback features of the DualSense (which go through an audio output with 4 audio devices).
-- v5: winepulse: Return device-specific values for GetDevicePeriod winepulse: Return device-specific values for GetMixFormat winepulse: Store device-specific format and periods
From: Claire Girka claire@sitedethib.com
In addition to those of the default device, also store device-specific format and periods so that they can be returned on GetMixFormat and GetPeriod calls. --- dlls/winepulse.drv/pulse.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-)
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 42d73db45f9..a22d6717e0c 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -89,6 +89,8 @@ typedef struct _PhysDevice { EndpointFormFactor form; UINT channel_mask; UINT index; + REFERENCE_TIME min_period, def_period; + WAVEFORMATEXTENSIBLE fmt; char pulse_name[0]; } PhysDevice;
@@ -659,7 +661,8 @@ static void convert_channel_map(const pa_channel_map *pa_map, WAVEFORMATEXTENSIB fmt->dwChannelMask = pa_mask; }
-static void pulse_probe_settings(int render, WAVEFORMATEXTENSIBLE *fmt) { +static void pulse_probe_settings(int render, const char *pulse_name, WAVEFORMATEXTENSIBLE *fmt, REFERENCE_TIME *def_period, REFERENCE_TIME *min_period) +{ WAVEFORMATEX *wfx = &fmt->Format; pa_stream *stream; pa_channel_map map; @@ -684,10 +687,10 @@ static void pulse_probe_settings(int render, WAVEFORMATEXTENSIBLE *fmt) { if (!stream) ret = -1; else if (render) - ret = pa_stream_connect_playback(stream, NULL, &attr, + ret = pa_stream_connect_playback(stream, pulse_name, &attr, PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS, NULL, NULL); else - ret = pa_stream_connect_record(stream, NULL, &attr, PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS); + ret = pa_stream_connect_record(stream, pulse_name, &attr, PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS); if (ret >= 0) { while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && pa_stream_get_state(stream) == PA_STREAM_CREATING) @@ -710,13 +713,13 @@ static void pulse_probe_settings(int render, WAVEFORMATEXTENSIBLE *fmt) { pa_stream_unref(stream);
if (length) - pulse_def_period[!render] = pulse_min_period[!render] = pa_bytes_to_usec(10 * length, &ss); + *def_period = *min_period = pa_bytes_to_usec(10 * length, &ss);
- if (pulse_min_period[!render] < MinimumPeriod) - pulse_min_period[!render] = MinimumPeriod; + if (*min_period < MinimumPeriod) + *min_period = MinimumPeriod;
- if (pulse_def_period[!render] < DefaultPeriod) - pulse_def_period[!render] = DefaultPeriod; + if (*def_period < DefaultPeriod) + *def_period = DefaultPeriod;
wfx->wFormatTag = WAVE_FORMAT_EXTENSIBLE; wfx->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); @@ -745,6 +748,7 @@ static NTSTATUS pulse_test_connect(void *args) { struct test_connect_params *params = args; struct pulse_config *config = params->config; + PhysDevice *dev; pa_operation *o; int ret;
@@ -787,8 +791,8 @@ static NTSTATUS pulse_test_connect(void *args) pa_context_get_server(pulse_ctx), pa_context_get_server_protocol_version(pulse_ctx));
- pulse_probe_settings(1, &pulse_fmt[0]); - pulse_probe_settings(0, &pulse_fmt[1]); + pulse_probe_settings(1, NULL, &pulse_fmt[0], &pulse_def_period[0], &pulse_min_period[0]); + pulse_probe_settings(0, NULL, &pulse_fmt[1], &pulse_def_period[1], &pulse_min_period[1]);
free_phys_device_lists(); list_init(&g_phys_speakers); @@ -813,6 +817,14 @@ static NTSTATUS pulse_test_connect(void *args) pa_operation_unref(o); }
+ LIST_FOR_EACH_ENTRY(dev, &g_phys_speakers, PhysDevice, entry) { + pulse_probe_settings(1, dev->pulse_name, &dev->fmt, &dev->def_period, &dev->min_period); + } + + LIST_FOR_EACH_ENTRY(dev, &g_phys_sources, PhysDevice, entry) { + pulse_probe_settings(0, dev->pulse_name, &dev->fmt, &dev->def_period, &dev->min_period); + } + pa_context_unref(pulse_ctx); pulse_ctx = NULL; pa_mainloop_free(pulse_ml);
From: Claire Girka claire@sitedethib.com
When GetMixFormat is used, return values specific to the selected device instead of those of the default one. This is especially useful when the default audio device features less channels than one specifically selected by the application. --- dlls/winepulse.drv/mmdevdrv.c | 10 ++++++- dlls/winepulse.drv/pulse.c | 49 +++++++++++++++++++++++++++++++++++ dlls/winepulse.drv/unixlib.h | 10 +++++++ 3 files changed, 68 insertions(+), 1 deletion(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 3cbbc1d8115..0d975dab24e 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -1135,6 +1135,7 @@ static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient3 *iface, static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, WAVEFORMATEX **pwfx) { + struct get_mix_format_params params; ACImpl *This = impl_from_IAudioClient3(iface);
TRACE("(%p)->(%p)\n", This, pwfx); @@ -1142,7 +1143,14 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, if (!pwfx) return E_POINTER;
- *pwfx = clone_format(&pulse_config.modes[This->dataflow == eCapture].format.Format); + params.flow = This->dataflow; + params.pulse_name = This->pulse_name; + params.config = &pulse_config; + + pulse_call(get_mix_format, ¶ms); + + *pwfx = clone_format(¶ms.fmt.Format); + if (!*pwfx) return E_OUTOFMEMORY; dump_fmt(*pwfx); diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index a22d6717e0c..ecae0e0072f 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -740,6 +740,31 @@ static void pulse_probe_settings(int render, const char *pulse_name, WAVEFORMATE fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; }
+static NTSTATUS pulse_get_mix_format(void *args) +{ + struct get_mix_format_params *params = args; + struct pulse_config *config = params->config; + struct list *list = (params->flow == eRender) ? &g_phys_speakers : &g_phys_sources; + PhysDevice *dev; + + LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) { + if (strcmp(params->pulse_name, dev->pulse_name)) + continue; + + params->fmt = dev->fmt; + params->result = S_OK; + + return STATUS_SUCCESS; + } + + params->fmt = config->modes[params->flow == eCapture].format; + params->result = S_OK; + + params->result = S_OK; + + return STATUS_SUCCESS; +} + /* some poorly-behaved applications call audio functions during DllMain, so we * have to do as much as possible without creating a new thread. this function * sets up a synchronous connection to verify the server is running and query @@ -2317,6 +2342,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_test_connect, pulse_is_started, pulse_get_prop_value, + pulse_get_mix_format, };
#ifdef _WIN64 @@ -2671,6 +2697,28 @@ static NTSTATUS pulse_wow64_get_prop_value(void *args) return STATUS_SUCCESS; }
+static NTSTATUS pulse_wow64_get_mix_format(void *args) +{ + struct + { + PTR32 pulse_name; + PTR32 config; + EDataFlow flow; + HRESULT result; + WAVEFORMATEXTENSIBLE fmt; + } *params32 = args; + struct get_mix_format_params params = + { + .pulse_name = ULongToPtr(params32->pulse_name), + .config = ULongToPtr(params32->config), /* struct pulse_config is identical */ + .flow = params32->flow, + }; + pulse_get_mix_format(¶ms); + params32->result = params.result; + params32->fmt = params.fmt; + return STATUS_SUCCESS; +} + const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { pulse_process_attach, @@ -2698,6 +2746,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = pulse_wow64_test_connect, pulse_is_started, pulse_wow64_get_prop_value, + pulse_wow64_get_mix_format, };
#endif /* _WIN64 */ diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index f224f26c909..99e9b228429 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -159,6 +159,15 @@ struct get_current_padding_params UINT32 *padding; };
+struct get_mix_format_params +{ + const char *pulse_name; + struct pulse_config *config; + EDataFlow flow; + HRESULT result; + WAVEFORMATEXTENSIBLE fmt; +}; + struct get_next_packet_size_params { stream_handle stream; @@ -252,4 +261,5 @@ enum unix_funcs test_connect, is_started, get_prop_value, + get_mix_format, };
From: Claire Girka claire@sitedethib.com
--- dlls/winepulse.drv/mmdevdrv.c | 14 ++++++--- dlls/winepulse.drv/pulse.c | 58 +++++++++++++++++++++++++++++++++++ dlls/winepulse.drv/unixlib.h | 11 +++++++ 3 files changed, 78 insertions(+), 5 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 0d975dab24e..6ed02cd0b9d 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -1160,6 +1160,7 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface, REFERENCE_TIME *defperiod, REFERENCE_TIME *minperiod) { + struct get_device_period_params params; ACImpl *This = impl_from_IAudioClient3(iface);
TRACE("(%p)->(%p, %p)\n", This, defperiod, minperiod); @@ -1167,12 +1168,15 @@ static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface, if (!defperiod && !minperiod) return E_POINTER;
- if (defperiod) - *defperiod = pulse_config.modes[This->dataflow == eCapture].def_period; - if (minperiod) - *minperiod = pulse_config.modes[This->dataflow == eCapture].min_period; + params.flow = This->dataflow; + params.pulse_name = This->pulse_name; + params.config = &pulse_config; + params.def_period = defperiod; + params.min_period = minperiod;
- return S_OK; + pulse_call(get_device_period, ¶ms); + + return params.result; }
static HRESULT WINAPI AudioClient_Start(IAudioClient3 *iface) diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index ecae0e0072f..42979d54896 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -760,6 +760,38 @@ static NTSTATUS pulse_get_mix_format(void *args) params->fmt = config->modes[params->flow == eCapture].format; params->result = S_OK;
+ return STATUS_SUCCESS; +} + +static NTSTATUS pulse_get_device_period(void *args) +{ + struct get_device_period_params *params = args; + struct pulse_config *config = params->config; + struct list *list = (params->flow == eRender) ? &g_phys_speakers : &g_phys_sources; + PhysDevice *dev; + + if (!params->def_period && !params->min_period) { + params->result = E_POINTER; + return STATUS_SUCCESS; + } + + LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) { + if (strcmp(params->pulse_name, dev->pulse_name)) + continue; + + if (params->def_period) + *params->def_period = dev->def_period; + if (params->min_period) + *params->min_period = dev->min_period; + params->result = S_OK; + + return STATUS_SUCCESS; + } + + if (params->def_period) + *params->def_period = config->modes[params->flow == eCapture].def_period; + if (params->min_period) + *params->min_period = config->modes[params->flow == eCapture].min_period; params->result = S_OK;
return STATUS_SUCCESS; @@ -2343,6 +2375,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_is_started, pulse_get_prop_value, pulse_get_mix_format, + pulse_get_device_period, };
#ifdef _WIN64 @@ -2719,6 +2752,30 @@ static NTSTATUS pulse_wow64_get_mix_format(void *args) return STATUS_SUCCESS; }
+static NTSTATUS pulse_wow64_get_device_period(void *args) +{ + struct + { + PTR32 pulse_name; + PTR32 config; + EDataFlow flow; + HRESULT result; + PTR32 def_period; + PTR32 min_period; + } *params32 = args; + struct get_device_period_params params = + { + .pulse_name = ULongToPtr(params32->pulse_name), + .config = ULongToPtr(params32->config), /* struct pulse_config is identical */ + .flow = params32->flow, + .def_period = ULongToPtr(params32->def_period), + .min_period = ULongToPtr(params32->min_period), + }; + pulse_get_device_period(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { pulse_process_attach, @@ -2747,6 +2804,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = pulse_is_started, pulse_wow64_get_prop_value, pulse_wow64_get_mix_format, + pulse_wow64_get_device_period, };
#endif /* _WIN64 */ diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index 99e9b228429..05ffa71fde5 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -168,6 +168,16 @@ struct get_mix_format_params WAVEFORMATEXTENSIBLE fmt; };
+struct get_device_period_params +{ + const char *pulse_name; + struct pulse_config *config; + EDataFlow flow; + HRESULT result; + REFERENCE_TIME *def_period; + REFERENCE_TIME *min_period; +}; + struct get_next_packet_size_params { stream_handle stream; @@ -262,4 +272,5 @@ enum unix_funcs is_started, get_prop_value, get_mix_format, + get_device_period, };
On Mon Jul 4 10:44:29 2022 +0000, Claire wrote:
Alright, I can do that, it's just that I changed from two unixcalls to one following an earlier review :sweat_smile:
I have now done so, though I think it leads to slightly more code duplication than the other approach.
Huw Davies (@huw) commented about dlls/winepulse.drv/unixlib.h:
UINT32 *padding;
};
+struct get_mix_format_params +{
- const char *pulse_name;
- struct pulse_config *config;
Passing the config struct back to the unixlib is ugly - the data is already cached there (in `pulse_fmt[]`), so use that. This also applies to the next commit.
Huw Davies (@huw) commented about dlls/winepulse.drv/unixlib.h:
UINT32 *padding;
};
+struct get_mix_format_params +{
- const char *pulse_name;
- struct pulse_config *config;
- EDataFlow flow;
- HRESULT result;
- WAVEFORMATEXTENSIBLE fmt;
This isn't how the alsa driver does it. Please pass a ptr to a `CoTaskMemAlloc()`ed fmt.
Huw Davies (@huw) commented about dlls/winepulse.drv/pulse.c:
- PhysDevice *dev;
- LIST_FOR_EACH_ENTRY(dev, list, PhysDevice, entry) {
if (strcmp(params->pulse_name, dev->pulse_name))
continue;
params->fmt = dev->fmt;
params->result = S_OK;
return STATUS_SUCCESS;
- }
- params->fmt = config->modes[params->flow == eCapture].format;
- params->result = S_OK;
- params->result = S_OK;
Duplicate lines.