From: Derek Lesho Subject: [PATCH v2 14/18] winegstreamer: Implement IMFMediaStream::RequestSample. Message-Id: <20200401220539.522012-14-dlesho@codeweavers.com> Date: Wed, 1 Apr 2020 17:05:35 -0500 In-Reply-To: <20200401220539.522012-1-dlesho@codeweavers.com> References: <20200401220539.522012-1-dlesho@codeweavers.com> Signed-off-by: Derek Lesho --- dlls/mfplat/tests/mfplat.c | 4 - dlls/winegstreamer/gst_cbs.c | 10 ++ dlls/winegstreamer/gst_cbs.h | 6 ++ dlls/winegstreamer/gst_private.h | 1 + dlls/winegstreamer/media_source.c | 169 +++++++++++++++++++++++++++++- dlls/winegstreamer/mfplat.c | 90 ++++++++++++++++ 6 files changed, 272 insertions(+), 8 deletions(-) diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index b666b54cc0..cb001633e4 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -537,13 +537,10 @@ static void test_source_resolver(void) hr = IMFMediaStream_RequestSample(video_stream, NULL); if (i == sample_count) break; -todo_wine ok(hr == S_OK, "Failed to request sample %u, hr %#x.\n", i + 1, hr); if (hr != S_OK) break; } - if (FAILED(hr)) - goto skip_source_tests; for (i = 0; i < sample_count; ++i) { @@ -584,7 +581,6 @@ todo_wine get_event((IMFMediaEventGenerator *)mediasource, MEEndOfPresentation, NULL); -skip_source_tests: IMFMediaStream_Release(video_stream); IMFMediaTypeHandler_Release(handler); IMFPresentationDescriptor_Release(descriptor); diff --git a/dlls/winegstreamer/gst_cbs.c b/dlls/winegstreamer/gst_cbs.c index 5038ea3397..49c04a55e5 100644 --- a/dlls/winegstreamer/gst_cbs.c +++ b/dlls/winegstreamer/gst_cbs.c @@ -416,3 +416,13 @@ GstFlowReturn stream_new_sample_wrapper(GstElement *appsink, gpointer user) return cbdata.u.new_sample_data.ret; } + +void stream_eos_wrapper(GstElement *appsink, gpointer user) +{ + struct cb_data cbdata = { STREAM_EOS }; + + cbdata.u.eos_data.appsink = appsink; + cbdata.u.eos_data.user = user; + + call_cb(&cbdata); +} diff --git a/dlls/winegstreamer/gst_cbs.h b/dlls/winegstreamer/gst_cbs.h index 106368a064..4840b89b59 100644 --- a/dlls/winegstreamer/gst_cbs.h +++ b/dlls/winegstreamer/gst_cbs.h @@ -53,6 +53,7 @@ enum CB_TYPE { SOURCE_STREAM_REMOVED, SOURCE_ALL_STREAMS, STREAM_NEW_SAMPLE, + STREAM_EOS, MEDIA_SOURCE_MAX, }; @@ -143,6 +144,10 @@ struct cb_data { gpointer user; GstFlowReturn ret; } new_sample_data; + struct eos_data { + GstElement *appsink; + gpointer user; + } eos_data; } u; int finished; @@ -179,5 +184,6 @@ void source_stream_added_wrapper(GstElement *bin, GstPad *pad, gpointer user) DE void source_stream_removed_wrapper(GstElement *element, GstPad *pad, gpointer user) DECLSPEC_HIDDEN; void source_all_streams_wrapper(GstElement *element, gpointer user) DECLSPEC_HIDDEN; GstFlowReturn stream_new_sample_wrapper(GstElement *appsink, gpointer user) DECLSPEC_HIDDEN; +void stream_eos_wrapper(GstElement *appsink, gpointer user) DECLSPEC_HIDDEN; #endif diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 780cf1b02f..823e023f52 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -57,6 +57,7 @@ extern HRESULT mfplat_get_class_object(REFCLSID rclsid, REFIID riid, void **obj) GstCaps *make_mf_compatible_caps(GstCaps *caps); IMFMediaType *mf_media_type_from_caps(GstCaps *caps); +IMFSample *mf_sample_from_gst_buffer(GstBuffer *in); enum source_type { diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index dc1b5dca85..b0f0cf4162 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -32,6 +32,12 @@ static struct source_desc } }; +struct sample_request +{ + struct list entry; + IUnknown *token; +}; + struct media_source; struct media_stream @@ -52,6 +58,10 @@ struct media_stream STREAM_RUNNING, STREAM_SHUTDOWN, } state; + BOOL eos; + CRITICAL_SECTION dispatch_samples_cs; + struct list sample_requests; + unsigned int pending_samples; }; struct media_source @@ -82,6 +92,87 @@ struct media_source /* stream */ +static void media_source_notify_stream_ended(struct media_source *source); +static void stream_dispatch_samples(struct media_stream *This) +{ + struct sample_request *req, *cursor2; + unsigned int fufilled_counter = 0; + + if (This->state != STREAM_RUNNING && This->state != STREAM_SHUTDOWN) + return; + + EnterCriticalSection(&This->dispatch_samples_cs); + + LIST_FOR_EACH_ENTRY_SAFE(req, cursor2, &This->sample_requests, struct sample_request, entry) + { + IMFSample *sample; + + if (This->state == STREAM_SHUTDOWN + /* Not sure if this is correct: */ + || (!(This->pending_samples) && This->eos)) + { + if (req->token) + { + IUnknown_Release(req->token); + } + list_remove(&req->entry); + continue; + } + + if (!(This->pending_samples)) + { + break; + } + + /* Get the sample from the appsink, then construct an IMFSample */ + /* We do this in the dispatch function so we can have appsink buffer for us */ + { + GstSample *gst_sample; + + g_signal_emit_by_name(This->appsink, "pull-sample", &gst_sample); + if (!gst_sample) + { + ERR("Appsink has no samples and pending_samples != 0\n"); + break; + } + + sample = mf_sample_from_gst_buffer(gst_sample_get_buffer(gst_sample)); + + gst_sample_unref(gst_sample); + } + + if (req->token) + { + IMFSample_SetUnknown(sample, &MFSampleExtension_Token, req->token); + } + + IMFMediaEventQueue_QueueEventParamUnk(This->event_queue, MEMediaSample, &GUID_NULL, S_OK, (IUnknown *)sample); + + if (req->token) + { + IUnknown_Release(req->token); + } + + list_remove(&req->entry); + + This->pending_samples--; + fufilled_counter++; + } + + TRACE("Fufilled %u sample requests. %u pending samples. %s more requests.\n", + fufilled_counter, This->pending_samples, list_empty(&This->sample_requests) ? "no" : ""); + + if (This->eos && !This->pending_samples && This->state == STREAM_RUNNING) + { + PROPVARIANT empty; + empty.vt = VT_EMPTY; + + IMFMediaEventQueue_QueueEventParamVar(This->event_queue, MEEndOfStream, &GUID_NULL, S_OK, &empty); + media_source_notify_stream_ended(This->parent_source); + } + LeaveCriticalSection(&This->dispatch_samples_cs); +} + static inline struct media_stream *impl_from_IMFMediaStream(IMFMediaStream *iface) { return CONTAINING_RECORD(iface, struct media_stream, IMFMediaStream_iface); @@ -217,13 +308,31 @@ static HRESULT WINAPI media_stream_GetStreamDescriptor(IMFMediaStream* iface, IM static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token) { struct media_stream *This = impl_from_IMFMediaStream(iface); + struct sample_request *req; TRACE("(%p)->(%p)\n", iface, token); if (This->state == STREAM_SHUTDOWN) return MF_E_SHUTDOWN; - return E_NOTIMPL; + if (This->state == STREAM_INACTIVE || This->state == STREAM_ENABLED) + { + WARN("Stream isn't active\n"); + return MF_E_MEDIA_SOURCE_WRONGSTATE; + } + + if (This->eos && !This->pending_samples) + return MF_E_END_OF_STREAM; + + req = heap_alloc(sizeof(*req)); + if (token) + IUnknown_AddRef(token); + req->token = token; + list_add_tail(&This->sample_requests, &req->entry); + + stream_dispatch_samples(This); + + return S_OK; } static const IMFMediaStreamVtbl media_stream_vtbl = @@ -243,15 +352,33 @@ static const IMFMediaStreamVtbl media_stream_vtbl = static GstFlowReturn stream_new_sample(GstElement *appsink, gpointer user) { struct media_stream *This = (struct media_stream *) user; - GstSample *discard_sample; TRACE("(%p) got sample\n", This); - g_signal_emit_by_name(This->appsink, "pull-sample", &discard_sample); - gst_sample_unref(discard_sample); + if (This->state == STREAM_INACTIVE) + { + GstSample *discard_sample; + g_signal_emit_by_name(This->appsink, "pull-sample", &discard_sample); + gst_sample_unref(discard_sample); + return GST_FLOW_OK; + } + + This->pending_samples++; + stream_dispatch_samples(This); return GST_FLOW_OK; } +void stream_eos(GstElement *appsink, gpointer user) +{ + struct media_stream *This = (struct media_stream *) user; + + TRACE("(%p) EOS\n", This); + + This->eos = TRUE; + + stream_dispatch_samples(This); +} + static void media_stream_teardown(struct media_stream *This) { TRACE("(%p)\n", This); @@ -262,12 +389,18 @@ static void media_stream_teardown(struct media_stream *This) gst_object_unref(GST_OBJECT(This->their_src)); if (This->my_sink) gst_object_unref(GST_OBJECT(This->my_sink)); + + /* Frees pending requests and samples when state == STREAM_SHUTDOWN */ + stream_dispatch_samples(This); + if (This->descriptor) IMFStreamDescriptor_Release(This->descriptor); if (This->event_queue) IMFMediaEventQueue_Release(This->event_queue); if (This->parent_source) IMFMediaSource_Release(&This->parent_source->IMFMediaSource_iface); + + DeleteCriticalSection(&This->dispatch_samples_cs); } static HRESULT media_stream_constructor(struct media_source *source, GstPad *pad, DWORD stream_id, struct media_stream **out_stream) @@ -281,6 +414,10 @@ static HRESULT media_stream_constructor(struct media_source *source, GstPad *pad TRACE("(%p %p)->(%p)\n", source, pad, out_stream); This->state = STREAM_INACTIVE; + This->pending_samples = 0; + list_init(&This->sample_requests); + This->eos = FALSE; + InitializeCriticalSection(&This->dispatch_samples_cs); if (FAILED(hr = IMFMediaSource_AddRef(&source->IMFMediaSource_iface))) { @@ -303,6 +440,7 @@ static HRESULT media_stream_constructor(struct media_source *source, GstPad *pad g_object_set(This->appsink, "emit-signals", TRUE, NULL); g_object_set(This->appsink, "sync", FALSE, NULL); g_signal_connect(This->appsink, "new-sample", G_CALLBACK(stream_new_sample_wrapper), This); + g_signal_connect(This->appsink, "eos", G_CALLBACK(stream_eos_wrapper), This); if (FAILED(hr = MFCreateMediaType(&media_type))) { @@ -1004,6 +1142,23 @@ static void source_all_streams(GstElement *element, gpointer user) LeaveCriticalSection(&source->streams_cs); } +static void media_source_notify_stream_ended(struct media_source *This) +{ + PROPVARIANT empty; + empty.vt = VT_EMPTY; + + /* A stream has ended, check whether all have */ + for (unsigned int i = 0; i < This->stream_count; i++) + { + struct media_stream *stream = This->streams[i]; + + if (!stream->eos) + return; + } + + IMFMediaEventQueue_QueueEventParamVar(This->event_queue, MEEndOfPresentation, &GUID_NULL, S_OK, &empty); +} + static HRESULT media_source_constructor(IMFByteStream *bytestream, enum source_type type, struct media_source **out_media_source) { GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE( @@ -1649,6 +1804,12 @@ void perform_cb_media_source(struct cb_data *cbdata) cbdata->u.new_sample_data.ret = stream_new_sample(data->appsink, data->user); break; } + case STREAM_EOS: + { + struct eos_data *data = &cbdata->u.eos_data; + stream_eos(data->appsink, data->user); + break; + } default: { ERR("Wrong callback forwarder called\n"); diff --git a/dlls/winegstreamer/mfplat.c b/dlls/winegstreamer/mfplat.c index 02912ac3b6..8bb030f018 100644 --- a/dlls/winegstreamer/mfplat.c +++ b/dlls/winegstreamer/mfplat.c @@ -871,4 +871,94 @@ GstCaps *make_mf_compatible_caps(GstCaps *caps) } return ret; +} + +/* IMFSample = GstBuffer + IMFBuffer = GstMemory */ + +/* TODO: Future optimization could be to create a custom + IMFMediaBuffer wrapper around GstMemory, and to utilize + gst_memory_new_wrapped on IMFMediaBuffer data. However, + this wouldn't work if we allow the callers to allocate + the buffers. */ + +IMFSample* mf_sample_from_gst_buffer(GstBuffer *gst_buffer) +{ + IMFSample *out = NULL; + LONGLONG duration, time; + int buffer_count; + HRESULT hr; + + if (FAILED(hr = MFCreateSample(&out))) + goto fail; + + duration = GST_BUFFER_DURATION(gst_buffer); + time = GST_BUFFER_PTS(gst_buffer); + + if (FAILED(IMFSample_SetSampleDuration(out, duration / 100))) + goto fail; + + if (FAILED(IMFSample_SetSampleTime(out, time / 100))) + goto fail; + + buffer_count = gst_buffer_n_memory(gst_buffer); + + for (unsigned int i = 0; i < buffer_count; i++) + { + GstMemory *memory = gst_buffer_get_memory(gst_buffer, i); + IMFMediaBuffer *mf_buffer = NULL; + GstMapInfo map_info; + BYTE *buf_data; + + if (!memory) + { + hr = E_FAIL; + goto loop_done; + } + + if (!(gst_memory_map(memory, &map_info, GST_MAP_READ))) + { + hr = E_FAIL; + goto loop_done; + } + + if (FAILED(hr = MFCreateMemoryBuffer(map_info.maxsize, &mf_buffer))) + { + gst_memory_unmap(memory, &map_info); + goto loop_done; + } + + if (FAILED(hr = IMFMediaBuffer_Lock(mf_buffer, &buf_data, NULL, NULL))) + { + gst_memory_unmap(memory, &map_info); + goto loop_done; + } + + memcpy(buf_data, map_info.data, map_info.size); + + gst_memory_unmap(memory, &map_info); + + if (FAILED(hr = IMFMediaBuffer_Unlock(mf_buffer))) + goto loop_done; + + if (FAILED(hr = IMFMediaBuffer_SetCurrentLength(mf_buffer, map_info.size))) + goto loop_done; + + if (FAILED(hr = IMFSample_AddBuffer(out, mf_buffer))) + goto loop_done; + + loop_done: + if (mf_buffer) + IMFMediaBuffer_Release(mf_buffer); + if (memory) + gst_memory_unref(memory); + if (FAILED(hr)) + goto fail; + } + + return out; + fail: + ERR("Failed to copy IMFSample to GstBuffer, hr = %#x\n", hr); + IMFSample_Release(out); + return NULL; } \ No newline at end of file -- 2.26.0