[PATCH 0/3] MR5130: mfmediaengine: Implement IMFMediaEngineEx::SetCurrentTime/Ex().
This is a split from MR3572. It's needed for Underworld Island (2150830) video seeking. Changes compared to the patches in MR3572: 1. Avoid making session state changes when in paused state. 2. Add tests to show that the MF_MEDIA_ENGINE_EVENT_SEEKING notification is most likely blocking because notify->seeking_event_received is TRUE right after a SetCurrentTime() call. Same for MF_MEDIA_ENGINE_EVENT_TIMEUPDATE when in paused state. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/5130
From: Zhiyi Zhang <zzhang(a)codeweavers.com> --- dlls/mfmediaengine/main.c | 59 ++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/dlls/mfmediaengine/main.c b/dlls/mfmediaengine/main.c index 0e16305ddda..96eeb4bdbcc 100644 --- a/dlls/mfmediaengine/main.c +++ b/dlls/mfmediaengine/main.c @@ -94,6 +94,7 @@ enum media_engine_flags FLAGS_ENGINE_NEW_FRAME = 0x8000, FLAGS_ENGINE_SOURCE_PENDING = 0x10000, FLAGS_ENGINE_PLAY_PENDING = 0x20000, + FLAGS_ENGINE_SEEKING = 0x40000, }; struct vec3 @@ -164,6 +165,7 @@ struct media_engine { IMFMediaSource *source; IMFPresentationDescriptor *pd; + PROPVARIANT start_position; } presentation; struct effects video_effects; struct effects audio_effects; @@ -978,7 +980,14 @@ static HRESULT WINAPI media_engine_session_events_Invoke(IMFAsyncCallback *iface break; } case MESessionStarted: - + EnterCriticalSection(&engine->cs); + if (engine->flags & FLAGS_ENGINE_SEEKING) + { + media_engine_set_flag(engine, FLAGS_ENGINE_SEEKING | FLAGS_ENGINE_IS_ENDED, FALSE); + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_SEEKED, 0, 0); + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_TIMEUPDATE, 0, 0); + } + LeaveCriticalSection(&engine->cs); IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_PLAYING, 0, 0); break; case MESessionEnded: @@ -1339,10 +1348,9 @@ static HRESULT media_engine_create_topology(struct media_engine *engine, IMFMedi static void media_engine_start_playback(struct media_engine *engine) { - PROPVARIANT var; - - var.vt = VT_EMPTY; - IMFMediaSession_Start(engine->session, &GUID_NULL, &var); + IMFMediaSession_Start(engine->session, &GUID_NULL, &engine->presentation.start_position); + /* Reset the playback position to the current position */ + engine->presentation.start_position.vt = VT_EMPTY; } static HRESULT WINAPI media_engine_load_handler_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) @@ -1770,6 +1778,10 @@ static double WINAPI media_engine_GetCurrentTime(IMFMediaEngineEx *iface) { ret = engine->duration; } + else if (engine->flags & FLAGS_ENGINE_PAUSED && engine->presentation.start_position.vt == VT_I8) + { + ret = (double)engine->presentation.start_position.hVal.QuadPart / 10000000; + } else if (SUCCEEDED(IMFPresentationClock_GetTime(engine->clock, &clocktime))) { ret = mftime_to_seconds(clocktime); @@ -1779,17 +1791,50 @@ static double WINAPI media_engine_GetCurrentTime(IMFMediaEngineEx *iface) return ret; } +static HRESULT media_engine_set_current_time(struct media_engine *engine, double seektime) +{ + PROPVARIANT position; + DWORD caps; + HRESULT hr; + + hr = IMFMediaSession_GetSessionCapabilities(engine->session, &caps); + if (FAILED(hr) || !(caps & MFSESSIONCAP_SEEK)) + return hr; + + position.vt = VT_I8; + position.hVal.QuadPart = min(max(0, seektime), engine->duration) * 10000000; + + if (IMFMediaEngineEx_IsPaused(&engine->IMFMediaEngineEx_iface)) + { + engine->presentation.start_position = position; + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_SEEKING, 0, 0); + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_SEEKED, 0, 0); + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_TIMEUPDATE, 0, 0); + return S_OK; + } + + if (SUCCEEDED(hr = IMFMediaSession_Start(engine->session, &GUID_NULL, &position))) + { + media_engine_set_flag(engine, FLAGS_ENGINE_SEEKING, TRUE); + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_SEEKING, 0, 0); + } + + return hr; +} + static HRESULT WINAPI media_engine_SetCurrentTime(IMFMediaEngineEx *iface, double time) { struct media_engine *engine = impl_from_IMFMediaEngineEx(iface); - HRESULT hr = E_NOTIMPL; + HRESULT hr; - FIXME("(%p, %f): stub.\n", iface, time); + TRACE("%p, %f.\n", iface, time); EnterCriticalSection(&engine->cs); if (engine->flags & FLAGS_ENGINE_SHUT_DOWN) hr = MF_E_SHUTDOWN; + else + hr = media_engine_set_current_time(engine, time); LeaveCriticalSection(&engine->cs); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/5130
From: Zhiyi Zhang <zzhang(a)codeweavers.com> --- dlls/mfmediaengine/main.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/dlls/mfmediaengine/main.c b/dlls/mfmediaengine/main.c index 96eeb4bdbcc..444c6f1bb65 100644 --- a/dlls/mfmediaengine/main.c +++ b/dlls/mfmediaengine/main.c @@ -3020,9 +3020,24 @@ static HRESULT WINAPI media_engine_SetRealTimeMode(IMFMediaEngineEx *iface, BOOL static HRESULT WINAPI media_engine_SetCurrentTimeEx(IMFMediaEngineEx *iface, double seektime, MF_MEDIA_ENGINE_SEEK_MODE mode) { - FIXME("%p, %f, %#x stub.\n", iface, seektime, mode); + struct media_engine *engine = impl_from_IMFMediaEngineEx(iface); + HRESULT hr; - return E_NOTIMPL; + TRACE("%p, %f, %#x.\n", iface, seektime, mode); + + if (mode) + FIXME("mode %#x is ignored.\n", mode); + + EnterCriticalSection(&engine->cs); + + if (engine->flags & FLAGS_ENGINE_SHUT_DOWN) + hr = MF_E_SHUTDOWN; + else + hr = media_engine_set_current_time(engine, seektime); + + LeaveCriticalSection(&engine->cs); + + return hr; } static HRESULT WINAPI media_engine_EnableTimeUpdateTimer(IMFMediaEngineEx *iface, BOOL enable) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/5130
From: Zhiyi Zhang <zzhang(a)codeweavers.com> --- dlls/mfmediaengine/tests/mfmediaengine.c | 201 +++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/dlls/mfmediaengine/tests/mfmediaengine.c b/dlls/mfmediaengine/tests/mfmediaengine.c index 87db94ce18d..361e79e2c63 100644 --- a/dlls/mfmediaengine/tests/mfmediaengine.c +++ b/dlls/mfmediaengine/tests/mfmediaengine.c @@ -2184,6 +2184,11 @@ struct test_seek_notify { IMFMediaEngineNotify IMFMediaEngineNotify_iface; HANDLE playing_event; + HANDLE seeking_event; + HANDLE seeked_event; + HANDLE time_update_event; + BOOL seeking_event_received; + BOOL time_update_event_received; HRESULT expected_error; HRESULT error; LONG refcount; @@ -2223,6 +2228,9 @@ static ULONG WINAPI test_seek_notify_Release(IMFMediaEngineNotify *iface) if (!refcount) { CloseHandle(notify->playing_event); + CloseHandle(notify->seeking_event); + CloseHandle(notify->seeked_event); + CloseHandle(notify->time_update_event); free(notify); } @@ -2239,6 +2247,17 @@ static HRESULT WINAPI test_seek_notify_EventNotify(IMFMediaEngineNotify *iface, case MF_MEDIA_ENGINE_EVENT_PLAYING: SetEvent(notify->playing_event); break; + case MF_MEDIA_ENGINE_EVENT_SEEKING: + notify->seeking_event_received = TRUE; + SetEvent(notify->seeking_event); + break; + case MF_MEDIA_ENGINE_EVENT_SEEKED: + SetEvent(notify->seeked_event); + break; + case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE: + notify->time_update_event_received = TRUE; + SetEvent(notify->time_update_event); + break; case MF_MEDIA_ENGINE_EVENT_ERROR: ok(param2 == notify->expected_error, "Unexpected error %#lx\n", param2); notify->error = param2; @@ -2263,7 +2282,13 @@ static struct test_seek_notify *create_seek_notify(void) object = calloc(1, sizeof(*object)); object->IMFMediaEngineNotify_iface.lpVtbl = &test_seek_notify_vtbl; object->playing_event = CreateEventW(NULL, FALSE, FALSE, NULL); + object->seeking_event = CreateEventW(NULL, FALSE, FALSE, NULL); + object->seeked_event = CreateEventW(NULL, FALSE, FALSE, NULL); + object->time_update_event = CreateEventW(NULL, FALSE, FALSE, NULL); ok(!!object->playing_event, "Failed to create an event, error %lu.\n", GetLastError()); + ok(!!object->seeking_event, "Failed to create an event, error %lu.\n", GetLastError()); + ok(!!object->seeked_event, "Failed to create an event, error %lu.\n", GetLastError()); + ok(!!object->time_update_event, "Failed to create an event, error %lu.\n", GetLastError()); object->refcount = 1; return object; } @@ -2505,6 +2530,181 @@ static void test_media_extension(void) IMFMediaEngineExtension_Release(&extension->IMFMediaEngineExtension_iface); } +#define test_seek_result(a, b, c) _test_seek_result(__LINE__, a, b, c) +static void _test_seek_result(int line, IMFMediaEngineEx *media_engine, + struct test_seek_notify *notify, double expected_time) +{ + static const double allowed_error = 0.05; + static const int timeout = 1000; + double time; + DWORD res; + + ok(notify->seeking_event_received, "Seeking event not received.\n"); + notify->seeking_event_received = FALSE; + res = WaitForSingleObject(notify->seeking_event, timeout); + ok_(__FILE__, line)(!res, "Waiting for seeking event returned %#lx.\n", res); + res = WaitForSingleObject(notify->seeked_event, timeout); + ok_(__FILE__, line)(!res, "Waiting for seeked event returned %#lx.\n", res); + res = WaitForSingleObject(notify->time_update_event, timeout); + ok_(__FILE__, line)(!res, "Waiting for ready event returned %#lx.\n", res); + time = IMFMediaEngineEx_GetCurrentTime(media_engine); + ok_(__FILE__, line)(compare_double(time, expected_time, allowed_error), "Unexpected time %lf.\n", time); +} + +static void test_SetCurrentTime(void) +{ + static const double allowed_error = 0.05; + static const int timeout = 1000; + IMFByteStream *stream, *unseekable_stream = NULL; + double time, duration, start, end; + struct test_seek_notify *notify; + IMFMediaEngineEx *media_engine; + ULONG refcount; + HRESULT hr; + DWORD res; + BOOL ret; + BSTR url; + + notify = create_seek_notify(); + hr = create_media_engine(¬ify->IMFMediaEngineNotify_iface, NULL, DXGI_FORMAT_B8G8R8X8_UNORM, + &IID_IMFMediaEngineEx, (void **)&media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaEngineNotify_Release(¬ify->IMFMediaEngineNotify_iface); + + stream = load_resource(L"i420-64x64.avi", L"video/avi"); + url = SysAllocString(L"i420-64x64.avi"); + hr = IMFMediaEngineEx_SetSourceFromByteStream(media_engine, stream, url); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaEngineEx_Play(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + res = WaitForSingleObject(notify->playing_event, 5000); + ok(!res, "Unexpected res %#lx.\n", res); + + duration = IMFMediaEngineEx_GetDuration(media_engine); + ok(duration > 0, "Got invalid duration.\n"); + start = 0; + end = duration; + + /* Test playing state */ + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, end); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + test_seek_result(media_engine, notify, end); + + /* Test seeking with a negative position */ + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, -1); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + test_seek_result(media_engine, notify, 0); + + /* Test seeking beyond duration */ + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, end + 1); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + test_seek_result(media_engine, notify, end); + + hr = IMFMediaEngineEx_SetCurrentTimeEx(media_engine, start, MF_MEDIA_ENGINE_SEEK_MODE_NORMAL); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + test_seek_result(media_engine, notify, start); + + hr = IMFMediaEngineEx_SetCurrentTimeEx(media_engine, end, MF_MEDIA_ENGINE_SEEK_MODE_APPROXIMATE); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + test_seek_result(media_engine, notify, end); + + /* Test paused state */ + hr = IMFMediaEngineEx_Pause(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, start); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + ok(notify->seeking_event_received, "Seeking event not received.\n"); + notify->seeking_event_received = FALSE; + ok(notify->time_update_event_received, "Time update event not received.\n"); + notify->time_update_event_received = FALSE; + res = WaitForSingleObject(notify->seeking_event, timeout); + ok(!res, "Unexpected res %#lx.\n", res); + res = WaitForSingleObject(notify->seeked_event, timeout); + ok(res == WAIT_TIMEOUT || res == 0, /* No timeout sometimes on Win10+ */ + "Unexpected res %#lx.\n", res); + res = WaitForSingleObject(notify->time_update_event, timeout); + ok(!res, "Unexpected res %#lx.\n", res); + time = IMFMediaEngineEx_GetCurrentTime(media_engine); + ok(compare_double(time, start, allowed_error), "Unexpected time %lf.\n", time); + } + + Sleep(end * 1000); + + ret = IMFMediaEngineEx_IsPaused(media_engine); + ok(ret, "Unexpected ret %d.\n", ret); + time = IMFMediaEngineEx_GetCurrentTime(media_engine); + ok(compare_double(time, start, allowed_error) + || broken(time >= end) /* Windows 11 21H2 AMD GPU TestBot */, "Unexpected time %lf.\n", time); + + hr = IMFMediaEngineEx_Play(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + res = WaitForSingleObject(notify->seeked_event, timeout); + ok(res == WAIT_TIMEOUT, "Unexpected res %#lx.\n", res); + + /* Media engine is shut down */ + hr = IMFMediaEngineEx_Shutdown(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, start); + ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaEngineEx_SetCurrentTimeEx(media_engine, start, MF_MEDIA_ENGINE_SEEK_MODE_NORMAL); + ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr); + + refcount = IMFMediaEngineEx_Release(media_engine); + todo_wine + ok(!refcount, "Got unexpected refcount %lu.\n", refcount); + + /* Unseekable bytestreams */ + notify = create_seek_notify(); + hr = create_media_engine(¬ify->IMFMediaEngineNotify_iface, NULL, DXGI_FORMAT_B8G8R8X8_UNORM, + &IID_IMFMediaEngineEx, (void **)&media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaEngineNotify_Release(¬ify->IMFMediaEngineNotify_iface); + unseekable_stream = create_unseekable_stream(stream); + hr = IMFMediaEngineEx_SetSourceFromByteStream(media_engine, unseekable_stream, url); + todo_wine + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (FAILED(hr)) + goto done; + + hr = IMFMediaEngineEx_Play(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + notify->expected_error = MF_E_INVALIDREQUEST; + res = WaitForSingleObject(notify->playing_event, 5000); + ok(res == S_OK, "Unexpected res %#lx.\n", res); + + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, end); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + ok(!notify->seeking_event_received, "Seeking event received.\n"); + res = WaitForSingleObject(notify->seeking_event, timeout); + ok(res == WAIT_TIMEOUT, "Unexpected res %#lx.\n", res); + res = WaitForSingleObject(notify->seeked_event, timeout); + ok(res == WAIT_TIMEOUT, "Unexpected res %#lx.\n", res); + res = WaitForSingleObject(notify->time_update_event, timeout); + ok(!res, "Unexpected res %#lx.\n", res); + } + +done: + hr = IMFMediaEngineEx_Shutdown(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + refcount = IMFMediaEngineEx_Release(media_engine); + ok(!refcount || broken(refcount == 1) /* Win8.1 */, "Got unexpected refcount %lu.\n", refcount); + IMFByteStream_Release(unseekable_stream); + SysFreeString(url); + IMFByteStream_Release(stream); +} + START_TEST(mfmediaengine) { HRESULT hr; @@ -2540,6 +2740,7 @@ START_TEST(mfmediaengine) test_GetDuration(); test_GetSeekable(); test_media_extension(); + test_SetCurrentTime(); IMFMediaEngineClassFactory_Release(factory); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/5130
Hi, It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated. The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=143328 Your paranoid android. === build (build log) === error: patch failed: dlls/winevulkan/vulkan.c:1983 Task: Patch failed to apply === debian11 (build log) === error: patch failed: dlls/winevulkan/vulkan.c:1983 Task: Patch failed to apply === debian11b (build log) === error: patch failed: dlls/winevulkan/vulkan.c:1983 Task: Patch failed to apply
This merge request was approved by Nikolay Sivov. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/5130
participants (4)
-
Marvin -
Nikolay Sivov (@nsivov) -
Zhiyi Zhang -
Zhiyi Zhang (@zhiyi)