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 #define GST_SAMPLE_FLAG_WG_CAPS_CHANGED (GST_MINI_OBJECT_FLAG_LAST << 0)
46 GstElement
*container
;
47 GstAllocator
*allocator
;
48 GstPad
*my_src
, *my_sink
;
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
;
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
);
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
);
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
));
111 case GST_QUERY_ALLOCATION
:
113 gsize plane_align
= transform
->output_plane_align
;
114 GstStructure
*config
, *params
;
115 GstVideoAlignment align
;
121 gst_query_parse_allocation(query
, &caps
, &needs_pool
);
122 if (stream_type_from_caps(caps
) != GST_STREAM_TYPE_VIDEO
|| !needs_pool
)
125 if (!gst_video_info_from_caps(&info
, caps
)
126 || !(pool
= gst_video_buffer_pool_new()))
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
,
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
);
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
,
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
);
170 GstCaps
*caps
, *filter
, *temp
;
173 gst_query_parse_caps(query
, &filter
);
174 caps
= gst_caps_ref(transform
->output_caps
);
178 temp
= gst_caps_intersect(caps
, filter
);
179 gst_caps_unref(caps
);
183 str
= gst_caps_to_string(caps
);
184 GST_INFO("Returning caps %s", str
);
187 gst_query_set_caps_result(query
, caps
);
188 gst_caps_unref(caps
);
193 GST_WARNING("Ignoring \"%s\" query.", gst_query_type_get_name(query
->type
));
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
));
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
);
222 GST_WARNING("Ignoring \"%s\" event.", GST_EVENT_TYPE_NAME(event
));
226 gst_event_unref(event
);
230 NTSTATUS
wg_transform_destroy(void *args
)
232 struct wg_transform
*transform
= args
;
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
);
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
;
286 if (!(transform
= calloc(1, sizeof(*transform
))))
287 return STATUS_NO_MEMORY
;
288 if (!(transform
->container
= gst_bin_new("wg_transform")))
290 if (!(transform
->input_queue
= gst_atomic_queue_new(8)))
292 if (!(transform
->output_queue
= gst_atomic_queue_new(8)))
294 if (!(transform
->drain_query
= gst_query_new_drain()))
296 if (!(transform
->allocator
= wg_allocator_create(transform_request_sample
, transform
)))
298 transform
->input_max_length
= 1;
299 transform
->output_plane_align
= 0;
301 if (!(src_caps
= wg_format_to_caps(&input_format
)))
303 if (!(template = gst_pad_template_new("src", GST_PAD_SRC
, GST_PAD_ALWAYS
, src_caps
)))
305 transform
->my_src
= gst_pad_new_from_template(template, "src");
306 g_object_unref(template);
307 if (!transform
->my_src
)
310 if (!(transform
->output_caps
= wg_format_to_caps(&output_format
)))
312 if (!(template = gst_pad_template_new("sink", GST_PAD_SINK
, GST_PAD_ALWAYS
, transform
->output_caps
)))
314 transform
->my_sink
= gst_pad_new_from_template(template, "sink");
315 g_object_unref(template);
316 if (!transform
->my_sink
)
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
)))
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
))
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
);
360 case WG_MAJOR_TYPE_AUDIO
:
361 case WG_MAJOR_TYPE_VIDEO
:
363 case WG_MAJOR_TYPE_UNKNOWN
:
364 GST_FIXME("Format %u not implemented!", input_format
.major_type
);
365 gst_caps_unref(raw_caps
);
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
))
386 if (!(element
= create_element("audioresample", "base"))
387 || !append_element(transform
->container
, element
, &first
, &last
))
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
))
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
))
402 /* Let GStreamer choose a default number of threads. */
403 gst_util_set_object_arg(G_OBJECT(element
), "n-threads", "0");
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
);
417 if (!link_src_to_element(transform
->my_src
, first
))
419 if (!link_element_to_sink(last
, transform
->my_sink
))
421 if (!gst_pad_set_active(transform
->my_sink
, 1))
423 if (!gst_pad_set_active(transform
->my_src
, 1))
426 gst_element_set_state(transform
->container
, GST_STATE_PAUSED
);
427 if (!gst_element_get_state(transform
->container
, NULL
, NULL
, -1))
430 if (!(event
= gst_event_new_stream_start("stream"))
431 || !gst_pad_push_event(transform
->my_src
, event
))
433 if (!(event
= gst_event_new_caps(src_caps
))
434 || !gst_pad_push_event(transform
->my_src
, event
))
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
))
446 gst_caps_unref(src_caps
);
448 GST_INFO("Created winegstreamer transform %p.", transform
);
449 params
->transform
= transform
;
450 return STATUS_SUCCESS
;
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
);
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
);
475 GST_ERROR("Failed to create winegstreamer transform.");
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
;
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
)
512 if (transform
->input_is_flipped
!= wg_format_video_is_flipped(format
))
513 value
= "vertical-flip";
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
);
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
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
;
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
;
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
;
599 if (!gst_video_info_from_caps(&src_info
, caps
))
601 GST_ERROR("Failed to get video info from caps.");
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.");
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");
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.");
627 if (!gst_video_frame_map(&dst_frame
, &dst_info
, dst_buffer
, GST_MAP_WRITE
))
628 GST_ERROR("Failed to map destination frame.");
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
);
642 static bool copy_buffer(GstBuffer
*buffer
, GstCaps
*caps
, struct wg_sample
*sample
,
647 if (!gst_buffer_map(buffer
, &info
, GST_MAP_READ
))
650 if (sample
->max_size
>= info
.size
)
651 sample
->size
= info
.size
;
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
;
668 static NTSTATUS
read_transform_output_data(GstBuffer
*buffer
, GstCaps
*caps
, gsize plane_align
,
669 struct wg_sample
*sample
)
671 bool ret
, needs_copy
;
675 if (!gst_buffer_map(buffer
, &info
, GST_MAP_READ
))
677 GST_ERROR("Failed to map buffer %p", buffer
);
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
);
689 ret
= copy_buffer(buffer
, caps
, sample
, &total_size
);
693 GST_ERROR("Failed to copy buffer %p", buffer
);
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
;
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
);
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
);
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
)))
749 if ((ret
= gst_pad_push(transform
->my_src
, input_buffer
)))
751 GST_ERROR("Failed to push transform input, error %d", ret
);
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
;
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
)
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
);
798 gsize plane_align
= transform
->output_plane_align
;
799 GstVideoAlignment align
;
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);
836 if (sample
->flags
& WG_SAMPLE_FLAG_INCOMPLETE
)
837 discard_data
= false;
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
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
;