[PATCH v3 4/5] winegstreamer: Implement IMFMediaSource::Start.

Derek Lesho dlesho at codeweavers.com
Thu Oct 8 16:49:06 CDT 2020


Signed-off-by: Derek Lesho <dlesho at codeweavers.com>
---
v3: Address comments, which include:
- Reorganizing handling of uncompressed formats in IMFMediaType->GstCaps conversion.
- Remove initial caching of stream type, and instead always reconfigure upon the first media type we receive from the application.
- Don't wait after pushing a seek, gstreamer handles this.
---
 dlls/mfplat/tests/mfplat.c        |   8 +-
 dlls/winegstreamer/gst_private.h  |   1 +
 dlls/winegstreamer/media_source.c | 305 +++++++++++++++++++++++++++++-
 dlls/winegstreamer/mfplat.c       | 125 ++++++++++++
 4 files changed, 431 insertions(+), 8 deletions(-)

diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c
index 15d5bcba3d6..bffce2bc114 100644
--- a/dlls/mfplat/tests/mfplat.c
+++ b/dlls/mfplat/tests/mfplat.c
@@ -605,10 +605,7 @@ todo_wine
 
     var.vt = VT_EMPTY;
     hr = IMFMediaSource_Start(mediasource, descriptor, &GUID_NULL, &var);
-todo_wine
     ok(hr == S_OK, "Failed to start media source, hr %#x.\n", hr);
-    if (FAILED(hr))
-        goto skip_source_tests;
 
     get_event((IMFMediaEventGenerator *)mediasource, MENewStream, &var);
     ok(var.vt == VT_UNKNOWN, "Unexpected value type %u from MENewStream event.\n", var.vt);
@@ -626,10 +623,13 @@ todo_wine
         hr = IMFMediaStream_RequestSample(video_stream, NULL);
         if (i == sample_count)
             break;
+todo_wine
         ok(hr == S_OK, "Failed to request sample %u, hr %#x.\n", i + 1, hr);
         if (hr != S_OK)
             break;
     }
+    if (FAILED(hr))
+        goto skip_source_tests;
 
     for (i = 0; i < sample_count; ++i)
     {
@@ -667,11 +667,11 @@ todo_wine
 
     hr = IMFMediaStream_RequestSample(video_stream, NULL);
     ok(hr == MF_E_END_OF_STREAM, "Unexpected hr %#x.\n", hr);
-    IMFMediaStream_Release(video_stream);
 
     get_event((IMFMediaEventGenerator *)mediasource, MEEndOfPresentation, NULL);
 
 skip_source_tests:
+    IMFMediaStream_Release(video_stream);
     IMFMediaTypeHandler_Release(handler);
     IMFPresentationDescriptor_Release(descriptor);
 
diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h
index 60b38a48f5a..07556802a51 100644
--- a/dlls/winegstreamer/gst_private.h
+++ b/dlls/winegstreamer/gst_private.h
@@ -57,6 +57,7 @@ extern HRESULT mfplat_get_class_object(REFCLSID rclsid, REFIID riid, void **obj)
 
 HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj) DECLSPEC_HIDDEN;
 IMFMediaType *mf_media_type_from_caps(const GstCaps *caps) DECLSPEC_HIDDEN;
+GstCaps *caps_from_mf_media_type(IMFMediaType *type) DECLSPEC_HIDDEN;
 
 HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj) DECLSPEC_HIDDEN;
 
diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c
index de15765d31c..52c52fc64ac 100644
--- a/dlls/winegstreamer/media_source.c
+++ b/dlls/winegstreamer/media_source.c
@@ -55,14 +55,38 @@ struct media_stream
     {
         STREAM_INACTIVE,
         STREAM_SHUTDOWN,
+        STREAM_RUNNING,
     } state;
     DWORD stream_id;
 };
 
