-- v3: mmdevapi/tests: Add test for capturing render loopback. winepulse.drv: Implement pulse_get_loopback_capture_device(). winepulse.drv: Factor out wait_pa_operation_complete(). mmdevapi: Stub AUDCLNT_STREAMFLAGS_LOOPBACK support. mmdevapi: Adjust timing after main loop start in client_Initialize(). mmdevapi: Store device_name as a pointer in struct audio_client.
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 132b6abae5e..4a205632b12 100644 --- a/dlls/mmdevapi/client.c +++ b/dlls/mmdevapi/client.c @@ -623,6 +623,7 @@ static ULONG WINAPI client_Release(IAudioClient3 *iface) if (This->stream) stream_release(This->stream, This->timer_thread);
+ free(This->device_name); free(This); }
@@ -1492,7 +1493,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); @@ -1507,15 +1507,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; @@ -1529,6 +1527,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 4a205632b12..882ee32dd21 100644 --- a/dlls/mmdevapi/client.c +++ b/dlls/mmdevapi/client.c @@ -390,9 +390,6 @@ static HRESULT stream_init(struct audio_client *client, const BOOLEAN force_def_ return E_INVALIDARG; }
- if (FAILED(params.result = adjust_timing(client, force_def_period, &duration, &period, mode, flags, fmt))) - return params.result; - sessions_lock();
if (client->stream) { @@ -405,6 +402,12 @@ static HRESULT stream_init(struct audio_client *client, const BOOLEAN force_def_ return params.result; }
+ if (FAILED(params.result = adjust_timing(client, force_def_period, &duration, &period, mode, flags, fmt))) + { + sessions_unlock(); + return params.result; + } + params.name = name = get_application_name(); params.device = client->device_name; params.flow = client->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 882ee32dd21..ee73ae608f1 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> @@ -402,6 +405,42 @@ static HRESULT stream_init(struct audio_client *client, const BOOLEAN force_def_ return params.result; }
+ if (flags & AUDCLNT_STREAMFLAGS_LOOPBACK) + { + struct get_loopback_capture_device_params params; + + if (client->dataflow != eRender) + { + sessions_unlock(); + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + } + + params.device = client->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(client->device_name); + client->device_name = params.ret_device; + client->dataflow = eCapture; + } + if (FAILED(params.result = adjust_timing(client, force_def_period, &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 | 51 ++++++++++++-------------------------- 1 file changed, 16 insertions(+), 35 deletions(-)
diff --git a/dlls/winepulse.drv/pulse.c b/dlls/winepulse.drv/pulse.c index 9d3363e2638..dac1b40257f 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -203,6 +203,17 @@ static char *wstr_to_str(const WCHAR *wstr) return str; }
+static BOOL wait_pa_operation_complete(pa_operation *o) +{ + if (!o) + return FALSE; + + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pulse_cond_wait(); + pa_operation_unref(o); + return TRUE; +} + /* 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 +1559,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 +1576,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) { @@ -1653,7 +1657,6 @@ static NTSTATUS pulse_start(void *args) struct start_params *params = args; struct pulse_stream *stream = handle_get_stream(params->stream); int success; - pa_operation *o;
params->result = S_OK; pulse_lock(); @@ -1682,14 +1685,7 @@ static NTSTATUS pulse_start(void *args)
if (pa_stream_is_corked(stream->stream)) { - 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); - } - else + if (!wait_pa_operation_complete(pa_stream_cork(stream->stream, 0, pulse_op_cb, &success))) success = 0; if (!success) params->result = E_FAIL; @@ -1708,7 +1704,6 @@ static NTSTATUS pulse_stop(void *args) { struct stop_params *params = args; struct pulse_stream *stream = handle_get_stream(params->stream); - pa_operation *o; int success;
pulse_lock(); @@ -1729,14 +1724,7 @@ static NTSTATUS pulse_stop(void *args) params->result = S_OK; if (stream->dataflow == eRender) { - 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); - } - else + if (!wait_pa_operation_complete(pa_stream_cork(stream->stream, 1, pulse_op_cb, &success))) success = 0; if (!success) params->result = E_FAIL; @@ -1779,15 +1767,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 dac1b40257f..263d0f1a45a 100644 --- a/dlls/winepulse.drv/pulse.c +++ b/dlls/winepulse.drv/pulse.c @@ -2199,6 +2199,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; @@ -2517,7 +2600,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, @@ -2709,6 +2792,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->ret_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 @@ -2989,7 +3097,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);
On Tue Jun 25 00:11:26 2024 +0000, Paul Gofman wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/5870/diffs?diff_id=119359&start_sha=65bf2610d36f2bed1a296f77327abdcb5681eadf#152f5af0ef9650fb7529b3638869ab8c7c4ff082_1733_1727)
The return value is `BOOL` (4-bytes large), but that's fine.
On Tue Jun 25 00:11:22 2024 +0000, Paul Gofman wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/5870/diffs?diff_id=119359&start_sha=65bf2610d36f2bed1a296f77327abdcb5681eadf#99913733944fc35298a259705e992a42b21541f5_611_685)
The rest of `client.c` uses K&R braces, so let's be consistent.