ntdll: Use a separate memory allocation for the kernel stack.
[wine.git] / dlls / winegstreamer / wg_transform.c
blob2f21e601e0b61ee85584265c152391a18f8c66f5
1 /*
2 * GStreamer transform backend
4 * Copyright 2022 RĂ©mi Bernon for CodeWeavers
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 #if 0
22 #pragma makedep unix
23 #endif
25 #include "config.h"
27 #include <assert.h>
28 #include <stdarg.h>
29 #include <stdio.h>
31 #include <gst/gst.h>
32 #include <gst/video/video.h>
33 #include <gst/audio/audio.h>
35 #include "ntstatus.h"
36 #define WIN32_NO_STATUS
37 #include "winternl.h"
38 #include "mferror.h"
40 #include "unix_private.h"
42 #define GST_SAMPLE_FLAG_WG_CAPS_CHANGED (GST_MINI_OBJECT_FLAG_LAST << 0)
44 struct wg_transform
46 GstElement *container;
47 GstAllocator *allocator;
48 GstPad *my_src, *my_sink;
49 GstSegment segment;
50 GstQuery *drain_query;
52 guint input_max_length;
53 GstAtomicQueue *input_queue;
55 bool input_is_flipped;
56 GstElement *video_flip;
58 guint output_plane_align;
59 struct wg_sample *output_wg_sample;
60 GstAtomicQueue *output_queue;
61 GstSample *output_sample;
62 bool output_caps_changed;
63 GstCaps *output_caps;
66 static void align_video_info_planes(gsize plane_align, GstVideoInfo *info, GstVideoAlignment *align)
68 gst_video_alignment_reset(align);
70 align->padding_right = ((plane_align + 1) - (info->width & plane_align)) & plane_align;
71 align->padding_bottom = ((plane_align + 1) - (info->height & plane_align)) & plane_align;
72 align->stride_align[0] = plane_align;
73 align->stride_align[1] = plane_align;
74 align->stride_align[2] = plane_align;
75 align->stride_align[3] = plane_align;
77 gst_video_info_align(info, align);
80 static GstFlowReturn transform_sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *buffer)
82 struct wg_transform *transform = gst_pad_get_element_private(pad);
83 GstSample *sample;
85 GST_LOG("transform %p, buffer %p.", transform, buffer);
87 if (!(sample = gst_sample_new(buffer, transform->output_caps, NULL, NULL)))
89 GST_ERROR("Failed to allocate transform %p output sample.", transform);
90 gst_buffer_unref(buffer);
91 return GST_FLOW_ERROR;
94 if (transform->output_caps_changed)
95 GST_MINI_OBJECT_FLAG_SET(sample, GST_SAMPLE_FLAG_WG_CAPS_CHANGED);
96 transform->output_caps_changed = false;
98 gst_atomic_queue_push(transform->output_queue, sample);
99 gst_buffer_unref(buffer);
100 return GST_FLOW_OK;
103 static gboolean transform_sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query)
105 struct wg_transform *transform = gst_pad_get_element_private(pad);
107 GST_LOG("transform %p, type \"%s\".", transform, gst_query_type_get_name(query->type));
109 switch (query->type)
111 case GST_QUERY_ALLOCATION:
113 gsize plane_align = transform->output_plane_align;
114 GstStructure *config, *params;
115 GstVideoAlignment align;
116 gboolean needs_pool;
117 GstBufferPool *pool;
118 GstVideoInfo info;
119 GstCaps *caps;
121 gst_query_parse_allocation(query, &caps, &needs_pool);
122 if (stream_type_from_caps(caps) != GST_STREAM_TYPE_VIDEO || !needs_pool)
123 break;
125 if (!gst_video_info_from_caps(&info, caps)
126 || !(pool = gst_video_buffer_pool_new()))
127 break;
129 align_video_info_planes(plane_align, &info, &align);
131 if ((params = gst_structure_new("video-meta",
132 "padding-top", G_TYPE_UINT, align.padding_top,
133 "padding-bottom", G_TYPE_UINT, align.padding_bottom,
134 "padding-left", G_TYPE_UINT, align.padding_left,
135 "padding-right", G_TYPE_UINT, align.padding_right,
136 NULL)))
137 gst_query_add_allocation_meta(query, GST_VIDEO_META_API_TYPE, params);
139 if (!(config = gst_buffer_pool_get_config(pool)))
140 GST_ERROR("Failed to get pool %p config.", pool);
141 else
143 gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_META);
144 gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
145 gst_buffer_pool_config_set_video_alignment(config, &align);
147 gst_buffer_pool_config_set_params(config, caps,
148 info.size, 0, 0);
149 gst_buffer_pool_config_set_allocator(config, transform->allocator, NULL);
150 if (!gst_buffer_pool_set_config(pool, config))
151 GST_ERROR("Failed to set pool %p config.", pool);
154 /* Prevent pool reconfiguration, we don't want another alignment. */
155 if (!gst_buffer_pool_set_active(pool, true))
156 GST_ERROR("Pool %p failed to activate.", pool);
158 gst_query_add_allocation_pool(query, pool, info.size, 0, 0);
159 gst_query_add_allocation_param(query, transform->allocator, NULL);
161 GST_INFO("Proposing pool %p, buffer size %#zx, allocator %p, for query %p.",
162 pool, info.size, transform->allocator, query);
164 g_object_unref(pool);
165 return true;
168 case GST_QUERY_CAPS:
170 GstCaps *caps, *filter, *temp;
171 gchar *str;
173 gst_query_parse_caps(query, &filter);
174 caps = gst_caps_ref(transform->output_caps);
176 if (filter)
178 temp = gst_caps_intersect(caps, filter);
179 gst_caps_unref(caps);
180 caps = temp;
183 str = gst_caps_to_string(caps);
184 GST_INFO("Returning caps %s", str);
185 g_free(str);
187 gst_query_set_caps_result(query, caps);
188 gst_caps_unref(caps);
189 return true;
192 default:
193 GST_WARNING("Ignoring \"%s\" query.", gst_query_type_get_name(query->type));
194 break;
197 return gst_pad_query_default(pad, parent, query);
200 static gboolean transform_sink_event_cb(GstPad *pad, GstObject *parent, GstEvent *event)
202 struct wg_transform *transform = gst_pad_get_element_private(pad);
204 GST_LOG("transform %p, type \"%s\".", transform, GST_EVENT_TYPE_NAME(event));
206 switch (event->type)
208 case GST_EVENT_CAPS:
210 GstCaps *caps;
212 gst_event_parse_caps(event, &caps);
214 transform->output_caps_changed = transform->output_caps_changed
215 || !gst_caps_is_always_compatible(transform->output_caps, caps);
217 gst_caps_unref(transform->output_caps);
218 transform->output_caps = gst_caps_ref(caps);
219 break;
221 default:
222 GST_WARNING("Ignoring \"%s\" event.", GST_EVENT_TYPE_NAME(event));
223 break;
226 gst_event_unref(event);
227 return TRUE;
230 NTSTATUS wg_transform_destroy(void *args)
232 struct wg_transform *transform = args;
233 GstSample *sample;
234 GstBuffer *buffer;
236 while ((buffer = gst_atomic_queue_pop(transform->input_queue)))
237 gst_buffer_unref(buffer);
238 gst_atomic_queue_unref(transform->input_queue);
240 gst_element_set_state(transform->container, GST_STATE_NULL);
242 if (transform->output_sample)
243 gst_sample_unref(transform->output_sample);
244 while ((sample = gst_atomic_queue_pop(transform->output_queue)))
245 gst_sample_unref(sample);
247 wg_allocator_destroy(transform->allocator);
248 g_object_unref(transform->container);
249 g_object_unref(transform->my_sink);
250 g_object_unref(transform->my_src);
251 gst_query_unref(transform->drain_query);
252 gst_caps_unref(transform->output_caps);
253 gst_atomic_queue_unref(transform->output_queue);
254 free(transform);
256 return STATUS_SUCCESS;
259 static struct wg_sample *transform_request_sample(gsize size, void *context)
261 struct wg_transform *transform = context;
263 GST_LOG("size %#zx, context %p", size, transform);
265 return InterlockedExchangePointer((void **)&transform->output_wg_sample, NULL);
268 static bool wg_format_video_is_flipped(const struct wg_format *format)
270 return format->major_type == WG_MAJOR_TYPE_VIDEO && (format->u.video.height < 0);
273 NTSTATUS wg_transform_create(void *args)
275 struct wg_transform_create_params *params = args;
276 struct wg_format output_format = *params->output_format;
277 struct wg_format input_format = *params->input_format;
278 GstElement *first = NULL, *last = NULL, *element;
279 GstCaps *raw_caps = NULL, *src_caps = NULL;
280 NTSTATUS status = STATUS_UNSUCCESSFUL;
281 GstPadTemplate *template = NULL;
282 struct wg_transform *transform;
283 const gchar *media_type;
284 GstEvent *event;
286 if (!(transform = calloc(1, sizeof(*transform))))
287 return STATUS_NO_MEMORY;
288 if (!(transform->container = gst_bin_new("wg_transform")))
289 goto out;
290 if (!(transform->input_queue = gst_atomic_queue_new(8)))
291 goto out;
292 if (!(transform->output_queue = gst_atomic_queue_new(8)))
293 goto out;
294 if (!(transform->drain_query = gst_query_new_drain()))
295 goto out;
296 if (!(transform->allocator = wg_allocator_create(transform_request_sample, transform)))
297 goto out;
298 transform->input_max_length = 1;
299 transform->output_plane_align = 0;
301 if (!(src_caps = wg_format_to_caps(&input_format)))
302 goto out;
303 if (!(template = gst_pad_template_new("src", GST_PAD_SRC, GST_PAD_ALWAYS, src_caps)))
304 goto out;
305 transform->my_src = gst_pad_new_from_template(template, "src");
306 g_object_unref(template);
307 if (!transform->my_src)
308 goto out;
310 if (!(transform->output_caps = wg_format_to_caps(&output_format)))
311 goto out;
312 if (!(template = gst_pad_template_new("sink", GST_PAD_SINK, GST_PAD_ALWAYS, transform->output_caps)))
313 goto out;
314 transform->my_sink = gst_pad_new_from_template(template, "sink");
315 g_object_unref(template);
316 if (!transform->my_sink)
317 goto out;
319 gst_pad_set_element_private(transform->my_sink, transform);
320 gst_pad_set_event_function(transform->my_sink, transform_sink_event_cb);
321 gst_pad_set_query_function(transform->my_sink, transform_sink_query_cb);
322 gst_pad_set_chain_function(transform->my_sink, transform_sink_chain_cb);
324 /* Since we append conversion elements, we don't want to filter decoders
325 * based on the actual output caps now. Matching decoders with the
326 * raw output media type should be enough.
328 media_type = gst_structure_get_name(gst_caps_get_structure(transform->output_caps, 0));
329 if (!(raw_caps = gst_caps_new_empty_simple(media_type)))
330 goto out;
332 switch (input_format.major_type)
334 case WG_MAJOR_TYPE_VIDEO_H264:
335 /* Call of Duty: Black Ops 3 doesn't care about the ProcessInput/ProcessOutput
336 * return values, it calls them in a specific order and expects the decoder
337 * transform to be able to queue its input buffers. We need to use a buffer list
338 * to match its expectations.
340 transform->input_max_length = 16;
341 transform->output_plane_align = 15;
342 if (!(element = create_element("h264parse", "base"))
343 || !append_element(transform->container, element, &first, &last))
344 goto out;
345 /* fallthrough */
346 case WG_MAJOR_TYPE_AUDIO_MPEG1:
347 case WG_MAJOR_TYPE_AUDIO_MPEG4:
348 case WG_MAJOR_TYPE_AUDIO_WMA:
349 case WG_MAJOR_TYPE_VIDEO_CINEPAK:
350 case WG_MAJOR_TYPE_VIDEO_INDEO:
351 case WG_MAJOR_TYPE_VIDEO_WMV:
352 if (!(element = find_element(GST_ELEMENT_FACTORY_TYPE_DECODER, src_caps, raw_caps))
353 || !append_element(transform->container, element, &first, &last))
355 gst_caps_unref(raw_caps);
356 goto out;
358 break;
360 case WG_MAJOR_TYPE_AUDIO:
361 case WG_MAJOR_TYPE_VIDEO:
362 break;
363 case WG_MAJOR_TYPE_UNKNOWN:
364 GST_FIXME("Format %u not implemented!", input_format.major_type);
365 gst_caps_unref(raw_caps);
366 goto out;
369 gst_caps_unref(raw_caps);
371 switch (output_format.major_type)
373 case WG_MAJOR_TYPE_AUDIO:
374 /* The MF audio decoder transforms allow decoding to various formats
375 * as well as resampling the audio at the same time, whereas
376 * GStreamer decoder plugins usually only support decoding to a
377 * single format and at the original rate.
379 * The WMA decoder transform also has output samples interleaved on
380 * Windows, whereas GStreamer avdec_wmav2 output uses
381 * non-interleaved format.
383 if (!(element = create_element("audioconvert", "base"))
384 || !append_element(transform->container, element, &first, &last))
385 goto out;
386 if (!(element = create_element("audioresample", "base"))
387 || !append_element(transform->container, element, &first, &last))
388 goto out;
389 break;
391 case WG_MAJOR_TYPE_VIDEO:
392 case WG_MAJOR_TYPE_VIDEO_WMV:
393 if (!(transform->video_flip = create_element("videoflip", "base"))
394 || !append_element(transform->container, transform->video_flip, &first, &last))
395 goto out;
396 transform->input_is_flipped = wg_format_video_is_flipped(&input_format);
397 if (transform->input_is_flipped != wg_format_video_is_flipped(&output_format))
398 gst_util_set_object_arg(G_OBJECT(transform->video_flip), "method", "vertical-flip");
399 if (!(element = create_element("videoconvert", "base"))
400 || !append_element(transform->container, element, &first, &last))
401 goto out;
402 /* Let GStreamer choose a default number of threads. */
403 gst_util_set_object_arg(G_OBJECT(element), "n-threads", "0");
404 break;
406 case WG_MAJOR_TYPE_AUDIO_MPEG1:
407 case WG_MAJOR_TYPE_AUDIO_MPEG4:
408 case WG_MAJOR_TYPE_AUDIO_WMA:
409 case WG_MAJOR_TYPE_VIDEO_CINEPAK:
410 case WG_MAJOR_TYPE_VIDEO_H264:
411 case WG_MAJOR_TYPE_UNKNOWN:
412 case WG_MAJOR_TYPE_VIDEO_INDEO:
413 GST_FIXME("Format %u not implemented!", output_format.major_type);
414 goto out;
417 if (!link_src_to_element(transform->my_src, first))
418 goto out;
419 if (!link_element_to_sink(last, transform->my_sink))
420 goto out;
421 if (!gst_pad_set_active(transform->my_sink, 1))
422 goto out;
423 if (!gst_pad_set_active(transform->my_src, 1))
424 goto out;
426 gst_element_set_state(transform->container, GST_STATE_PAUSED);
427 if (!gst_element_get_state(transform->container, NULL, NULL, -1))
428 goto out;
430 if (!(event = gst_event_new_stream_start("stream"))
431 || !gst_pad_push_event(transform->my_src, event))
432 goto out;
433 if (!(event = gst_event_new_caps(src_caps))
434 || !gst_pad_push_event(transform->my_src, event))
435 goto out;
437 /* We need to use GST_FORMAT_TIME here because it's the only format
438 * some elements such avdec_wmav2 correctly support. */
439 gst_segment_init(&transform->segment, GST_FORMAT_TIME);
440 transform->segment.start = 0;
441 transform->segment.stop = -1;
442 if (!(event = gst_event_new_segment(&transform->segment))
443 || !gst_pad_push_event(transform->my_src, event))
444 goto out;
446 gst_caps_unref(src_caps);
448 GST_INFO("Created winegstreamer transform %p.", transform);
449 params->transform = transform;
450 return STATUS_SUCCESS;
452 out:
453 if (transform->my_sink)
454 gst_object_unref(transform->my_sink);
455 if (transform->output_caps)
456 gst_caps_unref(transform->output_caps);
457 if (transform->my_src)
458 gst_object_unref(transform->my_src);
459 if (src_caps)
460 gst_caps_unref(src_caps);
461 if (transform->allocator)
462 wg_allocator_destroy(transform->allocator);
463 if (transform->drain_query)
464 gst_query_unref(transform->drain_query);
465 if (transform->output_queue)
466 gst_atomic_queue_unref(transform->output_queue);
467 if (transform->input_queue)
468 gst_atomic_queue_unref(transform->input_queue);
469 if (transform->container)
471 gst_element_set_state(transform->container, GST_STATE_NULL);
472 gst_object_unref(transform->container);
474 free(transform);
475 GST_ERROR("Failed to create winegstreamer transform.");
476 return status;
479 NTSTATUS wg_transform_set_output_format(void *args)
481 struct wg_transform_set_output_format_params *params = args;
482 struct wg_transform *transform = params->transform;
483 const struct wg_format *format = params->format;
484 GstSample *sample;
485 GstCaps *caps;
486 gchar *str;
488 if (!(caps = wg_format_to_caps(format)))
490 GST_ERROR("Failed to convert format %p to caps.", format);
491 return STATUS_UNSUCCESSFUL;
494 if (gst_caps_is_always_compatible(transform->output_caps, caps))
496 gst_caps_unref(caps);
497 return STATUS_SUCCESS;
500 if (!gst_pad_peer_query(transform->my_src, transform->drain_query))
502 GST_ERROR("Failed to drain transform %p.", transform);
503 return STATUS_UNSUCCESSFUL;
506 gst_caps_unref(transform->output_caps);
507 transform->output_caps = caps;
509 if (transform->video_flip)
511 const char *value;
512 if (transform->input_is_flipped != wg_format_video_is_flipped(format))
513 value = "vertical-flip";
514 else
515 value = "none";
516 gst_util_set_object_arg(G_OBJECT(transform->video_flip), "method", value);
518 if (!gst_pad_push_event(transform->my_sink, gst_event_new_reconfigure()))
520 GST_ERROR("Failed to reconfigure transform %p.", transform);
521 return STATUS_UNSUCCESSFUL;
524 str = gst_caps_to_string(caps);
525 GST_INFO("Configured new caps %s.", str);
526 g_free(str);
528 /* Ideally and to be fully compatible with native transform, the queued
529 * output buffers will need to be converted to the new output format and
530 * kept queued.
532 if (transform->output_sample)
533 gst_sample_unref(transform->output_sample);
534 while ((sample = gst_atomic_queue_pop(transform->output_queue)))
535 gst_sample_unref(sample);
536 transform->output_sample = NULL;
538 return STATUS_SUCCESS;
541 static void wg_sample_free_notify(void *arg)
543 struct wg_sample *sample = arg;
544 GST_DEBUG("Releasing wg_sample %p", sample);
545 InterlockedDecrement(&sample->refcount);
548 NTSTATUS wg_transform_push_data(void *args)
550 struct wg_transform_push_data_params *params = args;
551 struct wg_transform *transform = params->transform;
552 struct wg_sample *sample = params->sample;
553 GstBuffer *buffer;
554 guint length;
556 length = gst_atomic_queue_length(transform->input_queue);
557 if (length >= transform->input_max_length)
559 GST_INFO("Refusing %u bytes, %u buffers already queued", sample->size, length);
560 params->result = MF_E_NOTACCEPTING;
561 return STATUS_SUCCESS;
564 if (!(buffer = gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_READONLY, sample->data, sample->max_size,
565 0, sample->size, sample, wg_sample_free_notify)))
567 GST_ERROR("Failed to allocate input buffer");
568 return STATUS_NO_MEMORY;
570 else
572 InterlockedIncrement(&sample->refcount);
573 GST_INFO("Wrapped %u/%u bytes from sample %p to buffer %p", sample->size, sample->max_size, sample, buffer);
576 if (sample->flags & WG_SAMPLE_FLAG_HAS_PTS)
577 GST_BUFFER_PTS(buffer) = sample->pts * 100;
578 if (sample->flags & WG_SAMPLE_FLAG_HAS_DURATION)
579 GST_BUFFER_DURATION(buffer) = sample->duration * 100;
580 if (!(sample->flags & WG_SAMPLE_FLAG_SYNC_POINT))
581 GST_BUFFER_FLAG_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT);
582 if (sample->flags & WG_SAMPLE_FLAG_DISCONTINUITY)
583 GST_BUFFER_FLAG_SET(buffer, GST_BUFFER_FLAG_DISCONT);
584 gst_atomic_queue_push(transform->input_queue, buffer);
586 params->result = S_OK;
587 return STATUS_SUCCESS;
590 static bool copy_video_buffer(GstBuffer *buffer, GstCaps *caps, gsize plane_align,
591 struct wg_sample *sample, gsize *total_size)
593 GstVideoFrame src_frame, dst_frame;
594 GstVideoInfo src_info, dst_info;
595 GstVideoAlignment align;
596 GstBuffer *dst_buffer;
597 bool ret = false;
599 if (!gst_video_info_from_caps(&src_info, caps))
601 GST_ERROR("Failed to get video info from caps.");
602 return false;
605 dst_info = src_info;
606 align_video_info_planes(plane_align, &dst_info, &align);
608 if (sample->max_size < dst_info.size)
610 GST_ERROR("Output buffer is too small.");
611 return false;
614 if (!(dst_buffer = gst_buffer_new_wrapped_full(0, sample->data, sample->max_size,
615 0, sample->max_size, 0, NULL)))
617 GST_ERROR("Failed to wrap wg_sample into GstBuffer");
618 return false;
620 gst_buffer_set_size(dst_buffer, dst_info.size);
621 *total_size = sample->size = dst_info.size;
623 if (!gst_video_frame_map(&src_frame, &src_info, buffer, GST_MAP_READ))
624 GST_ERROR("Failed to map source frame.");
625 else
627 if (!gst_video_frame_map(&dst_frame, &dst_info, dst_buffer, GST_MAP_WRITE))
628 GST_ERROR("Failed to map destination frame.");
629 else
631 if (!(ret = gst_video_frame_copy(&dst_frame, &src_frame)))
632 GST_ERROR("Failed to copy video frame.");
633 gst_video_frame_unmap(&dst_frame);
635 gst_video_frame_unmap(&src_frame);
638 gst_buffer_unref(dst_buffer);
639 return ret;
642 static bool copy_buffer(GstBuffer *buffer, GstCaps *caps, struct wg_sample *sample,
643 gsize *total_size)
645 GstMapInfo info;
647 if (!gst_buffer_map(buffer, &info, GST_MAP_READ))
648 return false;
650 if (sample->max_size >= info.size)
651 sample->size = info.size;
652 else
654 sample->flags |= WG_SAMPLE_FLAG_INCOMPLETE;
655 sample->size = sample->max_size;
658 memcpy(sample->data, info.data, sample->size);
659 gst_buffer_unmap(buffer, &info);
661 if (sample->flags & WG_SAMPLE_FLAG_INCOMPLETE)
662 gst_buffer_resize(buffer, sample->size, -1);
664 *total_size = info.size;
665 return true;
668 static NTSTATUS read_transform_output_data(GstBuffer *buffer, GstCaps *caps, gsize plane_align,
669 struct wg_sample *sample)
671 bool ret, needs_copy;
672 gsize total_size;
673 GstMapInfo info;
675 if (!gst_buffer_map(buffer, &info, GST_MAP_READ))
677 GST_ERROR("Failed to map buffer %p", buffer);
678 sample->size = 0;
679 return STATUS_UNSUCCESSFUL;
681 needs_copy = info.data != sample->data;
682 gst_buffer_unmap(buffer, &info);
684 if ((ret = !needs_copy))
685 total_size = sample->size = info.size;
686 else if (stream_type_from_caps(caps) == GST_STREAM_TYPE_VIDEO)
687 ret = copy_video_buffer(buffer, caps, plane_align, sample, &total_size);
688 else
689 ret = copy_buffer(buffer, caps, sample, &total_size);
691 if (!ret)
693 GST_ERROR("Failed to copy buffer %p", buffer);
694 sample->size = 0;
695 return STATUS_UNSUCCESSFUL;
698 if (GST_BUFFER_PTS_IS_VALID(buffer))
700 sample->flags |= WG_SAMPLE_FLAG_HAS_PTS;
701 sample->pts = GST_BUFFER_PTS(buffer) / 100;
703 if (GST_BUFFER_DURATION_IS_VALID(buffer))
705 GstClockTime duration = GST_BUFFER_DURATION(buffer) / 100;
707 duration = (duration * sample->size) / total_size;
708 GST_BUFFER_DURATION(buffer) -= duration * 100;
709 if (GST_BUFFER_PTS_IS_VALID(buffer))
710 GST_BUFFER_PTS(buffer) += duration * 100;
712 sample->flags |= WG_SAMPLE_FLAG_HAS_DURATION;
713 sample->duration = duration;
715 if (!GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT))
716 sample->flags |= WG_SAMPLE_FLAG_SYNC_POINT;
717 if (GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DISCONT))
718 sample->flags |= WG_SAMPLE_FLAG_DISCONTINUITY;
720 if (needs_copy)
722 if (stream_type_from_caps(caps) == GST_STREAM_TYPE_VIDEO)
723 GST_WARNING("Copied %u bytes, sample %p, flags %#x", sample->size, sample, sample->flags);
724 else
725 GST_INFO("Copied %u bytes, sample %p, flags %#x", sample->size, sample, sample->flags);
727 else if (sample->flags & WG_SAMPLE_FLAG_INCOMPLETE)
728 GST_ERROR("Partial read %u bytes, sample %p, flags %#x", sample->size, sample, sample->flags);
729 else
730 GST_INFO("Read %u bytes, sample %p, flags %#x", sample->size, sample, sample->flags);
732 return STATUS_SUCCESS;
735 static bool get_transform_output(struct wg_transform *transform, struct wg_sample *sample)
737 GstFlowReturn ret = GST_FLOW_OK;
738 GstBuffer *input_buffer;
740 /* Provide the sample for transform_request_sample to pick it up */
741 InterlockedIncrement(&sample->refcount);
742 InterlockedExchangePointer((void **)&transform->output_wg_sample, sample);
744 while (!(transform->output_sample = gst_atomic_queue_pop(transform->output_queue)))
746 if (!(input_buffer = gst_atomic_queue_pop(transform->input_queue)))
747 break;
749 if ((ret = gst_pad_push(transform->my_src, input_buffer)))
751 GST_ERROR("Failed to push transform input, error %d", ret);
752 break;
756 /* Remove the sample so transform_request_sample cannot use it */
757 if (InterlockedExchangePointer((void **)&transform->output_wg_sample, NULL))
758 InterlockedDecrement(&sample->refcount);
760 return ret == GST_FLOW_OK;
763 NTSTATUS wg_transform_read_data(void *args)
765 struct wg_transform_read_data_params *params = args;
766 struct wg_transform *transform = params->transform;
767 struct wg_sample *sample = params->sample;
768 struct wg_format *format = params->format;
769 GstBuffer *output_buffer;
770 GstCaps *output_caps;
771 bool discard_data;
772 NTSTATUS status;
774 if (!transform->output_sample && !get_transform_output(transform, sample))
776 wg_allocator_release_sample(transform->allocator, sample, false);
777 return STATUS_UNSUCCESSFUL;
780 if (!transform->output_sample)
782 sample->size = 0;
783 params->result = MF_E_TRANSFORM_NEED_MORE_INPUT;
784 GST_INFO("Cannot read %u bytes, no output available", sample->max_size);
785 wg_allocator_release_sample(transform->allocator, sample, false);
786 return STATUS_SUCCESS;
789 output_buffer = gst_sample_get_buffer(transform->output_sample);
790 output_caps = gst_sample_get_caps(transform->output_sample);
792 if (GST_MINI_OBJECT_FLAG_IS_SET(transform->output_sample, GST_SAMPLE_FLAG_WG_CAPS_CHANGED))
794 GST_MINI_OBJECT_FLAG_UNSET(transform->output_sample, GST_SAMPLE_FLAG_WG_CAPS_CHANGED);
796 if (format)
798 gsize plane_align = transform->output_plane_align;
799 GstVideoAlignment align;
800 GstVideoInfo info;
802 wg_format_from_caps(format, output_caps);
804 if (format->major_type == WG_MAJOR_TYPE_VIDEO
805 && gst_video_info_from_caps(&info, output_caps))
807 align_video_info_planes(plane_align, &info, &align);
809 GST_INFO("Returning video alignment left %u, top %u, right %u, bottom %u.", align.padding_left,
810 align.padding_top, align.padding_right, align.padding_bottom);
812 format->u.video.padding.left = align.padding_left;
813 format->u.video.width += format->u.video.padding.left;
814 format->u.video.padding.right = align.padding_right;
815 format->u.video.width += format->u.video.padding.right;
816 format->u.video.padding.top = align.padding_top;
817 format->u.video.height += format->u.video.padding.top;
818 format->u.video.padding.bottom = align.padding_bottom;
819 format->u.video.height += format->u.video.padding.bottom;
823 params->result = MF_E_TRANSFORM_STREAM_CHANGE;
824 GST_INFO("Format changed detected, returning no output");
825 wg_allocator_release_sample(transform->allocator, sample, false);
826 return STATUS_SUCCESS;
829 if ((status = read_transform_output_data(output_buffer, output_caps,
830 transform->output_plane_align, sample)))
832 wg_allocator_release_sample(transform->allocator, sample, false);
833 return status;
836 if (sample->flags & WG_SAMPLE_FLAG_INCOMPLETE)
837 discard_data = false;
838 else
840 /* Taint the buffer memory to make sure it cannot be reused by the buffer pool,
841 * for the pool to always requests new memory from the allocator, and so we can
842 * then always provide output sample memory to achieve zero-copy.
844 * However, some decoder keep a reference on the buffer they passed downstream,
845 * to re-use it later. In this case, it will not be possible to do zero-copy,
846 * and we should copy the data back to the buffer and leave it unchanged.
848 * Some other plugins make assumptions that the returned buffer will always have
849 * at least one memory attached, we cannot just remove it and need to replace the
850 * memory instead.
852 if ((discard_data = gst_buffer_is_writable(output_buffer)))
853 gst_buffer_replace_all_memory(output_buffer, gst_allocator_alloc(NULL, 0, NULL));
855 gst_sample_unref(transform->output_sample);
856 transform->output_sample = NULL;
859 params->result = S_OK;
860 wg_allocator_release_sample(transform->allocator, sample, discard_data);
861 return STATUS_SUCCESS;
864 NTSTATUS wg_transform_get_status(void *args)
866 struct wg_transform_get_status_params *params = args;
867 struct wg_transform *transform = params->transform;
869 params->accepts_input = gst_atomic_queue_length(transform->input_queue) < transform->input_max_length;
870 return STATUS_SUCCESS;