-- v3: mfplay/tests: Add MF_SD_LANGUAGE and MF_SD_STREAM_NAME value tests. winegstreamer: Extract stream name from QT demuxer private data. winegstreamer: Query stream tags and set MF_SD_LANGUAGE attribute.
From: Paul Gofman pgofman@codeweavers.com
Loosely based on a patch by Derek Lesho and Rémi Bernon. --- dlls/winegstreamer/gst_private.h | 2 +- dlls/winegstreamer/main.c | 31 +++++++++++++++ dlls/winegstreamer/media_source.c | 30 ++++++++++++++ dlls/winegstreamer/unixlib.h | 15 +++++++ dlls/winegstreamer/wg_parser.c | 62 ++++++++++++++++++++++++++++- dlls/winegstreamer/wm_asyncreader.c | 1 + dlls/winegstreamer/wm_reader.c | 2 + 7 files changed, 140 insertions(+), 3 deletions(-)
diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 523e2711edb..3a067dfd8a8 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -33,7 +33,6 @@ #define NONAMELESSUNION #include "dshow.h" #include "mfidl.h" -#include "wmsdk.h" #include "wine/debug.h" #include "wine/strmbase.h"
@@ -96,6 +95,7 @@ void wg_parser_stream_notify_qos(struct wg_parser_stream *stream,
/* Returns the duration in 100-nanosecond units. */ uint64_t wg_parser_stream_get_duration(struct wg_parser_stream *stream); +char *wg_parser_stream_get_tag(struct wg_parser_stream *stream, enum wg_parser_tag tag); /* start_pos and stop_pos are in 100-nanosecond units. */ void wg_parser_stream_seek(struct wg_parser_stream *stream, double rate, uint64_t start_pos, uint64_t stop_pos, DWORD start_flags, DWORD stop_flags); diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index fc7344df50c..a14965bbaa1 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -21,6 +21,9 @@ #define WINE_NO_NAMELESS_EXTENSION
#define EXTERN_GUID DEFINE_GUID + +#include "ntstatus.h" +#define WIN32_NO_STATUS #include "initguid.h" #include "gst_private.h" #include "winternl.h" @@ -271,6 +274,34 @@ uint64_t wg_parser_stream_get_duration(struct wg_parser_stream *stream) return params.duration; }
+char *wg_parser_stream_get_tag(struct wg_parser_stream *stream, enum wg_parser_tag tag) +{ + uint32_t size = 0; + struct wg_parser_stream_get_tag_params params = + { + .stream = stream, + .tag = tag, + .size = &size, + }; + char *buffer; + + if (WINE_UNIX_CALL(unix_wg_parser_stream_get_tag, ¶ms) != STATUS_BUFFER_TOO_SMALL) + return NULL; + if (!(buffer = malloc(size))) + { + ERR("No memory.\n"); + return NULL; + } + params.buffer = buffer; + if (WINE_UNIX_CALL(unix_wg_parser_stream_get_tag, ¶ms)) + { + ERR("wg_parser_stream_get_tag failed unexpectedly.\n"); + free(buffer); + return NULL; + } + return buffer; +} + void wg_parser_stream_seek(struct wg_parser_stream *stream, double rate, uint64_t start_pos, uint64_t stop_pos, DWORD start_flags, DWORD stop_flags) { diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 9bb7a441a8f..59047a5ef82 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -1481,7 +1481,37 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_ descriptors = malloc(object->stream_count * sizeof(IMFStreamDescriptor *)); for (i = 0; i < object->stream_count; i++) { + static const struct + { + enum wg_parser_tag tag; + const GUID *mf_attr; + } + tags[] = + { + {WG_PARSER_TAG_LANGUAGE, &MF_SD_LANGUAGE}, + }; + unsigned int j; + WCHAR *strW; + DWORD len; + char *str; + IMFMediaStream_GetStreamDescriptor(&object->streams[i]->IMFMediaStream_iface, &descriptors[i]); + + for (j = 0; j < ARRAY_SIZE(tags); ++j) + { + if (!(str = wg_parser_stream_get_tag(object->streams[i]->wg_stream, tags[j].tag))) + continue; + if (!(len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0))) + { + free(str); + continue; + } + strW = malloc(len * sizeof(*strW)); + if (MultiByteToWideChar(CP_UTF8, 0, str, -1, strW, len)) + IMFStreamDescriptor_SetString(descriptors[i], tags[j].mf_attr, strW); + free(strW); + free(str); + } }
if (FAILED(hr = MFCreatePresentationDescriptor(object->stream_count, descriptors, &object->pres_desc))) diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index 09aa68eb4cc..f048426b27a 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -252,6 +252,20 @@ struct wg_parser_stream_get_duration_params UINT64 duration; };
+enum wg_parser_tag +{ + WG_PARSER_TAG_LANGUAGE, + WG_PARSER_TAG_COUNT +}; + +struct wg_parser_stream_get_tag_params +{ + struct wg_parser_stream *stream; + enum wg_parser_tag tag; + char *buffer; + UINT32 *size; +}; + struct wg_parser_stream_seek_params { struct wg_parser_stream *stream; @@ -312,6 +326,7 @@ enum unix_funcs unix_wg_parser_stream_notify_qos,
unix_wg_parser_stream_get_duration, + unix_wg_parser_stream_get_tag, unix_wg_parser_stream_seek,
unix_wg_transform_create, diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 14970d327c8..dc98901c226 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -34,6 +34,8 @@ #include <gst/video/video.h> #include <gst/audio/audio.h>
+#include "ntstatus.h" +#define WIN32_NO_STATUS #include "winternl.h" #include "dshow.h"
@@ -107,9 +109,10 @@ struct wg_parser_stream GstBuffer *buffer; GstMapInfo map_info;
- bool flushing, eos, enabled, has_caps; + bool flushing, eos, enabled, has_caps, has_tags, has_buffer;
uint64_t duration; + gchar *tags[WG_PARSER_TAG_COUNT]; };
static NTSTATUS wg_parser_get_stream_count(void *args) @@ -397,6 +400,24 @@ static NTSTATUS wg_parser_stream_get_duration(void *args) return S_OK; }
+static NTSTATUS wg_parser_stream_get_tag(void *args) +{ + struct wg_parser_stream_get_tag_params *params = args; + uint32_t len; + + if (params->tag >= WG_PARSER_TAG_COUNT) + return STATUS_INVALID_PARAMETER; + if (!params->stream->tags[params->tag]) + return STATUS_NOT_FOUND; + if ((len = strlen(params->stream->tags[params->tag]) + 1) > *params->size) + { + *params->size = len; + return STATUS_BUFFER_TOO_SMALL; + } + memcpy(params->buffer, params->stream->tags[params->tag], len); + return STATUS_SUCCESS; +} + static NTSTATUS wg_parser_stream_seek(void *args) { GstSeekType start_type = GST_SEEK_TYPE_SET, stop_type = GST_SEEK_TYPE_SET; @@ -574,6 +595,13 @@ static gboolean sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) break; }
+ case GST_EVENT_TAG: + pthread_mutex_lock(&parser->mutex); + stream->has_tags = true; + pthread_cond_signal(&parser->init_cond); + pthread_mutex_unlock(&parser->mutex); + break; + default: GST_WARNING("Ignoring "%s" event.", GST_EVENT_TYPE_NAME(event)); } @@ -590,6 +618,12 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu
pthread_mutex_lock(&parser->mutex);
+ if (!stream->has_buffer) + { + stream->has_buffer = true; + pthread_cond_signal(&parser->init_cond); + } + /* Allow this buffer to be flushed by GStreamer. We are effectively * implementing a queue object here. */
@@ -756,6 +790,8 @@ static struct wg_parser_stream *create_stream(struct wg_parser *parser)
static void free_stream(struct wg_parser_stream *stream) { + unsigned int i; + if (stream->their_src) { if (stream->post_sink) @@ -771,6 +807,11 @@ static void free_stream(struct wg_parser_stream *stream) pthread_cond_destroy(&stream->event_cond); pthread_cond_destroy(&stream->event_empty_cond);
+ for (i = 0; i < ARRAY_SIZE(stream->tags); ++i) + { + if (stream->tags[i]) + g_free(stream->tags[i]); + } free(stream); }
@@ -1232,6 +1273,19 @@ static gboolean src_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) return ret; }
+static void query_tags(struct wg_parser_stream *stream) +{ + GstTagList *tag_list; + GstEvent *tag_event; + + if (!(tag_event = gst_pad_get_sticky_event(stream->their_src, GST_EVENT_TAG, 0))) + return; + + gst_event_parse_tag(tag_event, &tag_list); + gst_tag_list_get_string(tag_list, "language-code", &stream->tags[WG_PARSER_TAG_LANGUAGE]); + gst_event_unref(tag_event); +} + static NTSTATUS wg_parser_connect(void *args) { GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE("quartz_src", @@ -1290,7 +1344,8 @@ static NTSTATUS wg_parser_connect(void *args) struct wg_parser_stream *stream = parser->streams[i]; gint64 duration;
- while (!stream->has_caps && !parser->error) + /* If we receieved a buffer waiting for tags or caps does not make sense anymore. */ + while ((!stream->has_caps || !stream->has_tags) && !parser->error && !stream->has_buffer) pthread_cond_wait(&parser->init_cond, &parser->mutex);
/* GStreamer doesn't actually provide any guarantees about when duration @@ -1354,6 +1409,8 @@ static NTSTATUS wg_parser_connect(void *args) } }
+ query_tags(stream); + /* Now that we're fully initialized, enable the stream so that further * samples get queued instead of being discarded. We don't actually need * the samples (in particular, the frontend should seek before @@ -1676,6 +1733,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = X(wg_parser_stream_notify_qos),
X(wg_parser_stream_get_duration), + X(wg_parser_stream_get_tag), X(wg_parser_stream_seek),
X(wg_transform_create), diff --git a/dlls/winegstreamer/wm_asyncreader.c b/dlls/winegstreamer/wm_asyncreader.c index 409ebeae1af..e668542da71 100644 --- a/dlls/winegstreamer/wm_asyncreader.c +++ b/dlls/winegstreamer/wm_asyncreader.c @@ -17,6 +17,7 @@ */
#include "gst_private.h" +#include "wmsdk.h"
#include "wine/list.h"
diff --git a/dlls/winegstreamer/wm_reader.c b/dlls/winegstreamer/wm_reader.c index 804af4a9aed..0e9e7927549 100644 --- a/dlls/winegstreamer/wm_reader.c +++ b/dlls/winegstreamer/wm_reader.c @@ -17,6 +17,8 @@ */
#include "gst_private.h" +#include "initguid.h" +#include "wmsdk.h"
WINE_DEFAULT_DEBUG_CHANNEL(wmvcore);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/winegstreamer/media_source.c | 1 + dlls/winegstreamer/unixlib.h | 1 + dlls/winegstreamer/wg_parser.c | 33 +++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+)
diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 59047a5ef82..542189b28f5 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -1489,6 +1489,7 @@ static HRESULT media_source_constructor(IMFByteStream *bytestream, struct media_ tags[] = { {WG_PARSER_TAG_LANGUAGE, &MF_SD_LANGUAGE}, + {WG_PARSER_TAG_NAME, &MF_SD_STREAM_NAME}, }; unsigned int j; WCHAR *strW; diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index f048426b27a..2b5b8af37d5 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -255,6 +255,7 @@ struct wg_parser_stream_get_duration_params enum wg_parser_tag { WG_PARSER_TAG_LANGUAGE, + WG_PARSER_TAG_NAME, WG_PARSER_TAG_COUNT };
diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index dc98901c226..d2db1f039e4 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -1275,14 +1275,47 @@ static gboolean src_event_cb(GstPad *pad, GstObject *parent, GstEvent *event)
static void query_tags(struct wg_parser_stream *stream) { + const gchar *struct_name; GstTagList *tag_list; GstEvent *tag_event; + guint i, tag_count; + const GValue *val; + GstSample *sample; + GstBuffer *buf; + gsize size;
if (!(tag_event = gst_pad_get_sticky_event(stream->their_src, GST_EVENT_TAG, 0))) return;
gst_event_parse_tag(tag_event, &tag_list); gst_tag_list_get_string(tag_list, "language-code", &stream->tags[WG_PARSER_TAG_LANGUAGE]); + + /* Extract stream name from Quick Time demuxer private tag where it puts unrecognized chunks. */ + tag_count = gst_tag_list_get_tag_size(tag_list, "private-qt-tag"); + for (i = 0; i < tag_count; ++i) + { + if (!(val = gst_tag_list_get_value_index(tag_list, "private-qt-tag", i))) + continue; + if (!GST_VALUE_HOLDS_SAMPLE(val) || !(sample = gst_value_get_sample(val))) + continue; + struct_name = gst_structure_get_name(gst_sample_get_info(sample)); + if (!struct_name || strcmp(struct_name, "application/x-gst-qt-name-tag")) + continue; + if (!(buf = gst_sample_get_buffer(sample))) + continue; + if ((size = gst_buffer_get_size(buf)) < 8) + continue; + size -= 8; + if (!(stream->tags[WG_PARSER_TAG_NAME] = g_malloc(size + 1))) + continue; + if (gst_buffer_extract(buf, 8, stream->tags[WG_PARSER_TAG_NAME], size) != size) + { + g_free(stream->tags[WG_PARSER_TAG_NAME]); + stream->tags[WG_PARSER_TAG_NAME] = NULL; + continue; + } + stream->tags[WG_PARSER_TAG_NAME][size] = 0; + } gst_event_unref(tag_event); }
From: Paul Gofman pgofman@codeweavers.com
--- dlls/mfplay/tests/Makefile.in | 2 +- dlls/mfplay/tests/mfplay.c | 14 +++++++++++++- dlls/mfplay/tests/test.mp4 | Bin 1554 -> 1610 bytes 3 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/dlls/mfplay/tests/Makefile.in b/dlls/mfplay/tests/Makefile.in index e790fa3e2ad..aabc08258f9 100644 --- a/dlls/mfplay/tests/Makefile.in +++ b/dlls/mfplay/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = mfplay.dll -IMPORTS = mfplay user32 uuid mfuuid mfplat +IMPORTS = mfplay user32 uuid mfuuid mfplat ole32
C_SRCS = \ mfplay.c diff --git a/dlls/mfplay/tests/mfplay.c b/dlls/mfplay/tests/mfplay.c index 50ecb1b09b9..6cecd3bbdd9 100644 --- a/dlls/mfplay/tests/mfplay.c +++ b/dlls/mfplay/tests/mfplay.c @@ -356,7 +356,19 @@ static void test_media_item(void) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
hr = IMFPMediaItem_GetStreamAttribute(item, 0, &MF_SD_LANGUAGE, &propvar); - ok(hr == MF_E_ATTRIBUTENOTFOUND, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(propvar.vt == VT_LPWSTR, "Unexpected vt %u.\n", propvar.vt); + ok(!wcscmp(propvar.pwszVal, L"en"), "Unexpected value %s.\n", debugstr_w(propvar.pwszVal)); + PropVariantClear(&propvar); + + hr = IMFPMediaItem_GetStreamAttribute(item, 0, &MF_SD_STREAM_NAME, &propvar); + ok(hr == S_OK || broken(hr == MF_E_ATTRIBUTENOTFOUND) /* Before Win10 1607. */, "Unexpected hr %#lx.\n", hr); + if (hr == S_OK) + { + ok(propvar.vt == VT_LPWSTR, "Unexpected vt %u.\n", propvar.vt); + ok(!wcscmp(propvar.pwszVal, L"test"), "Unexpected value %s.\n", debugstr_w(propvar.pwszVal)); + PropVariantClear(&propvar); + }
hr = IMFPMediaItem_GetPresentationAttribute(item, &MF_PD_DURATION, &propvar); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); diff --git a/dlls/mfplay/tests/test.mp4 b/dlls/mfplay/tests/test.mp4 index a5bbca6bbf7692eab047050b79b8ccc83516e6b4..6770d064716330cf4a7652f826c09a3eabce119e 100644 GIT binary patch delta 137 zcmbQlbBbrf3MR&-lUFhoGoGI;%iPXrJb5Xz0+Z<R$;X+E81GH~#cVG*uec;Bhk=3N zRdGpi3Xlc?Mj*a5If!MeqCi1naRF2WBr1|rQd9z@fxr+*8<tFFVO=jMQkqhd2o&PU ZOUz9zNi8m!yn!`}!&1-4T+h(J006FdBrE^`
delta 85 zcmX@bGl^%z3MR(f$t#(P8K+N{Wo~EWpS+Y=fhqLJ<m1dnjJqcPVz!s8DlSRNVPIf5 lR$Nk?0;EBJ5s0@<4r19lS(|mu<kzg>92R<p=6Z$(h5(wY8D9VZ
- Allocate buffer for wg_parser_stream_get_tag() dynamically. I had to move #include "wmsdk.h" from gst_private.h header to be able to include ntstatus.h (to check the result in wg_parser_stream_get_tag()*. wmsdk.h includes nserror.h which redefines STATUS_SEVERITY* which causes compiler warnings. I checked with Win SDK 22621 that the relevant parts of nserror.h, wmsdk.h and ntstatus.h are the same, so it looks like the only right way to workaround that is not to include wmsdk.h where not necessary;
Probably, yeah. We should probably make some of those includes more localized, although currently the backend still uses a lot of dshow and mfplat definitions for things.
- use gst_buffer_extract() instead of mapping the buffer. gst_buffer_extract_dup() does not fit well because the string is stored without terminating 0 char and it is probably more straightforward to continue adding that in query_tags() at once rather than deal with non zero terminated strings elsewhere. Also given that for language we get zero terminated string at once from gst_tag_list_get_string().
Ah, makes sense, I didn't think of that.
This merge request was approved by Zebediah Figura.
This merge request was approved by Nikolay Sivov.