/* GStreamer * Copyright (C) 2021 Igalia, S.L. * Author: Víctor Jáquez * * 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 #include #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 (GST_CAPS_FEATURE_MEMORY_VA, "{ 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 "); 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; }