[PATCH v7 0/6] MR10126: mf: Implement Scrubbing in SAR.
This MR adds the `IMFPresentationTimeSource` interface to SAR and adds support for scrubbing. -- v7: mf: Report seek time up until we render past this point. mf: Discard samples prior to current seek time. mf: Implement Scrubbing in SAR. mf: Check provided presentation clock has a time source. mf: Implement presentation time source for SAR. mf: Replace state variable with clock_state. https://gitlab.winehq.org/wine/wine/-/merge_requests/10126
From: Brendan McGrath <bmcgrath@codeweavers.com> --- dlls/mf/sar.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/dlls/mf/sar.c b/dlls/mf/sar.c index 19e4eeda6b7..c1b00d7c9d8 100644 --- a/dlls/mf/sar.c +++ b/dlls/mf/sar.c @@ -30,13 +30,6 @@ WINE_DEFAULT_DEBUG_CHANNEL(mfplat); -enum stream_state -{ - STREAM_STATE_STOPPED = 0, - STREAM_STATE_RUNNING, - STREAM_STATE_PAUSED, -}; - enum audio_renderer_flags { SAR_SHUT_DOWN = 0x1, @@ -104,9 +97,10 @@ struct audio_renderer unsigned int queued_frames; unsigned int max_frames; struct list queue; - enum stream_state state; unsigned int flags; CRITICAL_SECTION cs; + + MFCLOCK_STATE clock_state; }; static void release_pending_object(struct queued_object *object) @@ -636,11 +630,11 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStart(IMFClockStateSink * EnterCriticalSection(&renderer->cs); if (renderer->audio_client) { - if (renderer->state != STREAM_STATE_RUNNING) + if (renderer->clock_state != MFCLOCK_STATE_RUNNING) { if (FAILED(hr = IAudioClient_Start(renderer->audio_client))) WARN("Failed to start audio client, hr %#lx.\n", hr); - renderer->state = STREAM_STATE_RUNNING; + renderer->clock_state = MFCLOCK_STATE_RUNNING; } } else @@ -664,7 +658,7 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStop(IMFClockStateSink *i EnterCriticalSection(&renderer->cs); if (renderer->audio_client) { - if (renderer->state != STREAM_STATE_STOPPED) + if (renderer->clock_state != MFCLOCK_STATE_STOPPED) { if (SUCCEEDED(hr = IAudioClient_Stop(renderer->audio_client))) { @@ -673,7 +667,7 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStop(IMFClockStateSink *i } else WARN("Failed to stop audio client, hr %#lx.\n", hr); - renderer->state = STREAM_STATE_STOPPED; + renderer->clock_state = MFCLOCK_STATE_STOPPED; renderer->flags &= ~SAR_PREROLLED; } } @@ -694,13 +688,13 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockPause(IMFClockStateSink * TRACE("%p, %s.\n", iface, debugstr_time(systime)); EnterCriticalSection(&renderer->cs); - if (renderer->state == STREAM_STATE_RUNNING) + if (renderer->clock_state == MFCLOCK_STATE_RUNNING) { if (renderer->audio_client) { if (FAILED(hr = IAudioClient_Stop(renderer->audio_client))) WARN("Failed to stop audio client, hr %#lx.\n", hr); - renderer->state = STREAM_STATE_PAUSED; + renderer->clock_state = MFCLOCK_STATE_PAUSED; } else hr = MF_E_NOT_INITIALIZED; @@ -725,11 +719,11 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockRestart(IMFClockStateSink EnterCriticalSection(&renderer->cs); if (renderer->audio_client) { - if ((preroll = (renderer->state != STREAM_STATE_RUNNING))) + if ((preroll = (renderer->clock_state != MFCLOCK_STATE_RUNNING))) { if (FAILED(hr = IAudioClient_Start(renderer->audio_client))) WARN("Failed to start audio client, hr %#lx.\n", hr); - renderer->state = STREAM_STATE_RUNNING; + renderer->clock_state = MFCLOCK_STATE_RUNNING; } } else @@ -1375,11 +1369,11 @@ static HRESULT WINAPI audio_renderer_stream_ProcessSample(IMFStreamSink *iface, hr = MF_E_STREAMSINK_REMOVED; else { - if (renderer->state == STREAM_STATE_RUNNING) + if (renderer->clock_state == MFCLOCK_STATE_RUNNING) hr = stream_queue_sample(renderer, sample); renderer->flags &= ~SAR_SAMPLE_REQUESTED; - if (renderer->queued_frames < renderer->max_frames && renderer->state == STREAM_STATE_RUNNING) + if (renderer->queued_frames < renderer->max_frames && renderer->clock_state == MFCLOCK_STATE_RUNNING) { IMFMediaEventQueue_QueueEventParamVar(renderer->stream_event_queue, MEStreamSinkRequestSample, &GUID_NULL, S_OK, NULL); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10126
From: Brendan McGrath <bmcgrath@codeweavers.com> --- dlls/mf/sar.c | 184 ++++++++++++++++++++++++++++++++++++++++++++- dlls/mf/tests/mf.c | 33 +++++++- 2 files changed, 210 insertions(+), 7 deletions(-) diff --git a/dlls/mf/sar.c b/dlls/mf/sar.c index c1b00d7c9d8..62d158eef73 100644 --- a/dlls/mf/sar.c +++ b/dlls/mf/sar.c @@ -74,6 +74,7 @@ struct audio_renderer IMFSimpleAudioVolume IMFSimpleAudioVolume_iface; IMFAudioStreamVolume IMFAudioStreamVolume_iface; IMFAudioPolicy IMFAudioPolicy_iface; + IMFPresentationTimeSource IMFPresentationTimeSource_iface; IMFAsyncCallback render_callback; LONG refcount; IMFMediaEventQueue *event_queue; @@ -86,6 +87,7 @@ struct audio_renderer IAudioRenderClient *audio_render_client; IAudioStreamVolume *stream_volume; ISimpleAudioVolume *audio_volume; + IAudioClock *audio_clock; struct { unsigned int flags; @@ -101,6 +103,10 @@ struct audio_renderer CRITICAL_SECTION cs; MFCLOCK_STATE clock_state; + DWORD sample_rate; + LONGLONG pts; + UINT64 position; + UINT64 audio_clock_frequency; }; static void release_pending_object(struct queued_object *object) @@ -169,6 +175,11 @@ static struct audio_renderer *impl_from_IMFMediaTypeHandler(IMFMediaTypeHandler return CONTAINING_RECORD(iface, struct audio_renderer, IMFMediaTypeHandler_iface); } +static struct audio_renderer *impl_from_IMFPresentationTimeSource(IMFPresentationTimeSource *iface) +{ + return CONTAINING_RECORD(iface, struct audio_renderer, IMFPresentationTimeSource_iface); +} + static struct audio_renderer *impl_from_render_callback_IMFAsyncCallback(IMFAsyncCallback *iface) { return CONTAINING_RECORD(iface, struct audio_renderer, render_callback); @@ -201,6 +212,10 @@ static HRESULT WINAPI audio_renderer_sink_QueryInterface(IMFMediaSink *iface, RE { *obj = &renderer->IMFGetService_iface; } + else if (IsEqualIID(riid, &IID_IMFPresentationTimeSource)) + { + *obj = &renderer->IMFPresentationTimeSource_iface; + } else { WARN("Unsupported %s.\n", debugstr_guid(riid)); @@ -238,6 +253,8 @@ static void audio_renderer_release_audio_client(struct audio_renderer *renderer) IAudioClient_Reset(renderer->audio_client); IAudioClient_Release(renderer->audio_client); } + renderer->position = 0; + renderer->pts = 0; renderer->audio_client = NULL; if (renderer->audio_render_client) IAudioRenderClient_Release(renderer->audio_render_client); @@ -248,6 +265,9 @@ static void audio_renderer_release_audio_client(struct audio_renderer *renderer) if (renderer->audio_volume) ISimpleAudioVolume_Release(renderer->audio_volume); renderer->audio_volume = NULL; + if (renderer->audio_clock) + IAudioClock_Release(renderer->audio_clock); + renderer->audio_clock = NULL; renderer->flags &= ~SAR_PREROLLED; } @@ -662,7 +682,12 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStop(IMFClockStateSink *i { if (SUCCEEDED(hr = IAudioClient_Stop(renderer->audio_client))) { - if (FAILED(hr = IAudioClient_Reset(renderer->audio_client))) + if (SUCCEEDED(hr = IAudioClient_Reset(renderer->audio_client))) + { + renderer->position = 0; + renderer->pts = 0; + } + else WARN("Failed to reset audio client, hr %#lx.\n", hr); } else @@ -1139,6 +1164,113 @@ static const IMFAudioPolicyVtbl audio_renderer_policy_vtbl = audio_renderer_policy_GetIconPath, }; +static HRESULT WINAPI audio_renderer_time_source_QueryInterface(IMFPresentationTimeSource *iface, REFIID riid, void **obj) +{ + struct audio_renderer *renderer = impl_from_IMFPresentationTimeSource(iface); + return IMFMediaSink_QueryInterface(&renderer->IMFMediaSink_iface, riid, obj); +} + +static ULONG WINAPI audio_renderer_time_source_AddRef(IMFPresentationTimeSource *iface) +{ + struct audio_renderer *renderer = impl_from_IMFPresentationTimeSource(iface); + return IMFMediaSink_AddRef(&renderer->IMFMediaSink_iface); +} + +static ULONG WINAPI audio_renderer_time_source_Release(IMFPresentationTimeSource *iface) +{ + struct audio_renderer *renderer = impl_from_IMFPresentationTimeSource(iface); + return IMFMediaSink_Release(&renderer->IMFMediaSink_iface); +} + +static HRESULT WINAPI audio_renderer_time_source_GetClockCharacteristics(IMFPresentationTimeSource *iface, DWORD *flags) +{ + TRACE("%p, %p.\n", iface, flags); + + *flags = MFCLOCK_CHARACTERISTICS_FLAG_FREQUENCY_10MHZ; + + return S_OK; +} + +static HRESULT WINAPI audio_renderer_time_source_GetCorrelatedTime(IMFPresentationTimeSource *iface, DWORD reserved, LONGLONG *clock_time, + MFTIME *system_time) +{ + struct audio_renderer *renderer = impl_from_IMFPresentationTimeSource(iface); + UINT64 position, counter; + HRESULT hr; + + TRACE("%p, %#lx, %p, %p.\n", iface, reserved, clock_time, system_time); + + if (!renderer->audio_clock) + return MF_E_NOT_INITIALIZED; + + if (FAILED(hr = IAudioClock_GetPosition(renderer->audio_clock, &position, &counter))) + return hr; + + *clock_time = renderer->pts + (INT64)(position - renderer->position) * MFCLOCK_FREQUENCY_HNS / (INT64)renderer->audio_clock_frequency; + *system_time = counter; + + return S_OK; +} + +static HRESULT WINAPI audio_renderer_time_source_GetContinuityKey(IMFPresentationTimeSource *iface, DWORD *key) +{ + TRACE("%p, %p.\n", iface, key); + + *key = 0; + + return S_OK; +} + +static HRESULT WINAPI audio_renderer_time_source_GetState(IMFPresentationTimeSource *iface, DWORD reserved, MFCLOCK_STATE *state) +{ + struct audio_renderer *renderer = impl_from_IMFPresentationTimeSource(iface); + TRACE("%p, %#lx, %p.\n", iface, reserved, state); + + *state = renderer->clock_state; + + return S_OK; +} + +static HRESULT WINAPI audio_renderer_time_source_GetProperties(IMFPresentationTimeSource *iface, MFCLOCK_PROPERTIES *props) +{ + TRACE("%p, %p.\n", iface, props); + + if (!props) + return E_POINTER; + + memset(props, 0, sizeof(*props)); + props->qwClockFrequency = MFCLOCK_FREQUENCY_HNS; + props->dwClockTolerance = MFCLOCK_TOLERANCE_UNKNOWN; + props->dwClockJitter = 1; + + return S_OK; +} + +static HRESULT WINAPI audio_renderer_time_source_GetUnderlyingClock(IMFPresentationTimeSource *iface, IMFClock **ppClock) +{ + TRACE("%p, %p.\n", iface, ppClock); + + if (!ppClock) + return E_POINTER; + + *ppClock = NULL; + + return MF_E_NO_CLOCK; +} + +static const IMFPresentationTimeSourceVtbl audio_renderer_time_source_vtbl = +{ + audio_renderer_time_source_QueryInterface, + audio_renderer_time_source_AddRef, + audio_renderer_time_source_Release, + audio_renderer_time_source_GetClockCharacteristics, + audio_renderer_time_source_GetCorrelatedTime, + audio_renderer_time_source_GetContinuityKey, + audio_renderer_time_source_GetState, + audio_renderer_time_source_GetProperties, + audio_renderer_time_source_GetUnderlyingClock, +}; + static HRESULT sar_create_mmdevice(IMFAttributes *attributes, struct audio_renderer *renderer) { WCHAR *endpoint; @@ -1331,13 +1463,23 @@ static HRESULT WINAPI audio_renderer_stream_GetMediaTypeHandler(IMFStreamSink *i static HRESULT stream_queue_sample(struct audio_renderer *renderer, IMFSample *sample) { + DWORD sample_len, sample_frames, buffer_count; struct queued_object *object; - DWORD sample_len, sample_frames; + LONGLONG start_time; HRESULT hr; + if (FAILED(hr = IMFSample_GetBufferCount(sample, &buffer_count))) + return hr; + + if (!buffer_count) + return E_INVALIDARG; + if (FAILED(hr = IMFSample_GetTotalLength(sample, &sample_len))) return hr; + if (FAILED(hr = IMFSample_GetSampleTime(sample, &start_time))) + return MF_E_INVALID_TIMESTAMP; + sample_frames = sample_len / renderer->frame_size; if (!(object = calloc(1, sizeof(*object)))) @@ -1452,7 +1594,12 @@ static HRESULT WINAPI audio_renderer_stream_Flush(IMFStreamSink *iface) } } renderer->queued_frames = 0; - if (FAILED(hr = IAudioClient_Reset(renderer->audio_client))) + if (SUCCEEDED(hr = IAudioClient_Reset(renderer->audio_client))) + { + renderer->position = 0; + renderer->pts = 0; + } + else WARN("Failed to reset audio client, hr %#lx.\n", hr); LeaveCriticalSection(&renderer->cs); @@ -1597,6 +1744,7 @@ static HRESULT audio_renderer_create_audio_client(struct audio_renderer *rendere } renderer->frame_size = wfx->wBitsPerSample * wfx->nChannels / 8; + renderer->sample_rate = wfx->nSamplesPerSec; flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; if (renderer->stream_config.flags & MF_AUDIO_RENDERER_ATTRIBUTE_FLAGS_CROSSPROCESS) @@ -1632,6 +1780,19 @@ static HRESULT audio_renderer_create_audio_client(struct audio_renderer *rendere return hr; } + if (FAILED(hr = IAudioClient_GetService(renderer->audio_client, &IID_IAudioClock, + (void **)&renderer->audio_clock))) + { + WARN("Failed to get audio clock, hr %#lx.\n", hr); + return hr; + } + + if (FAILED(hr = IAudioClock_GetFrequency(renderer->audio_clock, &renderer->audio_clock_frequency))) + { + WARN("Failed to get audio clock frequency, hr %#lx.\n", hr); + return hr; + } + if (FAILED(hr = IAudioClient_SetEventHandle(renderer->audio_client, renderer->buffer_ready_event))) { WARN("Failed to set event handle, hr %#lx.\n", hr); @@ -1819,8 +1980,10 @@ static void audio_renderer_render(struct audio_renderer *renderer, IMFAsyncResul struct queued_object *obj, *obj2; BOOL keep_sample = FALSE; IMFMediaBuffer *buffer; + UINT64 position; BYTE *dst, *src; DWORD src_len; + LONGLONG pts; HRESULT hr; LIST_FOR_EACH_ENTRY_SAFE(obj, obj2, &renderer->queue, struct queued_object, entry) @@ -1843,16 +2006,28 @@ static void audio_renderer_render(struct audio_renderer *renderer, IMFAsyncResul if (SUCCEEDED(IAudioClient_GetCurrentPadding(renderer->audio_client, &pad_frames))) { max_frames -= pad_frames; + + IMFSample_GetSampleTime(obj->u.sample.sample, &pts); + IAudioClock_GetPosition(renderer->audio_clock, &position, NULL); + position += pad_frames * renderer->audio_clock_frequency / renderer->sample_rate; + src_frames -= obj->u.sample.frame_offset; dst_frames = min(src_frames, max_frames); + hr = S_OK; - if (SUCCEEDED(hr = IAudioRenderClient_GetBuffer(renderer->audio_render_client, dst_frames, &dst))) + if (dst_frames && SUCCEEDED(hr = IAudioRenderClient_GetBuffer(renderer->audio_render_client, dst_frames, &dst))) { memcpy(dst, src + obj->u.sample.frame_offset * renderer->frame_size, dst_frames * renderer->frame_size); IAudioRenderClient_ReleaseBuffer(renderer->audio_render_client, dst_frames, 0); + if (obj->u.sample.frame_offset == 0) + { + renderer->pts = pts; + renderer->position = position; + } + obj->u.sample.frame_offset += dst_frames; renderer->queued_frames -= dst_frames; } @@ -1925,6 +2100,7 @@ static HRESULT sar_create_object(IMFAttributes *attributes, void *user_context, renderer->IMFSimpleAudioVolume_iface.lpVtbl = &audio_renderer_simple_volume_vtbl; renderer->IMFAudioStreamVolume_iface.lpVtbl = &audio_renderer_stream_volume_vtbl; renderer->IMFAudioPolicy_iface.lpVtbl = &audio_renderer_policy_vtbl; + renderer->IMFPresentationTimeSource_iface.lpVtbl = &audio_renderer_time_source_vtbl; renderer->render_callback.lpVtbl = &audio_renderer_render_callback_vtbl; renderer->refcount = 1; InitializeCriticalSection(&renderer->cs); diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 01c305e3fa4..b4660402dcf 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -5199,6 +5199,7 @@ static WINAPI HRESULT presentation_clock_AddClockStateSink(IMFPresentationClock { struct presentation_clock *pc = impl_from_IMFPresentationClock(iface); + todo_wine_if(!expect_presentation_clock_AddClockStateSink) CHECK_EXPECT(presentation_clock_AddClockStateSink); if (pc->clock_state_sink) @@ -5214,6 +5215,7 @@ static WINAPI HRESULT presentation_clock_RemoveClockStateSink(IMFPresentationClo { struct presentation_clock *pc = impl_from_IMFPresentationClock(iface); + todo_wine_if(!expect_presentation_clock_RemoveClockStateSink) CHECK_EXPECT(presentation_clock_RemoveClockStateSink); if (pc->clock_state_sink == state_sink) @@ -6552,7 +6554,7 @@ static void test_sar(void) check_interface(sink, &IID_IMFMediaEventGenerator, TRUE); check_interface(sink, &IID_IMFClockStateSink, TRUE); check_interface(sink, &IID_IMFGetService, TRUE); - todo_wine check_interface(sink, &IID_IMFPresentationTimeSource, TRUE); + check_interface(sink, &IID_IMFPresentationTimeSource, TRUE); todo_wine check_service_interface(sink, &MF_RATE_CONTROL_SERVICE, &IID_IMFRateSupport, TRUE); check_service_interface(sink, &MF_RATE_CONTROL_SERVICE, &IID_IMFRateControl, FALSE); check_service_interface(sink, &MR_POLICY_VOLUME_SERVICE, &IID_IMFSimpleAudioVolume, TRUE); @@ -7103,7 +7105,6 @@ if (rate_support1) /* Test IMFPresentationTimeSource interface */ hr = IMFMediaSink_QueryInterface(sink, &IID_IMFPresentationTimeSource, (void**)&time_source); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = IMFMediaSink_QueryInterface(sink, &IID_IMFClockStateSink, (void **)&state_sink1); @@ -7213,7 +7214,9 @@ if (time_source) SET_EXPECT(presentation_clock_GetTimeSource); hr = IMFMediaSink_SetPresentationClock(sink, clock); + todo_wine ok(hr == MF_E_CLOCK_NO_TIME_SOURCE, "Unexpected hr %#lx.\n", hr); + todo_wine CHECK_CALLED(presentation_clock_GetTimeSource); IMFPresentationTimeSource_AddRef(presentation_clock->time_source = time_source); @@ -7222,6 +7225,7 @@ if (time_source) SET_EXPECT(presentation_clock_AddClockStateSink); hr = IMFMediaSink_SetPresentationClock(sink, clock); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine CHECK_CALLED(presentation_clock_GetTimeSource); CHECK_CALLED(presentation_clock_AddClockStateSink); @@ -7255,6 +7259,7 @@ if (time_source) /* But no MEStreamSinkPrerolled will be provided until we provide a second sample */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkPrerolled, 100, &propvar); + todo_wine ok(hr == WAIT_TIMEOUT, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7274,6 +7279,7 @@ if (time_source) /* Now we get the pre-roll event. The third sample does not need to be provided */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkPrerolled, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7288,15 +7294,18 @@ if (time_source) /* Two more samples are immediately requested */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRequestSample, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRequestSample, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); /* Before we get the started event */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkStarted, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7308,6 +7317,7 @@ if (time_source) } /* Clock time will halt at exactly 200000 as this is the total duration of the two samples */ + todo_wine ok(time == 200000, "Unexpected time %I64d.\n", time); /* Provide a third sample */ @@ -7340,6 +7350,7 @@ if (time_source) } /* Time is now greater than 300000 as, due to the ENDOFSEGMENT marker, SAR will now insert silence and continue the timer */ + todo_wine ok(time > 300000, "Unexpected time %I64d.\n", time); /* No new samples are requested after the marker */ @@ -7387,6 +7398,7 @@ if (time_source) /* But no MEStreamSinkPrerolled will be provided until we provide at least 200ms of data */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkPrerolled, 100, &propvar); + todo_wine ok(hr == WAIT_TIMEOUT, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7413,6 +7425,7 @@ if (time_source) /* Confirm a start prior to pre-roll completion will fail */ hr = IMFPresentationClock_Start(clock, 0); + todo_wine ok(hr == MF_E_STATE_TRANSITION_PENDING, "Unexpected hr %#lx.\n", hr); /* Complete the pre-roll, we still need 180ms of duration. We'll send an 80ms sample and four 25ms. @@ -7452,12 +7465,14 @@ if (time_source) /* A new sample is not requested if duration is provided and the total duration of samples buffered is 200ms or more * Instead there is a preroll event */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkPrerolled, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); /* Check clock time before start */ hr = IMFPresentationClock_GetTime(clock, &time); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(time == 0, "Unexpected time %I64d.\n", time); /* Start clock */ @@ -7468,6 +7483,7 @@ if (time_source) for (i = 0; i < 7; i++) { hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRequestSample, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); } @@ -7501,10 +7517,12 @@ if (time_source) /* Test scrubbing. Start by setting clock rate to zero. */ hr = IMFClockStateSink_OnClockSetRate(state_sink1, 0, 0.0); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); /* Wait for the rate changed event */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRateChanged, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7528,6 +7546,7 @@ if (time_source) /* And then the scrub complete event. No samples need to be provided. */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkScrubSampleComplete, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7542,6 +7561,7 @@ if (time_source) /* ... no new sample is requested ... */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRequestSample, 100, &propvar); + todo_wine ok(hr == WAIT_TIMEOUT, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7561,6 +7581,7 @@ if (time_source) /* ... but the clock remains at zero */ hr = IMFPresentationClock_GetTime(clock, &time); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine_if(time != 0) ok(time == 0, "Unexpected time %I64d.\n", time); /* to start the playback, we pause ... */ @@ -7573,9 +7594,11 @@ if (time_source) /* ... set rate back to 1 ... */ hr = IMFClockStateSink_OnClockSetRate(state_sink1, 0, 1.0); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRateChanged, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7584,14 +7607,17 @@ if (time_source) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRequestSample, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRequestSample, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkStarted, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7631,9 +7657,10 @@ if (time_source) SET_EXPECT(presentation_clock_RemoveClockStateSink); hr = IMFMediaSink_Shutdown(sink); ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr); - todo_wine CHECK_CALLED(presentation_clock_RemoveClockStateSink); + Sleep(20); + ref = IMFMediaSink_Release(sink); ok(ref == 0, "Release returned %ld\n", ref); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10126
From: Brendan McGrath <bmcgrath@codeweavers.com> --- dlls/mf/sar.c | 7 ++++++- dlls/mf/tests/mf.c | 4 ---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dlls/mf/sar.c b/dlls/mf/sar.c index 62d158eef73..6ad1cd5e967 100644 --- a/dlls/mf/sar.c +++ b/dlls/mf/sar.c @@ -419,6 +419,7 @@ static void audio_renderer_set_presentation_clock(struct audio_renderer *rendere static HRESULT WINAPI audio_renderer_sink_SetPresentationClock(IMFMediaSink *iface, IMFPresentationClock *clock) { struct audio_renderer *renderer = impl_from_IMFMediaSink(iface); + IMFPresentationTimeSource *time_source = NULL; HRESULT hr = S_OK; TRACE("%p, %p.\n", iface, clock); @@ -427,8 +428,12 @@ static HRESULT WINAPI audio_renderer_sink_SetPresentationClock(IMFMediaSink *ifa if (renderer->flags & SAR_SHUT_DOWN) hr = MF_E_SHUTDOWN; - else + else if (!clock || SUCCEEDED(hr = IMFPresentationClock_GetTimeSource(clock, &time_source))) + { + if (time_source) + IMFPresentationTimeSource_Release(time_source); audio_renderer_set_presentation_clock(renderer, clock); + } LeaveCriticalSection(&renderer->cs); diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index b4660402dcf..cb766706c7a 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -6582,7 +6582,6 @@ static void test_sar(void) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = IMFMediaSink_SetPresentationClock(sink, present_clock); - todo_wine ok(hr == MF_E_CLOCK_NO_TIME_SOURCE, "Unexpected hr %#lx.\n", hr); hr = MFCreateSystemTimeSource(&time_source); @@ -7214,9 +7213,7 @@ if (time_source) SET_EXPECT(presentation_clock_GetTimeSource); hr = IMFMediaSink_SetPresentationClock(sink, clock); - todo_wine ok(hr == MF_E_CLOCK_NO_TIME_SOURCE, "Unexpected hr %#lx.\n", hr); - todo_wine CHECK_CALLED(presentation_clock_GetTimeSource); IMFPresentationTimeSource_AddRef(presentation_clock->time_source = time_source); @@ -7225,7 +7222,6 @@ if (time_source) SET_EXPECT(presentation_clock_AddClockStateSink); hr = IMFMediaSink_SetPresentationClock(sink, clock); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine CHECK_CALLED(presentation_clock_GetTimeSource); CHECK_CALLED(presentation_clock_AddClockStateSink); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10126
From: Brendan McGrath <bmcgrath@codeweavers.com> --- dlls/mf/sar.c | 38 +++++++++++++++++++++++++++++++++----- dlls/mf/tests/mf.c | 8 ++------ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/dlls/mf/sar.c b/dlls/mf/sar.c index 6ad1cd5e967..a1b652041aa 100644 --- a/dlls/mf/sar.c +++ b/dlls/mf/sar.c @@ -107,6 +107,7 @@ struct audio_renderer LONGLONG pts; UINT64 position; UINT64 audio_clock_frequency; + float rate; }; static void release_pending_object(struct queued_object *object) @@ -512,7 +513,7 @@ static void audio_renderer_preroll(struct audio_renderer *renderer) { unsigned int i; - if (renderer->flags & SAR_PREROLLED) + if (renderer->flags & SAR_PREROLLED || renderer->rate == 0.0f) return; for (i = 0; i < 2; ++i) @@ -657,7 +658,7 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStart(IMFClockStateSink * { if (renderer->clock_state != MFCLOCK_STATE_RUNNING) { - if (FAILED(hr = IAudioClient_Start(renderer->audio_client))) + if (renderer->rate != 0.0f && FAILED(hr = IAudioClient_Start(renderer->audio_client))) WARN("Failed to start audio client, hr %#lx.\n", hr); renderer->clock_state = MFCLOCK_STATE_RUNNING; } @@ -666,6 +667,8 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStart(IMFClockStateSink * hr = MF_E_NOT_INITIALIZED; IMFMediaEventQueue_QueueEventParamVar(renderer->stream_event_queue, MEStreamSinkStarted, &GUID_NULL, hr, NULL); + if (renderer->rate == 0.0f) + IMFMediaEventQueue_QueueEventParamVar(renderer->stream_event_queue, MEStreamSinkScrubSampleComplete, &GUID_NULL, hr, NULL); if (SUCCEEDED(hr)) audio_renderer_preroll(renderer); LeaveCriticalSection(&renderer->cs); @@ -724,6 +727,8 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockPause(IMFClockStateSink * { if (FAILED(hr = IAudioClient_Stop(renderer->audio_client))) WARN("Failed to stop audio client, hr %#lx.\n", hr); + else + hr = S_OK; renderer->clock_state = MFCLOCK_STATE_PAUSED; } else @@ -751,7 +756,7 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockRestart(IMFClockStateSink { if ((preroll = (renderer->clock_state != MFCLOCK_STATE_RUNNING))) { - if (FAILED(hr = IAudioClient_Start(renderer->audio_client))) + if (renderer->rate != 0.0f && FAILED(hr = IAudioClient_Start(renderer->audio_client))) WARN("Failed to start audio client, hr %#lx.\n", hr); renderer->clock_state = MFCLOCK_STATE_RUNNING; } @@ -760,6 +765,8 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockRestart(IMFClockStateSink hr = MF_E_NOT_INITIALIZED; IMFMediaEventQueue_QueueEventParamVar(renderer->stream_event_queue, MEStreamSinkStarted, &GUID_NULL, hr, NULL); + if (renderer->rate == 0.0f) + IMFMediaEventQueue_QueueEventParamVar(renderer->stream_event_queue, MEStreamSinkScrubSampleComplete, &GUID_NULL, hr, NULL); if (preroll) audio_renderer_preroll(renderer); @@ -770,9 +777,29 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockRestart(IMFClockStateSink static HRESULT WINAPI audio_renderer_clock_sink_OnClockSetRate(IMFClockStateSink *iface, MFTIME systime, float rate) { - FIXME("%p, %s, %f.\n", iface, debugstr_time(systime), rate); + struct audio_renderer *renderer = impl_from_IMFClockStateSink(iface); + HRESULT hr = S_OK; - return E_NOTIMPL; + TRACE("%p, %s, %f.\n", iface, debugstr_time(systime), rate); + + EnterCriticalSection(&renderer->cs); + + if (rate == 0.0 || rate == 1.0) + { + renderer->rate = rate; + } + else + { + WARN("%f is an unsupported rate.\n", rate); + hr = MF_E_UNSUPPORTED_RATE; + } + + if (hr == S_OK) + IMFMediaEventQueue_QueueEventParamVar(renderer->stream_event_queue, MEStreamSinkRateChanged, &GUID_NULL, hr, NULL); + + LeaveCriticalSection(&renderer->cs); + + return hr; } static const IMFClockStateSinkVtbl audio_renderer_clock_sink_vtbl = @@ -2107,6 +2134,7 @@ static HRESULT sar_create_object(IMFAttributes *attributes, void *user_context, renderer->IMFAudioPolicy_iface.lpVtbl = &audio_renderer_policy_vtbl; renderer->IMFPresentationTimeSource_iface.lpVtbl = &audio_renderer_time_source_vtbl; renderer->render_callback.lpVtbl = &audio_renderer_render_callback_vtbl; + renderer->rate = 1.0f; renderer->refcount = 1; InitializeCriticalSection(&renderer->cs); renderer->buffer_ready_event = CreateEventW(NULL, FALSE, FALSE, NULL); diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index cb766706c7a..94d4b367105 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -7513,12 +7513,10 @@ if (time_source) /* Test scrubbing. Start by setting clock rate to zero. */ hr = IMFClockStateSink_OnClockSetRate(state_sink1, 0, 0.0); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); /* Wait for the rate changed event */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRateChanged, 1000, &propvar); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7533,10 +7531,12 @@ if (time_source) /* Then two samples are requested */ hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRequestSample, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRequestSample, 1000, &propvar); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7590,11 +7590,9 @@ if (time_source) /* ... set rate back to 1 ... */ hr = IMFClockStateSink_OnClockSetRate(state_sink1, 0, 1.0); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRateChanged, 1000, &propvar); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); @@ -7603,12 +7601,10 @@ if (time_source) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRequestSample, 1000, &propvar); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); hr = gen_wait_media_event_until_blocking((IMFMediaEventGenerator*)stream, callback, MEStreamSinkRequestSample, 1000, &propvar); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); PropVariantClear(&propvar); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10126
From: Brendan McGrath <bmcgrath@codeweavers.com> --- dlls/mf/sar.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dlls/mf/sar.c b/dlls/mf/sar.c index a1b652041aa..5d522a427e1 100644 --- a/dlls/mf/sar.c +++ b/dlls/mf/sar.c @@ -35,6 +35,7 @@ enum audio_renderer_flags SAR_SHUT_DOWN = 0x1, SAR_PREROLLED = 0x2, SAR_SAMPLE_REQUESTED = 0x4, + SAR_SEEKING = 0x8, }; enum queued_object_type @@ -105,6 +106,7 @@ struct audio_renderer MFCLOCK_STATE clock_state; DWORD sample_rate; LONGLONG pts; + LONGLONG seek_pts; UINT64 position; UINT64 audio_clock_frequency; float rate; @@ -656,6 +658,11 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStart(IMFClockStateSink * EnterCriticalSection(&renderer->cs); if (renderer->audio_client) { + if (offset != PRESENTATION_CURRENT_POSITION) + { + renderer->seek_pts = offset; + renderer->flags |= SAR_SEEKING; + } if (renderer->clock_state != MFCLOCK_STATE_RUNNING) { if (renderer->rate != 0.0f && FAILED(hr = IAudioClient_Start(renderer->audio_client))) @@ -1514,6 +1521,13 @@ static HRESULT stream_queue_sample(struct audio_renderer *renderer, IMFSample *s sample_frames = sample_len / renderer->frame_size; + /* Discard samples that are prior to our seeking location */ + if (renderer->flags & SAR_SEEKING && start_time + + (LONGLONG)sample_frames * MFCLOCK_FREQUENCY_HNS / renderer->sample_rate < renderer->seek_pts) + return S_OK; + + renderer->flags &= ~SAR_SEEKING; + if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10126
From: Brendan McGrath <bmcgrath@codeweavers.com> --- dlls/mf/sar.c | 45 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/dlls/mf/sar.c b/dlls/mf/sar.c index 5d522a427e1..0ad8b195a0e 100644 --- a/dlls/mf/sar.c +++ b/dlls/mf/sar.c @@ -36,6 +36,7 @@ enum audio_renderer_flags SAR_PREROLLED = 0x2, SAR_SAMPLE_REQUESTED = 0x4, SAR_SEEKING = 0x8, + SAR_REPORT_SEEK_TIME = 0x10, }; enum queued_object_type @@ -661,7 +662,7 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStart(IMFClockStateSink * if (offset != PRESENTATION_CURRENT_POSITION) { renderer->seek_pts = offset; - renderer->flags |= SAR_SEEKING; + renderer->flags |= (SAR_SEEKING | SAR_REPORT_SEEK_TIME); } if (renderer->clock_state != MFCLOCK_STATE_RUNNING) { @@ -1230,27 +1231,51 @@ static HRESULT WINAPI audio_renderer_time_source_GetClockCharacteristics(IMFPres return S_OK; } -static HRESULT WINAPI audio_renderer_time_source_GetCorrelatedTime(IMFPresentationTimeSource *iface, DWORD reserved, LONGLONG *clock_time, +static HRESULT audio_renderer_get_correlated_time(struct audio_renderer *renderer, LONGLONG *clock_time, MFTIME *system_time) { - struct audio_renderer *renderer = impl_from_IMFPresentationTimeSource(iface); UINT64 position, counter; + LONGLONG time; HRESULT hr; - TRACE("%p, %#lx, %p, %p.\n", iface, reserved, clock_time, system_time); - - if (!renderer->audio_clock) - return MF_E_NOT_INITIALIZED; - if (FAILED(hr = IAudioClock_GetPosition(renderer->audio_clock, &position, &counter))) return hr; - *clock_time = renderer->pts + (INT64)(position - renderer->position) * MFCLOCK_FREQUENCY_HNS / (INT64)renderer->audio_clock_frequency; - *system_time = counter; + time = renderer->pts + (INT64)(position - renderer->position) * MFCLOCK_FREQUENCY_HNS / (INT64)renderer->audio_clock_frequency; + if (renderer->flags & SAR_REPORT_SEEK_TIME) + { + if (time < renderer->seek_pts) + time = renderer->seek_pts; + else + renderer->flags &= ~SAR_REPORT_SEEK_TIME; + } + + if (clock_time) + *clock_time = time; + if (system_time) + *system_time = counter; return S_OK; } +static HRESULT WINAPI audio_renderer_time_source_GetCorrelatedTime(IMFPresentationTimeSource *iface, DWORD reserved, LONGLONG *clock_time, + MFTIME *system_time) +{ + struct audio_renderer *renderer = impl_from_IMFPresentationTimeSource(iface); + HRESULT hr; + + TRACE("%p, %#lx, %p, %p.\n", iface, reserved, clock_time, system_time); + + EnterCriticalSection(&renderer->cs); + if (!renderer->audio_clock) + hr = MF_E_NOT_INITIALIZED; + else + hr = audio_renderer_get_correlated_time(renderer, clock_time, system_time); + LeaveCriticalSection(&renderer->cs); + + return hr; +} + static HRESULT WINAPI audio_renderer_time_source_GetContinuityKey(IMFPresentationTimeSource *iface, DWORD *key) { TRACE("%p, %p.\n", iface, key); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10126
I think this one is out of place, because it's only called here for a side effect.
Agree actually. I think I'm trying to do two subtly different things with the one flag. I've split it in to two flags now: 1. For discarding samples prior to the seek time (so we never write them to the output buffer); and 2. For reporting the seek time up until the audio being played is past it So basically, the first flag handles the writing of the samples and the second the reading. So now the only impact of the new flag (`SAR_REPORT_SEEK_TIME`) is within the `GetCorrelatedTime` function; hence I think it makes sense to clear it there.
Also it's using IAudioClock::GetPosition() to reset this flag, while we use GetPosition() in render() itself, without any changes to seek flag. All of that might work, it's not easy to see if it does.
Yes, `GetPosition` provides the position within the buffer from where audio is currently being read. So in `audio_renderer_render`, it also adds the `pad_frames` to calculate where in the buffer it will be writing. We then associate that writing position with the PTS of the sample being rendered. That way we can calculate the timestamp of what is currently being read by applying the delta of `GetPosition` to that writing position with respect to the associated PTS. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10126#note_131924
v7: - Split the reading portion of `SAR_SEEKING` in to a separate flag (`SAR_REPORT_SEEK_TIME`) - Rebase on to master -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10126#note_131925
This merge request was approved by Nikolay Sivov. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10126
participants (3)
-
Brendan McGrath -
Brendan McGrath (@redmcg) -
Nikolay Sivov (@nsivov)