1 /* GStreamer
2 * Copyright (C) <2019> Seungha Yang <seungha.yang@navercorp.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 /**
21 * SECTION:element-d3d11download
22 * @title: d3d11download
23 * @short_description: Downloads Direct3D11 texture memory into system memory
24 *
25 * Downloads Direct3D11 texture memory into system memory
26 *
27 * ## Example launch line
28 * ```
29 * gst-launch-1.0 filesrc location=test_h264.mp4 ! parsebin ! d3d11h264dec ! \
30 * d3d11convert ! d3d11download ! video/x-raw,width=640,height=480 ! mfh264enc ! \
31 * h264parse ! mp4mux ! filesink location=output.mp4
32 * ```
33 * This pipeline will resize decoded (by #d3d11h264dec) frames to 640x480
34 * resolution by using #d3d11convert. Then it will be copied into system memory
35 * by d3d11download. Finally downloaded frames will be encoded as a new
36 * H.264 stream via #mfh264enc and muxed via mp4mux
37 *
38 * Since: 1.18
39 *
40 */
41
42 #ifdef HAVE_CONFIG_H
43 # include <config.h>
44 #endif
45
46 #include "gstd3d11download.h"
47
48 GST_DEBUG_CATEGORY_STATIC (gst_d3d11_download_debug);
49 #define GST_CAT_DEFAULT gst_d3d11_download_debug
50
51 static GstStaticCaps sink_template_caps =
52 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
53 (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, GST_D3D11_ALL_FORMATS) "; "
54 GST_VIDEO_CAPS_MAKE_WITH_FEATURES
55 (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY ","
56 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION,
57 GST_D3D11_ALL_FORMATS) "; "
58 GST_VIDEO_CAPS_MAKE (GST_D3D11_ALL_FORMATS) "; "
59 GST_VIDEO_CAPS_MAKE_WITH_FEATURES
60 (GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY ","
61 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION,
62 GST_D3D11_ALL_FORMATS));
63
64 static GstStaticCaps src_template_caps =
65 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
66 (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, GST_D3D11_ALL_FORMATS) "; "
67 GST_VIDEO_CAPS_MAKE_WITH_FEATURES
68 (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY ","
69 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION,
70 GST_D3D11_ALL_FORMATS) "; "
71 GST_VIDEO_CAPS_MAKE (GST_D3D11_ALL_FORMATS) "; "
72 GST_VIDEO_CAPS_MAKE_WITH_FEATURES
73 (GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY ","
74 GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION,
75 GST_D3D11_ALL_FORMATS));
76
77 struct _GstD3D11Download
78 {
79 GstD3D11BaseFilter parent;
80
81 GstBuffer *staging_buffer;
82 };
83
84 #define gst_d3d11_download_parent_class parent_class
85 G_DEFINE_TYPE (GstD3D11Download, gst_d3d11_download,
86 GST_TYPE_D3D11_BASE_FILTER);
87
88 static void gst_d3d11_download_dispose (GObject * object);
89 static gboolean gst_d3d11_download_stop (GstBaseTransform * trans);
90 static gboolean gst_d3d11_download_sink_event (GstBaseTransform * trans,
91 GstEvent * event);
92 static GstCaps *gst_d3d11_download_transform_caps (GstBaseTransform * trans,
93 GstPadDirection direction, GstCaps * caps, GstCaps * filter);
94 static gboolean gst_d3d11_download_propose_allocation (GstBaseTransform * trans,
95 GstQuery * decide_query, GstQuery * query);
96 static gboolean gst_d3d11_download_decide_allocation (GstBaseTransform * trans,
97 GstQuery * query);
98 static GstFlowReturn gst_d3d11_download_transform (GstBaseTransform * trans,
99 GstBuffer * inbuf, GstBuffer * outbuf);
100 static gboolean gst_d3d11_download_set_info (GstD3D11BaseFilter * filter,
101 GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps,
102 GstVideoInfo * out_info);
103
104 static void
gst_d3d11_download_class_init(GstD3D11DownloadClass * klass)105 gst_d3d11_download_class_init (GstD3D11DownloadClass * klass)
106 {
107 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
108 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
109 GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
110 GstD3D11BaseFilterClass *bfilter_class = GST_D3D11_BASE_FILTER_CLASS (klass);
111 GstCaps *caps;
112
113 gobject_class->dispose = gst_d3d11_download_dispose;
114
115 caps = gst_d3d11_get_updated_template_caps (&sink_template_caps);
116 gst_element_class_add_pad_template (element_class,
117 gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps));
118 gst_caps_unref (caps);
119
120 caps = gst_d3d11_get_updated_template_caps (&src_template_caps);
121 gst_element_class_add_pad_template (element_class,
122 gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, caps));
123 gst_caps_unref (caps);
124
125 gst_element_class_set_static_metadata (element_class,
126 "Direct3D11 downloader", "Filter/Video",
127 "Downloads Direct3D11 texture memory into system memory",
128 "Seungha Yang <seungha.yang@navercorp.com>");
129
130 trans_class->passthrough_on_same_caps = TRUE;
131
132 trans_class->stop = GST_DEBUG_FUNCPTR (gst_d3d11_download_stop);
133 trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_d3d11_download_sink_event);
134 trans_class->transform_caps =
135 GST_DEBUG_FUNCPTR (gst_d3d11_download_transform_caps);
136 trans_class->propose_allocation =
137 GST_DEBUG_FUNCPTR (gst_d3d11_download_propose_allocation);
138 trans_class->decide_allocation =
139 GST_DEBUG_FUNCPTR (gst_d3d11_download_decide_allocation);
140 trans_class->transform = GST_DEBUG_FUNCPTR (gst_d3d11_download_transform);
141
142 bfilter_class->set_info = GST_DEBUG_FUNCPTR (gst_d3d11_download_set_info);
143
144 GST_DEBUG_CATEGORY_INIT (gst_d3d11_download_debug,
145 "d3d11download", 0, "d3d11download Element");
146 }
147
148 static void
gst_d3d11_download_init(GstD3D11Download * download)149 gst_d3d11_download_init (GstD3D11Download * download)
150 {
151 }
152
153 static void
gst_d3d11_download_dispose(GObject * object)154 gst_d3d11_download_dispose (GObject * object)
155 {
156 GstD3D11Download *self = GST_D3D11_DOWNLOAD (object);
157
158 gst_clear_buffer (&self->staging_buffer);
159
160 G_OBJECT_CLASS (parent_class)->dispose (object);
161 }
162
163 static gboolean
gst_d3d11_download_stop(GstBaseTransform * trans)164 gst_d3d11_download_stop (GstBaseTransform * trans)
165 {
166 GstD3D11Download *self = GST_D3D11_DOWNLOAD (trans);
167
168 gst_clear_buffer (&self->staging_buffer);
169
170 return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (trans);
171 }
172
173 static gboolean
gst_d3d11_download_sink_event(GstBaseTransform * trans,GstEvent * event)174 gst_d3d11_download_sink_event (GstBaseTransform * trans, GstEvent * event)
175 {
176 GstD3D11Download *self = GST_D3D11_DOWNLOAD (trans);
177
178 switch (GST_EVENT_TYPE (event)) {
179 case GST_EVENT_EOS:
180 /* We don't need to hold this staging buffer after eos */
181 gst_clear_buffer (&self->staging_buffer);
182 break;
183 default:
184 break;
185 }
186
187 return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
188 }
189
190 static GstCaps *
_set_caps_features(const GstCaps * caps,const gchar * feature_name)191 _set_caps_features (const GstCaps * caps, const gchar * feature_name)
192 {
193 GstCaps *tmp = gst_caps_copy (caps);
194 guint n = gst_caps_get_size (tmp);
195 guint i = 0;
196
197 for (i = 0; i < n; i++)
198 gst_caps_set_features (tmp, i,
199 gst_caps_features_from_string (feature_name));
200
201 return tmp;
202 }
203
204 static GstCaps *
gst_d3d11_download_transform_caps(GstBaseTransform * trans,GstPadDirection direction,GstCaps * caps,GstCaps * filter)205 gst_d3d11_download_transform_caps (GstBaseTransform * trans,
206 GstPadDirection direction, GstCaps * caps, GstCaps * filter)
207 {
208 GstCaps *result, *tmp;
209
210 GST_DEBUG_OBJECT (trans,
211 "Transforming caps %" GST_PTR_FORMAT " in direction %s", caps,
212 (direction == GST_PAD_SINK) ? "sink" : "src");
213
214 if (direction == GST_PAD_SINK) {
215 tmp = _set_caps_features (caps, GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY);
216 tmp = gst_caps_merge (gst_caps_ref (caps), tmp);
217 } else {
218 GstCaps *newcaps;
219 tmp = gst_caps_ref (caps);
220
221 newcaps = _set_caps_features (caps, GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY);
222 tmp = gst_caps_merge (tmp, newcaps);
223 }
224
225 if (filter) {
226 result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
227 gst_caps_unref (tmp);
228 } else {
229 result = tmp;
230 }
231
232 GST_DEBUG_OBJECT (trans, "returning caps: %" GST_PTR_FORMAT, result);
233
234 return result;
235 }
236
237 static gboolean
gst_d3d11_download_propose_allocation(GstBaseTransform * trans,GstQuery * decide_query,GstQuery * query)238 gst_d3d11_download_propose_allocation (GstBaseTransform * trans,
239 GstQuery * decide_query, GstQuery * query)
240 {
241 GstD3D11BaseFilter *filter = GST_D3D11_BASE_FILTER (trans);
242 GstVideoInfo info;
243 GstBufferPool *pool;
244 GstCaps *caps;
245 guint size;
246
247 if (!GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (trans,
248 decide_query, query))
249 return FALSE;
250
251 /* passthrough, we're done */
252 if (decide_query == NULL)
253 return TRUE;
254
255 gst_query_parse_allocation (query, &caps, NULL);
256
257 if (caps == NULL)
258 return FALSE;
259
260 if (!gst_video_info_from_caps (&info, caps))
261 return FALSE;
262
263 if (gst_query_get_n_allocation_pools (query) == 0) {
264 GstCapsFeatures *features;
265 GstStructure *config;
266 gboolean is_d3d11 = FALSE;
267
268 features = gst_caps_get_features (caps, 0);
269
270 if (features && gst_caps_features_contains (features,
271 GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) {
272 GST_DEBUG_OBJECT (filter, "upstream support d3d11 memory");
273 pool = gst_d3d11_buffer_pool_new (filter->device);
274 is_d3d11 = TRUE;
275 } else {
276 pool = gst_video_buffer_pool_new ();
277 }
278
279 config = gst_buffer_pool_get_config (pool);
280
281 gst_buffer_pool_config_add_option (config,
282 GST_BUFFER_POOL_OPTION_VIDEO_META);
283
284 /* d3d11 pool does not support video alignment */
285 if (!is_d3d11) {
286 gst_buffer_pool_config_add_option (config,
287 GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
288 }
289
290 size = GST_VIDEO_INFO_SIZE (&info);
291 gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
292
293 if (!gst_buffer_pool_set_config (pool, config))
294 goto config_failed;
295
296 gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
297
298 if (is_d3d11) {
299 /* d3d11 buffer pool will update buffer size based on allocated texture,
300 * get size from config again */
301 config = gst_buffer_pool_get_config (pool);
302 gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr,
303 nullptr);
304 gst_structure_free (config);
305 }
306
307 gst_query_add_allocation_pool (query, pool, size, 0, 0);
308
309 gst_object_unref (pool);
310 }
311
312 return TRUE;
313
314 /* ERRORS */
315 config_failed:
316 {
317 GST_ERROR_OBJECT (filter, "failed to set config");
318 gst_object_unref (pool);
319 return FALSE;
320 }
321 }
322
323 static gboolean
gst_d3d11_download_decide_allocation(GstBaseTransform * trans,GstQuery * query)324 gst_d3d11_download_decide_allocation (GstBaseTransform * trans,
325 GstQuery * query)
326 {
327 GstBufferPool *pool = NULL;
328 GstStructure *config;
329 guint min, max, size;
330 gboolean update_pool;
331 GstCaps *outcaps = NULL;
332
333 if (gst_query_get_n_allocation_pools (query) > 0) {
334 gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
335
336 if (!pool)
337 gst_query_parse_allocation (query, &outcaps, NULL);
338
339 update_pool = TRUE;
340 } else {
341 GstVideoInfo vinfo;
342
343 gst_query_parse_allocation (query, &outcaps, NULL);
344 gst_video_info_from_caps (&vinfo, outcaps);
345 size = vinfo.size;
346 min = max = 0;
347 update_pool = FALSE;
348 }
349
350 if (!pool)
351 pool = gst_video_buffer_pool_new ();
352
353 config = gst_buffer_pool_get_config (pool);
354 gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
355 if (outcaps)
356 gst_buffer_pool_config_set_params (config, outcaps, size, 0, 0);
357 gst_buffer_pool_set_config (pool, config);
358
359 if (update_pool)
360 gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
361 else
362 gst_query_add_allocation_pool (query, pool, size, min, max);
363
364 gst_object_unref (pool);
365
366 return GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans,
367 query);
368 }
369
370 static gboolean
gst_d3d11_download_can_use_staging_buffer(GstD3D11Download * self,GstBuffer * inbuf)371 gst_d3d11_download_can_use_staging_buffer (GstD3D11Download * self,
372 GstBuffer * inbuf)
373 {
374 GstD3D11BaseFilter *filter = GST_D3D11_BASE_FILTER (self);
375 ID3D11Device *device_handle =
376 gst_d3d11_device_get_device_handle (filter->device);
377
378 if (!gst_d3d11_buffer_can_access_device (inbuf, device_handle))
379 return FALSE;
380
381 if (self->staging_buffer)
382 return TRUE;
383
384 self->staging_buffer = gst_d3d11_allocate_staging_buffer_for (inbuf,
385 &filter->in_info, TRUE);
386
387 if (!self->staging_buffer) {
388 GST_WARNING_OBJECT (self, "Couldn't allocate staging buffer");
389 return FALSE;
390 }
391
392 return TRUE;
393 }
394
395 static GstFlowReturn
gst_d3d11_download_transform(GstBaseTransform * trans,GstBuffer * inbuf,GstBuffer * outbuf)396 gst_d3d11_download_transform (GstBaseTransform * trans, GstBuffer * inbuf,
397 GstBuffer * outbuf)
398 {
399 GstD3D11BaseFilter *filter = GST_D3D11_BASE_FILTER (trans);
400 GstD3D11Download *self = GST_D3D11_DOWNLOAD (trans);
401 GstVideoFrame in_frame, out_frame;
402 GstFlowReturn ret = GST_FLOW_OK;
403 gboolean use_staging_buf;
404 GstBuffer *target_inbuf = inbuf;
405 guint i;
406
407 use_staging_buf = gst_d3d11_download_can_use_staging_buffer (self, inbuf);
408
409 if (use_staging_buf) {
410 GST_TRACE_OBJECT (self, "Copy input buffer to staging buffer");
411
412 /* Copy d3d11 texture to staging texture */
413 if (!gst_d3d11_buffer_copy_into (self->staging_buffer,
414 inbuf, &filter->in_info)) {
415 GST_ERROR_OBJECT (self,
416 "Failed to copy input buffer into staging texture");
417 return GST_FLOW_ERROR;
418 }
419
420 target_inbuf = self->staging_buffer;
421 }
422
423 if (!gst_video_frame_map (&in_frame, &filter->in_info, target_inbuf,
424 (GstMapFlags) (GST_MAP_READ | GST_VIDEO_FRAME_MAP_FLAG_NO_REF)))
425 goto invalid_buffer;
426
427 if (!gst_video_frame_map (&out_frame, &filter->out_info, outbuf,
428 (GstMapFlags) (GST_MAP_WRITE | GST_VIDEO_FRAME_MAP_FLAG_NO_REF))) {
429 gst_video_frame_unmap (&in_frame);
430 goto invalid_buffer;
431 }
432
433 for (i = 0; i < GST_VIDEO_FRAME_N_PLANES (&in_frame); i++) {
434 if (!gst_video_frame_copy_plane (&out_frame, &in_frame, i)) {
435 GST_ERROR_OBJECT (filter, "Couldn't copy %dth plane", i);
436 ret = GST_FLOW_ERROR;
437 break;
438 }
439 }
440
441 gst_video_frame_unmap (&out_frame);
442 gst_video_frame_unmap (&in_frame);
443
444 return ret;
445
446 /* ERRORS */
447 invalid_buffer:
448 {
449 GST_ELEMENT_WARNING (filter, CORE, NOT_IMPLEMENTED, (NULL),
450 ("invalid video buffer received"));
451 return GST_FLOW_ERROR;
452 }
453 }
454
455 static gboolean
gst_d3d11_download_set_info(GstD3D11BaseFilter * filter,GstCaps * incaps,GstVideoInfo * in_info,GstCaps * outcaps,GstVideoInfo * out_info)456 gst_d3d11_download_set_info (GstD3D11BaseFilter * filter,
457 GstCaps * incaps, GstVideoInfo * in_info, GstCaps * outcaps,
458 GstVideoInfo * out_info)
459 {
460 GstD3D11Download *self = GST_D3D11_DOWNLOAD (filter);
461
462 gst_clear_buffer (&self->staging_buffer);
463
464 return TRUE;
465 }
466