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
32 #include <gst/video/video.h>
33 #include <gst/audio/audio.h>
36 #define WIN32_NO_STATUS
40 #include "unix_private.h"
42 GST_DEBUG_CATEGORY_EXTERN(wine
);
43 #define GST_CAT_DEFAULT wine
45 #define GST_SAMPLE_FLAG_WG_CAPS_CHANGED (GST_MINI_OBJECT_FLAG_LAST << 0)
49 GstElement
*container
;
50 GstAllocator
*allocator
;
51 GstPad
*my_src
, *my_sink
;
52 GstPad
*their_sink
, *their_src
;
54 GstQuery
*drain_query
;
56 guint input_max_length
;
57 GstAtomicQueue
*input_queue
;
59 guint output_plane_align
;
60 struct wg_sample
*output_wg_sample
;
61 GstAtomicQueue
*output_queue
;
62 GstSample
*output_sample
;
63 bool output_caps_changed
;
67 static bool is_caps_video(GstCaps
*caps
)
69 const gchar
*media_type
;
71 if (!caps
|| !gst_caps_get_size(caps
))
74 media_type
= gst_structure_get_name(gst_caps_get_structure(caps
, 0));
75 return g_str_has_prefix(media_type
, "video/");
78 static void align_video_info_planes(gsize plane_align
, GstVideoInfo
*info
, GstVideoAlignment
*align
)
80 gst_video_alignment_reset(align
);
82 align
->padding_right
= ((plane_align
+ 1) - (info
->width
& plane_align
)) & plane_align
;
83 align
->padding_bottom
= ((plane_align
+ 1) - (info
->height
& plane_align
)) & plane_align
;
84 align
->stride_align
[0] = plane_align
;
85 align
->stride_align
[1] = plane_align
;
86 align
->stride_align
[2] = plane_align
;
87 align
->stride_align
[3] = plane_align
;
89 gst_video_info_align(info
, align
);
92 static GstFlowReturn
transform_sink_chain_cb(GstPad
*pad
, GstObject
*parent
, GstBuffer
*buffer
)
94 struct wg_transform
*transform
= gst_pad_get_element_private(pad
);
97 GST_LOG("transform %p, buffer %p.", transform
, buffer
);
99 if (!(sample
= gst_sample_new(buffer
, transform
->output_caps
, NULL
, NULL
)))
101 GST_ERROR("Failed to allocate transform %p output sample.", transform
);
102 gst_buffer_unref(buffer
);
103 return GST_FLOW_ERROR
;
106 if (transform
->output_caps_changed
)
107 GST_MINI_OBJECT_FLAG_SET(sample
, GST_SAMPLE_FLAG_WG_CAPS_CHANGED
);
108 transform
->output_caps_changed
= false;
110 gst_atomic_queue_push(transform
->output_queue
, sample
);
111 gst_buffer_unref(buffer
);
115 static gboolean
transform_sink_query_cb(GstPad
*pad
, GstObject
*parent
, GstQuery
*query
)
117 struct wg_transform
*transform
= gst_pad_get_element_private(pad
);
119 GST_LOG("transform %p, type \"%s\".", transform
, gst_query_type_get_name(query
->type
));
123 case GST_QUERY_ALLOCATION
:
125 gsize plane_align
= transform
->output_plane_align
;
126 GstStructure
*config
, *params
;
127 GstVideoAlignment align
;
133 gst_query_parse_allocation(query
, &caps
, &needs_pool
);
134 if (!is_caps_video(caps
) || !needs_pool
)
137 if (!gst_video_info_from_caps(&info
, caps
)
138 || !(pool
= gst_video_buffer_pool_new()))
141 align_video_info_planes(plane_align
, &info
, &align
);
143 if ((params
= gst_structure_new("video-meta",
144 "padding-top", G_TYPE_UINT
, align
.padding_top
,
145 "padding-bottom", G_TYPE_UINT
, align
.padding_bottom
,
146 "padding-left", G_TYPE_UINT
, align
.padding_left
,
147 "padding-right", G_TYPE_UINT
, align
.padding_right
,
149 gst_query_add_allocation_meta(query
, GST_VIDEO_META_API_TYPE
, params
);
151 if (!(config
= gst_buffer_pool_get_config(pool
)))
152 GST_ERROR("Failed to get pool %p config.", pool
);
155 gst_buffer_pool_config_add_option(config
, GST_BUFFER_POOL_OPTION_VIDEO_META
);
156 gst_buffer_pool_config_add_option(config
, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT
);
157 gst_buffer_pool_config_set_video_alignment(config
, &align
);
159 gst_buffer_pool_config_set_params(config
, caps
,
161 gst_buffer_pool_config_set_allocator(config
, transform
->allocator
, NULL
);
162 if (!gst_buffer_pool_set_config(pool
, config
))
163 GST_ERROR("Failed to set pool %p config.", pool
);
166 /* Prevent pool reconfiguration, we don't want another alignment. */
167 if (!gst_buffer_pool_set_active(pool
, true))
168 GST_ERROR("Pool %p failed to activate.", pool
);
170 gst_query_add_allocation_pool(query
, pool
, info
.size
, 0, 0);
171 gst_query_add_allocation_param(query
, transform
->allocator
, NULL
);
173 GST_INFO("Proposing pool %p, buffer size %#zx, allocator %p, for query %p.",
174 pool
, info
.size
, transform
->allocator
, query
);
176 g_object_unref(pool
);
182 GstCaps
*caps
, *filter
, *temp
;
185 gst_query_parse_caps(query
, &filter
);
186 caps
= gst_caps_ref(transform
->output_caps
);
190 temp
= gst_caps_intersect(caps
, filter
);
191 gst_caps_unref(caps
);
195 str
= gst_caps_to_string(caps
);
196 GST_INFO("Returning caps %s", str
);
199 gst_query_set_caps_result(query
, caps
);
200 gst_caps_unref(caps
);
205 GST_WARNING("Ignoring \"%s\" query.", gst_query_type_get_name(query
->type
));
209 return gst_pad_query_default(pad
, parent
, query
);
212 static gboolean
transform_sink_event_cb(GstPad
*pad
, GstObject
*parent
, GstEvent
*event
)
214 struct wg_transform
*transform
= gst_pad_get_element_private(pad
);
216 GST_LOG("transform %p, type \"%s\".", transform
, GST_EVENT_TYPE_NAME(event
));
224 gst_event_parse_caps(event
, &caps
);
226 transform
->output_caps_changed
= transform
->output_caps_changed
227 || !gst_caps_is_always_compatible(transform
->output_caps
, caps
);
229 gst_caps_unref(transform
->output_caps
);
230 transform
->output_caps
= gst_caps_ref(caps
);
234 GST_WARNING("Ignoring \"%s\" event.", GST_EVENT_TYPE_NAME(event
));
238 gst_event_unref(event
);
242 NTSTATUS
wg_transform_destroy(void *args
)
244 struct wg_transform
*transform
= args
;
248 while ((buffer
= gst_atomic_queue_pop(transform
->input_queue
)))
249 gst_buffer_unref(buffer
);
250 gst_atomic_queue_unref(transform
->input_queue
);
252 gst_element_set_state(transform
->container
, GST_STATE_NULL
);
254 if (transform
->output_sample
)
255 gst_sample_unref(transform
->output_sample
);
256 while ((sample
= gst_atomic_queue_pop(transform
->output_queue
)))
257 gst_sample_unref(sample
);
259 wg_allocator_destroy(transform
->allocator
);
260 g_object_unref(transform
->their_sink
);
261 g_object_unref(transform
->their_src
);
262 g_object_unref(transform
->container
);
263 g_object_unref(transform
->my_sink
);
264 g_object_unref(transform
->my_src
);
265 gst_query_unref(transform
->drain_query
);
266 gst_caps_unref(transform
->output_caps
);
267 gst_atomic_queue_unref(transform
->output_queue
);
270 return STATUS_SUCCESS
;
273 static GstElement
*transform_find_element(GstElementFactoryListType type
, GstCaps
*src_caps
, GstCaps
*sink_caps
)
275 GstElement
*element
= NULL
;
276 GList
*tmp
, *transforms
;
279 if (!(transforms
= gst_element_factory_list_get_elements(type
, GST_RANK_MARGINAL
)))
282 tmp
= gst_element_factory_list_filter(transforms
, src_caps
, GST_PAD_SINK
, FALSE
);
283 gst_plugin_feature_list_free(transforms
);
284 if (!(transforms
= tmp
))
287 tmp
= gst_element_factory_list_filter(transforms
, sink_caps
, GST_PAD_SRC
, FALSE
);
288 gst_plugin_feature_list_free(transforms
);
289 if (!(transforms
= tmp
))
292 transforms
= g_list_sort(transforms
, gst_plugin_feature_rank_compare_func
);
293 for (tmp
= transforms
; tmp
!= NULL
&& element
== NULL
; tmp
= tmp
->next
)
295 name
= gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(tmp
->data
));
296 if (!(element
= gst_element_factory_create(GST_ELEMENT_FACTORY(tmp
->data
), NULL
)))
297 GST_WARNING("Failed to create %s element.", name
);
299 gst_plugin_feature_list_free(transforms
);
304 GST_DEBUG("Created %s element %p.", name
, element
);
308 gchar
*src_str
= gst_caps_to_string(src_caps
), *sink_str
= gst_caps_to_string(sink_caps
);
309 GST_WARNING("Failed to create transform matching caps %s / %s.", src_str
, sink_str
);
317 static bool transform_append_element(struct wg_transform
*transform
, GstElement
*element
,
318 GstElement
**first
, GstElement
**last
)
320 gchar
*name
= gst_element_get_name(element
);
321 bool success
= false;
323 if (!gst_bin_add(GST_BIN(transform
->container
), element
) ||
324 (*last
&& !gst_element_link(*last
, element
)))
326 GST_ERROR("Failed to link %s element.", name
);
330 GST_DEBUG("Linked %s element %p.", name
, element
);
341 static struct wg_sample
*transform_request_sample(gsize size
, void *context
)
343 struct wg_transform
*transform
= context
;
345 GST_LOG("size %#zx, context %p", size
, transform
);
347 return InterlockedExchangePointer((void **)&transform
->output_wg_sample
, NULL
);
350 NTSTATUS
wg_transform_create(void *args
)
352 struct wg_transform_create_params
*params
= args
;
353 struct wg_format output_format
= *params
->output_format
;
354 struct wg_format input_format
= *params
->input_format
;
355 GstElement
*first
= NULL
, *last
= NULL
, *element
;
356 GstCaps
*raw_caps
= NULL
, *src_caps
= NULL
;
357 NTSTATUS status
= STATUS_UNSUCCESSFUL
;
358 GstPadTemplate
*template = NULL
;
359 struct wg_transform
*transform
;
360 const gchar
*media_type
;
363 if (!init_gstreamer())
364 return STATUS_UNSUCCESSFUL
;
366 if (!(transform
= calloc(1, sizeof(*transform
))))
367 return STATUS_NO_MEMORY
;
368 if (!(transform
->container
= gst_bin_new("wg_transform")))
370 if (!(transform
->input_queue
= gst_atomic_queue_new(8)))
372 if (!(transform
->output_queue
= gst_atomic_queue_new(8)))
374 if (!(transform
->drain_query
= gst_query_new_drain()))
376 if (!(transform
->allocator
= wg_allocator_create(transform_request_sample
, transform
)))
378 transform
->input_max_length
= 1;
379 transform
->output_plane_align
= 0;
381 if (!(src_caps
= wg_format_to_caps(&input_format
)))
383 if (!(template = gst_pad_template_new("src", GST_PAD_SRC
, GST_PAD_ALWAYS
, src_caps
)))
385 transform
->my_src
= gst_pad_new_from_template(template, "src");
386 g_object_unref(template);
387 if (!transform
->my_src
)
390 if (!(transform
->output_caps
= wg_format_to_caps(&output_format
)))
392 if (!(template = gst_pad_template_new("sink", GST_PAD_SINK
, GST_PAD_ALWAYS
, transform
->output_caps
)))
394 transform
->my_sink
= gst_pad_new_from_template(template, "sink");
395 g_object_unref(template);
396 if (!transform
->my_sink
)
399 gst_pad_set_element_private(transform
->my_sink
, transform
);
400 gst_pad_set_event_function(transform
->my_sink
, transform_sink_event_cb
);
401 gst_pad_set_query_function(transform
->my_sink
, transform_sink_query_cb
);
402 gst_pad_set_chain_function(transform
->my_sink
, transform_sink_chain_cb
);
404 /* Since we append conversion elements, we don't want to filter decoders
405 * based on the actual output caps now. Matching decoders with the
406 * raw output media type should be enough.
408 media_type
= gst_structure_get_name(gst_caps_get_structure(transform
->output_caps
, 0));
409 if (!(raw_caps
= gst_caps_new_empty_simple(media_type
)))
412 switch (input_format
.major_type
)
414 case WG_MAJOR_TYPE_H264
:
415 /* Call of Duty: Black Ops 3 doesn't care about the ProcessInput/ProcessOutput
416 * return values, it calls them in a specific order and expects the decoder
417 * transform to be able to queue its input buffers. We need to use a buffer list
418 * to match its expectations.
420 transform
->input_max_length
= 16;
421 transform
->output_plane_align
= 15;
422 if (!(element
= create_element("h264parse", "base"))
423 || !transform_append_element(transform
, element
, &first
, &last
))
426 case WG_MAJOR_TYPE_MPEG1_AUDIO
:
427 case WG_MAJOR_TYPE_WMA
:
428 if (!(element
= transform_find_element(GST_ELEMENT_FACTORY_TYPE_DECODER
, src_caps
, raw_caps
))
429 || !transform_append_element(transform
, element
, &first
, &last
))
431 gst_caps_unref(raw_caps
);
436 case WG_MAJOR_TYPE_AUDIO
:
437 case WG_MAJOR_TYPE_VIDEO
:
439 case WG_MAJOR_TYPE_UNKNOWN
:
440 GST_FIXME("Format %u not implemented!", input_format
.major_type
);
441 gst_caps_unref(raw_caps
);
445 gst_caps_unref(raw_caps
);
447 switch (output_format
.major_type
)
449 case WG_MAJOR_TYPE_AUDIO
:
450 /* The MF audio decoder transforms allow decoding to various formats
451 * as well as resampling the audio at the same time, whereas
452 * GStreamer decoder plugins usually only support decoding to a
453 * single format and at the original rate.
455 * The WMA decoder transform also has output samples interleaved on
456 * Windows, whereas GStreamer avdec_wmav2 output uses
457 * non-interleaved format.
459 if (!(element
= create_element("audioconvert", "base"))
460 || !transform_append_element(transform
, element
, &first
, &last
))
462 if (!(element
= create_element("audioresample", "base"))
463 || !transform_append_element(transform
, element
, &first
, &last
))
467 case WG_MAJOR_TYPE_VIDEO
:
468 if (!(element
= create_element("videoconvert", "base"))
469 || !transform_append_element(transform
, element
, &first
, &last
))
471 /* Let GStreamer choose a default number of threads. */
472 gst_util_set_object_arg(G_OBJECT(element
), "n-threads", "0");
475 case WG_MAJOR_TYPE_MPEG1_AUDIO
:
476 case WG_MAJOR_TYPE_H264
:
477 case WG_MAJOR_TYPE_WMA
:
478 case WG_MAJOR_TYPE_UNKNOWN
:
479 GST_FIXME("Format %u not implemented!", output_format
.major_type
);
483 if (!(transform
->their_sink
= gst_element_get_static_pad(first
, "sink")))
485 if (!(transform
->their_src
= gst_element_get_static_pad(last
, "src")))
487 if (gst_pad_link(transform
->my_src
, transform
->their_sink
) < 0)
489 if (gst_pad_link(transform
->their_src
, transform
->my_sink
) < 0)
491 if (!gst_pad_set_active(transform
->my_sink
, 1))
493 if (!gst_pad_set_active(transform
->my_src
, 1))
496 gst_element_set_state(transform
->container
, GST_STATE_PAUSED
);
497 if (!gst_element_get_state(transform
->container
, NULL
, NULL
, -1))
500 if (!(event
= gst_event_new_stream_start("stream"))
501 || !gst_pad_push_event(transform
->my_src
, event
))
503 if (!(event
= gst_event_new_caps(src_caps
))
504 || !gst_pad_push_event(transform
->my_src
, event
))
507 /* We need to use GST_FORMAT_TIME here because it's the only format
508 * some elements such avdec_wmav2 correctly support. */
509 gst_segment_init(&transform
->segment
, GST_FORMAT_TIME
);
510 transform
->segment
.start
= 0;
511 transform
->segment
.stop
= -1;
512 if (!(event
= gst_event_new_segment(&transform
->segment
))
513 || !gst_pad_push_event(transform
->my_src
, event
))
516 gst_caps_unref(src_caps
);
518 GST_INFO("Created winegstreamer transform %p.", transform
);
519 params
->transform
= transform
;
520 return STATUS_SUCCESS
;
523 if (transform
->their_sink
)
524 gst_object_unref(transform
->their_sink
);
525 if (transform
->their_src
)
526 gst_object_unref(transform
->their_src
);
527 if (transform
->my_sink
)
528 gst_object_unref(transform
->my_sink
);
529 if (transform
->output_caps
)
530 gst_caps_unref(transform
->output_caps
);
531 if (transform
->my_src
)
532 gst_object_unref(transform
->my_src
);
534 gst_caps_unref(src_caps
);
535 if (transform
->allocator
)
536 wg_allocator_destroy(transform
->allocator
);
537 if (transform
->drain_query
)
538 gst_query_unref(transform
->drain_query
);
539 if (transform
->output_queue
)
540 gst_atomic_queue_unref(transform
->output_queue
);
541 if (transform
->input_queue
)
542 gst_atomic_queue_unref(transform
->input_queue
);
543 if (transform
->container
)
545 gst_element_set_state(transform
->container
, GST_STATE_NULL
);
546 gst_object_unref(transform
->container
);
549 GST_ERROR("Failed to create winegstreamer transform.");
553 NTSTATUS
wg_transform_set_output_format(void *args
)
555 struct wg_transform_set_output_format_params
*params
= args
;
556 struct wg_transform
*transform
= params
->transform
;
557 const struct wg_format
*format
= params
->format
;
562 if (!(caps
= wg_format_to_caps(format
)))
564 GST_ERROR("Failed to convert format %p to caps.", format
);
565 return STATUS_UNSUCCESSFUL
;
568 if (gst_caps_is_always_compatible(transform
->output_caps
, caps
))
570 gst_caps_unref(caps
);
571 return STATUS_SUCCESS
;
574 if (!gst_pad_peer_query(transform
->my_src
, transform
->drain_query
))
576 GST_ERROR("Failed to drain transform %p.", transform
);
577 return STATUS_UNSUCCESSFUL
;
580 gst_caps_unref(transform
->output_caps
);
581 transform
->output_caps
= caps
;
583 if (!gst_pad_push_event(transform
->my_sink
, gst_event_new_reconfigure()))
585 GST_ERROR("Failed to reconfigure transform %p.", transform
);
586 return STATUS_UNSUCCESSFUL
;
589 str
= gst_caps_to_string(caps
);
590 GST_INFO("Configured new caps %s.", str
);
593 /* Ideally and to be fully compatible with native transform, the queued
594 * output buffers will need to be converted to the new output format and
597 if (transform
->output_sample
)
598 gst_sample_unref(transform
->output_sample
);
599 while ((sample
= gst_atomic_queue_pop(transform
->output_queue
)))
600 gst_sample_unref(sample
);
601 transform
->output_sample
= NULL
;
603 return STATUS_SUCCESS
;
606 static void wg_sample_free_notify(void *arg
)
608 struct wg_sample
*sample
= arg
;
609 GST_DEBUG("Releasing wg_sample %p", sample
);
610 InterlockedDecrement(&sample
->refcount
);
613 NTSTATUS
wg_transform_push_data(void *args
)
615 struct wg_transform_push_data_params
*params
= args
;
616 struct wg_transform
*transform
= params
->transform
;
617 struct wg_sample
*sample
= params
->sample
;
621 length
= gst_atomic_queue_length(transform
->input_queue
);
622 if (length
>= transform
->input_max_length
)
624 GST_INFO("Refusing %u bytes, %u buffers already queued", sample
->size
, length
);
625 params
->result
= MF_E_NOTACCEPTING
;
626 return STATUS_SUCCESS
;
629 if (!(buffer
= gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_READONLY
, sample
->data
, sample
->max_size
,
630 0, sample
->size
, sample
, wg_sample_free_notify
)))
632 GST_ERROR("Failed to allocate input buffer");
633 return STATUS_NO_MEMORY
;
637 InterlockedIncrement(&sample
->refcount
);
638 GST_INFO("Wrapped %u/%u bytes from sample %p to buffer %p", sample
->size
, sample
->max_size
, sample
, buffer
);
641 if (sample
->flags
& WG_SAMPLE_FLAG_HAS_PTS
)
642 GST_BUFFER_PTS(buffer
) = sample
->pts
* 100;
643 if (sample
->flags
& WG_SAMPLE_FLAG_HAS_DURATION
)
644 GST_BUFFER_DURATION(buffer
) = sample
->duration
* 100;
645 if (!(sample
->flags
& WG_SAMPLE_FLAG_SYNC_POINT
))
646 GST_BUFFER_FLAG_SET(buffer
, GST_BUFFER_FLAG_DELTA_UNIT
);
647 gst_atomic_queue_push(transform
->input_queue
, buffer
);
649 params
->result
= S_OK
;
650 return STATUS_SUCCESS
;
653 static bool copy_video_buffer(GstBuffer
*buffer
, GstCaps
*caps
, gsize plane_align
,
654 struct wg_sample
*sample
, gsize
*total_size
)
656 GstVideoFrame src_frame
, dst_frame
;
657 GstVideoInfo src_info
, dst_info
;
658 GstVideoAlignment align
;
659 GstBuffer
*dst_buffer
;
662 if (!gst_video_info_from_caps(&src_info
, caps
))
664 GST_ERROR("Failed to get video info from caps.");
669 align_video_info_planes(plane_align
, &dst_info
, &align
);
671 if (sample
->max_size
< dst_info
.size
)
673 GST_ERROR("Output buffer is too small.");
677 if (!(dst_buffer
= gst_buffer_new_wrapped_full(0, sample
->data
, sample
->max_size
,
678 0, sample
->max_size
, 0, NULL
)))
680 GST_ERROR("Failed to wrap wg_sample into GstBuffer");
683 gst_buffer_set_size(dst_buffer
, dst_info
.size
);
684 *total_size
= sample
->size
= dst_info
.size
;
686 if (!gst_video_frame_map(&src_frame
, &src_info
, buffer
, GST_MAP_READ
))
687 GST_ERROR("Failed to map source frame.");
690 if (!gst_video_frame_map(&dst_frame
, &dst_info
, dst_buffer
, GST_MAP_WRITE
))
691 GST_ERROR("Failed to map destination frame.");
694 if (!(ret
= gst_video_frame_copy(&dst_frame
, &src_frame
)))
695 GST_ERROR("Failed to copy video frame.");
696 gst_video_frame_unmap(&dst_frame
);
698 gst_video_frame_unmap(&src_frame
);
701 gst_buffer_unref(dst_buffer
);
705 static bool copy_buffer(GstBuffer
*buffer
, GstCaps
*caps
, struct wg_sample
*sample
,
710 if (!gst_buffer_map(buffer
, &info
, GST_MAP_READ
))
713 if (sample
->max_size
>= info
.size
)
714 sample
->size
= info
.size
;
717 sample
->flags
|= WG_SAMPLE_FLAG_INCOMPLETE
;
718 sample
->size
= sample
->max_size
;
721 memcpy(sample
->data
, info
.data
, sample
->size
);
722 gst_buffer_unmap(buffer
, &info
);
724 if (sample
->flags
& WG_SAMPLE_FLAG_INCOMPLETE
)
725 gst_buffer_resize(buffer
, sample
->size
, -1);
727 *total_size
= info
.size
;
731 static NTSTATUS
read_transform_output_data(GstBuffer
*buffer
, GstCaps
*caps
, gsize plane_align
,
732 struct wg_sample
*sample
)
734 bool ret
, needs_copy
;
738 if (!gst_buffer_map(buffer
, &info
, GST_MAP_READ
))
740 GST_ERROR("Failed to map buffer %p", buffer
);
742 return STATUS_UNSUCCESSFUL
;
744 needs_copy
= info
.data
!= sample
->data
;
745 gst_buffer_unmap(buffer
, &info
);
747 if ((ret
= !needs_copy
))
748 total_size
= sample
->size
= info
.size
;
749 else if (is_caps_video(caps
))
750 ret
= copy_video_buffer(buffer
, caps
, plane_align
, sample
, &total_size
);
752 ret
= copy_buffer(buffer
, caps
, sample
, &total_size
);
756 GST_ERROR("Failed to copy buffer %p", buffer
);
758 return STATUS_UNSUCCESSFUL
;
761 if (GST_BUFFER_PTS_IS_VALID(buffer
))
763 sample
->flags
|= WG_SAMPLE_FLAG_HAS_PTS
;
764 sample
->pts
= GST_BUFFER_PTS(buffer
) / 100;
766 if (GST_BUFFER_DURATION_IS_VALID(buffer
))
768 GstClockTime duration
= GST_BUFFER_DURATION(buffer
) / 100;
770 duration
= (duration
* sample
->size
) / total_size
;
771 GST_BUFFER_DURATION(buffer
) -= duration
* 100;
772 if (GST_BUFFER_PTS_IS_VALID(buffer
))
773 GST_BUFFER_PTS(buffer
) += duration
* 100;
775 sample
->flags
|= WG_SAMPLE_FLAG_HAS_DURATION
;
776 sample
->duration
= duration
;
778 if (!GST_BUFFER_FLAG_IS_SET(buffer
, GST_BUFFER_FLAG_DELTA_UNIT
))
779 sample
->flags
|= WG_SAMPLE_FLAG_SYNC_POINT
;
783 if (is_caps_video(caps
))
784 GST_WARNING("Copied %u bytes, sample %p, flags %#x", sample
->size
, sample
, sample
->flags
);
786 GST_INFO("Copied %u bytes, sample %p, flags %#x", sample
->size
, sample
, sample
->flags
);
788 else if (sample
->flags
& WG_SAMPLE_FLAG_INCOMPLETE
)
789 GST_ERROR("Partial read %u bytes, sample %p, flags %#x", sample
->size
, sample
, sample
->flags
);
791 GST_INFO("Read %u bytes, sample %p, flags %#x", sample
->size
, sample
, sample
->flags
);
793 return STATUS_SUCCESS
;
796 static bool get_transform_output(struct wg_transform
*transform
, struct wg_sample
*sample
)
798 GstFlowReturn ret
= GST_FLOW_OK
;
799 GstBuffer
*input_buffer
;
801 /* Provide the sample for transform_request_sample to pick it up */
802 InterlockedIncrement(&sample
->refcount
);
803 InterlockedExchangePointer((void **)&transform
->output_wg_sample
, sample
);
805 while (!(transform
->output_sample
= gst_atomic_queue_pop(transform
->output_queue
)))
807 if (!(input_buffer
= gst_atomic_queue_pop(transform
->input_queue
)))
810 if ((ret
= gst_pad_push(transform
->my_src
, input_buffer
)))
812 GST_ERROR("Failed to push transform input, error %d", ret
);
817 /* Remove the sample so transform_request_sample cannot use it */
818 if (InterlockedExchangePointer((void **)&transform
->output_wg_sample
, NULL
))
819 InterlockedDecrement(&sample
->refcount
);
821 return ret
== GST_FLOW_OK
;
824 NTSTATUS
wg_transform_read_data(void *args
)
826 struct wg_transform_read_data_params
*params
= args
;
827 struct wg_transform
*transform
= params
->transform
;
828 struct wg_sample
*sample
= params
->sample
;
829 struct wg_format
*format
= params
->format
;
830 GstBuffer
*output_buffer
;
831 GstCaps
*output_caps
;
835 if (!transform
->output_sample
&& !get_transform_output(transform
, sample
))
837 wg_allocator_release_sample(transform
->allocator
, sample
, false);
838 return STATUS_UNSUCCESSFUL
;
841 if (!transform
->output_sample
)
844 params
->result
= MF_E_TRANSFORM_NEED_MORE_INPUT
;
845 GST_INFO("Cannot read %u bytes, no output available", sample
->max_size
);
846 wg_allocator_release_sample(transform
->allocator
, sample
, false);
847 return STATUS_SUCCESS
;
850 output_buffer
= gst_sample_get_buffer(transform
->output_sample
);
851 output_caps
= gst_sample_get_caps(transform
->output_sample
);
853 if (GST_MINI_OBJECT_FLAG_IS_SET(transform
->output_sample
, GST_SAMPLE_FLAG_WG_CAPS_CHANGED
))
855 GST_MINI_OBJECT_FLAG_UNSET(transform
->output_sample
, GST_SAMPLE_FLAG_WG_CAPS_CHANGED
);
859 gsize plane_align
= transform
->output_plane_align
;
860 GstVideoAlignment align
;
863 wg_format_from_caps(format
, output_caps
);
865 if (format
->major_type
== WG_MAJOR_TYPE_VIDEO
866 && gst_video_info_from_caps(&info
, output_caps
))
868 align_video_info_planes(plane_align
, &info
, &align
);
870 GST_INFO("Returning video alignment left %u, top %u, right %u, bottom %u.", align
.padding_left
,
871 align
.padding_top
, align
.padding_right
, align
.padding_bottom
);
873 format
->u
.video
.padding
.left
= align
.padding_left
;
874 format
->u
.video
.width
+= format
->u
.video
.padding
.left
;
875 format
->u
.video
.padding
.right
= align
.padding_right
;
876 format
->u
.video
.width
+= format
->u
.video
.padding
.right
;
877 format
->u
.video
.padding
.top
= align
.padding_top
;
878 format
->u
.video
.height
+= format
->u
.video
.padding
.top
;
879 format
->u
.video
.padding
.bottom
= align
.padding_bottom
;
880 format
->u
.video
.height
+= format
->u
.video
.padding
.bottom
;
884 params
->result
= MF_E_TRANSFORM_STREAM_CHANGE
;
885 GST_INFO("Format changed detected, returning no output");
886 wg_allocator_release_sample(transform
->allocator
, sample
, false);
887 return STATUS_SUCCESS
;
890 if ((status
= read_transform_output_data(output_buffer
, output_caps
,
891 transform
->output_plane_align
, sample
)))
893 wg_allocator_release_sample(transform
->allocator
, sample
, false);
897 if (sample
->flags
& WG_SAMPLE_FLAG_INCOMPLETE
)
898 discard_data
= false;
901 /* Taint the buffer memory to make sure it cannot be reused by the buffer pool,
902 * for the pool to always requests new memory from the allocator, and so we can
903 * then always provide output sample memory to achieve zero-copy.
905 * However, some decoder keep a reference on the buffer they passed downstream,
906 * to re-use it later. In this case, it will not be possible to do zero-copy,
907 * and we should copy the data back to the buffer and leave it unchanged.
909 * Some other plugins make assumptions that the returned buffer will always have
910 * at least one memory attached, we cannot just remove it and need to replace the
913 if ((discard_data
= gst_buffer_is_writable(output_buffer
)))
914 gst_buffer_replace_all_memory(output_buffer
, gst_allocator_alloc(NULL
, 0, NULL
));
916 gst_sample_unref(transform
->output_sample
);
917 transform
->output_sample
= NULL
;
920 params
->result
= S_OK
;
921 wg_allocator_release_sample(transform
->allocator
, sample
, discard_data
);
922 return STATUS_SUCCESS
;