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).
-- v4: 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..5a6e260c590 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 | 9 +++++- dlls/winepulse.drv/pulse.c | 55 +++++++++++++++++++++++++++++++++++ dlls/winepulse.drv/unixlib.h | 12 ++++++++ 3 files changed, 75 insertions(+), 1 deletion(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 3cbbc1d8115..8c5d0cf7214 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_device_settings_params params; ACImpl *This = impl_from_IAudioClient3(iface);
TRACE("(%p)->(%p)\n", This, pwfx); @@ -1142,7 +1143,13 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, if (!pwfx) return E_POINTER;
- *pwfx = clone_format(&pulse_config.modes[This->dataflow == eCapture].format.Format); + params.render = This->dataflow == eRender; + params.pulse_name = This->pulse_name; + params.config = &pulse_config; + pulse_call(get_device_settings, ¶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 5a6e260c590..477876f4430 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -740,6 +740,33 @@ static void pulse_probe_settings(int render, const CHAR *pulse_name, WAVEFORMATE fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; }
+static NTSTATUS pulse_get_device_settings(void *args) +{ + struct get_device_settings_params *params = args; + struct pulse_config *config = params->config; + struct list *list = params->render ? &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->def_period = dev->def_period; + params->min_period = dev->min_period; + params->result = S_OK; + + return STATUS_SUCCESS; + } + + params->fmt = config->modes[!params->render].format; + params->def_period = config->modes[!params->render].def_period; + params->min_period = config->modes[!params->render].min_period; + 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 +2344,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_test_connect, pulse_is_started, pulse_get_prop_value, + pulse_get_device_settings, };
#ifdef _WIN64 @@ -2671,6 +2699,32 @@ static NTSTATUS pulse_wow64_get_prop_value(void *args) return STATUS_SUCCESS; }
+static NTSTATUS pulse_wow64_get_device_settings(void *args) +{ + struct + { + PTR32 pulse_name; + PTR32 config; + BOOL render; + HRESULT result; + WAVEFORMATEXTENSIBLE fmt; + REFERENCE_TIME def_period; + REFERENCE_TIME min_period; + } *params32 = args; + struct get_device_settings_params params = + { + .pulse_name = ULongToPtr(params32->pulse_name), + .config = ULongToPtr(params32->config), /* struct pulse_config is identical */ + .render = params32->render, + }; + pulse_get_device_settings(¶ms); + params32->result = params.result; + params32->fmt = params.fmt; + params32->def_period = params.def_period; + params32->min_period = params.min_period; + return STATUS_SUCCESS; +} + const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { pulse_process_attach, @@ -2698,6 +2752,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_device_settings, };
#endif /* _WIN64 */ diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index f224f26c909..576a0bfb326 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -159,6 +159,17 @@ struct get_current_padding_params UINT32 *padding; };
+struct get_device_settings_params +{ + const char *pulse_name; + struct pulse_config *config; + BOOL render; + HRESULT result; + WAVEFORMATEXTENSIBLE fmt; + REFERENCE_TIME def_period; + REFERENCE_TIME min_period; +}; + struct get_next_packet_size_params { stream_handle stream; @@ -252,4 +263,5 @@ enum unix_funcs test_connect, is_started, get_prop_value, + get_device_settings, };
From: Claire Girka claire@sitedethib.com
--- dlls/winepulse.drv/mmdevdrv.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 8c5d0cf7214..c1755157d94 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -1159,6 +1159,7 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface, REFERENCE_TIME *defperiod, REFERENCE_TIME *minperiod) { + struct get_device_settings_params params; ACImpl *This = impl_from_IAudioClient3(iface);
TRACE("(%p)->(%p, %p)\n", This, defperiod, minperiod); @@ -1166,10 +1167,15 @@ static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface, if (!defperiod && !minperiod) return E_POINTER;
+ params.render = This->dataflow == eRender; + params.pulse_name = This->pulse_name; + params.config = &pulse_config; + pulse_call(get_device_settings, ¶ms); + if (defperiod) - *defperiod = pulse_config.modes[This->dataflow == eCapture].def_period; + *defperiod = params.def_period; if (minperiod) - *minperiod = pulse_config.modes[This->dataflow == eCapture].min_period; + *minperiod = params.min_period;
return S_OK; }
On Mon Jul 4 08:41:05 2022 +0000, Huw Davies wrote:
The former. Keep the first patch in the series, then add a `get_mix_format` unixcall that `GetMixFormat()` always calls and use `pulse_name` to determine the correct values in the unixlib. Then do a similar thing for `GetDevicePeriod()`. Note, when you add or change a unixcall, you also need to add (or change) the appropriate wow64 handler (see e.g. `wow64_get_mix_format()` in the alsa driver.
I reordered `pulse_probe_settings`'s arguments as well as `get_device_settings_params` attributes to have inputs first and outputs last. I have also moved the `pulse_name[0]` checks to the unixlib, and made it fallback to the default settings if the device could not be found. I have added the missing wow64 handler, though I am not 100% sure it is correct as I am not familiar with it.
Huw Davies (@huw) commented about dlls/winepulse.drv/pulse.c:
fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
}
+static NTSTATUS pulse_get_device_settings(void *args) +{
- struct get_device_settings_params *params = args;
- struct pulse_config *config = params->config;
- struct list *list = params->render ? &g_phys_speakers : &g_phys_sources;
- PhysDevice *dev;
As mentioned, please add a `get_mix_format` unixcall that follows the example of the alsa driver.
You'll then need to create a `get_device_periord` unixcall for `GetDevicePeriod()`. This isn't implemented in any of the drivers; I'd suggest a params struct like this ```c struct get_device_period_marams { const char *pulse_name; HRESULT result; REFERENCE_TIME *def_period; REFERENCE_TIME *min_period; }; ```
Huw Davies (@huw) commented about dlls/winepulse.drv/pulse.c:
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)
Please use lowercase `char`.
On Mon Jul 4 10:29:21 2022 +0000, Huw Davies wrote:
As mentioned, please add a `get_mix_format` unixcall that follows the example of the alsa driver. You'll then need to create a `get_device_period` unixcall for `GetDevicePeriod()`. This isn't implemented in any of the drivers; I'd suggest a params struct like this
struct get_device_period_params { const char *pulse_name; HRESULT result; REFERENCE_TIME *def_period; REFERENCE_TIME *min_period; };
Alright, I can do that, it's just that I changed from two unixcalls to one following an earlier review :sweat_smile: