From 114832224fc5babd1417562dbd64d489fabe1e01 Mon Sep 17 00:00:00 2001 From: Zebediah Figura Date: Tue, 14 Jul 2020 19:56:26 -0500 Subject: [PATCH] quartz: Run the graph asynchronously if necessary. Signed-off-by: Zebediah Figura Signed-off-by: Alexandre Julliard --- dlls/quartz/filtergraph.c | 236 +++++++++++++++++++++++++----- dlls/quartz/tests/filtergraph.c | 12 +- dlls/quartz/tests/videorenderer.c | 6 +- dlls/quartz/tests/vmr9.c | 6 +- 4 files changed, 208 insertions(+), 52 deletions(-) diff --git a/dlls/quartz/filtergraph.c b/dlls/quartz/filtergraph.c index 273cd021109..22d7f7b4450 100644 --- a/dlls/quartz/filtergraph.c +++ b/dlls/quartz/filtergraph.c @@ -191,6 +191,9 @@ struct filter_graph struct list filters; unsigned int name_index; + OAFilterState state; + TP_WORK *async_run_work; + IReferenceClock *refClock; IBaseFilter *refClockProvider; EventsQueue evqueue; @@ -202,7 +205,6 @@ struct filter_graph int HandleEcComplete; int HandleEcRepaint; int HandleEcClockChanged; - OAFilterState state; CRITICAL_SECTION cs; ITF_CACHE_ENTRY ItfCacheEntries[MAX_ITF_CACHE_ENTRIES]; int nItfCacheEntries; @@ -219,6 +221,8 @@ struct filter_graph REFERENCE_TIME stream_start, stream_elapsed; LONGLONG current_pos; + + unsigned int needs_async_run : 1; }; struct enum_filters @@ -1719,13 +1723,179 @@ static HRESULT WINAPI MediaControl_Invoke(IMediaControl *iface, DISPID dispIdMem return S_OK; } +static void update_render_count(struct filter_graph *graph) +{ + /* Some filters (e.g. MediaStreamFilter) can become renderers when they are + * already in the graph. */ + struct filter *filter; + graph->nRenderers = 0; + LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) + { + if (is_renderer(filter)) + ++graph->nRenderers; + } +} + +/* Perform the paused -> running transition. The caller must hold graph->cs. */ +static HRESULT graph_start(struct filter_graph *graph, REFERENCE_TIME stream_start) +{ + struct filter *filter; + HRESULT hr = S_OK; + + graph->EcCompleteCount = 0; + update_render_count(graph); + + if (graph->defaultclock && !graph->refClock) + IFilterGraph2_SetDefaultSyncSource(&graph->IFilterGraph2_iface); + + if (!stream_start && graph->refClock) + { + IReferenceClock_GetTime(graph->refClock, &graph->stream_start); + stream_start = graph->stream_start - graph->stream_elapsed; + /* Delay presentation time by 200 ms, to give filters time to + * initialize. */ + stream_start += 200 * 10000; + } + + LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) + { + HRESULT filter_hr = IBaseFilter_Run(filter->filter, stream_start); + if (hr == S_OK) + hr = filter_hr; + TRACE("Filter %p returned %#x.\n", filter->filter, filter_hr); + } + + if (FAILED(hr)) + WARN("Failed to start stream, hr %#x.\n", hr); + + return hr; +} + +static void CALLBACK async_run_cb(TP_CALLBACK_INSTANCE *instance, void *context, TP_WORK *work) +{ + struct filter_graph *graph = context; + struct filter *filter; + FILTER_STATE state; + HRESULT hr; + + TRACE("Performing asynchronous state change.\n"); + + /* We can't just call GetState(), since that will return State_Running and + * VFW_S_STATE_INTERMEDIATE regardless of whether we're done pausing yet. + * Instead replicate it here. */ + + for (;;) + { + IBaseFilter *async_filter = NULL; + + hr = S_OK; + + EnterCriticalSection(&graph->cs); + + if (!graph->needs_async_run) + break; + + LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) + { + hr = IBaseFilter_GetState(filter->filter, 0, &state); + + if (hr == VFW_S_STATE_INTERMEDIATE) + async_filter = filter->filter; + + if (SUCCEEDED(hr) && state != State_Paused) + ERR("Filter %p reported incorrect state %u.\n", filter->filter, state); + + if (hr != S_OK) + break; + } + + if (hr != VFW_S_STATE_INTERMEDIATE) + break; + + LeaveCriticalSection(&graph->cs); + + IBaseFilter_GetState(async_filter, 10, &state); + } + + if (hr == S_OK && graph->needs_async_run) + { + sort_filters(graph); + graph_start(graph, 0); + graph->needs_async_run = 0; + } + + LeaveCriticalSection(&graph->cs); + IUnknown_Release(graph->outer_unk); +} + static HRESULT WINAPI MediaControl_Run(IMediaControl *iface) { struct filter_graph *graph = impl_from_IMediaControl(iface); + BOOL need_async_run = TRUE; + struct filter *filter; + FILTER_STATE state; + HRESULT hr = S_OK; TRACE("graph %p.\n", graph); - return IMediaFilter_Run(&graph->IMediaFilter_iface, 0); + EnterCriticalSection(&graph->cs); + + if (graph->state == State_Running) + { + LeaveCriticalSection(&graph->cs); + return S_OK; + } + + sort_filters(graph); + update_render_count(graph); + + if (graph->state == State_Stopped) + { + if (graph->defaultclock && !graph->refClock) + IFilterGraph2_SetDefaultSyncSource(&graph->IFilterGraph2_iface); + + LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) + { + HRESULT filter_hr = IBaseFilter_Pause(filter->filter); + if (hr == S_OK) + hr = filter_hr; + TRACE("Filter %p returned %#x.\n", filter->filter, filter_hr); + + /* If a filter returns VFW_S_CANT_CUE, we shouldn't wait for a + * paused state. */ + filter_hr = IBaseFilter_GetState(filter->filter, 0, &state); + if (filter_hr != S_OK && filter_hr != VFW_S_STATE_INTERMEDIATE) + need_async_run = FALSE; + } + + if (FAILED(hr)) + { + LeaveCriticalSection(&graph->cs); + WARN("Failed to pause, hr %#x.\n", hr); + return hr; + } + } + + graph->state = State_Running; + + if (SUCCEEDED(hr)) + { + if (hr != S_OK && need_async_run) + { + if (!graph->async_run_work) + graph->async_run_work = CreateThreadpoolWork(async_run_cb, graph, NULL); + graph->needs_async_run = 1; + IUnknown_AddRef(graph->outer_unk); + SubmitThreadpoolWork(graph->async_run_work); + } + else + { + graph_start(graph, 0); + } + } + + LeaveCriticalSection(&graph->cs); + return hr; } static HRESULT WINAPI MediaControl_Pause(IMediaControl *iface) @@ -4778,6 +4948,7 @@ static HRESULT WINAPI MediaFilter_Stop(IMediaFilter *iface) struct filter_graph *graph = impl_from_IMediaFilter(iface); HRESULT hr = S_OK, filter_hr; struct filter *filter; + TP_WORK *work; TRACE("graph %p.\n", graph); @@ -4809,26 +4980,20 @@ static HRESULT WINAPI MediaFilter_Stop(IMediaFilter *iface) } graph->state = State_Stopped; + graph->needs_async_run = 0; + work = graph->async_run_work; /* Update the current position, probably to synchronize multiple streams. */ IMediaSeeking_SetPositions(&graph->IMediaSeeking_iface, &graph->current_pos, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning); LeaveCriticalSection(&graph->cs); - return hr; -} -static void update_render_count(struct filter_graph *graph) -{ - /* Some filters (e.g. MediaStreamFilter) can become renderers when they are - * already in the graph. */ - struct filter *filter; - graph->nRenderers = 0; - LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) - { - if (is_renderer(filter)) - ++graph->nRenderers; - } + /* Don't cancel the callback; it's holding a reference to the graph. */ + if (work) + WaitForThreadpoolWorkCallbacks(work, FALSE); + + return hr; } static HRESULT WINAPI MediaFilter_Pause(IMediaFilter *iface) @@ -4836,6 +5001,7 @@ static HRESULT WINAPI MediaFilter_Pause(IMediaFilter *iface) struct filter_graph *graph = impl_from_IMediaFilter(iface); HRESULT hr = S_OK, filter_hr; struct filter *filter; + TP_WORK *work; TRACE("graph %p.\n", graph); @@ -4869,17 +5035,22 @@ static HRESULT WINAPI MediaFilter_Pause(IMediaFilter *iface) } graph->state = State_Paused; + graph->needs_async_run = 0; + work = graph->async_run_work; LeaveCriticalSection(&graph->cs); + + /* Don't cancel the callback; it's holding a reference to the graph. */ + if (work) + WaitForThreadpoolWorkCallbacks(work, FALSE); + return hr; } static HRESULT WINAPI MediaFilter_Run(IMediaFilter *iface, REFERENCE_TIME start) { struct filter_graph *graph = impl_from_IMediaFilter(iface); - REFERENCE_TIME stream_start = start; - HRESULT hr = S_OK, filter_hr; - struct filter *filter; + HRESULT hr; TRACE("graph %p, start %s.\n", graph, debugstr_time(start)); @@ -4890,31 +5061,13 @@ static HRESULT WINAPI MediaFilter_Run(IMediaFilter *iface, REFERENCE_TIME start) LeaveCriticalSection(&graph->cs); return S_OK; } - graph->EcCompleteCount = 0; sort_filters(graph); - update_render_count(graph); - if (graph->defaultclock && !graph->refClock) - IFilterGraph2_SetDefaultSyncSource(&graph->IFilterGraph2_iface); - - if (!start && graph->refClock) - { - IReferenceClock_GetTime(graph->refClock, &graph->stream_start); - stream_start = graph->stream_start - graph->stream_elapsed; - /* Delay presentation time by 200 ms, to give filters time to - * initialize. */ - stream_start += 200 * 10000; - } - - LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) - { - filter_hr = IBaseFilter_Run(filter->filter, stream_start); - if (hr == S_OK) - hr = filter_hr; - } + hr = graph_start(graph, start); graph->state = State_Running; + graph->needs_async_run = 0; LeaveCriticalSection(&graph->cs); return hr; @@ -4924,6 +5077,7 @@ static HRESULT WINAPI MediaFilter_GetState(IMediaFilter *iface, DWORD timeout, F { struct filter_graph *graph = impl_from_IMediaFilter(iface); DWORD end = GetTickCount() + timeout; + FILTER_STATE expect_state; HRESULT hr; TRACE("graph %p, timeout %u, state %p.\n", graph, timeout, state); @@ -4939,6 +5093,7 @@ static HRESULT WINAPI MediaFilter_GetState(IMediaFilter *iface, DWORD timeout, F EnterCriticalSection(&graph->cs); *state = graph->state; + expect_state = graph->needs_async_run ? State_Paused : graph->state; for (;;) { @@ -4970,8 +5125,9 @@ static HRESULT WINAPI MediaFilter_GetState(IMediaFilter *iface, DWORD timeout, F else if (filter_state != graph->state && filter_state != State_Paused) hr = E_FAIL; - if (filter_state != graph->state) - ERR("Filter %p reported incorrect state %u.\n", filter->filter, filter_state); + if (filter_state != expect_state) + ERR("Filter %p reported incorrect state %u (expected %u).\n", + filter->filter, filter_state, expect_state); } LeaveCriticalSection(&graph->cs); diff --git a/dlls/quartz/tests/filtergraph.c b/dlls/quartz/tests/filtergraph.c index 99120133bb6..0c627aaeafb 100644 --- a/dlls/quartz/tests/filtergraph.c +++ b/dlls/quartz/tests/filtergraph.c @@ -3497,8 +3497,8 @@ todo_wine hr = IMediaControl_GetState(control, 0, &state); ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#x.\n", hr); ok(state == State_Running, "Got state %u.\n", state); - todo_wine ok(sink.state == State_Paused, "Got state %u.\n", sink.state); - todo_wine ok(source.state == State_Paused, "Got state %u.\n", source.state); + ok(sink.state == State_Paused, "Got state %u.\n", sink.state); + ok(source.state == State_Paused, "Got state %u.\n", source.state); hr = IMediaControl_Run(control); todo_wine ok(hr == S_FALSE, "Got hr %#x.\n", hr); @@ -3506,16 +3506,16 @@ todo_wine hr = IMediaControl_GetState(control, 0, &state); ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#x.\n", hr); ok(state == State_Running, "Got state %u.\n", state); - todo_wine ok(sink.state == State_Paused, "Got state %u.\n", sink.state); - todo_wine ok(source.state == State_Paused, "Got state %u.\n", source.state); + ok(sink.state == State_Paused, "Got state %u.\n", sink.state); + ok(source.state == State_Paused, "Got state %u.\n", source.state); sink.state_hr = sink.GetState_hr = S_OK; while ((hr = IMediaControl_GetState(control, INFINITE, &state)) == VFW_S_STATE_INTERMEDIATE) { ok(state == State_Running, "Got state %u.\n", state); - todo_wine ok(sink.state == State_Paused, "Got state %u.\n", sink.state); - todo_wine ok(source.state == State_Paused, "Got state %u.\n", source.state); + ok(sink.state == State_Paused, "Got state %u.\n", sink.state); + ok(source.state == State_Paused, "Got state %u.\n", source.state); Sleep(10); } ok(hr == S_OK, "Got hr %#x.\n", hr); diff --git a/dlls/quartz/tests/videorenderer.c b/dlls/quartz/tests/videorenderer.c index b157700a92b..4ae320e0225 100644 --- a/dlls/quartz/tests/videorenderer.c +++ b/dlls/quartz/tests/videorenderer.c @@ -1172,7 +1172,7 @@ static void test_eos(IPin *pin, IMemInputPin *input, IFilterGraph2 *graph) * done rendering. */ hr = IMediaControl_Run(control); - todo_wine ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); hr = join_thread(send_frame(input)); ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaControl_GetState(control, 1000, &state); @@ -1195,7 +1195,7 @@ static void test_eos(IPin *pin, IMemInputPin *input, IFilterGraph2 *graph) /* Test sending EOS while flushing. */ hr = IMediaControl_Run(control); - todo_wine ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); hr = join_thread(send_frame(input)); ok(hr == S_OK, "Got hr %#x.\n", hr); @@ -1214,7 +1214,7 @@ static void test_eos(IPin *pin, IMemInputPin *input, IFilterGraph2 *graph) /* Test sending EOS and then flushing or stopping. */ hr = IMediaControl_Run(control); - todo_wine ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); hr = join_thread(send_frame(input)); ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaControl_GetState(control, 1000, &state); diff --git a/dlls/quartz/tests/vmr9.c b/dlls/quartz/tests/vmr9.c index e953c05dd1e..cd4df58f791 100644 --- a/dlls/quartz/tests/vmr9.c +++ b/dlls/quartz/tests/vmr9.c @@ -1308,7 +1308,7 @@ static void test_eos(IPin *pin, IMemInputPin *input, IMediaControl *control) commit_allocator(input); hr = IMediaControl_Run(control); - todo_wine ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); hr = join_thread(send_frame(input)); ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaControl_GetState(control, 1000, &state); @@ -1332,7 +1332,7 @@ static void test_eos(IPin *pin, IMemInputPin *input, IMediaControl *control) commit_allocator(input); hr = IMediaControl_Run(control); - todo_wine ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); hr = join_thread(send_frame(input)); ok(hr == S_OK, "Got hr %#x.\n", hr); @@ -1352,7 +1352,7 @@ static void test_eos(IPin *pin, IMemInputPin *input, IMediaControl *control) commit_allocator(input); hr = IMediaControl_Run(control); - todo_wine ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); hr = join_thread(send_frame(input)); ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaControl_GetState(control, 1000, &state);