Add vadeinterlace element.
authorVíctor Manuel Jáquez Leal <vjaquez@igalia.com>
Mon, 23 Aug 2021 09:24:40 +0000 (11:24 +0200)
committerVíctor Manuel Jáquez Leal <vjaquez@igalia.com>
Fri, 10 Sep 2021 15:48:23 +0000 (17:48 +0200)
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2495>

docs/plugins/gst_plugins_cache.json
sys/va/gstvadeinterlace.c [new file with mode: 0644]
sys/va/gstvadeinterlace.h [new file with mode: 0644]
sys/va/meson.build
sys/va/plugin.c

index f0349478e4399b53fe34159b21def6e36371a724..fca7c53b37fa892388b4393b3a78bb153ae2026b 100644 (file)
     "va": {
         "description": "VA-API codecs plugin",
         "elements": {
+            "vadeinterlace": {
+                "author": "Víctor Jáquez <vjaquez@igalia.com>",
+                "description": "VA-API based deinterlacer",
+                "hierarchy": [
+                    "GstVaDeinterlace",
+                    "GstVaBaseTransform",
+                    "GstBaseTransform",
+                    "GstElement",
+                    "GstObject",
+                    "GInitiallyUnowned",
+                    "GObject"
+                ],
+                "klass": "Filter/Effect/Video/Deinterlace",
+                "long-name": "VA-API Deinterlacer",
+                "pad-templates": {
+                    "sink": {
+                        "caps": "video/x-raw(memory:VAMemory):\n         format: { NV12, I420, YV12, YUY2, RGBA, BGRA, P010_10LE, ARGB, ABGR }\n          width: [ 1, 2147483647 ]\n         height: [ 1, 2147483647 ]\n      framerate: [ 0/1, 2147483647/1 ]\nvideo/x-raw:\n         format: { VUYA, GRAY8, NV12, NV21, YUY2, UYVY, YV12, I420, P010_10LE, RGBA, BGRA, ARGB, ABGR }\n          width: [ 1, 2147483647 ]\n         height: [ 1, 2147483647 ]\n      framerate: [ 0/1, 2147483647/1 ]\n",
+                        "direction": "sink",
+                        "presence": "always"
+                    },
+                    "src": {
+                        "caps": "video/x-raw(memory:VAMemory):\n         format: { NV12, I420, YV12, YUY2, RGBA, BGRA, P010_10LE, ARGB, ABGR }\n          width: [ 1, 2147483647 ]\n         height: [ 1, 2147483647 ]\n      framerate: [ 0/1, 2147483647/1 ]\nvideo/x-raw:\n         format: { VUYA, GRAY8, NV12, NV21, YUY2, UYVY, YV12, I420, P010_10LE, RGBA, BGRA, ARGB, ABGR }\n          width: [ 1, 2147483647 ]\n         height: [ 1, 2147483647 ]\n      framerate: [ 0/1, 2147483647/1 ]\n",
+                        "direction": "src",
+                        "presence": "always"
+                    }
+                },
+                "properties": {
+                    "method": {
+                        "blurb": "Deinterlace Method",
+                        "conditionally-available": false,
+                        "construct": false,
+                        "construct-only": false,
+                        "controllable": false,
+                        "default": "bob (1)",
+                        "mutable": "playing",
+                        "readable": true,
+                        "type": "GstVaDeinterlaceMethods",
+                        "writable": true
+                    }
+                },
+                "rank": "none"
+            },
             "vah264dec": {
                 "author": "Víctor Jáquez <vjaquez@igalia.com>",
                 "description": "VA-API based H.264 video decoder",
                     "GObject"
                 ],
                 "kind": "object"
+            },
+            "GstVaDeinterlaceMethods": {
+                "kind": "enum",
+                "values": [
+                    {
+                        "desc": "Bob: Interpolating missing lines by using the adjacent lines.",
+                        "name": "bob",
+                        "value": "1"
+                    },
+                    {
+                        "desc": "Adaptive: Interpolating missing lines by using spatial/temporal references.",
+                        "name": "adaptive",
+                        "value": "3"
+                    },
+                    {
+                        "desc": "Compensation: Recreating missing lines by using motion vector.",
+                        "name": "compensated",
+                        "value": "4"
+                    }
+                ]
             }
         },
         "package": "GStreamer Bad Plug-ins",
