Commit 982be1de authored by Damjan Jovanovic's avatar Damjan Jovanovic Committed by Alexandre Julliard

qcap: Add the SmartTee filter automatically as necessary, and test this.

parent 419be239
......@@ -249,6 +249,118 @@ fnCaptureGraphBuilder2_FindInterface(ICaptureGraphBuilder2 * iface,
*/
}
static HRESULT match_smart_tee_pin(CaptureGraphImpl *This,
const GUID *pCategory,
const GUID *pType,
IUnknown *pSource,
IPin **source_out)
{
static const WCHAR inputW[] = {'I','n','p','u','t',0};
static const WCHAR captureW[] = {'C','a','p','t','u','r','e',0};
static const WCHAR previewW[] = {'P','r','e','v','i','e','w',0};
IPin *capture = NULL;
IPin *preview = NULL;
IPin *peer = NULL;
IBaseFilter *smartTee = NULL;
BOOL needSmartTee = FALSE;
HRESULT hr;
TRACE("(%p, %s, %s, %p, %p)\n", This, debugstr_guid(pCategory), debugstr_guid(pType), pSource, source_out);
hr = ICaptureGraphBuilder2_FindPin(&This->ICaptureGraphBuilder2_iface, pSource,
PINDIR_OUTPUT, &PIN_CATEGORY_CAPTURE, pType, FALSE, 0, &capture);
if (SUCCEEDED(hr)) {
hr = ICaptureGraphBuilder2_FindPin(&This->ICaptureGraphBuilder2_iface, pSource,
PINDIR_OUTPUT, &PIN_CATEGORY_PREVIEW, pType, FALSE, 0, &preview);
if (FAILED(hr))
needSmartTee = TRUE;
} else {
hr = E_INVALIDARG;
goto end;
}
if (!needSmartTee) {
if (IsEqualIID(pCategory, &PIN_CATEGORY_CAPTURE)) {
hr = IPin_ConnectedTo(capture, &peer);
if (hr == VFW_E_NOT_CONNECTED) {
*source_out = capture;
IPin_AddRef(*source_out);
hr = S_OK;
} else
hr = E_INVALIDARG;
} else {
hr = IPin_ConnectedTo(preview, &peer);
if (hr == VFW_E_NOT_CONNECTED) {
*source_out = preview;
IPin_AddRef(*source_out);
hr = S_OK;
} else
hr = E_INVALIDARG;
}
goto end;
}
hr = IPin_ConnectedTo(capture, &peer);
if (SUCCEEDED(hr)) {
PIN_INFO pinInfo;
GUID classID;
hr = IPin_QueryPinInfo(peer, &pinInfo);
if (SUCCEEDED(hr)) {
hr = IBaseFilter_GetClassID(pinInfo.pFilter, &classID);
if (SUCCEEDED(hr)) {
if (IsEqualIID(&classID, &CLSID_SmartTee)) {
smartTee = pinInfo.pFilter;
IBaseFilter_AddRef(smartTee);
}
}
IBaseFilter_Release(pinInfo.pFilter);
}
if (!smartTee) {
hr = E_INVALIDARG;
goto end;
}
} else if (hr == VFW_E_NOT_CONNECTED) {
hr = CoCreateInstance(&CLSID_SmartTee, NULL, CLSCTX_INPROC_SERVER,
&IID_IBaseFilter, (LPVOID*)&smartTee);
if (SUCCEEDED(hr)) {
hr = IGraphBuilder_AddFilter(This->mygraph, smartTee, NULL);
if (SUCCEEDED(hr)) {
IPin *smartTeeInput = NULL;
hr = IBaseFilter_FindPin(smartTee, inputW, &smartTeeInput);
if (SUCCEEDED(hr)) {
hr = IGraphBuilder_ConnectDirect(This->mygraph, capture, smartTeeInput, NULL);
IPin_Release(smartTeeInput);
}
}
}
if (FAILED(hr)) {
TRACE("adding SmartTee failed with hr=0x%08x\n", hr);
hr = E_INVALIDARG;
goto end;
}
} else {
hr = E_INVALIDARG;
goto end;
}
if (IsEqualIID(pCategory, &PIN_CATEGORY_CAPTURE))
hr = IBaseFilter_FindPin(smartTee, captureW, source_out);
else {
hr = IBaseFilter_FindPin(smartTee, previewW, source_out);
if (SUCCEEDED(hr))
hr = VFW_S_NOPREVIEWPIN;
}
end:
if (capture)
IPin_Release(capture);
if (preview)
IPin_Release(preview);
if (peer)
IPin_Release(peer);
if (smartTee)
IBaseFilter_Release(smartTee);
TRACE("for %s returning hr=0x%08x, *source_out=%p\n", IsEqualIID(pCategory, &PIN_CATEGORY_CAPTURE) ? "capture" : "preview", hr, source_out ? *source_out : 0);
return hr;
}
static HRESULT WINAPI
fnCaptureGraphBuilder2_RenderStream(ICaptureGraphBuilder2 * iface,
const GUID *pCategory,
......@@ -258,7 +370,8 @@ fnCaptureGraphBuilder2_RenderStream(ICaptureGraphBuilder2 * iface,
IBaseFilter *pfRenderer)
{
CaptureGraphImpl *This = impl_from_ICaptureGraphBuilder2(iface);
IPin *source_out, *renderer_in, *capture, *preview;
IPin *source_out = NULL, *renderer_in;
BOOL usedSmartTeePreviewPin = FALSE;
HRESULT hr;
FIXME("(%p/%p)->(%s, %s, %p, %p, %p) semi-stub!\n", This, iface,
......@@ -276,24 +389,26 @@ fnCaptureGraphBuilder2_RenderStream(ICaptureGraphBuilder2 * iface,
return E_NOTIMPL;
}
hr = ICaptureGraphBuilder2_FindPin(iface, pSource, PINDIR_OUTPUT, pCategory, pType, TRUE, 0, &source_out);
if (FAILED(hr))
return E_INVALIDARG;
if (pCategory && IsEqualIID(pCategory, &PIN_CATEGORY_VBI)) {
FIXME("Tee/Sink-to-Sink filter not supported\n");
IPin_Release(source_out);
return E_NOTIMPL;
}
hr = ICaptureGraphBuilder2_FindPin(iface, pSource, PINDIR_OUTPUT, &PIN_CATEGORY_CAPTURE, NULL, TRUE, 0, &capture);
if (SUCCEEDED(hr)) {
hr = ICaptureGraphBuilder2_FindPin(iface, pSource, PINDIR_OUTPUT, &PIN_CATEGORY_PREVIEW, NULL, TRUE, 0, &preview);
} else if (pCategory && (IsEqualIID(pCategory, &PIN_CATEGORY_CAPTURE) || IsEqualIID(pCategory, &PIN_CATEGORY_PREVIEW))){
IBaseFilter *sourceFilter = NULL;
hr = IUnknown_QueryInterface(pSource, &IID_IBaseFilter, (void**)&sourceFilter);
if (SUCCEEDED(hr)) {
hr = match_smart_tee_pin(This, pCategory, pType, pSource, &source_out);
if (hr == VFW_S_NOPREVIEWPIN)
usedSmartTeePreviewPin = TRUE;
IBaseFilter_Release(sourceFilter);
} else {
hr = ICaptureGraphBuilder2_FindPin(iface, pSource, PINDIR_OUTPUT, pCategory, pType, TRUE, 0, &source_out);
}
if (FAILED(hr))
FIXME("Smart Tee filter not supported - not creating preview pin\n");
else
IPin_Release(preview);
IPin_Release(capture);
return E_INVALIDARG;
} else {
hr = ICaptureGraphBuilder2_FindPin(iface, pSource, PINDIR_OUTPUT, pCategory, pType, TRUE, 0, &source_out);
if (FAILED(hr))
return E_INVALIDARG;
}
hr = ICaptureGraphBuilder2_FindPin(iface, (IUnknown*)pfRenderer, PINDIR_INPUT, NULL, NULL, TRUE, 0, &renderer_in);
......@@ -331,6 +446,8 @@ fnCaptureGraphBuilder2_RenderStream(ICaptureGraphBuilder2 * iface,
IPin_Release(source_out);
IPin_Release(renderer_in);
if (SUCCEEDED(hr) && usedSmartTeePreviewPin)
hr = VFW_S_NOPREVIEWPIN;
return hr;
}
......
......@@ -140,121 +140,7 @@ static const struct {
BOOL wine_extra;
BOOL optional; /* fails on wine if missing */
BOOL broken;
} renderstream_cat_media[] = {
{BASEFILTER_QUERYINTERFACE, SOURCE_FILTER},
{BASEFILTER_ENUMPINS, SOURCE_FILTER},
{ENUMPINS_NEXT, SOURCE_FILTER},
{PIN_QUERYDIRECTION, SOURCE_FILTER},
{PIN_CONNECTEDTO, SOURCE_FILTER},
{PIN_QUERYPININFO, SOURCE_FILTER, TRUE},
{KSPROPERTYSET_GET, SOURCE_FILTER},
{PIN_ENUMMEDIATYPES, SOURCE_FILTER},
{ENUMMEDIATYPES_RESET, SOURCE_FILTER},
{ENUMMEDIATYPES_NEXT, SOURCE_FILTER},
{BASEFILTER_QUERYINTERFACE, SOURCE_FILTER},
{BASEFILTER_ENUMPINS, SOURCE_FILTER},
{ENUMPINS_NEXT, SOURCE_FILTER},
{PIN_QUERYDIRECTION, SOURCE_FILTER},
{PIN_CONNECTEDTO, SOURCE_FILTER},
{PIN_QUERYPININFO, SOURCE_FILTER, TRUE},
{KSPROPERTYSET_GET, SOURCE_FILTER},
{ENUMPINS_NEXT, SOURCE_FILTER},
{BASEFILTER_QUERYINTERFACE, SOURCE_FILTER, TRUE},
{BASEFILTER_ENUMPINS, SOURCE_FILTER, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, TRUE},
{PIN_QUERYDIRECTION, SOURCE_FILTER, TRUE},
{PIN_CONNECTEDTO, SOURCE_FILTER, TRUE},
{PIN_QUERYPININFO, SOURCE_FILTER, TRUE},
{KSPROPERTYSET_GET, SOURCE_FILTER, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, TRUE},
{BASEFILTER_QUERYINTERFACE, SOURCE_FILTER, TRUE},
{BASEFILTER_ENUMPINS, SOURCE_FILTER, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, TRUE},
{PIN_QUERYDIRECTION, SOURCE_FILTER, TRUE},
{PIN_CONNECTEDTO, SOURCE_FILTER, TRUE},
{PIN_QUERYPININFO, SOURCE_FILTER, TRUE},
{KSPROPERTYSET_GET, SOURCE_FILTER, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, TRUE},
{PIN_ENUMMEDIATYPES, SOURCE_FILTER, TRUE, FALSE, TRUE},
{ENUMMEDIATYPES_NEXT, SOURCE_FILTER, TRUE, FALSE, TRUE},
{BASEFILTER_QUERYINTERFACE, SINK_FILTER, FALSE, TRUE},
{BASEFILTER_ENUMPINS, SINK_FILTER},
{ENUMPINS_NEXT, SINK_FILTER},
{PIN_QUERYDIRECTION, SINK_FILTER},
{PIN_CONNECTEDTO, SINK_FILTER},
{GRAPHBUILDER_CONNECT, NOT_FILTER},
{BASEFILTER_GETSTATE, SOURCE_FILTER, TRUE, FALSE, TRUE},
{BASEFILTER_ENUMPINS, SOURCE_FILTER, FALSE, FALSE, FALSE, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, FALSE, FALSE, FALSE, TRUE},
{PIN_QUERYDIRECTION, SOURCE_FILTER, FALSE, FALSE, FALSE, TRUE},
{PIN_CONNECTEDTO, SOURCE_FILTER, FALSE, FALSE, FALSE, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, FALSE, FALSE, FALSE, TRUE},
{END, NOT_FILTER}
}, renderstream_intermediate[] = {
{BASEFILTER_QUERYINTERFACE, SOURCE_FILTER},
{BASEFILTER_ENUMPINS, SOURCE_FILTER},
{ENUMPINS_NEXT, SOURCE_FILTER},
{PIN_QUERYDIRECTION, SOURCE_FILTER},
{PIN_CONNECTEDTO, SOURCE_FILTER},
{PIN_QUERYPININFO, SOURCE_FILTER, TRUE},
{KSPROPERTYSET_GET, SOURCE_FILTER},
{PIN_ENUMMEDIATYPES, SOURCE_FILTER},
{ENUMMEDIATYPES_RESET, SOURCE_FILTER},
{ENUMMEDIATYPES_NEXT, SOURCE_FILTER},
{BASEFILTER_QUERYINTERFACE, SOURCE_FILTER},
{BASEFILTER_ENUMPINS, SOURCE_FILTER},
{ENUMPINS_NEXT, SOURCE_FILTER},
{PIN_QUERYDIRECTION, SOURCE_FILTER},
{PIN_CONNECTEDTO, SOURCE_FILTER},
{PIN_QUERYPININFO, SOURCE_FILTER, TRUE},
{KSPROPERTYSET_GET, SOURCE_FILTER},
{ENUMPINS_NEXT, SOURCE_FILTER},
{BASEFILTER_QUERYINTERFACE, SOURCE_FILTER, TRUE},
{BASEFILTER_ENUMPINS, SOURCE_FILTER, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, TRUE},
{PIN_QUERYDIRECTION, SOURCE_FILTER, TRUE},
{PIN_CONNECTEDTO, SOURCE_FILTER, TRUE},
{PIN_QUERYPININFO, SOURCE_FILTER, TRUE},
{KSPROPERTYSET_GET, SOURCE_FILTER, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, TRUE},
{BASEFILTER_QUERYINTERFACE, SOURCE_FILTER, TRUE},
{BASEFILTER_ENUMPINS, SOURCE_FILTER, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, TRUE},
{PIN_QUERYDIRECTION, SOURCE_FILTER, TRUE},
{PIN_CONNECTEDTO, SOURCE_FILTER, TRUE},
{PIN_QUERYPININFO, SOURCE_FILTER, TRUE},
{KSPROPERTYSET_GET, SOURCE_FILTER, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, TRUE},
{PIN_ENUMMEDIATYPES, SOURCE_FILTER, TRUE, FALSE, TRUE},
{ENUMMEDIATYPES_NEXT, SOURCE_FILTER, TRUE, FALSE, TRUE},
{BASEFILTER_QUERYINTERFACE, SINK_FILTER, FALSE, TRUE},
{BASEFILTER_ENUMPINS, SINK_FILTER},
{ENUMPINS_NEXT, SINK_FILTER},
{PIN_QUERYDIRECTION, SINK_FILTER},
{PIN_CONNECTEDTO, SINK_FILTER},
{BASEFILTER_QUERYINTERFACE, INTERMEDIATE_FILTER, FALSE, TRUE},
{BASEFILTER_ENUMPINS, INTERMEDIATE_FILTER},
{ENUMPINS_NEXT, INTERMEDIATE_FILTER},
{PIN_QUERYDIRECTION, INTERMEDIATE_FILTER},
{PIN_CONNECTEDTO, INTERMEDIATE_FILTER},
{ENUMPINS_NEXT, INTERMEDIATE_FILTER},
{PIN_QUERYDIRECTION, INTERMEDIATE_FILTER},
{PIN_CONNECTEDTO, INTERMEDIATE_FILTER},
{GRAPHBUILDER_CONNECT, NOT_FILTER},
{BASEFILTER_QUERYINTERFACE, INTERMEDIATE_FILTER, FALSE, TRUE},
{BASEFILTER_ENUMPINS, INTERMEDIATE_FILTER},
{ENUMPINS_NEXT, INTERMEDIATE_FILTER},
{PIN_QUERYDIRECTION, INTERMEDIATE_FILTER},
{PIN_CONNECTEDTO, INTERMEDIATE_FILTER},
{GRAPHBUILDER_CONNECT, NOT_FILTER},
{BASEFILTER_GETSTATE, SOURCE_FILTER, TRUE, FALSE, TRUE},
{BASEFILTER_ENUMPINS, SOURCE_FILTER, FALSE, FALSE, FALSE, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, FALSE, FALSE, FALSE, TRUE},
{PIN_QUERYDIRECTION, SOURCE_FILTER, FALSE, FALSE, FALSE, TRUE},
{PIN_CONNECTEDTO, SOURCE_FILTER, FALSE, FALSE, FALSE, TRUE},
{ENUMPINS_NEXT, SOURCE_FILTER, FALSE, FALSE, FALSE, TRUE},
{END, NOT_FILTER}
}, *current_calls_list;
} *current_calls_list;
int call_no;
static void check_calls_list(const char *func, call_id id, filter_type type)
......@@ -1268,49 +1154,6 @@ static void init_test_filter(test_filter *This, PIN_DIRECTION dir, filter_type t
This->filter_type = type;
}
static void test_CaptureGraphBuilder_RenderStream(void)
{
test_filter source_filter, sink_filter, intermediate_filter;
ICaptureGraphBuilder2 *cgb;
HRESULT hr;
init_test_filter(&source_filter, PINDIR_OUTPUT, SOURCE_FILTER);
init_test_filter(&sink_filter, PINDIR_INPUT, SINK_FILTER);
init_test_filter(&intermediate_filter, PINDIR_OUTPUT, INTERMEDIATE_FILTER);
hr = CoCreateInstance(&CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER,
&IID_ICaptureGraphBuilder2, (void**)&cgb);
ok(hr == S_OK || broken(hr == REGDB_E_CLASSNOTREG),
"couldn't create CaptureGraphBuilder, hr = %08x\n", hr);
if(hr != S_OK) {
win_skip("CaptureGraphBuilder is not registered\n");
return;
}
hr = ICaptureGraphBuilder2_SetFiltergraph(cgb, &GraphBuilder);
ok(hr == S_OK, "SetFiltergraph failed: %08x\n", hr);
trace("RenderStream with category and mediatype test\n");
current_calls_list = renderstream_cat_media;
call_no = 0;
hr = ICaptureGraphBuilder2_RenderStream(cgb, &PIN_CATEGORY_EDS,
&MEDIATYPE_Video, (IUnknown*)&source_filter.IBaseFilter_iface,
NULL, &sink_filter.IBaseFilter_iface);
ok(hr == S_OK, "RenderStream failed: %08x\n", hr);
check_calls_list("test_CaptureGraphBuilder_RenderStream", END, NOT_FILTER);
trace("RenderStream with intermediate filter\n");
current_calls_list = renderstream_intermediate;
call_no = 0;
hr = ICaptureGraphBuilder2_RenderStream(cgb, &PIN_CATEGORY_EDS,
&MEDIATYPE_Video, (IUnknown*)&source_filter.IBaseFilter_iface,
&intermediate_filter.IBaseFilter_iface, &sink_filter.IBaseFilter_iface);
ok(hr == S_OK, "RenderStream failed: %08x\n", hr);
check_calls_list("test_CaptureGraphBuilder_RenderStream", END, NOT_FILTER);
ICaptureGraphBuilder2_Release(cgb);
}
static void test_AviMux_QueryInterface(void)
{
IUnknown *avimux, *unk;
......@@ -2016,7 +1859,6 @@ START_TEST(qcap)
arg_c = winetest_get_mainargs(&arg_v);
test_CaptureGraphBuilder_RenderStream();
test_AviMux_QueryInterface();
test_AviMux(arg_c>2 ? arg_v[2] : NULL);
test_AviCo();
......
......@@ -602,12 +602,14 @@ typedef struct {
IBaseFilter IBaseFilter_iface;
LONG ref;
IPin IPin_iface;
IKsPropertySet IKsPropertySet_iface;
CRITICAL_SECTION cs;
FILTER_STATE state;
IReferenceClock *referenceClock;
FILTER_INFO filterInfo;
AM_MEDIA_TYPE mediaType;
VIDEOINFOHEADER videoInfo;
WAVEFORMATEX audioInfo;
IPin *connectedTo;
IMemInputPin *memInputPin;
IMemAllocator *allocator;
......@@ -621,9 +623,17 @@ typedef struct {
SourceFilter *filter;
} SourceEnumPins;
typedef struct {
IEnumMediaTypes IEnumMediaTypes_iface;
LONG ref;
ULONG index;
SourceFilter *filter;
} SourceEnumMediaTypes;
static const WCHAR sourcePinName[] = {'C','a','p','t','u','r','e',0};
static SourceEnumPins* create_SourceEnumPins(SourceFilter *filter);
static SourceEnumMediaTypes* create_SourceEnumMediaTypes(SourceFilter *filter);
static inline SourceFilter* impl_from_SourceFilter_IBaseFilter(IBaseFilter *iface)
{
......@@ -635,11 +645,21 @@ static inline SourceFilter* impl_from_SourceFilter_IPin(IPin *iface)
return CONTAINING_RECORD(iface, SourceFilter, IPin_iface);
}
static inline SourceFilter* impl_from_SourceFilter_IKsPropertySet(IKsPropertySet *iface)
{
return CONTAINING_RECORD(iface, SourceFilter, IKsPropertySet_iface);
}
static inline SourceEnumPins* impl_from_SourceFilter_IEnumPins(IEnumPins *iface)
{
return CONTAINING_RECORD(iface, SourceEnumPins, IEnumPins_iface);
}
static inline SourceEnumMediaTypes* impl_from_SourceFilter_IEnumMediaTypes(IEnumMediaTypes *iface)
{
return CONTAINING_RECORD(iface, SourceEnumMediaTypes, IEnumMediaTypes_iface);
}
static HRESULT WINAPI SourceFilter_QueryInterface(IBaseFilter *iface, REFIID riid, void **ppv)
{
SourceFilter *This = impl_from_SourceFilter_IBaseFilter(iface);
......@@ -979,6 +999,122 @@ static SourceEnumPins* create_SourceEnumPins(SourceFilter *filter)
return This;
}
static HRESULT WINAPI SourceEnumMediaTypes_QueryInterface(IEnumMediaTypes *iface, REFIID riid, void **ppv)
{
SourceEnumMediaTypes *This = impl_from_SourceFilter_IEnumMediaTypes(iface);
if(IsEqualIID(riid, &IID_IUnknown)) {
*ppv = &This->IEnumMediaTypes_iface;
} else if(IsEqualIID(riid, &IID_IEnumMediaTypes)) {
*ppv = &This->IEnumMediaTypes_iface;
} else {
trace("no interface for %s\n", wine_dbgstr_guid(riid));
*ppv = NULL;
return E_NOINTERFACE;
}
IUnknown_AddRef((IUnknown*)*ppv);
return S_OK;
}
static ULONG WINAPI SourceEnumMediaTypes_AddRef(IEnumMediaTypes *iface)
{
SourceEnumMediaTypes *This = impl_from_SourceFilter_IEnumMediaTypes(iface);
return InterlockedIncrement(&This->ref);
}
static ULONG WINAPI SourceEnumMediaTypes_Release(IEnumMediaTypes *iface)
{
SourceEnumMediaTypes *This = impl_from_SourceFilter_IEnumMediaTypes(iface);
ULONG ref;
ref = InterlockedDecrement(&This->ref);
if (ref == 0)
{
IBaseFilter_Release(&This->filter->IBaseFilter_iface);
CoTaskMemFree(This);
}
return ref;
}
static HRESULT WINAPI SourceEnumMediaTypes_Next(IEnumMediaTypes *iface, ULONG cMediaTypes, AM_MEDIA_TYPE **ppMediaTypes, ULONG *pcFetched)
{
SourceEnumMediaTypes *This = impl_from_SourceFilter_IEnumMediaTypes(iface);
if (!ppMediaTypes)
return E_POINTER;
if (cMediaTypes > 1 && !pcFetched)
return E_INVALIDARG;
if (pcFetched)
*pcFetched = 0;
if (cMediaTypes == 0)
return S_OK;
if (This->index == 0) {
ppMediaTypes[0] = CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE));
if (ppMediaTypes[0]) {
*ppMediaTypes[0] = This->filter->mediaType;
ppMediaTypes[0]->pbFormat = CoTaskMemAlloc(This->filter->mediaType.cbFormat);
if (ppMediaTypes[0]->pbFormat) {
memcpy(ppMediaTypes[0]->pbFormat, This->filter->mediaType.pbFormat, This->filter->mediaType.cbFormat);
++This->index;
if (pcFetched)
*pcFetched = 1;
return S_OK;
}
CoTaskMemFree(ppMediaTypes[0]);
}
return E_OUTOFMEMORY;
}
return S_FALSE;
}
static HRESULT WINAPI SourceEnumMediaTypes_Skip(IEnumMediaTypes *iface, ULONG cMediaTypes)
{
SourceEnumMediaTypes *This = impl_from_SourceFilter_IEnumMediaTypes(iface);
This->index += cMediaTypes;
if (This->index >= 1)
return S_FALSE;
return S_OK;
}
static HRESULT WINAPI SourceEnumMediaTypes_Reset(IEnumMediaTypes *iface)
{
SourceEnumMediaTypes *This = impl_from_SourceFilter_IEnumMediaTypes(iface);
This->index = 0;
return S_OK;
}
static HRESULT WINAPI SourceEnumMediaTypes_Clone(IEnumMediaTypes *iface, IEnumMediaTypes **ppEnum)
{
SourceEnumMediaTypes *This = impl_from_SourceFilter_IEnumMediaTypes(iface);
SourceEnumMediaTypes *clone = create_SourceEnumMediaTypes(This->filter);
if (clone == NULL)
return E_OUTOFMEMORY;
clone->index = This->index;
return S_OK;
}
static const IEnumMediaTypesVtbl SourceEnumMediaTypesVtbl = {
SourceEnumMediaTypes_QueryInterface,
SourceEnumMediaTypes_AddRef,
SourceEnumMediaTypes_Release,
SourceEnumMediaTypes_Next,
SourceEnumMediaTypes_Skip,
SourceEnumMediaTypes_Reset,
SourceEnumMediaTypes_Clone
};
static SourceEnumMediaTypes* create_SourceEnumMediaTypes(SourceFilter *filter)
{
SourceEnumMediaTypes *This;
This = CoTaskMemAlloc(sizeof(*This));
if (This == NULL) {
return NULL;
}
This->IEnumMediaTypes_iface.lpVtbl = &SourceEnumMediaTypesVtbl;
This->ref = 1;
This->index = 0;
This->filter = filter;
IBaseFilter_AddRef(&filter->IBaseFilter_iface);
return This;
}
static HRESULT WINAPI SourcePin_QueryInterface(IPin *iface, REFIID riid, void **ppv)
{
SourceFilter *This = impl_from_SourceFilter_IPin(iface);
......@@ -986,6 +1122,8 @@ static HRESULT WINAPI SourcePin_QueryInterface(IPin *iface, REFIID riid, void **
*ppv = &This->IPin_iface;
} else if(IsEqualIID(riid, &IID_IPin)) {
*ppv = &This->IPin_iface;
} else if(IsEqualIID(riid, &IID_IKsPropertySet)) {
*ppv = &This->IKsPropertySet_iface;
} else {
trace("no interface for %s\n", wine_dbgstr_guid(riid));
*ppv = NULL;
......@@ -1011,6 +1149,7 @@ static HRESULT WINAPI SourcePin_Connect(IPin *iface, IPin *pReceivePin, const AM
{
SourceFilter *This = impl_from_SourceFilter_IPin(iface);
HRESULT hr;
if (pmt && !IsEqualGUID(&pmt->majortype, &GUID_NULL) && !IsEqualGUID(&pmt->majortype, &MEDIATYPE_Video))
return VFW_E_TYPE_NOT_ACCEPTED;
if (pmt && !IsEqualGUID(&pmt->subtype, &GUID_NULL) && !IsEqualGUID(&pmt->subtype, &MEDIASUBTYPE_RGB32))
......@@ -1168,7 +1307,14 @@ static HRESULT WINAPI SourcePin_QueryAccept(IPin *iface, const AM_MEDIA_TYPE *pm
static HRESULT WINAPI SourcePin_EnumMediaTypes(IPin *iface, IEnumMediaTypes **ppEnum)
{
return VFW_E_NOT_CONNECTED;
SourceFilter *This = impl_from_SourceFilter_IPin(iface);
SourceEnumMediaTypes *sourceEnumMediaTypes = create_SourceEnumMediaTypes(This);
if (sourceEnumMediaTypes) {
*ppEnum = &sourceEnumMediaTypes->IEnumMediaTypes_iface;
return S_OK;
}
else
return E_OUTOFMEMORY;
}
static HRESULT WINAPI SourcePin_QueryInternalConnections(IPin *iface, IPin **apPin, ULONG *nPin)
......@@ -1218,6 +1364,75 @@ static const IPinVtbl SourcePinVtbl = {
SourcePin_NewSegment
};
static HRESULT WINAPI SourceKSP_QueryInterface(IKsPropertySet *iface, REFIID riid, LPVOID *ppv)
{
SourceFilter *This = impl_from_SourceFilter_IKsPropertySet(iface);
return IPin_QueryInterface(&This->IPin_iface, riid, ppv);
}
static ULONG WINAPI SourceKSP_AddRef(IKsPropertySet *iface)
{
SourceFilter *This = impl_from_SourceFilter_IKsPropertySet(iface);
return IBaseFilter_AddRef(&This->IBaseFilter_iface);
}
static ULONG WINAPI SourceKSP_Release(IKsPropertySet *iface)
{
SourceFilter *This = impl_from_SourceFilter_IKsPropertySet(iface);
return IBaseFilter_Release(&This->IBaseFilter_iface);
}
static HRESULT WINAPI SourceKSP_Set(IKsPropertySet *iface, REFGUID guidPropSet, DWORD dwPropID,
LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData)
{
SourceFilter *This = impl_from_SourceFilter_IKsPropertySet(iface);
trace("(%p)->(%s, %u, %p, %u, %p, %u): stub\n", This, wine_dbgstr_guid(guidPropSet),
dwPropID, pInstanceData, cbInstanceData, pPropData, cbPropData);
return E_NOTIMPL;
}
static HRESULT WINAPI SourceKSP_Get(IKsPropertySet *iface, REFGUID guidPropSet, DWORD dwPropID,
LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData,
DWORD cbPropData, DWORD *pcbReturned)
{
SourceFilter *This = impl_from_SourceFilter_IKsPropertySet(iface);
trace("(%p)->(%s, %u, %p, %u, %p, %u, %p)\n", This, wine_dbgstr_guid(guidPropSet),
dwPropID, pInstanceData, cbInstanceData, pPropData, cbPropData, pcbReturned);
if (IsEqualIID(guidPropSet, &AMPROPSETID_Pin)) {
if (pcbReturned)
*pcbReturned = sizeof(GUID);
if (pPropData) {
LPGUID guid = pPropData;
if (cbPropData >= sizeof(GUID))
*guid = PIN_CATEGORY_CAPTURE;
} else {
if (!pcbReturned)
return E_POINTER;
}
return S_OK;
}
return E_PROP_SET_UNSUPPORTED;
}
static HRESULT WINAPI SourceKSP_QuerySupported(IKsPropertySet *iface, REFGUID guidPropSet,
DWORD dwPropID, DWORD *pTypeSupport)
{
SourceFilter *This = impl_from_SourceFilter_IKsPropertySet(iface);
trace("(%p)->(%s, %u, %p): stub\n", This, wine_dbgstr_guid(guidPropSet),
dwPropID, pTypeSupport);
return E_NOTIMPL;
}
static const IKsPropertySetVtbl SourceKSPVtbl =
{
SourceKSP_QueryInterface,
SourceKSP_AddRef,
SourceKSP_Release,
SourceKSP_Set,
SourceKSP_Get,
SourceKSP_QuerySupported
};
static SourceFilter* create_SourceFilter(void)
{
SourceFilter *This = NULL;
......@@ -1227,35 +1442,68 @@ static SourceFilter* create_SourceFilter(void)
This->IBaseFilter_iface.lpVtbl = &SourceFilterVtbl;
This->ref = 1;
This->IPin_iface.lpVtbl = &SourcePinVtbl;
This->IKsPropertySet_iface.lpVtbl = &SourceKSPVtbl;
InitializeCriticalSection(&This->cs);
This->mediaType.majortype = MEDIATYPE_Video;
This->mediaType.subtype = MEDIASUBTYPE_RGB32;
This->mediaType.bFixedSizeSamples = FALSE;
This->mediaType.bTemporalCompression = FALSE;
This->mediaType.lSampleSize = 0;
This->mediaType.formattype = FORMAT_VideoInfo;
This->mediaType.pUnk = NULL;
This->mediaType.cbFormat = sizeof(VIDEOINFOHEADER);
This->mediaType.pbFormat = (BYTE*) &This->videoInfo;
This->videoInfo.dwBitRate = 1000000;
This->videoInfo.dwBitErrorRate = 0;
This->videoInfo.AvgTimePerFrame = 400000;
This->videoInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
This->videoInfo.bmiHeader.biWidth = 10;
This->videoInfo.bmiHeader.biHeight = 10;
This->videoInfo.bmiHeader.biPlanes = 1;
This->videoInfo.bmiHeader.biBitCount = 32;
This->videoInfo.bmiHeader.biCompression = BI_RGB;
This->videoInfo.bmiHeader.biSizeImage = 0;
This->videoInfo.bmiHeader.biXPelsPerMeter = 96;
This->videoInfo.bmiHeader.biYPelsPerMeter = 96;
This->videoInfo.bmiHeader.biClrUsed = 0;
This->videoInfo.bmiHeader.biClrImportant = 0;
return This;
}
return NULL;
}
static SourceFilter* create_video_SourceFilter(void)
{
SourceFilter *This = create_SourceFilter();
if (!This)
return NULL;
This->mediaType.majortype = MEDIATYPE_Video;
This->mediaType.subtype = MEDIASUBTYPE_RGB32;
This->mediaType.bFixedSizeSamples = FALSE;
This->mediaType.bTemporalCompression = FALSE;
This->mediaType.lSampleSize = 0;
This->mediaType.formattype = FORMAT_VideoInfo;
This->mediaType.pUnk = NULL;
This->mediaType.cbFormat = sizeof(VIDEOINFOHEADER);
This->mediaType.pbFormat = (BYTE*) &This->videoInfo;
This->videoInfo.dwBitRate = 1000000;
This->videoInfo.dwBitErrorRate = 0;
This->videoInfo.AvgTimePerFrame = 400000;
This->videoInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
This->videoInfo.bmiHeader.biWidth = 10;
This->videoInfo.bmiHeader.biHeight = 10;
This->videoInfo.bmiHeader.biPlanes = 1;
This->videoInfo.bmiHeader.biBitCount = 32;
This->videoInfo.bmiHeader.biCompression = BI_RGB;
This->videoInfo.bmiHeader.biSizeImage = 0;
This->videoInfo.bmiHeader.biXPelsPerMeter = 96;
This->videoInfo.bmiHeader.biYPelsPerMeter = 96;
This->videoInfo.bmiHeader.biClrUsed = 0;
This->videoInfo.bmiHeader.biClrImportant = 0;
return This;
}
static SourceFilter* create_audio_SourceFilter(void)
{
SourceFilter *This = create_SourceFilter();
if (!This)
return NULL;
This->mediaType.majortype = MEDIATYPE_Audio;
This->mediaType.subtype = MEDIASUBTYPE_PCM;
This->mediaType.bFixedSizeSamples = FALSE;
This->mediaType.bTemporalCompression = FALSE;
This->mediaType.lSampleSize = 0;
This->mediaType.formattype = FORMAT_WaveFormatEx;
This->mediaType.pUnk = NULL;
This->mediaType.cbFormat = sizeof(WAVEFORMATEX);
This->mediaType.pbFormat = (BYTE*) &This->audioInfo;
This->audioInfo.wFormatTag = WAVE_FORMAT_PCM;
This->audioInfo.nChannels = 1;
This->audioInfo.nSamplesPerSec = 8000;
This->audioInfo.nAvgBytesPerSec = 16000;
This->audioInfo.nBlockAlign = 2;
This->audioInfo.wBitsPerSample = 16;
This->audioInfo.cbSize = 0;
return This;
}
static BOOL has_interface(IUnknown *unknown, REFIID uuid)
{
HRESULT hr;
......@@ -1320,7 +1568,7 @@ static void test_smart_tee_filter_in_graph(IBaseFilter *smartTeeFilter, IPin *in
hr = IGraphBuilder_Connect(graphBuilder, previewPin, &previewSinkFilter->IPin_iface);
ok(hr == VFW_E_NOT_CONNECTED, "connecting Preview pin without first connecting Input pin returned 0x%08x\n", hr);
sourceFilter = create_SourceFilter();
sourceFilter = create_video_SourceFilter();
if (sourceFilter == NULL) {
skip("couldn't create source filter\n");
goto end;
......@@ -1556,7 +1804,7 @@ end:
static void test_smart_tee_filter_aggregation(void)
{
SourceFilter *sourceFilter = create_SourceFilter();
SourceFilter *sourceFilter = create_video_SourceFilter();
if (sourceFilter) {
IUnknown *unknown = NULL;
HRESULT hr = CoCreateInstance(&CLSID_SmartTee, (IUnknown*)&sourceFilter->IBaseFilter_iface,
......@@ -1569,6 +1817,207 @@ static void test_smart_tee_filter_aggregation(void)
ok(0, "out of memory allocating SourceFilter for test\n");
}
static HRESULT get_connected_filter_classid(IPin *pin, GUID *guid)
{
IPin *connectedPin = NULL;
PIN_INFO connectedPinInfo;
HRESULT hr = IPin_ConnectedTo(pin, &connectedPin);
ok(SUCCEEDED(hr), "IPin_ConnectedTo() failed, hr=0x%08x\n", hr);
if (FAILED(hr))
goto end;
hr = IPin_QueryPinInfo(connectedPin, &connectedPinInfo);
ok(SUCCEEDED(hr), "IPin_QueryPinInfo() failed, hr=0x%08x\n", hr);
if (FAILED(hr))
goto end;
if (connectedPinInfo.pFilter) {
hr = IBaseFilter_GetClassID(connectedPinInfo.pFilter, guid);
ok(SUCCEEDED(hr), "IBaseFilter_GetClassID() failed, hr=0x%08x\n", hr);
IBaseFilter_Release(connectedPinInfo.pFilter);
}
end:
if (connectedPin)
IPin_Release(connectedPin);
return hr;
}
static void test_audio_preview(ICaptureGraphBuilder2 *captureGraphBuilder, IGraphBuilder *graphBuilder,
SourceFilter *audioSource, IBaseFilter *nullRenderer)
{
GUID clsid;
HRESULT hr = ICaptureGraphBuilder2_RenderStream(captureGraphBuilder, &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Audio,
(IUnknown*)&audioSource->IBaseFilter_iface, NULL, nullRenderer);
ok(hr == VFW_S_NOPREVIEWPIN, "ICaptureGraphBuilder2_RenderStream() returned hr=0x%08x\n", hr);
hr = get_connected_filter_classid(&audioSource->IPin_iface, &clsid);
if (FAILED(hr))
return;
ok(IsEqualIID(&clsid, &CLSID_SmartTee), "unexpected connected filter %s\n",
wine_dbgstr_guid(&clsid));
}
static void test_audio_capture(ICaptureGraphBuilder2 *captureGraphBuilder, IGraphBuilder *graphBuilder,
SourceFilter *audioSource, IBaseFilter *nullRenderer)
{
GUID clsid;
HRESULT hr = ICaptureGraphBuilder2_RenderStream(captureGraphBuilder, &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio,
(IUnknown*)&audioSource->IBaseFilter_iface, NULL, nullRenderer);
ok(hr == S_OK, "ICaptureGraphBuilder2_RenderStream() returned hr=0x%08x\n", hr);
hr = get_connected_filter_classid(&audioSource->IPin_iface, &clsid);
if (FAILED(hr))
return;
ok(IsEqualIID(&clsid, &CLSID_SmartTee), "unexpected connected filter %s\n",
wine_dbgstr_guid(&clsid));
}
static void test_video_preview(ICaptureGraphBuilder2 *captureGraphBuilder, IGraphBuilder *graphBuilder,
SourceFilter *videoSource, IBaseFilter *nullRenderer)
{
GUID clsid;
HRESULT hr = ICaptureGraphBuilder2_RenderStream(captureGraphBuilder, &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
(IUnknown*)&videoSource->IBaseFilter_iface, NULL, nullRenderer);
ok(hr == VFW_S_NOPREVIEWPIN, "ICaptureGraphBuilder2_RenderStream() failed, hr=0x%08x\n", hr);
hr = get_connected_filter_classid(&videoSource->IPin_iface, &clsid);
if (FAILED(hr))
return;
ok(IsEqualIID(&clsid, &CLSID_SmartTee), "unexpected connected filter %s\n",
wine_dbgstr_guid(&clsid));
}
static void test_video_capture(ICaptureGraphBuilder2 *captureGraphBuilder, IGraphBuilder *graphBuilder,
SourceFilter *videoSource, IBaseFilter *nullRenderer)
{
GUID clsid;
HRESULT hr = ICaptureGraphBuilder2_RenderStream(captureGraphBuilder, &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,
(IUnknown*)&videoSource->IBaseFilter_iface, NULL, nullRenderer);
ok(hr == S_OK, "ICaptureGraphBuilder2_RenderStream() failed, hr=0x%08x\n", hr);
hr = get_connected_filter_classid(&videoSource->IPin_iface, &clsid);
if (FAILED(hr))
return;
ok(IsEqualIID(&clsid, &CLSID_SmartTee), "unexpected connected filter %s\n",
wine_dbgstr_guid(&clsid));
}
static void test_audio_smart_tee_filter_auto_insertion(
void (*test_function)(ICaptureGraphBuilder2 *cgb, IGraphBuilder *gb,
SourceFilter *audioSource, IBaseFilter *nullRenderer))
{
HRESULT hr;
ICaptureGraphBuilder2 *captureGraphBuilder = NULL;
IGraphBuilder *graphBuilder = NULL;
IBaseFilter *nullRenderer = NULL;
SourceFilter *audioSource = NULL;
hr = CoCreateInstance(&CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER,
&IID_ICaptureGraphBuilder2, (void**)&captureGraphBuilder);
ok(SUCCEEDED(hr), "couldn't create capture graph builder, hr=0x%08x\n", hr);
if (FAILED(hr))
goto end;
hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IGraphBuilder,
(LPVOID*)&graphBuilder);
ok(SUCCEEDED(hr), "couldn't create graph builder, hr=0x%08x\n", hr);
if (FAILED(hr))
goto end;
hr = ICaptureGraphBuilder2_SetFiltergraph(captureGraphBuilder, graphBuilder);
ok(SUCCEEDED(hr), "ICaptureGraphBuilder2_SetFilterGraph() failed, 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(graphBuilder, nullRenderer, NULL);
ok(SUCCEEDED(hr), "IGraphBuilder_AddFilter() failed, hr=0x%08x\n", hr);
if (FAILED(hr))
goto end;
audioSource = create_audio_SourceFilter();
ok(audioSource != NULL, "couldn't create audio source\n");
if (audioSource == NULL)
goto end;
hr = IGraphBuilder_AddFilter(graphBuilder, &audioSource->IBaseFilter_iface, NULL);
ok(SUCCEEDED(hr), "IGraphBuilder_AddFilter() failed, hr=0x%08x\n", hr);
if (FAILED(hr))
goto end;
test_function(captureGraphBuilder, graphBuilder, audioSource, nullRenderer);
end:
if (nullRenderer)
IBaseFilter_Release(nullRenderer);
if (audioSource)
IBaseFilter_Release(&audioSource->IBaseFilter_iface);
if (captureGraphBuilder)
ICaptureGraphBuilder2_Release(captureGraphBuilder);
if (graphBuilder)
IGraphBuilder_Release(graphBuilder);
}
static void test_video_smart_tee_filter_auto_insertion(
void (*test_function)(ICaptureGraphBuilder2 *cgb, IGraphBuilder *gb,
SourceFilter *videoSource, IBaseFilter *nullRenderer))
{
HRESULT hr;
ICaptureGraphBuilder2 *captureGraphBuilder = NULL;
IGraphBuilder *graphBuilder = NULL;
IBaseFilter *nullRenderer = NULL;
SourceFilter *videoSource = NULL;
hr = CoCreateInstance(&CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER,
&IID_ICaptureGraphBuilder2, (void**)&captureGraphBuilder);
ok(SUCCEEDED(hr), "couldn't create capture graph builder, hr=0x%08x\n", hr);
if (FAILED(hr))
goto end;
hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IGraphBuilder,
(LPVOID*)&graphBuilder);
ok(SUCCEEDED(hr), "couldn't create graph builder, hr=0x%08x\n", hr);
if (FAILED(hr))
goto end;
hr = ICaptureGraphBuilder2_SetFiltergraph(captureGraphBuilder, graphBuilder);
ok(SUCCEEDED(hr), "ICaptureGraphBuilder2_SetFilterGraph() failed, 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(graphBuilder, nullRenderer, NULL);
ok(SUCCEEDED(hr), "IGraphBuilder_AddFilter() failed, hr=0x%08x\n", hr);
if (FAILED(hr))
goto end;
videoSource = create_video_SourceFilter();
ok(videoSource != NULL, "couldn't create audio source\n");
if (videoSource == NULL)
goto end;
hr = IGraphBuilder_AddFilter(graphBuilder, &videoSource->IBaseFilter_iface, NULL);
ok(SUCCEEDED(hr), "IGraphBuilder_AddFilter() failed, hr=0x%08x\n", hr);
if (FAILED(hr))
goto end;
test_function(captureGraphBuilder, graphBuilder, videoSource, nullRenderer);
end:
if (nullRenderer)
IBaseFilter_Release(nullRenderer);
if (videoSource)
IBaseFilter_Release(&videoSource->IBaseFilter_iface);
if (captureGraphBuilder)
ICaptureGraphBuilder2_Release(captureGraphBuilder);
if (graphBuilder)
IGraphBuilder_Release(graphBuilder);
}
START_TEST(smartteefilter)
{
if (SUCCEEDED(CoInitialize(NULL)))
......@@ -1577,6 +2026,13 @@ START_TEST(smartteefilter)
if (event) {
test_smart_tee_filter_aggregation();
test_smart_tee_filter();
test_audio_smart_tee_filter_auto_insertion(test_audio_preview);
test_audio_smart_tee_filter_auto_insertion(test_audio_capture);
test_video_smart_tee_filter_auto_insertion(test_video_preview);
test_video_smart_tee_filter_auto_insertion(test_video_capture);
CloseHandle(event);
} else
skip("CreateEvent failed, error=%u\n", GetLastError());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment