va: Fix struct empty initialization syntax
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / sys / va / gstvavpp.c
1 /* GStreamer
2  * Copyright (C) 2020 Igalia, S.L.
3  *     Author: Víctor Jáquez <vjaquez@igalia.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the0
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 /**
22  * SECTION:element-vapostproc
23  * @title: vapostproc
24  * @short_description: A VA-API base video postprocessing filter
25  *
26  * vapostproc applies different video filters to VA surfaces. These
27  * filters vary depending on the installed and chosen
28  * [VA-API](https://01.org/linuxmedia/vaapi) driver, but usually
29  * resizing and color conversion are available.
30  *
31  * The generated surfaces can be mapped onto main memory as video
32  * frames.
33  *
34  * Use gst-inspect-1.0 to introspect the available capabilities of the
35  * driver's post-processor entry point.
36  *
37  * ## Example launch line
38  * ```
39  * gst-launch-1.0 videotestsrc ! "video/x-raw,format=(string)NV12" ! vapostproc ! autovideosink
40  * ```
41  *
42  * Cropping is supported via buffers' crop meta. It's only done if the
43  * postproccessor is not in passthrough mode or if downstream doesn't
44  * support the crop meta API.
45  *
46  * ### Cropping example
47  * ```
48  * gst-launch-1.0 videotestsrc ! "video/x-raw,format=(string)NV12" ! videocrop bottom=50 left=100 ! vapostproc ! autovideosink
49  * ```
50  *
51  * If the VA driver support color balance filter, with controls such
52  * as hue, brightness, contrast, etc., those controls are exposed both
53  * as element properties and through the #GstColorBalance interface.
54  *
55  * Since: 1.20
56  *
57  */
58
59 #ifdef HAVE_CONFIG_H
60 #include "config.h"
61 #endif
62
63 #include "gstvavpp.h"
64
65 #include <gst/va/gstva.h>
66 #include <gst/video/video.h>
67 #include <va/va_drmcommon.h>
68
69 #include "gstvabasetransform.h"
70 #include "gstvacaps.h"
71 #include "gstvadisplay_priv.h"
72 #include "gstvafilter.h"
73
74 GST_DEBUG_CATEGORY_STATIC (gst_va_vpp_debug);
75 #define GST_CAT_DEFAULT gst_va_vpp_debug
76
77 #define GST_VA_VPP(obj)           ((GstVaVpp *) obj)
78 #define GST_VA_VPP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_FROM_INSTANCE (obj), GstVaVppClass))
79 #define GST_VA_VPP_CLASS(klass)    ((GstVaVppClass *) klass)
80
81 #define SWAP_INT(a, b) G_STMT_START { \
82   gint __tmp = a; \
83   a = b; \
84   b = __tmp; \
85 } G_STMT_END
86
87 typedef struct _GstVaVpp GstVaVpp;
88 typedef struct _GstVaVppClass GstVaVppClass;
89
90 struct _GstVaVppClass
91 {
92   /* GstVideoFilter overlaps functionality */
93   GstVaBaseTransformClass parent_class;
94 };
95
96 struct _GstVaVpp
97 {
98   GstVaBaseTransform parent;
99
100   gboolean rebuild_filters;
101   guint op_flags;
102
103   /* filters */
104   float denoise;
105   float sharpen;
106   float skintone;
107   float brightness;
108   float contrast;
109   float hue;
110   float saturation;
111   gboolean auto_contrast;
112   gboolean auto_brightness;
113   gboolean auto_saturation;
114   GstVideoOrientationMethod direction;
115   GstVideoOrientationMethod prev_direction;
116   GstVideoOrientationMethod tag_direction;
117   gboolean add_borders;
118   gint borders_h;
119   gint borders_w;
120   guint32 scale_method;
121
122   gboolean hdr_mapping;
123   gboolean has_hdr_meta;
124   VAHdrMetaDataHDR10 hdr_meta;
125
126   GList *channels;
127 };
128
129 static GstElementClass *parent_class = NULL;
130
131 struct CData
132 {
133   gchar *render_device_path;
134   gchar *description;
135 };
136
137 enum
138 {
139   PROP_DISABLE_PASSTHROUGH = GST_VA_FILTER_PROP_LAST + 1,
140   PROP_ADD_BORDERS,
141   PROP_SCALE_METHOD,
142   N_PROPERTIES
143 };
144
145 static GParamSpec *properties[N_PROPERTIES - GST_VA_FILTER_PROP_LAST];
146
147 #define PROPERTIES(idx) properties[idx - GST_VA_FILTER_PROP_LAST]
148
149 /* convertions that disable passthrough */
150 enum
151 {
152   VPP_CONVERT_SIZE = 1 << 0,
153   VPP_CONVERT_FORMAT = 1 << 1,
154   VPP_CONVERT_FILTERS = 1 << 2,
155   VPP_CONVERT_DIRECTION = 1 << 3,
156   VPP_CONVERT_FEATURE = 1 << 4,
157   VPP_CONVERT_CROP = 1 << 5,
158   VPP_CONVERT_DUMMY = 1 << 6,
159 };
160
161 /* *INDENT-OFF* */
162 static const gchar *caps_str =
163     GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_VA,
164         "{ NV12, I420, YV12, YUY2, RGBA, BGRA, P010_10LE, ARGB, ABGR }") " ;"
165     GST_VIDEO_CAPS_MAKE ("{ VUYA, GRAY8, NV12, NV21, YUY2, UYVY, YV12, "
166         "I420, P010_10LE, RGBA, BGRA, ARGB, ABGR  }");
167 /* *INDENT-ON* */
168
169 #define META_TAG_COLORSPACE meta_tag_colorspace_quark
170 static GQuark meta_tag_colorspace_quark;
171 #define META_TAG_SIZE meta_tag_size_quark
172 static GQuark meta_tag_size_quark;
173 #define META_TAG_ORIENTATION meta_tag_orientation_quark
174 static GQuark meta_tag_orientation_quark;
175 #define META_TAG_VIDEO meta_tag_video_quark
176 static GQuark meta_tag_video_quark;
177
178 static void gst_va_vpp_colorbalance_init (gpointer iface, gpointer data);
179 static void gst_va_vpp_rebuild_filters (GstVaVpp * self);
180
181 static void
182 gst_va_vpp_dispose (GObject * object)
183 {
184   GstVaVpp *self = GST_VA_VPP (object);
185
186   if (self->channels)
187     g_list_free_full (g_steal_pointer (&self->channels), g_object_unref);
188
189   G_OBJECT_CLASS (parent_class)->dispose (object);
190 }
191
192 static void
193 gst_va_vpp_update_passthrough (GstVaVpp * self, gboolean reconf)
194 {
195   GstBaseTransform *trans = GST_BASE_TRANSFORM (self);
196   gboolean old, new;
197
198   old = gst_base_transform_is_passthrough (trans);
199
200   GST_OBJECT_LOCK (self);
201   new = (self->op_flags == 0);
202   GST_OBJECT_UNLOCK (self);
203
204   if (old != new) {
205     GST_INFO_OBJECT (self, "%s passthrough", new ? "enabling" : "disabling");
206     if (reconf)
207       gst_base_transform_reconfigure_src (trans);
208     gst_base_transform_set_passthrough (trans, new);
209   }
210 }
211
212 static void
213 _update_properties_unlocked (GstVaVpp * self)
214 {
215   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
216
217   if (!btrans->filter)
218     return;
219
220   if ((self->direction != GST_VIDEO_ORIENTATION_AUTO
221           && self->direction != self->prev_direction)
222       || (self->direction == GST_VIDEO_ORIENTATION_AUTO
223           && self->tag_direction != self->prev_direction)) {
224
225     GstVideoOrientationMethod direction =
226         (self->direction == GST_VIDEO_ORIENTATION_AUTO) ?
227         self->tag_direction : self->direction;
228
229     if (!gst_va_filter_set_orientation (btrans->filter, direction)) {
230       if (self->direction == GST_VIDEO_ORIENTATION_AUTO)
231         self->tag_direction = self->prev_direction;
232       else
233         self->direction = self->prev_direction;
234
235       self->op_flags &= ~VPP_CONVERT_DIRECTION;
236
237       /* FIXME: unlocked bus warning message */
238       GST_WARNING_OBJECT (self,
239           "Driver cannot set resquested orientation. Setting it back.");
240     } else {
241       self->prev_direction = direction;
242
243       self->op_flags |= VPP_CONVERT_DIRECTION;
244
245       gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (self));
246     }
247   } else {
248     self->op_flags &= ~VPP_CONVERT_DIRECTION;
249   }
250
251   if (!gst_va_filter_set_scale_method (btrans->filter, self->scale_method))
252     GST_WARNING_OBJECT (self, "could not set the filter scale method.");
253 }
254
255 static void
256 gst_va_vpp_set_property (GObject * object, guint prop_id,
257     const GValue * value, GParamSpec * pspec)
258 {
259   GstVaVpp *self = GST_VA_VPP (object);
260
261   GST_OBJECT_LOCK (object);
262   switch (prop_id) {
263     case GST_VA_FILTER_PROP_DENOISE:
264       self->denoise = g_value_get_float (value);
265       g_atomic_int_set (&self->rebuild_filters, TRUE);
266       break;
267     case GST_VA_FILTER_PROP_SHARPEN:
268       self->sharpen = g_value_get_float (value);
269       g_atomic_int_set (&self->rebuild_filters, TRUE);
270       break;
271     case GST_VA_FILTER_PROP_SKINTONE:
272       if (G_VALUE_TYPE (value) == G_TYPE_BOOLEAN)
273         self->skintone = (float) g_value_get_boolean (value);
274       else
275         self->skintone = g_value_get_float (value);
276       g_atomic_int_set (&self->rebuild_filters, TRUE);
277       break;
278     case GST_VA_FILTER_PROP_VIDEO_DIR:{
279       GstVideoOrientationMethod direction = g_value_get_enum (value);
280       self->prev_direction = (direction == GST_VIDEO_ORIENTATION_AUTO) ?
281           self->tag_direction : self->direction;
282       self->direction = direction;
283       break;
284     }
285     case GST_VA_FILTER_PROP_HUE:
286       self->hue = g_value_get_float (value);
287       g_atomic_int_set (&self->rebuild_filters, TRUE);
288       break;
289     case GST_VA_FILTER_PROP_SATURATION:
290       self->saturation = g_value_get_float (value);
291       g_atomic_int_set (&self->rebuild_filters, TRUE);
292       break;
293     case GST_VA_FILTER_PROP_BRIGHTNESS:
294       self->brightness = g_value_get_float (value);
295       g_atomic_int_set (&self->rebuild_filters, TRUE);
296       break;
297     case GST_VA_FILTER_PROP_CONTRAST:
298       self->contrast = g_value_get_float (value);
299       g_atomic_int_set (&self->rebuild_filters, TRUE);
300       break;
301     case GST_VA_FILTER_PROP_AUTO_SATURATION:
302       self->auto_saturation = g_value_get_boolean (value);
303       g_atomic_int_set (&self->rebuild_filters, TRUE);
304       break;
305     case GST_VA_FILTER_PROP_AUTO_BRIGHTNESS:
306       self->auto_brightness = g_value_get_boolean (value);
307       g_atomic_int_set (&self->rebuild_filters, TRUE);
308       break;
309     case GST_VA_FILTER_PROP_AUTO_CONTRAST:
310       self->auto_contrast = g_value_get_boolean (value);
311       g_atomic_int_set (&self->rebuild_filters, TRUE);
312       break;
313     case GST_VA_FILTER_PROP_HDR:
314       self->hdr_mapping = g_value_get_boolean (value);
315       g_atomic_int_set (&self->rebuild_filters, TRUE);
316       break;
317     case PROP_DISABLE_PASSTHROUGH:{
318       gboolean disable_passthrough = g_value_get_boolean (value);
319       if (disable_passthrough)
320         self->op_flags |= VPP_CONVERT_DUMMY;
321       else
322         self->op_flags &= ~VPP_CONVERT_DUMMY;
323       break;
324     }
325     case PROP_ADD_BORDERS:
326       self->add_borders = g_value_get_boolean (value);
327       break;
328     case PROP_SCALE_METHOD:
329       self->scale_method = g_value_get_enum (value);
330       break;
331     default:
332       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
333       break;
334   }
335
336   _update_properties_unlocked (self);
337   GST_OBJECT_UNLOCK (object);
338
339   gst_va_vpp_update_passthrough (self, FALSE);
340 }
341
342 static void
343 gst_va_vpp_get_property (GObject * object, guint prop_id, GValue * value,
344     GParamSpec * pspec)
345 {
346   GstVaVpp *self = GST_VA_VPP (object);
347
348   GST_OBJECT_LOCK (object);
349   switch (prop_id) {
350     case GST_VA_FILTER_PROP_DENOISE:
351       g_value_set_float (value, self->denoise);
352       break;
353     case GST_VA_FILTER_PROP_SHARPEN:
354       g_value_set_float (value, self->sharpen);
355       break;
356     case GST_VA_FILTER_PROP_SKINTONE:
357       if (G_VALUE_TYPE (value) == G_TYPE_BOOLEAN)
358         g_value_set_boolean (value, self->skintone > 0);
359       else
360         g_value_set_float (value, self->skintone);
361       break;
362     case GST_VA_FILTER_PROP_VIDEO_DIR:
363       g_value_set_enum (value, self->direction);
364       break;
365     case GST_VA_FILTER_PROP_HUE:
366       g_value_set_float (value, self->hue);
367       break;
368     case GST_VA_FILTER_PROP_SATURATION:
369       g_value_set_float (value, self->saturation);
370       break;
371     case GST_VA_FILTER_PROP_BRIGHTNESS:
372       g_value_set_float (value, self->brightness);
373       break;
374     case GST_VA_FILTER_PROP_CONTRAST:
375       g_value_set_float (value, self->contrast);
376       break;
377     case GST_VA_FILTER_PROP_AUTO_SATURATION:
378       g_value_set_boolean (value, self->auto_saturation);
379       break;
380     case GST_VA_FILTER_PROP_AUTO_BRIGHTNESS:
381       g_value_set_boolean (value, self->auto_brightness);
382       break;
383     case GST_VA_FILTER_PROP_AUTO_CONTRAST:
384       g_value_set_boolean (value, self->auto_contrast);
385       break;
386     case GST_VA_FILTER_PROP_HDR:
387       g_value_set_boolean (value, self->hdr_mapping);
388       break;
389     case PROP_DISABLE_PASSTHROUGH:
390       g_value_set_boolean (value, (self->op_flags & VPP_CONVERT_DUMMY));
391       break;
392     case PROP_ADD_BORDERS:
393       g_value_set_boolean (value, self->add_borders);
394       break;
395     case PROP_SCALE_METHOD:
396       g_value_set_enum (value, self->scale_method);
397       break;
398     default:
399       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
400       break;
401   }
402   GST_OBJECT_UNLOCK (object);
403 }
404
405 static gboolean
406 gst_va_vpp_propose_allocation (GstBaseTransform * trans,
407     GstQuery * decide_query, GstQuery * query)
408 {
409   /* if we are not passthrough, we can handle crop meta */
410   if (decide_query)
411     gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL);
412
413   return GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (trans,
414       decide_query, query);
415 }
416
417 static void
418 gst_va_vpp_update_properties (GstVaBaseTransform * btrans)
419 {
420   GstVaVpp *self = GST_VA_VPP (btrans);
421
422   gst_va_vpp_rebuild_filters (self);
423   _update_properties_unlocked (self);
424 }
425
426 static void
427 _set_hdr_metadata (GstVaVpp * self, GstCaps * caps)
428 {
429   GstVideoMasteringDisplayInfo mdinfo;
430   GstVideoContentLightLevel llevel;
431
432   self->has_hdr_meta = FALSE;
433
434   if (gst_video_mastering_display_info_from_caps (&mdinfo, caps)) {
435     self->hdr_meta.display_primaries_x[0] = mdinfo.display_primaries[1].x;
436     self->hdr_meta.display_primaries_x[1] = mdinfo.display_primaries[2].x;
437     self->hdr_meta.display_primaries_x[2] = mdinfo.display_primaries[0].x;
438
439     self->hdr_meta.display_primaries_y[0] = mdinfo.display_primaries[1].y;
440     self->hdr_meta.display_primaries_y[1] = mdinfo.display_primaries[2].y;
441     self->hdr_meta.display_primaries_y[2] = mdinfo.display_primaries[0].y;
442
443     self->hdr_meta.white_point_x = mdinfo.white_point.x;
444     self->hdr_meta.white_point_y = mdinfo.white_point.y;
445
446     self->hdr_meta.max_display_mastering_luminance =
447         mdinfo.max_display_mastering_luminance;
448     self->hdr_meta.min_display_mastering_luminance =
449         mdinfo.min_display_mastering_luminance;
450
451     self->has_hdr_meta = TRUE;
452   }
453
454
455   if (gst_video_content_light_level_from_caps (&llevel, caps)) {
456     self->hdr_meta.max_content_light_level = llevel.max_content_light_level;
457     self->hdr_meta.max_pic_average_light_level =
458         llevel.max_frame_average_light_level;
459
460     self->has_hdr_meta = TRUE;
461   };
462
463   /* rebuild filters only if hdr mapping is enabled */
464   g_atomic_int_set (&self->rebuild_filters, self->hdr_mapping);
465 }
466
467 static gboolean
468 gst_va_vpp_set_info (GstVaBaseTransform * btrans, GstCaps * incaps,
469     GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
470 {
471   GstVaVpp *self = GST_VA_VPP (btrans);
472   GstCapsFeatures *infeat, *outfeat;
473   gint from_dar_n, from_dar_d, to_dar_n, to_dar_d;
474
475   if (GST_VIDEO_INFO_INTERLACE_MODE (in_info) !=
476       GST_VIDEO_INFO_INTERLACE_MODE (out_info)) {
477     GST_ERROR_OBJECT (self, "input and output formats do not match");
478     return FALSE;
479   }
480
481   /* calculate possible borders if display-aspect-ratio change */
482   {
483     if (!gst_util_fraction_multiply (GST_VIDEO_INFO_WIDTH (in_info),
484             GST_VIDEO_INFO_HEIGHT (in_info), GST_VIDEO_INFO_PAR_N (in_info),
485             GST_VIDEO_INFO_PAR_D (in_info), &from_dar_n, &from_dar_d)) {
486       from_dar_n = from_dar_d = -1;
487     }
488
489     if (!gst_util_fraction_multiply (GST_VIDEO_INFO_WIDTH (out_info),
490             GST_VIDEO_INFO_HEIGHT (out_info), GST_VIDEO_INFO_PAR_N (out_info),
491             GST_VIDEO_INFO_PAR_D (out_info), &to_dar_n, &to_dar_d)) {
492       to_dar_n = to_dar_d = -1;
493     }
494
495     /* if video-orientation changes consider it for borders */
496     switch (gst_va_filter_get_orientation (btrans->filter)) {
497       case GST_VIDEO_ORIENTATION_90R:
498       case GST_VIDEO_ORIENTATION_90L:
499       case GST_VIDEO_ORIENTATION_UL_LR:
500       case GST_VIDEO_ORIENTATION_UR_LL:
501         SWAP_INT (from_dar_n, from_dar_d);
502         break;
503       default:
504         break;
505     }
506
507     self->borders_h = self->borders_w = 0;
508     if (to_dar_n != from_dar_n || to_dar_d != from_dar_d) {
509       if (self->add_borders) {
510         gint n, d, to_h, to_w;
511
512         if (from_dar_n != -1 && from_dar_d != -1
513             && gst_util_fraction_multiply (from_dar_n, from_dar_d,
514                 out_info->par_d, out_info->par_n, &n, &d)) {
515           to_h = gst_util_uint64_scale_int (out_info->width, d, n);
516           if (to_h <= out_info->height) {
517             self->borders_h = out_info->height - to_h;
518             self->borders_w = 0;
519           } else {
520             to_w = gst_util_uint64_scale_int (out_info->height, n, d);
521             g_assert (to_w <= out_info->width);
522             self->borders_h = 0;
523             self->borders_w = out_info->width - to_w;
524           }
525         } else {
526           GST_WARNING_OBJECT (self, "Can't calculate borders");
527         }
528       } else {
529         GST_WARNING_OBJECT (self, "Can't keep DAR!");
530       }
531     }
532   }
533
534   if (gst_video_info_is_equal (in_info, out_info)) {
535     self->op_flags &= ~VPP_CONVERT_FORMAT & ~VPP_CONVERT_SIZE;
536   } else {
537     if ((GST_VIDEO_INFO_FORMAT (in_info) != GST_VIDEO_INFO_FORMAT (out_info))
538         || !gst_video_colorimetry_is_equivalent (&GST_VIDEO_INFO_COLORIMETRY
539             (in_info), GST_VIDEO_INFO_COMP_DEPTH (in_info, 0),
540             &GST_VIDEO_INFO_COLORIMETRY (out_info),
541             GST_VIDEO_INFO_COMP_DEPTH (out_info, 0)))
542       self->op_flags |= VPP_CONVERT_FORMAT;
543     else
544       self->op_flags &= ~VPP_CONVERT_FORMAT;
545
546     if (GST_VIDEO_INFO_WIDTH (in_info) != GST_VIDEO_INFO_WIDTH (out_info)
547         || GST_VIDEO_INFO_HEIGHT (in_info) != GST_VIDEO_INFO_HEIGHT (out_info)
548         || self->borders_h > 0 || self->borders_w > 0)
549       self->op_flags |= VPP_CONVERT_SIZE;
550     else
551       self->op_flags &= ~VPP_CONVERT_SIZE;
552   }
553
554   infeat = gst_caps_get_features (incaps, 0);
555   outfeat = gst_caps_get_features (outcaps, 0);
556   if (!gst_caps_features_is_equal (infeat, outfeat))
557     self->op_flags |= VPP_CONVERT_FEATURE;
558   else
559     self->op_flags &= ~VPP_CONVERT_FEATURE;
560
561   if (gst_va_filter_set_video_info (btrans->filter, in_info, out_info)) {
562     _set_hdr_metadata (self, incaps);
563     gst_va_vpp_update_passthrough (self, FALSE);
564     return TRUE;
565   }
566
567   return FALSE;
568 }
569
570 static inline gboolean
571 _get_filter_value (GstVaVpp * self, VAProcFilterType type, gfloat * value)
572 {
573   gboolean ret = TRUE;
574
575   GST_OBJECT_LOCK (self);
576   switch (type) {
577     case VAProcFilterNoiseReduction:
578       *value = self->denoise;
579       break;
580     case VAProcFilterSharpening:
581       *value = self->sharpen;
582       break;
583     case VAProcFilterSkinToneEnhancement:
584       *value = self->skintone;
585       break;
586     default:
587       ret = FALSE;
588       break;
589   }
590   GST_OBJECT_UNLOCK (self);
591
592   return ret;
593 }
594
595 static inline gboolean
596 _add_filter_buffer (GstVaVpp * self, VAProcFilterType type,
597     const VAProcFilterCap * cap)
598 {
599   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
600   VAProcFilterParameterBuffer param;
601   gfloat value = 0;
602
603   if (!_get_filter_value (self, type, &value))
604     return FALSE;
605   if (value == cap->range.default_value)
606     return FALSE;
607
608   /* *INDENT-OFF* */
609   param = (VAProcFilterParameterBuffer) {
610     .type = type,
611     .value = value,
612   };
613   /* *INDENT-ON* */
614
615   return gst_va_filter_add_filter_buffer (btrans->filter, &param,
616       sizeof (param), 1);
617 }
618
619 static inline gboolean
620 _get_filter_cb_value (GstVaVpp * self, VAProcColorBalanceType type,
621     gfloat * value)
622 {
623   gboolean ret = TRUE;
624
625   GST_OBJECT_LOCK (self);
626   switch (type) {
627     case VAProcColorBalanceHue:
628       *value = self->hue;
629       break;
630     case VAProcColorBalanceSaturation:
631       *value = self->saturation;
632       break;
633     case VAProcColorBalanceBrightness:
634       *value = self->brightness;
635       break;
636     case VAProcColorBalanceContrast:
637       *value = self->contrast;
638       break;
639     case VAProcColorBalanceAutoSaturation:
640       *value = self->auto_saturation;
641       break;
642     case VAProcColorBalanceAutoBrightness:
643       *value = self->auto_brightness;
644       break;
645     case VAProcColorBalanceAutoContrast:
646       *value = self->auto_contrast;
647       break;
648     default:
649       ret = FALSE;
650       break;
651   }
652   GST_OBJECT_UNLOCK (self);
653
654   return ret;
655 }
656
657 static inline gboolean
658 _add_filter_cb_buffer (GstVaVpp * self,
659     const VAProcFilterCapColorBalance * caps, guint num_caps)
660 {
661   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
662   VAProcFilterParameterBufferColorBalance param[VAProcColorBalanceCount] =
663       { 0, };
664   gfloat value;
665   guint i, c = 0;
666
667   value = 0;
668   for (i = 0; i < num_caps && i < VAProcColorBalanceCount; i++) {
669     if (!_get_filter_cb_value (self, caps[i].type, &value))
670       continue;
671     if (value == caps[i].range.default_value)
672       continue;
673
674     /* *INDENT-OFF* */
675     param[c++] = (VAProcFilterParameterBufferColorBalance) {
676       .type = VAProcFilterColorBalance,
677       .attrib = caps[i].type,
678       .value = value,
679     };
680     /* *INDENT-ON* */
681   }
682
683   if (c > 0) {
684     return gst_va_filter_add_filter_buffer (btrans->filter, param,
685         sizeof (*param), c);
686   }
687   return FALSE;
688 }
689
690 static inline gboolean
691 _add_filter_hdr_buffer (GstVaVpp * self,
692     const VAProcFilterCapHighDynamicRange * caps)
693 {
694   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
695   /* *INDENT-OFF* */
696   VAProcFilterParameterBufferHDRToneMapping params = {
697     .type = VAProcFilterHighDynamicRangeToneMapping,
698     .data = {
699       .metadata_type = VAProcHighDynamicRangeMetadataHDR10,
700       .metadata = &self->hdr_meta,
701       .metadata_size = sizeof (self->hdr_meta),
702     },
703   };
704   /* *INDENT-ON* */
705
706   /* if not has hdr meta, it may try later again */
707   if (!(self->has_hdr_meta && self->hdr_mapping))
708     return FALSE;
709
710   if (!(caps && caps->metadata_type == VAProcHighDynamicRangeMetadataHDR10
711           && (caps->caps_flag & VA_TONE_MAPPING_HDR_TO_SDR)))
712     goto bail;
713
714   if (self->op_flags & VPP_CONVERT_FORMAT) {
715     GST_WARNING_OBJECT (self, "Cannot apply HDR with color conversion");
716     goto bail;
717   }
718
719   return gst_va_filter_add_filter_buffer (btrans->filter, &params,
720       sizeof (params), 1);
721
722 bail:
723   self->hdr_mapping = FALSE;
724   g_object_notify (G_OBJECT (self), "hdr-tone-mapping");
725   return FALSE;
726 }
727
728 static void
729 _build_filters (GstVaVpp * self)
730 {
731   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
732   static const VAProcFilterType filter_types[] = { VAProcFilterNoiseReduction,
733     VAProcFilterSharpening, VAProcFilterSkinToneEnhancement,
734     VAProcFilterColorBalance, VAProcFilterHighDynamicRangeToneMapping,
735   };
736   guint i, num_caps;
737   gboolean apply = FALSE;
738
739   for (i = 0; i < G_N_ELEMENTS (filter_types); i++) {
740     const gpointer caps = gst_va_filter_get_filter_caps (btrans->filter,
741         filter_types[i], &num_caps);
742     if (!caps)
743       continue;
744
745     switch (filter_types[i]) {
746       case VAProcFilterNoiseReduction:
747         apply |= _add_filter_buffer (self, filter_types[i], caps);
748         break;
749       case VAProcFilterSharpening:
750         apply |= _add_filter_buffer (self, filter_types[i], caps);
751         break;
752       case VAProcFilterSkinToneEnhancement:
753         apply |= _add_filter_buffer (self, filter_types[i], caps);
754         break;
755       case VAProcFilterColorBalance:
756         apply |= _add_filter_cb_buffer (self, caps, num_caps);
757         break;
758       case VAProcFilterHighDynamicRangeToneMapping:
759         apply |= _add_filter_hdr_buffer (self, caps);
760       default:
761         break;
762     }
763   }
764
765   GST_OBJECT_LOCK (self);
766   if (apply)
767     self->op_flags |= VPP_CONVERT_FILTERS;
768   else
769     self->op_flags &= ~VPP_CONVERT_FILTERS;
770   GST_OBJECT_UNLOCK (self);
771 }
772
773 static void
774 gst_va_vpp_rebuild_filters (GstVaVpp * self)
775 {
776   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
777
778   if (!g_atomic_int_get (&self->rebuild_filters))
779     return;
780
781   gst_va_filter_drop_filter_buffers (btrans->filter);
782   _build_filters (self);
783   g_atomic_int_set (&self->rebuild_filters, FALSE);
784 }
785
786 static void
787 gst_va_vpp_before_transform (GstBaseTransform * trans, GstBuffer * inbuf)
788 {
789   GstVaVpp *self = GST_VA_VPP (trans);
790   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
791   GstClockTime ts, stream_time;
792   gboolean is_passthrough;
793
794   ts = GST_BUFFER_TIMESTAMP (inbuf);
795   stream_time =
796       gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME, ts);
797
798   GST_TRACE_OBJECT (self, "sync to %" GST_TIME_FORMAT, GST_TIME_ARGS (ts));
799
800   if (GST_CLOCK_TIME_IS_VALID (stream_time))
801     gst_object_sync_values (GST_OBJECT (self), stream_time);
802
803   gst_va_vpp_rebuild_filters (self);
804   gst_va_vpp_update_passthrough (self, TRUE);
805
806   /* cropping is only enabled if vapostproc is not in passthrough */
807   is_passthrough = gst_base_transform_is_passthrough (trans);
808   GST_OBJECT_LOCK (self);
809   if (!is_passthrough && gst_buffer_get_video_crop_meta (inbuf)) {
810     self->op_flags |= VPP_CONVERT_CROP;
811   } else {
812     self->op_flags &= ~VPP_CONVERT_CROP;
813   }
814   gst_va_filter_enable_cropping (btrans->filter,
815       (self->op_flags & VPP_CONVERT_CROP));
816   GST_OBJECT_UNLOCK (self);
817 }
818
819 static GstFlowReturn
820 gst_va_vpp_transform (GstBaseTransform * trans, GstBuffer * inbuf,
821     GstBuffer * outbuf)
822 {
823   GstVaVpp *self = GST_VA_VPP (trans);
824   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (trans);
825   GstBuffer *buf = NULL;
826   GstFlowReturn res = GST_FLOW_OK;
827   GstVaSample src, dst;
828
829   if (G_UNLIKELY (!btrans->negotiated))
830     goto unknown_format;
831
832   res = gst_va_base_transform_import_buffer (btrans, inbuf, &buf);
833   if (res != GST_FLOW_OK)
834     return res;
835
836   /* *INDENT-OFF* */
837   src = (GstVaSample) {
838     .buffer = buf,
839     .flags = gst_va_buffer_get_surface_flags (buf, &btrans->in_info),
840   };
841
842   dst = (GstVaSample) {
843     .buffer = outbuf,
844     .borders_h = self->borders_h,
845     .borders_w = self->borders_w,
846     .flags = gst_va_buffer_get_surface_flags (outbuf, &btrans->out_info),
847   };
848   /* *INDENT-ON* */
849
850   if (!gst_va_filter_process (btrans->filter, &src, &dst)) {
851     gst_buffer_set_flags (outbuf, GST_BUFFER_FLAG_CORRUPTED);
852     res = GST_BASE_TRANSFORM_FLOW_DROPPED;
853   }
854
855   gst_buffer_unref (buf);
856
857   return res;
858
859   /* ERRORS */
860 unknown_format:
861   {
862     GST_ELEMENT_ERROR (self, CORE, NOT_IMPLEMENTED, (NULL), ("unknown format"));
863     return GST_FLOW_NOT_NEGOTIATED;
864   }
865 }
866
867 static gboolean
868 gst_va_vpp_transform_meta (GstBaseTransform * trans, GstBuffer * inbuf,
869     GstMeta * meta, GstBuffer * outbuf)
870 {
871   GstVaVpp *self = GST_VA_VPP (trans);
872   const GstMetaInfo *info = meta->info;
873   const gchar *const *tags;
874
875   tags = gst_meta_api_type_get_tags (info->api);
876
877   if (!tags)
878     return TRUE;
879
880   /* don't copy colorspace/size/orientation specific metadata */
881   if ((self->op_flags & VPP_CONVERT_FORMAT)
882       && gst_meta_api_type_has_tag (info->api, META_TAG_COLORSPACE))
883     return FALSE;
884   else if ((self->op_flags & (VPP_CONVERT_SIZE | VPP_CONVERT_CROP))
885       && gst_meta_api_type_has_tag (info->api, META_TAG_SIZE))
886     return FALSE;
887   else if ((self->op_flags & VPP_CONVERT_DIRECTION)
888       && gst_meta_api_type_has_tag (info->api, META_TAG_ORIENTATION))
889     return FALSE;
890   else if (gst_meta_api_type_has_tag (info->api, META_TAG_VIDEO))
891     return TRUE;
892
893   return FALSE;
894 }
895
896 /* In structures with supported caps features it's:
897  * + Rangified resolution size.
898  * + Rangified "pixel-aspect-ratio" if present.
899  * + Removed "format", "colorimetry", "chroma-site"
900  *
901  * Structures with unsupported caps features are copied as-is.
902  */
903 static GstCaps *
904 gst_va_vpp_caps_remove_fields (GstCaps * caps)
905 {
906   GstCaps *ret;
907   GstStructure *structure;
908   GstCapsFeatures *features;
909   gint i, j, n, m;
910
911   ret = gst_caps_new_empty ();
912
913   n = gst_caps_get_size (caps);
914   for (i = 0; i < n; i++) {
915     structure = gst_caps_get_structure (caps, i);
916     features = gst_caps_get_features (caps, i);
917
918     /* If this is already expressed by the existing caps
919      * skip this structure */
920     if (i > 0 && gst_caps_is_subset_structure_full (ret, structure, features))
921       continue;
922
923     structure = gst_structure_copy (structure);
924
925     m = gst_caps_features_get_size (features);
926     for (j = 0; j < m; j++) {
927       const gchar *feature = gst_caps_features_get_nth (features, j);
928
929       if (g_strcmp0 (feature, GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY) == 0
930           || g_strcmp0 (feature, GST_CAPS_FEATURE_MEMORY_DMABUF) == 0
931           || g_strcmp0 (feature, GST_CAPS_FEATURE_MEMORY_VA) == 0) {
932
933         /* rangify frame size */
934         gst_structure_set (structure, "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
935             "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
936
937         /* if pixel aspect ratio, make a range of it */
938         if (gst_structure_has_field (structure, "pixel-aspect-ratio")) {
939           gst_structure_set (structure, "pixel-aspect-ratio",
940               GST_TYPE_FRACTION_RANGE, 1, G_MAXINT, G_MAXINT, 1, NULL);
941         }
942
943         /* remove format-related fields */
944         gst_structure_remove_fields (structure, "format", "colorimetry",
945             "chroma-site", NULL);
946
947         break;
948       }
949     }
950
951     gst_caps_append_structure_full (ret, structure,
952         gst_caps_features_copy (features));
953   }
954
955   return ret;
956 }
957
958 /* Returns all structures in @caps without @feature_name but now with
959  * @feature_name */
960 static GstCaps *
961 gst_va_vpp_complete_caps_features (const GstCaps * caps,
962     const gchar * feature_name)
963 {
964   guint i, j, m, n;
965   GstCaps *tmp;
966
967   tmp = gst_caps_new_empty ();
968
969   n = gst_caps_get_size (caps);
970   for (i = 0; i < n; i++) {
971     GstCapsFeatures *features, *orig_features;
972     GstStructure *s = gst_caps_get_structure (caps, i);
973     gboolean contained = FALSE;
974
975     orig_features = gst_caps_get_features (caps, i);
976     features = gst_caps_features_new (feature_name, NULL);
977
978     m = gst_caps_features_get_size (orig_features);
979     for (j = 0; j < m; j++) {
980       const gchar *feature = gst_caps_features_get_nth (orig_features, j);
981
982       /* if we already have the features */
983       if (gst_caps_features_contains (features, feature)) {
984         contained = TRUE;
985         break;
986       }
987     }
988
989     if (!contained && !gst_caps_is_subset_structure_full (tmp, s, features))
990       gst_caps_append_structure_full (tmp, gst_structure_copy (s), features);
991     else
992       gst_caps_features_free (features);
993   }
994
995   return tmp;
996 }
997
998 static GstCaps *
999 gst_va_vpp_transform_caps (GstBaseTransform * trans, GstPadDirection direction,
1000     GstCaps * caps, GstCaps * filter)
1001 {
1002   GstVaVpp *self = GST_VA_VPP (trans);
1003   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (trans);
1004   GstCaps *ret, *tmp, *filter_caps;
1005
1006   GST_DEBUG_OBJECT (self,
1007       "Transforming caps %" GST_PTR_FORMAT " in direction %s", caps,
1008       (direction == GST_PAD_SINK) ? "sink" : "src");
1009
1010   filter_caps = gst_va_base_transform_get_filter_caps (btrans);
1011   if (filter_caps && !gst_caps_can_intersect (caps, filter_caps)) {
1012     ret = gst_caps_ref (caps);
1013     goto bail;
1014   }
1015
1016   ret = gst_va_vpp_caps_remove_fields (caps);
1017
1018   tmp = gst_va_vpp_complete_caps_features (ret, GST_CAPS_FEATURE_MEMORY_VA);
1019   if (!gst_caps_is_subset (tmp, ret)) {
1020     gst_caps_append (ret, tmp);
1021   } else {
1022     gst_caps_unref (tmp);
1023   }
1024
1025   tmp = gst_va_vpp_complete_caps_features (ret, GST_CAPS_FEATURE_MEMORY_DMABUF);
1026   if (!gst_caps_is_subset (tmp, ret)) {
1027     gst_caps_append (ret, tmp);
1028   } else {
1029     gst_caps_unref (tmp);
1030   }
1031
1032   tmp = gst_va_vpp_complete_caps_features (ret,
1033       GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY);
1034   if (!gst_caps_is_subset (tmp, ret)) {
1035     gst_caps_append (ret, tmp);
1036   } else {
1037     gst_caps_unref (tmp);
1038   }
1039
1040 bail:
1041   if (filter) {
1042     GstCaps *intersection;
1043
1044     intersection =
1045         gst_caps_intersect_full (filter, ret, GST_CAPS_INTERSECT_FIRST);
1046     gst_caps_unref (ret);
1047     ret = intersection;
1048   }
1049
1050   GST_DEBUG_OBJECT (trans, "returning caps: %" GST_PTR_FORMAT, ret);
1051
1052   return ret;
1053 }
1054
1055 /*
1056  * This is an incomplete matrix of in formats and a score for the preferred output
1057  * format.
1058  *
1059  *         out: RGB24   RGB16  ARGB  AYUV  YUV444  YUV422 YUV420 YUV411 YUV410  PAL  GRAY
1060  *  in
1061  * RGB24          0      2       1     2     2       3      4      5      6      7    8
1062  * RGB16          1      0       1     2     2       3      4      5      6      7    8
1063  * ARGB           2      3       0     1     4       5      6      7      8      9    10
1064  * AYUV           3      4       1     0     2       5      6      7      8      9    10
1065  * YUV444         2      4       3     1     0       5      6      7      8      9    10
1066  * YUV422         3      5       4     2     1       0      6      7      8      9    10
1067  * YUV420         4      6       5     3     2       1      0      7      8      9    10
1068  * YUV411         4      6       5     3     2       1      7      0      8      9    10
1069  * YUV410         6      8       7     5     4       3      2      1      0      9    10
1070  * PAL            1      3       2     6     4       6      7      8      9      0    10
1071  * GRAY           1      4       3     2     1       5      6      7      8      9    0
1072  *
1073  * PAL or GRAY are never preferred, if we can we would convert to PAL instead
1074  * of GRAY, though
1075  * less subsampling is preferred and if any, preferably horizontal
1076  * We would like to keep the alpha, even if we would need to to colorspace conversion
1077  * or lose depth.
1078  */
1079 #define SCORE_FORMAT_CHANGE       1
1080 #define SCORE_DEPTH_CHANGE        1
1081 #define SCORE_ALPHA_CHANGE        1
1082 #define SCORE_CHROMA_W_CHANGE     1
1083 #define SCORE_CHROMA_H_CHANGE     1
1084 #define SCORE_PALETTE_CHANGE      1
1085
1086 #define SCORE_COLORSPACE_LOSS     2     /* RGB <-> YUV */
1087 #define SCORE_DEPTH_LOSS          4     /* change bit depth */
1088 #define SCORE_ALPHA_LOSS          8     /* lose the alpha channel */
1089 #define SCORE_CHROMA_W_LOSS      16     /* vertical subsample */
1090 #define SCORE_CHROMA_H_LOSS      32     /* horizontal subsample */
1091 #define SCORE_PALETTE_LOSS       64     /* convert to palette format */
1092 #define SCORE_COLOR_LOSS        128     /* convert to GRAY */
1093
1094 #define COLORSPACE_MASK (GST_VIDEO_FORMAT_FLAG_YUV | \
1095                          GST_VIDEO_FORMAT_FLAG_RGB | GST_VIDEO_FORMAT_FLAG_GRAY)
1096 #define ALPHA_MASK      (GST_VIDEO_FORMAT_FLAG_ALPHA)
1097 #define PALETTE_MASK    (GST_VIDEO_FORMAT_FLAG_PALETTE)
1098
1099 /* calculate how much loss a conversion would be */
1100 static gboolean
1101 score_value (GstVaVpp * self, const GstVideoFormatInfo * in_info,
1102     GstVideoFormat format, gint * min_loss,
1103     const GstVideoFormatInfo ** out_info)
1104 {
1105   const GstVideoFormatInfo *t_info;
1106   GstVideoFormatFlags in_flags, t_flags;
1107   gint loss;
1108
1109   t_info = gst_video_format_get_info (format);
1110   if (!t_info || t_info->format == GST_VIDEO_FORMAT_UNKNOWN)
1111     return FALSE;
1112
1113   /* accept input format immediately without loss */
1114   if (in_info == t_info) {
1115     *min_loss = 0;
1116     *out_info = t_info;
1117     return TRUE;
1118   }
1119
1120   loss = SCORE_FORMAT_CHANGE;
1121
1122   in_flags = GST_VIDEO_FORMAT_INFO_FLAGS (in_info);
1123   in_flags &= ~GST_VIDEO_FORMAT_FLAG_LE;
1124   in_flags &= ~GST_VIDEO_FORMAT_FLAG_COMPLEX;
1125   in_flags &= ~GST_VIDEO_FORMAT_FLAG_UNPACK;
1126
1127   t_flags = GST_VIDEO_FORMAT_INFO_FLAGS (t_info);
1128   t_flags &= ~GST_VIDEO_FORMAT_FLAG_LE;
1129   t_flags &= ~GST_VIDEO_FORMAT_FLAG_COMPLEX;
1130   t_flags &= ~GST_VIDEO_FORMAT_FLAG_UNPACK;
1131
1132   if ((t_flags & PALETTE_MASK) != (in_flags & PALETTE_MASK)) {
1133     loss += SCORE_PALETTE_CHANGE;
1134     if (t_flags & PALETTE_MASK)
1135       loss += SCORE_PALETTE_LOSS;
1136   }
1137
1138   if ((t_flags & COLORSPACE_MASK) != (in_flags & COLORSPACE_MASK)) {
1139     loss += SCORE_COLORSPACE_LOSS;
1140     if (t_flags & GST_VIDEO_FORMAT_FLAG_GRAY)
1141       loss += SCORE_COLOR_LOSS;
1142   }
1143
1144   if ((t_flags & ALPHA_MASK) != (in_flags & ALPHA_MASK)) {
1145     loss += SCORE_ALPHA_CHANGE;
1146     if (in_flags & ALPHA_MASK)
1147       loss += SCORE_ALPHA_LOSS;
1148   }
1149
1150   if ((in_info->h_sub[1]) != (t_info->h_sub[1])) {
1151     loss += SCORE_CHROMA_H_CHANGE;
1152     if ((in_info->h_sub[1]) < (t_info->h_sub[1]))
1153       loss += SCORE_CHROMA_H_LOSS;
1154   }
1155   if ((in_info->w_sub[1]) != (t_info->w_sub[1])) {
1156     loss += SCORE_CHROMA_W_CHANGE;
1157     if ((in_info->w_sub[1]) < (t_info->w_sub[1]))
1158       loss += SCORE_CHROMA_W_LOSS;
1159   }
1160
1161   if ((in_info->bits) != (t_info->bits)) {
1162     loss += SCORE_DEPTH_CHANGE;
1163     if ((in_info->bits) > (t_info->bits))
1164       loss += SCORE_DEPTH_LOSS;
1165   }
1166
1167   GST_DEBUG_OBJECT (self, "score %s -> %s = %d",
1168       GST_VIDEO_FORMAT_INFO_NAME (in_info),
1169       GST_VIDEO_FORMAT_INFO_NAME (t_info), loss);
1170
1171   if (loss < *min_loss) {
1172     GST_DEBUG_OBJECT (self, "found new best %d", loss);
1173     *out_info = t_info;
1174     *min_loss = loss;
1175     return TRUE;
1176   }
1177
1178   return FALSE;
1179 }
1180
1181 static GstCaps *
1182 gst_va_vpp_fixate_format (GstVaVpp * self, GstCaps * caps, GstCaps * result)
1183 {
1184   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
1185   GstStructure *ins;
1186   const gchar *in_format;
1187   const GstVideoFormatInfo *in_info, *out_info = NULL;
1188   GstCapsFeatures *features;
1189   GstVideoFormat fmt;
1190   gint min_loss = G_MAXINT;
1191   guint i, best_i, capslen;
1192
1193   ins = gst_caps_get_structure (caps, 0);
1194   in_format = gst_structure_get_string (ins, "format");
1195   if (!in_format)
1196     return NULL;
1197
1198   GST_DEBUG_OBJECT (self, "source format %s", in_format);
1199
1200   in_info =
1201       gst_video_format_get_info (gst_video_format_from_string (in_format));
1202   if (!in_info)
1203     return NULL;
1204
1205   best_i = 0;
1206   capslen = gst_caps_get_size (result);
1207   GST_DEBUG_OBJECT (self, "iterate %d structures", capslen);
1208   for (i = 0; i < capslen; i++) {
1209     GstStructure *tests;
1210     const GValue *format;
1211
1212     tests = gst_caps_get_structure (result, i);
1213     format = gst_structure_get_value (tests, "format");
1214     /* should not happen */
1215     if (format == NULL)
1216       continue;
1217
1218     features = gst_caps_get_features (result, i);
1219
1220     if (GST_VALUE_HOLDS_LIST (format)) {
1221       gint j, len;
1222
1223       len = gst_value_list_get_size (format);
1224       GST_DEBUG_OBJECT (self, "have %d formats", len);
1225       for (j = 0; j < len; j++) {
1226         const GValue *val;
1227
1228         val = gst_value_list_get_value (format, j);
1229         if (G_VALUE_HOLDS_STRING (val)) {
1230           fmt = gst_video_format_from_string (g_value_get_string (val));
1231           if (!gst_va_filter_has_video_format (btrans->filter, fmt, features))
1232             continue;
1233           if (score_value (self, in_info, fmt, &min_loss, &out_info))
1234             best_i = i;
1235           if (min_loss == 0)
1236             break;
1237         }
1238       }
1239     } else if (G_VALUE_HOLDS_STRING (format)) {
1240       fmt = gst_video_format_from_string (g_value_get_string (format));
1241       if (!gst_va_filter_has_video_format (btrans->filter, fmt, features))
1242         continue;
1243       if (score_value (self, in_info, fmt, &min_loss, &out_info))
1244         best_i = i;
1245     }
1246
1247     if (min_loss == 0)
1248       break;
1249   }
1250
1251   if (out_info) {
1252     GstCaps *ret;
1253     GstStructure *out;
1254
1255     features = gst_caps_features_copy (gst_caps_get_features (result, best_i));
1256     out = gst_structure_copy (gst_caps_get_structure (result, best_i));
1257     gst_structure_set (out, "format", G_TYPE_STRING,
1258         GST_VIDEO_FORMAT_INFO_NAME (out_info), NULL);
1259     ret = gst_caps_new_full (out, NULL);
1260     gst_caps_set_features_simple (ret, features);
1261     return ret;
1262   }
1263
1264   return NULL;
1265 }
1266
1267 static void
1268 gst_va_vpp_fixate_size (GstVaVpp * self, GstPadDirection direction,
1269     GstCaps * caps, GstCaps * othercaps)
1270 {
1271   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
1272   GstStructure *ins, *outs;
1273   const GValue *from_par, *to_par;
1274   GValue fpar = { 0, };
1275   GValue tpar = { 0, };
1276
1277   ins = gst_caps_get_structure (caps, 0);
1278   outs = gst_caps_get_structure (othercaps, 0);
1279
1280   from_par = gst_structure_get_value (ins, "pixel-aspect-ratio");
1281   to_par = gst_structure_get_value (outs, "pixel-aspect-ratio");
1282
1283   /* If we're fixating from the sinkpad we always set the PAR and
1284    * assume that missing PAR on the sinkpad means 1/1 and
1285    * missing PAR on the srcpad means undefined
1286    */
1287   if (direction == GST_PAD_SINK) {
1288     if (!from_par) {
1289       g_value_init (&fpar, GST_TYPE_FRACTION);
1290       gst_value_set_fraction (&fpar, 1, 1);
1291       from_par = &fpar;
1292     }
1293     if (!to_par) {
1294       g_value_init (&tpar, GST_TYPE_FRACTION_RANGE);
1295       gst_value_set_fraction_range_full (&tpar, 1, G_MAXINT, G_MAXINT, 1);
1296       to_par = &tpar;
1297     }
1298   } else {
1299     if (!to_par) {
1300       g_value_init (&tpar, GST_TYPE_FRACTION);
1301       gst_value_set_fraction (&tpar, 1, 1);
1302       to_par = &tpar;
1303
1304       gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
1305           NULL);
1306     }
1307     if (!from_par) {
1308       g_value_init (&fpar, GST_TYPE_FRACTION);
1309       gst_value_set_fraction (&fpar, 1, 1);
1310       from_par = &fpar;
1311     }
1312   }
1313
1314   /* we have both PAR but they might not be fixated */
1315   {
1316     gint from_w, from_h, from_par_n, from_par_d, to_par_n, to_par_d;
1317     gint w = 0, h = 0;
1318     gint from_dar_n, from_dar_d;
1319     gint num, den;
1320
1321     /* from_par should be fixed */
1322     g_return_if_fail (gst_value_is_fixed (from_par));
1323
1324     from_par_n = gst_value_get_fraction_numerator (from_par);
1325     from_par_d = gst_value_get_fraction_denominator (from_par);
1326
1327     gst_structure_get_int (ins, "width", &from_w);
1328     gst_structure_get_int (ins, "height", &from_h);
1329
1330     gst_structure_get_int (outs, "width", &w);
1331     gst_structure_get_int (outs, "height", &h);
1332
1333     /* if video-orientation changes */
1334     switch (gst_va_filter_get_orientation (btrans->filter)) {
1335       case GST_VIDEO_ORIENTATION_90R:
1336       case GST_VIDEO_ORIENTATION_90L:
1337       case GST_VIDEO_ORIENTATION_UL_LR:
1338       case GST_VIDEO_ORIENTATION_UR_LL:
1339         SWAP_INT (from_w, from_h);
1340         SWAP_INT (from_par_n, from_par_d);
1341         break;
1342       default:
1343         break;
1344     }
1345
1346     /* if both width and height are already fixed, we can't do anything
1347      * about it anymore */
1348     if (w && h) {
1349       guint n, d;
1350
1351       GST_DEBUG_OBJECT (self, "dimensions already set to %dx%d, not fixating",
1352           w, h);
1353       if (!gst_value_is_fixed (to_par)) {
1354         if (gst_video_calculate_display_ratio (&n, &d, from_w, from_h,
1355                 from_par_n, from_par_d, w, h)) {
1356           GST_DEBUG_OBJECT (self, "fixating to_par to %dx%d", n, d);
1357           if (gst_structure_has_field (outs, "pixel-aspect-ratio"))
1358             gst_structure_fixate_field_nearest_fraction (outs,
1359                 "pixel-aspect-ratio", n, d);
1360           else if (n != d)
1361             gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1362                 n, d, NULL);
1363         }
1364       }
1365       goto done;
1366     }
1367
1368     /* Calculate input DAR */
1369     if (!gst_util_fraction_multiply (from_w, from_h, from_par_n, from_par_d,
1370             &from_dar_n, &from_dar_d)) {
1371       GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1372           ("Error calculating the output scaled size - integer overflow"));
1373       goto done;
1374     }
1375
1376     GST_DEBUG_OBJECT (self, "Input DAR is %d/%d", from_dar_n, from_dar_d);
1377
1378     /* If either width or height are fixed there's not much we
1379      * can do either except choosing a height or width and PAR
1380      * that matches the DAR as good as possible
1381      */
1382     if (h) {
1383       GstStructure *tmp;
1384       gint set_w, set_par_n, set_par_d;
1385
1386       GST_DEBUG_OBJECT (self, "height is fixed (%d)", h);
1387
1388       /* If the PAR is fixed too, there's not much to do
1389        * except choosing the width that is nearest to the
1390        * width with the same DAR */
1391       if (gst_value_is_fixed (to_par)) {
1392         to_par_n = gst_value_get_fraction_numerator (to_par);
1393         to_par_d = gst_value_get_fraction_denominator (to_par);
1394
1395         GST_DEBUG_OBJECT (self, "PAR is fixed %d/%d", to_par_n, to_par_d);
1396
1397         if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_d,
1398                 to_par_n, &num, &den)) {
1399           GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1400               ("Error calculating the output scaled size - integer overflow"));
1401           goto done;
1402         }
1403
1404         w = (guint) gst_util_uint64_scale_int_round (h, num, den);
1405         gst_structure_fixate_field_nearest_int (outs, "width", w);
1406
1407         goto done;
1408       }
1409
1410       /* The PAR is not fixed and it's quite likely that we can set
1411        * an arbitrary PAR. */
1412
1413       /* Check if we can keep the input width */
1414       tmp = gst_structure_copy (outs);
1415       gst_structure_fixate_field_nearest_int (tmp, "width", from_w);
1416       gst_structure_get_int (tmp, "width", &set_w);
1417
1418       /* Might have failed but try to keep the DAR nonetheless by
1419        * adjusting the PAR */
1420       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, h, set_w,
1421               &to_par_n, &to_par_d)) {
1422         GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1423             ("Error calculating the output scaled size - integer overflow"));
1424         gst_structure_free (tmp);
1425         goto done;
1426       }
1427
1428       if (!gst_structure_has_field (tmp, "pixel-aspect-ratio"))
1429         gst_structure_set_value (tmp, "pixel-aspect-ratio", to_par);
1430       gst_structure_fixate_field_nearest_fraction (tmp, "pixel-aspect-ratio",
1431           to_par_n, to_par_d);
1432       gst_structure_get_fraction (tmp, "pixel-aspect-ratio", &set_par_n,
1433           &set_par_d);
1434       gst_structure_free (tmp);
1435
1436       /* Check if the adjusted PAR is accepted */
1437       if (set_par_n == to_par_n && set_par_d == to_par_d) {
1438         if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1439             set_par_n != set_par_d)
1440           gst_structure_set (outs, "width", G_TYPE_INT, set_w,
1441               "pixel-aspect-ratio", GST_TYPE_FRACTION, set_par_n, set_par_d,
1442               NULL);
1443         goto done;
1444       }
1445
1446       /* Otherwise scale the width to the new PAR and check if the
1447        * adjusted with is accepted. If all that fails we can't keep
1448        * the DAR */
1449       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_par_d,
1450               set_par_n, &num, &den)) {
1451         GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1452             ("Error calculating the output scaled size - integer overflow"));
1453         goto done;
1454       }
1455
1456       w = (guint) gst_util_uint64_scale_int_round (h, num, den);
1457       gst_structure_fixate_field_nearest_int (outs, "width", w);
1458       if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1459           set_par_n != set_par_d)
1460         gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1461             set_par_n, set_par_d, NULL);
1462
1463       goto done;
1464     } else if (w) {
1465       GstStructure *tmp;
1466       gint set_h, set_par_n, set_par_d;
1467
1468       GST_DEBUG_OBJECT (self, "width is fixed (%d)", w);
1469
1470       /* If the PAR is fixed too, there's not much to do
1471        * except choosing the height that is nearest to the
1472        * height with the same DAR */
1473       if (gst_value_is_fixed (to_par)) {
1474         to_par_n = gst_value_get_fraction_numerator (to_par);
1475         to_par_d = gst_value_get_fraction_denominator (to_par);
1476
1477         GST_DEBUG_OBJECT (self, "PAR is fixed %d/%d", to_par_n, to_par_d);
1478
1479         if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_d,
1480                 to_par_n, &num, &den)) {
1481           GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1482               ("Error calculating the output scaled size - integer overflow"));
1483           goto done;
1484         }
1485
1486         h = (guint) gst_util_uint64_scale_int_round (w, den, num);
1487         gst_structure_fixate_field_nearest_int (outs, "height", h);
1488
1489         goto done;
1490       }
1491
1492       /* The PAR is not fixed and it's quite likely that we can set
1493        * an arbitrary PAR. */
1494
1495       /* Check if we can keep the input height */
1496       tmp = gst_structure_copy (outs);
1497       gst_structure_fixate_field_nearest_int (tmp, "height", from_h);
1498       gst_structure_get_int (tmp, "height", &set_h);
1499
1500       /* Might have failed but try to keep the DAR nonetheless by
1501        * adjusting the PAR */
1502       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_h, w,
1503               &to_par_n, &to_par_d)) {
1504         GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1505             ("Error calculating the output scaled size - integer overflow"));
1506         gst_structure_free (tmp);
1507         goto done;
1508       }
1509       if (!gst_structure_has_field (tmp, "pixel-aspect-ratio"))
1510         gst_structure_set_value (tmp, "pixel-aspect-ratio", to_par);
1511       gst_structure_fixate_field_nearest_fraction (tmp, "pixel-aspect-ratio",
1512           to_par_n, to_par_d);
1513       gst_structure_get_fraction (tmp, "pixel-aspect-ratio", &set_par_n,
1514           &set_par_d);
1515       gst_structure_free (tmp);
1516
1517       /* Check if the adjusted PAR is accepted */
1518       if (set_par_n == to_par_n && set_par_d == to_par_d) {
1519         if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1520             set_par_n != set_par_d)
1521           gst_structure_set (outs, "height", G_TYPE_INT, set_h,
1522               "pixel-aspect-ratio", GST_TYPE_FRACTION, set_par_n, set_par_d,
1523               NULL);
1524         goto done;
1525       }
1526
1527       /* Otherwise scale the height to the new PAR and check if the
1528        * adjusted with is accepted. If all that fails we can't keep
1529        * the DAR */
1530       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_par_d,
1531               set_par_n, &num, &den)) {
1532         GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1533             ("Error calculating the output scale sized - integer overflow"));
1534         goto done;
1535       }
1536
1537       h = (guint) gst_util_uint64_scale_int_round (w, den, num);
1538       gst_structure_fixate_field_nearest_int (outs, "height", h);
1539       if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1540           set_par_n != set_par_d)
1541         gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1542             set_par_n, set_par_d, NULL);
1543
1544       goto done;
1545     } else if (gst_value_is_fixed (to_par)) {
1546       GstStructure *tmp;
1547       gint set_h, set_w, f_h, f_w;
1548
1549       to_par_n = gst_value_get_fraction_numerator (to_par);
1550       to_par_d = gst_value_get_fraction_denominator (to_par);
1551
1552       /* Calculate scale factor for the PAR change */
1553       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_n,
1554               to_par_d, &num, &den)) {
1555         GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1556             ("Error calculating the output scaled size - integer overflow"));
1557         goto done;
1558       }
1559
1560       /* Try to keep the input height (because of interlacing) */
1561       tmp = gst_structure_copy (outs);
1562       gst_structure_fixate_field_nearest_int (tmp, "height", from_h);
1563       gst_structure_get_int (tmp, "height", &set_h);
1564
1565       /* This might have failed but try to scale the width
1566        * to keep the DAR nonetheless */
1567       w = (guint) gst_util_uint64_scale_int_round (set_h, num, den);
1568       gst_structure_fixate_field_nearest_int (tmp, "width", w);
1569       gst_structure_get_int (tmp, "width", &set_w);
1570       gst_structure_free (tmp);
1571
1572       /* We kept the DAR and the height is nearest to the original height */
1573       if (set_w == w) {
1574         gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
1575             G_TYPE_INT, set_h, NULL);
1576         goto done;
1577       }
1578
1579       f_h = set_h;
1580       f_w = set_w;
1581
1582       /* If the former failed, try to keep the input width at least */
1583       tmp = gst_structure_copy (outs);
1584       gst_structure_fixate_field_nearest_int (tmp, "width", from_w);
1585       gst_structure_get_int (tmp, "width", &set_w);
1586
1587       /* This might have failed but try to scale the width
1588        * to keep the DAR nonetheless */
1589       h = (guint) gst_util_uint64_scale_int_round (set_w, den, num);
1590       gst_structure_fixate_field_nearest_int (tmp, "height", h);
1591       gst_structure_get_int (tmp, "height", &set_h);
1592       gst_structure_free (tmp);
1593
1594       /* We kept the DAR and the width is nearest to the original width */
1595       if (set_h == h) {
1596         gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
1597             G_TYPE_INT, set_h, NULL);
1598         goto done;
1599       }
1600
1601       /* If all this failed, keep the dimensions with the DAR that was closest
1602        * to the correct DAR. This changes the DAR but there's not much else to
1603        * do here.
1604        */
1605       if (set_w * ABS (set_h - h) < ABS (f_w - w) * f_h) {
1606         f_h = set_h;
1607         f_w = set_w;
1608       }
1609       gst_structure_set (outs, "width", G_TYPE_INT, f_w, "height", G_TYPE_INT,
1610           f_h, NULL);
1611       goto done;
1612     } else {
1613       GstStructure *tmp;
1614       gint set_h, set_w, set_par_n, set_par_d, tmp2;
1615
1616       /* width, height and PAR are not fixed but passthrough is not possible */
1617
1618       /* First try to keep the height and width as good as possible
1619        * and scale PAR */
1620       tmp = gst_structure_copy (outs);
1621       gst_structure_fixate_field_nearest_int (tmp, "height", from_h);
1622       gst_structure_get_int (tmp, "height", &set_h);
1623       gst_structure_fixate_field_nearest_int (tmp, "width", from_w);
1624       gst_structure_get_int (tmp, "width", &set_w);
1625
1626       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_h, set_w,
1627               &to_par_n, &to_par_d)) {
1628         GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1629             ("Error calculating the output scaled size - integer overflow"));
1630         gst_structure_free (tmp);
1631         goto done;
1632       }
1633
1634       if (!gst_structure_has_field (tmp, "pixel-aspect-ratio"))
1635         gst_structure_set_value (tmp, "pixel-aspect-ratio", to_par);
1636       gst_structure_fixate_field_nearest_fraction (tmp, "pixel-aspect-ratio",
1637           to_par_n, to_par_d);
1638       gst_structure_get_fraction (tmp, "pixel-aspect-ratio", &set_par_n,
1639           &set_par_d);
1640       gst_structure_free (tmp);
1641
1642       if (set_par_n == to_par_n && set_par_d == to_par_d) {
1643         gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
1644             G_TYPE_INT, set_h, NULL);
1645
1646         if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1647             set_par_n != set_par_d)
1648           gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1649               set_par_n, set_par_d, NULL);
1650         goto done;
1651       }
1652
1653       /* Otherwise try to scale width to keep the DAR with the set
1654        * PAR and height */
1655       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_par_d,
1656               set_par_n, &num, &den)) {
1657         GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, (NULL),
1658             ("Error calculating the output scaled size - integer overflow"));
1659         goto done;
1660       }
1661
1662       w = (guint) gst_util_uint64_scale_int_round (set_h, num, den);
1663       tmp = gst_structure_copy (outs);
1664       gst_structure_fixate_field_nearest_int (tmp, "width", w);
1665       gst_structure_get_int (tmp, "width", &tmp2);
1666       gst_structure_free (tmp);
1667
1668       if (tmp2 == w) {
1669         gst_structure_set (outs, "width", G_TYPE_INT, tmp2, "height",
1670             G_TYPE_INT, set_h, NULL);
1671         if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1672             set_par_n != set_par_d)
1673           gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1674               set_par_n, set_par_d, NULL);
1675         goto done;
1676       }
1677
1678       /* ... or try the same with the height */
1679       h = (guint) gst_util_uint64_scale_int_round (set_w, den, num);
1680       tmp = gst_structure_copy (outs);
1681       gst_structure_fixate_field_nearest_int (tmp, "height", h);
1682       gst_structure_get_int (tmp, "height", &tmp2);
1683       gst_structure_free (tmp);
1684
1685       if (tmp2 == h) {
1686         gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
1687             G_TYPE_INT, tmp2, NULL);
1688         if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1689             set_par_n != set_par_d)
1690           gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1691               set_par_n, set_par_d, NULL);
1692         goto done;
1693       }
1694
1695       /* If all fails we can't keep the DAR and take the nearest values
1696        * for everything from the first try */
1697       gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
1698           G_TYPE_INT, set_h, NULL);
1699       if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1700           set_par_n != set_par_d)
1701         gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1702             set_par_n, set_par_d, NULL);
1703     }
1704   }
1705
1706 done:
1707   if (from_par == &fpar)
1708     g_value_unset (&fpar);
1709   if (to_par == &tpar)
1710     g_value_unset (&tpar);
1711 }
1712
1713 static gboolean
1714 subsampling_unchanged (GstVideoInfo * in_info, GstVideoInfo * out_info)
1715 {
1716   gint i;
1717   const GstVideoFormatInfo *in_format, *out_format;
1718
1719   if (GST_VIDEO_INFO_N_COMPONENTS (in_info) !=
1720       GST_VIDEO_INFO_N_COMPONENTS (out_info))
1721     return FALSE;
1722
1723   in_format = in_info->finfo;
1724   out_format = out_info->finfo;
1725
1726   for (i = 0; i < GST_VIDEO_INFO_N_COMPONENTS (in_info); i++) {
1727     if (GST_VIDEO_FORMAT_INFO_W_SUB (in_format,
1728             i) != GST_VIDEO_FORMAT_INFO_W_SUB (out_format, i))
1729       return FALSE;
1730     if (GST_VIDEO_FORMAT_INFO_H_SUB (in_format,
1731             i) != GST_VIDEO_FORMAT_INFO_H_SUB (out_format, i))
1732       return FALSE;
1733   }
1734
1735   return TRUE;
1736 }
1737
1738 static void
1739 transfer_colorimetry_from_input (GstVaVpp * self, GstCaps * in_caps,
1740     GstCaps * out_caps)
1741 {
1742   GstStructure *out_caps_s = gst_caps_get_structure (out_caps, 0);
1743   GstStructure *in_caps_s = gst_caps_get_structure (in_caps, 0);
1744   gboolean have_colorimetry =
1745       gst_structure_has_field (out_caps_s, "colorimetry");
1746   gboolean have_chroma_site =
1747       gst_structure_has_field (out_caps_s, "chroma-site");
1748
1749   /* If the output already has colorimetry and chroma-site, stop,
1750    * otherwise try and transfer what we can from the input caps */
1751   if (have_colorimetry && have_chroma_site)
1752     return;
1753
1754   {
1755     GstVideoInfo in_info, out_info;
1756     const GValue *in_colorimetry =
1757         gst_structure_get_value (in_caps_s, "colorimetry");
1758
1759     if (!gst_video_info_from_caps (&in_info, in_caps)) {
1760       GST_WARNING_OBJECT (self,
1761           "Failed to convert sink pad caps to video info");
1762       return;
1763     }
1764     if (!gst_video_info_from_caps (&out_info, out_caps)) {
1765       GST_WARNING_OBJECT (self, "Failed to convert src pad caps to video info");
1766       return;
1767     }
1768
1769     if (!have_colorimetry && in_colorimetry != NULL) {
1770       if ((GST_VIDEO_INFO_IS_YUV (&out_info)
1771               && GST_VIDEO_INFO_IS_YUV (&in_info))
1772           || (GST_VIDEO_INFO_IS_RGB (&out_info)
1773               && GST_VIDEO_INFO_IS_RGB (&in_info))
1774           || (GST_VIDEO_INFO_IS_GRAY (&out_info)
1775               && GST_VIDEO_INFO_IS_GRAY (&in_info))) {
1776         /* Can transfer the colorimetry intact from the input if it has it */
1777         gst_structure_set_value (out_caps_s, "colorimetry", in_colorimetry);
1778       } else {
1779         gchar *colorimetry_str;
1780
1781         /* Changing between YUV/RGB - forward primaries and transfer function, but use
1782          * default range and matrix.
1783          * the primaries is used for conversion between RGB and XYZ (CIE 1931 coordinate).
1784          * the transfer function could be another reference (e.g., HDR)
1785          */
1786         out_info.colorimetry.primaries = in_info.colorimetry.primaries;
1787         out_info.colorimetry.transfer = in_info.colorimetry.transfer;
1788
1789         colorimetry_str =
1790             gst_video_colorimetry_to_string (&out_info.colorimetry);
1791         gst_caps_set_simple (out_caps, "colorimetry", G_TYPE_STRING,
1792             colorimetry_str, NULL);
1793         g_free (colorimetry_str);
1794       }
1795     }
1796
1797     /* Only YUV output needs chroma-site. If the input was also YUV and had the same chroma
1798      * subsampling, transfer the siting. If the sub-sampling is changing, then the planes get
1799      * scaled anyway so there's no real reason to prefer the input siting. */
1800     if (!have_chroma_site && GST_VIDEO_INFO_IS_YUV (&out_info)) {
1801       if (GST_VIDEO_INFO_IS_YUV (&in_info)) {
1802         const GValue *in_chroma_site =
1803             gst_structure_get_value (in_caps_s, "chroma-site");
1804         if (in_chroma_site != NULL
1805             && subsampling_unchanged (&in_info, &out_info))
1806           gst_structure_set_value (out_caps_s, "chroma-site", in_chroma_site);
1807       }
1808     }
1809   }
1810 }
1811
1812 static void
1813 copy_misc_fields_from_input (GstCaps * in_caps, GstCaps * out_caps)
1814 {
1815   const gchar *fields[] = { "interlace-mode", "field-order", "multiview-mode",
1816     "multiview-flags", "framerate"
1817   };
1818   GstStructure *out_caps_s = gst_caps_get_structure (out_caps, 0);
1819   GstStructure *in_caps_s = gst_caps_get_structure (in_caps, 0);
1820   int i;
1821
1822   for (i = 0; i < G_N_ELEMENTS (fields); i++) {
1823     const GValue *in_field = gst_structure_get_value (in_caps_s, fields[i]);
1824     const GValue *out_field = gst_structure_get_value (out_caps_s, fields[i]);
1825
1826     if (out_field && gst_value_is_fixed (out_field))
1827       continue;
1828
1829     if (in_field)
1830       gst_structure_set_value (out_caps_s, fields[i], in_field);
1831   }
1832 }
1833
1834 static void
1835 update_hdr_fields (GstVaVpp * self, GstCaps * result)
1836 {
1837   GstStructure *s = gst_caps_get_structure (result, 0);
1838   GstVideoInfo out_info;
1839   gboolean have_colorimetry;
1840
1841   gst_structure_remove_fields (s, "mastering-display-info",
1842       "content-light-level", "hdr-format", NULL);
1843
1844   have_colorimetry = gst_structure_has_field (s, "colorimetry");
1845   if (!have_colorimetry) {
1846     if (gst_video_info_from_caps (&out_info, result)) {
1847       gchar *colorimetry_str =
1848           gst_video_colorimetry_to_string (&out_info.colorimetry);
1849       gst_caps_set_simple (result, "colorimetry", G_TYPE_STRING,
1850           colorimetry_str, NULL);
1851       g_free (colorimetry_str);
1852     } else {
1853       GST_WARNING_OBJECT (self, "Failed to convert src pad caps to video info");
1854     }
1855   }
1856 }
1857
1858 static GstCaps *
1859 gst_va_vpp_fixate_caps (GstBaseTransform * trans, GstPadDirection direction,
1860     GstCaps * caps, GstCaps * othercaps)
1861 {
1862   GstVaVpp *self = GST_VA_VPP (trans);
1863   GstCaps *result;
1864
1865   GST_DEBUG_OBJECT (self,
1866       "trying to fixate othercaps %" GST_PTR_FORMAT " based on caps %"
1867       GST_PTR_FORMAT, othercaps, caps);
1868
1869   /* will iterate in all structures to find one with "best color" */
1870   result = gst_va_vpp_fixate_format (self, caps, othercaps);
1871   if (!result)
1872     return othercaps;
1873
1874   gst_clear_caps (&othercaps);
1875
1876   gst_va_vpp_fixate_size (self, direction, caps, result);
1877
1878   /* some fields might be lost while feature caps conversion */
1879   copy_misc_fields_from_input (caps, result);
1880
1881   /* fixate remaining fields */
1882   result = gst_caps_fixate (result);
1883
1884   if (direction == GST_PAD_SINK) {
1885     if (self->hdr_mapping)
1886       update_hdr_fields (self, result);
1887
1888     /* Try and preserve input colorimetry / chroma information */
1889     transfer_colorimetry_from_input (self, caps, result);
1890
1891     if (gst_caps_is_subset (caps, result))
1892       gst_caps_replace (&result, caps);
1893   }
1894
1895   GST_DEBUG_OBJECT (self, "fixated othercaps to %" GST_PTR_FORMAT, result);
1896
1897   return result;
1898 }
1899
1900 static void
1901 _get_scale_factor (GstVaVpp * self, gdouble * w_factor, gdouble * h_factor)
1902 {
1903   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
1904   gdouble w = GST_VIDEO_INFO_WIDTH (&btrans->out_info);
1905   gdouble h = GST_VIDEO_INFO_HEIGHT (&btrans->out_info);
1906
1907   switch (gst_va_filter_get_orientation (btrans->filter)) {
1908     case GST_VIDEO_ORIENTATION_90R:
1909     case GST_VIDEO_ORIENTATION_90L:
1910     case GST_VIDEO_ORIENTATION_UR_LL:
1911     case GST_VIDEO_ORIENTATION_UL_LR:{
1912       gdouble tmp = h;
1913       h = w;
1914       w = tmp;
1915       break;
1916     }
1917     default:
1918       break;
1919   }
1920
1921   *w_factor = GST_VIDEO_INFO_WIDTH (&btrans->in_info);
1922   *w_factor /= w;
1923
1924   *h_factor = GST_VIDEO_INFO_HEIGHT (&btrans->in_info);
1925   *h_factor /= h;
1926 }
1927
1928 static gboolean
1929 gst_va_vpp_src_event (GstBaseTransform * trans, GstEvent * event)
1930 {
1931   GstVaVpp *self = GST_VA_VPP (trans);
1932   GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (trans);
1933   const GstVideoInfo *in_info = &btrans->in_info, *out_info = &btrans->out_info;
1934   gdouble new_x = 0, new_y = 0, x = 0, y = 0, w_factor = 1, h_factor = 1;
1935   gboolean ret;
1936
1937   GST_TRACE_OBJECT (self, "handling %s event", GST_EVENT_TYPE_NAME (event));
1938
1939   switch (GST_EVENT_TYPE (event)) {
1940     case GST_EVENT_NAVIGATION:
1941       if (GST_VIDEO_INFO_WIDTH (in_info) != GST_VIDEO_INFO_WIDTH (out_info)
1942           || GST_VIDEO_INFO_HEIGHT (in_info) != GST_VIDEO_INFO_HEIGHT (out_info)
1943           || gst_va_filter_get_orientation (btrans->filter) !=
1944           GST_VIDEO_ORIENTATION_IDENTITY) {
1945
1946         if (!gst_navigation_event_get_coordinates (event, &x, &y))
1947           break;
1948
1949         event = gst_event_make_writable (event);
1950
1951         /* video-direction compensation */
1952         switch (gst_va_filter_get_orientation (btrans->filter)) {
1953           case GST_VIDEO_ORIENTATION_90R:
1954             new_x = y;
1955             new_y = GST_VIDEO_INFO_WIDTH (out_info) - 1 - x;
1956             break;
1957           case GST_VIDEO_ORIENTATION_90L:
1958             new_x = GST_VIDEO_INFO_HEIGHT (out_info) - 1 - y;
1959             new_y = x;
1960             break;
1961           case GST_VIDEO_ORIENTATION_UL_LR:
1962             new_x = y;
1963             new_y = x;
1964             break;
1965           case GST_VIDEO_ORIENTATION_UR_LL:
1966             new_x = GST_VIDEO_INFO_HEIGHT (out_info) - 1 - y;
1967             new_y = GST_VIDEO_INFO_WIDTH (out_info) - 1 - x;
1968             break;
1969           case GST_VIDEO_ORIENTATION_180:
1970             /* FIXME: is this correct? */
1971             new_x = GST_VIDEO_INFO_WIDTH (out_info) - 1 - x;
1972             new_y = GST_VIDEO_INFO_HEIGHT (out_info) - 1 - y;
1973             break;
1974           case GST_VIDEO_ORIENTATION_HORIZ:
1975             new_x = GST_VIDEO_INFO_WIDTH (out_info) - 1 - x;
1976             new_y = y;
1977             break;
1978           case GST_VIDEO_ORIENTATION_VERT:
1979             new_x = x;
1980             new_y = GST_VIDEO_INFO_HEIGHT (out_info) - 1 - y;
1981             break;
1982           default:
1983             new_x = x;
1984             new_y = y;
1985             break;
1986         }
1987
1988         /* scale compensation */
1989         _get_scale_factor (self, &w_factor, &h_factor);
1990         new_x *= w_factor;
1991         new_y *= h_factor;
1992
1993         /* crop compensation is done by videocrop */
1994
1995         GST_TRACE_OBJECT (self, "from %fx%f to %fx%f", x, y, new_x, new_y);
1996         gst_navigation_event_set_coordinates (event, new_x, new_y);
1997       }
1998       break;
1999     default:
2000       break;
2001   }
2002
2003   ret = GST_BASE_TRANSFORM_CLASS (parent_class)->src_event (trans, event);
2004
2005   return ret;
2006 }
2007
2008 static gboolean
2009 gst_va_vpp_sink_event (GstBaseTransform * trans, GstEvent * event)
2010 {
2011   GstVaVpp *self = GST_VA_VPP (trans);
2012   GstTagList *taglist;
2013   GstVideoOrientationMethod method;
2014
2015   switch (GST_EVENT_TYPE (event)) {
2016     case GST_EVENT_TAG:
2017       gst_event_parse_tag (event, &taglist);
2018
2019       if (self->direction != GST_VIDEO_ORIENTATION_AUTO)
2020         break;
2021
2022       if (!gst_video_orientation_from_tag (taglist, &method))
2023         break;
2024
2025       GST_OBJECT_LOCK (self);
2026       self->tag_direction = method;
2027       _update_properties_unlocked (self);
2028       GST_OBJECT_UNLOCK (self);
2029
2030       gst_va_vpp_update_passthrough (self, FALSE);
2031
2032       break;
2033     default:
2034       break;
2035   }
2036
2037   return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
2038 }
2039
2040 static void
2041 _install_static_properties (GObjectClass * klass)
2042 {
2043   /**
2044    * GstVaPostProc:disable-passthrough:
2045    *
2046    * If set to %TRUE the filter will not enable passthrough mode, thus
2047    * each frame will be processed. It's useful for cropping, for
2048    * example.
2049    *
2050    * Since: 1.20
2051    */
2052   PROPERTIES (PROP_DISABLE_PASSTHROUGH) =
2053       g_param_spec_boolean ("disable-passthrough", "Disable Passthrough",
2054       "Forces passing buffers through the postprocessor", FALSE,
2055       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY);
2056   g_object_class_install_property (klass, PROP_DISABLE_PASSTHROUGH,
2057       PROPERTIES (PROP_DISABLE_PASSTHROUGH));
2058
2059   /**
2060    * GstVaPostProc:add-borders:
2061    *
2062    * If set to %TRUE the filter will add black borders if necessary to
2063    * keep the display aspect ratio.
2064    *
2065    * Since: 1.20
2066    */
2067   PROPERTIES (PROP_ADD_BORDERS) = g_param_spec_boolean ("add-borders",
2068       "Add Borders",
2069       "Add black borders if necessary to keep the display aspect ratio", FALSE,
2070       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_PLAYING);
2071   g_object_class_install_property (klass, PROP_ADD_BORDERS,
2072       PROPERTIES (PROP_ADD_BORDERS));
2073
2074   /**
2075    * GstVaPostProc:scale-method
2076    *
2077    * Sets the scale method algorithm to use when resizing.
2078    *
2079    * Since: 1.22
2080    */
2081   PROPERTIES (PROP_SCALE_METHOD) = g_param_spec_enum ("scale-method",
2082       "Scale Method", "Scale method to use", GST_TYPE_VA_SCALE_METHOD,
2083       VA_FILTER_SCALING_DEFAULT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
2084       | GST_PARAM_MUTABLE_PLAYING);
2085   g_object_class_install_property (klass, PROP_SCALE_METHOD,
2086       PROPERTIES (PROP_SCALE_METHOD));
2087 }
2088
2089 static void
2090 gst_va_vpp_class_init (gpointer g_class, gpointer class_data)
2091 {
2092   GstCaps *doc_caps, *caps = NULL;
2093   GstPadTemplate *sink_pad_templ, *src_pad_templ;
2094   GObjectClass *object_class = G_OBJECT_CLASS (g_class);
2095   GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (g_class);
2096   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
2097   GstVaBaseTransformClass *btrans_class = GST_VA_BASE_TRANSFORM_CLASS (g_class);
2098   GstVaDisplay *display;
2099   GstVaFilter *filter;
2100   struct CData *cdata = class_data;
2101   gchar *long_name;
2102   GString *klass;
2103
2104   parent_class = g_type_class_peek_parent (g_class);
2105
2106   btrans_class->render_device_path = g_strdup (cdata->render_device_path);
2107
2108   if (cdata->description) {
2109     long_name = g_strdup_printf ("VA-API Video Postprocessor in %s",
2110         cdata->description);
2111   } else {
2112     long_name = g_strdup ("VA-API Video Postprocessor");
2113   }
2114
2115   klass = g_string_new ("Converter/Filter/Colorspace/Scaler/Video/Hardware");
2116
2117   display = gst_va_display_drm_new_from_path (btrans_class->render_device_path);
2118   filter = gst_va_filter_new (display);
2119
2120   if (gst_va_filter_open (filter)) {
2121     caps = gst_va_filter_get_caps (filter);
2122
2123     /* adds any to enable passthrough */
2124     {
2125       GstCaps *any_caps = gst_caps_new_empty_simple ("video/x-raw");
2126       gst_caps_set_features_simple (any_caps, gst_caps_features_new_any ());
2127       caps = gst_caps_merge (caps, any_caps);
2128     }
2129
2130     /* add converter klass */
2131     {
2132       int i;
2133       VAProcFilterType types[] = { VAProcFilterColorBalance,
2134         VAProcFilterSkinToneEnhancement, VAProcFilterSharpening,
2135         VAProcFilterNoiseReduction
2136       };
2137
2138       for (i = 0; i < G_N_ELEMENTS (types); i++) {
2139         if (gst_va_filter_has_filter (filter, types[i])) {
2140           g_string_prepend (klass, "Effect/");
2141           break;
2142         }
2143       }
2144     }
2145   } else {
2146     caps = gst_caps_from_string (caps_str);
2147   }
2148
2149   gst_element_class_set_metadata (element_class, long_name, klass->str,
2150       "VA-API based video postprocessor",
2151       "Víctor Jáquez <vjaquez@igalia.com>");
2152
2153   g_string_free (klass, TRUE);
2154
2155   doc_caps = gst_caps_from_string (caps_str);
2156
2157   sink_pad_templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
2158       caps);
2159   gst_element_class_add_pad_template (element_class, sink_pad_templ);
2160   gst_pad_template_set_documentation_caps (sink_pad_templ,
2161       gst_caps_ref (doc_caps));
2162
2163   src_pad_templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
2164       caps);
2165   gst_element_class_add_pad_template (element_class, src_pad_templ);
2166   gst_pad_template_set_documentation_caps (src_pad_templ,
2167       gst_caps_ref (doc_caps));
2168   gst_caps_unref (doc_caps);
2169
2170   gst_caps_unref (caps);
2171
2172   object_class->dispose = gst_va_vpp_dispose;
2173   object_class->set_property = gst_va_vpp_set_property;
2174   object_class->get_property = gst_va_vpp_get_property;
2175
2176   trans_class->propose_allocation =
2177       GST_DEBUG_FUNCPTR (gst_va_vpp_propose_allocation);
2178   trans_class->transform_caps = GST_DEBUG_FUNCPTR (gst_va_vpp_transform_caps);
2179   trans_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_va_vpp_fixate_caps);
2180   trans_class->before_transform =
2181       GST_DEBUG_FUNCPTR (gst_va_vpp_before_transform);
2182   trans_class->transform = GST_DEBUG_FUNCPTR (gst_va_vpp_transform);
2183   trans_class->transform_meta = GST_DEBUG_FUNCPTR (gst_va_vpp_transform_meta);
2184   trans_class->src_event = GST_DEBUG_FUNCPTR (gst_va_vpp_src_event);
2185   trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_va_vpp_sink_event);
2186
2187   trans_class->transform_ip_on_passthrough = FALSE;
2188
2189   btrans_class->set_info = GST_DEBUG_FUNCPTR (gst_va_vpp_set_info);
2190   btrans_class->update_properties =
2191       GST_DEBUG_FUNCPTR (gst_va_vpp_update_properties);
2192
2193   gst_va_filter_install_properties (filter, object_class);
2194
2195   _install_static_properties (object_class);
2196
2197   g_free (long_name);
2198   g_free (cdata->description);
2199   g_free (cdata->render_device_path);
2200   g_free (cdata);
2201   gst_object_unref (filter);
2202   gst_object_unref (display);
2203 }
2204
2205 static inline void
2206 _create_colorbalance_channel (GstVaVpp * self, const gchar * label)
2207 {
2208   GstColorBalanceChannel *channel;
2209
2210   channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL);
2211   channel->label = g_strdup_printf ("VA-%s", label);
2212   channel->min_value = -1000;
2213   channel->max_value = 1000;
2214
2215   self->channels = g_list_append (self->channels, channel);
2216 }
2217
2218 static void
2219 gst_va_vpp_init (GTypeInstance * instance, gpointer g_class)
2220 {
2221   GstVaVpp *self = GST_VA_VPP (instance);
2222   GParamSpec *pspec;
2223
2224   self->direction = GST_VIDEO_ORIENTATION_IDENTITY;
2225   self->prev_direction = self->direction;
2226   self->tag_direction = GST_VIDEO_ORIENTATION_AUTO;
2227
2228   pspec = g_object_class_find_property (g_class, "denoise");
2229   if (pspec)
2230     self->denoise = g_value_get_float (g_param_spec_get_default_value (pspec));
2231
2232   pspec = g_object_class_find_property (g_class, "sharpen");
2233   if (pspec)
2234     self->sharpen = g_value_get_float (g_param_spec_get_default_value (pspec));
2235
2236   pspec = g_object_class_find_property (g_class, "skin-tone");
2237   if (pspec) {
2238     const GValue *value = g_param_spec_get_default_value (pspec);
2239     if (G_VALUE_TYPE (value) == G_TYPE_BOOLEAN)
2240       self->skintone = g_value_get_boolean (value);
2241     else
2242       self->skintone = g_value_get_float (value);
2243   }
2244
2245   /* color balance */
2246   pspec = g_object_class_find_property (g_class, "brightness");
2247   if (pspec) {
2248     self->brightness =
2249         g_value_get_float (g_param_spec_get_default_value (pspec));
2250     _create_colorbalance_channel (self, "BRIGHTNESS");
2251   }
2252   pspec = g_object_class_find_property (g_class, "contrast");
2253   if (pspec) {
2254     self->contrast = g_value_get_float (g_param_spec_get_default_value (pspec));
2255     _create_colorbalance_channel (self, "CONTRAST");
2256   }
2257   pspec = g_object_class_find_property (g_class, "hue");
2258   if (pspec) {
2259     self->hue = g_value_get_float (g_param_spec_get_default_value (pspec));
2260     _create_colorbalance_channel (self, "HUE");
2261   }
2262   pspec = g_object_class_find_property (g_class, "saturation");
2263   if (pspec) {
2264     self->saturation =
2265         g_value_get_float (g_param_spec_get_default_value (pspec));
2266     _create_colorbalance_channel (self, "SATURATION");
2267   }
2268
2269   /* HDR tone mapping */
2270   pspec = g_object_class_find_property (g_class, "hdr-tone-mapping");
2271   if (pspec) {
2272     self->hdr_mapping =
2273         g_value_get_boolean (g_param_spec_get_default_value (pspec));
2274   }
2275
2276   /* enable QoS */
2277   gst_base_transform_set_qos_enabled (GST_BASE_TRANSFORM (instance), TRUE);
2278 }
2279
2280 static gpointer
2281 _register_debug_category (gpointer data)
2282 {
2283   GST_DEBUG_CATEGORY_INIT (gst_va_vpp_debug, "vapostproc", 0,
2284       "VA Video Postprocessor");
2285
2286 #define D(type) \
2287   G_PASTE (META_TAG_, type) = \
2288     g_quark_from_static_string (G_PASTE (G_PASTE (GST_META_TAG_VIDEO_, type), _STR))
2289   D (COLORSPACE);
2290   D (SIZE);
2291   D (ORIENTATION);
2292 #undef D
2293   META_TAG_VIDEO = g_quark_from_static_string (GST_META_TAG_VIDEO_STR);
2294
2295   return NULL;
2296 }
2297
2298 gboolean
2299 gst_va_vpp_register (GstPlugin * plugin, GstVaDevice * device,
2300     gboolean has_colorbalance, guint rank)
2301 {
2302   static GOnce debug_once = G_ONCE_INIT;
2303   GType type;
2304   GTypeInfo type_info = {
2305     .class_size = sizeof (GstVaVppClass),
2306     .class_init = gst_va_vpp_class_init,
2307     .instance_size = sizeof (GstVaVpp),
2308     .instance_init = gst_va_vpp_init,
2309   };
2310   struct CData *cdata;
2311   gboolean ret;
2312   gchar *type_name, *feature_name;
2313
2314   g_return_val_if_fail (GST_IS_PLUGIN (plugin), FALSE);
2315   g_return_val_if_fail (GST_IS_VA_DEVICE (device), FALSE);
2316
2317   cdata = g_new (struct CData, 1);
2318   cdata->description = NULL;
2319   cdata->render_device_path = g_strdup (device->render_device_path);
2320
2321   type_info.class_data = cdata;
2322
2323   type_name = g_strdup ("GstVaPostProc");
2324   feature_name = g_strdup ("vapostproc");
2325
2326   /* The first postprocessor to be registered should use a constant
2327    * name, like vapostproc, for any additional postprocessors, we
2328    * create unique names, using inserting the render device name. */
2329   if (g_type_from_name (type_name)) {
2330     gchar *basename = g_path_get_basename (device->render_device_path);
2331     g_free (type_name);
2332     g_free (feature_name);
2333     type_name = g_strdup_printf ("GstVa%sPostProc", basename);
2334     feature_name = g_strdup_printf ("va%spostproc", basename);
2335     cdata->description = basename;
2336
2337     /* lower rank for non-first device */
2338     if (rank > 0)
2339       rank--;
2340   }
2341
2342   g_once (&debug_once, _register_debug_category, NULL);
2343
2344   type = g_type_register_static (GST_TYPE_VA_BASE_TRANSFORM, type_name,
2345       &type_info, 0);
2346
2347   if (has_colorbalance) {
2348     const GInterfaceInfo info = { gst_va_vpp_colorbalance_init, NULL, NULL };
2349     g_type_add_interface_static (type, GST_TYPE_COLOR_BALANCE, &info);
2350   }
2351
2352   ret = gst_element_register (plugin, feature_name, rank, type);
2353
2354   g_free (type_name);
2355   g_free (feature_name);
2356
2357   return ret;
2358 }
2359
2360 /* Color Balance interface */
2361 static const GList *
2362 gst_va_vpp_colorbalance_list_channels (GstColorBalance * balance)
2363 {
2364   GstVaVpp *self = GST_VA_VPP (balance);
2365
2366   return self->channels;
2367 }
2368
2369 /* This assumes --as happens with intel drivers-- that max values are
2370  * bigger than the simmetrical values of min values */
2371 static float
2372 make_max_simmetrical (GParamSpecFloat * fpspec)
2373 {
2374   gfloat max;
2375
2376   if (fpspec->default_value == 0)
2377     max = -fpspec->minimum;
2378   else
2379     max = fpspec->default_value + ABS (fpspec->minimum - fpspec->default_value);
2380
2381   return MIN (max, fpspec->maximum);
2382 }
2383
2384 static gboolean
2385 _set_cb_val (GstVaVpp * self, const gchar * name,
2386     GstColorBalanceChannel * channel, gint value, gfloat * cb)
2387 {
2388   GObjectClass *klass = G_OBJECT_CLASS (GST_VA_VPP_GET_CLASS (self));
2389   GParamSpec *pspec;
2390   GParamSpecFloat *fpspec;
2391   gfloat new_value, max;
2392   gboolean changed;
2393
2394   pspec = g_object_class_find_property (klass, name);
2395   if (!pspec)
2396     return FALSE;
2397
2398   fpspec = G_PARAM_SPEC_FLOAT (pspec);
2399   max = make_max_simmetrical (fpspec);
2400
2401   new_value = (value - channel->min_value) * (max - fpspec->minimum)
2402       / (channel->max_value - channel->min_value) + fpspec->minimum;
2403
2404   GST_OBJECT_LOCK (self);
2405   changed = new_value != *cb;
2406   *cb = new_value;
2407   value = (*cb + fpspec->minimum) * (channel->max_value - channel->min_value)
2408       / (max - fpspec->minimum) + channel->min_value;
2409   GST_OBJECT_UNLOCK (self);
2410
2411   if (changed) {
2412     GST_INFO_OBJECT (self, "%s: %d / %f", channel->label, value, new_value);
2413     gst_color_balance_value_changed (GST_COLOR_BALANCE (self), channel, value);
2414     g_atomic_int_set (&self->rebuild_filters, TRUE);
2415   }
2416
2417   return TRUE;
2418 }
2419
2420 static void
2421 gst_va_vpp_colorbalance_set_value (GstColorBalance * balance,
2422     GstColorBalanceChannel * channel, gint value)
2423 {
2424   GstVaVpp *self = GST_VA_VPP (balance);
2425
2426   if (g_str_has_suffix (channel->label, "HUE"))
2427     _set_cb_val (self, "hue", channel, value, &self->hue);
2428   else if (g_str_has_suffix (channel->label, "BRIGHTNESS"))
2429     _set_cb_val (self, "brightness", channel, value, &self->brightness);
2430   else if (g_str_has_suffix (channel->label, "CONTRAST"))
2431     _set_cb_val (self, "contrast", channel, value, &self->contrast);
2432   else if (g_str_has_suffix (channel->label, "SATURATION"))
2433     _set_cb_val (self, "saturation", channel, value, &self->saturation);
2434 }
2435
2436 static gboolean
2437 _get_cb_val (GstVaVpp * self, const gchar * name,
2438     GstColorBalanceChannel * channel, gfloat * cb, gint * val)
2439 {
2440   GObjectClass *klass = G_OBJECT_CLASS (GST_VA_VPP_GET_CLASS (self));
2441   GParamSpec *pspec;
2442   GParamSpecFloat *fpspec;
2443   gfloat max;
2444
2445   pspec = g_object_class_find_property (klass, name);
2446   if (!pspec)
2447     return FALSE;
2448
2449   fpspec = G_PARAM_SPEC_FLOAT (pspec);
2450   max = make_max_simmetrical (fpspec);
2451
2452   GST_OBJECT_LOCK (self);
2453   *val = (*cb + fpspec->minimum) * (channel->max_value - channel->min_value)
2454       / (max - fpspec->minimum) + channel->min_value;
2455   GST_OBJECT_UNLOCK (self);
2456
2457   return TRUE;
2458 }
2459
2460 static gint
2461 gst_va_vpp_colorbalance_get_value (GstColorBalance * balance,
2462     GstColorBalanceChannel * channel)
2463 {
2464   GstVaVpp *self = GST_VA_VPP (balance);
2465   gint value = 0;
2466
2467   if (g_str_has_suffix (channel->label, "HUE"))
2468     _get_cb_val (self, "hue", channel, &self->hue, &value);
2469   else if (g_str_has_suffix (channel->label, "BRIGHTNESS"))
2470     _get_cb_val (self, "brightness", channel, &self->brightness, &value);
2471   else if (g_str_has_suffix (channel->label, "CONTRAST"))
2472     _get_cb_val (self, "contrast", channel, &self->contrast, &value);
2473   else if (g_str_has_suffix (channel->label, "SATURATION"))
2474     _get_cb_val (self, "saturation", channel, &self->saturation, &value);
2475
2476   return value;
2477 }
2478
2479 static GstColorBalanceType
2480 gst_va_vpp_colorbalance_get_balance_type (GstColorBalance * balance)
2481 {
2482   return GST_COLOR_BALANCE_HARDWARE;
2483 }
2484
2485 static void
2486 gst_va_vpp_colorbalance_init (gpointer iface, gpointer data)
2487 {
2488   GstColorBalanceInterface *cbiface = iface;
2489
2490   cbiface->list_channels = gst_va_vpp_colorbalance_list_channels;
2491   cbiface->set_value = gst_va_vpp_colorbalance_set_value;
2492   cbiface->get_value = gst_va_vpp_colorbalance_get_value;
2493   cbiface->get_balance_type = gst_va_vpp_colorbalance_get_balance_type;
2494 }