va: Add base transform class.
authorVíctor Manuel Jáquez Leal <vjaquez@igalia.com>
Wed, 17 Feb 2021 16:15:22 +0000 (17:15 +0100)
committerVíctor Manuel Jáquez Leal <vjaquez@igalia.com>
Tue, 10 Aug 2021 17:31:58 +0000 (17:31 +0000)
This base transform class is a derivable class for VA-based filters,
for example vapostproc right now, but it will be used also for
future elements such as vadeinterlace.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2442>

sys/va/gstvabasetransform.c [new file with mode: 0644]
sys/va/gstvabasetransform.h [new file with mode: 0644]
sys/va/meson.build

diff --git a/sys/va/gstvabasetransform.c b/sys/va/gstvabasetransform.c
new file mode 100644 (file)
index 0000000..6d6ea97
--- /dev/null
@@ -0,0 +1,807 @@
+/* GStreamer
+ * Copyright (C) 2021 Igalia, S.L.
+ *     Author: Víctor Jáquez <vjaquez@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstvabasetransform.h"
+
+#include "gstvaallocator.h"
+#include "gstvacaps.h"
+#include "gstvapool.h"
+#include "gstvautils.h"
+
+#define GST_CAT_DEFAULT gst_va_base_transform_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+struct _GstVaBaseTransformPrivate
+{
+  GstVideoInfo srcpad_info;
+
+  GstBufferPool *other_pool;
+
+  GstCaps *sinkpad_caps;
+  GstVideoInfo sinkpad_info;
+  GstBufferPool *sinkpad_pool;
+};
+
+#define gst_va_base_transform_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstVaBaseTransform, gst_va_base_transform,
+    GST_TYPE_BASE_TRANSFORM, G_ADD_PRIVATE (GstVaBaseTransform)
+    GST_DEBUG_CATEGORY_INIT (gst_va_base_transform_debug,
+        "vabasetransform", 0, "vabasetransform element");
+    );
+
+extern GRecMutex GST_VA_SHARED_LOCK;
+
+static void
+gst_va_base_transform_dispose (GObject * object)
+{
+  GstVaBaseTransform *self = GST_VA_BASE_TRANSFORM (object);
+
+  if (self->priv->other_pool) {
+    gst_buffer_pool_set_active (self->priv->other_pool, FALSE);
+    gst_clear_object (&self->priv->other_pool);
+  }
+
+  gst_clear_caps (&self->out_caps);
+  gst_clear_caps (&self->in_caps);
+
+  gst_clear_object (&self->filter);
+  gst_clear_object (&self->display);
+
+  if (self->priv->sinkpad_pool) {
+    gst_buffer_pool_set_active (self->priv->sinkpad_pool, FALSE);
+    gst_clear_object (&self->priv->sinkpad_pool);
+  }
+
+  gst_clear_caps (&self->priv->sinkpad_caps);
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_va_base_transform_init (GstVaBaseTransform * self)
+{
+  gst_base_transform_set_qos_enabled (GST_BASE_TRANSFORM (self), TRUE);
+
+  self->priv = gst_va_base_transform_get_instance_private (self);
+}
+
+static gboolean
+gst_va_base_transform_query (GstBaseTransform * trans,
+    GstPadDirection direction, GstQuery * query)
+{
+  GstVaBaseTransform *self = GST_VA_BASE_TRANSFORM (trans);
+  gboolean ret = FALSE;
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_CONTEXT:
+    {
+      GstVaDisplay *display = NULL;
+
+      gst_object_replace ((GstObject **) & display,
+          (GstObject *) self->display);
+      ret = gst_va_handle_context_query (GST_ELEMENT_CAST (self), query,
+          display);
+      gst_clear_object (&display);
+      break;
+    }
+    default:
+      ret = GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction,
+          query);
+      break;
+  }
+
+  return ret;
+}
+
+static gboolean
+gst_va_base_transform_set_caps (GstBaseTransform * trans, GstCaps * incaps,
+    GstCaps * outcaps)
+{
+  GstVaBaseTransform *self = GST_VA_BASE_TRANSFORM (trans);
+  GstVaBaseTransformClass *fclass;
+  GstVideoInfo in_info, out_info;
+  gboolean res;
+
+  /* input caps */
+  if (!gst_video_info_from_caps (&in_info, incaps))
+    goto invalid_caps;
+
+  /* output caps */
+  if (!gst_video_info_from_caps (&out_info, outcaps))
+    goto invalid_caps;
+
+  fclass = GST_VA_BASE_TRANSFORM_GET_CLASS (self);
+  if (fclass->set_info)
+    res = fclass->set_info (self, incaps, &in_info, outcaps, &out_info);
+  else
+    res = TRUE;
+
+  self->negotiated = res;
+
+  if (res) {
+    gst_caps_replace (&self->in_caps, incaps);
+    gst_caps_replace (&self->out_caps, outcaps);
+
+    self->in_info = in_info;
+    self->out_info = out_info;
+  }
+
+  if (self->priv->sinkpad_pool) {
+    gst_buffer_pool_set_active (self->priv->sinkpad_pool, FALSE);
+    gst_clear_object (&self->priv->sinkpad_pool);
+  }
+
+  if (self->priv->other_pool) {
+    gst_buffer_pool_set_active (self->priv->other_pool, FALSE);
+    gst_clear_object (&self->priv->other_pool);
+  }
+
+  return res;
+
+  /* ERRORS */
+invalid_caps:
+  {
+    GST_ERROR_OBJECT (self, "invalid caps");
+    self->negotiated = FALSE;
+    return FALSE;
+  }
+}
+
+/* Answer upstream allocation query. */
+static gboolean
+gst_va_base_transform_propose_allocation (GstBaseTransform * trans,
+    GstQuery * decide_query, GstQuery * query)
+{
+  GstVaBaseTransform *self = GST_VA_BASE_TRANSFORM (trans);
+  GstAllocator *allocator = NULL;
+  GstAllocationParams params = { 0, };
+  GstBufferPool *pool;
+  GstCaps *caps;
+  GstVideoInfo info;
+  gboolean update_allocator = FALSE;
+  guint size, usage_hint = VA_SURFACE_ATTRIB_USAGE_HINT_GENERIC;        /* it migth be
+                                                                         * used by a va
+                                                                         * decoder */
+
+  gst_clear_caps (&self->priv->sinkpad_caps);
+
+  if (!GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (trans,
+          decide_query, query))
+    return FALSE;
+
+  /* passthrough, we're done */
+  if (!decide_query)
+    return TRUE;
+
+  if (gst_query_get_n_allocation_pools (query) > 0)
+    return TRUE;
+
+  gst_query_parse_allocation (query, &caps, NULL);
+  if (!caps)
+    return FALSE;
+  if (!gst_video_info_from_caps (&info, caps)) {
+    GST_ERROR_OBJECT (self, "Cannot parse caps %" GST_PTR_FORMAT, caps);
+    return FALSE;
+  }
+
+  size = GST_VIDEO_INFO_SIZE (&info);
+
+  if (gst_query_get_n_allocation_params (query) > 0) {
+    gst_query_parse_nth_allocation_param (query, 0, &allocator, &params);
+    if (!GST_IS_VA_DMABUF_ALLOCATOR (allocator)
+        && !GST_IS_VA_ALLOCATOR (allocator))
+      gst_clear_object (&allocator);
+    update_allocator = TRUE;
+  } else {
+    gst_allocation_params_init (&params);
+  }
+
+  if (!allocator) {
+    if (!(allocator = gst_va_base_transform_allocator_from_caps (self, caps)))
+      return FALSE;
+  }
+
+  pool = gst_va_pool_new_with_config (caps, size, 1 + self->extra_min_buffers,
+      0, usage_hint, allocator, &params);
+  if (!pool) {
+    gst_object_unref (allocator);
+    goto config_failed;
+  }
+
+  if (update_allocator)
+    gst_query_set_nth_allocation_param (query, 0, allocator, &params);
+  else
+    gst_query_add_allocation_param (query, allocator, &params);
+
+  gst_query_add_allocation_pool (query, pool, size, 1 + self->extra_min_buffers,
+      0);
+
+  GST_DEBUG_OBJECT (self,
+      "proposing %" GST_PTR_FORMAT " with allocator %" GST_PTR_FORMAT,
+      pool, allocator);
+
+  gst_object_unref (allocator);
+  gst_object_unref (pool);
+
+  gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
+
+  self->priv->sinkpad_caps = gst_caps_ref (caps);
+
+  return TRUE;
+
+  /* ERRORS */
+config_failed:
+  {
+    GST_ERROR_OBJECT (self, "failed to set config");
+    return FALSE;
+  }
+}
+
+static GstBufferPool *
+_create_other_pool (GstAllocator * allocator,
+    GstAllocationParams * params, GstCaps * caps, guint size)
+{
+  GstBufferPool *pool = NULL;
+  GstStructure *config;
+
+  pool = gst_video_buffer_pool_new ();
+  config = gst_buffer_pool_get_config (pool);
+
+  gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
+  gst_buffer_pool_config_set_allocator (config, allocator, params);
+  if (!gst_buffer_pool_set_config (pool, config)) {
+    gst_clear_object (&pool);
+  }
+
+  return pool;
+}
+
+/* configure the allocation query that was answered downstream, we can
+ * configure some properties on it. Only it's called when not in
+ * passthrough mode. */
+static gboolean
+gst_va_base_transform_decide_allocation (GstBaseTransform * trans,
+    GstQuery * query)
+{
+  GstVaBaseTransform *self = GST_VA_BASE_TRANSFORM (trans);
+  GstAllocator *allocator = NULL, *other_allocator = NULL;
+  GstAllocationParams params, other_params;
+  GstBufferPool *pool = NULL, *other_pool = NULL;
+  GstCaps *outcaps = NULL;
+  GstStructure *config;
+  GstVideoInfo vinfo;
+  guint min, max, size = 0, usage_hint = VA_SURFACE_ATTRIB_USAGE_HINT_VPP_WRITE;
+  gboolean update_pool, update_allocator, has_videometa, copy_frames;
+
+  gst_query_parse_allocation (query, &outcaps, NULL);
+
+  gst_allocation_params_init (&other_params);
+  gst_allocation_params_init (&params);
+
+  if (!gst_video_info_from_caps (&vinfo, outcaps)) {
+    GST_ERROR_OBJECT (self, "Cannot parse caps %" GST_PTR_FORMAT, outcaps);
+    return FALSE;
+  }
+
+  if (gst_query_get_n_allocation_params (query) > 0) {
+    gst_query_parse_nth_allocation_param (query, 0, &allocator, &other_params);
+    if (allocator && !(GST_IS_VA_DMABUF_ALLOCATOR (allocator)
+            || GST_IS_VA_ALLOCATOR (allocator))) {
+      /* save the allocator for the other pool */
+      other_allocator = allocator;
+      allocator = NULL;
+    }
+    update_allocator = TRUE;
+  } else {
+    update_allocator = FALSE;
+  }
+
+  if (gst_query_get_n_allocation_pools (query) > 0) {
+    gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
+
+    if (pool) {
+      if (!GST_IS_VA_POOL (pool)) {
+        GST_DEBUG_OBJECT (self,
+            "may need other pool for copy frames %" GST_PTR_FORMAT, pool);
+        other_pool = pool;
+        pool = NULL;
+      }
+    }
+
+    update_pool = TRUE;
+  } else {
+    size = GST_VIDEO_INFO_SIZE (&vinfo);
+    min = 1;
+    max = 0;
+    update_pool = FALSE;
+  }
+
+  if (!allocator) {
+    /* XXX(victor): USAGE_HINT_VPP_WRITE creates tiled dmabuf frames
+     * in iHD */
+    if (gst_caps_is_dmabuf (outcaps) && GST_VIDEO_INFO_IS_RGB (&vinfo))
+      usage_hint = VA_SURFACE_ATTRIB_USAGE_HINT_GENERIC;
+    if (!(allocator =
+            gst_va_base_transform_allocator_from_caps (self, outcaps)))
+      return FALSE;
+  }
+
+  if (!pool)
+    pool = gst_va_pool_new ();
+
+  config = gst_buffer_pool_get_config (pool);
+  gst_buffer_pool_config_set_allocator (config, allocator, &params);
+  gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
+  gst_buffer_pool_config_set_params (config, outcaps, size, min, max);
+  gst_buffer_pool_config_set_va_allocation_params (config, usage_hint);
+  if (!gst_buffer_pool_set_config (pool, config)) {
+    gst_object_unref (allocator);
+    gst_object_unref (pool);
+    return FALSE;
+  }
+
+  if (GST_IS_VA_DMABUF_ALLOCATOR (allocator)) {
+    gst_va_dmabuf_allocator_get_format (allocator, &self->priv->srcpad_info,
+        NULL);
+  } else if (GST_IS_VA_ALLOCATOR (allocator)) {
+    gst_va_allocator_get_format (allocator, &self->priv->srcpad_info, NULL);
+  }
+
+  if (update_allocator)
+    gst_query_set_nth_allocation_param (query, 0, allocator, &params);
+  else
+    gst_query_add_allocation_param (query, allocator, &params);
+
+  if (update_pool)
+    gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
+  else
+    gst_query_add_allocation_pool (query, pool, size, min, max);
+
+  has_videometa = gst_query_find_allocation_meta (query,
+      GST_VIDEO_META_API_TYPE, NULL);
+
+  copy_frames = (!has_videometa && gst_va_pool_requires_video_meta (pool)
+      && gst_caps_is_raw (outcaps));
+  if (copy_frames) {
+    if (other_pool) {
+      gst_object_replace ((GstObject **) & self->priv->other_pool,
+          (GstObject *) other_pool);
+    } else {
+      self->priv->other_pool =
+          _create_other_pool (other_allocator, &other_params, outcaps, size);
+    }
+    GST_DEBUG_OBJECT (self, "Use the other pool for copy %" GST_PTR_FORMAT,
+        self->priv->other_pool);
+  } else {
+    gst_clear_object (&self->priv->other_pool);
+  }
+
+  GST_DEBUG_OBJECT (self,
+      "decided pool %" GST_PTR_FORMAT " with allocator %" GST_PTR_FORMAT,
+      pool, allocator);
+
+  gst_object_unref (allocator);
+  gst_object_unref (pool);
+  gst_clear_object (&other_allocator);
+  gst_clear_object (&other_pool);
+
+  /* removes allocation metas */
+  return GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans,
+      query);
+
+}
+
+/* output buffers must be from our VA-based pool, they cannot be
+ * system-allocated */
+static gboolean
+gst_va_base_transform_transform_size (GstBaseTransform * trans,
+    GstPadDirection direction, GstCaps * caps, gsize size,
+    GstCaps * othercaps, gsize * othersize)
+{
+  return FALSE;
+}
+
+static GstFlowReturn
+gst_va_base_transform_generate_output (GstBaseTransform * trans,
+    GstBuffer ** outbuf)
+{
+  GstVaBaseTransform *self = GST_VA_BASE_TRANSFORM (trans);
+  GstVideoFrame src_frame;
+  GstVideoFrame dest_frame;
+  GstBuffer *buffer = NULL;
+  GstFlowReturn ret;
+
+  ret = GST_BASE_TRANSFORM_CLASS (parent_class)->generate_output (trans,
+      outbuf);
+
+  if (ret != GST_FLOW_OK || *outbuf == NULL)
+    return ret;
+
+  if (!self->priv->other_pool)
+    return GST_FLOW_OK;
+
+  /* Now need to copy the output buffer */
+  ret = GST_FLOW_ERROR;
+
+  if (!gst_buffer_pool_set_active (self->priv->other_pool, TRUE)) {
+    GST_WARNING_OBJECT (self, "failed to active the other pool %"
+        GST_PTR_FORMAT, self->priv->other_pool);
+    goto out;
+  }
+
+  ret = gst_buffer_pool_acquire_buffer (self->priv->other_pool, &buffer, NULL);
+  if (ret != GST_FLOW_OK)
+    goto out;
+
+  if (!gst_video_frame_map (&src_frame, &self->priv->srcpad_info, *outbuf,
+          GST_MAP_READ))
+    goto out;
+
+  if (!gst_video_frame_map (&dest_frame, &self->out_info, buffer,
+          GST_MAP_WRITE)) {
+    gst_video_frame_unmap (&src_frame);
+    goto out;
+  }
+
+  if (!gst_video_frame_copy (&dest_frame, &src_frame)) {
+    gst_video_frame_unmap (&src_frame);
+    gst_video_frame_unmap (&dest_frame);
+    goto out;
+  }
+
+  gst_video_frame_unmap (&src_frame);
+  gst_video_frame_unmap (&dest_frame);
+
+  gst_buffer_replace (outbuf, buffer);
+  ret = GST_FLOW_OK;
+
+out:
+  gst_clear_buffer (&buffer);
+  return ret;
+}
+
+static GstStateChangeReturn
+gst_va_base_transform_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  GstVaBaseTransform *self = GST_VA_BASE_TRANSFORM (element);
+  GstVaBaseTransformClass *klass = GST_VA_BASE_TRANSFORM_GET_CLASS (element);
+  GstStateChangeReturn ret;
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      if (!gst_va_ensure_element_data (element, klass->render_device_path,
+              &self->display))
+        goto open_failed;
+      gst_clear_object (&self->filter);
+      self->filter = gst_va_filter_new (self->display);
+      if (!gst_va_filter_open (self->filter))
+        goto open_failed;
+      if (klass->update_properties)
+        klass->update_properties (self);
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      gst_va_filter_close (self->filter);
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      gst_clear_object (&self->filter);
+      gst_clear_object (&self->display);
+      break;
+    default:
+      break;
+  }
+
+  return ret;
+
+  /* Errors */
+open_failed:
+  {
+    GST_ELEMENT_ERROR (self, LIBRARY, INIT, (NULL), ("Failed to open VPP"));
+    return GST_STATE_CHANGE_FAILURE;
+  }
+}
+
+static void
+gst_va_base_transform_set_context (GstElement * element, GstContext * context)
+{
+  GstVaDisplay *old_display, *new_display;
+  GstVaBaseTransform *self = GST_VA_BASE_TRANSFORM (element);
+  GstVaBaseTransformClass *klass = GST_VA_BASE_TRANSFORM_GET_CLASS (self);
+  gboolean ret;
+
+  old_display = self->display ? gst_object_ref (self->display) : NULL;
+  ret = gst_va_handle_set_context (element, context, klass->render_device_path,
+      &self->display);
+  new_display = self->display ? gst_object_ref (self->display) : NULL;
+
+  if (!ret
+      || (old_display && new_display && old_display != new_display
+          && self->filter)) {
+    GST_ELEMENT_WARNING (element, RESOURCE, BUSY,
+        ("Can't replace VA display while operating"), (NULL));
+  }
+
+  gst_clear_object (&old_display);
+  gst_clear_object (&new_display);
+
+  GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
+}
+
+static void
+gst_va_base_transform_class_init (GstVaBaseTransformClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *element_class;
+  GstBaseTransformClass *trans_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  element_class = GST_ELEMENT_CLASS (klass);
+  trans_class = GST_BASE_TRANSFORM_CLASS (klass);
+
+  gobject_class->dispose = gst_va_base_transform_dispose;
+
+  trans_class->query = GST_DEBUG_FUNCPTR (gst_va_base_transform_query);
+  trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_va_base_transform_set_caps);
+  trans_class->propose_allocation =
+      GST_DEBUG_FUNCPTR (gst_va_base_transform_propose_allocation);
+  trans_class->decide_allocation =
+      GST_DEBUG_FUNCPTR (gst_va_base_transform_decide_allocation);
+  trans_class->transform_size =
+      GST_DEBUG_FUNCPTR (gst_va_base_transform_transform_size);
+  trans_class->generate_output =
+      GST_DEBUG_FUNCPTR (gst_va_base_transform_generate_output);
+
+  element_class->set_context =
+      GST_DEBUG_FUNCPTR (gst_va_base_transform_set_context);
+  element_class->change_state =
+      GST_DEBUG_FUNCPTR (gst_va_base_transform_change_state);
+}
+
+GstAllocator *
+gst_va_base_transform_allocator_from_caps (GstVaBaseTransform * self,
+    GstCaps * caps)
+{
+  GstAllocator *allocator = NULL;
+
+  if (gst_caps_is_dmabuf (caps)) {
+    allocator = gst_va_dmabuf_allocator_new (self->display);
+  } else {
+    GArray *surface_formats = gst_va_filter_get_surface_formats (self->filter);
+    allocator = gst_va_allocator_new (self->display, surface_formats);
+  }
+
+  return allocator;
+}
+
+static inline gsize
+_get_plane_data_size (GstVideoInfo * info, guint plane)
+{
+  gint height, padded_height;
+
+  height = GST_VIDEO_INFO_HEIGHT (info);
+  padded_height =
+      GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (info->finfo, plane, height);
+
+  return GST_VIDEO_INFO_PLANE_STRIDE (info, plane) * padded_height;
+}
+
+static gboolean
+_try_import_dmabuf_unlocked (GstVaBaseTransform * self, GstBuffer * inbuf)
+{
+  GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
+  GstVideoMeta *meta;
+  GstVideoInfo in_info = btrans->in_info;
+  GstMemory *mems[GST_VIDEO_MAX_PLANES];
+  guint i, n_mem, n_planes;
+  gsize offset[GST_VIDEO_MAX_PLANES];
+  uintptr_t fd[GST_VIDEO_MAX_PLANES];
+
+  n_planes = GST_VIDEO_INFO_N_PLANES (&in_info);
+  n_mem = gst_buffer_n_memory (inbuf);
+  meta = gst_buffer_get_video_meta (inbuf);
+
+  /* This will eliminate most non-dmabuf out there */
+  if (!gst_is_dmabuf_memory (gst_buffer_peek_memory (inbuf, 0)))
+    return FALSE;
+
+  /* We cannot have multiple dmabuf per plane */
+  if (n_mem > n_planes)
+    return FALSE;
+
+  /* Update video info based on video meta */
+  if (meta) {
+    GST_VIDEO_INFO_WIDTH (&in_info) = meta->width;
+    GST_VIDEO_INFO_HEIGHT (&in_info) = meta->height;
+
+    for (i = 0; i < meta->n_planes; i++) {
+      GST_VIDEO_INFO_PLANE_OFFSET (&in_info, i) = meta->offset[i];
+      GST_VIDEO_INFO_PLANE_STRIDE (&in_info, i) = meta->stride[i];
+    }
+  }
+
+  /* Find and validate all memories */
+  for (i = 0; i < n_planes; i++) {
+    guint plane_size;
+    guint length;
+    guint mem_idx;
+    gsize mem_skip;
+
+    plane_size = _get_plane_data_size (&in_info, i);
+
+    if (!gst_buffer_find_memory (inbuf, in_info.offset[i], plane_size,
+            &mem_idx, &length, &mem_skip))
+      return FALSE;
+
+    /* We can't have more then one dmabuf per plane */
+    if (length != 1)
+      return FALSE;
+
+    mems[i] = gst_buffer_peek_memory (inbuf, mem_idx);
+
+    /* And all memory found must be dmabuf */
+    if (!gst_is_dmabuf_memory (mems[i]))
+      return FALSE;
+
+    offset[i] = mems[i]->offset + mem_skip;
+    fd[i] = gst_dmabuf_memory_get_fd (mems[i]);
+  }
+
+  /* Now create a VASurfaceID for the buffer */
+  return gst_va_dmabuf_memories_setup (btrans->display, &in_info, n_planes,
+      mems, fd, offset, VA_SURFACE_ATTRIB_USAGE_HINT_VPP_READ);
+}
+
+static GstBufferPool *
+_get_sinkpad_pool (GstVaBaseTransform * self)
+{
+  GstAllocator *allocator;
+  GstAllocationParams params = { 0, };
+  GstCaps *caps;
+  GstVideoInfo in_info;
+  guint size, usage_hint = VA_SURFACE_ATTRIB_USAGE_HINT_VPP_READ;
+
+  if (self->priv->sinkpad_pool)
+    return self->priv->sinkpad_pool;
+
+  gst_allocation_params_init (&params);
+
+  if (self->priv->sinkpad_caps) {
+    caps = self->priv->sinkpad_caps;
+    gst_video_info_from_caps (&in_info, caps);
+  } else {
+    caps = self->in_caps;
+    in_info = self->in_info;
+  }
+
+  size = GST_VIDEO_INFO_SIZE (&in_info);
+
+  allocator = gst_va_base_transform_allocator_from_caps (self, caps);
+  self->priv->sinkpad_pool = gst_va_pool_new_with_config (caps, size, 1, 0,
+      usage_hint, allocator, &params);
+  if (!self->priv->sinkpad_pool) {
+    gst_object_unref (allocator);
+    return NULL;
+  }
+
+  if (GST_IS_VA_DMABUF_ALLOCATOR (allocator)) {
+    gst_va_dmabuf_allocator_get_format (allocator, &self->priv->sinkpad_info,
+        NULL);
+  } else if (GST_IS_VA_ALLOCATOR (allocator)) {
+    gst_va_allocator_get_format (allocator, &self->priv->sinkpad_info, NULL);
+  }
+
+  gst_object_unref (allocator);
+
+  gst_buffer_pool_set_active (self->priv->sinkpad_pool, TRUE);
+
+  return self->priv->sinkpad_pool;
+}
+
+static gboolean
+_try_import_buffer (GstVaBaseTransform * self, GstBuffer * inbuf)
+{
+  VASurfaceID surface;
+  gboolean ret;
+
+  surface = gst_va_buffer_get_surface (inbuf);
+  if (surface != VA_INVALID_ID)
+    return TRUE;
+
+  g_rec_mutex_lock (&GST_VA_SHARED_LOCK);
+  ret = _try_import_dmabuf_unlocked (self, inbuf);
+  g_rec_mutex_unlock (&GST_VA_SHARED_LOCK);
+
+  return ret;
+}
+
+GstFlowReturn
+gst_va_base_transform_import_buffer (GstVaBaseTransform * self,
+    GstBuffer * inbuf, GstBuffer ** buf)
+{
+  GstBuffer *buffer = NULL;
+  GstBufferPool *pool;
+  GstFlowReturn ret;
+  GstVideoFrame in_frame, out_frame;
+  gboolean imported, copied;
+
+  imported = _try_import_buffer (self, inbuf);
+  if (imported) {
+    *buf = gst_buffer_ref (inbuf);
+    return GST_FLOW_OK;
+  }
+
+  /* input buffer doesn't come from a vapool, thus it is required to
+   * have a pool, grab from it a new buffer and copy the input
+   * buffer to the new one */
+  if (!(pool = _get_sinkpad_pool (self)))
+    return GST_FLOW_ERROR;
+
+  ret = gst_buffer_pool_acquire_buffer (pool, &buffer, NULL);
+  if (ret != GST_FLOW_OK)
+    return ret;
+
+  GST_LOG_OBJECT (self, "copying input frame");
+
+  if (!gst_video_frame_map (&in_frame, &self->in_info, inbuf, GST_MAP_READ))
+    goto invalid_buffer;
+
+  if (!gst_video_frame_map (&out_frame, &self->priv->sinkpad_info, buffer,
+          GST_MAP_WRITE)) {
+    gst_video_frame_unmap (&in_frame);
+    goto invalid_buffer;
+  }
+
+  copied = gst_video_frame_copy (&out_frame, &in_frame);
+
+  gst_video_frame_unmap (&out_frame);
+  gst_video_frame_unmap (&in_frame);
+
+  if (!copied)
+    goto invalid_buffer;
+
+  /* strictly speaking this is not needed but let's play safe */
+  if (!gst_buffer_copy_into (buffer, inbuf, GST_BUFFER_COPY_FLAGS |
+          GST_BUFFER_COPY_TIMESTAMPS, 0, -1))
+    return GST_FLOW_ERROR;
+
+  *buf = buffer;
+
+  return GST_FLOW_OK;
+
+invalid_buffer:
+  {
+    GST_ELEMENT_WARNING (self, CORE, NOT_IMPLEMENTED, (NULL),
+        ("invalid video buffer received"));
+    if (buffer)
+      gst_buffer_unref (buffer);
+    return GST_FLOW_OK;
+  }
+}
diff --git a/sys/va/gstvabasetransform.h b/sys/va/gstvabasetransform.h
new file mode 100644 (file)
index 0000000..3dcc0a5
--- /dev/null
@@ -0,0 +1,90 @@
+/* GStreamer
+ * Copyright (C) 2021 Igalia, S.L.
+ *     Author: Víctor Jáquez <vjaquez@igalia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <gst/base/gstbasetransform.h>
+
+#include "gstvafilter.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_VA_BASE_TRANSFORM            (gst_va_base_transform_get_type())
+#define GST_VA_BASE_TRANSFORM(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_VA_BASE_TRANSFORM, GstVaBaseTransform))
+#define GST_IS_VA_BASE_TRANSFORM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_VA_BASE_TRANSFORM))
+#define GST_VA_BASE_TRANSFORM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass),  GST_TYPE_VA_BASE_TRANSFORM, GstVaBaseTransformClass))
+#define GST_IS_VA_BASE_TRANSFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),  GST_TYPE_VA_BASE_TRANSFORM))
+#define GST_VA_BASE_TRANSFORM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj),  GST_TYPE_VA_BASE_TRANSFORM, GstVaBaseTransformClass))
+
+typedef struct _GstVaBaseTransform GstVaBaseTransform;
+typedef struct _GstVaBaseTransformClass GstVaBaseTransformClass;
+typedef struct _GstVaBaseTransformPrivate GstVaBaseTransformPrivate;
+
+struct _GstVaBaseTransform
+{
+  GstBaseTransform parent;
+
+  /*< public >*/
+  GstVaDisplay *display;
+  GstVaFilter *filter;
+
+  GstCaps *in_caps;
+  GstCaps *out_caps;
+  GstVideoInfo in_info;
+  GstVideoInfo out_info;
+
+  gboolean negotiated;
+
+  guint extra_min_buffers;
+
+  /*< private >*/
+  GstVaBaseTransformPrivate *priv;
+
+  gpointer _padding[GST_PADDING];
+};
+
+struct _GstVaBaseTransformClass
+{
+  GstBaseTransformClass parent_class;
+
+  /*< public >*/
+  gboolean (*set_info) (GstVaBaseTransform *self,
+                        GstCaps *incaps, GstVideoInfo *in_info,
+                        GstCaps *outcaps, GstVideoInfo *out_info);
+
+  void (*update_properties) (GstVaBaseTransform *self);
+
+  /*< private >*/
+  gchar *render_device_path;
+
+  gpointer _padding[GST_PADDING];
+};
+
+GType                 gst_va_base_transform_get_type      (void);
+
+GstAllocator *        gst_va_base_transform_allocator_from_caps
+                                                          (GstVaBaseTransform * self,
+                                                           GstCaps * caps);
+
+GstFlowReturn         gst_va_base_transform_import_buffer (GstVaBaseTransform * self,
+                                                           GstBuffer * inbuf,
+                                                           GstBuffer ** buf);
+
+G_END_DECLS
index 55b664b..cdc4697 100644 (file)
@@ -2,6 +2,7 @@ va_sources = [
   'plugin.c',
   'gstvaallocator.c',
   'gstvabasedec.c',
+  'gstvabasetransform.c',
   'gstvacaps.c',
   'gstvadecoder.c',
   'gstvadevice.c',