/*
 *  gstvaapipostproc.c - VA-API video postprocessing
 *
 *  Copyright (C) 2012-2014 Intel Corporation
 *    Author: Gwenole Beauchesne <gwenole.beauchesne@intel.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301 USA
 */

/**
 * SECTION:element-vaapipostproc
 * @short_description: A VA-API base video postprocessing filter
 *
 * vaapipostproc consists in various postprocessing algorithms to be
 * applied to VA surfaces.
 *
 * ## Example launch line
 *
 * |[
 * gst-launch-1.0 videotestsrc ! vaapipostproc ! video/x-raw, width=1920, height=1080 ! vaapisink
 * ]|
 */

#include "gstcompat.h"
#include <gst/video/video.h>

#include <gst/vaapi/gstvaapivalue.h>

#include "gstvaapipostproc.h"
#include "gstvaapipostprocutil.h"
#include "gstvaapipluginutil.h"
#include "gstvaapivideobuffer.h"
#include "gstvaapivideobufferpool.h"
#include "gstvaapivideomemory.h"

#define GST_PLUGIN_NAME "vaapipostproc"
#define GST_PLUGIN_DESC "A VA-API video postprocessing filter"

GST_DEBUG_CATEGORY_STATIC (gst_debug_vaapipostproc);
#ifndef GST_DISABLE_GST_DEBUG
#define GST_CAT_DEFAULT gst_debug_vaapipostproc
#else
#define GST_CAT_DEFAULT NULL
#endif

/* Default templates */
/* *INDENT-OFF* */
static const char gst_vaapipostproc_sink_caps_str[] =
  GST_VAAPI_MAKE_SURFACE_CAPS ", "
  GST_CAPS_INTERLACED_MODES "; "
  GST_VIDEO_CAPS_MAKE (GST_VAAPI_FORMATS_ALL) ", "
  GST_CAPS_INTERLACED_MODES;
/* *INDENT-ON* */

/* *INDENT-OFF* */
static const char gst_vaapipostproc_src_caps_str[] =
  GST_VAAPI_MAKE_SURFACE_CAPS ", "
  GST_CAPS_INTERLACED_FALSE "; "
#if (GST_VAAPI_USE_GLX || GST_VAAPI_USE_EGL)
  GST_VAAPI_MAKE_GLTEXUPLOAD_CAPS "; "
#endif
  GST_VIDEO_CAPS_MAKE (GST_VAAPI_FORMATS_ALL) ", "
  GST_CAPS_INTERLACED_MODES;
/* *INDENT-ON* */

/* *INDENT-OFF* */
static GstStaticPadTemplate gst_vaapipostproc_sink_factory =
  GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (gst_vaapipostproc_sink_caps_str));
/* *INDENT-ON* */

/* *INDENT-OFF* */
static GstStaticPadTemplate gst_vaapipostproc_src_factory =
  GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (gst_vaapipostproc_src_caps_str));
/* *INDENT-ON* */

static void gst_vaapipostproc_colorbalance_init (gpointer iface, gpointer data);

G_DEFINE_TYPE_WITH_CODE (GstVaapiPostproc, gst_vaapipostproc,
    GST_TYPE_BASE_TRANSFORM, GST_VAAPI_PLUGIN_BASE_INIT_INTERFACES
    G_IMPLEMENT_INTERFACE (GST_TYPE_COLOR_BALANCE,
        gst_vaapipostproc_colorbalance_init));

GST_VAAPI_PLUGIN_BASE_DEFINE_SET_CONTEXT (gst_vaapipostproc_parent_class);

static GstVideoFormat native_formats[] =
    { GST_VIDEO_FORMAT_NV12, GST_VIDEO_FORMAT_YV12, GST_VIDEO_FORMAT_I420 };

enum
{
  PROP_0,

#ifndef GST_REMOVE_DEPRECATED
  PROP_FORMAT,
  PROP_WIDTH,
  PROP_HEIGHT,
#endif
  PROP_FORCE_ASPECT_RATIO,
  PROP_DEINTERLACE_MODE,
  PROP_DEINTERLACE_METHOD,
  PROP_DENOISE,
  PROP_SHARPEN,
  PROP_HUE,
  PROP_SATURATION,
  PROP_BRIGHTNESS,
  PROP_CONTRAST,
  PROP_SCALE_METHOD,
  PROP_VIDEO_DIRECTION,
  PROP_CROP_LEFT,
  PROP_CROP_RIGHT,
  PROP_CROP_TOP,
  PROP_CROP_BOTTOM,
  PROP_HDR_TONE_MAP,
#ifndef GST_REMOVE_DEPRECATED
  PROP_SKIN_TONE_ENHANCEMENT,
#endif
  PROP_SKIN_TONE_ENHANCEMENT_LEVEL,
};

#define GST_VAAPI_TYPE_HDR_TONE_MAP \
    gst_vaapi_hdr_tone_map_get_type()

static GType
gst_vaapi_hdr_tone_map_get_type (void)
{
  static gsize g_type = 0;

  static const GEnumValue enum_values[] = {
    {GST_VAAPI_HDR_TONE_MAP_AUTO,
        "Auto detection", "auto"},
    {GST_VAAPI_HDR_TONE_MAP_DISABLED,
        "Disable HDR tone mapping", "disabled"},
    {0, NULL, NULL},
  };

  if (g_once_init_enter (&g_type)) {
    const GType type =
        g_enum_register_static ("GstVaapiHDRToneMap", enum_values);
    g_once_init_leave (&g_type, type);
  }
  return g_type;
}

#define GST_VAAPI_TYPE_DEINTERLACE_MODE \
    gst_vaapi_deinterlace_mode_get_type()

static GType
gst_vaapi_deinterlace_mode_get_type (void)
{
  static GType deinterlace_mode_type = 0;

  static const GEnumValue mode_types[] = {
    {GST_VAAPI_DEINTERLACE_MODE_AUTO,
        "Auto detection", "auto"},
    {GST_VAAPI_DEINTERLACE_MODE_INTERLACED,
        "Force deinterlacing", "interlaced"},
    {GST_VAAPI_DEINTERLACE_MODE_DISABLED,
        "Never deinterlace", "disabled"},
    {0, NULL, NULL},
  };

  if (!deinterlace_mode_type) {
    deinterlace_mode_type =
        g_enum_register_static ("GstVaapiDeinterlaceMode", mode_types);
  }
  return deinterlace_mode_type;
}

static void
ds_reset (GstVaapiDeinterlaceState * ds)
{
  guint i;

  for (i = 0; i < G_N_ELEMENTS (ds->buffers); i++)
    gst_buffer_replace (&ds->buffers[i], NULL);
  ds->buffers_index = 0;
  ds->num_surfaces = 0;
  ds->deint = FALSE;
  ds->tff = FALSE;
}

static void
ds_add_buffer (GstVaapiDeinterlaceState * ds, GstBuffer * buf)
{
  gst_buffer_replace (&ds->buffers[ds->buffers_index], buf);
  ds->buffers_index = (ds->buffers_index + 1) % G_N_ELEMENTS (ds->buffers);
}

static inline GstBuffer *
ds_get_buffer (GstVaapiDeinterlaceState * ds, guint index)
{
  /* Note: the index increases towards older buffers.
     i.e. buffer at index 0 means the immediately preceding buffer
     in the history, buffer at index 1 means the one preceding the
     surface at index 0, etc. */
  const guint n = ds->buffers_index + G_N_ELEMENTS (ds->buffers) - index - 1;
  return ds->buffers[n % G_N_ELEMENTS (ds->buffers)];
}

static void
ds_set_surfaces (GstVaapiDeinterlaceState * ds)
{
  GstVaapiVideoMeta *meta;
  guint i;

  ds->num_surfaces = 0;
  for (i = 0; i < G_N_ELEMENTS (ds->buffers); i++) {
    GstBuffer *const buf = ds_get_buffer (ds, i);
    if (!buf)
      break;

    meta = gst_buffer_get_vaapi_video_meta (buf);
    ds->surfaces[ds->num_surfaces++] = gst_vaapi_video_meta_get_surface (meta);
  }
}

static GstVaapiFilterOpInfo *
find_filter_op (GPtrArray * filter_ops, GstVaapiFilterOp op)
{
  guint i;

  if (filter_ops) {
    for (i = 0; i < filter_ops->len; i++) {
      GstVaapiFilterOpInfo *const filter_op = g_ptr_array_index (filter_ops, i);
      if (filter_op->op == op)
        return filter_op;
    }
  }
  return NULL;
}

static inline gboolean
gst_vaapipostproc_ensure_display (GstVaapiPostproc * postproc)
{
  return
      gst_vaapi_plugin_base_ensure_display (GST_VAAPI_PLUGIN_BASE (postproc));
}

static gboolean
gst_vaapipostproc_ensure_filter (GstVaapiPostproc * postproc)
{
  if (postproc->filter)
    return TRUE;

  if (!gst_vaapipostproc_ensure_display (postproc))
    return FALSE;

  gst_caps_replace (&postproc->allowed_srcpad_caps, NULL);
  gst_caps_replace (&postproc->allowed_sinkpad_caps, NULL);

  postproc->filter =
      gst_vaapi_filter_new (GST_VAAPI_PLUGIN_BASE_DISPLAY (postproc));
  if (!postproc->filter)
    return FALSE;
  return TRUE;
}

static gboolean
gst_vaapipostproc_ensure_filter_caps (GstVaapiPostproc * postproc)
{
  if (!gst_vaapipostproc_ensure_filter (postproc))
    return FALSE;

  if (!postproc->filter_ops) {
    postproc->filter_ops = gst_vaapi_filter_get_operations (postproc->filter);
    if (!postproc->filter_ops)
      return FALSE;
  }

  if (!postproc->filter_formats) {
    postproc->filter_formats = gst_vaapi_filter_get_formats
        (postproc->filter, NULL, NULL, NULL, NULL);
    if (!postproc->filter_formats)
      return FALSE;
  }
  return TRUE;
}

static gboolean
gst_vaapipostproc_create (GstVaapiPostproc * postproc)
{
  if (!gst_vaapi_plugin_base_open (GST_VAAPI_PLUGIN_BASE (postproc)))
    return FALSE;
  if (!gst_vaapipostproc_ensure_display (postproc))
    return FALSE;

  postproc->use_vpp = FALSE;
  postproc->has_vpp = gst_vaapipostproc_ensure_filter (postproc);
  return TRUE;
}

static void
gst_vaapipostproc_destroy_filter (GstVaapiPostproc * postproc)
{
  if (postproc->filter_formats) {
    g_array_unref (postproc->filter_formats);
    postproc->filter_formats = NULL;
  }

  if (postproc->filter_ops) {
    g_ptr_array_unref (postproc->filter_ops);
    postproc->filter_ops = NULL;
  }
  if (postproc->cb_channels) {
    g_list_free_full (postproc->cb_channels, g_object_unref);
    postproc->cb_channels = NULL;
  }
  gst_vaapi_filter_replace (&postproc->filter, NULL);
  gst_vaapi_video_pool_replace (&postproc->filter_pool, NULL);
}

static void
gst_vaapipostproc_destroy (GstVaapiPostproc * postproc)
{
  ds_reset (&postproc->deinterlace_state);
  gst_vaapipostproc_destroy_filter (postproc);

  gst_caps_replace (&postproc->allowed_sinkpad_caps, NULL);
  gst_caps_replace (&postproc->allowed_srcpad_caps, NULL);
  gst_vaapi_plugin_base_close (GST_VAAPI_PLUGIN_BASE (postproc));
}

static gboolean
gst_vaapipostproc_start (GstBaseTransform * trans)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);

  ds_reset (&postproc->deinterlace_state);
  if (!gst_vaapi_plugin_base_open (GST_VAAPI_PLUGIN_BASE (postproc)))
    return FALSE;
  g_mutex_lock (&postproc->postproc_lock);
  gst_vaapipostproc_ensure_filter (postproc);
  g_mutex_unlock (&postproc->postproc_lock);

  return TRUE;
}

