1 /* GStreamer
2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) 2005-2012 David Schleef <ds@schleef.org>
4 * Copyright (C) <2019> Seungha Yang <seungha.yang@navercorp.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
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 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22 /**
23 * SECTION:element-cudaconvert
24 * @title: cudaconvert
25 *
26 * Convert video frames between supported video formats.
27 *
28 * ## Example launch line
29 * |[
30 * gst-launch-1.0 -v videotestsrc ! video/x-raw,format=Y444_16LE ! cudaupload ! cudaconvert ! cudadownload ! autovideosink
31 * ]|
32 * This will output a test video (generated in Y444_16LE format) in a video
33 * window. If the video sink selected does not support Y444_16LE
34 * cudaconvert will automatically convert the video to a format understood
35 * by the video sink.
36 *
37 * Since: 1.20
38 */
39
40 #ifdef HAVE_CONFIG_H
41 # include <config.h>
42 #endif
43
44 #include "gstcudaconvert.h"
45 #include "gstcudautils.h"
46
47 GST_DEBUG_CATEGORY_STATIC (gst_cuda_convert_debug);
48 #define GST_CAT_DEFAULT gst_cuda_convert_debug
49
50 #define gst_cuda_convert_parent_class parent_class
51 G_DEFINE_TYPE (GstCudaConvert, gst_cuda_convert, GST_TYPE_CUDA_BASE_FILTER);
52
53 static GstCaps *gst_cuda_convert_transform_caps (GstBaseTransform * trans,
54 GstPadDirection direction, GstCaps * caps, GstCaps * filter);
55 static GstCaps *gst_cuda_convert_fixate_caps (GstBaseTransform * base,
56 GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
57 static gboolean gst_cuda_convert_filter_meta (GstBaseTransform * trans,
58 GstQuery * query, GType api, const GstStructure * params);
59 static gboolean
60 gst_cuda_convert_set_info (GstCudaBaseTransform * btrans, GstCaps * incaps,
61 GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info);
62
63 /* copies the given caps */
64 static GstCaps *
gst_cuda_convert_caps_remove_format_info(GstCaps * caps)65 gst_cuda_convert_caps_remove_format_info (GstCaps * caps)
66 {
67 GstStructure *st;
68 GstCapsFeatures *f;
69 gint i, n;
70 GstCaps *res;
71 GstCapsFeatures *feature =
72 gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_CUDA_MEMORY);
73
74 res = gst_caps_new_empty ();
75
76 n = gst_caps_get_size (caps);
77 for (i = 0; i < n; i++) {
78 st = gst_caps_get_structure (caps, i);
79 f = gst_caps_get_features (caps, i);
80
81 /* If this is already expressed by the existing caps
82 * skip this structure */
83 if (i > 0 && gst_caps_is_subset_structure_full (res, st, f))
84 continue;
85
86 st = gst_structure_copy (st);
87 /* Only remove format info for the cases when we can actually convert */
88 if (!gst_caps_features_is_any (f)
89 && gst_caps_features_is_equal (f, feature))
90 gst_structure_remove_fields (st, "format", "colorimetry", "chroma-site",
91 NULL);
92
93 gst_caps_append_structure_full (res, st, gst_caps_features_copy (f));
94 }
95 gst_caps_features_free (feature);
96
97 return res;
98 }
99
100 /*
101 * This is an incomplete matrix of in formats and a score for the prefered output
102 * format.
103 *
104 * out: RGB24 RGB16 ARGB AYUV YUV444 YUV422 YUV420 YUV411 YUV410 PAL GRAY
105 * in
106 * RGB24 0 2 1 2 2 3 4 5 6 7 8
107 * RGB16 1 0 1 2 2 3 4 5 6 7 8
108 * ARGB 2 3 0 1 4 5 6 7 8 9 10
109 * AYUV 3 4 1 0 2 5 6 7 8 9 10
110 * YUV444 2 4 3 1 0 5 6 7 8 9 10
111 * YUV422 3 5 4 2 1 0 6 7 8 9 10
112 * YUV420 4 6 5 3 2 1 0 7 8 9 10
113 * YUV411 4 6 5 3 2 1 7 0 8 9 10
114 * YUV410 6 8 7 5 4 3 2 1 0 9 10
115 * PAL 1 3 2 6 4 6 7 8 9 0 10
116 * GRAY 1 4 3 2 1 5 6 7 8 9 0
117 *
118 * PAL or GRAY are never preferred, if we can we would convert to PAL instead
119 * of GRAY, though
120 * less subsampling is preferred and if any, preferably horizontal
121 * We would like to keep the alpha, even if we would need to to colorspace conversion
122 * or lose depth.
123 */
124 #define SCORE_FORMAT_CHANGE 1
125 #define SCORE_DEPTH_CHANGE 1
126 #define SCORE_ALPHA_CHANGE 1
127 #define SCORE_CHROMA_W_CHANGE 1
128 #define SCORE_CHROMA_H_CHANGE 1
129 #define SCORE_PALETTE_CHANGE 1
130
131 #define SCORE_COLORSPACE_LOSS 2 /* RGB <-> YUV */
132 #define SCORE_DEPTH_LOSS 4 /* change bit depth */
133 #define SCORE_ALPHA_LOSS 8 /* lose the alpha channel */
134 #define SCORE_CHROMA_W_LOSS 16 /* vertical subsample */
135 #define SCORE_CHROMA_H_LOSS 32 /* horizontal subsample */
136 #define SCORE_PALETTE_LOSS 64 /* convert to palette format */
137 #define SCORE_COLOR_LOSS 128 /* convert to GRAY */
138
139 #define COLORSPACE_MASK (GST_VIDEO_FORMAT_FLAG_YUV | \
140 GST_VIDEO_FORMAT_FLAG_RGB | GST_VIDEO_FORMAT_FLAG_GRAY)
141 #define ALPHA_MASK (GST_VIDEO_FORMAT_FLAG_ALPHA)
142 #define PALETTE_MASK (GST_VIDEO_FORMAT_FLAG_PALETTE)
143
144 /* calculate how much loss a conversion would be */
145 static void
score_value(GstBaseTransform * base,const GstVideoFormatInfo * in_info,const GValue * val,gint * min_loss,const GstVideoFormatInfo ** out_info)146 score_value (GstBaseTransform * base, const GstVideoFormatInfo * in_info,
147 const GValue * val, gint * min_loss, const GstVideoFormatInfo ** out_info)
148 {
149 const gchar *fname;
150 const GstVideoFormatInfo *t_info;
151 GstVideoFormatFlags in_flags, t_flags;
152 gint loss;
153
154 fname = g_value_get_string (val);
155 t_info = gst_video_format_get_info (gst_video_format_from_string (fname));
156 if (!t_info)
157 return;
158
159 /* accept input format immediately without loss */
160 if (in_info == t_info) {
161 *min_loss = 0;
162 *out_info = t_info;
163 return;
164 }
165
166 loss = SCORE_FORMAT_CHANGE;
167
168 in_flags = GST_VIDEO_FORMAT_INFO_FLAGS (in_info);
169 in_flags &= ~GST_VIDEO_FORMAT_FLAG_LE;
170 in_flags &= ~GST_VIDEO_FORMAT_FLAG_COMPLEX;
171 in_flags &= ~GST_VIDEO_FORMAT_FLAG_UNPACK;
172
173 t_flags = GST_VIDEO_FORMAT_INFO_FLAGS (t_info);
174 t_flags &= ~GST_VIDEO_FORMAT_FLAG_LE;
175 t_flags &= ~GST_VIDEO_FORMAT_FLAG_COMPLEX;
176 t_flags &= ~GST_VIDEO_FORMAT_FLAG_UNPACK;
177
178 if ((t_flags & PALETTE_MASK) != (in_flags & PALETTE_MASK)) {
179 loss += SCORE_PALETTE_CHANGE;
180 if (t_flags & PALETTE_MASK)
181 loss += SCORE_PALETTE_LOSS;
182 }
183
184 if ((t_flags & COLORSPACE_MASK) != (in_flags & COLORSPACE_MASK)) {
185 loss += SCORE_COLORSPACE_LOSS;
186 if (t_flags & GST_VIDEO_FORMAT_FLAG_GRAY)
187 loss += SCORE_COLOR_LOSS;
188 }
189
190 if ((t_flags & ALPHA_MASK) != (in_flags & ALPHA_MASK)) {
191 loss += SCORE_ALPHA_CHANGE;
192 if (in_flags & ALPHA_MASK)
193 loss += SCORE_ALPHA_LOSS;
194 }
195
196 if ((in_info->h_sub[1]) != (t_info->h_sub[1])) {
197 loss += SCORE_CHROMA_H_CHANGE;
198 if ((in_info->h_sub[1]) < (t_info->h_sub[1]))
199 loss += SCORE_CHROMA_H_LOSS;
200 }
201 if ((in_info->w_sub[1]) != (t_info->w_sub[1])) {
202 loss += SCORE_CHROMA_W_CHANGE;
203 if ((in_info->w_sub[1]) < (t_info->w_sub[1]))
204 loss += SCORE_CHROMA_W_LOSS;
205 }
206
207 if ((in_info->bits) != (t_info->bits)) {
208 loss += SCORE_DEPTH_CHANGE;
209 if ((in_info->bits) > (t_info->bits))
210 loss += SCORE_DEPTH_LOSS;
211 }
212
213 GST_DEBUG_OBJECT (base, "score %s -> %s = %d",
214 GST_VIDEO_FORMAT_INFO_NAME (in_info),
215 GST_VIDEO_FORMAT_INFO_NAME (t_info), loss);
216
217 if (loss < *min_loss) {
218 GST_DEBUG_OBJECT (base, "found new best %d", loss);
219 *out_info = t_info;
220 *min_loss = loss;
221 }
222 }
223
224 static void
gst_cuda_convert_class_init(GstCudaConvertClass * klass)225 gst_cuda_convert_class_init (GstCudaConvertClass * klass)
226 {
227 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
228 GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
229 GstCudaBaseTransformClass *btrans_class =
230 GST_CUDA_BASE_TRANSFORM_CLASS (klass);
231
232 gst_element_class_set_static_metadata (element_class,
233 "CUDA Colorspace converter",
234 "Filter/Converter/Video/Hardware",
235 "Converts video from one colorspace to another using CUDA",
236 "Seungha Yang <seungha.yang@navercorp.com>");
237
238 trans_class->passthrough_on_same_caps = TRUE;
239
240 trans_class->transform_caps =
241 GST_DEBUG_FUNCPTR (gst_cuda_convert_transform_caps);
242 trans_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_cuda_convert_fixate_caps);
243 trans_class->filter_meta = GST_DEBUG_FUNCPTR (gst_cuda_convert_filter_meta);
244
245 btrans_class->set_info = GST_DEBUG_FUNCPTR (gst_cuda_convert_set_info);
246
247 GST_DEBUG_CATEGORY_INIT (gst_cuda_convert_debug,
248 "cudaconvert", 0, "Video ColorSpace convert using CUDA");
249
250 gst_type_mark_as_plugin_api (GST_TYPE_CUDA_BASE_FILTER, 0);
251 }
252
253 static void
gst_cuda_convert_init(GstCudaConvert * convert)254 gst_cuda_convert_init (GstCudaConvert * convert)
255 {
256 }
257
258 static GstCaps *
gst_cuda_convert_transform_caps(GstBaseTransform * trans,GstPadDirection direction,GstCaps * caps,GstCaps * filter)259 gst_cuda_convert_transform_caps (GstBaseTransform * trans,
260 GstPadDirection direction, GstCaps * caps, GstCaps * filter)
261 {
262 GstCaps *tmp, *tmp2;
263 GstCaps *result;
264
265 /* Get all possible caps that we can transform to */
266 tmp = gst_cuda_convert_caps_remove_format_info (caps);
267
268 if (filter) {
269 tmp2 = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
270 gst_caps_unref (tmp);
271 tmp = tmp2;
272 }
273
274 result = tmp;
275
276 GST_DEBUG_OBJECT (trans, "transformed %" GST_PTR_FORMAT " into %"
277 GST_PTR_FORMAT, caps, result);
278
279 return result;
280 }
281
282 /* fork of gstvideoconvert */
283 static void
gst_cuda_convert_fixate_format(GstBaseTransform * base,GstCaps * caps,GstCaps * result)284 gst_cuda_convert_fixate_format (GstBaseTransform * base, GstCaps * caps,
285 GstCaps * result)
286 {
287 GstStructure *ins, *outs;
288 const gchar *in_format;
289 const GstVideoFormatInfo *in_info, *out_info = NULL;
290 gint min_loss = G_MAXINT;
291 guint i, capslen;
292
293 ins = gst_caps_get_structure (caps, 0);
294 in_format = gst_structure_get_string (ins, "format");
295 if (!in_format)
296 return;
297
298 GST_DEBUG_OBJECT (base, "source format %s", in_format);
299
300 in_info =
301 gst_video_format_get_info (gst_video_format_from_string (in_format));
302 if (!in_info)
303 return;
304
305 outs = gst_caps_get_structure (result, 0);
306
307 capslen = gst_caps_get_size (result);
308 GST_DEBUG_OBJECT (base, "iterate %d structures", capslen);
309 for (i = 0; i < capslen; i++) {
310 GstStructure *tests;
311 const GValue *format;
312
313 tests = gst_caps_get_structure (result, i);
314 format = gst_structure_get_value (tests, "format");
315 /* should not happen */
316 if (format == NULL)
317 continue;
318
319 if (GST_VALUE_HOLDS_LIST (format)) {
320 gint j, len;
321
322 len = gst_value_list_get_size (format);
323 GST_DEBUG_OBJECT (base, "have %d formats", len);
324 for (j = 0; j < len; j++) {
325 const GValue *val;
326
327 val = gst_value_list_get_value (format, j);
328 if (G_VALUE_HOLDS_STRING (val)) {
329 score_value (base, in_info, val, &min_loss, &out_info);
330 if (min_loss == 0)
331 break;
332 }
333 }
334 } else if (G_VALUE_HOLDS_STRING (format)) {
335 score_value (base, in_info, format, &min_loss, &out_info);
336 }
337 }
338 if (out_info)
339 gst_structure_set (outs, "format", G_TYPE_STRING,
340 GST_VIDEO_FORMAT_INFO_NAME (out_info), NULL);
341 }
342
343 static GstCaps *
gst_cuda_convert_fixate_caps(GstBaseTransform * trans,GstPadDirection direction,GstCaps * caps,GstCaps * othercaps)344 gst_cuda_convert_fixate_caps (GstBaseTransform * trans,
345 GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
346 {
347 GstCaps *result;
348
349 GST_DEBUG_OBJECT (trans, "trying to fixate othercaps %" GST_PTR_FORMAT
350 " based on caps %" GST_PTR_FORMAT, othercaps, caps);
351
352 result = gst_caps_intersect (othercaps, caps);
353 if (gst_caps_is_empty (result)) {
354 gst_caps_unref (result);
355 result = othercaps;
356 } else {
357 gst_caps_unref (othercaps);
358 }
359
360 GST_DEBUG_OBJECT (trans, "now fixating %" GST_PTR_FORMAT, result);
361
362 result = gst_caps_make_writable (result);
363 gst_cuda_convert_fixate_format (trans, caps, result);
364
365 /* fixate remaining fields */
366 result = gst_caps_fixate (result);
367
368 if (direction == GST_PAD_SINK) {
369 if (gst_caps_is_subset (caps, result)) {
370 gst_caps_replace (&result, caps);
371 }
372 }
373
374 return result;
375 }
376
377 static gboolean
gst_cuda_convert_filter_meta(GstBaseTransform * trans,GstQuery * query,GType api,const GstStructure * params)378 gst_cuda_convert_filter_meta (GstBaseTransform * trans, GstQuery * query,
379 GType api, const GstStructure * params)
380 {
381 /* This element cannot passthrough the crop meta, because it would convert the
382 * wrong sub-region of the image, and worst, our output image may not be large
383 * enough for the crop to be applied later */
384 if (api == GST_VIDEO_CROP_META_API_TYPE)
385 return FALSE;
386
387 /* propose all other metadata upstream */
388 return TRUE;
389 }
390
391 static gboolean
gst_cuda_convert_set_info(GstCudaBaseTransform * btrans,GstCaps * incaps,GstVideoInfo * in_info,GstCaps * outcaps,GstVideoInfo * out_info)392 gst_cuda_convert_set_info (GstCudaBaseTransform * btrans, GstCaps * incaps,
393 GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
394 {
395 /* these must match */
396 if (in_info->width != out_info->width || in_info->height != out_info->height
397 || in_info->fps_n != out_info->fps_n || in_info->fps_d != out_info->fps_d)
398 goto format_mismatch;
399
400 /* if present, these must match too */
401 if (in_info->par_n != out_info->par_n || in_info->par_d != out_info->par_d)
402 goto format_mismatch;
403
404 /* if present, these must match too */
405 if (in_info->interlace_mode != out_info->interlace_mode)
406 goto format_mismatch;
407
408 return GST_CUDA_BASE_TRANSFORM_CLASS (parent_class)->set_info (btrans, incaps,
409 in_info, outcaps, out_info);
410
411 /* ERRORS */
412 format_mismatch:
413 {
414 GST_ERROR_OBJECT (btrans, "input and output formats do not match");
415 return FALSE;
416 }
417 }
418