[PATCH 1/8] quartz: Add more tracing to state change methods.
Signed-off-by: Zebediah Figura <z.figura12(a)gmail.com> --- dlls/quartz/filtergraph.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dlls/quartz/filtergraph.c b/dlls/quartz/filtergraph.c index 6a29b5d7484..e035b8f0ed1 100644 --- a/dlls/quartz/filtergraph.c +++ b/dlls/quartz/filtergraph.c @@ -1723,6 +1723,8 @@ static HRESULT WINAPI MediaControl_Run(IMediaControl *iface) { struct filter_graph *graph = impl_from_IMediaControl(iface); + TRACE("graph %p.\n", graph); + return IMediaFilter_Run(&graph->IMediaFilter_iface, 0); } @@ -1730,6 +1732,8 @@ static HRESULT WINAPI MediaControl_Pause(IMediaControl *iface) { struct filter_graph *graph = impl_from_IMediaControl(iface); + TRACE("graph %p.\n", graph); + return IMediaFilter_Pause(&graph->IMediaFilter_iface); } @@ -1737,6 +1741,8 @@ static HRESULT WINAPI MediaControl_Stop(IMediaControl *iface) { struct filter_graph *graph = impl_from_IMediaControl(iface); + TRACE("graph %p.\n", graph); + return IMediaFilter_Stop(&graph->IMediaFilter_iface); } @@ -1744,6 +1750,8 @@ static HRESULT WINAPI MediaControl_GetState(IMediaControl *iface, LONG timeout, { struct filter_graph *graph = impl_from_IMediaControl(iface); + TRACE("graph %p, timeout %u, state %p.\n", graph, timeout, state); + if (timeout < 0) timeout = INFINITE; return IMediaFilter_GetState(&graph->IMediaFilter_iface, timeout, (FILTER_STATE *)state); @@ -4951,6 +4959,7 @@ static HRESULT WINAPI MediaFilter_GetState(IMediaFilter *iface, DWORD timeout, F } LeaveCriticalSection(&graph->cs); + TRACE("Returning %#x, state %u.\n", hr, *state); return hr; } -- 2.27.0
Signed-off-by: Zebediah Figura <z.figura12(a)gmail.com> --- dlls/quartz/filtergraph.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/dlls/quartz/filtergraph.c b/dlls/quartz/filtergraph.c index e035b8f0ed1..5a10df9db2b 100644 --- a/dlls/quartz/filtergraph.c +++ b/dlls/quartz/filtergraph.c @@ -4933,8 +4933,6 @@ static HRESULT WINAPI MediaFilter_GetState(IMediaFilter *iface, DWORD timeout, F EnterCriticalSection(&graph->cs); - sort_filters(graph); - *state = graph->state; LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) -- 2.27.0
Signed-off-by: Zebediah Figura <z.figura12(a)gmail.com> --- dlls/quartz/tests/filtergraph.c | 199 ++++++++++++++++++++++++++++++-- 1 file changed, 190 insertions(+), 9 deletions(-) diff --git a/dlls/quartz/tests/filtergraph.c b/dlls/quartz/tests/filtergraph.c index de8e32034f8..c0076191a87 100644 --- a/dlls/quartz/tests/filtergraph.c +++ b/dlls/quartz/tests/filtergraph.c @@ -1173,7 +1173,7 @@ struct testfilter struct testpin *pins; unsigned int pin_count, enum_idx; - HRESULT state_hr, seek_hr; + HRESULT state_hr, GetState_hr, seek_hr; IAMFilterMiscFlags IAMFilterMiscFlags_iface; ULONG misc_flags; @@ -1393,7 +1393,7 @@ static HRESULT WINAPI testfilter_GetState(IBaseFilter *iface, DWORD timeout, FIL if (winetest_debug > 1) trace("%p->GetState(%u)\n", filter, timeout); *state = filter->state; - return filter->state_hr; + return filter->GetState_hr; } static HRESULT WINAPI testfilter_SetSyncSource(IBaseFilter *iface, IReferenceClock *clock) @@ -3199,6 +3199,7 @@ static void test_filter_state(void) REFERENCE_TIME start_time; IReferenceClock *clock; IMediaControl *control; + FILTER_STATE mf_state; IMediaFilter *filter; OAFilterState state; HRESULT hr; @@ -3402,33 +3403,213 @@ todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); check_filter_state(graph, State_Stopped); + /* Test asynchronous state change. */ + sink.state_hr = S_FALSE; + sink.GetState_hr = VFW_S_STATE_INTERMEDIATE; hr = IMediaControl_Pause(control); ok(hr == S_FALSE, "Got hr %#x.\n", hr); - sink.state_hr = VFW_S_STATE_INTERMEDIATE; hr = IMediaControl_GetState(control, 0, &state); ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#x.\n", hr); ok(state == State_Paused, "Got state %u.\n", state); - sink.state_hr = VFW_S_CANT_CUE; + sink.state_hr = sink.GetState_hr = S_OK; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(state == State_Paused, "Got state %u.\n", state); + + sink.state_hr = S_FALSE; + sink.GetState_hr = VFW_S_STATE_INTERMEDIATE; + hr = IMediaControl_Stop(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); + ok(state == State_Stopped, "Got state %u.\n", state); + + sink.state_hr = sink.GetState_hr = S_OK; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(state == State_Stopped, "Got state %u.\n", state); + + /* Renderers are expected to block completing a state change into paused + * until they receive a sample. Because the graph can transition from + * stopped -> paused -> running in one call, which itself needs to be + * asynchronous, it actually waits on a separate thread for all filters + * to be ready, then calls IMediaFilter::Run() once they are. + * + * However, IMediaControl::GetState() will return VFW_S_STATE_INTERMEDIATE + * if filters haven't caught up to the graph yet. To make matters worse, it + * doesn't take the above into account, meaning that it'll gladly return + * VFW_S_STATE_INTERMEDIATE even if passed an infinite timeout. */ + + sink.state_hr = S_FALSE; + sink.GetState_hr = VFW_S_STATE_INTERMEDIATE; + hr = IMediaControl_Run(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); + 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); + + hr = IMediaControl_Run(control); + todo_wine 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); + 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); + + 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); + Sleep(10); + } + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(state == State_Running, "Got state %u.\n", state); + ok(sink.state == State_Running, "Got state %u.\n", sink.state); + ok(source.state == State_Running, "Got state %u.\n", source.state); + + /* The above logic does not apply to the running -> paused -> stopped + * transition. The filter graph will stop a filter regardless of whether + * it's completely paused. Inasmuch as stopping the filter is like flushing + * it—i.e. it has to succeed—this makes sense. */ + + sink.state_hr = S_FALSE; + sink.GetState_hr = VFW_S_STATE_INTERMEDIATE; + hr = IMediaControl_Stop(control); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(sink.state == State_Stopped, "Got state %u.\n", sink.state); + ok(source.state == State_Stopped, "Got state %u.\n", source.state); + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#x.\n", hr); + ok(state == State_Stopped, "Got state %u.\n", state); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + sink.state_hr = sink.GetState_hr = S_OK; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(state == State_Stopped, "Got state %u.\n", state); + + /* Try an asynchronous stopped->paused->running transition, but pause or + * stop the graph before our filter is completely paused. */ + + sink.state_hr = S_FALSE; + sink.GetState_hr = VFW_S_STATE_INTERMEDIATE; + hr = IMediaControl_Run(control); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + hr = IMediaControl_Pause(control); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + sink.state_hr = sink.GetState_hr = S_OK; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(state == State_Paused, "Got state %u.\n", 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_Stop(control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + sink.state_hr = S_FALSE; + sink.GetState_hr = VFW_S_STATE_INTERMEDIATE; + hr = IMediaControl_Run(control); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + hr = IMediaControl_Stop(control); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(sink.state == State_Stopped, "Got state %u.\n", sink.state); + ok(source.state == State_Stopped, "Got state %u.\n", source.state); + + sink.state_hr = sink.GetState_hr = S_OK; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(state == State_Stopped, "Got state %u.\n", state); + ok(sink.state == State_Stopped, "Got state %u.\n", sink.state); + ok(source.state == State_Stopped, "Got state %u.\n", source.state); + + /* This logic doesn't apply when using IMediaFilter methods directly. */ + + sink.state_hr = S_FALSE; + sink.GetState_hr = VFW_S_STATE_INTERMEDIATE; + hr = IMediaFilter_Run(filter, 0); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + + hr = IMediaFilter_GetState(filter, 0, &mf_state); + ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#x.\n", hr); + ok(mf_state == State_Running, "Got state %u.\n", mf_state); + ok(sink.state == State_Running, "Got state %u.\n", sink.state); + ok(source.state == State_Running, "Got state %u.\n", source.state); + + sink.state_hr = sink.GetState_hr = S_OK; + hr = IMediaFilter_GetState(filter, 0, &mf_state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(mf_state == State_Running, "Got state %u.\n", mf_state); + ok(sink.state == State_Running, "Got state %u.\n", sink.state); + ok(source.state == State_Running, "Got state %u.\n", source.state); + + hr = IMediaFilter_Stop(filter); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(sink.state == State_Stopped, "Got state %u.\n", sink.state); + ok(source.state == State_Stopped, "Got state %u.\n", source.state); + + /* Test VFW_S_CANT_CUE. */ + + sink.GetState_hr = VFW_S_CANT_CUE; + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaControl_GetState(control, 0, &state); ok(hr == VFW_S_CANT_CUE, "Got hr %#x.\n", hr); ok(state == State_Paused, "Got state %u.\n", state); - sink.state_hr = VFW_S_STATE_INTERMEDIATE; - source.state_hr = VFW_S_CANT_CUE; + sink.GetState_hr = VFW_S_STATE_INTERMEDIATE; + source.GetState_hr = VFW_S_CANT_CUE; hr = IMediaControl_GetState(control, 0, &state); ok(hr == VFW_S_CANT_CUE, "Got hr %#x.\n", hr); ok(state == State_Paused, "Got state %u.\n", state); - sink.state_hr = VFW_S_CANT_CUE; - source.state_hr = VFW_S_STATE_INTERMEDIATE; + sink.GetState_hr = VFW_S_CANT_CUE; + source.GetState_hr = VFW_S_STATE_INTERMEDIATE; hr = IMediaControl_GetState(control, 0, &state); ok(hr == VFW_S_CANT_CUE, "Got hr %#x.\n", hr); ok(state == State_Paused, "Got state %u.\n", state); - sink.state_hr = source.state_hr = S_OK; + sink.GetState_hr = source.GetState_hr = S_OK; + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + sink.state_hr = S_FALSE; + sink.GetState_hr = VFW_S_STATE_INTERMEDIATE; + source.GetState_hr = VFW_S_CANT_CUE; + hr = IMediaControl_Run(control); + ok(hr == S_FALSE, "Got hr %#x.\n", hr); + ok(sink.state == State_Running, "Got state %u.\n", sink.state); + ok(source.state == State_Running, "Got state %u.\n", source.state); + + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == VFW_S_CANT_CUE, "Got hr %#x.\n", hr); + ok(state == State_Running, "Got state %u.\n", state); + ok(sink.state == State_Running, "Got state %u.\n", sink.state); + ok(source.state == State_Running, "Got state %u.\n", source.state); + + sink.state_hr = sink.GetState_hr = source.GetState_hr = S_OK; + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#x.\n", hr); /* Destroying the graph while it's running stops all filters. */ -- 2.27.0
Hi, While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check? Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=75398 Your paranoid android. === w1064v1507 (32 bit report) === quartz: filtergraph.c:3472: Test failed: Got state 2. filtergraph.c:3473: Test failed: Got state 2. === debiant (32 bit WoW report) === quartz: videorenderer.c:1053: Test failed: Got hr 0xdeadbeef. videorenderer.c:1054: Test failed: Got time 10005200000000.
Signed-off-by: Zebediah Figura <z.figura12(a)gmail.com> --- dlls/quartz/filtergraph.c | 55 +++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/dlls/quartz/filtergraph.c b/dlls/quartz/filtergraph.c index 5a10df9db2b..17963c2b757 100644 --- a/dlls/quartz/filtergraph.c +++ b/dlls/quartz/filtergraph.c @@ -4923,40 +4923,57 @@ static HRESULT WINAPI MediaFilter_GetState(IMediaFilter *iface, DWORD timeout, F { struct filter_graph *graph = impl_from_IMediaFilter(iface); DWORD end = GetTickCount() + timeout; - HRESULT hr = S_OK, filter_hr; - struct filter *filter; + HRESULT hr; TRACE("graph %p, timeout %u, state %p.\n", graph, timeout, state); if (!state) return E_POINTER; - EnterCriticalSection(&graph->cs); + /* Thread safety is a little tricky here. GetState() shouldn't block other + * functions from being called on the filter graph. However, we can't just + * call IBaseFilter::GetState() in one loop and drop the lock on every + * iteration, since the filter list might change beneath us. So instead we + * do what native does, and poll for it every 10 ms. */ + EnterCriticalSection(&graph->cs); *state = graph->state; - LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) + for (;;) { + IBaseFilter *async_filter = NULL; FILTER_STATE filter_state; - int wait; + struct filter *filter; - if (timeout == INFINITE) - wait = INFINITE; - else if (!timeout) - wait = 0; - else - wait = max(end - GetTickCount(), 0); + hr = S_OK; - filter_hr = IBaseFilter_GetState(filter->filter, wait, &filter_state); - if (hr == S_OK && filter_hr == VFW_S_STATE_INTERMEDIATE) - hr = VFW_S_STATE_INTERMEDIATE; - else if (filter_hr != S_OK && filter_hr != VFW_S_STATE_INTERMEDIATE) - hr = filter_hr; - if (filter_state != graph->state) - ERR("Filter %p reported incorrect state %u.\n", filter->filter, filter_state); + LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) + { + HRESULT filter_hr = IBaseFilter_GetState(filter->filter, 0, &filter_state); + + TRACE("Filter %p returned hr %#x, state %u.\n", filter->filter, filter_hr, filter_state); + + if (filter_hr == VFW_S_STATE_INTERMEDIATE) + async_filter = filter->filter; + + if (hr == S_OK && filter_hr == VFW_S_STATE_INTERMEDIATE) + hr = VFW_S_STATE_INTERMEDIATE; + else if (filter_hr != S_OK && filter_hr != VFW_S_STATE_INTERMEDIATE) + hr = filter_hr; + if (filter_state != graph->state) + ERR("Filter %p reported incorrect state %u.\n", filter->filter, filter_state); + } + + LeaveCriticalSection(&graph->cs); + + if (hr != VFW_S_STATE_INTERMEDIATE || (timeout != INFINITE && GetTickCount() >= end)) + break; + + IBaseFilter_GetState(async_filter, 10, &filter_state); + + EnterCriticalSection(&graph->cs); } - LeaveCriticalSection(&graph->cs); TRACE("Returning %#x, state %u.\n", hr, *state); return hr; } -- 2.27.0
This matches native, and gstreamer can be slow enough to start up that a bit of extra time would be useful. Signed-off-by: Zebediah Figura <z.figura12(a)gmail.com> --- dlls/quartz/filtergraph.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlls/quartz/filtergraph.c b/dlls/quartz/filtergraph.c index 17963c2b757..ff0f159c131 100644 --- a/dlls/quartz/filtergraph.c +++ b/dlls/quartz/filtergraph.c @@ -4903,7 +4903,7 @@ static HRESULT WINAPI MediaFilter_Run(IMediaFilter *iface, REFERENCE_TIME start) IReferenceClock_GetTime(graph->refClock, &graph->stream_start); stream_start = graph->stream_start - graph->stream_elapsed; if (graph->state == State_Stopped) - stream_start += 500000; + stream_start += 200 * 10000; } LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) -- 2.27.0
This matches native (actually, native goes one step further and increases the delay by 100 ms every time the filter is paused). Generally this makes sense, too, as otherwise a graph that is paused and then resumed will suddenly find itself 200 ms behind. Signed-off-by: Zebediah Figura <z.figura12(a)gmail.com> --- dlls/quartz/filtergraph.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dlls/quartz/filtergraph.c b/dlls/quartz/filtergraph.c index ff0f159c131..7319345931e 100644 --- a/dlls/quartz/filtergraph.c +++ b/dlls/quartz/filtergraph.c @@ -4902,8 +4902,9 @@ static HRESULT WINAPI MediaFilter_Run(IMediaFilter *iface, REFERENCE_TIME start) { IReferenceClock_GetTime(graph->refClock, &graph->stream_start); stream_start = graph->stream_start - graph->stream_elapsed; - if (graph->state == State_Stopped) - stream_start += 200 * 10000; + /* 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) -- 2.27.0
Signed-off-by: Zebediah Figura <z.figura12(a)gmail.com> --- dlls/quartz/filtergraph.c | 9 +++++++ dlls/quartz/tests/filtergraph.c | 45 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/dlls/quartz/filtergraph.c b/dlls/quartz/filtergraph.c index 7319345931e..273cd021109 100644 --- a/dlls/quartz/filtergraph.c +++ b/dlls/quartz/filtergraph.c @@ -4961,6 +4961,15 @@ static HRESULT WINAPI MediaFilter_GetState(IMediaFilter *iface, DWORD timeout, F hr = VFW_S_STATE_INTERMEDIATE; else if (filter_hr != S_OK && filter_hr != VFW_S_STATE_INTERMEDIATE) hr = filter_hr; + + if (hr == S_OK && filter_state == State_Paused && graph->state != State_Paused) + { + async_filter = filter->filter; + hr = VFW_S_STATE_INTERMEDIATE; + } + 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); } diff --git a/dlls/quartz/tests/filtergraph.c b/dlls/quartz/tests/filtergraph.c index c0076191a87..99120133bb6 100644 --- a/dlls/quartz/tests/filtergraph.c +++ b/dlls/quartz/tests/filtergraph.c @@ -3254,6 +3254,21 @@ static void test_filter_state(void) ok(hr == S_OK, "Got hr %#x.\n", hr); check_filter_state(graph, State_Paused); + sink.state = State_Stopped; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == E_FAIL, "Got hr %#x.\n", hr); + ok(state == State_Paused, "Got state %u.\n", state); + + sink.state = State_Running; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == E_FAIL, "Got hr %#x.\n", hr); + ok(state == State_Paused, "Got state %u.\n", state); + + sink.state = State_Paused; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(state == State_Paused, "Got state %u.\n", state); + hr = IMediaControl_Stop(control); ok(hr == S_OK, "Got hr %#x.\n", hr); check_filter_state(graph, State_Stopped); @@ -3262,10 +3277,40 @@ static void test_filter_state(void) ok(hr == S_OK, "Got hr %#x.\n", hr); check_filter_state(graph, State_Running); + sink.state = State_Stopped; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == E_FAIL, "Got hr %#x.\n", hr); + ok(state == State_Running, "Got state %u.\n", state); + + sink.state = State_Paused; + 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); + + sink.state = State_Running; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(state == State_Running, "Got state %u.\n", state); + hr = IMediaControl_Stop(control); ok(hr == S_OK, "Got hr %#x.\n", hr); check_filter_state(graph, State_Stopped); + sink.state = State_Running; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == E_FAIL, "Got hr %#x.\n", hr); + ok(state == State_Stopped, "Got state %u.\n", state); + + sink.state = State_Paused; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == VFW_S_STATE_INTERMEDIATE, "Got hr %#x.\n", hr); + ok(state == State_Stopped, "Got state %u.\n", state); + + sink.state = State_Stopped; + hr = IMediaControl_GetState(control, 0, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(state == State_Stopped, "Got state %u.\n", state); + hr = IMediaControl_Pause(control); ok(hr == S_OK, "Got hr %#x.\n", hr); check_filter_state(graph, State_Paused); -- 2.27.0
Signed-off-by: Zebediah Figura <z.figura12(a)gmail.com> --- 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); -- 2.27.0
Hi, While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check? Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=75396 Your paranoid android. === debiant (32 bit report) === quartz: videorenderer.c:1053: Test failed: Got hr 0xdeadbeef. videorenderer.c:1054: Test failed: Got time 10005600000000.
participants (2)
-
Marvin -
Zebediah Figura