static gboolean
gst_vaapipostproc_stop (GstBaseTransform * trans)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);

  g_mutex_lock (&postproc->postproc_lock);
  ds_reset (&postproc->deinterlace_state);
  gst_vaapi_plugin_base_close (GST_VAAPI_PLUGIN_BASE (postproc));

  postproc->field_duration = GST_CLOCK_TIME_NONE;
  gst_video_info_init (&postproc->sinkpad_info);
  gst_video_info_init (&postproc->srcpad_info);
  gst_video_info_init (&postproc->filter_pool_info);
  g_mutex_unlock (&postproc->postproc_lock);

  return TRUE;
}

static gboolean
should_deinterlace_buffer (GstVaapiPostproc * postproc, GstBuffer * buf)
{
  if (!(postproc->flags & GST_VAAPI_POSTPROC_FLAG_DEINTERLACE) ||
      postproc->deinterlace_mode == GST_VAAPI_DEINTERLACE_MODE_DISABLED)
    return FALSE;

  if (postproc->deinterlace_mode == GST_VAAPI_DEINTERLACE_MODE_INTERLACED)
    return TRUE;

  g_assert (postproc->deinterlace_mode == GST_VAAPI_DEINTERLACE_MODE_AUTO);

  switch (GST_VIDEO_INFO_INTERLACE_MODE (&postproc->sinkpad_info)) {
    case GST_VIDEO_INTERLACE_MODE_INTERLEAVED:
      return TRUE;
    case GST_VIDEO_INTERLACE_MODE_PROGRESSIVE:
      return FALSE;
    case GST_VIDEO_INTERLACE_MODE_MIXED:
      if (GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_BUFFER_FLAG_INTERLACED))
        return TRUE;
      break;
    default:
      GST_ERROR_OBJECT (postproc,
          "unhandled \"interlace-mode\", disabling deinterlacing");
      break;
  }
  return FALSE;
}

static GstBuffer *
create_output_buffer (GstVaapiPostproc * postproc)
{
  GstBuffer *outbuf;

  GstBufferPool *const pool =
      GST_VAAPI_PLUGIN_BASE_SRC_PAD_BUFFER_POOL (postproc);
  GstFlowReturn ret;

  g_return_val_if_fail (pool != NULL, NULL);

  if (!gst_buffer_pool_is_active (pool) &&
      !gst_buffer_pool_set_active (pool, TRUE))
    goto error_activate_pool;

  outbuf = NULL;
  ret = gst_buffer_pool_acquire_buffer (pool, &outbuf, NULL);
  if (ret != GST_FLOW_OK || !outbuf)
    goto error_create_buffer;
  return outbuf;

  /* ERRORS */
error_activate_pool:
  {
    GST_ERROR_OBJECT (postproc, "failed to activate output video buffer pool");
    return NULL;
  }
error_create_buffer:
  {
    GST_ERROR_OBJECT (postproc, "failed to create output video buffer");
    return NULL;
  }
}

static inline GstBuffer *
create_output_dump_buffer (GstVaapiPostproc * postproc)
{
  GstVaapiPluginBase *const plugin = GST_VAAPI_PLUGIN_BASE (postproc);

  return
      gst_buffer_new_allocate (GST_VAAPI_PLUGIN_BASE_OTHER_ALLOCATOR (plugin),
      GST_VIDEO_INFO_SIZE (GST_VAAPI_PLUGIN_BASE_SRC_PAD_INFO (plugin)),
      &GST_VAAPI_PLUGIN_BASE_OTHER_ALLOCATOR_PARAMS (plugin));
}

static void
copy_metadata (GstVaapiPostproc * postproc, GstBuffer * outbuf,
    GstBuffer * inbuf)
{
  GstBaseTransformClass *bclass = GST_BASE_TRANSFORM_GET_CLASS (postproc);
  GstBaseTransform *trans = GST_BASE_TRANSFORM (postproc);

  if (inbuf == outbuf)
    return;
  if (!bclass->copy_metadata)
    return;
  if (!bclass->copy_metadata (trans, inbuf, outbuf)) {
    /* something failed, post a warning */
    GST_ELEMENT_WARNING (trans, STREAM, NOT_IMPLEMENTED,
        ("could not copy metadata"), (NULL));
  }
}

static gboolean
append_output_buffer_metadata (GstVaapiPostproc * postproc, GstBuffer * outbuf,
    GstBuffer * inbuf, guint flags)
{
  GstVaapiVideoMeta *inbuf_meta, *outbuf_meta;
  GstVaapiSurfaceProxy *proxy;

  gst_buffer_copy_into (outbuf, inbuf, flags | GST_BUFFER_COPY_FLAGS, 0, -1);

  copy_metadata (postproc, outbuf, inbuf);

  /* GstVaapiVideoMeta */
  inbuf_meta = gst_buffer_get_vaapi_video_meta (inbuf);
  g_return_val_if_fail (inbuf_meta != NULL, FALSE);
  proxy = gst_vaapi_video_meta_get_surface_proxy (inbuf_meta);

  outbuf_meta = gst_buffer_get_vaapi_video_meta (outbuf);
  g_return_val_if_fail (outbuf_meta != NULL, FALSE);
  proxy = gst_vaapi_surface_proxy_copy (proxy);
  if (!proxy)
    return FALSE;

  gst_vaapi_video_meta_set_surface_proxy (outbuf_meta, proxy);
  gst_vaapi_surface_proxy_unref (proxy);
  return TRUE;
}

static gboolean
deint_method_is_advanced (GstVaapiDeinterlaceMethod deint_method)
{
  gboolean is_advanced;

  switch (deint_method) {
    case GST_VAAPI_DEINTERLACE_METHOD_MOTION_ADAPTIVE:
    case GST_VAAPI_DEINTERLACE_METHOD_MOTION_COMPENSATED:
      is_advanced = TRUE;
      break;
    default:
      is_advanced = FALSE;
      break;
  }
  return is_advanced;
}

static GstVaapiDeinterlaceMethod
get_next_deint_method (GstVaapiDeinterlaceMethod deint_method)
{
  switch (deint_method) {
    case GST_VAAPI_DEINTERLACE_METHOD_MOTION_COMPENSATED:
      deint_method = GST_VAAPI_DEINTERLACE_METHOD_MOTION_ADAPTIVE;
      break;
    default:
      /* Default to basic "bob" for all others */
      deint_method = GST_VAAPI_DEINTERLACE_METHOD_BOB;
      break;
  }
  return deint_method;
}

static gboolean
set_best_deint_method (GstVaapiPostproc * postproc, guint flags,
    GstVaapiDeinterlaceMethod * deint_method_ptr)
{
  GstVaapiDeinterlaceMethod deint_method = postproc->deinterlace_method;
  gboolean success;

  for (;;) {
    success = gst_vaapi_filter_set_deinterlacing (postproc->filter,
        deint_method, flags);
    if (success || deint_method == GST_VAAPI_DEINTERLACE_METHOD_BOB)
      break;
    deint_method = get_next_deint_method (deint_method);
  }
  *deint_method_ptr = deint_method;
  return success;
}

static gboolean
should_hdr_tone_map (GstVaapiPostproc * const postproc, const GstCaps * caps)
{
  switch (postproc->hdr_tone_map) {
    case GST_VAAPI_HDR_TONE_MAP_AUTO:
    {
      GstVideoMasteringDisplayInfo minfo;
      return gst_video_mastering_display_info_from_caps (&minfo, caps);
    }
    case GST_VAAPI_HDR_TONE_MAP_DISABLED:
      return FALSE;
    default:
      GST_ERROR_OBJECT (postproc, "unhandled \"hdr-tone-map\" option");
      break;
  }
  return FALSE;
}

static gboolean
configure_hdr_tone_map (GstVaapiPostproc * const postproc, const GstCaps * caps)
{
  gboolean enable;

  g_return_val_if_fail (postproc->has_vpp, FALSE);

  enable = should_hdr_tone_map (postproc, caps);

  if (!gst_vaapi_filter_set_hdr_tone_map (postproc->filter, enable))
    goto fail_configure_hdr_tone_map;

  if (enable) {
    GstVideoMasteringDisplayInfo minfo;
    GstVideoContentLightLevel linfo;

    gst_video_mastering_display_info_from_caps (&minfo, caps);
    gst_video_content_light_level_from_caps (&linfo, caps);

    if (!gst_vaapi_filter_set_hdr_tone_map_meta (postproc->filter, &minfo,
            &linfo))
      goto fail_configure_hdr_tone_map;

    postproc->flags |= GST_VAAPI_POSTPROC_FLAG_HDR_TONE_MAP;
  } else {
    postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_HDR_TONE_MAP);
  }

  return TRUE;

fail_configure_hdr_tone_map:
  {
    postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_HDR_TONE_MAP);
    return FALSE;
  }
}

static gboolean
check_filter_update (GstVaapiPostproc * postproc)
{
  guint filter_flag = postproc->flags;
  guint op_flag;
  gint i;

  if (!postproc->has_vpp)
    return FALSE;

  for (i = GST_VAAPI_FILTER_OP_DENOISE;
      i <= GST_VAAPI_FILTER_OP_SKINTONE_LEVEL; i++) {
    op_flag = (filter_flag >> i) & 1;
    if (op_flag)
      return TRUE;
  }

  return FALSE;
}

static gboolean
update_filter (GstVaapiPostproc * postproc)
{
  /* Validate filters */
  if ((postproc->flags & GST_VAAPI_POSTPROC_FLAG_FORMAT) &&
      !gst_vaapi_filter_set_format (postproc->filter, postproc->format))
    return FALSE;

  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_DENOISE) {
    if (!gst_vaapi_filter_set_denoising_level (postproc->filter,
            postproc->denoise_level))
      return FALSE;

    if (gst_vaapi_filter_get_denoising_level_default (postproc->filter) ==
        postproc->denoise_level)
      postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_DENOISE);
  }

  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_SHARPEN) {
    if (!gst_vaapi_filter_set_sharpening_level (postproc->filter,
            postproc->sharpen_level))
      return FALSE;

    if (gst_vaapi_filter_get_sharpening_level_default (postproc->filter) ==
        postproc->sharpen_level)
      postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_SHARPEN);
  }

  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_HUE) {
    if (!gst_vaapi_filter_set_hue (postproc->filter, postproc->hue))
      return FALSE;

    if (gst_vaapi_filter_get_hue_default (postproc->filter) == postproc->hue)
      postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_HUE);
  }

  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_SATURATION) {
    if (!gst_vaapi_filter_set_saturation (postproc->filter,
            postproc->saturation))
      return FALSE;

    if (gst_vaapi_filter_get_saturation_default (postproc->filter) ==
        postproc->saturation)
      postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_SATURATION);
  }

  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_BRIGHTNESS) {
    if (!gst_vaapi_filter_set_brightness (postproc->filter,
            postproc->brightness))
      return FALSE;

    if (gst_vaapi_filter_get_brightness_default (postproc->filter) ==
        postproc->brightness)
      postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_BRIGHTNESS);
  }

  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_CONTRAST) {
    if (!gst_vaapi_filter_set_contrast (postproc->filter, postproc->contrast))
      return FALSE;

    if (gst_vaapi_filter_get_contrast_default (postproc->filter) ==
        postproc->contrast)
      postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_CONTRAST);
  }

  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_SCALE) {
    if (!gst_vaapi_filter_set_scaling (postproc->filter,
            postproc->scale_method))
      return FALSE;

    if (gst_vaapi_filter_get_scaling_default (postproc->filter) ==
        postproc->scale_method)
      postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_SCALE);
  }

  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_VIDEO_DIRECTION) {
    GstVideoOrientationMethod method = postproc->video_direction;
    if (method == GST_VIDEO_ORIENTATION_AUTO)
      method = postproc->tag_video_direction;

    if (!gst_vaapi_filter_set_video_direction (postproc->filter, method)) {
      GST_ELEMENT_WARNING (postproc, LIBRARY, SETTINGS,
          ("Unsupported video direction '%s' by driver.",
              gst_vaapi_enum_type_get_nick
              (GST_TYPE_VIDEO_ORIENTATION_METHOD, method)),
          ("video direction transformation ignored"));

      /* Don't return FALSE because other filters might be set */
    }

    if (gst_vaapi_filter_get_video_direction_default (postproc->filter) ==
        method)
      postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_VIDEO_DIRECTION);
  }

  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_CROP)
    if ((postproc->crop_left | postproc->crop_right | postproc->crop_top
            | postproc->crop_bottom) == 0)
      postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_CROP);

  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_SKINTONE_LEVEL) {
    if (!gst_vaapi_filter_set_skintone_level (postproc->filter,
            postproc->skintone_value))
      return FALSE;

    if (gst_vaapi_filter_get_skintone_level_default (postproc->filter) ==
        postproc->skintone_value)
      postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_SKINTONE_LEVEL);

