From: Rémi Bernon rbernon@codeweavers.com
Unreal Engine has a race condition in its media player where it starts the resolution of a media source, waiting for its completion in an async task, and expects to set the media player source from its result.
At the same time, the media player uses timer to tick every 200ms or so, and if the first tick happens before the media source resolution end, the media player ends up in a corrupted state and playback may fail or get stuck.
On Windows, the media source resolution takes very little time, and usually less than 1ms. On Wine, because of the current design of the media soruce, it can often take more than 200ms depending on the setup, and very rarely less than 50ms. --- dlls/mf/tests/mf.c | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+)
diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index e8d7252aa3f..5fd8adb2f6b 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -41,6 +41,9 @@ #include "initguid.h" #include "evr9.h"
+DEFINE_GUID(CLSID_MPEG4ByteStreamHandler,0x271c3902,0x6095,0x4c45,0xa2,0x2f,0x20,0x09,0x18,0x16,0xee,0x9e); +DEFINE_GUID(CLSID_GStreamerByteStreamHandler,0x317df618,0x5e5a,0x468a,0x9f,0x15,0xd8,0x27,0xa9,0xa0,0x81,0x62); + #define DEFINE_EXPECT(func) \ static BOOL expect_ ## func = FALSE, called_ ## func = FALSE
@@ -1815,6 +1818,7 @@ struct test_callback LONG refcount;
HANDLE event; + IMFAsyncResult *result; IMFMediaEvent *media_event; BOOL check_media_event; }; @@ -1851,6 +1855,8 @@ static ULONG WINAPI testcallback_Release(IMFAsyncCallback *iface)
if (!refcount) { + if (callback->result) + IMFAsyncResult_Release(callback->result); if (callback->media_event) IMFMediaEvent_Release(callback->media_event); CloseHandle(callback->event); @@ -1890,6 +1896,7 @@ static HRESULT WINAPI testcallback_Invoke(IMFAsyncCallback *iface, IMFAsyncResul IUnknown_Release(object); }
+ IMFAsyncResult_AddRef((callback->result = result)); SetEvent(callback->event);
return S_OK; @@ -1920,6 +1927,18 @@ static IMFAsyncCallback *create_test_callback(BOOL check_media_event) return &callback->IMFAsyncCallback_iface; }
+static void wait_async_result(IMFAsyncCallback *iface, IMFAsyncResult **result) +{ + struct test_callback *impl = impl_from_IMFAsyncCallback(iface); + UINT ret; + + ret = WaitForSingleObject(impl->event, 5000); + ok(!ret, "WaitForSingleObject returned %#x\n", ret); + + if ((*result = impl->result)) + impl->result = NULL; +} + #define wait_media_event(a, b, c, d, e) wait_media_event_(__LINE__, a, b, c, d, e) static HRESULT wait_media_event_(int line, IMFMediaSession *session, IMFAsyncCallback *callback, MediaEventType expect_type, DWORD timeout, PROPVARIANT *value) @@ -6704,6 +6723,106 @@ static void test_MFRequireProtectedEnvironment(void) ok(ref == 0, "Release returned %ld\n", ref); }
+static void load_resource_stream(const WCHAR *name, IMFByteStream **stream) +{ + HRSRC resource = FindResourceW(NULL, name, (const WCHAR *)RT_RCDATA); + void *resource_data; + DWORD resource_len; + HRESULT hr; + + ok(resource != 0, "FindResourceW %s failed, error %lu\n", debugstr_w(name), GetLastError()); + resource_data = LockResource(LoadResource(GetModuleHandleW(NULL), resource)); + resource_len = SizeofResource(GetModuleHandleW(NULL), resource); + + hr = MFCreateTempFile(MF_ACCESSMODE_READWRITE, MF_OPENMODE_DELETE_IF_EXIST, MF_FILEFLAGS_NONE, stream); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFByteStream_Write(*stream, resource_data, resource_len, &resource_len); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFByteStream_SetCurrentPosition(*stream, 0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +} + +static void subtest_media_source(const WCHAR *resource, const GUID *guid) +{ + IMFByteStreamHandler *handler; + IMFAsyncCallback *callback; + IMFAsyncResult *result; + IMFByteStream *stream; + IUnknown *tmp_unknown; + MF_OBJECT_TYPE type; + DWORD time = 0; + QWORD bytes; + HRESULT hr; + + if (!strcmp(winetest_platform, "wine")) guid = &CLSID_GStreamerByteStreamHandler; + + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + ok(hr == S_OK, "got hr %#lx\n", hr); + + callback = create_test_callback(FALSE); + ok(!!callback, "got callback %p\n", callback); + + hr = CoCreateInstance(guid, NULL, CLSCTX_INPROC_SERVER, &IID_IMFByteStreamHandler, (void **)&handler); + ok(hr == S_OK, "got hr %#lx\n", hr); + + check_interface(handler, &IID_IUnknown, TRUE); + check_interface(handler, &IID_IMFAsyncCallback, FALSE); + + hr = IMFByteStreamHandler_GetMaxNumberOfBytesRequiredForResolution(handler, &bytes); + todo_wine ok(hr == S_OK, "got hr %#lx\n", hr); + + + load_resource_stream(resource, &stream); + + time -= GetTickCount(); + hr = IMFByteStreamHandler_BeginCreateObject(handler, stream, NULL, MF_RESOLUTION_MEDIASOURCE, NULL, NULL, callback, NULL); + ok(hr == S_OK, "got hr %#lx\n", hr); + wait_async_result(callback, &result); + time += GetTickCount(); + todo_wine ok(time <= 5 || broken(time <= 20) /* testbot may be very slow */, "source resolution took %lums\n", time); + ok(!!result, "got result %p\n", result); + + IMFByteStream_Release(stream); + + + hr = IMFAsyncResult_GetStatus(result); + if (hr == MF_E_CANNOT_PARSE_BYTESTREAM) + { + hr = IMFByteStreamHandler_EndCreateObject(handler, result, &type, &tmp_unknown); + ok(hr == MF_E_CANNOT_PARSE_BYTESTREAM, "got hr %#lx\n", hr); + IMFAsyncResult_Release(result); + + win_skip("MP4 media source is not supported, skipping tests.\n"); + goto skip_tests; + } + + ok(hr == S_OK, "got hr %#lx\n", hr); + hr = IMFAsyncResult_GetObject(result, &tmp_unknown); + todo_wine ok(hr == S_OK, "got hr %#lx\n", hr); + todo_wine ok(tmp_unknown == (void *)handler, "got object %p\n", tmp_unknown); + if (handler == (void *)tmp_unknown) IUnknown_Release(tmp_unknown); + hr = IMFAsyncResult_GetState(result, &tmp_unknown); + ok(hr == E_POINTER, "got hr %#lx\n", hr); + + hr = IMFByteStreamHandler_EndCreateObject(handler, result, &type, &tmp_unknown); + ok(hr == S_OK, "got hr %#lx\n", hr); + IMFAsyncResult_Release(result); + + IUnknown_Release(tmp_unknown); + +skip_tests: + IMFByteStreamHandler_Release(handler); + IMFAsyncCallback_Release(callback); + + hr = MFShutdown(); + ok(hr == S_OK, "got hr %#lx\n", hr); +} + +static void test_media_source(void) +{ + subtest_media_source(L"test.mp4", &CLSID_MPEG4ByteStreamHandler); +} + START_TEST(mf) { init_functions(); @@ -6737,4 +6856,5 @@ START_TEST(mf) test_MFGetSupportedSchemes(); test_MFGetTopoNodeCurrentType(); test_MFRequireProtectedEnvironment(); + test_media_source(); }