From: Paul Gofman pgofman@codeweavers.com
--- dlls/qcap/tests/videocapture.c | 154 +++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+)
diff --git a/dlls/qcap/tests/videocapture.c b/dlls/qcap/tests/videocapture.c index cb74c20481f..a7f6eea666a 100644 --- a/dlls/qcap/tests/videocapture.c +++ b/dlls/qcap/tests/videocapture.c @@ -20,6 +20,7 @@
#define COBJMACROS #include "dshow.h" +#include "mferror.h" #include "wine/test.h" #include "wine/strmbase.h"
@@ -649,6 +650,158 @@ static void test_connection(IMoniker *moniker) ok(!ref, "Got outstanding refcount %ld.\n", ref); }
+static void test_multiple_objects(IMoniker *moniker) +{ + struct testfilter testsink, testsink2; + IAMStreamConfig *config, *config2; + IMediaControl *control, *control2; + IFilterGraph2 *graph, *graph2; + IBaseFilter *filter, *filter2; + IEnumPins *enum_pins; + OAFilterState state; + IPin *pin, *pin2; + HRESULT hr; + DWORD ret; + ULONG ref; + + hr = IMoniker_BindToObject(moniker, NULL, NULL, &IID_IBaseFilter, (void **)&filter); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = IMoniker_BindToObject(moniker, NULL, NULL, &IID_IBaseFilter, (void **)&filter2); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + if (FAILED(hr)) + { + IBaseFilter_Release(filter); + return; + } + ok(filter != filter2, "got same objects.\n"); + + hr = IBaseFilter_EnumPins(filter, &enum_pins); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + pin = NULL; + while (IEnumPins_Next(enum_pins, 1, &pin, NULL) == S_OK) + { + PIN_DIRECTION dir; + IPin_QueryDirection(pin, &dir); + if (dir == PINDIR_OUTPUT) + break; + IPin_Release(pin); + } + IEnumPins_Release(enum_pins); + ok(!!pin, "got NULL.\n"); + + hr = IBaseFilter_EnumPins(filter2, &enum_pins); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + pin2 = NULL; + while (IEnumPins_Next(enum_pins, 1, &pin2, NULL) == S_OK) + { + PIN_DIRECTION dir; + IPin_QueryDirection(pin2, &dir); + if (dir == PINDIR_OUTPUT) + break; + IPin_Release(pin2); + } + IEnumPins_Release(enum_pins); + ok(!!pin2, "got NULL.\n"); + + CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, + &IID_IFilterGraph2, (void **)&graph); + IFilterGraph2_QueryInterface(graph, &IID_IMediaControl, (void **)&control); + testfilter_init(&testsink); + IFilterGraph2_AddFilter(graph, &testsink.filter.IBaseFilter_iface, L"sink"); + IFilterGraph2_AddFilter(graph, filter, L"source"); + hr = IPin_QueryInterface(pin, &IID_IAMStreamConfig, (void **)&config); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, + &IID_IFilterGraph2, (void **)&graph2); + IFilterGraph2_QueryInterface(graph2, &IID_IMediaControl, (void **)&control2); + testfilter_init(&testsink2); + IFilterGraph2_AddFilter(graph2, &testsink2.filter.IBaseFilter_iface, L"sink"); + IFilterGraph2_AddFilter(graph2, filter2, L"source"); + hr = IPin_QueryInterface(pin2, &IID_IAMStreamConfig, (void **)&config2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IFilterGraph2_ConnectDirect(graph, pin, &testsink.sink.pin.IPin_iface, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_ConnectDirect(graph2, pin2, &testsink2.sink.pin.IPin_iface, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Run(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Running, "Got state %lu.\n", state); + + hr = IBaseFilter_Pause(filter2); + todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + if (FAILED(hr)) + goto cleanup; + + hr = IMediaControl_Run(control2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaControl_GetState(control2, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Running, "Got state %lu.\n", state); + + ret = WaitForSingleObject(testsink2.got_sample, 5000); + ok(!ret, "Got %lu.\n", ret); + + if (0) + { + /* times out on Windows unless a sample was received before IMediaControl_Run(control2) which is unlikely. */ + ret = WaitForSingleObject(testsink.got_sample, 5000); + ok(ret == WAIT_TIMEOUT, "Got %lu.\n", ret); + } + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Running, "Got state %lu.\n", state); + hr = IMediaControl_GetState(control2, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Running, "Got state %lu.\n", state); + + hr = IMediaControl_Pause(control); + ok(hr == MF_E_VIDEO_RECORDING_DEVICE_PREEMPTED, "Got hr %#lx.\n", hr); + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Stopped, "Got state %lu.\n", state); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Stopped, "Got state %lu.\n", state); + + hr = IMediaControl_Pause(control2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaControl_GetState(control2, 0, &state); + ok(hr == VFW_S_CANT_CUE, "Got hr %#lx.\n", hr); + ok(state == State_Paused, "Got state %lu.\n", state); + hr = IMediaControl_Stop(control2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaControl_GetState(control2, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Stopped, "Got state %lu.\n", state); + +cleanup: + IAMStreamConfig_Release(config); + IMediaControl_Release(control); + ref = IFilterGraph2_Release(graph); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + + IPin_Release(pin); + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + + IAMStreamConfig_Release(config2); + IMediaControl_Release(control2); + ref = IFilterGraph2_Release(graph2); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + IPin_Release(pin2); + ref = IBaseFilter_Release(filter2); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + START_TEST(videocapture) { ICreateDevEnum *dev_enum; @@ -698,6 +851,7 @@ START_TEST(videocapture) ok(!ref, "Got outstanding refcount %ld.\n", ref);
test_connection(moniker); + test_multiple_objects(moniker);
IMoniker_Release(moniker); }
From: Paul Gofman pgofman@codeweavers.com
--- dlls/qcap/qcap_private.h | 6 ++++++ dlls/qcap/tests/videocapture.c | 7 +------ dlls/qcap/v4l.c | 30 ++++++++++++++++++++++++++---- dlls/qcap/vfwcapture.c | 8 ++++++++ 4 files changed, 41 insertions(+), 10 deletions(-)
diff --git a/dlls/qcap/qcap_private.h b/dlls/qcap/qcap_private.h index c9119b6a52e..d0acd9e3bd7 100644 --- a/dlls/qcap/qcap_private.h +++ b/dlls/qcap/qcap_private.h @@ -47,6 +47,11 @@ struct create_params video_capture_device_t *device; };
+struct start_params +{ + video_capture_device_t device; +}; + struct destroy_params { video_capture_device_t device; @@ -130,6 +135,7 @@ struct read_frame_params enum unix_funcs { unix_create, + unix_start, unix_destroy, unix_check_format, unix_set_format, diff --git a/dlls/qcap/tests/videocapture.c b/dlls/qcap/tests/videocapture.c index a7f6eea666a..92a89316a43 100644 --- a/dlls/qcap/tests/videocapture.c +++ b/dlls/qcap/tests/videocapture.c @@ -667,12 +667,7 @@ static void test_multiple_objects(IMoniker *moniker) hr = IMoniker_BindToObject(moniker, NULL, NULL, &IID_IBaseFilter, (void **)&filter); ok(hr == S_OK, "got %#lx.\n", hr); hr = IMoniker_BindToObject(moniker, NULL, NULL, &IID_IBaseFilter, (void **)&filter2); - todo_wine ok(hr == S_OK, "got %#lx.\n", hr); - if (FAILED(hr)) - { - IBaseFilter_Release(filter); - return; - } + ok(hr == S_OK, "got %#lx.\n", hr); ok(filter != filter2, "got same objects.\n");
hr = IBaseFilter_EnumPins(filter, &enum_pins); diff --git a/dlls/qcap/v4l.c b/dlls/qcap/v4l.c index 134c3eca9e1..d91a15fbfa3 100644 --- a/dlls/qcap/v4l.c +++ b/dlls/qcap/v4l.c @@ -93,6 +93,8 @@ struct video_capture_device const struct caps *current_caps; struct caps *caps; LONG caps_count; + struct v4l2_format device_format; + BOOL started;
int image_size, image_pitch; BYTE *image_data; @@ -162,7 +164,7 @@ static NTSTATUS v4l_device_check_format( void *args ) return E_FAIL; }
-static HRESULT set_caps(struct video_capture_device *device, const struct caps *caps) +static HRESULT set_caps(struct video_capture_device *device, const struct caps *caps, BOOL try) { struct v4l2_format format = {0}; LONG width, height, image_size; @@ -182,7 +184,7 @@ static HRESULT set_caps(struct video_capture_device *device, const struct caps * format.fmt.pix.pixelformat = caps->pixelformat; format.fmt.pix.width = width; format.fmt.pix.height = height; - if (xioctl(device->fd, VIDIOC_S_FMT, &format) == -1 + if (xioctl(device->fd, try ? VIDIOC_TRY_FMT : VIDIOC_S_FMT, &format) == -1 || format.fmt.pix.pixelformat != caps->pixelformat || format.fmt.pix.width != width || format.fmt.pix.height != height) @@ -192,6 +194,8 @@ static HRESULT set_caps(struct video_capture_device *device, const struct caps * return VFW_E_TYPE_NOT_ACCEPTED; }
+ device->started = !try; + device->device_format = format; device->current_caps = caps; device->image_size = image_size; device->image_pitch = width * caps->video_info.bmiHeader.biBitCount / 8; @@ -213,7 +217,7 @@ static NTSTATUS v4l_device_set_format( void *args ) if (device->current_caps == caps) return S_OK;
- return set_caps(device, caps); + return set_caps(device, caps, FALSE); }
static NTSTATUS v4l_device_get_format( void *args ) @@ -550,7 +554,7 @@ static NTSTATUS v4l_device_create( void *args ) for (i = 0; i < device->caps_count; ++i) device->caps[i].media_type.pbFormat = (BYTE *)&device->caps[i].video_info;
- if (FAILED(hr = set_caps(device, &device->caps[0]))) + if (FAILED(hr = set_caps(device, &device->caps[0], TRUE))) { if (hr == VFW_E_TYPE_NOT_ACCEPTED && !have_libv4l2) ERR_(winediag)("You may need libv4l2 to use this device.\n"); @@ -569,6 +573,22 @@ error: return E_FAIL; }
+static NTSTATUS v4l_device_start( void *args ) +{ + const struct start_params *params = args; + struct video_capture_device *device = get_device(params->device); + + if (device->started) return S_OK; + + if (xioctl(device->fd, VIDIOC_S_FMT, &device->device_format) == -1) + { + ERR("Failed to set pixel format: %s.\n", strerror(errno)); + return E_FAIL; + } + device->started = TRUE; + return S_OK; +} + static NTSTATUS v4l_device_destroy( void *args ) { const struct destroy_params *params = args; @@ -580,6 +600,7 @@ static NTSTATUS v4l_device_destroy( void *args ) const unixlib_entry_t __wine_unix_call_funcs[] = { v4l_device_create, + v4l_device_start, v4l_device_destroy, v4l_device_check_format, v4l_device_set_format, @@ -846,6 +867,7 @@ static NTSTATUS wow64_v4l_device_read_frame( void *args ) const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { wow64_v4l_device_create, + v4l_device_start, v4l_device_destroy, wow64_v4l_device_check_format, wow64_v4l_device_set_format, diff --git a/dlls/qcap/vfwcapture.c b/dlls/qcap/vfwcapture.c index 0899b228ec2..dba9f2f8288 100644 --- a/dlls/qcap/vfwcapture.c +++ b/dlls/qcap/vfwcapture.c @@ -195,6 +195,7 @@ static DWORD WINAPI stream_thread(void *arg) static HRESULT vfw_capture_init_stream(struct strmbase_filter *iface) { struct vfw_capture *filter = impl_from_strmbase_filter(iface); + struct start_params params; HRESULT hr;
if (!filter->source.pin.peer) @@ -203,6 +204,13 @@ static HRESULT vfw_capture_init_stream(struct strmbase_filter *iface) if (FAILED(hr = IMemAllocator_Commit(filter->source.pAllocator))) ERR("Failed to commit allocator, hr %#lx.\n", hr);
+ params.device = filter->device; + if (FAILED(hr = V4L_CALL( start, ¶ms ))) + { + ERR("start stream failed.\n"); + return hr; + } + EnterCriticalSection(&filter->state_cs); filter->state = State_Paused; LeaveCriticalSection(&filter->state_cs);
Fixes Burnout Paradies Remastered crashing on start when a camera is present.
The game creates qcap capture filter twice from devenum's moniker for some reason. Then, in actual graph, only one format is used. While v4l device can be opened multiple times it cannot really be used simultaneously. The second creation fails in set_caps() called from v4l_device_create(). Then, the game constructs just one dshow capture graph and with this patch camera actually works.
On Windows capture device also can't be really used simultaneously. But the filter creation would not ever fail, some failures and obscure behaviour only starts once the filter is connected into the graph and graph is started (i. e., actual frame capture takes place). If the device is opened in another process attempt to start a capture will fail. If it is already opened in the same process (like in included test), the behaviour is more convoluted: the second started graph will succeed and will be delivering samples while the first initialized graph will stop receiving samples. Then (at least here on Windows 11) attempt to change graph state on the first graph will result in MF error MF_E_VIDEO_RECORDING_DEVICE_PREEMPTED.
It is possible to implement that behaviour exactly of course by keeping a track of opened devices and closing those upon starting a new stream. But that will require quite some complications we tracking that, handling closed devices and synchronization. I suppose we don't need that until something depends on that (which is probably not all that likely). The present patch delays VIDIOC_S_FMT until stream initialization (using VIDIOC_TRY_FMT to test format caps during device creation) allowing multiple devices to be created and any first of them to be actually used, while attempt to init stream while another device is in use will fail.
Sorry for the delay, had to wait until I was somewhere with real hardware to test with.
Unfortunately these tests aren't passing on Windows for me. I get a failure in IBaseFilter_Pause(filter2) with HRESULT_FROM_WIN32(ERROR_NO_SYSTEM_RESOURCES). If I get rid of the Run() call on the first graph, pausing filter2 succeeds, but the subsequent Run() on the second graph fails with HRESULT_FROM_WIN32(ERROR_BAD_COMMAND). So I guess it tolerates multiple connection, but only the first one can actually be run?
Thanks for checking that. My test was exactly succeeding on a flavour of Rog Strix laptop with builtin camera on Windows 11. Ironically right now I am without that laptop around and don't have any Windows setup with a camera, I guess I go and get USB camera on the weekend and also test with that one.
From our vastly diverging results (including all the way weird error codes on both) I suspect that the actual behaviour on Windows depends on camera driver. On my previous setup it was exact opposite to what you observed: the device which started using camera first got it non-functional while the second graph was working.
So I guess it contributes to that trying to implement the actual capture preemption looks a is a moot point now, all I want to do is to let that enumeration and filter creation succeed. So probably in the end I would just remove those setup specific tests? Yet I'd try that with another camera here.
Thanks for checking that. My test was exactly succeeding on a flavour of Rog Strix laptop with builtin camera on Windows 11. Ironically right now I am without that laptop around and don't have any Windows setup with a camera, I guess I go and get USB camera on the weekend and also test with that one.
From our vastly diverging results (including all the way weird error codes on both) I suspect that the actual behaviour on Windows depends on camera driver. On my previous setup it was exact opposite to what you observed: the device which started using camera first got it non-functional while the second graph was working.
Yeah, it's likely driver-specific. It's also possible that Windows reimplemented quartz capture on top of mfplat between 7 and 11, given that one MF error code.
So I guess it contributes to that trying to implement the actual capture preemption looks as a moot point now, all I want to do is to let that enumeration and filter creation succeed. So probably in the end I would just remove those setup specific tests? Yet I'd try that with another camera here.
It may be worth keeping some form of documentation (alternate test results or a comment), but I don't know that it's that important. In any case I guess the tests should be restricted to something closer to what the application expects.