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.)
-- v10: https://gitlab.winehq.org/wine/wine/-/merge_requests/3938
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/mfplat.c | 1 + dlls/winegstreamer/quartz_parser.c | 137 +++++++++++++++++++++-------- dlls/winegstreamer/unixlib.h | 6 ++ dlls/winegstreamer/wg_format.c | 57 +++++++++++- dlls/winegstreamer/wg_transform.c | 2 + dlls/winegstreamer/wm_reader.c | 4 + 6 files changed, 171 insertions(+), 36 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 f9d8d79b259..81d6a12da24 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))
+unsigned int wg_format_get_bytes_for_uncompressed(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_bytes_for_uncompressed(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,10 @@ 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: + return wg_format_get_bytes_for_uncompressed(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 +603,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 +662,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 +879,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 +919,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 a3131e9f789..ff24886fce9 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 2353839bbc4..96b4e4b76e7 100644 --- a/dlls/winegstreamer/wg_format.c +++ b/dlls/winegstreamer/wg_format.c @@ -282,10 +282,39 @@ 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); const char *name = gst_structure_get_name(structure); + gboolean parsed;
memset(format, 0, sizeof(*format));
@@ -303,7 +332,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); } @@ -315,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 { gchar *str = gst_caps_to_string(caps); @@ -699,6 +732,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) @@ -723,6 +775,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; @@ -741,6 +795,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 e2b14527a20..5a2dae410bb 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -372,6 +372,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); @@ -425,6 +426,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 6440f8dbb60..675b83e57dd 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 | 33 ++++++++++++++++---- dlls/winegstreamer/unixlib.h | 2 +- dlls/winegstreamer/wg_parser.c | 48 ++++++++++++++++++++---------- 3 files changed, 61 insertions(+), 22 deletions(-)
diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index 81d6a12da24..a74d9baee69 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -1300,7 +1300,8 @@ static HRESULT parser_init_stream(struct strmbase_filter *iface) if (seeking->llStop) stop_flags = AM_SEEKING_AbsolutePositioning; wg_parser_stream_seek(filter->sources[0]->wg_stream, seeking->dRate, - seeking->llCurrent, seeking->llStop, AM_SEEKING_AbsolutePositioning, stop_flags); + seeking->llCurrent, seeking->llStop == seeking->llDuration ? -1 : seeking->llStop, + AM_SEEKING_AbsolutePositioning, stop_flags);
for (i = 0; i < filter->source_count; ++i) { @@ -2163,10 +2164,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; @@ -2181,8 +2182,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; } @@ -2242,7 +2263,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_MPEGSPLIT, &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 ff24886fce9..5d3bfefce46 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -208,7 +208,7 @@ enum wg_parser_type { WG_PARSER_DECODEBIN, WG_PARSER_AVIDEMUX, - WG_PARSER_MPEGAUDIOPARSE, + WG_PARSER_MPEGSPLIT, WG_PARSER_WAVPARSE, };
diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 299eea09c90..efac26b4fb3 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -80,6 +80,7 @@ struct wg_parser pthread_cond_t init_cond; bool no_more_pads, has_duration, error; bool err_on, warn_on; + bool chain_decodebins;
pthread_cond_t read_cond, read_done_cond; struct @@ -451,7 +452,8 @@ static NTSTATUS wg_parser_stream_seek(void *args) 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))) + flags, start_type, params->start_pos * 100, stop_type, + params->stop_pos == -1 ? -1 : params->stop_pos * 100))) GST_ERROR("Failed to seek.\n");
return S_OK; @@ -468,7 +470,7 @@ 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 (stream_time == -1) + if (stream_time == -1 || -params->diff*100 >= stream_time) { /* This can happen legitimately if the sample falls outside of the * segment bounds. GStreamer elements shouldn't present the sample in @@ -979,7 +981,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->chain_decodebins && format_is_compressed(&stream->codec_format)) { if (!stream_decodebin_create(stream)) { @@ -1768,6 +1770,7 @@ static BOOL decodebin_parser_init_gst(struct wg_parser *parser)
gst_bin_add(GST_BIN(parser->container), element); parser->decodebin = element; + parser->chain_decodebins = true;
g_signal_connect(element, "pad-added", G_CALLBACK(pad_added_cb), parser); g_signal_connect(element, "pad-removed", G_CALLBACK(pad_removed_cb), parser); @@ -1808,27 +1811,42 @@ static BOOL avi_parser_init_gst(struct wg_parser *parser) return TRUE; }
-static BOOL mpeg_audio_parser_init_gst(struct wg_parser *parser) +static BOOL mpeg_split_parser_init_gst(struct wg_parser *parser) { - struct wg_parser_stream *stream; GstElement *element;
- if (!(element = create_element("mpegaudioparse", "good"))) + if (!(element = create_element("decodebin", "base"))) return FALSE;
gst_bin_add(GST_BIN(parser->container), element); + parser->decodebin = element; + parser->chain_decodebins = false;
- if (!link_src_to_element(parser->my_src, element)) - return FALSE; + g_signal_connect(element, "pad-added", G_CALLBACK(pad_added_cb), parser); + g_signal_connect(element, "pad-removed", G_CALLBACK(pad_removed_cb), parser); + g_signal_connect(element, "autoplug-continue", G_CALLBACK(autoplug_continue_cb), parser); + g_signal_connect(element, "autoplug-select", G_CALLBACK(autoplug_select_cb), parser); + g_signal_connect(element, "no-more-pads", G_CALLBACK(no_more_pads_cb), parser);
- if (!(stream = create_stream(parser))) - return FALSE; + g_object_set(element, "caps", + gst_caps_new_full( + gst_structure_new("video/mpeg", + "mpegversion", G_TYPE_INT, 1, + "systemstream", G_TYPE_BOOLEAN, FALSE, + "parsed", G_TYPE_BOOLEAN, TRUE, + NULL), + gst_structure_new("audio/mpeg", + "mpegversion", G_TYPE_INT, 1, + "parsed", G_TYPE_BOOLEAN, TRUE, + NULL), + NULL), NULL);
- if (!link_element_to_sink(element, stream->my_sink)) - return FALSE; - gst_pad_set_active(stream->my_sink, 1); + pthread_mutex_lock(&parser->mutex); + parser->no_more_pads = false; + pthread_mutex_unlock(&parser->mutex);
- parser->no_more_pads = true; + if (!link_src_to_element(parser->my_src, element)) + return FALSE;
return TRUE; } @@ -1864,7 +1882,7 @@ 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_MPEGSPLIT] = mpeg_split_parser_init_gst, [WG_PARSER_WAVPARSE] = wave_parser_init_gst, };
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 | 156 +++++++++++++++++++ dlls/winegstreamer/wg_format.c | 4 + dlls/winegstreamer/wg_transform.c | 2 +- dlls/winegstreamer/winegstreamer_classes.idl | 7 + 7 files changed, 214 insertions(+), 1 deletion(-)
diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 8cfadd10bfc..465a7074cbe 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -113,11 +113,13 @@ HRESULT wg_transform_flush(wg_transform_t transform); HRESULT wg_muxer_create(const char *format, wg_muxer_t *muxer); void wg_muxer_destroy(wg_muxer_t muxer);
+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 853907e1825..afe4b5ee413 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -631,6 +631,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}; @@ -660,6 +661,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)) @@ -773,6 +776,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}, @@ -1008,6 +1043,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, @@ -1049,6 +1086,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 a74d9baee69..32521be893f 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -1892,6 +1892,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..0066cd7131c 100644 --- a/dlls/winegstreamer/quartz_transform.c +++ b/dlls/winegstreamer/quartz_transform.c @@ -738,6 +738,162 @@ 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) +{ + struct { + const GUID *subtype; + wg_video_format wg_format; + int bits_per_pixel; + DWORD compression; + } static const video_types[] = { + { &MEDIASUBTYPE_YV12, WG_VIDEO_FORMAT_YV12, 12, MAKEFOURCC('Y','V','1','2') }, + { &MEDIASUBTYPE_YUY2, WG_VIDEO_FORMAT_YUY2, 16, MAKEFOURCC('Y','U','Y','2') }, + { &MEDIASUBTYPE_UYVY, WG_VIDEO_FORMAT_UYVY, 16, MAKEFOURCC('U','Y','V','Y') }, + { &MEDIASUBTYPE_RGB24, WG_VIDEO_FORMAT_BGR, 24, BI_RGB }, + { &MEDIASUBTYPE_RGB32, WG_VIDEO_FORMAT_BGRx, 32, BI_RGB }, + { &MEDIASUBTYPE_RGB565, WG_VIDEO_FORMAT_RGB16, 16, BI_BITFIELDS }, + { &MEDIASUBTYPE_RGB555, WG_VIDEO_FORMAT_RGB15, 16, BI_RGB }, + }; + + MPEG1VIDEOINFO *input_format; + VIDEOINFO *output_format; + + if (!filter->sink.pin.peer) + return VFW_S_NO_MORE_ITEMS; + + if (index >= 7) + return VFW_S_NO_MORE_ITEMS; + + input_format = (MPEG1VIDEOINFO*)filter->sink.pin.mt.pbFormat; + + output_format = CoTaskMemAlloc(sizeof(*output_format)); + if (!output_format) + return E_OUTOFMEMORY; + + memcpy(output_format, &input_format->hdr, sizeof(VIDEOINFOHEADER)); + output_format->bmiHeader.biPlanes = 1; + output_format->bmiHeader.biBitCount = video_types[index].bits_per_pixel; + output_format->bmiHeader.biCompression = video_types[index].compression; + output_format->bmiHeader.biSizeImage = + wg_format_get_bytes_for_uncompressed(video_types[index].wg_format, + output_format->bmiHeader.biWidth, output_format->bmiHeader.biHeight); + output_format->dwBitRate = MulDiv(output_format->bmiHeader.biSizeImage * 8, 10000000, output_format->AvgTimePerFrame); + + memset(mt, 0, sizeof(*mt)); + mt->majortype = MEDIATYPE_Video; + mt->subtype = *video_types[index].subtype; + mt->bFixedSizeSamples = TRUE; + mt->lSampleSize = output_format->bmiHeader.biSizeImage; + mt->formattype = FORMAT_VideoInfo; + mt->cbFormat = sizeof(VIDEOINFOHEADER); + mt->pbFormat = (BYTE *)output_format; + + if (video_types[index].wg_format == WG_VIDEO_FORMAT_RGB16) + { + mt->cbFormat = offsetof(VIDEOINFO, dwBitMasks[3]); + output_format->dwBitMasks[iRED] = 0xf800; + output_format->dwBitMasks[iGREEN] = 0x07e0; + output_format->dwBitMasks[iBLUE] = 0x001f; + } + + 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_format.c b/dlls/winegstreamer/wg_format.c index 96b4e4b76e7..4969b2564d4 100644 --- a/dlls/winegstreamer/wg_format.c +++ b/dlls/winegstreamer/wg_format.c @@ -509,6 +509,10 @@ 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)); + info.fps_n = format->u.video.fps_n; + info.fps_d = format->u.video.fps_d; + if (!format->u.video.fps_d && !format->u.video.fps_n) + info.fps_d = 1; if ((caps = gst_video_info_to_caps(&info))) { for (i = 0; i < gst_caps_get_size(caps); ++i) diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index 5a2dae410bb..1610f27b981 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -361,6 +361,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)) { @@ -372,7 +373,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/winegstreamer/quartz_parser.c | 50 +++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 5 deletions(-)
diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index 32521be893f..440c9493bca 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -1579,17 +1579,57 @@ 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); + if (filter->sink.pin.peer) + *count = wg_parser_get_stream_count(filter->wg_parser); + else + *count = 0; + return S_OK; }
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); + wg_parser_stream_t stream = wg_parser_get_stream(filter->wg_parser, index); + + 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); + + if (!filter->sink.pin.peer) + return VFW_E_NOT_CONNECTED; + if (index < 0 || index >= wg_parser_get_stream_count(filter->wg_parser)) + return S_FALSE; + + if (mt) + { + struct wg_format fmt; + wg_parser_stream_get_preferred_format(stream, &fmt); + if (!(*mt = CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)))) + return E_OUTOFMEMORY; + if (!amt_from_wg_format(*mt, &fmt, FALSE)) + { + CoTaskMemFree(*mt); + return E_OUTOFMEMORY; + } + } + + 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; + + return S_OK; }
static HRESULT WINAPI stream_select_Enable(IAMStreamSelect *iface, LONG index, DWORD flags)
From: Alfred Agrell floating@muncher.se
--- dlls/winegstreamer/unixlib.c | 9 +-------- dlls/winegstreamer/wg_format.c | 11 ++++------- dlls/winegstreamer/wg_parser.c | 14 ++++---------- dlls/winegstreamer/wg_transform.c | 10 ++-------- 4 files changed, 11 insertions(+), 33 deletions(-)
diff --git a/dlls/winegstreamer/unixlib.c b/dlls/winegstreamer/unixlib.c index 513ece95a90..46f2c79da4d 100644 --- a/dlls/winegstreamer/unixlib.c +++ b/dlls/winegstreamer/unixlib.c @@ -124,16 +124,9 @@ GstElement *find_element(GstElementFactoryListType type, GstCaps *src_caps, GstC
done: if (element) - { GST_DEBUG("Created %s element %p.", name, element); - } else - { - gchar *src_str = gst_caps_to_string(src_caps), *sink_str = gst_caps_to_string(sink_caps); - GST_WARNING("Failed to create element matching caps %s / %s.", src_str, sink_str); - g_free(sink_str); - g_free(src_str); - } + GST_WARNING("Failed to create element matching caps %" GST_PTR_FORMAT " / %" GST_PTR_FORMAT ".", src_caps, sink_caps);
return element; } diff --git a/dlls/winegstreamer/wg_format.c b/dlls/winegstreamer/wg_format.c index 4969b2564d4..d5a72f40bf1 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; }
@@ -350,10 +350,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 efac26b4fb3..ad654bfbefc 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -674,6 +674,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; @@ -720,7 +721,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); @@ -737,9 +737,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) { @@ -774,12 +772,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; } diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index 1610f27b981..6b2910740d3 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); @@ -500,7 +497,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))) { @@ -539,9 +535,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/quartz/tests/mpegsplit.c | 112 +++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 10 deletions(-)
diff --git a/dlls/quartz/tests/mpegsplit.c b/dlls/quartz/tests/mpegsplit.c index b2cced84c68..6d131931661 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,9 @@ 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); + todo_wine_if (hr == VFW_S_NO_STOP_TIME) { + 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 +1239,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 +1274,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 +1377,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 +1732,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 +1876,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 +1968,91 @@ static void test_large_file(void) ok(ret, "Failed to delete file, error %lu.\n", GetLastError()); }
+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 n_streams; + HRESULT hr; + ULONG ref; + DWORD ret; + + IBaseFilter_QueryInterface(filter, &IID_IAMStreamSelect, (void **)&sel); + hr = IAMStreamSelect_Count(sel, &n_streams); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(n_streams == 0, "Got %lu streams.\n", n_streams); + + 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); + + hr = IAMStreamSelect_Count(sel, &n_streams); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(n_streams == 2, "Got %lu streams.\n", n_streams); + + 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); + + 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 +2079,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 | 1590 +++++++++++++++++++++++++++++++++ 2 files changed, 1591 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..53819cffa3d --- /dev/null +++ b/dlls/quartz/tests/mpegvideo.c @@ -0,0 +1,1590 @@ +/* + * 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 mpg_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 = 0, + .dwBitErrorRate = 0, + .AvgTimePerFrame = 400000, + .bmiHeader.biSize = sizeof(BITMAPINFOHEADER), + .bmiHeader.biWidth = 32, + .bmiHeader.biHeight = 24, + .bmiHeader.biPlanes = 1, + .bmiHeader.biBitCount = 16, + .bmiHeader.biCompression = 0, + .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); +} + +enum video_type_t { + vt_yv12, vt_y41p, vt_yuy2, vt_uyvy, + vt_rgb24, vt_rgb32, vt_rgb565, vt_rgb555, vt_rgb8, +}; +struct { + const GUID *subtype; + int bits_per_pixel; + DWORD compression; +} video_types[] = { + { &MEDIASUBTYPE_YV12, 12, MAKEFOURCC('Y','V','1','2') }, + { &MEDIASUBTYPE_Y41P, 12, MAKEFOURCC('Y','4','1','P') }, + { &MEDIASUBTYPE_YUY2, 16, MAKEFOURCC('Y','U','Y','2') }, + { &MEDIASUBTYPE_UYVY, 16, MAKEFOURCC('U','Y','V','Y') }, + { &MEDIASUBTYPE_RGB24, 24, BI_RGB }, + { &MEDIASUBTYPE_RGB32, 32, BI_RGB }, + { &MEDIASUBTYPE_RGB565, 16, BI_BITFIELDS }, + { &MEDIASUBTYPE_RGB555, 16, BI_RGB }, + { &MEDIASUBTYPE_RGB8, 8, BI_RGB }, +}; + +static void init_video_mt(AM_MEDIA_TYPE *mt, VIDEOINFOHEADER *format, enum video_type_t idx) +{ + memcpy(mt, &yuy2_mt, sizeof(AM_MEDIA_TYPE)); + memcpy(format, &yuy2_format, sizeof(VIDEOINFOHEADER)); + mt->subtype = *video_types[idx].subtype; + format->bmiHeader.biBitCount = video_types[idx].bits_per_pixel; + format->bmiHeader.biCompression = video_types[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 (idx == vt_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(); + VIDEOINFOHEADER format; + AM_MEDIA_TYPE mt; + HRESULT hr; + ULONG ref; + IPin *pin; + + IBaseFilter_FindPin(filter, L"In", &pin); + + hr = IPin_QueryAccept(pin, &mpg_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + mt = mpg_mt; + mt.subtype = MEDIASUBTYPE_MPEG1Packet; + hr = IPin_QueryAccept(pin, &mt); + todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + + mt = mpg_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); + + init_video_mt(&mt, &format, vt_yuy2); + hr = IPin_QueryAccept(pin, &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 wine_wrong_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; + HRESULT hr; + LONG size; + + size = IMediaSample_GetSize(sample); + ok(size == 1536, "Got size %lu.\n", size); + size = IMediaSample_GetActualDataLength(sample); + ok(size == 1536, "Got actual size %lu.\n", size); + + start = 0xdeadbeef; + stop = 0xdeadbeef; + hr = IMediaSample_GetTime(sample, &start, &stop); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + 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->wine_wrong_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) +{ + 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, &mpg_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + init_video_mt(&mt, &format, vt_yuy2); + 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 == 1536, "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, vt_yuy2); + 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 == 1536, "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, vt_yuy2); + 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 == 1536, "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 wine_fail) +{ + 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(wine_fail) /* 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) +{ + /* ffmpeg -t 1.0 -s 32x24 -f rawvideo -pix_fmt rgb24 -r 25 -i /dev/zero -c:v mpeg1video -q:v 0 -format mpegvideo empty.mpeg */ + /* gst-launch-1.0 filesrc location=empty.mpeg ! mpegpsdemux ! mpegvideoparse ! filesink location=empty-es.mpeg */ + /* 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, 0x13, 0xff, 0xff, 0xe0, 0x18, + 0x00, 0x00, 0x01, 0xb8, 0x00, 0x08, 0x00, 0x40, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0xff, 0xf8, + 0x00, 0x00, 0x01, 0x01, 0x13, 0xf8, 0x7d, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x20, + 0x00, 0x00, 0x01, 0x02, 0x13, 0xf8, 0x7d, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x20, + }; + static const BYTE empty_mpg_frame2[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x57, 0xff, 0xf8, 0x80, + 0x00, 0x00, 0x01, 0x01, 0x12, 0x79, 0xc0, + 0x00, 0x00, 0x01, 0x02, 0x12, 0x79, 0xc0, + }; + static const BYTE empty_mpg_frame3[] = { + 0x00, 0x00, 0x01, 0x00, 0x00, 0x97, 0xff, 0xf8, 0x80, + 0x00, 0x00, 0x01, 0x01, 0x12, 0x79, 0xc0, + 0x00, 0x00, 0x01, 0x02, 0x12, 0x79, 0xc0, + }; + 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; + + 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->wine_wrong_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->wine_wrong_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); + + 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; + + 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->wine_wrong_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->wine_wrong_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); + + 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; + VIDEOINFOHEADER source_format; + 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); + + test_source_allocator(graph, control, sink, source, &testsource, &testsink); + + /* 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, &mpg_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 = mpg_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, &mpg_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, &mpg_mt), "Media types didn't match.\n"); + ok(compare_media_types(&testsource.source.pin.mt, &mpg_mt), "Media types didn't match.\n"); + FreeMediaType(&mt); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + req_mt.majortype = GUID_NULL; + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + req_mt.subtype = GUID_NULL; + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + + /* broken on native */ + /* + init_video_mt(&req_mt, &req_format, vt_yv12); + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + */ + init_video_mt(&req_mt, &req_format, vt_y41p); + hr = IPin_QueryAccept(source, &req_mt); + todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + init_video_mt(&req_mt, &req_format, vt_yuy2); + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + init_video_mt(&req_mt, &req_format, vt_uyvy); + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + init_video_mt(&req_mt, &req_format, vt_rgb24); + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + init_video_mt(&req_mt, &req_format, vt_rgb32); + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + init_video_mt(&req_mt, &req_format, vt_rgb565); + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + init_video_mt(&req_mt, &req_format, vt_rgb555); + hr = IPin_QueryAccept(source, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + init_video_mt(&req_mt, &req_format, vt_rgb8); + hr = IPin_QueryAccept(source, &req_mt); + todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + 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 (TRUE) + { + VIDEOINFOHEADER expect_format; + AM_MEDIA_TYPE expect_mt; + + hr = IEnumMediaTypes_Next(enummt, 1, &pmt, NULL); + if (hr != S_OK) + break; + if (IsEqualGUID(&pmt->formattype, &FORMAT_VideoInfo)) + { + 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); + ++i; + while (winetest_platform_is_wine && (i == vt_y41p || i == vt_rgb8)) + { + todo_wine ok(FALSE, "%u: Format not implemented in Wine.\n", i); + ++i; + } + } + DeleteMediaType(pmt); + } + ok(i == 9, "Too few media types.\n"); + + 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. */ + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + init_video_mt(&req_mt, &req_format, vt_yuy2); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_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); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + + hr = IPin_ConnectedTo(source, &peer); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(peer == &testsink.sink.pin.IPin_iface, "Got peer %p.\n", peer); + IPin_Release(peer); + + hr = IPin_ConnectionMediaType(source, &mt); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + ok(compare_media_types(&mt, &req_mt), "Media types didn't match.\n"); + ok(compare_media_types(&testsink.sink.pin.mt, &req_mt), "Media types didn't match.\n"); + FreeMediaType(&mt); + + hr = IMediaControl_Pause(control); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_Disconnect(graph, source); + 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_sample_processing(control, meminput, &testsink); + test_streaming_events(control, sink, meminput, &testsink); + + hr = IFilterGraph2_Disconnect(graph, source); + ok(hr == S_OK, "Got hr %#lx.\n", hr); + hr = IFilterGraph2_Disconnect(graph, source); + ok(hr == S_FALSE, "Got hr %#lx.\n", hr); + ok(testsink.sink.pin.peer == source, "Got peer %p.\n", testsink.sink.pin.peer); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + req_mt.lSampleSize = 999; + req_mt.bTemporalCompression = TRUE; + hr = IFilterGraph2_ConnectDirect(graph, source, &testsink.sink.pin.IPin_iface, &req_mt); + 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"); + IFilterGraph2_Disconnect(graph, source); + IFilterGraph2_Disconnect(graph, &testsink.sink.pin.IPin_iface); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + 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. */ + + init_video_mt(&source_mt, &source_format, vt_yuy2); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + 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); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + req_mt.majortype = 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); + } + + init_video_mt(&req_mt, &req_format, vt_yuy2); + 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. */ + + init_video_mt(&req_mt, &req_format, vt_yuy2); + 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); + + init_video_mt(&req_mt, &req_format, vt_yuy2); + 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(); +}
On Tue Oct 10 15:23:22 2023 +0000, Gijs Vermeulen wrote:
@Alcaro It seems you inadvertently pushed a revert that is unrelated to this MR.
huh. I thought I reset --hard'd that.
Fixed, thanks.