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.)
-- v9: Revert "ntdll: Support relocating the main exe." quartz/tests: Add tests for CLSID_CMpegVideoCodec. quartz/tests: Add tests for new CLSID_MPEG1Splitter functionality. winegstreamer: Improve and clean up some debug logs. winegstreamer: Implement a little more of IAMStreamSelect in CLSID_MPEG1Splitter. winegstreamer: Implement CLSID_CMpegVideoCodec. winegstreamer: Add program stream and video output support to CLSID_MPEG1Splitter. winegstreamer: Add WG_MAJOR_TYPE_VIDEO_MPEG1 media type.
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(); +}
From: Alfred Agrell floating@muncher.se
This reverts commit 526b9fc89bc3aea2e972a5541a0f09766cd773c9. --- dlls/ntdll/loader.c | 3 +-- dlls/ntdll/unix/env.c | 2 +- dlls/ntdll/unix/loader.c | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/dlls/ntdll/loader.c b/dlls/ntdll/loader.c index e0cfead6893..29887b525a0 100644 --- a/dlls/ntdll/loader.c +++ b/dlls/ntdll/loader.c @@ -2041,8 +2041,7 @@ static NTSTATUS perform_relocations( void *module, IMAGE_NT_HEADERS *nt, SIZE_T if (nt->OptionalHeader.SectionAlignment < page_size) return STATUS_SUCCESS;
- if (!(nt->FileHeader.Characteristics & IMAGE_FILE_DLL) && - module != NtCurrentTeb()->Peb->ImageBaseAddress) + if (!(nt->FileHeader.Characteristics & IMAGE_FILE_DLL) && NtCurrentTeb()->Peb->ImageBaseAddress) return STATUS_SUCCESS;
relocs = &nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; diff --git a/dlls/ntdll/unix/env.c b/dlls/ntdll/unix/env.c index 80f151c4cb9..feae7d4b954 100644 --- a/dlls/ntdll/unix/env.c +++ b/dlls/ntdll/unix/env.c @@ -2116,7 +2116,7 @@ void init_startup_info(void)
status = load_main_exe( params->ImagePathName.Buffer, NULL, params->CommandLine.Buffer, machine, &image, &module ); - if (!NT_SUCCESS(status)) + if (status) { MESSAGE( "wine: failed to start %s\n", debugstr_us(¶ms->ImagePathName) ); NtTerminateProcess( GetCurrentProcess(), status ); diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index 0b83d11d373..5eab3d519c8 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -1743,7 +1743,7 @@ NTSTATUS load_start_exe( WCHAR **image, void **module ) wcscat( *image, startW ); init_unicode_string( &nt_name, *image ); status = find_builtin_dll( &nt_name, module, &size, &main_image_info, 0, 0, current_machine, 0, FALSE ); - if (!NT_SUCCESS(status)) + if (status) { MESSAGE( "wine: failed to load start.exe: %x\n", status ); NtTerminateProcess( GetCurrentProcess(), status );
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=138560
Your paranoid android.
=== w11pro64 (32 bit report) ===
quartz: mpegvideo.c:830: Test failed: Got stop time 43003b00320033, expected 0. mpegvideo.c:830: Test failed: Got stop time 43003b00325701, expected 56ce. mpegvideo.c:830: Test failed: Got stop time 43003b00320033, expected 0. mpegvideo.c:830: Test failed: Got stop time 43003b00320033, expected 0.
=== w8 (32 bit report) ===
quartz: systemclock.c:223: Test failed: Event should be signaled. systemclock.c:226: Test failed: Got hr 0.
=== w10pro64_zh_CN (64 bit report) ===
quartz: videorenderer.c:1078: Test failed: Thread should block in Receive().
=== debian11 (32 bit report) ===
quartz: mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0.
=== debian11 (32 bit ar:MA report) ===
quartz: mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0.
=== debian11 (32 bit de report) ===
quartz: mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0.
=== debian11 (32 bit fr report) ===
quartz: mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0.
=== debian11 (32 bit he:IL report) ===
quartz: mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0.
=== debian11 (32 bit hi:IN report) ===
quartz: mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0.
=== debian11 (32 bit ja:JP report) ===
quartz: mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0.
=== debian11 (32 bit zh:CN report) ===
quartz: mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0.
=== debian11b (32 bit WoW report) ===
quartz: mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0.
=== debian11b (64 bit WoW report) ===
quartz: mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0. mpegvideo.c:1044: Test succeeded inside todo block: Got hr 0.
@Alcaro It seems you inadvertently pushed a revert that is unrelated to this MR.