Adds a new Image Quality Assessment plugin.
[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  * @short_description: Image Quality Assessment plugin.
23  *
24  * IQA will perform full reference image quality assessment, with the
25  * first added pad being the reference.
26  *
27  * It will perform comparisons on video streams with the same geometry.
28  *
29  * The image output will be the heat map of differences, between
30  * the two pads with the highest measured difference.
31  *
32  * For each reference frame, IQA will post a message containing
33  * a structure named IQA.
34  *
35  * The only metric supported for now is "dssim", which will be available
36  * if https://github.com/pornel/dssim was installed on the system
37  * at the time that plugin was compiled.
38  *
39  * For each metric activated, this structure will contain another
40  * structure, named after the metric.
41  *
42  * The message will also contain a "time" field.
43  *
44  * For example, if do-dssim is set to true, and there are
45  * two compared streams, the emitted structure will look like this:
46  *
47  * IQA, dssim=(structure)"dssim\,\ sink_1\=\(double\)0.053621271267184856\,\
48  * sink_2\=\(double\)0.0082939683976297474\;",
49  * time=(guint64)0;
50  *
51  * <refsect2>
52  * <title>Example launch line</title>
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  * </refsect2>
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
79 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
80     GST_PAD_SRC,
81     GST_PAD_ALWAYS,
82     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (SRC_FORMAT))
83     );
84
85 enum
86 {
87   PROP_0,
88   PROP_DO_SSIM,
89   PROP_LAST,
90 };
91
92 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u",
93     GST_PAD_SINK,
94     GST_PAD_REQUEST,
95     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (SINK_FORMATS))
96     );
97
98
99 /* GstIqa */
100
101 #define gst_iqa_parent_class parent_class
102 G_DEFINE_TYPE (GstIqa, gst_iqa, GST_TYPE_VIDEO_AGGREGATOR);
103
104 #ifdef HAVE_DSSIM
105
106 inline static unsigned char
107 to_byte (float in)
108 {
109   if (in <= 0)
110     return 0;
111   if (in >= 255.f / 256.f)
112     return 255;
113   return in * 256.f;
114 }
115
116 static void
117 do_dssim (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp,
118     GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname)
119 {
120   dssim_attr *attr = dssim_create_attr ();
121   gint y;
122   unsigned char **ptrs, **ptrs2;
123   GstMapInfo ref_info;
124   GstMapInfo cmp_info;
125   GstMapInfo out_info;
126   dssim_image *ref_image;
127   dssim_image *cmp_image;
128   double dssim;
129   dssim_ssim_map map_meta;
130   float *map;
131   gint i;
132   dssim_rgba *out;
133   GstStructure *dssim_structure;
134
135   gst_structure_get (msg_structure, "dssim", GST_TYPE_STRUCTURE,
136       &dssim_structure, NULL);
137
138   dssim_set_save_ssim_maps (attr, 1, 1);
139   if (ref->info.width != cmp->info.width ||
140       ref->info.height != cmp->info.height) {
141     GST_WARNING_OBJECT (self,
142         "Cannot compare two images with a different geometry yet");
143     return;
144   }
145
146   gst_buffer_map (ref->buffer, &ref_info, GST_MAP_READ);
147   gst_buffer_map (cmp->buffer, &cmp_info, GST_MAP_READ);
148   gst_buffer_map (outbuf, &out_info, GST_MAP_WRITE);
149   out = (dssim_rgba *) out_info.data;
150
151   ptrs = g_malloc (sizeof (char **) * ref->info.height);
152   ptrs2 = g_malloc (sizeof (char **) * cmp->info.height);
153
154   for (y = 0; y < ref->info.height; y++) {
155     ptrs[y] = ref_info.data + (ref->info.width * 4 * y);
156   }
157
158   ref_image =
159       dssim_create_image (attr, ptrs, DSSIM_RGBA, ref->info.width,
160       ref->info.height, 0.45455);
161
162   ptrs2 = g_malloc (sizeof (char **) * cmp->info.height);
163
164   for (y = 0; y < cmp->info.height; y++) {
165     ptrs2[y] = cmp_info.data + (cmp->info.width * 4 * y);
166   }
167
168   cmp_image =
169       dssim_create_image (attr, ptrs2, DSSIM_RGBA, cmp->info.width,
170       cmp->info.height, 0.45455);
171   dssim = dssim_compare (attr, ref_image, cmp_image);
172
173   map_meta = dssim_pop_ssim_map (attr, 0, 0);
174
175   if (dssim > self->max_dssim) {
176     map = map_meta.data;
177
178     for (i = 0; i < map_meta.width * map_meta.height; i++) {
179       const float max = 1.0 - map[i];
180       const float maxsq = max * max;
181       out[i] = (dssim_rgba) {
182       .r = to_byte (max * 3.0),.g = to_byte (maxsq * 6.0),.b =
183             to_byte (max / ((1.0 - map_meta.dssim) * 4.0)),.a = 255,};
184     }
185     self->max_dssim = dssim;
186   }
187
188   gst_structure_set (dssim_structure, padname, G_TYPE_DOUBLE, dssim, NULL);
189   gst_structure_set (msg_structure, "dssim", GST_TYPE_STRUCTURE,
190       dssim_structure, NULL);
191   gst_structure_free (dssim_structure);
192
193   g_free (ptrs);
194   g_free (ptrs2);
195   gst_buffer_unmap (ref->buffer, &ref_info);
196   gst_buffer_unmap (cmp->buffer, &cmp_info);
197   gst_buffer_unmap (outbuf, &out_info);
198   dssim_dealloc_image (ref_image);
199   dssim_dealloc_image (cmp_image);
200   dssim_dealloc_attr (attr);
201 }
202 #else
203 static void
204 do_dssim (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp,
205     GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname)
206 {
207 }
208 #endif
209
210 static void
211 compare_frames (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp,
212     GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname)
213 {
214   if (self->do_dssim)
215     do_dssim (self, ref, cmp, outbuf, msg_structure, padname);
216 }
217
218 static GstFlowReturn
219 gst_iqa_aggregate_frames (GstVideoAggregator * vagg, GstBuffer * outbuf)
220 {
221   GList *l;
222   GstVideoFrame *ref_frame = NULL;
223   GstIqa *self = GST_IQA (vagg);
224   GstStructure *msg_structure = gst_structure_new_empty ("IQA");
225   GstMessage *m = gst_message_new_element (GST_OBJECT (self), msg_structure);
226   GstAggregator *agg = GST_AGGREGATOR (vagg);
227
228   if (self->do_dssim) {
229     gst_structure_set (msg_structure, "dssim", GST_TYPE_STRUCTURE,
230         gst_structure_new_empty ("dssim"), NULL);
231     self->max_dssim = 0.0;
232   }
233
234   GST_OBJECT_LOCK (vagg);
235   for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) {
236     GstVideoAggregatorPad *pad = l->data;
237
238     if (pad->aggregated_frame != NULL) {
239       if (!ref_frame) {
240         ref_frame = pad->aggregated_frame;
241       } else {
242         gchar *padname = gst_pad_get_name (pad);
243         GstVideoFrame *cmp_frame = pad->aggregated_frame;
244
245         compare_frames (self, ref_frame, cmp_frame, outbuf, msg_structure,
246             padname);
247         g_free (padname);
248       }
249     }
250   }
251
252   GST_OBJECT_UNLOCK (vagg);
253
254   /* We only post the message here, because we can't post it while the object
255    * is locked.
256    */
257   gst_structure_set (msg_structure, "time", GST_TYPE_CLOCK_TIME,
258       agg->segment.position, NULL);
259   gst_element_post_message (GST_ELEMENT (self), m);
260   return GST_FLOW_OK;
261 }
262
263 static void
264 _set_property (GObject * object, guint prop_id, const GValue * value,
265     GParamSpec * pspec)
266 {
267   GstIqa *self = GST_IQA (object);
268
269   switch (prop_id) {
270     case PROP_DO_SSIM:
271       self->do_dssim = g_value_get_boolean (value);
272       break;
273     default:
274       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
275       break;
276   }
277 }
278
279 static void
280 _get_property (GObject * object,
281     guint prop_id, GValue * value, GParamSpec * pspec)
282 {
283   GstIqa *self = GST_IQA (object);
284
285   switch (prop_id) {
286     case PROP_DO_SSIM:
287       g_value_set_boolean (value, self->do_dssim);
288       break;
289     default:
290       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
291       break;
292   }
293 }
294
295 /* GObject boilerplate */
296 static void
297 gst_iqa_class_init (GstIqaClass * klass)
298 {
299   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
300   GstElementClass *gstelement_class = (GstElementClass *) klass;
301   GstVideoAggregatorClass *videoaggregator_class =
302       (GstVideoAggregatorClass *) klass;
303
304   videoaggregator_class->aggregate_frames = gst_iqa_aggregate_frames;
305
306   gst_element_class_add_pad_template (gstelement_class,
307       gst_static_pad_template_get (&src_factory));
308   gst_element_class_add_pad_template (gstelement_class,
309       gst_static_pad_template_get (&sink_factory));
310
311   gobject_class->set_property = _set_property;
312   gobject_class->get_property = _get_property;
313
314 #ifdef HAVE_DSSIM
315   g_object_class_install_property (gobject_class, PROP_DO_SSIM,
316       g_param_spec_boolean ("do-dssim", "do-dssim",
317           "Run structural similarity checks", FALSE, G_PARAM_READWRITE));
318 #endif
319
320   gst_element_class_set_static_metadata (gstelement_class, "Iqa",
321       "Filter/Analyzer/Video",
322       "Provides various Image Quality Assessment metrics",
323       "Mathieu Duponchelle <mathieu.duponchelle@collabora.co.uk>");
324 }
325
326 static void
327 gst_iqa_init (GstIqa * self)
328 {
329 }
330
331 static gboolean
332 plugin_init (GstPlugin * plugin)
333 {
334   GST_DEBUG_CATEGORY_INIT (gst_iqa_debug, "iqa", 0, "iqa");
335
336   return gst_element_register (plugin, "iqa", GST_RANK_PRIMARY, GST_TYPE_IQA);
337 }
338
339 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
340     GST_VERSION_MINOR,
341     iqa,
342     "Iqa", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
343     GST_PACKAGE_ORIGIN)