Signed-off-by: Derek Lesho dlesho@codeweavers.com --- dlls/winegstreamer/gst_cbs.c | 58 +++++++++ dlls/winegstreamer/gst_cbs.h | 12 +- dlls/winegstreamer/main.c | 3 + dlls/winegstreamer/media_source.c | 189 +++++++++++++++++++++++++++++- 4 files changed, 258 insertions(+), 4 deletions(-)
diff --git a/dlls/winegstreamer/gst_cbs.c b/dlls/winegstreamer/gst_cbs.c index bf7103b1606..12b53bc5d68 100644 --- a/dlls/winegstreamer/gst_cbs.c +++ b/dlls/winegstreamer/gst_cbs.c @@ -49,6 +49,8 @@ static void CALLBACK perform_cb(TP_CALLBACK_INSTANCE *instance, void *user)
if (cbdata->type < GSTDEMUX_MAX) perform_cb_gstdemux(cbdata); + else if (cbdata->type < MEDIA_SOURCE_MAX) + perform_cb_media_source(cbdata);
pthread_mutex_lock(&cbdata->lock); cbdata->finished = 1; @@ -301,3 +303,59 @@ gboolean query_sink_wrapper(GstPad *pad, GstObject *parent, GstQuery *query)
return cbdata.u.query_sink_data.ret; } + +GstFlowReturn bytestream_wrapper_pull_wrapper(GstPad *pad, GstObject *parent, guint64 ofs, guint len, + GstBuffer **buf) +{ + struct cb_data cbdata = { BYTESTREAM_WRAPPER_PULL }; + + cbdata.u.getrange_data.pad = pad; + cbdata.u.getrange_data.parent = parent; + cbdata.u.getrange_data.ofs = ofs; + cbdata.u.getrange_data.len = len; + cbdata.u.getrange_data.buf = buf; + + call_cb(&cbdata); + + return cbdata.u.getrange_data.ret; +} + +gboolean bytestream_query_wrapper(GstPad *pad, GstObject *parent, GstQuery *query) +{ + struct cb_data cbdata = { BYTESTREAM_QUERY }; + + cbdata.u.query_function_data.pad = pad; + cbdata.u.query_function_data.parent = parent; + cbdata.u.query_function_data.query = query; + + call_cb(&cbdata); + + return cbdata.u.query_function_data.ret; +} + +gboolean bytestream_pad_mode_activate_wrapper(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean activate) +{ + struct cb_data cbdata = { BYTESTREAM_PAD_MODE_ACTIVATE }; + + cbdata.u.activate_mode_data.pad = pad; + cbdata.u.activate_mode_data.parent = parent; + cbdata.u.activate_mode_data.mode = mode; + cbdata.u.activate_mode_data.activate = activate; + + call_cb(&cbdata); + + return cbdata.u.activate_mode_data.ret; +} + +gboolean bytestream_pad_event_process_wrapper(GstPad *pad, GstObject *parent, GstEvent *event) +{ + struct cb_data cbdata = { BYTESTREAM_PAD_EVENT_PROCESS }; + + cbdata.u.event_src_data.pad = pad; + cbdata.u.event_src_data.parent = parent; + cbdata.u.event_src_data.event = event; + + call_cb(&cbdata); + + return cbdata.u.event_src_data.ret; +} diff --git a/dlls/winegstreamer/gst_cbs.h b/dlls/winegstreamer/gst_cbs.h index 4725f23ad1a..3459a9ef8ee 100644 --- a/dlls/winegstreamer/gst_cbs.h +++ b/dlls/winegstreamer/gst_cbs.h @@ -43,7 +43,12 @@ enum CB_TYPE { AUTOPLUG_BLACKLIST, UNKNOWN_TYPE, QUERY_SINK, - GSTDEMUX_MAX + GSTDEMUX_MAX, + BYTESTREAM_WRAPPER_PULL, + BYTESTREAM_QUERY, + BYTESTREAM_PAD_MODE_ACTIVATE, + BYTESTREAM_PAD_EVENT_PROCESS, + MEDIA_SOURCE_MAX, };
struct cb_data { @@ -138,6 +143,7 @@ struct cb_data {
void mark_wine_thread(void) DECLSPEC_HIDDEN; void perform_cb_gstdemux(struct cb_data *data) DECLSPEC_HIDDEN; +void perform_cb_media_source(struct cb_data *data) DECLSPEC_HIDDEN;
GstBusSyncReply watch_bus_wrapper(GstBus *bus, GstMessage *msg, gpointer user) DECLSPEC_HIDDEN; void existing_new_pad_wrapper(GstElement *bin, GstPad *pad, gpointer user) DECLSPEC_HIDDEN; @@ -154,5 +160,9 @@ GstAutoplugSelectResult autoplug_blacklist_wrapper(GstElement *bin, GstPad *pad, void unknown_type_wrapper(GstElement *bin, GstPad *pad, GstCaps *caps, gpointer user) DECLSPEC_HIDDEN; void Gstreamer_transform_pad_added_wrapper(GstElement *filter, GstPad *pad, gpointer user) DECLSPEC_HIDDEN; gboolean query_sink_wrapper(GstPad *pad, GstObject *parent, GstQuery *query) DECLSPEC_HIDDEN; +GstFlowReturn bytestream_wrapper_pull_wrapper(GstPad *pad, GstObject *parent, guint64 ofs, guint len, GstBuffer **buf) DECLSPEC_HIDDEN; +gboolean bytestream_query_wrapper(GstPad *pad, GstObject *parent, GstQuery *query) DECLSPEC_HIDDEN; +gboolean bytestream_pad_mode_activate_wrapper(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean activate) DECLSPEC_HIDDEN; +gboolean bytestream_pad_event_process_wrapper(GstPad *pad, GstObject *parent, GstEvent *event) DECLSPEC_HIDDEN;
#endif diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index 2872710b3e2..4ca371d58bd 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -146,6 +146,9 @@ HRESULT WINAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void **out)
TRACE("clsid %s, iid %s, out %p.\n", debugstr_guid(clsid), debugstr_guid(iid), out);
+ if (!init_gstreamer()) + return CLASS_E_CLASSNOTAVAILABLE; + if (SUCCEEDED(hr = mfplat_get_class_object(clsid, iid, out))) return hr;
diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 84ecf305d4c..a061443d893 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -17,9 +17,15 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include "config.h" + +#include <gst/gst.h> + #include "gst_private.h" +#include "gst_cbs.h"
#include <stdarg.h> +#include <assert.h>
#define COBJMACROS #define NONAMELESSUNION @@ -27,6 +33,7 @@ #include "mfapi.h" #include "mferror.h" #include "mfidl.h" +#include "mfobjects.h"
#include "wine/debug.h" #include "wine/heap.h" @@ -39,6 +46,8 @@ struct media_source IMFMediaSource IMFMediaSource_iface; LONG ref; IMFMediaEventQueue *event_queue; + IMFByteStream *byte_stream; + GstPad *my_src; enum { SOURCE_OPENING, @@ -52,6 +61,127 @@ static inline struct media_source *impl_from_IMFMediaSource(IMFMediaSource *ifac return CONTAINING_RECORD(iface, struct media_source, IMFMediaSource_iface); }
+static GstFlowReturn bytestream_wrapper_pull(GstPad *pad, GstObject *parent, guint64 ofs, guint len, + GstBuffer **buf) +{ + struct media_source *source = gst_pad_get_element_private(pad); + IMFByteStream *byte_stream = source->byte_stream; + ULONG bytes_read; + GstMapInfo info; + BOOL is_eof; + HRESULT hr; + + TRACE("requesting %u bytes at %s from source %p into buffer %p\n", len, wine_dbgstr_longlong(ofs), source, *buf); + + if (ofs != GST_BUFFER_OFFSET_NONE) + { + if (FAILED(IMFByteStream_SetCurrentPosition(byte_stream, ofs))) + return GST_FLOW_ERROR; + } + + if (FAILED(IMFByteStream_IsEndOfStream(byte_stream, &is_eof))) + return GST_FLOW_ERROR; + if (is_eof) + return GST_FLOW_EOS; + + if (!(*buf)) + *buf = gst_buffer_new_and_alloc(len); + gst_buffer_map(*buf, &info, GST_MAP_WRITE); + hr = IMFByteStream_Read(byte_stream, info.data, len, &bytes_read); + gst_buffer_unmap(*buf, &info); + + gst_buffer_set_size(*buf, bytes_read); + + if (FAILED(hr)) + return GST_FLOW_ERROR; + return GST_FLOW_OK; +} + +static gboolean bytestream_query(GstPad *pad, GstObject *parent, GstQuery *query) +{ + struct media_source *source = gst_pad_get_element_private(pad); + GstFormat format; + QWORD bytestream_len; + + TRACE("GStreamer queries source %p for %s\n", source, GST_QUERY_TYPE_NAME(query)); + + if (FAILED(IMFByteStream_GetLength(source->byte_stream, &bytestream_len))) + return FALSE; + + switch (GST_QUERY_TYPE(query)) + { + case GST_QUERY_DURATION: + { + gst_query_parse_duration(query, &format, NULL); + if (format == GST_FORMAT_PERCENT) + { + gst_query_set_duration(query, GST_FORMAT_PERCENT, GST_FORMAT_PERCENT_MAX); + return TRUE; + } + else if (format == GST_FORMAT_BYTES) + { + QWORD length; + IMFByteStream_GetLength(source->byte_stream, &length); + gst_query_set_duration(query, GST_FORMAT_BYTES, length); + return TRUE; + } + return FALSE; + } + case GST_QUERY_SEEKING: + { + gst_query_parse_seeking (query, &format, NULL, NULL, NULL); + if (format != GST_FORMAT_BYTES) + { + WARN("Cannot seek using format "%s".\n", gst_format_get_name(format)); + return FALSE; + } + gst_query_set_seeking(query, GST_FORMAT_BYTES, 1, 0, bytestream_len); + return TRUE; + } + case GST_QUERY_SCHEDULING: + { + gst_query_set_scheduling(query, GST_SCHEDULING_FLAG_SEEKABLE, 1, -1, 0); + gst_query_add_scheduling_mode(query, GST_PAD_MODE_PULL); + return TRUE; + } + default: + { + WARN("Unhandled query type %s\n", GST_QUERY_TYPE_NAME(query)); + return FALSE; + } + } +} + +static gboolean bytestream_pad_mode_activate(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean activate) +{ + struct media_source *source = gst_pad_get_element_private(pad); + + TRACE("%s source pad for mediasource %p in %s mode.\n", + activate ? "Activating" : "Deactivating", source, gst_pad_mode_get_name(mode)); + + return mode == GST_PAD_MODE_PULL; +} + +static gboolean bytestream_pad_event_process(GstPad *pad, GstObject *parent, GstEvent *event) +{ + struct media_source *source = gst_pad_get_element_private(pad); + + TRACE("source %p, type "%s".\n", source, GST_EVENT_TYPE_NAME(event)); + + switch (event->type) { + /* the seek event should fail in pull mode */ + case GST_EVENT_SEEK: + return FALSE; + default: + WARN("Ignoring "%s" event.\n", GST_EVENT_TYPE_NAME(event)); + case GST_EVENT_TAG: + case GST_EVENT_QOS: + case GST_EVENT_RECONFIGURE: + return gst_pad_event_default(pad, parent, event); + } + return TRUE; +} + static HRESULT WINAPI media_source_QueryInterface(IMFMediaSource *iface, REFIID riid, void **out) { struct media_source *source = impl_from_IMFMediaSource(iface); @@ -211,8 +341,12 @@ static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface)
source->state = SOURCE_SHUTDOWN;
+ if (source->my_src) + gst_object_unref(GST_OBJECT(source->my_src)); if (source->event_queue) IMFMediaEventQueue_Shutdown(source->event_queue); + if (source->byte_stream) + IMFByteStream_Release(source->byte_stream);
return S_OK; } @@ -236,19 +370,31 @@ static const IMFMediaSourceVtbl IMFMediaSource_vtbl =
static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_source **out_media_source) { + GstStaticPadTemplate src_template = + GST_STATIC_PAD_TEMPLATE("mf_src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); + struct media_source *object = heap_alloc_zero(sizeof(*object)); HRESULT hr;
if (!object) return E_OUTOFMEMORY;
+ object->IMFMediaSource_iface.lpVtbl = &IMFMediaSource_vtbl; + object->ref = 1; + object->byte_stream = bytestream; + IMFByteStream_AddRef(bytestream); + if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) goto fail;
- object->state = SOURCE_STOPPED; + object->my_src = gst_pad_new_from_static_template(&src_template, "mf-src"); + gst_pad_set_element_private(object->my_src, object); + gst_pad_set_getrange_function(object->my_src, bytestream_wrapper_pull_wrapper); + gst_pad_set_query_function(object->my_src, bytestream_query_wrapper); + gst_pad_set_activatemode_function(object->my_src, bytestream_pad_mode_activate_wrapper); + gst_pad_set_event_function(object->my_src, bytestream_pad_event_process_wrapper);
- object->IMFMediaSource_iface.lpVtbl = &IMFMediaSource_vtbl; - object->ref = 1; + object->state = SOURCE_STOPPED;
*out_media_source = object; return S_OK; @@ -717,3 +863,40 @@ HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj)
return hr; } + +/* helper for callback forwarding */ +void perform_cb_media_source(struct cb_data *cbdata) +{ + switch(cbdata->type) + { + case BYTESTREAM_WRAPPER_PULL: + { + struct getrange_data *data = &cbdata->u.getrange_data; + cbdata->u.getrange_data.ret = bytestream_wrapper_pull(data->pad, data->parent, + data->ofs, data->len, data->buf); + break; + } + case BYTESTREAM_QUERY: + { + struct query_function_data *data = &cbdata->u.query_function_data; + cbdata->u.query_function_data.ret = bytestream_query(data->pad, data->parent, data->query); + break; + } + case BYTESTREAM_PAD_MODE_ACTIVATE: + { + struct activate_mode_data *data = &cbdata->u.activate_mode_data; + cbdata->u.activate_mode_data.ret = bytestream_pad_mode_activate(data->pad, data->parent, data->mode, data->activate); + break; + } + case BYTESTREAM_PAD_EVENT_PROCESS: + { + struct event_src_data *data = &cbdata->u.event_src_data; + cbdata->u.event_src_data.ret = bytestream_pad_event_process(data->pad, data->parent, data->event); + break; + } + default: + { + assert(0); + } + } +}
Signed-off-by: Derek Lesho dlesho@codeweavers.com --- dlls/winegstreamer/gst_cbs.c | 45 ++++ dlls/winegstreamer/gst_cbs.h | 8 + dlls/winegstreamer/media_source.c | 405 +++++++++++++++++++++++++++++- 3 files changed, 457 insertions(+), 1 deletion(-)
diff --git a/dlls/winegstreamer/gst_cbs.c b/dlls/winegstreamer/gst_cbs.c index 12b53bc5d68..51aaefa911d 100644 --- a/dlls/winegstreamer/gst_cbs.c +++ b/dlls/winegstreamer/gst_cbs.c @@ -359,3 +359,48 @@ gboolean bytestream_pad_event_process_wrapper(GstPad *pad, GstObject *parent, Gs
return cbdata.u.event_src_data.ret; } + +GstBusSyncReply mf_src_bus_watch_wrapper(GstBus *bus, GstMessage *message, gpointer user) +{ + struct cb_data cbdata = { MF_SRC_BUS_WATCH }; + + cbdata.u.watch_bus_data.bus = bus; + cbdata.u.watch_bus_data.msg = message; + cbdata.u.watch_bus_data.user = user; + + call_cb(&cbdata); + + return cbdata.u.watch_bus_data.ret; +} + +void mf_src_stream_added_wrapper(GstElement *bin, GstPad *pad, gpointer user) +{ + struct cb_data cbdata = { MF_SRC_STREAM_ADDED }; + + cbdata.u.pad_added_data.element = bin; + cbdata.u.pad_added_data.pad = pad; + cbdata.u.pad_added_data.user = user; + + call_cb(&cbdata); +} + +void mf_src_stream_removed_wrapper(GstElement *element, GstPad *pad, gpointer user) +{ + struct cb_data cbdata = { MF_SRC_STREAM_REMOVED }; + + cbdata.u.pad_removed_data.element = element; + cbdata.u.pad_removed_data.pad = pad; + cbdata.u.pad_removed_data.user = user; + + call_cb(&cbdata); +} + +void mf_src_no_more_pads_wrapper(GstElement *element, gpointer user) +{ + struct cb_data cbdata = { MF_SRC_NO_MORE_PADS }; + + cbdata.u.no_more_pads_data.element = element; + cbdata.u.no_more_pads_data.user = user; + + call_cb(&cbdata); +} diff --git a/dlls/winegstreamer/gst_cbs.h b/dlls/winegstreamer/gst_cbs.h index 3459a9ef8ee..a48999bbf71 100644 --- a/dlls/winegstreamer/gst_cbs.h +++ b/dlls/winegstreamer/gst_cbs.h @@ -48,6 +48,10 @@ enum CB_TYPE { BYTESTREAM_QUERY, BYTESTREAM_PAD_MODE_ACTIVATE, BYTESTREAM_PAD_EVENT_PROCESS, + MF_SRC_BUS_WATCH, + MF_SRC_STREAM_ADDED, + MF_SRC_STREAM_REMOVED, + MF_SRC_NO_MORE_PADS, MEDIA_SOURCE_MAX, };
@@ -164,5 +168,9 @@ GstFlowReturn bytestream_wrapper_pull_wrapper(GstPad *pad, GstObject *parent, gu gboolean bytestream_query_wrapper(GstPad *pad, GstObject *parent, GstQuery *query) DECLSPEC_HIDDEN; gboolean bytestream_pad_mode_activate_wrapper(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean activate) DECLSPEC_HIDDEN; gboolean bytestream_pad_event_process_wrapper(GstPad *pad, GstObject *parent, GstEvent *event) DECLSPEC_HIDDEN; +GstBusSyncReply mf_src_bus_watch_wrapper(GstBus *bus, GstMessage *message, gpointer user) DECLSPEC_HIDDEN; +void mf_src_stream_added_wrapper(GstElement *bin, GstPad *pad, gpointer user) DECLSPEC_HIDDEN; +void mf_src_stream_removed_wrapper(GstElement *element, GstPad *pad, gpointer user) DECLSPEC_HIDDEN; +void mf_src_no_more_pads_wrapper(GstElement *element, gpointer user) DECLSPEC_HIDDEN;
#endif diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index a061443d893..462a646ee4f 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -24,6 +24,7 @@ #include "gst_private.h" #include "gst_cbs.h"
+#include <assert.h> #include <stdarg.h> #include <assert.h>
@@ -41,21 +42,47 @@
WINE_DEFAULT_DEBUG_CHANNEL(mfplat);
+struct media_stream +{ + IMFMediaStream IMFMediaStream_iface; + LONG ref; + struct media_source *parent_source; + IMFMediaEventQueue *event_queue; + GstElement *appsink; + GstPad *their_src, *my_sink; + enum + { + STREAM_STUB, + STREAM_SHUTDOWN, + } state; +}; + struct media_source { IMFMediaSource IMFMediaSource_iface; LONG ref; IMFMediaEventQueue *event_queue; IMFByteStream *byte_stream; - GstPad *my_src; + struct media_stream **streams; + ULONG stream_count; + GstBus *bus; + GstElement *container; + GstElement *decodebin; + GstPad *my_src, *their_sink; enum { SOURCE_OPENING, SOURCE_STOPPED, SOURCE_SHUTDOWN, } state; + HANDLE all_streams_event; };
+static inline struct media_stream *impl_from_IMFMediaStream(IMFMediaStream *iface) +{ + return CONTAINING_RECORD(iface, struct media_stream, IMFMediaStream_iface); +} + static inline struct media_source *impl_from_IMFMediaSource(IMFMediaSource *iface) { return CONTAINING_RECORD(iface, struct media_source, IMFMediaSource_iface); @@ -182,6 +209,242 @@ static gboolean bytestream_pad_event_process(GstPad *pad, GstObject *parent, Gst return TRUE; }
+GstBusSyncReply bus_watch(GstBus *bus, GstMessage *message, gpointer user) +{ + struct media_source *source = user; + gchar *dbg_info = NULL; + GError *err = NULL; + + TRACE("source %p message type %s\n", source, GST_MESSAGE_TYPE_NAME(message)); + + switch (message->type) + { + case GST_MESSAGE_ERROR: + gst_message_parse_error(message, &err, &dbg_info); + ERR("%s: %s\n", GST_OBJECT_NAME(message->src), err->message); + ERR("%s\n", dbg_info); + g_error_free(err); + g_free(dbg_info); + break; + case GST_MESSAGE_WARNING: + gst_message_parse_warning(message, &err, &dbg_info); + WARN("%s: %s\n", GST_OBJECT_NAME(message->src), err->message); + WARN("%s\n", dbg_info); + g_error_free(err); + g_free(dbg_info); + break; + default: + break; + } + + gst_message_unref(message); + return GST_BUS_DROP; +} + +static HRESULT WINAPI media_stream_QueryInterface(IMFMediaStream *iface, REFIID riid, void **out) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("(%p)->(%s %p)\n", stream, debugstr_guid(riid), out); + + if (IsEqualIID(riid, &IID_IMFMediaStream) || + IsEqualIID(riid, &IID_IMFMediaEventGenerator) || + IsEqualIID(riid, &IID_IUnknown)) + { + *out = &stream->IMFMediaStream_iface; + } + else + { + FIXME("(%s, %p)\n", debugstr_guid(riid), out); + *out = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*out); + return S_OK; +} + +static ULONG WINAPI media_stream_AddRef(IMFMediaStream *iface) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + ULONG ref = InterlockedIncrement(&stream->ref); + + TRACE("(%p) ref=%u\n", stream, ref); + + return ref; +} + +static ULONG WINAPI media_stream_Release(IMFMediaStream *iface) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + ULONG ref = InterlockedDecrement(&stream->ref); + + TRACE("(%p) ref=%u\n", stream, ref); + + if (!ref) + { + if (stream->my_sink) + gst_object_unref(GST_OBJECT(stream->my_sink)); + if (stream->event_queue) + IMFMediaEventQueue_Release(stream->event_queue); + if (stream->parent_source) + IMFMediaSource_Release(&stream->parent_source->IMFMediaSource_iface); + + heap_free(stream); + } + + return ref; +} + +static HRESULT WINAPI media_stream_GetEvent(IMFMediaStream *iface, DWORD flags, IMFMediaEvent **event) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("(%p)->(%#x, %p)\n", stream, flags, event); + + if (stream->state == STREAM_SHUTDOWN) + return MF_E_SHUTDOWN; + + return IMFMediaEventQueue_GetEvent(stream->event_queue, flags, event); +} + +static HRESULT WINAPI media_stream_BeginGetEvent(IMFMediaStream *iface, IMFAsyncCallback *callback, IUnknown *state) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("(%p)->(%p, %p)\n", stream, callback, state); + + if (stream->state == STREAM_SHUTDOWN) + return MF_E_SHUTDOWN; + + return IMFMediaEventQueue_BeginGetEvent(stream->event_queue, callback, state); +} + +static HRESULT WINAPI media_stream_EndGetEvent(IMFMediaStream *iface, IMFAsyncResult *result, IMFMediaEvent **event) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("(%p)->(%p, %p)\n", stream, result, event); + + if (stream->state == STREAM_SHUTDOWN) + return MF_E_SHUTDOWN; + + return IMFMediaEventQueue_EndGetEvent(stream->event_queue, result, event); +} + +static HRESULT WINAPI media_stream_QueueEvent(IMFMediaStream *iface, MediaEventType event_type, REFGUID ext_type, + HRESULT hr, const PROPVARIANT *value) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("(%p)->(%d, %s, %#x, %p)\n", stream, event_type, debugstr_guid(ext_type), hr, value); + + if (stream->state == STREAM_SHUTDOWN) + return MF_E_SHUTDOWN; + + return IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, event_type, ext_type, hr, value); +} + +static HRESULT WINAPI media_stream_GetMediaSource(IMFMediaStream *iface, IMFMediaSource **source) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + FIXME("stub (%p)->(%p)\n", stream, source); + + if (stream->state == STREAM_SHUTDOWN) + return MF_E_SHUTDOWN; + + return E_NOTIMPL; +} + +static HRESULT WINAPI media_stream_GetStreamDescriptor(IMFMediaStream* iface, IMFStreamDescriptor **descriptor) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("(%p)->(%p)\n", stream, descriptor); + + if (stream->state == STREAM_SHUTDOWN) + return MF_E_SHUTDOWN; + + return E_NOTIMPL; +} + +static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token) +{ + struct media_stream *stream = impl_from_IMFMediaStream(iface); + + TRACE("(%p)->(%p)\n", iface, token); + + if (stream->state == STREAM_SHUTDOWN) + return MF_E_SHUTDOWN; + + return E_NOTIMPL; +} + +static const IMFMediaStreamVtbl media_stream_vtbl = +{ + media_stream_QueryInterface, + media_stream_AddRef, + media_stream_Release, + media_stream_GetEvent, + media_stream_BeginGetEvent, + media_stream_EndGetEvent, + media_stream_QueueEvent, + media_stream_GetMediaSource, + media_stream_GetStreamDescriptor, + media_stream_RequestSample +}; + +/* creates a stub stream */ +static HRESULT new_media_stream(struct media_source *source, GstPad *pad, struct media_stream **out_stream) +{ + struct media_stream *object = heap_alloc_zero(sizeof(*object)); + HRESULT hr; + + TRACE("(%p %p)->(%p)\n", source, pad, out_stream); + + object->IMFMediaStream_iface.lpVtbl = &media_stream_vtbl; + object->ref = 1; + + IMFMediaSource_AddRef(&source->IMFMediaSource_iface); + object->parent_source = source; + object->their_src = pad; + + object->state = STREAM_STUB; + + if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) + goto fail; + + if (!(object->appsink = gst_element_factory_make("appsink", NULL))) + { + hr = E_OUTOFMEMORY; + goto fail; + } + gst_bin_add(GST_BIN(object->parent_source->container), object->appsink); + + g_object_set(object->appsink, "sync", FALSE, NULL); + g_object_set(object->appsink, "max-buffers", 5, NULL); + + object->my_sink = gst_element_get_static_pad(object->appsink, "sink"); + gst_pad_set_element_private(object->my_sink, object); + + gst_pad_link(object->their_src, object->my_sink); + + gst_element_sync_state_with_parent(object->appsink); + + TRACE("->(%p)\n", object); + *out_stream = object; + + return S_OK; + +fail: + WARN("Failed to construct media stream, hr %#x.\n", hr); + + IMFMediaStream_Release(&object->IMFMediaStream_iface); + return hr; +} + static HRESULT WINAPI media_source_QueryInterface(IMFMediaSource *iface, REFIID riid, void **out) { struct media_source *source = impl_from_IMFMediaSource(iface); @@ -333,6 +596,7 @@ static HRESULT WINAPI media_source_Pause(IMFMediaSource *iface) static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) { struct media_source *source = impl_from_IMFMediaSource(iface); + unsigned int i;
TRACE("(%p)\n", source);
@@ -341,13 +605,34 @@ static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface)
source->state = SOURCE_SHUTDOWN;
+ if (source->container) + { + gst_element_set_state(source->container, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(source->container)); + } + if (source->my_src) gst_object_unref(GST_OBJECT(source->my_src)); + if (source->their_sink) + gst_object_unref(GST_OBJECT(source->their_sink)); + if (source->event_queue) IMFMediaEventQueue_Shutdown(source->event_queue); if (source->byte_stream) IMFByteStream_Release(source->byte_stream);
+ for (i = 0; i < source->stream_count; i++) + { + source->streams[i]->state = STREAM_SHUTDOWN; + IMFMediaStream_Release(&source->streams[i]->IMFMediaStream_iface); + } + + if (source->stream_count) + heap_free(source->streams); + + if (source->all_streams_event) + CloseHandle(source->all_streams_event); + return S_OK; }
@@ -368,6 +653,50 @@ static const IMFMediaSourceVtbl IMFMediaSource_vtbl = media_source_Shutdown, };
+static void stream_added(GstElement *element, GstPad *pad, gpointer user) +{ + struct media_source *source = user; + struct media_stream **new_stream_array; + struct media_stream *stream; + + if (gst_pad_get_direction(pad) != GST_PAD_SRC) + return; + + if (FAILED(new_media_stream(source, pad, &stream))) + return; + + if (!(new_stream_array = heap_realloc(source->streams, (source->stream_count + 1) * (sizeof(*new_stream_array))))) + { + ERR("Failed to add stream to source\n"); + IMFMediaStream_Release(&stream->IMFMediaStream_iface); + return; + } + + source->streams = new_stream_array; + source->streams[source->stream_count++] = stream; +} + +static void stream_removed(GstElement *element, GstPad *pad, gpointer user) +{ + struct media_source *source = user; + unsigned int i; + + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + if (stream->their_src != pad) + continue; + stream->their_src = NULL; + } +} + +static void no_more_pads(GstElement *element, gpointer user) +{ + struct media_source *source = user; + + SetEvent(source->all_streams_event); +} + static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_source **out_media_source) { GstStaticPadTemplate src_template = @@ -375,6 +704,7 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_
struct media_source *object = heap_alloc_zero(sizeof(*object)); HRESULT hr; + int ret;
if (!object) return E_OUTOFMEMORY; @@ -383,10 +713,16 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_ object->ref = 1; object->byte_stream = bytestream; IMFByteStream_AddRef(bytestream); + object->all_streams_event = CreateEventA(NULL, FALSE, FALSE, NULL);
if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) goto fail;
+ object->container = gst_bin_new(NULL); + object->bus = gst_bus_new(); + gst_bus_set_sync_handler(object->bus, mf_src_bus_watch_wrapper, object, NULL); + gst_element_set_bus(object->container, object->bus); + object->my_src = gst_pad_new_from_static_template(&src_template, "mf-src"); gst_pad_set_element_private(object->my_src, object); gst_pad_set_getrange_function(object->my_src, bytestream_wrapper_pull_wrapper); @@ -394,6 +730,49 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_ gst_pad_set_activatemode_function(object->my_src, bytestream_pad_mode_activate_wrapper); gst_pad_set_event_function(object->my_src, bytestream_pad_event_process_wrapper);
+ if (!(object->decodebin = gst_element_factory_make("decodebin", NULL))) + { + WARN("Failed to create decodebin for source\n"); + hr = E_OUTOFMEMORY; + goto fail; + } + + /* In Media Foundation, sources will infinitely leak buffers, when a subset of the selected + streams are read from. This behavior is relied upon in the Unity3D engine game, Trailmakers, + where Unity selects both the video and audio streams, yet only reads from the video stream. + Removing these buffering limits reflects that behavior. */ + g_object_set(object->decodebin, "max-size-buffers", 0, NULL); + g_object_set(object->decodebin, "max-size-time", G_GUINT64_CONSTANT(0), NULL); + g_object_set(object->decodebin, "max-size-bytes", 0, NULL); + + gst_bin_add(GST_BIN(object->container), object->decodebin); + + g_signal_connect(object->decodebin, "pad-added", G_CALLBACK(mf_src_stream_added_wrapper), object); + g_signal_connect(object->decodebin, "pad-removed", G_CALLBACK(mf_src_stream_removed_wrapper), object); + g_signal_connect(object->decodebin, "no-more-pads", G_CALLBACK(mf_src_no_more_pads_wrapper), object); + + object->their_sink = gst_element_get_static_pad(object->decodebin, "sink"); + + if ((ret = gst_pad_link(object->my_src, object->their_sink)) < 0) + { + WARN("Failed to link our bytestream pad to the demuxer input\n"); + hr = E_OUTOFMEMORY; + goto fail; + } + + object->state = SOURCE_OPENING; + + gst_element_set_state(object->container, GST_STATE_PAUSED); + ret = gst_element_get_state(object->container, NULL, NULL, -1); + if (ret == GST_STATE_CHANGE_FAILURE) + { + ERR("Failed to play source.\n"); + hr = E_OUTOFMEMORY; + goto fail; + } + + WaitForSingleObject(object->all_streams_event, INFINITE); + object->state = SOURCE_STOPPED;
*out_media_source = object; @@ -894,6 +1273,30 @@ void perform_cb_media_source(struct cb_data *cbdata) cbdata->u.event_src_data.ret = bytestream_pad_event_process(data->pad, data->parent, data->event); break; } + case MF_SRC_BUS_WATCH: + { + struct watch_bus_data *data = &cbdata->u.watch_bus_data; + cbdata->u.watch_bus_data.ret = bus_watch(data->bus, data->msg, data->user); + break; + } + case MF_SRC_STREAM_ADDED: + { + struct pad_added_data *data = &cbdata->u.pad_added_data; + stream_added(data->element, data->pad, data->user); + break; + } + case MF_SRC_STREAM_REMOVED: + { + struct pad_removed_data *data = &cbdata->u.pad_removed_data; + stream_removed(data->element, data->pad, data->user); + break; + } + case MF_SRC_NO_MORE_PADS: + { + struct no_more_pads_data *data = &cbdata->u.no_more_pads_data; + no_more_pads(data->element, data->user); + break; + } default: { assert(0);
On 9/21/20 2:01 PM, Derek Lesho wrote:
Signed-off-by: Derek Lesho dlesho@codeweavers.com
dlls/winegstreamer/gst_cbs.c | 45 ++++ dlls/winegstreamer/gst_cbs.h | 8 + dlls/winegstreamer/media_source.c | 405 +++++++++++++++++++++++++++++- 3 files changed, 457 insertions(+), 1 deletion(-)
diff --git a/dlls/winegstreamer/gst_cbs.c b/dlls/winegstreamer/gst_cbs.c index 12b53bc5d68..51aaefa911d 100644 --- a/dlls/winegstreamer/gst_cbs.c +++ b/dlls/winegstreamer/gst_cbs.c @@ -359,3 +359,48 @@ gboolean bytestream_pad_event_process_wrapper(GstPad *pad, GstObject *parent, Gs
return cbdata.u.event_src_data.ret;
}
+GstBusSyncReply mf_src_bus_watch_wrapper(GstBus *bus, GstMessage *message, gpointer user) +{
- struct cb_data cbdata = { MF_SRC_BUS_WATCH };
- cbdata.u.watch_bus_data.bus = bus;
- cbdata.u.watch_bus_data.msg = message;
- cbdata.u.watch_bus_data.user = user;
- call_cb(&cbdata);
- return cbdata.u.watch_bus_data.ret;
+}
+void mf_src_stream_added_wrapper(GstElement *bin, GstPad *pad, gpointer user) +{
- struct cb_data cbdata = { MF_SRC_STREAM_ADDED };
- cbdata.u.pad_added_data.element = bin;
- cbdata.u.pad_added_data.pad = pad;
- cbdata.u.pad_added_data.user = user;
- call_cb(&cbdata);
+}
+void mf_src_stream_removed_wrapper(GstElement *element, GstPad *pad, gpointer user) +{
- struct cb_data cbdata = { MF_SRC_STREAM_REMOVED };
- cbdata.u.pad_removed_data.element = element;
- cbdata.u.pad_removed_data.pad = pad;
- cbdata.u.pad_removed_data.user = user;
- call_cb(&cbdata);
+}
+void mf_src_no_more_pads_wrapper(GstElement *element, gpointer user) +{
- struct cb_data cbdata = { MF_SRC_NO_MORE_PADS };
- cbdata.u.no_more_pads_data.element = element;
- cbdata.u.no_more_pads_data.user = user;
- call_cb(&cbdata);
+} diff --git a/dlls/winegstreamer/gst_cbs.h b/dlls/winegstreamer/gst_cbs.h index 3459a9ef8ee..a48999bbf71 100644 --- a/dlls/winegstreamer/gst_cbs.h +++ b/dlls/winegstreamer/gst_cbs.h @@ -48,6 +48,10 @@ enum CB_TYPE { BYTESTREAM_QUERY, BYTESTREAM_PAD_MODE_ACTIVATE, BYTESTREAM_PAD_EVENT_PROCESS,
- MF_SRC_BUS_WATCH,
- MF_SRC_STREAM_ADDED,
- MF_SRC_STREAM_REMOVED,
- MF_SRC_NO_MORE_PADS, MEDIA_SOURCE_MAX,
};
@@ -164,5 +168,9 @@ GstFlowReturn bytestream_wrapper_pull_wrapper(GstPad *pad, GstObject *parent, gu gboolean bytestream_query_wrapper(GstPad *pad, GstObject *parent, GstQuery *query) DECLSPEC_HIDDEN; gboolean bytestream_pad_mode_activate_wrapper(GstPad *pad, GstObject *parent, GstPadMode mode, gboolean activate) DECLSPEC_HIDDEN; gboolean bytestream_pad_event_process_wrapper(GstPad *pad, GstObject *parent, GstEvent *event) DECLSPEC_HIDDEN; +GstBusSyncReply mf_src_bus_watch_wrapper(GstBus *bus, GstMessage *message, gpointer user) DECLSPEC_HIDDEN; +void mf_src_stream_added_wrapper(GstElement *bin, GstPad *pad, gpointer user) DECLSPEC_HIDDEN; +void mf_src_stream_removed_wrapper(GstElement *element, GstPad *pad, gpointer user) DECLSPEC_HIDDEN; +void mf_src_no_more_pads_wrapper(GstElement *element, gpointer user) DECLSPEC_HIDDEN;
#endif diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index a061443d893..462a646ee4f 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -24,6 +24,7 @@ #include "gst_private.h" #include "gst_cbs.h"
+#include <assert.h> #include <stdarg.h> #include <assert.h>
@@ -41,21 +42,47 @@
WINE_DEFAULT_DEBUG_CHANNEL(mfplat);
+struct media_stream +{
- IMFMediaStream IMFMediaStream_iface;
- LONG ref;
- struct media_source *parent_source;
- IMFMediaEventQueue *event_queue;
- GstElement *appsink;
- GstPad *their_src, *my_sink;
- enum
- {
STREAM_STUB,
STREAM_SHUTDOWN,
- } state;
+};
struct media_source { IMFMediaSource IMFMediaSource_iface; LONG ref; IMFMediaEventQueue *event_queue; IMFByteStream *byte_stream;
- GstPad *my_src;
- struct media_stream **streams;
- ULONG stream_count;
- GstBus *bus;
- GstElement *container;
- GstElement *decodebin;
- GstPad *my_src, *their_sink; enum { SOURCE_OPENING, SOURCE_STOPPED, SOURCE_SHUTDOWN, } state;
- HANDLE all_streams_event;
};
+static inline struct media_stream *impl_from_IMFMediaStream(IMFMediaStream *iface) +{
- return CONTAINING_RECORD(iface, struct media_stream, IMFMediaStream_iface);
+}
static inline struct media_source *impl_from_IMFMediaSource(IMFMediaSource *iface) { return CONTAINING_RECORD(iface, struct media_source, IMFMediaSource_iface); @@ -182,6 +209,242 @@ static gboolean bytestream_pad_event_process(GstPad *pad, GstObject *parent, Gst return TRUE; }
+GstBusSyncReply bus_watch(GstBus *bus, GstMessage *message, gpointer user) +{
- struct media_source *source = user;
- gchar *dbg_info = NULL;
- GError *err = NULL;
- TRACE("source %p message type %s\n", source, GST_MESSAGE_TYPE_NAME(message));
- switch (message->type)
- {
case GST_MESSAGE_ERROR:
gst_message_parse_error(message, &err, &dbg_info);
ERR("%s: %s\n", GST_OBJECT_NAME(message->src), err->message);
ERR("%s\n", dbg_info);
g_error_free(err);
g_free(dbg_info);
break;
case GST_MESSAGE_WARNING:
gst_message_parse_warning(message, &err, &dbg_info);
WARN("%s: %s\n", GST_OBJECT_NAME(message->src), err->message);
WARN("%s\n", dbg_info);
g_error_free(err);
g_free(dbg_info);
break;
default:
break;
- }
- gst_message_unref(message);
- return GST_BUS_DROP;
+}
+static HRESULT WINAPI media_stream_QueryInterface(IMFMediaStream *iface, REFIID riid, void **out) +{
- struct media_stream *stream = impl_from_IMFMediaStream(iface);
- TRACE("(%p)->(%s %p)\n", stream, debugstr_guid(riid), out);
- if (IsEqualIID(riid, &IID_IMFMediaStream) ||
IsEqualIID(riid, &IID_IMFMediaEventGenerator) ||
IsEqualIID(riid, &IID_IUnknown))
- {
*out = &stream->IMFMediaStream_iface;
- }
- else
- {
FIXME("(%s, %p)\n", debugstr_guid(riid), out);
*out = NULL;
return E_NOINTERFACE;
- }
- IUnknown_AddRef((IUnknown*)*out);
- return S_OK;
+}
+static ULONG WINAPI media_stream_AddRef(IMFMediaStream *iface) +{
- struct media_stream *stream = impl_from_IMFMediaStream(iface);
- ULONG ref = InterlockedIncrement(&stream->ref);
- TRACE("(%p) ref=%u\n", stream, ref);
- return ref;
+}
+static ULONG WINAPI media_stream_Release(IMFMediaStream *iface) +{
- struct media_stream *stream = impl_from_IMFMediaStream(iface);
- ULONG ref = InterlockedDecrement(&stream->ref);
- TRACE("(%p) ref=%u\n", stream, ref);
- if (!ref)
- {
if (stream->my_sink)
gst_object_unref(GST_OBJECT(stream->my_sink));
if (stream->event_queue)
IMFMediaEventQueue_Release(stream->event_queue);
if (stream->parent_source)
IMFMediaSource_Release(&stream->parent_source->IMFMediaSource_iface);
heap_free(stream);
- }
- return ref;
+}
+static HRESULT WINAPI media_stream_GetEvent(IMFMediaStream *iface, DWORD flags, IMFMediaEvent **event) +{
- struct media_stream *stream = impl_from_IMFMediaStream(iface);
- TRACE("(%p)->(%#x, %p)\n", stream, flags, event);
- if (stream->state == STREAM_SHUTDOWN)
return MF_E_SHUTDOWN;
- return IMFMediaEventQueue_GetEvent(stream->event_queue, flags, event);
+}
+static HRESULT WINAPI media_stream_BeginGetEvent(IMFMediaStream *iface, IMFAsyncCallback *callback, IUnknown *state) +{
- struct media_stream *stream = impl_from_IMFMediaStream(iface);
- TRACE("(%p)->(%p, %p)\n", stream, callback, state);
- if (stream->state == STREAM_SHUTDOWN)
return MF_E_SHUTDOWN;
- return IMFMediaEventQueue_BeginGetEvent(stream->event_queue, callback, state);
+}
+static HRESULT WINAPI media_stream_EndGetEvent(IMFMediaStream *iface, IMFAsyncResult *result, IMFMediaEvent **event) +{
- struct media_stream *stream = impl_from_IMFMediaStream(iface);
- TRACE("(%p)->(%p, %p)\n", stream, result, event);
- if (stream->state == STREAM_SHUTDOWN)
return MF_E_SHUTDOWN;
- return IMFMediaEventQueue_EndGetEvent(stream->event_queue, result, event);
+}
+static HRESULT WINAPI media_stream_QueueEvent(IMFMediaStream *iface, MediaEventType event_type, REFGUID ext_type,
HRESULT hr, const PROPVARIANT *value)
+{
- struct media_stream *stream = impl_from_IMFMediaStream(iface);
- TRACE("(%p)->(%d, %s, %#x, %p)\n", stream, event_type, debugstr_guid(ext_type), hr, value);
- if (stream->state == STREAM_SHUTDOWN)
return MF_E_SHUTDOWN;
- return IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, event_type, ext_type, hr, value);
+}
+static HRESULT WINAPI media_stream_GetMediaSource(IMFMediaStream *iface, IMFMediaSource **source) +{
- struct media_stream *stream = impl_from_IMFMediaStream(iface);
- FIXME("stub (%p)->(%p)\n", stream, source);
- if (stream->state == STREAM_SHUTDOWN)
return MF_E_SHUTDOWN;
- return E_NOTIMPL;
+}
+static HRESULT WINAPI media_stream_GetStreamDescriptor(IMFMediaStream* iface, IMFStreamDescriptor **descriptor) +{
- struct media_stream *stream = impl_from_IMFMediaStream(iface);
- TRACE("(%p)->(%p)\n", stream, descriptor);
- if (stream->state == STREAM_SHUTDOWN)
return MF_E_SHUTDOWN;
- return E_NOTIMPL;
+}
+static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token) +{
- struct media_stream *stream = impl_from_IMFMediaStream(iface);
- TRACE("(%p)->(%p)\n", iface, token);
- if (stream->state == STREAM_SHUTDOWN)
return MF_E_SHUTDOWN;
- return E_NOTIMPL;
+}
+static const IMFMediaStreamVtbl media_stream_vtbl = +{
- media_stream_QueryInterface,
- media_stream_AddRef,
- media_stream_Release,
- media_stream_GetEvent,
- media_stream_BeginGetEvent,
- media_stream_EndGetEvent,
- media_stream_QueueEvent,
- media_stream_GetMediaSource,
- media_stream_GetStreamDescriptor,
- media_stream_RequestSample
+};
+/* creates a stub stream */ +static HRESULT new_media_stream(struct media_source *source, GstPad *pad, struct media_stream **out_stream) +{
- struct media_stream *object = heap_alloc_zero(sizeof(*object));
- HRESULT hr;
- TRACE("(%p %p)->(%p)\n", source, pad, out_stream);
- object->IMFMediaStream_iface.lpVtbl = &media_stream_vtbl;
- object->ref = 1;
- IMFMediaSource_AddRef(&source->IMFMediaSource_iface);
- object->parent_source = source;
- object->their_src = pad;
- object->state = STREAM_STUB;
- if (FAILED(hr = MFCreateEventQueue(&object->event_queue)))
goto fail;
- if (!(object->appsink = gst_element_factory_make("appsink", NULL)))
- {
hr = E_OUTOFMEMORY;
goto fail;
- }
- gst_bin_add(GST_BIN(object->parent_source->container), object->appsink);
- g_object_set(object->appsink, "sync", FALSE, NULL);
- g_object_set(object->appsink, "max-buffers", 5, NULL);
- object->my_sink = gst_element_get_static_pad(object->appsink, "sink");
- gst_pad_set_element_private(object->my_sink, object);
- gst_pad_link(object->their_src, object->my_sink);
- gst_element_sync_state_with_parent(object->appsink);
- TRACE("->(%p)\n", object);
- *out_stream = object;
- return S_OK;
+fail:
- WARN("Failed to construct media stream, hr %#x.\n", hr);
- IMFMediaStream_Release(&object->IMFMediaStream_iface);
- return hr;
+}
static HRESULT WINAPI media_source_QueryInterface(IMFMediaSource *iface, REFIID riid, void **out) { struct media_source *source = impl_from_IMFMediaSource(iface); @@ -333,6 +596,7 @@ static HRESULT WINAPI media_source_Pause(IMFMediaSource *iface) static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface) { struct media_source *source = impl_from_IMFMediaSource(iface);
unsigned int i;
TRACE("(%p)\n", source);
@@ -341,13 +605,34 @@ static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface)
source->state = SOURCE_SHUTDOWN;
if (source->container)
{
gst_element_set_state(source->container, GST_STATE_NULL);
gst_object_unref(GST_OBJECT(source->container));
}
if (source->my_src) gst_object_unref(GST_OBJECT(source->my_src));
if (source->their_sink)
gst_object_unref(GST_OBJECT(source->their_sink));
if (source->event_queue) IMFMediaEventQueue_Shutdown(source->event_queue); if (source->byte_stream) IMFByteStream_Release(source->byte_stream);
for (i = 0; i < source->stream_count; i++)
{
source->streams[i]->state = STREAM_SHUTDOWN;
IMFMediaStream_Release(&source->streams[i]->IMFMediaStream_iface);
}
if (source->stream_count)
heap_free(source->streams);
if (source->all_streams_event)
CloseHandle(source->all_streams_event);
return S_OK;
}
@@ -368,6 +653,50 @@ static const IMFMediaSourceVtbl IMFMediaSource_vtbl = media_source_Shutdown, };
+static void stream_added(GstElement *element, GstPad *pad, gpointer user) +{
- struct media_source *source = user;
- struct media_stream **new_stream_array;
- struct media_stream *stream;
- if (gst_pad_get_direction(pad) != GST_PAD_SRC)
return;
- if (FAILED(new_media_stream(source, pad, &stream)))
return;
- if (!(new_stream_array = heap_realloc(source->streams, (source->stream_count + 1) * (sizeof(*new_stream_array)))))
- {
ERR("Failed to add stream to source\n");
IMFMediaStream_Release(&stream->IMFMediaStream_iface);
return;
- }
- source->streams = new_stream_array;
- source->streams[source->stream_count++] = stream;
+}
+static void stream_removed(GstElement *element, GstPad *pad, gpointer user) +{
- struct media_source *source = user;
- unsigned int i;
- for (i = 0; i < source->stream_count; i++)
- {
struct media_stream *stream = source->streams[i];
if (stream->their_src != pad)
continue;
stream->their_src = NULL;
- }
+}
+static void no_more_pads(GstElement *element, gpointer user) +{
- struct media_source *source = user;
- SetEvent(source->all_streams_event);
+}
Renaming the event too would be great, for consistency.
static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_source **out_media_source) { GstStaticPadTemplate src_template = @@ -375,6 +704,7 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_
struct media_source *object = heap_alloc_zero(sizeof(*object)); HRESULT hr;
int ret;
if (!object) return E_OUTOFMEMORY;
@@ -383,10 +713,16 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_ object->ref = 1; object->byte_stream = bytestream; IMFByteStream_AddRef(bytestream);
object->all_streams_event = CreateEventA(NULL, FALSE, FALSE, NULL);
if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) goto fail;
object->container = gst_bin_new(NULL);
object->bus = gst_bus_new();
gst_bus_set_sync_handler(object->bus, mf_src_bus_watch_wrapper, object, NULL);
gst_element_set_bus(object->container, object->bus);
object->my_src = gst_pad_new_from_static_template(&src_template, "mf-src"); gst_pad_set_element_private(object->my_src, object); gst_pad_set_getrange_function(object->my_src, bytestream_wrapper_pull_wrapper);
@@ -394,6 +730,49 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_ gst_pad_set_activatemode_function(object->my_src, bytestream_pad_mode_activate_wrapper); gst_pad_set_event_function(object->my_src, bytestream_pad_event_process_wrapper);
- if (!(object->decodebin = gst_element_factory_make("decodebin", NULL)))
- {
WARN("Failed to create decodebin for source\n");
hr = E_OUTOFMEMORY;
goto fail;
- }
- /* In Media Foundation, sources will infinitely leak buffers, when a subset of the selected
streams are read from. This behavior is relied upon in the Unity3D engine game, Trailmakers,
where Unity selects both the video and audio streams, yet only reads from the video stream.
Removing these buffering limits reflects that behavior. */
This is a bit misleadingly worded, I think; it implies that the game relies on buffers being leaked, when it really relies on the ability to read the entirety of one stream without ever reading the other, but also without deselecting it. The fact that we leak memory while doing so is just an unfortunate side effect, which is acceptable because Windows does as well. I.e. if all GStreamer elements read each stream with a different thread (as I think some do, though I can't remember which if any) the game would also be satisfied, and wouldn't need infinite buffering. Since they don't [and in many cases can't], we need to allow the element to buffer forever.
- g_object_set(object->decodebin, "max-size-buffers", 0, NULL);
- g_object_set(object->decodebin, "max-size-time", G_GUINT64_CONSTANT(0), NULL);
- g_object_set(object->decodebin, "max-size-bytes", 0, NULL);
- gst_bin_add(GST_BIN(object->container), object->decodebin);
- g_signal_connect(object->decodebin, "pad-added", G_CALLBACK(mf_src_stream_added_wrapper), object);
- g_signal_connect(object->decodebin, "pad-removed", G_CALLBACK(mf_src_stream_removed_wrapper), object);
- g_signal_connect(object->decodebin, "no-more-pads", G_CALLBACK(mf_src_no_more_pads_wrapper), object);
- object->their_sink = gst_element_get_static_pad(object->decodebin, "sink");
- if ((ret = gst_pad_link(object->my_src, object->their_sink)) < 0)
- {
WARN("Failed to link our bytestream pad to the demuxer input\n");
hr = E_OUTOFMEMORY;
Printing the actual return code is generally a good idea.
(E_OUTOFMEMORY is also not exactly great, since it doesn't correspond to any element of GstPadLinkReturn, but that's not particularly important.)
goto fail;
- }
- object->state = SOURCE_OPENING;
- gst_element_set_state(object->container, GST_STATE_PAUSED);
- ret = gst_element_get_state(object->container, NULL, NULL, -1);
- if (ret == GST_STATE_CHANGE_FAILURE)
- {
ERR("Failed to play source.\n");
Same here.
hr = E_OUTOFMEMORY;
goto fail;
}
WaitForSingleObject(object->all_streams_event, INFINITE);
object->state = SOURCE_STOPPED;
*out_media_source = object;
@@ -894,6 +1273,30 @@ void perform_cb_media_source(struct cb_data *cbdata) cbdata->u.event_src_data.ret = bytestream_pad_event_process(data->pad, data->parent, data->event); break; }
- case MF_SRC_BUS_WATCH:
{
struct watch_bus_data *data = &cbdata->u.watch_bus_data;
cbdata->u.watch_bus_data.ret = bus_watch(data->bus, data->msg, data->user);
break;
}
- case MF_SRC_STREAM_ADDED:
{
struct pad_added_data *data = &cbdata->u.pad_added_data;
stream_added(data->element, data->pad, data->user);
break;
}
- case MF_SRC_STREAM_REMOVED:
{
struct pad_removed_data *data = &cbdata->u.pad_removed_data;
stream_removed(data->element, data->pad, data->user);
break;
}
- case MF_SRC_NO_MORE_PADS:
{
struct no_more_pads_data *data = &cbdata->u.no_more_pads_data;
no_more_pads(data->element, data->user);
break;
default: { assert(0);}
On 9/24/20 8:05 PM, Zebediah Figura wrote:
- /* In Media Foundation, sources will infinitely leak buffers, when a subset of the selected
streams are read from. This behavior is relied upon in the Unity3D engine game, Trailmakers,
where Unity selects both the video and audio streams, yet only reads from the video stream.
Removing these buffering limits reflects that behavior. */
This is a bit misleadingly worded, I think; it implies that the game relies on buffers being leaked, when it really relies on the ability to read the entirety of one stream without ever reading the other, but also without deselecting it. The fact that we leak memory while doing so is just an unfortunate side effect, which is acceptable because Windows does as well. I.e. if all GStreamer elements read each stream with a different thread (as I think some do, though I can't remember which if any) the game would also be satisfied, and wouldn't need infinite buffering. Since they don't [and in many cases can't], we need to allow the element to buffer forever.
It's not really a leak. Nothing gets lost, and there is no reason not to discard those pending samples on Stop or similar.
Signed-off-by: Derek Lesho dlesho@codeweavers.com --- v3: Use pull-preroll and gst_pad_get_current_caps to establish media types, instead of a pad probe. --- dlls/winegstreamer/gst_private.h | 4 + dlls/winegstreamer/media_source.c | 75 +++++++++++++- dlls/winegstreamer/mfplat.c | 166 ++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+), 5 deletions(-)
diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index ef07d3591e7..60b38a48f5a 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -36,6 +36,7 @@ #include "winuser.h" #include "dshow.h" #include "strmif.h" +#include "mfobjects.h" #include "wine/heap.h" #include "wine/strmbase.h"
@@ -54,6 +55,9 @@ void start_dispatch_thread(void) DECLSPEC_HIDDEN;
extern HRESULT mfplat_get_class_object(REFCLSID rclsid, REFIID riid, void **obj) DECLSPEC_HIDDEN;
+HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj) DECLSPEC_HIDDEN; +IMFMediaType *mf_media_type_from_caps(const GstCaps *caps) DECLSPEC_HIDDEN; + HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj) DECLSPEC_HIDDEN;
#endif /* __GST_PRIVATE_INCLUDED__ */ diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 462a646ee4f..a20b080d2a5 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -48,13 +48,17 @@ struct media_stream LONG ref; struct media_source *parent_source; IMFMediaEventQueue *event_queue; + IMFStreamDescriptor *descriptor; GstElement *appsink; GstPad *their_src, *my_sink; enum { STREAM_STUB, + STREAM_INACTIVE, STREAM_SHUTDOWN, } state; + /* used when in STUB state: */ + DWORD stream_id; };
struct media_source @@ -286,6 +290,8 @@ static ULONG WINAPI media_stream_Release(IMFMediaStream *iface) { if (stream->my_sink) gst_object_unref(GST_OBJECT(stream->my_sink)); + if (stream->descriptor) + IMFStreamDescriptor_Release(stream->descriptor); if (stream->event_queue) IMFMediaEventQueue_Release(stream->event_queue); if (stream->parent_source) @@ -367,7 +373,10 @@ static HRESULT WINAPI media_stream_GetStreamDescriptor(IMFMediaStream* iface, IM if (stream->state == STREAM_SHUTDOWN) return MF_E_SHUTDOWN;
- return E_NOTIMPL; + IMFStreamDescriptor_AddRef(stream->descriptor); + *descriptor = stream->descriptor; + + return S_OK; }
static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token) @@ -397,7 +406,7 @@ static const IMFMediaStreamVtbl media_stream_vtbl = };
/* creates a stub stream */ -static HRESULT new_media_stream(struct media_source *source, GstPad *pad, struct media_stream **out_stream) +static HRESULT new_media_stream(struct media_source *source, GstPad *pad, DWORD stream_id, struct media_stream **out_stream) { struct media_stream *object = heap_alloc_zero(sizeof(*object)); HRESULT hr; @@ -410,6 +419,7 @@ static HRESULT new_media_stream(struct media_source *source, GstPad *pad, struct IMFMediaSource_AddRef(&source->IMFMediaSource_iface); object->parent_source = source; object->their_src = pad; + object->stream_id = stream_id;
object->state = STREAM_STUB;
@@ -427,8 +437,6 @@ static HRESULT new_media_stream(struct media_source *source, GstPad *pad, struct g_object_set(object->appsink, "max-buffers", 5, NULL);
object->my_sink = gst_element_get_static_pad(object->appsink, "sink"); - gst_pad_set_element_private(object->my_sink, object); - gst_pad_link(object->their_src, object->my_sink);
gst_element_sync_state_with_parent(object->appsink); @@ -445,6 +453,47 @@ fail: return hr; }
+static HRESULT media_stream_init_desc(struct media_stream *stream) +{ + GstCaps *current_caps = gst_pad_get_current_caps(stream->their_src); + IMFMediaTypeHandler *type_handler; + IMFMediaType *stream_type = NULL; + HRESULT hr; + + if (!current_caps) + { + hr = E_FAIL; + goto fail; + } + + stream_type = mf_media_type_from_caps(current_caps); + gst_caps_unref(current_caps); + if (!stream_type) + { + hr = E_FAIL; + goto fail; + } + + if (FAILED(hr = MFCreateStreamDescriptor(stream->stream_id, 1, &stream_type, &stream->descriptor))) + goto fail; + + if (FAILED(hr = IMFStreamDescriptor_GetMediaTypeHandler(stream->descriptor, &type_handler))) + goto fail; + + if (FAILED(hr = IMFMediaTypeHandler_SetCurrentMediaType(type_handler, stream_type))) + goto fail; + + IMFMediaTypeHandler_Release(type_handler); + + stream->state = STREAM_INACTIVE; + + return S_OK; + fail: + if (type_handler) + IMFMediaTypeHandler_Release(type_handler); + return hr; +} + static HRESULT WINAPI media_source_QueryInterface(IMFMediaSource *iface, REFIID riid, void **out) { struct media_source *source = impl_from_IMFMediaSource(iface); @@ -662,7 +711,7 @@ static void stream_added(GstElement *element, GstPad *pad, gpointer user) if (gst_pad_get_direction(pad) != GST_PAD_SRC) return;
- if (FAILED(new_media_stream(source, pad, &stream))) + if (FAILED(new_media_stream(source, pad, source->stream_count, &stream))) return;
if (!(new_stream_array = heap_realloc(source->streams, (source->stream_count + 1) * (sizeof(*new_stream_array))))) @@ -687,6 +736,8 @@ static void stream_removed(GstElement *element, GstPad *pad, gpointer user) if (stream->their_src != pad) continue; stream->their_src = NULL; + if (stream->state != STREAM_INACTIVE) + stream->state = STREAM_INACTIVE; } }
@@ -703,6 +754,7 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_ GST_STATIC_PAD_TEMPLATE("mf_src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
struct media_source *object = heap_alloc_zero(sizeof(*object)); + unsigned int i; HRESULT hr; int ret;
@@ -772,6 +824,19 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_ }
WaitForSingleObject(object->all_streams_event, INFINITE); + for (i = 0; i < object->stream_count; i++) + { + GstSample *preroll; + g_signal_emit_by_name(object->streams[i]->appsink, "pull-preroll", &preroll); + hr = E_FAIL; + if (!preroll || FAILED(hr = media_stream_init_desc(object->streams[i]))) + { + ERR("Failed to finish initialization of media stream %p, hr %x.\n", object->streams[i], hr); + IMFMediaStream_Release(&object->streams[i]->IMFMediaStream_iface); + goto fail; + } + gst_sample_unref(preroll); + }
object->state = SOURCE_STOPPED;
diff --git a/dlls/winegstreamer/mfplat.c b/dlls/winegstreamer/mfplat.c index c996f06211e..2c2216f94b6 100644 --- a/dlls/winegstreamer/mfplat.c +++ b/dlls/winegstreamer/mfplat.c @@ -16,6 +16,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include "config.h" +#include <gst/gst.h> + +#include "gst_private.h" + #include <stdarg.h>
#include "gst_private.h" @@ -436,3 +441,164 @@ HRESULT mfplat_get_class_object(REFCLSID rclsid, REFIID riid, void **obj)
return CLASS_E_CLASSNOTAVAILABLE; } + +static const struct +{ + const GUID *subtype; + GstVideoFormat format; +} +uncompressed_video_formats[] = +{ + {&MFVideoFormat_ARGB32, GST_VIDEO_FORMAT_BGRA}, + {&MFVideoFormat_RGB32, GST_VIDEO_FORMAT_BGRx}, + {&MFVideoFormat_RGB24, GST_VIDEO_FORMAT_BGR}, + {&MFVideoFormat_RGB565, GST_VIDEO_FORMAT_BGR16}, + {&MFVideoFormat_RGB555, GST_VIDEO_FORMAT_BGR15}, +}; + +/* returns NULL if doesn't match exactly */ +IMFMediaType *mf_media_type_from_caps(const GstCaps *caps) +{ + IMFMediaType *media_type; + GstStructure *info; + const char *mime_type; + + if (TRACE_ON(mfplat)) + { + gchar *human_readable = gst_caps_to_string(caps); + TRACE("caps = %s\n", debugstr_a(human_readable)); + g_free(human_readable); + } + + if (FAILED(MFCreateMediaType(&media_type))) + return NULL; + + info = gst_caps_get_structure(caps, 0); + mime_type = gst_structure_get_name(info); + + if (!strncmp(mime_type, "video", 5)) + { + GstVideoInfo video_info; + + if (!gst_video_info_from_caps(&video_info, caps)) + { + return NULL; + } + + IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); + + IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_SIZE, ((UINT64)video_info.width << 32) | video_info.height); + + IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_RATE, ((UINT64)video_info.fps_n << 32) | video_info.fps_d); + + if (!strcmp(mime_type, "video/x-raw")) + { + GUID fourcc_subtype = MFVideoFormat_Base; + unsigned int i; + + IMFMediaType_SetUINT32(media_type, &MF_MT_COMPRESSED, FALSE); + + /* First try FOURCC */ + if ((fourcc_subtype.Data1 = gst_video_format_to_fourcc(video_info.finfo->format))) + { + IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &fourcc_subtype); + } + else + { + for (i = 0; i < ARRAY_SIZE(uncompressed_video_formats); i++) + { + if (uncompressed_video_formats[i].format == video_info.finfo->format) + { + IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, uncompressed_video_formats[i].subtype); + break; + } + } + if (i == ARRAY_SIZE(uncompressed_video_formats)) + { + FIXME("Unrecognized uncompressed video format %x\n", video_info.finfo->format); + IMFMediaType_Release(media_type); + return NULL; + } + } + } + else + { + FIXME("Unrecognized video format %s\n", mime_type); + return NULL; + } + } + else if (!strncmp(mime_type, "audio", 5)) + { + gint rate, channels, bitrate; + guint64 channel_mask; + IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio); + + if (gst_structure_get_int(info, "rate", &rate)) + IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, rate); + + if (gst_structure_get_int(info, "channels", &channels)) + IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_NUM_CHANNELS, channels); + + if (gst_structure_get(info, "channel-mask", GST_TYPE_BITMASK, &channel_mask, NULL)) + IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_CHANNEL_MASK, channel_mask); + + if (gst_structure_get_int(info, "bitrate", &bitrate)) + IMFMediaType_SetUINT32(media_type, &MF_MT_AVG_BITRATE, bitrate); + + if (!strcmp(mime_type, "audio/x-raw")) + { + GstAudioInfo audio_info; + + if (gst_audio_info_from_caps(&audio_info, caps)) + { + DWORD depth = GST_AUDIO_INFO_DEPTH(&audio_info); + + /* validation */ + if ((audio_info.finfo->flags & GST_AUDIO_FORMAT_FLAG_INTEGER && depth > 8) || + (audio_info.finfo->flags & GST_AUDIO_FORMAT_FLAG_SIGNED && depth <= 8) || + (audio_info.finfo->endianness != G_LITTLE_ENDIAN && depth > 8)) + { + IMFMediaType_Release(media_type); + return NULL; + } + + /* conversion */ + switch (audio_info.finfo->flags) + { + case GST_AUDIO_FORMAT_FLAG_FLOAT: + IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &MFAudioFormat_Float); + break; + case GST_AUDIO_FORMAT_FLAG_INTEGER: + case GST_AUDIO_FORMAT_FLAG_SIGNED: + IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &MFAudioFormat_PCM); + break; + default: + FIXME("Unrecognized audio format %x\n", audio_info.finfo->format); + IMFMediaType_Release(media_type); + return NULL; + } + + IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, depth); + } + else + { + ERR("Failed to get caps audio info\n"); + IMFMediaType_Release(media_type); + return NULL; + } + } + else + { + FIXME("Unrecognized audio format %s\n", mime_type); + IMFMediaType_Release(media_type); + return NULL; + } + } + else + { + IMFMediaType_Release(media_type); + return NULL; + } + + return media_type; +}
On 9/21/20 2:01 PM, Derek Lesho wrote:
Signed-off-by: Derek Lesho dlesho@codeweavers.com
v3: Use pull-preroll and gst_pad_get_current_caps to establish media types, instead of a pad probe.
dlls/winegstreamer/gst_private.h | 4 + dlls/winegstreamer/media_source.c | 75 +++++++++++++- dlls/winegstreamer/mfplat.c | 166 ++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+), 5 deletions(-)
diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index ef07d3591e7..60b38a48f5a 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -36,6 +36,7 @@ #include "winuser.h" #include "dshow.h" #include "strmif.h" +#include "mfobjects.h" #include "wine/heap.h" #include "wine/strmbase.h"
@@ -54,6 +55,9 @@ void start_dispatch_thread(void) DECLSPEC_HIDDEN;
extern HRESULT mfplat_get_class_object(REFCLSID rclsid, REFIID riid, void **obj) DECLSPEC_HIDDEN;
+HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj) DECLSPEC_HIDDEN; +IMFMediaType *mf_media_type_from_caps(const GstCaps *caps) DECLSPEC_HIDDEN;
HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj) DECLSPEC_HIDDEN;
#endif /* __GST_PRIVATE_INCLUDED__ */ diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 462a646ee4f..a20b080d2a5 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -48,13 +48,17 @@ struct media_stream LONG ref; struct media_source *parent_source; IMFMediaEventQueue *event_queue;
- IMFStreamDescriptor *descriptor; GstElement *appsink; GstPad *their_src, *my_sink; enum { STREAM_STUB,
STREAM_INACTIVE,
What's the difference between these two states? There doesn't seem to be a difference in this patch.
STREAM_SHUTDOWN, } state;
- /* used when in STUB state: */
- DWORD stream_id;
What's the point of this comment? Is the stream ID not valid otherwise?
};
struct media_source @@ -286,6 +290,8 @@ static ULONG WINAPI media_stream_Release(IMFMediaStream *iface) { if (stream->my_sink) gst_object_unref(GST_OBJECT(stream->my_sink));
if (stream->descriptor)
IMFStreamDescriptor_Release(stream->descriptor); if (stream->event_queue) IMFMediaEventQueue_Release(stream->event_queue); if (stream->parent_source)
@@ -367,7 +373,10 @@ static HRESULT WINAPI media_stream_GetStreamDescriptor(IMFMediaStream* iface, IM if (stream->state == STREAM_SHUTDOWN) return MF_E_SHUTDOWN;
- return E_NOTIMPL;
- IMFStreamDescriptor_AddRef(stream->descriptor);
- *descriptor = stream->descriptor;
- return S_OK;
}
static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token) @@ -397,7 +406,7 @@ static const IMFMediaStreamVtbl media_stream_vtbl = };
/* creates a stub stream */ -static HRESULT new_media_stream(struct media_source *source, GstPad *pad, struct media_stream **out_stream) +static HRESULT new_media_stream(struct media_source *source, GstPad *pad, DWORD stream_id, struct media_stream **out_stream) { struct media_stream *object = heap_alloc_zero(sizeof(*object)); HRESULT hr; @@ -410,6 +419,7 @@ static HRESULT new_media_stream(struct media_source *source, GstPad *pad, struct IMFMediaSource_AddRef(&source->IMFMediaSource_iface); object->parent_source = source; object->their_src = pad;
object->stream_id = stream_id;
object->state = STREAM_STUB;
@@ -427,8 +437,6 @@ static HRESULT new_media_stream(struct media_source *source, GstPad *pad, struct g_object_set(object->appsink, "max-buffers", 5, NULL);
object->my_sink = gst_element_get_static_pad(object->appsink, "sink");
gst_pad_set_element_private(object->my_sink, object);
gst_pad_link(object->their_src, object->my_sink);
gst_element_sync_state_with_parent(object->appsink);
@@ -445,6 +453,47 @@ fail: return hr; }
+static HRESULT media_stream_init_desc(struct media_stream *stream) +{
- GstCaps *current_caps = gst_pad_get_current_caps(stream->their_src);
- IMFMediaTypeHandler *type_handler;
- IMFMediaType *stream_type = NULL;
- HRESULT hr;
- if (!current_caps)
- {
hr = E_FAIL;
goto fail;
- }
Can that happen?
Note that this "goto" and a couple others below could be replaced with "return hr". In general I'm inclined to think goto isn't worthwhile when there's only one line of cleanup.
(Note also that the return before the goto is a little redundant as is; you release type_handler in both success and failure cases.)
- stream_type = mf_media_type_from_caps(current_caps);
- gst_caps_unref(current_caps);
- if (!stream_type)
- {
hr = E_FAIL;
goto fail;
- }
- if (FAILED(hr = MFCreateStreamDescriptor(stream->stream_id, 1, &stream_type, &stream->descriptor)))
goto fail;
- if (FAILED(hr = IMFStreamDescriptor_GetMediaTypeHandler(stream->descriptor, &type_handler)))
goto fail;
- if (FAILED(hr = IMFMediaTypeHandler_SetCurrentMediaType(type_handler, stream_type)))
goto fail;
- IMFMediaTypeHandler_Release(type_handler);
- stream->state = STREAM_INACTIVE;
- return S_OK;
- fail:
I find labels easier to read when not preceded by any spaces.
- if (type_handler)
IMFMediaTypeHandler_Release(type_handler);
- return hr;
+}
static HRESULT WINAPI media_source_QueryInterface(IMFMediaSource *iface, REFIID riid, void **out) { struct media_source *source = impl_from_IMFMediaSource(iface); @@ -662,7 +711,7 @@ static void stream_added(GstElement *element, GstPad *pad, gpointer user) if (gst_pad_get_direction(pad) != GST_PAD_SRC) return;
- if (FAILED(new_media_stream(source, pad, &stream)))
if (FAILED(new_media_stream(source, pad, source->stream_count, &stream))) return;
if (!(new_stream_array = heap_realloc(source->streams, (source->stream_count + 1) * (sizeof(*new_stream_array)))))
@@ -687,6 +736,8 @@ static void stream_removed(GstElement *element, GstPad *pad, gpointer user) if (stream->their_src != pad) continue; stream->their_src = NULL;
if (stream->state != STREAM_INACTIVE)
stream->state = STREAM_INACTIVE;
This "if" is superfluous.
}
}
@@ -703,6 +754,7 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_ GST_STATIC_PAD_TEMPLATE("mf_src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
struct media_source *object = heap_alloc_zero(sizeof(*object));
- unsigned int i; HRESULT hr; int ret;
@@ -772,6 +824,19 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_ }
WaitForSingleObject(object->all_streams_event, INFINITE);
- for (i = 0; i < object->stream_count; i++)
- {
GstSample *preroll;
g_signal_emit_by_name(object->streams[i]->appsink, "pull-preroll", &preroll);
Calling gst_app_sink_pull_preroll() directly seems a little less confusing (i.e. in the sense that I wouldn't have had to look up how G_SIGNAL_ACTION works).
hr = E_FAIL;
if (!preroll || FAILED(hr = media_stream_init_desc(object->streams[i])))
I don't think you want the "!preroll" check; a stream might be empty.
{
ERR("Failed to finish initialization of media stream %p, hr %x.\n", object->streams[i], hr);
IMFMediaStream_Release(&object->streams[i]->IMFMediaStream_iface);
goto fail;
}
gst_sample_unref(preroll);
}
object->state = SOURCE_STOPPED;
diff --git a/dlls/winegstreamer/mfplat.c b/dlls/winegstreamer/mfplat.c index c996f06211e..2c2216f94b6 100644 --- a/dlls/winegstreamer/mfplat.c +++ b/dlls/winegstreamer/mfplat.c @@ -16,6 +16,11 @@
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
+#include "config.h" +#include <gst/gst.h>
+#include "gst_private.h"
#include <stdarg.h>
#include "gst_private.h" @@ -436,3 +441,164 @@ HRESULT mfplat_get_class_object(REFCLSID rclsid, REFIID riid, void **obj)
return CLASS_E_CLASSNOTAVAILABLE;
}
+static const struct +{
- const GUID *subtype;
- GstVideoFormat format;
+} +uncompressed_video_formats[] = +{
- {&MFVideoFormat_ARGB32, GST_VIDEO_FORMAT_BGRA},
- {&MFVideoFormat_RGB32, GST_VIDEO_FORMAT_BGRx},
- {&MFVideoFormat_RGB24, GST_VIDEO_FORMAT_BGR},
- {&MFVideoFormat_RGB565, GST_VIDEO_FORMAT_BGR16},
- {&MFVideoFormat_RGB555, GST_VIDEO_FORMAT_BGR15},
+};
+/* returns NULL if doesn't match exactly */ +IMFMediaType *mf_media_type_from_caps(const GstCaps *caps) +{
- IMFMediaType *media_type;
- GstStructure *info;
- const char *mime_type;
- if (TRACE_ON(mfplat))
- {
gchar *human_readable = gst_caps_to_string(caps);
TRACE("caps = %s\n", debugstr_a(human_readable));
g_free(human_readable);
- }
- if (FAILED(MFCreateMediaType(&media_type)))
return NULL;
- info = gst_caps_get_structure(caps, 0);
- mime_type = gst_structure_get_name(info);
- if (!strncmp(mime_type, "video", 5))
- {
GstVideoInfo video_info;
if (!gst_video_info_from_caps(&video_info, caps))
{
return NULL;
}
IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video);
IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_SIZE, ((UINT64)video_info.width << 32) | video_info.height);
IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_RATE, ((UINT64)video_info.fps_n << 32) | video_info.fps_d);
if (!strcmp(mime_type, "video/x-raw"))
{
GUID fourcc_subtype = MFVideoFormat_Base;
unsigned int i;
IMFMediaType_SetUINT32(media_type, &MF_MT_COMPRESSED, FALSE);
/* First try FOURCC */
if ((fourcc_subtype.Data1 = gst_video_format_to_fourcc(video_info.finfo->format)))
{
IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &fourcc_subtype);
}
else
{
for (i = 0; i < ARRAY_SIZE(uncompressed_video_formats); i++)
{
if (uncompressed_video_formats[i].format == video_info.finfo->format)
{
IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, uncompressed_video_formats[i].subtype);
break;
}
}
if (i == ARRAY_SIZE(uncompressed_video_formats))
{
FIXME("Unrecognized uncompressed video format %x\n", video_info.finfo->format);
"%#x" is usually a good idea; you could also use gst_video_format_to_string().
IMFMediaType_Release(media_type);
return NULL;
}
}
}
else
{
FIXME("Unrecognized video format %s\n", mime_type);
return NULL;
}
- }
- else if (!strncmp(mime_type, "audio", 5))
- {
gint rate, channels, bitrate;
guint64 channel_mask;
IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio);
if (gst_structure_get_int(info, "rate", &rate))
IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, rate);
if (gst_structure_get_int(info, "channels", &channels))
IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_NUM_CHANNELS, channels);
if (gst_structure_get(info, "channel-mask", GST_TYPE_BITMASK, &channel_mask, NULL))
IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_CHANNEL_MASK, channel_mask);
if (gst_structure_get_int(info, "bitrate", &bitrate))
IMFMediaType_SetUINT32(media_type, &MF_MT_AVG_BITRATE, bitrate);
if (!strcmp(mime_type, "audio/x-raw"))
{
GstAudioInfo audio_info;
if (gst_audio_info_from_caps(&audio_info, caps))
Early return could save a level of indentation here.
{
DWORD depth = GST_AUDIO_INFO_DEPTH(&audio_info);
/* validation */
if ((audio_info.finfo->flags & GST_AUDIO_FORMAT_FLAG_INTEGER && depth > 8) ||
(audio_info.finfo->flags & GST_AUDIO_FORMAT_FLAG_SIGNED && depth <= 8) ||
(audio_info.finfo->endianness != G_LITTLE_ENDIAN && depth > 8))
{
IMFMediaType_Release(media_type);
return NULL;
}
/* conversion */
switch (audio_info.finfo->flags)
{
case GST_AUDIO_FORMAT_FLAG_FLOAT:
IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &MFAudioFormat_Float);
break;
case GST_AUDIO_FORMAT_FLAG_INTEGER:
case GST_AUDIO_FORMAT_FLAG_SIGNED:
IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &MFAudioFormat_PCM);
break;
default:
FIXME("Unrecognized audio format %x\n", audio_info.finfo->format);
IMFMediaType_Release(media_type);
return NULL;
}
IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, depth);
}
else
{
ERR("Failed to get caps audio info\n");
IMFMediaType_Release(media_type);
return NULL;
}
}
else
{
FIXME("Unrecognized audio format %s\n", mime_type);
IMFMediaType_Release(media_type);
return NULL;
}
- }
- else
- {
IMFMediaType_Release(media_type);
return NULL;
- }
- return media_type;
+}
On 9/24/20 12:27 PM, Zebediah Figura wrote:
enum { STREAM_STUB,
STREAM_INACTIVE,
What's the difference between these two states? There doesn't seem to be a difference in this patch.
I think it's just a vestige of a prior version of the patchset, I'll merge STREAM_STUB into STREAM_INACTIVE.
+static HRESULT media_stream_init_desc(struct media_stream *stream) +{
- GstCaps *current_caps = gst_pad_get_current_caps(stream->their_src);
- IMFMediaTypeHandler *type_handler;
- IMFMediaType *stream_type = NULL;
- HRESULT hr;
- if (!current_caps)
- {
hr = E_FAIL;
goto fail;
- }
Can that happen?
I don't think so, I'll remove it since you don't think so either.
Note that this "goto" and a couple others below could be replaced with "return hr". In general I'm inclined to think goto isn't worthwhile when there's only one line of cleanup.
I agree, however the cleanup gets more advanced in future patches. Shall I just defer the label cleanup until then?
g_signal_emit_by_name(object->streams[i]->appsink, "pull-preroll", &preroll);
Calling gst_app_sink_pull_preroll() directly seems a little less confusing (i.e. in the sense that I wouldn't have had to look up how G_SIGNAL_ACTION works).
Doesn't this require linking the libgstapp library? Seems like a lot of effort to go through to make a few lines prettier.
On 9/24/20 3:56 PM, Derek Lesho wrote:
On 9/24/20 12:27 PM, Zebediah Figura wrote:
enum { STREAM_STUB,
STREAM_INACTIVE,
What's the difference between these two states? There doesn't seem to be a difference in this patch.
I think it's just a vestige of a prior version of the patchset, I'll merge STREAM_STUB into STREAM_INACTIVE.
Are there other members of this enumeration that will be introduced, or can it become a simple boolean flag?
+static HRESULT media_stream_init_desc(struct media_stream *stream) +{
- GstCaps *current_caps = gst_pad_get_current_caps(stream->their_src);
- IMFMediaTypeHandler *type_handler;
- IMFMediaType *stream_type = NULL;
- HRESULT hr;
- if (!current_caps)
- {
hr = E_FAIL;
goto fail;
- }
Can that happen?
I don't think so, I'll remove it since you don't think so either.
Note that this "goto" and a couple others below could be replaced with "return hr". In general I'm inclined to think goto isn't worthwhile when there's only one line of cleanup.
I agree, however the cleanup gets more advanced in future patches. Shall I just defer the label cleanup until then?
Probably, though if it's too much of a hassle don't worry about it.
g_signal_emit_by_name(object->streams[i]->appsink, "pull-preroll", &preroll);
Calling gst_app_sink_pull_preroll() directly seems a little less confusing (i.e. in the sense that I wouldn't have had to look up how G_SIGNAL_ACTION works).
Doesn't this require linking the libgstapp library? Seems like a lot of effort to go through to make a few lines prettier.
Yes, you're right, in that case it's probably best to leave it as is.
On 9/24/20 6:42 PM, Zebediah Figura wrote:
On 9/24/20 3:56 PM, Derek Lesho wrote:
On 9/24/20 12:27 PM, Zebediah Figura wrote:
enum { STREAM_STUB,
STREAM_INACTIVE,
What's the difference between these two states? There doesn't seem to be a difference in this patch.
I think it's just a vestige of a prior version of the patchset, I'll merge STREAM_STUB into STREAM_INACTIVE.
Are there other members of this enumeration that will be introduced, or can it become a simple boolean flag?
There's one more state I have in future patches, STREAM_RUNNING. [1] I presume STREAM_PAUSED will also be necessary once pausing support is added. I am open to turning these into simple boolean flags if you think that's better.
1: https://github.com/Guy1524/wine/blob/mfplat_cleanup/dlls/winegstreamer/media...
On 9/24/20 6:48 PM, Derek Lesho wrote:
On 9/24/20 6:42 PM, Zebediah Figura wrote:
On 9/24/20 3:56 PM, Derek Lesho wrote:
On 9/24/20 12:27 PM, Zebediah Figura wrote:
enum { STREAM_STUB,
STREAM_INACTIVE,
What's the difference between these two states? There doesn't seem to be a difference in this patch.
I think it's just a vestige of a prior version of the patchset, I'll merge STREAM_STUB into STREAM_INACTIVE.
Are there other members of this enumeration that will be introduced, or can it become a simple boolean flag?
There's one more state I have in future patches, STREAM_RUNNING. [1] I presume STREAM_PAUSED will also be necessary once pausing support is added. I am open to turning these into simple boolean flags if you think that's better.
I don't have a strong opinion on it. Given that there's a distinction between running/paused/stopped that doesn't translate nicely to boolean flags, I guess it's probably clearest to leave it as an enumeration.
1: https://github.com/Guy1524/wine/blob/mfplat_cleanup/dlls/winegstreamer/media...