Follow-up to !8184.
This also adds a bunch of helpers for writing media source tests (without involving the mf session) in mfsrcsnk, these could be helpful for more tests of this kind in the future. I'm somewhat unsure as to why we haven't done tests for source behavior so far: while those are plugins, applications rely on a bunch of things in the builtin sources that microsoft provides.
Since this affects both mfsrcsnk and winegstreamer sources, tests should be run both with `HKCU\Software\Wine\MediaFoundation\DisableGstByteStreamHandler` enabled and disabled. Due to this I figured it'd make sense to implement everything first and then remove the test todo_wine's and the statements rejecting the thin parameter in a single commit ("Allow thinning"). Perhaps this could be solved more nicely by setting the registry key in the tests themselves? I'm unsure if that is something we do in tests, generally.
`MEStreamThinMode` events need to be emitted between the last sample using the outdated thinning parameter and the first sample using the updated thinning parameter. The winegstreamer implementation for this turned out a bit complex, if there is a simpler way to do this please let me know.
Regarding winegstreamer, note that buffers need to be intercepted before the decoder because decoders often discard `GST_BUFFER_FLAG_DELTA_UNIT` flags (which is already annoying in itself - it causes all samples to be marked as `MFSampleExtension_CleanPoint`, this should potentially be worked around in the future). But even besides that, intercepting before the decoder is the "proper" implementation, since the point of thinning is increasing decoding speed by skipping delta frames, tho there are some games that rely on the semantics as well.
-- v2: mfsrcsnk: Emit MEStreamThinMode event. mfsrcsnk: Move media_source_send_sample. mfsrcsnk: Implement thinning. winedmo: Implement thinning. winedmo: Generate timestamps if missing. winegstreamer: Implement thinning in media source. winegstreamer: Implement thinning in wg_parser. mfsrcsnk/tests: Add tests for thinning.
From: Charlotte Pabst cpabst@codeweavers.com
--- dlls/mfsrcsnk/tests/Makefile.in | 3 +- dlls/mfsrcsnk/tests/mfsrcsnk.c | 528 ++++++++++++++++++++++++++ dlls/mfsrcsnk/tests/resource.rc | 31 ++ dlls/mfsrcsnk/tests/test_thinning.avi | Bin 0 -> 7716 bytes 4 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 dlls/mfsrcsnk/tests/resource.rc create mode 100644 dlls/mfsrcsnk/tests/test_thinning.avi
diff --git a/dlls/mfsrcsnk/tests/Makefile.in b/dlls/mfsrcsnk/tests/Makefile.in index 55a84256266..89889d6f723 100644 --- a/dlls/mfsrcsnk/tests/Makefile.in +++ b/dlls/mfsrcsnk/tests/Makefile.in @@ -2,4 +2,5 @@ TESTDLL = mfsrcsnk.dll IMPORTS = ole32 mfsrcsnk mfplat mf uuid mfuuid
SOURCES = \ - mfsrcsnk.c + mfsrcsnk.c \ + resource.rc diff --git a/dlls/mfsrcsnk/tests/mfsrcsnk.c b/dlls/mfsrcsnk/tests/mfsrcsnk.c index 1aba4094c62..e78fe8adbb8 100644 --- a/dlls/mfsrcsnk/tests/mfsrcsnk.c +++ b/dlls/mfsrcsnk/tests/mfsrcsnk.c @@ -26,9 +26,31 @@ #include "mfapi.h" #include "mfidl.h" #include "mferror.h" +#include "wine/mfinternal.h"
#include "wine/test.h"
+static const char *debugstr_time(LONGLONG time) +{ + ULONGLONG abstime = time >= 0 ? time : -time; + unsigned int i = 0, j = 0; + char buffer[23], rev[23]; + + while (abstime || i <= 8) + { + buffer[i++] = '0' + (abstime % 10); + abstime /= 10; + if (i == 7) buffer[i++] = '.'; + } + if (time < 0) buffer[i++] = '-'; + + while (i--) rev[j++] = buffer[i]; + while (rev[j-1] == '0' && rev[j-2] != '.') --j; + rev[j] = 0; + + return wine_dbg_sprintf("%s", rev); +} + #define check_interface(a, b, c) check_interface_(__LINE__, a, b, c) static void check_interface_(unsigned int line, void *iface_ptr, REFIID iid, BOOL supported) { @@ -204,6 +226,511 @@ static void test_wave_sink(void) IMFByteStream_Release(bytestream); }
+struct source_create_callback +{ + IMFAsyncCallback iface; + LONG refcount; + + IMFByteStreamHandler *handler; + HRESULT hr; + MF_OBJECT_TYPE type; + IUnknown *object; + HANDLE event; +}; + +struct source_create_callback *source_create_callback_from_iface(IMFAsyncCallback *iface) +{ + return CONTAINING_RECORD(iface, struct source_create_callback, iface); +} + +static HRESULT WINAPI source_create_callback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj) +{ + if (IsEqualIID(riid, &IID_IMFAsyncCallback) || IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IMFAsyncCallback_AddRef(iface); + return S_OK; + } + + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI source_create_callback_AddRef(IMFAsyncCallback *iface) +{ + struct source_create_callback *callback = source_create_callback_from_iface(iface); + return InterlockedIncrement(&callback->refcount); +} + +static ULONG WINAPI source_create_callback_Release(IMFAsyncCallback *iface) +{ + struct source_create_callback *callback = source_create_callback_from_iface(iface); + ULONG refcount = InterlockedDecrement(&callback->refcount); + if (refcount == 0) + { + if (callback->object) + IUnknown_Release(callback->object); + IMFByteStreamHandler_Release(callback->handler); + CloseHandle(callback->event); + free(callback); + } + return refcount; +} + +static HRESULT WINAPI source_create_callback_GetParameters(IMFAsyncCallback *iface, DWORD *flags, DWORD *queue) +{ + return E_NOTIMPL; +} + +static HRESULT WINAPI source_create_callback_stream_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) +{ + struct source_create_callback *callback = source_create_callback_from_iface(iface); + callback->hr = IMFByteStreamHandler_EndCreateObject(callback->handler, result, &callback->type, + &callback->object); + SetEvent(callback->event); + return callback->hr; +} + +static const IMFAsyncCallbackVtbl source_create_callback_vtbl = +{ + &source_create_callback_QueryInterface, + &source_create_callback_AddRef, + &source_create_callback_Release, + &source_create_callback_GetParameters, + &source_create_callback_stream_Invoke, +}; + +static HRESULT create_source(const GUID *guid_handler, IMFByteStream *stream, IMFMediaSource **source) +{ + HRESULT hr; + IMFByteStreamHandler *handler; + struct source_create_callback *callback; + + if (!(callback = calloc(1, sizeof *callback))) + return E_OUTOFMEMORY; + hr = CoCreateInstance(guid_handler, NULL, CLSCTX_INPROC_SERVER, &IID_IMFByteStreamHandler, (void **)&handler); + if (FAILED(hr)) + { + free(callback); + return hr; + } + callback->iface.lpVtbl = &source_create_callback_vtbl; + callback->refcount = 1; + callback->handler = handler; + callback->object = NULL; + callback->type = MF_OBJECT_INVALID; + callback->hr = E_PENDING; + callback->event = CreateEventW(NULL, FALSE, FALSE, NULL); + + hr = IMFByteStreamHandler_BeginCreateObject(callback->handler, stream, NULL, + MF_RESOLUTION_MEDIASOURCE, NULL, NULL, &callback->iface, NULL); + if (FAILED(hr)) + goto done; + + WaitForSingleObject(callback->event, INFINITE); + if (FAILED(hr = callback->hr)) + goto done; + if (callback->type != MF_OBJECT_MEDIASOURCE) + { + hr = E_UNEXPECTED; + goto done; + } + + hr = S_OK; + *source = (IMFMediaSource *)callback->object; + callback->object = NULL; + +done: + IMFAsyncCallback_Release(&callback->iface); + return hr; +} + +static IMFByteStream *create_byte_stream(const BYTE *data, ULONG data_len) +{ + IMFByteStream *stream; + HRESULT hr; + + 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, data, data_len, &data_len); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFByteStream_SetCurrentPosition(stream, 0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + return stream; +} + +static IMFByteStream *create_resource_byte_stream(const WCHAR *name) +{ + const BYTE *resource_data; + ULONG resource_len; + HRSRC resource; + + resource = FindResourceW(NULL, name, (const WCHAR *)RT_RCDATA); + 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); + + return create_byte_stream(resource_data, resource_len); +} + +struct test_callback +{ + IMFAsyncCallback IMFAsyncCallback_iface; + LONG refcount; + + HANDLE event; + IMFMediaEvent *media_event; + BOOL check_media_event; +}; + +static struct test_callback *impl_from_IMFAsyncCallback(IMFAsyncCallback *iface) +{ + return CONTAINING_RECORD(iface, struct test_callback, IMFAsyncCallback_iface); +} + +static HRESULT WINAPI testcallback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj) +{ + if (IsEqualIID(riid, &IID_IMFAsyncCallback) || + IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IMFAsyncCallback_AddRef(iface); + return S_OK; + } + + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI testcallback_AddRef(IMFAsyncCallback *iface) +{ + struct test_callback *callback = impl_from_IMFAsyncCallback(iface); + return InterlockedIncrement(&callback->refcount); +} + +static ULONG WINAPI testcallback_Release(IMFAsyncCallback *iface) +{ + struct test_callback *callback = impl_from_IMFAsyncCallback(iface); + ULONG refcount = InterlockedDecrement(&callback->refcount); + + if (!refcount) + { + if (callback->media_event) + IMFMediaEvent_Release(callback->media_event); + CloseHandle(callback->event); + free(callback); + } + + return refcount; +} + +static HRESULT WINAPI testcallback_GetParameters(IMFAsyncCallback *iface, DWORD *flags, DWORD *queue) +{ + ok(flags != NULL && queue != NULL, "Unexpected arguments.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI testcallback_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) +{ + struct test_callback *callback = CONTAINING_RECORD(iface, struct test_callback, IMFAsyncCallback_iface); + IUnknown *object; + HRESULT hr; + + ok(result != NULL, "Unexpected result object.\n"); + + if (callback->media_event) + IMFMediaEvent_Release(callback->media_event); + + if (callback->check_media_event) + { + hr = IMFAsyncResult_GetObject(result, &object); + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + + hr = IMFAsyncResult_GetState(result, &object); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + callback->media_event = (void *)0xdeadbeef; + hr = IMFMediaEventGenerator_EndGetEvent((IMFMediaEventGenerator *)object, + result, &callback->media_event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IUnknown_Release(object); + } + + SetEvent(callback->event); + + return S_OK; +} + +static const IMFAsyncCallbackVtbl testcallbackvtbl = +{ + testcallback_QueryInterface, + testcallback_AddRef, + testcallback_Release, + testcallback_GetParameters, + testcallback_Invoke, +}; + +static IMFAsyncCallback *create_test_callback(BOOL check_media_event) +{ + struct test_callback *callback; + + if (!(callback = calloc(1, sizeof(*callback)))) + return NULL; + + callback->refcount = 1; + callback->check_media_event = check_media_event; + callback->IMFAsyncCallback_iface.lpVtbl = &testcallbackvtbl; + callback->event = CreateEventW(NULL, FALSE, FALSE, NULL); + ok(!!callback->event, "CreateEventW failed, error %lu\n", GetLastError()); + + return &callback->IMFAsyncCallback_iface; +} + +#define next_media_event(a, b, c, d) next_media_event_(__LINE__, (IMFMediaEventGenerator *)a, b, c, d) +static HRESULT next_media_event_(int line, IMFMediaEventGenerator *source, IMFAsyncCallback *callback, DWORD timeout, + IMFMediaEvent **event) +{ + struct test_callback *impl = impl_from_IMFAsyncCallback(callback); + HRESULT hr; + DWORD ret; + + hr = IMFMediaEventGenerator_BeginGetEvent(source, &impl->IMFAsyncCallback_iface, (IUnknown *)source); + ok_(__FILE__, line)(hr == S_OK || hr == MF_S_MULTIPLE_BEGIN, "Unexpected hr %#lx.\n", hr); + ret = WaitForSingleObject(impl->event, timeout); + *event = impl->media_event; + impl->media_event = NULL; + + return ret; +} + +#define wait_media_event(a, b, c, d, e) wait_media_event_(__LINE__, (IMFMediaEventGenerator *)a, b, c, d, e) +static HRESULT wait_media_event_(int line, IMFMediaEventGenerator *source, IMFAsyncCallback *callback, + MediaEventType expect_type, DWORD timeout, PROPVARIANT *value) +{ + IMFMediaEvent *event = NULL; + MediaEventType type; + HRESULT hr, status; + DWORD ret; + GUID guid; + + do + { + if (event) IMFMediaEvent_Release(event); + ret = next_media_event(source, callback, timeout, &event); + if (ret) return MF_E_NO_EVENTS_AVAILABLE; + hr = IMFMediaEvent_GetType(event, &type); + ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok_(__FILE__, line)(type == expect_type, "got %#lx.\n", type); + } while (type != expect_type); + + hr = IMFMediaEvent_GetExtendedType(event, &guid); + ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok_(__FILE__, line)(IsEqualGUID(&guid, &GUID_NULL), "got extended type %s\n", debugstr_guid(&guid)); + + hr = IMFMediaEvent_GetValue(event, value); + ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaEvent_GetStatus(event, &status); + ok_(__FILE__, line)(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMFMediaEvent_Release(event); + return status; +} + +static void test_sample_times_at_rate(IMFMediaSource *source, FLOAT rate, BOOL thin) +{ + static LONGLONG expect_times[] = + { + 0, 333666, + 1668333, 333666, + 2002000, 333666, + 2335666, 333666, + 2669333, 333666, + }; + static LONGLONG expect_times_thin[ARRAY_SIZE(expect_times)] = + { + 0, 333666, + 1668333, 333666, + 3336666, 333666, + 5005000, 333666, + 6673333, 333666, + }; + IMFAsyncCallback *callback; + IMFRateControl *rate_control; + IMFPresentationDescriptor *pd; + IMFMediaStream *stream; + IMFMediaEvent *event; + PROPVARIANT value; + LONGLONG time; + HRESULT hr; + DWORD ret; + + winetest_push_context("%f/%u", rate, thin); + + hr = IMFMediaSource_CreatePresentationDescriptor(source, &pd); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + value.vt = VT_EMPTY; + hr = IMFMediaSource_Start(source, pd, &GUID_NULL, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFPresentationDescriptor_Release(pd); + + callback = create_test_callback(TRUE); + if (!winetest_platform_is_wine) + { + hr = wait_media_event(source, callback, thin ? MENewStream : MEUpdatedStream, 100, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + } + else + { + ret = next_media_event(source, callback, 100, &event); + ok(ret == 0, "Unexpected ret %#lx.\n", ret); + hr = IMFMediaEvent_GetType(event, &ret); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine_if(!thin) + ok(ret == (thin ? MENewStream : MEUpdatedStream), "Unexpected type %#lx.\n", ret); + hr = IMFMediaEvent_GetValue(event, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaEvent_Release(event); + } + ok(value.vt == VT_UNKNOWN, "got vt %u\n", value.vt); + stream = (IMFMediaStream *)value.punkVal; + IMFMediaStream_AddRef(stream); + PropVariantClear(&value); + + hr = wait_media_event(stream, callback, MEStreamStarted, 100, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(value.vt == VT_I8, "got vt %u\n", value.vt); + hr = wait_media_event(source, callback, MESourceStarted, 100, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(value.vt == VT_I8, "got vt %u\n", value.vt); + + winetest_push_context("sample 0"); + + hr = IMFMediaStream_RequestSample(stream, NULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(stream, callback, MEMediaSample, 100, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(value.vt == VT_UNKNOWN, "got vt %u\n", value.vt); + hr = IMFSample_GetSampleTime((IMFSample *)value.punkVal, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(time == expect_times[0], "Unexpected time %s.\n", debugstr_time(time)); + hr = IMFSample_GetSampleDuration((IMFSample *)value.punkVal, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(time == expect_times[1], "Unexpected time %s.\n", debugstr_time(time)); + hr = IMFSample_GetSampleFlags((IMFSample *)value.punkVal, &ret); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(ret == 0, "Unexpected flags %#lx.\n", ret); + PropVariantClear(&value); + + winetest_pop_context(); + + hr = MFGetService((IUnknown *)source, &MF_RATE_CONTROL_SERVICE, &IID_IMFRateControl, (void **)&rate_control); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFRateControl_SetRate(rate_control, thin, rate); + todo_wine_if(thin && hr == MF_E_THINNING_UNSUPPORTED) + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFRateControl_Release(rate_control); + + hr = wait_media_event(source, callback, MESourceRateChanged, 100, &value); + todo_wine_if(thin) + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine + ok(value.vt == VT_R4, "got vt %u\n", value.vt); + ret = next_media_event(source, callback, 100, &event); + ok(ret == WAIT_TIMEOUT, "Unexpected ret %#lx.\n", ret); + + hr = IMFMediaStream_RequestSample(stream, NULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (!winetest_platform_is_wine) + { + hr = wait_media_event(stream, callback, MEStreamThinMode, 100, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(value.vt == VT_INT, "got vt %u\n", value.vt); + ok(value.iVal == thin, "Unexpected thin %d\n", value.iVal); + } + + winetest_push_context("sample 1"); + + hr = wait_media_event(stream, callback, MEMediaSample, 100, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(value.vt == VT_UNKNOWN, "got vt %u\n", value.vt); + hr = IMFSample_GetSampleTime((IMFSample *)value.punkVal, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine + ok(time == expect_times[2], "Unexpected time %s.\n", debugstr_time(time)); + hr = IMFSample_GetSampleDuration((IMFSample *)value.punkVal, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(time == expect_times[3], "Unexpected time %s.\n", debugstr_time(time)); + hr = IMFSample_GetSampleFlags((IMFSample *)value.punkVal, &ret); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(ret == 0, "Unexpected flags %#lx.\n", ret); + PropVariantClear(&value); + + winetest_pop_context(); + + for (int i = 2; i < ARRAY_SIZE(expect_times) / 2; i++) + { + winetest_push_context("sample %u", i); + + hr = IMFMediaStream_RequestSample(stream, NULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(stream, callback, MEMediaSample, 100, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(value.vt == VT_UNKNOWN, "got vt %u\n", value.vt); + hr = IMFSample_GetSampleTime((IMFSample *)value.punkVal, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine + ok(time == (thin ? expect_times_thin[2 * i] : expect_times[2 * i]), "Unexpected time %s.\n", debugstr_time(time)); + hr = IMFSample_GetSampleDuration((IMFSample *)value.punkVal, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(time == (thin ? expect_times_thin[2 * i + 1] : expect_times[2 * i + 1]), "Unexpected time %s.\n", debugstr_time(time)); + hr = IMFSample_GetSampleFlags((IMFSample *)value.punkVal, &ret); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(ret == 0, "Unexpected flags %#lx.\n", ret); + PropVariantClear(&value); + + winetest_pop_context(); + } + + hr = IMFMediaSource_Stop(source); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(source, callback, MESourceStopped, 100, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(value.vt == VT_EMPTY, "got vt %u\n", value.vt); + hr = wait_media_event(stream, callback, MEStreamStopped, 100, &value); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(value.vt == VT_EMPTY, "got vt %u\n", value.vt); + + IMFMediaStream_Release(stream); + IMFAsyncCallback_Release(callback); + + winetest_pop_context(); +} + +static void test_thinning(void) +{ + IMFMediaSource *source; + IMFByteStream *stream; + HRESULT hr; + + stream = create_resource_byte_stream(L"test_thinning.avi"); + hr = create_source(&CLSID_AVIByteStreamPlugin, stream, &source); + IMFByteStream_Release(stream); + + if (FAILED(hr)) + { + win_skip("Failed to create MPEG4 source: %#lx.\n", hr); + return; + } + + test_sample_times_at_rate(source, 2.0, TRUE); + test_sample_times_at_rate(source, 3.0, FALSE); + + hr = IMFMediaSource_Shutdown(source); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaSource_Release(source); +} + START_TEST(mfsrcsnk) { HRESULT hr; @@ -212,6 +739,7 @@ START_TEST(mfsrcsnk) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
test_wave_sink(); + test_thinning();
hr = MFShutdown(); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); diff --git a/dlls/mfsrcsnk/tests/resource.rc b/dlls/mfsrcsnk/tests/resource.rc new file mode 100644 index 00000000000..7b3a6532f0a --- /dev/null +++ b/dlls/mfsrcsnk/tests/resource.rc @@ -0,0 +1,31 @@ +/* + * Resources for mfsrcsnk test suite. + * + * Copyright 2025 Charlotte Pabst for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "windef.h" + +/* Generated with: + gst-launch-1.0 videotestsrc num-buffers=60 pattern=smpte100 ! \ + video/x-raw,format=I420,width=64,height=64,framerate=30000/1001 ! \ + videoflip method=clockwise ! videoconvert ! \ + x264enc key-int-max=5 ! qtmux ! filesink location=tmp.mp4 && \ + ffmpeg -i tmp.mp4 dlls/mfsrcsnk/tests/test_thinning.avi + */ +/* @makedep: test_thinning.avi */ +test_thinning.avi RCDATA test_thinning.avi diff --git a/dlls/mfsrcsnk/tests/test_thinning.avi b/dlls/mfsrcsnk/tests/test_thinning.avi new file mode 100644 index 0000000000000000000000000000000000000000..acc35cce915e86f5fd394f24a397bcf33be034c9 GIT binary patch literal 7716 zcmWIYbaRuDV_<L$^HlKh3=a9h#K4e|Qk0WemYHF}z`zjJ#K16p9xDR~2rw`(*nk)e z3=E7=+JS+A0feDkI0Z6m3KIiEaY<25aY+%_tg_6MVh<xT6SyJ<28Nf+3=9UP5D}2s zsw@yTGFe<wl%~PJzyQJ^yFi$cL4pBn7RXV^iWwLfyh8oFC72i(U`81jq$Hs!8Rd@P z5CEl3P_oHS$<0a0&B*|z6HwX%i3>3>FnId8`FjStg@96h1vtHeWc0)t7;^K=GC`>c zno1ctLB=qqr!a`Dx#^;IAgY;}IjDhw@k;|kseyZ!$Z1eU$$0%{lMVv|BOB}g|L^a( zzTErlrpNYaIyV$<D!+RVGG0MfAvwP&Rl(5AM4`yo(7-~$*f7=5GR;y!SHVNi$jn6F zH^9|h*F?cF%vm8hKP5F;L07>!zo0TFHLXO!$iTo@*T}%gSV31Iqokz3N?*Ucyj-s= zGbJ@YCoxYizbIWFWQ$%#Np6mUu0no6NoIatv6Vt{Vp3wVt)W6uYMQOFLP}~<PJVK> zt)Z2Hm4QNHUSdvVajLCBg|U@Eg`uIbLT;*UMrwsZacNR+s;#*~L2;$6A&87GO0hN6 zGcd3+&@(Vl$jz)sO^FAYXsD2z8ef!{m!4{CXr_>yQIwyX7@wPJYp76Cl$w*1S!`>l zU{PU_l3ZeIsE}NkYipp8lA4%Om7kYtYh<WnXsD2uSX>fcP@J7v08#-lps*l5KP{~| zwZvA}NTDR7C^a#q*w#oPCqF+sF(WlGB_1Ybs8F1fnVgCcFi^-V0^5<CnVVPwaz|!f zNor9}VsdJVt${*PPH9nMWqfjeZb4#+t${*veqM1&QDSCZYD#=&UP)0RNKIN%Vs2`& zt+7H<d_iSVVs2)Nt&u`fd}2ys0mv6g@kyD9#UM*Ei&DY<Do@Q!&nQW<HB`tiNX?5+ z&o8hwP=E*)*cvHhr&ea>mDrj>8S%N9dA5cM#mT98smY}!wk8G&V5>plS(I9wVQZjJ zlpGKDtF58ALQ%49a#E2(Zc<56D#%YsnI%Oa-x(R0SSWy4CHXm^=us#H`OMZp&&)uf zumEHam`<#)HM3MGEGRBXEwD9FC`&4f&rPfV>9Yl!01`+lO)Ji<O0_jKGO|#}OU#MS zC`z%-%g;+yNXyL0Nd*OJW<h*WVo7Gct)ZTYfkI-Tt)UetNiZ}pFfgWev@l5hfAinZ ze%q4@sV&79>%U*0y-`v6df4T!zjmvCNjdZ(<G{P5i8?#)Z1sB^ZSnBx<?e&WW`~P$ z>1dSMJKuAk@_TFg<nHM${Vi$b|7KYz{Vd(K{jb$&)~@1IM-leij<%V_C1<xEkDC;d zJp0Yt-T>iU1y5Br2XG1PFDsiCv~~N$NbAxia^YFd-qpvp-{14!JfO>MV{K)-&BB6K z!E-JX56=2`X+@S;)r&P}IwTsh1CQM}_*?m%qT?aq*r(?{Xx~vyT%YqH?UhDZ^&z{} z9=wLF>y5YB8Wqof<S2IH!QV2aw~Hpl39p~Yp)x_~^fu;24dUU4zAm+!H~H+6w^xE~ z89=3lFr=(y<TPMlV04_NlH-{FZ>#ouupFqo0hJP*0U)_~P8H4a3?+^rU4k%OAQ{Ga zi6xHtjD}!6XfnxJj`mE)!7_G`YMG>RDYc`Cfvx^cy`TNEzl@6v9$&rwv90;}0!v*D z{_lLuRW~?(7e>8{j@tL|$L-n&M-1NH;k?o$vxcqtR-=y5`=iQf;s(FhvioaVyg1Qx z|HX-qb&P+r?DWf@|8#yi^OC1iQ)g!n7xSjH>hCAi?5>@t&R;gY!`Si40ae8k#ki-k zf9%#>vC5fX$2j$`k8IY)B<Gbg&OZ!W7xOHx@3W*}AJ5Xrs<_FW_kaC1NqL!k`HJtt ziKT}{9Cm-X%NlT*b@2}6{^Kj=<^(sIM!jEQIsKT+0goGJbaP5{Z^$|-ge_4@{Glwl zO4e!(Pr$a#PqUJxqIbkBzqRmj_Ol~f(^oS{W;MNfrT9jBQ%ba(y?bQKOOY!+4?!U> z0t<O~eAWNE*6Y>?lEa9vrvD7*F=fE<RnKs~0YeYtyyPrLm<)OG)xse8e>A=b#usMT zJI-^e0L2%k3@pB|#Uv!Yz%t~;*J%DCDt}?d3MKIcHjTXa8qHtu_<8^;zep|*!C{Y5 zQjX>?`qW=TBEClR7g6~OGZ(?iLvUS$HGg61fweclddMpeN9!-5;tL$YD6v8~zA*Jr zA-+cQ7g6zrnO6wM7p5Lqe1T;~^VewqYqY<d;|T6AgL4Gd@(@#oaQhIf$6;8FZ}1yy zZ*!Zzg5@Y*Reww>@2_i8I~NrnKNZ8UM@ZJKvE<Oi!`q(zbLg?%7~S*y-uqQ87G7Ts z)IH6g9qFiI@9mY7_3`qk<DF~fs@!vsi6tjt$FjrD$ApZo?~2kizjbQQWXVUK_tbao zca2fv&2ziK6aD0c|Ivckl_a<5o9!9m9YDHJ;*y9M%S@>-JOG;CF-S=Q=`~_zU;vGr zgUn)LU|>jKXJ7!0jluXe><kPbbuj)kb_NE}SQm`H0f`S%cZ!{X0aW6`<OMhx7(jg$ z7+;4I)jT&eegYC7WL^~~s(A;vP|dr;g=*e6G`;{gLLOwE7B{MS1w5$c_3$9ngTiAO z8vg(iA7tJw9#r!b_)yKW;X^eq42@ra#0Qzz#fNI%1%6cX-teQE$0mTvS3u%}%(D_e zHLpPs)x0@^sQR~|@h>3pLFT;@L^aPq7}Y!<VN~<d(D)5Ve2{swgi+0VAcAV%9}!gZ z#6(f~21tC6d0wKZ=1mYoHE)d=s{UhW{0B&Uka@qvP|b6YKs7H$0@b`SH2wr6KFGXP b5)2HW5)+mmK1rgQ$0vnqo(3A<3Cafm!3?dO
literal 0 HcmV?d00001
From: Charlotte Pabst cpabst@codeweavers.com
--- dlls/winegstreamer/gst_private.h | 2 + dlls/winegstreamer/main.c | 12 ++++ dlls/winegstreamer/unixlib.h | 8 +++ dlls/winegstreamer/wg_parser.c | 100 +++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+)
diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 60e0c3af8bc..dac5a88bc5c 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -81,6 +81,8 @@ void wg_parser_disconnect(wg_parser_t parser); bool wg_parser_get_next_read_offset(wg_parser_t parser, uint64_t *offset, uint32_t *size); void wg_parser_push_data(wg_parser_t parser, const void *data, uint32_t size);
+void wg_parser_set_thin(wg_parser_t parser, BOOL thin); + uint32_t wg_parser_get_stream_count(wg_parser_t parser); wg_parser_stream_t wg_parser_get_stream(wg_parser_t parser, uint32_t index);
diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index d18db8b21e8..94a779bf4fb 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -240,6 +240,18 @@ void wg_parser_push_data(wg_parser_t parser, const void *data, uint32_t size) WINE_UNIX_CALL(unix_wg_parser_push_data, ¶ms); }
+void wg_parser_set_thin(wg_parser_t parser, BOOL thin) +{ + struct wg_parser_set_thin_params params = + { + .parser = parser, + .thin = thin, + }; + TRACE("parser %#I64x, thin %d.\n", parser, thin); + + WINE_UNIX_CALL(unix_wg_parser_set_thin, ¶ms); +} + uint32_t wg_parser_get_stream_count(wg_parser_t parser) { struct wg_parser_get_stream_count_params params = diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index 179f15f78f7..bde18afd77e 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -246,6 +246,12 @@ struct wg_parser_push_data_params UINT32 size; };
+struct wg_parser_set_thin_params +{ + wg_parser_t parser; + BOOL thin; +}; + struct wg_parser_get_stream_count_params { wg_parser_t parser; @@ -430,6 +436,8 @@ enum unix_funcs unix_wg_parser_get_next_read_offset, unix_wg_parser_push_data,
+ unix_wg_parser_set_thin, + unix_wg_parser_get_stream_count, unix_wg_parser_get_stream,
diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index e806298fb57..6aaa29bb67a 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -80,6 +80,7 @@ struct wg_parser bool output_compressed; bool no_more_pads, has_duration, error; bool err_on, warn_on; + bool thin;
pthread_cond_t read_cond, read_done_cond; struct @@ -226,6 +227,18 @@ static NTSTATUS wg_parser_push_data(void *args) return S_OK; }
+static NTSTATUS wg_parser_set_thin(void *args) +{ + const struct wg_parser_set_thin_params *params = args; + struct wg_parser *parser = get_parser(params->parser); + + pthread_mutex_lock(&parser->mutex); + parser->thin = params->thin; + pthread_mutex_unlock(&parser->mutex); + + return S_OK; +} + static NTSTATUS wg_parser_stream_get_current_format(void *args) { const struct wg_parser_stream_get_current_format_params *params = args; @@ -600,10 +613,93 @@ static void no_more_pads_cb(GstElement *element, gpointer user) pthread_cond_signal(&parser->init_cond); }
+static GstPadProbeReturn decoder_sink_buffer_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user) +{ + struct wg_parser *parser = user; + GstBuffer *buffer = gst_pad_probe_info_get_buffer(info); + bool thin, delta; + + if (!buffer) + return GST_PAD_PROBE_OK; + + delta = GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT); + + pthread_mutex_lock(&parser->mutex); + thin = parser->thin; + pthread_mutex_unlock(&parser->mutex); + + if (thin) + { + if (delta) + { + GST_DEBUG("Stream is thinned; discarding delta frame."); + return GST_PAD_PROBE_DROP; + } + else + { + GST_DEBUG("Stream is thinned; found key frame."); + } + } + + if (!delta) + { + GstStructure *structure = gst_structure_new_empty("keyframe"); + GstEvent *event = gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, structure); + gst_pad_send_event(pad, event); + } + + return GST_PAD_PROBE_OK; +} + +static GstPadProbeReturn decoder_src_event_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user) +{ + GstEvent *event = gst_pad_probe_info_get_event(info); + bool *keyframe = user; + + if (event && event->type == GST_EVENT_CUSTOM_DOWNSTREAM + && gst_structure_has_name(gst_event_get_structure(event), "keyframe")) + { + *keyframe = true; + return GST_PAD_PROBE_DROP; + } + + return GST_PAD_PROBE_OK; +} + +static GstPadProbeReturn decoder_src_buffer_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user) +{ + GstBuffer *buffer = gst_pad_probe_info_get_buffer(info); + bool *keyframe = user; + + if (buffer && !*keyframe) + GST_BUFFER_FLAG_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT); + + *keyframe = false; + + return GST_PAD_PROBE_OK; +} + static void deep_element_added_cb(GstBin *self, GstBin *sub_bin, GstElement *element, gpointer user) { + struct wg_parser *parser = user; + if (element) + { + GstElementFactory *fact = GST_ELEMENT_GET_CLASS(element)->elementfactory; + const char *klass = gst_element_factory_get_klass(fact); + GstPad *sink, *src; + if (strstr(klass, GST_ELEMENT_FACTORY_KLASS_DECODER) + && (sink = gst_element_get_static_pad(element, "sink")) + && (src = gst_element_get_static_pad(element, "src"))) + { + bool *keyframe = calloc(1, sizeof *keyframe); + gst_pad_add_probe(sink, GST_PAD_PROBE_TYPE_BUFFER, decoder_sink_buffer_probe, parser, NULL); + gst_pad_add_probe(src, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, decoder_src_event_probe, keyframe, NULL); + gst_pad_add_probe(src, GST_PAD_PROBE_TYPE_BUFFER, decoder_src_buffer_probe, keyframe, free); + } + set_max_threads(element); + } }
static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) @@ -1911,6 +2007,8 @@ const unixlib_entry_t __wine_unix_call_funcs[] = X(wg_parser_get_next_read_offset), X(wg_parser_push_data),
+ X(wg_parser_set_thin), + X(wg_parser_get_stream_count), X(wg_parser_get_stream),
@@ -2308,6 +2406,8 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = X(wg_parser_get_next_read_offset), X64(wg_parser_push_data),
+ X(wg_parser_set_thin), + X(wg_parser_get_stream_count), X(wg_parser_get_stream),
From: Charlotte Pabst cpabst@codeweavers.com
--- dlls/mfsrcsnk/tests/mfsrcsnk.c | 8 +----- dlls/winegstreamer/media_source.c | 43 ++++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 14 deletions(-)
diff --git a/dlls/mfsrcsnk/tests/mfsrcsnk.c b/dlls/mfsrcsnk/tests/mfsrcsnk.c index e78fe8adbb8..d6a154b88c4 100644 --- a/dlls/mfsrcsnk/tests/mfsrcsnk.c +++ b/dlls/mfsrcsnk/tests/mfsrcsnk.c @@ -627,12 +627,10 @@ static void test_sample_times_at_rate(IMFMediaSource *source, FLOAT rate, BOOL t hr = MFGetService((IUnknown *)source, &MF_RATE_CONTROL_SERVICE, &IID_IMFRateControl, (void **)&rate_control); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = IMFRateControl_SetRate(rate_control, thin, rate); - todo_wine_if(thin && hr == MF_E_THINNING_UNSUPPORTED) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); IMFRateControl_Release(rate_control);
hr = wait_media_event(source, callback, MESourceRateChanged, 100, &value); - todo_wine_if(thin) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); todo_wine ok(value.vt == VT_R4, "got vt %u\n", value.vt); @@ -641,13 +639,11 @@ static void test_sample_times_at_rate(IMFMediaSource *source, FLOAT rate, BOOL t
hr = IMFMediaStream_RequestSample(stream, NULL); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - if (!winetest_platform_is_wine) - { + hr = wait_media_event(stream, callback, MEStreamThinMode, 100, &value); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(value.vt == VT_INT, "got vt %u\n", value.vt); ok(value.iVal == thin, "Unexpected thin %d\n", value.iVal); - }
winetest_push_context("sample 1");
@@ -656,7 +652,6 @@ static void test_sample_times_at_rate(IMFMediaSource *source, FLOAT rate, BOOL t ok(value.vt == VT_UNKNOWN, "got vt %u\n", value.vt); hr = IMFSample_GetSampleTime((IMFSample *)value.punkVal, &time); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(time == expect_times[2], "Unexpected time %s.\n", debugstr_time(time)); hr = IMFSample_GetSampleDuration((IMFSample *)value.punkVal, &time); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); @@ -679,7 +674,6 @@ static void test_sample_times_at_rate(IMFMediaSource *source, FLOAT rate, BOOL t ok(value.vt == VT_UNKNOWN, "got vt %u\n", value.vt); hr = IMFSample_GetSampleTime((IMFSample *)value.punkVal, &time); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(time == (thin ? expect_times_thin[2 * i] : expect_times[2 * i]), "Unexpected time %s.\n", debugstr_time(time)); hr = IMFSample_GetSampleDuration((IMFSample *)value.punkVal, &time); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index ab842b3e438..c1dcb0dffa0 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -137,6 +137,7 @@ struct media_stream DWORD stream_id; BOOL active; BOOL eos; + BOOL thin; };
enum source_async_op @@ -202,6 +203,7 @@ struct media_source SOURCE_SHUTDOWN, } state; float rate; + BOOL thin;
HANDLE read_thread; bool read_thread_shutdown; @@ -795,12 +797,39 @@ static HRESULT wait_on_sample(struct media_stream *stream, IUnknown *token) { struct media_source *source = impl_from_IMFMediaSource(stream->media_source); struct wg_parser_buffer buffer; + PROPVARIANT param; + HRESULT hr;
TRACE("%p, %p\n", stream, token);
while (wg_parser_stream_get_buffer(source->wg_parser, stream->wg_stream, &buffer)) { - HRESULT hr = media_stream_send_sample(stream, &buffer, token); + if (stream->thin != source->thin) + { + param.vt = VT_INT; + param.iVal = source->thin; + if (FAILED(hr = IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEStreamThinMode, &GUID_NULL, S_OK, ¶m))) + WARN("Failed to queue MEStreamThinMode event, hr %#lx\n", hr); + stream->thin = source->thin; + + if (!source->thin && buffer.has_pts) + { + wg_parser_stream_seek(stream->wg_stream, 1.0, buffer.pts, 0, + AM_SEEKING_AbsolutePositioning, AM_SEEKING_NoPositioning); + /* gstreamer gives us the next buffer twice, once with duration=0, discard */ + wg_parser_stream_get_buffer(source->wg_parser, stream->wg_stream, &buffer); + wg_parser_stream_release_buffer(stream->wg_stream); + continue; + } + } + + if (source->thin && buffer.delta) + { + wg_parser_stream_release_buffer(stream->wg_stream); + continue; + } + + hr = media_stream_send_sample(stream, &buffer, token); if (hr != S_FALSE) return hr; } @@ -1285,16 +1314,16 @@ static HRESULT WINAPI media_source_rate_control_SetRate(IMFRateControl *iface, B if (rate < 0.0f) return MF_E_REVERSE_UNSUPPORTED;
- if (thin) - return MF_E_THINNING_UNSUPPORTED; - if (FAILED(hr = IMFRateSupport_IsRateSupported(&source->IMFRateSupport_iface, thin, rate, NULL))) return hr;
EnterCriticalSection(&source->cs); source->rate = rate; + source->thin = thin; LeaveCriticalSection(&source->cs);
+ wg_parser_set_thin(source->wg_parser, thin); + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourceRateChanged, &GUID_NULL, S_OK, NULL); }
@@ -1304,11 +1333,10 @@ static HRESULT WINAPI media_source_rate_control_GetRate(IMFRateControl *iface, B
TRACE("%p, %p, %p.\n", iface, thin, rate);
- if (thin) - *thin = FALSE; - EnterCriticalSection(&source->cs); *rate = source->rate; + if (thin) + *thin = source->thin; LeaveCriticalSection(&source->cs);
return S_OK; @@ -1734,6 +1762,7 @@ static HRESULT media_source_create(struct object_context *context, IMFMediaSourc IMFByteStream_AddRef(context->stream); object->file_size = context->file_size; object->rate = 1.0f; + object->thin = FALSE; InitializeCriticalSectionEx(&object->cs, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO); object->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": cs");
From: Charlotte Pabst cpabst@codeweavers.com
--- dlls/winedmo/unix_demuxer.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/dlls/winedmo/unix_demuxer.c b/dlls/winedmo/unix_demuxer.c index 7a1a262e6c9..e177cd53dcf 100644 --- a/dlls/winedmo/unix_demuxer.c +++ b/dlls/winedmo/unix_demuxer.c @@ -38,6 +38,7 @@ struct stream { AVBSFContext *filter; BOOL eos; + int64_t next_pts; };
struct demuxer @@ -233,7 +234,16 @@ static NTSTATUS demuxer_filter_packet( struct demuxer *demuxer, AVPacket **packe if (!(stream = demuxer->last_stream)) ret = 0; else { - if (!(ret = av_bsf_receive_packet( stream->filter, *packet ))) return STATUS_SUCCESS; + if (!(ret = av_bsf_receive_packet( stream->filter, *packet ))) { + if ((*packet)->pts == AV_NOPTS_VALUE) + (*packet)->pts = stream->next_pts; + if ((*packet)->pts != AV_NOPTS_VALUE && (*packet)->duration != 0) + stream->next_pts = (*packet)->pts + (*packet)->duration; + else + stream->next_pts = AV_NOPTS_VALUE; + + return STATUS_SUCCESS; + } if (ret == AVERROR_EOF) stream->eos = TRUE; if (!ret || ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) ret = 0; else WARN( "Failed to read packet from filter, error %s.\n", debugstr_averr( ret ) ); @@ -321,6 +331,7 @@ NTSTATUS demuxer_seek( void *arg ) { av_bsf_flush( demuxer->streams[i].filter ); demuxer->streams[i].eos = FALSE; + demuxer->streams[i].next_pts = timestamp; } av_packet_free( &demuxer->last_packet ); demuxer->last_stream = NULL;
From: Charlotte Pabst cpabst@codeweavers.com
--- dlls/mfsrcsnk/media_source.c | 2 +- dlls/winedmo/main.c | 6 +++--- dlls/winedmo/unix_demuxer.c | 12 ++++++++++-- dlls/winedmo/unixlib.h | 1 + include/wine/winedmo.h | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/dlls/mfsrcsnk/media_source.c b/dlls/mfsrcsnk/media_source.c index d7f20e511c8..9203fd33374 100644 --- a/dlls/mfsrcsnk/media_source.c +++ b/dlls/mfsrcsnk/media_source.c @@ -588,7 +588,7 @@ static HRESULT demuxer_read_sample(struct winedmo_demuxer demuxer, UINT *index,
if (FAILED(hr = create_media_buffer_sample(buffer_size, &sample, &output.pBuffer))) return hr; - if ((status = winedmo_demuxer_read(demuxer, index, &output, &buffer_size))) + if ((status = winedmo_demuxer_read(demuxer, index, &output, &buffer_size, FALSE))) { if (status == STATUS_BUFFER_TOO_SMALL) hr = E_PENDING; else if (status == STATUS_END_OF_FILE) hr = MF_E_END_OF_STREAM; diff --git a/dlls/winedmo/main.c b/dlls/winedmo/main.c index 1e91d8e2e51..6a462555f71 100644 --- a/dlls/winedmo/main.c +++ b/dlls/winedmo/main.c @@ -222,12 +222,12 @@ NTSTATUS CDECL winedmo_demuxer_destroy( struct winedmo_demuxer *demuxer ) return status; }
-NTSTATUS CDECL winedmo_demuxer_read( struct winedmo_demuxer demuxer, UINT *stream, DMO_OUTPUT_DATA_BUFFER *buffer, UINT *buffer_size ) +NTSTATUS CDECL winedmo_demuxer_read( struct winedmo_demuxer demuxer, UINT *stream, DMO_OUTPUT_DATA_BUFFER *buffer, UINT *buffer_size, BOOL thin ) { - struct demuxer_read_params params = {.demuxer = demuxer}; + struct demuxer_read_params params = {.demuxer = demuxer, .thin = thin}; NTSTATUS status;
- TRACE( "demuxer %#I64x, stream %p, buffer %p, buffer_size %p\n", demuxer.handle, stream, buffer, buffer_size ); + TRACE( "demuxer %#I64x, stream %p, buffer %p, buffer_size %p, thin %d\n", demuxer.handle, stream, buffer, buffer_size, thin );
buffer_lock( buffer, ¶ms.sample ); status = UNIX_CALL( demuxer_read, ¶ms ); diff --git a/dlls/winedmo/unix_demuxer.c b/dlls/winedmo/unix_demuxer.c index e177cd53dcf..3266849dfe5 100644 --- a/dlls/winedmo/unix_demuxer.c +++ b/dlls/winedmo/unix_demuxer.c @@ -221,7 +221,7 @@ NTSTATUS demuxer_destroy( void *arg ) return STATUS_SUCCESS; }
-static NTSTATUS demuxer_filter_packet( struct demuxer *demuxer, AVPacket **packet ) +static NTSTATUS demuxer_filter_packet( struct demuxer *demuxer, AVPacket **packet, BOOL thin ) { struct stream *stream; int i, ret; @@ -242,6 +242,14 @@ static NTSTATUS demuxer_filter_packet( struct demuxer *demuxer, AVPacket **packe else stream->next_pts = AV_NOPTS_VALUE;
+ if ((*packet)->flags & AV_PKT_FLAG_KEY) TRACE("Thinning: Found key frame.\n"); + else + { + TRACE("Thinning: Skipping delta frame.\n"); + av_packet_free( packet ); + continue; + } + return STATUS_SUCCESS; } if (ret == AVERROR_EOF) stream->eos = TRUE; @@ -290,7 +298,7 @@ NTSTATUS demuxer_read( void *arg )
TRACE( "demuxer %p, capacity %#x\n", demuxer, capacity );
- if ((status = demuxer_filter_packet( demuxer, &packet ))) return status; + if ((status = demuxer_filter_packet( demuxer, &packet, params->thin ))) return status;
params->sample.size = packet->size; if ((capacity < packet->size)) diff --git a/dlls/winedmo/unixlib.h b/dlls/winedmo/unixlib.h index cf37bd5342a..eda808e2cd0 100644 --- a/dlls/winedmo/unixlib.h +++ b/dlls/winedmo/unixlib.h @@ -121,6 +121,7 @@ struct demuxer_read_params struct winedmo_demuxer demuxer; UINT32 stream; struct sample sample; + BOOL thin; };
struct demuxer_seek_params diff --git a/include/wine/winedmo.h b/include/wine/winedmo.h index c068dfa1de8..ab592777eab 100644 --- a/include/wine/winedmo.h +++ b/include/wine/winedmo.h @@ -46,7 +46,7 @@ NTSTATUS CDECL winedmo_demuxer_check( const char *mime_type ); NTSTATUS CDECL winedmo_demuxer_create( const WCHAR *url, struct winedmo_stream *stream, UINT64 stream_size, INT64 *duration, UINT *stream_count, WCHAR *mime_type, struct winedmo_demuxer *demuxer ); NTSTATUS CDECL winedmo_demuxer_destroy( struct winedmo_demuxer *demuxer ); -NTSTATUS CDECL winedmo_demuxer_read( struct winedmo_demuxer demuxer, UINT *stream, DMO_OUTPUT_DATA_BUFFER *buffer, UINT *buffer_size ); +NTSTATUS CDECL winedmo_demuxer_read( struct winedmo_demuxer demuxer, UINT *stream, DMO_OUTPUT_DATA_BUFFER *buffer, UINT *buffer_size, BOOL thin ); NTSTATUS CDECL winedmo_demuxer_seek( struct winedmo_demuxer demuxer, INT64 timestamp ); NTSTATUS CDECL winedmo_demuxer_stream_lang( struct winedmo_demuxer demuxer, UINT stream, WCHAR *buffer, UINT len ); NTSTATUS CDECL winedmo_demuxer_stream_name( struct winedmo_demuxer demuxer, UINT stream, WCHAR *buffer, UINT len );
From: Charlotte Pabst cpabst@codeweavers.com
--- dlls/mfsrcsnk/media_source.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/dlls/mfsrcsnk/media_source.c b/dlls/mfsrcsnk/media_source.c index 9203fd33374..7200b30f923 100644 --- a/dlls/mfsrcsnk/media_source.c +++ b/dlls/mfsrcsnk/media_source.c @@ -240,6 +240,8 @@ struct media_source IMFByteStream *stream; WCHAR *url; float rate; + BOOL thin; + BOOL prev_thin;
struct winedmo_demuxer winedmo_demuxer; struct winedmo_stream winedmo_stream; @@ -575,7 +577,7 @@ static HRESULT create_media_buffer_sample(UINT buffer_size, IMFSample **sample, return hr; }
-static HRESULT demuxer_read_sample(struct winedmo_demuxer demuxer, UINT *index, IMFSample **out) +static HRESULT demuxer_read_sample(struct winedmo_demuxer demuxer, UINT *index, IMFSample **out, BOOL thin) { UINT buffer_size = 0x1000; IMFSample *sample; @@ -588,7 +590,7 @@ static HRESULT demuxer_read_sample(struct winedmo_demuxer demuxer, UINT *index,
if (FAILED(hr = create_media_buffer_sample(buffer_size, &sample, &output.pBuffer))) return hr; - if ((status = winedmo_demuxer_read(demuxer, index, &output, &buffer_size, FALSE))) + if ((status = winedmo_demuxer_read(demuxer, index, &output, &buffer_size, thin))) { if (status == STATUS_BUFFER_TOO_SMALL) hr = E_PENDING; else if (status == STATUS_END_OF_FILE) hr = MF_E_END_OF_STREAM; @@ -631,11 +633,19 @@ static HRESULT media_source_read(struct media_source *source) IMFSample *sample; UINT i, index; HRESULT hr; + BOOL thin = source->thin;
if (source->state != SOURCE_RUNNING) return S_OK;
- if (FAILED(hr = demuxer_read_sample(source->winedmo_demuxer, &index, &sample)) && hr != MF_E_END_OF_STREAM) + /* emulate latency */ + if (thin != source->prev_thin) + { + source->prev_thin = thin; + thin = TRUE; + } + + if (FAILED(hr = demuxer_read_sample(source->winedmo_demuxer, &index, &sample, thin)) && hr != MF_E_END_OF_STREAM) { WARN("Failed to read stream %u data, hr %#lx\n", index, hr); return hr; @@ -1047,14 +1057,13 @@ static HRESULT WINAPI media_source_IMFRateControl_SetRate(IMFRateControl *iface,
if (rate < 0.0f) return MF_E_REVERSE_UNSUPPORTED; - if (thin) - return MF_E_THINNING_UNSUPPORTED;
if (FAILED(hr = IMFRateSupport_IsRateSupported(&source->IMFRateSupport_iface, thin, rate, NULL))) return hr;
EnterCriticalSection(&source->cs); source->rate = rate; + source->thin = thin; LeaveCriticalSection(&source->cs);
return IMFMediaEventQueue_QueueEventParamVar(source->queue, MESourceRateChanged, &GUID_NULL, S_OK, NULL); @@ -1066,11 +1075,10 @@ static HRESULT WINAPI media_source_IMFRateControl_GetRate(IMFRateControl *iface,
TRACE("source %p, thin %p, rate %p\n", source, thin, rate);
- if (thin) - *thin = FALSE; - EnterCriticalSection(&source->cs); *rate = source->rate; + if (thin) + *thin = source->thin; LeaveCriticalSection(&source->cs);
return S_OK;
From: Charlotte Pabst cpabst@codeweavers.com
--- dlls/mfsrcsnk/media_source.c | 44 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-)
diff --git a/dlls/mfsrcsnk/media_source.c b/dlls/mfsrcsnk/media_source.c index 7200b30f923..8a94baa62f6 100644 --- a/dlls/mfsrcsnk/media_source.c +++ b/dlls/mfsrcsnk/media_source.c @@ -291,28 +291,6 @@ static struct media_stream *media_stream_from_index(struct media_source *source, return NULL; }
-static HRESULT media_source_send_sample(struct media_source *source, UINT index, IMFSample *sample) -{ - struct media_stream *stream; - IUnknown *token; - HRESULT hr; - - if (!(stream = media_stream_from_index(source, index)) || !stream->active) - return S_FALSE; - - if (SUCCEEDED(hr = object_queue_pop(&stream->tokens, &token))) - { - media_stream_send_sample(stream, sample, token); - if (token) IUnknown_Release(token); - return S_OK; - } - - if (FAILED(hr = object_queue_push(&stream->samples, (IUnknown *)sample))) - return hr; - - return S_FALSE; -} - static void queue_media_event_object(IMFMediaEventQueue *queue, MediaEventType type, IUnknown *object) { HRESULT hr; @@ -336,6 +314,28 @@ static void queue_media_source_read(struct media_source *source) source->pending_reads++; }
+static HRESULT media_source_send_sample(struct media_source *source, UINT index, IMFSample *sample) +{ + struct media_stream *stream; + IUnknown *token; + HRESULT hr; + + if (!(stream = media_stream_from_index(source, index)) || !stream->active) + return S_FALSE; + + if (SUCCEEDED(hr = object_queue_pop(&stream->tokens, &token))) + { + media_stream_send_sample(stream, sample, token); + if (token) IUnknown_Release(token); + return S_OK; + } + + if (FAILED(hr = object_queue_push(&stream->samples, (IUnknown *)sample))) + return hr; + + return S_FALSE; +} + static void media_stream_start(struct media_stream *stream, UINT index, const PROPVARIANT *position) { struct media_source *source = media_source_from_IMFMediaSource(stream->source);
From: Charlotte Pabst cpabst@codeweavers.com
--- dlls/mfsrcsnk/media_source.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/dlls/mfsrcsnk/media_source.c b/dlls/mfsrcsnk/media_source.c index 8a94baa62f6..a5d9fa45774 100644 --- a/dlls/mfsrcsnk/media_source.c +++ b/dlls/mfsrcsnk/media_source.c @@ -220,6 +220,7 @@ struct media_stream
BOOL active; BOOL eos; + BOOL thin; };
struct media_source @@ -314,17 +315,26 @@ static void queue_media_source_read(struct media_source *source) source->pending_reads++; }
-static HRESULT media_source_send_sample(struct media_source *source, UINT index, IMFSample *sample) +static HRESULT media_source_send_sample(struct media_source *source, UINT index, IMFSample *sample, BOOL update_thin_mode) { struct media_stream *stream; IUnknown *token; HRESULT hr; + PROPVARIANT param;
if (!(stream = media_stream_from_index(source, index)) || !stream->active) return S_FALSE;
if (SUCCEEDED(hr = object_queue_pop(&stream->tokens, &token))) { + if (update_thin_mode && stream->thin != source->thin) + { + param.vt = VT_INT; + param.iVal = source->thin; + queue_media_event_value(stream->queue, MEStreamThinMode, ¶m); + stream->thin = source->thin; + } + media_stream_send_sample(stream, sample, token); if (token) IUnknown_Release(token); return S_OK; @@ -366,7 +376,7 @@ static void media_stream_start(struct media_stream *stream, UINT index, const PR list_move_head(&samples, &stream->samples); while (object_queue_pop(&samples, (IUnknown **)&sample) != E_PENDING) { - media_source_send_sample(source, index, sample); + media_source_send_sample(source, index, sample, FALSE); IMFSample_Release(sample); }
@@ -658,7 +668,7 @@ static HRESULT media_source_read(struct media_source *source) return S_OK; }
- if ((hr = media_source_send_sample(source, index, sample)) == S_FALSE) + if ((hr = media_source_send_sample(source, index, sample, TRUE)) == S_FALSE) queue_media_source_read(source); IMFSample_Release(sample);
On Tue Jul 8 19:11:08 2025 +0000, Charlotte Pabst wrote:
changed this line in [version 2 of the diff](/wine/wine/-/merge_requests/8505/diffs?diff_id=191619&start_sha=1f7ea7c23ea2f4da1938f41504eaccea3761bcb9#9c89a7a7f07e43f18328b43c0445ed822d60bd2a_663_607)
I've replaced the tests by your commit with minor modifications now. It took some time to make winegstreamer conform properly, I've basically changed the approach on that implementation (out of necessity, this also fixes the previously mentioned problem of the delta frame flag being dropped by the decoder now). I've also added a frame of artificial latency for the thinning change to propagate in mfsrcsnk to pass the tests (It's funny - winegstreamer buffers a lot and needs to have the pipeline flushed to pass, mfsrcsnk buffers nothing and needs to essentially do the opposite to pass. I still feel like it's a bit sad we're trying to match windows behavior so closely, because if we just followed what's legal as per MSDN, neither would be necessary).
Also, it seems like winedmo doesn't give us timestamps for AVI yet, so I've added a commit to generate them to pass the tests - this is also important to thinning because previously in an actual pipeline those missing timestamps would be added by the video decoder MFT later on, which in the case of thinning would assign incorrect timestamps because its timestamping logic assumes that samples are continuous.
This could potentially be a bit problematic tho, there might be a reason why video decoder does what it does, so if the winedmo/mfsrcsnk changes aren't fine yet, it could make sense to get the winegstreamer changes merged first and send the mfsrcsnk changes in a separate MR.