Signed-off-by: Derek Lesho dlesho@codeweavers.com --- dlls/winegstreamer/gst_cbs.c | 13 ++ dlls/winegstreamer/gst_cbs.h | 8 ++ dlls/winegstreamer/gst_private.h | 4 + dlls/winegstreamer/media_source.c | 137 +++++++++++++++++++- dlls/winegstreamer/mfplat.c | 208 ++++++++++++++++++++++++++++++ 5 files changed, 367 insertions(+), 3 deletions(-)
diff --git a/dlls/winegstreamer/gst_cbs.c b/dlls/winegstreamer/gst_cbs.c index 4755f5b42f1..e56c987eb38 100644 --- a/dlls/winegstreamer/gst_cbs.c +++ b/dlls/winegstreamer/gst_cbs.c @@ -404,3 +404,16 @@ void source_all_streams_wrapper(GstElement *element, gpointer user)
call_cb(&cbdata); } + +GstPadProbeReturn caps_listener_wrapper(GstPad *pad, GstPadProbeInfo *info, gpointer user) +{ + struct cb_data cbdata = { STREAM_PAD_EVENT }; + + cbdata.u.pad_probe_data.pad = pad; + cbdata.u.pad_probe_data.info = info; + cbdata.u.pad_probe_data.user = user; + + call_cb(&cbdata); + + return cbdata.u.pad_probe_data.ret; +} diff --git a/dlls/winegstreamer/gst_cbs.h b/dlls/winegstreamer/gst_cbs.h index d87cc8c21e9..7173c09746e 100644 --- a/dlls/winegstreamer/gst_cbs.h +++ b/dlls/winegstreamer/gst_cbs.h @@ -52,6 +52,7 @@ enum CB_TYPE { SOURCE_STREAM_ADDED, SOURCE_STREAM_REMOVED, SOURCE_ALL_STREAMS, + STREAM_PAD_EVENT, MEDIA_SOURCE_MAX, };
@@ -137,6 +138,12 @@ struct cb_data { GstQuery *query; gboolean ret; } query_sink_data; + struct pad_probe_data { + GstPad *pad; + GstPadProbeInfo *info; + gpointer user; + GstPadProbeReturn ret; + } pad_probe_data; } u;
int finished; @@ -172,5 +179,6 @@ GstBusSyncReply watch_source_bus_wrapper(GstBus *bus, GstMessage *message, gpoin void source_stream_added_wrapper(GstElement *bin, GstPad *pad, gpointer user) DECLSPEC_HIDDEN; void source_stream_removed_wrapper(GstElement *element, GstPad *pad, gpointer user) DECLSPEC_HIDDEN; void source_all_streams_wrapper(GstElement *element, gpointer user) DECLSPEC_HIDDEN; +GstPadProbeReturn caps_listener_wrapper(GstPad *pad, GstPadProbeInfo *info, gpointer user);
#endif diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 71ca4290885..780cf1b02fa 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;
+GstCaps *make_mf_compatible_caps(GstCaps *caps); +IMFMediaType *mf_media_type_from_caps(GstCaps *caps); + enum source_type { SOURCE_TYPE_MPEG_4, diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index fa0e1065ea7..f08d46c48c5 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -38,8 +38,10 @@ struct media_stream LONG ref; struct media_source *parent_source; IMFMediaEventQueue *event_queue; + IMFStreamDescriptor *descriptor; GstElement *appsink; GstPad *their_src, *my_sink; + GstCaps *their_caps; /* usually reflects state of source */ enum { @@ -47,6 +49,9 @@ struct media_stream STREAM_INACTIVE, STREAM_SHUTDOWN, } state; + /* used when in STUB state: */ + DWORD stream_id; + HANDLE caps_event; };
struct media_source @@ -305,6 +310,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) @@ -386,7 +393,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) @@ -429,9 +439,12 @@ static HRESULT new_media_stream(struct media_source *source, GstPad *pad, DWORD IMFMediaSource_AddRef(&source->IMFMediaSource_iface); object->parent_source = source; object->their_src = pad; + object->stream_id = stream_id;
object->state = STREAM_STUB;
+ object->caps_event = CreateEventA(NULL, TRUE, FALSE, NULL); + if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) goto fail;
@@ -448,10 +461,10 @@ static HRESULT new_media_stream(struct media_source *source, GstPad *pad, DWORD g_object_set(object->appsink, "wait-on-eos", FALSE, 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_pad_add_probe(object->my_sink, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, caps_listener_wrapper, object, NULL); + gst_element_sync_state_with_parent(object->appsink);
TRACE("->(%p)\n", object); @@ -466,6 +479,43 @@ static HRESULT new_media_stream(struct media_source *source, GstPad *pad, DWORD return hr; }
+static HRESULT media_stream_init_desc(struct media_stream *stream) +{ + HRESULT hr; + IMFMediaTypeHandler *type_handler; + IMFMediaType *stream_type = NULL; + + stream->their_caps = gst_caps_fixate(stream->their_caps); + + stream_type = mf_media_type_from_caps(stream->their_caps); + gst_caps_unref(stream->their_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: + ERR("media stream initialization failed with %x\n", hr); + 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); @@ -684,6 +734,23 @@ static const IMFMediaSourceVtbl IMFMediaSource_vtbl = media_source_Shutdown, };
+/* If this callback is extended to use any significant win32 APIs, a wrapper function + should be added */ +gboolean stream_found(GstElement *bin, GstPad *pad, GstCaps *caps, gpointer user) +{ + GstCaps *target_caps; + + /* if the stream can be converted into an MF compatible type, we'll go that route + otherwise, we'll rely on decodebin for the whole process */ + + if ((target_caps = make_mf_compatible_caps(caps))) + { + gst_caps_unref(target_caps); + return FALSE; + } + return TRUE; +} + static void source_stream_added(GstElement *element, GstPad *pad, gpointer user) { struct media_source *source = (struct media_source *) user; @@ -701,6 +768,36 @@ static void source_stream_added(GstElement *element, GstPad *pad, gpointer user)
TRACE("stream-id: %u\n", stream_id);
+ /* This codepath is currently never triggered, as we don't need to ever restart the gstreamer pipeline. It is retained in + case this becomes necessary in the future, for example in a case where different media types require different + post-processing elements. */ + for (unsigned int i = 0; i < source->stream_count; i++) + { + DWORD existing_stream_id; + IMFStreamDescriptor *descriptor = source->streams[i]->descriptor; + + if (source->streams[i]->state == STREAM_STUB) + continue; + + if (FAILED(IMFStreamDescriptor_GetStreamIdentifier(descriptor, &existing_stream_id))) + goto leave; + + if (existing_stream_id == stream_id) + { + struct media_stream *existing_stream = source->streams[i]; + GstPadLinkReturn ret; + + TRACE("Found existing stream %p\n", existing_stream); + + existing_stream->their_src = pad; + + if ((ret = gst_pad_link(existing_stream->their_src, existing_stream->my_sink)) != GST_PAD_LINK_OK) + ERR("Error linking decodebin pad to stream %p, err = %d\n", existing_stream, ret); + + goto leave; + } + } + if (FAILED(new_media_stream(source, pad, stream_id, &stream))) { goto leave; @@ -741,6 +838,26 @@ static void source_all_streams(GstElement *element, gpointer user) SetEvent(source->all_streams_event); }
+static GstPadProbeReturn caps_listener(GstPad *pad, GstPadProbeInfo *info, gpointer user) +{ + struct media_stream *stream = (struct media_stream *) user; + GstEvent *event = gst_pad_probe_info_get_event(info); + + if (GST_EVENT_TYPE(event) == GST_EVENT_CAPS) + { + GstCaps *caps; + TRACE("got caps for stream %p\n", stream); + + gst_event_parse_caps(event, &caps); + stream->their_caps = gst_caps_copy(caps); + SetEvent(stream->caps_event); + + return GST_PAD_PROBE_REMOVE; + } + + return GST_PAD_PROBE_OK; +} + static HRESULT media_source_constructor(IMFByteStream *bytestream, enum source_type type, struct media_source **out_media_source) { GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE( @@ -793,6 +910,8 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, enum source_t
gst_bin_add(GST_BIN(object->container), object->decodebin);
+ if(!GetEnvironmentVariableA("MF_DECODE_IN_SOURCE", NULL, 0)) + g_signal_connect(object->decodebin, "autoplug-continue", G_CALLBACK(stream_found), object); g_signal_connect(object->decodebin, "pad-added", G_CALLBACK(source_stream_added_wrapper), object); g_signal_connect(object->decodebin, "pad-removed", G_CALLBACK(source_stream_removed_wrapper), object); g_signal_connect(object->decodebin, "no-more-pads", G_CALLBACK(source_all_streams_wrapper), object); @@ -818,6 +937,12 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, enum source_t }
WaitForSingleObject(object->all_streams_event, INFINITE); + for (unsigned int i = 0; i < object->stream_count; i++) + { + WaitForSingleObject(object->streams[i]->caps_event, INFINITE); + if (FAILED(hr = media_stream_init_desc(object->streams[i]))) + goto fail; + }
object->state = SOURCE_STOPPED;
@@ -1349,6 +1474,12 @@ void perform_cb_media_source(struct cb_data *cbdata) source_all_streams(data->element, data->user); break; } + case STREAM_PAD_EVENT: + { + struct pad_probe_data *data = &cbdata->u.pad_probe_data; + cbdata->u.pad_probe_data.ret = caps_listener(data->pad, data->info, data->user); + break; + } default: { ERR("Wrong callback forwarder called\n"); diff --git a/dlls/winegstreamer/mfplat.c b/dlls/winegstreamer/mfplat.c index 16e55247de1..06f7984bbe6 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" @@ -442,3 +447,206 @@ HRESULT mfplat_get_class_object(REFCLSID rclsid, REFIID riid, void **obj)
return CLASS_E_CLASSNOTAVAILABLE; } + +const static struct +{ + const GUID *subtype; + GstVideoFormat format; +} +uncompressed_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}, +}; + +/* caps will be modified to represent the exact type needed for the format */ +static IMFMediaType* transform_to_media_type(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_formats); i++) + { + if (uncompressed_formats[i].format == video_info.finfo->format) + { + IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, uncompressed_formats[i].subtype); + break; + } + } + if (i == ARRAY_SIZE(uncompressed_formats)) + FIXME("Unrecognized format.\n"); + } + } + else + { + FIXME("Unrecognized video format %s\n", mime_type); + IMFMediaType_Release(media_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, (DWORD)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"))) + { + const char *format; + if ((format = gst_structure_get_string(info, "format"))) + { + char type; + unsigned int bits_per_sample; + char endian[2]; + char new_format[6]; + if ((strlen(format) > 5) || (sscanf(format, "%c%u%2c", &type, &bits_per_sample, endian) < 2)) + { + FIXME("Unhandled audio format %s\n", format); + IMFMediaType_Release(media_type); + return NULL; + } + + if (type == 'F') + { + IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &MFAudioFormat_Float); + } + else if (type == 'U' || type == 'S') + { + IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &MFAudioFormat_PCM); + if (bits_per_sample == 8) + type = 'U'; + else + type = 'S'; + } + else + { + FIXME("Unrecognized audio format: %s\n", format); + IMFMediaType_Release(media_type); + return NULL; + } + + IMFMediaType_SetUINT32(media_type, &MF_MT_AUDIO_BITS_PER_SAMPLE, bits_per_sample); + + if (endian[0] == 'B') + endian[0] = 'L'; + + sprintf(new_format, "%c%u%.2s", type, bits_per_sample, bits_per_sample > 8 ? endian : 0); + gst_caps_set_simple(caps, "format", G_TYPE_STRING, new_format, NULL); + } + else + { + ERR("Failed to get audio format\n"); + } + } + 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; +} + +/* returns NULL if doesn't match exactly */ +IMFMediaType *mf_media_type_from_caps(GstCaps *caps) +{ + GstCaps *writeable_caps; + IMFMediaType *ret; + + writeable_caps = gst_caps_copy(caps); + ret = transform_to_media_type(writeable_caps); + + if (!(gst_caps_is_equal(caps, writeable_caps))) + { + IMFMediaType_Release(ret); + ret = NULL; + } + gst_caps_unref(writeable_caps); + return ret; +} + +GstCaps *make_mf_compatible_caps(GstCaps *caps) +{ + GstCaps *ret; + IMFMediaType *media_type; + + ret = gst_caps_copy(caps); + + if ((media_type = transform_to_media_type(ret))) + IMFMediaType_Release(media_type); + + if (!media_type) + { + gst_caps_unref(ret); + return NULL; + } + + return ret; +} \ No newline at end of file