+enum source_async_op
+{
+    SOURCE_ASYNC_START,
+};
+
+struct source_async_command
+{
+    IUnknown IUnknown_iface;
+    LONG refcount;
+    enum source_async_op op;
+    union
+    {
+        struct
+        {
+            IMFPresentationDescriptor *descriptor;
+            GUID format;
+            PROPVARIANT position;
+        } start;
+    } u;
+};
+
 struct media_source
 {
     IMFMediaSource IMFMediaSource_iface;
+    IMFAsyncCallback async_commands_callback;
     LONG ref;
+    DWORD async_commands_queue;
     IMFMediaEventQueue *event_queue;
     IMFByteStream *byte_stream;
     struct media_stream **streams;
@@ -76,6 +100,7 @@ struct media_source
     {
         SOURCE_OPENING,
         SOURCE_STOPPED,
+        SOURCE_RUNNING,
         SOURCE_SHUTDOWN,
     } state;
     HANDLE no_more_pads_event;
@@ -91,7 +116,258 @@ 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,
+static inline struct media_source *impl_from_async_commands_callback_IMFAsyncCallback(IMFAsyncCallback *iface)
+{
+    return CONTAINING_RECORD(iface, struct media_source, async_commands_callback);
+}
+
+static inline struct source_async_command *impl_from_async_command_IUnknown(IUnknown *iface)
+{
+    return CONTAINING_RECORD(iface, struct source_async_command, IUnknown_iface);
+}
+
+static HRESULT WINAPI source_async_command_QueryInterface(IUnknown *iface, REFIID riid, void **obj)
+{
+    if (IsEqualIID(riid, &IID_IUnknown))
+    {
+        *obj = iface;
+        IUnknown_AddRef(iface);
+        return S_OK;
+    }
+
+    WARN("Unsupported interface %s.\n", debugstr_guid(riid));
+    *obj = NULL;
+    return E_NOINTERFACE;
+}
+
+static ULONG WINAPI source_async_command_AddRef(IUnknown *iface)
+{
+    struct source_async_command *command = impl_from_async_command_IUnknown(iface);
+    return InterlockedIncrement(&command->refcount);
+}
+
+static ULONG WINAPI source_async_command_Release(IUnknown *iface)
+{
+    struct source_async_command *command = impl_from_async_command_IUnknown(iface);
+    ULONG refcount = InterlockedDecrement(&command->refcount);
+
+    if (!refcount)
+    {
+        if (command->op == SOURCE_ASYNC_START)
+            PropVariantClear(&command->u.start.position);
+        heap_free(command);
+    }
+
+    return refcount;
+}
+
+static const IUnknownVtbl source_async_command_vtbl =
+{
+    source_async_command_QueryInterface,
+    source_async_command_AddRef,
+    source_async_command_Release,
+};
+
+static HRESULT source_create_async_op(enum source_async_op op, struct source_async_command **ret)
+{
+    struct source_async_command *command;
+
+    if (!(command = heap_alloc_zero(sizeof(*command))))
+        return E_OUTOFMEMORY;
+
+    command->IUnknown_iface.lpVtbl = &source_async_command_vtbl;
+    command->op = op;
+
+    *ret = command;
+
+    return S_OK;
+}
+
+static HRESULT WINAPI callback_QueryInterface(IMFAsyncCallback *iface, REFIID riid, void **obj)
+{
+    TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj);
+
+    if (IsEqualIID(riid, &IID_IMFAsyncCallback) ||
+            IsEqualIID(riid, &IID_IUnknown))
+    {
+        *obj = iface;
+        IMFAsyncCallback_AddRef(iface);
+        return S_OK;
+    }
+
+    WARN("Unsupported %s.\n", debugstr_guid(riid));
+    *obj = NULL;
+    return E_NOINTERFACE;
+}
+
+static HRESULT WINAPI callback_GetParameters(IMFAsyncCallback *iface,
+        DWORD *flags, DWORD *queue)
+{
+    return E_NOTIMPL;
+}
+
+static ULONG WINAPI source_async_commands_callback_AddRef(IMFAsyncCallback *iface)
+{
+    struct media_source *source = impl_from_async_commands_callback_IMFAsyncCallback(iface);
+    return IMFMediaSource_AddRef(&source->IMFMediaSource_iface);
+}
+
+static ULONG WINAPI source_async_commands_callback_Release(IMFAsyncCallback *iface)
+{
+    struct media_source *source = impl_from_async_commands_callback_IMFAsyncCallback(iface);
+    return IMFMediaSource_Release(&source->IMFMediaSource_iface);
+}
+
+static IMFStreamDescriptor *stream_descriptor_from_id(IMFPresentationDescriptor *pres_desc, DWORD id, BOOL *selected)
+{
+    ULONG sd_count;
+    IMFStreamDescriptor *ret;
+    unsigned int i;
+
+    if (FAILED(IMFPresentationDescriptor_GetStreamDescriptorCount(pres_desc, &sd_count)))
+        return NULL;
+
+    for (i = 0; i < sd_count; i++)
+    {
+        DWORD stream_id;
+
+        if (FAILED(IMFPresentationDescriptor_GetStreamDescriptorByIndex(pres_desc, i, selected, &ret)))
+            return NULL;
+
+        if (SUCCEEDED(IMFStreamDescriptor_GetStreamIdentifier(ret, &stream_id)) && stream_id == id)
+            return ret;
+
+        IMFStreamDescriptor_Release(ret);
+    }
+    return NULL;
+}
+
+static void start_pipeline(struct media_source *source, struct source_async_command *command)
+{
+    PROPVARIANT *position = &command->u.start.position;
+    BOOL seek_message = source->state != SOURCE_STOPPED && position->vt != VT_EMPTY;
+    GstStateChangeReturn ret;
+    unsigned int i;
+
+    gst_element_set_state(source->container, GST_STATE_PAUSED);
+    ret = gst_element_get_state(source->container, NULL, NULL, -1);
+    assert(ret == GST_STATE_CHANGE_SUCCESS);
+
+    /* seek to beginning on stop->play */
+    if (source->state == SOURCE_STOPPED && position->vt == VT_EMPTY)
+    {
+        position->vt = VT_I8;
+        position->u.hVal.QuadPart = 0;
+    }
+
+    for (i = 0; i < source->stream_count; i++)
+    {
+        struct media_stream *stream;
+        IMFStreamDescriptor *sd;
+        IMFMediaTypeHandler *mth;
+        IMFMediaType *current_mt;
+        GstCaps *current_caps;
+        GstCaps *prev_caps;
+        DWORD stream_id;
+        BOOL was_active;
+        BOOL selected;
+
+        stream = source->streams[i];
+
+        IMFStreamDescriptor_GetStreamIdentifier(stream->descriptor, &stream_id);
+
+        sd = stream_descriptor_from_id(command->u.start.descriptor, stream_id, &selected);
+        IMFStreamDescriptor_Release(sd);
+
+        was_active = stream->state != STREAM_INACTIVE;
+
+        stream->state = selected ? STREAM_RUNNING : STREAM_INACTIVE;
+
+        if (selected)
+        {
+            IMFStreamDescriptor_GetMediaTypeHandler(stream->descriptor, &mth);
+            IMFMediaTypeHandler_GetCurrentMediaType(mth, &current_mt);
+            current_caps = caps_from_mf_media_type(current_mt);
+            g_object_get(stream->appsink, "caps", &prev_caps, NULL);
+            if (!gst_caps_is_equal(prev_caps, current_caps))
+            {
+                GstEvent *reconfigure_event = gst_event_new_reconfigure();
+                g_object_set(stream->appsink, "caps", current_caps, NULL);
+                gst_pad_push_event(gst_element_get_static_pad(stream->appsink, "sink"), reconfigure_event);
+            }
+
+            gst_caps_unref(current_caps);
+            gst_caps_unref(prev_caps);
+            IMFMediaType_Release(current_mt);
+            IMFMediaTypeHandler_Release(mth);
+        }
+
+        if (position->vt != VT_EMPTY)
+        {
+            GstEvent *seek_event = gst_event_new_seek(1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
+                    GST_SEEK_TYPE_SET, position->u.hVal.QuadPart / 100, GST_SEEK_TYPE_NONE, 0);
+            GstSample *preroll;
+
+            gst_pad_push_event(stream->my_sink, seek_event);
+        }
+
+        if (selected)
+        {
+            TRACE("Stream %u (%p) selected\n", i, stream);
+            IMFMediaEventQueue_QueueEventParamUnk(source->event_queue,
+                was_active ? MEUpdatedStream : MENewStream, &GUID_NULL,
+                S_OK, (IUnknown*) &stream->IMFMediaStream_iface);
+
+            IMFMediaEventQueue_QueueEventParamVar(stream->event_queue,
+                seek_message ? MEStreamSeeked : MEStreamStarted, &GUID_NULL, S_OK, position);
+        }
+    }
+
+    IMFMediaEventQueue_QueueEventParamVar(source->event_queue,
+        seek_message ? MESourceSeeked : MESourceStarted,
+        &GUID_NULL, S_OK, position);
+
+    source->state = SOURCE_RUNNING;
+
+    gst_element_set_state(source->container, GST_STATE_PLAYING);
+}
+
+static HRESULT WINAPI source_async_commands_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result)
+{
+    struct media_source *source = impl_from_async_commands_callback_IMFAsyncCallback(iface);
+    struct source_async_command *command;
+    IUnknown *state;
+    HRESULT hr;
+
+    if (source->state == SOURCE_SHUTDOWN)
+        return S_OK;
+
+    if (FAILED(hr = IMFAsyncResult_GetState(result, &state)))
+        return hr;
+
+    command = impl_from_async_command_IUnknown(state);
+    switch (command->op)
+    {
+        case SOURCE_ASYNC_START:
+            start_pipeline(source, command);
+            break;
+    }
+
+    IUnknown_Release(state);
+
+    return S_OK;
+}
+
+static const IMFAsyncCallbackVtbl source_async_commands_callback_vtbl =
+{
+    callback_QueryInterface,
+    source_async_commands_callback_AddRef,
+    source_async_commands_callback_Release,
+    callback_GetParameters,
+    source_async_commands_Invoke,
+};
+
+GstFlowReturn bytestream_wrapper_pull(GstPad *pad, GstObject *parent, guint64 ofs, guint len,
         GstBuffer **buf)
 {
     struct media_source *source = gst_pad_get_element_private(pad);
@@ -682,16 +958,30 @@ static HRESULT WINAPI media_source_CreatePresentationDescriptor(IMFMediaSource *
 }
 
 static HRESULT WINAPI media_source_Start(IMFMediaSource *iface, IMFPresentationDescriptor *descriptor,
-                                     const GUID *time_format, const PROPVARIANT *start_position)
+                                     const GUID *time_format, const PROPVARIANT *position)
 {
     struct media_source *source = impl_from_IMFMediaSource(iface);
+    struct source_async_command *command;
+    HRESULT hr;
 
-    FIXME("(%p)->(%p, %p, %p): stub\n", source, descriptor, time_format, start_position);
+    TRACE("(%p)->(%p, %p, %p)\n", source, descriptor, time_format, position);
 
     if (source->state == SOURCE_SHUTDOWN)
         return MF_E_SHUTDOWN;
 
-    return E_NOTIMPL;
+    if (!(IsEqualIID(time_format, &GUID_NULL)))
+        return MF_E_UNSUPPORTED_TIME_FORMAT;
+
+    if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_START, &command)))
+    {
+        command->u.start.descriptor = descriptor;
+        command->u.start.format = *time_format;
+        PropVariantCopy(&command->u.start.position, position);
+
+        hr = MFPutWorkItem(source->async_commands_queue, &source->async_commands_callback, &command->IUnknown_iface);
+    }
+
+    return hr;
 }
 
 static HRESULT WINAPI media_source_Stop(IMFMediaSource *iface)
