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).
-- v6: winepulse.drv: remove unused pulse_config winepulse: Return device-specific values for GetDevicePeriod winepulse: Return device-specific values for GetMixFormat
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 | 21 +++++++++++++---- dlls/winepulse.drv/pulse.c | 44 +++++++++++++++++++++++++++++++++++ dlls/winepulse.drv/unixlib.h | 9 +++++++ 3 files changed, 70 insertions(+), 4 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 3cbbc1d8115..61875b7352d 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -1136,17 +1136,30 @@ static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, WAVEFORMATEX **pwfx) { ACImpl *This = impl_from_IAudioClient3(iface); + struct get_mix_format_params params;
TRACE("(%p)->(%p)\n", This, pwfx);
if (!pwfx) return E_POINTER; + *pwfx = NULL;
- *pwfx = clone_format(&pulse_config.modes[This->dataflow == eCapture].format.Format); - if (!*pwfx) + params.pulse_name = This->pulse_name; + params.flow = This->dataflow; + params.fmt = CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)); + if (!params.fmt) return E_OUTOFMEMORY; - dump_fmt(*pwfx); - return S_OK; + + pulse_call(get_mix_format, ¶ms); + + if (SUCCEEDED(params.result)) { + *pwfx = ¶ms.fmt->Format; + dump_fmt(*pwfx); + } else { + CoTaskMemFree(params.fmt); + } + + return params.result; }
static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface, diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index a22d6717e0c..52d266a362c 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -740,6 +740,28 @@ 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 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 = pulse_fmt[params->flow == eCapture]; + 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 +2339,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 +2694,26 @@ 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; + EDataFlow flow; + HRESULT result; + PTR32 fmt; + } *params32 = args; + struct get_mix_format_params params = + { + .pulse_name = ULongToPtr(params32->pulse_name), + .flow = params32->flow, + .fmt = ULongToPtr(params32->fmt), + }; + pulse_get_mix_format(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { pulse_process_attach, @@ -2698,6 +2741,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..95e9dac65b8 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -159,6 +159,14 @@ struct get_current_padding_params UINT32 *padding; };
+struct get_mix_format_params +{ + const char *pulse_name; + EDataFlow flow; + HRESULT result; + WAVEFORMATEXTENSIBLE *fmt; +}; + struct get_next_packet_size_params { stream_handle stream; @@ -252,4 +260,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 | 13 +++++--- dlls/winepulse.drv/pulse.c | 57 +++++++++++++++++++++++++++++++++++ dlls/winepulse.drv/unixlib.h | 10 ++++++ 3 files changed, 75 insertions(+), 5 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 61875b7352d..6b78a56388b 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -1165,6 +1165,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); @@ -1172,12 +1173,14 @@ 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.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 52d266a362c..6e7fb48fdb6 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -762,6 +762,39 @@ static NTSTATUS pulse_get_mix_format(void *args) return STATUS_SUCCESS; }
+static NTSTATUS pulse_get_device_period(void *args) +{ + struct get_device_period_params *params = args; + 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 = pulse_def_period[params->flow == eCapture]; + if (params->min_period) + *params->min_period = pulse_min_period[params->flow == eCapture]; + 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 @@ -2340,6 +2373,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 @@ -2714,6 +2748,28 @@ 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; + EDataFlow flow; + HRESULT result; + PTR32 def_period; + PTR32 min_period; + } *params32 = args; + struct get_device_period_params params = + { + .pulse_name = ULongToPtr(params32->pulse_name), + .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, @@ -2742,6 +2798,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 95e9dac65b8..51d6baf500a 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -167,6 +167,15 @@ struct get_mix_format_params WAVEFORMATEXTENSIBLE *fmt; };
+struct get_device_period_params +{ + const char *pulse_name; + EDataFlow flow; + HRESULT result; + REFERENCE_TIME *def_period; + REFERENCE_TIME *min_period; +}; + struct get_next_packet_size_params { stream_handle stream; @@ -261,4 +270,5 @@ enum unix_funcs is_started, get_prop_value, get_mix_format, + get_device_period, };
From: Claire Girka claire@sitedethib.com
--- dlls/winepulse.drv/mmdevdrv.c | 3 --- dlls/winepulse.drv/pulse.c | 10 ---------- dlls/winepulse.drv/unixlib.h | 11 ----------- 3 files changed, 24 deletions(-)
diff --git a/dlls/winepulse.drv/mmdevdrv.c b/dlls/winepulse.drv/mmdevdrv.c index 6b78a56388b..446354fb9a5 100644 --- a/dlls/winepulse.drv/mmdevdrv.c +++ b/dlls/winepulse.drv/mmdevdrv.c @@ -60,8 +60,6 @@ enum DriverPriority { Priority_Preferred };
-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); @@ -492,7 +490,6 @@ int WINAPI AUDDRV_GetPriority(void) char *name;
params.name = name = get_application_name(FALSE); - params.config = &pulse_config; pulse_call(test_connect, ¶ms); free(name); return SUCCEEDED(params.result) ? Priority_Preferred : Priority_Unavailable; diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 6e7fb48fdb6..7a73d0d10c9 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -802,7 +802,6 @@ static NTSTATUS pulse_get_device_period(void *args) 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; @@ -885,13 +884,6 @@ static NTSTATUS pulse_test_connect(void *args) pa_mainloop_free(pulse_ml); pulse_ml = NULL;
- 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]; - config->modes[1].format = pulse_fmt[1]; - config->modes[1].def_period = pulse_def_period[1]; - config->modes[1].min_period = pulse_min_period[1]; - pulse_unlock();
params->result = S_OK; @@ -2673,12 +2665,10 @@ static NTSTATUS pulse_wow64_test_connect(void *args) { PTR32 name; HRESULT result; - PTR32 config; } *params32 = args; struct test_connect_params params = { .name = ULongToPtr(params32->name), - .config = ULongToPtr(params32->config), /* struct pulse_config is identical */ }; pulse_test_connect(¶ms); params32->result = params.result; diff --git a/dlls/winepulse.drv/unixlib.h b/dlls/winepulse.drv/unixlib.h index 51d6baf500a..a9224b3fbb3 100644 --- a/dlls/winepulse.drv/unixlib.h +++ b/dlls/winepulse.drv/unixlib.h @@ -29,16 +29,6 @@ enum phys_device_bus_type { phys_device_bus_usb };
-struct pulse_config -{ - struct - { - WAVEFORMATEXTENSIBLE format; - REFERENCE_TIME def_period; - REFERENCE_TIME min_period; - } modes[2]; -}; - struct endpoint { unsigned int name; @@ -218,7 +208,6 @@ struct test_connect_params { const char *name; HRESULT result; - struct pulse_config *config; };
struct is_started_params
I have stopped passing `pulse_config` back and forth, and actually added a commit to remove it altogether as it was only used for `GetMixFormat` and `GetDevicePeriod`.
This merge request was approved by Huw Davies.
This is breaking a bunch of tests here:
``` tools/runtest -q -P wine -T . -M mmdevapi.dll -p dlls/mmdevapi/tests/mmdevapi_test.exe capture && touch dlls/mmdevapi/tests/capture.ok capture.c:259: Test failed: GetNextPacketSize 480 vs. GetDevicePeriod 36176 make: *** [Makefile:68094: dlls/mmdevapi/tests/capture.ok] Error 1
tools/runtest -q -P wine -T . -M mmdevapi.dll -p dlls/mmdevapi/tests/mmdevapi_test.exe render && touch dlls/mmdevapi/tests/render.ok render.c:427: Test failed: Latency < default period, delta -1022376220us (30d40 vs 746573646165485f) render.c:815: Test failed: Expected 10ms default period: 1634027615 render.c:831: Test failed: GetBuffer failed: 88890006 render.c:832: Test failed: NULL buffer returned wine: Unhandled page fault on read access to 00000000 at address 0040B3F7 (thread 015c), starting debugger...
tools/runtest -q -P wine -T . -M mmdevapi.dll -p dlls/mmdevapi/tests/mmdevapi_test.exe spatialaudio && touch dlls/mmdevapi/tests/spatialaudio.ok spatialaudio.c:163: Test failed: Failed to activate spatial audio stream: 0x8007000e wine: Unhandled page fault on read access to 00000000 at address 00416955 (thread 0188), starting debugger... ```
On Mon Jul 4 18:50:19 2022 +0000, Alexandre Julliard wrote:
This is breaking a bunch of tests here:
tools/runtest -q -P wine -T . -M mmdevapi.dll -p dlls/mmdevapi/tests/mmdevapi_test.exe capture && touch dlls/mmdevapi/tests/capture.ok capture.c:259: Test failed: GetNextPacketSize 480 vs. GetDevicePeriod 36176 make: *** [Makefile:68094: dlls/mmdevapi/tests/capture.ok] Error 1 tools/runtest -q -P wine -T . -M mmdevapi.dll -p dlls/mmdevapi/tests/mmdevapi_test.exe render && touch dlls/mmdevapi/tests/render.ok render.c:427: Test failed: Latency < default period, delta -1022376220us (30d40 vs 746573646165485f) render.c:815: Test failed: Expected 10ms default period: 1634027615 render.c:831: Test failed: GetBuffer failed: 88890006 render.c:832: Test failed: NULL buffer returned wine: Unhandled page fault on read access to 00000000 at address 0040B3F7 (thread 015c), starting debugger... tools/runtest -q -P wine -T . -M mmdevapi.dll -p dlls/mmdevapi/tests/mmdevapi_test.exe spatialaudio && touch dlls/mmdevapi/tests/spatialaudio.ok spatialaudio.c:163: Test failed: Failed to activate spatial audio stream: 0x8007000e wine: Unhandled page fault on read access to 00000000 at address 00416955 (thread 0188), starting debugger...
Seems to be caused by allowing comparisons to a blank (`pulse_name[0] == 0`), I'm not completely sure why that would be an issue, especially with `pulse_get_prop_value` not doing that kind of test either, but adding the test fixes it, so I'll re-submit patches with this additional check. Sorry.