Branch data Line data Source code
1 : : /*
2 : : * GStreamer
3 : : * Copyright (C) 2011 Robert Swain <robert.swain@collabora.co.uk>
4 : : *
5 : : * Permission is hereby granted, free of charge, to any person obtaining a
6 : : * copy of this software and associated documentation files (the "Software"),
7 : : * to deal in the Software without restriction, including without limitation
8 : : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 : : * and/or sell copies of the Software, and to permit persons to whom the
10 : : * Software is furnished to do so, subject to the following conditions:
11 : : *
12 : : * The above copyright notice and this permission notice shall be included in
13 : : * all copies or substantial portions of the Software.
14 : : *
15 : : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 : : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 : : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 : : * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 : : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 : : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 : : * DEALINGS IN THE SOFTWARE.
22 : : *
23 : : * Alternatively, the contents of this file may be used under the
24 : : * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
25 : : * which case the following provisions apply instead of the ones
26 : : * mentioned above:
27 : : *
28 : : * This library is free software; you can redistribute it and/or
29 : : * modify it under the terms of the GNU Library General Public
30 : : * License as published by the Free Software Foundation; either
31 : : * version 2 of the License, or (at your option) any later version.
32 : : *
33 : : * This library is distributed in the hope that it will be useful,
34 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
35 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
36 : : * Library General Public License for more details.
37 : : *
38 : : * You should have received a copy of the GNU Library General Public
39 : : * License along with this library; if not, write to the
40 : : * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
41 : : * Boston, MA 02111-1307, USA.
42 : : */
43 : :
44 : : /**
45 : : * SECTION:element-fieldanalysis
46 : : *
47 : : * Analyse fields from video buffers to identify whether the buffers are
48 : : * progressive/telecined/interlaced and, if telecined, the telecine pattern
49 : : * used.
50 : : *
51 : : * <refsect2>
52 : : * <title>Example launch line</title>
53 : : * |[
54 : : * gst-launch -v uridecodebin uri=/path/to/foo.bar ! fieldanalysis ! deinterlace ! ffmpegcolorspace ! autovideosink
55 : : * ]| This pipeline will analyse a video stream with default metrics and thresholds and output progressive frames.
56 : : * </refsect2>
57 : : */
58 : :
59 : : #ifdef HAVE_CONFIG_H
60 : : # include <config.h>
61 : : #endif
62 : :
63 : : #include <gst/gst.h>
64 : : #include <gst/video/video.h>
65 : : #include <string.h>
66 : : #include <stdlib.h> /* for abs() */
67 : :
68 : : #include "gstfieldanalysis.h"
69 : : #include "gstfieldanalysisorc.h"
70 : :
71 : : GST_DEBUG_CATEGORY_STATIC (gst_field_analysis_debug);
72 : : #define GST_CAT_DEFAULT gst_field_analysis_debug
73 : :
74 : : #define DEFAULT_FIELD_METRIC GST_FIELDANALYSIS_SSD
75 : : #define DEFAULT_FRAME_METRIC GST_FIELDANALYSIS_5_TAP
76 : : #define DEFAULT_NOISE_FLOOR 16
77 : : #define DEFAULT_FIELD_THRESH 0.08f
78 : : #define DEFAULT_FRAME_THRESH 0.002f
79 : : #define DEFAULT_COMB_METHOD METHOD_5_TAP
80 : : #define DEFAULT_SPATIAL_THRESH 9
81 : : #define DEFAULT_BLOCK_WIDTH 16
82 : : #define DEFAULT_BLOCK_HEIGHT 16
83 : : #define DEFAULT_BLOCK_THRESH 80
84 : : #define DEFAULT_IGNORED_LINES 2
85 : :
86 : : enum
87 : : {
88 : : PROP_0,
89 : : PROP_FIELD_METRIC,
90 : : PROP_FRAME_METRIC,
91 : : PROP_NOISE_FLOOR,
92 : : PROP_FIELD_THRESH,
93 : : PROP_FRAME_THRESH,
94 : : PROP_COMB_METHOD,
95 : : PROP_SPATIAL_THRESH,
96 : : PROP_BLOCK_WIDTH,
97 : : PROP_BLOCK_HEIGHT,
98 : : PROP_BLOCK_THRESH,
99 : : PROP_IGNORED_LINES
100 : : };
101 : :
102 : : static GstStaticPadTemplate sink_factory =
103 : : GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
104 : : GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("{YUY2,UYVY,I420,YV12}")));
105 : :
106 : : static GstStaticPadTemplate src_factory =
107 : : GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
108 : : GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("{YUY2,UYVY,I420,YV12}")));
109 : :
110 [ + + ]: 61 : GST_BOILERPLATE (GstFieldAnalysis, gst_field_analysis, GstElement,
111 : 61 : GST_TYPE_ELEMENT);
112 : :
113 : : static void gst_field_analysis_set_property (GObject * object, guint prop_id,
114 : : const GValue * value, GParamSpec * pspec);
115 : : static void gst_field_analysis_get_property (GObject * object, guint prop_id,
116 : : GValue * value, GParamSpec * pspec);
117 : :
118 : : static gboolean gst_field_analysis_set_caps (GstPad * pad, GstCaps * caps);
119 : : static gboolean gst_field_analysis_sink_event (GstPad * pad, GstEvent * event);
120 : : static GstFlowReturn gst_field_analysis_chain (GstPad * pad, GstBuffer * buf);
121 : : static GstStateChangeReturn gst_field_analysis_change_state (GstElement *
122 : : element, GstStateChange transition);
123 : : static void gst_field_analysis_finalize (GObject * self);
124 : :
125 : : static GQueue *gst_field_analysis_flush_queue (GstFieldAnalysis * filter,
126 : : GQueue * queue);
127 : :
128 : : static void
129 : 6 : gst_field_analysis_base_init (gpointer gclass)
130 : : {
131 : 6 : GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
132 : :
133 : 6 : gst_element_class_set_details_simple (element_class,
134 : : "Video field analysis",
135 : : "Filter/Analysis/Video",
136 : : "Analyse fields from video frames to identify if they are progressive/telecined/interlaced",
137 : : "Robert Swain <robert.swain@collabora.co.uk>");
138 : :
139 : 6 : gst_element_class_add_pad_template (element_class,
140 : : gst_static_pad_template_get (&src_factory));
141 : 6 : gst_element_class_add_pad_template (element_class,
142 : : gst_static_pad_template_get (&sink_factory));
143 : 6 : }
144 : :
145 : : typedef enum
146 : : {
147 : : GST_FIELDANALYSIS_SAD,
148 : : GST_FIELDANALYSIS_SSD,
149 : : GST_FIELDANALYSIS_3_TAP
150 : : } GstFieldAnalysisFieldMetric;
151 : :
152 : : #define GST_TYPE_FIELDANALYSIS_FIELD_METRIC (gst_fieldanalysis_field_metric_get_type())
153 : : static GType
154 : 6 : gst_fieldanalysis_field_metric_get_type (void)
155 : : {
156 : : static GType fieldanalysis_field_metric_type = 0;
157 : :
158 [ + - ]: 6 : if (!fieldanalysis_field_metric_type) {
159 : : static const GEnumValue fieldanalysis_field_metrics[] = {
160 : : {GST_FIELDANALYSIS_SAD, "Sum of Absolute Differences", "sad"},
161 : : {GST_FIELDANALYSIS_SSD, "Sum of Squared Differences", "ssd"},
162 : : {GST_FIELDANALYSIS_3_TAP, "Difference of 3-tap [1,4,1] Horizontal Filter",
163 : : "3-tap"},
164 : : {0, NULL, NULL},
165 : : };
166 : :
167 : 6 : fieldanalysis_field_metric_type =
168 : 6 : g_enum_register_static ("GstFieldAnalysisFieldMetric",
169 : : fieldanalysis_field_metrics);
170 : : }
171 : :
172 : 6 : return fieldanalysis_field_metric_type;
173 : : }
174 : :
175 : : typedef enum
176 : : {
177 : : GST_FIELDANALYSIS_5_TAP,
178 : : GST_FIELDANALYSIS_WINDOWED_COMB
179 : : } GstFieldAnalysisFrameMetric;
180 : :
181 : : #define GST_TYPE_FIELDANALYSIS_FRAME_METRIC (gst_fieldanalysis_frame_metric_get_type())
182 : : static GType
183 : 6 : gst_fieldanalysis_frame_metric_get_type (void)
184 : : {
185 : : static GType fieldanalysis_frame_metric_type = 0;
186 : :
187 [ + - ]: 6 : if (!fieldanalysis_frame_metric_type) {
188 : : static const GEnumValue fieldanalyis_frame_metrics[] = {
189 : : {GST_FIELDANALYSIS_5_TAP, "5-tap [1,-3,4,-3,1] Vertical Filter", "5-tap"},
190 : : {GST_FIELDANALYSIS_WINDOWED_COMB,
191 : : "Windowed Comb Detection (not optimised)",
192 : : "windowed-comb"},
193 : : {0, NULL, NULL},
194 : : };
195 : :
196 : 6 : fieldanalysis_frame_metric_type =
197 : 6 : g_enum_register_static ("GstFieldAnalysisFrameMetric",
198 : : fieldanalyis_frame_metrics);
199 : : }
200 : :
201 : 6 : return fieldanalysis_frame_metric_type;
202 : : }
203 : :
204 : : #define GST_TYPE_FIELDANALYSIS_COMB_METHOD (gst_fieldanalysis_comb_method_get_type())
205 : : static GType
206 : 6 : gst_fieldanalysis_comb_method_get_type (void)
207 : : {
208 : : static GType fieldanalysis_comb_method_type = 0;
209 : :
210 [ + - ]: 6 : if (!fieldanalysis_comb_method_type) {
211 : : static const GEnumValue fieldanalyis_comb_methods[] = {
212 : : {METHOD_32DETECT,
213 : : "Difference to above sample in same field small and difference to sample in other field large",
214 : : "32-detect"},
215 : : {METHOD_IS_COMBED,
216 : : "Differences between current sample and the above/below samples in other field multiplied together, larger than squared spatial threshold (from Tritical's isCombed)",
217 : : "isCombed"},
218 : : {METHOD_5_TAP,
219 : : "5-tap [1,-3,4,-3,1] vertical filter result is larger than spatial threshold*6",
220 : : "5-tap"},
221 : : {0, NULL, NULL},
222 : : };
223 : :
224 : 6 : fieldanalysis_comb_method_type =
225 : 6 : g_enum_register_static ("FieldAnalysisCombMethod",
226 : : fieldanalyis_comb_methods);
227 : : }
228 : :
229 : 6 : return fieldanalysis_comb_method_type;
230 : : }
231 : :
232 : : static void
233 : 6 : gst_field_analysis_class_init (GstFieldAnalysisClass * klass)
234 : : {
235 : : GObjectClass *gobject_class;
236 : : GstElementClass *gstelement_class;
237 : :
238 : 6 : gobject_class = (GObjectClass *) klass;
239 : 6 : gstelement_class = (GstElementClass *) klass;
240 : :
241 : 6 : gobject_class->set_property = gst_field_analysis_set_property;
242 : 6 : gobject_class->get_property = gst_field_analysis_get_property;
243 : 6 : gobject_class->finalize = gst_field_analysis_finalize;
244 : :
245 : 6 : g_object_class_install_property (gobject_class, PROP_FIELD_METRIC,
246 : : g_param_spec_enum ("field-metric", "Field Metric",
247 : : "Metric to be used for comparing same parity fields to decide if they are a repeated field for telecine",
248 : : GST_TYPE_FIELDANALYSIS_FIELD_METRIC, DEFAULT_FIELD_METRIC,
249 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
250 : 6 : g_object_class_install_property (gobject_class, PROP_FRAME_METRIC,
251 : : g_param_spec_enum ("frame-metric", "Frame Metric",
252 : : "Metric to be used for comparing opposite parity fields to decide if they are a progressive frame",
253 : : GST_TYPE_FIELDANALYSIS_FRAME_METRIC, DEFAULT_FRAME_METRIC,
254 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
255 : 6 : g_object_class_install_property (gobject_class, PROP_NOISE_FLOOR,
256 : : g_param_spec_uint ("noise-floor", "Noise Floor",
257 : : "Noise floor for appropriate metrics (per-pixel metric values with a score less than this will be ignored)",
258 : : 0, G_MAXUINT32,
259 : : DEFAULT_NOISE_FLOOR, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
260 : 6 : g_object_class_install_property (gobject_class, PROP_FIELD_THRESH,
261 : : g_param_spec_float ("field-threshold", "Field Threshold",
262 : : "Threshold for field metric decisions", 0.0f, G_MAXFLOAT,
263 : : DEFAULT_FIELD_THRESH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
264 : 6 : g_object_class_install_property (gobject_class, PROP_FRAME_THRESH,
265 : : g_param_spec_float ("frame-threshold", "Frame Threshold",
266 : : "Threshold for frame metric decisions", 0.0f, G_MAXFLOAT,
267 : : DEFAULT_FRAME_THRESH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
268 : 6 : g_object_class_install_property (gobject_class, PROP_COMB_METHOD,
269 : : g_param_spec_enum ("comb-method", "Comb-detection Method",
270 : : "Metric to be used for identifying comb artifacts if using windowed comb detection",
271 : : GST_TYPE_FIELDANALYSIS_COMB_METHOD, DEFAULT_COMB_METHOD,
272 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
273 : 6 : g_object_class_install_property (gobject_class, PROP_SPATIAL_THRESH,
274 : : g_param_spec_int64 ("spatial-threshold", "Spatial Combing Threshold",
275 : : "Threshold for combing metric decisions", 0, G_MAXINT64,
276 : : DEFAULT_SPATIAL_THRESH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
277 : 6 : g_object_class_install_property (gobject_class, PROP_BLOCK_WIDTH,
278 : : g_param_spec_uint64 ("block-width", "Block width",
279 : : "Block width for windowed comb detection", 0, G_MAXUINT64,
280 : : DEFAULT_BLOCK_WIDTH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
281 : 6 : g_object_class_install_property (gobject_class, PROP_BLOCK_HEIGHT,
282 : : g_param_spec_uint64 ("block-height", "Block height",
283 : : "Block height for windowed comb detection", 0, G_MAXUINT64,
284 : : DEFAULT_BLOCK_HEIGHT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
285 : 6 : g_object_class_install_property (gobject_class, PROP_BLOCK_THRESH,
286 : : g_param_spec_uint64 ("block-threshold", "Block threshold",
287 : : "Block threshold for windowed comb detection", 0, G_MAXUINT64,
288 : : DEFAULT_BLOCK_THRESH, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
289 : 6 : g_object_class_install_property (gobject_class, PROP_IGNORED_LINES,
290 : : g_param_spec_uint64 ("ignored-lines", "Ignored lines",
291 : : "Ignore this many lines from the top and bottom for windowed comb detection",
292 : : 2, G_MAXUINT64, DEFAULT_IGNORED_LINES,
293 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
294 : :
295 : 6 : gstelement_class->change_state =
296 : 6 : GST_DEBUG_FUNCPTR (gst_field_analysis_change_state);
297 : 6 : }
298 : :
299 : : static gfloat same_parity_sad (GstFieldAnalysis * filter,
300 : : FieldAnalysisFields * fields);
301 : : static gfloat same_parity_ssd (GstFieldAnalysis * filter,
302 : : FieldAnalysisFields * fields);
303 : : static gfloat same_parity_3_tap (GstFieldAnalysis * filter,
304 : : FieldAnalysisFields * fields);
305 : : static gfloat opposite_parity_5_tap (GstFieldAnalysis * filter,
306 : : FieldAnalysisFields * fields);
307 : : static guint64 block_score_for_row_32detect (GstFieldAnalysis * filter,
308 : : guint8 * base_fj, guint8 * base_fjp1);
309 : : static guint64 block_score_for_row_iscombed (GstFieldAnalysis * filter,
310 : : guint8 * base_fj, guint8 * base_fjp1);
311 : : static guint64 block_score_for_row_5_tap (GstFieldAnalysis * filter,
312 : : guint8 * base_fj, guint8 * base_fjp1);
313 : : static gfloat opposite_parity_windowed_comb (GstFieldAnalysis * filter,
314 : : FieldAnalysisFields * fields);
315 : :
316 : :
317 : : static void
318 : 15 : gst_field_analysis_reset (GstFieldAnalysis * filter)
319 : : {
320 [ + - ]: 15 : if (filter->frames) {
321 : 15 : guint length = g_queue_get_length (filter->frames);
322 [ - + ]: 15 : GST_DEBUG_OBJECT (filter, "Clearing queue (size %u)", length);
323 [ - + ]: 15 : while (length) {
324 : : /* each buffer in the queue should have a ref on it and so to clear the
325 : : * queue we must pop and unref each buffer here */
326 : 0 : gst_buffer_unref (g_queue_pop_head (filter->frames));
327 : 0 : length--;
328 : : }
329 : : }
330 [ - + ]: 15 : GST_DEBUG_OBJECT (filter, "Resetting context");
331 : 15 : memset (filter->results, 0, 2 * sizeof (FieldAnalysis));
332 : 15 : filter->is_telecine = FALSE;
333 : 15 : filter->first_buffer = TRUE;
334 : 15 : filter->width = 0;
335 : 15 : g_free (filter->comb_mask);
336 : 15 : filter->comb_mask = NULL;
337 : 15 : g_free (filter->block_scores);
338 : 15 : filter->block_scores = NULL;
339 : 15 : }
340 : :
341 : : static void
342 : 4 : gst_field_analysis_init (GstFieldAnalysis * filter,
343 : : GstFieldAnalysisClass * gclass)
344 : : {
345 : 4 : filter->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
346 : 4 : gst_pad_set_setcaps_function (filter->sinkpad,
347 : 4 : GST_DEBUG_FUNCPTR (gst_field_analysis_set_caps));
348 : 4 : gst_pad_set_getcaps_function (filter->sinkpad,
349 : 4 : GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps));
350 : 4 : gst_pad_set_event_function (filter->sinkpad,
351 : 4 : GST_DEBUG_FUNCPTR (gst_field_analysis_sink_event));
352 : 4 : gst_pad_set_chain_function (filter->sinkpad,
353 : 4 : GST_DEBUG_FUNCPTR (gst_field_analysis_chain));
354 : :
355 : 4 : filter->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
356 : 4 : gst_pad_set_getcaps_function (filter->srcpad,
357 : 4 : GST_DEBUG_FUNCPTR (gst_pad_proxy_getcaps));
358 : :
359 : 4 : gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
360 : 4 : gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
361 : :
362 : 4 : filter->frames = g_queue_new ();
363 : 4 : gst_field_analysis_reset (filter);
364 : 4 : filter->same_field = &same_parity_ssd;
365 : 4 : filter->field_thresh = DEFAULT_FIELD_THRESH;
366 : 4 : filter->same_frame = &opposite_parity_5_tap;
367 : 4 : filter->frame_thresh = DEFAULT_FRAME_THRESH;
368 : 4 : filter->noise_floor = DEFAULT_NOISE_FLOOR;
369 : 4 : filter->block_score_for_row = &block_score_for_row_5_tap;
370 : 4 : filter->spatial_thresh = DEFAULT_SPATIAL_THRESH;
371 : 4 : filter->block_width = DEFAULT_BLOCK_WIDTH;
372 : 4 : filter->block_height = DEFAULT_BLOCK_HEIGHT;
373 : 4 : filter->block_thresh = DEFAULT_BLOCK_THRESH;
374 : 4 : filter->ignored_lines = DEFAULT_IGNORED_LINES;
375 : 4 : }
376 : :
377 : : static void
378 : 0 : gst_field_analysis_set_property (GObject * object, guint prop_id,
379 : : const GValue * value, GParamSpec * pspec)
380 : : {
381 : 0 : GstFieldAnalysis *filter = GST_FIELDANALYSIS (object);
382 : :
383 [ # # # # : 0 : switch (prop_id) {
# # # # #
# # # ]
384 : : case PROP_FIELD_METRIC:
385 [ # # # # ]: 0 : switch (g_value_get_enum (value)) {
386 : : case GST_FIELDANALYSIS_SAD:
387 : 0 : filter->same_field = &same_parity_sad;
388 : 0 : break;
389 : : case GST_FIELDANALYSIS_SSD:
390 : 0 : filter->same_field = &same_parity_ssd;
391 : 0 : break;
392 : : case GST_FIELDANALYSIS_3_TAP:
393 : 0 : filter->same_field = &same_parity_3_tap;
394 : 0 : break;
395 : : default:
396 : 0 : break;
397 : : }
398 : 0 : break;
399 : : case PROP_FRAME_METRIC:
400 [ # # # ]: 0 : switch (g_value_get_enum (value)) {
401 : : case GST_FIELDANALYSIS_5_TAP:
402 : 0 : filter->same_frame = &opposite_parity_5_tap;
403 : 0 : break;
404 : : case GST_FIELDANALYSIS_WINDOWED_COMB:
405 : 0 : filter->same_frame = &opposite_parity_windowed_comb;
406 : 0 : break;
407 : : default:
408 : 0 : break;
409 : : }
410 : 0 : break;
411 : : case PROP_NOISE_FLOOR:
412 : 0 : filter->noise_floor = g_value_get_uint (value);
413 : 0 : break;
414 : : case PROP_FIELD_THRESH:
415 : 0 : filter->field_thresh = g_value_get_float (value);
416 : 0 : break;
417 : : case PROP_FRAME_THRESH:
418 : 0 : filter->frame_thresh = g_value_get_float (value);
419 : 0 : break;
420 : : case PROP_COMB_METHOD:
421 [ # # # # ]: 0 : switch (g_value_get_enum (value)) {
422 : : case METHOD_32DETECT:
423 : 0 : filter->block_score_for_row = &block_score_for_row_32detect;
424 : 0 : break;
425 : : case METHOD_IS_COMBED:
426 : 0 : filter->block_score_for_row = &block_score_for_row_iscombed;
427 : 0 : break;
428 : : case METHOD_5_TAP:
429 : 0 : filter->block_score_for_row = &block_score_for_row_5_tap;
430 : 0 : break;
431 : : default:
432 : 0 : break;
433 : : }
434 : 0 : break;
435 : : case PROP_SPATIAL_THRESH:
436 : 0 : filter->spatial_thresh = g_value_get_int64 (value);
437 : 0 : break;
438 : : case PROP_BLOCK_WIDTH:
439 : 0 : filter->block_width = g_value_get_uint64 (value);
440 [ # # ]: 0 : if (filter->width) {
441 [ # # ]: 0 : if (filter->block_scores) {
442 : 0 : gsize nbytes = (filter->width / filter->block_width) * sizeof (guint);
443 : 0 : filter->block_scores = g_realloc (filter->block_scores, nbytes);
444 : 0 : memset (filter->block_scores, 0, nbytes);
445 : : } else {
446 : 0 : filter->block_scores =
447 : 0 : g_malloc0 ((filter->width / filter->block_width) *
448 : : sizeof (guint));
449 : : }
450 : : }
451 : 0 : break;
452 : : case PROP_BLOCK_HEIGHT:
453 : 0 : filter->block_height = g_value_get_uint64 (value);
454 : 0 : break;
455 : : case PROP_BLOCK_THRESH:
456 : 0 : filter->block_thresh = g_value_get_uint64 (value);
457 : 0 : break;
458 : : case PROP_IGNORED_LINES:
459 : 0 : filter->ignored_lines = g_value_get_uint64 (value);
460 : 0 : break;
461 : : default:
462 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
463 : 0 : break;
464 : : }
465 : 0 : }
466 : :
467 : : static void
468 : 11 : gst_field_analysis_get_property (GObject * object, guint prop_id,
469 : : GValue * value, GParamSpec * pspec)
470 : : {
471 : 11 : GstFieldAnalysis *filter = GST_FIELDANALYSIS (object);
472 : :
473 [ + + + + : 11 : switch (prop_id) {
+ + + + +
+ + - ]
474 : : case PROP_FIELD_METRIC:
475 : : {
476 : 1 : GstFieldAnalysisFieldMetric metric = DEFAULT_FIELD_METRIC;
477 [ - + ]: 1 : if (filter->same_field == &same_parity_sad) {
478 : 0 : metric = GST_FIELDANALYSIS_SAD;
479 [ + - ]: 1 : } else if (filter->same_field == &same_parity_ssd) {
480 : 1 : metric = GST_FIELDANALYSIS_SSD;
481 [ # # ]: 0 : } else if (filter->same_field == &same_parity_3_tap) {
482 : 0 : metric = GST_FIELDANALYSIS_3_TAP;
483 : : }
484 : 1 : g_value_set_enum (value, metric);
485 : 1 : break;
486 : : }
487 : : case PROP_FRAME_METRIC:
488 : : {
489 : 1 : GstFieldAnalysisFrameMetric metric = DEFAULT_FRAME_METRIC;
490 [ + - ]: 1 : if (filter->same_frame == &opposite_parity_5_tap) {
491 : 1 : metric = GST_FIELDANALYSIS_5_TAP;
492 [ # # ]: 0 : } else if (filter->same_frame == &opposite_parity_windowed_comb) {
493 : 0 : metric = GST_FIELDANALYSIS_WINDOWED_COMB;
494 : : }
495 : 1 : g_value_set_enum (value, metric);
496 : 1 : break;
497 : : }
498 : : case PROP_NOISE_FLOOR:
499 : 1 : g_value_set_uint (value, filter->noise_floor);
500 : 1 : break;
501 : : case PROP_FIELD_THRESH:
502 : 1 : g_value_set_float (value, filter->field_thresh);
503 : 1 : break;
504 : : case PROP_FRAME_THRESH:
505 : 1 : g_value_set_float (value, filter->frame_thresh);
506 : 1 : break;
507 : : case PROP_COMB_METHOD:
508 : : {
509 : 1 : FieldAnalysisCombMethod method = DEFAULT_COMB_METHOD;
510 [ - + ]: 1 : if (filter->block_score_for_row == &block_score_for_row_32detect) {
511 : 0 : method = METHOD_32DETECT;
512 [ - + ]: 1 : } else if (filter->block_score_for_row == &block_score_for_row_iscombed) {
513 : 0 : method = METHOD_IS_COMBED;
514 [ + - ]: 1 : } else if (filter->block_score_for_row == &block_score_for_row_5_tap) {
515 : 1 : method = METHOD_5_TAP;
516 : : }
517 : 1 : g_value_set_enum (value, method);
518 : 1 : break;
519 : : }
520 : : case PROP_SPATIAL_THRESH:
521 : 1 : g_value_set_int64 (value, filter->spatial_thresh);
522 : 1 : break;
523 : : case PROP_BLOCK_WIDTH:
524 : 1 : g_value_set_uint64 (value, filter->block_width);
525 : 1 : break;
526 : : case PROP_BLOCK_HEIGHT:
527 : 1 : g_value_set_uint64 (value, filter->block_height);
528 : 1 : break;
529 : : case PROP_BLOCK_THRESH:
530 : 1 : g_value_set_uint64 (value, filter->block_thresh);
531 : 1 : break;
532 : : case PROP_IGNORED_LINES:
533 : 1 : g_value_set_uint64 (value, filter->ignored_lines);
534 : 1 : break;
535 : : default:
536 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
537 : 0 : break;
538 : : }
539 : 11 : }
540 : :
541 : : static void
542 : 0 : gst_field_analysis_update_format (GstFieldAnalysis * filter, GstCaps * caps)
543 : : {
544 : : GstStructure *struc;
545 : : guint32 fourcc;
546 : : GstVideoFormat vformat;
547 : : gint width, height, data_offset, sample_incr, line_stride;
548 : : GQueue *outbufs;
549 : :
550 : 0 : struc = gst_caps_get_structure (caps, 0);
551 : 0 : gst_structure_get_fourcc (struc, "format", &fourcc);
552 : 0 : vformat = gst_video_format_from_fourcc (fourcc);
553 : :
554 : 0 : gst_structure_get_int (struc, "width", &width);
555 : 0 : gst_structure_get_int (struc, "height", &height);
556 : :
557 : 0 : data_offset =
558 : 0 : gst_video_format_get_component_offset (vformat, 0, width, height);
559 : 0 : sample_incr = gst_video_format_get_pixel_stride (vformat, 0);
560 : 0 : line_stride = gst_video_format_get_row_stride (vformat, 0, width);
561 : :
562 : : /* if format is unchanged in our eyes, don't update the context */
563 [ # # ][ # # ]: 0 : if ((filter->width == width) && (filter->height == height)
564 [ # # ]: 0 : && (filter->data_offset == data_offset)
565 [ # # ]: 0 : && (filter->sample_incr == sample_incr)
566 [ # # ]: 0 : && (filter->line_stride == line_stride))
567 : 0 : return;
568 : :
569 : : /* format changed - process and push buffers before updating context */
570 : :
571 : 0 : GST_OBJECT_LOCK (filter);
572 : 0 : filter->flushing = TRUE;
573 : 0 : outbufs = gst_field_analysis_flush_queue (filter, filter->frames);
574 : 0 : GST_OBJECT_UNLOCK (filter);
575 : :
576 [ # # ]: 0 : if (outbufs) {
577 [ # # ]: 0 : while (g_queue_get_length (outbufs))
578 : 0 : gst_pad_push (filter->srcpad, g_queue_pop_head (outbufs));
579 : : }
580 : :
581 : 0 : GST_OBJECT_LOCK (filter);
582 : 0 : filter->flushing = FALSE;
583 : :
584 : 0 : filter->width = width;
585 : 0 : filter->height = height;
586 : 0 : filter->data_offset = data_offset;
587 : 0 : filter->sample_incr = sample_incr;
588 : 0 : filter->line_stride = line_stride;
589 : :
590 : : /* update allocations for metric scores */
591 [ # # ]: 0 : if (filter->comb_mask) {
592 : 0 : filter->comb_mask = g_realloc (filter->comb_mask, width);
593 : : } else {
594 : 0 : filter->comb_mask = g_malloc (width);
595 : : }
596 [ # # ]: 0 : if (filter->block_scores) {
597 : 0 : gsize nbytes = (width / filter->block_width) * sizeof (guint);
598 : 0 : filter->block_scores = g_realloc (filter->block_scores, nbytes);
599 : 0 : memset (filter->block_scores, 0, nbytes);
600 : : } else {
601 : 0 : filter->block_scores =
602 : 0 : g_malloc0 ((width / filter->block_width) * sizeof (guint));
603 : : }
604 : :
605 : 0 : GST_OBJECT_UNLOCK (filter);
606 : 0 : return;
607 : : }
608 : :
609 : : static gboolean
610 : 0 : gst_field_analysis_set_caps (GstPad * pad, GstCaps * caps)
611 : : {
612 : 0 : gboolean ret = TRUE;
613 : 0 : GstFieldAnalysis *filter = GST_FIELDANALYSIS (gst_pad_get_parent (pad));
614 : :
615 : 0 : gst_field_analysis_update_format (filter, caps);
616 : :
617 : 0 : ret = gst_pad_set_caps (filter->srcpad, caps);
618 : :
619 : 0 : gst_object_unref (filter);
620 : :
621 : 0 : return ret;
622 : : }
623 : :
624 : : #define FIELD_ANALYSIS_TOP_BOTTOM (1 << 0)
625 : : #define FIELD_ANALYSIS_BOTTOM_TOP (1 << 1)
626 : : #define FIELD_ANALYSIS_TOP_MATCH (1 << 2)
627 : : #define FIELD_ANALYSIS_BOTTOM_MATCH (1 << 3)
628 : :
629 : : /* decorate removes a buffer from the internal queue, on which we have a ref,
630 : : * then makes its metadata writable (could be the same buffer, could be a new
631 : : * buffer, but either way we have a ref on it), decorates this buffer and
632 : : * returns it */
633 : : static GstBuffer *
634 : 0 : gst_field_analysis_decorate (GstFieldAnalysis * filter, gboolean tff,
635 : : gboolean onefield, FieldAnalysisConclusion conclusion, gboolean gap)
636 : : {
637 : 0 : GstBuffer *buf = NULL;
638 : : GstCaps *caps;
639 : :
640 : 0 : caps = gst_caps_copy (GST_PAD_CAPS (filter->srcpad));
641 : :
642 : : /* deal with incoming buffer */
643 [ # # ][ # # ]: 0 : if (conclusion > FIELD_ANALYSIS_PROGRESSIVE || filter->is_telecine == TRUE) {
644 : 0 : gst_caps_set_simple (caps, "interlaced", G_TYPE_BOOLEAN, TRUE, NULL);
645 : 0 : filter->is_telecine = conclusion != FIELD_ANALYSIS_INTERLACED;
646 [ # # ]: 0 : if (conclusion >= FIELD_ANALYSIS_TELECINE_PROGRESSIVE
647 [ # # ]: 0 : || filter->is_telecine == TRUE) {
648 : 0 : gst_caps_set_simple (caps, "interlacing-method", G_TYPE_STRING,
649 : : "telecine", NULL);
650 : : } else {
651 : 0 : gst_caps_set_simple (caps, "interlacing-method", G_TYPE_STRING, "unknown",
652 : : NULL);
653 : : }
654 : : } else {
655 : 0 : gst_structure_remove_field (gst_caps_get_structure (caps, 0),
656 : : "interlacing-method");
657 : 0 : gst_caps_set_simple (caps, "interlaced", G_TYPE_BOOLEAN, FALSE, NULL);
658 : : }
659 : :
660 : : /* get buffer from queue
661 : : * this takes our ref on the buf that was in the queue and gives us a buf
662 : : * on which we have a refi (could be the same buffer, but need not be) */
663 : 0 : buf = gst_buffer_make_metadata_writable (g_queue_pop_head (filter->frames));
664 : :
665 : : /* set buffer flags */
666 [ # # ]: 0 : if (!tff) {
667 : 0 : GST_BUFFER_FLAG_UNSET (buf, GST_VIDEO_BUFFER_TFF);
668 [ # # ][ # # ]: 0 : } else if (tff == 1 || (tff == -1
669 [ # # ]: 0 : && GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_BUFFER_TFF))) {
670 : 0 : GST_BUFFER_FLAG_SET (buf, GST_VIDEO_BUFFER_TFF);
671 : : }
672 : :
673 [ # # ]: 0 : if (onefield) {
674 : 0 : GST_BUFFER_FLAG_SET (buf, GST_VIDEO_BUFFER_ONEFIELD);
675 : : } else {
676 : 0 : GST_BUFFER_FLAG_UNSET (buf, GST_VIDEO_BUFFER_ONEFIELD);
677 : : }
678 : :
679 : 0 : GST_BUFFER_FLAG_UNSET (buf, GST_VIDEO_BUFFER_RFF);
680 : :
681 [ # # ]: 0 : if (gap)
682 : 0 : GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_GAP);
683 : :
684 [ # # ][ # # ]: 0 : if (conclusion == FIELD_ANALYSIS_TELECINE_PROGRESSIVE || (filter->is_telecine
685 [ # # ]: 0 : && conclusion == FIELD_ANALYSIS_PROGRESSIVE))
686 : 0 : GST_BUFFER_FLAG_SET (buf, GST_VIDEO_BUFFER_PROGRESSIVE);
687 : :
688 : : /* set the caps on the src pad and buffer before pushing */
689 [ # # ]: 0 : if (gst_caps_is_equal (caps, GST_PAD_CAPS (filter->srcpad))) {
690 : 0 : gst_buffer_set_caps (buf, GST_PAD_CAPS (filter->srcpad));
691 : : } else {
692 : 0 : gboolean ret = TRUE;
693 : :
694 : 0 : GST_OBJECT_UNLOCK (filter);
695 : 0 : ret = gst_pad_set_caps (filter->srcpad, caps);
696 : 0 : GST_OBJECT_LOCK (filter);
697 : :
698 [ # # ]: 0 : if (!ret) {
699 [ # # ]: 0 : GST_ERROR_OBJECT (filter, "Could not set pad caps");
700 : 0 : gst_buffer_unref (buf);
701 : 0 : return NULL;
702 : : }
703 : 0 : gst_buffer_set_caps (buf, caps);
704 : : }
705 : : /* drop our ref to the caps as the buffer and pad have their own */
706 : 0 : gst_caps_unref (caps);
707 : :
708 [ # # ]: 0 : GST_DEBUG_OBJECT (filter,
709 : : "Pushing buffer with flags: %p (%p), p %d, tff %d, 1f %d, gap %d; conc %d",
710 : : GST_BUFFER_DATA (buf), buf,
711 : : GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_BUFFER_PROGRESSIVE),
712 : : GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_BUFFER_TFF),
713 : : GST_BUFFER_FLAG_IS_SET (buf, GST_VIDEO_BUFFER_ONEFIELD),
714 : : GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP), conclusion);
715 : :
716 : 0 : return buf;
717 : : }
718 : :
719 : : /* _flush_one does not touch the buffer ref counts directly but _decorate ()
720 : : * has some influence on ref counts - see its annotation for details */
721 : : static GstBuffer *
722 : 0 : gst_field_analysis_flush_one (GstFieldAnalysis * filter, GQueue * outbufs)
723 : : {
724 : 0 : GstBuffer *buf = NULL;
725 : 0 : guint n_queued = g_queue_get_length (filter->frames);
726 : 0 : guint idx = n_queued - 1;
727 : :
728 [ # # ][ # # ]: 0 : if (!n_queued || n_queued > 2)
729 : 0 : return buf;
730 : :
731 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "Flushing last buffer (queue length %d)", n_queued);
732 [ # # ]: 0 : if (filter->results[idx].holding == 1 + TOP_FIELD
733 [ # # ]: 0 : || filter->results[idx].holding == 1 + BOTTOM_FIELD) {
734 : : /* should be only one field needed */
735 : 0 : buf =
736 : 0 : gst_field_analysis_decorate (filter,
737 : 0 : filter->results[idx].holding == 1 + TOP_FIELD, TRUE,
738 : : filter->results[idx].conclusion, FALSE);
739 : : } else {
740 : : /* possibility that both fields are needed */
741 : 0 : buf =
742 : 0 : gst_field_analysis_decorate (filter, -1, FALSE,
743 : 0 : filter->results[idx].conclusion, !filter->results[idx].holding);
744 : : }
745 [ # # ]: 0 : if (buf) {
746 [ # # ]: 0 : if (outbufs)
747 : 0 : g_queue_push_tail (outbufs, buf);
748 : : } else {
749 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "Error occurred during decoration");
750 : : }
751 : 0 : return buf;
752 : : }
753 : :
754 : : /* _flush_queue () has no direct influence on refcounts and nor does _flush_one,
755 : : * but _decorate () does and so this function does indirectly */
756 : : static GQueue *
757 : 0 : gst_field_analysis_flush_queue (GstFieldAnalysis * filter, GQueue * queue)
758 : : {
759 : : GQueue *outbufs;
760 : 0 : guint length = 0;
761 : :
762 [ # # ]: 0 : if (queue)
763 : 0 : length = g_queue_get_length (queue);
764 : :
765 [ # # ]: 0 : if (length < 2)
766 : 0 : return NULL;
767 : :
768 : 0 : outbufs = g_queue_new ();
769 : :
770 [ # # ]: 0 : while (length) {
771 : 0 : gst_field_analysis_flush_one (filter, outbufs);
772 : 0 : length--;
773 : : }
774 : :
775 : 0 : return outbufs;
776 : : }
777 : :
778 : : static gboolean
779 : 0 : gst_field_analysis_sink_event (GstPad * pad, GstEvent * event)
780 : : {
781 : 0 : GstFieldAnalysis *filter = GST_FIELDANALYSIS (gst_pad_get_parent (pad));
782 : :
783 [ # # ]: 0 : GST_LOG_OBJECT (pad, "received %s event: %" GST_PTR_FORMAT,
784 : : GST_EVENT_TYPE_NAME (event), event);
785 : :
786 [ # # # ]: 0 : switch (GST_EVENT_TYPE (event)) {
787 : : case GST_EVENT_NEWSEGMENT:
788 : : case GST_EVENT_EOS:
789 : : {
790 : : /* for both NEWSEGMENT and EOS it is safest to process and push queued
791 : : * buffers */
792 : : GQueue *outbufs;
793 : :
794 : 0 : GST_OBJECT_LOCK (filter);
795 : 0 : filter->flushing = TRUE;
796 : 0 : outbufs = gst_field_analysis_flush_queue (filter, filter->frames);
797 : 0 : GST_OBJECT_UNLOCK (filter);
798 : :
799 [ # # ]: 0 : if (outbufs) {
800 [ # # ]: 0 : while (g_queue_get_length (outbufs))
801 : 0 : gst_pad_push (filter->srcpad, g_queue_pop_head (outbufs));
802 : : }
803 : :
804 : 0 : GST_OBJECT_LOCK (filter);
805 : 0 : filter->flushing = FALSE;
806 : 0 : GST_OBJECT_UNLOCK (filter);
807 : 0 : break;
808 : : }
809 : : case GST_EVENT_FLUSH_STOP:
810 : : /* if we have any buffers left in the queue, unref them until the queue
811 : : * is empty */
812 : 0 : GST_OBJECT_LOCK (filter);
813 : 0 : gst_field_analysis_reset (filter);
814 : 0 : GST_OBJECT_UNLOCK (filter);
815 : 0 : break;
816 : : default:
817 : 0 : break;
818 : : }
819 : :
820 : : /* NOTE: we always forward the event currently, change this as necessary */
821 : 0 : return gst_pad_push_event (filter->srcpad, event);
822 : : }
823 : :
824 : :
825 : : static gfloat
826 : 0 : same_parity_sad (GstFieldAnalysis * filter, FieldAnalysisFields * fields)
827 : : {
828 : : gint j;
829 : : gfloat sum;
830 : : guint8 *f1j, *f2j;
831 : :
832 : 0 : const gint y_offset = filter->data_offset;
833 : 0 : const gint stride = filter->line_stride;
834 : 0 : const gint stridex2 = stride << 1;
835 : 0 : const guint32 noise_floor = filter->noise_floor;
836 : :
837 : 0 : f1j = GST_BUFFER_DATA (fields[0].buf) + y_offset + fields[0].parity * stride;
838 : 0 : f2j = GST_BUFFER_DATA (fields[1].buf) + y_offset + fields[1].parity * stride;
839 : :
840 : 0 : sum = 0.0f;
841 [ # # ]: 0 : for (j = 0; j < (filter->height >> 1); j++) {
842 : 0 : guint32 tempsum = 0;
843 : 0 : orc_same_parity_sad_planar_yuv (&tempsum, f1j, f2j, noise_floor,
844 : : filter->width);
845 : 0 : sum += tempsum;
846 : 0 : f1j += stridex2;
847 : 0 : f2j += stridex2;
848 : : }
849 : :
850 : 0 : return sum / (0.5f * filter->width * filter->height);
851 : : }
852 : :
853 : : static gfloat
854 : 0 : same_parity_ssd (GstFieldAnalysis * filter, FieldAnalysisFields * fields)
855 : : {
856 : : gint j;
857 : : gfloat sum;
858 : : guint8 *f1j, *f2j;
859 : :
860 : 0 : const gint y_offset = filter->data_offset;
861 : 0 : const gint stride = filter->line_stride;
862 : 0 : const gint stridex2 = stride << 1;
863 : : /* noise floor needs to be squared for SSD */
864 : 0 : const guint32 noise_floor = filter->noise_floor * filter->noise_floor;
865 : :
866 : 0 : f1j = GST_BUFFER_DATA (fields[0].buf) + y_offset + fields[0].parity * stride;
867 : 0 : f2j = GST_BUFFER_DATA (fields[1].buf) + y_offset + fields[1].parity * stride;
868 : :
869 : 0 : sum = 0.0f;
870 [ # # ]: 0 : for (j = 0; j < (filter->height >> 1); j++) {
871 : 0 : guint32 tempsum = 0;
872 : 0 : orc_same_parity_ssd_planar_yuv (&tempsum, f1j, f2j, noise_floor,
873 : : filter->width);
874 : 0 : sum += tempsum;
875 : 0 : f1j += stridex2;
876 : 0 : f2j += stridex2;
877 : : }
878 : :
879 : 0 : return sum / (0.5f * filter->width * filter->height); /* field is half height */
880 : : }
881 : :
882 : : /* horizontal [1,4,1] diff between fields - is this a good idea or should the
883 : : * current sample be emphasised more or less? */
884 : : static gfloat
885 : 0 : same_parity_3_tap (GstFieldAnalysis * filter, FieldAnalysisFields * fields)
886 : : {
887 : : gint i, j;
888 : : gfloat sum;
889 : : guint8 *f1j, *f2j;
890 : :
891 : 0 : const gint y_offset = filter->data_offset;
892 : 0 : const gint stride = filter->line_stride;
893 : 0 : const gint stridex2 = stride << 1;
894 : 0 : const gint incr = filter->sample_incr;
895 : : /* noise floor needs to be squared for [1,4,1] */
896 : 0 : const guint32 noise_floor = filter->noise_floor * 6;
897 : :
898 : 0 : f1j = GST_BUFFER_DATA (fields[0].buf) + y_offset + fields[0].parity * stride;
899 : 0 : f2j = GST_BUFFER_DATA (fields[1].buf) + y_offset + fields[1].parity * stride;
900 : :
901 : 0 : sum = 0.0f;
902 [ # # ]: 0 : for (j = 0; j < (filter->height >> 1); j++) {
903 : 0 : guint32 tempsum = 0;
904 : : guint32 diff;
905 : :
906 : : /* unroll first as it is a special case */
907 : 0 : diff = abs (((f1j[0] << 2) + (f1j[incr] << 1))
908 : 0 : - ((f2j[0] << 2) + (f2j[incr] << 1)));
909 [ # # ]: 0 : if (diff > noise_floor)
910 : 0 : sum += diff;
911 : :
912 : 0 : orc_same_parity_3_tap_planar_yuv (&tempsum, f1j, &f1j[incr],
913 : 0 : &f1j[incr << 1], f2j, &f2j[incr], &f2j[incr << 1], noise_floor,
914 : 0 : filter->width - 1);
915 : 0 : sum += tempsum;
916 : :
917 : : /* unroll last as it is a special case */
918 : 0 : i = filter->width - 1;
919 : 0 : diff = abs (((f1j[i - incr] << 1) + (f1j[i] << 2))
920 : 0 : - ((f2j[i - incr] << 1) + (f2j[i] << 2)));
921 [ # # ]: 0 : if (diff > noise_floor)
922 : 0 : sum += diff;
923 : :
924 : 0 : f1j += stridex2;
925 : 0 : f2j += stridex2;
926 : : }
927 : :
928 : 0 : return sum / ((6.0f / 2.0f) * filter->width * filter->height); /* 1 + 4 + 1 = 6; field is half height */
929 : : }
930 : :
931 : : /* vertical [1,-3,4,-3,1] - same as is used in FieldDiff from TIVTC,
932 : : * tritical's AVISynth IVTC filter */
933 : : /* 0th field's parity defines operation */
934 : : static gfloat
935 : 0 : opposite_parity_5_tap (GstFieldAnalysis * filter, FieldAnalysisFields * fields)
936 : : {
937 : : gint j;
938 : : gfloat sum;
939 : : guint8 *fjm2, *fjm1, *fj, *fjp1, *fjp2;
940 : : guint32 tempsum;
941 : :
942 : 0 : const gint y_offset = filter->data_offset;
943 : 0 : const gint stride = filter->line_stride;
944 : 0 : const gint stridex2 = stride << 1;
945 : : /* noise floor needs to be *6 for [1,-3,4,-3,1] */
946 : 0 : const guint32 noise_floor = filter->noise_floor * 6;
947 : :
948 : 0 : sum = 0.0f;
949 : :
950 : : /* fj is line j of the combined frame made from the top field even lines of
951 : : * field 0 and the bottom field odd lines from field 1
952 : : * fjp1 is one line down from fj
953 : : * fjm2 is two lines up from fj
954 : : * fj with j == 0 is the 0th line of the top field
955 : : * fj with j == 1 is the 0th line of the bottom field or the 1st field of
956 : : * the frame*/
957 : :
958 : : /* unroll first line as it is a special case */
959 [ # # ]: 0 : if (fields[0].parity == TOP_FIELD) {
960 : 0 : fj = GST_BUFFER_DATA (fields[0].buf) + y_offset;
961 : 0 : fjp1 = GST_BUFFER_DATA (fields[1].buf) + y_offset + stride;
962 : : } else {
963 : 0 : fj = GST_BUFFER_DATA (fields[1].buf) + y_offset;
964 : 0 : fjp1 = GST_BUFFER_DATA (fields[0].buf) + y_offset + stride;
965 : : }
966 : 0 : fjp2 = fj + stridex2;
967 : :
968 : 0 : tempsum = 0;
969 : 0 : orc_opposite_parity_5_tap_planar_yuv (&tempsum, fjp2, fjp1, fj, fjp1, fjp2,
970 : : noise_floor, filter->width);
971 : 0 : sum += tempsum;
972 : :
973 [ # # ]: 0 : for (j = 1; j < (filter->height >> 1) - 1; j++) {
974 : : /* shift everything down a line in the field of interest (means += stridex2) */
975 : 0 : fjm2 = fj;
976 : 0 : fjm1 = fjp1;
977 : 0 : fj = fjp2;
978 : 0 : fjp1 += stridex2;
979 : 0 : fjp2 += stridex2;
980 : :
981 : 0 : tempsum = 0;
982 : 0 : orc_opposite_parity_5_tap_planar_yuv (&tempsum, fjm2, fjm1, fj, fjp1, fjp2,
983 : : noise_floor, filter->width);
984 : 0 : sum += tempsum;
985 : : }
986 : :
987 : : /* unroll the last line as it is a special case */
988 : : /* shift everything down a line in the field of interest (means += stridex2) */
989 : 0 : fjm2 = fj;
990 : 0 : fjm1 = fjp1;
991 : 0 : fj = fjp2;
992 : :
993 : 0 : tempsum = 0;
994 : 0 : orc_opposite_parity_5_tap_planar_yuv (&tempsum, fjm2, fjm1, fj, fjm1, fjm2,
995 : : noise_floor, filter->width);
996 : 0 : sum += tempsum;
997 : :
998 : 0 : return sum / ((6.0f / 2.0f) * filter->width * filter->height); /* 1 + 4 + 1 == 3 + 3 == 6; field is half height */
999 : : }
1000 : :
1001 : : /* this metric was sourced from HandBrake but originally from transcode
1002 : : * the return value is the highest block score for the row of blocks */
1003 : : static inline guint64
1004 : 0 : block_score_for_row_32detect (GstFieldAnalysis * filter, guint8 * base_fj,
1005 : : guint8 * base_fjp1)
1006 : : {
1007 : : guint64 i, j;
1008 : 0 : guint8 *comb_mask = filter->comb_mask;
1009 : 0 : guint *block_scores = filter->block_scores;
1010 : : guint64 block_score;
1011 : : guint8 *fjm2, *fjm1, *fj, *fjp1;
1012 : 0 : const gint incr = filter->sample_incr;
1013 : 0 : const gint stridex2 = filter->line_stride << 1;
1014 : 0 : const guint64 block_width = filter->block_width;
1015 : 0 : const guint64 block_height = filter->block_height;
1016 : 0 : const gint64 spatial_thresh = filter->spatial_thresh;
1017 : 0 : const gint width = filter->width - (filter->width % block_width);
1018 : :
1019 : 0 : fjm2 = base_fj - stridex2;
1020 : 0 : fjm1 = base_fjp1 - stridex2;
1021 : 0 : fj = base_fj;
1022 : 0 : fjp1 = base_fjp1;
1023 : :
1024 [ # # ]: 0 : for (j = 0; j < block_height; j++) {
1025 : : /* we have to work one result ahead of ourselves which results in some small
1026 : : * peculiarities below */
1027 : : gint diff1, diff2;
1028 : :
1029 : 0 : diff1 = fj[0] - fjm1[0];
1030 : 0 : diff2 = fj[0] - fjp1[0];
1031 : : /* change in the same direction */
1032 [ # # ][ # # ]: 0 : if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
1033 [ # # ][ # # ]: 0 : || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
1034 [ # # ][ # # ]: 0 : comb_mask[0] = abs (fj[0] - fjm2[0]) < 10 && abs (fj[0] - fjm1[0]) > 15;
[ # # ]
1035 : : } else {
1036 : 0 : comb_mask[0] = FALSE;
1037 : : }
1038 : :
1039 [ # # ]: 0 : for (i = 1; i < width; i++) {
1040 : 0 : const guint64 idx = i * incr;
1041 : 0 : const guint64 res_idx = (i - 1) / block_width;
1042 : :
1043 : 0 : diff1 = fj[idx] - fjm1[idx];
1044 : 0 : diff2 = fj[idx] - fjp1[idx];
1045 [ # # ][ # # ]: 0 : if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
1046 [ # # ][ # # ]: 0 : || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
1047 [ # # ][ # # ]: 0 : comb_mask[i] = abs (fj[idx] - fjm2[idx]) < 10
1048 [ # # ]: 0 : && abs (fj[idx] - fjm1[idx]) > 15;
1049 : : } else {
1050 : 0 : comb_mask[i] = FALSE;
1051 : : }
1052 : :
1053 [ # # ][ # # ]: 0 : if (i == 1 && comb_mask[i - 1] && comb_mask[i]) {
[ # # ]
1054 : : /* left edge */
1055 : 0 : block_scores[res_idx]++;
1056 [ # # ]: 0 : } else if (i == width - 1) {
1057 : : /* right edge */
1058 [ # # ][ # # ]: 0 : if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i])
[ # # ]
1059 : 0 : block_scores[res_idx]++;
1060 [ # # ][ # # ]: 0 : if (comb_mask[i - 1] && comb_mask[i])
1061 : 0 : block_scores[i / block_width]++;
1062 [ # # ][ # # ]: 0 : } else if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i]) {
[ # # ]
1063 : 0 : block_scores[res_idx]++;
1064 : : }
1065 : : }
1066 : : /* advance down a line */
1067 : 0 : fjm2 = fjm1;
1068 : 0 : fjm1 = fj;
1069 : 0 : fj = fjp1;
1070 : 0 : fjp1 = fjm1 + stridex2;
1071 : : }
1072 : :
1073 : 0 : block_score = 0;
1074 [ # # ]: 0 : for (i = 0; i < width / block_width; i++) {
1075 [ # # ]: 0 : if (block_scores[i] > block_score)
1076 : 0 : block_score = block_scores[i];
1077 : : }
1078 : :
1079 : 0 : g_free (block_scores);
1080 : 0 : g_free (comb_mask);
1081 : 0 : return block_score;
1082 : : }
1083 : :
1084 : : /* this metric was sourced from HandBrake but originally from
1085 : : * tritical's isCombedT Avisynth function
1086 : : * the return value is the highest block score for the row of blocks */
1087 : : static inline guint64
1088 : 0 : block_score_for_row_iscombed (GstFieldAnalysis * filter, guint8 * base_fj,
1089 : : guint8 * base_fjp1)
1090 : : {
1091 : : guint64 i, j;
1092 : 0 : guint8 *comb_mask = filter->comb_mask;
1093 : 0 : guint *block_scores = filter->block_scores;
1094 : : guint64 block_score;
1095 : : guint8 *fjm1, *fj, *fjp1;
1096 : 0 : const gint incr = filter->sample_incr;
1097 : 0 : const gint stridex2 = filter->line_stride << 1;
1098 : 0 : const guint64 block_width = filter->block_width;
1099 : 0 : const guint64 block_height = filter->block_height;
1100 : 0 : const gint64 spatial_thresh = filter->spatial_thresh;
1101 : 0 : const gint64 spatial_thresh_squared = spatial_thresh * spatial_thresh;
1102 : 0 : const gint width = filter->width - (filter->width % block_width);
1103 : :
1104 : 0 : fjm1 = base_fjp1 - stridex2;
1105 : 0 : fj = base_fj;
1106 : 0 : fjp1 = base_fjp1;
1107 : :
1108 [ # # ]: 0 : for (j = 0; j < block_height; j++) {
1109 : : /* we have to work one result ahead of ourselves which results in some small
1110 : : * peculiarities below */
1111 : : gint diff1, diff2;
1112 : :
1113 : 0 : diff1 = fj[0] - fjm1[0];
1114 : 0 : diff2 = fj[0] - fjp1[0];
1115 : : /* change in the same direction */
1116 [ # # ][ # # ]: 0 : if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
1117 [ # # ][ # # ]: 0 : || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
1118 : 0 : comb_mask[0] =
1119 : 0 : (fjm1[0] - fj[0]) * (fjp1[0] - fj[0]) > spatial_thresh_squared;
1120 : : } else {
1121 : 0 : comb_mask[0] = FALSE;
1122 : : }
1123 : :
1124 [ # # ]: 0 : for (i = 1; i < width; i++) {
1125 : 0 : const guint64 idx = i * incr;
1126 : 0 : const guint64 res_idx = (i - 1) / block_width;
1127 : :
1128 : 0 : diff1 = fj[idx] - fjm1[idx];
1129 : 0 : diff2 = fj[idx] - fjp1[idx];
1130 [ # # ][ # # ]: 0 : if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
1131 [ # # ][ # # ]: 0 : || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
1132 : 0 : comb_mask[i] =
1133 : 0 : (fjm1[idx] - fj[idx]) * (fjp1[idx] - fj[idx]) >
1134 : : spatial_thresh_squared;
1135 : : } else {
1136 : 0 : comb_mask[i] = FALSE;
1137 : : }
1138 : :
1139 [ # # ][ # # ]: 0 : if (i == 1 && comb_mask[i - 1] && comb_mask[i]) {
[ # # ]
1140 : : /* left edge */
1141 : 0 : block_scores[res_idx]++;
1142 [ # # ]: 0 : } else if (i == width - 1) {
1143 : : /* right edge */
1144 [ # # ][ # # ]: 0 : if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i])
[ # # ]
1145 : 0 : block_scores[res_idx]++;
1146 [ # # ][ # # ]: 0 : if (comb_mask[i - 1] && comb_mask[i])
1147 : 0 : block_scores[i / block_width]++;
1148 [ # # ][ # # ]: 0 : } else if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i]) {
[ # # ]
1149 : 0 : block_scores[res_idx]++;
1150 : : }
1151 : : }
1152 : : /* advance down a line */
1153 : 0 : fjm1 = fj;
1154 : 0 : fj = fjp1;
1155 : 0 : fjp1 = fjm1 + stridex2;
1156 : : }
1157 : :
1158 : 0 : block_score = 0;
1159 [ # # ]: 0 : for (i = 0; i < width / block_width; i++) {
1160 [ # # ]: 0 : if (block_scores[i] > block_score)
1161 : 0 : block_score = block_scores[i];
1162 : : }
1163 : :
1164 : 0 : g_free (block_scores);
1165 : 0 : g_free (comb_mask);
1166 : 0 : return block_score;
1167 : : }
1168 : :
1169 : : /* this metric was sourced from HandBrake but originally from
1170 : : * tritical's isCombedT Avisynth function
1171 : : * the return value is the highest block score for the row of blocks */
1172 : : static inline guint64
1173 : 0 : block_score_for_row_5_tap (GstFieldAnalysis * filter, guint8 * base_fj,
1174 : : guint8 * base_fjp1)
1175 : : {
1176 : : guint64 i, j;
1177 : 0 : guint8 *comb_mask = filter->comb_mask;
1178 : 0 : guint *block_scores = filter->block_scores;
1179 : : guint64 block_score;
1180 : : guint8 *fjm2, *fjm1, *fj, *fjp1, *fjp2;
1181 : 0 : const gint incr = filter->sample_incr;
1182 : 0 : const gint stridex2 = filter->line_stride << 1;
1183 : 0 : const guint64 block_width = filter->block_width;
1184 : 0 : const guint64 block_height = filter->block_height;
1185 : 0 : const gint64 spatial_thresh = filter->spatial_thresh;
1186 : 0 : const gint64 spatial_threshx6 = 6 * spatial_thresh;
1187 : 0 : const gint width = filter->width - (filter->width % block_width);
1188 : :
1189 : 0 : fjm2 = base_fj - stridex2;
1190 : 0 : fjm1 = base_fjp1 - stridex2;
1191 : 0 : fj = base_fj;
1192 : 0 : fjp1 = base_fjp1;
1193 : 0 : fjp2 = fj + stridex2;
1194 : :
1195 [ # # ]: 0 : for (j = 0; j < block_height; j++) {
1196 : : /* we have to work one result ahead of ourselves which results in some small
1197 : : * peculiarities below */
1198 : : gint diff1, diff2;
1199 : :
1200 : 0 : diff1 = fj[0] - fjm1[0];
1201 : 0 : diff2 = fj[0] - fjp1[0];
1202 : : /* change in the same direction */
1203 [ # # ][ # # ]: 0 : if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
1204 [ # # ][ # # ]: 0 : || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
1205 : 0 : comb_mask[0] =
1206 : 0 : abs (fjm2[0] + (fj[0] << 2) + fjp2[0] - 3 * (fjm1[0] + fjp1[0])) >
1207 : : spatial_threshx6;
1208 : :
1209 : : /* motion detection that needs previous and next frames
1210 : : this isn't really necessary, but acts as an optimisation if the
1211 : : additional delay isn't a problem
1212 : : if (motion_detection) {
1213 : : if (abs(fpj[idx] - fj[idx] ) > motion_thresh &&
1214 : : abs( fjm1[idx] - fnjm1[idx]) > motion_thresh &&
1215 : : abs( fjp1[idx] - fnjp1[idx]) > motion_thresh)
1216 : : motion++;
1217 : : if (abs( fj[idx] - fnj[idx]) > motion_thresh &&
1218 : : abs(fpjm1[idx] - fjm1[idx] ) > motion_thresh &&
1219 : : abs(fpjp1[idx] - fjp1[idx] ) > motion_thresh)
1220 : : motion++;
1221 : : } else {
1222 : : motion = 1;
1223 : : }
1224 : : */
1225 : : } else {
1226 : 0 : comb_mask[0] = FALSE;
1227 : : }
1228 : :
1229 [ # # ]: 0 : for (i = 1; i < width; i++) {
1230 : 0 : const guint64 idx = i * incr;
1231 : 0 : const guint64 res_idx = (i - 1) / block_width;
1232 : :
1233 : 0 : diff1 = fj[idx] - fjm1[idx];
1234 : 0 : diff2 = fj[idx] - fjp1[idx];
1235 [ # # ][ # # ]: 0 : if ((diff1 > spatial_thresh && diff2 > spatial_thresh)
1236 [ # # ][ # # ]: 0 : || (diff1 < -spatial_thresh && diff2 < -spatial_thresh)) {
1237 : 0 : comb_mask[i] =
1238 : 0 : abs (fjm2[idx] + (fj[idx] << 2) + fjp2[idx] - 3 * (fjm1[idx] +
1239 : 0 : fjp1[idx])) > spatial_threshx6;
1240 : : } else {
1241 : 0 : comb_mask[i] = FALSE;
1242 : : }
1243 : :
1244 [ # # ][ # # ]: 0 : if (i == 1 && comb_mask[i - 1] && comb_mask[i]) {
[ # # ]
1245 : : /* left edge */
1246 : 0 : block_scores[res_idx]++;
1247 [ # # ]: 0 : } else if (i == width - 1) {
1248 : : /* right edge */
1249 [ # # ][ # # ]: 0 : if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i])
[ # # ]
1250 : 0 : block_scores[res_idx]++;
1251 [ # # ][ # # ]: 0 : if (comb_mask[i - 1] && comb_mask[i])
1252 : 0 : block_scores[i / block_width]++;
1253 [ # # ][ # # ]: 0 : } else if (comb_mask[i - 2] && comb_mask[i - 1] && comb_mask[i]) {
[ # # ]
1254 : 0 : block_scores[res_idx]++;
1255 : : }
1256 : : }
1257 : : /* advance down a line */
1258 : 0 : fjm2 = fjm1;
1259 : 0 : fjm1 = fj;
1260 : 0 : fj = fjp1;
1261 : 0 : fjp1 = fjp2;
1262 : 0 : fjp2 = fj + stridex2;
1263 : : }
1264 : :
1265 : 0 : block_score = 0;
1266 [ # # ]: 0 : for (i = 0; i < width / block_width; i++) {
1267 [ # # ]: 0 : if (block_scores[i] > block_score)
1268 : 0 : block_score = block_scores[i];
1269 : : }
1270 : :
1271 : 0 : g_free (block_scores);
1272 : 0 : g_free (comb_mask);
1273 : 0 : return block_score;
1274 : : }
1275 : :
1276 : : /* a pass is made over the field using one of three comb-detection metrics
1277 : : and the results are then analysed block-wise. if the samples to the left
1278 : : and right are combed, they contribute to the block score. if the block
1279 : : score is above the given threshold, the frame is combed. if the block
1280 : : score is between half the threshold and the threshold, the block is
1281 : : slightly combed. if when analysis is complete, slight combing is detected
1282 : : that is returned. if any results are observed that are above the threshold,
1283 : : the function returns immediately */
1284 : : /* 0th field's parity defines operation */
1285 : : static gfloat
1286 : 0 : opposite_parity_windowed_comb (GstFieldAnalysis * filter,
1287 : : FieldAnalysisFields * fields)
1288 : : {
1289 : : gint j;
1290 : : gboolean slightly_combed;
1291 : :
1292 : 0 : const gint y_offset = filter->data_offset;
1293 : 0 : const gint stride = filter->line_stride;
1294 : 0 : const guint64 block_thresh = filter->block_thresh;
1295 : 0 : const guint64 block_height = filter->block_height;
1296 : : guint8 *base_fj, *base_fjp1;
1297 : :
1298 [ # # ]: 0 : if (fields[0].parity == TOP_FIELD) {
1299 : 0 : base_fj = GST_BUFFER_DATA (fields[0].buf) + y_offset;
1300 : 0 : base_fjp1 = GST_BUFFER_DATA (fields[1].buf) + y_offset + stride;
1301 : : } else {
1302 : 0 : base_fj = GST_BUFFER_DATA (fields[1].buf) + y_offset;
1303 : 0 : base_fjp1 = GST_BUFFER_DATA (fields[0].buf) + y_offset + stride;
1304 : : }
1305 : :
1306 : : /* we operate on a row of blocks of height block_height through each iteration */
1307 : 0 : slightly_combed = FALSE;
1308 [ # # ]: 0 : for (j = 0; j <= filter->height - filter->ignored_lines - block_height;
1309 : 0 : j += block_height) {
1310 : 0 : guint64 line_offset = (filter->ignored_lines + j) * stride;
1311 : 0 : guint block_score =
1312 : 0 : filter->block_score_for_row (filter, base_fj + line_offset,
1313 : : base_fjp1 + line_offset);
1314 : :
1315 [ # # ]: 0 : if (block_score > (block_thresh >> 1)
1316 [ # # ]: 0 : && block_score <= block_thresh) {
1317 : : /* blend if nothing more combed comes along */
1318 : 0 : slightly_combed = TRUE;
1319 [ # # ]: 0 : } else if (block_score > block_thresh) {
1320 : 0 : GstCaps *caps = GST_BUFFER_CAPS (fields[0].buf);
1321 : 0 : GstStructure *struc = gst_caps_get_structure (caps, 0);
1322 : : gboolean interlaced;
1323 [ # # ]: 0 : if (gst_structure_get_boolean (struc, "interlaced", &interlaced)
1324 [ # # ]: 0 : && interlaced == TRUE) {
1325 : 0 : return 1.0f; /* blend */
1326 : : } else {
1327 : 0 : return 2.0f; /* deinterlace */
1328 : : }
1329 : : }
1330 : : }
1331 : :
1332 : 0 : return (gfloat) slightly_combed; /* TRUE means blend, else don't */
1333 : : }
1334 : :
1335 : : /* this is where the magic happens
1336 : : *
1337 : : * the buffer incoming to the chain function (buf_to_queue) is added to the
1338 : : * internal queue and then should no longer be used until it is popped from the
1339 : : * queue.
1340 : : *
1341 : : * analysis is performed on the incoming buffer (peeked from the queue) and the
1342 : : * previous buffer using two classes of metrics making up five individual
1343 : : * scores.
1344 : : *
1345 : : * there are two same-parity comparisons: top of current with top of previous
1346 : : * and bottom of current with bottom of previous
1347 : : *
1348 : : * there are three opposing parity comparisons: top of current with bottom of
1349 : : * _current_, top of current with bottom of previous and bottom of current with
1350 : : * top of previous.
1351 : : *
1352 : : * from the results of these comparisons we can use some rather complex logic to
1353 : : * identify the state of the previous buffer, decorate and return it and
1354 : : * identify some preliminary state of the current buffer.
1355 : : *
1356 : : * the returned buffer has a ref on it (it has come from _make_metadata_writable
1357 : : * that was called on an incoming buffer that was queued and then popped) */
1358 : : static GstBuffer *
1359 : 0 : gst_field_analysis_process_buffer (GstFieldAnalysis * filter,
1360 : : GstBuffer ** buf_to_queue)
1361 : : {
1362 : : GQueue *queue;
1363 : : guint n_queued;
1364 : : /* res0/1 correspond to f0/1 */
1365 : : FieldAnalysis *res0, *res1;
1366 : : FieldAnalysisFields fields[2];
1367 : 0 : GstBuffer *outbuf = NULL;
1368 : :
1369 : 0 : queue = filter->frames;
1370 : :
1371 : : /* move previous result to res1 */
1372 : 0 : filter->results[1] = filter->results[0];
1373 : :
1374 : 0 : res0 = &filter->results[0]; /* results for current frame */
1375 : 0 : res1 = &filter->results[1]; /* results for previous frame */
1376 : :
1377 : : /* we have a ref on buf_to_queue when it is added to the queue */
1378 : 0 : g_queue_push_tail (queue, (gpointer) * buf_to_queue);
1379 : : /* WARNING: buf_to_queue must not be used again!!! */
1380 : 0 : *buf_to_queue = NULL;
1381 : :
1382 : 0 : n_queued = g_queue_get_length (queue);
1383 : :
1384 : : /* we do it like this because the first frame has no predecessor so this is
1385 : : * the only result we can get for it */
1386 [ # # ]: 0 : if (n_queued >= 1) {
1387 : : /* compare the fields within the buffer, if the buffer exhibits combing it
1388 : : * could be interlaced or a mixed telecine frame */
1389 : 0 : fields[0].buf = fields[1].buf = g_queue_peek_tail (queue);
1390 : 0 : fields[0].parity = TOP_FIELD;
1391 : 0 : fields[1].parity = BOTTOM_FIELD;
1392 : 0 : res0->f = filter->same_frame (filter, fields);
1393 : 0 : res0->t = res0->b = res0->t_b = res0->b_t = G_MAXINT64;
1394 [ # # ]: 0 : if (n_queued == 1)
1395 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "Scores: f %f, t , b , t_b , b_t ", res0->f);
1396 [ # # ]: 0 : if (res0->f <= filter->frame_thresh) {
1397 : 0 : res0->conclusion = FIELD_ANALYSIS_PROGRESSIVE;
1398 : : } else {
1399 : 0 : res0->conclusion = FIELD_ANALYSIS_INTERLACED;
1400 : : }
1401 : 0 : res0->holding = -1; /* needed fields unknown */
1402 : 0 : res0->gap = FALSE;
1403 : : }
1404 : :
1405 [ # # ]: 0 : if (n_queued >= 2) {
1406 : : guint telecine_matches;
1407 : 0 : gboolean first_buffer = filter->first_buffer;
1408 : :
1409 : 0 : filter->first_buffer = FALSE;
1410 : :
1411 : 0 : fields[1].buf = g_queue_peek_nth (queue, n_queued - 2);
1412 : :
1413 : : /* compare the top and bottom fields to the previous frame */
1414 : 0 : fields[0].parity = TOP_FIELD;
1415 : 0 : fields[1].parity = TOP_FIELD;
1416 : 0 : res0->t = filter->same_field (filter, fields);
1417 : 0 : fields[0].parity = BOTTOM_FIELD;
1418 : 0 : fields[1].parity = BOTTOM_FIELD;
1419 : 0 : res0->b = filter->same_field (filter, fields);
1420 : :
1421 : : /* compare the top field from this frame to the bottom of the previous for
1422 : : * for combing (and vice versa) */
1423 : 0 : fields[0].parity = TOP_FIELD;
1424 : 0 : fields[1].parity = BOTTOM_FIELD;
1425 : 0 : res0->t_b = filter->same_frame (filter, fields);
1426 : 0 : fields[0].parity = BOTTOM_FIELD;
1427 : 0 : fields[1].parity = TOP_FIELD;
1428 : 0 : res0->b_t = filter->same_frame (filter, fields);
1429 : :
1430 [ # # ]: 0 : GST_DEBUG_OBJECT (filter,
1431 : : "Scores: f %f, t %f, b %f, t_b %f, b_t %f", res0->f,
1432 : : res0->t, res0->b, res0->t_b, res0->b_t);
1433 : :
1434 : : /* analysis */
1435 : 0 : telecine_matches = 0;
1436 [ # # ]: 0 : if (res0->t_b <= filter->frame_thresh)
1437 : 0 : telecine_matches |= FIELD_ANALYSIS_TOP_BOTTOM;
1438 [ # # ]: 0 : if (res0->b_t <= filter->frame_thresh)
1439 : 0 : telecine_matches |= FIELD_ANALYSIS_BOTTOM_TOP;
1440 : : /* normally if there is a top or bottom field match, it is significantly
1441 : : * smaller than the other match - try 10% */
1442 [ # # ][ # # ]: 0 : if (res0->t <= filter->field_thresh || res0->t * (100 / 10) < res0->b)
1443 : 0 : telecine_matches |= FIELD_ANALYSIS_TOP_MATCH;
1444 [ # # ][ # # ]: 0 : if (res0->b <= filter->field_thresh || res0->b * (100 / 10) < res0->t)
1445 : 0 : telecine_matches |= FIELD_ANALYSIS_BOTTOM_MATCH;
1446 : :
1447 [ # # ]: 0 : if (telecine_matches & (FIELD_ANALYSIS_TOP_MATCH |
1448 : : FIELD_ANALYSIS_BOTTOM_MATCH)) {
1449 : : /* we have a repeated field => some kind of telecine */
1450 [ # # ]: 0 : if (res1->f <= filter->frame_thresh) {
1451 : : /* prev P */
1452 [ # # ]: 0 : if ((telecine_matches & FIELD_ANALYSIS_TOP_MATCH)
1453 [ # # ]: 0 : && (telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH)) {
1454 : : /* prev P, cur repeated => cur P */
1455 : 0 : res0->conclusion = FIELD_ANALYSIS_TELECINE_PROGRESSIVE;
1456 : 0 : res0->holding = 1 + BOTH_FIELDS;
1457 : : /* push prev P, GAP */
1458 : 0 : res1->gap = TRUE;
1459 : 0 : outbuf =
1460 : 0 : gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
1461 : : res1->gap);
1462 : : } else {
1463 : : /* prev P, cur t xor b matches => cur TCM */
1464 : 0 : res0->conclusion = FIELD_ANALYSIS_TELECINE_MIXED;
1465 : : /* hold non-repeated: if bottom match, hold top = 1 + 0 */
1466 [ # # ]: 0 : res0->holding = 1 + !(telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH);
1467 : : /* push prev P */
1468 : 0 : outbuf =
1469 : 0 : gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
1470 : : res1->gap);
1471 : : }
1472 : : } else {
1473 : : /* prev !P */
1474 : : gboolean b, t;
1475 : :
1476 [ # # ]: 0 : if (res0->f <= filter->frame_thresh) {
1477 : : /* cur P */
1478 : 0 : res0->conclusion = FIELD_ANALYSIS_TELECINE_PROGRESSIVE;
1479 : 0 : res0->holding = 1 + BOTH_FIELDS;
1480 : : } else {
1481 : : /* cur !P */
1482 : 0 : res0->conclusion = FIELD_ANALYSIS_TELECINE_MIXED;
1483 [ # # ]: 0 : if (telecine_matches & FIELD_ANALYSIS_TOP_MATCH
1484 [ # # ]: 0 : && telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH) {
1485 : : /* cur t && b */
1486 : 0 : res0->holding = 0;
1487 : : } else {
1488 : : /* cur t xor b; hold non-repeated */
1489 : 0 : res0->holding =
1490 [ # # ]: 0 : 1 + !(telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH);
1491 : : }
1492 : : }
1493 : :
1494 [ # # ]: 0 : if (res1->holding == -1) {
1495 : 0 : b = t = TRUE;
1496 : : } else {
1497 : 0 : b = res1->holding == 1 + BOTTOM_FIELD;
1498 : 0 : t = res1->holding == 1 + TOP_FIELD;
1499 : : }
1500 : :
1501 [ # # ][ # # ]: 0 : if ((t && telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH) || (b
[ # # ]
1502 [ # # ]: 0 : && telecine_matches & FIELD_ANALYSIS_TOP_MATCH)) {
1503 [ # # ][ # # ]: 0 : if (t && telecine_matches & FIELD_ANALYSIS_BOTTOM_MATCH) {
1504 : 0 : res1->holding = 1 + TOP_FIELD;
1505 [ # # ][ # # ]: 0 : } else if (b && telecine_matches & FIELD_ANALYSIS_TOP_MATCH) {
1506 : 0 : res1->holding = 1 + BOTTOM_FIELD;
1507 : : }
1508 : : /* push 1F held field */
1509 : 0 : outbuf =
1510 : 0 : gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
1511 : : res1->conclusion, res1->gap);
1512 [ # # ][ # # ]: 0 : } else if (res0->f > filter->frame_thresh && ((t
1513 [ # # ][ # # ]: 0 : && telecine_matches & FIELD_ANALYSIS_BOTTOM_TOP) || (b
1514 [ # # ]: 0 : && telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM))) {
1515 [ # # ][ # # ]: 0 : if (t && telecine_matches & FIELD_ANALYSIS_BOTTOM_TOP) {
1516 : 0 : res1->holding = 1 + TOP_FIELD;
1517 [ # # ][ # # ]: 0 : } else if (b && telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM) {
1518 : 0 : res1->holding = 1 + BOTTOM_FIELD;
1519 : : }
1520 : 0 : res0->conclusion = FIELD_ANALYSIS_TELECINE_MIXED;
1521 : : /* hold the opposite field to the one held in the last frame */
1522 [ # # ]: 0 : res0->holding = 1 + (res1->holding == 1 + TOP_FIELD);
1523 : : /* push 1F held field */
1524 : 0 : outbuf =
1525 : 0 : gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
1526 : : res1->conclusion, res1->gap);
1527 [ # # ][ # # ]: 0 : } else if (first_buffer && (telecine_matches & FIELD_ANALYSIS_BOTTOM_TOP
1528 [ # # ]: 0 : || telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM)) {
1529 : : /* non-matched field is an orphan in the first buffer - push orphan as 1F */
1530 : 0 : res1->conclusion = FIELD_ANALYSIS_TELECINE_MIXED;
1531 : : /* if prev b matched, prev t is orphan */
1532 [ # # ]: 0 : res1->holding = 1 + !(telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM);
1533 : : /* push 1F held field */
1534 : 0 : outbuf =
1535 : 0 : gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
1536 : : res1->conclusion, res1->gap);
1537 [ # # ][ # # ]: 0 : } else if (res1->holding == 1 + BOTH_FIELDS || res1->holding == -1) {
1538 : : /* holding both fields, push prev as is */
1539 : 0 : outbuf =
1540 : 0 : gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
1541 : : res1->gap);
1542 : : } else {
1543 : : /* push prev as is with GAP */
1544 : 0 : res1->gap = TRUE;
1545 : 0 : outbuf =
1546 : 0 : gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
1547 : : res1->gap);
1548 : : }
1549 : : }
1550 [ # # ]: 0 : } else if (res0->f <= filter->frame_thresh) {
1551 : : /* cur P */
1552 : 0 : res0->conclusion = FIELD_ANALYSIS_PROGRESSIVE;
1553 : 0 : res0->holding = 1 + BOTH_FIELDS;
1554 [ # # ][ # # ]: 0 : if (res1->holding == 1 + BOTH_FIELDS || res1->holding == -1) {
1555 : : /* holding both fields, push prev as is */
1556 : 0 : outbuf =
1557 : 0 : gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
1558 : : res1->gap);
1559 [ # # ]: 0 : } else if (res1->holding > 0) {
1560 : : /* holding one field, push prev 1F held */
1561 : 0 : outbuf =
1562 : 0 : gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
1563 : : res1->conclusion, res1->gap);
1564 : : } else {
1565 : : /* unknown or no fields held, push prev as is with GAP */
1566 : : /* this will push unknown as gap - should be pushed as not gap? */
1567 : 0 : res1->gap = TRUE;
1568 : 0 : outbuf =
1569 : 0 : gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
1570 : : res1->gap);
1571 : : }
1572 : : } else {
1573 : : /* cur !P */
1574 [ # # ]: 0 : if (telecine_matches & (FIELD_ANALYSIS_TOP_BOTTOM |
1575 : : FIELD_ANALYSIS_BOTTOM_TOP)) {
1576 : : /* cross-parity match => TCM */
1577 : : gboolean b, t;
1578 : :
1579 [ # # ]: 0 : if (res1->holding == -1) {
1580 : 0 : b = t = TRUE;
1581 : : } else {
1582 : 0 : b = res1->holding == 1 + BOTTOM_FIELD;
1583 : 0 : t = res1->holding == 1 + TOP_FIELD;
1584 : : }
1585 : :
1586 : 0 : res0->conclusion = FIELD_ANALYSIS_TELECINE_MIXED;
1587 : : /* leave holding as unknown */
1588 [ # # ]: 0 : if (res1->holding == 1 + BOTH_FIELDS) {
1589 : : /* prev P/TCP/I [or TCM repeated (weird case)] */
1590 : : /* push prev as is */
1591 : 0 : outbuf =
1592 : 0 : gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
1593 : : res1->gap);
1594 [ # # ][ # # ]: 0 : } else if ((t && telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM) || (b
[ # # ]
1595 [ # # ]: 0 : && telecine_matches & FIELD_ANALYSIS_BOTTOM_TOP)) {
1596 : : /* held is opposite to matched => need both field from prev */
1597 : : /* if t_b, hold bottom from prev and top from current, else vice-versa */
1598 : 0 : res1->holding = 1 + ! !(telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM);
1599 [ # # ]: 0 : res0->holding = 1 + !(telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM);
1600 : : /* push prev TCM */
1601 : 0 : outbuf =
1602 : 0 : gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
1603 : : res1->gap);
1604 [ # # ][ # # ]: 0 : } else if ((res1->holding > 0 && res1->holding != 1 + BOTH_FIELDS) || (t
[ # # ]
1605 [ # # ][ # # ]: 0 : && telecine_matches & FIELD_ANALYSIS_BOTTOM_TOP) || (b
1606 [ # # ]: 0 : && telecine_matches & FIELD_ANALYSIS_TOP_BOTTOM)) {
1607 : : /* held field is needed, push prev 1F held */
1608 : 0 : outbuf =
1609 : 0 : gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
1610 : : res1->conclusion, res1->gap);
1611 : : } else {
1612 : : /* holding none or unknown */
1613 : : /* push prev as is with GAP */
1614 : 0 : res1->gap = TRUE;
1615 : 0 : outbuf =
1616 : 0 : gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
1617 : : res1->gap);
1618 : : }
1619 : : } else {
1620 : : /* cur I */
1621 : 0 : res0->conclusion = FIELD_ANALYSIS_INTERLACED;
1622 : 0 : res0->holding = 1 + BOTH_FIELDS;
1623 : : /* push prev appropriately */
1624 : 0 : res1->gap = res1->holding <= 0;
1625 [ # # ]: 0 : if (res1->holding != 0) {
1626 : 0 : res1->gap = FALSE;
1627 [ # # ][ # # ]: 0 : if (res1->holding == 1 + BOTH_FIELDS || res1->holding == -1) {
1628 : : /* push prev as is */
1629 : 0 : outbuf =
1630 : 0 : gst_field_analysis_decorate (filter, -1, FALSE,
1631 : : res1->conclusion, res1->gap);
1632 : : } else {
1633 : : /* push prev 1F held */
1634 : 0 : outbuf =
1635 : 0 : gst_field_analysis_decorate (filter, !(res1->holding - 1), TRUE,
1636 : : res1->conclusion, res1->gap);
1637 : : }
1638 : : } else {
1639 : : /* push prev as is with GAP */
1640 : 0 : res1->gap = TRUE;
1641 : 0 : outbuf =
1642 : 0 : gst_field_analysis_decorate (filter, -1, FALSE, res1->conclusion,
1643 : : res1->gap);
1644 : : }
1645 : : }
1646 : : }
1647 : : }
1648 : :
1649 [ # # # # : 0 : switch (res0->conclusion) {
# ]
1650 : : case FIELD_ANALYSIS_PROGRESSIVE:
1651 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "Conclusion: PROGRESSIVE");
1652 : 0 : break;
1653 : : case FIELD_ANALYSIS_INTERLACED:
1654 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "Conclusion: INTERLACED");
1655 : 0 : break;
1656 : : case FIELD_ANALYSIS_TELECINE_PROGRESSIVE:
1657 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "Conclusion: TC PROGRESSIVE");
1658 : 0 : break;
1659 : : case FIELD_ANALYSIS_TELECINE_MIXED:
1660 [ # # ][ # # ]: 0 : GST_DEBUG_OBJECT (filter, "Conclusion: TC MIXED %s",
[ # # ]
1661 : : res0->holding ==
1662 : : 1 + BOTH_FIELDS ? "top and bottom" : res0->holding ==
1663 : : 1 + BOTTOM_FIELD ? "bottom" : "top");
1664 : 0 : break;
1665 : : default:
1666 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "Invalid conclusion! This is a bug!");
1667 : 0 : break;
1668 : : }
1669 : :
1670 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "Items remaining in the queue: %d",
1671 : : g_queue_get_length (queue));
1672 : :
1673 : 0 : return outbuf;
1674 : : }
1675 : :
1676 : : /* we have a ref on buf when it comes into chain */
1677 : : static GstFlowReturn
1678 : 0 : gst_field_analysis_chain (GstPad * pad, GstBuffer * buf)
1679 : : {
1680 : 0 : GstFlowReturn ret = GST_FLOW_OK;
1681 : : GstFieldAnalysis *filter;
1682 : 0 : GstBuffer *outbuf = NULL;
1683 : :
1684 : 0 : filter = GST_FIELDANALYSIS (GST_OBJECT_PARENT (pad));
1685 : :
1686 : 0 : GST_OBJECT_LOCK (filter);
1687 [ # # ]: 0 : if (filter->flushing) {
1688 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "We are flushing.");
1689 : : /* we have a ref on buf so it must be unreffed */
1690 : 0 : goto unref_unlock_ret;
1691 : : }
1692 : :
1693 [ # # ]: 0 : if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT)) {
1694 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "Discont: flushing");
1695 : : /* we should have a ref on outbuf, either because we had one when it entered
1696 : : * the queue and _make_metadata_writable () inside _decorate () returned
1697 : : * the same buffer or because it returned a new buffer on which we have one
1698 : : * ref */
1699 : 0 : outbuf = gst_field_analysis_flush_one (filter, NULL);
1700 : :
1701 [ # # ]: 0 : if (outbuf) {
1702 : : /* we give away our ref on outbuf here */
1703 : 0 : GST_OBJECT_UNLOCK (filter);
1704 : 0 : ret = gst_pad_push (filter->srcpad, outbuf);
1705 : 0 : GST_OBJECT_LOCK (filter);
1706 [ # # ]: 0 : if (filter->flushing) {
1707 [ # # ]: 0 : GST_DEBUG_OBJECT (filter, "We are flushing. outbuf already pushed.");
1708 : : /* we have a ref on buf so it must be unreffed */
1709 : 0 : goto unref_unlock_ret;
1710 : : }
1711 : : }
1712 : :
1713 : 0 : gst_field_analysis_reset (filter);
1714 : :
1715 [ # # ]: 0 : if (ret != GST_FLOW_OK) {
1716 [ # # ]: 0 : GST_DEBUG_OBJECT (filter,
1717 : : "Pushing of flushed buffer failed with return %d", ret);
1718 : : /* we have a ref on buf so it must be unreffed */
1719 : 0 : goto unref_unlock_ret;
1720 : : } else {
1721 : 0 : outbuf = NULL;
1722 : : }
1723 : : }
1724 : :
1725 : : /* after this function, buf has been pushed to the internal queue and its ref
1726 : : * retained there and we have a ref on outbuf */
1727 : 0 : outbuf = gst_field_analysis_process_buffer (filter, &buf);
1728 : :
1729 : 0 : GST_OBJECT_UNLOCK (filter);
1730 : :
1731 : : /* here we give up our ref on outbuf */
1732 [ # # ]: 0 : if (outbuf)
1733 : 0 : ret = gst_pad_push (filter->srcpad, outbuf);
1734 : :
1735 : 0 : return ret;
1736 : :
1737 : : unref_unlock_ret:
1738 : : /* we must unref the input buffer here */
1739 : 0 : gst_buffer_unref (buf);
1740 : 0 : GST_OBJECT_UNLOCK (filter);
1741 : 0 : return ret;
1742 : : }
1743 : :
1744 : : static GstStateChangeReturn
1745 : 34 : gst_field_analysis_change_state (GstElement * element,
1746 : : GstStateChange transition)
1747 : : {
1748 : : GstStateChangeReturn ret;
1749 : 34 : GstFieldAnalysis *filter = GST_FIELDANALYSIS (element);
1750 : :
1751 [ + + + + ]: 34 : switch (transition) {
1752 : : case GST_STATE_CHANGE_NULL_TO_READY:
1753 : 4 : break;
1754 : : case GST_STATE_CHANGE_READY_TO_PAUSED:
1755 : 7 : break;
1756 : : case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1757 : 6 : break;
1758 : : default:
1759 : 17 : break;
1760 : : }
1761 : :
1762 : 34 : ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1763 [ - + ]: 34 : if (ret != GST_STATE_CHANGE_SUCCESS)
1764 : 0 : return ret;
1765 : :
1766 [ + + + ]: 34 : switch (transition) {
1767 : : case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1768 : 6 : break;
1769 : : case GST_STATE_CHANGE_PAUSED_TO_READY:
1770 : 7 : gst_field_analysis_reset (filter);
1771 : 7 : break;
1772 : : case GST_STATE_CHANGE_READY_TO_NULL:
1773 : : default:
1774 : 21 : break;
1775 : : }
1776 : :
1777 : 34 : return ret;
1778 : : }
1779 : :
1780 : : static void
1781 : 4 : gst_field_analysis_finalize (GObject * object)
1782 : : {
1783 : 4 : GstFieldAnalysis *filter = GST_FIELDANALYSIS (object);
1784 : :
1785 : 4 : gst_field_analysis_reset (filter);
1786 : 4 : g_queue_free (filter->frames);
1787 : :
1788 : 4 : G_OBJECT_CLASS (parent_class)->finalize (object);
1789 : 4 : }
1790 : :
1791 : :
1792 : : static gboolean
1793 : 6 : fieldanalysis_init (GstPlugin * fieldanalysis)
1794 : : {
1795 [ + - ]: 6 : GST_DEBUG_CATEGORY_INIT (gst_field_analysis_debug, "fieldanalysis",
1796 : : 0, "Video field analysis");
1797 : :
1798 : 6 : gst_fieldanalysis_orc_init ();
1799 : :
1800 : 6 : return gst_element_register (fieldanalysis, "fieldanalysis", GST_RANK_NONE,
1801 : : GST_TYPE_FIELDANALYSIS);
1802 : : }
1803 : :
1804 : : GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1805 : : GST_VERSION_MINOR,
1806 : : "fieldanalysis",
1807 : : "Video field analysis",
1808 : : fieldanalysis_init, VERSION, "LGPL", "GStreamer", "http://gstreamer.net/")
|