[PATCH v6 0/5] MR10126: mf: Implement Scrubbing in SAR.
This MR adds the `IMFPresentationTimeSource` interface to SAR and adds support for scrubbing. -- v6: 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. 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 8bc3f83019c..b2d7265b4da 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -5186,6 +5186,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) @@ -5201,6 +5202,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) @@ -6539,7 +6541,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); @@ -7090,7 +7092,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); @@ -7200,7 +7201,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); @@ -7209,6 +7212,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); @@ -7242,6 +7246,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); @@ -7261,6 +7266,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); @@ -7275,15 +7281,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); @@ -7295,6 +7304,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 */ @@ -7327,6 +7337,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 */ @@ -7374,6 +7385,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); @@ -7400,6 +7412,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. @@ -7439,12 +7452,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 */ @@ -7455,6 +7470,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); } @@ -7488,10 +7504,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); @@ -7515,6 +7533,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); @@ -7529,6 +7548,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); @@ -7548,6 +7568,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 ... */ @@ -7560,9 +7581,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); @@ -7571,14 +7594,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); @@ -7618,9 +7644,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 b2d7265b4da..9b3a2729286 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -6569,7 +6569,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); @@ -7201,9 +7200,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); @@ -7212,7 +7209,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 9b3a2729286..c03402810de 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -7500,12 +7500,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); @@ -7520,10 +7518,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); @@ -7577,11 +7577,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); @@ -7590,12 +7588,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 a1b652041aa..1ba0f8283da 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))) @@ -1223,27 +1230,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); @@ -1514,6 +1545,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; @@ -2087,6 +2123,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
On Mon Mar 2 02:59:03 2026 +0000, Brendan McGrath wrote:
changed this file in version 6 of the diff Looks like yes. They seem to keep identical state. I've just pushed a new commit that replaces the pre-existing `state` variable with `clock_state`.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10126#note_130886
v6: - new commit that replaces `state` variable with `clock_state` - rebase to master -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10126#note_130887
participants (2)
-
Brendan McGrath -
Brendan McGrath (@redmcg)