Patch 1.
The tests on Windows show that when the timer callback is queued behind the clock time it is queued and executed quite soon with 0-15ms delay which corresponds to default timer granularity. The timeout tolerance in the test is quite bigger to avoid a flaky test. Wine currently may execute the callback right away or delay it indefinitely depending on the time difference and current system time.
Patch 2. It turns out when the session is paused and then restarted and all the samples were delivered to sample grabber before restart, nothing is going to request the samples again when the session is restarted. I am attaching a test in a patch which hopefully confirms how that should work. I am not including that in the patches as the test involves delays (to make sure all the async events settle at certain points) and inherently flaky. Also, _ IMFStreamSink_GetEvent(stream, ...) in the test crashes on Win7 for some unknown reason.
[0001-mf-tests-Add-test-for-pausing-and-restarting-sample-.patch](/uploads/58c1738d48b3392e7ec430a0757210d0/0001-mf-tests-Add-test-for-pausing-and-restarting-sample-.patch)
-- v4: mf/samplegrabber: Handle samples receieved in paused state. mf/samplegrabber: Process samples in paused state in ignore_clock mode. mf: Handle timer time behind clock time in present_clock_schedule_timer().
From: Paul Gofman pgofman@codeweavers.com
--- dlls/mf/clock.c | 5 ++++- dlls/mf/tests/mf.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/dlls/mf/clock.c b/dlls/mf/clock.c index e9877533a5a..aef4366c6d9 100644 --- a/dlls/mf/clock.c +++ b/dlls/mf/clock.c @@ -819,7 +819,10 @@ static HRESULT present_clock_schedule_timer(struct presentation_clock *clock, DW WARN("Failed to get clock time, hr %#lx.\n", hr); return hr; } - time -= clocktime; + if (time > clocktime) + time -= clocktime; + else + time = 0; }
frequency = clock->frequency / 1000; diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 9dab87b928d..2b116ab0d9e 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -1428,8 +1428,14 @@ static const struct test_stream_sink test_stream_sink = {.IMFStreamSink_iface.lp struct test_callback { IMFAsyncCallback IMFAsyncCallback_iface; + HANDLE event; };
+static struct test_callback *test_callback_from_IMFAsyncCallback(IMFAsyncCallback *iface) +{ + return CONTAINING_RECORD(iface, struct test_callback, IMFAsyncCallback_iface); +} + static HRESULT WINAPI testcallback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj) { if (IsEqualIID(riid, &IID_IMFAsyncCallback) || @@ -1462,8 +1468,12 @@ static HRESULT WINAPI testcallback_GetParameters(IMFAsyncCallback *iface, DWORD
static HRESULT WINAPI testcallback_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) { + struct test_callback *test = test_callback_from_IMFAsyncCallback(iface); ok(result != NULL, "Unexpected result object.\n");
+ if (test->event) + SetEvent(test->event); + return E_NOTIMPL; }
@@ -1479,6 +1489,7 @@ static const IMFAsyncCallbackVtbl testcallbackvtbl = static void init_test_callback(struct test_callback *callback) { callback->IMFAsyncCallback_iface.lpVtbl = &testcallbackvtbl; + callback->event = NULL; }
static void test_session_events(IMFMediaSession *session) @@ -2907,15 +2918,19 @@ static void test_presentation_clock(void) }; IMFClockStateSink test_sink = { &test_clock_sink_vtbl }; IMFPresentationTimeSource *time_source; + struct test_callback timer_callback; MFCLOCK_PROPERTIES props, props2; IMFRateControl *rate_control; IMFPresentationClock *clock; + IUnknown *timer_cancel_key; MFSHUTDOWN_STATUS status; IMFShutdown *shutdown; MFTIME systime, time; LONGLONG clock_time; MFCLOCK_STATE state; + IMFTimer *timer; unsigned int i; + DWORD t1, t2; DWORD value; float rate; HRESULT hr; @@ -3139,6 +3154,33 @@ static void test_presentation_clock(void)
IMFRateControl_Release(rate_control);
+ + hr = IMFPresentationClock_QueryInterface(clock, &IID_IMFTimer, (void **)&timer); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IMFPresentationClock_Start(clock, 200000); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + hr = IMFPresentationClock_GetCorrelatedTime(clock, 0, &time, &systime); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + init_test_callback(&timer_callback); + timer_callback.event = CreateEventW(NULL, FALSE, FALSE, NULL); + hr = IMFTimer_SetTimer(timer, 0, 100000, + &timer_callback.IMFAsyncCallback_iface, NULL, &timer_cancel_key); + ok(hr == S_OK, "got hr %#lx.\n", hr); + + t1 = GetTickCount(); + ok(WaitForSingleObject(timer_callback.event, 4000) == WAIT_OBJECT_0, "WaitForSingleObject failed.\n"); + t2 = GetTickCount(); + + ok(t2 - t1 < 200, "unexpected time difference %lu.\n", t2 - t1); + + CloseHandle(timer_callback.event); + IUnknown_Release(timer_cancel_key); + IMFTimer_Release(timer); + + hr = IMFPresentationClock_QueryInterface(clock, &IID_IMFShutdown, (void **)&shutdown); ok(hr == S_OK, "Failed to get shutdown interface, hr %#lx.\n", hr);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/mf/samplegrabber.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dlls/mf/samplegrabber.c b/dlls/mf/samplegrabber.c index 2d9f8109aca..7b47b517f02 100644 --- a/dlls/mf/samplegrabber.c +++ b/dlls/mf/samplegrabber.c @@ -424,7 +424,7 @@ static HRESULT WINAPI sample_grabber_stream_ProcessSample(IMFStreamSink *iface,
if (grabber->is_shut_down) hr = MF_E_STREAMSINK_REMOVED; - else if (grabber->state == SINK_STATE_RUNNING) + else if (grabber->state == SINK_STATE_RUNNING || (grabber->state == SINK_STATE_PAUSED && grabber->ignore_clock)) { hr = IMFSample_GetSampleTime(sample, &sampletime);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/mf/samplegrabber.c | 50 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-)
diff --git a/dlls/mf/samplegrabber.c b/dlls/mf/samplegrabber.c index 7b47b517f02..8803856a620 100644 --- a/dlls/mf/samplegrabber.c +++ b/dlls/mf/samplegrabber.c @@ -19,6 +19,7 @@ #define COBJMACROS
#include <float.h> +#include <assert.h>
#include "mfidl.h" #include "mf_private.h" @@ -85,6 +86,8 @@ struct sample_grabber UINT64 sample_time_offset; enum sink_state state; CRITICAL_SECTION cs; + UINT32 sample_request_on_start_count; + IMFSample *samples_queued_while_paused[4]; };
static IMFSampleGrabberSinkCallback *sample_grabber_get_callback(const struct sample_grabber *sink) @@ -442,6 +445,14 @@ static HRESULT WINAPI sample_grabber_stream_ProcessSample(IMFStreamSink *iface, hr = stream_queue_sample(grabber, sample); } } + else if (grabber->state == SINK_STATE_PAUSED) + { + if (grabber->sample_request_on_start_count < 4) + { + IMFSample_AddRef(sample); + grabber->samples_queued_while_paused[grabber->sample_request_on_start_count++] = sample; + } + }
LeaveCriticalSection(&grabber->cs);
@@ -826,6 +837,20 @@ static void sample_grabber_release_pending_items(struct sample_grabber *grabber) } }
+static void release_samples_queued_while_paused(struct sample_grabber *grabber) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(grabber->samples_queued_while_paused); ++i) + { + if (grabber->samples_queued_while_paused[i]) + { + IMFSample_Release(grabber->samples_queued_while_paused[i]); + grabber->samples_queued_while_paused[i] = NULL; + } + } +} + static ULONG WINAPI sample_grabber_sink_Release(IMFMediaSink *iface) { struct sample_grabber *grabber = impl_from_IMFMediaSink(iface); @@ -857,6 +882,7 @@ static ULONG WINAPI sample_grabber_sink_Release(IMFMediaSink *iface) if (grabber->sample_attributes) IMFAttributes_Release(grabber->sample_attributes); sample_grabber_release_pending_items(grabber); + release_samples_queued_while_paused(grabber); DeleteCriticalSection(&grabber->cs); free(grabber); } @@ -1128,14 +1154,31 @@ static HRESULT sample_grabber_set_state(struct sample_grabber *grabber, enum sin else { if (state == SINK_STATE_STOPPED) + { sample_grabber_cancel_timer(grabber); + release_samples_queued_while_paused(grabber); + grabber->sample_request_on_start_count = 4; + }
- if (state == SINK_STATE_RUNNING && grabber->state == SINK_STATE_STOPPED) + if (state == SINK_STATE_RUNNING && grabber->state != SINK_STATE_RUNNING) { /* Every transition to running state sends a bunch requests to build up initial queue. */ - for (i = 0; i < 4; ++i) - sample_grabber_stream_request_sample(grabber); + for (i = 0; i < grabber->sample_request_on_start_count; ++i) + { + if (grabber->state == SINK_STATE_PAUSED && offset == PRESENTATION_CURRENT_POSITION) + { + assert(grabber->samples_queued_while_paused[i]); + stream_queue_sample(grabber, grabber->samples_queued_while_paused[i]); + } + else + { + sample_grabber_stream_request_sample(grabber); + } + } + release_samples_queued_while_paused(grabber); + grabber->sample_request_on_start_count = 0; } + do_callback = state != grabber->state || state != SINK_STATE_PAUSED; if (do_callback) IMFStreamSink_QueueEvent(&grabber->IMFStreamSink_iface, events[state], &GUID_NULL, S_OK, NULL); @@ -1419,6 +1462,7 @@ static HRESULT sample_grabber_create_object(IMFAttributes *attributes, void *use object->IMFStreamSink_iface.lpVtbl = &sample_grabber_stream_vtbl; object->IMFMediaTypeHandler_iface.lpVtbl = &sample_grabber_stream_type_handler_vtbl; object->timer_callback.lpVtbl = &sample_grabber_stream_timer_callback_vtbl; + object->sample_request_on_start_count = 4; object->refcount = 1; if (FAILED(IMFSampleGrabberSinkCallback_QueryInterface(context->callback, &IID_IMFSampleGrabberSinkCallback2, (void **)&object->callback2)))
I did more testing and I also see that in MF_SAMPLEGRABBERSINK_IGNORE_CLOCK mode the sample is processed regardless of pause state.
As for async mode, the grabber indeed stores up to 4 samples in paused state (as seen by reference count). But then when resuming it seems not to depend on sample time but rather how the clock is restarted: if restart time is PRESENTATION_CURRENT_POSITION queued samples are processed (either with zero time or with the time in the future); otherwise (even if the resume time is the clock pause time) the samples are dropped and behaviour matches my previous patch version.
I also briefly tested with calling _OnClockRestart / _OnClockStart grabber's sink callback directly to see if it responds to different callback type or to position special value, and it looks like _OnClockStart(...,PRESENTATION_CURRENT_POSITION) is the same as _OnClockRestart. The latter test is not integrated to my standalone test. Yet the updated one (attached) tests the different clock resuming modes and ignore_clock behaviour.
v4 changes: - Add patch forcing sample processing while paused in ignore_clock mode; - Store samples received while paused (if not in ignore clock mode); - Replay stored samples if resumed with PRESENTATION_CURRENT_POSITION.
[0001-mf-tests-Add-test-for-pausing-and-restarting-sample-.patch](/uploads/8e4a90ee04616f449a7d8d028452b302/0001-mf-tests-Add-test-for-pausing-and-restarting-sample-.patch)