After end of presentation, the session should return to the stopped state, which means that can be restarted again. END_OF_PRESENTATION flags should not remain set in this case.
Signed-off-by: Giovanni Mascellani gmascellani@codeweavers.com --- dlls/mf/session.c | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/dlls/mf/session.c b/dlls/mf/session.c index 9f233295773..ef2d15c9ef5 100644 --- a/dlls/mf/session.c +++ b/dlls/mf/session.c @@ -1061,6 +1061,7 @@ static void session_set_stopped(struct media_session *session, HRESULT status) { MediaEventType event_type; IMFMediaEvent *event; + struct media_source *source;;
session->state = SESSION_STATE_STOPPED; event_type = session->presentation.flags & SESSION_FLAG_END_OF_PRESENTATION ? MESessionEnded : MESessionStopped; @@ -1071,6 +1072,11 @@ static void session_set_stopped(struct media_session *session, HRESULT status) IMFMediaEventQueue_QueueEvent(session->event_queue, event); IMFMediaEvent_Release(event); } + session->presentation.flags &= ~SESSION_FLAG_END_OF_PRESENTATION; + LIST_FOR_EACH_ENTRY(source, &session->presentation.sources, struct media_source, entry) + { + source->flags &= ~SOURCE_FLAG_END_OF_PRESENTATION; + } session_command_complete(session); }
Signed-off-by: Giovanni Mascellani gmascellani@codeweavers.com --- dlls/winegstreamer/media_source.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 3c87bbb2146..3e1e3001dc7 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -100,8 +100,6 @@ struct media_source SOURCE_SHUTDOWN, } state;
- LONGLONG start_time; - HANDLE read_thread; bool read_thread_shutdown; }; @@ -269,7 +267,6 @@ static void start_pipeline(struct media_source *source, struct source_async_comm position->vt = VT_I8; position->hVal.QuadPart = 0; } - source->start_time = position->hVal.QuadPart;
for (i = 0; i < source->stream_count; i++) { @@ -422,7 +419,7 @@ static void send_buffer(struct media_stream *stream, const struct wg_parser_even goto out; }
- if (FAILED(hr = IMFSample_SetSampleTime(sample, event->u.buffer.pts - stream->parent_source->start_time))) + if (FAILED(hr = IMFSample_SetSampleTime(sample, event->u.buffer.pts))) { ERR("Failed to set sample time, hr %#x.\n", hr); goto out;
Signed-off-by: Giovanni Mascellani gmascellani@codeweavers.com --- dlls/winegstreamer/media_source.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 3e1e3001dc7..9188a350305 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -324,8 +324,9 @@ static void start_pipeline(struct media_source *source, struct source_async_comm
source->state = SOURCE_RUNNING;
- unix_funcs->wg_parser_stream_seek(source->streams[0]->wg_stream, 1.0, - position->hVal.QuadPart, 0, AM_SEEKING_AbsolutePositioning, AM_SEEKING_NoPositioning); + if (position->vt == VT_I8) + unix_funcs->wg_parser_stream_seek(source->streams[0]->wg_stream, 1.0, + position->hVal.QuadPart, 0, AM_SEEKING_AbsolutePositioning, AM_SEEKING_NoPositioning); unix_funcs->wg_parser_end_flush(source->wg_parser); }
On 6/11/21 5:53 AM, Giovanni Mascellani wrote:
Signed-off-by: Giovanni Mascellani gmascellani@codeweavers.com
dlls/winegstreamer/media_source.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 3e1e3001dc7..9188a350305 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -324,8 +324,9 @@ static void start_pipeline(struct media_source *source, struct source_async_comm
source->state = SOURCE_RUNNING;
- unix_funcs->wg_parser_stream_seek(source->streams[0]->wg_stream, 1.0,
position->hVal.QuadPart, 0, AM_SEEKING_AbsolutePositioning, AM_SEEKING_NoPositioning);
- if (position->vt == VT_I8)
unix_funcs->wg_parser_stream_seek(source->streams[0]->wg_stream, 1.0,
}position->hVal.QuadPart, 0, AM_SEEKING_AbsolutePositioning, AM_SEEKING_NoPositioning); unix_funcs->wg_parser_end_flush(source->wg_parser);
I don't think this works reliably as-is. The API is a little awkward and doesn't communicate this clearly (sorry), but if GStreamer gives us a sample between wg_parser_stream_enable() and wg_parser_end_flush(), we'll return GST_FLOW_FLUSHING, which will generally cause the upstream element to stop sending data. The seek event is necessary to tell it to start sending data again.
This situation is probably more than a little undesirable. We probably need to modify the interface and backend to prevent any samples from getting lost (and, ideally, to avoid seeking the GStreamer backend if we don't need to).
What happens if you change which streams are selected without seeking? Currently the WG backend discards any events/buffers on disabled streams, which means that in general they won't be synchronized and we may end up throwing away an entire stream. My guess is that this is fine, because as far as I can tell mfplat doesn't actually make any synchronization guarantees even among selected streams, but I'd like to make sure...
On 6/11/21 4:10 PM, Zebediah Figura (she/her) wrote:
On 6/11/21 5:53 AM, Giovanni Mascellani wrote:
Signed-off-by: Giovanni Mascellani gmascellani@codeweavers.com
dlls/winegstreamer/media_source.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 3e1e3001dc7..9188a350305 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -324,8 +324,9 @@ static void start_pipeline(struct media_source *source, struct source_async_comm
source->state = SOURCE_RUNNING;
- unix_funcs->wg_parser_stream_seek(source->streams[0]->wg_stream, 1.0,
position->hVal.QuadPart, 0, AM_SEEKING_AbsolutePositioning, AM_SEEKING_NoPositioning);
- if (position->vt == VT_I8)
unix_funcs->wg_parser_stream_seek(source->streams[0]->wg_stream, 1.0,
}position->hVal.QuadPart, 0, AM_SEEKING_AbsolutePositioning, AM_SEEKING_NoPositioning); unix_funcs->wg_parser_end_flush(source->wg_parser);
I don't think this works reliably as-is. The API is a little awkward and doesn't communicate this clearly (sorry), but if GStreamer gives us a sample between wg_parser_stream_enable() and wg_parser_end_flush(), we'll return GST_FLOW_FLUSHING, which will generally cause the upstream element to stop sending data. The seek event is necessary to tell it to start sending data again.
This situation is probably more than a little undesirable. We probably need to modify the interface and backend to prevent any samples from getting lost (and, ideally, to avoid seeking the GStreamer backend if we don't need to).
Urgh, I confused myself, this isn't actually how it works. wg_parser_begin_flush() actually only exists to unblock a thread stuck in wg_parser_stream_get_event(). Unfortunately I named two different variables "flushing" :-(
So I think the patch should be fine as-is.
What happens if you change which streams are selected without seeking? Currently the WG backend discards any events/buffers on disabled streams, which means that in general they won't be synchronized and we may end up throwing away an entire stream. My guess is that this is fine, because as far as I can tell mfplat doesn't actually make any synchronization guarantees even among selected streams, but I'd like to make sure...
I am still curious about this part, though.
Signed-off-by: Giovanni Mascellani gmascellani@codeweavers.com --- dlls/winegstreamer/media_source.c | 102 ++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 5 deletions(-)
diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 9188a350305..953c0c84e90 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -37,6 +37,10 @@ struct media_stream
struct wg_parser_stream *wg_stream;
+ IUnknown **token_queue; + LONG token_queue_count; + LONG token_queue_cap; + enum { STREAM_INACTIVE, @@ -50,6 +54,7 @@ struct media_stream enum source_async_op { SOURCE_ASYNC_START, + SOURCE_ASYNC_PAUSE, SOURCE_ASYNC_STOP, SOURCE_ASYNC_REQUEST_SAMPLE, }; @@ -96,6 +101,7 @@ struct media_source { SOURCE_OPENING, SOURCE_STOPPED, + SOURCE_PAUSED, SOURCE_RUNNING, SOURCE_SHUTDOWN, } state; @@ -255,6 +261,54 @@ static IMFStreamDescriptor *stream_descriptor_from_id(IMFPresentationDescriptor return NULL; }
+static BOOL enqueue_token(struct media_stream *stream, IUnknown *token) +{ + if (stream->token_queue_count == stream->token_queue_cap) + { + IUnknown **buf; + stream->token_queue_cap = stream->token_queue_cap * 2 + 1; + buf = realloc(stream->token_queue, stream->token_queue_cap * sizeof(*buf)); + if (buf) + stream->token_queue = buf; + else + { + stream->token_queue_cap = stream->token_queue_count; + return FALSE; + } + } + stream->token_queue[stream->token_queue_count++] = token; + return TRUE; +} + +static void flush_token_queue(struct media_stream *stream, BOOL send) +{ + LONG i; + + for (i = 0; i < stream->token_queue_count; i++) + { + if (send) + { + HRESULT hr; + struct source_async_command *command; + if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &command))) + { + command->u.request_sample.stream = stream; + command->u.request_sample.token = stream->token_queue[i]; + + hr = MFPutWorkItem(stream->parent_source->async_commands_queue, &stream->parent_source->async_commands_callback, &command->IUnknown_iface); + } + if (FAILED(hr)) + WARN("Could not enqueue sample request, hr %#x\n", hr); + } + else if (stream->token_queue[i]) + IUnknown_Release(stream->token_queue[i]); + } + free(stream->token_queue); + stream->token_queue = NULL; + stream->token_queue_count = 0; + stream->token_queue_cap = 0; +} + static void start_pipeline(struct media_source *source, struct source_async_command *command) { PROPVARIANT *position = &command->u.start.position; @@ -328,6 +382,27 @@ static void start_pipeline(struct media_source *source, struct source_async_comm unix_funcs->wg_parser_stream_seek(source->streams[0]->wg_stream, 1.0, position->hVal.QuadPart, 0, AM_SEEKING_AbsolutePositioning, AM_SEEKING_NoPositioning); unix_funcs->wg_parser_end_flush(source->wg_parser); + + for (i = 0; i < source->stream_count; i++) + flush_token_queue(source->streams[i], position->vt == VT_EMPTY); +} + +static void pause_pipeline(struct media_source *source) +{ + unsigned int i; + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + if (stream->state != STREAM_INACTIVE) + { + IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEStreamPaused, &GUID_NULL, S_OK, NULL); + } + } + + IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourcePaused, &GUID_NULL, S_OK, NULL); + + source->state = SOURCE_PAUSED; }
static void stop_pipeline(struct media_source *source) @@ -349,6 +424,9 @@ static void stop_pipeline(struct media_source *source) IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MESourceStopped, &GUID_NULL, S_OK, NULL);
source->state = SOURCE_STOPPED; + + for (i = 0; i < source->stream_count; i++) + flush_token_queue(source->streams[i], FALSE); }
static void dispatch_end_of_presentation(struct media_source *source) @@ -499,11 +577,17 @@ static HRESULT WINAPI source_async_commands_Invoke(IMFAsyncCallback *iface, IMFA case SOURCE_ASYNC_START: start_pipeline(source, command); break; + case SOURCE_ASYNC_PAUSE: + pause_pipeline(source); + break; case SOURCE_ASYNC_STOP: stop_pipeline(source); break; case SOURCE_ASYNC_REQUEST_SAMPLE: - wait_on_sample(command->u.request_sample.stream, command->u.request_sample.token); + if (source->state == SOURCE_PAUSED) + enqueue_token(command->u.request_sample.stream, command->u.request_sample.token); + else + wait_on_sample(command->u.request_sample.stream, command->u.request_sample.token); break; }
@@ -595,6 +679,7 @@ static ULONG WINAPI media_stream_Release(IMFMediaStream *iface) { if (stream->event_queue) IMFMediaEventQueue_Release(stream->event_queue); + flush_token_queue(stream, FALSE); free(stream); }
@@ -697,7 +782,6 @@ static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown IUnknown_AddRef(token); command->u.request_sample.token = token;
- /* Once pause support is added, this will need to put into a stream queue, and synchronization will need to be added*/ hr = MFPutWorkItem(stream->parent_source->async_commands_queue, &stream->parent_source->async_commands_callback, &command->IUnknown_iface); }
@@ -1121,7 +1205,7 @@ static HRESULT WINAPI media_source_GetCharacteristics(IMFMediaSource *iface, DWO if (source->state == SOURCE_SHUTDOWN) return MF_E_SHUTDOWN;
- *characteristics = MFMEDIASOURCE_CAN_SEEK; + *characteristics = MFMEDIASOURCE_CAN_SEEK | MFMEDIASOURCE_CAN_PAUSE;
return S_OK; } @@ -1185,13 +1269,21 @@ static HRESULT WINAPI media_source_Stop(IMFMediaSource *iface) static HRESULT WINAPI media_source_Pause(IMFMediaSource *iface) { struct media_source *source = impl_from_IMFMediaSource(iface); + struct source_async_command *command; + HRESULT hr;
- FIXME("(%p): stub\n", source); + TRACE("(%p)\n", source);
if (source->state == SOURCE_SHUTDOWN) return MF_E_SHUTDOWN;
- return E_NOTIMPL; + if (source->state != SOURCE_RUNNING) + return MF_E_INVALID_STATE_TRANSITION; + + if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_PAUSE, &command))) + hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, &command->IUnknown_iface); + + return S_OK; }
static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface)
Signed-off-by: Derek Lesho dlesho@codeweavers.com
I tested this a bit in mfplay context, and I don't think it's enough. I resent this patch with some additional fixes that make looping on MFP_EVENT_TYPE_PLAYBACK_ENDED event work for me (only tried wav -> sar path, evr has its own issues with restarting it seems).