[PATCH v5 0/4] MR10126: mf: Implement Scrubbing in SAR.
This MR adds the `IMFPresentationTimeSource` interface to SAR and adds support for scrubbing. -- v5: mf: Report requested seek time until seek is complete. mf: Implement Scrubbing in SAR. mf: Check provided presentation clock has a time source. mf: Implement presentation time source for SAR. https://gitlab.winehq.org/wine/wine/-/merge_requests/10126
From: Brendan McGrath <bmcgrath@codeweavers.com> --- dlls/mf/sar.c | 189 ++++++++++++++++++++++++++++++++++++++++++++- dlls/mf/tests/mf.c | 33 +++++++- 2 files changed, 215 insertions(+), 7 deletions(-) diff --git a/dlls/mf/sar.c b/dlls/mf/sar.c index 19e4eeda6b7..1b9e14d3476 100644 --- a/dlls/mf/sar.c +++ b/dlls/mf/sar.c @@ -81,6 +81,7 @@ struct audio_renderer IMFSimpleAudioVolume IMFSimpleAudioVolume_iface; IMFAudioStreamVolume IMFAudioStreamVolume_iface; IMFAudioPolicy IMFAudioPolicy_iface; + IMFPresentationTimeSource IMFPresentationTimeSource_iface; IMFAsyncCallback render_callback; LONG refcount; IMFMediaEventQueue *event_queue; @@ -93,6 +94,7 @@ struct audio_renderer IAudioRenderClient *audio_render_client; IAudioStreamVolume *stream_volume; ISimpleAudioVolume *audio_volume; + IAudioClock *audio_clock; struct { unsigned int flags; @@ -107,6 +109,12 @@ struct audio_renderer enum stream_state state; unsigned int flags; CRITICAL_SECTION cs; + + DWORD sample_rate; + MFCLOCK_STATE clock_state; + LONGLONG pts; + UINT64 position; + UINT64 audio_clock_frequency; }; static void release_pending_object(struct queued_object *object) @@ -175,6 +183,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); @@ -207,6 +220,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)); @@ -244,6 +261,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); @@ -254,6 +273,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; } @@ -636,6 +658,7 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStart(IMFClockStateSink * EnterCriticalSection(&renderer->cs); if (renderer->audio_client) { + renderer->clock_state = MFCLOCK_STATE_RUNNING; if (renderer->state != STREAM_STATE_RUNNING) { if (FAILED(hr = IAudioClient_Start(renderer->audio_client))) @@ -664,11 +687,17 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStop(IMFClockStateSink *i EnterCriticalSection(&renderer->cs); if (renderer->audio_client) { + renderer->clock_state = MFCLOCK_STATE_STOPPED; if (renderer->state != STREAM_STATE_STOPPED) { 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 @@ -698,6 +727,7 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockPause(IMFClockStateSink * { if (renderer->audio_client) { + renderer->clock_state = MFCLOCK_STATE_PAUSED; if (FAILED(hr = IAudioClient_Stop(renderer->audio_client))) WARN("Failed to stop audio client, hr %#lx.\n", hr); renderer->state = STREAM_STATE_PAUSED; @@ -1145,6 +1175,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; @@ -1337,13 +1474,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)))) @@ -1458,7 +1605,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); @@ -1603,6 +1755,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) @@ -1638,6 +1791,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); @@ -1825,8 +1991,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) @@ -1849,16 +2017,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; } @@ -1931,6 +2111,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 df1362381b2..9e3d5afcc90 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -4959,6 +4959,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) @@ -4974,6 +4975,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) @@ -6312,7 +6314,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); @@ -6863,7 +6865,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); @@ -6973,7 +6974,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); @@ -6982,6 +6985,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); @@ -7015,6 +7019,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); @@ -7034,6 +7039,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); @@ -7048,15 +7054,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); @@ -7068,6 +7077,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 */ @@ -7100,6 +7110,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 */ @@ -7147,6 +7158,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); @@ -7173,6 +7185,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. @@ -7212,12 +7225,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 */ @@ -7228,6 +7243,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); } @@ -7261,10 +7277,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); @@ -7288,6 +7306,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); @@ -7302,6 +7321,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); @@ -7321,6 +7341,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 ... */ @@ -7333,9 +7354,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); @@ -7344,14 +7367,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); @@ -7391,9 +7417,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 1b9e14d3476..4b269f8ab0d 100644 --- a/dlls/mf/sar.c +++ b/dlls/mf/sar.c @@ -427,6 +427,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); @@ -435,8 +436,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 9e3d5afcc90..85e6bc50552 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -6342,7 +6342,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); @@ -6974,9 +6973,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); @@ -6985,7 +6982,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 4b269f8ab0d..ccc1ad9dccd 100644 --- a/dlls/mf/sar.c +++ b/dlls/mf/sar.c @@ -115,6 +115,7 @@ struct audio_renderer LONGLONG pts; UINT64 position; UINT64 audio_clock_frequency; + float rate; }; static void release_pending_object(struct queued_object *object) @@ -520,7 +521,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) @@ -666,7 +667,7 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockStart(IMFClockStateSink * renderer->clock_state = MFCLOCK_STATE_RUNNING; if (renderer->state != STREAM_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->state = STREAM_STATE_RUNNING; } @@ -675,6 +676,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); @@ -735,6 +738,8 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockPause(IMFClockStateSink * renderer->clock_state = MFCLOCK_STATE_PAUSED; if (FAILED(hr = IAudioClient_Stop(renderer->audio_client))) WARN("Failed to stop audio client, hr %#lx.\n", hr); + else + hr = S_OK; renderer->state = STREAM_STATE_PAUSED; } else @@ -762,7 +767,7 @@ static HRESULT WINAPI audio_renderer_clock_sink_OnClockRestart(IMFClockStateSink { if ((preroll = (renderer->state != STREAM_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->state = STREAM_STATE_RUNNING; } @@ -771,6 +776,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); @@ -781,9 +788,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 = @@ -2118,6 +2145,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 85e6bc50552..5e1c2909d39 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -7273,12 +7273,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); @@ -7293,10 +7291,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); @@ -7350,11 +7350,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); @@ -7363,12 +7361,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 | 57 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/dlls/mf/sar.c b/dlls/mf/sar.c index ccc1ad9dccd..9b19acdff75 100644 --- a/dlls/mf/sar.c +++ b/dlls/mf/sar.c @@ -42,6 +42,7 @@ enum audio_renderer_flags SAR_SHUT_DOWN = 0x1, SAR_PREROLLED = 0x2, SAR_SAMPLE_REQUESTED = 0x4, + SAR_SEEKING = 0x8, }; enum queued_object_type @@ -113,6 +114,7 @@ struct audio_renderer DWORD sample_rate; MFCLOCK_STATE clock_state; LONGLONG pts; + LONGLONG seek_pts; UINT64 position; UINT64 audio_clock_frequency; float rate; @@ -664,6 +666,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; + } renderer->clock_state = MFCLOCK_STATE_RUNNING; if (renderer->state != STREAM_STATE_RUNNING) { @@ -1234,27 +1241,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_SEEKING) + { + if (time < renderer->seek_pts) + time = renderer->seek_pts; + else + renderer->flags &= ~SAR_SEEKING; + } + + 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); @@ -1525,6 +1556,11 @@ 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; + if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY; @@ -2098,6 +2134,9 @@ static void audio_renderer_render(struct audio_renderer *renderer, IMFAsyncResul renderer->flags |= SAR_SAMPLE_REQUESTED; } + /* this is called to potentially remove the SAR_SEEKING flag */ + audio_renderer_get_correlated_time(renderer, NULL, NULL); + if (FAILED(hr = MFPutWaitingWorkItem(renderer->buffer_ready_event, 0, result, &renderer->buffer_ready_key))) WARN("Failed to submit wait item, hr %#lx.\n", hr); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10126
v5: - Move calculation of playback position to a helper function; - Call helper function from within `audio_renderer_render` (so the `SAR_SEEKING` flag can be removed without needing to call `GetCorrelatedTime`); - Add a CriticalSection barrier to `GetCorrelatedTime`; and - Rebase to master -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10126#note_130311
why would seeking state depend on asking for time with GetCorrelatedTime()
I did debate if that was the best place for it. It was the only place where the current playback position was being calculated. But I've moved that logic to a helper function and I now call it from `audio_renderer_render` (which is called on a timer).
why would you check for before seeking position only
This seems to be what Windows does. It would always return the seek time, but if I provided a sample with a PTS greater than that, then it would return that PTS. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10126#note_130312
Nikolay Sivov (@nsivov) commented about dlls/mf/sar.c:
EnterCriticalSection(&renderer->cs); if (renderer->audio_client) { + renderer->clock_state = MFCLOCK_STATE_RUNNING; if (renderer->state != STREAM_STATE_RUNNING) {
Could those two states be merged? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10126#note_130769
participants (3)
-
Brendan McGrath -
Brendan McGrath (@redmcg) -
Nikolay Sivov (@nsivov)