summaryrefslogtreecommitdiffstats
path: root/gst/deinterlace/gstdeinterlace.c
diff options
context:
space:
mode:
Diffstat (limited to 'gst/deinterlace/gstdeinterlace.c')
-rw-r--r--gst/deinterlace/gstdeinterlace.c1515
1 files changed, 1515 insertions, 0 deletions
diff --git a/gst/deinterlace/gstdeinterlace.c b/gst/deinterlace/gstdeinterlace.c
new file mode 100644
index 00000000..af730f4f
--- /dev/null
+++ b/gst/deinterlace/gstdeinterlace.c
@@ -0,0 +1,1515 @@
+/*
+ * GStreamer
+ * Copyright (C) 2005 Martin Eikermann <meiker@upb.de>
+ * Copyright (C) 2008-2009 Sebastian Dröge <slomo@collabora.co.uk>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION:element-deinterlace
+ *
+ * deinterlace deinterlaces interlaced video frames to progressive video frames.
+ * For this different algorithms can be selected which will be described later.
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch -v filesrc location=/path/to/file ! decodebin2 ! ffmpegcolorspace ! deinterlace ! ffmpegcolorspace ! autovideosink
+ * ]| This pipeline deinterlaces a video file with the default deinterlacing options.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <liboil/liboil.h>
+
+#include "gstdeinterlace.h"
+#include "tvtime/plugins.h"
+
+#include <string.h>
+
+GST_DEBUG_CATEGORY_STATIC (deinterlace_debug);
+#define GST_CAT_DEFAULT (deinterlace_debug)
+
+/* Object signals and args */
+enum
+{
+ LAST_SIGNAL
+};
+
+/* Properties */
+
+#define DEFAULT_MODE GST_DEINTERLACE_MODE_INTERLACED
+#define DEFAULT_METHOD GST_DEINTERLACE_GREEDY_H
+#define DEFAULT_FIELDS GST_DEINTERLACE_ALL
+#define DEFAULT_FIELD_LAYOUT GST_DEINTERLACE_LAYOUT_AUTO
+
+enum
+{
+ PROP_0,
+ PROP_MODE,
+ PROP_METHOD,
+ PROP_FIELDS,
+ PROP_FIELD_LAYOUT,
+ PROP_LAST
+};
+
+G_DEFINE_TYPE (GstDeinterlaceMethod, gst_deinterlace_method, GST_TYPE_OBJECT);
+
+static void
+gst_deinterlace_method_class_init (GstDeinterlaceMethodClass * klass)
+{
+
+}
+
+static void
+gst_deinterlace_method_init (GstDeinterlaceMethod * self)
+{
+
+}
+
+static void
+gst_deinterlace_method_deinterlace_frame (GstDeinterlaceMethod * self,
+ GstDeinterlace * parent, GstBuffer * outbuf)
+{
+ GstDeinterlaceMethodClass *klass = GST_DEINTERLACE_METHOD_GET_CLASS (self);
+
+ klass->deinterlace_frame (self, parent, outbuf);
+}
+
+static gint
+gst_deinterlace_method_get_fields_required (GstDeinterlaceMethod * self)
+{
+ GstDeinterlaceMethodClass *klass = GST_DEINTERLACE_METHOD_GET_CLASS (self);
+
+ return klass->fields_required;
+}
+
+static gint
+gst_deinterlace_method_get_latency (GstDeinterlaceMethod * self)
+{
+ GstDeinterlaceMethodClass *klass = GST_DEINTERLACE_METHOD_GET_CLASS (self);
+
+ return klass->latency;
+}
+
+
+G_DEFINE_TYPE (GstDeinterlaceSimpleMethod, gst_deinterlace_simple_method,
+ GST_TYPE_DEINTERLACE_METHOD);
+
+static void
+gst_deinterlace_simple_method_interpolate_scanline (GstDeinterlaceMethod * self,
+ GstDeinterlace * parent, guint8 * out,
+ GstDeinterlaceScanlineData * scanlines, gint width)
+{
+ oil_memcpy (out, scanlines->m1, parent->row_stride);
+}
+
+static void
+gst_deinterlace_simple_method_copy_scanline (GstDeinterlaceMethod * self,
+ GstDeinterlace * parent, guint8 * out,
+ GstDeinterlaceScanlineData * scanlines, gint width)
+{
+ oil_memcpy (out, scanlines->m0, parent->row_stride);
+}
+
+static void
+gst_deinterlace_simple_method_deinterlace_frame (GstDeinterlaceMethod * self,
+ GstDeinterlace * parent, GstBuffer * outbuf)
+{
+ GstDeinterlaceSimpleMethodClass *dsm_class =
+ GST_DEINTERLACE_SIMPLE_METHOD_GET_CLASS (self);
+ GstDeinterlaceMethodClass *dm_class = GST_DEINTERLACE_METHOD_GET_CLASS (self);
+ GstDeinterlaceScanlineData scanlines;
+ guint8 *out = GST_BUFFER_DATA (outbuf);
+ guint8 *field0 = NULL, *field1 = NULL, *field2 = NULL, *field3 = NULL;
+ gint cur_field_idx = parent->history_count - dm_class->fields_required;
+ guint cur_field_flags = parent->field_history[cur_field_idx].flags;
+ gint line;
+
+ field0 = GST_BUFFER_DATA (parent->field_history[cur_field_idx].buf);
+
+ g_assert (dm_class->fields_required <= 4);
+
+ if (dm_class->fields_required >= 2)
+ field1 = GST_BUFFER_DATA (parent->field_history[cur_field_idx + 1].buf);
+ if (dm_class->fields_required >= 3)
+ field2 = GST_BUFFER_DATA (parent->field_history[cur_field_idx + 2].buf);
+ if (dm_class->fields_required >= 4)
+ field3 = GST_BUFFER_DATA (parent->field_history[cur_field_idx + 3].buf);
+
+
+ if (cur_field_flags == PICTURE_INTERLACED_BOTTOM) {
+ /* double the first scanline of the bottom field */
+ oil_memcpy (out, field0, parent->row_stride);
+ out += parent->row_stride;
+ }
+
+ oil_memcpy (out, field0, parent->row_stride);
+ out += parent->row_stride;
+
+ for (line = 2; line <= parent->field_height; line++) {
+
+ memset (&scanlines, 0, sizeof (scanlines));
+ scanlines.bottom_field = (cur_field_flags == PICTURE_INTERLACED_BOTTOM);
+
+ /* interp. scanline */
+ scanlines.t0 = field0;
+ scanlines.b0 = field0 + parent->field_stride;
+
+ if (field1 != NULL) {
+ scanlines.tt1 = field1;
+ scanlines.m1 = field1 + parent->field_stride;
+ scanlines.bb1 = field1 + parent->field_stride * 2;
+ field1 += parent->field_stride;
+ }
+
+ if (field2 != NULL) {
+ scanlines.t2 = field2;
+ scanlines.b2 = field2 + parent->field_stride;
+ }
+
+ if (field3 != NULL) {
+ scanlines.tt3 = field3;
+ scanlines.m3 = field3 + parent->field_stride;
+ scanlines.bb3 = field3 + parent->field_stride * 2;
+ field3 += parent->field_stride;
+ }
+
+ /* set valid data for corner cases */
+ if (line == 2) {
+ scanlines.tt1 = scanlines.bb1;
+ scanlines.tt3 = scanlines.bb3;
+ } else if (line == parent->field_height) {
+ scanlines.bb1 = scanlines.tt1;
+ scanlines.bb3 = scanlines.tt3;
+ }
+
+ dsm_class->interpolate_scanline (self, parent, out, &scanlines,
+ parent->frame_width);
+ out += parent->row_stride;
+
+ memset (&scanlines, 0, sizeof (scanlines));
+ scanlines.bottom_field = (cur_field_flags == PICTURE_INTERLACED_BOTTOM);
+
+ /* copy a scanline */
+ scanlines.tt0 = field0;
+ scanlines.m0 = field0 + parent->field_stride;
+ scanlines.bb0 = field0 + parent->field_stride * 2;
+ field0 += parent->field_stride;
+
+ if (field1 != NULL) {
+ scanlines.t1 = field1;
+ scanlines.b1 = field1 + parent->field_stride;
+ }
+
+ if (field2 != NULL) {
+ scanlines.tt2 = field2;
+ scanlines.m2 = field2 + parent->field_stride;
+ scanlines.bb2 = field2 + parent->field_stride * 2;
+ field2 += parent->field_stride;
+ }
+
+ if (field3 != NULL) {
+ scanlines.t3 = field3;
+ scanlines.b3 = field3 + parent->field_stride;
+ }
+
+ /* set valid data for corner cases */
+ if (line == parent->field_height) {
+ scanlines.bb0 = scanlines.tt0;
+ scanlines.b1 = scanlines.t1;
+ scanlines.bb2 = scanlines.tt2;
+ scanlines.b3 = scanlines.t3;
+ }
+
+ dsm_class->copy_scanline (self, parent, out, &scanlines,
+ parent->frame_width);
+ out += parent->row_stride;
+ }
+
+ if (cur_field_flags == PICTURE_INTERLACED_TOP) {
+ /* double the last scanline of the top field */
+ oil_memcpy (out, field0, parent->row_stride);
+ }
+}
+
+static void
+gst_deinterlace_simple_method_class_init (GstDeinterlaceSimpleMethodClass *
+ klass)
+{
+ GstDeinterlaceMethodClass *dm_class = (GstDeinterlaceMethodClass *) klass;
+
+ dm_class->deinterlace_frame = gst_deinterlace_simple_method_deinterlace_frame;
+ dm_class->fields_required = 2;
+
+ klass->interpolate_scanline =
+ gst_deinterlace_simple_method_interpolate_scanline;
+ klass->copy_scanline = gst_deinterlace_simple_method_copy_scanline;
+}
+
+static void
+gst_deinterlace_simple_method_init (GstDeinterlaceSimpleMethod * self)
+{
+}
+
+#define GST_TYPE_DEINTERLACE_METHODS (gst_deinterlace_methods_get_type ())
+static GType
+gst_deinterlace_methods_get_type (void)
+{
+ static GType deinterlace_methods_type = 0;
+
+ static const GEnumValue methods_types[] = {
+ {GST_DEINTERLACE_TOMSMOCOMP, "Motion Adaptive: Motion Search",
+ "tomsmocomp"},
+ {GST_DEINTERLACE_GREEDY_H, "Motion Adaptive: Advanced Detection",
+ "greedyh"},
+ {GST_DEINTERLACE_GREEDY_L, "Motion Adaptive: Simple Detection", "greedyl"},
+ {GST_DEINTERLACE_VFIR, "Blur Vertical", "vfir"},
+ {GST_DEINTERLACE_LINEAR, "Television: Full resolution", "linear"},
+ {GST_DEINTERLACE_LINEAR_BLEND, "Blur: Temporal", "linearblend"},
+ {GST_DEINTERLACE_SCALER_BOB, "Double lines", "scalerbob"},
+ {GST_DEINTERLACE_WEAVE, "Weave", "weave"},
+ {GST_DEINTERLACE_WEAVE_TFF, "Progressive: Top Field First", "weavetff"},
+ {GST_DEINTERLACE_WEAVE_BFF, "Progressive: Bottom Field First", "weavebff"},
+ {0, NULL, NULL},
+ };
+
+ if (!deinterlace_methods_type) {
+ deinterlace_methods_type =
+ g_enum_register_static ("GstDeinterlaceMethods", methods_types);
+ }
+ return deinterlace_methods_type;
+}
+
+#define GST_TYPE_DEINTERLACE_FIELDS (gst_deinterlace_fields_get_type ())
+static GType
+gst_deinterlace_fields_get_type (void)
+{
+ static GType deinterlace_fields_type = 0;
+
+ static const GEnumValue fields_types[] = {
+ {GST_DEINTERLACE_ALL, "All fields", "all"},
+ {GST_DEINTERLACE_TF, "Top fields only", "top"},
+ {GST_DEINTERLACE_BF, "Bottom fields only", "bottom"},
+ {0, NULL, NULL},
+ };
+
+ if (!deinterlace_fields_type) {
+ deinterlace_fields_type =
+ g_enum_register_static ("GstDeinterlaceFields", fields_types);
+ }
+ return deinterlace_fields_type;
+}
+
+#define GST_TYPE_DEINTERLACE_FIELD_LAYOUT (gst_deinterlace_field_layout_get_type ())
+static GType
+gst_deinterlace_field_layout_get_type (void)
+{
+ static GType deinterlace_field_layout_type = 0;
+
+ static const GEnumValue field_layout_types[] = {
+ {GST_DEINTERLACE_LAYOUT_AUTO, "Auto detection", "auto"},
+ {GST_DEINTERLACE_LAYOUT_TFF, "Top field first", "tff"},
+ {GST_DEINTERLACE_LAYOUT_BFF, "Bottom field first", "bff"},
+ {0, NULL, NULL},
+ };
+
+ if (!deinterlace_field_layout_type) {
+ deinterlace_field_layout_type =
+ g_enum_register_static ("GstDeinterlaceFieldLayout",
+ field_layout_types);
+ }
+ return deinterlace_field_layout_type;
+}
+
+#define GST_TYPE_DEINTERLACE_MODES (gst_deinterlace_modes_get_type ())
+static GType
+gst_deinterlace_modes_get_type (void)
+{
+ static GType deinterlace_modes_type = 0;
+
+ static const GEnumValue modes_types[] = {
+ {GST_DEINTERLACE_MODE_AUTO, "Auto detection", "auto"},
+ {GST_DEINTERLACE_MODE_INTERLACED, "Enfore deinterlacing", "interlaced"},
+ {GST_DEINTERLACE_MODE_DISABLED, "Run in passthrough mode", "disabled"},
+ {0, NULL, NULL},
+ };
+
+ if (!deinterlace_modes_type) {
+ deinterlace_modes_type =
+ g_enum_register_static ("GstDeinterlaceModes", modes_types);
+ }
+ return deinterlace_modes_type;
+}
+
+static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("YUY2") ";"
+ GST_VIDEO_CAPS_YUV ("YVYU"))
+ );
+
+static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("YUY2") ";"
+ GST_VIDEO_CAPS_YUV ("YVYU"))
+ );
+
+static void gst_deinterlace_finalize (GObject * self);
+static void gst_deinterlace_set_property (GObject * self, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_deinterlace_get_property (GObject * self, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static GstCaps *gst_deinterlace_getcaps (GstPad * pad);
+static gboolean gst_deinterlace_setcaps (GstPad * pad, GstCaps * caps);
+static gboolean gst_deinterlace_sink_event (GstPad * pad, GstEvent * event);
+static GstFlowReturn gst_deinterlace_chain (GstPad * pad, GstBuffer * buffer);
+static GstStateChangeReturn gst_deinterlace_change_state (GstElement * element,
+ GstStateChange transition);
+
+static gboolean gst_deinterlace_src_event (GstPad * pad, GstEvent * event);
+static gboolean gst_deinterlace_src_query (GstPad * pad, GstQuery * query);
+static const GstQueryType *gst_deinterlace_src_query_types (GstPad * pad);
+
+static void gst_deinterlace_reset (GstDeinterlace * self);
+
+static void gst_deinterlace_child_proxy_interface_init (gpointer g_iface,
+ gpointer iface_data);
+
+static void
+_do_init (GType object_type)
+{
+ const GInterfaceInfo child_proxy_interface_info = {
+ (GInterfaceInitFunc) gst_deinterlace_child_proxy_interface_init,
+ NULL, /* interface_finalize */
+ NULL /* interface_data */
+ };
+
+ g_type_add_interface_static (object_type, GST_TYPE_CHILD_PROXY,
+ &child_proxy_interface_info);
+}
+
+GST_BOILERPLATE_FULL (GstDeinterlace, gst_deinterlace, GstElement,
+ GST_TYPE_ELEMENT, _do_init);
+
+static void
+gst_deinterlace_set_method (GstDeinterlace * self, GstDeinterlaceMethods method)
+{
+
+ if (self->method) {
+ gst_child_proxy_child_removed (GST_OBJECT (self),
+ GST_OBJECT (self->method));
+ gst_object_unparent (GST_OBJECT (self->method));
+ self->method = NULL;
+ }
+
+ switch (method) {
+ case GST_DEINTERLACE_TOMSMOCOMP:
+ self->method = g_object_new (GST_TYPE_DEINTERLACE_TOMSMOCOMP, NULL);
+ break;
+ case GST_DEINTERLACE_GREEDY_H:
+ self->method = g_object_new (GST_TYPE_DEINTERLACE_GREEDY_H, NULL);
+ break;
+ case GST_DEINTERLACE_GREEDY_L:
+ self->method = g_object_new (GST_TYPE_DEINTERLACE_GREEDY_L, NULL);
+ break;
+ case GST_DEINTERLACE_VFIR:
+ self->method = g_object_new (GST_TYPE_DEINTERLACE_VFIR, NULL);
+ break;
+ case GST_DEINTERLACE_LINEAR:
+ self->method = g_object_new (GST_TYPE_DEINTERLACE_LINEAR, NULL);
+ break;
+ case GST_DEINTERLACE_LINEAR_BLEND:
+ self->method = g_object_new (GST_TYPE_DEINTERLACE_LINEAR_BLEND, NULL);
+ break;
+ case GST_DEINTERLACE_SCALER_BOB:
+ self->method = g_object_new (GST_TYPE_DEINTERLACE_SCALER_BOB, NULL);
+ break;
+ case GST_DEINTERLACE_WEAVE:
+ self->method = g_object_new (GST_TYPE_DEINTERLACE_WEAVE, NULL);
+ break;
+ case GST_DEINTERLACE_WEAVE_TFF:
+ self->method = g_object_new (GST_TYPE_DEINTERLACE_WEAVE_TFF, NULL);
+ break;
+ case GST_DEINTERLACE_WEAVE_BFF:
+ self->method = g_object_new (GST_TYPE_DEINTERLACE_WEAVE_BFF, NULL);
+ break;
+ default:
+ GST_WARNING_OBJECT (self, "Invalid Deinterlacer Method");
+ return;
+ }
+
+ self->method_id = method;
+
+ gst_object_set_name (GST_OBJECT (self->method), "method");
+ gst_object_set_parent (GST_OBJECT (self->method), GST_OBJECT (self));
+ gst_child_proxy_child_added (GST_OBJECT (self), GST_OBJECT (self->method));
+}
+
+static void
+gst_deinterlace_base_init (gpointer klass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&src_templ));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&sink_templ));
+
+ gst_element_class_set_details_simple (element_class,
+ "Deinterlacer",
+ "Filter/Video",
+ "Deinterlace Methods ported from DScaler/TvTime",
+ "Martin Eikermann <meiker@upb.de>, "
+ "Sebastian Dröge <slomo@circular-chaos.org>");
+}
+
+static void
+gst_deinterlace_class_init (GstDeinterlaceClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+
+ GstElementClass *element_class = (GstElementClass *) klass;
+
+ gobject_class->set_property = gst_deinterlace_set_property;
+ gobject_class->get_property = gst_deinterlace_get_property;
+ gobject_class->finalize = gst_deinterlace_finalize;
+
+ /**
+ * GstDeinterlace:mode
+ *
+ * This selects whether the deinterlacing methods 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 (gobject_class, PROP_MODE,
+ g_param_spec_enum ("mode",
+ "Mode",
+ "Deinterlace Mode",
+ GST_TYPE_DEINTERLACE_MODES,
+ DEFAULT_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+ );
+
+ /**
+ * GstDeinterlace:method
+ *
+ * Selects the different deinterlacing algorithms that can be used.
+ * These provide different quality and CPU usage.
+ *
+ * Some methods provide parameters which can be set by getting
+ * the "method" child via the #GstChildProxy interface and
+ * setting the appropiate properties on it.
+ *
+ * <itemizedlist>
+ * <listitem>
+ * <para>
+ * tomsmocomp
+ * Motion Adaptive: Motion Search
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * greedyh
+ * Motion Adaptive: Advanced Detection
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * greedyl
+ * Motion Adaptive: Simple Detection
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * vfir
+ * Blur vertical
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * linear
+ * Linear interpolation
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * linearblend
+ * Linear interpolation in time domain
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * scalerbob
+ * Double lines
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * weave
+ * Weave
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * weavetff
+ * Progressive: Top Field First
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * weavebff
+ * Progressive: Bottom Field First
+ * </para>
+ * </listitem>
+ * </itemizedlist>
+ */
+ g_object_class_install_property (gobject_class, PROP_METHOD,
+ g_param_spec_enum ("method",
+ "Method",
+ "Deinterlace Method",
+ GST_TYPE_DEINTERLACE_METHODS,
+ DEFAULT_METHOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+ );
+
+ /**
+ * GstDeinterlace:fields
+ *
+ * This selects which fields should be output. If "all" is selected
+ * the output framerate will be double.
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_FIELDS,
+ g_param_spec_enum ("fields",
+ "fields",
+ "Fields to use for deinterlacing",
+ GST_TYPE_DEINTERLACE_FIELDS,
+ DEFAULT_FIELDS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+ );
+
+ /**
+ * GstDeinterlace:layout
+ *
+ * This selects which fields is the first in time.
+ *
+ */
+ g_object_class_install_property (gobject_class, PROP_FIELD_LAYOUT,
+ g_param_spec_enum ("tff",
+ "tff",
+ "Deinterlace top field first",
+ GST_TYPE_DEINTERLACE_FIELD_LAYOUT,
+ DEFAULT_FIELD_LAYOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+ );
+
+ element_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_deinterlace_change_state);
+}
+
+static GstObject *
+gst_deinterlace_child_proxy_get_child_by_index (GstChildProxy * child_proxy,
+ guint index)
+{
+ GstDeinterlace *self = GST_DEINTERLACE (child_proxy);
+
+ g_return_val_if_fail (index == 0, NULL);
+
+ return gst_object_ref (self->method);
+}
+
+static guint
+gst_deinterlace_child_proxy_get_children_count (GstChildProxy * child_proxy)
+{
+ return 1;
+}
+
+static void
+gst_deinterlace_child_proxy_interface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ GstChildProxyInterface *iface = g_iface;
+
+ iface->get_child_by_index = gst_deinterlace_child_proxy_get_child_by_index;
+ iface->get_children_count = gst_deinterlace_child_proxy_get_children_count;
+}
+
+static void
+gst_deinterlace_init (GstDeinterlace * self, GstDeinterlaceClass * klass)
+{
+ self->sinkpad = gst_pad_new_from_static_template (&sink_templ, "sink");
+ gst_pad_set_chain_function (self->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_deinterlace_chain));
+ gst_pad_set_event_function (self->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_deinterlace_sink_event));
+ gst_pad_set_setcaps_function (self->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_deinterlace_setcaps));
+ gst_pad_set_getcaps_function (self->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_deinterlace_getcaps));
+ gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
+
+ self->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
+ gst_pad_set_event_function (self->srcpad,
+ GST_DEBUG_FUNCPTR (gst_deinterlace_src_event));
+ gst_pad_set_query_type_function (self->srcpad,
+ GST_DEBUG_FUNCPTR (gst_deinterlace_src_query_types));
+ gst_pad_set_query_function (self->srcpad,
+ GST_DEBUG_FUNCPTR (gst_deinterlace_src_query));
+ gst_pad_set_setcaps_function (self->srcpad,
+ GST_DEBUG_FUNCPTR (gst_deinterlace_setcaps));
+ gst_pad_set_getcaps_function (self->srcpad,
+ GST_DEBUG_FUNCPTR (gst_deinterlace_getcaps));
+ gst_element_add_pad (GST_ELEMENT (self), self->srcpad);
+
+ gst_element_no_more_pads (GST_ELEMENT (self));
+
+ self->mode = DEFAULT_MODE;
+ gst_deinterlace_set_method (self, DEFAULT_METHOD);
+ self->fields = DEFAULT_FIELDS;
+ self->field_layout = DEFAULT_FIELD_LAYOUT;
+
+ gst_deinterlace_reset (self);
+}
+
+static void
+gst_deinterlace_reset_history (GstDeinterlace * self)
+{
+ gint i;
+
+ for (i = 0; i < self->history_count; i++) {
+ if (self->field_history[i].buf) {
+ gst_buffer_unref (self->field_history[i].buf);
+ self->field_history[i].buf = NULL;
+ }
+ }
+ memset (self->field_history, 0, MAX_FIELD_HISTORY * sizeof (GstPicture));
+ self->history_count = 0;
+}
+
+static void
+gst_deinterlace_reset (GstDeinterlace * self)
+{
+ self->row_stride = 0;
+ self->frame_width = 0;
+ self->frame_height = 0;
+ self->frame_rate_n = 0;
+ self->frame_rate_d = 0;
+ self->field_height = 0;
+ self->field_stride = 0;
+
+ gst_deinterlace_reset_history (self);
+}
+
+static void
+gst_deinterlace_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstDeinterlace *self;
+
+ g_return_if_fail (GST_IS_DEINTERLACE (object));
+ self = GST_DEINTERLACE (object);
+
+ switch (prop_id) {
+ case PROP_MODE:{
+ gint oldmode;
+
+ GST_OBJECT_LOCK (self);
+ oldmode = self->mode;
+ self->mode = g_value_get_enum (value);
+ if (self->mode != oldmode && GST_PAD_CAPS (self->srcpad))
+ gst_deinterlace_setcaps (self->sinkpad, GST_PAD_CAPS (self->sinkpad));
+ GST_OBJECT_UNLOCK (self);
+ break;
+ }
+ case PROP_METHOD:
+ gst_deinterlace_set_method (self, g_value_get_enum (value));
+ break;
+ case PROP_FIELDS:{
+ gint oldfields;
+
+ GST_OBJECT_LOCK (self);
+ oldfields = self->fields;
+ self->fields = g_value_get_enum (value);
+ if (self->fields != oldfields && GST_PAD_CAPS (self->srcpad))
+ gst_deinterlace_setcaps (self->sinkpad, GST_PAD_CAPS (self->sinkpad));
+ GST_OBJECT_UNLOCK (self);
+ break;
+ }
+ case PROP_FIELD_LAYOUT:
+ self->field_layout = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
+ }
+
+}
+
+static void
+gst_deinterlace_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstDeinterlace *self;
+
+ g_return_if_fail (GST_IS_DEINTERLACE (object));
+ self = GST_DEINTERLACE (object);
+
+ switch (prop_id) {
+ case PROP_MODE:
+ g_value_set_enum (value, self->mode);
+ break;
+ case PROP_METHOD:
+ g_value_set_enum (value, self->method_id);
+ break;
+ case PROP_FIELDS:
+ g_value_set_enum (value, self->fields);
+ break;
+ case PROP_FIELD_LAYOUT:
+ g_value_set_enum (value, self->field_layout);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, prop_id, pspec);
+ }
+}
+
+static void
+gst_deinterlace_finalize (GObject * object)
+{
+ GstDeinterlace *self = GST_DEINTERLACE (object);
+
+ gst_deinterlace_reset (self);
+
+ if (self->method) {
+ gst_object_unparent (GST_OBJECT (self->method));
+ self->method = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static GstBuffer *
+gst_deinterlace_pop_history (GstDeinterlace * self)
+{
+ GstBuffer *buffer = NULL;
+
+ g_assert (self->history_count > 0);
+
+ buffer = self->field_history[self->history_count - 1].buf;
+
+ self->history_count--;
+ GST_DEBUG_OBJECT (self, "pop, size(history): %d", self->history_count);
+
+ return buffer;
+}
+
+#if 0
+static GstBuffer *
+gst_deinterlace_head_history (GstDeinterlace * self)
+{
+ return self->field_history[self->history_count - 1].buf;
+}
+#endif
+
+
+/* invariant: field with smallest timestamp is self->field_history[self->history_count-1]
+
+*/
+
+static void
+gst_deinterlace_push_history (GstDeinterlace * self, GstBuffer * buffer)
+{
+ int i = 1;
+ GstClockTime timestamp;
+ GstDeinterlaceFieldLayout field_layout = self->field_layout;
+ gboolean repeated = GST_BUFFER_FLAG_IS_SET (buffer, GST_VIDEO_BUFFER_RFF);
+ gboolean tff = GST_BUFFER_FLAG_IS_SET (buffer, GST_VIDEO_BUFFER_TFF);
+ gboolean onefield =
+ GST_BUFFER_FLAG_IS_SET (buffer, GST_VIDEO_BUFFER_ONEFIELD);
+ GstBuffer *field1, *field2;
+ guint fields_to_push = (onefield) ? 1 : (!repeated) ? 2 : 3;
+ gint field1_flags, field2_flags;
+
+ g_assert (self->history_count < MAX_FIELD_HISTORY - fields_to_push);
+
+ for (i = MAX_FIELD_HISTORY - 1; i >= fields_to_push; i--) {
+ self->field_history[i].buf = self->field_history[i - fields_to_push].buf;
+ self->field_history[i].flags =
+ self->field_history[i - fields_to_push].flags;
+ }
+
+ if (field_layout == GST_DEINTERLACE_LAYOUT_AUTO) {
+ if (!self->interlaced) {
+ GST_WARNING_OBJECT (self, "Can't detect field layout -- assuming TFF");
+ field_layout = GST_DEINTERLACE_LAYOUT_TFF;
+ } else if (tff) {
+ field_layout = GST_DEINTERLACE_LAYOUT_TFF;
+ } else {
+ field_layout = GST_DEINTERLACE_LAYOUT_BFF;
+ }
+ }
+
+ if (field_layout == GST_DEINTERLACE_LAYOUT_TFF) {
+ GST_DEBUG_OBJECT (self, "Top field first");
+ field1 = gst_buffer_ref (buffer);
+ field1_flags = PICTURE_INTERLACED_TOP;
+ field2 = gst_buffer_create_sub (buffer, self->row_stride,
+ GST_BUFFER_SIZE (buffer) - self->row_stride);
+ field2_flags = PICTURE_INTERLACED_BOTTOM;
+ } else {
+ GST_DEBUG_OBJECT (self, "Bottom field first");
+ field1 = gst_buffer_create_sub (buffer, self->row_stride,
+ GST_BUFFER_SIZE (buffer) - self->row_stride);
+ field1_flags = PICTURE_INTERLACED_BOTTOM;
+ field2 = gst_buffer_ref (buffer);
+ field2_flags = PICTURE_INTERLACED_TOP;
+ }
+
+ /* Timestamps are assigned to the field buffers under the assumption that
+ the timestamp of the buffer equals the first fields timestamp */
+
+ timestamp = GST_BUFFER_TIMESTAMP (buffer);
+ GST_BUFFER_TIMESTAMP (field1) = timestamp;
+ GST_BUFFER_TIMESTAMP (field2) = timestamp + self->field_duration;
+ if (repeated)
+ GST_BUFFER_TIMESTAMP (field2) += self->field_duration;
+
+ if (repeated) {
+ self->field_history[0].buf = field2;
+ self->field_history[0].flags = field2_flags;
+ self->field_history[1].buf = gst_buffer_ref (field1);
+ GST_BUFFER_TIMESTAMP (self->field_history[1].buf) += self->field_duration;
+ self->field_history[1].flags = field1_flags;
+ self->field_history[2].buf = field1;
+ self->field_history[2].flags = field1_flags;
+ } else if (!onefield) {
+ self->field_history[0].buf = field2;
+ self->field_history[0].flags = field2_flags;
+ self->field_history[1].buf = field1;
+ self->field_history[1].flags = field1_flags;
+ } else { /* onefield */
+ self->field_history[0].buf = field1;
+ self->field_history[0].flags = field1_flags;
+ gst_buffer_unref (field2);
+ }
+
+ self->history_count += fields_to_push;
+ GST_DEBUG_OBJECT (self, "push, size(history): %d", self->history_count);
+
+ gst_buffer_unref (buffer);
+}
+
+static GstFlowReturn
+gst_deinterlace_chain (GstPad * pad, GstBuffer * buf)
+{
+ GstDeinterlace *self = NULL;
+ GstClockTime timestamp;
+ GstFlowReturn ret = GST_FLOW_OK;
+ gint fields_required = 0;
+ gint cur_field_idx = 0;
+ GstBuffer *outbuf;
+
+ self = GST_DEINTERLACE (GST_PAD_PARENT (pad));
+
+ if (self->mode == GST_DEINTERLACE_MODE_DISABLED || (!self->interlaced
+ && self->mode != GST_DEINTERLACE_MODE_INTERLACED))
+ return gst_pad_push (self->srcpad, buf);
+
+ if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT)) {
+ GST_DEBUG_OBJECT (self, "DISCONT buffer, resetting history");
+ gst_deinterlace_reset_history (self);
+ }
+
+ gst_deinterlace_push_history (self, buf);
+ buf = NULL;
+
+ fields_required = gst_deinterlace_method_get_fields_required (self->method);
+
+ /* Not enough fields in the history */
+ if (self->history_count < fields_required + 1) {
+ /* TODO: do bob or just forward frame */
+ GST_DEBUG_OBJECT (self, "HistoryCount=%d", self->history_count);
+ return GST_FLOW_OK;
+ }
+
+ while (self->history_count >= fields_required) {
+ if (self->fields == GST_DEINTERLACE_ALL)
+ GST_DEBUG_OBJECT (self, "All fields");
+ if (self->fields == GST_DEINTERLACE_TF)
+ GST_DEBUG_OBJECT (self, "Top fields");
+ if (self->fields == GST_DEINTERLACE_BF)
+ GST_DEBUG_OBJECT (self, "Bottom fields");
+
+ cur_field_idx = self->history_count - fields_required;
+
+ if ((self->field_history[cur_field_idx].flags == PICTURE_INTERLACED_TOP
+ && self->fields == GST_DEINTERLACE_TF) ||
+ self->fields == GST_DEINTERLACE_ALL) {
+ GST_DEBUG_OBJECT (self, "deinterlacing top field");
+
+ /* create new buffer */
+ ret = gst_pad_alloc_buffer_and_set_caps (self->srcpad,
+ GST_BUFFER_OFFSET_NONE, self->frame_size,
+ GST_PAD_CAPS (self->srcpad), &outbuf);
+ if (ret != GST_FLOW_OK)
+ return ret;
+
+ /* do magic calculus */
+ gst_deinterlace_method_deinterlace_frame (self->method, self, outbuf);
+
+ g_assert (self->history_count - 1 -
+ gst_deinterlace_method_get_latency (self->method) >= 0);
+ buf =
+ self->field_history[self->history_count - 1 -
+ gst_deinterlace_method_get_latency (self->method)].buf;
+ timestamp = GST_BUFFER_TIMESTAMP (buf);
+
+ gst_buffer_unref (gst_deinterlace_pop_history (self));
+
+ GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
+ if (self->fields == GST_DEINTERLACE_ALL)
+ GST_BUFFER_DURATION (outbuf) = self->field_duration;
+ else
+ GST_BUFFER_DURATION (outbuf) = 2 * self->field_duration;
+
+ ret = gst_pad_push (self->srcpad, outbuf);
+ outbuf = NULL;
+ if (ret != GST_FLOW_OK)
+ return ret;
+ }
+ /* no calculation done: remove excess field */
+ else if (self->field_history[cur_field_idx].flags ==
+ PICTURE_INTERLACED_TOP && self->fields == GST_DEINTERLACE_BF) {
+ GST_DEBUG_OBJECT (self, "Removing unused top field");
+ gst_buffer_unref (gst_deinterlace_pop_history (self));
+ }
+
+ cur_field_idx = self->history_count - fields_required;
+ if (self->history_count < fields_required)
+ break;
+
+ /* deinterlace bottom_field */
+ if ((self->field_history[cur_field_idx].flags == PICTURE_INTERLACED_BOTTOM
+ && self->fields == GST_DEINTERLACE_BF) ||
+ self->fields == GST_DEINTERLACE_ALL) {
+ GST_DEBUG_OBJECT (self, "deinterlacing bottom field");
+
+ /* create new buffer */
+ ret = gst_pad_alloc_buffer_and_set_caps (self->srcpad,
+ GST_BUFFER_OFFSET_NONE, self->frame_size,
+ GST_PAD_CAPS (self->srcpad), &outbuf);
+ if (ret != GST_FLOW_OK)
+ return ret;
+
+ /* do magic calculus */
+ gst_deinterlace_method_deinterlace_frame (self->method, self, outbuf);
+
+ g_assert (self->history_count - 1 -
+ gst_deinterlace_method_get_latency (self->method) >= 0);
+ buf =
+ self->field_history[self->history_count - 1 -
+ gst_deinterlace_method_get_latency (self->method)].buf;
+ timestamp = GST_BUFFER_TIMESTAMP (buf);
+
+ gst_buffer_unref (gst_deinterlace_pop_history (self));
+
+ GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
+ if (self->fields == GST_DEINTERLACE_ALL)
+ GST_BUFFER_DURATION (outbuf) = self->field_duration;
+ else
+ GST_BUFFER_DURATION (outbuf) = 2 * self->field_duration;
+
+ ret = gst_pad_push (self->srcpad, outbuf);
+ outbuf = NULL;
+
+ if (ret != GST_FLOW_OK)
+ return ret;
+ }
+ /* no calculation done: remove excess field */
+ else if (self->field_history[cur_field_idx].flags ==
+ PICTURE_INTERLACED_BOTTOM && self->fields == GST_DEINTERLACE_TF) {
+ GST_DEBUG_OBJECT (self, "Removing unused bottom field");
+ gst_buffer_unref (gst_deinterlace_pop_history (self));
+ }
+ }
+
+ GST_DEBUG_OBJECT (self, "----chain end ----\n\n");
+
+ return ret;
+}
+
+static gint
+gst_greatest_common_divisor (gint a, gint b)
+{
+ while (b != 0) {
+ int temp = a;
+
+ a = b;
+ b = temp % b;
+ }
+
+ return ABS (a);
+}
+
+static gboolean
+gst_fraction_double (gint * n_out, gint * d_out, gboolean half)
+{
+ gint n, d, gcd;
+
+ n = *n_out;
+ d = *d_out;
+
+ if (d == 0)
+ return FALSE;
+
+ if (n == 0 || (n == G_MAXINT && d == 1))
+ return TRUE;
+
+ gcd = gst_greatest_common_divisor (n, d);
+ n /= gcd;
+ d /= gcd;
+
+ if (!half) {
+ if (G_MAXINT / 2 >= ABS (n)) {
+ n *= 2;
+ } else if (d >= 2) {
+ d /= 2;
+ } else {
+ return FALSE;
+ }
+ } else {
+ if (G_MAXINT / 2 >= ABS (d)) {
+ d *= 2;
+ } else if (n >= 2) {
+ n /= 2;
+ } else {
+ return FALSE;
+ }
+ }
+
+ *n_out = n;
+ *d_out = d;
+
+ return TRUE;
+}
+
+static GstCaps *
+gst_deinterlace_getcaps (GstPad * pad)
+{
+ GstCaps *ret;
+ GstDeinterlace *self = GST_DEINTERLACE (gst_pad_get_parent (pad));
+ GstPad *otherpad;
+ gint len;
+ const GstCaps *ourcaps;
+ GstCaps *peercaps;
+
+ GST_OBJECT_LOCK (self);
+
+ otherpad = (pad == self->srcpad) ? self->sinkpad : self->srcpad;
+
+ ourcaps = gst_pad_get_pad_template_caps (pad);
+ peercaps = gst_pad_peer_get_caps (otherpad);
+
+ if (peercaps) {
+ ret = gst_caps_intersect (ourcaps, peercaps);
+ gst_caps_unref (peercaps);
+ } else {
+ ret = gst_caps_copy (ourcaps);
+ }
+
+ GST_OBJECT_UNLOCK (self);
+
+ if ((self->interlaced || self->mode == GST_DEINTERLACE_MODE_INTERLACED) &&
+ self->fields == GST_DEINTERLACE_ALL
+ && self->mode != GST_DEINTERLACE_MODE_DISABLED) {
+ for (len = gst_caps_get_size (ret); len > 0; len--) {
+ GstStructure *s = gst_caps_get_structure (ret, len - 1);
+ const GValue *val;
+
+ val = gst_structure_get_value (s, "framerate");
+ if (!val)
+ continue;
+
+ if (G_VALUE_TYPE (val) == GST_TYPE_FRACTION) {
+ gint n, d;
+
+ n = gst_value_get_fraction_numerator (val);
+ d = gst_value_get_fraction_denominator (val);
+
+ if (!gst_fraction_double (&n, &d, pad != self->srcpad)) {
+ goto error;
+ }
+
+ gst_structure_set (s, "framerate", GST_TYPE_FRACTION, n, d, NULL);
+ } else if (G_VALUE_TYPE (val) == GST_TYPE_FRACTION_RANGE) {
+ const GValue *min, *max;
+ GValue nrange = { 0, }, nmin = {
+ 0,}, nmax = {
+ 0,};
+ gint n, d;
+
+ g_value_init (&nrange, GST_TYPE_FRACTION_RANGE);
+ g_value_init (&nmin, GST_TYPE_FRACTION);
+ g_value_init (&nmax, GST_TYPE_FRACTION);
+
+ min = gst_value_get_fraction_range_min (val);
+ max = gst_value_get_fraction_range_max (val);
+
+ n = gst_value_get_fraction_numerator (min);
+ d = gst_value_get_fraction_denominator (min);
+
+ if (!gst_fraction_double (&n, &d, pad != self->srcpad)) {
+ g_value_unset (&nrange);
+ g_value_unset (&nmax);
+ g_value_unset (&nmin);
+ goto error;
+ }
+
+ gst_value_set_fraction (&nmin, n, d);
+
+ n = gst_value_get_fraction_numerator (max);
+ d = gst_value_get_fraction_denominator (max);
+
+ if (!gst_fraction_double (&n, &d, pad != self->srcpad)) {
+ g_value_unset (&nrange);
+ g_value_unset (&nmax);
+ g_value_unset (&nmin);
+ goto error;
+ }
+
+ gst_value_set_fraction (&nmax, n, d);
+ gst_value_set_fraction_range (&nrange, &nmin, &nmax);
+
+ gst_structure_set_value (s, "framerate", &nrange);
+
+ g_value_unset (&nmin);
+ g_value_unset (&nmax);
+ g_value_unset (&nrange);
+ } else if (G_VALUE_TYPE (val) == GST_TYPE_LIST) {
+ const GValue *lval;
+ GValue nlist = { 0, };
+ GValue nval = { 0, };
+ gint i;
+
+ g_value_init (&nlist, GST_TYPE_LIST);
+ for (i = gst_value_list_get_size (val); i > 0; i--) {
+ gint n, d;
+
+ lval = gst_value_list_get_value (val, i);
+
+ if (G_VALUE_TYPE (lval) != GST_TYPE_FRACTION)
+ continue;
+
+ n = gst_value_get_fraction_numerator (lval);
+ d = gst_value_get_fraction_denominator (lval);
+
+ /* Double/Half the framerate but if this fails simply
+ * skip this value from the list */
+ if (!gst_fraction_double (&n, &d, pad != self->srcpad)) {
+ continue;
+ }
+
+ g_value_init (&nval, GST_TYPE_FRACTION);
+
+ gst_value_set_fraction (&nval, n, d);
+ gst_value_list_append_value (&nlist, &nval);
+ g_value_unset (&nval);
+ }
+ gst_structure_set_value (s, "framerate", &nlist);
+ g_value_unset (&nlist);
+ }
+ }
+ }
+
+ GST_DEBUG_OBJECT (pad, "Returning caps %" GST_PTR_FORMAT, ret);
+
+ return ret;
+
+error:
+ GST_ERROR_OBJECT (pad, "Unable to transform peer caps");
+ gst_caps_unref (ret);
+ return NULL;
+}
+
+static gboolean
+gst_deinterlace_setcaps (GstPad * pad, GstCaps * caps)
+{
+ gboolean res = TRUE;
+ GstDeinterlace *self = GST_DEINTERLACE (gst_pad_get_parent (pad));
+ GstPad *otherpad;
+ GstStructure *structure;
+ GstVideoFormat fmt;
+ guint32 fourcc;
+ GstCaps *othercaps;
+
+ otherpad = (pad == self->srcpad) ? self->sinkpad : self->srcpad;
+
+ structure = gst_caps_get_structure (caps, 0);
+
+ res = gst_structure_get_int (structure, "width", &self->frame_width);
+ res &= gst_structure_get_int (structure, "height", &self->frame_height);
+ res &=
+ gst_structure_get_fraction (structure, "framerate", &self->frame_rate_n,
+ &self->frame_rate_d);
+ res &= gst_structure_get_fourcc (structure, "format", &fourcc);
+ res &= gst_video_format_parse_caps_interlaced (caps, &self->interlaced);
+ if (!res)
+ goto invalid_caps;
+
+ if ((self->interlaced || self->mode == GST_DEINTERLACE_MODE_INTERLACED) &&
+ self->fields == GST_DEINTERLACE_ALL
+ && self->mode != GST_DEINTERLACE_MODE_DISABLED) {
+ gint fps_n = self->frame_rate_n, fps_d = self->frame_rate_d;
+
+ if (!gst_fraction_double (&fps_n, &fps_d, otherpad != self->srcpad))
+ goto invalid_caps;
+
+ othercaps = gst_caps_copy (caps);
+
+ gst_caps_set_simple (othercaps, "framerate", GST_TYPE_FRACTION, fps_n,
+ fps_d, NULL);
+ } else {
+ othercaps = gst_caps_ref (caps);
+ }
+
+ if (!gst_pad_set_caps (otherpad, othercaps))
+ goto caps_not_accepted;
+ gst_caps_unref (othercaps);
+
+ self->field_height = self->frame_height / 2;
+
+ fmt = gst_video_format_from_fourcc (fourcc);
+
+ /* TODO: only true if fields are subbuffers of interlaced frames,
+ change when the buffer-fields concept has landed */
+ self->field_stride =
+ gst_video_format_get_row_stride (fmt, 0, self->frame_width) * 2;
+
+ /* in bytes */
+ self->row_stride =
+ gst_video_format_get_row_stride (fmt, 0, self->frame_width);
+ self->frame_size =
+ gst_video_format_get_size (fmt, self->frame_width, self->frame_height);
+
+ if (self->fields == GST_DEINTERLACE_ALL && otherpad == self->srcpad)
+ self->field_duration =
+ gst_util_uint64_scale (GST_SECOND, self->frame_rate_d,
+ self->frame_rate_n);
+ else
+ self->field_duration =
+ gst_util_uint64_scale (GST_SECOND, self->frame_rate_d,
+ 2 * self->frame_rate_n);
+
+ GST_DEBUG_OBJECT (self, "Set caps: %" GST_PTR_FORMAT, caps);
+
+done:
+
+ gst_object_unref (self);
+ return res;
+
+invalid_caps:
+ res = FALSE;
+ GST_ERROR_OBJECT (pad, "Invalid caps: %" GST_PTR_FORMAT, caps);
+ goto done;
+
+caps_not_accepted:
+ res = FALSE;
+ GST_ERROR_OBJECT (pad, "Caps not accepted: %" GST_PTR_FORMAT, othercaps);
+ gst_caps_unref (othercaps);
+ goto done;
+}
+
+static gboolean
+gst_deinterlace_sink_event (GstPad * pad, GstEvent * event)
+{
+ gboolean res = TRUE;
+ GstDeinterlace *self = GST_DEINTERLACE (gst_pad_get_parent (pad));
+
+ GST_LOG_OBJECT (pad, "received %s event", GST_EVENT_TYPE_NAME (event));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_FLUSH_STOP:
+ case GST_EVENT_EOS:
+ case GST_EVENT_NEWSEGMENT:
+ gst_deinterlace_reset_history (self);
+
+ /* fall through */
+ default:
+ res = gst_pad_event_default (pad, event);
+ break;
+ }
+
+ gst_object_unref (self);
+ return res;
+}
+
+static GstStateChangeReturn
+gst_deinterlace_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret;
+ GstDeinterlace *self = GST_DEINTERLACE (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+ if (ret != GST_STATE_CHANGE_SUCCESS)
+ return ret;
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ gst_deinterlace_reset (self);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static gboolean
+gst_deinterlace_src_event (GstPad * pad, GstEvent * event)
+{
+ GstDeinterlace *self = GST_DEINTERLACE (gst_pad_get_parent (pad));
+ gboolean res;
+
+ GST_DEBUG_OBJECT (pad, "received %s event", GST_EVENT_TYPE_NAME (event));
+
+ switch (GST_EVENT_TYPE (event)) {
+ default:
+ res = gst_pad_event_default (pad, event);
+ break;
+ }
+
+ gst_object_unref (self);
+
+ return res;
+}
+
+static gboolean
+gst_deinterlace_src_query (GstPad * pad, GstQuery * query)
+{
+ GstDeinterlace *self = GST_DEINTERLACE (gst_pad_get_parent (pad));
+ gboolean res = FALSE;
+
+ GST_LOG_OBJECT (self, "%s query", GST_QUERY_TYPE_NAME (query));
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_LATENCY:
+ if ((self->interlaced || self->mode == GST_DEINTERLACE_MODE_INTERLACED)
+ && self->mode != GST_DEINTERLACE_MODE_DISABLED) {
+ GstClockTime min, max;
+ gboolean live;
+ GstPad *peer;
+
+ if ((peer = gst_pad_get_peer (self->sinkpad))) {
+ if ((res = gst_pad_query (peer, query))) {
+ GstClockTime latency;
+ gint fields_required = 0;
+ gint method_latency = 0;
+
+ if (self->method) {
+ fields_required =
+ gst_deinterlace_method_get_fields_required (self->method);
+ method_latency =
+ gst_deinterlace_method_get_latency (self->method);
+ }
+
+ 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 */
+ latency = (fields_required + method_latency) * self->field_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;
+ else
+ 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);
+ }
+ gst_object_unref (peer);
+ } else {
+ res = gst_pad_query_default (pad, query);
+ }
+ break;
+ }
+ default:
+ res = gst_pad_query_default (pad, query);
+ break;
+ }
+
+ gst_object_unref (self);
+ return res;
+}
+
+static const GstQueryType *
+gst_deinterlace_src_query_types (GstPad * pad)
+{
+ static const GstQueryType types[] = {
+ GST_QUERY_LATENCY,
+ GST_QUERY_NONE
+ };
+ return types;
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ GST_DEBUG_CATEGORY_INIT (deinterlace_debug, "deinterlace", 0, "Deinterlacer");
+
+ oil_init ();
+
+ if (!gst_element_register (plugin, "deinterlace", GST_RANK_NONE,
+ GST_TYPE_DEINTERLACE)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "deinterlace",
+ "Deinterlacer", plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME,
+ GST_PACKAGE_ORIGIN);