[PATCH 0/2] MR10885: quartz: Call GetCurrentPosition() to get the latest position before stopping the graph.
Unless I missed something, info about sample delivery is not available in the quartz module, so I left this as a todo. With a typical framerate, the position set should be pretty close anyway. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10885
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/quartz/tests/filtergraph.c | 93 +++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/dlls/quartz/tests/filtergraph.c b/dlls/quartz/tests/filtergraph.c index 087c37be538..08de3538c9a 100644 --- a/dlls/quartz/tests/filtergraph.c +++ b/dlls/quartz/tests/filtergraph.c @@ -6071,6 +6071,98 @@ static void test_event_dispatch(void) ok(!ref, "Got outstanding refcount %ld.\n", ref); } +static void test_stopped_current_position(void) +{ + IEnumFilters *enum_filters; + LONG_PTR lparam1, lparam2; + IMediaEvent *media_event; + IMediaSeeking *seeking; + REFERENCE_TIME current; + IMediaControl *control; + IFilterGraph2 *graph; + IBaseFilter *filter; + unsigned int i = 0; + WCHAR *filename; + ULONG fetched; + long ev_code; + HANDLE event; + HRESULT hr; + LONG code; + + filename = load_resource(L"test.avi"); + + graph = create_graph(); + + hr = IFilterGraph2_RenderFile(graph, filename, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IFilterGraph2_QueryInterface(graph, &IID_IMediaControl, (void **)&control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_QueryInterface(graph, &IID_IMediaEvent, (void **)&media_event); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaEvent_GetEventHandle(media_event, (OAEVENT *)&event); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + /* Flush existing events. */ + while ((hr = IMediaEvent_GetEvent(media_event, &code, &lparam1, &lparam2, 0)) == S_OK); + + hr = IMediaControl_Run(control); + ok(SUCCEEDED(hr), "Got hr %#lx.\n", hr); + + ok(WaitForSingleObject(event, 1000) == 0, "Event should be signaled.\n"); + + /* Wait 2.5 seconds of playback time. Source video is 1 fps. */ + IMediaEvent_WaitForCompletion(media_event, 2500, &ev_code); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IFilterGraph2_QueryInterface(graph, &IID_IMediaSeeking, (void **)&seeking); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaSeeking_GetCurrentPosition(seeking, ¤t); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + todo_wine + ok(current >= 24700000 && current < 25200000, "Got current %I64d.\n", current); + + IMediaSeeking_Release(seeking); + + hr = IFilterGraph2_EnumFilters(graph, &enum_filters); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + while ((hr = IEnumFilters_Next(enum_filters, 1, &filter, &fetched)) == S_OK) + { + winetest_push_context("Filter %u:", i++); + + ok(fetched, "Filter not fetched.\n"); + + if (FAILED(hr = IBaseFilter_QueryInterface(filter, &IID_IMediaSeeking, (void **)&seeking))) + { + ok(hr == E_NOINTERFACE, "Got hr %#lx.\n", hr); + } + else + { + hr = IMediaSeeking_GetCurrentPosition(seeking, ¤t); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + todo_wine + ok(current == 20000000, "Got current %I64d.\n", current); + + IMediaSeeking_Release(seeking); + } + + IBaseFilter_Release(filter); + winetest_pop_context(); + } + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + IEnumFilters_Release(enum_filters); + IMediaEvent_Release(media_event); + IMediaControl_Release(control); + IFilterGraph2_Release(graph); + DeleteFileW(filename); +} + START_TEST(filtergraph) { CoInitializeEx(NULL, COINIT_MULTITHREADED); @@ -6100,6 +6192,7 @@ START_TEST(filtergraph) test_set_notify_flags(); test_events(); test_event_dispatch(); + test_stopped_current_position(); CoUninitialize(); test_render_with_multithread(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10885
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/quartz/filtergraph.c | 6 +++++- dlls/quartz/tests/filtergraph.c | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dlls/quartz/filtergraph.c b/dlls/quartz/filtergraph.c index 79fcc425e53..9d57096c8eb 100644 --- a/dlls/quartz/filtergraph.c +++ b/dlls/quartz/filtergraph.c @@ -2452,6 +2452,7 @@ static HRESULT WINAPI MediaSeeking_SetPositions(IMediaSeeking *iface, LONGLONG * if (state == State_Running && !graph->needs_async_run) IMediaControl_Pause(&graph->IMediaControl_iface); + /* TODO: according to native tests, filter positions should be updated per sample. */ LIST_FOR_EACH_ENTRY(filter, &graph->filters, struct filter, entry) { LONGLONG current = current_ptr ? *current_ptr : 0, stop = stop_ptr ? *stop_ptr : 0; @@ -5066,10 +5067,13 @@ static HRESULT WINAPI MediaFilter_Stop(IMediaFilter *iface) struct filter_graph *graph = impl_from_IMediaFilter(iface); HRESULT hr = S_OK, filter_hr; struct filter *filter; + LONGLONG current; TP_WORK *work; TRACE("graph %p.\n", graph); + IMediaSeeking_GetCurrentPosition(&graph->IMediaSeeking_iface, ¤t); + EnterCriticalSection(&graph->cs); if (graph->state == State_Stopped) @@ -5102,7 +5106,7 @@ static HRESULT WINAPI MediaFilter_Stop(IMediaFilter *iface) work = graph->async_run_work; /* Update the current position, probably to synchronize multiple streams. */ - IMediaSeeking_SetPositions(&graph->IMediaSeeking_iface, &graph->current_pos, + IMediaSeeking_SetPositions(&graph->IMediaSeeking_iface, ¤t, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning); LeaveCriticalSection(&graph->cs); diff --git a/dlls/quartz/tests/filtergraph.c b/dlls/quartz/tests/filtergraph.c index 08de3538c9a..160b216a7b2 100644 --- a/dlls/quartz/tests/filtergraph.c +++ b/dlls/quartz/tests/filtergraph.c @@ -6123,7 +6123,6 @@ static void test_stopped_current_position(void) hr = IMediaSeeking_GetCurrentPosition(seeking, ¤t); ok(hr == S_OK, "Got hr %#lx.\n", hr); - todo_wine ok(current >= 24700000 && current < 25200000, "Got current %I64d.\n", current); IMediaSeeking_Release(seeking); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10885
``` + /* TODO: according to native tests, filter positions should be updated per sample. */ ``` I don't understand what this means? The tests imply that querying the filters is wrong—the source video you're using is 1 fps, but GetCurrentPosition() returns something more granular than that. Rather, as test_graph_seeking() shows, the current position is computed from the reference clock. The actual problem here seems to be that the updates to stream_elapsed and current_pos done in Pause() need to also be done in Stop(). As for the tests, I'd prefer adding tests to test_graph_seeking(), which tests this mechanism on a bit lower level anyway and already has tests nearly identical to the ones we need. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10885#note_139906
The time set on the filter is always exactly 20000000 so I figured it must have come from the sample time, is all. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10885#note_139907
The time set on the filter is always exactly 20000000 so I figured it must have come from the sample time, is all.
Oh, you're referring to GetCurrentPositions() on the individual filters? That doesn't really have anything to do with the filter graph, though; the filter graph doesn't actually trust individual filters with GetCurrentPosition() at all, as far as we know. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10885#note_139908
On Fri May 15 00:46:30 2026 +0000, Elizabeth Figura wrote:
The time set on the filter is always exactly 20000000 so I figured it must have come from the sample time, is all. Oh, you're referring to GetCurrentPositions() on the individual filters? That doesn't really have anything to do with the filter graph, though; the filter graph doesn't actually trust individual filters with GetCurrentPosition() at all, as far as we know. I made those checks because this game has a custom filter which passes the time set on the filter to WMReader_Start(), and does it in response to filter Pause(). Makes no sense, but it does it.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10885#note_139909
On Fri May 15 00:46:30 2026 +0000, Conor McCarthy wrote:
I made those checks because this game has a custom filter which passes the time set on the filter to WMReader_Start(), and does it in response to filter Pause(). Makes no sense, but it does it. Well, sure. I'm just saying that comment doesn't make much sense in filtergraph.c. It's the responsibility of the individual filters to report the right position.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10885#note_139911
On Fri May 15 01:09:38 2026 +0000, Elizabeth Figura wrote:
Well, sure. I'm just saying that comment doesn't make much sense in filtergraph.c. It's the responsibility of the individual filters to report the right position. `IMediaSeeking_SetPositions()` is called on the filters in `MediaSeeking_SetPositions()` and the game's custom filter doesn't update its own position.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10885#note_139921
On Fri May 15 06:13:51 2026 +0000, Conor McCarthy wrote:
`IMediaSeeking_SetPositions()` is called on the filters in `MediaSeeking_SetPositions()` and the game's custom filter doesn't update its own position. I'm not sure I understand what you mean.
If you're saying that the custom filter also needs to update its position after every frame, and that that should be the responsibility of the filter graph, that's not how it works. Filters are largely self-sufficient. The AVI splitter might report the position of the last sample it sent, but that doesn't mean that any other filter does. SetPositions() is not a command to update the reported position; it is a command to seek, and calling it every frame would be a bad idea. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10885#note_140038
participants (3)
-
Conor McCarthy -
Conor McCarthy (@cmccarthy) -
Elizabeth Figura (@zfigura)