#ifndef GST_REMOVE_DEPRECATED
    /*
     * When use skin tone level property, disable old skin tone property always
     */
    postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_SKINTONE);
#endif
  } else {
#ifndef GST_REMOVE_DEPRECATED
    if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_SKINTONE) {
      if (!gst_vaapi_filter_set_skintone (postproc->filter,
              postproc->skintone_enhance))
        return FALSE;

      if (gst_vaapi_filter_get_skintone_default (postproc->filter) ==
          postproc->skintone_enhance)
        postproc->flags &= ~(GST_VAAPI_POSTPROC_FLAG_SKINTONE);
    }
#endif
  }

  return TRUE;
}

static void
gst_vaapipostproc_set_passthrough (GstBaseTransform * trans)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  gboolean filter_updated = FALSE;

  if (check_filter_update (postproc) && update_filter (postproc)) {
    /* check again if changed value is default */
    filter_updated = check_filter_update (postproc);
  }

  gst_base_transform_set_passthrough (trans, postproc->same_caps
      && !filter_updated);
}

static gboolean
replace_to_dumb_buffer_if_required (GstVaapiPostproc * postproc,
    GstBuffer ** fieldbuf)
{
  GstVaapiPluginBase *const plugin = GST_VAAPI_PLUGIN_BASE (postproc);
  GstBuffer *newbuf;

  if (!GST_VAAPI_PLUGIN_BASE_COPY_OUTPUT_FRAME (postproc))
    return TRUE;

  newbuf = create_output_dump_buffer (postproc);
  if (!newbuf)
    return FALSE;

  if (!gst_vaapi_plugin_copy_va_buffer (plugin, *fieldbuf, newbuf)) {
    gst_buffer_unref (newbuf);
    return FALSE;
  }

  gst_buffer_replace (fieldbuf, newbuf);
  gst_buffer_unref (newbuf);

  return TRUE;
}

static gboolean
use_vpp_crop (GstVaapiPostproc * postproc)
{
  return !(postproc->forward_crop
      && !(postproc->flags & GST_VAAPI_POSTPROC_FLAG_CROP));
}

static void
rotate_crop_meta (GstVaapiPostproc * const postproc, const GstVideoMeta * vmeta,
    GstVideoCropMeta * crop)
{
  guint tmp;

  g_return_if_fail (postproc->has_vpp);

  /* The video meta is required since the caps width/height are smaller,
   * which would not result in a usable GstVideoInfo for mapping the
   * buffer. */
  if (!vmeta || !crop)
    return;

  switch (gst_vaapi_filter_get_video_direction (postproc->filter)) {
    case GST_VIDEO_ORIENTATION_HORIZ:
      crop->x = vmeta->width - crop->width - crop->x;
      break;
    case GST_VIDEO_ORIENTATION_VERT:
      crop->y = vmeta->height - crop->height - crop->y;
      break;
    case GST_VIDEO_ORIENTATION_90R:
      tmp = crop->x;
      crop->x = vmeta->height - crop->height - crop->y;
      crop->y = tmp;
      G_PRIMITIVE_SWAP (guint, crop->width, crop->height);
      break;
    case GST_VIDEO_ORIENTATION_180:
      crop->x = vmeta->width - crop->width - crop->x;
      crop->y = vmeta->height - crop->height - crop->y;
      break;
    case GST_VIDEO_ORIENTATION_90L:
      tmp = crop->x;
      crop->x = crop->y;
      crop->y = vmeta->width - crop->width - tmp;
      G_PRIMITIVE_SWAP (guint, crop->width, crop->height);
      break;
    case GST_VIDEO_ORIENTATION_UR_LL:
      tmp = crop->x;
      crop->x = vmeta->height - crop->height - crop->y;
      crop->y = vmeta->width - crop->width - tmp;
      G_PRIMITIVE_SWAP (guint, crop->width, crop->height);
      break;
    case GST_VIDEO_ORIENTATION_UL_LR:
      G_PRIMITIVE_SWAP (guint, crop->x, crop->y);
      G_PRIMITIVE_SWAP (guint, crop->width, crop->height);
      break;
    default:
      break;
  }
}

static GstFlowReturn
gst_vaapipostproc_process_vpp (GstBaseTransform * trans, GstBuffer * inbuf,
    GstBuffer * outbuf)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  GstVaapiDeinterlaceState *const ds = &postproc->deinterlace_state;
  GstVaapiVideoMeta *inbuf_meta, *outbuf_meta;
  GstVaapiSurface *inbuf_surface, *outbuf_surface;
  GstVaapiSurfaceProxy *proxy;
  GstVaapiFilterStatus status;
  GstClockTime timestamp;
  GstFlowReturn ret;
  GstBuffer *fieldbuf;
  GstVaapiDeinterlaceMethod deint_method;
  guint flags, deint_flags;
  gboolean tff, deint, deint_refs, deint_changed, discont;
  const GstVideoCropMeta *crop_meta;
  GstVaapiRectangle *crop_rect = NULL;
  GstVaapiRectangle tmp_rect;

  inbuf_meta = gst_buffer_get_vaapi_video_meta (inbuf);
  if (!inbuf_meta)
    goto error_invalid_buffer;
  inbuf_surface = gst_vaapi_video_meta_get_surface (inbuf_meta);

  if (use_vpp_crop (postproc)) {
    crop_rect = &tmp_rect;
    crop_rect->x = postproc->crop_left;
    crop_rect->y = postproc->crop_top;
    crop_rect->width = GST_VIDEO_INFO_WIDTH (&postproc->sinkpad_info)
        - (postproc->crop_left + postproc->crop_right);
    crop_rect->height = GST_VIDEO_INFO_HEIGHT (&postproc->sinkpad_info)
        - (postproc->crop_top + postproc->crop_bottom);

    crop_meta = gst_buffer_get_video_crop_meta (inbuf);
    if (crop_meta) {
      crop_rect->x += crop_meta->x;
      crop_rect->y += crop_meta->y;
    }
  }

  if (!crop_rect)
    crop_rect = (GstVaapiRectangle *)
        gst_vaapi_video_meta_get_render_rect (inbuf_meta);

  timestamp = GST_BUFFER_TIMESTAMP (inbuf);
  tff = GST_BUFFER_FLAG_IS_SET (inbuf, GST_VIDEO_BUFFER_FLAG_TFF);
  discont = GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_DISCONT);
  deint = should_deinterlace_buffer (postproc, inbuf);

  /* Drop references if deinterlacing conditions changed */
  deint_changed = deint != ds->deint;
  if (deint_changed || (ds->num_surfaces > 0 && tff != ds->tff))
    ds_reset (ds);

  deint_method = postproc->deinterlace_method;
  deint_refs = deint_method_is_advanced (deint_method);
  if (deint_refs && 0) {
    GstBuffer *const prev_buf = ds_get_buffer (ds, 0);
    GstClockTime prev_pts, pts = GST_BUFFER_TIMESTAMP (inbuf);
    /* Reset deinterlacing state when there is a discontinuity */
    if (prev_buf && (prev_pts = GST_BUFFER_TIMESTAMP (prev_buf)) != pts) {
      const GstClockTimeDiff pts_diff = GST_CLOCK_DIFF (prev_pts, pts);
      if (pts_diff < 0 || (postproc->field_duration > 0 &&
              pts_diff >= postproc->field_duration * 3 - 1))
        ds_reset (ds);
    }
  }

  ds->deint = deint;
  ds->tff = tff;

  flags = gst_vaapi_video_meta_get_render_flags (inbuf_meta) &
      ~GST_VAAPI_PICTURE_STRUCTURE_MASK;

  /* First field */
  if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_DEINTERLACE) {
    fieldbuf = create_output_buffer (postproc);
    if (!fieldbuf)
      goto error_create_buffer;

    outbuf_meta = gst_buffer_get_vaapi_video_meta (fieldbuf);
    if (!outbuf_meta)
      goto error_create_meta;

    if (!gst_vaapi_video_meta_get_surface_proxy (outbuf_meta)) {
      proxy =
          gst_vaapi_surface_proxy_new_from_pool (GST_VAAPI_SURFACE_POOL
          (postproc->filter_pool));
      if (!proxy)
        goto error_create_proxy;
      gst_vaapi_video_meta_set_surface_proxy (outbuf_meta, proxy);
      gst_vaapi_surface_proxy_unref (proxy);
    }

    if (deint) {
      deint_flags = (tff ? GST_VAAPI_DEINTERLACE_FLAG_TOPFIELD : 0);
      if (tff)
        deint_flags |= GST_VAAPI_DEINTERLACE_FLAG_TFF;
      if (!set_best_deint_method (postproc, deint_flags, &deint_method))
        goto error_op_deinterlace;

      if (deint_method != postproc->deinterlace_method) {
        GST_DEBUG ("unsupported deinterlace-method %u. Using %u instead",
            postproc->deinterlace_method, deint_method);
        postproc->deinterlace_method = deint_method;
        deint_refs = deint_method_is_advanced (deint_method);
      }

      if (deint_refs) {
        ds_set_surfaces (ds);
        if (!gst_vaapi_filter_set_deinterlacing_references (postproc->filter,
                ds->surfaces, ds->num_surfaces, NULL, 0))
          goto error_op_deinterlace;
      }
    } else if (deint_changed) {
      // Reset internal filter to non-deinterlacing mode
      deint_method = GST_VAAPI_DEINTERLACE_METHOD_NONE;
      if (!gst_vaapi_filter_set_deinterlacing (postproc->filter,
              deint_method, 0))
        goto error_op_deinterlace;
    }

    outbuf_surface = gst_vaapi_video_meta_get_surface (outbuf_meta);
    gst_vaapi_filter_set_cropping_rectangle (postproc->filter, crop_rect);
    status = gst_vaapi_filter_process (postproc->filter, inbuf_surface,
        outbuf_surface, flags);
    if (status != GST_VAAPI_FILTER_STATUS_SUCCESS)
      goto error_process_vpp;

    copy_metadata (postproc, fieldbuf, inbuf);
    GST_BUFFER_TIMESTAMP (fieldbuf) = timestamp;
    GST_BUFFER_DURATION (fieldbuf) = postproc->field_duration;
    if (discont) {
      GST_BUFFER_FLAG_SET (fieldbuf, GST_BUFFER_FLAG_DISCONT);
      discont = FALSE;
    }

    if (!replace_to_dumb_buffer_if_required (postproc, &fieldbuf))
      goto error_copy_buffer;

    ret = gst_pad_push (trans->srcpad, fieldbuf);
    if (ret != GST_FLOW_OK)
      goto error_push_buffer;
  }
  fieldbuf = NULL;

  /* Second field */
  outbuf_meta = gst_buffer_get_vaapi_video_meta (outbuf);
  if (!outbuf_meta)
    goto error_create_meta;

  if (!gst_vaapi_video_meta_get_surface_proxy (outbuf_meta)) {
    proxy =
        gst_vaapi_surface_proxy_new_from_pool (GST_VAAPI_SURFACE_POOL
        (postproc->filter_pool));
    if (!proxy)
      goto error_create_proxy;
    gst_vaapi_video_meta_set_surface_proxy (outbuf_meta, proxy);
    gst_vaapi_surface_proxy_unref (proxy);
  }

  if (deint) {
    deint_flags = (tff ? 0 : GST_VAAPI_DEINTERLACE_FLAG_TOPFIELD);
    if (tff)
      deint_flags |= GST_VAAPI_DEINTERLACE_FLAG_TFF;
    if (!gst_vaapi_filter_set_deinterlacing (postproc->filter,
            deint_method, deint_flags))
      goto error_op_deinterlace;

    if (deint_refs
        && !gst_vaapi_filter_set_deinterlacing_references (postproc->filter,
            ds->surfaces, ds->num_surfaces, NULL, 0))
      goto error_op_deinterlace;
  } else if (deint_changed
      && !gst_vaapi_filter_set_deinterlacing (postproc->filter, deint_method,
          0))
    goto error_op_deinterlace;

  outbuf_surface = gst_vaapi_video_meta_get_surface (outbuf_meta);
  gst_vaapi_filter_set_cropping_rectangle (postproc->filter, crop_rect);
  status = gst_vaapi_filter_process (postproc->filter, inbuf_surface,
      outbuf_surface, flags);
  if (status != GST_VAAPI_FILTER_STATUS_SUCCESS)
    goto error_process_vpp;

  if (!(postproc->flags & GST_VAAPI_POSTPROC_FLAG_DEINTERLACE))
    gst_buffer_copy_into (outbuf, inbuf, GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
  else {
    GST_BUFFER_TIMESTAMP (outbuf) = timestamp + postproc->field_duration;
    GST_BUFFER_DURATION (outbuf) = postproc->field_duration;
    if (discont) {
      GST_BUFFER_FLAG_SET (fieldbuf, GST_BUFFER_FLAG_DISCONT);
      discont = FALSE;
    }
  }

  copy_metadata (postproc, outbuf, inbuf);

  rotate_crop_meta (postproc, gst_buffer_get_video_meta (inbuf),
      gst_buffer_get_video_crop_meta (outbuf));

  if (deint && deint_refs)
    ds_add_buffer (ds, inbuf);
  postproc->use_vpp = TRUE;
  return GST_FLOW_OK;

  /* ERRORS */
error_invalid_buffer:
  {
    GST_ERROR_OBJECT (postproc, "failed to validate source buffer");
    return GST_FLOW_ERROR;
  }
error_create_buffer:
  {
    GST_ERROR_OBJECT (postproc, "failed to create output buffer");
    return GST_FLOW_ERROR;
  }
error_create_meta:
  {
    GST_ERROR_OBJECT (postproc, "failed to create new output buffer meta");
    gst_buffer_replace (&fieldbuf, NULL);
    return GST_FLOW_ERROR;
  }
error_create_proxy:
  {
    GST_ERROR_OBJECT (postproc, "failed to create surface proxy from pool");
    gst_buffer_replace (&fieldbuf, NULL);
    return GST_FLOW_ERROR;
  }
error_op_deinterlace:
  {
    GST_ERROR_OBJECT (postproc, "failed to apply deinterlacing filter");
    gst_buffer_replace (&fieldbuf, NULL);
    return GST_FLOW_NOT_SUPPORTED;
  }
error_process_vpp:
  {
    GST_ERROR_OBJECT (postproc, "failed to apply VPP filters (error %d)",
        status);
    gst_buffer_replace (&fieldbuf, NULL);
    return GST_FLOW_ERROR;
  }
error_copy_buffer:
  {
    GST_ERROR_OBJECT (postproc, "failed to copy field buffer to dumb buffer");
    gst_buffer_replace (&fieldbuf, NULL);
    return GST_FLOW_ERROR;
  }
error_push_buffer:
  {
    GST_DEBUG_OBJECT (postproc, "failed to push output buffer: %s",
        gst_flow_get_name (ret));
    return ret;
  }
}

