From: Damjan Jovanovic Subject: [PATCH] qcap: implement VfwPin_CheckMediaType(), and test connecting to a NullRenderer Message-Id: Date: Mon, 22 Apr 2019 09:40:05 +0200 Currently a video capture device cannot even connect to a NullRenderer, as VfwPin_CheckMediaType() always return a hardcoded E_NOTIMPL, preventing any media format from being negotiated. Implement it and test. Signed-off-by: Damjan Jovanovic --- dlls/qcap/capture.h | 1 + dlls/qcap/tests/Makefile.in | 3 +- dlls/qcap/tests/videocapture.c | 172 +++++++++++++++++++++++++++++++++ dlls/qcap/v4l.c | 47 +++++++-- dlls/qcap/vfwcapture.c | 4 +- 5 files changed, 218 insertions(+), 9 deletions(-) create mode 100644 dlls/qcap/tests/videocapture.c diff --git a/dlls/qcap/capture.h b/dlls/qcap/capture.h index 65ed2dfc27..aed88893e5 100644 --- a/dlls/qcap/capture.h +++ b/dlls/qcap/capture.h @@ -25,6 +25,7 @@ typedef struct _Capture Capture; Capture *qcap_driver_init(IPin*,USHORT) DECLSPEC_HIDDEN; HRESULT qcap_driver_destroy(Capture*) DECLSPEC_HIDDEN; +HRESULT qcap_driver_check_format(Capture*,const AM_MEDIA_TYPE*) DECLSPEC_HIDDEN; HRESULT qcap_driver_set_format(Capture*,AM_MEDIA_TYPE*) DECLSPEC_HIDDEN; HRESULT qcap_driver_get_format(const Capture*,AM_MEDIA_TYPE**) DECLSPEC_HIDDEN; HRESULT qcap_driver_get_prop_range(Capture*,VideoProcAmpProperty,LONG*,LONG*,LONG*,LONG*,LONG*) DECLSPEC_HIDDEN; diff --git a/dlls/qcap/tests/Makefile.in b/dlls/qcap/tests/Makefile.in index 2b988476ad..a5b9d0e474 100644 --- a/dlls/qcap/tests/Makefile.in +++ b/dlls/qcap/tests/Makefile.in @@ -5,4 +5,5 @@ C_SRCS = \ audiorecord.c \ avico.c \ qcap.c \ - smartteefilter.c + smartteefilter.c \ + videocapture.c diff --git a/dlls/qcap/tests/videocapture.c b/dlls/qcap/tests/videocapture.c new file mode 100644 index 0000000000..0fb959ec65 --- /dev/null +++ b/dlls/qcap/tests/videocapture.c @@ -0,0 +1,172 @@ +/* + * Video capture device tests. + * + * Copyright 2019 Damjan Jovanovic + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define COBJMACROS +#include "dshow.h" +#include "wine/test.h" + +static HRESULT find_pin(IBaseFilter *filter, PIN_DIRECTION wantedDir, IPin **pPin) +{ + IEnumPins *enumPins; + HRESULT hr; + + hr = IBaseFilter_EnumPins(filter, &enumPins); + if (SUCCEEDED(hr)) { + IPin *pin; + while ((hr = IEnumPins_Next(enumPins, 1, &pin, NULL)) == S_OK) { + PIN_DIRECTION pinDirection; + IPin_QueryDirection(pin, &pinDirection); + if (pinDirection == wantedDir) { + *pPin = pin; + hr = S_OK; + break; + } + IPin_Release(pin); + } + IEnumPins_Release(enumPins); + } + return hr; +} + +static void test_connect_null_renderer(IBaseFilter *captureDevice) +{ + IGraphBuilder *graph = NULL; + IBaseFilter *nullRenderer = NULL; + IPin *captureDeviceOut = NULL; + IPin *nullRendererIn = NULL; + HRESULT hr; + + hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IGraphBuilder, + (LPVOID*)&graph); + ok(SUCCEEDED(hr), "couldn't create graph builder, hr=0x%08x\n", hr); + if (FAILED(hr)) + goto end; + + hr = CoCreateInstance(&CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (LPVOID*)&nullRenderer); + ok(SUCCEEDED(hr) || + /* Windows 2008: http://stackoverflow.com/questions/29410348/initialize-nullrender-failed-with-error-regdb-e-classnotreg-on-win2008-r2 */ + broken(hr == REGDB_E_CLASSNOTREG), "couldn't create NullRenderer, hr=0x%08x\n", hr); + if (FAILED(hr)) + goto end; + hr = IGraphBuilder_AddFilter(graph, nullRenderer, NULL); + ok(SUCCEEDED(hr), "IGraphBuilder_AddFilter() failed, hr=0x%08x\n", hr); + if (FAILED(hr)) + goto end; + + hr = IGraphBuilder_AddFilter(graph, captureDevice, NULL); + ok(SUCCEEDED(hr), "IGraphBuilder_AddFilter() failed, hr=0x%08x\n", hr); + if (FAILED(hr)) + goto end; + + hr = find_pin(nullRenderer, PINDIR_INPUT, &nullRendererIn); + ok(hr == S_OK, "couldn't find NullRenderer input pin, hr=0x%08x\n", hr); + if (hr != S_OK) + goto end; + + hr = find_pin(captureDevice, PINDIR_OUTPUT, &captureDeviceOut); + ok(hr == S_OK, "couldn't find capture device output pin, hr=0x%08x\n", hr); + if (hr != S_OK) + goto end; + + hr = IGraphBuilder_Connect(graph, captureDeviceOut, nullRendererIn); + ok(SUCCEEDED(hr), "couldn't connect capture device to NullRenderer, hr=0x%08x\n", hr); + +end: + if (graph != NULL) + IGraphBuilder_Release(graph); + if (nullRenderer != NULL) + IBaseFilter_Release(nullRenderer); + if (captureDeviceOut != NULL) + IPin_Release(captureDeviceOut); + if (nullRendererIn != NULL) + IPin_Release(nullRendererIn); +} + +static void test_property_bag(IMoniker *moniker) +{ + IPropertyBag *propertyBag = NULL; + HRESULT hr; + + hr = IMoniker_BindToStorage(moniker, NULL, NULL, &IID_IPropertyBag, (void**)&propertyBag); + ok(SUCCEEDED(hr), "IMoniker_BindToStorage() failed, hr=0x%08x\n", hr); + + /* On Windows, all properties can fail to be read, so don't bother testing */ + + if (propertyBag != NULL) + IPropertyBag_Release(propertyBag); +} + +START_TEST(videocapture) +{ + ICreateDevEnum *devEnum = NULL; + IEnumMoniker *classEnum = NULL; + IMoniker *moniker = NULL; + HRESULT hr; + + CoInitialize(NULL); + + hr = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, + &IID_ICreateDevEnum, (void **)&devEnum); + ok(SUCCEEDED(hr), "Error creating CLSID_SystemDeviceEnum, hr=0x%08x\n", hr); + if (FAILED(hr)) + goto end; + + hr = ICreateDevEnum_CreateClassEnumerator(devEnum, &CLSID_VideoInputDeviceCategory, &classEnum, 0); + if (hr == S_FALSE) { + skip("No video capture devices present\n"); + goto end; + } + ok(hr == S_OK, "ICreateDevEnum_CreateClassEnumerator() failed, hr=0x%08x\n", hr); + if (FAILED(hr)) + goto end; + + while (IEnumMoniker_Next(classEnum, 1, &moniker, NULL) == S_OK) { + WCHAR *name; + IBaseFilter *captureDevice = NULL; + + hr = IMoniker_GetDisplayName(moniker, NULL, NULL, &name); + ok(hr == S_OK, "IMoniker_GetDisplayName() failed, hr=0x%08x\n", hr); + trace("Testing device %s\n", wine_dbgstr_w(name)); + CoTaskMemFree(name); + + /* It's not always an error if this fails - some /dev/video* devices aren't capture devices */ + hr = IMoniker_BindToObject(moniker, NULL, NULL, &IID_IBaseFilter, (void**)&captureDevice); + if (SUCCEEDED(hr)) + { + test_property_bag(moniker); + + test_connect_null_renderer(captureDevice); + } + else + skip("Failed to open capture device, hr=0x%08x\n", hr); + + IMoniker_Release(moniker); + if (captureDevice != NULL) + IBaseFilter_Release(captureDevice); + } + +end: + if (devEnum != NULL) + ICreateDevEnum_Release(devEnum); + if (classEnum != NULL) + IEnumMoniker_Release(classEnum); + CoUninitialize(); +} diff --git a/dlls/qcap/v4l.c b/dlls/qcap/v4l.c index 32c2c02512..c0d5fb5e0c 100644 --- a/dlls/qcap/v4l.c +++ b/dlls/qcap/v4l.c @@ -131,20 +131,55 @@ HRESULT qcap_driver_destroy(Capture *capBox) return S_OK; } +HRESULT qcap_driver_check_format(Capture *device, const AM_MEDIA_TYPE *mt) +{ + HRESULT hr; + TRACE("(%p)->(AM_MEDIA_TYPE(%p))\n", device, mt); + dump_AM_MEDIA_TYPE(mt); + + if (!IsEqualGUID(&mt->majortype, &MEDIATYPE_Video) + && !IsEqualGUID(&mt->majortype, &GUID_NULL)) + { + hr = VFW_E_NO_ACCEPTABLE_TYPES; + goto end; + } + + if (IsEqualGUID(&mt->formattype, &FORMAT_VideoInfo) && + mt->pbFormat != NULL && + mt->cbFormat >= sizeof(VIDEOINFOHEADER)) + { + VIDEOINFOHEADER *vih = (VIDEOINFOHEADER *)mt->pbFormat; + if (vih->bmiHeader.biBitCount == 24 && vih->bmiHeader.biCompression == BI_RGB) + hr = S_OK; + else + { + FIXME("Unsupported compression %#x, bpp %u.\n", vih->bmiHeader.biCompression, + vih->bmiHeader.biBitCount); + hr = VFW_E_INVALIDMEDIATYPE; + } + } + else if (IsEqualGUID(&mt->formattype, &GUID_NULL)) + hr = S_OK; + else + hr = VFW_E_NO_ACCEPTABLE_TYPES; + +end: + TRACE("returning 0x%08x\n", hr); + return hr; +} + HRESULT qcap_driver_set_format(Capture *device, AM_MEDIA_TYPE *mt) { struct v4l2_format format = {0}; int newheight, newwidth; VIDEOINFOHEADER *vih; int fd = device->fd; + HRESULT hr; + hr = qcap_driver_check_format(device, mt); + if (FAILED(hr)) + return hr; vih = (VIDEOINFOHEADER *)mt->pbFormat; - if (vih->bmiHeader.biBitCount != 24 || vih->bmiHeader.biCompression != BI_RGB) - { - FIXME("Unsupported compression %#x, bpp %u.\n", vih->bmiHeader.biCompression, - vih->bmiHeader.biBitCount); - return VFW_E_INVALIDMEDIATYPE; - } newwidth = vih->bmiHeader.biWidth; newheight = vih->bmiHeader.biHeight; diff --git a/dlls/qcap/vfwcapture.c b/dlls/qcap/vfwcapture.c index 6297a80e61..06dbe5eae8 100644 --- a/dlls/qcap/vfwcapture.c +++ b/dlls/qcap/vfwcapture.c @@ -660,8 +660,8 @@ static inline VfwPinImpl *impl_from_BasePin(BasePin *pin) static HRESULT WINAPI VfwPin_CheckMediaType(BasePin *pin, const AM_MEDIA_TYPE *amt) { - FIXME("(%p) stub\n", pin); - return E_NOTIMPL; + VfwPinImpl *This = impl_from_BasePin(pin); + return qcap_driver_check_format(This->parent->driver_info, amt); } static HRESULT WINAPI VfwPin_GetMediaType(BasePin *pin, int iPosition, AM_MEDIA_TYPE *pmt)