This MR adds tests to `mfplat` to illustrate the timestamp values output on the samples for the respective Byte Stream handlers.
I have also included a fix for a bug in `winegstreamer` (and copied to `mfsrcsnk`) where a `NULL` value for the `pguidTimeFormat` parameter in `IMFMediaSource::Start` causes a segmentation fault. On Windows this is allowed. It is included in this MR as the `mfplat` tests pass NULL (so without this fix, the tests crash).
-- v11: mfplat/tests: Add tests for Byte Stream Timestamps. mfsrcsnk: Allow NULL for time_format. winegstreamer: Allow NULL for time_format. mfplat/tests: Fix leak of media source. mfplat/tests: Fix leak of media events.
From: Brendan McGrath bmcgrath@codeweavers.com
It seems Windows will crash on a subsequent call to MFShutdown if a local byte/scheme handler is registered outside corresponding calls to MFStartup/MFShutdown. --- dlls/mfplat/tests/mfplat.c | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index bdaffee9e83..f814de68427 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -6346,6 +6346,9 @@ static void test_local_handlers(void) return; }
+ hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr); + hr = pMFRegisterLocalSchemeHandler(NULL, NULL); ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr);
@@ -6375,6 +6378,9 @@ static void test_local_handlers(void)
hr = pMFRegisterLocalByteStreamHandler(localW, localW, &local_activate); ok(hr == S_OK, "Failed to register stream handler, hr %#lx.\n", hr); + + hr = MFShutdown(); + ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr); }
static void test_create_property_store(void)
From: Brendan McGrath bmcgrath@codeweavers.com
--- dlls/mfplat/tests/mfplat.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index f814de68427..31f7ba006ce 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -1046,6 +1046,9 @@ static BOOL get_event(IMFMediaEventGenerator *generator, MediaEventType expected
break; } + + IMFMediaEvent_Release(callback->media_event); + callback->media_event = NULL; }
if (callback->media_event)
From: Brendan McGrath bmcgrath@codeweavers.com
If IMFMediaSource::Shutdown is not called, then the streams are not released and they continue to hold a reference to the IMFMediaSource, so the reference count of the source never reaches 0. --- dlls/mfplat/tests/mfplat.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index 31f7ba006ce..1cb60d168e1 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -1233,6 +1233,7 @@ static void test_compressed_media_types(IMFSourceResolver *resolver) IMFStreamDescriptor_Release(sd);
IMFPresentationDescriptor_Release(descriptor); + IMFMediaSource_Shutdown(source); IMFMediaSource_Release(source); IMFByteStream_Release(stream);
@@ -1380,8 +1381,8 @@ static void test_source_resolver(void) ok(mediasource != NULL, "got %p\n", mediasource); ok(obj_type == MF_OBJECT_MEDIASOURCE, "got %d\n", obj_type);
+ IMFMediaSource_Shutdown(mediasource); refcount = IMFMediaSource_Release(mediasource); - todo_wine ok(!refcount, "Unexpected refcount %ld\n", refcount); IMFByteStream_Release(stream);
@@ -1393,7 +1394,11 @@ static void test_source_resolver(void) hr = IMFSourceResolver_CreateObjectFromByteStream(resolver, stream, NULL, MF_RESOLUTION_MEDIASOURCE, NULL, &obj_type, (IUnknown **)&mediasource); ok(hr == S_OK || broken(hr == MF_E_UNSUPPORTED_BYTESTREAM_TYPE) /* w7 || w8 */, "Unexpected hr %#lx.\n", hr); - if (hr == S_OK) IMFMediaSource_Release(mediasource); + if (hr == S_OK) + { + IMFMediaSource_Shutdown(mediasource); + IMFMediaSource_Release(mediasource); + } IMFByteStream_Release(stream);
hr = MFCreateFile(MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST, MF_FILEFLAGS_NONE, filename, &stream); @@ -1405,7 +1410,11 @@ static void test_source_resolver(void) hr = IMFSourceResolver_CreateObjectFromByteStream(resolver, stream, NULL, MF_RESOLUTION_MEDIASOURCE, NULL, &obj_type, (IUnknown **)&mediasource); ok(hr == S_OK || broken(hr == MF_E_UNSUPPORTED_BYTESTREAM_TYPE) /* w7 || w8 */, "Unexpected hr %#lx.\n", hr); - if (hr == S_OK) IMFMediaSource_Release(mediasource); + if (hr == S_OK) + { + IMFMediaSource_Shutdown(mediasource); + IMFMediaSource_Release(mediasource); + } IMFByteStream_Release(stream);
/* stream must have a valid header, media cannot start in the middle of a stream */
From: Brendan McGrath bmcgrath@codeweavers.com
Allow the value of pguidTimeFormat to be NULL on a call to IMFMediaSource::Start. Currently, if NULL is used, Wine crashes with a SIGSEGV.
MS documentation states: This parameter can be NULL. If the value is NULL, it is equivalent to GUID_NULL.
https://learn.microsoft.com/en-us/windows/win32/api/mfidl/nf-mfidl-imfmedias... --- dlls/winegstreamer/media_source.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 88056b27c5d..ab842b3e438 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -1552,6 +1552,9 @@ static HRESULT WINAPI media_source_Start(IMFMediaSource *iface, IMFPresentationD
TRACE("%p, %p, %p, %p.\n", iface, descriptor, time_format, position);
+ if (!time_format) + time_format = &GUID_NULL; + EnterCriticalSection(&source->cs);
if (source->state == SOURCE_SHUTDOWN)
From: Brendan McGrath bmcgrath@codeweavers.com
Allow the value of pguidTimeFormat to be NULL on a call to IMFMediaSource::Start. Currently, if NULL is used, Wine crashes with a SIGSEGV.
MS documentation states: This parameter can be NULL. If the value is NULL, it is equivalent to GUID_NULL.
https://learn.microsoft.com/en-us/windows/win32/api/mfidl/nf-mfidl-imfmedias... --- dlls/mfsrcsnk/media_source.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/dlls/mfsrcsnk/media_source.c b/dlls/mfsrcsnk/media_source.c index 1e7918562fd..d7f20e511c8 100644 --- a/dlls/mfsrcsnk/media_source.c +++ b/dlls/mfsrcsnk/media_source.c @@ -1262,6 +1262,9 @@ static HRESULT WINAPI media_source_Start(IMFMediaSource *iface, IMFPresentationD TRACE("source %p, descriptor %p, format %s, position %s\n", source, descriptor, debugstr_guid(format), debugstr_propvar(position));
+ if (!format) + format = &GUID_NULL; + EnterCriticalSection(&source->cs);
if (source->state == SOURCE_SHUTDOWN)
From: Brendan McGrath bmcgrath@codeweavers.com
--- dlls/mfplat/tests/mfplat.c | 305 +++++++++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+)
diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index 1cb60d168e1..b963a8d1e55 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -86,6 +86,9 @@ DEFINE_GUID(DUMMY_GUID2, 0x12345678,0x1234,0x1234,0x22,0x22,0x22,0x22,0x22,0x22, DEFINE_GUID(DUMMY_GUID3, 0x12345678,0x1234,0x1234,0x23,0x23,0x23,0x23,0x23,0x23,0x23,0x23);
extern const CLSID CLSID_FileSchemePlugin; +extern const CLSID CLSID_AsfByteStreamPlugin; +extern const CLSID CLSID_MPEG4ByteStreamHandlerPlugin; +extern const CLSID CLSID_AVIByteStreamPlugin;
DEFINE_MEDIATYPE_GUID(MEDIASUBTYPE_Base,0); DEFINE_GUID(MEDIASUBTYPE_ABGR32,D3DFMT_A8B8G8R8,0x524f,0x11ce,0x9f,0x53,0x00,0x20,0xaf,0x0b,0xa7,0x70); @@ -13606,6 +13609,307 @@ static void test_undefined_queue_id(void) ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr); }
+static void test_ByteStreamTimestamps(void) +{ + #define TIME (1 << 0) + #define DURATION_VALUE (1 << 1) + #define DURATION_HR (1 << 2) + #define DTS_VALUE (1 << 3) + #define DTS_HR (1 << 4) + #define DURATION (DURATION_VALUE | DURATION_HR) + #define DTS (DTS_VALUE | DTS_HR) + #define ALL (TIME | DURATION | DTS) + + static const WCHAR asf_mime_type[] = L"video/x-ms-wmv"; + static const WCHAR mp4_mime_type[] = L"video/mp4"; + static const WCHAR avi_mime_type[] = L"video/avi"; + + struct timestamps + { + LONGLONG time; + HRESULT duration_hr; + LONGLONG duration; + HRESULT dts_hr; + UINT64 dts; + UINT8 todo; + }; + static const struct + { + const char *name; + const WCHAR *resource; + const WCHAR *mime_type; + UINT32 mime_type_sz; + const CLSID *class_id; + DWORD timeout; + struct timestamps expected[5]; + } + tests[] = + { + { + "asf/wmv1", + L"test-wmv1.wmv", + asf_mime_type, + sizeof(asf_mime_type), + &CLSID_AsfByteStreamPlugin, + 1000, + { + { 0, MF_E_NO_SAMPLE_DURATION, 0, MF_E_ATTRIBUTENOTFOUND, 0, DURATION }, + { 330000, MF_E_NO_SAMPLE_DURATION, 0, MF_E_ATTRIBUTENOTFOUND, 0, DURATION }, + { 660000, MF_E_NO_SAMPLE_DURATION, 0, MF_E_ATTRIBUTENOTFOUND, 0, DURATION }, + { 1000000, MF_E_NO_SAMPLE_DURATION, 0, MF_E_ATTRIBUTENOTFOUND, 0, DURATION }, + { 1330000, MF_E_NO_SAMPLE_DURATION, 0, MF_E_ATTRIBUTENOTFOUND, 0, DURATION }, + }, + }, + { + "mp4/h-264", + L"test-h264.mp4", + mp4_mime_type, + sizeof(mp4_mime_type), + &CLSID_MPEG4ByteStreamHandlerPlugin, + 2000, /* Wine requires over one second to create MPEG4 byte stream handler, Windows is much faster */ + { + { 1333332, S_OK, 333334, S_OK , 666666, TIME | DURATION_VALUE | DTS }, + { 2666665, S_OK, 333333, S_OK , 999999, TIME | DTS }, + { 1999998, S_OK, 333334, S_OK , 1333332, TIME | DURATION_VALUE | DTS }, + { 1666666, S_OK, 333332, MF_E_ATTRIBUTENOTFOUND, 0, DURATION_VALUE }, + { 2333332, S_OK, 333333, S_OK , 1999999, TIME | DTS }, + }, + }, + { + "mp4/mpeg4", + L"test.mp4", + mp4_mime_type, + sizeof(mp4_mime_type), + &CLSID_MPEG4ByteStreamHandlerPlugin, + 2000, /* Wine requires over one second to create MPEG4 byte stream handler, Windows is much faster */ + { + { 0, S_OK, 400000, MF_E_ATTRIBUTENOTFOUND, 0 }, + { 400000, S_OK, 400000, MF_E_ATTRIBUTENOTFOUND, 0 }, + { 800000, S_OK, 400000, MF_E_ATTRIBUTENOTFOUND, 0 }, + { 1200000, S_OK, 400000, MF_E_ATTRIBUTENOTFOUND, 0 }, + { 1600000, S_OK, 400000, MF_E_ATTRIBUTENOTFOUND, 0 }, + }, + }, + { + "avi/i420", + L"test-i420.avi", + avi_mime_type, + sizeof(avi_mime_type), + &CLSID_AVIByteStreamPlugin, + 1000, + { + { 0, S_OK, 333333, MF_E_ATTRIBUTENOTFOUND, 0 }, + { 333333, S_OK, 333333, MF_E_ATTRIBUTENOTFOUND, 0 }, + { 666666, S_OK, 333333, MF_E_ATTRIBUTENOTFOUND, 0 }, + { 1000000, S_OK, 333333, MF_E_ATTRIBUTENOTFOUND, 0 }, + { 1333333, S_OK, 333333, MF_E_ATTRIBUTENOTFOUND, 0 }, + }, + }, + }; + + IMFByteStreamHandler *byte_stream_handler; + const struct timestamps *expected; + struct test_callback *callback; + IMFPresentationDescriptor *pd; + IMFByteStream *byte_stream; + MF_OBJECT_TYPE object_type; + IMFAttributes *attributes; + LONGLONG time, duration; + IMFMediaSource *source; + IMFMediaStream *stream; + IMFMediaEvent *event; + MediaEventType met; + PROPVARIANT value; + IMFSample *sample; + WCHAR *filename; + HRESULT hr; + UINT64 dts; + ULONG ret; + INT i, j; + + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + for (i = 0; i < ARRAYSIZE(tests); i++) + { + winetest_push_context("%s timestamps", tests[i].name); + + filename = load_resource(tests[i].resource); + + hr = MFCreateFile(MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST, MF_FILEFLAGS_NONE, filename, &byte_stream); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMFByteStream_QueryInterface(byte_stream, &IID_IMFAttributes, (void**)&attributes); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMFAttributes_SetBlob(attributes, &MF_BYTESTREAM_CONTENT_TYPE, + (const UINT8*) tests[i].mime_type, tests[i].mime_type_sz); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ret = IMFAttributes_Release(attributes); + ok(ret == 1, "Unexpected reference count %ld\n", ret); + + hr = CoCreateInstance(tests[i].class_id, NULL, CLSCTX_INPROC_SERVER, + &IID_IMFByteStreamHandler, (void**)&byte_stream_handler); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + callback = create_test_callback(&test_async_callback_result_vtbl); + + hr = IMFByteStreamHandler_BeginCreateObject(byte_stream_handler, byte_stream, + filename, MF_RESOLUTION_MEDIASOURCE, NULL, NULL, &callback->IMFAsyncCallback_iface, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + /* we don't test the reference count here as it fluctuates with time + * and implementation which are details we don't need/want to test + */ + IMFByteStream_Release(byte_stream); + + if (WaitForSingleObject(callback->event, tests[i].timeout) == WAIT_TIMEOUT) + { + ok(0, "timeout\n"); + IMFAsyncCallback_Release(&callback->IMFAsyncCallback_iface); + IMFByteStreamHandler_Release(byte_stream_handler); + goto next_test; + } + + hr = IMFByteStreamHandler_EndCreateObject(byte_stream_handler, callback->result, + &object_type, (IUnknown**)&source); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(object_type == MF_OBJECT_MEDIASOURCE, "Unexpected object_type %d\n", object_type); + + Sleep(10); /* To ensure reference count is 0, we sleep */ + ret = IMFAsyncResult_Release(callback->result); + ok(ret == 0, "Unexpected reference count %ld\n", ret); + + ret = IMFAsyncCallback_Release(&callback->IMFAsyncCallback_iface); + ok(ret == 0, "Unexpected reference count %ld\n", ret); + + ret = IMFByteStreamHandler_Release(byte_stream_handler); + ok(ret == 0, "Unexpected reference count %ld\n", ret); + + hr = IMFMediaSource_CreatePresentationDescriptor(source, &pd); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + PropVariantInit(&value); + hr = IMFMediaSource_Start(source, pd, NULL, &value); + + hr = IMFMediaSource_GetEvent(source, 0, &event); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMFMediaEvent_GetType(event, &met); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(met == MENewStream, "Got unexpected event %ld\n", met); + + hr = IMFMediaEvent_GetValue(event, &value); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(value.vt == VT_UNKNOWN, "Unexpected value type %d\n", value.vt); + + IMFMediaEvent_Release(event); + + hr = IUnknown_QueryInterface(value.punkVal, &IID_IMFMediaStream, (void**)&stream); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = PropVariantClear(&value); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMFMediaStream_GetEvent(stream, 0, &event); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMFMediaEvent_GetType(event, &met); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(met == MEStreamStarted, "Got unexpected event %ld\n", met); + + IMFMediaEvent_Release(event); + + hr = IMFMediaSource_GetEvent(source, 0, &event); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMFMediaEvent_GetType(event, &met); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(met == MESourceStarted, "Got unexpected event %ld\n", met); + + IMFMediaEvent_Release(event); + + for (j = 0; j < ARRAYSIZE(tests[i].expected); j++) + { + winetest_push_context("sample %d", j); + + expected = tests[i].expected + j; + + /* Request a media sample */ + hr = IMFMediaStream_RequestSample(stream, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMFMediaStream_GetEvent(stream, 0, &event); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMFMediaEvent_GetType(event, &met); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(met == MEMediaSample, "Got unexpected event %ld\n", met); + + if (met == MEMediaSample) + { + hr = IMFMediaEvent_GetValue(event, &value); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(value.vt == VT_UNKNOWN, "Unexpected value type %d\n", value.vt); + + hr = IUnknown_QueryInterface(value.punkVal, &IID_IMFSample, (void**)&sample); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = PropVariantClear(&value); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + time = -1; + hr = IMFSample_GetSampleTime(sample, &time); + ok(hr == S_OK, "Got unexpected hr %#lx when requesting sample time, expected S_OK.\n", hr); + todo_wine_if(expected->todo & TIME) + ok(time == expected->time, "Unexpected sample time %I64d, expected %I64d\n", time, expected->time); + + duration = -1; + hr = IMFSample_GetSampleDuration(sample, &duration); + todo_wine_if(expected->todo & DURATION_HR) + ok(hr == expected->duration_hr, + "Got hr %#lx when requesting sample duration, expected %#lx.\n", hr, expected->duration_hr); + if (expected->duration_hr == S_OK) + todo_wine_if(expected->todo & DURATION_VALUE) + ok(duration == expected->duration, + "Unexpected sample duration %I64d, expected %I64d\n", duration, expected->duration); + dts = -1; + hr = IMFSample_GetUINT64(sample, &MFSampleExtension_DecodeTimestamp, &dts); + todo_wine_if(expected->todo & DTS_HR) + ok(hr == expected->dts_hr, + "Got unexpected hr %#lx when requesting DTS, expected %#lx.\n", hr, expected->dts_hr); + if (expected->dts_hr == S_OK) + todo_wine_if(expected->todo & DTS_VALUE) + ok(dts == expected->dts, "Unexpected sample dts %I64d, expected %I64d\n", dts, expected->dts); + + IMFSample_Release(sample); + } + + IMFMediaEvent_Release(event); + + winetest_pop_context(); + } + + IMFMediaStream_Release(stream); + + hr = IMFMediaSource_Shutdown(source); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ret = IMFMediaSource_Release(source); + ok(ret == 0, "Unexpected reference count %ld\n", ret); + +next_test: + ret = DeleteFileW(filename); + ok(ret, "Failed to delete %s, error %lu.\n", debugstr_w(filename), GetLastError()); + + winetest_pop_context(); + } + + hr = MFShutdown(); + ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr); +} + START_TEST(mfplat) { char **argv; @@ -13709,6 +14013,7 @@ START_TEST(mfplat) test_MFInitMediaTypeFromAMMediaType(); test_MFCreatePathFromURL(); test_2dbuffer_copy(); + test_ByteStreamTimestamps();
CoUninitialize(); }
On Fri Mar 14 20:23:51 2025 +0000, Brendan McGrath wrote:
Actually, I think the DTS value in the file is a delta. In that the actual DTS value is PTS - DTS. Certainly the difference between the Windows (and FFmpeg) PTS and DTS of each sample is the DTS value from the file. So I guess it's just a matter of understanding why the PTS values are different. Possibly Windows adjusts the PTS values so that there are no negative DTS values (given its passed as an unsigned 64-bit value). I'm thinking we should probably do the same.
I looked further in to the MP4 format and I've got a better understanding now. It looks like the Windows values are right. There's an 'Edit List' atom in the file with a start time of 666666, which is why Windows earliest DTS is 666666. It looks like GStreamer uses that value to set the earliest PTS (I'm not sure which one is actually right). But the values I used as PTS is actually DTS - this start time. And finally PTS is the start time + DTS (which I listed as PTS) + the delta (which I listed as DTS). So I've added another column to the table above to show the 'Fixed file value'. It's very similar to Windows.
Nikolay Sivov (@nsivov) commented about dlls/mfplat/tests/mfplat.c:
break; }
IMFMediaEvent_Release(callback->media_event);
}callback->media_event = NULL;
This is fixing the leak of "unexpected" events that happened before the one we are checking for, right?