static GstFlowReturn
gst_vaapipostproc_process (GstBaseTransform * trans, GstBuffer * inbuf,
    GstBuffer * outbuf)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  GstVaapiVideoMeta *meta;
  GstClockTime timestamp;
  GstFlowReturn ret;
  GstBuffer *fieldbuf;
  guint fieldbuf_flags, outbuf_flags, flags;
  gboolean tff, deint;

  meta = gst_buffer_get_vaapi_video_meta (inbuf);
  if (!meta)
    goto error_invalid_buffer;

  timestamp = GST_BUFFER_TIMESTAMP (inbuf);
  tff = GST_BUFFER_FLAG_IS_SET (inbuf, GST_VIDEO_BUFFER_FLAG_TFF);
  deint = should_deinterlace_buffer (postproc, inbuf);

  flags = gst_vaapi_video_meta_get_render_flags (meta) &
      ~GST_VAAPI_PICTURE_STRUCTURE_MASK;

  /* First field */
  fieldbuf = create_output_buffer (postproc);
  if (!fieldbuf)
    goto error_create_buffer;
  append_output_buffer_metadata (postproc, fieldbuf, inbuf, 0);

  meta = gst_buffer_get_vaapi_video_meta (fieldbuf);
  fieldbuf_flags = flags;
  fieldbuf_flags |= deint ? (tff ?
      GST_VAAPI_PICTURE_STRUCTURE_TOP_FIELD :
      GST_VAAPI_PICTURE_STRUCTURE_BOTTOM_FIELD) :
      GST_VAAPI_PICTURE_STRUCTURE_FRAME;
  gst_vaapi_video_meta_set_render_flags (meta, fieldbuf_flags);

  GST_BUFFER_TIMESTAMP (fieldbuf) = timestamp;
  GST_BUFFER_DURATION (fieldbuf) = postproc->field_duration;

  if (!replace_to_dumb_buffer_if_required (postproc, &fieldbuf))
    goto error_copy_buffer;

  ret = gst_pad_push (trans->srcpad, fieldbuf);
  if (ret != GST_FLOW_OK)
    goto error_push_buffer;

  /* Second field */
  append_output_buffer_metadata (postproc, outbuf, inbuf, 0);

  meta = gst_buffer_get_vaapi_video_meta (outbuf);
  outbuf_flags = flags;
  outbuf_flags |= deint ? (tff ?
      GST_VAAPI_PICTURE_STRUCTURE_BOTTOM_FIELD :
      GST_VAAPI_PICTURE_STRUCTURE_TOP_FIELD) :
      GST_VAAPI_PICTURE_STRUCTURE_FRAME;
  gst_vaapi_video_meta_set_render_flags (meta, outbuf_flags);

  GST_BUFFER_TIMESTAMP (outbuf) = timestamp + postproc->field_duration;
  GST_BUFFER_DURATION (outbuf) = postproc->field_duration;
  return GST_FLOW_OK;

  /* ERRORS */
error_invalid_buffer:
  {
    GST_ERROR_OBJECT (postproc, "failed to validate source buffer");
    return GST_FLOW_ERROR;
  }
error_create_buffer:
  {
    GST_ERROR_OBJECT (postproc, "failed to create output buffer");
    return GST_FLOW_EOS;
  }
error_copy_buffer:
  {
    GST_ERROR_OBJECT (postproc, "failed to copy field buffer to dumb buffer");
    gst_buffer_replace (&fieldbuf, NULL);
    return GST_FLOW_ERROR;
  }
error_push_buffer:
  {
    GST_DEBUG_OBJECT (postproc, "failed to push output buffer: %s",
        gst_flow_get_name (ret));
    return ret;
  }
}

static GstFlowReturn
gst_vaapipostproc_passthrough (GstBaseTransform * trans, GstBuffer * inbuf,
    GstBuffer * outbuf)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  GstVaapiVideoMeta *meta;

  /* No video processing needed, simply copy buffer metadata */
  meta = gst_buffer_get_vaapi_video_meta (inbuf);
  if (!meta)
    goto error_invalid_buffer;

  append_output_buffer_metadata (postproc, outbuf, inbuf,
      GST_BUFFER_COPY_TIMESTAMPS);
  return GST_FLOW_OK;

  /* ERRORS */
error_invalid_buffer:
  {
    GST_ERROR_OBJECT (postproc, "failed to validate source buffer");
    return GST_FLOW_ERROR;
  }
}

static gboolean
video_info_changed (GstVideoInfo * old_vip, GstVideoInfo * new_vip)
{
  if (gst_video_info_changed (old_vip, new_vip))
    return TRUE;
  if (GST_VIDEO_INFO_INTERLACE_MODE (old_vip) !=
      GST_VIDEO_INFO_INTERLACE_MODE (new_vip))
    return TRUE;
  return FALSE;
}

static gboolean
video_info_update (GstCaps * caps, GstVideoInfo * info,
    gboolean * caps_changed_ptr)
{
  GstVideoInfo vi;

  if (!gst_video_info_from_caps (&vi, caps))
    return FALSE;

  *caps_changed_ptr = FALSE;
  if (video_info_changed (info, &vi)) {
    *caps_changed_ptr = TRUE;
    *info = vi;
  }

  return TRUE;
}

static gboolean
gst_vaapipostproc_update_sink_caps (GstVaapiPostproc * postproc, GstCaps * caps,
    gboolean * caps_changed_ptr)
{
  GstVideoInfo vi;
  gboolean deinterlace;

  GST_INFO_OBJECT (postproc, "new sink caps = %" GST_PTR_FORMAT, caps);

  if (!video_info_update (caps, &postproc->sinkpad_info, caps_changed_ptr))
    return FALSE;

  vi = postproc->sinkpad_info;
  deinterlace = is_deinterlace_enabled (postproc, &vi);
  if (deinterlace)
    postproc->flags |= GST_VAAPI_POSTPROC_FLAG_DEINTERLACE;
  postproc->field_duration = GST_VIDEO_INFO_FPS_N (&vi) > 0 ?
      gst_util_uint64_scale (GST_SECOND, GST_VIDEO_INFO_FPS_D (&vi),
      (1 + deinterlace) * GST_VIDEO_INFO_FPS_N (&vi)) : 0;

  postproc->get_va_surfaces = gst_caps_has_vaapi_surface (caps);
  return TRUE;
}

static gboolean
gst_vaapipostproc_update_src_caps (GstVaapiPostproc * postproc, GstCaps * caps,
    gboolean * caps_changed_ptr)
{
  GST_INFO_OBJECT (postproc, "new src caps = %" GST_PTR_FORMAT, caps);

  if (!video_info_update (caps, &postproc->srcpad_info, caps_changed_ptr))
    return FALSE;

  if (postproc->format != GST_VIDEO_INFO_FORMAT (&postproc->sinkpad_info) &&
      postproc->format != DEFAULT_FORMAT)
    postproc->flags |= GST_VAAPI_POSTPROC_FLAG_FORMAT;

  if (GST_VIDEO_INFO_WIDTH (&postproc->srcpad_info) !=
      GST_VIDEO_INFO_WIDTH (&postproc->sinkpad_info)
      || GST_VIDEO_INFO_HEIGHT (&postproc->srcpad_info) !=
      GST_VIDEO_INFO_HEIGHT (&postproc->sinkpad_info))
    postproc->flags |= GST_VAAPI_POSTPROC_FLAG_SIZE;

  return TRUE;
}

static gboolean
ensure_allowed_sinkpad_caps (GstVaapiPostproc * postproc)
{
  GstCaps *out_caps = NULL;
  guint mem_types;
  gint min_width, min_height, max_width, max_height;
  GArray *mem_formats = NULL;
  gboolean ret = TRUE;
  guint i, num_structure;

  if (postproc->allowed_sinkpad_caps) {
    ret = TRUE;
    goto out;
  }

  if (!GST_VAAPI_PLUGIN_BASE_DISPLAY (postproc) ||
      !gst_vaapipostproc_ensure_filter_caps (postproc)) {
    ret = FALSE;
    goto out;
  }

  mem_types = gst_vaapi_filter_get_memory_types (postproc->filter);
  mem_formats = gst_vaapi_filter_get_formats (postproc->filter, &min_width,
      &min_height, &max_width, &max_height);

  out_caps = gst_vaapi_build_caps_from_formats (mem_formats, min_width,
      min_height, max_width, max_height, mem_types);
  if (!out_caps) {
    GST_WARNING_OBJECT (postproc, "failed to create VA sink caps");
    ret = FALSE;
    goto out;
  }

  /* For raw yuv caps, we need to replace va attrib formats with all image formats */
  num_structure = gst_caps_get_size (out_caps);
  for (i = 0; i < num_structure; i++) {
    GstStructure *structure;
    GstCapsFeatures *features = gst_caps_get_features (out_caps, i);
    GValue v_formats = G_VALUE_INIT;

    structure = gst_caps_get_structure (out_caps, i);
    if (!structure)
      continue;

    if (gst_caps_features_contains (features,
            GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY)) {
      mem_formats = gst_vaapi_display_get_image_formats
          (GST_VAAPI_PLUGIN_BASE_DISPLAY (postproc));
      if (!gst_vaapi_value_set_format_list (&v_formats, mem_formats)) {
        ret = FALSE;
        goto out;
      }
      gst_structure_set_value (structure, "format", &v_formats);
      g_value_unset (&v_formats);
    }
  }

  postproc->allowed_sinkpad_caps = out_caps;
  out_caps = NULL;
  GST_INFO_OBJECT (postproc, "postproc sink allowed caps is %" GST_PTR_FORMAT,
      postproc->allowed_sinkpad_caps);
out:
  if (out_caps)
    gst_caps_unref (out_caps);
  if (mem_formats)
    g_array_unref (mem_formats);

  return ret;
}

