Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/qedit/nullrenderer.c | 24 ++ dlls/qedit/tests/nullrenderer.c | 521 ++++++++++++++++++++++++++++++++ 2 files changed, 545 insertions(+)
diff --git a/dlls/qedit/nullrenderer.c b/dlls/qedit/nullrenderer.c index c64d5e2..f1dbea2 100644 --- a/dlls/qedit/nullrenderer.c +++ b/dlls/qedit/nullrenderer.c @@ -29,6 +29,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(qedit); struct null_renderer { struct strmbase_renderer renderer; + HANDLE run_event; };
static struct null_renderer *impl_from_strmbase_renderer(struct strmbase_renderer *iface) @@ -41,7 +42,14 @@ static HRESULT WINAPI NullRenderer_DoRenderSample(struct strmbase_renderer *ifac struct null_renderer *filter = impl_from_strmbase_renderer(iface);
if (filter->renderer.filter.state == State_Paused) + { + const HANDLE events[2] = { filter->run_event, filter->renderer.flush_event }; + SetEvent(filter->renderer.state_event); + LeaveCriticalSection(&filter->renderer.csRenderLock); + WaitForMultipleObjects(2, events, FALSE, INFINITE); + EnterCriticalSection(&filter->renderer.csRenderLock); + }
return S_OK; } @@ -56,14 +64,29 @@ static void null_renderer_destroy(struct strmbase_renderer *iface) { struct null_renderer *filter = impl_from_strmbase_renderer(iface);
+ CloseHandle(filter->run_event); strmbase_renderer_cleanup(&filter->renderer); free(filter); }
+static void null_renderer_start_stream(struct strmbase_renderer *iface) +{ + struct null_renderer *filter = impl_from_strmbase_renderer(iface); + SetEvent(filter->run_event); +} + +static void null_renderer_stop_stream(struct strmbase_renderer *iface) +{ + struct null_renderer *filter = impl_from_strmbase_renderer(iface); + ResetEvent(filter->run_event); +} + static const struct strmbase_renderer_ops renderer_ops = { .pfnCheckMediaType = NullRenderer_CheckMediaType, .pfnDoRenderSample = NullRenderer_DoRenderSample, + .renderer_start_stream = null_renderer_start_stream, + .renderer_stop_stream = null_renderer_stop_stream, .renderer_destroy = null_renderer_destroy, };
@@ -75,6 +98,7 @@ HRESULT null_renderer_create(IUnknown *outer, IUnknown **out) return E_OUTOFMEMORY;
strmbase_renderer_init(&object->renderer, outer, &CLSID_NullRenderer, L"In", &renderer_ops); + object->run_event = CreateEventW(NULL, TRUE, FALSE, NULL);
TRACE("Created null renderer %p.\n", object); *out = &object->renderer.filter.IUnknown_inner; diff --git a/dlls/qedit/tests/nullrenderer.c b/dlls/qedit/tests/nullrenderer.c index 8f26e47..01fda46 100644 --- a/dlls/qedit/tests/nullrenderer.c +++ b/dlls/qedit/tests/nullrenderer.c @@ -20,6 +20,7 @@
#define COBJMACROS #include "dshow.h" +#include "wine/strmbase.h" #include "wine/test.h"
static IBaseFilter *create_null_renderer(void) @@ -420,6 +421,525 @@ static void test_enum_media_types(void) ok(!ref, "Got outstanding refcount %d.\n", ref); }
+struct testfilter +{ + struct strmbase_filter filter; + struct strmbase_source source; + IMediaSeeking IMediaSeeking_iface; +}; + +static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface) +{ + return CONTAINING_RECORD(iface, struct testfilter, filter); +} + +static struct strmbase_pin *testfilter_get_pin(struct strmbase_filter *iface, unsigned int index) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + + return index ? NULL : &filter->source.pin; +} + +static void testfilter_destroy(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + + strmbase_source_cleanup(&filter->source); + strmbase_filter_cleanup(&filter->filter); +} + +static HRESULT testfilter_init_stream(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + HRESULT hr; + + hr = BaseOutputPinImpl_Active(&filter->source); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + return hr; +} + +static HRESULT testfilter_cleanup_stream(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + HRESULT hr; + + hr = BaseOutputPinImpl_Inactive(&filter->source); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + return hr; +} + +static const struct strmbase_filter_ops testfilter_ops = +{ + .filter_get_pin = testfilter_get_pin, + .filter_destroy = testfilter_destroy, + .filter_init_stream = testfilter_init_stream, + .filter_cleanup_stream = testfilter_cleanup_stream +}; + +static HRESULT testsource_get_media_type(struct strmbase_pin *iface, unsigned int index, AM_MEDIA_TYPE *mt) +{ + static const VIDEOINFOHEADER source_format = + { + .bmiHeader.biSize = sizeof(BITMAPINFOHEADER), + .bmiHeader.biWidth = 32, + .bmiHeader.biHeight = -16, + .bmiHeader.biPlanes = 1, + .bmiHeader.biBitCount = 16, + .bmiHeader.biCompression = BI_RGB, + .bmiHeader.biSizeImage = 32 * 16 * 2 + }; + + if (index) + return S_FALSE; + + mt->majortype = MEDIATYPE_Video; + mt->subtype = MEDIASUBTYPE_RGB565; + mt->bFixedSizeSamples = TRUE; + mt->bTemporalCompression = FALSE; + mt->lSampleSize = source_format.bmiHeader.biSizeImage; + mt->formattype = FORMAT_VideoInfo; + mt->pUnk = NULL; + mt->cbFormat = sizeof(source_format); + mt->pbFormat = CoTaskMemAlloc(mt->cbFormat); + memcpy(mt->pbFormat, &source_format, mt->cbFormat); + return S_OK; +} + +static HRESULT testsource_query_interface(struct strmbase_pin *iface, REFIID iid, void **out) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->filter); + + if (IsEqualGUID(iid, &IID_IMediaSeeking)) + *out = &filter->IMediaSeeking_iface; + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*out); + return S_OK; +} + +static HRESULT WINAPI testsource_DecideBufferSize(struct strmbase_source *iface, + IMemAllocator *allocator, ALLOCATOR_PROPERTIES *requested) +{ + ALLOCATOR_PROPERTIES actual; + + if (!requested->cbAlign) + requested->cbAlign = 1; + + if (requested->cbBuffer < 32 * 16 * 2) + requested->cbBuffer = 32 * 16 * 2; + + if (!requested->cBuffers) + requested->cBuffers = 1; + + return IMemAllocator_SetProperties(allocator, requested, &actual); +} + +static HRESULT WINAPI testsource_DecideAllocator(struct strmbase_source *iface, + IMemInputPin *peer, IMemAllocator **allocator) +{ + ALLOCATOR_PROPERTIES props = {0}; + HRESULT hr; + + hr = BaseOutputPinImpl_InitAllocator(iface, allocator); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + IMemInputPin_GetAllocatorRequirements(peer, &props); + hr = testsource_DecideBufferSize(iface, *allocator, &props); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + return IMemInputPin_NotifyAllocator(peer, *allocator, FALSE); +} + +static const struct strmbase_source_ops testsource_ops = +{ + .base.pin_get_media_type = testsource_get_media_type, + .base.pin_query_interface = testsource_query_interface, + .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection, + .pfnDecideBufferSize = testsource_DecideBufferSize, + .pfnDecideAllocator = testsource_DecideAllocator +}; + +static struct testfilter *impl_from_IMediaSeeking(IMediaSeeking *iface) +{ + return CONTAINING_RECORD(iface, struct testfilter, IMediaSeeking_iface); +} + +static HRESULT WINAPI testseek_QueryInterface(IMediaSeeking *iface, REFIID iid, void **out) +{ + struct testfilter *filter = impl_from_IMediaSeeking(iface); + return IUnknown_QueryInterface(filter->filter.outer_unk, iid, out); +} + +static ULONG WINAPI testseek_AddRef(IMediaSeeking *iface) +{ + struct testfilter *filter = impl_from_IMediaSeeking(iface); + return IUnknown_AddRef(filter->filter.outer_unk); +} + +static ULONG WINAPI testseek_Release(IMediaSeeking *iface) +{ + struct testfilter *filter = impl_from_IMediaSeeking(iface); + return IUnknown_Release(filter->filter.outer_unk); +} + +static HRESULT WINAPI testseek_GetCapabilities(IMediaSeeking *iface, DWORD *caps) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_CheckCapabilities(IMediaSeeking *iface, DWORD *caps) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_IsFormatSupported(IMediaSeeking *iface, const GUID *format) +{ + if (winetest_debug > 1) trace("IMediaSeeking_IsFormatSupported(%s)\n", wine_dbgstr_guid(format)); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_QueryPreferredFormat(IMediaSeeking *iface, GUID *format) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_GetTimeFormat(IMediaSeeking *iface, GUID *format) +{ + if (winetest_debug > 1) trace("IMediaSeeking_GetTimeFormat()\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_IsUsingTimeFormat(IMediaSeeking *iface, const GUID *format) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_SetTimeFormat(IMediaSeeking *iface, const GUID *format) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_GetDuration(IMediaSeeking *iface, LONGLONG *duration) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_GetStopPosition(IMediaSeeking *iface, LONGLONG *stop) +{ + if (winetest_debug > 1) trace("IMediaSeeking_GetStopPosition()\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_GetCurrentPosition(IMediaSeeking *iface, LONGLONG *current) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_ConvertTimeFormat(IMediaSeeking *iface, LONGLONG *target, + const GUID *target_format, LONGLONG source, const GUID *source_format) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_SetPositions(IMediaSeeking *iface, LONGLONG *current, + DWORD current_flags, LONGLONG *stop, DWORD stop_flags ) +{ + if (winetest_debug > 1) trace("IMediaSeeking_SetPositions()\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_GetPositions(IMediaSeeking *iface, LONGLONG *current, LONGLONG *stop) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_GetAvailable(IMediaSeeking *iface, LONGLONG *earliest, LONGLONG *latest) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_SetRate(IMediaSeeking *iface, double rate) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_GetRate(IMediaSeeking *iface, double *rate) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testseek_GetPreroll(IMediaSeeking *iface, LONGLONG *preroll) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static const IMediaSeekingVtbl testseek_vtbl = +{ + testseek_QueryInterface, + testseek_AddRef, + testseek_Release, + testseek_GetCapabilities, + testseek_CheckCapabilities, + testseek_IsFormatSupported, + testseek_QueryPreferredFormat, + testseek_GetTimeFormat, + testseek_IsUsingTimeFormat, + testseek_SetTimeFormat, + testseek_GetDuration, + testseek_GetStopPosition, + testseek_GetCurrentPosition, + testseek_ConvertTimeFormat, + testseek_SetPositions, + testseek_GetPositions, + testseek_GetAvailable, + testseek_SetRate, + testseek_GetRate, + testseek_GetPreroll +}; + +static void testfilter_init(struct testfilter *filter) +{ + static const GUID clsid = {0xabacab}; + + strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); + strmbase_source_init(&filter->source, &filter->filter, L"", &testsource_ops); + filter->IMediaSeeking_iface.lpVtbl = &testseek_vtbl; +} + +struct frame_thread_params +{ + IMemInputPin *sink; + IMediaSample *sample; +}; + +static DWORD WINAPI frame_thread(void *arg) +{ + struct frame_thread_params *params = arg; + HRESULT hr; + + if (winetest_debug > 1) trace("%04x: Sending frame.\n", GetCurrentThreadId()); + hr = IMemInputPin_Receive(params->sink, params->sample); + if (winetest_debug > 1) trace("%04x: Returned %#x.\n", GetCurrentThreadId(), hr); + IMediaSample_Release(params->sample); + free(params); + return hr; +} + +static HANDLE send_frame(IMemInputPin *sink) +{ + struct frame_thread_params *params = malloc(sizeof(*params)); + REFERENCE_TIME start_time, end_time; + IMemAllocator *allocator; + IMediaSample *sample; + HANDLE thread; + HRESULT hr; + BYTE *data; + + hr = IMemInputPin_GetAllocator(sink, &allocator); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0); + if (hr == VFW_E_NOT_COMMITTED) + { + IMemAllocator_Commit(allocator); + hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0); + } + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaSample_GetPointer(sample, &data); + ok(hr == S_OK, "Got hr %#x.\n", hr); + memset(data, 0xaa, 32 * 16 * 2); + + hr = IMediaSample_SetActualDataLength(sample, 32 * 16 * 2); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + start_time = 0; + end_time = 400000; + hr = IMediaSample_SetTime(sample, &start_time, &end_time); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + params->sink = sink; + params->sample = sample; + thread = CreateThread(NULL, 0, frame_thread, params, 0, NULL); + + IMemAllocator_Release(allocator); + return thread; +} + +static HRESULT join_thread_(unsigned line, HANDLE thread) +{ + DWORD ret; + ok_(__FILE__, line)(!WaitForSingleObject(thread, 1000), "Wait failed.\n"); + GetExitCodeThread(thread, &ret); + CloseHandle(thread); + return ret; +} +#define join_thread(thread) join_thread_(__LINE__, thread) + +static void test_state(void) +{ + IBaseFilter *renderer = create_null_renderer(); + struct testfilter testfilter; + IMediaControl *control; + IGraphBuilder *graph; + OAFilterState state; + IMemInputPin *input; + IEnumPins *pins; + HANDLE thread; + HRESULT hr; + IPin *pin; + + testfilter_init(&testfilter); + + hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, &IID_IGraphBuilder, (void**)&graph); + ok(hr == S_OK, "Failed to create Filter Graph: %08x.\n", hr); + hr = IGraphBuilder_AddFilter(graph, &testfilter.filter.IBaseFilter_iface, L"Source"); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IGraphBuilder_AddFilter(graph, renderer, L"NullRenderer"); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IBaseFilter_EnumPins(renderer, &pins); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IEnumPins_Next(pins, 1, &pin, NULL); + ok(hr == S_OK, "Got hr %#x.\n", hr); + IEnumPins_Release(pins); + + hr = IGraphBuilder_Connect(graph, &testfilter.source.pin.IPin_iface, pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IPin_QueryInterface(testfilter.source.pin.peer, &IID_IMemInputPin, (void**)&input); + ok(hr == S_OK, "QueryInterface for IID_IMemInputPin failed: %08x\n", hr); + hr = IGraphBuilder_QueryInterface(graph, &IID_IMediaControl, (void**)&control); + ok(hr == S_OK, "QueryInterface for IID_IMediaControl failed: %08x\n", hr); + IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface); + IBaseFilter_Release(renderer); + IGraphBuilder_Release(graph); + IPin_Release(pin); + + /* Pause should block */ + thread = send_frame(input); + hr = join_thread(thread); + todo_wine ok(hr == E_FAIL, "Got hr %#x.\n", hr); + + hr = IMediaControl_Pause(control); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#x.\n", hr); + + thread = send_frame(input); + + hr = IMediaControl_GetState(control, 1000, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(WaitForSingleObject(thread, 100) == WAIT_TIMEOUT, "Thread should block in Receive().\n"); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = join_thread(thread); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaControl_Pause(control); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#x.\n", hr); + + thread = send_frame(input); + + hr = IMediaControl_GetState(control, 1000, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(WaitForSingleObject(thread, 100) == WAIT_TIMEOUT, "Thread should block in Receive().\n"); + + hr = IMediaControl_Run(control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = join_thread(thread); + ok(hr == S_OK, "Got hr %#x.\n", hr); + thread = send_frame(input); + hr = join_thread(thread); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaControl_Pause(control); + todo_wine ok(hr == S_FALSE, "Got hr %#x.\n", hr); + hr = IMediaControl_GetState(control, 0, &state); + todo_wine ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#x.\n", hr); + + thread = send_frame(input); + + hr = IMediaControl_GetState(control, 1000, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(WaitForSingleObject(thread, 100) == WAIT_TIMEOUT, "Thread should block in Receive().\n"); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + /* Test flushing */ + hr = IMediaControl_Pause(control); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + thread = send_frame(input); + ok(WaitForSingleObject(thread, 100) == WAIT_TIMEOUT, "Thread should block in Receive().\n"); + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IPin_BeginFlush(pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = join_thread(thread); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = join_thread(send_frame(input)); + todo_wine ok(hr == E_FAIL, "Got hr %#x.\n", hr); + + hr = IPin_EndFlush(pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaControl_GetState(control, 0, &state); + todo_wine ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#x.\n", hr); + + thread = send_frame(input); + ok(WaitForSingleObject(thread, 100) == WAIT_TIMEOUT, "Thread should block in Receive().\n"); + + hr = IMediaControl_Run(control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = join_thread(thread); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IPin_BeginFlush(pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = join_thread(send_frame(input)); + todo_wine ok(hr == E_FAIL, "Got hr %#x.\n", hr); + + hr = IPin_EndFlush(pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = join_thread(send_frame(input)); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + IMediaControl_Release(control); +} + START_TEST(nullrenderer) { IBaseFilter *filter; @@ -443,6 +963,7 @@ START_TEST(nullrenderer) test_aggregation(); test_media_types(); test_enum_media_types(); + test_state();
CoUninitialize(); }