From: Paul Gofman pgofman@codeweavers.com
--- dlls/mmdevapi/client.c | 9 ++++----- dlls/mmdevapi/mmdevdrv.h | 4 +--- 2 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/dlls/mmdevapi/client.c b/dlls/mmdevapi/client.c index ed635bcf98a..6ee010fd3a6 100644 --- a/dlls/mmdevapi/client.c +++ b/dlls/mmdevapi/client.c @@ -526,6 +526,7 @@ static ULONG WINAPI client_Release(IAudioClient3 *iface) if (This->stream) stream_release(This->stream, This->timer_thread);
+ free(This->device_name); free(This); }
@@ -1462,7 +1463,6 @@ HRESULT AudioClient_Create(GUID *guid, IMMDevice *device, IAudioClient **out) struct audio_client *This; char *name; EDataFlow dataflow; - size_t size; HRESULT hr;
TRACE("%s %p %p\n", debugstr_guid(guid), device, out); @@ -1477,15 +1477,13 @@ HRESULT AudioClient_Create(GUID *guid, IMMDevice *device, IAudioClient **out) return E_UNEXPECTED; }
- size = strlen(name) + 1; - This = calloc(1, FIELD_OFFSET(struct audio_client, device_name[size])); + This = calloc(1, sizeof(*This)); if (!This) { free(name); return E_OUTOFMEMORY; }
- memcpy(This->device_name, name, size); - free(name); + This->device_name = name;
This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl; This->IAudioClient3_iface.lpVtbl = &AudioClient3_Vtbl; @@ -1499,6 +1497,7 @@ HRESULT AudioClient_Create(GUID *guid, IMMDevice *device, IAudioClient **out)
hr = CoCreateFreeThreadedMarshaler((IUnknown *)&This->IAudioClient3_iface, &This->marshal); if (FAILED(hr)) { + free(This->device_name); free(This); return hr; } diff --git a/dlls/mmdevapi/mmdevdrv.h b/dlls/mmdevapi/mmdevdrv.h index 42b01443ff0..7683867a9c1 100644 --- a/dlls/mmdevapi/mmdevdrv.h +++ b/dlls/mmdevapi/mmdevdrv.h @@ -80,7 +80,5 @@ struct audio_client { struct audio_session_wrapper *session_wrapper;
struct list entry; - - /* Keep at end */ - char device_name[0]; + char *device_name; };
From: Paul Gofman pgofman@codeweavers.com
--- dlls/mmdevapi/client.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/dlls/mmdevapi/client.c b/dlls/mmdevapi/client.c index 6ee010fd3a6..ad7cc9ff2ab 100644 --- a/dlls/mmdevapi/client.c +++ b/dlls/mmdevapi/client.c @@ -569,9 +569,6 @@ static HRESULT WINAPI client_Initialize(IAudioClient3 *iface, AUDCLNT_SHAREMODE return E_INVALIDARG; }
- if (FAILED(params.result = adjust_timing(This, &duration, &period, mode, flags, fmt))) - return params.result; - sessions_lock();
if (This->stream) { @@ -584,6 +581,12 @@ static HRESULT WINAPI client_Initialize(IAudioClient3 *iface, AUDCLNT_SHAREMODE return params.result; }
+ if (FAILED(params.result = adjust_timing(This, &duration, &period, mode, flags, fmt))) + { + sessions_unlock(); + return params.result; + } + params.name = name = get_application_name(); params.device = This->device_name; params.flow = This->dataflow;
From: Paul Gofman pgofman@codeweavers.com
--- dlls/mmdevapi/client.c | 39 ++++++++++++++++++++++++++++++ dlls/mmdevapi/unixlib.h | 10 ++++++++ dlls/winealsa.drv/alsa.c | 2 ++ dlls/winecoreaudio.drv/coreaudio.c | 2 ++ dlls/wineoss.drv/oss.c | 2 ++ dlls/winepulse.drv/pulse.c | 2 ++ 6 files changed, 57 insertions(+)
diff --git a/dlls/mmdevapi/client.c b/dlls/mmdevapi/client.c index ad7cc9ff2ab..5f6357a3bb9 100644 --- a/dlls/mmdevapi/client.c +++ b/dlls/mmdevapi/client.c @@ -21,6 +21,9 @@
#define COBJMACROS
+#include "ntstatus.h" +#define WIN32_NO_STATUS + #include <wchar.h>
#include <audiopolicy.h> @@ -581,6 +584,42 @@ static HRESULT WINAPI client_Initialize(IAudioClient3 *iface, AUDCLNT_SHAREMODE return params.result; }
+ if (flags & AUDCLNT_STREAMFLAGS_LOOPBACK) + { + struct get_loopback_capture_device_params params; + + if (This->dataflow != eRender) + { + sessions_unlock(); + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + } + + params.device = This->device_name; + params.name = name = get_application_name(); + params.ret_device_len = 0; + params.ret_device = NULL; + params.result = E_NOTIMPL; + wine_unix_call(get_loopback_capture_device, ¶ms); + while (params.result == STATUS_BUFFER_TOO_SMALL) + { + free(params.ret_device); + params.ret_device = malloc(params.ret_device_len); + wine_unix_call(get_loopback_capture_device, ¶ms); + } + free(name); + if (FAILED(params.result)) + { + sessions_unlock(); + free(params.ret_device); + if (params.result == E_NOTIMPL) + FIXME("get_loopback_capture_device is not supported by backend.\n"); + return params.result; + } + free(This->device_name); + This->device_name = params.ret_device; + This->dataflow = eCapture; + } + if (FAILED(params.result = adjust_timing(This, &duration, &period, mode, flags, fmt))) { sessions_unlock(); diff --git a/dlls/mmdevapi/unixlib.h b/dlls/mmdevapi/unixlib.h index d83ed918a51..4cb4c881bcf 100644 --- a/dlls/mmdevapi/unixlib.h +++ b/dlls/mmdevapi/unixlib.h @@ -140,6 +140,15 @@ struct is_format_supported_params HRESULT result; };
+struct get_loopback_capture_device_params +{ + const WCHAR *name; + const char *device; + char *ret_device; + UINT32 ret_device_len; + HRESULT result; +}; + struct get_mix_format_params { const char *device; @@ -313,6 +322,7 @@ enum unix_funcs get_capture_buffer, release_capture_buffer, is_format_supported, + get_loopback_capture_device, get_mix_format, get_device_period, get_buffer_size, diff --git a/dlls/winealsa.drv/alsa.c b/dlls/winealsa.drv/alsa.c index a24cb56d1d8..970db59ad12 100644 --- a/dlls/winealsa.drv/alsa.c +++ b/dlls/winealsa.drv/alsa.c @@ -2491,6 +2491,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = alsa_get_capture_buffer, alsa_release_capture_buffer, alsa_is_format_supported, + alsa_not_implemented, alsa_get_mix_format, alsa_get_device_period, alsa_get_buffer_size, @@ -2947,6 +2948,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = alsa_wow64_get_capture_buffer, alsa_release_capture_buffer, alsa_wow64_is_format_supported, + alsa_not_implemented, alsa_wow64_get_mix_format, alsa_wow64_get_device_period, alsa_wow64_get_buffer_size, diff --git a/dlls/winecoreaudio.drv/coreaudio.c b/dlls/winecoreaudio.drv/coreaudio.c index deb9df1b45a..db5e1116bf8 100644 --- a/dlls/winecoreaudio.drv/coreaudio.c +++ b/dlls/winecoreaudio.drv/coreaudio.c @@ -1837,6 +1837,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = unix_get_capture_buffer, unix_release_capture_buffer, unix_is_format_supported, + unix_not_implemented, unix_get_mix_format, unix_get_device_period, unix_get_buffer_size, @@ -2292,6 +2293,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = unix_wow64_get_capture_buffer, unix_release_capture_buffer, unix_wow64_is_format_supported, + unix_not_implemented, unix_wow64_get_mix_format, unix_wow64_get_device_period, unix_wow64_get_buffer_size, diff --git a/dlls/wineoss.drv/oss.c b/dlls/wineoss.drv/oss.c index 819e876606c..a017d242a98 100644 --- a/dlls/wineoss.drv/oss.c +++ b/dlls/wineoss.drv/oss.c @@ -1691,6 +1691,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = oss_get_capture_buffer, oss_release_capture_buffer, oss_is_format_supported, + oss_not_implemented, oss_get_mix_format, oss_get_device_period, oss_get_buffer_size, @@ -2186,6 +2187,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = oss_wow64_get_capture_buffer, oss_release_capture_buffer, oss_wow64_is_format_supported, + oss_not_implemented, oss_wow64_get_mix_format, oss_wow64_get_device_period, oss_wow64_get_buffer_size, diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 068245f03b2..9d3363e2638 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -2536,6 +2536,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_get_capture_buffer, pulse_release_capture_buffer, pulse_is_format_supported, + pulse_not_implemented, pulse_get_mix_format, pulse_get_device_period, pulse_get_buffer_size, @@ -3007,6 +3008,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = pulse_wow64_get_capture_buffer, pulse_release_capture_buffer, pulse_wow64_is_format_supported, + pulse_not_implemented, pulse_wow64_get_mix_format, pulse_wow64_get_device_period, pulse_wow64_get_buffer_size,
From: Paul Gofman pgofman@codeweavers.com
--- dlls/winepulse.drv/pulse.c | 42 ++++++++++++++------------------------ 1 file changed, 15 insertions(+), 27 deletions(-)
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 9d3363e2638..726a86e299d 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -203,6 +203,16 @@ static char *wstr_to_str(const WCHAR *wstr) return str; }
+static void wait_pa_operation_complete(pa_operation *o) +{ + if (!o) + return; + + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pulse_cond_wait(); + pa_operation_unref(o); +} + /* Following pulseaudio design here, mainloop has the lock taken whenever * it is handling something for pulse, and the lock is required whenever * doing any pa_* call that can affect the state in any way @@ -1548,7 +1558,6 @@ static NTSTATUS pulse_timer_loop(void *args) pa_usec_t last_time; UINT32 adv_bytes; int success; - pa_operation *o;
pulse_lock(); delay.QuadPart = -stream->mmdev_period_usec * 10; @@ -1566,13 +1575,7 @@ static NTSTATUS pulse_timer_loop(void *args)
delay.QuadPart = -stream->mmdev_period_usec * 10;
- o = pa_stream_update_timing_info(stream->stream, pulse_op_cb, &success); - if (o) - { - while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) - pulse_cond_wait(); - pa_operation_unref(o); - } + wait_pa_operation_complete(pa_stream_update_timing_info(stream->stream, pulse_op_cb, &success)); err = pa_stream_get_time(stream->stream, &now); if (err == 0) { @@ -1684,11 +1687,7 @@ static NTSTATUS pulse_start(void *args) { o = pa_stream_cork(stream->stream, 0, pulse_op_cb, &success); if (o) - { - while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) - pulse_cond_wait(); - pa_operation_unref(o); - } + wait_pa_operation_complete(o); else success = 0; if (!success) @@ -1731,11 +1730,7 @@ static NTSTATUS pulse_stop(void *args) { o = pa_stream_cork(stream->stream, 1, pulse_op_cb, &success); if (o) - { - while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) - pulse_cond_wait(); - pa_operation_unref(o); - } + wait_pa_operation_complete(o); else success = 0; if (!success) @@ -1779,15 +1774,8 @@ static NTSTATUS pulse_reset(void *args) /* If there is still data in the render buffer it needs to be removed from the server */ int success = 0; if (stream->held_bytes) - { - pa_operation *o = pa_stream_flush(stream->stream, pulse_op_cb, &success); - if (o) - { - while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) - pulse_cond_wait(); - pa_operation_unref(o); - } - } + wait_pa_operation_complete(pa_stream_flush(stream->stream, pulse_op_cb, &success)); + if (success || !stream->held_bytes) { stream->clock_lastpos = stream->clock_written = 0;
From: Paul Gofman pgofman@codeweavers.com
--- dlls/winepulse.drv/pulse.c | 112 ++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-)
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 726a86e299d..7a38b55087e 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -2206,6 +2206,89 @@ static NTSTATUS pulse_is_format_supported(void *args) return STATUS_SUCCESS; }
+static void sink_name_info_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) +{ + uint32_t *current_device_index = userdata; + pulse_broadcast(); + + if (!i || !i->name || !i->name[0]) + return; + *current_device_index = i->index; +} + +struct find_monitor_of_sink_cb_param +{ + struct get_loopback_capture_device_params *params; + uint32_t current_device_index; +}; + +static void find_monitor_of_sink_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) +{ + struct find_monitor_of_sink_cb_param *p = userdata; + unsigned int len; + + pulse_broadcast(); + + if (!i || !i->name || !i->name[0]) + return; + if (i->monitor_of_sink != p->current_device_index) + return; + + len = strlen(i->name) + 1; + if (len <= p->params->ret_device_len) + { + memcpy(p->params->ret_device, i->name, len); + p->params->result = STATUS_SUCCESS; + return; + } + p->params->ret_device_len = len; + p->params->result = STATUS_BUFFER_TOO_SMALL; +} + +static NTSTATUS pulse_get_loopback_capture_device(void *args) +{ + struct get_loopback_capture_device_params *params = args; + uint32_t current_device_index = PA_INVALID_INDEX; + struct find_monitor_of_sink_cb_param p; + const char *device_name; + char *name; + + pulse_lock(); + + if (!pulse_ml) + { + pulse_unlock(); + ERR("Called without main loop running.\n"); + params->result = E_INVALIDARG; + return STATUS_SUCCESS; + } + + name = wstr_to_str(params->name); + params->result = pulse_connect(name); + free(name); + + if (FAILED(params->result)) + { + pulse_unlock(); + return STATUS_SUCCESS; + } + + device_name = params->device; + if (device_name && !device_name[0]) device_name = NULL; + + params->result = E_FAIL; + wait_pa_operation_complete(pa_context_get_sink_info_by_name(pulse_ctx, device_name, &sink_name_info_cb, ¤t_device_index)); + if (current_device_index != PA_INVALID_INDEX) + { + p.current_device_index = current_device_index; + p.params = params; + wait_pa_operation_complete(pa_context_get_source_info_list(pulse_ctx, &find_monitor_of_sink_cb, &p)); + } + + pulse_unlock(); + return STATUS_SUCCESS; +} + static NTSTATUS pulse_get_mix_format(void *args) { struct get_mix_format_params *params = args; @@ -2524,7 +2607,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = pulse_get_capture_buffer, pulse_release_capture_buffer, pulse_is_format_supported, - pulse_not_implemented, + pulse_get_loopback_capture_device, pulse_get_mix_format, pulse_get_device_period, pulse_get_buffer_size, @@ -2716,6 +2799,31 @@ static NTSTATUS pulse_wow64_is_format_supported(void *args) return STATUS_SUCCESS; }
+static NTSTATUS pulse_wow64_get_loopback_capture_device(void *args) +{ + struct + { + PTR32 name; + PTR32 device; + PTR32 ret_device; + UINT32 ret_device_len; + HRESULT result; + } *params32 = args; + + struct get_loopback_capture_device_params params = + { + .name = ULongToPtr(params32->name), + .device = ULongToPtr(params32->device), + .ret_device = ULongToPtr(params32->device), + .ret_device_len = params32->ret_device_len, + }; + + pulse_get_loopback_capture_device(¶ms); + params32->result = params.result; + params32->ret_device_len = params.ret_device_len; + return STATUS_SUCCESS; +} + static NTSTATUS pulse_wow64_get_mix_format(void *args) { struct @@ -2996,7 +3104,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = pulse_wow64_get_capture_buffer, pulse_release_capture_buffer, pulse_wow64_is_format_supported, - pulse_not_implemented, + pulse_wow64_get_loopback_capture_device, pulse_wow64_get_mix_format, pulse_wow64_get_device_period, pulse_wow64_get_buffer_size,
From: Paul Gofman pgofman@codeweavers.com
--- dlls/mmdevapi/tests/capture.c | 62 +++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-)
diff --git a/dlls/mmdevapi/tests/capture.c b/dlls/mmdevapi/tests/capture.c index 08ab730ef1a..e3ae87fd9bd 100644 --- a/dlls/mmdevapi/tests/capture.c +++ b/dlls/mmdevapi/tests/capture.c @@ -41,7 +41,8 @@ /* undocumented error code */ #define D3D11_ERROR_4E MAKE_HRESULT(SEVERITY_ERROR, FACILITY_DIRECT3D11, 0x4e)
-static IMMDevice *dev = NULL; +static IMMDeviceEnumerator *mme; +static IMMDevice *dev; static const LARGE_INTEGER ullZero;
static void test_uninitialized(IAudioClient *ac) @@ -1012,10 +1013,66 @@ static void test_marshal(void)
}
+static void test_render_loopback(void) +{ + IAudioCaptureClient *capture; + IAudioRenderClient *render; + WAVEFORMATEX *pwfx; + IMMDevice *device; + IAudioClient *ac; + HRESULT hr; + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mme, eRender, eMultimedia, &device); + ok(hr == S_OK || hr == E_NOTFOUND, "GetDefaultAudioEndpoint failed: %#lx.\n", hr); + + if (hr != S_OK || !dev) + { + if (hr == E_NOTFOUND) + skip("No sound card available\n"); + else + skip("GetDefaultAudioEndpoint returns 0x%08lx\n", hr); + return; + } + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, (void**)&ac); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_LOOPBACK, 5000000, 0, pwfx, NULL); + ok(hr == AUDCLNT_E_WRONG_ENDPOINT_TYPE, "got %#lx.\n", hr); + IAudioClient_Release(ac); + CoTaskMemFree(pwfx); + + hr = IMMDevice_Activate(device, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, (void**)&ac); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, 5000000, 0, pwfx, NULL); + todo_wine_if(hr == E_NOTIMPL) ok(hr == S_OK, "got %#lx.\n", hr); + if (FAILED(hr)) + goto done; + + hr = IAudioClient_GetService(ac, &IID_IAudioRenderClient, (void **)&render); + ok(hr == AUDCLNT_E_WRONG_ENDPOINT_TYPE, "got %#lx.\n", hr); + + hr = IAudioClient_GetService(ac, &IID_IAudioCaptureClient, (void **)&capture); + ok(hr == S_OK, "got %#lx.\n", hr); + IAudioCaptureClient_Release(capture); + +done: + IAudioClient_Release(ac); + IMMDevice_Release(device); + CoTaskMemFree(pwfx); +} + START_TEST(capture) { HRESULT hr; - IMMDeviceEnumerator *mme = NULL;
CoInitializeEx(NULL, COINIT_MULTITHREADED); hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&mme); @@ -1042,6 +1099,7 @@ START_TEST(capture) test_simplevolume(); test_volume_dependence(); test_marshal(); + test_render_loopback();
IMMDevice_Release(dev);
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=146378
Your paranoid android.
=== debian11b (32 bit WoW report) ===
mmdevapi: capture.c:161: Test failed: GetNextPacketSize 480 vs. GetBuffer 0
Fixes voice chat in Helldivers 2 (which works though PlayFab so it is probably going to affect more games once those pick that version).
I tested the real captured sound with a separate test program, attaching that just in case (it captures the sound card output for 5 sec and writes that to sound.wav).
I tried to incorporate test_capture() into the added test and, while that works perfectly on my real Win11 machine, that fails in a weird way on Testbot on Windows machines: IAudioClient_Start() never signals event and no data come through, that's across all the machines having sound card. I am guessing something doesn't work quite right with loopback capture in VMs.
[captureoutput.c](/uploads/d6a5ea9e974f24f1b12d0265d4d361f9/captureoutput.c)