[PATCH 0/2] MR9646: mf/tests: Scrubbing tests
This MR adds tests for how scrubbing and rate changes work within the Media Session. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9646
From: Brendan McGrath <bmcgrath(a)codeweavers.com> --- dlls/mf/tests/mf.c | 271 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 265 insertions(+), 6 deletions(-) diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 0c0590ed195..40e6ebc6ccd 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -757,11 +757,14 @@ static void test_handler_clear_current_type(struct test_handler *handler) struct test_media_sink { IMFMediaSink IMFMediaSink_iface; + IMFMediaSinkPreroll IMFMediaSinkPreroll_iface; LONG refcount; IMFMediaTypeHandler *handler; IMFPresentationClock *clock; IMFStreamSink *stream; BOOL shutdown; + DWORD characteristics; + HANDLE preroll_event; }; static struct test_media_sink *impl_from_IMFMediaSink(IMFMediaSink *iface) @@ -771,15 +774,26 @@ static struct test_media_sink *impl_from_IMFMediaSink(IMFMediaSink *iface) static HRESULT WINAPI test_media_sink_QueryInterface(IMFMediaSink *iface, REFIID riid, void **obj) { + struct test_media_sink *sink = impl_from_IMFMediaSink(iface); + if (IsEqualIID(riid, &IID_IMFMediaSink) || IsEqualIID(riid, &IID_IUnknown)) { - IMFMediaSink_AddRef((*obj = iface)); - return S_OK; + *obj = iface; + } + else if (IsEqualIID(riid, &IID_IMFMediaSinkPreroll)) + { + *obj = &sink->IMFMediaSinkPreroll_iface; + } + else + { + *obj = NULL; + return E_NOINTERFACE; } - *obj = NULL; - return E_NOINTERFACE; + IUnknown_AddRef((IUnknown*)*obj); + return S_OK; + } static ULONG WINAPI test_media_sink_AddRef(IMFMediaSink *iface) @@ -799,6 +813,8 @@ static ULONG WINAPI test_media_sink_Release(IMFMediaSink *iface) IMFMediaSink_Shutdown(iface); if (sink->handler) IMFMediaTypeHandler_Release(sink->handler); + if (sink->preroll_event) + CloseHandle(sink->preroll_event); free(sink); } @@ -807,7 +823,8 @@ static ULONG WINAPI test_media_sink_Release(IMFMediaSink *iface) static HRESULT WINAPI test_media_sink_GetCharacteristics(IMFMediaSink *iface, DWORD *characteristics) { - *characteristics = 0; + struct test_media_sink *sink = impl_from_IMFMediaSink(iface); + *characteristics = sink->characteristics; return S_OK; } @@ -982,6 +999,48 @@ static const IMFMediaSinkVtbl test_media_sink_vtbl = test_media_sink_Shutdown, }; +DEFINE_EXPECT(test_media_sink_preroll_NotifyPreroll); + +static struct test_media_sink *impl_from_IMFMediaSinkPreroll(IMFMediaSinkPreroll *iface) +{ + return CONTAINING_RECORD(iface, struct test_media_sink, IMFMediaSinkPreroll_iface); +} + +static HRESULT WINAPI test_media_sink_preroll_QueryInterface(IMFMediaSinkPreroll *iface, REFIID riid, void **obj) +{ + struct test_media_sink *sink = impl_from_IMFMediaSinkPreroll(iface); + return IMFMediaSink_QueryInterface(&sink->IMFMediaSink_iface, riid, obj); +} + +static ULONG WINAPI test_media_sink_preroll_AddRef(IMFMediaSinkPreroll *iface) +{ + struct test_media_sink *sink = impl_from_IMFMediaSinkPreroll(iface); + return IMFMediaSink_AddRef(&sink->IMFMediaSink_iface); +} + +static ULONG WINAPI test_media_sink_preroll_Release(IMFMediaSinkPreroll *iface) +{ + struct test_media_sink *sink = impl_from_IMFMediaSinkPreroll(iface); + return IMFMediaSink_Release(&sink->IMFMediaSink_iface); +} + +static HRESULT WINAPI test_media_sink_preroll_NotifyPreroll(IMFMediaSinkPreroll *iface, MFTIME time) +{ + struct test_media_sink *sink = impl_from_IMFMediaSinkPreroll(iface); + todo_wine_if(!expect_test_media_sink_preroll_NotifyPreroll) + CHECK_EXPECT(test_media_sink_preroll_NotifyPreroll); + SetEvent(sink->preroll_event); + return S_OK; +} + +static const IMFMediaSinkPrerollVtbl test_media_sink_preroll_vtbl = +{ + test_media_sink_preroll_QueryInterface, + test_media_sink_preroll_AddRef, + test_media_sink_preroll_Release, + test_media_sink_preroll_NotifyPreroll, +}; + struct test_stream_sink { IMFStreamSink IMFStreamSink_iface; @@ -1266,6 +1325,7 @@ static struct test_media_sink *create_test_media_sink(IMFMediaTypeHandler *handl sink = calloc(1, sizeof(*sink)); sink->IMFMediaSink_iface.lpVtbl = &test_media_sink_vtbl; + sink->IMFMediaSinkPreroll_iface.lpVtbl = &test_media_sink_preroll_vtbl; sink->refcount = 1; if (handler) IMFMediaTypeHandler_AddRef(sink->handler = handler); @@ -8039,7 +8099,7 @@ static HRESULT WINAPI test_transform_ProcessMessage(IMFTransform *iface, MFT_MES return S_OK; case MFT_MESSAGE_COMMAND_FLUSH: - CHECK_EXPECT(test_transform_ProcessMessage_FLUSH); + CHECK_EXPECT2(test_transform_ProcessMessage_FLUSH); add_object_state(&actual_object_state_record, MFT_FLUSH); return S_OK; @@ -8436,6 +8496,204 @@ static void test_media_session_seek(void) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); } +static void test_media_session_scrubbing(void) +{ + IMFClockStateSink test_seek_clock_sink = {&test_seek_clock_sink_vtbl}; + MFT_OUTPUT_STREAM_INFO output_stream_info = {0}; + IMFPresentationClock *presentation_clock; + struct test_callback *test_callback; + struct test_media_sink *media_sink; + struct test_source *media_source; + struct test_handler *handler; + IMFRateControl *rate_control; + IMFAsyncCallback *callback; + IMFMediaSession *session; + IMFMediaSource *source; + IMFTopology *topology; + PROPVARIANT propvar; + IMFMediaType *type; + IMFTransform *mft; + IMFClock *clock; + UINT32 status; + HRESULT hr; + INT i; + + /* Allocate and initialise required resources */ + handler = create_test_handler(); + media_sink = create_test_media_sink(&handler->IMFMediaTypeHandler_iface); + media_sink->characteristics = MEDIASINK_CAN_PREROLL | MEDIASINK_FIXED_STREAMS; + media_sink->preroll_event = CreateEventA(NULL, FALSE, FALSE, NULL); + IMFMediaTypeHandler_Release(&handler->IMFMediaTypeHandler_iface); + + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr); + + hr = MFCreateMediaSession(NULL, &session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + source = create_test_source(TRUE); + media_source = impl_test_source_from_IMFMediaSource(source); + for (i = 0; i < media_source->stream_count; i++) + media_source->streams[i]->test_expect = TRUE; + + hr = MFCreateMediaType(&type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetGUID(type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetGUID(type, &MF_MT_SUBTYPE, &MFVideoFormat_RGB32); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetUINT64(type, &MF_MT_FRAME_SIZE, (UINT64)640 << 32 | 480); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + mft = NULL; + hr = test_transform_create(1, &type, 1, &type, FALSE, &mft); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + test_transform_set_output_stream_info(mft, &output_stream_info); + IMFMediaType_Release(type); + + hr = MFGetService((IUnknown*)session, &MF_RATE_CONTROL_SERVICE, &IID_IMFRateControl, (void**)&rate_control); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + PropVariantInit(&propvar); + + /* Create and set-up the required topology */ + SET_EXPECT(test_transform_ProcessMessage_BEGIN_STREAMING); + topology = create_test_topology_unk(source, (IUnknown*)media_sink->stream, (IUnknown*) mft, NULL); + hr = IMFMediaSession_SetTopology(session, 0, topology); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFTopology_Release(topology); + + hr = IMFMediaSession_GetClock(session, &clock); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFClock_QueryInterface(clock, &IID_IMFPresentationClock, (void **)&presentation_clock); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFPresentationClock_AddClockStateSink(presentation_clock, &test_seek_clock_sink); + ok(hr == S_OK, "Failed to add a sink, hr %#lx.\n", hr); + IMFClock_Release(clock); + + callback = create_test_callback(TRUE); + test_callback = impl_from_IMFAsyncCallback(callback); + hr = wait_media_event(session, callback, MESessionTopologyStatus, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaEvent_GetUINT32(test_callback->media_event, &MF_EVENT_TOPOLOGY_STATUS, &status); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(status == MF_TOPOSTATUS_READY, "Unexpected status %d.\n", status); + PropVariantClear(&propvar); + CHECK_CALLED(test_transform_ProcessMessage_BEGIN_STREAMING); + + /* Test that when rate is zero (i.e. we're scrubbing), no preroll occurs */ + hr = IMFRateControl_SetRate(rate_control, FALSE, 0.0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + SET_EXPECT(test_media_sink_GetPresentationClock); + SET_EXPECT(test_media_sink_SetPresentationClock); + SET_EXPECT(test_transform_ProcessMessage_START_OF_STREAM); + SET_EXPECT(test_media_sink_GetStreamSinkCount); + propvar.vt = VT_I8; /* hVal will be zero */ + hr = IMFMediaSession_Start(session, NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = WaitForSingleObject(media_sink->preroll_event, 100); + todo_wine + ok(hr == WAIT_TIMEOUT, "Unexpected hr %#lx.\n", hr); + + todo_wine + CHECK_CALLED(test_media_sink_GetPresentationClock); + CHECK_CALLED(test_media_sink_SetPresentationClock); + todo_wine + CHECK_CALLED(test_transform_ProcessMessage_START_OF_STREAM); + todo_wine + CHECK_CALLED(test_media_sink_GetStreamSinkCount); + + hr = IMFStreamSink_QueueEvent(media_sink->stream, MEStreamSinkScrubSampleComplete, &GUID_NULL, S_OK, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + + hr = IMFStreamSink_QueueEvent(media_sink->stream, MEStreamSinkStarted, &GUID_NULL, S_OK, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + + hr = wait_media_event_until_blocking(session, callback, MESessionStarted, 1000, &propvar); + todo_wine + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + + SET_EXPECT(test_transform_ProcessMessage_FLUSH); + SET_EXPECT(test_stream_sink_Flush); + hr = IMFMediaSession_Stop(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFStreamSink_QueueEvent(media_sink->stream, MEStreamSinkStopped, &GUID_NULL, S_OK, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + + hr = wait_media_event_until_blocking(session, callback, MESessionStopped, 1000, &propvar); + todo_wine + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + todo_wine + CHECK_CALLED(test_transform_ProcessMessage_FLUSH); + todo_wine + CHECK_CALLED(test_stream_sink_Flush); + + /* Test that during a standard start (i.e. rate == 1.0), preroll is called on the sink */ + hr = IMFRateControl_SetRate(rate_control, FALSE, 1.0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + SET_EXPECT(test_media_sink_GetPresentationClock); + SET_EXPECT(test_transform_ProcessMessage_START_OF_STREAM); + SET_EXPECT(test_media_sink_preroll_NotifyPreroll); + propvar.vt = VT_I8; /* hVal will be zero */ + hr = IMFMediaSession_Start(session, NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = WaitForSingleObject(media_sink->preroll_event, 100); + todo_wine + ok(hr == WAIT_OBJECT_0, "Unexpected hr %#lx.\n", hr); + + todo_wine + CHECK_CALLED(test_media_sink_GetPresentationClock); + todo_wine + CHECK_CALLED(test_transform_ProcessMessage_START_OF_STREAM); + CHECK_CALLED(test_media_sink_preroll_NotifyPreroll); + + SET_EXPECT(test_media_sink_GetStreamSinkCount); + hr = IMFStreamSink_QueueEvent(media_sink->stream, MEStreamSinkPrerolled, &GUID_NULL, S_OK, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + + hr = IMFStreamSink_QueueEvent(media_sink->stream, MEStreamSinkStarted, &GUID_NULL, S_OK, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + + hr = wait_media_event_until_blocking(session, callback, MESessionStarted, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + todo_wine + CHECK_CALLED(test_media_sink_GetStreamSinkCount); + + /* Release all the used resources */ + IMFPresentationClock_RemoveClockStateSink(presentation_clock, &test_seek_clock_sink); + IMFPresentationClock_Release(presentation_clock); + IMFAsyncCallback_Release(callback); + IMFTransform_Release(mft); + + IMFRateControl_Release(rate_control); + + hr = IMFMediaSession_Shutdown(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(media_sink->shutdown, "Media sink didn't shutdown.\n"); + + hr = IMFMediaSource_Shutdown(source); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMFMediaSession_Release(session); + IMFMediaSource_Release(source); + IMFMediaSink_Release(&media_sink->IMFMediaSink_iface); + + hr = MFShutdown(); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +} + START_TEST(mf) { init_functions(); @@ -8475,4 +8733,5 @@ START_TEST(mf) test_media_session_source_shutdown(); test_media_session_thinning(); test_media_session_seek(); + test_media_session_scrubbing(); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9646
From: Brendan McGrath <bmcgrath(a)codeweavers.com> --- dlls/mf/tests/mf.c | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 40e6ebc6ccd..82ecd017100 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -2359,6 +2359,8 @@ static IMFMediaSource *create_test_source(BOOL seekable) return &source->IMFMediaSource_iface; } +DEFINE_EXPECT(test_seek_clock_sink_OnClockSetRate); + static HRESULT WINAPI test_seek_clock_sink_QueryInterface(IMFClockStateSink *iface, REFIID riid, void **obj) { if (IsEqualIID(riid, &IID_IMFClockStateSink) || @@ -2409,6 +2411,7 @@ static HRESULT WINAPI test_seek_clock_sink_OnClockRestart(IMFClockStateSink *ifa static HRESULT WINAPI test_seek_clock_sink_OnClockSetRate(IMFClockStateSink *iface, MFTIME system_time, float rate) { + CHECK_EXPECT(test_seek_clock_sink_OnClockSetRate); add_object_state(&actual_object_state_record, SINK_ON_CLOCK_SETRATE); return S_OK; } @@ -8581,11 +8584,12 @@ static void test_media_session_scrubbing(void) CHECK_CALLED(test_transform_ProcessMessage_BEGIN_STREAMING); /* Test that when rate is zero (i.e. we're scrubbing), no preroll occurs */ + SET_EXPECT(test_media_sink_GetPresentationClock); + SET_EXPECT(test_media_sink_SetPresentationClock); + SET_EXPECT(test_seek_clock_sink_OnClockSetRate); hr = IMFRateControl_SetRate(rate_control, FALSE, 0.0); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - SET_EXPECT(test_media_sink_GetPresentationClock); - SET_EXPECT(test_media_sink_SetPresentationClock); SET_EXPECT(test_transform_ProcessMessage_START_OF_STREAM); SET_EXPECT(test_media_sink_GetStreamSinkCount); propvar.vt = VT_I8; /* hVal will be zero */ @@ -8603,6 +8607,7 @@ static void test_media_session_scrubbing(void) CHECK_CALLED(test_transform_ProcessMessage_START_OF_STREAM); todo_wine CHECK_CALLED(test_media_sink_GetStreamSinkCount); + CHECK_CALLED(test_seek_clock_sink_OnClockSetRate); hr = IMFStreamSink_QueueEvent(media_sink->stream, MEStreamSinkScrubSampleComplete, &GUID_NULL, S_OK, &propvar); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); @@ -8636,10 +8641,11 @@ static void test_media_session_scrubbing(void) CHECK_CALLED(test_stream_sink_Flush); /* Test that during a standard start (i.e. rate == 1.0), preroll is called on the sink */ + SET_EXPECT(test_seek_clock_sink_OnClockSetRate); + SET_EXPECT(test_media_sink_GetPresentationClock); hr = IMFRateControl_SetRate(rate_control, FALSE, 1.0); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - SET_EXPECT(test_media_sink_GetPresentationClock); SET_EXPECT(test_transform_ProcessMessage_START_OF_STREAM); SET_EXPECT(test_media_sink_preroll_NotifyPreroll); propvar.vt = VT_I8; /* hVal will be zero */ @@ -8655,6 +8661,8 @@ static void test_media_session_scrubbing(void) todo_wine CHECK_CALLED(test_transform_ProcessMessage_START_OF_STREAM); CHECK_CALLED(test_media_sink_preroll_NotifyPreroll); + todo_wine + CHECK_CALLED(test_seek_clock_sink_OnClockSetRate); SET_EXPECT(test_media_sink_GetStreamSinkCount); hr = IMFStreamSink_QueueEvent(media_sink->stream, MEStreamSinkPrerolled, &GUID_NULL, S_OK, &propvar); @@ -8671,6 +8679,37 @@ static void test_media_session_scrubbing(void) todo_wine CHECK_CALLED(test_media_sink_GetStreamSinkCount); + /* Test that a rate change whilst in the PLAY state is a no-op */ + hr = IMFRateControl_SetRate(rate_control, FALSE, 0.0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* But registers with the sink in the PAUSE state */ + hr = IMFMediaSession_Pause(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFStreamSink_QueueEvent(media_sink->stream, MEStreamSinkPaused, &GUID_NULL, S_OK, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + + hr = wait_media_event_until_blocking(session, callback, MESessionPaused, 1000, &propvar); + todo_wine + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + + SET_EXPECT(test_media_sink_GetPresentationClock); + SET_EXPECT(test_seek_clock_sink_OnClockSetRate); + hr = IMFRateControl_SetRate(rate_control, FALSE, 0.0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = wait_media_event_until_blocking(session, callback, MESessionRateChanged, 1000, &propvar); + todo_wine + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + todo_wine + CHECK_CALLED(test_media_sink_GetPresentationClock); + todo_wine + CHECK_CALLED(test_seek_clock_sink_OnClockSetRate); + /* Release all the used resources */ IMFPresentationClock_RemoveClockStateSink(presentation_clock, &test_seek_clock_sink); IMFPresentationClock_Release(presentation_clock); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9646
Nikolay Sivov (@nsivov) commented about dlls/mf/tests/mf.c:
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); }
+static void test_media_session_scrubbing(void) +{ + IMFClockStateSink test_seek_clock_sink = {&test_seek_clock_sink_vtbl};
This needs to be allocated and refcounted, it should be accessible when getting out of scope too. We had issues with tests like that, it can cause corruptions and random crashes. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9646#note_124863
Nikolay Sivov (@nsivov) commented about dlls/mf/tests/mf.c:
+ /* Test that a rate change whilst in the PLAY state is a no-op */ + hr = IMFRateControl_SetRate(rate_control, FALSE, 0.0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* But registers with the sink in the PAUSE state */ + hr = IMFMediaSession_Pause(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFStreamSink_QueueEvent(media_sink->stream, MEStreamSinkPaused, &GUID_NULL, S_OK, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); + + hr = wait_media_event_until_blocking(session, callback, MESessionPaused, 1000, &propvar); + todo_wine + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + PropVariantClear(&propvar); Why do you need to raise MEStreamSinkPaused manually? This should normally happen in response to close state changes, in the sink itself.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9646#note_124864
participants (3)
-
Brendan McGrath -
Brendan McGrath (@redmcg) -
Nikolay Sivov (@nsivov)