@@ -772,6 +1062,9 @@ static HRESULT WINAPI media_source_Shutdown(IMFMediaSource *iface)
     if (source->no_more_pads_event)
         CloseHandle(source->no_more_pads_event);
 
+    if (source->async_commands_queue)
+        MFUnlockWorkQueue(source->async_commands_queue);
+
     return S_OK;
 }
 
@@ -852,6 +1145,7 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_
         return E_OUTOFMEMORY;
 
     object->IMFMediaSource_iface.lpVtbl = &IMFMediaSource_vtbl;
+    object->async_commands_callback.lpVtbl = &source_async_commands_callback_vtbl;
     object->ref = 1;
     object->byte_stream = bytestream;
     IMFByteStream_AddRef(bytestream);
@@ -860,6 +1154,9 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_
     if (FAILED(hr = MFCreateEventQueue(&object->event_queue)))
         goto fail;
 
+    if (FAILED(hr = MFAllocateWorkQueue(&object->async_commands_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);
diff --git a/dlls/winegstreamer/mfplat.c b/dlls/winegstreamer/mfplat.c
index 2e8b0978648..43895c2cb5d 100644
--- a/dlls/winegstreamer/mfplat.c
+++ b/dlls/winegstreamer/mfplat.c
@@ -601,3 +601,128 @@ IMFMediaType *mf_media_type_from_caps(const GstCaps *caps)
 
     return media_type;
 }
+
+GstCaps *caps_from_mf_media_type(IMFMediaType *type)
+{
+    GUID major_type;
+    GUID subtype;
+    GstCaps *output = NULL;
+
+    if (FAILED(IMFMediaType_GetMajorType(type, &major_type)))
+        return NULL;
+    if (FAILED(IMFMediaType_GetGUID(type, &MF_MT_SUBTYPE, &subtype)))
+        return NULL;
+
+    if (IsEqualGUID(&major_type, &MFMediaType_Video))
+    {
+        UINT64 frame_rate = 0, frame_size = 0;
+        DWORD width, height;
+        UINT32 unused;
+        GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN;
+        GUID subtype_base;
+        GstVideoInfo info;
+        unsigned int i;
+
+        if (FAILED(IMFMediaType_GetUINT64(type, &MF_MT_FRAME_SIZE, &frame_size)))
+            return NULL;
+        width = frame_size >> 32;
+        height = frame_size;
+
+        output = gst_caps_new_empty_simple("video/x-raw");
+
+        for (i = 0; i < ARRAY_SIZE(uncompressed_video_formats); i++)
+        {
+            if (IsEqualGUID(uncompressed_video_formats[i].subtype, &subtype))
+            {
+                format = uncompressed_video_formats[i].format;
+                break;
+            }
+        }
+
+        subtype_base = subtype;
+        subtype_base.Data1 = 0;
+        if (format == GST_VIDEO_FORMAT_UNKNOWN && IsEqualGUID(&MFVideoFormat_Base, &subtype_base))
+            format = gst_video_format_from_fourcc(subtype.Data1);
+
+        if (format == GST_VIDEO_FORMAT_UNKNOWN)
+        {
+            FIXME("Unrecognized format %s\n", debugstr_guid(&subtype));
+            return NULL;
+        }
+
+        gst_video_info_set_format(&info, format, width, height);
+        output = gst_video_info_to_caps(&info);
+
+        if (frame_size)
+        {
+            gst_caps_set_simple(output, "width", G_TYPE_INT, width, NULL);
+            gst_caps_set_simple(output, "height", G_TYPE_INT, height, NULL);
+        }
+        if (SUCCEEDED(IMFMediaType_GetUINT64(type, &MF_MT_FRAME_RATE, &frame_rate)))
+            gst_caps_set_simple(output, "framerate", GST_TYPE_FRACTION, frame_rate >> 32, (DWORD32) frame_rate, NULL);
+        return output;
+    }
+    else if (IsEqualGUID(&major_type, &MFMediaType_Audio))
+    {
+        DWORD rate, channels, channel_mask, bitrate;
+
+        if (IsEqualGUID(&subtype, &MFAudioFormat_Float))
+        {
+            output = gst_caps_new_empty_simple("audio/x-raw");
+
+            gst_caps_set_simple(output, "format", G_TYPE_STRING, "F32LE", NULL);
+            gst_caps_set_simple(output, "layout", G_TYPE_STRING, "interleaved", NULL);
+        }
+        else if (IsEqualGUID(&subtype, &MFAudioFormat_PCM))
+        {
+            DWORD bits_per_sample;
+
+            if (SUCCEEDED(IMFMediaType_GetUINT32(type, &MF_MT_AUDIO_BITS_PER_SAMPLE, &bits_per_sample)))
+            {
+                char format[6];
+                char type;
+
+                type = bits_per_sample > 8 ? 'S' : 'U';
+
+                output = gst_caps_new_empty_simple("audio/x-raw");
+
+                sprintf(format, "%c%u%s", type, bits_per_sample, bits_per_sample > 8 ? "LE" : "");
+
+                gst_caps_set_simple(output, "format", G_TYPE_STRING, format, NULL);
+            }
+            else
+            {
+                ERR("Bits per sample not set.\n");
+                return NULL;
+            }
+        }
+        else
+        {
+            FIXME("Unrecognized subtype %s\n", debugstr_guid(&subtype));
+            return NULL;
+        }
+
+        if (SUCCEEDED(IMFMediaType_GetUINT32(type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, &rate)))
+        {
+            gst_caps_set_simple(output, "rate", G_TYPE_INT, rate, NULL);
+        }
+        if (SUCCEEDED(IMFMediaType_GetUINT32(type, &MF_MT_AUDIO_NUM_CHANNELS, &channels)))
+        {
+            gst_caps_set_simple(output, "channels", G_TYPE_INT, channels, NULL);
+        }
+        if (SUCCEEDED(IMFMediaType_GetUINT32(type, &MF_MT_AUDIO_CHANNEL_MASK, &channel_mask)))
+        {
+            gst_caps_set_simple(output, "channel-mask", GST_TYPE_BITMASK, (guint64) channel_mask, NULL);
+        }
+
+        if (SUCCEEDED(IMFMediaType_GetUINT32(type, &MF_MT_AVG_BITRATE, &bitrate)))
+        {
+            gst_caps_set_simple(output, "bitrate", G_TYPE_INT, bitrate, NULL);
+        }
+
+        return output;
+    }
+
+    FIXME("Unrecognized major type %s\n", debugstr_guid(&major_type));
+    return NULL;
+}
-- 
2.28.0




More information about the wine-devel mailing list