c27bcd9f516ce0852e50b5b0e4ee6ef446a61b5b
[platform/upstream/gstreamer.git] / ext / iqa / iqa.c
1 /* Image Quality Assessment plugin
2  * Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@collabora.co.uk>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /**
21  * SECTION:element-iqa
22  * @title: iqa
23  * @short_description: Image Quality Assessment plugin.
24  *
25  * IQA will perform full reference image quality assessment, with the
26  * first added pad being the reference.
27  *
28  * It will perform comparisons on video streams with the same geometry.
29  *
30  * The image output will be the heat map of differences, between
31  * the two pads with the highest measured difference.
32  *
33  * For each reference frame, IQA will post a message containing
34  * a structure named IQA.
35  *
36  * The only metric supported for now is "dssim", which will be available
37  * if https://github.com/pornel/dssim was installed on the system
38  * at the time that plugin was compiled.
39  *
40  * For each metric activated, this structure will contain another
41  * structure, named after the metric.
42  *
43  * The message will also contain a "time" field.
44  *
45  * For example, if do-dssim is set to true, and there are
46  * two compared streams, the emitted structure will look like this:
47  *
48  * IQA, dssim=(structure)"dssim\,\ sink_1\=\(double\)0.053621271267184856\,\
49  * sink_2\=\(double\)0.0082939683976297474\;",
50  * time=(guint64)0;
51  *
52  * ## Example launch line
53  * |[
54  * gst-launch-1.0 -m uridecodebin uri=file:///test/file/1 ! iqa name=iqa do-dssim=true \
55  * ! videoconvert ! autovideosink uridecodebin uri=file:///test/file/2 ! iqa.
56  * ]| This pipeline will output messages to the console for each set of compared frames.
57  *
58  */
59
60 #ifdef HAVE_CONFIG_H
61 #include "config.h"
62 #endif
63
64 #include "iqa.h"
65
66 #ifdef HAVE_DSSIM
67 #include "dssim.h"
68 #endif
69
70 GST_DEBUG_CATEGORY_STATIC (gst_iqa_debug);
71 #define GST_CAT_DEFAULT gst_iqa_debug
72
73 #define SINK_FORMATS " { AYUV, BGRA, ARGB, RGBA, ABGR, Y444, Y42B, YUY2, UYVY, "\
74                 "   YVYU, I420, YV12, NV12, NV21, Y41B, RGB, BGR, xRGB, xBGR, "\
75                 "   RGBx, BGRx } "
76
77 #define SRC_FORMAT " { RGBA } "
78 #define DEFAULT_DSSIM_ERROR_THRESHOLD -1.0
79
80 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
81     GST_PAD_SRC,
82     GST_PAD_ALWAYS,
83     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (SRC_FORMAT))
84     );
85
86 enum
87 {
88   PROP_0,
89   PROP_DO_SSIM,
90   PROP_SSIM_ERROR_THRESHOLD,
91   PROP_MODE,
92   PROP_LAST,
93 };
94
95 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u",
96     GST_PAD_SINK,
97     GST_PAD_REQUEST,
98     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (SINK_FORMATS))
99     );
100
101 /* Child proxy implementation */
102 static GObject *
103 gst_iqa_child_proxy_get_child_by_index (GstChildProxy * child_proxy,
104     guint index)
105 {
106   GstIqa *iqa = GST_IQA (child_proxy);
107   GObject *obj = NULL;
108
109   GST_OBJECT_LOCK (iqa);
110   obj = g_list_nth_data (GST_ELEMENT_CAST (iqa)->sinkpads, index);
111   if (obj)
112     gst_object_ref (obj);
113   GST_OBJECT_UNLOCK (iqa);
114
115   return obj;
116 }
117
118 static guint
119 gst_iqa_child_proxy_get_children_count (GstChildProxy * child_proxy)
120 {
121   guint count = 0;
122   GstIqa *iqa = GST_IQA (child_proxy);
123
124   GST_OBJECT_LOCK (iqa);
125   count = GST_ELEMENT_CAST (iqa)->numsinkpads;
126   GST_OBJECT_UNLOCK (iqa);
127   GST_INFO_OBJECT (iqa, "Children Count: %d", count);
128
129   return count;
130 }
131
132 static void
133 gst_iqa_child_proxy_init (gpointer g_iface, gpointer iface_data)
134 {
135   GstChildProxyInterface *iface = g_iface;
136
137   iface->get_child_by_index = gst_iqa_child_proxy_get_child_by_index;
138   iface->get_children_count = gst_iqa_child_proxy_get_children_count;
139 }
140
141 /**
142  * GstIqaMode:
143  * @GST_IQA_MODE_STRICT: Strict checks of the frames is enabled, this for
144  * example implies that an error will be posted in case all the streams don't
145  * have the exact same number of frames.
146  *
147  * Since: 1.18
148  */
149 typedef enum
150 {
151   GST_IQA_MODE_STRICT = (1 << 1),
152 } GstIqaMode;
153
154 #define GST_TYPE_IQA_MODE (gst_iqa_mode_flags_get_type())
155 static GType
156 gst_iqa_mode_flags_get_type (void)
157 {
158   static const GFlagsValue values[] = {
159     {GST_IQA_MODE_STRICT, "Strict comparison of frames.", "strict"},
160     {0, NULL, NULL}
161   };
162   static volatile GType id = 0;
163
164   if (g_once_init_enter ((gsize *) & id)) {
165     GType _id;
166
167     _id = g_flags_register_static ("GstIqaMode", values);
168
169     g_once_init_leave ((gsize *) & id, _id);
170   }
171
172   return id;
173 }
174
175 /* GstIqa */
176 #define gst_iqa_parent_class parent_class
177 G_DEFINE_TYPE_WITH_CODE (GstIqa, gst_iqa, GST_TYPE_VIDEO_AGGREGATOR,
178     G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY, gst_iqa_child_proxy_init));
179
180 #ifdef HAVE_DSSIM
181 inline static unsigned char
182 to_byte (float in)
183 {
184   if (in <= 0)
185     return 0;
186   if (in >= 255.f / 256.f)
187     return 255;
188   return in * 256.f;
189 }
190
191 static gboolean
192 do_dssim (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp,
193     GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname)
194 {
195   dssim_attr *attr;
196   gint y;
197   unsigned char **ptrs, **ptrs2;
198   GstMapInfo ref_info;
199   GstMapInfo cmp_info;
200   GstMapInfo out_info;
201   dssim_image *ref_image;
202   dssim_image *cmp_image;
203   double dssim;
204   dssim_ssim_map map_meta;
205   float *map;
206   gint i;
207   dssim_rgba *out;
208   GstStructure *dssim_structure;
209   gboolean ret = TRUE;
210
211   if (ref->info.width != cmp->info.width ||
212       ref->info.height != cmp->info.height) {
213     GST_OBJECT_UNLOCK (self);
214
215     GST_ELEMENT_ERROR (self, STREAM, FAILED,
216         ("Video streams do not have the same sizes (add videoscale"
217             " and force the sizes to be equal on all sink pads.)"),
218         ("Reference width %d - compared width: %d. "
219             "Reference height %d - compared height: %d",
220             ref->info.width, cmp->info.width, ref->info.height,
221             cmp->info.height));
222
223     GST_OBJECT_LOCK (self);
224     return FALSE;
225   }
226
227   gst_structure_get (msg_structure, "dssim", GST_TYPE_STRUCTURE,
228       &dssim_structure, NULL);
229
230   attr = dssim_create_attr ();
231   dssim_set_save_ssim_maps (attr, 1, 1);
232
233   gst_buffer_map (ref->buffer, &ref_info, GST_MAP_READ);
234   gst_buffer_map (cmp->buffer, &cmp_info, GST_MAP_READ);
235   gst_buffer_map (outbuf, &out_info, GST_MAP_WRITE);
236   out = (dssim_rgba *) out_info.data;
237
238   ptrs = g_malloc (sizeof (char **) * ref->info.height);
239
240   for (y = 0; y < ref->info.height; y++) {
241     ptrs[y] = ref_info.data + (ref->info.width * 4 * y);
242   }
243
244   ref_image =
245       dssim_create_image (attr, ptrs, DSSIM_RGBA, ref->info.width,
246       ref->info.height, 0.45455);
247
248   ptrs2 = g_malloc (sizeof (char **) * cmp->info.height);
249
250   for (y = 0; y < cmp->info.height; y++) {
251     ptrs2[y] = cmp_info.data + (cmp->info.width * 4 * y);
252   }
253
254   cmp_image =
255       dssim_create_image (attr, ptrs2, DSSIM_RGBA, cmp->info.width,
256       cmp->info.height, 0.45455);
257   dssim = dssim_compare (attr, ref_image, cmp_image);
258
259   map_meta = dssim_pop_ssim_map (attr, 0, 0);
260
261   /* Comparing floats... should not be a big deal anyway */
262   if (self->ssim_threshold > 0 && dssim > self->ssim_threshold) {
263     /* We do not really care about our state... we are going to error ou
264      * anyway! */
265     GST_OBJECT_UNLOCK (self);
266
267     GST_ELEMENT_ERROR (self, STREAM, FAILED,
268         ("Dssim check failed on %s at %"
269             GST_TIME_FORMAT " with dssim %f > %f",
270             padname,
271             GST_TIME_ARGS (GST_AGGREGATOR_PAD (GST_AGGREGATOR (self)->
272                     srcpad)->segment.position), dssim, self->ssim_threshold),
273         (NULL));
274
275     GST_OBJECT_LOCK (self);
276
277     ret = FALSE;
278     goto cleanup_return;
279   }
280
281   if (dssim > self->max_dssim) {
282     map = map_meta.data;
283
284     for (i = 0; i < map_meta.width * map_meta.height; i++) {
285       const float max = 1.0 - map[i];
286       const float maxsq = max * max;
287       out[i] = (dssim_rgba) {
288       .r = to_byte (max * 3.0),.g = to_byte (maxsq * 6.0),.b =
289             to_byte (max / ((1.0 - map_meta.dssim) * 4.0)),.a = 255,};
290     }
291     self->max_dssim = dssim;
292   }
293
294   gst_structure_set (dssim_structure, padname, G_TYPE_DOUBLE, dssim, NULL);
295   gst_structure_set (msg_structure, "dssim", GST_TYPE_STRUCTURE,
296       dssim_structure, NULL);
297
298   ret = TRUE;
299
300 cleanup_return:
301
302   gst_structure_free (dssim_structure);
303
304   free (map_meta.data);
305   g_free (ptrs);
306   g_free (ptrs2);
307   gst_buffer_unmap (ref->buffer, &ref_info);
308   gst_buffer_unmap (cmp->buffer, &cmp_info);
309   gst_buffer_unmap (outbuf, &out_info);
310   dssim_dealloc_image (ref_image);
311   dssim_dealloc_image (cmp_image);
312   dssim_dealloc_attr (attr);
313
314   return ret;
315 }
316 #endif
317
318 static gboolean
319 compare_frames (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp,
320     GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname)
321 {
322 #ifdef HAVE_DSSIM
323   if (self->do_dssim) {
324     if (!do_dssim (self, ref, cmp, outbuf, msg_structure, padname))
325       return FALSE;
326   }
327 #endif
328
329   return TRUE;
330 }
331
332 static GstFlowReturn
333 gst_iqa_aggregate_frames (GstVideoAggregator * vagg, GstBuffer * outbuf)
334 {
335   GList *l;
336   GstVideoFrame *ref_frame = NULL;
337   GstIqa *self = GST_IQA (vagg);
338   GstStructure *msg_structure = gst_structure_new_empty ("IQA");
339   GstMessage *m = gst_message_new_element (GST_OBJECT (self), msg_structure);
340   GstAggregator *agg = GST_AGGREGATOR (vagg);
341
342   if (self->do_dssim) {
343     gst_structure_set (msg_structure, "dssim", GST_TYPE_STRUCTURE,
344         gst_structure_new_empty ("dssim"), NULL);
345     self->max_dssim = 0.0;
346   }
347
348   GST_OBJECT_LOCK (vagg);
349   for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) {
350     GstVideoAggregatorPad *pad = l->data;
351     GstVideoFrame *prepared_frame =
352         gst_video_aggregator_pad_get_prepared_frame (pad);
353
354     if (prepared_frame != NULL) {
355       if (!ref_frame) {
356         ref_frame = prepared_frame;
357       } else {
358         gboolean res;
359         gchar *padname = gst_pad_get_name (pad);
360         GstVideoFrame *cmp_frame = prepared_frame;
361
362         res = compare_frames (self, ref_frame, cmp_frame, outbuf, msg_structure,
363             padname);
364         g_free (padname);
365
366         if (!res)
367           goto failed;
368       }
369     } else if ((self->mode & GST_IQA_MODE_STRICT) && ref_frame) {
370       GST_OBJECT_UNLOCK (vagg);
371
372       GST_ELEMENT_ERROR (self, STREAM, FAILED,
373           ("All sources are supposed to have the same number of buffers"
374               " but got no buffer matching %" GST_PTR_FORMAT " on pad: %"
375               GST_PTR_FORMAT, outbuf, pad), (NULL));
376
377       GST_OBJECT_LOCK (vagg);
378       break;
379     }
380   }
381
382   GST_OBJECT_UNLOCK (vagg);
383
384   /* We only post the message here, because we can't post it while the object
385    * is locked.
386    */
387   gst_structure_set (msg_structure, "time", GST_TYPE_CLOCK_TIME,
388       GST_AGGREGATOR_PAD (agg->srcpad)->segment.position, NULL);
389   gst_element_post_message (GST_ELEMENT (self), m);
390   return GST_FLOW_OK;
391
392 failed:
393   GST_OBJECT_UNLOCK (vagg);
394
395   return GST_FLOW_ERROR;
396 }
397
398 static void
399 _set_property (GObject * object, guint prop_id, const GValue * value,
400     GParamSpec * pspec)
401 {
402   GstIqa *self = GST_IQA (object);
403
404   switch (prop_id) {
405     case PROP_DO_SSIM:
406       GST_OBJECT_LOCK (self);
407       self->do_dssim = g_value_get_boolean (value);
408       GST_OBJECT_UNLOCK (self);
409       break;
410     case PROP_SSIM_ERROR_THRESHOLD:
411       GST_OBJECT_LOCK (self);
412       self->ssim_threshold = g_value_get_double (value);
413       GST_OBJECT_UNLOCK (self);
414       break;
415     case PROP_MODE:
416       GST_OBJECT_LOCK (self);
417       self->mode = g_value_get_flags (value);
418       GST_OBJECT_UNLOCK (self);
419       break;
420     default:
421       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
422       break;
423   }
424 }
425
426 static void
427 _get_property (GObject * object,
428     guint prop_id, GValue * value, GParamSpec * pspec)
429 {
430   GstIqa *self = GST_IQA (object);
431
432   switch (prop_id) {
433     case PROP_DO_SSIM:
434       GST_OBJECT_LOCK (self);
435       g_value_set_boolean (value, self->do_dssim);
436       GST_OBJECT_UNLOCK (self);
437       break;
438     case PROP_SSIM_ERROR_THRESHOLD:
439       GST_OBJECT_LOCK (self);
440       g_value_set_double (value, self->ssim_threshold);
441       GST_OBJECT_UNLOCK (self);
442       break;
443     case PROP_MODE:
444       GST_OBJECT_LOCK (self);
445       g_value_set_flags (value, self->mode);
446       GST_OBJECT_UNLOCK (self);
447       break;
448     default:
449       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
450       break;
451   }
452 }
453
454 /* GObject boilerplate */
455 static void
456 gst_iqa_class_init (GstIqaClass * klass)
457 {
458   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
459   GstElementClass *gstelement_class = (GstElementClass *) klass;
460   GstVideoAggregatorClass *videoaggregator_class =
461       (GstVideoAggregatorClass *) klass;
462
463   videoaggregator_class->aggregate_frames = gst_iqa_aggregate_frames;
464
465   gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
466       &src_factory, GST_TYPE_AGGREGATOR_PAD);
467   gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
468       &sink_factory, GST_TYPE_VIDEO_AGGREGATOR_CONVERT_PAD);
469
470   gobject_class->set_property = _set_property;
471   gobject_class->get_property = _get_property;
472
473 #ifdef HAVE_DSSIM
474   g_object_class_install_property (gobject_class, PROP_DO_SSIM,
475       g_param_spec_boolean ("do-dssim", "do-dssim",
476           "Run structural similarity checks", FALSE, G_PARAM_READWRITE));
477
478   g_object_class_install_property (gobject_class, PROP_SSIM_ERROR_THRESHOLD,
479       g_param_spec_double ("dssim-error-threshold", "dssim error threshold",
480           "dssim value over which the element will post an error message on the bus."
481           " A value < 0.0 means 'disabled'.",
482           -1.0, G_MAXDOUBLE, DEFAULT_DSSIM_ERROR_THRESHOLD, G_PARAM_READWRITE));
483 #endif
484
485   /**
486    * iqa:mode:
487    *
488    * Controls the frame comparison mode.
489    *
490    * Since: 1.18
491    */
492   g_object_class_install_property (gobject_class, PROP_MODE,
493       g_param_spec_flags ("mode", "IQA mode",
494           "Controls the frame comparison mode.", GST_TYPE_IQA_MODE,
495           0, G_PARAM_READWRITE));
496
497   gst_type_mark_as_plugin_api (GST_TYPE_IQA_MODE, 0);
498
499   gst_element_class_set_static_metadata (gstelement_class, "Iqa",
500       "Filter/Analyzer/Video",
501       "Provides various Image Quality Assessment metrics",
502       "Mathieu Duponchelle <mathieu.duponchelle@collabora.co.uk>");
503 }
504
505 static void
506 gst_iqa_init (GstIqa * self)
507 {
508 }
509
510 static gboolean
511 plugin_init (GstPlugin * plugin)
512 {
513   GST_DEBUG_CATEGORY_INIT (gst_iqa_debug, "iqa", 0, "iqa");
514
515   return gst_element_register (plugin, "iqa", GST_RANK_PRIMARY, GST_TYPE_IQA);
516 }
517
518 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
519     GST_VERSION_MINOR,
520     iqa,
521     "Iqa", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
522     GST_PACKAGE_ORIGIN)