[PATCH v3 0/1] MR10342: mfmediaengine: Implement scrubbing.
Implements scrubbing in the media engine. This allows the first frame to be ready prior to calling `IMFMediaEngine::Play`. -- v3: mfmediaengine: Implement scrubbing. https://gitlab.winehq.org/wine/wine/-/merge_requests/10342
From: Brendan McGrath <bmcgrath@codeweavers.com> --- dlls/mfmediaengine/main.c | 82 ++++++++++++++++++++++-- dlls/mfmediaengine/tests/mfmediaengine.c | 6 +- dlls/mfmediaengine/video_frame_sink.c | 24 +++++-- 3 files changed, 96 insertions(+), 16 deletions(-) diff --git a/dlls/mfmediaengine/main.c b/dlls/mfmediaengine/main.c index a80ae30b272..fc04cdbd567 100644 --- a/dlls/mfmediaengine/main.c +++ b/dlls/mfmediaengine/main.c @@ -99,6 +99,7 @@ enum media_engine_flags FLAGS_ENGINE_SOURCE_PENDING = 0x10000, FLAGS_ENGINE_PLAY_PENDING = 0x20000, FLAGS_ENGINE_SEEKING = 0x40000, + FLAGS_ENGINE_SCRUBBING = 0x80000, }; struct vec3 @@ -921,14 +922,32 @@ static HRESULT WINAPI media_engine_callback_GetParameters(IMFAsyncCallback *ifac return E_NOTIMPL; } +static HRESULT media_engine_set_rate(struct media_engine *engine, BOOL thin, double rate) +{ + IMFRateControl *rate_control; + HRESULT hr; + + if (FAILED(hr = MFGetService((IUnknown *)engine->session, &MF_RATE_CONTROL_SERVICE, &IID_IMFRateControl, (void **)&rate_control))) + return hr; + + if (FAILED(hr = IMFRateControl_SetRate(rate_control, thin, rate))) + WARN("Failed to set rate, hr %#lx.\n", hr); + + IMFRateControl_Release(rate_control); + + return hr; +} + static HRESULT media_engine_set_current_time(struct media_engine *engine, double seektime); +static void media_engine_start_playback(struct media_engine *engine); static HRESULT WINAPI media_engine_session_events_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) { struct media_engine *engine = impl_from_session_events_IMFAsyncCallback(iface); + BOOL playing_event, ended_event = FALSE; IMFMediaEvent *event = NULL; MediaEventType event_type; - BOOL ended_event = FALSE; + PROPVARIANT rate; HRESULT hr; if (FAILED(hr = IMFMediaSession_EndGetEvent(engine->session, result, &event))) @@ -989,6 +1008,9 @@ static HRESULT WINAPI media_engine_session_events_Invoke(IMFAsyncCallback *iface IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_LOADEDDATA, 0, 0); IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_CANPLAY, 0, 0); + if (engine->flags & FLAGS_ENGINE_SCRUBBING) + media_engine_set_rate(engine, FALSE, 0.0); + LeaveCriticalSection(&engine->cs); PropVariantClear(&value); @@ -1005,8 +1027,11 @@ static HRESULT WINAPI media_engine_session_events_Invoke(IMFAsyncCallback *iface if (isfinite(engine->next_seek)) media_engine_set_current_time(engine, engine->next_seek); } + + playing_event = !(engine->flags & FLAGS_ENGINE_SCRUBBING); LeaveCriticalSection(&engine->cs); - IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_PLAYING, 0, 0); + if (playing_event) + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_PLAYING, 0, 0); break; case MESessionEnded: EnterCriticalSection(&engine->cs); @@ -1029,6 +1054,45 @@ static HRESULT WINAPI media_engine_session_events_Invoke(IMFAsyncCallback *iface case MEEndOfPresentationSegment: video_frame_sink_notify_end_of_presentation_segment(engine->presentation.frame_sink); break; + + case MESessionRateChanged: + + EnterCriticalSection(&engine->cs); + if (engine->flags & FLAGS_ENGINE_SCRUBBING && + IMFMediaEvent_GetValue(event, &rate) == S_OK && + rate.vt == VT_R4) + { + if (rate.fltVal == 0.0) + { + /* Start playback with rate at 0.0 */ + media_engine_start_playback(engine); + } + else + { + /* Scrubbing is complete */ + media_engine_set_flag(engine, FLAGS_ENGINE_SCRUBBING, FALSE); + if (engine->flags & FLAGS_ENGINE_PLAY_PENDING) + media_engine_start_playback(engine); + } + } + LeaveCriticalSection(&engine->cs); + break; + + case MESessionScrubSampleComplete: + + EnterCriticalSection(&engine->cs); + if (engine->flags & FLAGS_ENGINE_SCRUBBING && FAILED(hr = IMFMediaSession_Pause(engine->session))) + WARN("Failed to pause media session %#lx.\n", hr); + LeaveCriticalSection(&engine->cs); + break; + + case MESessionPaused: + + EnterCriticalSection(&engine->cs); + if (engine->flags & FLAGS_ENGINE_SCRUBBING) + media_engine_set_rate(engine, FALSE, engine->default_playback_rate); + LeaveCriticalSection(&engine->cs); + break; } failed: @@ -1492,6 +1556,11 @@ static HRESULT WINAPI media_engine_load_handler_Invoke(IMFAsyncCallback *iface, engine->network_state = MF_MEDIA_ENGINE_NETWORK_IDLE; if (start_playback) media_engine_start_playback(engine); + else + { + /* start scrubbing playback */ + media_engine_set_flag(engine, FLAGS_ENGINE_SCRUBBING, TRUE); + } } else { @@ -2182,7 +2251,7 @@ static HRESULT WINAPI media_engine_Play(IMFMediaEngineEx *iface) media_engine_set_flag(engine, FLAGS_ENGINE_PAUSED | FLAGS_ENGINE_IS_ENDED, FALSE); IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_PLAY, 0, 0); - if (!(engine->flags & FLAGS_ENGINE_SOURCE_PENDING)) + if (!(engine->flags & (FLAGS_ENGINE_SOURCE_PENDING | FLAGS_ENGINE_SCRUBBING))) media_engine_start_playback(engine); else media_engine_set_flag(engine, FLAGS_ENGINE_PLAY_PENDING, TRUE); @@ -2893,8 +2962,11 @@ static HRESULT WINAPI media_engine_OnVideoStreamTick(IMFMediaEngineEx *iface, LO else { MFTIME clocktime; - IMFPresentationClock_GetTime(engine->clock, &clocktime); - hr = video_frame_sink_get_pts(engine->presentation.frame_sink, clocktime, pts); + *pts = MINLONGLONG; + if (SUCCEEDED(IMFPresentationClock_GetTime(engine->clock, &clocktime))) + hr = video_frame_sink_get_pts(engine->presentation.frame_sink, clocktime, pts); + else + hr = S_FALSE; } LeaveCriticalSection(&engine->cs); diff --git a/dlls/mfmediaengine/tests/mfmediaengine.c b/dlls/mfmediaengine/tests/mfmediaengine.c index c90f4c4d4dc..5018cefb82e 100644 --- a/dlls/mfmediaengine/tests/mfmediaengine.c +++ b/dlls/mfmediaengine/tests/mfmediaengine.c @@ -1507,7 +1507,6 @@ static void test_TransferVideoFrame(void) /* now that byte stream is set, we will recieve a frame */ res = WaitForSingleObject(notify->frame_ready_event, 5000); - todo_wine ok(!res, "Unexpected res %#lx.\n", res); if (FAILED(notify->error)) @@ -1533,9 +1532,7 @@ static void test_TransferVideoFrame(void) /* confirm we have a frame available before calling play */ pts = 0; hr = IMFMediaEngineEx_OnVideoStreamTick(media_engine, &pts); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(pts == 0, "Unexpected timestamp.\n"); /* confirm we can transfer a frame before calling play */ @@ -1544,7 +1541,6 @@ static void test_TransferVideoFrame(void) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); res = compare_rgb32(texture, &dst_rect, rb_texture, L"rgb32frame.bmp"); - todo_wine ok(res == 0, "Unexpected %lu%% diff\n", res); hr = IMFMediaEngineEx_Play(media_engine); @@ -2176,6 +2172,8 @@ static void test_effect(void) if (SUCCEEDED(hr = MFCreateAudioRenderer(NULL, &sink))) { + Sleep(100); + count = test_transform_get_sample_count(audio_effect); ok(count > 0, "Unexpected processing count %u.\n", count); count = test_transform_get_sample_count(audio_effect2); diff --git a/dlls/mfmediaengine/video_frame_sink.c b/dlls/mfmediaengine/video_frame_sink.c index 095282532ea..5f2dcbb8c95 100644 --- a/dlls/mfmediaengine/video_frame_sink.c +++ b/dlls/mfmediaengine/video_frame_sink.c @@ -384,6 +384,7 @@ static void video_frame_sink_notify(struct video_frame_sink *sink, unsigned int static HRESULT WINAPI video_frame_sink_stream_ProcessSample(IMFStreamSink *iface, IMFSample *sample) { struct video_frame_sink *sink = impl_from_IMFStreamSink(iface); + BOOL first_frame = FALSE; LONGLONG sampletime; HRESULT hr = S_OK; @@ -410,11 +411,19 @@ static HRESULT WINAPI video_frame_sink_stream_ProcessSample(IMFStreamSink *iface { video_frame_sink_notify(sink, MF_MEDIA_ENGINE_EVENT_FIRSTFRAMEREADY); video_frame_sink_set_flag(sink, FLAGS_FIRST_FRAME, TRUE); + first_frame = TRUE; } /* else TODO: send MEQualityNotify event */ IMFSample_AddRef(sample); - video_frame_sink_sample_queue_push(sink, sample, FALSE); + if (first_frame && sink->rate == 0.0) + { + video_frame_sink_sample_queue_set_presentation(sink, sample); + IMFStreamSink_QueueEvent(&sink->IMFStreamSink_iface, MEStreamSinkScrubSampleComplete, + &GUID_NULL, S_OK, NULL); + } + else + video_frame_sink_sample_queue_push(sink, sample, FALSE); if (sink->queue.used != ARRAY_SIZE(sink->queue.samples)) video_frame_sink_stream_request_sample(sink); @@ -1012,10 +1021,6 @@ static HRESULT video_frame_sink_set_state(struct video_frame_sink *sink, enum si if (state != sink->state || state != SINK_STATE_PAUSED) { - if (sink->rate == 0.0f && state == SINK_STATE_RUNNING) - IMFStreamSink_QueueEvent(&sink->IMFStreamSink_iface, MEStreamSinkScrubSampleComplete, - &GUID_NULL, S_OK, NULL); - IMFStreamSink_QueueEvent(&sink->IMFStreamSink_iface, events[state], &GUID_NULL, S_OK, NULL); } sink->state = state; @@ -1262,6 +1267,7 @@ static HRESULT sample_get_pts(IMFSample *sample, MFTIME clocktime, LONGLONG *pts HRESULT video_frame_sink_get_pts(struct video_frame_sink *sink, MFTIME clocktime, LONGLONG *pts) { HRESULT hr = S_FALSE; + LONGLONG sample_pts; *pts = MINLONGLONG; if (sink) @@ -1286,8 +1292,12 @@ HRESULT video_frame_sink_get_pts(struct video_frame_sink *sink, MFTIME clocktime if (transfer_sample) video_frame_sink_stream_request_sample(sink); - else if (!sink->queue.sample_presented) - hr = sample_get_pts(sink->queue.presentation_sample, clocktime, pts); + else if (sink->queue.presentation_sample && !sink->queue.sample_presented) + { + hr = IMFSample_GetSampleTime(sink->queue.presentation_sample, &sample_pts); + if (hr == S_OK) + *pts = sample_pts; + } LeaveCriticalSection(&sink->cs); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10342
v3: - Rebase to master (fixing merge conflict) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10342#note_132471
This merge request was approved by Nikolay Sivov. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10342
participants (3)
-
Brendan McGrath -
Brendan McGrath (@redmcg) -
Nikolay Sivov (@nsivov)