/* Fixup output caps so that to reflect the supported set of pixel formats */
static GstCaps *
expand_allowed_srcpad_caps (GstVaapiPostproc * postproc, GstCaps * caps)
{
  GValue value = G_VALUE_INIT, v_format = G_VALUE_INIT;
  guint i, num_structures;
  gint gl_upload_meta_idx = -1;

  if (postproc->filter == NULL)
    goto cleanup;
  if (!gst_vaapipostproc_ensure_filter_caps (postproc))
    goto cleanup;

  /* Reset "format" field for each structure */
  if (!gst_vaapi_value_set_format_list (&value, postproc->filter_formats))
    goto cleanup;
  if (gst_vaapi_value_set_format (&v_format, GST_VIDEO_FORMAT_ENCODED)) {
    gst_value_list_prepend_value (&value, &v_format);
    g_value_unset (&v_format);
  }

  num_structures = gst_caps_get_size (caps);
  for (i = 0; i < num_structures; i++) {
    GstCapsFeatures *const features = gst_caps_get_features (caps, i);
    GstStructure *structure;

    structure = gst_caps_get_structure (caps, i);
    if (!structure)
      continue;

    gst_vaapi_filter_append_caps (postproc->filter, structure);

    if (gst_caps_features_contains (features,
            GST_CAPS_FEATURE_META_GST_VIDEO_GL_TEXTURE_UPLOAD_META)) {
      gl_upload_meta_idx = i;
      continue;
    }

    gst_structure_set_value (structure, "format", &value);
  }
  g_value_unset (&value);

  if ((GST_VAAPI_PLUGIN_BASE_SRC_PAD_CAN_DMABUF (postproc)
          || !gst_vaapi_display_has_opengl (GST_VAAPI_PLUGIN_BASE_DISPLAY
              (postproc)))
      && gl_upload_meta_idx > -1) {
    gst_caps_remove_structure (caps, gl_upload_meta_idx);
  }

cleanup:
  return caps;
}

static gboolean
ensure_allowed_srcpad_caps (GstVaapiPostproc * postproc)
{
  GstCaps *out_caps;

  if (postproc->allowed_srcpad_caps)
    return TRUE;

  /* Create initial caps from pad template */
  out_caps = gst_caps_from_string (gst_vaapipostproc_src_caps_str);
  if (!out_caps) {
    GST_ERROR_OBJECT (postproc, "failed to create VA src caps");
    return FALSE;
  }

  postproc->allowed_srcpad_caps =
      expand_allowed_srcpad_caps (postproc, out_caps);
  return postproc->allowed_srcpad_caps != NULL;
}

static GstCaps *
gst_vaapipostproc_transform_caps_impl (GstBaseTransform * trans,
    GstPadDirection direction)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);

  /* Generate the sink pad caps, that could be fixated afterwards */
  if (direction == GST_PAD_SRC) {
    if (!ensure_allowed_sinkpad_caps (postproc))
      return gst_caps_from_string (gst_vaapipostproc_sink_caps_str);
    return gst_caps_ref (postproc->allowed_sinkpad_caps);
  }

  /* Generate complete set of src pad caps */
  if (!ensure_allowed_srcpad_caps (postproc))
    return NULL;
  return gst_vaapipostproc_transform_srccaps (postproc);
}

static GstCaps *
gst_vaapipostproc_transform_caps (GstBaseTransform * trans,
    GstPadDirection direction, GstCaps * caps, GstCaps * filter)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  GstCaps *out_caps;

  GST_DEBUG_OBJECT (trans,
      "Transforming caps %" GST_PTR_FORMAT " in direction %s", caps,
      (direction == GST_PAD_SINK) ? "sink" : "src");

  g_mutex_lock (&postproc->postproc_lock);
  out_caps = gst_vaapipostproc_transform_caps_impl (trans, direction);
  g_mutex_unlock (&postproc->postproc_lock);

  if (out_caps && filter) {
    GstCaps *intersection;

    intersection = gst_caps_intersect_full (out_caps, filter,
        GST_CAPS_INTERSECT_FIRST);
    gst_caps_unref (out_caps);
    out_caps = intersection;
  }

  GST_DEBUG_OBJECT (trans, "returning caps: %" GST_PTR_FORMAT, out_caps);

  return out_caps;
}

static GstCaps *
gst_vaapipostproc_fixate_caps (GstBaseTransform * trans,
    GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  GstCaps *outcaps = NULL;
  gboolean same_caps, filter_updated = FALSE;

  GST_DEBUG_OBJECT (trans, "trying to fixate othercaps %" GST_PTR_FORMAT
      " based on caps %" GST_PTR_FORMAT " in direction %s", othercaps, caps,
      (direction == GST_PAD_SINK) ? "sink" : "src");

  if (direction == GST_PAD_SRC) {
    /* @TODO: we can do better */
    outcaps = gst_caps_fixate (othercaps);
    goto done;
  }

  g_mutex_lock (&postproc->postproc_lock);
  postproc->has_vpp = gst_vaapipostproc_ensure_filter_caps (postproc);
  if (check_filter_update (postproc) && update_filter (postproc)) {
    /* check again if changed value is default */
    filter_updated = check_filter_update (postproc);
  }

  outcaps = gst_vaapipostproc_fixate_srccaps (postproc, caps, othercaps);
  g_mutex_unlock (&postproc->postproc_lock);
  if (!outcaps)
    goto done;

  /* set passthrough according to caps changes or filter changes */
  same_caps = gst_caps_is_equal (caps, outcaps);
  gst_base_transform_set_passthrough (trans, same_caps && !filter_updated);

done:
  if (outcaps)
    GST_DEBUG_OBJECT (trans, "fixated othercaps to %" GST_PTR_FORMAT, outcaps);
  gst_caps_unref (othercaps);

  return outcaps;
}

static gboolean
gst_vaapipostproc_transform_size (GstBaseTransform * trans,
    GstPadDirection direction, GstCaps * caps, gsize size,
    GstCaps * othercaps, gsize * othersize)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);

  if (direction == GST_PAD_SINK || postproc->get_va_surfaces)
    *othersize = 0;
  else
    *othersize = size;
  return TRUE;
}

static gboolean
gst_vaapipostproc_transform_meta (GstBaseTransform * trans, GstBuffer * outbuf,
    GstMeta * meta, GstBuffer * inbuf)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);

  /* don't copy GstVideoCropMeta if we are using vpp crop */
  if (meta->info->api == GST_VIDEO_CROP_META_API_TYPE
      && use_vpp_crop (postproc))
    return FALSE;

  /* don't copy GstParentBufferMeta if use_vpp */
  if (meta->info->api == GST_PARENT_BUFFER_META_API_TYPE && postproc->use_vpp)
    return FALSE;

  return TRUE;
}

static GstFlowReturn
gst_vaapipostproc_transform (GstBaseTransform * trans, GstBuffer * inbuf,
    GstBuffer * outbuf)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  GstVaapiPluginBase *const plugin = GST_VAAPI_PLUGIN_BASE (postproc);
  GstBuffer *buf, *sys_buf = NULL;
  GstFlowReturn ret;

  ret = gst_vaapi_plugin_base_get_input_buffer (plugin, inbuf, &buf);
  if (ret != GST_FLOW_OK)
    return GST_FLOW_ERROR;

  if (GST_VAAPI_PLUGIN_BASE_COPY_OUTPUT_FRAME (trans)) {
    GstBuffer *va_buf = create_output_buffer (postproc);
    if (!va_buf) {
      ret = GST_FLOW_ERROR;
      goto done;
    }
    sys_buf = outbuf;
    outbuf = va_buf;
  }

  ret = GST_FLOW_NOT_SUPPORTED;
  if (postproc->flags) {
    /* Use VA/VPP extensions to process this frame */
    if (postproc->has_vpp) {
      ret = gst_vaapipostproc_process_vpp (trans, buf, outbuf);
      if (ret != GST_FLOW_NOT_SUPPORTED)
        goto done;
      GST_WARNING_OBJECT (postproc, "unsupported VPP filters. Disabling");
    }

    /* Only append picture structure meta data (top/bottom field) */
    if (postproc->flags & GST_VAAPI_POSTPROC_FLAG_DEINTERLACE) {
      ret = gst_vaapipostproc_process (trans, buf, outbuf);
      if (ret != GST_FLOW_NOT_SUPPORTED)
        goto done;
    }
  }

  /* Fallback: passthrough to the downstream element as is */
  ret = gst_vaapipostproc_passthrough (trans, buf, outbuf);

done:
  if (sys_buf)
    copy_metadata (postproc, sys_buf, buf);

  gst_buffer_unref (buf);

  if (sys_buf) {
    if (!gst_vaapi_plugin_copy_va_buffer (plugin, outbuf, sys_buf))
      return GST_FLOW_ERROR;

    gst_buffer_unref (outbuf);
    outbuf = sys_buf;
  }

  return ret;
}

static gboolean
ensure_buffer_pool (GstVaapiPostproc * postproc, GstVideoInfo * vi)
{
  GstVaapiVideoPool *pool;

  if (!vi)
    return FALSE;

  gst_video_info_change_format (vi, postproc->format,
      GST_VIDEO_INFO_WIDTH (vi), GST_VIDEO_INFO_HEIGHT (vi));

  if (postproc->filter_pool
      && !video_info_changed (&postproc->filter_pool_info, vi))
    return TRUE;
  postproc->filter_pool_info = *vi;

  pool =
      gst_vaapi_surface_pool_new_full (GST_VAAPI_PLUGIN_BASE_DISPLAY (postproc),
      &postproc->filter_pool_info, 0);
  if (!pool)
    return FALSE;

  gst_vaapi_video_pool_replace (&postproc->filter_pool, pool);
  gst_vaapi_video_pool_unref (pool);
  return TRUE;
}

static GstFlowReturn
gst_vaapipostproc_prepare_output_buffer (GstBaseTransform * trans,
    GstBuffer * inbuf, GstBuffer ** outbuf_ptr)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  const GstVideoMeta *video_meta;
  GstVideoInfo info;

  if (gst_base_transform_is_passthrough (trans)) {
    *outbuf_ptr = inbuf;
    return GST_FLOW_OK;
  }

  /* If we are not using vpp crop (i.e. forwarding crop meta to downstream)
   * then, ensure our output buffer pool is sized and rotated for uncropped
   * output */
  if (gst_buffer_get_video_crop_meta (inbuf) && !use_vpp_crop (postproc)) {
    /* The video meta is required since the caps width/height are smaller,
     * which would not result in a usable GstVideoInfo for mapping the
     * buffer. */
    video_meta = gst_buffer_get_video_meta (inbuf);
    if (!video_meta)
      return GST_FLOW_ERROR;

    info = postproc->srcpad_info;
    info.width = video_meta->width;
    info.height = video_meta->height;

    if (postproc->has_vpp) {
      /* compensate for rotation if needed */
      switch (gst_vaapi_filter_get_video_direction (postproc->filter)) {
        case GST_VIDEO_ORIENTATION_90R:
        case GST_VIDEO_ORIENTATION_UL_LR:
        case GST_VIDEO_ORIENTATION_90L:
        case GST_VIDEO_ORIENTATION_UR_LL:
          G_PRIMITIVE_SWAP (guint, info.width, info.height);
        default:
          break;
      }
    }

    ensure_buffer_pool (postproc, &info);
  }

  if (GST_VAAPI_PLUGIN_BASE_COPY_OUTPUT_FRAME (trans)) {
    *outbuf_ptr = create_output_dump_buffer (postproc);
  } else {
    *outbuf_ptr = create_output_buffer (postproc);
  }

  if (!*outbuf_ptr)
    return GST_FLOW_ERROR;

  return GST_FLOW_OK;
}

static gboolean
ensure_srcpad_buffer_pool (GstVaapiPostproc * postproc, GstCaps * caps)
{
  GstVideoInfo vi;

  if (!gst_video_info_from_caps (&vi, caps))
    return FALSE;

  return ensure_buffer_pool (postproc, &vi);
}

