It's possible that a state object pointer not in the topology node collection gets passed to session_get_node_object(). Instead of returning the last node when the object is not found, we should return a NULL so that the state of the last node is not changed by mistake.
From: Zhiyi Zhang zzhang@codeweavers.com
It's possible that a state object pointer not in the topology node collection gets passed to session_get_node_object(). Instead of returning the last node when the object is not found, we should return a NULL so that the state of the last node is not changed by mistake. --- dlls/mf/session.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 555a71dcdf7..8fe3260e9f0 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -2809,15 +2809,20 @@ static HRESULT session_start_clock(struct media_session *session) static struct topo_node *session_get_node_object(struct media_session *session, IUnknown *object, MF_TOPOLOGY_TYPE node_type) { - struct topo_node *node = NULL; + struct topo_node *node = NULL, *found = NULL;
LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) { if (node->type == node_type && object == node->object.object) + { + found = node; break; + } }
- return node; + if (!found) + WARN("Failed to find object %p.\n", object); + return found; }
static BOOL session_set_node_object_state(struct media_session *session, IUnknown *object,
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/mf/session.c | 57 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 8fe3260e9f0..7a628801310 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -110,6 +110,7 @@ enum object_state OBJ_STATE_STARTED, OBJ_STATE_PAUSED, OBJ_STATE_PREROLLED, + OBJ_STATE_SEEKING, OBJ_STATE_INVALID, };
@@ -889,9 +890,27 @@ static HRESULT session_subscribe_sources(struct media_session *session) return hr; }
+static void session_set_source_output_nodes_seeking(struct media_session *session) +{ + struct media_source *source; + struct topo_node *node; + + LIST_FOR_EACH_ENTRY(source, &session->presentation.sources, struct media_source, entry) + { + source->state = OBJ_STATE_SEEKING; + } + + LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) + { + if (node->type == MF_TOPOLOGY_SOURCESTREAM_NODE || node->type == MF_TOPOLOGY_OUTPUT_NODE) + node->state = OBJ_STATE_SEEKING; + } +} + static void session_start(struct media_session *session, const GUID *time_format, const PROPVARIANT *start_position) { struct media_source *source; + MFTIME duration; HRESULT hr;
switch (session->state) @@ -907,6 +926,13 @@ static void session_start(struct media_session *session, const GUID *time_format
/* fallthrough */ case SESSION_STATE_PAUSED: + case SESSION_STATE_STARTED: + if (session->state == SESSION_STATE_STARTED && !(session->caps & MFSESSIONCAP_SEEK)) + { + WARN("Seeking is not supported for this session.\n"); + session_command_complete(session); + return; + }
session->presentation.time_format = *time_format; session->presentation.start_position.vt = VT_EMPTY; @@ -920,6 +946,14 @@ static void session_start(struct media_session *session, const GUID *time_format
LIST_FOR_EACH_ENTRY(source, &session->presentation.sources, struct media_source, entry) { + hr = IMFPresentationDescriptor_GetUINT64(source->pd, &MF_PD_DURATION, (UINT64 *)&duration); + if (SUCCEEDED(hr) && start_position->vt == VT_I8 && start_position->hVal.QuadPart > duration) + { + WARN("Start position %s out of range, hr %#lx.\n", wine_dbgstr_longlong(start_position->hVal.QuadPart), hr); + session_command_complete_with_event(session, MESessionStarted, MF_E_INVALID_POSITION, NULL); + return; + } + if (FAILED(hr = IMFMediaSource_Start(source->source, source->pd, &GUID_NULL, start_position))) { WARN("Failed to start media source %p, hr %#lx.\n", source->source, hr); @@ -928,12 +962,22 @@ static void session_start(struct media_session *session, const GUID *time_format } }
+ if (session->state == SESSION_STATE_STARTED) + { + struct topo_node *node; + + LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) + { + if (node->type == MF_TOPOLOGY_OUTPUT_NODE) + IMFStreamSink_Flush(node->object.sink_stream); + else if (node->type == MF_TOPOLOGY_TRANSFORM_NODE) + IMFTransform_ProcessMessage(node->object.transform, MFT_MESSAGE_COMMAND_FLUSH, 0); + } + + session_set_source_output_nodes_seeking(session); + } session->state = SESSION_STATE_STARTING_SOURCES; break; - case SESSION_STATE_STARTED: - FIXME("Seeking is not implemented.\n"); - session_command_complete(session); - break; default: session_command_complete_with_event(session, MESessionStarted, MF_E_INVALIDREQUEST, NULL); break; @@ -2167,6 +2211,9 @@ static HRESULT WINAPI mfsession_Start(IMFMediaSession *iface, const GUID *format if (!start_position) return E_POINTER;
+ if (FAILED(hr = session_is_shut_down(session))) + return hr; + if (FAILED(hr = create_session_op(SESSION_CMD_START, &op))) return hr;
@@ -3659,8 +3706,6 @@ static HRESULT WINAPI session_events_callback_Invoke(IMFAsyncCallback *iface, IM { case MESourceSeeked: case MEStreamSeeked: - FIXME("Source/stream seeking, semi-stub!\n"); - /* fallthrough */ case MESourceStarted: case MESourcePaused: case MESourceStopped:
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/mf/tests/mf.c | 92 ++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 44 deletions(-)
diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 743946f7fba..bf9c077ef19 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -4922,6 +4922,53 @@ static void test_sample_grabber_is_mediatype_supported(void) IMFSampleGrabberSinkCallback_Release(grabber_callback); }
+/* create a media session with the specified source and sink */ +static void create_media_session_with_source_sink(IMFMediaSource *source, IMFActivate *sink_activate, + IMFMediaSession **session) +{ + IMFTopologyNode *src_node, *sink_node; + IMFPresentationDescriptor *pd; + IMFStreamDescriptor *sd; + IMFTopology *topology; + BOOL selected; + HRESULT hr; + + hr = MFCreateMediaSession(NULL, session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = MFCreateTopology(&topology); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &sink_node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &src_node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFTopology_AddNode(topology, sink_node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFTopology_AddNode(topology, src_node); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFTopologyNode_ConnectOutput(src_node, 0, sink_node, 0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaSource_CreatePresentationDescriptor(source, &pd); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, 0, &selected, &sd); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(selected, "got selected %u.\n", !!selected); + init_source_node(source, -1, src_node, pd, sd); + hr = IMFTopologyNode_SetObject(sink_node, (IUnknown *)sink_activate); + ok(hr == S_OK, "Failed to set object, hr %#lx.\n", hr); + hr = IMFTopologyNode_SetUINT32(sink_node, &MF_TOPONODE_CONNECT_METHOD, MF_CONNECT_ALLOW_DECODER); + ok(hr == S_OK, "Failed to set connect method, hr %#lx.\n", hr); + hr = IMFTopology_SetUINT32(topology, &MF_TOPOLOGY_ENUMERATE_SOURCE_TYPES, TRUE); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaSession_SetTopology(*session, 0, topology); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMFStreamDescriptor_Release(sd); + IMFPresentationDescriptor_Release(pd); + IMFTopologyNode_Release(src_node); + IMFTopologyNode_Release(sink_node); + IMFTopology_Release(topology); +} + static void test_sample_grabber_orientation(GUID subtype) { media_type_desc video_rgb32_desc = @@ -4931,17 +4978,12 @@ static void test_sample_grabber_orientation(GUID subtype) };
struct test_grabber_callback *grabber_callback; - IMFTopologyNode *src_node, *sink_node; - IMFPresentationDescriptor *pd; IMFAsyncCallback *callback; IMFActivate *sink_activate; IMFMediaType *output_type; IMFMediaSession *session; - IMFStreamDescriptor *sd; IMFMediaSource *source; - IMFTopology *topology; PROPVARIANT propvar; - BOOL selected; HRESULT hr; DWORD res;
@@ -4962,33 +5004,6 @@ static void test_sample_grabber_orientation(GUID subtype) grabber_callback->done_event = CreateEventW(NULL, FALSE, FALSE, NULL); ok(!!grabber_callback->done_event, "CreateEventW failed, error %lu\n", GetLastError());
- hr = MFCreateMediaSession(NULL, &session); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - - hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &sink_node); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &src_node); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - - hr = MFCreateTopology(&topology); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - hr = IMFTopology_AddNode(topology, sink_node); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - hr = IMFTopology_AddNode(topology, src_node); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - hr = IMFTopologyNode_ConnectOutput(src_node, 0, sink_node, 0); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - - hr = IMFMediaSource_CreatePresentationDescriptor(source, &pd); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, 0, &selected, &sd); - ok(selected, "got selected %u.\n", !!selected); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - init_source_node(source, -1, src_node, pd, sd); - IMFTopologyNode_Release(src_node); - IMFPresentationDescriptor_Release(pd); - IMFStreamDescriptor_Release(sd); - hr = MFCreateMediaType(&output_type); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); init_media_type(output_type, video_rgb32_desc, -1); @@ -4996,18 +5011,7 @@ static void test_sample_grabber_orientation(GUID subtype) ok(hr == S_OK, "Failed to create grabber sink, hr %#lx.\n", hr); IMFMediaType_Release(output_type);
- hr = IMFTopologyNode_SetObject(sink_node, (IUnknown *)sink_activate); - ok(hr == S_OK, "Failed to set object, hr %#lx.\n", hr); - hr = IMFTopologyNode_SetUINT32(sink_node, &MF_TOPONODE_CONNECT_METHOD, MF_CONNECT_ALLOW_DECODER); - ok(hr == S_OK, "Failed to set connect method, hr %#lx.\n", hr); - IMFTopologyNode_Release(sink_node); - - hr = IMFTopology_SetUINT32(topology, &MF_TOPOLOGY_ENUMERATE_SOURCE_TYPES, TRUE); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - - hr = IMFMediaSession_SetTopology(session, 0, topology); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - IMFTopology_Release(topology); + create_media_session_with_source_sink(source, sink_activate, &session);
propvar.vt = VT_EMPTY; hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar);
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/mf/tests/mf.c | 710 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 702 insertions(+), 8 deletions(-)
diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index bf9c077ef19..884100b6718 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -2085,6 +2085,448 @@ static IMFMediaSource *create_media_source(const WCHAR *name, const WCHAR *mime) return source; }
+enum source_state +{ + SOURCE_STOPPED = 0, + SOURCE_RUNNING, +}; + +struct test_media_stream +{ + IMFMediaStream IMFMediaStream_iface; + IMFMediaEventQueue *event_queue; + IMFStreamDescriptor *sd; + IMFMediaSource *source; + LONGLONG sample_duration; + LONGLONG sample_time; + BOOL is_new; + LONG refcount; +}; + +static struct test_media_stream *impl_from_IMFMediaStream(IMFMediaStream *iface) +{ + return CONTAINING_RECORD(iface, struct test_media_stream, IMFMediaStream_iface); +} + +static HRESULT WINAPI test_media_stream_QueryInterface(IMFMediaStream *iface, REFIID riid, void **out) +{ + if (IsEqualIID(riid, &IID_IMFMediaStream) + || IsEqualIID(riid, &IID_IMFMediaEventGenerator) + || IsEqualIID(riid, &IID_IUnknown)) + { + *out = iface; + } + else + { + *out = NULL; + return E_NOINTERFACE; + } + + IMFMediaStream_AddRef(iface); + return S_OK; +} + +static ULONG WINAPI test_media_stream_AddRef(IMFMediaStream *iface) +{ + struct test_media_stream *stream = impl_from_IMFMediaStream(iface); + return InterlockedIncrement(&stream->refcount); +} + +static ULONG WINAPI test_media_stream_Release(IMFMediaStream *iface) +{ + struct test_media_stream *stream = impl_from_IMFMediaStream(iface); + ULONG refcount = InterlockedDecrement(&stream->refcount); + + if (!refcount) + { + IMFMediaEventQueue_Release(stream->event_queue); + free(stream); + } + + return refcount; +} + +static HRESULT WINAPI test_media_stream_GetEvent(IMFMediaStream *iface, DWORD flags, IMFMediaEvent **event) +{ + struct test_media_stream *stream = impl_from_IMFMediaStream(iface); + return IMFMediaEventQueue_GetEvent(stream->event_queue, flags, event); +} + +static HRESULT WINAPI test_media_stream_BeginGetEvent(IMFMediaStream *iface, IMFAsyncCallback *callback, IUnknown *state) +{ + struct test_media_stream *stream = impl_from_IMFMediaStream(iface); + return IMFMediaEventQueue_BeginGetEvent(stream->event_queue, callback, state); +} + +static HRESULT WINAPI test_media_stream_EndGetEvent(IMFMediaStream *iface, IMFAsyncResult *result, IMFMediaEvent **event) +{ + struct test_media_stream *stream = impl_from_IMFMediaStream(iface); + return IMFMediaEventQueue_EndGetEvent(stream->event_queue, result, event); +} + +static HRESULT WINAPI test_media_stream_QueueEvent(IMFMediaStream *iface, MediaEventType event_type, REFGUID ext_type, + HRESULT hr, const PROPVARIANT *value) +{ + struct test_media_stream *stream = impl_from_IMFMediaStream(iface); + return IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, event_type, ext_type, hr, value); +} + +static HRESULT WINAPI test_media_stream_GetMediaSource(IMFMediaStream *iface, IMFMediaSource **source) +{ + struct test_media_stream *stream = impl_from_IMFMediaStream(iface); + + *source = stream->source; + IMFMediaSource_AddRef(*source); + + return S_OK; +} + +static HRESULT WINAPI test_media_stream_GetStreamDescriptor(IMFMediaStream *iface, IMFStreamDescriptor **sd) +{ + struct test_media_stream *stream = impl_from_IMFMediaStream(iface); + + *sd = stream->sd; + IMFStreamDescriptor_AddRef(*sd); + + return S_OK; +} + +static HRESULT WINAPI test_media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token) +{ + struct test_media_stream *stream = impl_from_IMFMediaStream(iface); + IMFMediaBuffer *buffer; + IMFSample *sample; + HRESULT hr; + + hr = MFCreateSample(&sample); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (stream->sample_duration) + { + hr = IMFSample_SetSampleDuration(sample, stream->sample_duration); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFSample_SetSampleTime(sample, stream->sample_time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + stream->sample_time += stream->sample_duration; + } + else + { + hr = IMFSample_SetSampleTime(sample, 123); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFSample_SetSampleDuration(sample, 1); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + } + + if (token) + IMFSample_SetUnknown(sample, &MFSampleExtension_Token, token); + + /* Reader expects buffers, empty samples are considered an error. */ + hr = MFCreateMemoryBuffer(8, &buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSample_AddBuffer(sample, buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaBuffer_Release(buffer); + + hr = IMFMediaEventQueue_QueueEventParamUnk(stream->event_queue, MEMediaSample, &GUID_NULL, S_OK, + (IUnknown *)sample); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFSample_Release(sample); + + return S_OK; +} + +static const IMFMediaStreamVtbl test_media_stream_vtbl = +{ + test_media_stream_QueryInterface, + test_media_stream_AddRef, + test_media_stream_Release, + test_media_stream_GetEvent, + test_media_stream_BeginGetEvent, + test_media_stream_EndGetEvent, + test_media_stream_QueueEvent, + test_media_stream_GetMediaSource, + test_media_stream_GetStreamDescriptor, + test_media_stream_RequestSample, +}; + +#define TEST_SOURCE_NUM_STREAMS 3 + +struct unseekable_source +{ + IMFMediaSource IMFMediaSource_iface; + IMFMediaEventQueue *event_queue; + IMFPresentationDescriptor *pd; + struct test_media_stream *streams[TEST_SOURCE_NUM_STREAMS]; + enum source_state state; + unsigned stream_count; + CRITICAL_SECTION cs; + LONG refcount; +}; + +static struct unseekable_source *impl_unseekable_source_from_IMFMediaSource(IMFMediaSource *iface) +{ + return CONTAINING_RECORD(iface, struct unseekable_source, IMFMediaSource_iface); +} + +static HRESULT WINAPI unseekable_source_QueryInterface(IMFMediaSource *iface, REFIID riid, void **out) +{ + if (IsEqualIID(riid, &IID_IMFMediaSource) + || IsEqualIID(riid, &IID_IMFMediaEventGenerator) + || IsEqualIID(riid, &IID_IUnknown)) + { + *out = iface; + } + else + { + *out = NULL; + return E_NOINTERFACE; + } + + IMFMediaSource_AddRef(iface); + return S_OK; +} + +static ULONG WINAPI unseekable_source_AddRef(IMFMediaSource *iface) +{ + struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); + return InterlockedIncrement(&source->refcount); +} + +static ULONG WINAPI unseekable_source_Release(IMFMediaSource *iface) +{ + struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); + ULONG refcount = InterlockedDecrement(&source->refcount); + + if (!refcount) + { + IMFMediaEventQueue_Release(source->event_queue); + free(source); + } + + return refcount; +} + +static HRESULT WINAPI unseekable_source_GetEvent(IMFMediaSource *iface, DWORD flags, IMFMediaEvent **event) +{ + struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); + return IMFMediaEventQueue_GetEvent(source->event_queue, flags, event); +} + +static HRESULT WINAPI unseekable_source_BeginGetEvent(IMFMediaSource *iface, IMFAsyncCallback *callback, IUnknown *state) +{ + struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); + return IMFMediaEventQueue_BeginGetEvent(source->event_queue, callback, state); +} + +static HRESULT WINAPI unseekable_source_EndGetEvent(IMFMediaSource *iface, IMFAsyncResult *result, IMFMediaEvent **event) +{ + struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); + return IMFMediaEventQueue_EndGetEvent(source->event_queue, result, event); +} + +static HRESULT WINAPI unseekable_source_QueueEvent(IMFMediaSource *iface, MediaEventType event_type, REFGUID ext_type, + HRESULT hr, const PROPVARIANT *value) +{ + struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); + return IMFMediaEventQueue_QueueEventParamVar(source->event_queue, event_type, ext_type, hr, value); +} + +static HRESULT WINAPI unseekable_source_GetCharacteristics(IMFMediaSource *iface, DWORD *flags) +{ + *flags = 0; + return S_OK; +} + +static HRESULT WINAPI unseekable_source_CreatePresentationDescriptor(IMFMediaSource *iface, IMFPresentationDescriptor **pd) +{ + struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); + IMFStreamDescriptor *sds[ARRAY_SIZE(source->streams)]; + IMFMediaType *media_type; + HRESULT hr = S_OK; + int i; + + EnterCriticalSection(&source->cs); + + if (source->pd) + { + *pd = source->pd; + IMFPresentationDescriptor_AddRef(*pd); + } + else + { + for (i = 0; i < source->stream_count; ++i) + { + hr = MFCreateMediaType(&media_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &MFVideoFormat_RGB32); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_SIZE, (UINT64)640 << 32 | 480); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = MFCreateStreamDescriptor(i, 1, &media_type, &sds[i]); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMFMediaType_Release(media_type); + } + + hr = MFCreatePresentationDescriptor(source->stream_count, sds, &source->pd); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFPresentationDescriptor_SetUINT64(source->pd, &MF_PD_DURATION, 10*10000000); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFPresentationDescriptor_SelectStream(source->pd, 0); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + for (i = 0; i < source->stream_count; ++i) + IMFStreamDescriptor_Release(sds[i]); + + *pd = source->pd; + IMFPresentationDescriptor_AddRef(*pd); + } + + LeaveCriticalSection(&source->cs); + + return hr; +} + +static BOOL is_stream_selected(IMFPresentationDescriptor *pd, DWORD index) +{ + IMFStreamDescriptor *sd; + BOOL selected = FALSE; + + if (SUCCEEDED(IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, index, &selected, &sd))) + IMFStreamDescriptor_Release(sd); + + return selected; +} + +static HRESULT WINAPI unseekable_source_Start(IMFMediaSource *iface, IMFPresentationDescriptor *pd, const GUID *time_format, + const PROPVARIANT *start_position) +{ + struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); + MediaEventType event_type; + PROPVARIANT var; + HRESULT hr; + int i; + + ok(time_format && IsEqualGUID(time_format, &GUID_NULL), "Unexpected time format %s.\n", + wine_dbgstr_guid(time_format)); + ok(start_position && (start_position->vt == VT_I8 || start_position->vt == VT_EMPTY), + "Unexpected position type.\n"); + + EnterCriticalSection(&source->cs); + + event_type = source->state == SOURCE_RUNNING ? MESourceSeeked : MESourceStarted; + hr = IMFMediaEventQueue_QueueEventParamVar(source->event_queue, event_type, &GUID_NULL, S_OK, NULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + for (i = 0; i < source->stream_count; ++i) + { + if (!is_stream_selected(pd, i)) + continue; + + var.vt = VT_UNKNOWN; + var.punkVal = (IUnknown *)&source->streams[i]->IMFMediaStream_iface; + event_type = source->streams[i]->is_new ? MENewStream : MEUpdatedStream; + source->streams[i]->is_new = FALSE; + hr = IMFMediaEventQueue_QueueEventParamVar(source->event_queue, event_type, &GUID_NULL, S_OK, &var); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + event_type = source->state == SOURCE_RUNNING ? MEStreamSeeked : MEStreamStarted; + hr = IMFMediaEventQueue_QueueEventParamVar(source->streams[i]->event_queue, event_type, &GUID_NULL, + S_OK, NULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + } + + source->state = SOURCE_RUNNING; + + LeaveCriticalSection(&source->cs); + + return S_OK; +} + +static HRESULT WINAPI unseekable_source_Stop(IMFMediaSource *iface) +{ + return S_OK; +} + +static HRESULT WINAPI unseekable_source_Pause(IMFMediaSource *iface) +{ + ok(0, "Unexpected call.\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI unseekable_source_Shutdown(IMFMediaSource *iface) +{ + struct unseekable_source *source = impl_unseekable_source_from_IMFMediaSource(iface); + HRESULT hr; + + hr = IMFMediaEventQueue_Shutdown(source->event_queue); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + return S_OK; +} + +static const IMFMediaSourceVtbl unseekable_source_vtbl = +{ + unseekable_source_QueryInterface, + unseekable_source_AddRef, + unseekable_source_Release, + unseekable_source_GetEvent, + unseekable_source_BeginGetEvent, + unseekable_source_EndGetEvent, + unseekable_source_QueueEvent, + unseekable_source_GetCharacteristics, + unseekable_source_CreatePresentationDescriptor, + unseekable_source_Start, + unseekable_source_Stop, + unseekable_source_Pause, + unseekable_source_Shutdown, +}; + +static struct test_media_stream *create_test_stream(DWORD stream_index, IMFMediaSource *source) +{ + struct test_media_stream *stream; + IMFPresentationDescriptor *pd; + BOOL selected; + HRESULT hr; + + stream = calloc(1, sizeof(*stream)); + stream->IMFMediaStream_iface.lpVtbl = &test_media_stream_vtbl; + stream->refcount = 1; + hr = MFCreateEventQueue(&stream->event_queue); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + stream->source = source; + IMFMediaSource_AddRef(stream->source); + stream->is_new = TRUE; + + IMFMediaSource_CreatePresentationDescriptor(source, &pd); + IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, stream_index, &selected, &stream->sd); + IMFPresentationDescriptor_Release(pd); + + return stream; +} + +static IMFMediaSource *create_unseekable_source(void) +{ + struct unseekable_source *source; + int i; + + source = calloc(1, sizeof(*source)); + source->IMFMediaSource_iface.lpVtbl = &unseekable_source_vtbl; + source->refcount = 1; + source->stream_count = 1; + MFCreateEventQueue(&source->event_queue); + InitializeCriticalSection(&source->cs); + for (i = 0; i < source->stream_count; ++i) + source->streams[i] = create_test_stream(i, &source->IMFMediaSource_iface); + + return &source->IMFMediaSource_iface; +} + static void test_media_session_events(void) { static const media_type_desc audio_float_44100 = @@ -2886,27 +3328,27 @@ static ULONG WINAPI test_grabber_callback_Release(IMFSampleGrabberSinkCallback *
static HRESULT WINAPI test_grabber_callback_OnClockStart(IMFSampleGrabberSinkCallback *iface, MFTIME time, LONGLONG offset) { - return E_NOTIMPL; + return S_OK; }
static HRESULT WINAPI test_grabber_callback_OnClockStop(IMFSampleGrabberSinkCallback *iface, MFTIME time) { - return E_NOTIMPL; + return S_OK; }
static HRESULT WINAPI test_grabber_callback_OnClockPause(IMFSampleGrabberSinkCallback *iface, MFTIME time) { - return E_NOTIMPL; + return S_OK; }
static HRESULT WINAPI test_grabber_callback_OnClockRestart(IMFSampleGrabberSinkCallback *iface, MFTIME time) { - return E_NOTIMPL; + return S_OK; }
static HRESULT WINAPI test_grabber_callback_OnClockSetRate(IMFSampleGrabberSinkCallback *iface, MFTIME time, float rate) { - return E_NOTIMPL; + return S_OK; }
static HRESULT WINAPI test_grabber_callback_OnSetPresentationClock(IMFSampleGrabberSinkCallback *iface, @@ -4922,9 +5364,9 @@ static void test_sample_grabber_is_mediatype_supported(void) IMFSampleGrabberSinkCallback_Release(grabber_callback); }
-/* create a media session with the specified source and sink */ +/* create a media session with the specified source and sink, and return duration if required */ static void create_media_session_with_source_sink(IMFMediaSource *source, IMFActivate *sink_activate, - IMFMediaSession **session) + IMFMediaSession **session, UINT64 *duration) { IMFTopologyNode *src_node, *sink_node; IMFPresentationDescriptor *pd; @@ -4952,6 +5394,11 @@ static void create_media_session_with_source_sink(IMFMediaSource *source, IMFAct hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(pd, 0, &selected, &sd); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); ok(selected, "got selected %u.\n", !!selected); + if (duration) + { + hr = IMFPresentationDescriptor_GetUINT64(pd, &MF_PD_DURATION, duration); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + } init_source_node(source, -1, src_node, pd, sd); hr = IMFTopologyNode_SetObject(sink_node, (IUnknown *)sink_activate); ok(hr == S_OK, "Failed to set object, hr %#lx.\n", hr); @@ -5011,7 +5458,7 @@ static void test_sample_grabber_orientation(GUID subtype) ok(hr == S_OK, "Failed to create grabber sink, hr %#lx.\n", hr); IMFMediaType_Release(output_type);
- create_media_session_with_source_sink(source, sink_activate, &session); + create_media_session_with_source_sink(source, sink_activate, &session, NULL);
propvar.vt = VT_EMPTY; hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); @@ -7055,6 +7502,252 @@ static void test_mpeg4_media_sink(void) IMFMediaType_Release(audio_type); }
+static void test_media_session_Start(void) +{ + media_type_desc video_rgb32_desc = + { + ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), + ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32), + }; + static const MFTIME allowed_error = 500000; + struct test_grabber_callback *grabber_callback; + IMFPresentationClock *presentation_clock; + IMFActivate *sink_activate; + IMFAsyncCallback *callback; + IMFMediaType *output_type; + IMFMediaSession *session; + IMFMediaSource *source; + MFTIME time, old_time; + PROPVARIANT propvar; + IMFClock *clock; + UINT64 duration; + ULONG refcount; + DWORD caps; + HRESULT hr; + + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr); + + if (!(source = create_media_source(L"test.mp4", L"video/mp4"))) + { + win_skip("MP4 media source is not supported, skipping tests.\n"); + MFShutdown(); + return; + } + + grabber_callback = impl_from_IMFSampleGrabberSinkCallback(create_test_grabber_callback()); + hr = MFCreateMediaType(&output_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(output_type, video_rgb32_desc, -1); + hr = MFCreateSampleGrabberSinkActivate(output_type, &grabber_callback->IMFSampleGrabberSinkCallback_iface, &sink_activate); + ok(hr == S_OK, "Failed to create grabber sink, hr %#lx.\n", hr); + IMFMediaType_Release(output_type); + create_media_session_with_source_sink(source, sink_activate, &session, &duration); + + hr = IMFMediaSession_GetClock(session, &clock); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFClock_QueryInterface(clock, &IID_IMFPresentationClock, (void **)&presentation_clock); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFClock_Release(clock); + + propvar.vt = VT_EMPTY; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + callback = create_test_callback(TRUE); + hr = wait_media_event(session, callback, MESessionStarted, 5000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Seek to 1s */ + propvar.vt = VT_I8; + propvar.hVal.QuadPart = 10000000; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFPresentationClock_GetTime(presentation_clock, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(llabs(time - 10000000) <= allowed_error, "Unexpected time %I64d.\n", time); + + /* Seek to beyond duration */ + propvar.vt = VT_I8; + propvar.hVal.QuadPart = duration + 10000000; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); + ok(hr == MF_E_INVALID_POSITION, "Unexpected hr %#lx.\n", hr); + hr = IMFPresentationClock_GetTime(presentation_clock, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(llabs(time - 10000000) <= allowed_error, "Unexpected time %I64d.\n", time); + + /* Seek to negative position */ + propvar.vt = VT_I8; + propvar.hVal.QuadPart = -10000000; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFPresentationClock_GetTime(presentation_clock, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(llabs(time - (-10000000)) <= allowed_error, "Unexpected time %I64d.\n", time); + + /* Seek backwards to 0s */ + propvar.vt = VT_I8; + propvar.hVal.QuadPart = 0; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFPresentationClock_GetTime(presentation_clock, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(llabs(time) <= allowed_error, "Unexpected time %I64d.\n", time); + + /* Seek to 1s while in paused state */ + hr = IMFMediaSession_Pause(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(session, callback, MESessionPaused, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + propvar.vt = VT_I8; + propvar.hVal.QuadPart = 10000000; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFPresentationClock_GetTime(presentation_clock, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(llabs(time - 10000000) <= allowed_error, "Unexpected time %I64d.\n", time); + old_time = time; + + /* Expected the presentation clock is running */ + Sleep(100); + hr = IMFPresentationClock_GetTime(presentation_clock, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(time > old_time, "Unexpected time %I64d.\n", time); + + hr = IMFMediaSession_Stop(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaSession_Close(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Media session is shut down */ + hr = IMFMediaSource_Shutdown(source); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaSession_Shutdown(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + propvar.vt = VT_I8; + propvar.hVal.QuadPart = 10000000; + hr = IMFMediaSession_Start(session, &GUID_NULL, NULL); + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + + propvar.vt = VT_I8; + propvar.hVal.QuadPart = 10000000; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr); + + propvar.vt = VT_EMPTY; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr); + + IMFPresentationClock_Release(presentation_clock); + IMFMediaSource_Release(source); + IMFAsyncCallback_Release(callback); + /* sometimes briefly leaking */ + IMFMediaSession_Release(session); + IMFActivate_ShutdownObject(sink_activate); + IMFActivate_Release(sink_activate); + IMFSampleGrabberSinkCallback_Release(&grabber_callback->IMFSampleGrabberSinkCallback_iface); + + /* Unseekable media source */ + source = create_unseekable_source(); + hr = IMFMediaSource_GetCharacteristics(source, &caps); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok((caps & MFMEDIASOURCE_CAN_SEEK) == 0, "Got unexpected caps %#lx.\n", caps); + grabber_callback = impl_from_IMFSampleGrabberSinkCallback(create_test_grabber_callback()); + hr = MFCreateMediaType(&output_type); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + init_media_type(output_type, video_rgb32_desc, -1); + hr = MFCreateSampleGrabberSinkActivate(output_type, &grabber_callback->IMFSampleGrabberSinkCallback_iface, &sink_activate); + ok(hr == S_OK, "Failed to create grabber sink, hr %#lx.\n", hr); + IMFMediaType_Release(output_type); + create_media_session_with_source_sink(source, sink_activate, &session, &duration); + + hr = IMFMediaSession_GetClock(session, &clock); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFClock_QueryInterface(clock, &IID_IMFPresentationClock, (void **)&presentation_clock); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFClock_Release(clock); + + propvar.vt = VT_EMPTY; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + callback = create_test_callback(TRUE); + hr = wait_media_event(session, callback, MESessionStarted, 1000, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaSession_GetSessionCapabilities(session, &caps); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok((caps & MFSESSIONCAP_SEEK) == 0, "Got unexpected caps %#lx\n", caps); + + /* Seek to 1s */ + propvar.vt = VT_I8; + propvar.hVal.QuadPart = 10000000; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + /* Waiting for MESessionStarted will timeout, skip checking MESessionStarted */ + hr = IMFPresentationClock_GetTime(presentation_clock, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(llabs(time) <= allowed_error, "Unexpected time %I64d.\n", time); + + /* Seek to 0s */ + propvar.vt = VT_EMPTY; + propvar.hVal.QuadPart = 0; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + /* Waiting for MESessionStarted will timeout, skip checking MESessionStarted */ + hr = IMFPresentationClock_GetTime(presentation_clock, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(llabs(time) <= allowed_error, "Unexpected time %I64d.\n", time); + + /* Seek backwards to 0s */ + Sleep(200); + hr = IMFPresentationClock_GetTime(presentation_clock, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(llabs(time - 2000000) <= allowed_error, "Unexpected time %I64d.\n", time); + + propvar.vt = VT_I8; + propvar.hVal.QuadPart = 0; + hr = IMFMediaSession_Start(session, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + /* Waiting for MESessionStarted will timeout, skip checking MESessionStarted */ + hr = IMFPresentationClock_GetTime(presentation_clock, &time); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(llabs(time - 2000000) <= allowed_error, "Unexpected time %I64d.\n", time); + + hr = IMFMediaSession_Stop(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaSession_Close(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaSession_Shutdown(session); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaSource_Shutdown(source); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMFPresentationClock_Release(presentation_clock); + IMFAsyncCallback_Release(callback); + refcount = IMFMediaSession_Release(session); + todo_wine + ok(!refcount || broken(refcount == 4) /* leak on Win7 sometimes */, "Got unexpected refcount %lu.\n", refcount); + IMFMediaSource_Release(source); + IMFActivate_ShutdownObject(sink_activate); + IMFActivate_Release(sink_activate); + IMFSampleGrabberSinkCallback_Release(&grabber_callback->IMFSampleGrabberSinkCallback_iface); + + hr = MFShutdown(); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); +} + START_TEST(mf) { init_functions(); @@ -7089,4 +7782,5 @@ START_TEST(mf) test_MFGetTopoNodeCurrentType(); test_MFRequireProtectedEnvironment(); test_mpeg4_media_sink(); + test_media_session_Start(); }
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/mfmediaengine/main.c | 62 +++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 15 deletions(-)
diff --git a/dlls/mfmediaengine/main.c b/dlls/mfmediaengine/main.c index 1395eb9bffc..4f378d52afd 100644 --- a/dlls/mfmediaengine/main.c +++ b/dlls/mfmediaengine/main.c @@ -94,6 +94,7 @@ enum media_engine_flags FLAGS_ENGINE_NEW_FRAME = 0x8000, FLAGS_ENGINE_SOURCE_PENDING = 0x10000, FLAGS_ENGINE_PLAY_PENDING = 0x20000, + FLAGS_ENGINE_SEEKING = 0x40000, };
struct vec3 @@ -977,7 +978,14 @@ static HRESULT WINAPI media_engine_session_events_Invoke(IMFAsyncCallback *iface break; } case MESessionStarted: - + EnterCriticalSection(&engine->cs); + if (engine->flags & FLAGS_ENGINE_SEEKING) + { + media_engine_set_flag(engine, FLAGS_ENGINE_SEEKING | FLAGS_ENGINE_IS_ENDED, FALSE); + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_SEEKED, 0, 0); + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_TIMEUPDATE, 0, 0); + } + LeaveCriticalSection(&engine->cs); IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_PLAYING, 0, 0); break; case MESessionEnded: @@ -1761,19 +1769,9 @@ static double WINAPI media_engine_GetCurrentTime(IMFMediaEngineEx *iface)
static HRESULT WINAPI media_engine_SetCurrentTime(IMFMediaEngineEx *iface, double time) { - struct media_engine *engine = impl_from_IMFMediaEngineEx(iface); - HRESULT hr = E_NOTIMPL; + TRACE("%p, %f.\n", iface, time);
- FIXME("(%p, %f): stub.\n", iface, time); - - EnterCriticalSection(&engine->cs); - - if (engine->flags & FLAGS_ENGINE_SHUT_DOWN) - hr = MF_E_SHUTDOWN; - - LeaveCriticalSection(&engine->cs); - - return hr; + return IMFMediaEngineEx_SetCurrentTimeEx(iface, time, MF_MEDIA_ENGINE_SEEK_MODE_NORMAL); }
static double WINAPI media_engine_GetStartTime(IMFMediaEngineEx *iface) @@ -2947,9 +2945,43 @@ static HRESULT WINAPI media_engine_SetRealTimeMode(IMFMediaEngineEx *iface, BOOL
static HRESULT WINAPI media_engine_SetCurrentTimeEx(IMFMediaEngineEx *iface, double seektime, MF_MEDIA_ENGINE_SEEK_MODE mode) { - FIXME("%p, %f, %#x stub.\n", iface, seektime, mode); + struct media_engine *engine = impl_from_IMFMediaEngineEx(iface); + PROPVARIANT position; + BOOL paused; + DWORD caps; + HRESULT hr;
- return E_NOTIMPL; + TRACE("%p, %f, %#x.\n", iface, seektime, mode); + + if (mode) + FIXME("mode %#x is ignored.\n", mode); + + EnterCriticalSection(&engine->cs); + + if (engine->flags & FLAGS_ENGINE_SHUT_DOWN) + hr = MF_E_SHUTDOWN; + else + { + hr = IMFMediaSession_GetSessionCapabilities(engine->session, &caps); + if (SUCCEEDED(hr) && caps & MFSESSIONCAP_SEEK) + { + paused = IMFMediaEngineEx_IsPaused(iface); + + position.vt = VT_I8; + position.hVal.QuadPart = min(max(0, seektime), engine->duration) * 10000000; + hr = IMFMediaSession_Start(engine->session, &GUID_NULL, &position); + if (SUCCEEDED(hr)) + { + media_engine_set_flag(engine, FLAGS_ENGINE_SEEKING, TRUE); + IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_SEEKING, 0, 0); + if (paused) + hr = IMFMediaSession_Pause(engine->session); + } + } + } + + LeaveCriticalSection(&engine->cs); + return hr; }
static HRESULT WINAPI media_engine_EnableTimeUpdateTimer(IMFMediaEngineEx *iface, BOOL enable)
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/mfmediaengine/tests/mfmediaengine.c | 188 +++++++++++++++++++++++ 1 file changed, 188 insertions(+)
diff --git a/dlls/mfmediaengine/tests/mfmediaengine.c b/dlls/mfmediaengine/tests/mfmediaengine.c index 318684dc4f9..ac32128d732 100644 --- a/dlls/mfmediaengine/tests/mfmediaengine.c +++ b/dlls/mfmediaengine/tests/mfmediaengine.c @@ -2177,6 +2177,9 @@ struct test_seek_notify { IMFMediaEngineNotify IMFMediaEngineNotify_iface; HANDLE playing_event; + HANDLE seeking_event; + HANDLE seeked_event; + HANDLE time_update_event; HRESULT expected_error; HRESULT error; LONG refcount; @@ -2216,6 +2219,9 @@ static ULONG WINAPI test_seek_notify_Release(IMFMediaEngineNotify *iface) if (!refcount) { CloseHandle(notify->playing_event); + CloseHandle(notify->seeking_event); + CloseHandle(notify->seeked_event); + CloseHandle(notify->time_update_event); free(notify); }
@@ -2232,6 +2238,15 @@ static HRESULT WINAPI test_seek_notify_EventNotify(IMFMediaEngineNotify *iface, case MF_MEDIA_ENGINE_EVENT_PLAYING: SetEvent(notify->playing_event); break; + case MF_MEDIA_ENGINE_EVENT_SEEKING: + SetEvent(notify->seeking_event); + break; + case MF_MEDIA_ENGINE_EVENT_SEEKED: + SetEvent(notify->seeked_event); + break; + case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE: + SetEvent(notify->time_update_event); + break; case MF_MEDIA_ENGINE_EVENT_ERROR: ok(param2 == notify->expected_error, "Unexpected error %#lx\n", param2); notify->error = param2; @@ -2256,7 +2271,13 @@ static struct test_seek_notify *create_seek_notify(void) object = calloc(1, sizeof(*object)); object->IMFMediaEngineNotify_iface.lpVtbl = &test_seek_notify_vtbl; object->playing_event = CreateEventW(NULL, FALSE, FALSE, NULL); + object->seeking_event = CreateEventW(NULL, FALSE, FALSE, NULL); + object->seeked_event = CreateEventW(NULL, FALSE, FALSE, NULL); + object->time_update_event = CreateEventW(NULL, FALSE, FALSE, NULL); ok(!!object->playing_event, "Failed to create an event, error %lu.\n", GetLastError()); + ok(!!object->seeking_event, "Failed to create an event, error %lu.\n", GetLastError()); + ok(!!object->seeked_event, "Failed to create an event, error %lu.\n", GetLastError()); + ok(!!object->time_update_event, "Failed to create an event, error %lu.\n", GetLastError()); object->refcount = 1; return object; } @@ -2363,6 +2384,172 @@ done: IMFByteStream_Release(stream); }
+#define test_seek_result(a, b, c) _test_seek_result(__LINE__, a, b, c) +static void _test_seek_result(int line, IMFMediaEngineEx *media_engine, + const struct test_seek_notify *notify, double expected_time) +{ + static const double allowed_error = 0.05; + static const int timeout = 1000; + double time; + DWORD res; + + res = WaitForSingleObject(notify->seeking_event, timeout); + ok_(__FILE__, line)(!res, "Waiting for seeking event returned %#lx.\n", res); + res = WaitForSingleObject(notify->seeked_event, timeout); + ok_(__FILE__, line)(!res, "Waiting for seeked event returned %#lx.\n", res); + res = WaitForSingleObject(notify->time_update_event, timeout); + ok_(__FILE__, line)(!res, "Waiting for ready event returned %#lx.\n", res); + time = IMFMediaEngineEx_GetCurrentTime(media_engine); + ok_(__FILE__, line)(compare_double(time, expected_time, allowed_error), "Unexpected time %lf.\n", time); +} + +static void test_SetCurrentTime(void) +{ + static const double allowed_error = 0.05; + static const int timeout = 1000; + IMFByteStream *stream, *unseekable_stream = NULL; + double time, duration, start, end; + struct test_seek_notify *notify; + IMFMediaEngineEx *media_engine; + ULONG refcount; + HRESULT hr; + DWORD res; + BOOL ret; + BSTR url; + + notify = create_seek_notify(); + media_engine = create_media_engine_ex(¬ify->IMFMediaEngineNotify_iface, NULL, DXGI_FORMAT_B8G8R8X8_UNORM); + ok(!!media_engine, "create_media_engine_ex failed.\n"); + IMFMediaEngineNotify_Release(¬ify->IMFMediaEngineNotify_iface); + + stream = load_resource(L"i420-64x64.avi", L"video/avi"); + url = SysAllocString(L"i420-64x64.avi"); + hr = IMFMediaEngineEx_SetSourceFromByteStream(media_engine, stream, url); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaEngineEx_Play(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + res = WaitForSingleObject(notify->playing_event, 5000); + ok(!res, "Unexpected res %#lx.\n", res); + + duration = IMFMediaEngineEx_GetDuration(media_engine); + ok(duration > 0, "Got invalid duration.\n"); + start = 0; + end = duration; + + /* Test playing state */ + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, end); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + test_seek_result(media_engine, notify, end); + + /* Test seeking with a negative position */ + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, -1); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + test_seek_result(media_engine, notify, 0); + + /* Test seeking beyond duration */ + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, end + 1); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + test_seek_result(media_engine, notify, end); + + hr = IMFMediaEngineEx_SetCurrentTimeEx(media_engine, start, MF_MEDIA_ENGINE_SEEK_MODE_NORMAL); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + test_seek_result(media_engine, notify, start); + + hr = IMFMediaEngineEx_SetCurrentTimeEx(media_engine, end, MF_MEDIA_ENGINE_SEEK_MODE_APPROXIMATE); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + test_seek_result(media_engine, notify, end); + + /* Test paused state */ + hr = IMFMediaEngineEx_Pause(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, start); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + res = WaitForSingleObject(notify->seeking_event, timeout); + ok(!res, "Unexpected res %#lx.\n", res); + res = WaitForSingleObject(notify->seeked_event, timeout); + ok(res == WAIT_TIMEOUT || res == 0, /* No timeout sometimes on Win10+ */ + "Unexpected res %#lx.\n", res); + res = WaitForSingleObject(notify->time_update_event, timeout); + ok(!res, "Unexpected res %#lx.\n", res); + time = IMFMediaEngineEx_GetCurrentTime(media_engine); + ok(compare_double(time, start, allowed_error), "Unexpected time %lf.\n", time); + } + + Sleep(end * 1000); + + ret = IMFMediaEngineEx_IsPaused(media_engine); + ok(ret, "Unexpected ret %d.\n", ret); + time = IMFMediaEngineEx_GetCurrentTime(media_engine); + ok(compare_double(time, start, allowed_error) + || broken(time >= end), /* Windows 11 21H2 AMD GPU TestBot */ + "Unexpected time %lf.\n", time); + + hr = IMFMediaEngineEx_Play(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + res = WaitForSingleObject(notify->seeked_event, timeout); + ok(res == WAIT_TIMEOUT, "Unexpected res %#lx.\n", res); + + /* Media engine is shut down */ + hr = IMFMediaEngineEx_Shutdown(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, start); + ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaEngineEx_SetCurrentTimeEx(media_engine, start, MF_MEDIA_ENGINE_SEEK_MODE_NORMAL); + ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr); + + refcount = IMFMediaEngineEx_Release(media_engine); + todo_wine + ok(!refcount, "Got unexpected refcount %lu.\n", refcount); + + /* Unseekable bytestreams */ + notify = create_seek_notify(); + media_engine = create_media_engine_ex(¬ify->IMFMediaEngineNotify_iface, NULL, DXGI_FORMAT_B8G8R8X8_UNORM); + IMFMediaEngineNotify_Release(¬ify->IMFMediaEngineNotify_iface); + unseekable_stream = create_unseekable_stream(stream); + hr = IMFMediaEngineEx_SetSourceFromByteStream(media_engine, unseekable_stream, url); + todo_wine + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (FAILED(hr)) + goto done; + + hr = IMFMediaEngineEx_Play(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + notify->expected_error = MF_E_INVALIDREQUEST; + res = WaitForSingleObject(notify->playing_event, 5000); + ok(res == S_OK, "Unexpected res %#lx.\n", res); + + hr = IMFMediaEngineEx_SetCurrentTime(media_engine, end); + ok(hr == S_OK || broken(hr == MF_INVALID_STATE_ERR) /* Win8 */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + res = WaitForSingleObject(notify->seeking_event, timeout); + ok(res == WAIT_TIMEOUT, "Unexpected res %#lx.\n", res); + res = WaitForSingleObject(notify->seeked_event, timeout); + ok(res == WAIT_TIMEOUT, "Unexpected res %#lx.\n", res); + res = WaitForSingleObject(notify->time_update_event, timeout); + ok(!res, "Unexpected res %#lx.\n", res); + } + +done: + hr = IMFMediaEngineEx_Shutdown(media_engine); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + refcount = IMFMediaEngineEx_Release(media_engine); + ok(!refcount, "Got unexpected refcount %lu.\n", refcount); + IMFByteStream_Release(unseekable_stream); + SysFreeString(url); + IMFByteStream_Release(stream); +} + START_TEST(mfmediaengine) { HRESULT hr; @@ -2397,6 +2584,7 @@ START_TEST(mfmediaengine) test_effect(); test_GetDuration(); test_GetSeekable(); + test_SetCurrentTime();
IMFMediaEngineClassFactory_Release(factory);
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=136202
Your paranoid android.
=== w10pro64_en_AE_u8 (64 bit report) ===
mf: mf.c:7580: Test failed: Unexpected time 11870625. mf.c:7701: Test failed: Unexpected time 3507244. mf.c:7711: Test failed: Unexpected time 3511942. mf.c:7717: Test failed: Unexpected time 5647425. mf.c:7726: Test failed: Unexpected time 5652080.
=== debian11 (32 bit ar:MA report) ===
mfmediaengine: mfmediaengine.c:2444: Test failed: Unexpected time 0.000000.
=== debian11 (32 bit hi:IN report) ===
mfmediaengine: mfmediaengine.c:2444: Test failed: Unexpected time 0.000000.
=== debian11b (64 bit WoW report) ===
Report validation errors: xinput1_3:xinput crashed (c0000005)
Nikolay Sivov (@nsivov) commented about dlls/mf/session.c:
struct topo_node *node = NULL, *found = NULL;
LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) { if (node->type == node_type && object == node->object.object)
{
found = node; break;
}
}
- return node;
- if (!found)
WARN("Failed to find object %p.\n", object);
- return found;
}
This is too complicated, simply return "node" from within the loop, and NULL otherwise.
Nikolay Sivov (@nsivov) commented about dlls/mf/session.c:
if (!start_position) return E_POINTER;
- if (FAILED(hr = session_is_shut_down(session)))
return hr;
- if (FAILED(hr = create_session_op(SESSION_CMD_START, &op))) return hr;
Why do you need this check?
Nikolay Sivov (@nsivov) commented about dlls/mf/session.c:
{ case MESourceSeeked: case MEStreamSeeked:
FIXME("Source/stream seeking, semi-stub!\n");
/* fallthrough */ case MESourceStarted: case MESourcePaused: case MESourceStopped:
Surely this isn't enough for seek to work? Whole patch will only put objects in new state, and they will state in it?
Nikolay Sivov (@nsivov) commented about dlls/mf/session.c:
LIST_FOR_EACH_ENTRY(source, &session->presentation.sources, struct media_source, entry) {
hr = IMFPresentationDescriptor_GetUINT64(source->pd, &MF_PD_DURATION, (UINT64 *)&duration);
if (SUCCEEDED(hr) && start_position->vt == VT_I8 && start_position->hVal.QuadPart > duration)
{
WARN("Start position %s out of range, hr %#lx.\n", wine_dbgstr_longlong(start_position->hVal.QuadPart), hr);
session_command_complete_with_event(session, MESessionStarted, MF_E_INVALID_POSITION, NULL);
return;
}
This only works for GUID_NULL time format, even if we don't support other formats yet.
Nikolay Sivov (@nsivov) commented about dlls/mf/session.c:
}
if (session->state == SESSION_STATE_STARTED)
{
struct topo_node *node;
LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry)
{
if (node->type == MF_TOPOLOGY_OUTPUT_NODE)
IMFStreamSink_Flush(node->object.sink_stream);
else if (node->type == MF_TOPOLOGY_TRANSFORM_NODE)
IMFTransform_ProcessMessage(node->object.transform, MFT_MESSAGE_COMMAND_FLUSH, 0);
}
session_set_source_output_nodes_seeking(session);
}
There is a similar block for SESSION_STATE_STOPPING_SOURCES. If we really need to flush on seek, this should go to some helper.
Nikolay Sivov (@nsivov) commented about dlls/mf/tests/mf.c:
IMFSampleGrabberSinkCallback_Release(grabber_callback);
}
+/* create a media session with the specified source and sink */ +static void create_media_session_with_source_sink(IMFMediaSource *source, IMFActivate *sink_activate,
IMFMediaSession **session)
+{
Just "create_media_session" is fine. With null arguments if we need to.
Nikolay Sivov (@nsivov) commented about dlls/mfmediaengine/main.c:
- struct media_engine *engine = impl_from_IMFMediaEngineEx(iface);
- HRESULT hr = E_NOTIMPL;
- TRACE("%p, %f.\n", iface, time);
- FIXME("(%p, %f): stub.\n", iface, time);
- EnterCriticalSection(&engine->cs);
- if (engine->flags & FLAGS_ENGINE_SHUT_DOWN)
hr = MF_E_SHUTDOWN;
- LeaveCriticalSection(&engine->cs);
- return hr;
- return IMFMediaEngineEx_SetCurrentTimeEx(iface, time, MF_MEDIA_ENGINE_SEEK_MODE_NORMAL);
}
To get cleaner logs, let's add a helper, and use it for both methods.
Nikolay Sivov (@nsivov) commented about dlls/mfmediaengine/main.c:
hr = IMFMediaSession_GetSessionCapabilities(engine->session, &caps);
if (SUCCEEDED(hr) && caps & MFSESSIONCAP_SEEK)
{
paused = IMFMediaEngineEx_IsPaused(iface);
position.vt = VT_I8;
position.hVal.QuadPart = min(max(0, seektime), engine->duration) * 10000000;
hr = IMFMediaSession_Start(engine->session, &GUID_NULL, &position);
if (SUCCEEDED(hr))
{
media_engine_set_flag(engine, FLAGS_ENGINE_SEEKING, TRUE);
IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_SEEKING, 0, 0);
if (paused)
hr = IMFMediaSession_Pause(engine->session);
}
}
There is a lot going on here, main problem I see is the need to make two state changes to pause again. Have you tried running this change on Windows, to see how smooth that is? It would be useful to have a test player application, where you can change positions, start, stop, etc. There is one for mfplay in Windows Classic samples, and it's a quite similar API.
Another thing, did you check that notification here is really blocking?
I think it's better to have media engine patches in a separate MR, after making sure they work on Windows first.
On Tue Aug 15 09:32:08 2023 +0000, Nikolay Sivov wrote:
Why do you need this check?
Because IMFMediaSession::Start() should return MF_E_SHUTDOWN when the media session is shut down. Test test_media_session_Start() covers this.
On Tue Aug 15 09:32:08 2023 +0000, Nikolay Sivov wrote:
Surely this isn't enough for seek to work? Whole patch will only put objects in new state, and they will state in it?
Of course not. I just removed these FIXMEs as they're no longer necessary. The real state transition happens in the SESSION_STATE_STARTED case in session_start(), where all source and sink nodes will be put in the OBJ_STATE_SEEKING state and media session in the SESSION_STATE_STARTING_SOURCES state. Then when MESourceSeeked and MEStreamSeeked are received, set the corresponding node to OBJ_STATE_STARTED. And when all source nodes are in OBJ_STATE_STARTED, the presentation clock is re/started, and then the session state becomes SESSION_STATE_STARTING_SINKS. Finally, after a new MEStreamSinkStarted is received by the media session, the media session state becomes SESSION_STATE_STARTED and seeking is completed.
On Wed Aug 16 06:52:28 2023 +0000, Zhiyi Zhang wrote:
Because IMFMediaSession::Start() should return MF_E_SHUTDOWN when the media session is shut down. Test test_media_session_Start() covers this.
Yes, but doesn't session_submit_command() already handle that?
On Tue Aug 15 09:32:11 2023 +0000, Nikolay Sivov wrote:
There is a lot going on here, main problem I see is the need to make two state changes to pause again. Have you tried running this change on Windows, to see how smooth that is? It would be useful to have a test player application, where you can change positions, start, stop, etc. There is one for mfplay in Windows Classic samples, and it's a quite similar API. Another thing, did you check that notification here is really blocking?
It's pretty smooth. No delay and no extra frames.
Maybe IMFMediaEngine is performing a [scrubbing](https://learn.microsoft.com/en-us/windows/win32/medfound/how-to-perform-scru...) and then it restores the playback rate for playback?