Resolves https://bugs.winehq.org/show_bug.cgi?id=9127 . (Some of the named programs in that issue may require additional currently-missing functionality, I didn't check; but if so, that's separate issues.)
Tested with
- winetest on Windows 7 - winetest on Windows 10 - winetest in Wine, of course - microkiri https://bugs.winehq.org/show_bug.cgi?id=9127#c102 - Wagamama High Spec Trial Edition https://wagahigh.com/download_trial.php#normal (ダウンロード means download) - Ninki Seiyuu no Tsukurikata Trial https://archive.org/details/sayou_trial
(WMV files in microkiri and Wagamama don't work, but that's separate issues. Also, they need the LC_ALL=ja_JP env, or they throw various goofy errors.)
-- v22: quartz/tests: Add tests for CLSID_CMpegVideoCodec. quartz/tests: Add tests for new CLSID_MPEG1Splitter functionality. winegstreamer: Implement CLSID_CMpegVideoCodec. winegstreamer: Add program stream and video output support to CLSID_MPEG1Splitter. winegstreamer: Add WG_MAJOR_TYPE_VIDEO_MPEG1 media type. winegstreamer: Use the new output_compressed property instead of mpegaudioparse in MPEG splitter. winegstreamer: Add output_compressed parameter to wg_parser_create(). winegstreamer: Implement parts of IAMStreamSelect::Info in CLSID_MPEG1Splitter. winegstreamer: Implement IAMStreamSelect::Count in CLSID_MPEG1Splitter. winegstreamer: Improve and clean up some debug logs. winegstreamer: Include the framerate when converting video format to GstCaps. winegstreamer: Seek to end of stream instead of to stream duration. winegstreamer: Clamp QoS events to stay inside the stream's running time. winegstreamer: Don't read format from unparsed MPEG audio.
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/wg_format.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/dlls/winegstreamer/wg_format.c b/dlls/winegstreamer/wg_format.c index 2353839bbc4..9c6e49afcc7 100644 --- a/dlls/winegstreamer/wg_format.c +++ b/dlls/winegstreamer/wg_format.c @@ -286,6 +286,7 @@ void wg_format_from_caps(struct wg_format *format, const GstCaps *caps) { const GstStructure *structure = gst_caps_get_structure(caps, 0); const char *name = gst_structure_get_name(structure); + gboolean parsed;
memset(format, 0, sizeof(*format));
@@ -303,7 +304,7 @@ void wg_format_from_caps(struct wg_format *format, const GstCaps *caps) if (gst_video_info_from_caps(&info, caps)) wg_format_from_video_info(format, &info); } - else if (!strcmp(name, "audio/mpeg")) + else if (!strcmp(name, "audio/mpeg") && gst_structure_get_boolean(structure, "parsed", &parsed) && parsed) { wg_format_from_caps_audio_mpeg1(format, caps); }
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/wg_parser.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 2d2e3450ccb..70c25d8c0a0 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -461,6 +461,7 @@ static NTSTATUS wg_parser_stream_notify_qos(void *args) { const struct wg_parser_stream_notify_qos_params *params = args; struct wg_parser_stream *stream = get_stream(params->stream); + GstClockTimeDiff diff = params->diff * 100; GstClockTime stream_time; GstEvent *event;
@@ -468,6 +469,8 @@ static NTSTATUS wg_parser_stream_notify_qos(void *args) * file (or other medium), but gst_event_new_qos() expects the timestamp in * running time. */ stream_time = gst_segment_to_running_time(&stream->segment, GST_FORMAT_TIME, params->timestamp * 100); + if (diff < (GstClockTimeDiff)-stream_time) + diff = -stream_time; if (stream_time == -1) { /* This can happen legitimately if the sample falls outside of the @@ -478,7 +481,7 @@ static NTSTATUS wg_parser_stream_notify_qos(void *args) }
if (!(event = gst_event_new_qos(params->underflow ? GST_QOS_TYPE_UNDERFLOW : GST_QOS_TYPE_OVERFLOW, - params->proportion, params->diff * 100, stream_time))) + params->proportion, diff, stream_time))) GST_ERROR("Failed to create QOS event.\n"); push_event(stream->my_sink, event);
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/wg_parser.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 70c25d8c0a0..e097325a419 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -436,8 +436,11 @@ static NTSTATUS wg_parser_stream_seek(void *args) const struct wg_parser_stream_seek_params *params = args; DWORD start_flags = params->start_flags; DWORD stop_flags = params->stop_flags; + const struct wg_parser_stream *stream; GstSeekFlags flags = 0;
+ stream = get_stream(params->stream); + if (start_flags & AM_SEEKING_SeekToKeyFrame) flags |= GST_SEEK_FLAG_KEY_UNIT; if (start_flags & AM_SEEKING_Segment) @@ -450,8 +453,9 @@ static NTSTATUS wg_parser_stream_seek(void *args) if ((stop_flags & AM_SEEKING_PositioningBitsMask) == AM_SEEKING_NoPositioning) stop_type = GST_SEEK_TYPE_NONE;
- if (!push_event(get_stream(params->stream)->my_sink, gst_event_new_seek(params->rate, GST_FORMAT_TIME, - flags, start_type, params->start_pos * 100, stop_type, params->stop_pos * 100))) + if (!push_event(stream->my_sink, gst_event_new_seek(params->rate, GST_FORMAT_TIME, + flags, start_type, params->start_pos * 100, stop_type, + params->stop_pos == stream->duration ? -1 : params->stop_pos * 100))) GST_ERROR("Failed to seek.\n");
return S_OK;
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/wg_format.c | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/dlls/winegstreamer/wg_format.c b/dlls/winegstreamer/wg_format.c index 9c6e49afcc7..743f674bcd6 100644 --- a/dlls/winegstreamer/wg_format.c +++ b/dlls/winegstreamer/wg_format.c @@ -477,6 +477,11 @@ static GstCaps *wg_format_to_caps_video(const struct wg_format *format) return NULL;
gst_video_info_set_format(&info, video_format, format->u.video.width, abs(format->u.video.height)); + if (format->u.video.fps_d) + { + info.fps_n = format->u.video.fps_n; + info.fps_d = format->u.video.fps_d; + } if ((caps = gst_video_info_to_caps(&info))) { for (i = 0; i < gst_caps_get_size(caps); ++i)
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/wg_format.c | 11 ++++------- dlls/winegstreamer/wg_parser.c | 27 ++++++++++----------------- dlls/winegstreamer/wg_transform.c | 10 ++-------- 3 files changed, 16 insertions(+), 32 deletions(-)
diff --git a/dlls/winegstreamer/wg_format.c b/dlls/winegstreamer/wg_format.c index 743f674bcd6..d8a580b0197 100644 --- a/dlls/winegstreamer/wg_format.c +++ b/dlls/winegstreamer/wg_format.c @@ -181,17 +181,17 @@ static void wg_format_from_caps_audio_mpeg1(struct wg_format *format, const GstC
if (!gst_structure_get_int(structure, "layer", &layer)) { - GST_WARNING("Missing "layer" value."); + GST_WARNING("Missing "layer" value in %" GST_PTR_FORMAT ".", caps); return; } if (!gst_structure_get_int(structure, "channels", &channels)) { - GST_WARNING("Missing "channels" value."); + GST_WARNING("Missing "channels" value in %" GST_PTR_FORMAT ".", caps); return; } if (!gst_structure_get_int(structure, "rate", &rate)) { - GST_WARNING("Missing "rate" value."); + GST_WARNING("Missing "rate" value in %" GST_PTR_FORMAT ".", caps); return; }
@@ -318,10 +318,7 @@ void wg_format_from_caps(struct wg_format *format, const GstCaps *caps) } else { - gchar *str = gst_caps_to_string(caps); - - GST_FIXME("Unhandled caps %s.", str); - g_free(str); + GST_FIXME("Unhandled caps %" GST_PTR_FORMAT ".", caps); } }
diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index e097325a419..e36fbb92fae 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -456,7 +456,7 @@ static NTSTATUS wg_parser_stream_seek(void *args) if (!push_event(stream->my_sink, gst_event_new_seek(params->rate, GST_FORMAT_TIME, flags, start_type, params->start_pos * 100, stop_type, params->stop_pos == stream->duration ? -1 : params->stop_pos * 100))) - GST_ERROR("Failed to seek.\n"); + GST_ERROR("Failed to seek.");
return S_OK; } @@ -480,13 +480,12 @@ static NTSTATUS wg_parser_stream_notify_qos(void *args) /* This can happen legitimately if the sample falls outside of the * segment bounds. GStreamer elements shouldn't present the sample in * that case, but DirectShow doesn't care. */ - GST_LOG("Ignoring QoS event.\n"); + GST_LOG("Ignoring QoS event."); return S_OK; } - if (!(event = gst_event_new_qos(params->underflow ? GST_QOS_TYPE_UNDERFLOW : GST_QOS_TYPE_OVERFLOW, params->proportion, diff, stream_time))) - GST_ERROR("Failed to create QOS event.\n"); + GST_ERROR("Failed to create QOS event."); push_event(stream->my_sink, event);
return S_OK; @@ -679,6 +678,7 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu
if (!stream->enabled) { + GST_LOG("Stream is disabled; discarding buffer."); pthread_mutex_unlock(&parser->mutex); gst_buffer_unref(buffer); return GST_FLOW_OK; @@ -695,7 +695,7 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu if (!gst_buffer_map(buffer, &stream->map_info, GST_MAP_READ)) { pthread_mutex_unlock(&parser->mutex); - GST_ERROR("Failed to map buffer.\n"); + GST_ERROR("Failed to map buffer."); gst_buffer_unref(buffer); return GST_FLOW_ERROR; } @@ -725,7 +725,6 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) case GST_QUERY_CAPS: { GstCaps *caps, *filter, *temp; - gchar *str; gsize i;
gst_query_parse_caps(query, &filter); @@ -742,9 +741,7 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) gst_structure_remove_fields(gst_caps_get_structure(caps, i), "framerate", "pixel-aspect-ratio", NULL);
- str = gst_caps_to_string(caps); - GST_LOG("Stream caps are "%s".", str); - g_free(str); + GST_LOG("Stream caps are "%" GST_PTR_FORMAT "".", caps);
if (filter) { @@ -779,12 +776,8 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query)
pthread_mutex_unlock(&parser->mutex);
- if (!ret && gst_debug_category_get_threshold(GST_CAT_DEFAULT) >= GST_LEVEL_WARNING) - { - gchar *str = gst_caps_to_string(caps); - GST_WARNING("Rejecting caps "%s".", str); - g_free(str); - } + if (!ret) + GST_WARNING("Rejecting caps "%" GST_PTR_FORMAT "".", caps); gst_query_set_accept_caps_result(query, ret); return TRUE; } @@ -1585,7 +1578,7 @@ static NTSTATUS wg_parser_connect(void *args) ret = gst_element_get_state(parser->container, NULL, NULL, -1); if (ret == GST_STATE_CHANGE_FAILURE) { - GST_ERROR("Failed to play stream.\n"); + GST_ERROR("Failed to play stream."); goto out; }
@@ -1648,7 +1641,7 @@ static NTSTATUS wg_parser_connect(void *args) if (stream->eos) { stream->duration = 0; - GST_WARNING("Failed to query duration.\n"); + GST_WARNING("Failed to query duration."); break; }
diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index e2b14527a20..d033ff27f43 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -196,7 +196,6 @@ static gboolean transform_sink_query_cb(GstPad *pad, GstObject *parent, GstQuery case GST_QUERY_CAPS: { GstCaps *caps, *filter, *temp; - gchar *str;
gst_query_parse_caps(query, &filter); if (!(caps = wg_format_to_caps(&transform->output_format))) @@ -209,9 +208,7 @@ static gboolean transform_sink_query_cb(GstPad *pad, GstObject *parent, GstQuery caps = temp; }
- str = gst_caps_to_string(caps); - GST_INFO("Returning caps %s", str); - g_free(str); + GST_INFO("Returning caps %" GST_PTR_FORMAT, caps);
gst_query_set_caps_result(query, caps); gst_caps_unref(caps); @@ -498,7 +495,6 @@ NTSTATUS wg_transform_set_output_format(void *args) const struct wg_format *format = params->format; GstSample *sample; GstCaps *caps; - gchar *str;
if (!(caps = wg_format_to_caps(format))) { @@ -537,9 +533,7 @@ NTSTATUS wg_transform_set_output_format(void *args) return STATUS_UNSUCCESSFUL; }
- str = gst_caps_to_string(caps); - GST_INFO("Configured new caps %s.", str); - g_free(str); + GST_INFO("Configured new caps %" GST_PTR_FORMAT ".", caps);
/* Ideally and to be fully compatible with native transform, the queued * output buffers will need to be converted to the new output format and
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/quartz_parser.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index f9d8d79b259..b1a463af531 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -1511,8 +1511,15 @@ static ULONG WINAPI stream_select_Release(IAMStreamSelect *iface)
static HRESULT WINAPI stream_select_Count(IAMStreamSelect *iface, DWORD *count) { - FIXME("iface %p, count %p, stub!\n", iface, count); - return E_NOTIMPL; + struct parser *filter = impl_from_IAMStreamSelect(iface); + TRACE("filter %p, count %p\n", filter, count); + EnterCriticalSection(&filter->filter.filter_cs); + if (filter->sink.pin.peer) + *count = wg_parser_get_stream_count(filter->wg_parser); + else + *count = 0; + LeaveCriticalSection(&filter->filter.filter_cs); + return S_OK; }
static HRESULT WINAPI stream_select_Info(IAMStreamSelect *iface, LONG index,
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/quartz_parser.c | 38 +++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-)
diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index b1a463af531..fe050602db6 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -1526,9 +1526,41 @@ static HRESULT WINAPI stream_select_Info(IAMStreamSelect *iface, LONG index, AM_MEDIA_TYPE **mt, DWORD *flags, LCID *lcid, DWORD *group, WCHAR **name, IUnknown **object, IUnknown **unknown) { - FIXME("iface %p, index %ld, mt %p, flags %p, lcid %p, group %p, name %p, object %p, unknown %p, stub!\n", - iface, index, mt, flags, lcid, group, name, object, unknown); - return E_NOTIMPL; + struct parser *filter = impl_from_IAMStreamSelect(iface); + HRESULT hr = S_OK; + + FIXME("filter %p, index %ld, mt %p, flags %p, lcid %p, group %p, name %p, object %p, unknown %p, semi-stub!\n", + filter, index, mt, flags, lcid, group, name, object, unknown); + EnterCriticalSection(&filter->filter.filter_cs); + + if (!filter->sink.pin.peer) + { + LeaveCriticalSection(&filter->filter.filter_cs); + return VFW_E_NOT_CONNECTED; + } + if (index < 0 || index >= filter->source_count) + { + LeaveCriticalSection(&filter->filter.filter_cs); + return S_FALSE; + } + + if (mt) + *mt = CreateMediaType(&filter->sources[index]->pin.pin.mt); + if (flags) /* todo */ + *flags = 0; + if (lcid) /* todo */ + *lcid = 0; + if (group) /* todo */ + *group = 0; + if (name) /* todo */ + *name = NULL; + if (object) /* todo */ + *object = NULL; + if (unknown) + *unknown = NULL; + + LeaveCriticalSection(&filter->filter.filter_cs); + return hr; }
static HRESULT WINAPI stream_select_Enable(IAMStreamSelect *iface, LONG index, DWORD flags)
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/gst_private.h | 2 +- dlls/winegstreamer/main.c | 3 ++- dlls/winegstreamer/media_source.c | 2 +- dlls/winegstreamer/quartz_parser.c | 12 ++++++------ dlls/winegstreamer/unixlib.h | 1 + dlls/winegstreamer/wg_parser.c | 4 +++- dlls/winegstreamer/wm_reader.c | 2 +- 7 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 23b59766d0c..4db6c2dbbb6 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -70,7 +70,7 @@ HRESULT wg_sample_queue_create(struct wg_sample_queue **out); void wg_sample_queue_destroy(struct wg_sample_queue *queue); void wg_sample_queue_flush(struct wg_sample_queue *queue, bool all);
-wg_parser_t wg_parser_create(enum wg_parser_type type); +wg_parser_t wg_parser_create(enum wg_parser_type type, bool output_compressed); void wg_parser_destroy(wg_parser_t parser);
HRESULT wg_parser_connect(wg_parser_t parser, uint64_t file_size); diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index 55817922e9b..b855c5080c6 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -66,11 +66,12 @@ bool array_reserve(void **elements, size_t *capacity, size_t count, size_t size) return TRUE; }
-wg_parser_t wg_parser_create(enum wg_parser_type type) +wg_parser_t wg_parser_create(enum wg_parser_type type, bool output_compressed) { struct wg_parser_create_params params = { .type = type, + .output_compressed = output_compressed, .err_on = ERR_ON(quartz), .warn_on = WARN_ON(quartz), }; diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 8b9d42ea3f0..9cc54f8e146 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -1643,7 +1643,7 @@ static HRESULT media_source_create(struct object_context *context, IMFMediaSourc if (FAILED(hr = MFAllocateWorkQueue(&object->async_commands_queue))) goto fail;
- if (!(parser = wg_parser_create(WG_PARSER_DECODEBIN))) + if (!(parser = wg_parser_create(WG_PARSER_DECODEBIN, FALSE))) { hr = E_OUTOFMEMORY; goto fail; diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index fe050602db6..eb31f19b33d 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -1444,14 +1444,14 @@ static HRESULT decodebin_parser_source_get_media_type(struct parser_source *pin, return VFW_S_NO_MORE_ITEMS; }
-static HRESULT parser_create(enum wg_parser_type type, struct parser **parser) +static HRESULT parser_create(enum wg_parser_type type, BOOL output_compressed, struct parser **parser) { struct parser *object;
if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY;
- if (!(object->wg_parser = wg_parser_create(type))) + if (!(object->wg_parser = wg_parser_create(type, output_compressed))) { free(object); return E_OUTOFMEMORY; @@ -1471,7 +1471,7 @@ HRESULT decodebin_parser_create(IUnknown *outer, IUnknown **out) struct parser *object; HRESULT hr;
- if (FAILED(hr = parser_create(WG_PARSER_DECODEBIN, &object))) + if (FAILED(hr = parser_create(WG_PARSER_DECODEBIN, FALSE, &object))) return hr;
strmbase_filter_init(&object->filter, outer, &CLSID_decodebin_parser, &filter_ops); @@ -2039,7 +2039,7 @@ HRESULT wave_parser_create(IUnknown *outer, IUnknown **out) struct parser *object; HRESULT hr;
- if (FAILED(hr = parser_create(WG_PARSER_WAVPARSE, &object))) + if (FAILED(hr = parser_create(WG_PARSER_WAVPARSE, FALSE, &object))) return hr;
strmbase_filter_init(&object->filter, outer, &CLSID_WAVEParser, &filter_ops); @@ -2117,7 +2117,7 @@ HRESULT avi_splitter_create(IUnknown *outer, IUnknown **out) struct parser *object; HRESULT hr;
- if (FAILED(hr = parser_create(WG_PARSER_AVIDEMUX, &object))) + if (FAILED(hr = parser_create(WG_PARSER_AVIDEMUX, FALSE, &object))) return hr;
strmbase_filter_init(&object->filter, outer, &CLSID_AviSplitter, &filter_ops); @@ -2214,7 +2214,7 @@ HRESULT mpeg_splitter_create(IUnknown *outer, IUnknown **out) struct parser *object; HRESULT hr;
- if (FAILED(hr = parser_create(WG_PARSER_MPEGAUDIOPARSE, &object))) + if (FAILED(hr = parser_create(WG_PARSER_MPEGAUDIOPARSE, FALSE, &object))) return hr;
strmbase_filter_init(&object->filter, outer, &CLSID_MPEG1Splitter, &mpeg_splitter_ops); diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index d00790c6b05..14895cd2fca 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -215,6 +215,7 @@ struct wg_parser_create_params { wg_parser_t parser; wg_parser_type type; + UINT8 output_compressed; UINT8 err_on; UINT8 warn_on; }; diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index e36fbb92fae..9aec97231e0 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -78,6 +78,7 @@ struct wg_parser pthread_mutex_t mutex;
pthread_cond_t init_cond; + bool output_compressed; bool no_more_pads, has_duration, error; bool err_on, warn_on;
@@ -976,7 +977,7 @@ static void pad_added_cb(GstElement *element, GstPad *pad, gpointer user) gst_caps_unref(caps);
/* For compressed stream, create an extra decodebin to decode it. */ - if (format_is_compressed(&stream->codec_format)) + if (!parser->output_compressed && format_is_compressed(&stream->codec_format)) { if (!stream_decodebin_create(stream)) { @@ -1876,6 +1877,7 @@ static NTSTATUS wg_parser_create(void *args) pthread_cond_init(&parser->read_cond, NULL); pthread_cond_init(&parser->read_done_cond, NULL); parser->init_gst = init_funcs[params->type]; + parser->output_compressed = params->output_compressed; parser->err_on = params->err_on; parser->warn_on = params->warn_on; GST_DEBUG("Created winegstreamer parser %p.", parser); diff --git a/dlls/winegstreamer/wm_reader.c b/dlls/winegstreamer/wm_reader.c index 6440f8dbb60..c8b98c7c769 100644 --- a/dlls/winegstreamer/wm_reader.c +++ b/dlls/winegstreamer/wm_reader.c @@ -1459,7 +1459,7 @@ static HRESULT init_stream(struct wm_reader *reader, QWORD file_size) HRESULT hr; WORD i;
- if (!(wg_parser = wg_parser_create(WG_PARSER_DECODEBIN))) + if (!(wg_parser = wg_parser_create(WG_PARSER_DECODEBIN, FALSE))) return E_OUTOFMEMORY;
reader->wg_parser = wg_parser;
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/quartz_parser.c | 2 +- dlls/winegstreamer/unixlib.h | 1 - dlls/winegstreamer/wg_parser.c | 26 -------------------------- 3 files changed, 1 insertion(+), 28 deletions(-)
diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index eb31f19b33d..0e8a5068aff 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -2214,7 +2214,7 @@ HRESULT mpeg_splitter_create(IUnknown *outer, IUnknown **out) struct parser *object; HRESULT hr;
- if (FAILED(hr = parser_create(WG_PARSER_MPEGAUDIOPARSE, FALSE, &object))) + if (FAILED(hr = parser_create(WG_PARSER_DECODEBIN, TRUE, &object))) return hr;
strmbase_filter_init(&object->filter, outer, &CLSID_MPEG1Splitter, &mpeg_splitter_ops); diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index 14895cd2fca..201131e31ab 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -202,7 +202,6 @@ enum wg_parser_type { WG_PARSER_DECODEBIN, WG_PARSER_AVIDEMUX, - WG_PARSER_MPEGAUDIOPARSE, WG_PARSER_WAVPARSE, };
diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 9aec97231e0..2015b1f46c5 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -1806,31 +1806,6 @@ static BOOL avi_parser_init_gst(struct wg_parser *parser) return TRUE; }
-static BOOL mpeg_audio_parser_init_gst(struct wg_parser *parser) -{ - struct wg_parser_stream *stream; - GstElement *element; - - if (!(element = create_element("mpegaudioparse", "good"))) - return FALSE; - - gst_bin_add(GST_BIN(parser->container), element); - - if (!link_src_to_element(parser->my_src, element)) - return FALSE; - - if (!(stream = create_stream(parser))) - return FALSE; - - if (!link_element_to_sink(element, stream->my_sink)) - return FALSE; - gst_pad_set_active(stream->my_sink, 1); - - parser->no_more_pads = true; - - return TRUE; -} - static BOOL wave_parser_init_gst(struct wg_parser *parser) { struct wg_parser_stream *stream; @@ -1862,7 +1837,6 @@ static NTSTATUS wg_parser_create(void *args) { [WG_PARSER_DECODEBIN] = decodebin_parser_init_gst, [WG_PARSER_AVIDEMUX] = avi_parser_init_gst, - [WG_PARSER_MPEGAUDIOPARSE] = mpeg_audio_parser_init_gst, [WG_PARSER_WAVPARSE] = wave_parser_init_gst, };
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/mfplat.c | 1 + dlls/winegstreamer/quartz_parser.c | 140 +++++++++++++++++++++-------- dlls/winegstreamer/unixlib.h | 6 ++ dlls/winegstreamer/wg_format.c | 54 +++++++++++ dlls/winegstreamer/wg_transform.c | 2 + dlls/winegstreamer/wm_reader.c | 4 + 6 files changed, 172 insertions(+), 35 deletions(-)
diff --git a/dlls/winegstreamer/mfplat.c b/dlls/winegstreamer/mfplat.c index c7906a8bc1f..0e1d489d8e5 100644 --- a/dlls/winegstreamer/mfplat.c +++ b/dlls/winegstreamer/mfplat.c @@ -565,6 +565,7 @@ IMFMediaType *mf_media_type_from_wg_format(const struct wg_format *format) case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_WMV: case WG_MAJOR_TYPE_VIDEO_INDEO: + case WG_MAJOR_TYPE_VIDEO_MPEG1: FIXME("Format %u not implemented!\n", format->major_type); /* fallthrough */ case WG_MAJOR_TYPE_UNKNOWN: diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index 0e8a5068aff..a5528e0e218 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -280,46 +280,50 @@ static bool amt_from_wg_format_audio_mpeg1(AM_MEDIA_TYPE *mt, const struct wg_fo
#define ALIGN(n, alignment) (((n) + (alignment) - 1) & ~((alignment) - 1))
+static unsigned int wg_format_get_max_size_video_raw(enum wg_video_format format, unsigned int width, unsigned int height) +{ + switch (format) + { + case WG_VIDEO_FORMAT_BGRA: + case WG_VIDEO_FORMAT_BGRx: + case WG_VIDEO_FORMAT_AYUV: + return width * height * 4; + + case WG_VIDEO_FORMAT_BGR: + return ALIGN(width * 3, 4) * height; + + case WG_VIDEO_FORMAT_RGB15: + case WG_VIDEO_FORMAT_RGB16: + case WG_VIDEO_FORMAT_UYVY: + case WG_VIDEO_FORMAT_YUY2: + case WG_VIDEO_FORMAT_YVYU: + return ALIGN(width * 2, 4) * height; + + case WG_VIDEO_FORMAT_I420: + case WG_VIDEO_FORMAT_YV12: + return ALIGN(width, 4) * ALIGN(height, 2) /* Y plane */ + + 2 * ALIGN((width + 1) / 2, 4) * ((height + 1) / 2); /* U and V planes */ + + case WG_VIDEO_FORMAT_NV12: + return ALIGN(width, 4) * ALIGN(height, 2) /* Y plane */ + + ALIGN(width, 4) * ((height + 1) / 2); /* U/V plane */ + + case WG_VIDEO_FORMAT_UNKNOWN: + FIXME("Cannot guess maximum sample size for unknown video format.\n"); + return 0; + } + + assert(0); + return 0; +} + unsigned int wg_format_get_max_size(const struct wg_format *format) { switch (format->major_type) { case WG_MAJOR_TYPE_VIDEO: - { - unsigned int width = format->u.video.width, height = abs(format->u.video.height); - - switch (format->u.video.format) - { - case WG_VIDEO_FORMAT_BGRA: - case WG_VIDEO_FORMAT_BGRx: - case WG_VIDEO_FORMAT_AYUV: - return width * height * 4; - - case WG_VIDEO_FORMAT_BGR: - return ALIGN(width * 3, 4) * height; - - case WG_VIDEO_FORMAT_RGB15: - case WG_VIDEO_FORMAT_RGB16: - case WG_VIDEO_FORMAT_UYVY: - case WG_VIDEO_FORMAT_YUY2: - case WG_VIDEO_FORMAT_YVYU: - return ALIGN(width * 2, 4) * height; - - case WG_VIDEO_FORMAT_I420: - case WG_VIDEO_FORMAT_YV12: - return ALIGN(width, 4) * ALIGN(height, 2) /* Y plane */ - + 2 * ALIGN((width + 1) / 2, 4) * ((height + 1) / 2); /* U and V planes */ - - case WG_VIDEO_FORMAT_NV12: - return ALIGN(width, 4) * ALIGN(height, 2) /* Y plane */ - + ALIGN(width, 4) * ((height + 1) / 2); /* U/V plane */ - - case WG_VIDEO_FORMAT_UNKNOWN: - FIXME("Cannot guess maximum sample size for unknown video format.\n"); - return 0; - } - break; - } + return wg_format_get_max_size_video_raw(format->u.video.format, + format->u.video.width, abs(format->u.video.height));
case WG_MAJOR_TYPE_VIDEO_CINEPAK: /* Both ffmpeg's encoder and a Cinepak file seen in the wild report @@ -327,6 +331,13 @@ unsigned int wg_format_get_max_size(const struct wg_format *format) * but as long as every sample fits into our allocator, we're fine. */ return format->u.video_cinepak.width * format->u.video_cinepak.height * 3;
+ case WG_MAJOR_TYPE_VIDEO_MPEG1: + /* Estimated max size of a compressed video frame. + * There's no way to no way to know the real upper bound, + * so let's just use the decompressed size and hope it works. */ + return wg_format_get_max_size_video_raw(WG_VIDEO_FORMAT_YV12, + format->u.video_mpeg1.width, format->u.video_mpeg1.height); + case WG_MAJOR_TYPE_AUDIO: { unsigned int rate = format->u.audio.rate, channels = format->u.audio.channels; @@ -595,6 +606,36 @@ static bool amt_from_wg_format_video_wmv(AM_MEDIA_TYPE *mt, const struct wg_form return true; }
+static bool amt_from_wg_format_video_mpeg1(AM_MEDIA_TYPE *mt, const struct wg_format *format) +{ + MPEG1VIDEOINFO *video_format; + uint32_t frame_time; + + if (!(video_format = CoTaskMemAlloc(sizeof(*video_format)))) + return false; + + mt->majortype = MEDIATYPE_Video; + mt->subtype = MEDIASUBTYPE_MPEG1Payload; + mt->bTemporalCompression = TRUE; + mt->lSampleSize = 1; + mt->formattype = FORMAT_MPEGVideo; + mt->cbFormat = sizeof(MPEG1VIDEOINFO); + mt->pbFormat = (BYTE *)video_format; + + memset(video_format, 0, sizeof(*video_format)); + if ((frame_time = MulDiv(10000000, format->u.video_mpeg1.fps_d, format->u.video_mpeg1.fps_n)) != -1) + video_format->hdr.AvgTimePerFrame = frame_time; + video_format->hdr.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + video_format->hdr.bmiHeader.biWidth = format->u.video_mpeg1.width; + video_format->hdr.bmiHeader.biHeight = format->u.video_mpeg1.height; + video_format->hdr.bmiHeader.biPlanes = 1; + video_format->hdr.bmiHeader.biBitCount = 12; + video_format->hdr.bmiHeader.biCompression = mt->subtype.Data1; + video_format->hdr.bmiHeader.biSizeImage = wg_format_get_max_size(format); + + return true; +} + bool amt_from_wg_format(AM_MEDIA_TYPE *mt, const struct wg_format *format, bool wm) { memset(mt, 0, sizeof(*mt)); @@ -624,6 +665,9 @@ bool amt_from_wg_format(AM_MEDIA_TYPE *mt, const struct wg_format *format, bool
case WG_MAJOR_TYPE_VIDEO_WMV: return amt_from_wg_format_video_wmv(mt, format); + + case WG_MAJOR_TYPE_VIDEO_MPEG1: + return amt_from_wg_format_video_mpeg1(mt, format); }
assert(0); @@ -838,6 +882,30 @@ static bool amt_to_wg_format_video_wmv(const AM_MEDIA_TYPE *mt, struct wg_format return true; }
+static bool amt_to_wg_format_video_mpeg1(const AM_MEDIA_TYPE *mt, struct wg_format *format) +{ + const MPEG1VIDEOINFO *video_format = (const MPEG1VIDEOINFO *)mt->pbFormat; + + if (!IsEqualGUID(&mt->formattype, &FORMAT_MPEGVideo)) + { + FIXME("Unknown format type %s.\n", debugstr_guid(&mt->formattype)); + return false; + } + if (mt->cbFormat < sizeof(VIDEOINFOHEADER) || !mt->pbFormat) + { + ERR("Unexpected format size %lu.\n", mt->cbFormat); + return false; + } + + format->major_type = WG_MAJOR_TYPE_VIDEO_MPEG1; + format->u.video_mpeg1.width = video_format->hdr.bmiHeader.biWidth; + format->u.video_mpeg1.height = video_format->hdr.bmiHeader.biHeight; + format->u.video_mpeg1.fps_n = 10000000; + format->u.video_mpeg1.fps_d = video_format->hdr.AvgTimePerFrame; + + return true; +} + bool amt_to_wg_format(const AM_MEDIA_TYPE *mt, struct wg_format *format) { memset(format, 0, sizeof(*format)); @@ -854,6 +922,8 @@ bool amt_to_wg_format(const AM_MEDIA_TYPE *mt, struct wg_format *format) || IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_WMV3) || IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_VC1S)) return amt_to_wg_format_video_wmv(mt, format); + if (IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_MPEG1Payload)) + return amt_to_wg_format_video_mpeg1(mt, format); return amt_to_wg_format_video(mt, format); } if (IsEqualGUID(&mt->majortype, &MEDIATYPE_Audio)) diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index 201131e31ab..33457c52681 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -42,6 +42,7 @@ enum wg_major_type WG_MAJOR_TYPE_VIDEO_H264, WG_MAJOR_TYPE_VIDEO_WMV, WG_MAJOR_TYPE_VIDEO_INDEO, + WG_MAJOR_TYPE_VIDEO_MPEG1, };
typedef UINT32 wg_audio_format; @@ -163,6 +164,11 @@ struct wg_format uint32_t fps_n, fps_d; uint32_t version; } video_indeo; + struct + { + int32_t width, height; + uint32_t fps_n, fps_d; + } video_mpeg1; } u; };
diff --git a/dlls/winegstreamer/wg_format.c b/dlls/winegstreamer/wg_format.c index d8a580b0197..7d4bc7a5f8c 100644 --- a/dlls/winegstreamer/wg_format.c +++ b/dlls/winegstreamer/wg_format.c @@ -282,6 +282,34 @@ static void wg_format_from_caps_video_wmv(struct wg_format *format, const GstCap format->u.video_wmv.fps_d = fps_d; }
+static void wg_format_from_caps_video_mpeg1(struct wg_format *format, const GstCaps *caps) +{ + const GstStructure *structure = gst_caps_get_structure(caps, 0); + gint width, height, fps_n, fps_d; + + if (!gst_structure_get_int(structure, "width", &width)) + { + GST_WARNING("Missing "width" value in %" GST_PTR_FORMAT ".", caps); + return; + } + if (!gst_structure_get_int(structure, "height", &height)) + { + GST_WARNING("Missing "height" value in %" GST_PTR_FORMAT ".", caps); + return; + } + if (!gst_structure_get_fraction(structure, "framerate", &fps_n, &fps_d)) + { + fps_n = 0; + fps_d = 1; + } + + format->major_type = WG_MAJOR_TYPE_VIDEO_MPEG1; + format->u.video_mpeg1.width = width; + format->u.video_mpeg1.height = height; + format->u.video_mpeg1.fps_n = fps_n; + format->u.video_mpeg1.fps_d = fps_d; +} + void wg_format_from_caps(struct wg_format *format, const GstCaps *caps) { const GstStructure *structure = gst_caps_get_structure(caps, 0); @@ -316,6 +344,10 @@ void wg_format_from_caps(struct wg_format *format, const GstCaps *caps) { wg_format_from_caps_video_wmv(format, caps); } + else if (!strcmp(name, "video/mpeg") && gst_structure_get_boolean(structure, "parsed", &parsed) && parsed) + { + wg_format_from_caps_video_mpeg1(format, caps); + } else { GST_FIXME("Unhandled caps %" GST_PTR_FORMAT ".", caps); @@ -702,6 +734,25 @@ static GstCaps *wg_format_to_caps_video_indeo(const struct wg_format *format) return caps; }
+static GstCaps *wg_format_to_caps_video_mpeg1(const struct wg_format *format) +{ + GstCaps *caps; + + if (!(caps = gst_caps_new_empty_simple("video/mpeg"))) + return NULL; + + gst_caps_set_simple(caps, "mpegversion", G_TYPE_INT, 1, NULL); + gst_caps_set_simple(caps, "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + gst_caps_set_simple(caps, "parsed", G_TYPE_BOOLEAN, TRUE, NULL); + if (format->u.video_mpeg1.width) + gst_caps_set_simple(caps, "width", G_TYPE_INT, format->u.video_mpeg1.width, NULL); + if (format->u.video_mpeg1.height) + gst_caps_set_simple(caps, "height", G_TYPE_INT, format->u.video_mpeg1.height, NULL); + if (format->u.video_mpeg1.fps_d || format->u.video_cinepak.fps_n) + gst_caps_set_simple(caps, "framerate", GST_TYPE_FRACTION, format->u.video_mpeg1.fps_n, format->u.video_mpeg1.fps_d, NULL); + return caps; +} + GstCaps *wg_format_to_caps(const struct wg_format *format) { switch (format->major_type) @@ -726,6 +777,8 @@ GstCaps *wg_format_to_caps(const struct wg_format *format) return wg_format_to_caps_video_wmv(format); case WG_MAJOR_TYPE_VIDEO_INDEO: return wg_format_to_caps_video_indeo(format); + case WG_MAJOR_TYPE_VIDEO_MPEG1: + return wg_format_to_caps_video_mpeg1(format); } assert(0); return NULL; @@ -744,6 +797,7 @@ bool wg_format_compare(const struct wg_format *a, const struct wg_format *b) case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_WMV: case WG_MAJOR_TYPE_VIDEO_INDEO: + case WG_MAJOR_TYPE_VIDEO_MPEG1: GST_FIXME("Format %u not implemented!", a->major_type); /* fallthrough */ case WG_MAJOR_TYPE_UNKNOWN: diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index d033ff27f43..44681301552 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -369,6 +369,7 @@ NTSTATUS wg_transform_create(void *args) case WG_MAJOR_TYPE_AUDIO: case WG_MAJOR_TYPE_VIDEO: break; + case WG_MAJOR_TYPE_VIDEO_MPEG1: case WG_MAJOR_TYPE_UNKNOWN: GST_FIXME("Format %u not implemented!", input_format.major_type); gst_caps_unref(raw_caps); @@ -422,6 +423,7 @@ NTSTATUS wg_transform_create(void *args) case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_INDEO: case WG_MAJOR_TYPE_VIDEO_WMV: + case WG_MAJOR_TYPE_VIDEO_MPEG1: GST_FIXME("Format %u not implemented!", output_format.major_type); goto out; } diff --git a/dlls/winegstreamer/wm_reader.c b/dlls/winegstreamer/wm_reader.c index c8b98c7c769..882b6df1bbb 100644 --- a/dlls/winegstreamer/wm_reader.c +++ b/dlls/winegstreamer/wm_reader.c @@ -1589,6 +1589,8 @@ static const char *get_major_type_string(enum wg_major_type type) return "wmv"; case WG_MAJOR_TYPE_VIDEO_INDEO: return "indeo"; + case WG_MAJOR_TYPE_VIDEO_MPEG1: + return "mpeg1-video"; case WG_MAJOR_TYPE_UNKNOWN: return "unknown"; } @@ -1948,6 +1950,7 @@ static HRESULT WINAPI reader_GetOutputFormat(IWMSyncReader2 *iface, case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_WMV: case WG_MAJOR_TYPE_VIDEO_INDEO: + case WG_MAJOR_TYPE_VIDEO_MPEG1: FIXME("Format %u not implemented!\n", format.major_type); break; case WG_MAJOR_TYPE_UNKNOWN: @@ -1990,6 +1993,7 @@ static HRESULT WINAPI reader_GetOutputFormatCount(IWMSyncReader2 *iface, DWORD o case WG_MAJOR_TYPE_VIDEO_H264: case WG_MAJOR_TYPE_VIDEO_WMV: case WG_MAJOR_TYPE_VIDEO_INDEO: + case WG_MAJOR_TYPE_VIDEO_MPEG1: FIXME("Format %u not implemented!\n", format.major_type); /* fallthrough */ case WG_MAJOR_TYPE_AUDIO:
From: Alfred Agrell floating@muncher.se
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=9127 --- dlls/winegstreamer/quartz_parser.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index a5528e0e218..5f7b85b861b 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -2205,10 +2205,10 @@ static HRESULT mpeg_splitter_sink_query_accept(struct strmbase_pin *iface, const { if (!IsEqualGUID(&mt->majortype, &MEDIATYPE_Stream)) return S_FALSE; - if (IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_MPEG1Audio)) + if (IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_MPEG1Audio) + || IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_MPEG1System)) return S_OK; if (IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_MPEG1Video) - || IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_MPEG1System) || IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_MPEG1VideoCD)) FIXME("Unsupported subtype %s.\n", wine_dbgstr_guid(&mt->subtype)); return S_FALSE; @@ -2223,8 +2223,28 @@ static const struct strmbase_sink_ops mpeg_splitter_sink_ops =
static BOOL mpeg_splitter_filter_init_gst(struct parser *filter) { - if (!create_pin(filter, wg_parser_get_stream(filter->wg_parser, 0), L"Audio")) - return FALSE; + wg_parser_t parser = filter->wg_parser; + unsigned int i, stream_count; + wg_parser_stream_t stream; + struct wg_format fmt; + + stream_count = wg_parser_get_stream_count(parser); + for (i = 0; i < stream_count; ++i) + { + stream = wg_parser_get_stream(parser, i); + wg_parser_stream_get_preferred_format(stream, &fmt); + if (fmt.major_type == WG_MAJOR_TYPE_VIDEO_MPEG1) + { + if (!create_pin(filter, wg_parser_get_stream(parser, i), L"Video")) + return FALSE; + } + else if (fmt.major_type == WG_MAJOR_TYPE_AUDIO_MPEG1) + { + if (!create_pin(filter, wg_parser_get_stream(parser, i), L"Audio")) + return FALSE; + } + else FIXME("unexpected format %u\n", fmt.major_type); + }
return TRUE; }
From: Alfred Agrell floating@muncher.se
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=9127 --- dlls/winegstreamer/gst_private.h | 2 + dlls/winegstreamer/main.c | 38 +++++ dlls/winegstreamer/quartz_parser.c | 6 + dlls/winegstreamer/quartz_transform.c | 141 +++++++++++++++++++ dlls/winegstreamer/wg_transform.c | 2 +- dlls/winegstreamer/winegstreamer_classes.idl | 7 + 6 files changed, 195 insertions(+), 1 deletion(-)
diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 4db6c2dbbb6..cc408177e7c 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -114,11 +114,13 @@ HRESULT wg_muxer_create(const char *format, wg_muxer_t *muxer); void wg_muxer_destroy(wg_muxer_t muxer); HRESULT wg_muxer_add_stream(wg_muxer_t muxer, UINT32 stream_id, const struct wg_format *format);
+unsigned int wg_format_get_bytes_for_uncompressed(wg_video_format format, unsigned int width, unsigned int height); unsigned int wg_format_get_max_size(const struct wg_format *format);
HRESULT avi_splitter_create(IUnknown *outer, IUnknown **out); HRESULT decodebin_parser_create(IUnknown *outer, IUnknown **out); HRESULT mpeg_audio_codec_create(IUnknown *outer, IUnknown **out); +HRESULT mpeg_video_codec_create(IUnknown *outer, IUnknown **out); HRESULT mpeg_layer3_decoder_create(IUnknown *outer, IUnknown **out); HRESULT mpeg_splitter_create(IUnknown *outer, IUnknown **out); HRESULT wave_parser_create(IUnknown *outer, IUnknown **out); diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index b855c5080c6..1893ca8d3a9 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -657,6 +657,7 @@ static const IClassFactoryVtbl class_factory_vtbl = static struct class_factory avi_splitter_cf = {{&class_factory_vtbl}, avi_splitter_create}; static struct class_factory decodebin_parser_cf = {{&class_factory_vtbl}, decodebin_parser_create}; static struct class_factory mpeg_audio_codec_cf = {{&class_factory_vtbl}, mpeg_audio_codec_create}; +static struct class_factory mpeg_video_codec_cf = {{&class_factory_vtbl}, mpeg_video_codec_create}; static struct class_factory mpeg_layer3_decoder_cf = {{&class_factory_vtbl}, mpeg_layer3_decoder_create}; static struct class_factory mpeg_splitter_cf = {{&class_factory_vtbl}, mpeg_splitter_create}; static struct class_factory wave_parser_cf = {{&class_factory_vtbl}, wave_parser_create}; @@ -686,6 +687,8 @@ HRESULT WINAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void **out) factory = &decodebin_parser_cf; else if (IsEqualGUID(clsid, &CLSID_CMpegAudioCodec)) factory = &mpeg_audio_codec_cf; + else if (IsEqualGUID(clsid, &CLSID_CMpegVideoCodec)) + factory = &mpeg_video_codec_cf; else if (IsEqualGUID(clsid, &CLSID_mpeg_layer3_decoder)) factory = &mpeg_layer3_decoder_cf; else if (IsEqualGUID(clsid, &CLSID_MPEG1Splitter)) @@ -799,6 +802,38 @@ static const REGFILTER2 reg_mpeg_audio_codec = .u.s2.rgPins2 = reg_mpeg_audio_codec_pins, };
+static const REGPINTYPES reg_mpeg_video_codec_sink_mts[2] = +{ + {&MEDIATYPE_Video, &MEDIASUBTYPE_MPEG1Packet}, + {&MEDIATYPE_Video, &MEDIASUBTYPE_MPEG1Payload}, +}; + +static const REGPINTYPES reg_mpeg_video_codec_source_mts[1] = +{ + {&MEDIATYPE_Video, &GUID_NULL}, +}; + +static const REGFILTERPINS2 reg_mpeg_video_codec_pins[2] = +{ + { + .nMediaTypes = 2, + .lpMediaType = reg_mpeg_video_codec_sink_mts, + }, + { + .dwFlags = REG_PINFLAG_B_OUTPUT, + .nMediaTypes = 1, + .lpMediaType = reg_mpeg_video_codec_source_mts, + }, +}; + +static const REGFILTER2 reg_mpeg_video_codec = +{ + .dwVersion = 2, + .dwMerit = 0x40000001, + .u.s2.cPins2 = 2, + .u.s2.rgPins2 = reg_mpeg_video_codec_pins, +}; + static const REGPINTYPES reg_mpeg_layer3_decoder_sink_mts[1] = { {&MEDIATYPE_Audio, &MEDIASUBTYPE_MP3}, @@ -1034,6 +1069,8 @@ HRESULT WINAPI DllRegisterServer(void) L"GStreamer splitter filter", NULL, NULL, NULL, ®_decodebin_parser); IFilterMapper2_RegisterFilter(mapper, &CLSID_CMpegAudioCodec, L"MPEG Audio Decoder", NULL, NULL, NULL, ®_mpeg_audio_codec); + IFilterMapper2_RegisterFilter(mapper, &CLSID_CMpegVideoCodec, + L"MPEG Video Decoder", NULL, NULL, NULL, ®_mpeg_video_codec); IFilterMapper2_RegisterFilter(mapper, &CLSID_mpeg_layer3_decoder, L"MPEG Layer-3 Decoder", NULL, NULL, NULL, ®_mpeg_layer3_decoder); IFilterMapper2_RegisterFilter(mapper, &CLSID_MPEG1Splitter, @@ -1075,6 +1112,7 @@ HRESULT WINAPI DllUnregisterServer(void) IFilterMapper2_UnregisterFilter(mapper, NULL, NULL, &CLSID_AviSplitter); IFilterMapper2_UnregisterFilter(mapper, NULL, NULL, &CLSID_decodebin_parser); IFilterMapper2_UnregisterFilter(mapper, NULL, NULL, &CLSID_CMpegAudioCodec); + IFilterMapper2_UnregisterFilter(mapper, NULL, NULL, &CLSID_CMpegVideoCodec); IFilterMapper2_UnregisterFilter(mapper, NULL, NULL, &CLSID_mpeg_layer3_decoder); IFilterMapper2_UnregisterFilter(mapper, NULL, NULL, &CLSID_MPEG1Splitter); IFilterMapper2_UnregisterFilter(mapper, NULL, NULL, &CLSID_WAVEParser); diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index 5f7b85b861b..0670fbd5181 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -1933,6 +1933,12 @@ static HRESULT WINAPI GSTOutPin_DecideBufferSize(struct strmbase_source *iface, VIDEOINFOHEADER *format = (VIDEOINFOHEADER *)pin->pin.pin.mt.pbFormat; buffer_size = format->bmiHeader.biSizeImage; } + else if (IsEqualGUID(&pin->pin.pin.mt.formattype, &FORMAT_MPEGVideo)) + { + MPEG1VIDEOINFO *format = (MPEG1VIDEOINFO *)pin->pin.pin.mt.pbFormat; + buffer_size = format->hdr.bmiHeader.biSizeImage; + buffer_count = 8; + } else if (IsEqualGUID(&pin->pin.pin.mt.formattype, &FORMAT_WaveFormatEx) && (IsEqualGUID(&pin->pin.pin.mt.subtype, &MEDIASUBTYPE_PCM) || IsEqualGUID(&pin->pin.pin.mt.subtype, &MEDIASUBTYPE_IEEE_FLOAT))) diff --git a/dlls/winegstreamer/quartz_transform.c b/dlls/winegstreamer/quartz_transform.c index 1dce20d9cff..fee275bb1ee 100644 --- a/dlls/winegstreamer/quartz_transform.c +++ b/dlls/winegstreamer/quartz_transform.c @@ -738,6 +738,147 @@ HRESULT mpeg_audio_codec_create(IUnknown *outer, IUnknown **out) return hr; }
+static HRESULT mpeg_video_codec_sink_query_accept(struct transform *filter, const AM_MEDIA_TYPE *mt) +{ + if (!IsEqualGUID(&mt->majortype, &MEDIATYPE_Video) + || !IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_MPEG1Payload) + || !IsEqualGUID(&mt->formattype, &FORMAT_MPEGVideo) + || mt->cbFormat < sizeof(MPEG1VIDEOINFO)) + return S_FALSE; + + return S_OK; +} + +static HRESULT mpeg_video_codec_source_query_accept(struct transform *filter, const AM_MEDIA_TYPE *mt) +{ + if (!filter->sink.pin.peer) + return S_FALSE; + + if (!IsEqualGUID(&mt->majortype, &MEDIATYPE_Video) + || !IsEqualGUID(&mt->formattype, &FORMAT_VideoInfo) + || mt->cbFormat < sizeof(VIDEOINFOHEADER)) + return S_FALSE; + + if (!IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_YV12) + /* missing: MEDIASUBTYPE_Y41P, not supported by GStreamer */ + && !IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_YUY2) + && !IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_UYVY) + && !IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_RGB24) + && !IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_RGB32) + && !IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_RGB565) + && !IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_RGB555) + /* missing: MEDIASUBTYPE_RGB8, not supported by GStreamer */) + return S_FALSE; + + return S_OK; +} + +static HRESULT mpeg_video_codec_source_get_media_type(struct transform *filter, unsigned int index, AM_MEDIA_TYPE *mt) +{ + static const enum wg_video_format formats[] = { + WG_VIDEO_FORMAT_YV12, + WG_VIDEO_FORMAT_YUY2, + WG_VIDEO_FORMAT_UYVY, + WG_VIDEO_FORMAT_BGR, + WG_VIDEO_FORMAT_BGRx, + WG_VIDEO_FORMAT_RGB16, + WG_VIDEO_FORMAT_RGB15, + }; + + const MPEG1VIDEOINFO *input_format = (MPEG1VIDEOINFO*)filter->sink.pin.mt.pbFormat; + struct wg_format wg_format = {}; + VIDEOINFO *video_format; + + if (!filter->sink.pin.peer) + return VFW_S_NO_MORE_ITEMS; + + if (index >= ARRAY_SIZE(formats)) + return VFW_S_NO_MORE_ITEMS; + + input_format = (MPEG1VIDEOINFO*)filter->sink.pin.mt.pbFormat; + wg_format.major_type = WG_MAJOR_TYPE_VIDEO; + wg_format.u.video.format = formats[index]; + wg_format.u.video.width = input_format->hdr.bmiHeader.biWidth; + wg_format.u.video.height = input_format->hdr.bmiHeader.biHeight; + wg_format.u.video.fps_n = 10000000; + wg_format.u.video.fps_d = input_format->hdr.AvgTimePerFrame; + if (!amt_from_wg_format(mt, &wg_format, false)) + return E_OUTOFMEMORY; + + video_format = (VIDEOINFO*)mt->pbFormat; + video_format->bmiHeader.biHeight = abs(video_format->bmiHeader.biHeight); + SetRect(&video_format->rcSource, 0, 0, video_format->bmiHeader.biWidth, video_format->bmiHeader.biHeight); + + video_format->bmiHeader.biXPelsPerMeter = 2000; + video_format->bmiHeader.biYPelsPerMeter = 2000; + video_format->dwBitRate = MulDiv(video_format->bmiHeader.biSizeImage * 8, 10000000, video_format->AvgTimePerFrame); + mt->lSampleSize = video_format->bmiHeader.biSizeImage; + mt->bTemporalCompression = FALSE; + mt->bFixedSizeSamples = TRUE; + + return S_OK; +} + +static HRESULT mpeg_video_codec_source_decide_buffer_size(struct transform *filter, IMemAllocator *allocator, ALLOCATOR_PROPERTIES *props) +{ + VIDEOINFOHEADER *output_format = (VIDEOINFOHEADER *)filter->source.pin.mt.pbFormat; + ALLOCATOR_PROPERTIES ret_props; + + props->cBuffers = max(props->cBuffers, 1); + props->cbBuffer = max(props->cbBuffer, output_format->bmiHeader.biSizeImage); + props->cbAlign = max(props->cbAlign, 1); + + return IMemAllocator_SetProperties(allocator, props, &ret_props); +} + +static const struct transform_ops mpeg_video_codec_transform_ops = +{ + mpeg_video_codec_sink_query_accept, + mpeg_video_codec_source_query_accept, + mpeg_video_codec_source_get_media_type, + mpeg_video_codec_source_decide_buffer_size, +}; + +HRESULT mpeg_video_codec_create(IUnknown *outer, IUnknown **out) +{ + static const struct wg_format output_format = + { + .major_type = WG_MAJOR_TYPE_VIDEO, + .u.video = { + .format = WG_VIDEO_FORMAT_I420, + /* size doesn't matter, this one is only used to check if the GStreamer plugin exists */ + }, + }; + static const struct wg_format input_format = + { + .major_type = WG_MAJOR_TYPE_VIDEO_MPEG1, + .u.video_mpeg1 = {}, + }; + struct wg_transform_attrs attrs = {0}; + wg_transform_t transform; + struct transform *object; + HRESULT hr; + + transform = wg_transform_create(&input_format, &output_format, &attrs); + if (!transform) + { + ERR_(winediag)("GStreamer doesn't support MPEG-1 video decoding, please install appropriate plugins.\n"); + return E_FAIL; + } + wg_transform_destroy(transform); + + hr = transform_create(outer, &CLSID_CMpegVideoCodec, &mpeg_video_codec_transform_ops, &object); + if (FAILED(hr)) + return hr; + + wcscpy(object->sink.pin.name, L"Input"); + wcscpy(object->source.pin.name, L"Output"); + + TRACE("Created MPEG video decoder %p.\n", object); + *out = &object->filter.IUnknown_inner; + return hr; +} + static HRESULT mpeg_layer3_decoder_sink_query_accept(struct transform *filter, const AM_MEDIA_TYPE *mt) { const MPEGLAYER3WAVEFORMAT *format; diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index 44681301552..6b2910740d3 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -358,6 +358,7 @@ NTSTATUS wg_transform_create(void *args) case WG_MAJOR_TYPE_VIDEO_CINEPAK: case WG_MAJOR_TYPE_VIDEO_INDEO: case WG_MAJOR_TYPE_VIDEO_WMV: + case WG_MAJOR_TYPE_VIDEO_MPEG1: if (!(element = find_element(GST_ELEMENT_FACTORY_TYPE_DECODER, src_caps, raw_caps)) || !append_element(transform->container, element, &first, &last)) { @@ -369,7 +370,6 @@ NTSTATUS wg_transform_create(void *args) case WG_MAJOR_TYPE_AUDIO: case WG_MAJOR_TYPE_VIDEO: break; - case WG_MAJOR_TYPE_VIDEO_MPEG1: case WG_MAJOR_TYPE_UNKNOWN: GST_FIXME("Format %u not implemented!", input_format.major_type); gst_caps_unref(raw_caps); diff --git a/dlls/winegstreamer/winegstreamer_classes.idl b/dlls/winegstreamer/winegstreamer_classes.idl index 3e9b19c90e9..bb727ca8645 100644 --- a/dlls/winegstreamer/winegstreamer_classes.idl +++ b/dlls/winegstreamer/winegstreamer_classes.idl @@ -35,6 +35,13 @@ coclass AviSplitter {} ] coclass CMpegAudioCodec {}
+[ + helpstring("MPEG Video Decoder"), + threading(both), + uuid(feb50740-7bef-11ce-9bd9-0000e202599c) +] +coclass CMpegVideoCodec {} + [ helpstring("MPEG Layer-3 Decoder"), threading(both),
From: Alfred Agrell floating@muncher.se
--- dlls/quartz/tests/mpegsplit.c | 183 ++++++++++++++++++++++++++++++++-- 1 file changed, 173 insertions(+), 10 deletions(-)
diff --git a/dlls/quartz/tests/mpegsplit.c b/dlls/quartz/tests/mpegsplit.c index b2cced84c68..f002d0228e2 100644 --- a/dlls/quartz/tests/mpegsplit.c +++ b/dlls/quartz/tests/mpegsplit.c @@ -60,7 +60,7 @@ static WCHAR *load_resource(const WCHAR *name) res = FindResourceW(NULL, name, (LPCWSTR)RT_RCDATA); ok(!!res, "Failed to load resource, error %lu.\n", GetLastError()); ptr = LockResource(LoadResource(GetModuleHandleA(NULL), res)); - WriteFile(file, ptr, SizeofResource( GetModuleHandleA(NULL), res), &written, NULL); + WriteFile(file, ptr, SizeofResource(GetModuleHandleA(NULL), res), &written, NULL); ok(written == SizeofResource(GetModuleHandleA(NULL), res), "Failed to write resource.\n"); CloseHandle(file);
@@ -609,7 +609,7 @@ static void test_media_types(void) todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); mt.subtype = MEDIASUBTYPE_MPEG1System; hr = IPin_QueryAccept(pin, &mt); - todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(hr == S_OK, "Got hr %#lx.\n", hr); mt.subtype = MEDIASUBTYPE_MPEG1AudioPayload; hr = IPin_QueryAccept(pin, &mt); ok(hr == S_FALSE, "Got hr %#lx.\n", hr); @@ -1097,8 +1097,8 @@ struct testfilter IAsyncReader IAsyncReader_iface, *reader; const AM_MEDIA_TYPE *mt; HANDLE eos_event; - unsigned int sample_count, eos_count, new_segment_count; - REFERENCE_TIME segment_start, segment_end, seek_start, seek_end; + unsigned int sample_count, eos_count, new_segment_count, byte_count; + REFERENCE_TIME segment_start, segment_end_min, segment_end_max, seek_start, seek_end; };
static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface) @@ -1221,7 +1221,7 @@ static HRESULT WINAPI testsink_Receive(struct strmbase_sink *iface, IMediaSample HRESULT hr;
hr = IMediaSample_GetTime(sample, &start, &end); - todo_wine_if (hr == VFW_S_NO_STOP_TIME) ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(hr == S_OK || (filter->sample_count > 0 && hr == VFW_E_SAMPLE_TIME_NOT_SET), "Got hr %#lx.\n", hr);
if (winetest_debug > 1) trace("%04lx: Got sample with timestamps %I64d-%I64d.\n", GetCurrentThreadId(), start, end); @@ -1237,6 +1237,7 @@ static HRESULT WINAPI testsink_Receive(struct strmbase_sink *iface, IMediaSample
ok(!filter->eos_count, "Got a sample after EOS.\n"); ++filter->sample_count; + filter->byte_count += IMediaSample_GetActualDataLength(sample); return S_OK; }
@@ -1271,7 +1272,8 @@ static HRESULT testsink_new_segment(struct strmbase_sink *iface, IMediaSeeking_Release(seeking);
ok(start == filter->segment_start, "Expected start %I64d, got %I64d.\n", filter->segment_start, start); - ok(end == filter->segment_end, "Expected end %I64d, got %I64d.\n", filter->segment_end, end); + ok(end >= filter->segment_end_min && end <= filter->segment_end_max, + "Expected end %I64d to %I64d, got %I64d.\n", filter->segment_end_min, filter->segment_end_max, end); ok(rate == 1.0, "Got rate %.16e.\n", rate);
return S_OK; @@ -1373,7 +1375,8 @@ static void testfilter_init(struct testfilter *filter) strmbase_sink_init(&filter->sink, &filter->filter, L"sink", &testsink_ops, NULL); filter->IAsyncReader_iface.lpVtbl = &async_reader_vtbl; filter->eos_event = CreateEventW(NULL, FALSE, FALSE, NULL); - filter->segment_end = 5392500; + filter->segment_end_min = 5000000; /* 5392500 on native */ + filter->segment_end_max = 5500000; }
static void test_connect_pin(void) @@ -1727,7 +1730,7 @@ static void test_seeking(void) duration = 0; hr = IMediaSeeking_GetDuration(seeking, &duration); ok(hr == S_OK, "Got hr %#lx.\n", hr); - ok(duration == 5392500, "Got duration %I64d.\n", duration); + ok(duration >= 5000000 && duration <= 5500000, "Got duration %I64d.\n", duration);
stop = current = 0xdeadbeef; hr = IMediaSeeking_GetStopPosition(seeking, &stop); @@ -1871,9 +1874,10 @@ static void test_streaming(void)
testsink.new_segment_count = testsink.sample_count = testsink.eos_count = 0; testsink.segment_start = 100 * 10000; - testsink.segment_end = 300 * 10000; + testsink.segment_end_min = 300 * 10000; + testsink.segment_end_max = 300 * 10000; hr = IMediaSeeking_SetPositions(seeking, &testsink.segment_start, AM_SEEKING_AbsolutePositioning, - &testsink.segment_end, AM_SEEKING_AbsolutePositioning); + &testsink.segment_end_min, AM_SEEKING_AbsolutePositioning); ok(hr == S_OK, "Got hr %#lx.\n", hr);
ok(!WaitForSingleObject(testsink.eos_event, 1000), "Did not receive EOS.\n"); @@ -1962,6 +1966,164 @@ static void test_large_file(void) ok(ret, "Failed to delete file, error %lu.\n", GetLastError()); }
+static void test_streamselect(IAMStreamSelect *sel) +{ + AM_MEDIA_TYPE mt_pin, *mt; + IBaseFilter *filter; + IUnknown *unk_video; + IUnknown *unk_audio; + DWORD stream_count; + IUnknown *object; + IPin *pin_video; + IPin *pin_audio; + IUnknown *unk; + DWORD flags; + DWORD group; + LPWSTR name; + HRESULT hr; + LCID lcid; + + IAMStreamSelect_QueryInterface(sel, &IID_IBaseFilter, (void **)&filter); + IBaseFilter_FindPin(filter, L"Video", &pin_video); + IBaseFilter_FindPin(filter, L"Audio", &pin_audio); + IPin_QueryInterface(pin_video, &IID_IUnknown, (void **)&unk_video); + IPin_QueryInterface(pin_audio, &IID_IUnknown, (void **)&unk_audio); + + hr = IAMStreamSelect_Count(sel, &stream_count); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(stream_count == 2, "Got %lu streams.\n", stream_count); + + hr = IAMStreamSelect_Info(sel, 0, &mt, &flags, &lcid, &group, &name, &object, &unk); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + IPin_ConnectionMediaType(pin_video, &mt_pin); + ok(compare_media_types(mt, &mt_pin), "Media types don't match\n"); + FreeMediaType(&mt_pin); + todo_wine ok(flags == AMSTREAMSELECTINFO_ENABLED, "Got flags %lx.\n", flags); + ok(lcid == 0, "Got LCID %lx.\n", lcid); + ok(group == 0, "Got group %lx.\n", group); + todo_wine ok(name && !wcscmp(name, L"Stream(E0)"), "Got name %ls.\n", name ? name : L"(null)"); + todo_wine ok(object == unk_video, "Got object %p, expected %p.\n", object, unk_video); + ok(unk == NULL, "Got unknown %p.\n", unk); + + if (name) + CoTaskMemFree(name); + if (mt) + DeleteMediaType(mt); + if (object) + IUnknown_Release(object); + + hr = IAMStreamSelect_Info(sel, 1, &mt, &flags, &lcid, &group, &name, &object, &unk); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + IPin_ConnectionMediaType(pin_audio, &mt_pin); + ok(compare_media_types(mt, &mt_pin), "Media types don't match\n"); + FreeMediaType(&mt_pin); + todo_wine ok(flags == AMSTREAMSELECTINFO_ENABLED, "Got flags %lx.\n", flags); + ok(lcid == 0, "Got LCID %lx.\n", lcid); + todo_wine ok(group == 1, "Got group %lx.\n", group); + todo_wine ok(name && !wcscmp(name, L"Stream(C0)"), "Got name %ls.\n", name ? name : L"(null)"); + todo_wine ok(object == unk_audio, "Got object %p, expected %p.\n", object, unk_audio); + ok(unk == NULL, "Got unknown %p.\n", unk); + + if (name) + CoTaskMemFree(name); + if (mt) + DeleteMediaType(mt); + if (object) + IUnknown_Release(object); + + hr = IAMStreamSelect_Info(sel, 2, &mt, &flags, &lcid, &group, &name, &object, &unk); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + IBaseFilter_Release(filter); + IUnknown_Release(pin_video); + IUnknown_Release(pin_audio); + IUnknown_Release(unk_video); + IUnknown_Release(unk_audio); +} + +static void test_video_file(void) +{ + const WCHAR *filename = load_resource(L"test.mpg"); + IBaseFilter *filter = create_mpeg_splitter(); + struct testfilter testsink_video; + struct testfilter testsink_audio; + IPin *source_video = NULL; + IPin *source_audio = NULL; + IMediaControl *control; + IFilterGraph2 *graph; + IAMStreamSelect *sel; + DWORD stream_count; + HRESULT hr; + ULONG ref; + DWORD ret; + + IBaseFilter_QueryInterface(filter, &IID_IAMStreamSelect, (void **)&sel); + hr = IAMStreamSelect_Count(sel, &stream_count); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(stream_count == 0, "Got %lu streams.\n", stream_count); + + graph = connect_input(filter, filename); + hr = IBaseFilter_FindPin(filter, L"Video", &source_video); + ok(source_video != NULL, "No video pin, hr %#lx.\n", hr); + hr = IBaseFilter_FindPin(filter, L"Audio", &source_audio); + ok(source_audio != NULL, "No audio pin, hr %#lx.\n", hr); + + testfilter_init(&testsink_video); + testfilter_init(&testsink_audio); + testsink_video.segment_end_min = 1000000; /* 11232612 on native, 1197000 in Wine */ + testsink_video.segment_end_max = 20000000; + testsink_audio.segment_end_min = 1000000; + testsink_audio.segment_end_max = 20000000; + + hr = IFilterGraph2_AddFilter(graph, &testsink_video.filter.IBaseFilter_iface, L"sink_video"); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_AddFilter(graph, &testsink_audio.filter.IBaseFilter_iface, L"sink_audio"); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IFilterGraph2_ConnectDirect(graph, source_video, &testsink_video.sink.pin.IPin_iface, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_ConnectDirect(graph, source_audio, &testsink_audio.sink.pin.IPin_iface, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + test_streamselect(sel); + + ok(IsEqualGUID(&testsink_video.sink.pin.mt.majortype, &MEDIATYPE_Video), "Media types didn't match.\n"); + ok(IsEqualGUID(&testsink_video.sink.pin.mt.subtype, &MEDIASUBTYPE_MPEG1Payload), "Media types didn't match.\n"); + ok(IsEqualGUID(&testsink_video.sink.pin.mt.formattype, &FORMAT_MPEGVideo), "Media types didn't match.\n"); + + ok(IsEqualGUID(&testsink_audio.sink.pin.mt.majortype, &MEDIATYPE_Audio), "Media types didn't match.\n"); + ok(IsEqualGUID(&testsink_audio.sink.pin.mt.subtype, &MEDIASUBTYPE_MPEG1AudioPayload), "Media types didn't match.\n"); + ok(IsEqualGUID(&testsink_audio.sink.pin.mt.formattype, &FORMAT_WaveFormatEx), "Media types didn't match.\n"); + + testsink_video.new_segment_count = 0; + testsink_audio.new_segment_count = 0; + IFilterGraph2_QueryInterface(graph, &IID_IMediaControl, (void **)&control); + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaControl_Run(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ok(!WaitForSingleObject(testsink_video.eos_event, 1000), "Video sink did not receive EOS.\n"); + ok(!WaitForSingleObject(testsink_audio.eos_event, 1000), "Audio sink did not receive EOS.\n"); + ok(testsink_video.new_segment_count == 1, "Video sink got %u segments.\n", testsink_video.new_segment_count); + ok(testsink_audio.new_segment_count == 1, "Audio sink got %u segments.\n", testsink_audio.new_segment_count); + + /* Native also supports subtype MEDIASUBTYPE_MPEG1Packet, yielding 1230 and 8828 bytes, respectively */ + ok(testsink_video.byte_count == 1214, "Video sink got %u bytes.\n", testsink_video.byte_count); + ok(testsink_audio.byte_count == 8777, "Audio sink got %u bytes.\n", testsink_audio.byte_count); + + IAMStreamSelect_Release(sel); + IPin_Release(source_video); + IPin_Release(source_audio); + IMediaControl_Release(control); + ref = IFilterGraph2_Release(graph); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + ret = DeleteFileW(filename); + ok(ret, "Failed to delete file, error %lu.\n", GetLastError()); +} + START_TEST(mpegsplit) { IBaseFilter *filter; @@ -1988,6 +2150,7 @@ START_TEST(mpegsplit) test_seeking(); test_streaming(); test_large_file(); + test_video_file();
CoUninitialize(); }
From: Alfred Agrell floating@muncher.se
--- dlls/quartz/tests/Makefile.in | 1 + dlls/quartz/tests/mpegvideo.c | 1620 +++++++++++++++++++++++++++++++++ 2 files changed, 1621 insertions(+) create mode 100644 dlls/quartz/tests/mpegvideo.c
diff --git a/dlls/quartz/tests/Makefile.in b/dlls/quartz/tests/Makefile.in index 1613907424c..de7a1a6d914 100644 --- a/dlls/quartz/tests/Makefile.in +++ b/dlls/quartz/tests/Makefile.in @@ -13,6 +13,7 @@ C_SRCS = \ mpegaudio.c \ mpeglayer3.c \ mpegsplit.c \ + mpegvideo.c \ passthrough.c \ systemclock.c \ videorenderer.c \ diff --git a/dlls/quartz/tests/mpegvideo.c b/dlls/quartz/tests/mpegvideo.c new file mode 100644 index 00000000000..09156a87524 --- /dev/null +++ b/dlls/quartz/tests/mpegvideo.c @@ -0,0 +1,1620 @@ +/* + * MPEG video decoder filter unit tests + * + * Copyright 2022 Anton Baskanov + * Copyright 2018 Zebediah Figura + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define COBJMACROS +#include "dshow.h" +#include "mmreg.h" +#include "ks.h" +#include "ksmedia.h" +#include "wine/strmbase.h" +#include "wine/test.h" + +/* same as normal MPEG1VIDEOINFO, except bSequenceHeader is 12 bytes */ +typedef struct tagMPEG1VIDEOINFO_12 { + VIDEOINFOHEADER hdr; + DWORD dwStartTimeCode; + DWORD cbSequenceHeader; + BYTE bSequenceHeader[12]; +} MPEG1VIDEOINFO_12; + +static const MPEG1VIDEOINFO_12 mpg_format = +{ + .hdr.rcSource = { 0, 0, 32, 24 }, + .hdr.rcTarget = { 0, 0, 0, 0 }, + .hdr.dwBitRate = 0, + .hdr.dwBitErrorRate = 0, + .hdr.AvgTimePerFrame = 400000, /* 25fps, 40ms */ + .hdr.bmiHeader.biSize = sizeof(BITMAPINFOHEADER), + .hdr.bmiHeader.biWidth = 32, + .hdr.bmiHeader.biHeight = 24, + .hdr.bmiHeader.biPlanes = 0, + .hdr.bmiHeader.biBitCount = 0, + .hdr.bmiHeader.biCompression = 0, + .hdr.bmiHeader.biSizeImage = 0, + .hdr.bmiHeader.biXPelsPerMeter = 2000, + .hdr.bmiHeader.biYPelsPerMeter = 2000, + .hdr.bmiHeader.biClrUsed = 0, + .hdr.bmiHeader.biClrImportant = 0, + .dwStartTimeCode = 4096, + .cbSequenceHeader = 12, + .bSequenceHeader = { 0x00, 0x00, 0x01, 0xb3, 0x02, 0x00, 0x18, 0x13, 0xff, 0xff, 0xe0, 0x18 }, +}; + +static const AM_MEDIA_TYPE mpeg_mt = +{ + /* MEDIATYPE_Video, MEDIASUBTYPE_MPEG1Payload, FORMAT_MPEGVideo */ + .majortype = {0x73646976, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}, + .subtype = {0xe436eb81, 0x524f, 0x11ce, {0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70}}, + .bFixedSizeSamples = TRUE, + .lSampleSize = 1, + .formattype = {0x05589f82, 0xc356, 0x11ce, {0xbf, 0x01, 0x00, 0xaa, 0x00, 0x55, 0x59, 0x5a}}, + .cbFormat = sizeof(MPEG1VIDEOINFO_12), + .pbFormat = (BYTE *)&mpg_format, +}; + +static const VIDEOINFOHEADER yuy2_format = +{ + .rcSource = { 0, 0, 32, 24 }, + .rcTarget = { 0, 0, 0, 0 }, + .dwBitRate = 32 * 24 * 16 * 25, + .dwBitErrorRate = 0, + .AvgTimePerFrame = 400000, + .bmiHeader.biSize = sizeof(BITMAPINFOHEADER), + .bmiHeader.biWidth = 32, + .bmiHeader.biHeight = 24, + .bmiHeader.biPlanes = 1, + .bmiHeader.biBitCount = 16, + .bmiHeader.biCompression = MAKEFOURCC('Y','U','Y','2'), + .bmiHeader.biSizeImage = 32 * 24 * 16 / 8, + .bmiHeader.biXPelsPerMeter = 2000, + .bmiHeader.biYPelsPerMeter = 2000, +}; + +static const AM_MEDIA_TYPE yuy2_mt = +{ + /* MEDIATYPE_Video, MEDIASUBTYPE_YUY2, FORMAT_VideoInfo */ + .majortype = {0x73646976, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}, + .subtype = {0x32595559, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}, + .bFixedSizeSamples = TRUE, + .lSampleSize = 32 * 24 * 16 / 8, + .formattype = {0x05589f80, 0xc356, 0x11ce, {0xbf, 0x01, 0x00, 0xaa, 0x00, 0x55, 0x59, 0x5a}}, + .cbFormat = sizeof(VIDEOINFOHEADER), + .pbFormat = (BYTE *)&yuy2_format, +}; + +static IBaseFilter *create_mpeg_video_codec(void) +{ + IBaseFilter *filter = NULL; + HRESULT hr = CoCreateInstance(&CLSID_CMpegVideoCodec, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (void **)&filter); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + return filter; +} + +static inline BOOL compare_media_types(const AM_MEDIA_TYPE *a, const AM_MEDIA_TYPE *b) +{ + return !memcmp(a, b, offsetof(AM_MEDIA_TYPE, pbFormat)) + && !memcmp(a->pbFormat, b->pbFormat, a->cbFormat); +} + +static const struct +{ + const GUID *subtype; + int bits_per_pixel; + DWORD compression; + BOOL works_native; + BOOL works_wine; +} +video_types[] = +{ + /* native's EnumMediaTypes offers YV12, but that media type refuses to connect */ + /* Wine doesn't enumerate unsupported ones (Y41P and RGB8) at all */ + { &MEDIASUBTYPE_YV12, 12, MAKEFOURCC('Y','V','1','2'), FALSE, TRUE }, + { &MEDIASUBTYPE_Y41P, 12, MAKEFOURCC('Y','4','1','P'), TRUE, FALSE }, + { &MEDIASUBTYPE_YUY2, 16, MAKEFOURCC('Y','U','Y','2'), TRUE, TRUE }, + { &MEDIASUBTYPE_UYVY, 16, MAKEFOURCC('U','Y','V','Y'), TRUE, TRUE }, + { &MEDIASUBTYPE_RGB24, 24, BI_RGB, TRUE, TRUE }, + { &MEDIASUBTYPE_RGB32, 32, BI_RGB, TRUE, TRUE }, + { &MEDIASUBTYPE_RGB565, 16, BI_BITFIELDS, TRUE, TRUE }, + { &MEDIASUBTYPE_RGB555, 16, BI_RGB, TRUE, TRUE }, + { &MEDIASUBTYPE_RGB8, 8, BI_RGB, TRUE, FALSE }, +}; + +static void init_video_mt(AM_MEDIA_TYPE *mt, VIDEOINFOHEADER *format, unsigned type_idx) +{ + memcpy(mt, &yuy2_mt, sizeof(AM_MEDIA_TYPE)); + memcpy(format, &yuy2_format, sizeof(VIDEOINFOHEADER)); + mt->subtype = *video_types[type_idx].subtype; + mt->pbFormat = (BYTE*)format; + format->bmiHeader.biBitCount = video_types[type_idx].bits_per_pixel; + format->bmiHeader.biCompression = video_types[type_idx].compression; + format->bmiHeader.biSizeImage = format->bmiHeader.biBitCount * format->bmiHeader.biWidth * format->bmiHeader.biHeight / 8; + format->dwBitRate = format->bmiHeader.biSizeImage*8 * 25; + mt->lSampleSize = format->bmiHeader.biSizeImage; + if (IsEqualGUID(&mt->subtype, &MEDIASUBTYPE_RGB8)) + format->bmiHeader.biClrUsed = 0x100; +} + +static ULONG get_refcount(void *iface) +{ + IUnknown *unknown = iface; + IUnknown_AddRef(unknown); + return IUnknown_Release(unknown); +} + +#define check_interface(a, b, c) check_interface_(__LINE__, a, b, c) +static void check_interface_(unsigned int line, void *iface_ptr, REFIID iid, BOOL supported) +{ + IUnknown *iface = iface_ptr; + HRESULT hr, expected_hr; + IUnknown *unk; + + expected_hr = supported ? S_OK : E_NOINTERFACE; + + hr = IUnknown_QueryInterface(iface, iid, (void **)&unk); + ok_(__FILE__, line)(hr == expected_hr, "Got hr %#lx, expected %#lx.\n", hr, expected_hr); + if (SUCCEEDED(hr)) + IUnknown_Release(unk); +} + +static void test_interfaces(void) +{ + IBaseFilter *filter = create_mpeg_video_codec(); + IPin *pin; + + check_interface(filter, &IID_IBaseFilter, TRUE); + check_interface(filter, &IID_IMediaFilter, TRUE); + check_interface(filter, &IID_IPersist, TRUE); + check_interface(filter, &IID_IUnknown, TRUE); + + check_interface(filter, &IID_IAMFilterMiscFlags, FALSE); + check_interface(filter, &IID_IBasicAudio, FALSE); + check_interface(filter, &IID_IBasicVideo, FALSE); + check_interface(filter, &IID_IKsPropertySet, FALSE); + check_interface(filter, &IID_IMediaPosition, FALSE); + check_interface(filter, &IID_IMediaSeeking, FALSE); + check_interface(filter, &IID_IPin, FALSE); + check_interface(filter, &IID_IQualityControl, FALSE); + check_interface(filter, &IID_IQualProp, FALSE); + check_interface(filter, &IID_IReferenceClock, FALSE); + check_interface(filter, &IID_IVideoWindow, FALSE); + check_interface(filter, &IID_IPersistPropertyBag, FALSE); + + IBaseFilter_FindPin(filter, L"In", &pin); + + check_interface(pin, &IID_IMemInputPin, TRUE); + check_interface(pin, &IID_IPin, TRUE); + check_interface(pin, &IID_IQualityControl, TRUE); + check_interface(pin, &IID_IUnknown, TRUE); + + check_interface(pin, &IID_IMediaPosition, FALSE); + check_interface(pin, &IID_IMediaSeeking, FALSE); + + IPin_Release(pin); + + IBaseFilter_FindPin(filter, L"Out", &pin); + + check_interface(pin, &IID_IPin, TRUE); + check_interface(pin, &IID_IMediaPosition, TRUE); + check_interface(pin, &IID_IMediaSeeking, TRUE); + check_interface(pin, &IID_IQualityControl, TRUE); + check_interface(pin, &IID_IUnknown, TRUE); + + check_interface(pin, &IID_IAsyncReader, FALSE); + + IPin_Release(pin); + + IBaseFilter_Release(filter); +} + +static const GUID test_iid = {0x33333333}; +static LONG outer_ref = 1; + +static HRESULT WINAPI outer_QueryInterface(IUnknown *iface, REFIID iid, void **out) +{ + if (IsEqualGUID(iid, &IID_IUnknown) + || IsEqualGUID(iid, &IID_IBaseFilter) + || IsEqualGUID(iid, &test_iid)) + { + *out = (IUnknown *)0xdeadbeef; + return S_OK; + } + ok(0, "unexpected call %s\n", wine_dbgstr_guid(iid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI outer_AddRef(IUnknown *iface) +{ + return InterlockedIncrement(&outer_ref); +} + +static ULONG WINAPI outer_Release(IUnknown *iface) +{ + return InterlockedDecrement(&outer_ref); +} + +static const IUnknownVtbl outer_vtbl = +{ + outer_QueryInterface, + outer_AddRef, + outer_Release, +}; + +static IUnknown test_outer = {&outer_vtbl}; + +static void test_aggregation(void) +{ + IBaseFilter *filter, *filter2; + IUnknown *unk, *unk2; + HRESULT hr; + ULONG ref; + + filter = (IBaseFilter *)0xdeadbeef; + hr = CoCreateInstance(&CLSID_CMpegVideoCodec, &test_outer, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (void **)&filter); + ok(hr == E_NOINTERFACE, "Got hr %#lx.\n", hr); + ok(!filter, "Got interface %p.\n", filter); + + hr = CoCreateInstance(&CLSID_CMpegVideoCodec, &test_outer, CLSCTX_INPROC_SERVER, + &IID_IUnknown, (void **)&unk); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(outer_ref == 1, "Got unexpected refcount %ld.\n", outer_ref); + ok(unk != &test_outer, "Returned IUnknown should not be outer IUnknown.\n"); + ref = get_refcount(unk); + ok(ref == 1, "Got unexpected refcount %ld.\n", ref); + + ref = IUnknown_AddRef(unk); + ok(ref == 2, "Got unexpected refcount %ld.\n", ref); + ok(outer_ref == 1, "Got unexpected refcount %ld.\n", outer_ref); + + ref = IUnknown_Release(unk); + ok(ref == 1, "Got unexpected refcount %ld.\n", ref); + ok(outer_ref == 1, "Got unexpected refcount %ld.\n", outer_ref); + + hr = IUnknown_QueryInterface(unk, &IID_IUnknown, (void **)&unk2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(unk2 == unk, "Got unexpected IUnknown %p.\n", unk2); + IUnknown_Release(unk2); + + hr = IUnknown_QueryInterface(unk, &IID_IBaseFilter, (void **)&filter); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IBaseFilter_QueryInterface(filter, &IID_IUnknown, (void **)&unk2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(unk2 == (IUnknown *)0xdeadbeef, "Got unexpected IUnknown %p.\n", unk2); + + hr = IBaseFilter_QueryInterface(filter, &IID_IBaseFilter, (void **)&filter2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(filter2 == (IBaseFilter *)0xdeadbeef, "Got unexpected IBaseFilter %p.\n", filter2); + + hr = IUnknown_QueryInterface(unk, &test_iid, (void **)&unk2); + ok(hr == E_NOINTERFACE, "Got hr %#lx.\n", hr); + ok(!unk2, "Got unexpected IUnknown %p.\n", unk2); + + hr = IBaseFilter_QueryInterface(filter, &test_iid, (void **)&unk2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(unk2 == (IUnknown *)0xdeadbeef, "Got unexpected IUnknown %p.\n", unk2); + + IBaseFilter_Release(filter); + ref = IUnknown_Release(unk); + ok(!ref, "Got unexpected refcount %ld.\n", ref); + ok(outer_ref == 1, "Got unexpected refcount %ld.\n", outer_ref); +} + +static void test_unconnected_filter_state(void) +{ + IBaseFilter *filter = create_mpeg_video_codec(); + FILTER_STATE state; + HRESULT hr; + ULONG ref; + + hr = IBaseFilter_GetState(filter, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Stopped, "Got state %u.\n", state); + + hr = IBaseFilter_Pause(filter); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IBaseFilter_GetState(filter, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Paused, "Got state %u.\n", state); + + hr = IBaseFilter_Run(filter, 0); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IBaseFilter_GetState(filter, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Running, "Got state %u.\n", state); + + hr = IBaseFilter_Pause(filter); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IBaseFilter_GetState(filter, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Paused, "Got state %u.\n", state); + + hr = IBaseFilter_Stop(filter); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IBaseFilter_GetState(filter, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Stopped, "Got state %u.\n", state); + + hr = IBaseFilter_Run(filter, 0); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IBaseFilter_GetState(filter, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Running, "Got state %u.\n", state); + + hr = IBaseFilter_Stop(filter); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IBaseFilter_GetState(filter, 0, &state); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(state == State_Stopped, "Got state %u.\n", state); + + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + +static void test_enum_pins(void) +{ + IBaseFilter *filter = create_mpeg_video_codec(); + IEnumPins *enum1, *enum2; + ULONG count, ref; + IPin *pins[3]; + HRESULT hr; + + ref = get_refcount(filter); + ok(ref == 1, "Got unexpected refcount %ld.\n", ref); + + hr = IBaseFilter_EnumPins(filter, NULL); + ok(hr == E_POINTER, "Got hr %#lx.\n", hr); + + hr = IBaseFilter_EnumPins(filter, &enum1); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ref = get_refcount(filter); + ok(ref == 2, "Got unexpected refcount %ld.\n", ref); + ref = get_refcount(enum1); + ok(ref == 1, "Got unexpected refcount %ld.\n", ref); + + hr = IEnumPins_Next(enum1, 1, NULL, NULL); + ok(hr == E_POINTER, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Next(enum1, 1, pins, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ref = get_refcount(filter); + ok(ref == 3, "Got unexpected refcount %ld.\n", ref); + ref = get_refcount(pins[0]); + ok(ref == 3, "Got unexpected refcount %ld.\n", ref); + ref = get_refcount(enum1); + ok(ref == 1, "Got unexpected refcount %ld.\n", ref); + IPin_Release(pins[0]); + ref = get_refcount(filter); + ok(ref == 2, "Got unexpected refcount %ld.\n", ref); + + hr = IEnumPins_Next(enum1, 1, pins, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ref = get_refcount(filter); + ok(ref == 3, "Got unexpected refcount %ld.\n", ref); + ref = get_refcount(pins[0]); + ok(ref == 3, "Got unexpected refcount %ld.\n", ref); + ref = get_refcount(enum1); + ok(ref == 1, "Got unexpected refcount %ld.\n", ref); + IPin_Release(pins[0]); + ref = get_refcount(filter); + ok(ref == 2, "Got unexpected refcount %ld.\n", ref); + + hr = IEnumPins_Next(enum1, 1, pins, NULL); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Reset(enum1); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Next(enum1, 1, pins, &count); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(count == 1, "Got count %lu.\n", count); + IPin_Release(pins[0]); + + hr = IEnumPins_Next(enum1, 1, pins, &count); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(count == 1, "Got count %lu.\n", count); + IPin_Release(pins[0]); + + hr = IEnumPins_Next(enum1, 1, pins, &count); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + ok(!count, "Got count %lu.\n", count); + + hr = IEnumPins_Reset(enum1); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Next(enum1, 2, pins, NULL); + ok(hr == E_INVALIDARG, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Next(enum1, 2, pins, &count); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(count == 2, "Got count %lu.\n", count); + IPin_Release(pins[0]); + IPin_Release(pins[1]); + + hr = IEnumPins_Next(enum1, 2, pins, &count); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + ok(!count, "Got count %lu.\n", count); + + hr = IEnumPins_Reset(enum1); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Next(enum1, 3, pins, &count); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + ok(count == 2, "Got count %lu.\n", count); + IPin_Release(pins[0]); + IPin_Release(pins[1]); + + hr = IEnumPins_Reset(enum1); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Clone(enum1, &enum2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Skip(enum1, 3); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Skip(enum1, 2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Skip(enum1, 1); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Next(enum1, 1, pins, NULL); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IEnumPins_Next(enum2, 1, pins, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + IPin_Release(pins[0]); + + IEnumPins_Release(enum2); + IEnumPins_Release(enum1); + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + +static void test_find_pin(void) +{ + IBaseFilter *filter = create_mpeg_video_codec(); + IEnumPins *enum_pins; + IPin *pin, *pin2; + HRESULT hr; + ULONG ref; + + hr = IBaseFilter_EnumPins(filter, &enum_pins); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IBaseFilter_FindPin(filter, L"In", &pin); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IEnumPins_Next(enum_pins, 1, &pin2, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(pin == pin2, "Pins didn't match.\n"); + IPin_Release(pin); + IPin_Release(pin2); + + hr = IBaseFilter_FindPin(filter, L"Out", &pin); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IEnumPins_Next(enum_pins, 1, &pin2, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(pin == pin2, "Pins didn't match.\n"); + IPin_Release(pin); + IPin_Release(pin2); + + hr = IBaseFilter_FindPin(filter, L"XForm In", &pin); + ok(hr == VFW_E_NOT_FOUND, "Got hr %#lx.\n", hr); + hr = IBaseFilter_FindPin(filter, L"XForm Out", &pin); + ok(hr == VFW_E_NOT_FOUND, "Got hr %#lx.\n", hr); + hr = IBaseFilter_FindPin(filter, L"input pin", &pin); + ok(hr == VFW_E_NOT_FOUND, "Got hr %#lx.\n", hr); + hr = IBaseFilter_FindPin(filter, L"output pin", &pin); + ok(hr == VFW_E_NOT_FOUND, "Got hr %#lx.\n", hr); + + IEnumPins_Release(enum_pins); + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + +static void test_pin_info(void) +{ + IBaseFilter *filter = create_mpeg_video_codec(); + PIN_DIRECTION dir; + PIN_INFO info; + HRESULT hr; + WCHAR *id; + ULONG ref; + IPin *pin; + + hr = IBaseFilter_FindPin(filter, L"In", &pin); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ref = get_refcount(filter); + ok(ref == 2, "Got unexpected refcount %ld.\n", ref); + ref = get_refcount(pin); + ok(ref == 2, "Got unexpected refcount %ld.\n", ref); + + hr = IPin_QueryPinInfo(pin, &info); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(info.pFilter == filter, "Expected filter %p, got %p.\n", filter, info.pFilter); + ok(info.dir == PINDIR_INPUT, "Got direction %d.\n", info.dir); + ok(!wcscmp(info.achName, L"Input"), "Got name %s.\n", debugstr_w(info.achName)); + ref = get_refcount(filter); + ok(ref == 3, "Got unexpected refcount %ld.\n", ref); + ref = get_refcount(pin); + ok(ref == 3, "Got unexpected refcount %ld.\n", ref); + IBaseFilter_Release(info.pFilter); + + hr = IPin_QueryDirection(pin, &dir); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(dir == PINDIR_INPUT, "Got direction %d.\n", dir); + + hr = IPin_QueryId(pin, &id); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(!wcscmp(id, L"In"), "Got id %s.\n", wine_dbgstr_w(id)); + CoTaskMemFree(id); + + hr = IPin_QueryInternalConnections(pin, NULL, NULL); + ok(hr == E_NOTIMPL, "Got hr %#lx.\n", hr); + + IPin_Release(pin); + + hr = IBaseFilter_FindPin(filter, L"Out", &pin); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IPin_QueryPinInfo(pin, &info); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(info.pFilter == filter, "Expected filter %p, got %p.\n", filter, info.pFilter); + ok(info.dir == PINDIR_OUTPUT, "Got direction %d.\n", info.dir); + ok(!wcscmp(info.achName, L"Output"), "Got name %s.\n", debugstr_w(info.achName)); + IBaseFilter_Release(info.pFilter); + + hr = IPin_QueryDirection(pin, &dir); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(dir == PINDIR_OUTPUT, "Got direction %d.\n", dir); + + hr = IPin_QueryId(pin, &id); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(!wcscmp(id, L"Out"), "Got id %s.\n", wine_dbgstr_w(id)); + CoTaskMemFree(id); + + hr = IPin_QueryInternalConnections(pin, NULL, NULL); + ok(hr == E_NOTIMPL, "Got hr %#lx.\n", hr); + + IPin_Release(pin); + + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + +static void test_enum_media_types(void) +{ + IBaseFilter *filter = create_mpeg_video_codec(); + IEnumMediaTypes *enum1, *enum2; + AM_MEDIA_TYPE *mts[1]; + ULONG ref, count; + HRESULT hr; + IPin *pin; + + IBaseFilter_FindPin(filter, L"In", &pin); + + hr = IPin_EnumMediaTypes(pin, &enum1); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Next(enum1, 1, mts, NULL); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Next(enum1, 1, mts, &count); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + ok(!count, "Got count %lu.\n", count); + + hr = IEnumMediaTypes_Reset(enum1); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Next(enum1, 1, mts, NULL); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Clone(enum1, &enum2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Skip(enum1, 1); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Next(enum2, 1, mts, NULL); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + IEnumMediaTypes_Release(enum1); + IEnumMediaTypes_Release(enum2); + IPin_Release(pin); + + IBaseFilter_FindPin(filter, L"Out", &pin); + + hr = IPin_EnumMediaTypes(pin, &enum1); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Next(enum1, 1, mts, NULL); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Next(enum1, 1, mts, &count); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + ok(!count, "Got count %lu.\n", count); + + hr = IEnumMediaTypes_Reset(enum1); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Next(enum1, 1, mts, NULL); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Clone(enum1, &enum2); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Skip(enum1, 1); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IEnumMediaTypes_Next(enum2, 1, mts, NULL); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + IEnumMediaTypes_Release(enum1); + IEnumMediaTypes_Release(enum2); + IPin_Release(pin); + + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + +static void test_media_types(void) +{ + IBaseFilter *filter = create_mpeg_video_codec(); + AM_MEDIA_TYPE mt; + HRESULT hr; + ULONG ref; + IPin *pin; + + IBaseFilter_FindPin(filter, L"In", &pin); + + hr = IPin_QueryAccept(pin, &mpeg_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + mt = mpeg_mt; + mt.subtype = MEDIASUBTYPE_MPEG1Packet; + hr = IPin_QueryAccept(pin, &mt); + todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + + mt = mpeg_mt; + mt.subtype = GUID_NULL; + hr = IPin_QueryAccept(pin, &mt); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + IPin_Release(pin); + + IBaseFilter_FindPin(filter, L"Out", &pin); + + hr = IPin_QueryAccept(pin, &yuy2_mt); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + IPin_Release(pin); + + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + +struct testfilter +{ + struct strmbase_filter filter; + struct strmbase_source source; + struct strmbase_sink sink; + const AM_MEDIA_TYPE *mt; + unsigned int got_sample, got_new_segment, got_eos, got_begin_flush, got_end_flush; + REFERENCE_TIME expected_start_time; + REFERENCE_TIME expected_stop_time; + BOOL todo_stop_time; +}; + +static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface) +{ + return CONTAINING_RECORD(iface, struct testfilter, filter); +} + +static struct strmbase_pin *testfilter_get_pin(struct strmbase_filter *iface, unsigned int index) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + if (!index) + return &filter->source.pin; + return NULL; +} + +static void testfilter_destroy(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + strmbase_source_cleanup(&filter->source); + strmbase_sink_cleanup(&filter->sink); + strmbase_filter_cleanup(&filter->filter); +} + +static const struct strmbase_filter_ops testfilter_ops = +{ + .filter_get_pin = testfilter_get_pin, + .filter_destroy = testfilter_destroy, +}; + +static HRESULT testsource_query_interface(struct strmbase_pin *iface, REFIID iid, void **out) +{ + return E_NOINTERFACE; +} + +static HRESULT WINAPI testsource_DecideAllocator(struct strmbase_source *iface, + IMemInputPin *peer, IMemAllocator **allocator) +{ + return S_OK; +} + +static const struct strmbase_source_ops testsource_ops = +{ + .base.pin_query_interface = testsource_query_interface, + .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection, + .pfnDecideAllocator = testsource_DecideAllocator, +}; + +static HRESULT testsink_query_interface(struct strmbase_pin *iface, REFIID iid, void **out) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->filter); + + if (IsEqualGUID(iid, &IID_IMemInputPin)) + *out = &filter->sink.IMemInputPin_iface; + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*out); + return S_OK; +} + +static HRESULT testsink_get_media_type(struct strmbase_pin *iface, unsigned int index, AM_MEDIA_TYPE *mt) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->filter); + if (!index && filter->mt) + { + CopyMediaType(mt, filter->mt); + return S_OK; + } + return VFW_S_NO_MORE_ITEMS; +} + +static HRESULT testsink_connect(struct strmbase_sink *iface, IPin *peer, const AM_MEDIA_TYPE *mt) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->pin.filter); + if (filter->mt && !IsEqualGUID(&mt->majortype, &filter->mt->majortype)) + return VFW_E_TYPE_NOT_ACCEPTED; + return S_OK; +} + +static HRESULT WINAPI testsink_Receive(struct strmbase_sink *iface, IMediaSample *sample) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->pin.filter); + REFERENCE_TIME start, stop; + AM_MEDIA_TYPE *mt = (AM_MEDIA_TYPE *)0xdeadbeef; + HRESULT hr; + LONG size; + + size = IMediaSample_GetSize(sample); + ok(size == filter->sink.pin.mt.lSampleSize, "Got size %lu, expected %lu.\n", size, filter->sink.pin.mt.lSampleSize); + size = IMediaSample_GetActualDataLength(sample); + ok(size == filter->sink.pin.mt.lSampleSize, "Got actual size %lu, expected %lu.\n", size, filter->sink.pin.mt.lSampleSize); + ok(IMediaSample_GetActualDataLength(sample) <= IMediaSample_GetSize(sample), "Buffer overflow"); + + start = 0xdeadbeef; + stop = 0xdeadbeef; + hr = IMediaSample_GetTime(sample, &start, &stop); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaSample_GetMediaType(sample, &mt); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + if (mt) + DeleteMediaType(mt); + + if (filter->got_sample == 0 && filter->expected_start_time != (REFERENCE_TIME)-1) + { + ok(start == filter->expected_start_time, "Got start time %s, expected %s.\n", + wine_dbgstr_longlong(start), wine_dbgstr_longlong(filter->expected_start_time)); + todo_wine_if(filter->todo_stop_time) + ok(stop == filter->expected_stop_time, "Got stop time %s, expected %s.\n", + wine_dbgstr_longlong(stop), wine_dbgstr_longlong(filter->expected_stop_time)); + } + + ++filter->got_sample; + + return S_OK; +} + +static HRESULT testsink_new_segment(struct strmbase_sink *iface, + REFERENCE_TIME start, REFERENCE_TIME stop, double rate) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->pin.filter); + ++filter->got_new_segment; + ok(start == 10000, "Got start %s.\n", wine_dbgstr_longlong(start)); + ok(stop == 20000, "Got stop %s.\n", wine_dbgstr_longlong(stop)); + ok(rate == 1.0, "Got rate %.16e.\n", rate); + return S_OK; +} + +static HRESULT testsink_eos(struct strmbase_sink *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->pin.filter); + ++filter->got_eos; + return S_OK; +} + +static HRESULT testsink_begin_flush(struct strmbase_sink *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->pin.filter); + ++filter->got_begin_flush; + return S_OK; +} + +static HRESULT testsink_end_flush(struct strmbase_sink *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface->pin.filter); + ++filter->got_end_flush; + return S_OK; +} + +static const struct strmbase_sink_ops testsink_ops = +{ + .base.pin_query_interface = testsink_query_interface, + .base.pin_get_media_type = testsink_get_media_type, + .sink_connect = testsink_connect, + .pfnReceive = testsink_Receive, + .sink_new_segment = testsink_new_segment, + .sink_eos = testsink_eos, + .sink_begin_flush = testsink_begin_flush, + .sink_end_flush = testsink_end_flush, +}; + +static void testfilter_init(struct testfilter *filter) +{ + static const GUID clsid = {0xabacab}; + memset(filter, 0, sizeof(*filter)); + strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); + strmbase_source_init(&filter->source, &filter->filter, L"source", &testsource_ops); + strmbase_sink_init(&filter->sink, &filter->filter, L"sink", &testsink_ops, NULL); +} + +static void test_sink_allocator(IMemInputPin *input) +{ + IMemAllocator *req_allocator, *ret_allocator; + ALLOCATOR_PROPERTIES props, ret_props; + HRESULT hr; + + hr = IMemInputPin_GetAllocatorRequirements(input, &props); + ok(hr == E_NOTIMPL, "Got hr %#lx.\n", hr); + + hr = IMemInputPin_GetAllocator(input, &ret_allocator); + todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + + if (hr == S_OK) + { + hr = IMemAllocator_GetProperties(ret_allocator, &props); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(!props.cBuffers, "Got %ld buffers.\n", props.cBuffers); + ok(!props.cbBuffer, "Got size %ld.\n", props.cbBuffer); + ok(!props.cbAlign, "Got alignment %ld.\n", props.cbAlign); + ok(!props.cbPrefix, "Got prefix %ld.\n", props.cbPrefix); + + hr = IMemInputPin_NotifyAllocator(input, ret_allocator, TRUE); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + IMemAllocator_Release(ret_allocator); + } + + hr = IMemInputPin_NotifyAllocator(input, NULL, TRUE); + ok(hr == E_POINTER, "Got hr %#lx.\n", hr); + + CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC_SERVER, + &IID_IMemAllocator, (void **)&req_allocator); + + props.cBuffers = 1; + props.cbBuffer = 256; + props.cbAlign = 1; + props.cbPrefix = 0; + hr = IMemAllocator_SetProperties(req_allocator, &props, &ret_props); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemInputPin_NotifyAllocator(input, req_allocator, TRUE); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemInputPin_GetAllocator(input, &ret_allocator); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(ret_allocator == req_allocator, "Allocators didn't match.\n"); + + IMemAllocator_Release(req_allocator); + IMemAllocator_Release(ret_allocator); +} + +static void test_source_allocator(IFilterGraph2 *graph, IMediaControl *control, + IPin *sink, IPin *source, struct testfilter *testsource, struct testfilter *testsink, + unsigned type_idx, BOOL should_work) +{ + ALLOCATOR_PROPERTIES props, req_props = {2, 30000, 32, 0}; + IMemAllocator *allocator; + IMediaSample *sample; + VIDEOINFOHEADER format; + AM_MEDIA_TYPE mt; + HRESULT hr; + + hr = IFilterGraph2_ConnectDirect(graph, &testsource->source.pin.IPin_iface, sink, &mpeg_mt); + ok(hr == S_OK, "(%u) Got hr %#lx.\n", type_idx, hr); + + init_video_mt(&mt, &format, type_idx); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink->sink.pin.IPin_iface, &mt); + if (!should_work) + { + ok(hr == VFW_E_TYPE_NOT_ACCEPTED, "(%u) Got hr %#lx.\n", type_idx, hr); + IFilterGraph2_Disconnect(graph, sink); + IFilterGraph2_Disconnect(graph, &testsource->source.pin.IPin_iface); + return; + } + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ok(!!testsink->sink.pAllocator, "Expected an allocator.\n"); + hr = IMemAllocator_GetProperties(testsink->sink.pAllocator, &props); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(props.cBuffers == 1, "Got %ld buffers.\n", props.cBuffers); + ok(props.cbBuffer == mt.lSampleSize, "Got size %ld.\n", props.cbBuffer); + ok(props.cbAlign == 1, "Got alignment %ld.\n", props.cbAlign); + ok(!props.cbPrefix, "Got prefix %ld.\n", props.cbPrefix); + + hr = IMemAllocator_GetBuffer(testsink->sink.pAllocator, &sample, NULL, NULL, 0); + ok(hr == VFW_E_NOT_COMMITTED, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemAllocator_GetBuffer(testsink->sink.pAllocator, &sample, NULL, NULL, 0); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + if (hr == S_OK) + IMediaSample_Release(sample); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemAllocator_GetBuffer(testsink->sink.pAllocator, &sample, NULL, NULL, 0); + ok(hr == VFW_E_NOT_COMMITTED, "Got hr %#lx.\n", hr); + + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink->sink.pin.IPin_iface); + + init_video_mt(&mt, &format, type_idx); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink->sink.pin.IPin_iface, &mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ok(!!testsink->sink.pAllocator, "Expected an allocator.\n"); + hr = IMemAllocator_GetProperties(testsink->sink.pAllocator, &props); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(props.cBuffers == 1, "Got %ld buffers.\n", props.cBuffers); + ok(props.cbBuffer == mt.lSampleSize, "Got size %ld.\n", props.cbBuffer); + ok(props.cbAlign == 1, "Got alignment %ld.\n", props.cbAlign); + ok(!props.cbPrefix, "Got prefix %ld.\n", props.cbPrefix); + + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink->sink.pin.IPin_iface); + + CoCreateInstance(&CLSID_MemoryAllocator, NULL, CLSCTX_INPROC_SERVER, + &IID_IMemAllocator, (void **)&allocator); + testsink->sink.pAllocator = allocator; + + hr = IMemAllocator_SetProperties(allocator, &req_props, &props); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + init_video_mt(&mt, &format, type_idx); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink->sink.pin.IPin_iface, &mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ok(testsink->sink.pAllocator == allocator, "Expected an allocator.\n"); + hr = IMemAllocator_GetProperties(testsink->sink.pAllocator, &props); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(props.cBuffers == 1, "Got %ld buffers.\n", props.cBuffers); + ok(props.cbBuffer == mt.lSampleSize, "Got size %ld.\n", props.cbBuffer); + ok(props.cbAlign == 1, "Got alignment %ld.\n", props.cbAlign); + ok(!props.cbPrefix, "Got prefix %ld.\n", props.cbPrefix); + + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink->sink.pin.IPin_iface); + + IFilterGraph2_Disconnect(graph, sink); + IFilterGraph2_Disconnect(graph, &testsource->source.pin.IPin_iface); +} + +static void test_send_sample(IMemInputPin *input, IMediaSample *sample, const BYTE *data, LONG len, BOOL todo) +{ + BYTE *target_data; + HRESULT hr; + LONG size; + hr = IMediaSample_GetPointer(sample, &target_data); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + size = IMediaSample_GetSize(sample); + ok(size >= len, "Got size %ld, expected at least %ld.\n", size, len); + + memcpy(target_data, data, len); + hr = IMediaSample_SetActualDataLength(sample, len); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemInputPin_Receive(input, sample); + todo_wine_if(todo) /* 0xc00d6d61 is MF_E_TRANSFORM_STREAM_CHANGE */ + ok(hr == S_OK, "Got hr %#lx.\n", hr); +} + +static void test_send_video(IMemInputPin *input, IMediaSample *sample) +{ + /* gst-launch-1.0 -v videotestsrc pattern=black num-buffers=10 ! video/x-raw,width=32,height=24 ! mpeg2enc ! filesink location=empty-es2.mpg */ + /* then truncate to taste */ + /* each 00 00 01 b3 or 00 00 01 00 starts a new frame, except the first 00 00 01 00 after a 00 00 01 b3 */ + static const BYTE empty_mpg_frame1[] = { + 0x00, 0x00, 0x01, 0xb3, 0x02, 0x00, 0x18, 0x15, 0x02, 0xbf, 0x60, 0x9c, + 0x00, 0x00, 0x01, 0xb8, 0x00, 0x08, 0x00, 0x40, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0xff, 0xf8, + 0x00, 0x00, 0x01, 0x01, 0x0b, 0xf8, 0x7d, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x20, + 0x00, 0x00, 0x01, 0x02, 0x0b, 0xf8, 0x7d, 0x2e, 0x7d, 0x2f, 0xcf, 0xc1, 0x04, 0x03, 0xa0, 0x11, 0xb1, + 0x41, 0x28, 0x88, 0x13, 0xb9, 0x6f, 0xcf, 0xc1, 0x04, 0x03, 0xa0, 0x11, 0xb1, 0x41, 0x28, 0x88, + 0x13, 0xb9, 0x6f, 0xa1, 0x4b, 0x9f, 0x48, 0x04, 0x10, 0x0e, 0x80, 0x46, 0xc5, 0x04, 0xa2, + 0x20, 0x4e, 0xe5, 0x80, 0x41, 0x00, 0xe8, 0x04, 0x6c, 0x50, 0x4a, 0x22, 0x04, 0xee, 0x58, + }; + static const BYTE empty_mpg_frame2[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x57, 0xff, 0xf9, 0x80, + 0x00, 0x00, 0x01, 0x01, 0x0a, 0x79, 0xc0, + 0x00, 0x00, 0x01, 0x02, 0x0a, 0x79, 0xc0, + }; + static const BYTE empty_mpg_frame3[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x97, 0xff, 0xf9, 0x80, + 0x00, 0x00, 0x01, 0x01, 0x0a, 0x79, 0xc0, + 0x00, 0x00, 0x01, 0x02, 0x0a, 0x79, 0xc0, 0x00, 0x00, 0x01, 0xb7, + }; + HRESULT hr; + IPin *pin; + + /* frame 1 - it's a complete frame, but due to how MPEG framing works, the decoder doesn't know that */ + /* frame 2 - new frame starts, frame 1 can be emitted - but Wine gets confused by colorimetry and returns an error */ + /* frame 3 - Wine emits frames 1 and 2 */ + /* meanwhile, native won't emit anything until an unknown-sized internal buffer is filled, or EOS is announced */ + test_send_sample(input, sample, empty_mpg_frame1, ARRAY_SIZE(empty_mpg_frame1), FALSE); + test_send_sample(input, sample, empty_mpg_frame2, ARRAY_SIZE(empty_mpg_frame2), TRUE); + test_send_sample(input, sample, empty_mpg_frame3, ARRAY_SIZE(empty_mpg_frame3), FALSE); + + hr = IMemInputPin_QueryInterface(input, &IID_IPin, (void **)&pin); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IPin_EndOfStream(pin); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + IPin_Release(pin); +} + +static void test_sample_processing(IMediaControl *control, IMemInputPin *input, struct testfilter *sink) +{ + REFERENCE_TIME start, stop; + IMemAllocator *allocator; + IMediaSample *sample; + HRESULT hr; + IPin *pin; + LONG size; + + sink->got_sample = 0; + sink->got_eos = 0; + + hr = IMemInputPin_QueryInterface(input, &IID_IPin, (void **)&pin); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemInputPin_ReceiveCanBlock(input); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemInputPin_GetAllocator(input, &allocator); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0); + ok(hr == VFW_E_NOT_COMMITTED, "Got hr %#lx.\n", hr); + + hr = IMemAllocator_Commit(allocator); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + size = IMediaSample_GetSize(sample); + ok(size == 256, "Got size %ld.\n", size); + + hr = IMediaSample_SetTime(sample, NULL, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + sink->expected_start_time = 0; + sink->expected_stop_time = 0; + sink->todo_stop_time = FALSE; + hr = IMediaSample_SetTime(sample, &sink->expected_start_time, &sink->expected_stop_time); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + test_send_video(input, sample); + ok(sink->got_sample >= 1, "Got %u calls to Receive().\n", sink->got_sample); + ok(sink->got_eos == 1, "Got %u calls to EndOfStream().\n", sink->got_eos); + sink->got_sample = 0; + sink->got_eos = 0; + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + start = 22222; + hr = IMediaSample_SetTime(sample, &start, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + sink->expected_start_time = -1; /* native returns start and stop 0xff80000000000001 */ + test_send_video(input, sample); + ok(sink->got_sample >= 1, "Got %u calls to Receive().\n", sink->got_sample); + ok(sink->got_eos == 1, "Got %u calls to EndOfStream().\n", sink->got_eos); + sink->got_sample = 0; + sink->got_eos = 0; + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + start = 22222; + stop = 33333; + hr = IMediaSample_SetTime(sample, &start, &stop); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + sink->expected_start_time = 22222; + sink->expected_stop_time = 22222; + sink->todo_stop_time = TRUE; + test_send_video(input, sample); + ok(sink->got_sample >= 1, "Got %u calls to Receive().\n", sink->got_sample); + ok(sink->got_eos == 1, "Got %u calls to EndOfStream().\n", sink->got_eos); + sink->got_sample = 0; + sink->got_eos = 0; + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemInputPin_Receive(input, sample); + ok(hr == VFW_E_WRONG_STATE, "Got hr %#lx.\n", hr); + + hr=IMemAllocator_Decommit(allocator); + IPin_Release(pin); + IMediaSample_Release(sample); + IMemAllocator_Release(allocator); +} + +static void test_streaming_events(IMediaControl *control, IPin *sink, + IMemInputPin *input, struct testfilter *testsink) +{ + REFERENCE_TIME start, stop; + IMemAllocator *allocator; + IMediaSample *sample; + HRESULT hr; + IPin *pin; + + testsink->got_new_segment = 0; + testsink->got_sample = 0; + testsink->got_eos = 0; + testsink->got_begin_flush = 0; + testsink->got_end_flush = 0; + + hr = IMemInputPin_QueryInterface(input, &IID_IPin, (void **)&pin); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IMemInputPin_GetAllocator(input, &allocator); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMemAllocator_Commit(allocator); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + start = 0; + stop = 120000; + hr = IMediaSample_SetTime(sample, &start, &stop); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + ok(!testsink->got_new_segment, "Got %u calls to IPin::NewSegment().\n", testsink->got_new_segment); + hr = IPin_NewSegment(sink, 10000, 20000, 1.0); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(testsink->got_new_segment == 1, "Got %u calls to IPin::NewSegment().\n", testsink->got_new_segment); + + ok(!testsink->got_eos, "Got %u calls to IPin::EndOfStream().\n", testsink->got_eos); + hr = IPin_EndOfStream(sink); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(!testsink->got_sample, "Got %u calls to Receive().\n", testsink->got_sample); + ok(testsink->got_eos == 1, "Got %u calls to IPin::EndOfStream().\n", testsink->got_eos); + testsink->got_eos = 0; + + hr = IPin_EndOfStream(sink); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(testsink->got_eos == 1, "Got %u calls to IPin::EndOfStream().\n", testsink->got_eos); + + testsink->expected_start_time = 0; + testsink->expected_stop_time = 0; + testsink->todo_stop_time = TRUE; + test_send_video(input, sample); + ok(testsink->got_sample >= 1, "Got %u calls to Receive().\n", testsink->got_sample); + testsink->got_sample = 0; + + ok(!testsink->got_begin_flush, "Got %u calls to IPin::BeginFlush().\n", testsink->got_begin_flush); + hr = IPin_BeginFlush(sink); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(testsink->got_begin_flush == 1, "Got %u calls to IPin::BeginFlush().\n", testsink->got_begin_flush); + + hr = IMemInputPin_Receive(input, sample); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IPin_EndOfStream(sink); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + ok(!testsink->got_end_flush, "Got %u calls to IPin::EndFlush().\n", testsink->got_end_flush); + hr = IPin_EndFlush(sink); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(testsink->got_end_flush == 1, "Got %u calls to IPin::EndFlush().\n", testsink->got_end_flush); + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + testsink->expected_start_time = 0; + testsink->expected_stop_time = 0; + testsink->todo_stop_time = TRUE; + test_send_video(input, sample); + ok(testsink->got_sample >= 1, "Got %u calls to Receive().\n", testsink->got_sample); + testsink->got_sample = 0; + + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + IMemAllocator_Decommit(allocator); + IPin_Release(pin); + IMediaSample_Release(sample); + IMemAllocator_Release(allocator); +} + +static void test_connect_pin(void) +{ + IBaseFilter *filter = create_mpeg_video_codec(); + struct testfilter testsource, testsink; + AM_MEDIA_TYPE mt, source_mt, *pmt; + IPin *sink, *source, *peer; + VIDEOINFOHEADER req_format; + IEnumMediaTypes *enummt; + IMediaControl *control; + IMemInputPin *meminput; + AM_MEDIA_TYPE req_mt; + IFilterGraph2 *graph; + unsigned int i; + HRESULT hr; + ULONG ref; + + CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, + &IID_IFilterGraph2, (void **)&graph); + testfilter_init(&testsource); + testfilter_init(&testsink); + IFilterGraph2_AddFilter(graph, &testsink.filter.IBaseFilter_iface, L"sink"); + IFilterGraph2_AddFilter(graph, &testsource.filter.IBaseFilter_iface, L"source"); + IFilterGraph2_AddFilter(graph, filter, L"MPEG video decoder"); + IBaseFilter_FindPin(filter, L"In", &sink); + IBaseFilter_FindPin(filter, L"Out", &source); + IPin_QueryInterface(sink, &IID_IMemInputPin, (void **)&meminput); + IFilterGraph2_QueryInterface(graph, &IID_IMediaControl, (void **)&control); + + for (i=0;i<ARRAY_SIZE(video_types);++i) + { + BOOL should_work; + if (winetest_platform_is_wine) should_work = video_types[i].works_wine; + else should_work = video_types[i].works_native; + test_source_allocator(graph, control, sink, source, &testsource, &testsink, i, should_work); + } + + /* Test sink connection. */ + + peer = (IPin *)0xdeadbeef; + hr = IPin_ConnectedTo(sink, &peer); + ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr); + ok(!peer, "Got peer %p.\n", peer); + + hr = IPin_ConnectionMediaType(sink, &mt); + ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_ConnectDirect(graph, &testsource.source.pin.IPin_iface, sink, &mpeg_mt); + ok(hr == VFW_E_NOT_STOPPED, "Got hr %#lx.\n", hr); + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + req_mt = mpeg_mt; + req_mt.subtype = MEDIASUBTYPE_RGB24; + hr = IFilterGraph2_ConnectDirect(graph, &testsource.source.pin.IPin_iface, sink, &req_mt); + ok(hr == VFW_E_TYPE_NOT_ACCEPTED, "Got hr %#lx.\n", hr); + + hr = IFilterGraph2_ConnectDirect(graph, &testsource.source.pin.IPin_iface, sink, &mpeg_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IPin_ConnectedTo(sink, &peer); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(peer == &testsource.source.pin.IPin_iface, "Got peer %p.\n", peer); + IPin_Release(peer); + + hr = IPin_ConnectionMediaType(sink, &mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&mt, &mpeg_mt), "Media types didn't match.\n"); + ok(compare_media_types(&testsource.source.pin.mt, &mpeg_mt), "Media types didn't match.\n"); + FreeMediaType(&mt); + + hr = IPin_QueryAccept(source, &yuy2_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + req_mt = yuy2_mt; + req_mt.majortype = GUID_NULL; + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + req_mt = yuy2_mt; + req_mt.subtype = GUID_NULL; + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + for (i=0;i<ARRAY_SIZE(video_types);++i) + { + init_video_mt(&req_mt, &req_format, i); + hr = IPin_QueryAccept(source, &req_mt); + if (!winetest_platform_is_wine && !video_types[i].works_native) + continue; + todo_wine_if(!video_types[i].works_wine) + ok(hr == S_OK, "Got hr %#lx.\n", hr); + } + + req_mt = yuy2_mt; + req_mt.formattype = GUID_NULL; + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_Disconnect(graph, sink); + ok(hr == VFW_E_NOT_STOPPED, "Got hr %#lx.\n", hr); + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + test_sink_allocator(meminput); + + hr = IPin_EnumMediaTypes(source, &enummt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + i = 0; + while (i < ARRAY_SIZE(video_types)) + { + VIDEOINFOHEADER expect_format; + AM_MEDIA_TYPE expect_mt; + + hr = IEnumMediaTypes_Next(enummt, 1, &pmt, NULL); + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + while (IsEqualGUID(&pmt->formattype, &FORMAT_VideoInfo2)) + { + DeleteMediaType(pmt); + hr = IEnumMediaTypes_Next(enummt, 1, &pmt, NULL); + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + } + + init_video_mt(&expect_mt, &expect_format, i); + + ok(!memcmp(pmt, &expect_mt, offsetof(AM_MEDIA_TYPE, cbFormat)), + "%u: Media types didn't match.\n", i); + ok(!memcmp(pmt->pbFormat, &expect_format, sizeof(VIDEOINFOHEADER)), + "%u: Format blocks didn't match.\n", i); + DeleteMediaType(pmt); + + ++i; + while (winetest_platform_is_wine && !video_types[i].works_wine) + ++i; + } + + hr = IEnumMediaTypes_Next(enummt, 1, &pmt, NULL); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + IEnumMediaTypes_Release(enummt); + + /* Test source connection. */ + + peer = (IPin *)0xdeadbeef; + hr = IPin_ConnectedTo(source, &peer); + ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr); + ok(!peer, "Got peer %p.\n", peer); + + hr = IPin_ConnectionMediaType(source, &mt); + ok(hr == VFW_E_NOT_CONNECTED, "Got hr %#lx.\n", hr); + + /* Exact connection. */ + + for (i=0;i<ARRAY_SIZE(video_types);i++) + { + if (!winetest_platform_is_wine && !video_types[i].works_native) + continue; + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + init_video_mt(&req_mt, &req_format, i); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == VFW_E_NOT_STOPPED, "%u: Got hr %#lx.\n", i, hr); + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + + init_video_mt(&req_mt, &req_format, i); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + todo_wine_if(!video_types[i].works_wine) + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + if (hr != S_OK) + continue; + + hr = IPin_ConnectedTo(source, &peer); + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + ok(peer == &testsink.sink.pin.IPin_iface, "%u: Got peer %p.\n", i, peer); + IPin_Release(peer); + + hr = IPin_ConnectionMediaType(source, &mt); + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + ok(compare_media_types(&mt, &req_mt), "%u: Media types didn't match.\n", i); + ok(compare_media_types(&testsink.sink.pin.mt, &req_mt), "%u: Media types didn't match.\n", i); + FreeMediaType(&mt); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + hr = IFilterGraph2_Disconnect(graph, source); + ok(hr == VFW_E_NOT_STOPPED, "%u: Got hr %#lx.\n", i, hr); + hr = IMediaControl_Stop(control); + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + + test_sample_processing(control, meminput, &testsink); + test_streaming_events(control, sink, meminput, &testsink); + + hr = IFilterGraph2_Disconnect(graph, source); + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + hr = IFilterGraph2_Disconnect(graph, source); + ok(hr == S_FALSE, "%u: Got hr %#lx.\n", i, hr); + ok(testsink.sink.pin.peer == source, "%u: Got peer %p.\n", i, testsink.sink.pin.peer); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + init_video_mt(&req_mt, &req_format, i); + req_mt.lSampleSize = 999; + req_mt.bTemporalCompression = TRUE; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == S_OK, "%u: Got hr %#lx.\n", i, hr); + ok(compare_media_types(&testsink.sink.pin.mt, &req_mt), "%u: Media types didn't match.\n", i); + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + } + + req_mt = yuy2_mt; + req_mt.formattype = FORMAT_None; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == VFW_E_TYPE_NOT_ACCEPTED, "Got hr %#lx.\n", hr); + + /* Connection with wildcards. */ + + source_mt = yuy2_mt; + + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(IsEqualGUID(&testsink.sink.pin.mt.majortype, &source_mt.majortype), "Media types didn't match.\n"); + /* don't worry too much about sub/format type */ + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + req_mt = yuy2_mt; + req_format = yuy2_format; + req_mt.pbFormat = (BYTE *)&req_format; + req_mt.majortype = GUID_NULL; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(IsEqualGUID(&testsink.sink.pin.mt.majortype, &MEDIATYPE_Video), "Media types didn't match.\n"); + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + req_mt = yuy2_mt; + req_format = yuy2_format; + req_mt.pbFormat = (BYTE *)&req_format; + req_mt.majortype = GUID_NULL; + req_format.dwBitRate = 0; /* native looks at this specific field if the major type is GUID_NULL */ + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + todo_wine ok(hr == VFW_E_NO_ACCEPTABLE_TYPES, "Got hr %#lx.\n", hr); + if (hr == S_OK) + { + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + } + + req_mt = yuy2_mt; + req_mt.majortype = MEDIATYPE_Audio; + req_mt.subtype = GUID_NULL; + req_mt.formattype = GUID_NULL; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == VFW_E_NO_ACCEPTABLE_TYPES, "Got hr %#lx.\n", hr); + + /* Test enumeration of sink media types. */ + + req_mt = yuy2_mt; + req_mt.majortype = MEDIATYPE_Audio; + req_mt.subtype = GUID_NULL; + req_mt.formattype = GUID_NULL; + testsink.mt = &req_mt; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, NULL); + ok(hr == VFW_E_NO_ACCEPTABLE_TYPES, "Got hr %#lx.\n", hr); + + req_mt = yuy2_mt; + req_mt.lSampleSize = 444; + testsink.mt = &req_mt; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, NULL); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&testsink.sink.pin.mt, &req_mt), "Media types didn't match.\n"); + + hr = IFilterGraph2_Disconnect(graph, sink); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_Disconnect(graph, sink); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + ok(testsource.source.pin.peer == sink, "Got peer %p.\n", testsource.source.pin.peer); + IFilterGraph2_Disconnect(graph, &testsource.source.pin.IPin_iface); + + IMemInputPin_Release(meminput); + IPin_Release(sink); + IPin_Release(source); + IMediaControl_Release(control); + ref = IFilterGraph2_Release(graph); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + ref = IBaseFilter_Release(filter); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + ref = IBaseFilter_Release(&testsource.filter.IBaseFilter_iface); + ok(!ref, "Got outstanding refcount %ld.\n", ref); + ref = IBaseFilter_Release(&testsink.filter.IBaseFilter_iface); + ok(!ref, "Got outstanding refcount %ld.\n", ref); +} + +START_TEST(mpegvideo) +{ + IBaseFilter *filter; + + CoInitialize(NULL); + + if (FAILED(CoCreateInstance(&CLSID_CMpegVideoCodec, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (void **)&filter))) + { + skip("Failed to create MPEG video decoder instance.\n"); + return; + } + IBaseFilter_Release(filter); + + test_interfaces(); + test_aggregation(); + test_unconnected_filter_state(); + test_enum_pins(); + test_find_pin(); + test_pin_info(); + test_enum_media_types(); + test_media_types(); + test_connect_pin(); + + CoUninitialize(); +}
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=139423
Your paranoid android.
=== build (build log) ===
error: patch failed: dlls/winegstreamer/gst_private.h:114 Task: Patch failed to apply
=== debian11 (build log) ===
error: patch failed: dlls/winegstreamer/gst_private.h:114 Task: Patch failed to apply
=== debian11b (build log) ===
error: patch failed: dlls/winegstreamer/gst_private.h:114 Task: Patch failed to apply
Sigh... I was hoping this quest would be done once it's approved, but apparently I got a last minute merge conflict with https://gitlab.winehq.org/wine/wine/-/commit/eb7571c8b1c40784b50fe75eb1110ec....
Sure, can fix...
This merge request was approved by Rémi Bernon.