summaryrefslogtreecommitdiff
path: root/gst/fieldanalysis/gstfieldanalysis.c
diff options
context:
space:
mode:
authorRobert Swain <robert.swain@collabora.co.uk>2010-09-15 17:32:09 +0200
committerRobert Swain <robert.swain@collabora.co.uk>2011-03-01 18:45:52 +0100
commit14fb72014982c26a0847e9faeacac4fc25b738be (patch)
treea127d15765e122deaa8ad4acf16cecd6f85a9bcd /gst/fieldanalysis/gstfieldanalysis.c
parent8e60680c3381b5a579d8e6fd21110289c7125b26 (diff)
downloadgstreamer-plugins-bad-14fb72014982c26a0847e9faeacac4fc25b738be.tar.gz
fieldanalysis: Add fieldanalysis element
This element analyses video buffers to identify if they are progressive, interlaced or telecined and outputs buffers with appropriate flags for a downstream element (which will be the deinterlace element, after some forthcoming modifications) to be able to output progressive frames and adjust timestamps resulting in a progressive stream.
Diffstat (limited to 'gst/fieldanalysis/gstfieldanalysis.c')
-rw-r--r--gst/fieldanalysis/gstfieldanalysis.c1806
1 files changed, 1806 insertions, 0 deletions
diff --git a/gst/fieldanalysis/gstfieldanalysis.c b/gst/fieldanalysis/gstfieldanalysis.c
new file mode 100644
index 000000000..5f093320f
--- /dev/null
+++ b/gst/fieldanalysis/gstfieldanalysis.c
@@ -0,0 +1,1806 @@
+/*
+ * GStreamer
+ * Copyright (C) 2011 Robert Swain <robert.swain@collabora.co.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Alternatively, the contents of this file may be used under the
+ * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
+ * which case the following provisions apply instead of the ones
+ * mentioned above:
+ *
+ * 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-fieldanalysis
+ *
+ * Analyse fields from video buffers to identify whether the buffers are
+ * progressive/telecined/interlaced and, if telecined, the telecine pattern
+ * used.
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch -v uridecodebin uri=/path/to/foo.bar ! fieldanalysis ! deinterlace ! ffmpegcolorspace ! autovideosink
+ * ]| This pipeline will analyse a video stream with default metrics and thresholds and output progressive frames.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <string.h>
+#include <stdlib.h> /* for abs() */
+
+#include "gstfieldanalysis.h"
+#include "gstfieldanalysisorc.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_field_analysis_debug);
+#define GST_CAT_DEFAULT gst_field_analysis_debug
+
+#define DEFAULT_FIELD_METRIC GST_FIELDANALYSIS_SSD
+#define DEFAULT_FRAME_METRIC GST_FIELDANALYSIS_5_TAP
+#define DEFAULT_NOISE_FLOOR 16
+#define DEFAULT_FIELD_THRESH 0.08f
+#define DEFAULT_FRAME_THRESH 0.002f
+#define DEFAULT_COMB_METHOD METHOD_5_TAP
+#define DEFAULT_SPATIAL_THRESH 9
+#define DEFAULT_BLOCK_WIDTH 16
+#define DEFAULT_BLOCK_HEIGHT 16
+#define DEFAULT_BLOCK_THRESH 80
+#define DEFAULT_IGNORED_LINES 2
+
+enum
+{
+ PROP_0,
+ PROP_FIELD_METRIC,
+ PROP_FRAME_METRIC,
+ PROP_NOISE_FLOOR,
+ PROP_FIELD_THRESH,
+ PROP_FRAME_THRESH,
+ PROP_COMB_METHOD,
+ PROP_SPATIAL_THRESH,
+ PROP_BLOCK_WIDTH,
+ PROP_BLOCK_HEIGHT,
+ PROP_BLOCK_THRESH,
+ PROP_IGNORED_LINES
+};
+
+static GstStaticPadTemplate sink_factory =
+GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("{YUY2,UYVY,I420,YV12}")));
+
+static GstStaticPadTemplate src_factory =
+GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("{YUY2,UYVY,I420,YV12}")));
+
+GST_BOILERPLATE (GstFieldAnalysis, gst_field_analysis, GstElement,
+ GST_TYPE_ELEMENT);
+
+static void gst_field_analysis_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_field_analysis_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_field_analysis_set_caps (GstPad * pad, GstCaps * caps);
+static gboolean gst_field_analysis_sink_event (GstPad * pad, GstEvent * event);
+static GstFlowReturn gst_field_analysis_chain (GstPad * pad, GstBuffer * buf);
+static GstStateChangeReturn gst_field_analysis_change_state (GstElement *
+ element, GstStateChange transition);
+static void gst_field_analysis_finalize (GObject * self);
+
+static GQueue *gst_field_analysis_flush_queue (GstFieldAnalysis * filter,
+ GQueue * queue);
+
+static void
+gst_field_analysis_base_init (gpointer gclass)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
+
+ gst_element_class_set_details_simple (element_class,
+ "Video field analysis",
+ "Filter/Analysis/Video",
+ "Analyse fields from video frames to identify if they are progressive/telecined/interlaced",
+ "Robert Swain <robert.swain@collabora.co.uk>");
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&src_factory));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&sink_factory));
+}
+
+typedef enum
+{
+ GST_FIELDANALYSIS_SAD,
+ GST_FIELDANALYSIS_SSD,
+ GST_FIELDANALYSIS_3_TAP
+} GstFieldAnalysisFieldMetric;
+
+#define GST_TYPE_FIELDANALYSIS_FIELD_METRIC (gst_fieldanalysis_field_metric_get_type())
+static GType
+gst_fieldanalysis_field_metric_get_type (void)
+{
+ static GType fieldanalysis_field_metric_type = 0;
+
+ if (!fieldanalysis_field_metric_type) {
+ static const GEnumValue fieldanalysis_field_metrics[] = {
+ {GST_FIELDANALYSIS_SAD, "Sum of Absolute Differences", "sad"},
+ {GST_FIELDANALYSIS_SSD, "Sum of Squared Differences", "ssd"},
+ {GST_FIELDANALYSIS_3_TAP, "Difference of 3-tap [1,4,1] Horizontal Filter",
+ "3-tap"},
+ {0, NULL, NULL},
+ };
+
+ fieldanalysis_field_metric_type =
+ g_enum_register_static ("GstFieldAnalysisFieldMetric",
+ fieldanalysis_field_metrics);
+ }
+
+ return fieldanalysis_field_metric_type;
+}
+
+typedef enum
+{
+ GST_FIELDANALYSIS_5_TAP,
+ GST_FIELDANALYSIS_WINDOWED_COMB
+} GstFieldAnalysisFrameMetric;
+
+#define GST_TYPE_FIELDANALYSIS_FRAME_METRIC (gst_fieldanalysis_frame_metric_get_type())
+static GType
+gst_fieldanalysis_frame_metric_get_type (void)
+{
+ static GType fieldanalysis_frame_metric_type = 0;
+
+ if (!fieldanalysis_frame_metric_type) {
+ static const GEnumValue fieldanalyis_frame_metrics[] = {
+ {GST_FIELDANALYSIS_5_TAP, "5-tap [1,-3,4,-3,1] Vertical Filter", "5-tap"},
+ {GST_FIELDANALYSIS_WINDOWED_COMB,
+ "Windowed Comb Detection (not optimised)",
+ "windowed-comb"},
+ {0, NULL, NULL},
+ };
+
+ fieldanalysis_frame_metric_type =
+ g_enum_register_static ("GstFieldAnalysisFrameMetric",
+ fieldanalyis_frame_metrics);
+ }
+
+ return fieldanalysis_frame_metric_type;
+}
+
+#define GST_TYPE_FIELDANALYSIS_COMB_METHOD (gst_fieldanalysis_comb_method_get_type())
+static GType
+gst_fieldanalysis_comb_method_get_type (void)
+{
+ static GType fieldanalysis_comb_method_type = 0;
+
+ if (!fieldanalysis_comb_method_type) {
+ static const GEnumValue fieldanalyis_comb_methods[] = {
+ {METHOD_32DETECT,
+ "Difference to above sample in same field small and difference to sample in other field large",
+ "32-detect"},
+ {METHOD_IS_COMBED,
+ "Differences between current sample and the above/below samples in other field multiplied together, larger than squared spatial threshold (from Tritical's isCombed)",
+ "isCombed"},
+ {METHOD_5_TAP,
+ "5-tap [1,-3,4,-3,1] vertical filter result is larger than spatial threshold*6",
+ "5-tap"},
+ {0, NULL, NULL},
+ };
+
+ fieldanalysis_comb_method_type =
+ g_enum_register_static ("FieldAnalysisCombMethod",
+ fieldanalyis_comb_methods);
+ }
+
+ return fieldanalysis_comb_method_type;
+}
+
+static void
+gst_field_analysis_class_init (GstFieldAnalysisClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+
+ gobject_class->set_property = gst_field_analysis_set_property;
+ gobject_class->get_property = gst_field_analysis_get_property;
+ gobject_class->finalize = gst_field_analysis_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_FIELD_METRIC,
+ g_param_spec_enum ("field-metric", "Field Metric",
+ "Metric to be used for comparing same parity fields to decide if they are a repeated field for telecine",
+ GST_TYPE_FIELDANALYSIS_FIELD_METRIC, DEFAULT_FIELD_METRIC,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_FRAME_METRIC,
+ g_param_spec_enum ("frame-metric", "Frame Metric",
+ "Metric to be used for comparing opposite parity fields to decide if they are a progressive frame",
+ GST_TYPE_FIELDANALYSIS_FRAME_METRIC, DEFAULT_FRAME_METRIC,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_NOISE_FLOOR,
+ g_param_spec_uint ("noise-floor", "Noise Floor",
+ "Noise floor for appropriate metrics (per-pixel metric values with a score less than this will be ignored)",
+ 0, G_MAXUINT32,
+ DEFAULT_NOISE_FLOOR, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_FIELD_THRESH,
+ g_param_spec_float ("field-threshold", "Field Threshold",
+ "Threshold for field metric decisions", 0.0f, G_MAXFLOAT,
+ DEFAULT_FIELD_THRESH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_FRAME_THRESH,
+ g_param_spec_float ("frame-threshold", "Frame Threshold",
+ "Threshold for frame metric decisions", 0.0f, G_MAXFLOAT,
+ DEFAULT_FRAME_THRESH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_COMB_METHOD,
+ g_param_spec_enum ("comb-method", "Comb-detection Method",
+ "Metric to be used for identifying comb artifacts if using windowed comb detection",
+ GST_TYPE_FIELDANALYSIS_COMB_METHOD, DEFAULT_COMB_METHOD,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_SPATIAL_THRESH,
+ g_param_spec_int64 ("spatial-threshold", "Spatial Combing Threshold",
+ "Threshold for combing metric decisions", 0, G_MAXINT64,
+ DEFAULT_SPATIAL_THRESH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BLOCK_WIDTH,
+ g_param_spec_uint64 ("block-width", "Block width",
+ "Block width for windowed comb detection", 0, G_MAXUINT64,
+ DEFAULT_BLOCK_WIDTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BLOCK_HEIGHT,
+ g_param_spec_uint64 ("block-height", "Block height",
+ "Block height for windowed comb detection", 0, G_MAXUINT64,
+ DEFAULT_BLOCK_HEIGHT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_BLOCK_THRESH,
+ g_param_spec_uint64 ("block-threshold", "Block threshold",
+ "Block threshold for windowed comb detection", 0, G_MAXUINT64,
+ DEFAULT_BLOCK_THRESH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_IGNORED_LINES,
+ g_param_spec_uint64 ("ignored-lines", "Ignored lines",
+ "Ignore this many lines from the top and bottom for windowed comb detection",
+ 2, G_MAXUINT64, DEFAULT_IGNORED_LINES,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gstelement_class->change_state =
+ GST_DEBUG_FUNCPTR (gst_field_analysis_change_state);
+}
+
+static gfloat same_parity_sad (GstFieldAnalysis * filter,
+ FieldAnalysisFields * fields);
+static gfloat same_parity_ssd (GstFieldAnalysis * filter,
+ FieldAnalysisFields * fields);
+static gfloat same_parity_3_tap (GstFieldAnalysis * filter,
+ FieldAnalysisFields * fields);
+static gfloat opposite_parity_5_tap (GstFieldAnalysis * filter,
+ FieldAnalysisFields * fields);
+static guint64 block_score_for_row_32detect (GstFieldAnalysis * filter,
+ guint8 * base_fj, guint8 * base_fjp1);
+static guint64 block_score_for_row_iscombed (GstFieldAnalysis * filter,
+ guint8 * base_fj, guint8 * base_fjp1);
+static guint64 block_score_for_row_5_tap (GstFieldAnalysis * filter,
+ guint8 * base_fj, guint8 * base_fjp1);
+static gfloat opposite_parity_windowed_comb (GstFieldAnalysis * filter,
+ FieldAnalysisFields * fields);
+
+
+static void
+gst_field_analysis_reset (GstFieldAnalysis * filter)
+{
+ if (filter->frames) {
+ guint length = g_queue_get_length (filter->frames);
+ GST_DEBUG_OBJECT (filter, "Clearing queue (size %u)", length);
+ while (length) {
+ /* each buffer in the queue should have a ref on it and so to clear the
+ * queue we must pop and unref each buffer here */
+ gst_buffer_unref (g_queue_pop_head (filter->frames));
+ length--;
+ }
+ }
+ GST_DEBUG_OBJECT (filter, "Resetting context");
+ memset (filter->results, 0, 2 * sizeof (FieldAnalysis));
+ filter->is_telecine = FALSE;
+ filter->first_buffer = TRUE;
+ filter->width = 0;
+ g_free (filter->comb_mask);
+ g_free (filter->block_scores);
+}
+
+static void
+gst_field_analysis_init (GstFieldAnalysis * filter,
+ GstFieldAnalysisClass * gclass)
+{
+ filter->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
+ gst_pad_set_setcaps_function (filter->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_field_analysis_set_caps));
+ gst_pad_set_getcaps_function (filter->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps));
+ gst_pad_set_event_function (filter->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_field_analysis_sink_event));
+ gst_pad_set_chain_function (filter->sinkpad,
+ GST_DEBUG_FUNCPTR (gst_field_analysis_chain));
+
+ filter->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
+ gst_pad_set_getcaps_function (filter->srcpad,
+ GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps));
+
+ gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
+ gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
+
+ filter->frames = g_queue_new ();
+ gst_field_analysis_reset (filter);
+ filter->same_field = &same_parity_ssd;
+ filter->field_thresh = DEFAULT_FIELD_THRESH;
+ filter->same_frame = &opposite_parity_5_tap;
+ filter->frame_thresh = DEFAULT_FRAME_THRESH;
+ filter->noise_floor = DEFAULT_NOISE_FLOOR;
+ filter->block_score_for_row = &block_score_for_row_5_tap;
+ filter->spatial_thresh = DEFAULT_SPATIAL_THRESH;
+ filter->block_width = DEFAULT_BLOCK_WIDTH;
+ filter->block_height = DEFAULT_BLOCK_HEIGHT;
+ filter->block_thresh = DEFAULT_BLOCK_THRESH;
+ filter->ignored_lines = DEFAULT_IGNORED_LINES;
+}
+
+static void
+gst_field_analysis_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstFieldAnalysis *filter = GST_FIELDANALYSIS (object);
+
+ switch (prop_id) {
+ case PROP_FIELD_METRIC:
+ switch (g_value_get_enum (value)) {
+ case GST_FIELDANALYSIS_SAD:
+ filter->same_field = &same_parity_sad;
+ break;
+ case GST_FIELDANALYSIS_SSD:
+ filter->same_field = &same_parity_ssd;
+ break;
+ case GST_FIELDANALYSIS_3_TAP:
+ filter->same_field = &same_parity_3_tap;
+ break;
+ default:
+ break;
+ }
+ break;
+ case PROP_FRAME_METRIC:
+ switch (g_value_get_enum (value)) {
+ case GST_FIELDANALYSIS_5_TAP:
+ filter->same_frame = &opposite_parity_5_tap;
+ break;
+ case GST_FIELDANALYSIS_WINDOWED_COMB:
+ filter->same_frame = &opposite_parity_windowed_comb;
+ break;
+ default:
+ break;
+ }
+ break;
+ case PROP_NOISE_FLOOR:
+ filter->noise_floor = g_value_get_uint (value);
+ break;
+ case PROP_FIELD_THRESH:
+ filter->field_thresh = g_value_get_float (value);
+ break;
+ case PROP_FRAME_THRESH:
+ filter->frame_thresh = g_value_get_float (value);
+ break;
+ case PROP_COMB_METHOD:
+ switch (g_value_get_enum (value)) {
+ case METHOD_32DETECT:
+ filter->block_score_for_row = &block_score_for_row_32detect;
+ break;
+ case METHOD_IS_COMBED:
+ filter->block_score_for_row = &block_score_for_row_iscombed;
+ break;
+ case METHOD_5_TAP:
+ filter->block_score_for_row = &block_score_for_row_5_tap;
+ break;
+ default:
+ break;
+ }
+ break;
+ case PROP_SPATIAL_THRESH:
+ filter->spatial_thresh = g_value_get_int64 (value);
+ break;
+ case PROP_BLOCK_WIDTH:
+ filter->block_width = g_value_get_uint64 (value);
+ if (filter->width) {
+ if (filter->block_scores) {
+ gsize nbytes = (filter->width / filter->block_width) * sizeof (guint);
+ filter->block_scores = g_realloc (filter->block_scores, nbytes);
+ memset (filter->block_scores, 0, nbytes);
+ } else {
+ filter->block_scores =
+ g_malloc0 ((filter->width / filter->block_width) *
+ sizeof (guint));
+ }
+ }
+ break;
+ case PROP_BLOCK_HEIGHT:
+ filter->block_height = g_value_get_uint64 (value);
+ break;
+ case PROP_BLOCK_THRESH:
+ filter->block_thresh = g_value_get_uint64 (value);
+ break;
+ case PROP_IGNORED_LINES:
+ filter->ignored_lines = g_value_get_uint64 (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_field_analysis_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstFieldAnalysis *filter = GST_FIELDANALYSIS (object);
+
+ switch (prop_id) {
+ case PROP_FIELD_METRIC:
+ {
+ GstFieldAnalysisFieldMetric metric = DEFAULT_FIELD_METRIC;
+ if (filter->same_field == &same_parity_sad) {
+ metric = GST_FIELDANALYSIS_SAD;
+ } else if (filter->same_field == &same_parity_ssd) {
+ metric = GST_FIELDANALYSIS_SSD;
+ } else if (filter->same_field == &same_parity_3_tap) {
+ metric = GST_FIELDANALYSIS_3_TAP;
+ }
+ g_value_set_enum (value, metric);
+ break;
+ }
+ case PROP_FRAME_METRIC:
+ {
+ GstFieldAnalysisFrameMetric metric = DEFAULT_FRAME_METRIC;
+ if (filter->same_frame == &opposite_parity_5_tap) {
+ metric = GST_FIELDANALYSIS_5_TAP;
+ } else if (filter->same_frame == &opposite_parity_windowed_comb) {
+ metric = GST_FIELDANALYSIS_WINDOWED_COMB;
+ }
+ g_value_set_enum (value, metric);
+ break;
+ }
+ case PROP_NOISE_FLOOR:
+ g_value_set_uint (value, filter->noise_floor);
+ break;
+ case PROP_FIELD_THRESH:
+ g_value_set_float (value, filter->field_thresh);
+ break;
+ case PROP_FRAME_THRESH:
+ g_value_set_float (value, filter->frame_thresh);
+ break;
+ case PROP_COMB_METHOD:
+ {
+ FieldAnalysisCombMethod method = DEFAULT_COMB_METHOD;
+ if (filter->block_score_for_row == &block_score_for_row_32detect) {
+ method = METHOD_32DETECT;
+ } else if (filter->block_score_for_row == &block_score_for_row_iscombed) {
+ method = METHOD_IS_COMBED;
+ } else if (filter->block_score_for_row == &block_score_for_row_5_tap) {
+ method = METHOD_5_TAP;
+ }
+ g_value_set_enum (value, method);
+ break;
+ }
+ case PROP_SPATIAL_THRESH:
+ g_value_set_int64 (value, filter->spatial_thresh);
+ break;
+ case PROP_BLOCK_WIDTH:
+ g_value_set_uint64 (value, filter->block_width);
+ break;
+ case PROP_BLOCK_HEIGHT:
+ g_value_set_uint64 (value, filter->block_height);
+ break;
+ case PROP_BLOCK_THRESH:
+ g_value_set_uint64 (value, filter->block_thresh);
+ break;
+ case PROP_IGNORED_LINES:
+ g_value_set_uint64 (value, filter->ignored_lines);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_field_analysis_update_format (GstFieldAnalysis * filter, GstCaps * caps)
+{
+ GstStructure *struc;
+ guint32 fourcc;
+ GstVideoFormat vformat;
+ gint width, height, data_offset, sample_incr, line_stride;
+ GQueue *outbufs;
+
+ struc = gst_caps_get_structure (caps, 0);
+ gst_structure_get_fourcc (struc, "format", &fourcc);
+ vformat = gst_video_format_from_fourcc (fourcc);
+
+ gst_structure_get_int (struc, "width", &width);
+ gst_structure_get_int (struc, "height", &height);
+
+ data_offset =
+ gst_video_format_get_component_offset (vformat, 0, width, height);
+ sample_incr = gst_video_format_get_pixel_stride (vformat, 0);
+ line_stride = gst_video_format_get_row_stride (vformat, 0, width);
+
+ /* if format is unchanged in our eyes, don't update the context */
+ if ((filter->width == width) && (filter->height == height)
+ && (filter->data_offset == data_offset)
+ && (filter->sample_incr == sample_incr)
+ && (filter->line_stride == line_stride))
+ return;
+
+ /* format changed - process and push buffers before updating context */
+
+ GST_OBJECT_LOCK (filter);
+ filter->flushing = TRUE;
+ outbufs = gst_field_analysis_flush_queue (filter, filter->frames);
+ GST_OBJECT_UNLOCK (filter);
+
+ if (outbufs) {
+ while (g_queue_get_length (outbufs))
+ gst_pad_push (filter->srcpad, g_queue_pop_head (outbufs));
+ }
+
+ GST_OBJECT_LOCK (filter);
+ filter->flushing = FALSE;
+
+ filter->width = width;
+ filter->height = height;
+ filter->data_offset = data_offset;
+ filter->sample_incr = sample_incr;
+ filter->line_stride = line_stride;
+
+ /* update allocations for metric scores */
+ if (filter->comb_mask) {
+ filter->comb_mask = g_realloc (filter->comb_mask, width);
+ } else {
+ filter->comb_mask = g_malloc (width);
+ }
+ if (filter->block_scores) {
+ gsize nbytes = (width / filter->block_width) * sizeof (guint);
+ filter->block_scores = g_realloc (filter->block_scores, nbytes);
+ memset (filter->block_scores, 0, nbytes);
+ } else {
+ filter->block_scores =
+ g_malloc0 ((width / filter->block_width) * sizeof (guint));
+ }
+
+ GST_OBJECT_UNLOCK (filter);
+ return;
+}
+
+static gboolean
+gst_field_analysis_set_caps (GstPad * pad, GstCaps * caps)
+{
+ gboolean ret = TRUE;
+ GstFieldAnalysis *filter = GST_FIELDANALYSIS (gst_pad_get_parent (pad));
+
+ gst_field_analysis_update_format (filter, caps);
+
+ ret = gst_pad_set_caps (filter->srcpad, caps);
+
+ gst_object_unref (filter);
+
+ return ret;
+}
+
+#define FIELD_ANALYSIS_TOP_BOTTOM (1 << 0)
+#define FIELD_ANALYSIS_BOTTOM_TOP (1 << 1)
+#define FIELD_ANALYSIS_TOP_MATCH (1 << 2)
+#define FIELD_ANALYSIS_BOTTOM_MATCH (1 << 3)
+
+/* decorate removes a buffer from the internal queue, on which we have a ref,
+ * then makes its metadata writable (could be the same buffer, could be a new
+ * buffer, but either way we have a ref on it), decorates this buffer and
+ * returns it */
+static GstBuffer *
+gst_field_analysis_decorate (GstFieldAnalysis * filter, gboolean tff,
+ gboolean onefield, FieldAnalysisConclusion conclusion, gboolean gap)
+{
+ GstBuffer *buf = NULL;
+ GstCaps *caps;
+
+ caps = gst_caps_copy (GST_PAD_CAPS (filter->srcpad));
+
+ /* deal with incoming buffer */
+ if (conclusion > FIELD_ANALYSIS_PROGRESSIVE || filter->is_telecine == TRUE) {
+ gst_caps_set_simple (caps, "interlaced", G_TYPE_BOOLEAN, TRUE, NULL);
+ filter->is_telecine = conclusion != FIELD_ANALYSIS_INTERLACED;
+ if (conclusion >= FIELD_ANALYSIS_TELECINE_PROGRESSIVE
+ || filter->is_telecine == TRUE) {
+ gst_caps_set_simple (caps, "interlacing-method", G_TYPE_STRING,
+ "telecine", NULL);
+ } else {
+ gst_caps_set_simple (caps, "interlacing-method", G_TYPE_STRING, "unknown",
+ NULL);
+ }
+ } else {
+ gst_structure_remove_field (gst_caps_get_structure (caps, 0),
+ "interlacing-method");
+ gst_caps_set_simple (caps, "interlaced", G_TYPE_BOOLEAN, FALSE, NULL);
+ }
+
+ /* get buffer from queue
+ * this takes our ref on the buf that was in the queue and gives us a buf
+ * on which we have a refi (could be the same buffer, but need not be) */
+ buf = gst_buffer_make_metadata_writable (g_queue_pop_head (filter->frames));
+
+ /* set buffer flags */
+ if (!tff) {
+ GST_BUFFER_FLAG_UNSET (buf, GST_VIDEO_BUFFER_TFF);
+ } else if (tff == 1 || (tff == -1
+ && GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_BUFFER_TFF))) {
+ GST_BUFFER_FLAG_SET (buf, GST_VIDEO_BUFFER_TFF);
+ }
+
+ if (onefield) {
+ GST_BUFFER_FLAG_SET (buf, GST_VIDEO_BUFFER_ONEFIELD);
+ } else {
+ GST_BUFFER_FLAG_UNSET (buf, GST_VIDEO_BUFFER_ONEFIELD);
+ }
+
+ GST_BUFFER_FLAG_UNSET (buf, GST_VIDEO_BUFFER_RFF);
+
+ if (gap)
+ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_GAP);
+
+ if (conclusion == FIELD_ANALYSIS_TELECINE_PROGRESSIVE || (filter->is_telecine
+ && conclusion == FIELD_ANALYSIS_PROGRESSIVE))
+ GST_BUFFER_FLAG_SET (buf, GST_VIDEO_BUFFER_PROGRESSIVE);
+
+ /* set the caps on the src pad and buffer before pushing */
+ if (gst_caps_is_equal (caps, GST_PAD_CAPS (filter->srcpad))) {
+ gst_buffer_set_caps (buf, GST_PAD_CAPS (filter->srcpad));
+ } else {
+ gboolean ret = TRUE;
+
+ GST_OBJECT_UNLOCK (filter);
+ ret = gst_pad_set_caps (filter->srcpad, caps);
+ GST_OBJECT_LOCK (filter);
+
+ if (!ret) {
+ GST_ERROR_OBJECT (filter, "Could not set pad caps");
+ gst_buffer_unref (buf);
+ return NULL;
+ }
+ gst_buffer_set_caps (buf, caps);
+ }
+ /* drop our ref to the caps as the buffer and pad have their own */
+ gst_caps_unref (caps);
+
+ GST_DEBUG_OBJECT (filter,
+ "Pushing buffer with flags: %p (%p), p %d, tff %d, 1f %d, gap %d; conc %d",
+ GST_BUFFER_DATA (buf), buf,
+ GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_BUFFER_PROGRESSIVE),
+ GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_BUFFER_TFF),
+ GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_BUFFER_ONEFIELD),
+ GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP), conclusion);
+
+ return buf;
+}
+
+/* _flush_one does not touch the buffer ref counts directly but _decorate ()
+ * has some influence on ref counts - see its annotation for details */
+static GstBuffer *
+gst_field_analysis_flush_one (GstFieldAnalysis * filter, GQueue * outbufs)
+{
+ GstBuffer *buf = NULL;
+ guint n_queued = g_queue_get_length (filter->frames);
+ guint idx = n_queued - 1;
+
+ if (!n_queued || n_queued > 2)
+ return buf;
+
+ GST_DEBUG_OBJECT (filter, "Flushing last buffer (queue length %d)", n_queued);
+ if (filter->results[idx].holding == 1 + TOP_FIELD
+ || filter->results[idx].holding == 1 + BOTTOM_FIELD) {
+ /* should be only one field needed */
+ buf =
+ gst_field_analysis_decorate (filter,
+ filter->results[idx].holding == 1 + TOP_FIELD, TRUE,
+ filter->results[idx].conclusion, FALSE);
+ } else {
+ /* possibility that both fields are needed */
+ buf =
+ gst_field_analysis_decorate (filter, -1, FALSE,
+ filter->results[idx].conclusion, !filter->results[idx].holding);
+ }
+ if (buf) {
+ if (outbufs)
+ g_queue_push_tail (outbufs, buf);
+ } else {
+ GST_DEBUG_OBJECT (filter, "Error occurred during decoration");
+ }
+ return buf;
+}
+
+/* _flush_queue () has no direct influence on refcounts and nor does _flush_one,
+ * but _decorate () does and so this function does indirectly */
+static GQueue *
+gst_field_analysis_flush_queue (GstFieldAnalysis * filter, GQueue * queue)
+{
+ GQueue *outbufs;
+ guint length = 0;
+
+ if (queue)
+ length = g_queue_get_length (queue);
+
+ if (length < 2)
+ return NULL;
+
+ outbufs = g_queue_new ();
+
+ while (length) {
+ gst_field_analysis_flush_one (filter, outbufs);
+ length--;
+ }
+
+ return outbufs;
+}
+
+static gboolean
+gst_field_analysis_sink_event (GstPad * pad, GstEvent * event)
+{
+ GstFieldAnalysis *filter = GST_FIELDANALYSIS (gst_pad_get_parent (pad));
+
+ GST_LOG_OBJECT (pad, "received %s event: %" GST_PTR_FORMAT,
+ GST_EVENT_TYPE_NAME (event), event);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_NEWSEGMENT:
+ case GST_EVENT_EOS:
+ {
+ /* for both NEWSEGMENT and EOS it is safest to process and push queued
+ * buffers */
+ GQueue *outbufs;
+
+ GST_OBJECT_LOCK (filter);
+ filter->flushing = TRUE;
+ outbufs = gst_field_analysis_flush_queue (filter, filter->frames);
+ GST_OBJECT_UNLOCK (filter);
+
+ if (outbufs) {
+ while (g_queue_get_length (outbufs))
+ gst_pad_push (filter->srcpad, g_queue_pop_head (outbufs));
+ }
+
+ GST_OBJECT_LOCK (filter);
+ filter->flushing = FALSE;
+ GST_OBJECT_UNLOCK (filter);
+ break;
+ }
+ case GST_EVENT_FLUSH_STOP:
+ /* if we have any buffers left in the queue, unref them until the queue
+ * is empty */
+ GST_OBJECT_LOCK (filter);
+ gst_field_analysis_reset (filter);
+ GST_OBJECT_UNLOCK (filter);
+ break;
+ default:
+ break;
+ }
+
+ /* NOTE: we always forward the event currently, change this as necessary */
+ return gst_pad_push_event (filter->srcpad, event);
+}
+
+
+static gfloat
+same_parity_sad (GstFieldAnalysis * filter, FieldAnalysisFields * fields)
+{
+ gint j;
+ gfloat sum;
+ guint8 *f1j, *f2j;
+
+ const gint y_offset = filter->data_offset;
+ const gint stride = filter->line_stride;
+ const gint stridex2 = stride << 1;
+ const guint32 noise_floor = filter->noise_floor;
+
+ f1j = GST_BUFFER_DATA (fields[0].buf) + y_offset + fields[0].parity * stride;
+ f2j = GST_BUFFER_DATA (fields[1].buf) + y_offset + fields[1].parity * stride;
+
+ sum = 0.0f;
+ for (j = 0; j < (filter->height >> 1); j++) {
+ guint32 tempsum = 0;
+ orc_same_parity_sad_planar_yuv (&tempsum, f1j, f2j, noise_floor,
+ filter->width);
+ sum += tempsum;
+ f1j += stridex2;
+ f2j += stridex2;
+ }
+
+ return sum / (0.5f * filter->width * filter->height);
+}
+
+static gfloat
+same_parity_ssd (GstFieldAnalysis * filter, FieldAnalysisFields * fields)
+{
+ gint j;
+ gfloat sum;
+ guint8 *f1j, *f2j;
+
+ const gint y_offset = filter->data_offset;
+ const gint stride = filter->line_stride;
+ const gint stridex2 = stride << 1;
+ /* noise floor needs to be squared for SSD */
+ const guint32 noise_floor = filter->noise_floor * filter->noise_floor;
+
+ f1j = GST_BUFFER_DATA (fields[0].buf) + y_offset + fields[0].parity * stride;
+ f2j = GST_BUFFER_DATA (fields[1].buf) + y_offset + fields[1].parity * stride;
+
+ sum = 0.0f;
+ for (j = 0; j < (filter->height >> 1); j++) {
+ guint32 tempsum = 0;
+ orc_same_parity_ssd_planar_yuv (&tempsum, f1j, f2j, noise_floor,
+ filter->width);
+ sum += tempsum;
+ f1j += stridex2;
+ f2j += stridex2;
+ }
+
+ return sum / (0.5f * filter->width * filter->height); /* field is half height */
+}
+
+/* horizontal [1,4,1] diff between fields - is this a good idea or should the
+ * current sample be emphasised more or less? */
+static gfloat
+same_parity_3_tap (GstFieldAnalysis * filter, FieldAnalysisFields * fields)
+{
+ gint i, j;
+ gfloat sum;
+ guint8 *f1j, *f2j;
+
+ const gint y_offset = filter->data_offset;
+ const gint stride = filter->line_stride;
+ const gint stridex2 = stride << 1;
+ const gint incr = filter->sample_incr;
+ /* noise floor needs to be squared for [1,4,1] */
+ const guint32 noise_floor = filter->noise_floor * 6;
+
+ f1j = GST_BUFFER_DATA (fields[0].buf) + y_offset + fields[0].parity * stride;
+ f2j = GST_BUFFER_DATA (fields[1].buf) + y_offset + fields[1].parity * stride;
+
+ sum = 0.0f;
+ for (j = 0; j < (filter->height >> 1); j++) {
+ guint32 tempsum = 0;
+ guint32 diff;
+
+ /* unroll first as it is a special case */
+ diff = abs (((f1j[0] << 2) + (f1j[incr] << 1))
+ - ((f2j[0] << 2) + (f2j[incr] << 1)));
+ if (diff > noise_floor)
+ sum += diff;
+
+ orc_same_parity_3_tap_planar_yuv (&tempsum, f1j, &f1j[incr],
+ &f1j[incr << 1], f2j, &f2j[incr], &f2j[incr << 1], noise_floor,
+ filter->width - 1);
+ sum += tempsum;
+
+ /* unroll last as it is a special case */
+ i = filter->width - 1;
+ diff = abs (((f1j[i - incr] << 1) + (f1j[i] << 2))
+ - ((f2j[i - incr] << 1) + (f2j[i] << 2)));
+ if (diff > noise_floor)
+ sum += diff;
+
+ f1j += stridex2;
+ f2j += stridex2;
+ }
+
+ return sum / ((6.0f / 2.0f) * filter->width * filter->height); /* 1 + 4 + 1 = 6; field is half height */
+}
+
+/* vertical [1,-3,4,-3,1] - same as is used in FieldDiff from TIVTC,
+ * tritical's AVISynth IVTC filter */
+/* 0th field's parity defines operation */
+static gfloat
+opposite_parity_5_tap (GstFieldAnalysis * filter, FieldAnalysisFields * fields)
+{
+ gint j;
+ gfloat sum;
+ guint8 *fjm2, *fjm1, *fj, *fjp1, *fjp2;
+ guint32 tempsum;
+
+ const gint y_offset = filter->data_offset;
+ const gint stride = filter->line_stride;
+ const gint stridex2 = stride << 1;
+ /* noise floor needs to be *6 for [1,-3,4,-3,1] */
+ const guint32 noise_floor = filter->noise_floor * 6;
+
+ sum = 0.0f;
+
+ /* fj is line j of the combined frame made from the top field even lines of
+ * field 0 and the bottom field odd lines from field 1
+ * fjp1 is one line down from fj
+ * fjm2 is two lines up from fj
+ * fj with j == 0 is the 0th line of the top field
+ * fj with j == 1 is the 0th line of the bottom field or the 1st field of
+ * the frame*/
+
+ /* unroll first line as it is a special case */
+ if (fields[0].parity == TOP_FIELD) {
+ fj = GST_BUFFER_DATA (fields[0].buf) + y_offset;
+ fjp1 = GST_BUFFER_DATA (fields[1].buf) + y_offset + stride;
+ } else {
+ fj = GST_BUFFER_DATA (fields[1].buf) + y_offset;
+ fjp1 = GST_BUFFER_DATA (fields[0].buf) + y_offset + stride;
+ }
+ fjp2 = fj + stridex2;
+
+ tempsum = 0;
+ orc_opposite_parity_5_tap_planar_yuv (&tempsum, fjp2, fjp1, fj, fjp1, fjp2,
+ noise_floor, filter->width);
+ sum += tempsum;
+
+ for (j = 1; j < (filter->height >> 1) - 1; j++) {
+ /* shift everything down a line in the field of interest (means += stridex2) */
+ fjm2 = fj;
+ fjm1 = fjp1;
+ fj = fjp2;
+ fjp1 += stridex2;
+ fjp2 += stridex2;
+
+ tempsum = 0;
+ orc_opposite_parity_5_tap_planar_yuv (&tempsum, fjm2, fjm1, fj, fjp1, fjp2,
+ noise_floor, filter->width);
+ sum += tempsum;
+ }
+
+ /* unroll the last line as it is a special case */
+ /* shift everything down a line in the field of interest (means += stridex2) */
+ fjm2 = fj;
+ fjm1 = fjp1;
+ fj = fjp2;
+
+ tempsum = 0;
+ orc_opposite_parity_5_tap_planar_yuv (&tempsum, fjm2, fjm1, fj, fjm1, fjm2,
+ noise_floor, filter->width);
+ sum += tempsum;
+
+ return sum / ((6.0f / 2.0f) * filter->width * filter->height); /* 1 + 4 + 1 == 3 + 3 == 6; field is half height */
+}
+
+/* this metric was sourced from HandBrake but originally from transcode
+ * the return value is the highest block score for the row of blocks */
+static inline guint64
+block_score_for_row_32detect (GstFieldAnalysis * filter, guint8 * base_fj,
+ guint8 * base_fjp1)
+{
+ guint64 i, j;
+ guint8 *comb_mask = filter->comb_mask;
+ guint *block_scores = filter->block_scores;
+ guint64 block_score;
+ guint8 *fjm2, *fjm1, *fj, *fjp1;
+ const gint incr = filter->sample_incr;
+ const gint stridex2 = filter->line_stride << 1;
+ const guint64 block_width = filter->block_width;
+ const guint64 block_height = filter->block_height;
+ const gint64 spatial_thresh = filter->spatial_thresh;
+ const gint width = filter->width - (filter->width % block_width);
+
+ fjm2 = base_fj - stridex2;
+ fjm1 = base_fjp1 - stridex2;
+ fj = base_fj;
+ fjp1 = base_fjp1;
+
+ for (j = 0; j < block_height; j++) {
+ /* we have to work one result ahead of ourselves which results in some small
+ * peculiarities below */
+ gint diff1, diff2;
+
+ diff1 = fj[0] - fjm1[0];
+ diff2 = fj[0] - fjp1[0];
+ /* change in the same direction */
+ if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
+ || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
+ comb_mask[0] = abs (fj[0] - fjm2[0]) < 10 && abs (fj[0] - fjm1[0]) > 15;
+ } else {
+ comb_mask[0] = FALSE;
+ }
+
+ for (i = 1; i < width; i++) {
+ const guint64 idx = i * incr;
+ const guint64 res_idx = (i - 1) / block_width;
+
+ diff1 = fj[idx] - fjm1[idx];
+ diff2 = fj[idx] - fjp1[idx];
+ if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
+ || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
+ comb_mask[i] = abs (fj[idx] - fjm2[idx]) < 10
+ && abs (fj[idx] - fjm1[idx]) > 15;
+ } else {
+ comb_mask[i] = FALSE;
+ }
+
+ if (i == 1 && comb_mask[i - 1] && comb_mask[i]) {
+ /* left edge */
+ block_scores[res_idx]++;
+ } else if (i == width - 1) {
+ /* right edge */
+ if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i])
+ block_scores[res_idx]++;
+ if (comb_mask[i - 1] && comb_mask[i])
+ block_scores[i / block_width]++;
+ } else if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i]) {
+ block_scores[res_idx]++;
+ }
+ }
+ /* advance down a line */
+ fjm2 = fjm1;
+ fjm1 = fj;
+ fj = fjp1;
+ fjp1 = fjm1 + stridex2;
+ }
+
+ block_score = 0;
+ for (i = 0; i < width / block_width; i++) {
+ if (block_scores[i] > block_score)
+ block_score = block_scores[i];
+ }
+
+ g_free (block_scores);
+ g_free (comb_mask);
+ return block_score;
+}
+
+/* this metric was sourced from HandBrake but originally from
+ * tritical's isCombedT Avisynth function
+ * the return value is the highest block score for the row of blocks */
+static inline guint64
+block_score_for_row_iscombed (GstFieldAnalysis * filter, guint8 * base_fj,
+ guint8 * base_fjp1)
+{
+ guint64 i, j;
+ guint8 *comb_mask = filter->comb_mask;
+ guint *block_scores = filter->block_scores;
+ guint64 block_score;
+ guint8 *fjm1, *fj, *fjp1;
+ const gint incr = filter->sample_incr;
+ const gint stridex2 = filter->line_stride << 1;
+ const guint64 block_width = filter->block_width;
+ const guint64 block_height = filter->block_height;
+ const gint64 spatial_thresh = filter->spatial_thresh;
+ const gint64 spatial_thresh_squared = spatial_thresh * spatial_thresh;
+ const gint width = filter->width - (filter->width % block_width);
+
+ fjm1 = base_fjp1 - stridex2;
+ fj = base_fj;
+ fjp1 = base_fjp1;
+
+ for (j = 0; j < block_height; j++) {
+ /* we have to work one result ahead of ourselves which results in some small
+ * peculiarities below */
+ gint diff1, diff2;
+
+ diff1 = fj[0] - fjm1[0];
+ diff2 = fj[0] - fjp1[0];
+ /* change in the same direction */
+ if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
+ || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
+ comb_mask[0] =
+ (fjm1[0] - fj[0]) * (fjp1[0] - fj[0]) > spatial_thresh_squared;
+ } else {
+ comb_mask[0] = FALSE;
+ }
+
+ for (i = 1; i < width; i++) {
+ const guint64 idx = i * incr;
+ const guint64 res_idx = (i - 1) / block_width;
+
+ diff1 = fj[idx] - fjm1[idx];
+ diff2 = fj[idx] - fjp1[idx];
+ if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
+ || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
+ comb_mask[i] =
+ (fjm1[idx] - fj[idx]) * (fjp1[idx] - fj[idx]) >
+ spatial_thresh_squared;
+ } else {
+ comb_mask[i] = FALSE;
+ }
+
+ if (i == 1 && comb_mask[i - 1] && comb_mask[i]) {
+ /* left edge */
+ block_scores[res_idx]++;
+ } else if (i == width - 1) {
+ /* right edge */
+ if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i])
+ block_scores[res_idx]++;
+ if (comb_mask[i - 1] && comb_mask[i])
+ block_scores[i / block_width]++;
+ } else if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i]) {
+ block_scores[res_idx]++;
+ }
+ }
+ /* advance down a line */
+ fjm1 = fj;
+ fj = fjp1;
+ fjp1 = fjm1 + stridex2;
+ }
+
+ block_score = 0;
+ for (i = 0; i < width / block_width; i++) {
+ if (block_scores[i] > block_score)
+ block_score = block_scores[i];
+ }
+
+ g_free (block_scores);
+ g_free (comb_mask);
+ return block_score;
+}
+
+/* this metric was sourced from HandBrake but originally from
+ * tritical's isCombedT Avisynth function
+ * the return value is the highest block score for the row of blocks */
+static inline guint64
+block_score_for_row_5_tap (GstFieldAnalysis * filter, guint8 * base_fj,
+ guint8 * base_fjp1)
+{
+ guint64 i, j;
+ guint8 *comb_mask = filter->comb_mask;
+ guint *block_scores = filter->block_scores;
+ guint64 block_score;
+ guint8 *fjm2, *fjm1, *fj, *fjp1, *fjp2;
+ const gint incr = filter->sample_incr;
+ const gint stridex2 = filter->line_stride << 1;
+ const guint64 block_width = filter->block_width;
+ const guint64 block_height = filter->block_height;
+ const gint64 spatial_thresh = filter->spatial_thresh;
+ const gint64 spatial_threshx6 = 6 * spatial_thresh;
+ const gint width = filter->width - (filter->width % block_width);
+
+ fjm2 = base_fj - stridex2;
+ fjm1 = base_fjp1 - stridex2;
+ fj = base_fj;
+ fjp1 = base_fjp1;
+ fjp2 = fj + stridex2;
+
+ for (j = 0; j < block_height; j++) {
+ /* we have to work one result ahead of ourselves which results in some small
+ * peculiarities below */
+ gint diff1, diff2;
+
+ diff1 = fj[0] - fjm1[0];
+ diff2 = fj[0] - fjp1[0];
+ /* change in the same direction */
+ if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
+ || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
+ comb_mask[0] =
+ abs (fjm2[0] + (fj[0] << 2) + fjp2[0] - 3 * (fjm1[0] + fjp1[0])) >
+ spatial_threshx6;
+
+ /* motion detection that needs previous and next frames
+ this isn't really necessary, but acts as an optimisation if the
+ additional delay isn't a problem
+ if (motion_detection) {
+ if (abs(fpj[idx] - fj[idx] ) > motion_thresh &&
+ abs( fjm1[idx] - fnjm1[idx]) > motion_thresh &&
+ abs( fjp1[idx] - fnjp1[idx]) > motion_thresh)
+ motion++;
+ if (abs( fj[idx] - fnj[idx]) > motion_thresh &&
+ abs(fpjm1[idx] - fjm1[idx] ) > motion_thresh &&
+ abs(fpjp1[idx] - fjp1[idx] ) > motion_thresh)
+ motion++;
+ } else {
+ motion = 1;
+ }
+ */
+ } else {
+ comb_mask[0] = FALSE;
+ }
+
+ for (i = 1; i < width; i++) {
+ const guint64 idx = i * incr;
+ const guint64 res_idx = (i - 1) / block_width;
+
+ diff1 = fj[idx] - fjm1[idx];
+ diff2 = fj[idx] - fjp1[idx];
+ if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
+ || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
+ comb_mask[i] =
+ abs (fjm2[idx] + (fj[idx] << 2) + fjp2[idx] - 3 * (fjm1[idx] +
+ fjp1[idx])) > spatial_threshx6;
+ } else {
+ comb_mask[i] = FALSE;
+ }
+
+ if (i == 1 && comb_mask[i - 1] && comb_mask[i]) {
+ /* left edge */
+ block_scores[res_idx]++;
+ } else if (i == width - 1) {
+ /* right edge */
+ if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i])
+ block_scores[res_idx]++;
+ if (comb_mask[i - 1] && comb_mask[i])
+ block_scores[i / block_width]++;
+ } else if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i]) {
+ block_scores[res_idx]++;
+ }
+ }
+ /* advance down a line */
+ fjm2 = fjm1;
+ fjm1 = fj;
+ fj = fjp1;
+ fjp1 = fjp2;
+ fjp2 = fj + stridex2;
+ }
+
+ block_score = 0;
+ for (i = 0; i < width / block_width; i++) {
+ if (block_scores[i] > block_score)
+ block_score = block_scores[i];
+ }
+
+ g_free (block_scores);
+ g_free (comb_mask);
+ return block_score;
+}
+
+/* a pass is made over the field using one of three comb-detection metrics
+ and the results are then analysed block-wise. if the samples to the left
+ and right are combed, they contribute to the block score. if the block
+ score is above the given threshold, the frame is combed. if the block
+ score is between half the threshold and the threshold, the block is
+ slightly combed. if when analysis is complete, slight combing is detected
+ that is returned. if any results are observed that are above the threshold,
+ the function returns immediately */
+/* 0th field's parity defines operation */
+static gfloat
+opposite_parity_windowed_comb (GstFieldAnalysis * filter,
+ FieldAnalysisFields * fields)
+{
+ gint j;
+ gboolean slightly_combed;
+
+ const gint y_offset = filter->data_offset;
+ const gint stride = filter->line_stride;
+ const guint64 block_thresh = filter->block_thresh;
+ const guint64 block_height = filter->block_height;
+ guint8 *base_fj, *base_fjp1;
+
+ if (fields[0].parity == TOP_FIELD) {
+ base_fj = GST_BUFFER_DATA (fields[0].buf) + y_offset;
+ base_fjp1 = GST_BUFFER_DATA (fields[1].buf) + y_offset + stride;
+ } else {
+ base_fj = GST_BUFFER_DATA (fields[1].buf) + y_offset;
+ base_fjp1 = GST_BUFFER_DATA (fields[0].buf) + y_offset + stride;
+ }
+
+ /* we operate on a row of blocks of height block_height through each iteration */
+ slightly_combed = FALSE;
+ for (j = 0; j <= filter->height - filter->ignored_lines - block_height;
+ j += block_height) {
+ guint64 line_offset = (filter->ignored_lines + j) * stride;
+ guint block_score =
+ filter->block_score_for_row (filter, base_fj + line_offset,
+ base_fjp1 + line_offset);
+
+ if (block_score > (block_thresh >> 1)
+ && block_score <= block_thresh) {
+ /* blend if nothing more combed comes along */
+ slightly_combed = TRUE;
+ } else if (block_score > block_thresh) {
+ GstCaps *caps = GST_BUFFER_CAPS (fields[0].buf);
+ GstStructure *struc = gst_caps_get_structure (caps, 0);
+ gboolean interlaced;
+ if (gst_structure_get_boolean (struc, "interlaced", &interlaced)
+ && interlaced == TRUE) {
+ return 1.0f; /* blend */
+ } else {
+ return 2.0f; /* deinterlace */
+ }
+ }
+ }
+
+ return (gfloat) slightly_combed; /* TRUE means blend, else don't */
+}
+
+/* this is where the magic happens
+ *
+ * the buffer incoming to the chain function (buf_to_queue) is added to the
+ * internal queue and then should no longer be used until it is popped from the
+ * queue.
+ *
+ * analysis is performed on the incoming buffer (peeked from the queue) and the
+ * previous buffer using two classes of metrics making up five individual
+ * scores.
+ *
+ * there are two same-parity comparisons: top of current with top of previous
+ * and bottom of current with bottom of previous
+ *
+ * there are three opposing parity comparisons: top of current with bottom of
+ * _current_, top of current with bottom of previous and bottom of current with
+ * top of previous.
+ *
+ * from the results of these comparisons we can use some rather complex logic to
+ * identify the state of the previous buffer, decorate and return it and
+ * identify some preliminary state of the current buffer.
+ *
+ * the returned buffer has a ref on it (it has come from _make_metadata_writable
+ * that was called on an incoming buffer that was queued and then popped) */
+static GstBuffer *
+gst_field_analysis_process_buffer (GstFieldAnalysis * filter,
+ GstBuffer ** buf_to_queue)
+{
+ GQueue *queue;
+ guint n_queued;
+ /* res0/1 correspond to f0/1 */
+ FieldAnalysis *res0, *res1;
+ FieldAnalysisFields fields[2];
+ GstBuffer *outbuf = NULL;
+
+ queue = filter->frames;
+
+ /* move previous result to res1 */
+ filter->results[1] = filter->results[0];
+
+ res0 = &filter->results[0]; /* results for current frame */
+ res1 = &filter->results[1]; /* results for previous frame */
+
+ /* we have a ref on buf_to_queue when it is added to the queue */
+ g_queue_push_tail (queue, (gpointer) * buf_to_queue);
+ /* WARNING: buf_to_queue must not be used again!!! */
+ *buf_to_queue = NULL;
+
+ n_queued = g_queue_get_length (queue);
+
+ /* we do it like this because the first frame has no predecessor so this is
+ * the only result we can get for it */
+ if (n_queued >= 1) {
+ /* compare the fields within the buffer, if the buffer exhibits combing it
+ * could be interlaced or a mixed telecine frame */
+ fields[0].buf = fields[1].buf = g_queue_peek_tail (queue);
+ fields[0].parity = TOP_FIELD;
+ fields[1].parity = BOTTOM_FIELD;
+ res0->f = filter->same_frame (filter, fields);
+ res0->t = res0->b = res0->t_b = res0->b_t = G_MAXINT64;
+ if (n_queued == 1)
+ GST_DEBUG_OBJECT (filter, "Scores: f %f, t , b , t_b , b_t ", res0->f);
+ if (res0->f <= filter->frame_thresh) {
+ res0->conclusion = FIELD_ANALYSIS_PROGRESSIVE;
+ } else {
+ res0->conclusion = FIELD_ANALYSIS_INTERLACED;
+ }
+ res0->holding = -1; /* needed fields unknown */
+ res0->gap = FALSE;
+ }
+
+ if (n_queued >= 2) {
+ guint telecine_matches;
+ gboolean first_buffer = filter->first_buffer;
+
+ filter->first_buffer = FALSE;
+
+ fields[1].buf = g_queue_peek_nth (queue, n_queued - 2);
+
+ /* compare the top and bottom fields to the previous frame */
+ fields[0].parity = TOP_FIELD;
+ fields[1].parity = TOP_FIELD;
+ res0->t = filter->same_field (filter, fields);
+ fields[0].parity = BOTTOM_FIELD;
+ fields[1].parity = BOTTOM_FIELD;
+ res0->b = filter->same_field (filter, fields);
+
+ /* compare the top field from this frame to the bottom of the previous for
+ * for combing (and vice versa) */
+ fields[0].parity = TOP_FIELD;
+ fields[1].parity = BOTTOM_FIELD;
+ res0->t_b = filter->same_frame (filter, fields);
+ fields[0].parity = BOTTOM_FIELD;
+ fields[1].parity = TOP_FIELD;
+ res0->b_t = filter->same_frame (filter, fields);
+
+ GST_DEBUG_OBJECT (filter,
+ "Scores: f %f, t %f, b %f, t_b %f, b_t %f", res0->f,
+ res0->t, res0->b, res0->t_b, res0->b_t);
+
+ /* analysis */
+ telecine_matches = 0;
+ if (res0->t_b <= filter->frame_thresh)
+ telecine_matches |= FIELD_ANALYSIS_TOP_BOTTOM;
+ if (res0->b_t <= filter->frame_thresh)
+ telecine_matches |= FIELD_ANALYSIS_BOTTOM_TOP;
+ /* normally if there is a top or bottom field match, it is significantly
+ * smaller than the other match - try 10% */
+ if (res0->t <= filter->field_thresh || res0->t * (100 / 10) < res0->b)
+ telecine_matches |= FIELD_ANALYSIS_TOP_MATCH;
+ if (res0->b <= filter->field_thresh || res0->b * (100 / 10) < res0->t)
+ telecine_matches |= FIELD_ANALYSIS_BOTTOM_MATCH;
+
+ if (telecine_matches & (FIELD_ANALYSIS_TOP_MATCH |
+ FIELD_ANALYSIS_BOTTOM_MATCH)) {
+ /* we have a repeated field => some kind of telecine */
+ if (res1->f <= filter->frame_thresh) {
+ /* prev P */
+ if ((telecine_matches & FIELD_ANALYSIS_TOP_MATCH)
+ && (telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH)) {
+ /* prev P, cur repeated => cur P */
+ res0->conclusion = FIELD_ANALYSIS_TELECINE_PROGRESSIVE;
+ res0->holding = 1 + BOTH_FIELDS;
+ /* push prev P, GAP */
+ res1->gap = TRUE;
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
+ res1->gap);
+ } else {
+ /* prev P, cur t xor b matches => cur TCM */
+ res0->conclusion = FIELD_ANALYSIS_TELECINE_MIXED;
+ /* hold non-repeated: if bottom match, hold top = 1 + 0 */
+ res0->holding = 1 + !(telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH);
+ /* push prev P */
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
+ res1->gap);
+ }
+ } else {
+ /* prev !P */
+ gboolean b, t;
+
+ if (res0->f <= filter->frame_thresh) {
+ /* cur P */
+ res0->conclusion = FIELD_ANALYSIS_TELECINE_PROGRESSIVE;
+ res0->holding = 1 + BOTH_FIELDS;
+ } else {
+ /* cur !P */
+ res0->conclusion = FIELD_ANALYSIS_TELECINE_MIXED;
+ if (telecine_matches & FIELD_ANALYSIS_TOP_MATCH
+ && telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH) {
+ /* cur t && b */
+ res0->holding = 0;
+ } else {
+ /* cur t xor b; hold non-repeated */
+ res0->holding =
+ 1 + !(telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH);
+ }
+ }
+
+ if (res1->holding == -1) {
+ b = t = TRUE;
+ } else {
+ b = res1->holding == 1 + BOTTOM_FIELD;
+ t = res1->holding == 1 + TOP_FIELD;
+ }
+
+ if ((t && telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH) || (b
+ && telecine_matches & FIELD_ANALYSIS_TOP_MATCH)) {
+ if (t && telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH) {
+ res1->holding = 1 + TOP_FIELD;
+ } else if (b && telecine_matches & FIELD_ANALYSIS_TOP_MATCH) {
+ res1->holding = 1 + BOTTOM_FIELD;
+ }
+ /* push 1F held field */
+ outbuf =
+ gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
+ res1->conclusion, res1->gap);
+ } else if (res0->f > filter->frame_thresh && ((t
+ && telecine_matches & FIELD_ANALYSIS_BOTTOM_TOP) || (b
+ && telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM))) {
+ if (t && telecine_matches & FIELD_ANALYSIS_BOTTOM_TOP) {
+ res1->holding = 1 + TOP_FIELD;
+ } else if (b && telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM) {
+ res1->holding = 1 + BOTTOM_FIELD;
+ }
+ res0->conclusion = FIELD_ANALYSIS_TELECINE_MIXED;
+ /* hold the opposite field to the one held in the last frame */
+ res0->holding = 1 + (res1->holding == 1 + TOP_FIELD);
+ /* push 1F held field */
+ outbuf =
+ gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
+ res1->conclusion, res1->gap);
+ } else if (first_buffer && (telecine_matches & FIELD_ANALYSIS_BOTTOM_TOP
+ || telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM)) {
+ /* non-matched field is an orphan in the first buffer - push orphan as 1F */
+ res1->conclusion = FIELD_ANALYSIS_TELECINE_MIXED;
+ /* if prev b matched, prev t is orphan */
+ res1->holding = 1 + !(telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM);
+ /* push 1F held field */
+ outbuf =
+ gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
+ res1->conclusion, res1->gap);
+ } else if (res1->holding == 1 + BOTH_FIELDS || res1->holding == -1) {
+ /* holding both fields, push prev as is */
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
+ res1->gap);
+ } else {
+ /* push prev as is with GAP */
+ res1->gap = TRUE;
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
+ res1->gap);
+ }
+ }
+ } else if (res0->f <= filter->frame_thresh) {
+ /* cur P */
+ res0->conclusion = FIELD_ANALYSIS_PROGRESSIVE;
+ res0->holding = 1 + BOTH_FIELDS;
+ if (res1->holding == 1 + BOTH_FIELDS || res1->holding == -1) {
+ /* holding both fields, push prev as is */
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
+ res1->gap);
+ } else if (res1->holding > 0) {
+ /* holding one field, push prev 1F held */
+ outbuf =
+ gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
+ res1->conclusion, res1->gap);
+ } else {
+ /* unknown or no fields held, push prev as is with GAP */
+ /* this will push unknown as gap - should be pushed as not gap? */
+ res1->gap = TRUE;
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
+ res1->gap);
+ }
+ } else {
+ /* cur !P */
+ if (telecine_matches & (FIELD_ANALYSIS_TOP_BOTTOM |
+ FIELD_ANALYSIS_BOTTOM_TOP)) {
+ /* cross-parity match => TCM */
+ gboolean b, t;
+
+ if (res1->holding == -1) {
+ b = t = TRUE;
+ } else {
+ b = res1->holding == 1 + BOTTOM_FIELD;
+ t = res1->holding == 1 + TOP_FIELD;
+ }
+
+ res0->conclusion = FIELD_ANALYSIS_TELECINE_MIXED;
+ /* leave holding as unknown */
+ if (res1->holding == 1 + BOTH_FIELDS) {
+ /* prev P/TCP/I [or TCM repeated (weird case)] */
+ /* push prev as is */
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
+ res1->gap);
+ } else if ((t && telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM) || (b
+ && telecine_matches & FIELD_ANALYSIS_BOTTOM_TOP)) {
+ /* held is opposite to matched => need both field from prev */
+ /* if t_b, hold bottom from prev and top from current, else vice-versa */
+ res1->holding = 1 + ! !(telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM);
+ res0->holding = 1 + !(telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM);
+ /* push prev TCM */
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
+ res1->gap);
+ } else if ((res1->holding > 0 && res1->holding != 1 + BOTH_FIELDS) || (t
+ && telecine_matches & FIELD_ANALYSIS_BOTTOM_TOP) || (b
+ && telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM)) {
+ /* held field is needed, push prev 1F held */
+ outbuf =
+ gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
+ res1->conclusion, res1->gap);
+ } else {
+ /* holding none or unknown */
+ /* push prev as is with GAP */
+ res1->gap = TRUE;
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
+ res1->gap);
+ }
+ } else {
+ /* cur I */
+ res0->conclusion = FIELD_ANALYSIS_INTERLACED;
+ res0->holding = 1 + BOTH_FIELDS;
+ /* push prev appropriately */
+ res1->gap = res1->holding <= 0;
+ if (res1->holding != 0) {
+ res1->gap = FALSE;
+ if (res1->holding == 1 + BOTH_FIELDS || res1->holding == -1) {
+ /* push prev as is */
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE,
+ res1->conclusion, res1->gap);
+ } else {
+ /* push prev 1F held */
+ outbuf =
+ gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
+ res1->conclusion, res1->gap);
+ }
+ } else {
+ /* push prev as is with GAP */
+ res1->gap = TRUE;
+ outbuf =
+ gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
+ res1->gap);
+ }
+ }
+ }
+ }
+
+ switch (res0->conclusion) {
+ case FIELD_ANALYSIS_PROGRESSIVE:
+ GST_DEBUG_OBJECT (filter, "Conclusion: PROGRESSIVE");
+ break;
+ case FIELD_ANALYSIS_INTERLACED:
+ GST_DEBUG_OBJECT (filter, "Conclusion: INTERLACED");
+ break;
+ case FIELD_ANALYSIS_TELECINE_PROGRESSIVE:
+ GST_DEBUG_OBJECT (filter, "Conclusion: TC PROGRESSIVE");
+ break;
+ case FIELD_ANALYSIS_TELECINE_MIXED:
+ GST_DEBUG_OBJECT (filter, "Conclusion: TC MIXED %s",
+ res0->holding ==
+ 1 + BOTH_FIELDS ? "top and bottom" : res0->holding ==
+ 1 + BOTTOM_FIELD ? "bottom" : "top");
+ break;
+ default:
+ GST_DEBUG_OBJECT (filter, "Invalid conclusion! This is a bug!");
+ break;
+ }
+
+ GST_DEBUG_OBJECT (filter, "Items remaining in the queue: %d",
+ g_queue_get_length (queue));
+
+ return outbuf;
+}
+
+/* we have a ref on buf when it comes into chain */
+static GstFlowReturn
+gst_field_analysis_chain (GstPad * pad, GstBuffer * buf)
+{
+ GstFlowReturn ret = GST_FLOW_OK;
+ GstFieldAnalysis *filter;
+ GstBuffer *outbuf = NULL;
+
+ filter = GST_FIELDANALYSIS (GST_OBJECT_PARENT (pad));
+
+ GST_OBJECT_LOCK (filter);
+ if (filter->flushing) {
+ GST_DEBUG_OBJECT (filter, "We are flushing.");
+ /* we have a ref on buf so it must be unreffed */
+ goto unref_unlock_ret;
+ }
+
+ if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT)) {
+ GST_DEBUG_OBJECT (filter, "Discont: flushing");
+ /* we should have a ref on outbuf, either because we had one when it entered
+ * the queue and _make_metadata_writable () inside _decorate () returned
+ * the same buffer or because it returned a new buffer on which we have one
+ * ref */
+ outbuf = gst_field_analysis_flush_one (filter, NULL);
+
+ if (outbuf) {
+ /* we give away our ref on outbuf here */
+ GST_OBJECT_UNLOCK (filter);
+ ret = gst_pad_push (filter->srcpad, outbuf);
+ GST_OBJECT_LOCK (filter);
+ if (filter->flushing) {
+ GST_DEBUG_OBJECT (filter, "We are flushing. outbuf already pushed.");
+ /* we have a ref on buf so it must be unreffed */
+ goto unref_unlock_ret;
+ }
+ }
+
+ gst_field_analysis_reset (filter);
+
+ if (ret != GST_FLOW_OK) {
+ GST_DEBUG_OBJECT (filter,
+ "Pushing of flushed buffer failed with return %d", ret);
+ /* we have a ref on buf so it must be unreffed */
+ goto unref_unlock_ret;
+ } else {
+ outbuf = NULL;
+ }
+ }
+
+ /* after this function, buf has been pushed to the internal queue and its ref
+ * retained there and we have a ref on outbuf */
+ outbuf = gst_field_analysis_process_buffer (filter, &buf);
+
+ GST_OBJECT_UNLOCK (filter);
+
+ /* here we give up our ref on outbuf */
+ if (outbuf)
+ ret = gst_pad_push (filter->srcpad, outbuf);
+
+ return ret;
+
+unref_unlock_ret:
+ /* we must unref the input buffer here */
+ gst_buffer_unref (buf);
+ GST_OBJECT_UNLOCK (filter);
+ return ret;
+}
+
+static GstStateChangeReturn
+gst_field_analysis_change_state (GstElement * element,
+ GstStateChange transition)
+{
+ GstStateChangeReturn ret;
+ GstFieldAnalysis *filter = GST_FIELDANALYSIS (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_field_analysis_reset (filter);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void
+gst_field_analysis_finalize (GObject * object)
+{
+ GstFieldAnalysis *filter = GST_FIELDANALYSIS (object);
+
+ gst_field_analysis_reset (filter);
+ g_queue_free (filter->frames);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static gboolean
+fieldanalysis_init (GstPlugin * fieldanalysis)
+{
+ GST_DEBUG_CATEGORY_INIT (gst_field_analysis_debug, "fieldanalysis",
+ 0, "Video field analysis");
+
+ gst_fieldanalysis_orc_init ();
+
+ return gst_element_register (fieldanalysis, "fieldanalysis", GST_RANK_NONE,
+ GST_TYPE_FIELDANALYSIS);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "fieldanalysis",
+ "Video field analysis",
+ fieldanalysis_init, VERSION, "LGPL", "GStreamer", "http://gstreamer.net/")