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