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.
-- v2: mf/tests: Test IMFMediaSession::Start(). mf/tests: Add a create_media_session() helper. mf: Add seeking support for IMFMediaSession::Start(). mf: Add a session_flush_transform_output_nodes() helper. mf: Make session_get_node_object() more robust.
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 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 555a71dcdf7..7cd45c343be 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -2814,10 +2814,11 @@ static struct topo_node *session_get_node_object(struct media_session *session, LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) { if (node->type == node_type && object == node->object.object) - break; + return node; }
- return node; + WARN("Failed to find object %p.\n", object); + return NULL; }
static BOOL session_set_node_object_state(struct media_session *session, IUnknown *object,
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/mf/session.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 7cd45c343be..a9aca0710e2 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -889,6 +889,19 @@ static HRESULT session_subscribe_sources(struct media_session *session) return hr; }
+static void session_flush_transform_output_nodes(struct media_session *session) +{ + 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); + } +} + static void session_start(struct media_session *session, const GUID *time_format, const PROPVARIANT *start_position) { struct media_source *source; @@ -2843,7 +2856,6 @@ static void session_set_source_object_state(struct media_session *session, IUnkn struct media_source *src; struct media_sink *sink; enum object_state state; - struct topo_node *node; BOOL changed = FALSE; DWORD i, count; HRESULT hr; @@ -2939,21 +2951,7 @@ static void session_set_source_object_state(struct media_session *session, IUnkn if (!session_is_source_nodes_state(session, OBJ_STATE_STOPPED)) break;
- LIST_FOR_EACH_ENTRY(node, &session->presentation.nodes, struct topo_node, entry) - { - switch (node->type) - { - case MF_TOPOLOGY_OUTPUT_NODE: - IMFStreamSink_Flush(node->object.sink_stream); - break; - case MF_TOPOLOGY_TRANSFORM_NODE: - IMFTransform_ProcessMessage(node->object.transform, MFT_MESSAGE_COMMAND_FLUSH, 0); - break; - default: - ; - } - } - + session_flush_transform_output_nodes(session); session_set_caps(session, session->caps & ~MFSESSIONCAP_PAUSE);
if (session->presentation.flags & SESSION_FLAG_FINALIZE_SINKS)
From: Zhiyi Zhang zzhang@codeweavers.com
--- dlls/mf/session.c | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index a9aca0710e2..b2751774626 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, };
@@ -902,9 +903,27 @@ static void session_flush_transform_output_nodes(struct media_session *session) } }
+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) @@ -920,6 +939,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; @@ -933,6 +959,15 @@ 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) && IsEqualGUID(time_format, &GUID_NULL) + && 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); @@ -941,12 +976,13 @@ static void session_start(struct media_session *session, const GUID *time_format } }
+ if (session->state == SESSION_STATE_STARTED) + { + session_flush_transform_output_nodes(session); + 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; @@ -3653,8 +3689,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..bef2a48a87b 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(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(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 bef2a48a87b..a245f47e630 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(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(IMFMediaSource *source, IMFActivate *sink_activ 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(source, sink_activate, &session); + create_media_session(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(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(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(); }