• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_LAST,
92 };
93 
94 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u",
95     GST_PAD_SINK,
96     GST_PAD_REQUEST,
97     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (SINK_FORMATS))
98     );
99 
100 
101 /* GstIqa */
102 
103 #define gst_iqa_parent_class parent_class
104 G_DEFINE_TYPE (GstIqa, gst_iqa, GST_TYPE_VIDEO_AGGREGATOR);
105 
106 #ifdef HAVE_DSSIM
107 inline static unsigned char
to_byte(float in)108 to_byte (float in)
109 {
110   if (in <= 0)
111     return 0;
112   if (in >= 255.f / 256.f)
113     return 255;
114   return in * 256.f;
115 }
116 
117 static gboolean
do_dssim(GstIqa * self,GstVideoFrame * ref,GstVideoFrame * cmp,GstBuffer * outbuf,GstStructure * msg_structure,gchar * padname)118 do_dssim (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp,
119     GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname)
120 {
121   dssim_attr *attr = dssim_create_attr ();
122   gint y;
123   unsigned char **ptrs, **ptrs2;
124   GstMapInfo ref_info;
125   GstMapInfo cmp_info;
126   GstMapInfo out_info;
127   dssim_image *ref_image;
128   dssim_image *cmp_image;
129   double dssim;
130   dssim_ssim_map map_meta;
131   float *map;
132   gint i;
133   dssim_rgba *out;
134   GstStructure *dssim_structure;
135 
136   gst_structure_get (msg_structure, "dssim", GST_TYPE_STRUCTURE,
137       &dssim_structure, NULL);
138 
139   dssim_set_save_ssim_maps (attr, 1, 1);
140   if (ref->info.width != cmp->info.width ||
141       ref->info.height != cmp->info.height) {
142     GST_OBJECT_UNLOCK (self);
143 
144     GST_ELEMENT_ERROR (self, STREAM, FAILED,
145         ("Video streams do not have the same sizes (add videoscale"
146             " and force the sizes to be equal on all sink pads.)"),
147         ("Reference width %d - compared width: %d. "
148             "Reference height %d - compared height: %d",
149             ref->info.width, cmp->info.width, ref->info.height,
150             cmp->info.height));
151 
152     GST_OBJECT_LOCK (self);
153     return FALSE;
154   }
155 
156   gst_buffer_map (ref->buffer, &ref_info, GST_MAP_READ);
157   gst_buffer_map (cmp->buffer, &cmp_info, GST_MAP_READ);
158   gst_buffer_map (outbuf, &out_info, GST_MAP_WRITE);
159   out = (dssim_rgba *) out_info.data;
160 
161   ptrs = g_malloc (sizeof (char **) * ref->info.height);
162 
163   for (y = 0; y < ref->info.height; y++) {
164     ptrs[y] = ref_info.data + (ref->info.width * 4 * y);
165   }
166 
167   ref_image =
168       dssim_create_image (attr, ptrs, DSSIM_RGBA, ref->info.width,
169       ref->info.height, 0.45455);
170 
171   ptrs2 = g_malloc (sizeof (char **) * cmp->info.height);
172 
173   for (y = 0; y < cmp->info.height; y++) {
174     ptrs2[y] = cmp_info.data + (cmp->info.width * 4 * y);
175   }
176 
177   cmp_image =
178       dssim_create_image (attr, ptrs2, DSSIM_RGBA, cmp->info.width,
179       cmp->info.height, 0.45455);
180   dssim = dssim_compare (attr, ref_image, cmp_image);
181 
182   map_meta = dssim_pop_ssim_map (attr, 0, 0);
183 
184   /* Comparing floats... should not be a big deal anyway */
185   if (self->ssim_threshold > 0 && dssim > self->ssim_threshold) {
186     /* We do not really care about our state... we are going to error ou
187      * anyway! */
188     GST_OBJECT_UNLOCK (self);
189 
190     GST_ELEMENT_ERROR (self, STREAM, FAILED,
191         ("Dssim check failed on %s at %"
192             GST_TIME_FORMAT " with dssim %f > %f",
193             padname,
194             GST_TIME_ARGS (GST_AGGREGATOR_PAD (GST_AGGREGATOR (self)->
195                     srcpad)->segment.position), dssim, self->ssim_threshold),
196         (NULL));
197 
198     GST_OBJECT_LOCK (self);
199 
200     return FALSE;
201   }
202 
203   if (dssim > self->max_dssim) {
204     map = map_meta.data;
205 
206     for (i = 0; i < map_meta.width * map_meta.height; i++) {
207       const float max = 1.0 - map[i];
208       const float maxsq = max * max;
209       out[i] = (dssim_rgba) {
210       .r = to_byte (max * 3.0),.g = to_byte (maxsq * 6.0),.b =
211             to_byte (max / ((1.0 - map_meta.dssim) * 4.0)),.a = 255,};
212     }
213     self->max_dssim = dssim;
214   }
215 
216   gst_structure_set (dssim_structure, padname, G_TYPE_DOUBLE, dssim, NULL);
217   gst_structure_set (msg_structure, "dssim", GST_TYPE_STRUCTURE,
218       dssim_structure, NULL);
219   gst_structure_free (dssim_structure);
220 
221   free (map_meta.data);
222   g_free (ptrs);
223   g_free (ptrs2);
224   gst_buffer_unmap (ref->buffer, &ref_info);
225   gst_buffer_unmap (cmp->buffer, &cmp_info);
226   gst_buffer_unmap (outbuf, &out_info);
227   dssim_dealloc_image (ref_image);
228   dssim_dealloc_image (cmp_image);
229   dssim_dealloc_attr (attr);
230 
231   return TRUE;
232 }
233 #endif
234 
235 static gboolean
compare_frames(GstIqa * self,GstVideoFrame * ref,GstVideoFrame * cmp,GstBuffer * outbuf,GstStructure * msg_structure,gchar * padname)236 compare_frames (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp,
237     GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname)
238 {
239 #ifdef HAVE_DSSIM
240   if (self->do_dssim) {
241     if (!do_dssim (self, ref, cmp, outbuf, msg_structure, padname))
242       return FALSE;
243   }
244 #endif
245 
246   return TRUE;
247 }
248 
249 static GstFlowReturn
gst_iqa_aggregate_frames(GstVideoAggregator * vagg,GstBuffer * outbuf)250 gst_iqa_aggregate_frames (GstVideoAggregator * vagg, GstBuffer * outbuf)
251 {
252   GList *l;
253   GstVideoFrame *ref_frame = NULL;
254   GstIqa *self = GST_IQA (vagg);
255   GstStructure *msg_structure = gst_structure_new_empty ("IQA");
256   GstMessage *m = gst_message_new_element (GST_OBJECT (self), msg_structure);
257   GstAggregator *agg = GST_AGGREGATOR (vagg);
258 
259   if (self->do_dssim) {
260     gst_structure_set (msg_structure, "dssim", GST_TYPE_STRUCTURE,
261         gst_structure_new_empty ("dssim"), NULL);
262     self->max_dssim = 0.0;
263   }
264 
265   GST_OBJECT_LOCK (vagg);
266   for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) {
267     GstVideoAggregatorPad *pad = l->data;
268     GstVideoFrame *prepared_frame =
269         gst_video_aggregator_pad_get_prepared_frame (pad);
270 
271     if (prepared_frame != NULL) {
272       if (!ref_frame) {
273         ref_frame = prepared_frame;
274       } else {
275         gboolean res;
276         gchar *padname = gst_pad_get_name (pad);
277         GstVideoFrame *cmp_frame = prepared_frame;
278 
279         res = compare_frames (self, ref_frame, cmp_frame, outbuf, msg_structure,
280             padname);
281         g_free (padname);
282 
283         if (!res)
284           goto failed;
285       }
286     }
287   }
288 
289   GST_OBJECT_UNLOCK (vagg);
290 
291   /* We only post the message here, because we can't post it while the object
292    * is locked.
293    */
294   gst_structure_set (msg_structure, "time", GST_TYPE_CLOCK_TIME,
295       GST_AGGREGATOR_PAD (agg->srcpad)->segment.position, NULL);
296   gst_element_post_message (GST_ELEMENT (self), m);
297   return GST_FLOW_OK;
298 
299 failed:
300   GST_OBJECT_UNLOCK (vagg);
301 
302   return GST_FLOW_ERROR;
303 }
304 
305 static void
_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)306 _set_property (GObject * object, guint prop_id, const GValue * value,
307     GParamSpec * pspec)
308 {
309   GstIqa *self = GST_IQA (object);
310 
311   switch (prop_id) {
312     case PROP_DO_SSIM:
313       GST_OBJECT_LOCK (self);
314       self->do_dssim = g_value_get_boolean (value);
315       GST_OBJECT_UNLOCK (self);
316       break;
317     case PROP_SSIM_ERROR_THRESHOLD:
318       GST_OBJECT_LOCK (self);
319       self->ssim_threshold = g_value_get_double (value);
320       GST_OBJECT_UNLOCK (self);
321       break;
322     default:
323       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
324       break;
325   }
326 }
327 
328 static void
_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)329 _get_property (GObject * object,
330     guint prop_id, GValue * value, GParamSpec * pspec)
331 {
332   GstIqa *self = GST_IQA (object);
333 
334   switch (prop_id) {
335     case PROP_DO_SSIM:
336       GST_OBJECT_LOCK (self);
337       g_value_set_boolean (value, self->do_dssim);
338       GST_OBJECT_UNLOCK (self);
339       break;
340     case PROP_SSIM_ERROR_THRESHOLD:
341       GST_OBJECT_LOCK (self);
342       g_value_set_double (value, self->ssim_threshold);
343       GST_OBJECT_UNLOCK (self);
344       break;
345     default:
346       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
347       break;
348   }
349 }
350 
351 /* GObject boilerplate */
352 static void
gst_iqa_class_init(GstIqaClass * klass)353 gst_iqa_class_init (GstIqaClass * klass)
354 {
355   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
356   GstElementClass *gstelement_class = (GstElementClass *) klass;
357   GstVideoAggregatorClass *videoaggregator_class =
358       (GstVideoAggregatorClass *) klass;
359 
360   videoaggregator_class->aggregate_frames = gst_iqa_aggregate_frames;
361 
362   gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
363       &src_factory, GST_TYPE_AGGREGATOR_PAD);
364   gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
365       &sink_factory, GST_TYPE_VIDEO_AGGREGATOR_CONVERT_PAD);
366 
367   gobject_class->set_property = _set_property;
368   gobject_class->get_property = _get_property;
369 
370 #ifdef HAVE_DSSIM
371   g_object_class_install_property (gobject_class, PROP_DO_SSIM,
372       g_param_spec_boolean ("do-dssim", "do-dssim",
373           "Run structural similarity checks", FALSE, G_PARAM_READWRITE));
374 
375   g_object_class_install_property (gobject_class, PROP_SSIM_ERROR_THRESHOLD,
376       g_param_spec_double ("dssim-error-threshold", "dssim error threshold",
377           "dssim value over which the element will post an error message on the bus."
378           " A value < 0.0 means 'disabled'.",
379           -1.0, G_MAXDOUBLE, DEFAULT_DSSIM_ERROR_THRESHOLD, G_PARAM_READWRITE));
380 #endif
381 
382   gst_element_class_set_static_metadata (gstelement_class, "Iqa",
383       "Filter/Analyzer/Video",
384       "Provides various Image Quality Assessment metrics",
385       "Mathieu Duponchelle <mathieu.duponchelle@collabora.co.uk>");
386 }
387 
388 static void
gst_iqa_init(GstIqa * self)389 gst_iqa_init (GstIqa * self)
390 {
391 }
392 
393 static gboolean
plugin_init(GstPlugin * plugin)394 plugin_init (GstPlugin * plugin)
395 {
396   GST_DEBUG_CATEGORY_INIT (gst_iqa_debug, "iqa", 0, "iqa");
397 
398   return gst_element_register (plugin, "iqa", GST_RANK_PRIMARY, GST_TYPE_IQA);
399 }
400 
401 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
402     GST_VERSION_MINOR,
403     iqa,
404     "Iqa", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
405     GST_PACKAGE_ORIGIN)
406