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