static gboolean
is_native_video_format (GstVideoFormat format)
{
  guint i = 0;
  for (i = 0; i < G_N_ELEMENTS (native_formats); i++)
    if (native_formats[i] == format)
      return TRUE;
  return FALSE;
}

static gboolean
gst_vaapipostproc_set_caps (GstBaseTransform * trans, GstCaps * caps,
    GstCaps * out_caps)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  gboolean sink_caps_changed = FALSE;
  gboolean src_caps_changed = FALSE;
  GstVideoInfo vinfo;
  gboolean ret = FALSE;

  g_mutex_lock (&postproc->postproc_lock);
  if (!gst_vaapipostproc_update_sink_caps (postproc, caps, &sink_caps_changed))
    goto done;
  /* HACK: This is a workaround to deal with the va-intel-driver for non-native
   * formats while doing advanced deinterlacing. The format of reference surfaces must
   * be same as the format used by the driver internally for motion adaptive
   * deinterlacing and motion compensated deinterlacing */
  if (!gst_video_info_from_caps (&vinfo, caps))
    goto done;
  if (deint_method_is_advanced (postproc->deinterlace_method)
      && !is_native_video_format (GST_VIDEO_INFO_FORMAT (&vinfo))) {
    GST_WARNING_OBJECT (postproc,
        "Advanced deinterlacing requires the native video formats used by the driver internally");
    goto done;
  }
  if (!gst_vaapipostproc_update_src_caps (postproc, out_caps,
          &src_caps_changed))
    goto done;

  if (sink_caps_changed || src_caps_changed) {
    gst_vaapipostproc_destroy (postproc);
    if (!gst_vaapipostproc_create (postproc))
      goto done;
    if (!gst_vaapi_plugin_base_set_caps (GST_VAAPI_PLUGIN_BASE (trans),
            caps, out_caps))
      goto done;
  }

  if (postproc->has_vpp) {
    if (!gst_vaapi_filter_set_colorimetry (postproc->filter,
            &GST_VIDEO_INFO_COLORIMETRY (GST_VAAPI_PLUGIN_BASE_SINK_PAD_INFO
                (postproc)),
            &GST_VIDEO_INFO_COLORIMETRY (GST_VAAPI_PLUGIN_BASE_SRC_PAD_INFO
                (postproc))))
      goto done;

    if (!configure_hdr_tone_map (postproc,
            GST_VAAPI_PLUGIN_BASE_SINK_PAD_CAPS (postproc)))
      GST_WARNING_OBJECT (postproc,
          "Failed to configure HDR tone mapping."
          "  The driver may not support it.");
  }

  if (!ensure_srcpad_buffer_pool (postproc, out_caps))
    goto done;

  postproc->same_caps = gst_caps_is_equal (caps, out_caps);

  if (!src_caps_changed) {
    /* set passthrough according to caps changes or filter changes */
    gst_vaapipostproc_set_passthrough (trans);
  }

  ret = TRUE;

done:
  g_mutex_unlock (&postproc->postproc_lock);

  /* Updates the srcpad caps and send the caps downstream */
  if (ret && src_caps_changed)
    gst_base_transform_update_src_caps (trans, out_caps);

  return ret;
}

static gboolean
gst_vaapipostproc_query (GstBaseTransform * trans,
    GstPadDirection direction, GstQuery * query)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  GstElement *const element = GST_ELEMENT (trans);

  if (GST_QUERY_TYPE (query) == GST_QUERY_CONTEXT) {
    if (gst_vaapi_handle_context_query (element, query)) {
      GST_DEBUG_OBJECT (postproc, "sharing display %" GST_PTR_FORMAT,
          GST_VAAPI_PLUGIN_BASE_DISPLAY (postproc));
      return TRUE;
    }
  }

  return
      GST_BASE_TRANSFORM_CLASS (gst_vaapipostproc_parent_class)->query (trans,
      direction, query);
}

static gboolean
gst_vaapipostproc_propose_allocation (GstBaseTransform * trans,
    GstQuery * decide_query, GstQuery * query)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  GstVaapiPluginBase *const plugin = GST_VAAPI_PLUGIN_BASE (trans);
  GstCaps *allocation_caps;
  GstStructure *structure;
  gint allocation_width, allocation_height;
  gint negotiated_width, negotiated_height;

  /* passthrough query, we just bypass to the peer */
  if (decide_query == NULL) {
    return GST_BASE_TRANSFORM_CLASS
        (gst_vaapipostproc_parent_class)->propose_allocation (trans,
        decide_query, query);
  }

  /* advertise to upstream that we can handle crop meta */
  if (decide_query)
    gst_query_add_allocation_meta (query, GST_VIDEO_CROP_META_API_TYPE, NULL);

  negotiated_width = GST_VIDEO_INFO_WIDTH (&postproc->sinkpad_info);
  negotiated_height = GST_VIDEO_INFO_HEIGHT (&postproc->sinkpad_info);

  if (negotiated_width == 0 || negotiated_height == 0)
    goto bail;

  allocation_caps = NULL;
  gst_query_parse_allocation (query, &allocation_caps, NULL);
  if (!allocation_caps)
    goto bail;

  structure = gst_caps_get_structure (allocation_caps, 0);
  if (!gst_structure_get_int (structure, "width", &allocation_width))
    goto bail;
  if (!gst_structure_get_int (structure, "height", &allocation_height))
    goto bail;

  if (allocation_width != negotiated_width
      || allocation_height != negotiated_height) {
    g_mutex_lock (&postproc->postproc_lock);
    postproc->flags |= GST_VAAPI_POSTPROC_FLAG_SIZE;
    g_mutex_unlock (&postproc->postproc_lock);
  }

bail:
  /* Let vaapidecode allocate the video buffers */
  if (postproc->get_va_surfaces)
    return FALSE;
  if (!gst_vaapi_plugin_base_propose_allocation (plugin, query))
    return FALSE;
  return TRUE;
}

static gboolean
gst_vaapipostproc_decide_allocation (GstBaseTransform * trans, GstQuery * query)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);

  g_mutex_lock (&postproc->postproc_lock);
  /* Let downstream handle the crop meta if they support it */
  postproc->forward_crop = (gst_query_find_allocation_meta (query,
          GST_VIDEO_CROP_META_API_TYPE, NULL) &&
      gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL));
  GST_DEBUG_OBJECT (postproc, "use_vpp_crop=%d", use_vpp_crop (postproc));
  g_mutex_unlock (&postproc->postproc_lock);

  return gst_vaapi_plugin_base_decide_allocation (GST_VAAPI_PLUGIN_BASE (trans),
      query);
}

static void
get_scale_factor (GstVaapiPostproc * const postproc, gdouble * w_factor,
    gdouble * h_factor)
{
  gdouble wd = GST_VIDEO_INFO_WIDTH (&postproc->srcpad_info);
  gdouble hd = GST_VIDEO_INFO_HEIGHT (&postproc->srcpad_info);

  g_return_if_fail (postproc->has_vpp);

  switch (gst_vaapi_filter_get_video_direction (postproc->filter)) {
    case GST_VIDEO_ORIENTATION_90R:
    case GST_VIDEO_ORIENTATION_90L:
    case GST_VIDEO_ORIENTATION_UR_LL:
    case GST_VIDEO_ORIENTATION_UL_LR:
      G_PRIMITIVE_SWAP (gdouble, wd, hd);
      break;
    default:
      break;
  }

  *w_factor = GST_VIDEO_INFO_WIDTH (&postproc->sinkpad_info)
      - (postproc->crop_left + postproc->crop_right);
  *w_factor /= wd;

  *h_factor = GST_VIDEO_INFO_HEIGHT (&postproc->sinkpad_info)
      - (postproc->crop_top + postproc->crop_bottom);
  *h_factor /= hd;
}

static gboolean
gst_vaapipostproc_src_event (GstBaseTransform * trans, GstEvent * event)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  gdouble new_x = 0, new_y = 0, x = 0, y = 0, w_factor = 1, h_factor = 1;
  gboolean ret;

  GST_TRACE_OBJECT (postproc, "handling %s event", GST_EVENT_TYPE_NAME (event));

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_NAVIGATION:
      event = gst_event_make_writable (event);

      if (postproc->has_vpp &&
          gst_navigation_event_get_coordinates (event, &x, &y)) {
        GST_DEBUG_OBJECT (postproc, "converting %fx%f", x, y);

        /* video-direction compensation */
        switch (gst_vaapi_filter_get_video_direction (postproc->filter)) {
          case GST_VIDEO_ORIENTATION_90R:
            new_x = y;
            new_y = GST_VIDEO_INFO_WIDTH (&postproc->srcpad_info) - 1 - x;
            break;
          case GST_VIDEO_ORIENTATION_90L:
            new_x = GST_VIDEO_INFO_HEIGHT (&postproc->srcpad_info) - 1 - y;
            new_y = x;
            break;
          case GST_VIDEO_ORIENTATION_UR_LL:
            new_x = GST_VIDEO_INFO_HEIGHT (&postproc->srcpad_info) - 1 - y;
            new_y = GST_VIDEO_INFO_WIDTH (&postproc->srcpad_info) - 1 - x;
            break;
          case GST_VIDEO_ORIENTATION_UL_LR:
            new_x = y;
            new_y = x;
            break;
          case GST_VIDEO_ORIENTATION_180:
            new_x = GST_VIDEO_INFO_WIDTH (&postproc->srcpad_info) - 1 - x;
            new_y = GST_VIDEO_INFO_HEIGHT (&postproc->srcpad_info) - 1 - y;
            break;
          case GST_VIDEO_ORIENTATION_HORIZ:
            new_x = GST_VIDEO_INFO_WIDTH (&postproc->srcpad_info) - 1 - x;
            new_y = y;
            break;
          case GST_VIDEO_ORIENTATION_VERT:
            new_x = x;
            new_y = GST_VIDEO_INFO_HEIGHT (&postproc->srcpad_info) - 1 - y;
            break;
          default:
            new_x = x;
            new_y = y;
            break;
        }

        /* scale compensation */
        get_scale_factor (postproc, &w_factor, &h_factor);
        new_x *= w_factor;
        new_y *= h_factor;

        /* crop compensation */
        new_x += postproc->crop_left;
        new_y += postproc->crop_top;

        GST_DEBUG_OBJECT (postproc, "to %fx%f", new_x, new_y);
        gst_navigation_event_set_coordinates (event, new_x, new_y);
      }
      break;
    default:
      break;
  }

  ret =
      GST_BASE_TRANSFORM_CLASS (gst_vaapipostproc_parent_class)->src_event
      (trans, event);

  return ret;
}

static gboolean
gst_vaapipostproc_sink_event (GstBaseTransform * trans, GstEvent * event)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (trans);
  GstTagList *taglist;
  GstVideoOrientationMethod method;
  gboolean ret;
  gboolean do_reconf;

  GST_DEBUG_OBJECT (postproc, "handling %s event", GST_EVENT_TYPE_NAME (event));

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_TAG:
      gst_event_parse_tag (event, &taglist);

      do_reconf = gst_video_orientation_from_tag (taglist, &method);

      if (do_reconf) {
        postproc->tag_video_direction = method;
        postproc->flags |= GST_VAAPI_POSTPROC_FLAG_VIDEO_DIRECTION;
        gst_base_transform_reconfigure_src (trans);
      }
      break;
    default:
      break;
  }

  ret =
      GST_BASE_TRANSFORM_CLASS (gst_vaapipostproc_parent_class)->sink_event
      (trans, event);

  return ret;
}

static void
gst_vaapipostproc_finalize (GObject * object)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (object);

  gst_vaapipostproc_destroy (postproc);

  g_mutex_clear (&postproc->postproc_lock);
  gst_vaapi_plugin_base_finalize (GST_VAAPI_PLUGIN_BASE (postproc));
  G_OBJECT_CLASS (gst_vaapipostproc_parent_class)->finalize (object);
}

