Signed-off-by: Zebediah Figura z.figura12@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; }
Signed-off-by: Zebediah Figura z.figura12@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)
Signed-off-by: Zebediah Figura z.figura12@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. */
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@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; }
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@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)
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@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)
Signed-off-by: Zebediah Figura z.figura12@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);
Signed-off-by: Zebediah Figura z.figura12@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);
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.