1 /* GStreamer Element
2  *
3  * Copyright 2011 Collabora Ltd.
4  *  @author: Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
5  * Copyright 2011 Nokia Corp.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include <string.h>
27 
28 #include <gst/gst.h>
29 #include <gst/base/gstcollectpads.h>
30 #include <gst/video/video.h>
31 
32 #include "gstcompare.h"
33 
34 GST_DEBUG_CATEGORY_STATIC (compare_debug);
35 #define GST_CAT_DEFAULT   compare_debug
36 
37 
38 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
39     GST_PAD_SRC,
40     GST_PAD_ALWAYS,
41     GST_STATIC_CAPS_ANY);
42 
43 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
44     GST_PAD_SINK,
45     GST_PAD_ALWAYS,
46     GST_STATIC_CAPS_ANY);
47 
48 static GstStaticPadTemplate check_sink_factory =
49 GST_STATIC_PAD_TEMPLATE ("check",
50     GST_PAD_SINK,
51     GST_PAD_ALWAYS,
52     GST_STATIC_CAPS_ANY);
53 
54 enum GstCompareMethod
55 {
56   GST_COMPARE_METHOD_MEM,
57   GST_COMPARE_METHOD_MAX,
58   GST_COMPARE_METHOD_SSIM
59 };
60 
61 #define GST_COMPARE_METHOD_TYPE (gst_compare_method_get_type())
62 static GType
gst_compare_method_get_type(void)63 gst_compare_method_get_type (void)
64 {
65   static GType method_type = 0;
66 
67   static const GEnumValue method_types[] = {
68     {GST_COMPARE_METHOD_MEM, "Memory", "mem"},
69     {GST_COMPARE_METHOD_MAX, "Maximum metric", "max"},
70     {GST_COMPARE_METHOD_SSIM, "SSIM (raw video)", "ssim"},
71     {0, NULL, NULL}
72   };
73 
74   if (!method_type) {
75     method_type = g_enum_register_static ("GstCompareMethod", method_types);
76   }
77   return method_type;
78 }
79 
80 /* Filter signals and args */
81 enum
82 {
83   /* FILL ME */
84   LAST_SIGNAL
85 };
86 
87 enum
88 {
89   PROP_0,
90   PROP_META,
91   PROP_OFFSET_TS,
92   PROP_METHOD,
93   PROP_THRESHOLD,
94   PROP_UPPER
95 };
96 
97 #define DEFAULT_META             GST_BUFFER_COPY_ALL
98 #define DEFAULT_OFFSET_TS        FALSE
99 #define DEFAULT_METHOD           GST_COMPARE_METHOD_MEM
100 #define DEFAULT_THRESHOLD        0
101 #define DEFAULT_UPPER            TRUE
102 
103 static void gst_compare_set_property (GObject * object,
104     guint prop_id, const GValue * value, GParamSpec * pspec);
105 static void gst_compare_get_property (GObject * object,
106     guint prop_id, GValue * value, GParamSpec * pspec);
107 
108 static void gst_compare_reset (GstCompare * overlay);
109 
110 static gboolean gst_compare_query (GstPad * pad, GstObject * parent,
111     GstQuery * query);
112 static GstFlowReturn gst_compare_collect_pads (GstCollectPads * cpads,
113     GstCompare * comp);
114 
115 static GstStateChangeReturn gst_compare_change_state (GstElement * element,
116     GstStateChange transition);
117 
118 #define gst_compare_parent_class parent_class
119 G_DEFINE_TYPE (GstCompare, gst_compare, GST_TYPE_ELEMENT);
120 
121 static void
gst_compare_finalize(GObject * object)122 gst_compare_finalize (GObject * object)
123 {
124   GstCompare *comp = GST_COMPARE (object);
125 
126   gst_object_unref (comp->cpads);
127 
128   G_OBJECT_CLASS (parent_class)->finalize (object);
129 }
130 
131 static void
gst_compare_class_init(GstCompareClass * klass)132 gst_compare_class_init (GstCompareClass * klass)
133 {
134   GObjectClass *gobject_class;
135   GstElementClass *gstelement_class;
136 
137   gobject_class = (GObjectClass *) klass;
138   gstelement_class = (GstElementClass *) klass;
139 
140   GST_DEBUG_CATEGORY_INIT (compare_debug, "compare", 0, "Compare buffers");
141 
142   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_compare_change_state);
143 
144   gobject_class->set_property = gst_compare_set_property;
145   gobject_class->get_property = gst_compare_get_property;
146   gobject_class->finalize = gst_compare_finalize;
147 
148   g_object_class_install_property (gobject_class, PROP_META,
149       g_param_spec_flags ("meta", "Compare Meta",
150           "Indicates which metadata should be compared",
151           gst_buffer_copy_flags_get_type (), DEFAULT_META,
152           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
153   g_object_class_install_property (gobject_class, PROP_OFFSET_TS,
154       g_param_spec_boolean ("offset-ts", "Offsets Timestamps",
155           "Consider OFFSET and OFFSET_END part of timestamp metadata",
156           DEFAULT_OFFSET_TS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
157   g_object_class_install_property (gobject_class, PROP_METHOD,
158       g_param_spec_enum ("method", "Content Compare Method",
159           "Method to compare buffer content",
160           GST_COMPARE_METHOD_TYPE, DEFAULT_METHOD,
161           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
162   g_object_class_install_property (gobject_class, PROP_THRESHOLD,
163       g_param_spec_double ("threshold", "Content Threshold",
164           "Threshold beyond which to consider content different as determined by content-method",
165           0, G_MAXDOUBLE, DEFAULT_THRESHOLD,
166           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
167   g_object_class_install_property (gobject_class, PROP_UPPER,
168       g_param_spec_boolean ("upper", "Threshold Upper Bound",
169           "Whether threshold value is upper bound or lower bound for difference measure",
170           DEFAULT_UPPER, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
171 
172   gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
173   gst_element_class_add_static_pad_template (gstelement_class, &sink_factory);
174   gst_element_class_add_static_pad_template (gstelement_class,
175       &check_sink_factory);
176   gst_element_class_set_static_metadata (gstelement_class, "Compare buffers",
177       "Filter/Debug", "Compares incoming buffers",
178       "Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>");
179 }
180 
181 static void
gst_compare_init(GstCompare * comp)182 gst_compare_init (GstCompare * comp)
183 {
184   comp->cpads = gst_collect_pads_new ();
185   gst_collect_pads_set_function (comp->cpads,
186       (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_compare_collect_pads),
187       comp);
188 
189   comp->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
190   GST_PAD_SET_PROXY_CAPS (comp->sinkpad);
191   gst_element_add_pad (GST_ELEMENT (comp), comp->sinkpad);
192 
193   comp->checkpad =
194       gst_pad_new_from_static_template (&check_sink_factory, "check");
195   gst_pad_set_query_function (comp->checkpad, gst_compare_query);
196   gst_element_add_pad (GST_ELEMENT (comp), comp->checkpad);
197 
198   gst_collect_pads_add_pad (comp->cpads, comp->sinkpad,
199       sizeof (GstCollectData), NULL, TRUE);
200   gst_collect_pads_add_pad (comp->cpads, comp->checkpad,
201       sizeof (GstCollectData), NULL, TRUE);
202 
203   comp->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
204   gst_pad_set_query_function (comp->srcpad, gst_compare_query);
205   gst_element_add_pad (GST_ELEMENT (comp), comp->srcpad);
206 
207   /* init properties */
208   comp->meta = DEFAULT_META;
209   comp->offset_ts = DEFAULT_OFFSET_TS;
210   comp->method = DEFAULT_METHOD;
211   comp->threshold = DEFAULT_THRESHOLD;
212   comp->upper = DEFAULT_UPPER;
213 
214   gst_compare_reset (comp);
215 }
216 
217 static void
gst_compare_reset(GstCompare * comp)218 gst_compare_reset (GstCompare * comp)
219 {
220 }
221 
222 static gboolean
gst_compare_query(GstPad * pad,GstObject * parent,GstQuery * query)223 gst_compare_query (GstPad * pad, GstObject * parent, GstQuery * query)
224 {
225   GstCompare *comp;
226   GstPad *otherpad;
227 
228   comp = GST_COMPARE (parent);
229   otherpad = (pad == comp->srcpad ? comp->sinkpad : comp->srcpad);
230 
231   return gst_pad_peer_query (otherpad, query);
232 }
233 
234 static void
gst_compare_meta(GstCompare * comp,GstBuffer * buf1,GstCaps * caps1,GstBuffer * buf2,GstCaps * caps2)235 gst_compare_meta (GstCompare * comp, GstBuffer * buf1, GstCaps * caps1,
236     GstBuffer * buf2, GstCaps * caps2)
237 {
238   gint flags = 0;
239 
240   if (comp->meta & GST_BUFFER_COPY_FLAGS) {
241     if (GST_BUFFER_FLAGS (buf1) != GST_BUFFER_FLAGS (buf2)) {
242       flags |= GST_BUFFER_COPY_FLAGS;
243       GST_DEBUG_OBJECT (comp, "flags %d != flags %d", GST_BUFFER_FLAGS (buf1),
244           GST_BUFFER_FLAGS (buf2));
245     }
246   }
247   if (comp->meta & GST_BUFFER_COPY_TIMESTAMPS) {
248     if (GST_BUFFER_TIMESTAMP (buf1) != GST_BUFFER_TIMESTAMP (buf2)) {
249       flags |= GST_BUFFER_COPY_TIMESTAMPS;
250       GST_DEBUG_OBJECT (comp,
251           "ts %" GST_TIME_FORMAT " != ts %" GST_TIME_FORMAT,
252           GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf1)),
253           GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf2)));
254     }
255     if (GST_BUFFER_DURATION (buf1) != GST_BUFFER_DURATION (buf2)) {
256       flags |= GST_BUFFER_COPY_TIMESTAMPS;
257       GST_DEBUG_OBJECT (comp,
258           "dur %" GST_TIME_FORMAT " != dur %" GST_TIME_FORMAT,
259           GST_TIME_ARGS (GST_BUFFER_DURATION (buf1)),
260           GST_TIME_ARGS (GST_BUFFER_DURATION (buf2)));
261     }
262     if (comp->offset_ts) {
263       if (GST_BUFFER_OFFSET (buf1) != GST_BUFFER_OFFSET (buf2)) {
264         flags |= GST_BUFFER_COPY_TIMESTAMPS;
265         GST_DEBUG_OBJECT (comp,
266             "offset %" G_GINT64_FORMAT " != offset %" G_GINT64_FORMAT,
267             GST_BUFFER_OFFSET (buf1), GST_BUFFER_OFFSET (buf2));
268       }
269       if (GST_BUFFER_OFFSET_END (buf1) != GST_BUFFER_OFFSET_END (buf2)) {
270         flags |= GST_BUFFER_COPY_TIMESTAMPS;
271         GST_DEBUG_OBJECT (comp,
272             "offset_end %" G_GINT64_FORMAT " != offset_end %" G_GINT64_FORMAT,
273             GST_BUFFER_OFFSET_END (buf1), GST_BUFFER_OFFSET_END (buf2));
274       }
275     }
276   }
277 #if 0
278   /* FIXME ?? */
279   if (comp->meta & GST_BUFFER_COPY_CAPS) {
280     if (!gst_caps_is_equal (caps1, caps2)) {
281       flags |= GST_BUFFER_COPY_CAPS;
282       GST_DEBUG_OBJECT (comp,
283           "caps %" GST_PTR_FORMAT " != caps %" GST_PTR_FORMAT, caps1, caps2);
284     }
285   }
286 #endif
287 
288   /* signal mismatch by debug and message */
289   if (flags) {
290     GST_WARNING_OBJECT (comp, "buffers %p and %p failed metadata match %d",
291         buf1, buf2, flags);
292 
293     gst_element_post_message (GST_ELEMENT (comp),
294         gst_message_new_element (GST_OBJECT (comp),
295             gst_structure_new ("delta", "meta", G_TYPE_INT, flags, NULL)));
296   }
297 }
298 
299 /* when comparing contents, it is already ensured sizes are equal */
300 
301 static gint
gst_compare_mem(GstCompare * comp,GstBuffer * buf1,GstCaps * caps1,GstBuffer * buf2,GstCaps * caps2)302 gst_compare_mem (GstCompare * comp, GstBuffer * buf1, GstCaps * caps1,
303     GstBuffer * buf2, GstCaps * caps2)
304 {
305   GstMapInfo map1, map2;
306   gint c;
307 
308   gst_buffer_map (buf1, &map1, GST_MAP_READ);
309   gst_buffer_map (buf2, &map2, GST_MAP_READ);
310 
311   c = memcmp (map1.data, map2.data, map1.size);
312 
313   gst_buffer_unmap (buf1, &map1);
314   gst_buffer_unmap (buf2, &map2);
315 
316   return c ? 1 : 0;
317 }
318 
319 static gint
gst_compare_max(GstCompare * comp,GstBuffer * buf1,GstCaps * caps1,GstBuffer * buf2,GstCaps * caps2)320 gst_compare_max (GstCompare * comp, GstBuffer * buf1, GstCaps * caps1,
321     GstBuffer * buf2, GstCaps * caps2)
322 {
323   gint i, delta = 0;
324   gint8 *data1, *data2;
325   GstMapInfo map1, map2;
326 
327   gst_buffer_map (buf1, &map1, GST_MAP_READ);
328   gst_buffer_map (buf2, &map2, GST_MAP_READ);
329 
330   data1 = (gint8 *) map1.data;
331   data2 = (gint8 *) map2.data;
332 
333   /* primitive loop */
334   for (i = 0; i < map1.size; i++) {
335     gint diff = ABS (*data1 - *data2);
336     if (diff > 0)
337       GST_LOG_OBJECT (comp, "diff at %d = %d", i, diff);
338     delta = MAX (delta, ABS (*data1 - *data2));
339     data1++;
340     data2++;
341   }
342 
343   gst_buffer_unmap (buf1, &map1);
344   gst_buffer_unmap (buf2, &map2);
345 
346   return delta;
347 }
348 
349 static double
gst_compare_ssim_window(GstCompare * comp,guint8 * data1,guint8 * data2,gint width,gint height,gint step,gint stride)350 gst_compare_ssim_window (GstCompare * comp, guint8 * data1, guint8 * data2,
351     gint width, gint height, gint step, gint stride)
352 {
353   gint count = 0, i, j;
354   gint sum1 = 0, sum2 = 0, ssum1 = 0, ssum2 = 0, acov = 0;
355   gdouble avg1, avg2, var1, var2, cov;
356 
357   const gdouble k1 = 0.01;
358   const gdouble k2 = 0.03;
359   const gdouble L = 255.0;
360   const gdouble c1 = (k1 * L) * (k1 * L);
361   const gdouble c2 = (k2 * L) * (k2 * L);
362 
363   /* For empty images, return maximum similarity */
364   if (height <= 0 || width <= 0)
365     return 1.0;
366 
367   /* plain and simple; no fancy optimizations */
368   for (i = 0; i < height; i++) {
369     for (j = 0; j < width; j++) {
370       sum1 += *data1;
371       sum2 += *data2;
372       ssum1 += *data1 * *data1;
373       ssum2 += *data2 * *data2;
374       acov += *data1 * *data2;
375       count++;
376       data1 += step;
377       data2 += step;
378     }
379     data1 -= j * step;
380     data2 -= j * step;
381     data1 += stride;
382     data2 += stride;
383   }
384 
385   avg1 = sum1 / count;
386   avg2 = sum2 / count;
387   var1 = ssum1 / count - avg1 * avg1;
388   var2 = ssum2 / count - avg2 * avg2;
389   cov = acov / count - avg1 * avg2;
390 
391   return (2 * avg1 * avg2 + c1) * (2 * cov + c2) /
392       ((avg1 * avg1 + avg2 * avg2 + c1) * (var1 + var2 + c2));
393 }
394 
395 /* @width etc are for the particular component */
396 static gdouble
gst_compare_ssim_component(GstCompare * comp,guint8 * data1,guint8 * data2,gint width,gint height,gint step,gint stride)397 gst_compare_ssim_component (GstCompare * comp, guint8 * data1, guint8 * data2,
398     gint width, gint height, gint step, gint stride)
399 {
400   const gint window = 16;
401   gdouble ssim_sum = 0;
402   gint count = 0, i, j;
403 
404   for (j = 0; j + (window / 2) < height; j += (window / 2)) {
405     for (i = 0; i + (window / 2) < width; i += (window / 2)) {
406       gdouble ssim;
407 
408       ssim = gst_compare_ssim_window (comp, data1 + step * i + j * stride,
409           data2 + step * i + j * stride,
410           MIN (window, width - i), MIN (window, height - j), step, stride);
411       GST_LOG_OBJECT (comp, "ssim for %dx%d at (%d, %d) = %f", window, window,
412           i, j, ssim);
413       ssim_sum += ssim;
414       count++;
415     }
416   }
417 
418   /* For empty images, return maximum similarity */
419   if (count == 0)
420     return 1.0;
421 
422   return (ssim_sum / count);
423 }
424 
425 static gdouble
gst_compare_ssim(GstCompare * comp,GstBuffer * buf1,GstCaps * caps1,GstBuffer * buf2,GstCaps * caps2)426 gst_compare_ssim (GstCompare * comp, GstBuffer * buf1, GstCaps * caps1,
427     GstBuffer * buf2, GstCaps * caps2)
428 {
429   GstVideoInfo info1, info2;
430   GstVideoFrame frame1, frame2;
431   gint i, comps;
432   gdouble cssim[4], ssim, c[4] = { 1.0, 0.0, 0.0, 0.0 };
433 
434   if (!caps1)
435     goto invalid_input;
436 
437   if (!gst_video_info_from_caps (&info1, caps1))
438     goto invalid_input;
439 
440   if (!caps2)
441     goto invalid_input;
442 
443   if (!gst_video_info_from_caps (&info2, caps1))
444     goto invalid_input;
445 
446   if (GST_VIDEO_INFO_FORMAT (&info1) != GST_VIDEO_INFO_FORMAT (&info2) ||
447       GST_VIDEO_INFO_WIDTH (&info1) != GST_VIDEO_INFO_WIDTH (&info2) ||
448       GST_VIDEO_INFO_HEIGHT (&info1) != GST_VIDEO_INFO_HEIGHT (&info2))
449     return comp->threshold + 1;
450 
451   comps = GST_VIDEO_INFO_N_COMPONENTS (&info1);
452   /* note that some are reported both yuv and gray */
453   for (i = 0; i < comps; ++i)
454     c[i] = 1.0;
455   /* increase luma weight if yuv */
456   if (GST_VIDEO_INFO_IS_YUV (&info1) && (comps > 1))
457     c[0] = comps - 1;
458   for (i = 0; i < comps; ++i)
459     c[i] /= (GST_VIDEO_INFO_IS_YUV (&info1) && (comps > 1)) ?
460         2 * (comps - 1) : comps;
461 
462   gst_video_frame_map (&frame1, &info1, buf1, GST_MAP_READ);
463   gst_video_frame_map (&frame2, &info2, buf2, GST_MAP_READ);
464 
465   for (i = 0; i < comps; i++) {
466     gint cw, ch, step, stride;
467 
468     /* only support most common formats */
469     if (GST_VIDEO_INFO_COMP_DEPTH (&info1, i) != 8)
470       goto unsupported_input;
471     cw = GST_VIDEO_FRAME_COMP_WIDTH (&frame1, i);
472     ch = GST_VIDEO_FRAME_COMP_HEIGHT (&frame1, i);
473     step = GST_VIDEO_FRAME_COMP_PSTRIDE (&frame1, i);
474     stride = GST_VIDEO_FRAME_COMP_STRIDE (&frame1, i);
475 
476     GST_LOG_OBJECT (comp, "component %d", i);
477     cssim[i] = gst_compare_ssim_component (comp,
478         GST_VIDEO_FRAME_COMP_DATA (&frame1, i),
479         GST_VIDEO_FRAME_COMP_DATA (&frame2, i), cw, ch, step, stride);
480     GST_LOG_OBJECT (comp, "ssim[%d] = %f", i, cssim[i]);
481   }
482 
483   gst_video_frame_unmap (&frame1);
484   gst_video_frame_unmap (&frame2);
485 
486 #ifndef GST_DISABLE_GST_DEBUG
487   for (i = 0; i < 4; i++) {
488     GST_DEBUG_OBJECT (comp, "ssim[%d] = %f, c[%d] = %f", i, cssim[i], i, c[i]);
489   }
490 #endif
491 
492   ssim = cssim[0] * c[0] + cssim[1] * c[1] + cssim[2] * c[2] + cssim[3] * c[3];
493 
494   return ssim;
495 
496   /* ERRORS */
497 invalid_input:
498   {
499     GST_ERROR_OBJECT (comp, "ssim method needs raw video input");
500     return 0;
501   }
502 unsupported_input:
503   {
504     GST_ERROR_OBJECT (comp, "raw video format not supported %" GST_PTR_FORMAT,
505         caps1);
506     return 0;
507   }
508 }
509 
510 static void
gst_compare_buffers(GstCompare * comp,GstBuffer * buf1,GstCaps * caps1,GstBuffer * buf2,GstCaps * caps2)511 gst_compare_buffers (GstCompare * comp, GstBuffer * buf1, GstCaps * caps1,
512     GstBuffer * buf2, GstCaps * caps2)
513 {
514   gdouble delta = 0;
515   gsize size1, size2;
516 
517   /* first check metadata */
518   gst_compare_meta (comp, buf1, caps1, buf2, caps2);
519 
520   size1 = gst_buffer_get_size (buf1);
521   size2 = gst_buffer_get_size (buf1);
522 
523   /* check content according to method */
524   /* but at least size should match */
525   if (size1 != size2) {
526     delta = comp->threshold + 1;
527   } else {
528     GstMapInfo map1, map2;
529 
530     gst_buffer_map (buf1, &map1, GST_MAP_READ);
531     gst_buffer_map (buf2, &map2, GST_MAP_READ);
532     GST_MEMDUMP_OBJECT (comp, "buffer 1", map1.data, map2.size);
533     GST_MEMDUMP_OBJECT (comp, "buffer 2", map2.data, map2.size);
534     gst_buffer_unmap (buf1, &map1);
535     gst_buffer_unmap (buf2, &map2);
536     switch (comp->method) {
537       case GST_COMPARE_METHOD_MEM:
538         delta = gst_compare_mem (comp, buf1, caps1, buf2, caps2);
539         break;
540       case GST_COMPARE_METHOD_MAX:
541         delta = gst_compare_max (comp, buf1, caps1, buf2, caps2);
542         break;
543       case GST_COMPARE_METHOD_SSIM:
544         delta = gst_compare_ssim (comp, buf1, caps1, buf2, caps2);
545         break;
546       default:
547         g_assert_not_reached ();
548         break;
549     }
550   }
551 
552   if ((comp->upper && delta > comp->threshold) ||
553       (!comp->upper && delta < comp->threshold)) {
554     GST_WARNING_OBJECT (comp, "buffers %p and %p failed content match %f",
555         buf1, buf2, delta);
556 
557     gst_element_post_message (GST_ELEMENT (comp),
558         gst_message_new_element (GST_OBJECT (comp),
559             gst_structure_new ("delta", "content", G_TYPE_DOUBLE, delta,
560                 NULL)));
561   }
562 }
563 
564 static GstFlowReturn
gst_compare_collect_pads(GstCollectPads * cpads,GstCompare * comp)565 gst_compare_collect_pads (GstCollectPads * cpads, GstCompare * comp)
566 {
567   GstBuffer *buf1, *buf2;
568   GstCaps *caps1, *caps2;
569 
570   buf1 = gst_collect_pads_pop (comp->cpads,
571       gst_pad_get_element_private (comp->sinkpad));
572   caps1 = gst_pad_get_current_caps (comp->sinkpad);
573 
574   buf2 = gst_collect_pads_pop (comp->cpads,
575       gst_pad_get_element_private (comp->checkpad));
576   caps2 = gst_pad_get_current_caps (comp->checkpad);
577 
578   if (!buf1 && !buf2) {
579     gst_pad_push_event (comp->srcpad, gst_event_new_eos ());
580     return GST_FLOW_EOS;
581   } else if (buf1 && buf2) {
582     gst_compare_buffers (comp, buf1, caps1, buf2, caps2);
583   } else {
584     GST_WARNING_OBJECT (comp, "buffer %p != NULL", buf1 ? buf1 : buf2);
585 
586     comp->count++;
587     gst_element_post_message (GST_ELEMENT (comp),
588         gst_message_new_element (GST_OBJECT (comp),
589             gst_structure_new ("delta", "count", G_TYPE_INT, comp->count,
590                 NULL)));
591   }
592 
593   if (buf1)
594     gst_pad_push (comp->srcpad, buf1);
595 
596   if (buf2)
597     gst_buffer_unref (buf2);
598 
599   if (caps1)
600     gst_caps_unref (caps1);
601 
602   if (caps2)
603     gst_caps_unref (caps2);
604 
605   return GST_FLOW_OK;
606 }
607 
608 static void
gst_compare_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)609 gst_compare_set_property (GObject * object, guint prop_id,
610     const GValue * value, GParamSpec * pspec)
611 {
612   GstCompare *comp = GST_COMPARE (object);
613 
614   switch (prop_id) {
615     case PROP_META:
616       comp->meta = g_value_get_flags (value);
617       break;
618     case PROP_OFFSET_TS:
619       comp->offset_ts = g_value_get_boolean (value);
620       break;
621     case PROP_METHOD:
622       comp->method = g_value_get_enum (value);
623       break;
624     case PROP_THRESHOLD:
625       comp->threshold = g_value_get_double (value);
626       break;
627     case PROP_UPPER:
628       comp->upper = g_value_get_boolean (value);
629       break;
630     default:
631       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
632       break;
633   }
634 }
635 
636 static void
gst_compare_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)637 gst_compare_get_property (GObject * object, guint prop_id, GValue * value,
638     GParamSpec * pspec)
639 {
640   GstCompare *comp = GST_COMPARE (object);
641 
642   switch (prop_id) {
643     case PROP_META:
644       g_value_set_flags (value, comp->meta);
645       break;
646     case PROP_OFFSET_TS:
647       g_value_set_boolean (value, comp->offset_ts);
648       break;
649     case PROP_METHOD:
650       g_value_set_enum (value, comp->method);
651       break;
652     case PROP_THRESHOLD:
653       g_value_set_double (value, comp->threshold);
654       break;
655     case PROP_UPPER:
656       g_value_set_boolean (value, comp->upper);
657       break;
658     default:
659       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
660       break;
661   }
662 }
663 
664 static GstStateChangeReturn
gst_compare_change_state(GstElement * element,GstStateChange transition)665 gst_compare_change_state (GstElement * element, GstStateChange transition)
666 {
667   GstCompare *comp = GST_COMPARE (element);
668   GstStateChangeReturn ret;
669 
670   switch (transition) {
671     case GST_STATE_CHANGE_NULL_TO_READY:
672     case GST_STATE_CHANGE_READY_TO_PAUSED:
673       gst_collect_pads_start (comp->cpads);
674       break;
675     case GST_STATE_CHANGE_PAUSED_TO_READY:
676       gst_collect_pads_stop (comp->cpads);
677       break;
678     default:
679       break;
680   }
681 
682   ret = GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS, change_state,
683       (element, transition), GST_STATE_CHANGE_SUCCESS);
684   if (ret != GST_STATE_CHANGE_SUCCESS)
685     return ret;
686 
687   switch (transition) {
688     case GST_STATE_CHANGE_PAUSED_TO_READY:
689       gst_compare_reset (comp);
690       break;
691     default:
692       break;
693   }
694 
695   return GST_STATE_CHANGE_SUCCESS;
696 }
697