static void
gst_vaapipostproc_set_property (GObject * object,
    guint prop_id, const GValue * value, GParamSpec * pspec)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (object);
  gboolean do_reconf = FALSE;

  g_mutex_lock (&postproc->postproc_lock);
  switch (prop_id) {
#ifndef GST_REMOVE_DEPRECATED
    case PROP_FORMAT:
      postproc->format = g_value_get_enum (value);
      break;
    case PROP_WIDTH:
    {
      guint prev_width = postproc->width;
      postproc->width = g_value_get_uint (value);
      do_reconf = (prev_width != postproc->width);
      break;
    }
    case PROP_HEIGHT:
    {
      guint prev_height = postproc->height;
      postproc->height = g_value_get_uint (value);
      do_reconf = (prev_height != postproc->height);
      break;
    }
#endif
    case PROP_FORCE_ASPECT_RATIO:
      postproc->keep_aspect = g_value_get_boolean (value);
      break;
    case PROP_DEINTERLACE_MODE:
      postproc->deinterlace_mode = g_value_get_enum (value);
      break;
    case PROP_DEINTERLACE_METHOD:
      postproc->deinterlace_method = g_value_get_enum (value);
      break;
    case PROP_DENOISE:
      postproc->denoise_level = g_value_get_float (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_DENOISE;
      break;
    case PROP_SHARPEN:
      postproc->sharpen_level = g_value_get_float (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_SHARPEN;
      break;
    case PROP_HUE:
      postproc->hue = g_value_get_float (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_HUE;
      break;
    case PROP_SATURATION:
      postproc->saturation = g_value_get_float (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_SATURATION;
      break;
    case PROP_BRIGHTNESS:
      postproc->brightness = g_value_get_float (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_BRIGHTNESS;
      break;
    case PROP_CONTRAST:
      postproc->contrast = g_value_get_float (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_CONTRAST;
      break;
    case PROP_SCALE_METHOD:
      postproc->scale_method = g_value_get_enum (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_SCALE;
      break;
    case PROP_VIDEO_DIRECTION:
      postproc->video_direction = g_value_get_enum (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_VIDEO_DIRECTION;
      break;
#ifndef GST_REMOVE_DEPRECATED
    case PROP_SKIN_TONE_ENHANCEMENT:
      postproc->skintone_enhance = g_value_get_boolean (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_SKINTONE;
      break;
#endif
    case PROP_SKIN_TONE_ENHANCEMENT_LEVEL:
      postproc->skintone_value = g_value_get_uint (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_SKINTONE_LEVEL;
      break;
    case PROP_CROP_LEFT:
    {
      guint prev_crop_left = postproc->crop_left;
      postproc->crop_left = g_value_get_uint (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_CROP;
      do_reconf = (prev_crop_left != postproc->crop_left);
      break;
    }
    case PROP_CROP_RIGHT:
    {
      guint prev_crop_right = postproc->crop_right;
      postproc->crop_right = g_value_get_uint (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_CROP;
      do_reconf = (prev_crop_right != postproc->crop_right);
      break;
    }
    case PROP_CROP_TOP:
    {
      guint prev_crop_top = postproc->crop_top;
      postproc->crop_top = g_value_get_uint (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_CROP;
      do_reconf = (prev_crop_top != postproc->crop_top);
      break;
    }
    case PROP_CROP_BOTTOM:
    {
      guint prev_crop_bottom = postproc->crop_bottom;
      postproc->crop_bottom = g_value_get_uint (value);
      postproc->flags |= GST_VAAPI_POSTPROC_FLAG_CROP;
      do_reconf = (prev_crop_bottom != postproc->crop_bottom);
      break;
    }
    case PROP_HDR_TONE_MAP:
      postproc->hdr_tone_map = g_value_get_enum (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
  g_mutex_unlock (&postproc->postproc_lock);

  if (do_reconf || check_filter_update (postproc))
    gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (postproc));
}

static void
gst_vaapipostproc_get_property (GObject * object,
    guint prop_id, GValue * value, GParamSpec * pspec)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (object);

  g_mutex_lock (&postproc->postproc_lock);
  switch (prop_id) {
#ifndef GST_REMOVE_DEPRECATED
    case PROP_FORMAT:
      g_value_set_enum (value, postproc->format);
      break;
    case PROP_WIDTH:
      g_value_set_uint (value, postproc->width);
      break;
    case PROP_HEIGHT:
      g_value_set_uint (value, postproc->height);
      break;
#endif
    case PROP_FORCE_ASPECT_RATIO:
      g_value_set_boolean (value, postproc->keep_aspect);
      break;
    case PROP_DEINTERLACE_MODE:
      g_value_set_enum (value, postproc->deinterlace_mode);
      break;
    case PROP_DEINTERLACE_METHOD:
      g_value_set_enum (value, postproc->deinterlace_method);
      break;
    case PROP_DENOISE:
      g_value_set_float (value, postproc->denoise_level);
      break;
    case PROP_SHARPEN:
      g_value_set_float (value, postproc->sharpen_level);
      break;
    case PROP_HUE:
      g_value_set_float (value, postproc->hue);
      break;
    case PROP_SATURATION:
      g_value_set_float (value, postproc->saturation);
      break;
    case PROP_BRIGHTNESS:
      g_value_set_float (value, postproc->brightness);
      break;
    case PROP_CONTRAST:
      g_value_set_float (value, postproc->contrast);
      break;
    case PROP_SCALE_METHOD:
      g_value_set_enum (value, postproc->scale_method);
      break;
    case PROP_VIDEO_DIRECTION:
      g_value_set_enum (value, postproc->video_direction);
      break;
#ifndef GST_REMOVE_DEPRECATED
    case PROP_SKIN_TONE_ENHANCEMENT:
      g_value_set_boolean (value, postproc->skintone_enhance);
      break;
#endif
    case PROP_SKIN_TONE_ENHANCEMENT_LEVEL:
      g_value_set_uint (value, postproc->skintone_value);
      break;
    case PROP_CROP_LEFT:
      g_value_set_uint (value, postproc->crop_left);
      break;
    case PROP_CROP_RIGHT:
      g_value_set_uint (value, postproc->crop_right);
      break;
    case PROP_CROP_TOP:
      g_value_set_uint (value, postproc->crop_top);
      break;
    case PROP_CROP_BOTTOM:
      g_value_set_uint (value, postproc->crop_bottom);
      break;
    case PROP_HDR_TONE_MAP:
      g_value_set_enum (value, postproc->hdr_tone_map);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
  g_mutex_unlock (&postproc->postproc_lock);
}

static void
gst_vaapipostproc_class_init (GstVaapiPostprocClass * klass)
{
  GObjectClass *const object_class = G_OBJECT_CLASS (klass);
  GstElementClass *const element_class = GST_ELEMENT_CLASS (klass);
  GstBaseTransformClass *const trans_class = GST_BASE_TRANSFORM_CLASS (klass);
  GPtrArray *filter_ops;
  GstVaapiFilterOpInfo *filter_op;

  GST_DEBUG_CATEGORY_INIT (gst_debug_vaapipostproc,
      GST_PLUGIN_NAME, 0, GST_PLUGIN_DESC);

  gst_vaapi_plugin_base_class_init (GST_VAAPI_PLUGIN_BASE_CLASS (klass));

  object_class->finalize = gst_vaapipostproc_finalize;
  object_class->set_property = gst_vaapipostproc_set_property;
  object_class->get_property = gst_vaapipostproc_get_property;
  trans_class->start = gst_vaapipostproc_start;
  trans_class->stop = gst_vaapipostproc_stop;
  trans_class->fixate_caps = gst_vaapipostproc_fixate_caps;
  trans_class->transform_caps = gst_vaapipostproc_transform_caps;
  trans_class->transform_size = gst_vaapipostproc_transform_size;
  trans_class->transform_meta = gst_vaapipostproc_transform_meta;
  trans_class->transform = gst_vaapipostproc_transform;
  trans_class->set_caps = gst_vaapipostproc_set_caps;
  trans_class->query = gst_vaapipostproc_query;
  trans_class->propose_allocation = gst_vaapipostproc_propose_allocation;
  trans_class->decide_allocation = gst_vaapipostproc_decide_allocation;
  trans_class->src_event = gst_vaapipostproc_src_event;
  trans_class->sink_event = gst_vaapipostproc_sink_event;

  trans_class->prepare_output_buffer = gst_vaapipostproc_prepare_output_buffer;

  element_class->set_context = gst_vaapi_base_set_context;
  gst_element_class_set_static_metadata (element_class,
      "VA-API video postprocessing",
      "Filter/Converter/Effect/Video/Scaler/Deinterlace/Hardware",
      GST_PLUGIN_DESC, "Gwenole Beauchesne <gwenole.beauchesne@intel.com>");

  /* sink pad */
  gst_element_class_add_static_pad_template (element_class,
      &gst_vaapipostproc_sink_factory);

  /* src pad */
  gst_element_class_add_static_pad_template (element_class,
      &gst_vaapipostproc_src_factory);

  /**
   * GstVaapiPostproc:hdr-tone-map:
   *
   * Selects whether HDR tone mapping should not be applied or if it
   * should be only applied on content that has the HDR meta on the caps.
   */
  g_object_class_install_property
      (object_class,
      PROP_HDR_TONE_MAP,
      g_param_spec_enum ("hdr-tone-map",
          "HDR Tone Map",
          "Apply HDR tone mapping algorithm",
          GST_VAAPI_TYPE_HDR_TONE_MAP,
          GST_VAAPI_HDR_TONE_MAP_AUTO,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiPostproc:deinterlace-mode:
   *
   * This selects whether the deinterlacing should always be applied
   * or if they should only be applied on content that has the
   * "interlaced" flag on the caps.
   */
  g_object_class_install_property
      (object_class,
      PROP_DEINTERLACE_MODE,
      g_param_spec_enum ("deinterlace-mode",
          "Deinterlace mode",
          "Deinterlace mode to use",
          GST_VAAPI_TYPE_DEINTERLACE_MODE,
          DEFAULT_DEINTERLACE_MODE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiPostproc:deinterlace-method:
   *
   * This selects the deinterlacing method to apply.
   */
  g_object_class_install_property
      (object_class,
      PROP_DEINTERLACE_METHOD,
      g_param_spec_enum ("deinterlace-method",
          "Deinterlace method",
          "Deinterlace method to use",
          GST_VAAPI_TYPE_DEINTERLACE_METHOD,
          DEFAULT_DEINTERLACE_METHOD,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));


  filter_ops = gst_vaapi_filter_get_operations (NULL);
  if (!filter_ops)
    return;

#ifndef GST_REMOVE_DEPRECATED
  /**
   * GstVaapiPostproc:format:
   *
   * The forced output pixel format, expressed as a #GstVideoFormat.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_FORMAT);
  if (filter_op)
    g_object_class_install_property (object_class,
        PROP_FORMAT, filter_op->pspec);

  /**
   * GstVaapiPostproc:width:
   *
   * The forced output width in pixels. If set to zero, the width is
   * calculated from the height if aspect ration is preserved, or
   * inherited from the sink caps width
   */
  g_object_class_install_property
      (object_class,
      PROP_WIDTH,
      g_param_spec_uint ("width",
          "Width",
          "Forced output width",
          0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiPostproc:height:
   *
   * The forced output height in pixels. If set to zero, the height is
   * calculated from the width if aspect ration is preserved, or
   * inherited from the sink caps height
   */
  g_object_class_install_property
      (object_class,
      PROP_HEIGHT,
      g_param_spec_uint ("height",
          "Height",
          "Forced output height",
          0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
#endif

  /**
   * GstVaapiPostproc:crop-left:
   *
   * The number of pixels to crop at left.
   */
  g_object_class_install_property
      (object_class,
      PROP_CROP_LEFT,
      g_param_spec_uint ("crop-left",
          "Crop Left",
          "Pixels to crop at left",
          0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiPostproc:crop-right:
   *
   * The number of pixels to crop at right.
   */
  g_object_class_install_property
      (object_class,
      PROP_CROP_RIGHT,
      g_param_spec_uint ("crop-right",
          "Crop Right",
          "Pixels to crop at right",
          0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
  * GstVaapiPostproc:crop-top:
  *
  * The number of pixels to crop at top.
  */
  g_object_class_install_property
      (object_class,
      PROP_CROP_TOP,
      g_param_spec_uint ("crop-top",
          "Crop Top",
          "Pixels to crop at top",
          0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiPostproc:crop-bottom:
   *
   * The number of pixels to crop at bottom.
   */
  g_object_class_install_property
      (object_class,
      PROP_CROP_BOTTOM,
      g_param_spec_uint ("crop-bottom",
          "Crop Bottom",
          "Pixels to crop at bottom",
          0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiPostproc:force-aspect-ratio:
   *
   * When enabled, scaling respects video aspect ratio; when disabled,
   * the video is distorted to fit the width and height properties.
   */
  g_object_class_install_property
      (object_class,
      PROP_FORCE_ASPECT_RATIO,
      g_param_spec_boolean ("force-aspect-ratio",
          "Force aspect ratio",
          "When enabled, scaling will respect original aspect ratio",
          TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  /**
   * GstVaapiPostproc:denoise:
   *
   * The level of noise reduction to apply.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_DENOISE);
  if (filter_op)
    g_object_class_install_property (object_class,
        PROP_DENOISE, filter_op->pspec);

  /**
   * GstVaapiPostproc:sharpen:
   *
   * The level of sharpening to apply for positive values, or the
   * level of blurring for negative values.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_SHARPEN);
  if (filter_op)
    g_object_class_install_property (object_class,
        PROP_SHARPEN, filter_op->pspec);

  /**
   * GstVaapiPostproc:hue:
   *
   * The color hue, expressed as a float value. Range is -180.0 to
   * 180.0. Default value is 0.0 and represents no modification.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_HUE);
  if (filter_op)
    g_object_class_install_property (object_class, PROP_HUE, filter_op->pspec);

  /**
   * GstVaapiPostproc:saturation:
   *
   * The color saturation, expressed as a float value. Range is 0.0 to
   * 2.0. Default value is 1.0 and represents no modification.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_SATURATION);
  if (filter_op)
    g_object_class_install_property (object_class,
        PROP_SATURATION, filter_op->pspec);

  /**
   * GstVaapiPostproc:brightness:
   *
   * The color brightness, expressed as a float value. Range is -1.0
   * to 1.0. Default value is 0.0 and represents no modification.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_BRIGHTNESS);
  if (filter_op)
    g_object_class_install_property (object_class,
        PROP_BRIGHTNESS, filter_op->pspec);

  /**
   * GstVaapiPostproc:contrast:
   *
   * The color contrast, expressed as a float value. Range is 0.0 to
   * 2.0. Default value is 1.0 and represents no modification.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_CONTRAST);
  if (filter_op)
    g_object_class_install_property (object_class,
        PROP_CONTRAST, filter_op->pspec);

  /**
   * GstVaapiPostproc:scale-method:
   *
   * The scaling method to use, expressed as an enum value. See
   * #GstVaapiScaleMethod.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_SCALING);
  if (filter_op)
    g_object_class_install_property (object_class,
        PROP_SCALE_METHOD, filter_op->pspec);

  /**
   * GstVaapiPostproc:video-direction:
   *
   * The video-direction to use, expressed as an enum value. See
   * #GstVideoDirection.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_VIDEO_DIRECTION);
  if (filter_op)
    g_object_class_install_property (object_class,
        PROP_VIDEO_DIRECTION, filter_op->pspec);

#ifndef GST_REMOVE_DEPRECATED
  /**
   * GstVaapiPostproc:skin-tone-enhancement:
   *
   * Apply the skin tone enhancement algorithm.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_SKINTONE);
  if (filter_op)
    g_object_class_install_property (object_class,
        PROP_SKIN_TONE_ENHANCEMENT, filter_op->pspec);
#endif

  /**
   * GstVaapiPostproc:skin-tone-enhancement-setting:
   *
   * Apply the skin tone enhancement algorithm with specified value.
   */
  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_SKINTONE_LEVEL);
  if (filter_op)
    g_object_class_install_property (object_class,
        PROP_SKIN_TONE_ENHANCEMENT_LEVEL, filter_op->pspec);

  g_ptr_array_unref (filter_ops);
}

static float *
find_value_ptr (GstVaapiPostproc * postproc, GstVaapiFilterOp op)
{
  switch (op) {
    case GST_VAAPI_FILTER_OP_HUE:
      return &postproc->hue;
    case GST_VAAPI_FILTER_OP_SATURATION:
      return &postproc->saturation;
    case GST_VAAPI_FILTER_OP_BRIGHTNESS:
      return &postproc->brightness;
    case GST_VAAPI_FILTER_OP_CONTRAST:
      return &postproc->contrast;
    default:
      return NULL;
  }
}

static void
cb_set_default_value (GstVaapiPostproc * postproc, GPtrArray * filter_ops,
    GstVaapiFilterOp op)
{
  GstVaapiFilterOpInfo *filter_op;
  GParamSpecFloat *pspec;
  float *var;

  filter_op = find_filter_op (filter_ops, op);
  if (!filter_op)
    return;
  var = find_value_ptr (postproc, op);
  if (!var)
    return;
  pspec = G_PARAM_SPEC_FLOAT (filter_op->pspec);
  *var = pspec->default_value;
}

static void
skintone_set_default_value (GstVaapiPostproc * postproc, GPtrArray * filter_ops)
{
  GstVaapiFilterOpInfo *filter_op;
  GParamSpecUInt *pspec;

  filter_op = find_filter_op (filter_ops, GST_VAAPI_FILTER_OP_SKINTONE_LEVEL);
  if (!filter_op)
    return;
  pspec = G_PARAM_SPEC_UINT (filter_op->pspec);
  postproc->skintone_value = pspec->default_value;
}

static void
gst_vaapipostproc_init (GstVaapiPostproc * postproc)
{
  GPtrArray *filter_ops;
  guint i;

  gst_vaapi_plugin_base_init (GST_VAAPI_PLUGIN_BASE (postproc),
      GST_CAT_DEFAULT);

  g_mutex_init (&postproc->postproc_lock);
  postproc->format = DEFAULT_FORMAT;
  postproc->hdr_tone_map = GST_VAAPI_HDR_TONE_MAP_AUTO;
  postproc->deinterlace_mode = DEFAULT_DEINTERLACE_MODE;
  postproc->deinterlace_method = DEFAULT_DEINTERLACE_METHOD;
  postproc->field_duration = GST_CLOCK_TIME_NONE;
  postproc->keep_aspect = TRUE;
  postproc->get_va_surfaces = TRUE;
  postproc->forward_crop = FALSE;

  /* AUTO is not valid for tag_video_direction, this is just to
   * ensure we setup the method as sink event tag */
  postproc->tag_video_direction = GST_VIDEO_ORIENTATION_AUTO;

  filter_ops = gst_vaapi_filter_get_operations (NULL);
  if (filter_ops) {
    for (i = GST_VAAPI_FILTER_OP_HUE; i <= GST_VAAPI_FILTER_OP_CONTRAST; i++)
      cb_set_default_value (postproc, filter_ops, i);

    skintone_set_default_value (postproc, filter_ops);
    g_ptr_array_unref (filter_ops);
  }

  gst_video_info_init (&postproc->sinkpad_info);
  gst_video_info_init (&postproc->srcpad_info);
  gst_video_info_init (&postproc->filter_pool_info);
}

/* ------------------------------------------------------------------------ */
/* --- GstColorBalance interface                                        --- */
/* ------------------------------------------------------------------------ */

#define CB_CHANNEL_FACTOR 1000.0

typedef struct
{
  GstVaapiFilterOp op;
  const gchar *name;
} ColorBalanceChannel;

ColorBalanceChannel cb_channels[] = {
  {
      GST_VAAPI_FILTER_OP_HUE, "VA_FILTER_HUE"}, {
      GST_VAAPI_FILTER_OP_SATURATION, "VA_FILTER_SATURATION"}, {
      GST_VAAPI_FILTER_OP_BRIGHTNESS, "VA_FILTER_BRIGHTNESS"}, {
      GST_VAAPI_FILTER_OP_CONTRAST, "VA_FILTER_CONTRAST"},
};

static void
cb_channels_init (GstVaapiPostproc * postproc)
{
  GPtrArray *filter_ops;
  GstVaapiFilterOpInfo *filter_op;
  GParamSpecFloat *pspec;
  GstColorBalanceChannel *channel;
  guint i;

  if (postproc->cb_channels)
    return;

  g_mutex_lock (&postproc->postproc_lock);
  if (!gst_vaapipostproc_ensure_filter (postproc)) {
    g_mutex_unlock (&postproc->postproc_lock);
    return;
  }
  g_mutex_unlock (&postproc->postproc_lock);

  filter_ops = postproc->filter_ops ? g_ptr_array_ref (postproc->filter_ops)
      : gst_vaapi_filter_get_operations (postproc->filter);
  if (!filter_ops)
    return;

  for (i = 0; i < G_N_ELEMENTS (cb_channels); i++) {
    filter_op = find_filter_op (filter_ops, cb_channels[i].op);
    if (!filter_op)
      continue;

    pspec = G_PARAM_SPEC_FLOAT (filter_op->pspec);
    channel = g_object_new (GST_TYPE_COLOR_BALANCE_CHANNEL, NULL);
    channel->label = g_strdup (cb_channels[i].name);
    channel->min_value = pspec->minimum * CB_CHANNEL_FACTOR;
    channel->max_value = pspec->maximum * CB_CHANNEL_FACTOR;

    postproc->cb_channels = g_list_prepend (postproc->cb_channels, channel);
  }

  g_ptr_array_unref (filter_ops);
}

static const GList *
gst_vaapipostproc_colorbalance_list_channels (GstColorBalance * balance)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (balance);

  cb_channels_init (postproc);
  return postproc->cb_channels;
}

static gfloat *
cb_get_value_ptr (GstVaapiPostproc * postproc,
    GstColorBalanceChannel * channel, GstVaapiPostprocFlags * flags)
{
  guint i;
  gfloat *ret = NULL;

  for (i = 0; i < G_N_ELEMENTS (cb_channels); i++) {
    if (g_ascii_strcasecmp (cb_channels[i].name, channel->label) == 0)
      break;
  }
  if (i >= G_N_ELEMENTS (cb_channels))
    return NULL;

  ret = find_value_ptr (postproc, cb_channels[i].op);
  if (flags)
    *flags = 1 << cb_channels[i].op;
  return ret;
}

static void
gst_vaapipostproc_colorbalance_set_value (GstColorBalance * balance,
    GstColorBalanceChannel * channel, gint value)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (balance);
  GstVaapiPostprocFlags flags;
  gfloat new_val, *var;

  value = CLAMP (value, channel->min_value, channel->max_value);
  new_val = (gfloat) value / CB_CHANNEL_FACTOR;

  var = cb_get_value_ptr (postproc, channel, &flags);
  if (var) {
    *var = new_val;
    g_mutex_lock (&postproc->postproc_lock);
    postproc->flags |= flags;
    g_mutex_unlock (&postproc->postproc_lock);
    gst_color_balance_value_changed (balance, channel, value);
    if (check_filter_update (postproc))
      gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (postproc));
    return;
  }

  GST_WARNING_OBJECT (postproc, "unknown channel %s", channel->label);
}

static gint
gst_vaapipostproc_colorbalance_get_value (GstColorBalance * balance,
    GstColorBalanceChannel * channel)
{
  GstVaapiPostproc *const postproc = GST_VAAPIPOSTPROC (balance);
  gfloat *var;
  gint new_val;

  var = cb_get_value_ptr (postproc, channel, NULL);
  if (var) {
    new_val = (gint) ((*var) * CB_CHANNEL_FACTOR);
    new_val = CLAMP (new_val, channel->min_value, channel->max_value);
    return new_val;
  }

  GST_WARNING_OBJECT (postproc, "unknown channel %s", channel->label);
  return G_MININT;
}

static GstColorBalanceType
gst_vaapipostproc_colorbalance_get_balance_type (GstColorBalance * balance)
{
  return GST_COLOR_BALANCE_HARDWARE;
}

static void
gst_vaapipostproc_colorbalance_init (gpointer iface, gpointer data)
{
  GstColorBalanceInterface *cbface = iface;
  cbface->list_channels = gst_vaapipostproc_colorbalance_list_channels;
  cbface->set_value = gst_vaapipostproc_colorbalance_set_value;
  cbface->get_value = gst_vaapipostproc_colorbalance_get_value;
  cbface->get_balance_type = gst_vaapipostproc_colorbalance_get_balance_type;
}