diff --git a/sys/va/gstvadeinterlace.c b/sys/va/gstvadeinterlace.c
new file mode 100644 (file)
index 0000000..a936830
--- /dev/null
@@ -0,0 +1,861 @@
+/* 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 the0
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-vadeinterlace
+ * @title: vadeinterlace
+ * @short_description: A VA-API base video deinterlace filter
+ *
+ * vadeinterlace deinterlaces interlaced video frames to progressive
+ * video frames. This element and its deinterlacing methods depend on
+ * the installed and chosen [VA-API](https://01.org/linuxmedia/vaapi)
+ * driver, but it's usually avaialble with bob (linear) method.
+ *
+ * This element doesn't change the caps features, it only negotiates
+ * the same dowstream and upstream.
+ *
+ * ## Example launch line
+ * ```
+ * gst-launch-1.0 filesrc location=interlaced_video.mp4 ! parsebin ! vah264dec ! vadeinterlace ! vapostproc ! autovideosink
+ * ```
+ *
+ * Since: 1.20
+ *
+ */
+
+/* ToDo:
+ *
+ * + field property to select only one field and keep the same framerate
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstvadeinterlace.h"
+
+#include <gst/video/video.h>
+
+#include <va/va_drmcommon.h>
+
+#include "gstvaallocator.h"
+#include "gstvabasetransform.h"
+#include "gstvacaps.h"
+#include "gstvadisplay_priv.h"
+#include "gstvafilter.h"
+#include "gstvapool.h"
+#include "gstvautils.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_va_deinterlace_debug);
+#define GST_CAT_DEFAULT gst_va_deinterlace_debug
+
+#define GST_VA_DEINTERLACE(obj)           ((GstVaDeinterlace *) obj)
+#define GST_VA_DEINTERLACE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_FROM_INSTANCE (obj), GstVaDeinterlaceClass))
+#define GST_VA_DEINTERLACE_CLASS(klass)    ((GstVaDeinterlaceClass *) klass)
+
+typedef struct _GstVaDeinterlace GstVaDeinterlace;
+typedef struct _GstVaDeinterlaceClass GstVaDeinterlaceClass;
+
+enum CurrField
+{
+  UNKNOWN_FIELD,
+  FIRST_FIELD,
+  SECOND_FIELD,
+  FINISHED,
+};
+
+struct _GstVaDeinterlaceClass
+{
+  /* GstVideoFilter overlaps functionality */
+  GstVaBaseTransformClass parent_class;
+};
+
+struct _GstVaDeinterlace
+{
+  GstVaBaseTransform parent;
+
+  gboolean rebuild_filters;
+  VAProcDeinterlacingType method;
+
+  guint num_backward_references;
+
+  GstBuffer *history[8];
+  gint hcount;
+  gint hdepth;
+  gint hcurr;
+  enum CurrField curr_field;
+
+  /* Calculated buffer duration by using upstream framerate */
+  GstClockTime default_duration;
+};
+
+static GstElementClass *parent_class = NULL;
+
+struct CData
+{
+  gchar *render_device_path;
+  gchar *description;
+};
+
+/* *INDENT-OFF* */
+static const gchar *caps_str = GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("memory:VAMemory",
+            "{ NV12, I420, YV12, YUY2, RGBA, BGRA, P010_10LE, ARGB, ABGR }") " ;"
+            GST_VIDEO_CAPS_MAKE ("{ VUYA, GRAY8, NV12, NV21, YUY2, UYVY, YV12, "
+            "I420, P010_10LE, RGBA, BGRA, ARGB, ABGR  }");
+/* *INDENT-ON* */
+
+static void
+_reset_history (GstVaDeinterlace * self)
+{
+  gint i;
+
+  for (i = 0; i < self->hcount; i++)
+    gst_buffer_unref (self->history[i]);
+  self->hcount = 0;
+}
+
+static void
+gst_va_deinterlace_dispose (GObject * object)
+{
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (object);
+
+  _reset_history (self);
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_va_deinterlace_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (object);
+  guint method;
+
+  GST_OBJECT_LOCK (object);
+  switch (prop_id) {
+    case GST_VA_FILTER_PROP_DEINTERLACE_METHOD:
+      method = g_value_get_enum (value);
+      if (method != self->method) {
+        self->method = method;
+        g_atomic_int_set (&self->rebuild_filters, TRUE);
+      }
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_OBJECT_UNLOCK (object);
+}
+
+static void
+gst_va_deinterlace_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (object);
+
+  GST_OBJECT_LOCK (object);
+  switch (prop_id) {
+    case GST_VA_FILTER_PROP_DEINTERLACE_METHOD:{
+      g_value_set_enum (value, self->method);
+      break;
+    }
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_OBJECT_UNLOCK (object);
+}
+
+static GstFlowReturn
+gst_va_deinterlace_submit_input_buffer (GstBaseTransform * trans,
+    gboolean is_discont, GstBuffer * input)
+{
+  GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (trans);
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (trans);
+  GstBuffer *buf, *inbuf;
+  GstFlowReturn ret;
+  gint i;
+
+  /* Let baseclass handle QoS first */
+  ret = GST_BASE_TRANSFORM_CLASS (parent_class)->submit_input_buffer (trans,
+      is_discont, input);
+  if (ret != GST_FLOW_OK)
+    return ret;
+
+  if (gst_base_transform_is_passthrough (trans))
+    return ret;
+
+  /* at this moment, baseclass must hold queued_buf */
+  g_assert (trans->queued_buf != NULL);
+
+  /* Check if we can use this buffer directly. If not, copy this into
+   * our fallback buffer */
+  buf = trans->queued_buf;
+  trans->queued_buf = NULL;
+
+  ret = gst_va_base_transform_import_buffer (btrans, buf, &inbuf);
+  if (ret != GST_FLOW_OK)
+    return ret;
+
+  gst_buffer_unref (buf);
+
+  if (self->hcount < self->hdepth) {
+    self->history[self->hcount++] = inbuf;
+  } else {
+    gst_clear_buffer (&self->history[0]);
+    for (i = 0; i + 1 < self->hcount; i++)
+      self->history[i] = self->history[i + 1];
+    self->history[i] = inbuf;
+  }
+
+  if (self->history[self->hcurr])
+    self->curr_field = FIRST_FIELD;
+
+  return ret;
+}
+
+static void
+_build_filter (GstVaDeinterlace * self)
+{
+  GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
+  guint i, num_caps;
+  VAProcFilterCapDeinterlacing *caps;
+  guint32 num_forward_references;
+
+  caps = gst_va_filter_get_filter_caps (btrans->filter,
+      VAProcFilterDeinterlacing, &num_caps);
+  if (!caps)
+    return;
+
+  for (i = 0; i < num_caps; i++) {
+    if (caps[i].type != self->method)
+      continue;
+
+    if (gst_va_filter_add_deinterlace_buffer (btrans->filter, self->method,
+            &num_forward_references, &self->num_backward_references)) {
+      self->hdepth = num_forward_references + self->num_backward_references + 1;
+      if (self->hdepth > 8) {
+        GST_ELEMENT_ERROR (self, STREAM, FAILED,
+            ("Pipeline requires too many references: (%u forward, %u backward)",
+                num_forward_references, self->num_backward_references), (NULL));
+      }
+      GST_INFO_OBJECT (self, "References for method: %u forward / %u backward",
+          num_forward_references, self->num_backward_references);
+      self->hcurr = num_forward_references;
+      return;
+    }
+  }
+
+  GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS,
+      ("Invalid deinterlacing method: %d", self->method), (NULL));
+}
+
+static void
+gst_va_deinterlace_rebuild_filters (GstVaDeinterlace * self)
+{
+  GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (self);
+
+  if (!g_atomic_int_get (&self->rebuild_filters))
+    return;
+
+  _reset_history (self);
+  gst_va_filter_drop_filter_buffers (btrans->filter);
+  _build_filter (self);
+
+  /* extra number of buffers for propose_allocation */
+  if (self->hdepth > btrans->extra_min_buffers) {
+    btrans->extra_min_buffers = self->hdepth;
+    gst_base_transform_reconfigure_sink (GST_BASE_TRANSFORM (self));
+  }
+
+  g_atomic_int_set (&self->rebuild_filters, FALSE);
+}
+
+static gboolean
+gst_va_deinterlace_set_info (GstVaBaseTransform * btrans, GstCaps * incaps,
+    GstVideoInfo * in_info, GstCaps * outcaps, GstVideoInfo * out_info)
+{
+  GstBaseTransform *trans = GST_BASE_TRANSFORM (btrans);
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (btrans);
+
+  switch (GST_VIDEO_INFO_INTERLACE_MODE (in_info)) {
+    case GST_VIDEO_INTERLACE_MODE_PROGRESSIVE:
+      /* Nothing to do */
+      gst_base_transform_set_passthrough (trans, TRUE);
+      return TRUE;
+      break;
+    case GST_VIDEO_INTERLACE_MODE_ALTERNATE:
+    case GST_VIDEO_INTERLACE_MODE_FIELDS:
+      GST_ERROR_OBJECT (self, "Unsupported interlace mode.");
+      return FALSE;
+      break;
+    default:
+      break;
+  }
+
+  /* Calculate expected buffer duration. We might need to reference this value
+   * when buffer duration is unknown */
+  if (GST_VIDEO_INFO_FPS_N (in_info) > 0 && GST_VIDEO_INFO_FPS_D (in_info) > 0) {
+    self->default_duration =
+        gst_util_uint64_scale_int (GST_SECOND, GST_VIDEO_INFO_FPS_D (in_info),
+        GST_VIDEO_INFO_FPS_N (in_info));
+  } else {
+    /* Assume 25 fps. We need this for reporting latency at least  */
+    self->default_duration = gst_util_uint64_scale_int (GST_SECOND, 1, 25);
+  }
+
+  if (gst_va_filter_set_video_info (btrans->filter, in_info, out_info)) {
+    g_atomic_int_set (&self->rebuild_filters, TRUE);
+    gst_base_transform_set_passthrough (trans, FALSE);
+    gst_va_deinterlace_rebuild_filters (self);
+
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+static void
+gst_va_deinterlace_before_transform (GstBaseTransform * trans,
+    GstBuffer * inbuf)
+{
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (trans);
+  GstClockTime ts, stream_time;
+
+  ts = GST_BUFFER_TIMESTAMP (inbuf);
+  stream_time =
+      gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME, ts);
+
+  GST_TRACE_OBJECT (self, "sync to %" GST_TIME_FORMAT, GST_TIME_ARGS (ts));
+
+  if (GST_CLOCK_TIME_IS_VALID (stream_time))
+    gst_object_sync_values (GST_OBJECT (self), stream_time);
+
+  gst_va_deinterlace_rebuild_filters (self);
+}
+
+static void
+_set_field (GstVaDeinterlace * self, guint32 * surface_flags)
+{
+  GstBaseTransform *trans = GST_BASE_TRANSFORM (self);
+
+  if (trans->segment.rate < 0) {
+    if ((self->curr_field == FIRST_FIELD
+            && (*surface_flags & VA_TOP_FIELD_FIRST))
+        || (self->curr_field == SECOND_FIELD
+            && (*surface_flags & VA_BOTTOM_FIELD_FIRST))) {
+      *surface_flags |= VA_BOTTOM_FIELD;
+    } else {
+      *surface_flags |= VA_TOP_FIELD;
+    }
+  } else {
+    if ((self->curr_field == FIRST_FIELD
+            && (*surface_flags & VA_BOTTOM_FIELD_FIRST))
+        || (self->curr_field == SECOND_FIELD
+            && (*surface_flags & VA_TOP_FIELD_FIRST))) {
+      *surface_flags |= VA_BOTTOM_FIELD;
+    } else {
+      *surface_flags |= VA_TOP_FIELD;
+    }
+  }
+}
+
+static GstFlowReturn
+gst_va_deinterlace_transform (GstBaseTransform * trans, GstBuffer * inbuf,
+    GstBuffer * outbuf)
+{
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (trans);
+  GstVaBaseTransform *btrans = GST_VA_BASE_TRANSFORM (trans);
+  GstFlowReturn res = GST_FLOW_OK;
+  GstVaSample src, dst;
+  GstVideoInfo *info = &btrans->in_info;
+  VASurfaceID forward_references[8], backward_references[8];
+  guint i, surface_flags;
+
+  if (G_UNLIKELY (!btrans->negotiated))
+    goto unknown_format;
+
+  g_assert (self->curr_field == FIRST_FIELD
+      || self->curr_field == SECOND_FIELD);
+
+  surface_flags = 0;
+  if (GST_VIDEO_INFO_INTERLACE_MODE (info) == GST_VIDEO_INTERLACE_MODE_MIXED
+      || (GST_VIDEO_INFO_INTERLACE_MODE (info) ==
+          GST_VIDEO_INTERLACE_MODE_INTERLEAVED
+          && GST_VIDEO_INFO_FIELD_ORDER (info) ==
+          GST_VIDEO_FIELD_ORDER_UNKNOWN)) {
+    if (GST_BUFFER_FLAG_IS_SET (inbuf, GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
+      if (GST_BUFFER_FLAG_IS_SET (inbuf, GST_VIDEO_BUFFER_FLAG_TFF)) {
+        surface_flags = VA_TOP_FIELD_FIRST;
+      } else {
+        surface_flags = VA_BOTTOM_FIELD_FIRST;
+      }
+    } else {
+      /* complete frame? do it twice */
+      surface_flags = VA_FRAME_PICTURE;
+    }
+  } else if (GST_VIDEO_INFO_FIELD_ORDER (info) ==
+      GST_VIDEO_FIELD_ORDER_BOTTOM_FIELD_FIRST) {
+    surface_flags = VA_BOTTOM_FIELD_FIRST;
+  } else if (GST_VIDEO_INFO_FIELD_ORDER (info) ==
+      GST_VIDEO_FIELD_ORDER_TOP_FIELD_FIRST) {
+    surface_flags = VA_TOP_FIELD_FIRST;
+  } else {
+    g_assert_not_reached ();
+  }
+
+  if (surface_flags != VA_FRAME_PICTURE)
+    _set_field (self, &surface_flags);
+
+  GST_TRACE_OBJECT (self, "Processing %d field (flags = %u): %" GST_PTR_FORMAT,
+      self->curr_field, surface_flags, inbuf);
+
+  for (i = 0; i < self->hcurr; i++) {
+    forward_references[i] =
+        gst_va_buffer_get_surface (self->history[self->hcurr - i - 1]);
+  }
+  for (i = 0; i < self->num_backward_references; i++) {
+    backward_references[i] =
+        gst_va_buffer_get_surface (self->history[self->hcurr + i + 1]);
+  }
+
+  /* *INDENT-OFF* */
+  src = (GstVaSample) {
+    .buffer = inbuf,
+    .flags = surface_flags,
+    .forward_references = forward_references,
+    .num_forward_references = self->hcurr,
+    .backward_references = backward_references,
+    .num_backward_references = self->num_backward_references,
+  };
+  dst = (GstVaSample) {
+    .buffer = outbuf,
+  };
+  /* *INDENT-ON* */
+
+  if (!gst_va_filter_process (btrans->filter, &src, &dst)) {
+    gst_buffer_set_flags (outbuf, GST_BUFFER_FLAG_CORRUPTED);
+  }
+
+  return res;
+
+  /* ERRORS */
+unknown_format:
+  {
+    GST_ELEMENT_ERROR (self, CORE, NOT_IMPLEMENTED, (NULL), ("unknown format"));
+    return GST_FLOW_NOT_NEGOTIATED;
+  }
+}
+
+static GstFlowReturn
+gst_va_deinterlace_generate_output (GstBaseTransform * trans,
+    GstBuffer ** outbuf)
+{
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (trans);
+  GstFlowReturn ret;
+  GstBuffer *inbuf, *buf = NULL;
+
+  if (gst_base_transform_is_passthrough (trans)) {
+    return GST_BASE_TRANSFORM_CLASS (parent_class)->generate_output (trans,
+        outbuf);
+  }
+
+  *outbuf = NULL;
+
+  if (self->curr_field == FINISHED)
+    return GST_FLOW_OK;
+
+  inbuf = self->history[self->hcurr];
+  if (!inbuf)
+    return GST_FLOW_OK;
+
+  if (!self->history[self->hdepth - 1])
+    return GST_FLOW_OK;
+
+  ret = GST_BASE_TRANSFORM_CLASS (parent_class)->prepare_output_buffer (trans,
+      inbuf, &buf);
+  if (ret != GST_FLOW_OK || !buf) {
+    GST_WARNING_OBJECT (self, "Could not get buffer from pool: %s",
+        gst_flow_get_name (ret));
+    return ret;
+  }
+
+  ret = gst_va_deinterlace_transform (trans, inbuf, buf);
+  if (ret != GST_FLOW_OK) {
+    gst_buffer_unref (buf);
+    return ret;
+  }
+
+  if (!GST_BUFFER_PTS_IS_VALID (inbuf)) {
+    GST_LOG_OBJECT (self, "Input buffer timestamp is unknown");
+  } else {
+    GstClockTime duration;
+
+    if (GST_BUFFER_DURATION_IS_VALID (inbuf))
+      duration = GST_BUFFER_DURATION (inbuf) / 2;
+    else
+      duration = self->default_duration / 2;
+
+    GST_BUFFER_DURATION (buf) = duration;
+    if (self->curr_field == SECOND_FIELD)
+      GST_BUFFER_PTS (buf) = GST_BUFFER_PTS (buf) + duration;
+  }
+
+  *outbuf = buf;
+
+  GST_TRACE_OBJECT (self, "Pushing %" GST_PTR_FORMAT, buf);
+
+  if (self->curr_field == FIRST_FIELD)
+    self->curr_field = SECOND_FIELD;
+  else if (self->curr_field == SECOND_FIELD)
+    self->curr_field = FINISHED;
+
+  return ret;
+}
+
+static GstCaps *
+gst_va_deinterlace_remove_interlace (GstCaps * caps)
+{
+  GstStructure *st;
+  gint i, n;
+  GstCaps *res;
+  GstCapsFeatures *f;
+
+  res = gst_caps_new_empty ();
+
+  n = gst_caps_get_size (caps);
+  for (i = 0; i < n; i++) {
+    st = gst_caps_get_structure (caps, i);
+    f = gst_caps_get_features (caps, i);
+
+    /* If this is already expressed by the existing caps
+     * skip this structure */
+    if (i > 0 && gst_caps_is_subset_structure_full (res, st, f))
+      continue;
+
+    st = gst_structure_copy (st);
+    gst_structure_remove_fields (st, "interlace-mode", "field-order",
+        "framerate", NULL);
+
+    gst_caps_append_structure_full (res, st, gst_caps_features_copy (f));
+  }
+
+  return res;
+}
+
+static GstCaps *
+gst_va_deinterlace_transform_caps (GstBaseTransform * trans,
+    GstPadDirection direction, GstCaps * caps, GstCaps * filter)
+{
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (trans);
+  GstCaps *ret;
+
+  GST_DEBUG_OBJECT (self,
+      "Transforming caps %" GST_PTR_FORMAT " in direction %s", caps,
+      (direction == GST_PAD_SINK) ? "sink" : "src");
+
+  ret = gst_va_deinterlace_remove_interlace (caps);
+
+  if (filter) {
+    GstCaps *intersection;
+
+    intersection =
+        gst_caps_intersect_full (filter, ret, GST_CAPS_INTERSECT_FIRST);
+    gst_caps_unref (ret);
+    ret = intersection;
+  }
+
+  GST_DEBUG_OBJECT (trans, "returning caps: %" GST_PTR_FORMAT, ret);
+
+  return ret;
+}
+
+static GstCaps *
+gst_va_deinterlace_fixate_caps (GstBaseTransform * trans,
+    GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
+{
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (trans);
+  GstCaps *tmp;
+  GstStructure *s;
+  GstVideoInfo info;
+  gint fps_n, fps_d;
+  const gchar *interlace_mode;
+
+  GST_DEBUG_OBJECT (self,
+      "trying to fixate othercaps %" GST_PTR_FORMAT " based on caps %"
+      GST_PTR_FORMAT, othercaps, caps);
+
+  othercaps = gst_caps_truncate (othercaps);
+  othercaps = gst_caps_make_writable (othercaps);
+
+  if (direction == GST_PAD_SRC) {
+    othercaps = gst_caps_fixate (othercaps);
+    goto bail;
+  }
+
+  tmp = gst_caps_copy (caps);
+  tmp = gst_caps_fixate (tmp);
+
+  if (!gst_video_info_from_caps (&info, tmp)) {
+    GST_WARNING_OBJECT (self, "Invalid caps %" GST_PTR_FORMAT, caps);
+    gst_caps_unref (tmp);
+
+    othercaps = gst_caps_fixate (othercaps);
+    goto bail;
+  }
+
+  s = gst_caps_get_structure (tmp, 0);
+  if (gst_structure_get_fraction (s, "framerate", &fps_n, &fps_d)) {
+    fps_n *= 2;
+    gst_caps_set_simple (othercaps,
+        "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL);
+  }
+
+  interlace_mode = gst_structure_get_string (s, "interlace-mode");
+  if (g_strcmp0 ("progressive", interlace_mode) == 0) {
+    /* Just forward interlace-mode=progressive.
+     * By this way, basetransform will enable passthrough for non-interlaced
+     * stream*/
+    gst_caps_set_simple (othercaps,
+        "interlace-mode", G_TYPE_STRING, "progressive", NULL);
+  }
+
+  gst_caps_unref (tmp);
+
+bail:
+  GST_DEBUG_OBJECT (self, "fixated othercaps to %" GST_PTR_FORMAT, othercaps);
+
+  return othercaps;
+}
+
+static gboolean
+gst_va_deinterlace_query (GstBaseTransform * trans, GstPadDirection direction,
+    GstQuery * query)
+{
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (trans);
+
+  if (direction == GST_PAD_SRC && GST_QUERY_TYPE (query) == GST_QUERY_LATENCY) {
+    GstPad *peer;
+    GstClockTime latency, min, max;
+    gboolean res = FALSE;
+    gboolean live;
+
+    if (gst_base_transform_is_passthrough (trans))
+      return FALSE;
+
+    peer = gst_pad_get_peer (GST_BASE_TRANSFORM_SINK_PAD (trans));
+    if (!peer)
+      return FALSE;
+
+    res = gst_pad_query (peer, query);
+    gst_object_unref (peer);
+    if (!res)
+      return FALSE;
+
+    gst_query_parse_latency (query, &live, &min, &max);
+
+    GST_DEBUG_OBJECT (self, "Peer latency: min %" GST_TIME_FORMAT " max %"
+        GST_TIME_FORMAT, GST_TIME_ARGS (min), GST_TIME_ARGS (max));
+
+    /* add our own latency: number of fields + history depth */
+    latency = (2 + self->hdepth) * self->default_duration;
+
+    GST_DEBUG_OBJECT (self, "Our latency: min %" GST_TIME_FORMAT ", max %"
+        GST_TIME_FORMAT, GST_TIME_ARGS (latency), GST_TIME_ARGS (latency));
+
+    min += latency;
+    if (max != GST_CLOCK_TIME_NONE)
+      max += latency;
+
+    GST_DEBUG_OBJECT (self, "Calculated total latency : min %" GST_TIME_FORMAT
+        " max %" GST_TIME_FORMAT, GST_TIME_ARGS (min), GST_TIME_ARGS (max));
+
+    gst_query_set_latency (query, live, min, max);
+
+    return TRUE;
+  }
+
+  return GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction,
+      query);
+}
+
+static void
+gst_va_deinterlace_class_init (gpointer g_class, gpointer class_data)
+{
+  GstCaps *doc_caps, *sink_caps = NULL, *src_caps = NULL;
+  GstPadTemplate *sink_pad_templ, *src_pad_templ;
+  GObjectClass *object_class = G_OBJECT_CLASS (g_class);
+  GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (g_class);
+  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+  GstVaBaseTransformClass *btrans_class = GST_VA_BASE_TRANSFORM_CLASS (g_class);
+  GstVaDisplay *display;
+  GstVaFilter *filter;
+  struct CData *cdata = class_data;
+  gchar *long_name;
+
+  parent_class = g_type_class_peek_parent (g_class);
+
+  btrans_class->render_device_path = g_strdup (cdata->render_device_path);
+
+  if (cdata->description) {
+    long_name = g_strdup_printf ("VA-API Deinterlacer in %s",
+        cdata->description);
+  } else {
+    long_name = g_strdup ("VA-API Deinterlacer");
+  }
+
+  gst_element_class_set_metadata (element_class, long_name,
+      "Filter/Effect/Video/Deinterlace",
+      "VA-API based deinterlacer", "Víctor Jáquez <vjaquez@igalia.com>");
+
+  display = gst_va_display_drm_new_from_path (btrans_class->render_device_path);
+  filter = gst_va_filter_new (display);
+
+  if (gst_va_filter_open (filter))
+    src_caps = gst_va_filter_get_caps (filter);
+  else
+    src_caps = gst_caps_from_string (caps_str);
+
+  sink_caps = gst_va_deinterlace_remove_interlace (src_caps);
+
+  doc_caps = gst_caps_from_string (caps_str);
+
+  sink_pad_templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+      sink_caps);
+  gst_element_class_add_pad_template (element_class, sink_pad_templ);
+  gst_pad_template_set_documentation_caps (sink_pad_templ,
+      gst_caps_ref (doc_caps));
+
+  src_pad_templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+      src_caps);
+  gst_element_class_add_pad_template (element_class, src_pad_templ);
+  gst_pad_template_set_documentation_caps (src_pad_templ,
+      gst_caps_ref (doc_caps));
+  gst_caps_unref (doc_caps);
+
+  gst_caps_unref (src_caps);
+  gst_caps_unref (sink_caps);
+
+  object_class->dispose = gst_va_deinterlace_dispose;
+  object_class->set_property = gst_va_deinterlace_set_property;
+  object_class->get_property = gst_va_deinterlace_get_property;
+
+  trans_class->transform_caps =
+      GST_DEBUG_FUNCPTR (gst_va_deinterlace_transform_caps);
+  trans_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_va_deinterlace_fixate_caps);
+  trans_class->before_transform =
+      GST_DEBUG_FUNCPTR (gst_va_deinterlace_before_transform);
+  trans_class->transform = GST_DEBUG_FUNCPTR (gst_va_deinterlace_transform);
+  trans_class->submit_input_buffer =
+      GST_DEBUG_FUNCPTR (gst_va_deinterlace_submit_input_buffer);
+  trans_class->generate_output =
+      GST_DEBUG_FUNCPTR (gst_va_deinterlace_generate_output);
+  trans_class->query = GST_DEBUG_FUNCPTR (gst_va_deinterlace_query);
+
+  trans_class->transform_ip_on_passthrough = FALSE;
+
+  btrans_class->set_info = GST_DEBUG_FUNCPTR (gst_va_deinterlace_set_info);
+
+  gst_va_filter_install_deinterlace_properties (filter, object_class);
+
+  g_free (long_name);
+  g_free (cdata->description);
+  g_free (cdata->render_device_path);
+  g_free (cdata);
+  gst_object_unref (filter);
+  gst_object_unref (display);
+}
+
+static void
+gst_va_deinterlace_init (GTypeInstance * instance, gpointer g_class)
+{
+  GstVaDeinterlace *self = GST_VA_DEINTERLACE (instance);
+  GParamSpec *pspec;
+
+  pspec = g_object_class_find_property (g_class, "method");
+  g_assert (pspec);
+  self->method = g_value_get_enum (g_param_spec_get_default_value (pspec));
+}
+
+static gpointer
+_register_debug_category (gpointer data)
+{
+  GST_DEBUG_CATEGORY_INIT (gst_va_deinterlace_debug, "vadeinterlace", 0,
+      "VA Video Deinterlace");
+
+  return NULL;
+}
+
+gboolean
+gst_va_deinterlace_register (GstPlugin * plugin, GstVaDevice * device,
+    guint rank)
+{
+  static GOnce debug_once = G_ONCE_INIT;
+  GType type;
+  GTypeInfo type_info = {
+    .class_size = sizeof (GstVaDeinterlaceClass),
+    .class_init = gst_va_deinterlace_class_init,
+    .instance_size = sizeof (GstVaDeinterlace),
+    .instance_init = gst_va_deinterlace_init,
+  };
+  struct CData *cdata;
+  gboolean ret;
+  gchar *type_name, *feature_name;
+
+  g_return_val_if_fail (GST_IS_PLUGIN (plugin), FALSE);
+  g_return_val_if_fail (GST_IS_VA_DEVICE (device), FALSE);
+
+  cdata = g_new (struct CData, 1);
+  cdata->description = NULL;
+  cdata->render_device_path = g_strdup (device->render_device_path);
+
+  type_info.class_data = cdata;
+
+  type_name = g_strdup ("GstVaDeinterlace");
+  feature_name = g_strdup ("vadeinterlace");
+
+  /* The first postprocessor to be registered should use a constant
+   * name, like vadeinterlace, for any additional postprocessors, we
+   * create unique names, using inserting the render device name. */
+  if (g_type_from_name (type_name)) {
+    gchar *basename = g_path_get_basename (device->render_device_path);
+    g_free (type_name);
+    g_free (feature_name);
+    type_name = g_strdup_printf ("GstVa%sDeinterlace", basename);
+    feature_name = g_strdup_printf ("va%sdeinterlace", basename);
+    cdata->description = basename;
+
+    /* lower rank for non-first device */
+    if (rank > 0)
+      rank--;
+  }
+
+  g_once (&debug_once, _register_debug_category, NULL);
+
+  type = g_type_register_static (GST_TYPE_VA_BASE_TRANSFORM, type_name,
+      &type_info, 0);
+
+  ret = gst_element_register (plugin, feature_name, rank, type);
+
+  g_free (type_name);
+  g_free (feature_name);
+
+  return ret;
+}
diff --git a/sys/va/gstvadeinterlace.h b/sys/va/gstvadeinterlace.h
new file mode 100644 (file)
index 0000000..f026605
--- /dev/null
@@ -0,0 +1,31 @@
+/* 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 "gstvadevice.h"
+
+G_BEGIN_DECLS
+
+gboolean              gst_va_deinterlace_register         (GstPlugin * plugin,
+                                                           GstVaDevice * device,
+                                                           guint rank);
+
+G_END_DECLS
index cdc4697fea24098aeeefd9b2652caddd164fdcbd..a86a9068109508cba3e09845012f84f1b47ea2f0 100644 (file)
@@ -5,6 +5,7 @@ va_sources = [
   'gstvabasetransform.c',
   'gstvacaps.c',
   'gstvadecoder.c',
+  'gstvadeinterlace.c',
   'gstvadevice.c',
   'gstvadisplay_priv.c',
   'gstvafilter.c',
index b78f7ef69dffa94fab7013fba4070fb1756bd52a..12b879fba37659970ff5b7a44e49cd0aa47a291e 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "gstvaav1dec.h"
 #include "gstvacaps.h"
+#include "gstvadeinterlace.h"
 #include "gstvadevice.h"
 #include "gstvafilter.h"
 #include "gstvah264dec.h"
@@ -190,13 +191,16 @@ static void
 plugin_register_vpp (GstPlugin * plugin, GstVaDevice * device)
 {
   GstVaFilter *filter;
-  gboolean has_colorbalance;
+  gboolean has_colorbalance, has_deinterlace;
 
   has_colorbalance = FALSE;
+  has_deinterlace = FALSE;
   filter = gst_va_filter_new (device->display);
   if (gst_va_filter_open (filter)) {
     has_colorbalance =
         gst_va_filter_has_filter (filter, VAProcFilterColorBalance);
+    has_deinterlace =
+        gst_va_filter_has_filter (filter, VAProcFilterDeinterlacing);
   } else {
     GST_WARNING ("Failed open VA filter");
     gst_object_unref (filter);
@@ -206,6 +210,13 @@ plugin_register_vpp (GstPlugin * plugin, GstVaDevice * device)
 
   if (!gst_va_vpp_register (plugin, device, has_colorbalance, GST_RANK_NONE))
     GST_WARNING ("Failed to register postproc: %s", device->render_device_path);
+
+  if (has_deinterlace) {
+    if (!gst_va_deinterlace_register (plugin, device, GST_RANK_NONE)) {
+      GST_WARNING ("Failed to register deinterlace: %s",
+          device->render_device_path);
+    }
+  }
 }
 
 static inline void