Compared to last attempt, I moved the format pointer to a dedicated `wg_transform_read_data` parameter instead of a property of the sample. Most of the transforms use `NULL` there as they don't expect or support format change. H264 decoder passes a transform format member where it tracks the current stream format.
-- v3: winegstreamer: Expose output media type attributes from the stream format. winegstreamer: Support wg_transform output format change events. winegstreamer: Avoid leaking buffer list in wg_transform_read_data. winegstreamer: Track caps changes and keep them with the output buffers. winegstreamer: Only filter video caps attributes when format explicitely uses 0. winegstreamer: Set wg_transform output sample size on gst_buffer_map failure. winegstreamer: Append a videoconvert element to wg_transform video output.
From: Rémi Bernon rbernon@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45988 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47084 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49715 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52183 Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winegstreamer/wg_transform.c | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index 21392a82509..ae6e948318a 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -268,6 +268,11 @@ NTSTATUS wg_transform_create(void *args) break;
case WG_MAJOR_TYPE_VIDEO: + if (!(element = create_element("videoconvert", "base")) + || !transform_append_element(transform, element, &first, &last)) + goto out; + /* Let GStreamer choose a default number of threads. */ + gst_util_set_object_arg(G_OBJECT(element), "n-threads", "0"); break;
case WG_MAJOR_TYPE_MPEG1_AUDIO:
From: Rémi Bernon rbernon@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45988 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47084 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49715 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52183 Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winegstreamer/wg_transform.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index ae6e948318a..9699d7e0a31 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -392,6 +392,7 @@ static NTSTATUS read_transform_output_data(GstBuffer *buffer, struct wg_sample * if (!gst_buffer_map(buffer, &info, GST_MAP_READ)) { GST_ERROR("Failed to map buffer %p", buffer); + sample->size = 0; return STATUS_UNSUCCESSFUL; }
@@ -464,10 +465,7 @@ NTSTATUS wg_transform_read_data(void *args) }
if ((status = read_transform_output_data(transform->output_buffer, sample))) - { - sample->size = 0; return status; - }
if (!(sample->flags & WG_SAMPLE_FLAG_INCOMPLETE)) {
From: Rémi Bernon rbernon@codeweavers.com
In wg_transform we only want to remove width/height/framerate if the transform supports format change, and we want to keep the caps fixed otherwise so we can use gst_caps_is_always_compatible to monitor caps changes.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45988 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47084 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49715 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52183 Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/mf/tests/mf.c | 2 +- dlls/winegstreamer/h264_decoder.c | 8 ++++++++ dlls/winegstreamer/wg_format.c | 9 ++++++--- dlls/winegstreamer/wg_parser.c | 6 ++++++ 4 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index e873712085b..9de7308f9ff 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -7200,7 +7200,7 @@ static void test_h264_decoder(void) "got status %#lx\n", status); hr = IMFSample_GetTotalLength(output.pSample, &length); ok(hr == S_OK, "GetTotalLength returned %#lx\n", hr); - todo_wine_if(length == 1920 * 1080 * 3 / 2) + todo_wine ok(length == 0, "got length %lu\n", length); ret = IMFSample_Release(output.pSample); ok(ret == 0, "Release returned %lu\n", ret); diff --git a/dlls/winegstreamer/h264_decoder.c b/dlls/winegstreamer/h264_decoder.c index de823741ba9..912a5bdf0b1 100644 --- a/dlls/winegstreamer/h264_decoder.c +++ b/dlls/winegstreamer/h264_decoder.c @@ -75,6 +75,14 @@ static HRESULT try_create_wg_transform(struct h264_decoder *decoder) if (output_format.major_type == WG_MAJOR_TYPE_UNKNOWN) return MF_E_INVALIDMEDIATYPE;
+ /* Don't force any specific size, H264 streams already have the metadata for it + * and will generate a MF_E_TRANSFORM_STREAM_CHANGE result later. + */ + output_format.u.video.width = 0; + output_format.u.video.height = 0; + output_format.u.video.fps_d = 0; + output_format.u.video.fps_n = 0; + if (!(decoder->wg_transform = wg_transform_create(&input_format, &output_format))) return E_FAIL;
diff --git a/dlls/winegstreamer/wg_format.c b/dlls/winegstreamer/wg_format.c index 5b3a5617ff1..4cebeac9182 100644 --- a/dlls/winegstreamer/wg_format.c +++ b/dlls/winegstreamer/wg_format.c @@ -394,11 +394,14 @@ static GstCaps *wg_format_to_caps_video(const struct wg_format *format) gst_video_info_set_format(&info, video_format, format->u.video.width, abs(format->u.video.height)); if ((caps = gst_video_info_to_caps(&info))) { - /* Clear some fields that shouldn't prevent us from connecting. */ for (i = 0; i < gst_caps_get_size(caps); ++i) { - gst_structure_remove_fields(gst_caps_get_structure(caps, i), - "framerate", "pixel-aspect-ratio", "colorimetry", "chroma-site", NULL); + if (!format->u.video.width) + gst_structure_remove_fields(gst_caps_get_structure(caps, i), "width", NULL); + if (!format->u.video.height) + gst_structure_remove_fields(gst_caps_get_structure(caps, i), "height", NULL); + if (!format->u.video.fps_d && !format->u.video.fps_n) + gst_structure_remove_fields(gst_caps_get_structure(caps, i), "framerate", NULL); } } return caps; diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 48b88a4b11c..7d55897aa0a 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -598,6 +598,7 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) { GstCaps *caps, *filter, *temp; gchar *str; + gsize i;
gst_query_parse_caps(query, &filter);
@@ -608,6 +609,11 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) if (!caps) return FALSE;
+ /* Clear some fields that shouldn't prevent us from connecting. */ + for (i = 0; i < gst_caps_get_size(caps); ++i) + gst_structure_remove_fields(gst_caps_get_structure(caps, i), + "framerate", "pixel-aspect-ratio", "colorimetry", "chroma-site", NULL); + str = gst_caps_to_string(caps); GST_LOG("Stream caps are "%s".", str); g_free(str);
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=115291
Your paranoid android.
=== debian11 (build log) ===
0104:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0104:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0104:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0104:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0104:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0104:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins
=== debian11 (build log) ===
0118:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0118:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins
From: Rémi Bernon rbernon@codeweavers.com
Using a GstSample queue instead of GstBuffer queue to combine both.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45988 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47084 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49715 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52183 Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winegstreamer/wg_transform.c | 76 +++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 18 deletions(-)
diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index 9699d7e0a31..b6cc51aecb7 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -51,40 +51,77 @@ struct wg_transform GstBufferList *input; guint input_max_length; GstAtomicQueue *output_queue; - GstBuffer *output_buffer; + GstSample *output_sample; + GstCaps *output_caps; };
static GstFlowReturn transform_sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *buffer) { struct wg_transform *transform = gst_pad_get_element_private(pad); + GstSample *sample;
GST_LOG("transform %p, buffer %p.", transform, buffer);
- gst_atomic_queue_push(transform->output_queue, buffer); + if (!(sample = gst_sample_new(buffer, transform->output_caps, NULL, NULL))) + { + GST_ERROR("Failed to allocate transform %p output sample.", transform); + gst_buffer_unref(buffer); + return GST_FLOW_ERROR; + }
+ gst_atomic_queue_push(transform->output_queue, sample); + gst_buffer_unref(buffer); return GST_FLOW_OK; }
+static gboolean transform_sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event) +{ + struct wg_transform *transform = gst_pad_get_element_private(pad); + + GST_LOG("transform %p, type "%s".", transform, GST_EVENT_TYPE_NAME(event)); + + switch (event->type) + { + case GST_EVENT_CAPS: + { + GstCaps *caps; + + gst_event_parse_caps(event, &caps); + + gst_caps_unref(transform->output_caps); + transform->output_caps = gst_caps_ref(caps); + break; + } + default: + GST_WARNING("Ignoring "%s" event.", GST_EVENT_TYPE_NAME(event)); + break; + } + + gst_event_unref(event); + return TRUE; +} + NTSTATUS wg_transform_destroy(void *args) { struct wg_transform *transform = args; - GstBuffer *buffer; + GstSample *sample;
if (transform->input) gst_buffer_list_unref(transform->input);
gst_element_set_state(transform->container, GST_STATE_NULL);
- if (transform->output_buffer) - gst_buffer_unref(transform->output_buffer); - while ((buffer = gst_atomic_queue_pop(transform->output_queue))) - gst_buffer_unref(buffer); + if (transform->output_sample) + gst_sample_unref(transform->output_sample); + while ((sample = gst_atomic_queue_pop(transform->output_queue))) + gst_sample_unref(sample);
g_object_unref(transform->their_sink); g_object_unref(transform->their_src); g_object_unref(transform->container); g_object_unref(transform->my_sink); g_object_unref(transform->my_src); + gst_caps_unref(transform->output_caps); gst_atomic_queue_unref(transform->output_queue); free(transform);
@@ -162,10 +199,10 @@ static bool transform_append_element(struct wg_transform *transform, GstElement NTSTATUS wg_transform_create(void *args) { struct wg_transform_create_params *params = args; - GstCaps *raw_caps = NULL, *src_caps = NULL, *sink_caps = NULL; struct wg_format output_format = *params->output_format; struct wg_format input_format = *params->input_format; GstElement *first = NULL, *last = NULL, *element; + GstCaps *raw_caps = NULL, *src_caps = NULL; NTSTATUS status = STATUS_UNSUCCESSFUL; GstPadTemplate *template = NULL; struct wg_transform *transform; @@ -194,9 +231,9 @@ NTSTATUS wg_transform_create(void *args) if (!transform->my_src) goto out;
- if (!(sink_caps = wg_format_to_caps(&output_format))) + if (!(transform->output_caps = wg_format_to_caps(&output_format))) goto out; - if (!(template = gst_pad_template_new("sink", GST_PAD_SINK, GST_PAD_ALWAYS, sink_caps))) + if (!(template = gst_pad_template_new("sink", GST_PAD_SINK, GST_PAD_ALWAYS, transform->output_caps))) goto out; transform->my_sink = gst_pad_new_from_template(template, "sink"); g_object_unref(template); @@ -204,13 +241,14 @@ NTSTATUS wg_transform_create(void *args) goto out;
gst_pad_set_element_private(transform->my_sink, transform); + gst_pad_set_event_function(transform->my_sink, transform_sink_event_cb); gst_pad_set_chain_function(transform->my_sink, transform_sink_chain_cb);
/* Since we append conversion elements, we don't want to filter decoders * based on the actual output caps now. Matching decoders with the * raw output media type should be enough. */ - media_type = gst_structure_get_name(gst_caps_get_structure(sink_caps, 0)); + media_type = gst_structure_get_name(gst_caps_get_structure(transform->output_caps, 0)); if (!(raw_caps = gst_caps_new_empty_simple(media_type))) goto out;
@@ -316,7 +354,6 @@ NTSTATUS wg_transform_create(void *args) || !gst_pad_push_event(transform->my_src, event)) goto out;
- gst_caps_unref(sink_caps); gst_caps_unref(src_caps);
GST_INFO("Created winegstreamer transform %p.", transform); @@ -330,8 +367,8 @@ out: gst_object_unref(transform->their_src); if (transform->my_sink) gst_object_unref(transform->my_sink); - if (sink_caps) - gst_caps_unref(sink_caps); + if (transform->output_caps) + gst_caps_unref(transform->output_caps); if (transform->my_src) gst_object_unref(transform->my_src); if (src_caps) @@ -440,6 +477,7 @@ NTSTATUS wg_transform_read_data(void *args) struct wg_transform *transform = params->transform; struct wg_sample *sample = params->sample; GstBufferList *input = transform->input; + GstBuffer *output_buffer; GstFlowReturn ret; NTSTATUS status;
@@ -456,7 +494,7 @@ NTSTATUS wg_transform_read_data(void *args) return STATUS_UNSUCCESSFUL; }
- if (!transform->output_buffer && !(transform->output_buffer = gst_atomic_queue_pop(transform->output_queue))) + if (!transform->output_sample && !(transform->output_sample = gst_atomic_queue_pop(transform->output_queue))) { sample->size = 0; params->result = MF_E_TRANSFORM_NEED_MORE_INPUT; @@ -464,13 +502,15 @@ NTSTATUS wg_transform_read_data(void *args) return STATUS_SUCCESS; }
- if ((status = read_transform_output_data(transform->output_buffer, sample))) + output_buffer = gst_sample_get_buffer(transform->output_sample); + + if ((status = read_transform_output_data(output_buffer, sample))) return status;
if (!(sample->flags & WG_SAMPLE_FLAG_INCOMPLETE)) { - gst_buffer_unref(transform->output_buffer); - transform->output_buffer = NULL; + gst_sample_unref(transform->output_sample); + transform->output_sample = NULL; }
params->result = S_OK;
From: Rémi Bernon rbernon@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45988 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47084 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49715 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52183 Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winegstreamer/wg_transform.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index b6cc51aecb7..f8211001be9 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -486,6 +486,7 @@ NTSTATUS wg_transform_read_data(void *args) else if (!(transform->input = gst_buffer_list_new())) { GST_ERROR("Failed to allocate new input queue"); + gst_buffer_list_unref(input); return STATUS_NO_MEMORY; } else if ((ret = gst_pad_push_list(transform->my_src, input)))
From: Rémi Bernon rbernon@codeweavers.com
Using a separate peek_data entry point, which pushes the input buffer list and returns the current output sample format if requested.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45988 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47084 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49715 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52183 Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/mf/tests/mf.c | 4 --- dlls/winegstreamer/gst_private.h | 3 +- dlls/winegstreamer/h264_decoder.c | 19 +++++++++++-- dlls/winegstreamer/main.c | 23 +++++++++++---- dlls/winegstreamer/quartz_transform.c | 12 ++++---- dlls/winegstreamer/unix_private.h | 2 +- dlls/winegstreamer/unixlib.h | 40 ++++++++++++++++++++++++++- dlls/winegstreamer/wg_format.c | 31 --------------------- dlls/winegstreamer/wg_parser.c | 1 + dlls/winegstreamer/wg_transform.c | 32 +++++++++++++++------ dlls/winegstreamer/wma_decoder.c | 11 ++++++-- 11 files changed, 116 insertions(+), 62 deletions(-)
diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 9de7308f9ff..419faa61e2b 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -7187,20 +7187,16 @@ static void test_h264_decoder(void) ok(i == 2, "got %lu iterations\n", i); todo_wine ok(h264_encoded_data_len == 1180, "got h264_encoded_data_len %lu\n", h264_encoded_data_len); - todo_wine ok(hr == MF_E_TRANSFORM_STREAM_CHANGE, "ProcessOutput returned %#lx\n", hr); ok(output.dwStreamID == 0, "got dwStreamID %lu\n", output.dwStreamID); ok(!!output.pSample, "got pSample %p\n", output.pSample); - todo_wine ok(output.dwStatus == MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE, "got dwStatus %#lx\n", output.dwStatus); ok(!output.pEvents, "got pEvents %p\n", output.pEvents); - todo_wine ok(status == MFT_PROCESS_OUTPUT_STATUS_NEW_STREAMS, "got status %#lx\n", status); hr = IMFSample_GetTotalLength(output.pSample, &length); ok(hr == S_OK, "GetTotalLength returned %#lx\n", hr); - todo_wine ok(length == 0, "got length %lu\n", length); ret = IMFSample_Release(output.pSample); ok(ret == 0, "Release returned %lu\n", ret); diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 2a4b16079c1..c92d452fbd9 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -97,7 +97,8 @@ struct wg_transform *wg_transform_create(const struct wg_format *input_format, const struct wg_format *output_format); void wg_transform_destroy(struct wg_transform *transform); HRESULT wg_transform_push_data(struct wg_transform *transform, struct wg_sample *sample); -HRESULT wg_transform_read_data(struct wg_transform *transform, struct wg_sample *sample); +HRESULT wg_transform_peek_data(struct wg_transform *transform, struct wg_format *format); +bool wg_transform_read_data(struct wg_transform *transform, struct wg_sample *sample);
unsigned int wg_format_get_max_size(const struct wg_format *format);
diff --git a/dlls/winegstreamer/h264_decoder.c b/dlls/winegstreamer/h264_decoder.c index 912a5bdf0b1..db4c74d561e 100644 --- a/dlls/winegstreamer/h264_decoder.c +++ b/dlls/winegstreamer/h264_decoder.c @@ -50,6 +50,7 @@ struct h264_decoder IMFMediaType *input_type; IMFMediaType *output_type;
+ struct wg_format wg_format; struct wg_transform *wg_transform; };
@@ -543,6 +544,7 @@ static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, struct h264_decoder *decoder = impl_from_IMFTransform(iface); MFT_OUTPUT_STREAM_INFO info; struct wg_sample *wg_sample; + struct wg_format wg_format; HRESULT hr;
TRACE("iface %p, flags %#lx, count %lu, samples %p, status %p.\n", iface, flags, count, samples, status); @@ -565,8 +567,21 @@ static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags,
if (wg_sample->max_size < info.cbSize) hr = MF_E_BUFFERTOOSMALL; - else - hr = wg_transform_read_data(decoder->wg_transform, wg_sample); + else if (SUCCEEDED(hr = wg_transform_peek_data(decoder->wg_transform, &wg_format))) + { + if (!wg_format_compare(&decoder->wg_format, &wg_format)) + { + decoder->wg_format = wg_format; + samples[0].dwStatus |= MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; + *status |= MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; + hr = MF_E_TRANSFORM_STREAM_CHANGE; + } + else if (!wg_transform_read_data(decoder->wg_transform, wg_sample)) + { + ERR("Failed to read transform data\n"); + hr = E_FAIL; + } + }
mf_destroy_wg_sample(wg_sample); return hr; diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index c3adbb82d61..b3fea9e760c 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -329,23 +329,36 @@ HRESULT wg_transform_push_data(struct wg_transform *transform, struct wg_sample return params.result; }
-HRESULT wg_transform_read_data(struct wg_transform *transform, struct wg_sample *sample) +HRESULT wg_transform_peek_data(struct wg_transform *transform, struct wg_format *format) { - struct wg_transform_read_data_params params = + struct wg_transform_peek_data_params params = { .transform = transform, - .sample = sample, + .format = format, }; NTSTATUS status;
- TRACE("transform %p, sample %p.\n", transform, sample); + TRACE("transform %p, format %p.\n", transform, format);
- if ((status = __wine_unix_call(unix_handle, unix_wg_transform_read_data, ¶ms))) + if ((status = __wine_unix_call(unix_handle, unix_wg_transform_peek_data, ¶ms))) return HRESULT_FROM_NT(status);
return params.result; }
+bool wg_transform_read_data(struct wg_transform *transform, struct wg_sample *sample) +{ + struct wg_transform_read_data_params params = + { + .transform = transform, + .sample = sample, + }; + + TRACE("transform %p, sample %p.\n", transform, sample); + + return !__wine_unix_call(unix_handle, unix_wg_transform_read_data, ¶ms); +} + BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, void *reserved) { if (reason == DLL_PROCESS_ATTACH) diff --git a/dlls/winegstreamer/quartz_transform.c b/dlls/winegstreamer/quartz_transform.c index 447f331d474..5c9ff508846 100644 --- a/dlls/winegstreamer/quartz_transform.c +++ b/dlls/winegstreamer/quartz_transform.c @@ -342,16 +342,14 @@ static HRESULT WINAPI transform_sink_receive(struct strmbase_sink *pin, IMediaSa return hr; }
- hr = wg_transform_read_data(filter->transform, &output_wg_sample); - if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) - { - IMediaSample_Release(output_sample); - break; - } + hr = wg_transform_peek_data(filter->transform, NULL); + if (SUCCEEDED(hr) && !wg_transform_read_data(filter->transform, &output_wg_sample)) + hr = E_FAIL; + if (FAILED(hr)) { IMediaSample_Release(output_sample); - return hr; + return hr == MF_E_TRANSFORM_NEED_MORE_INPUT ? S_OK : hr; }
hr = IMediaSample_SetActualDataLength(output_sample, output_wg_sample.size); diff --git a/dlls/winegstreamer/unix_private.h b/dlls/winegstreamer/unix_private.h index 7bce8263aaf..10992992f2b 100644 --- a/dlls/winegstreamer/unix_private.h +++ b/dlls/winegstreamer/unix_private.h @@ -29,12 +29,12 @@ extern bool init_gstreamer(void) DECLSPEC_HIDDEN; extern GstElement *create_element(const char *name, const char *plugin_set) DECLSPEC_HIDDEN;
extern void wg_format_from_caps(struct wg_format *format, const GstCaps *caps) DECLSPEC_HIDDEN; -extern bool wg_format_compare(const struct wg_format *a, const struct wg_format *b) DECLSPEC_HIDDEN; extern GstCaps *wg_format_to_caps(const struct wg_format *format) DECLSPEC_HIDDEN;
extern NTSTATUS wg_transform_create(void *args) DECLSPEC_HIDDEN; extern NTSTATUS wg_transform_destroy(void *args) DECLSPEC_HIDDEN; extern NTSTATUS wg_transform_push_data(void *args) DECLSPEC_HIDDEN; +extern NTSTATUS wg_transform_peek_data(void *args) DECLSPEC_HIDDEN; extern NTSTATUS wg_transform_read_data(void *args) DECLSPEC_HIDDEN;
#endif /* __WINE_WINEGSTREAMER_UNIX_PRIVATE_H */ diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index 5911278530d..4a72ef4bd0e 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -23,6 +23,8 @@
#include <stdbool.h> #include <stdint.h> +#include <assert.h> + #include "windef.h" #include "winternl.h" #include "wtypes.h" @@ -114,6 +116,35 @@ struct wg_format } u; };
+static inline bool wg_format_compare(const struct wg_format *a, const struct wg_format *b) +{ + if (a->major_type != b->major_type) + return false; + + switch (a->major_type) + { + case WG_MAJOR_TYPE_MPEG1_AUDIO: + case WG_MAJOR_TYPE_WMA: + case WG_MAJOR_TYPE_H264: + case WG_MAJOR_TYPE_UNKNOWN: + return false; + + case WG_MAJOR_TYPE_AUDIO: + return a->u.audio.format == b->u.audio.format + && a->u.audio.channels == b->u.audio.channels + && a->u.audio.rate == b->u.audio.rate; + + case WG_MAJOR_TYPE_VIDEO: + /* Do not compare FPS. */ + return a->u.video.format == b->u.video.format + && a->u.video.width == b->u.video.width + && abs(a->u.video.height) == abs(b->u.video.height); + } + + assert(0); + return false; +} + enum wg_sample_flag { WG_SAMPLE_FLAG_INCOMPLETE = 1, @@ -253,11 +284,17 @@ struct wg_transform_push_data_params HRESULT result; };
+struct wg_transform_peek_data_params +{ + struct wg_transform *transform; + struct wg_format *format; + HRESULT result; +}; + struct wg_transform_read_data_params { struct wg_transform *transform; struct wg_sample *sample; - HRESULT result; };
enum unix_funcs @@ -290,6 +327,7 @@ enum unix_funcs unix_wg_transform_destroy,
unix_wg_transform_push_data, + unix_wg_transform_peek_data, unix_wg_transform_read_data, };
diff --git a/dlls/winegstreamer/wg_format.c b/dlls/winegstreamer/wg_format.c index 4cebeac9182..a1ef9fb327b 100644 --- a/dlls/winegstreamer/wg_format.c +++ b/dlls/winegstreamer/wg_format.c @@ -527,34 +527,3 @@ GstCaps *wg_format_to_caps(const struct wg_format *format) assert(0); return NULL; } - -bool wg_format_compare(const struct wg_format *a, const struct wg_format *b) -{ - if (a->major_type != b->major_type) - return false; - - switch (a->major_type) - { - case WG_MAJOR_TYPE_MPEG1_AUDIO: - case WG_MAJOR_TYPE_WMA: - case WG_MAJOR_TYPE_H264: - GST_FIXME("Format %u not implemented!", a->major_type); - /* fallthrough */ - case WG_MAJOR_TYPE_UNKNOWN: - return false; - - case WG_MAJOR_TYPE_AUDIO: - return a->u.audio.format == b->u.audio.format - && a->u.audio.channels == b->u.audio.channels - && a->u.audio.rate == b->u.audio.rate; - - case WG_MAJOR_TYPE_VIDEO: - /* Do not compare FPS. */ - return a->u.video.format == b->u.video.format - && a->u.video.width == b->u.video.width - && abs(a->u.video.height) == abs(b->u.video.height); - } - - assert(0); - return false; -} diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 7d55897aa0a..954fdd3dc3d 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -1627,5 +1627,6 @@ const unixlib_entry_t __wine_unix_call_funcs[] = X(wg_transform_destroy),
X(wg_transform_push_data), + X(wg_transform_peek_data), X(wg_transform_read_data), }; diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index f8211001be9..a4125a7d822 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -471,15 +471,13 @@ static NTSTATUS read_transform_output_data(GstBuffer *buffer, struct wg_sample * return STATUS_SUCCESS; }
-NTSTATUS wg_transform_read_data(void *args) +NTSTATUS wg_transform_peek_data(void *args) { - struct wg_transform_read_data_params *params = args; + struct wg_transform_peek_data_params *params = args; struct wg_transform *transform = params->transform; - struct wg_sample *sample = params->sample; + struct wg_format *format = params->format; GstBufferList *input = transform->input; - GstBuffer *output_buffer; GstFlowReturn ret; - NTSTATUS status;
if (!gst_buffer_list_length(transform->input)) GST_DEBUG("Not input buffer queued"); @@ -497,12 +495,31 @@ NTSTATUS wg_transform_read_data(void *args)
if (!transform->output_sample && !(transform->output_sample = gst_atomic_queue_pop(transform->output_queue))) { - sample->size = 0; params->result = MF_E_TRANSFORM_NEED_MORE_INPUT; - GST_INFO("Cannot read %u bytes, no output available", sample->max_size); + GST_INFO("No output sample available"); return STATUS_SUCCESS; }
+ if (format) + wg_format_from_caps(format, gst_sample_get_caps(transform->output_sample)); + + params->result = S_OK; + return STATUS_SUCCESS; +} + +NTSTATUS wg_transform_read_data(void *args) +{ + struct wg_transform_read_data_params *params = args; + struct wg_transform *transform = params->transform; + struct wg_sample *sample = params->sample; + GstBuffer *output_buffer; + NTSTATUS status; + + if (!transform->output_sample) + { + GST_ERROR("No output sample available"); + return STATUS_UNSUCCESSFUL; + } output_buffer = gst_sample_get_buffer(transform->output_sample);
if ((status = read_transform_output_data(output_buffer, sample))) @@ -514,6 +531,5 @@ NTSTATUS wg_transform_read_data(void *args) transform->output_sample = NULL; }
- params->result = S_OK; return STATUS_SUCCESS; } diff --git a/dlls/winegstreamer/wma_decoder.c b/dlls/winegstreamer/wma_decoder.c index 71369add244..c7466b74471 100644 --- a/dlls/winegstreamer/wma_decoder.c +++ b/dlls/winegstreamer/wma_decoder.c @@ -580,10 +580,17 @@ static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, wg_sample->size = 0; if (wg_sample->max_size < info.cbSize) hr = MF_E_BUFFERTOOSMALL; - else if (SUCCEEDED(hr = wg_transform_read_data(decoder->wg_transform, wg_sample))) + else if (SUCCEEDED(hr = wg_transform_peek_data(decoder->wg_transform, NULL))) { - if (wg_sample->flags & WG_SAMPLE_FLAG_INCOMPLETE) + if (!wg_transform_read_data(decoder->wg_transform, wg_sample)) + { + ERR("Failed to read transform data\n"); + hr = E_FAIL; + } + else if (wg_sample->flags & WG_SAMPLE_FLAG_INCOMPLETE) + { samples[0].dwStatus |= MFT_OUTPUT_DATA_BUFFER_INCOMPLETE; + } }
mf_destroy_wg_sample(wg_sample);
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=115294
Your paranoid android.
=== debian11 (build log) ===
0104:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0104:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0104:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0104:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0108:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0108:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0104:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0104:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0108:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0108:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0104:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0104:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins
=== debian11 (build log) ===
0110:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0110:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins
From: Rémi Bernon rbernon@codeweavers.com
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=45988 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47084 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=49715 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52183 Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/mf/tests/mf.c | 17 +++++------ dlls/winegstreamer/h264_decoder.c | 51 ++++++++++++++++--------------- 2 files changed, 34 insertions(+), 34 deletions(-)
diff --git a/dlls/mf/tests/mf.c b/dlls/mf/tests/mf.c index 419faa61e2b..3fe819ba23d 100644 --- a/dlls/mf/tests/mf.c +++ b/dlls/mf/tests/mf.c @@ -6841,7 +6841,7 @@ static void test_h264_decoder(void) ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_NV12), ATTR_RATIO(MF_MT_PIXEL_ASPECT_RATIO, 1, 1), - ATTR_RATIO(MF_MT_FRAME_RATE, 60000, 1000, .todo_value = TRUE), + ATTR_RATIO(MF_MT_FRAME_RATE, 60000, 1000), ATTR_RATIO(MF_MT_FRAME_SIZE, actual_width, actual_height, .todo_value = TRUE), ATTR_UINT32(MF_MT_SAMPLE_SIZE, actual_width * actual_height * 3 / 2, .todo_value = TRUE), ATTR_UINT32(MF_MT_DEFAULT_STRIDE, actual_width, .todo_value = TRUE), @@ -6855,7 +6855,7 @@ static void test_h264_decoder(void) ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_YV12), ATTR_RATIO(MF_MT_PIXEL_ASPECT_RATIO, 1, 1), - ATTR_RATIO(MF_MT_FRAME_RATE, 60000, 1000, .todo_value = TRUE), + ATTR_RATIO(MF_MT_FRAME_RATE, 60000, 1000), ATTR_RATIO(MF_MT_FRAME_SIZE, actual_width, actual_height, .todo_value = TRUE), ATTR_UINT32(MF_MT_SAMPLE_SIZE, actual_width * actual_height * 3 / 2, .todo_value = TRUE), ATTR_UINT32(MF_MT_DEFAULT_STRIDE, actual_width, .todo_value = TRUE), @@ -6869,7 +6869,7 @@ static void test_h264_decoder(void) ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_IYUV), ATTR_RATIO(MF_MT_PIXEL_ASPECT_RATIO, 1, 1), - ATTR_RATIO(MF_MT_FRAME_RATE, 60000, 1000, .todo_value = TRUE), + ATTR_RATIO(MF_MT_FRAME_RATE, 60000, 1000), ATTR_RATIO(MF_MT_FRAME_SIZE, actual_width, actual_height, .todo_value = TRUE), ATTR_UINT32(MF_MT_SAMPLE_SIZE, actual_width * actual_height * 3 / 2, .todo_value = TRUE), ATTR_UINT32(MF_MT_DEFAULT_STRIDE, actual_width, .todo_value = TRUE), @@ -6883,7 +6883,7 @@ static void test_h264_decoder(void) ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_I420), ATTR_RATIO(MF_MT_PIXEL_ASPECT_RATIO, 1, 1), - ATTR_RATIO(MF_MT_FRAME_RATE, 60000, 1000, .todo_value = TRUE), + ATTR_RATIO(MF_MT_FRAME_RATE, 60000, 1000), ATTR_RATIO(MF_MT_FRAME_SIZE, actual_width, actual_height, .todo_value = TRUE), ATTR_UINT32(MF_MT_SAMPLE_SIZE, actual_width * actual_height * 3 / 2, .todo_value = TRUE), ATTR_UINT32(MF_MT_DEFAULT_STRIDE, actual_width, .todo_value = TRUE), @@ -6897,7 +6897,7 @@ static void test_h264_decoder(void) ATTR_GUID(MF_MT_MAJOR_TYPE, MFMediaType_Video), ATTR_GUID(MF_MT_SUBTYPE, MFVideoFormat_YUY2), ATTR_RATIO(MF_MT_PIXEL_ASPECT_RATIO, 1, 1), - ATTR_RATIO(MF_MT_FRAME_RATE, 60000, 1000, .todo_value = TRUE), + ATTR_RATIO(MF_MT_FRAME_RATE, 60000, 1000), ATTR_RATIO(MF_MT_FRAME_SIZE, actual_width, actual_height, .todo_value = TRUE), ATTR_UINT32(MF_MT_SAMPLE_SIZE, actual_width * actual_height * 2, .todo_value = TRUE), ATTR_UINT32(MF_MT_DEFAULT_STRIDE, actual_width * 2, .todo_value = TRUE), @@ -7206,7 +7206,6 @@ static void test_h264_decoder(void) hr = IMFTransform_GetOutputStreamInfo(transform, 0, &output_info); ok(hr == S_OK, "GetOutputStreamInfo returned %#lx\n", hr); ok(output_info.dwFlags == flags, "got dwFlags %#lx\n", output_info.dwFlags); - todo_wine ok(output_info.cbSize == actual_width * actual_height * 2, "got cbSize %#lx\n", output_info.cbSize); ok(output_info.cbAlignment == 0, "got cbAlignment %#lx\n", output_info.cbAlignment);
@@ -7248,14 +7247,12 @@ static void test_h264_decoder(void) memset(&output, 0, sizeof(output)); output.pSample = create_sample(NULL, actual_width * actual_height * 2); hr = IMFTransform_ProcessOutput(transform, 0, 1, &output, &status); - todo_wine ok(hr == S_OK, "ProcessOutput returned %#lx\n", hr); ok(output.dwStreamID == 0, "got dwStreamID %lu\n", output.dwStreamID); ok(!!output.pSample, "got pSample %p\n", output.pSample); ok(output.dwStatus == 0, "got dwStatus %#lx\n", output.dwStatus); ok(!output.pEvents, "got pEvents %p\n", output.pEvents); ok(status == 0, "got status %#lx\n", status); - if (hr != S_OK) goto skip_nv12_tests;
hr = IMFSample_GetUINT32(sample, &MFSampleExtension_CleanPoint, &value); ok(hr == MF_E_ATTRIBUTENOTFOUND, "GetUINT32 MFSampleExtension_CleanPoint returned %#lx\n", hr); @@ -7272,9 +7269,7 @@ static void test_h264_decoder(void)
time = 0xdeadbeef; hr = IMFSample_GetSampleTime(output.pSample, &time); - todo_wine ok(hr == S_OK, "GetSampleTime returned %#lx\n", hr); - todo_wine ok(time == 0, "got time %I64d\n", time);
/* doesn't matter what frame rate we've selected, duration is defined by the stream */ @@ -7289,7 +7284,9 @@ static void test_h264_decoder(void) ok(hr == S_OK, "ConvertToContiguousBuffer returned %#lx\n", hr); hr = IMFMediaBuffer_Lock(media_buffer, &data, NULL, &length); ok(hr == S_OK, "Lock returned %#lx\n", hr); + todo_wine ok(length == nv12_frame_len, "got length %lu\n", length); + if (length != nv12_frame_len) goto skip_nv12_tests;
for (i = 0; i < actual_aperture.Area.cy; ++i) { diff --git a/dlls/winegstreamer/h264_decoder.c b/dlls/winegstreamer/h264_decoder.c index db4c74d561e..739bd2bb9c4 100644 --- a/dlls/winegstreamer/h264_decoder.c +++ b/dlls/winegstreamer/h264_decoder.c @@ -90,8 +90,10 @@ static HRESULT try_create_wg_transform(struct h264_decoder *decoder) return S_OK; }
-static HRESULT fill_output_media_type(IMFMediaType *media_type, IMFMediaType *default_type) +static HRESULT fill_output_media_type(struct h264_decoder *decoder, IMFMediaType *media_type) { + IMFMediaType *default_type = decoder->output_type; + struct wg_format *wg_format = &decoder->wg_format; UINT32 value, width, height; UINT64 ratio; GUID subtype; @@ -102,8 +104,7 @@ static HRESULT fill_output_media_type(IMFMediaType *media_type, IMFMediaType *de
if (FAILED(hr = IMFMediaType_GetUINT64(media_type, &MF_MT_FRAME_SIZE, &ratio))) { - if (!default_type || FAILED(hr = IMFMediaType_GetUINT64(default_type, &MF_MT_FRAME_SIZE, &ratio))) - ratio = (UINT64)1920 << 32 | 1080; + ratio = (UINT64)wg_format->u.video.width << 32 | wg_format->u.video.height; if (FAILED(hr = IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_SIZE, ratio))) return hr; } @@ -112,24 +113,21 @@ static HRESULT fill_output_media_type(IMFMediaType *media_type, IMFMediaType *de
if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_FRAME_RATE, NULL))) { - if (!default_type || FAILED(hr = IMFMediaType_GetUINT64(default_type, &MF_MT_FRAME_RATE, &ratio))) - ratio = (UINT64)30000 << 32 | 1001; + ratio = (UINT64)wg_format->u.video.fps_n << 32 | wg_format->u.video.fps_d; if (FAILED(hr = IMFMediaType_SetUINT64(media_type, &MF_MT_FRAME_RATE, ratio))) return hr; }
if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_PIXEL_ASPECT_RATIO, NULL))) { - if (!default_type || FAILED(hr = IMFMediaType_GetUINT64(default_type, &MF_MT_PIXEL_ASPECT_RATIO, &ratio))) - ratio = (UINT64)1 << 32 | 1; + ratio = (UINT64)1 << 32 | 1; /* FIXME: read it from format */ if (FAILED(hr = IMFMediaType_SetUINT64(media_type, &MF_MT_PIXEL_ASPECT_RATIO, ratio))) return hr; }
if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_SAMPLE_SIZE, NULL))) { - if ((!default_type || FAILED(hr = IMFMediaType_GetUINT32(default_type, &MF_MT_SAMPLE_SIZE, &value))) && - FAILED(hr = MFCalculateImageSize(&subtype, width, height, &value))) + if (FAILED(hr = MFCalculateImageSize(&subtype, width, height, &value))) return hr; if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_SAMPLE_SIZE, value))) return hr; @@ -137,8 +135,7 @@ static HRESULT fill_output_media_type(IMFMediaType *media_type, IMFMediaType *de
if (FAILED(hr = IMFMediaType_GetItem(media_type, &MF_MT_DEFAULT_STRIDE, NULL))) { - if ((!default_type || FAILED(hr = IMFMediaType_GetUINT32(default_type, &MF_MT_DEFAULT_STRIDE, &value))) && - FAILED(hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, (LONG *)&value))) + if (FAILED(hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, (LONG *)&value))) return hr; if (FAILED(hr = IMFMediaType_SetUINT32(media_type, &MF_MT_DEFAULT_STRIDE, value))) return hr; @@ -273,23 +270,15 @@ static HRESULT WINAPI transform_GetInputStreamInfo(IMFTransform *iface, DWORD id static HRESULT WINAPI transform_GetOutputStreamInfo(IMFTransform *iface, DWORD id, MFT_OUTPUT_STREAM_INFO *info) { struct h264_decoder *decoder = impl_from_IMFTransform(iface); - UINT32 sample_size; - UINT64 frame_size; + UINT32 actual_width, actual_height;
TRACE("iface %p, id %#lx, info %p.\n", iface, id, info);
- if (!decoder->output_type) - sample_size = 1920 * 1088 * 2; - else if (FAILED(IMFMediaType_GetUINT32(decoder->output_type, &MF_MT_SAMPLE_SIZE, &sample_size))) - { - if (FAILED(IMFMediaType_GetUINT64(decoder->output_type, &MF_MT_FRAME_SIZE, &frame_size))) - sample_size = 1920 * 1088 * 2; - else - sample_size = (frame_size >> 32) * (UINT32)frame_size * 2; - } + actual_width = (decoder->wg_format.u.video.width + 15) & ~15; + actual_height = (decoder->wg_format.u.video.height + 15) & ~15;
info->dwFlags = MFT_OUTPUT_STREAM_WHOLE_SAMPLES | MFT_OUTPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER | MFT_OUTPUT_STREAM_FIXED_SAMPLE_SIZE; - info->cbSize = sample_size; + info->cbSize = actual_width * actual_height * 2; info->cbAlignment = 0;
return S_OK; @@ -379,7 +368,7 @@ static HRESULT WINAPI transform_GetOutputAvailableType(IMFTransform *iface, DWOR if (FAILED(hr = IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, output_type))) goto done;
- hr = fill_output_media_type(media_type, NULL); + hr = fill_output_media_type(decoder, media_type);
done: if (SUCCEEDED(hr)) @@ -545,6 +534,7 @@ static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, MFT_OUTPUT_STREAM_INFO info; struct wg_sample *wg_sample; struct wg_format wg_format; + UINT64 frame_rate; HRESULT hr;
TRACE("iface %p, flags %#lx, count %lu, samples %p, status %p.\n", iface, flags, count, samples, status); @@ -572,6 +562,14 @@ static HRESULT WINAPI transform_ProcessOutput(IMFTransform *iface, DWORD flags, if (!wg_format_compare(&decoder->wg_format, &wg_format)) { decoder->wg_format = wg_format; + + /* keep the frame rate that was requested, GStreamer doesn't provide any */ + if (SUCCEEDED(IMFMediaType_GetUINT64(decoder->output_type, &MF_MT_FRAME_RATE, &frame_rate))) + { + decoder->wg_format.u.video.fps_n = frame_rate >> 32; + decoder->wg_format.u.video.fps_d = (UINT32)frame_rate; + } + samples[0].dwStatus |= MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; *status |= MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; hr = MF_E_TRANSFORM_STREAM_CHANGE; @@ -647,6 +645,11 @@ HRESULT h264_decoder_create(REFIID riid, void **ret)
decoder->IMFTransform_iface.lpVtbl = &transform_vtbl; decoder->refcount = 1; + decoder->wg_format.u.video.format = WG_VIDEO_FORMAT_UNKNOWN; + decoder->wg_format.u.video.width = 1920; + decoder->wg_format.u.video.height = 1080; + decoder->wg_format.u.video.fps_n = 30000; + decoder->wg_format.u.video.fps_d = 1001;
*ret = &decoder->IMFTransform_iface; TRACE("Created decoder %p\n", *ret);
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=115295
Your paranoid android.
=== debian11 (build log) ===
0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0104:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0104:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0104:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0104:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0104:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0104:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins 0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins
=== debian11 (build log) ===
0100:err:winediag:wma_decoder_create GStreamer doesn't support WMA decoding, please install appropriate plugins 0100:err:winediag:h264_decoder_create GStreamer doesn't support H.264 decoding, please install appropriate plugins
I implemented the separate peek entry point, even though I still don't think it makes nothing simpler, but I'd like to move forward with this.
On Tue May 24 00:37:52 2022 +0000, **** wrote:
Zebediah Figura replied on the mailing list:
On 5/17/22 13:47, Rémi Bernon (@rbernon) wrote: > Well it's actually pretty clear that it's not going to be possible to > implement it unless you move most of wg_transform_read_data logic out > of the unix side, to the PE-side, exposing additional entry points for > each of the steps it goes through (which is what you are doing there). > > I don't think it's a good idea, because: > > 1) The entry points can actually be reduced to push_data / read_data > operations, which intuitively maps to the MF transforms or the DMO > ProcessInput / ProcessOutput methods. But it doesn't map nicely to DirectShow, and it only works because ProcessOutput() is kind of badly designed. To be clear, I don't object in principle to having an internal API that closely matches a badly designed external one, but that's not quite what we have here. Some aspects of the Media Foundation API are implemented on the PE side, and the way in which they're split up feels awkward and less than fully intuitive to me. > > 2) It complicates the unixlib API by multiplying entry points, although > they are only going to work when called in a specific sequence. My proposal as given (and wg_parser as it exists, for that matter) is not great because get_buffer() kind of implies that it consumes a buffer, which it doesn't. But if you rename the first API to something like "wg_transform_get_output_format()" it becomes clearer. The point is that it doesn't actually affect the state and hence is well-defined at any point. > To > get zero-copy you'll have to add even more entry points, to provide > the output buffer beforehand, and release it after the processing. I'm confused about this, because this doesn't match my (somewhat dim) awareness of what would be necessary for zero-copy. So maybe it would help to hash that out now completely. My understanding is: * in some cases we can and should transform data in-place (perhaps because the PE side expects it, not that I'm aware of any such cases). In that case we should feed a writable GstBuffer into the pipeline, and if the output buffer was not the same as the input buffer, blit the latter into the former. * if we cannot transform data in-place (either because GStreamer doesn't support it or because it'd confuse the PE side, not that I'm aware of any such cases there either), we should try to let GStreamer elements fill buffers allocated by the PE side. In order to achieve this we should set up a GstBufferPool ahead of time containing a fixed number of buffers. (I think we can guarantee we won't run out?) If the output buffer comes from this pool, we can retrieve the corresponding input buffer (the original intent of struct wg_sample) and return that pointer to user space. If not, we should call gst_buffer_pool_acquire_buffer() ourselves and blit the output buffer into it. In both cases, though, it doesn't make sense to force a specific output buffer unless it's the same as the input buffer, which means there's no need for an extra API. Is there some extra consideration I'm missing here? The other consequence that falls out from this is that the act of pushing data will inherently cause data to be written; i.e. there is no concept of "peek" quite like pipes. On the other hand, we can still "peek" at the last output buffer (and, more importantly, its associated metadata) without actually dequeuing it (and hence without changing the wg_transform state at all), which is kind of what I was trying to propose in the first place. It's not obvious to me why this is problematic; am I missing something here as well? > > 3) This specific call sequence (although some steps such as format > change checks are not always useful, but will probably still be > needed every time) will have to be duplicated in each transform > when it can be factored in a single place. Sure. On the other hand, we already have (somewhat misleadingly named) helpers that do most of the sample post-processing already. Maybe I'm missing a reason that they couldn't be extended to cover the entirety of ProcessOutput()...?
On 5/17/22 13:47, Rémi Bernon (@rbernon) wrote:
Well it's actually pretty clear that it's not going to be possible to implement it unless you move most of wg_transform_read_data logic out of the unix side, to the PE-side, exposing additional entry points for each of the steps it goes through (which is what you are doing there).
I don't think it's a good idea, because:
- The entry points can actually be reduced to push_data / read_data operations, which intuitively maps to the MF transforms or the DMO ProcessInput / ProcessOutput methods.
But it doesn't map nicely to DirectShow, and it only works because ProcessOutput() is kind of badly designed.
To be clear, I don't object in principle to having an internal API that closely matches a badly designed external one, but that's not quite what we have here. Some aspects of the Media Foundation API are implemented on the PE side, and the way in which they're split up feels awkward and less than fully intuitive to me.
DMO have the same ProcessInput / ProcessOutput methods, and I expect they work the same way as transforms.
You say that it's badly designed but you don't say how. I don't think it's really that bad, it does the job and is well designed for user controlled buffer allocation.
- It complicates the unixlib API by multiplying entry points, although they are only going to work when called in a specific sequence.
My proposal as given (and wg_parser as it exists, for that matter) is not great because get_buffer() kind of implies that it consumes a buffer, which it doesn't. But if you rename the first API to something like "wg_transform_get_output_format()" it becomes clearer. The point is that it doesn't actually affect the state and hence is well-defined at any point.
Whatever that entry point would be named, it will affect the state because it will have to actually push input buffers that were queued. Otherwise you will never find any output or new format.
To get zero-copy you'll have to add even more entry points, to provide the output buffer beforehand, and release it after the processing.
I'm confused about this, because this doesn't match my (somewhat dim) awareness of what would be necessary for zero-copy. So maybe it would help to hash that out now completely.
My understanding is:
- in some cases we can and should transform data in-place (perhaps
because the PE side expects it, not that I'm aware of any such cases). In that case we should feed a writable GstBuffer into the pipeline, and if the output buffer was not the same as the input buffer, blit the latter into the former.
- if we cannot transform data in-place (either because GStreamer doesn't
support it or because it'd confuse the PE side, not that I'm aware of any such cases there either), we should try to let GStreamer elements fill buffers allocated by the PE side. In order to achieve this we should set up a GstBufferPool ahead of time containing a fixed number of buffers. (I think we can guarantee we won't run out?) If the output buffer comes from this pool, we can retrieve the corresponding input buffer (the original intent of struct wg_sample) and return that pointer to user space. If not, we should call gst_buffer_pool_acquire_buffer() ourselves and blit the output buffer into it.
In both cases, though, it doesn't make sense to force a specific output buffer unless it's the same as the input buffer, which means there's no need for an extra API. Is there some extra consideration I'm missing here?
The other consequence that falls out from this is that the act of pushing data will inherently cause data to be written; i.e. there is no concept of "peek" quite like pipes. On the other hand, we can still "peek" at the last output buffer (and, more importantly, its associated metadata) without actually dequeuing it (and hence without changing the wg_transform state at all), which is kind of what I was trying to propose in the first place. It's not obvious to me why this is problematic; am I missing something here as well?
There's no such thing as transforming in-place. Input buffers (as in provided by the app to ProcessInput) and output buffers (as in provided by the app to ProcessOutput) will never be the same. They just cannot in general simply because of a difference of size, but also because it's not how it's supposed to work: input buffers and kept referenced until they have been fully processed, and until one or more output buffers have been filled from it.
What zero-copy means however, is avoiding any unnecessary copy when passing data back and forth GStreamer and unix-side in general.
The only way this can be done, for ProcessOutput, like I described previously already, is by:
1) providing the application provided output buffer to GStreamer *before* processing any input, so that any future buffer allocation can use it to write the decoded data.
2) process the input buffers that were queued, checking for a format change event, or whether the output buffer has been used.
3) blit the output data to the output buffer if it wasn't picked up.
4) remove the output buffer from any GStreamer buffer thay may still be referencing it (This last step also possibly but hopefully doesn't involve copying the data back from the output buffer to GStreamer memory if the data isn't supposed to be discarded, this can happen in some case, at least when format change event is returned so we do not lose the data but not only).
On 5/24/22 01:07, Rémi Bernon (@rbernon) wrote:
To get zero-copy you'll have to add even more entry points, to provide the output buffer beforehand, and release it after the processing.
I'm confused about this, because this doesn't match my (somewhat dim) awareness of what would be necessary for zero-copy. So maybe it would help to hash that out now completely.
My understanding is:
- in some cases we can and should transform data in-place (perhaps
because the PE side expects it, not that I'm aware of any such cases). In that case we should feed a writable GstBuffer into the pipeline, and if the output buffer was not the same as the input buffer, blit the latter into the former.
- if we cannot transform data in-place (either because GStreamer doesn't
support it or because it'd confuse the PE side, not that I'm aware of any such cases there either), we should try to let GStreamer elements fill buffers allocated by the PE side. In order to achieve this we should set up a GstBufferPool ahead of time containing a fixed number of buffers. (I think we can guarantee we won't run out?) If the output buffer comes from this pool, we can retrieve the corresponding input buffer (the original intent of struct wg_sample) and return that pointer to user space. If not, we should call gst_buffer_pool_acquire_buffer() ourselves and blit the output buffer into it.
In both cases, though, it doesn't make sense to force a specific output buffer unless it's the same as the input buffer, which means there's no need for an extra API. Is there some extra consideration I'm missing here?
The other consequence that falls out from this is that the act of pushing data will inherently cause data to be written; i.e. there is no concept of "peek" quite like pipes. On the other hand, we can still "peek" at the last output buffer (and, more importantly, its associated metadata) without actually dequeuing it (and hence without changing the wg_transform state at all), which is kind of what I was trying to propose in the first place. It's not obvious to me why this is problematic; am I missing something here as well?
There's no such thing as transforming in-place. Input buffers (as in provided by the app to ProcessInput) and output buffers (as in provided by the app to ProcessOutput) will never be the same. They just cannot in general simply because of a difference of size, but also because it's not how it's supposed to work: input buffers and kept referenced until they have been fully processed, and until one or more output buffers have been filled from it.
I'm looking at MFT_INPUT_STREAM_PROCESSES_IN_PLACE. I haven't checked whether anything interesting exposes it, though; maybe nothing does?
(I don't think any of the GStreamer elements we care about are capable of in-place transform either, but theoretically some of them could be.)
What zero-copy means however, is avoiding any unnecessary copy when passing data back and forth GStreamer and unix-side in general.
The only way this can be done, for ProcessOutput, like I described previously already, is by:
providing the application provided output buffer to GStreamer *before* processing any input, so that any future buffer allocation can use it to write the decoded data.
process the input buffers that were queued, checking for a format change event, or whether the output buffer has been used.
blit the output data to the output buffer if it wasn't picked up.
remove the output buffer from any GStreamer buffer thay may still be referencing it (This last step also possibly but hopefully doesn't involve copying the data back from the output buffer to GStreamer memory if the data isn't supposed to be discarded, this can happen in some case, at least when format change event is returned so we do not lose the data but not only).
Okay, the thing I was missing was that in the case of Media Foundation transforms (or at least some of them), we can't actually choose the sample buffer, but rather it gets provided to us by the application on a per-sample basis. Same for DMOs I guess. (On the other hand, DirectShow can pool, and so can all three parser frontends. And Media Foundation transforms are capable of pooling, although neither of the ones we currently implement actually do.)
This is ugly to deal with. I guess you can provide a specific buffer by (ab)using GstBufferPool's acquire_buffer() method to block until ProcessOutput() is called and we have a buffer; I don't know if you had a better plan than that.
On the other hand, it's not clear to me how we're going to deal with MF_E_TRANSFORM_STREAM_CHANGE or MFT_OUTPUT_STATUS_SAMPLE_READY while maintaining zero-copy. What is your plan for those?
On Tue May 24 18:12:09 2022 +0000, **** wrote:
Zebediah Figura replied on the mailing list:
On 5/24/22 01:07, Rémi Bernon (@rbernon) wrote: > >>> To >>> get zero-copy you'll have to add even more entry points, to provide >>> the output buffer beforehand, and release it after the processing. >> >> I'm confused about this, because this doesn't match my (somewhat dim) >> awareness of what would be necessary for zero-copy. So maybe it would >> help to hash that out now completely. >> >> My understanding is: >> >> * in some cases we can and should transform data in-place (perhaps >> because the PE side expects it, not that I'm aware of any such cases). >> In that case we should feed a writable GstBuffer into the pipeline, and >> if the output buffer was not the same as the input buffer, blit the >> latter into the former. >> >> * if we cannot transform data in-place (either because GStreamer doesn't >> support it or because it'd confuse the PE side, not that I'm aware of >> any such cases there either), we should try to let GStreamer elements >> fill buffers allocated by the PE side. In order to achieve this we >> should set up a GstBufferPool ahead of time containing a fixed number of >> buffers. (I think we can guarantee we won't run out?) If the output >> buffer comes from this pool, we can retrieve the corresponding input >> buffer (the original intent of struct wg_sample) and return that pointer >> to user space. If not, we should call gst_buffer_pool_acquire_buffer() >> ourselves and blit the output buffer into it. >> >> In both cases, though, it doesn't make sense to force a specific output >> buffer unless it's the same as the input buffer, which means there's no >> need for an extra API. Is there some extra consideration I'm missing here? >> >> The other consequence that falls out from this is that the act of >> pushing data will inherently cause data to be written; i.e. there is no >> concept of "peek" quite like pipes. On the other hand, we can still >> "peek" at the last output buffer (and, more importantly, its associated >> metadata) without actually dequeuing it (and hence without changing the >> wg_transform state at all), which is kind of what I was trying to >> propose in the first place. It's not obvious to me why this is >> problematic; am I missing something here as well? > > There's no such thing as transforming in-place. Input buffers (as in > provided by the app to ProcessInput) and output buffers (as in provided > by the app to ProcessOutput) will never be the same. They just cannot > in general simply because of a difference of size, but also because it's > not how it's supposed to work: input buffers and kept referenced until > they have been fully processed, and until one or more output buffers > have been filled from it. I'm looking at MFT_INPUT_STREAM_PROCESSES_IN_PLACE. I haven't checked whether anything interesting exposes it, though; maybe nothing does? (I don't think any of the GStreamer elements we care about are capable of in-place transform either, but theoretically some of them could be.) > > What zero-copy means however, is avoiding any unnecessary copy when > passing data back and forth GStreamer and unix-side in general. > > The only way this can be done, for ProcessOutput, like I described > previously already, is by: > > 1) providing the application provided output buffer to GStreamer > *before* processing any input, so that any future buffer allocation > can use it to write the decoded data. > > 2) process the input buffers that were queued, checking for a format > change event, or whether the output buffer has been used. > > 3) blit the output data to the output buffer if it wasn't picked up. > > 4) remove the output buffer from any GStreamer buffer thay may still be > referencing it (This last step also possibly but hopefully doesn't > involve copying the data back from the output buffer to GStreamer > memory if the data isn't supposed to be discarded, this can happen > in some case, at least when format change event is returned so we > do not lose the data but not only). > Okay, the thing I was missing was that in the case of Media Foundation transforms (or at least some of them), we can't actually choose the sample buffer, but rather it gets provided to us by the application on a per-sample basis. Same for DMOs I guess. (On the other hand, DirectShow can pool, and so can all three parser frontends. And Media Foundation transforms are capable of pooling, although neither of the ones we currently implement actually do.) This is ugly to deal with. I guess you can provide a specific buffer by (ab)using GstBufferPool's acquire_buffer() method to block until ProcessOutput() is called and we have a buffer; I don't know if you had a better plan than that. On the other hand, it's not clear to me how we're going to deal with MF_E_TRANSFORM_STREAM_CHANGE or MFT_OUTPUT_STATUS_SAMPLE_READY while maintaining zero-copy. What is your plan for those?
I'm looking at MFT_INPUT_STREAM_PROCESSES_IN_PLACE. I haven't checked whether anything interesting exposes it, though; maybe nothing does?
(I don't think any of the GStreamer elements we care about are capable of in-place transform either, but theoretically some of them could be.)
As far as the tests go, no transform is currently exposing this (and we start to have quite a few in the tests).
There's also the MFT_OUTPUT_STREAM_(CAN_)PROVIDES_SAMPLES flags that could have been useful, but there too, it doesn't seem to be used by native transforms, and I think games aren't expecting to see it.
Thus the implementation has all been around the worst case, which is when output buffer is app provided and cannot be swapped behind the scenes.
This is ugly to deal with. I guess you can provide a specific buffer by (ab)using GstBufferPool's acquire_buffer() method to block until ProcessOutput() is called and we have a buffer; I don't know if you had a better plan than that.
A bit like this, but it's actually more tricky because some decoders aren't playing ball.
They are happy to allocate buffers from a downstream pool, but they won't release the buffers so easily. I understand that they keep the buffers for previous frame reference in their decoding process, and that needs to outlive the app provided output buffer.
Because they hold a ref on the buffer, it isn't writable and we cannot detach its memory either.
On the other hand, it's not clear to me how we're going to deal with MF_E_TRANSFORM_STREAM_CHANGE or MFT_OUTPUT_STATUS_SAMPLE_READY while maintaining zero-copy. What is your plan for those?
So my plan, which will also cover the case above, is to use instead a custom allocator (going to be generally useful imho if we want zero-copy in wg_parser too), which will allocate memory normally with an optional app provided memory overlay.
When allocation needs to be done without an app provided buffer, the allocator will work as the default allocator, allocate unix side memory, and return a pointer to it on map. This is for instance going to happen if more than one buffer gets decoded. In that case, on the next ProcessOutput call, the data will have to be copied.
When we have an app provided buffer, and in the good cases, it would instead overlayed the unix memory with it and return the app memory on map so the decoder writes directly to it. When the app buffer needs to be released, we remove the overlay and the unix memory will later be used if there's any re-use.
In the bad cases, when either the output buffer is still referenced by the decoder, or when we actually should keep the output sample pending, and when we have incorrectly overlayed app buffer, we will copy back the data to the unix memory before detaching the overlay. In any case we still get only one buffer copy, so not worst than the normal copy.
In practice the bad cases shouldn't happen too often: when decoder hold on the buffers, we cannot remove memory from them. This also means the pool will not discard the buffers on release, and after a few allocs the decoder will have enough buffers to use on them and the allocator will not receive any more allocation requests. We will have to copy the data anyway, but in the right direction this time.
In the good cases, releasing buffer memory will make sure to taint the buffers and the pool will free them on release. The allocator will receive a request every time a buffer is needed.
On 5/24/22 14:14, Rémi Bernon (@rbernon) wrote:
I'm looking at MFT_INPUT_STREAM_PROCESSES_IN_PLACE. I haven't checked whether anything interesting exposes it, though; maybe nothing does?
(I don't think any of the GStreamer elements we care about are capable of in-place transform either, but theoretically some of them could be.)
As far as the tests go, no transform is currently exposing this (and we start to have quite a few in the tests).
There's also the MFT_OUTPUT_STREAM_(CAN_)PROVIDES_SAMPLES flags that could have been useful, but there too, it doesn't seem to be used by native transforms, and I think games aren't expecting to see it.
Thus the implementation has all been around the worst case, which is when output buffer is app provided and cannot be swapped behind the scenes.
This is ugly to deal with. I guess you can provide a specific buffer by (ab)using GstBufferPool's acquire_buffer() method to block until ProcessOutput() is called and we have a buffer; I don't know if you had a better plan than that.
A bit like this, but it's actually more tricky because some decoders aren't playing ball.
They are happy to allocate buffers from a downstream pool, but they won't release the buffers so easily. I understand that they keep the buffers for previous frame reference in their decoding process, and that needs to outlive the app provided output buffer.
Because they hold a ref on the buffer, it isn't writable and we cannot detach its memory either.
On the other hand, it's not clear to me how we're going to deal with MF_E_TRANSFORM_STREAM_CHANGE or MFT_OUTPUT_STATUS_SAMPLE_READY while maintaining zero-copy. What is your plan for those?
So my plan, which will also cover the case above, is to use instead a custom allocator (going to be generally useful imho if we want zero-copy in wg_parser too), which will allocate memory normally with an optional app provided memory overlay.
When allocation needs to be done without an app provided buffer, the allocator will work as the default allocator, allocate unix side memory, and return a pointer to it on map. This is for instance going to happen if more than one buffer gets decoded. In that case, on the next ProcessOutput call, the data will have to be copied.
When we have an app provided buffer, and in the good cases, it would instead overlayed the unix memory with it and return the app memory on map so the decoder writes directly to it. When the app buffer needs to be released, we remove the overlay and the unix memory will later be used if there's any re-use.
In the bad cases, when either the output buffer is still referenced by the decoder, or when we actually should keep the output sample pending, and when we have incorrectly overlayed app buffer, we will copy back the data to the unix memory before detaching the overlay. In any case we still get only one buffer copy, so not worst than the normal copy.
In practice the bad cases shouldn't happen too often: when decoder hold on the buffers, we cannot remove memory from them. This also means the pool will not discard the buffers on release, and after a few allocs the decoder will have enough buffers to use on them and the allocator will not receive any more allocation requests. We will have to copy the data anyway, but in the right direction this time.
In the good cases, releasing buffer memory will make sure to taint the buffers and the pool will free them on release. The allocator will receive a request every time a buffer is needed.
Thank you for the context; the initial design decisions behind this patch make a lot more sense now.
Once you take it as a given that we have to explicitly specify the output buffer per sample even in the zero-copy case, it indeed doesn't seem plausible to try to peek without dequeuing. In that case I'm inclined to agree that the best solution is a monolithic wg_transform_read_data() that returns MF_E_FORMAT_CHANGED_OR_WHATEVER.
Based on IRC conversation I get the impression we can plausibly track it all on the Unix side after all, possibly without even needing an extra API to invalidate the format? That would feel the least awkward to me. We'd still return the new format from the Unix side as part of wg_transform_read_data(), but it'd be output-only.
(I'm starting to suspect we should reinstate the wg_stream_event union... I'll have to examine that possibility after this is upstream.)
Sorry for all the runaround (and the slowness of review). This is an ugly API mismatch we have to deal with and I have found it quite difficult to grasp all of the many considerations.