Move files from gst-devtools into the "subprojects/gst-devtools/" subdir
[platform/upstream/gstreamer.git] / subprojects / gst-devtools / validate / gst-libs / gst / video / gstvalidatessim.c
1 /* GStreamer
2  *
3  * Copyright (C) 2015 Raspberry Pi Foundation
4  *  Author: Thibault Saunier <thibault.saunier@collabora.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21 #include <glib-object.h>
22 #include <glib.h>
23 #include <math.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26
27 #include <errno.h>
28 #include "gstvalidatessim.h"
29 #include "gssim.h"
30
31 #include <cairo.h>
32 #include <gio/gio.h>
33 #include <gst/gst.h>
34 #include <gst/video/video.h>
35
36 #define GST_CAT_DEFAULT gstvalidatessim_debug
37 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
38
39 #define SIMILARITY_ISSUE_WITH_PREVIOUS g_quark_from_static_string ("ssim::image-not-similar-enough-with-theoretical-reference")
40 #define SIMILARITY_ISSUE g_quark_from_static_string ("ssim::image-not-similar-enough")
41 #define GENERAL_INPUT_ERROR g_quark_from_static_string ("ssim::general-file-error")
42 #define WRONG_FORMAT g_quark_from_static_string ("ssim::wrong-format")
43
44 enum
45 {
46   PROP_FIRST_PROP = 1,
47   PROP_RUNNER,
48   PROP_LAST
49 };
50
51 typedef struct
52 {
53   GstVideoConverter *converter;
54   GstVideoInfo in_info;
55   GstVideoInfo out_info;
56 } SSimConverterInfo;
57
58 struct _GstValidateSsimPrivate
59 {
60   gint width;
61   gint height;
62
63   Gssim *ssim;
64
65   GList *converters;
66   GstVideoInfo out_info;
67
68   SSimConverterInfo outconverter_info;
69
70   gfloat min_avg_similarity;
71   gfloat min_lowest_similarity;
72
73   GHashTable *ref_frames_cache;
74   gint fps_n, fps_d;
75 };
76
77 G_DEFINE_TYPE_WITH_CODE (GstValidateSsim, gst_validate_ssim,
78     GST_TYPE_OBJECT, G_ADD_PRIVATE (GstValidateSsim)
79     G_IMPLEMENT_INTERFACE (GST_TYPE_VALIDATE_REPORTER, NULL));
80
81 static void
82 ssim_convert_info_free (SSimConverterInfo * info)
83 {
84   if (info->converter)
85     gst_video_converter_free (info->converter);
86
87   g_slice_free (SSimConverterInfo, info);
88 }
89
90 static gboolean
91 gst_validate_ssim_convert (GstValidateSsim * self, SSimConverterInfo * info,
92     GstVideoFrame * frame, GstVideoFrame * converted_frame)
93 {
94   gboolean res = TRUE;
95   GstBuffer *outbuf = NULL;
96
97   g_return_val_if_fail (info != NULL, FALSE);
98
99   outbuf = gst_buffer_new_allocate (NULL, info->out_info.size, NULL);
100   if (!gst_video_frame_map (converted_frame, &info->out_info, outbuf,
101           GST_MAP_WRITE)) {
102     GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
103         "Could not map output converted_frame");
104     goto fail;
105   }
106
107   gst_video_converter_frame (info->converter, frame, converted_frame);
108
109 done:
110   if (outbuf)
111     gst_buffer_unref (outbuf);
112
113   return res;
114
115 fail:
116   res = FALSE;
117   goto done;
118 }
119
120 static gchar *
121 gst_validate_ssim_save_out (GstValidateSsim * self, GstBuffer * buffer,
122     const gchar * ref_file, const gchar * file, const gchar * outfolder)
123 {
124   GstVideoFrame frame, converted;
125
126   if (!g_file_test (outfolder, G_FILE_TEST_IS_DIR)) {
127     if (g_mkdir_with_parents (outfolder, 0755) != 0) {
128
129       GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
130           "Could not create output directory %s", outfolder);
131       return NULL;
132     }
133   }
134
135   if (self->priv->outconverter_info.converter == NULL ||
136       self->priv->width != self->priv->outconverter_info.out_info.width ||
137       self->priv->height != self->priv->outconverter_info.out_info.height) {
138
139     if (self->priv->outconverter_info.converter)
140       gst_video_converter_free (self->priv->outconverter_info.converter);
141
142     gst_video_info_init (&self->priv->outconverter_info.in_info);
143     gst_video_info_set_format (&self->priv->outconverter_info.in_info,
144         GST_VIDEO_FORMAT_GRAY8, self->priv->width, self->priv->height);
145
146     gst_video_info_init (&self->priv->outconverter_info.out_info);
147     gst_video_info_set_format (&self->priv->outconverter_info.out_info,
148         GST_VIDEO_FORMAT_RGBx, self->priv->width, self->priv->height);
149
150     self->priv->outconverter_info.converter =
151         gst_video_converter_new (&self->priv->outconverter_info.in_info,
152         &self->priv->outconverter_info.out_info, NULL);
153   }
154
155   if (!gst_video_frame_map (&frame, &self->priv->outconverter_info.in_info,
156           buffer, GST_MAP_READ)) {
157     GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
158         "Could not map output frame");
159
160     return NULL;
161   }
162
163   if (gst_validate_ssim_convert (self, &self->priv->outconverter_info,
164           &frame, &converted)) {
165     cairo_status_t status;
166     cairo_surface_t *surface;
167     gchar *bn1 = g_path_get_basename (ref_file);
168     gchar *bn2 = g_path_get_basename (file);
169     gchar *fname = g_strdup_printf ("%s.VS.%s.result.png", bn1, bn2);
170     gchar *outfile = g_build_path (G_DIR_SEPARATOR_S, outfolder, fname, NULL);
171
172     surface =
173         cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA
174         (&converted, 0), CAIRO_FORMAT_RGB24, GST_VIDEO_FRAME_WIDTH (&converted),
175         GST_VIDEO_FRAME_HEIGHT (&converted),
176         GST_VIDEO_FRAME_PLANE_STRIDE (&converted, 0));
177
178     if ((status = cairo_surface_write_to_png (surface, outfile)) !=
179         CAIRO_STATUS_SUCCESS) {
180       GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
181           "Could not save '%s', cairo status is '%s'", outfile,
182           cairo_status_to_string (status));
183     }
184
185     cairo_surface_destroy (surface);
186     gst_video_frame_unmap (&frame);
187     gst_video_frame_unmap (&converted);
188     g_free (bn1);
189     g_free (bn2);
190     g_free (fname);
191     return outfile;
192   }
193
194   return NULL;
195 }
196
197 static gboolean
198 gst_validate_ssim_configure (GstValidateSsim * self, gint width, gint height)
199 {
200   if (width == self->priv->width && height == self->priv->height)
201     return FALSE;
202
203   gssim_configure (self->priv->ssim, width, height);
204
205   self->priv->width = width;
206   self->priv->height = height;
207
208   gst_video_info_init (&self->priv->out_info);
209   gst_video_info_set_format (&self->priv->out_info, GST_VIDEO_FORMAT_I420,
210       self->priv->width, self->priv->height);
211
212   return TRUE;
213 }
214
215 static void
216 gst_validate_ssim_configure_converter (GstValidateSsim * self, gint index,
217     gboolean force, GstVideoFormat in_format, gint width, gint height)
218 {
219   SSimConverterInfo *info = g_list_nth_data (self->priv->converters, index);
220
221   if (!info) {
222     info = g_slice_new0 (SSimConverterInfo);
223
224     self->priv->converters =
225         g_list_insert (self->priv->converters, info, index);
226   }
227
228   if (force || info->in_info.height != height || info->in_info.width != width ||
229       info->in_info.finfo->format != in_format) {
230     gst_video_info_init (&info->in_info);
231     gst_video_info_set_format (&info->in_info, in_format, width, height);
232
233     if (info->converter)
234       gst_video_converter_free (info->converter);
235
236     info->out_info = self->priv->out_info;
237
238     if (gst_video_info_is_equal (&info->in_info, &info->out_info))
239       info->converter = NULL;
240     else
241       info->converter =
242           gst_video_converter_new (&info->in_info, &info->out_info, NULL);
243   }
244 }
245
246 static GstVideoFormat
247 _get_format_from_surface (cairo_surface_t * surface)
248 {
249 #if G_BYTE_ORDER == G_BIG_ENDIAN
250   if (cairo_surface_get_content (surface) == CAIRO_CONTENT_COLOR_ALPHA)
251     return GST_VIDEO_FORMAT_BGRA;
252   else
253     return GST_VIDEO_FORMAT_BGRx;
254 #else
255   if (cairo_surface_get_content (surface) == CAIRO_CONTENT_COLOR_ALPHA)
256     return GST_VIDEO_FORMAT_ARGB;
257   else
258     return GST_VIDEO_FORMAT_RGBx;
259 #endif
260 }
261
262 void
263 gst_validate_ssim_compare_frames (GstValidateSsim * self,
264     GstVideoFrame * ref_frame, GstVideoFrame * frame, GstBuffer ** outbuf,
265     gfloat * mean, gfloat * lowest, gfloat * highest)
266 {
267   gboolean reconf;
268   guint8 *outdata = NULL;
269   GstMapInfo map1, map2, outmap;
270
271   GstVideoFrame converted_frame1, converted_frame2;
272   SSimConverterInfo *convinfo1, *convinfo2;
273
274   reconf =
275       gst_validate_ssim_configure (self, ref_frame->info.width,
276       ref_frame->info.height);
277
278   gst_validate_ssim_configure_converter (self, 0, reconf,
279       ref_frame->info.finfo->format, ref_frame->info.width,
280       ref_frame->info.height);
281
282   gst_validate_ssim_configure_converter (self, 1, reconf,
283       frame->info.finfo->format, frame->info.width, frame->info.height);
284
285   convinfo1 = (SSimConverterInfo *) g_list_nth_data (self->priv->converters, 0);
286   if (convinfo1->converter)
287     gst_validate_ssim_convert (self, convinfo1, ref_frame, &converted_frame1);
288   else
289     converted_frame1 = *ref_frame;
290
291   convinfo2 = (SSimConverterInfo *) g_list_nth_data (self->priv->converters, 1);
292   if (convinfo2->converter)
293     gst_validate_ssim_convert (self, convinfo2, frame, &converted_frame2);
294   else
295     converted_frame2 = *frame;
296
297   if (!gst_buffer_map (converted_frame1.buffer, &map1, GST_MAP_READ)) {
298     GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
299         "Could not map reference frame");
300
301     return;
302   }
303
304   if (!gst_buffer_map (converted_frame2.buffer, &map2, GST_MAP_READ)) {
305     gst_buffer_unmap (converted_frame1.buffer, &map1);
306     GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
307         "Could not map compared frame");
308
309     return;
310   }
311
312   if (outbuf) {
313     *outbuf = gst_buffer_new_and_alloc (GST_ROUND_UP_4 (self->priv->width) *
314         self->priv->height);
315     if (!gst_buffer_map (*outbuf, &outmap, GST_MAP_WRITE)) {
316       GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
317           "Could not map output frame");
318
319       gst_buffer_unref (*outbuf);
320       gst_buffer_unmap (converted_frame1.buffer, &map1);
321       gst_buffer_unmap (converted_frame2.buffer, &map2);
322       *outbuf = NULL;
323
324       return;
325     }
326
327     outdata = outmap.data;
328   }
329
330   gssim_compare (self->priv->ssim, map1.data, map2.data, outdata, mean,
331       lowest, highest);
332
333   gst_buffer_unmap (ref_frame->buffer, &map1);
334   gst_buffer_unmap (frame->buffer, &map2);
335
336   if (convinfo1->converter)
337     gst_video_frame_unmap (&converted_frame1);
338   if (convinfo2->converter)
339     gst_video_frame_unmap (&converted_frame2);
340
341   if (outbuf)
342     gst_buffer_unmap (*outbuf, &outmap);
343 }
344
345 static gboolean
346 gst_validate_ssim_get_frame_from_png (GstValidateSsim * self, const char *file,
347     GstVideoFrame * frame)
348 {
349   guint8 *data;
350   GstBuffer *buf;
351   GstVideoInfo info;
352   cairo_surface_t *surface = NULL;
353
354   surface = cairo_image_surface_create_from_png (file);
355   if (surface == NULL
356       || (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)) {
357     GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, "Could not open %s: %s",
358         file, cairo_status_to_string (cairo_surface_status (surface)));
359
360     return FALSE;
361   }
362
363   gst_video_info_init (&info);
364   gst_video_info_set_format (&info,
365       _get_format_from_surface (surface),
366       cairo_image_surface_get_width (surface),
367       cairo_image_surface_get_height (surface));
368
369   cairo_surface_flush (surface);
370   data = cairo_image_surface_get_data (surface);
371   buf = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
372       data, info.size, 0, info.size, surface,
373       (GDestroyNotify) cairo_surface_destroy);
374   if (!gst_video_frame_map (frame, &info, buf, GST_MAP_READ)) {
375     gst_buffer_unref (buf);
376     GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
377         "Could not map input frame");
378
379     return FALSE;
380   }
381
382   gst_buffer_unref (buf);
383
384   return TRUE;
385 }
386
387 static gboolean
388 gst_validate_ssim_get_frame_from_file (GstValidateSsim * self, const char *file,
389     GstVideoFrame * frame)
390 {
391   gchar *data;
392   gsize length;
393   GstBuffer *buf;
394   GstVideoInfo info;
395   GstVideoFormat format;
396   gint strv_length, width, height;
397
398   gboolean res = TRUE;
399   gchar **splited_name = NULL, **splited_size = NULL, *strformat;
400
401   GError *error = NULL;
402
403   if (g_str_has_suffix (file, ".png")) {
404     return gst_validate_ssim_get_frame_from_png (self, file, frame);
405   }
406
407   splited_name = g_strsplit (file, ".", -1);
408   strv_length = g_strv_length (splited_name);
409
410   strformat = splited_name[strv_length - 1];
411   format = gst_video_format_from_string (strformat);
412   if (format == GST_VIDEO_FORMAT_UNKNOWN) {
413     GST_VALIDATE_REPORT (self, WRONG_FORMAT, "Unknown format: %s", strformat);
414
415     goto fail;
416   }
417
418   splited_size = g_strsplit (splited_name[strv_length - 2], "x", -1);
419   if (g_strv_length (splited_size) != 2) {
420     GST_VALIDATE_REPORT (self, WRONG_FORMAT,
421         "Can not determine video size from filename: %s ", file);
422
423     goto fail;
424   }
425
426   errno = 0;
427   width = g_ascii_strtoull (splited_size[0], NULL, 10);
428   if (errno) {
429     GST_VALIDATE_REPORT (self, WRONG_FORMAT,
430         "Can not determine video size from filename: %s ", file);
431
432     goto fail;
433   }
434
435   errno = 0;
436   height = g_ascii_strtoull (splited_size[1], NULL, 10);
437   if (errno) {
438     GST_VALIDATE_REPORT (self, WRONG_FORMAT,
439         "Can not determine video size from filename: %s ", file);
440
441     goto fail;
442   }
443
444   gst_video_info_init (&info);
445   gst_video_info_set_format (&info, format, width, height);
446
447   if (!g_file_get_contents (file, &data, &length, &error)) {
448     GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, "Could not open %s: %s",
449         file, error->message);
450     g_error_free (error);
451
452     goto fail;
453   }
454
455   buf = gst_buffer_new_wrapped (data, length);
456   if (!gst_video_frame_map (frame, &info, buf, GST_MAP_READ)) {
457     gst_buffer_unref (buf);
458     GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
459         "Could not map input frame");
460
461     goto fail;
462   }
463   gst_buffer_unref (buf);
464
465 done:
466   g_strfreev (splited_name);
467   g_strfreev (splited_size);
468
469   return res;
470
471 fail:
472   res = FALSE;
473
474   goto done;
475 }
476
477 static gboolean
478 _filename_get_timestamp (GstValidateSsim * self, const gchar * filename,
479     GstClockTime * ts)
480 {
481   guint h, m, s, ns;
482   gchar *bname = g_path_get_basename (filename);
483   gchar *other = g_strdup (bname);
484   gboolean res = TRUE;
485
486   if (sscanf (bname, "%" GST_VALIDATE_SSIM_TIME_FORMAT "%s", &h, &m, &s, &ns,
487           other) < 4) {
488     GST_INFO_OBJECT (self, "Can not sscanf %s", bname);
489
490     goto fail;
491   }
492
493   *ts = (h * 3600 + m * 60 + s) * GST_SECOND + ns;
494
495 done:
496   g_free (other);
497   g_free (bname);
498   return res;
499
500 fail:
501   res = FALSE;
502   goto done;
503 }
504
505 typedef struct
506 {
507   gchar *path;
508   GstClockTime ts;
509 } Frame;
510
511 static void
512 _free_frame (Frame * frame)
513 {
514   g_free (frame->path);
515 }
516
517 static gint
518 _sort_frames (Frame * a, Frame * b)
519 {
520   if (a->ts < b->ts)
521     return -1;
522
523   if (a->ts == b->ts)
524     return 0;
525
526   return 1;
527 }
528
529 static Frame *
530 _find_frame (GstValidateSsim * self, GArray * frames, GstClockTime ts,
531     gboolean get_next)
532 {
533   guint i;
534   Frame *lframe = &g_array_index (frames, Frame, 0);
535
536   if (self->priv->fps_n) {
537     gint64 frame_number = gst_util_uint64_scale (ts, self->priv->fps_n,
538         self->priv->fps_d * GST_SECOND);
539
540     if (frames->len < frame_number)
541       return NULL;
542
543     return &g_array_index (frames, Frame, frame_number);
544   }
545
546   if (frames->len == 1) {
547     Frame *iframe = &g_array_index (frames, Frame, 0);
548
549     if (iframe->ts == ts)
550       return iframe;
551
552     return NULL;
553   }
554
555   for (i = 1; i < frames->len; i++) {
556     Frame *iframe = &g_array_index (frames, Frame, i);
557
558     if (ts >= lframe->ts && iframe->ts > ts) {
559       if (get_next)
560         return iframe;
561
562       return lframe;
563     } else if (i + 1 == frames->len) {
564       return iframe;
565     }
566
567     lframe = iframe;
568   }
569
570   return NULL;
571 }
572
573 static GArray *
574 _get_ref_frame_cache (GstValidateSsim * self, const gchar * ref_file)
575 {
576   GFile *ref_dir_file = NULL;
577   GFileInfo *info;
578   GFileEnumerator *fenum;
579   GArray *frames = NULL;
580   gchar *ref_dir = NULL;
581
582   ref_dir = g_path_get_dirname (ref_file);
583
584   frames = g_hash_table_lookup (self->priv->ref_frames_cache, ref_file);
585   if (frames)
586     goto done;
587
588   ref_dir_file = g_file_new_for_path (ref_dir);
589   if (!(fenum = g_file_enumerate_children (ref_dir_file,
590               "standard::*", G_FILE_QUERY_INFO_NONE, NULL, NULL))) {
591     GST_INFO ("%s is not a folder", ref_dir);
592
593     goto done;
594   }
595
596   for (info = g_file_enumerator_next_file (fenum, NULL, NULL);
597       info; info = g_file_enumerator_next_file (fenum, NULL, NULL)) {
598     Frame iframe;
599     const gchar *display_name = g_file_info_get_display_name (info);
600
601     if (!_filename_get_timestamp (self, display_name, &iframe.ts)) {
602       g_object_unref (info);
603       continue;
604     }
605
606     iframe.path = g_build_path (G_DIR_SEPARATOR_S,
607         ref_dir, g_file_info_get_name (info), NULL);
608
609     g_object_unref (info);
610
611     if (!frames) {
612       frames = g_array_new (TRUE, TRUE, sizeof (Frame));
613
614       g_array_set_clear_func (frames, (GDestroyNotify) _free_frame);
615     }
616     g_array_append_val (frames, iframe);
617   }
618   g_object_unref (fenum);
619
620   if (frames) {
621     g_array_sort (frames, (GCompareFunc) _sort_frames);
622
623     g_hash_table_insert (self->priv->ref_frames_cache, g_strdup (ref_dir),
624         frames);
625   }
626
627 done:
628   g_clear_object (&ref_dir_file);
629   g_free (ref_dir);
630
631   return frames;
632 }
633
634 static gchar *
635 _get_ref_file_path (GstValidateSsim * self, const gchar * ref_file,
636     const gchar * file, gboolean get_next)
637 {
638   Frame *frame;
639   GArray *frames;
640   gchar *real_ref_file = NULL;
641   GstClockTime file_ts;
642
643   if (!g_strrstr (ref_file, "*"))
644     return g_strdup (ref_file);
645
646   if (!_filename_get_timestamp (self, file, &file_ts)) {
647     goto done;
648   }
649
650   frames = _get_ref_frame_cache (self, ref_file);
651   if (frames) {
652     frame = _find_frame (self, frames, file_ts, get_next);
653
654     if (frame)
655       real_ref_file = g_strdup (frame->path);
656   }
657
658 done:
659
660   return real_ref_file;
661 }
662
663 static gboolean
664 gst_validate_ssim_compare_image_file (GstValidateSsim * self,
665     const gchar * ref_file, const gchar * file, gfloat * mean, gfloat * lowest,
666     gfloat * highest, const gchar * outfolder)
667 {
668   GstBuffer *outbuf = NULL, **poutbuf = NULL;
669   gboolean res = TRUE;
670   GstVideoFrame ref_frame, frame;
671   gchar *real_ref_file = NULL;
672   gchar *output_failure_image = NULL, *failure_info = NULL;
673
674   real_ref_file = _get_ref_file_path (self, ref_file, file, FALSE);
675
676   if (!real_ref_file) {
677     GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
678         "Could find ref file for %s", ref_file);
679     goto fail;
680   }
681
682   if (!gst_validate_ssim_get_frame_from_file (self, real_ref_file, &ref_frame))
683     goto fail;
684
685
686   if (!gst_validate_ssim_get_frame_from_file (self, file, &frame)) {
687     gst_video_frame_unmap (&ref_frame);
688
689     goto fail;
690   }
691
692   if (outfolder) {
693     poutbuf = &outbuf;
694   }
695
696   gst_validate_ssim_compare_frames (self, &ref_frame, &frame,
697       poutbuf, mean, lowest, highest);
698
699   if (*mean < self->priv->min_avg_similarity) {
700     GstClockTime ref_ts, f_ts;
701
702     gst_video_frame_unmap (&ref_frame);
703     gst_video_frame_unmap (&frame);
704
705     _filename_get_timestamp (self, real_ref_file, &ref_ts);
706     _filename_get_timestamp (self, file, &f_ts);
707
708     if (g_strcmp0 (ref_file, real_ref_file) && ref_ts != f_ts) {
709       gchar *tmpref = real_ref_file;
710
711       real_ref_file = _get_ref_file_path (self, ref_file, file, TRUE);
712
713       GST_VALIDATE_REPORT (self, SIMILARITY_ISSUE_WITH_PREVIOUS,
714           "\nComparing %s with %s failed, (mean %f "
715           " min %f), checking next %s\n",
716           tmpref, file, *mean, *lowest, real_ref_file);
717
718       g_free (tmpref);
719
720       res = gst_validate_ssim_compare_image_file (self,
721           real_ref_file, file, mean, lowest, highest, outfolder);
722       goto done;
723     }
724
725     if (outbuf)
726       output_failure_image =
727           gst_validate_ssim_save_out (self, outbuf, real_ref_file, file,
728           outfolder);
729
730     if (output_failure_image)
731       failure_info =
732           g_strdup_printf (" (See %s to check differences in images)",
733           output_failure_image);
734
735     GST_VALIDATE_REPORT (self, SIMILARITY_ISSUE,
736         "Average similarity '%f' between %s and %s inferior"
737         " than the minimum average: %f%s", *mean,
738         real_ref_file, file, self->priv->min_avg_similarity, failure_info);
739
740     goto fail;
741   }
742
743   if (*lowest < self->priv->min_lowest_similarity) {
744     if (outbuf)
745       output_failure_image =
746           gst_validate_ssim_save_out (self, outbuf, real_ref_file, file,
747           outfolder);
748
749     if (output_failure_image)
750       failure_info =
751           g_strdup_printf (" (See %s to check differences in images)",
752           output_failure_image);
753
754     GST_VALIDATE_REPORT (self, SIMILARITY_ISSUE,
755         "Lowest similarity '%f' between %s and %s inferior"
756         " than the minimum lowest similarity: %f%s", *lowest,
757         real_ref_file, file, self->priv->min_lowest_similarity, failure_info);
758
759     gst_video_frame_unmap (&ref_frame);
760     gst_video_frame_unmap (&frame);
761
762     goto fail;
763   }
764
765   gst_video_frame_unmap (&ref_frame);
766   gst_video_frame_unmap (&frame);
767
768 done:
769
770   g_free (failure_info);
771   g_free (output_failure_image);
772   g_free (real_ref_file);
773   if (outbuf)
774     gst_buffer_unref (outbuf);
775
776   return res;
777
778 fail:
779   res = FALSE;
780
781   goto done;
782 }
783
784 static gboolean
785 _check_directory (GstValidateSsim * self, const gchar * ref_dir,
786     const gchar * compared_dir, gfloat * mean, gfloat * lowest,
787     gfloat * highest, const gchar * outfolder)
788 {
789   gint nfiles = 0, nnotfound = 0, nfailures = 0;
790   gboolean res = TRUE;
791   GFileInfo *info;
792   GFileEnumerator *fenum;
793   gfloat min_avg = 1.0, min_min = 1.0, total_avg = 0;
794   GFile *file = g_file_new_for_path (ref_dir);
795
796   if (!(fenum = g_file_enumerate_children (file,
797               "standard::*", G_FILE_QUERY_INFO_NONE, NULL, NULL))) {
798     GST_INFO ("%s is not a folder", ref_dir);
799     res = FALSE;
800
801     goto done;
802   }
803
804   for (info = g_file_enumerator_next_file (fenum, NULL, NULL);
805       info; info = g_file_enumerator_next_file (fenum, NULL, NULL)) {
806
807     if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR ||
808         g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK) {
809       gchar *compared_file = g_build_path (G_DIR_SEPARATOR_S,
810           compared_dir, g_file_info_get_name (info), NULL);
811       gchar *ref_file = NULL;
812
813       if (!g_file_test (compared_file, G_FILE_TEST_IS_REGULAR)) {
814         GST_INFO_OBJECT (self, "Could not find file %s", compared_file);
815         nnotfound++;
816         res = FALSE;
817       } else {
818
819         ref_file =
820             g_build_path (G_DIR_SEPARATOR_S, ref_dir,
821             g_file_info_get_name (info), NULL);
822         if (!gst_validate_ssim_compare_image_files (self, ref_file,
823                 compared_file, mean, lowest, highest, outfolder)) {
824           nfailures++;
825           res = FALSE;
826         } else {
827           nfiles++;
828         }
829       }
830
831       min_avg = MIN (min_avg, *mean);
832       min_min = MIN (min_min, *lowest);
833       total_avg += *mean;
834       gst_validate_printf (NULL,
835           "<position: %s duration: %" GST_TIME_FORMAT
836           " avg: %f min: %f (Passed: %d failed: %d, %d not found)/>\r",
837           g_file_info_get_display_name (info),
838           GST_TIME_ARGS (GST_CLOCK_TIME_NONE),
839           *mean, *lowest, nfiles, nfailures, nnotfound);
840
841       g_free (compared_file);
842       g_free (ref_file);
843     }
844
845     g_object_unref (info);
846   }
847
848   if (nfiles == 0) {
849     gst_validate_printf (NULL, "\nNo files to verify.\n");
850   } else {
851     gst_validate_printf (NULL,
852         "\nAverage similarity: %f, min_avg: %f, min_min: %f\n",
853         total_avg / nfiles, min_avg, min_min);
854   }
855
856 done:
857   gst_object_unref (file);
858   if (fenum)
859     gst_object_unref (fenum);
860
861   return res;
862 }
863
864 gboolean
865 gst_validate_ssim_compare_image_files (GstValidateSsim * self,
866     const gchar * ref_file, const gchar * file, gfloat * mean, gfloat * lowest,
867     gfloat * highest, const gchar * outfolder)
868 {
869   if (g_file_test (ref_file, G_FILE_TEST_IS_DIR)) {
870     if (!g_file_test (file, G_FILE_TEST_IS_DIR)) {
871       GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
872           "%s is a directory but %s is not", ref_file, file);
873
874       return FALSE;
875     }
876
877     return _check_directory (self, ref_file, file, mean, lowest, highest,
878         outfolder);
879   } else {
880     return gst_validate_ssim_compare_image_file (self, ref_file, file, mean,
881         lowest, highest, outfolder);
882   }
883 }
884
885 static void
886 gst_validate_ssim_get_property (GObject * object,
887     guint property_id, GValue * value, GParamSpec * pspec)
888 {
889   switch (property_id) {
890     case PROP_RUNNER:
891       /* we assume the runner is valid as long as this scenario is,
892        * no ref taken */
893       g_value_set_object (value,
894           gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (object)));
895       break;
896     default:
897       break;
898   }
899 }
900
901 static void
902 gst_validate_ssim_set_property (GObject * object,
903     guint property_id, const GValue * value, GParamSpec * pspec)
904 {
905   switch (property_id) {
906     case PROP_RUNNER:
907       /* we assume the runner is valid as long as this scenario is,
908        * no ref taken */
909       gst_validate_reporter_set_runner (GST_VALIDATE_REPORTER (object),
910           g_value_get_object (value));
911       break;
912     default:
913       break;
914   }
915 }
916
917 static void
918 gst_validate_ssim_dispose (GObject * object)
919 {
920   GstValidateSsim *self = GST_VALIDATE_SSIM (object);
921   void (*chain_up) (GObject *) =
922       ((GObjectClass *) gst_validate_ssim_parent_class)->dispose;
923
924   gst_object_unref (self->priv->ssim);
925
926   chain_up (object);
927 }
928
929 static void
930 gst_validate_ssim_finalize (GObject * object)
931 {
932   GstValidateSsim *self = GST_VALIDATE_SSIM (object);
933   void (*chain_up) (GObject *) =
934       ((GObjectClass *) gst_validate_ssim_parent_class)->finalize;
935
936   g_list_free_full (self->priv->converters,
937       (GDestroyNotify) ssim_convert_info_free);
938
939   if (self->priv->outconverter_info.converter)
940     gst_video_converter_free (self->priv->outconverter_info.converter);
941   g_hash_table_unref (self->priv->ref_frames_cache);
942
943   chain_up (object);
944 }
945
946 static gpointer
947 _register_issues (gpointer data)
948 {
949   gst_validate_issue_register (gst_validate_issue_new_full (SIMILARITY_ISSUE,
950           "Compared images were not similar enough",
951           "The images checker detected that the images"
952           " it is comparing do not have the similarity"
953           " level defined with min-avg-similarity or"
954           " min-lowest-similarity", GST_VALIDATE_REPORT_LEVEL_CRITICAL,
955           GST_VALIDATE_ISSUE_FLAGS_FULL_DETAILS |
956           GST_VALIDATE_ISSUE_FLAGS_NO_BACKTRACE));
957
958   gst_validate_issue_register (gst_validate_issue_new
959       (SIMILARITY_ISSUE_WITH_PREVIOUS,
960           "Comparison with theoretical reference image failed",
961           " In a case where we have reference frames with the following"
962           " timestamps: [0.00, 0.10, 0.20, 0.30], comparing a frame with"
963           " 0.05 as a timestamp will be done with the first frame."
964           " If this fails, a ssim::image-not-similar-enough-with-theoretical-reference"
965           " warning is issued and the system then tries with the second reference frame.",
966           GST_VALIDATE_REPORT_LEVEL_WARNING));
967
968   gst_validate_issue_register (gst_validate_issue_new (GENERAL_INPUT_ERROR,
969           "Something went wrong handling image files for ssim comparison",
970           "An error occurred when working with input files",
971           GST_VALIDATE_REPORT_LEVEL_CRITICAL));
972
973   gst_validate_issue_register (gst_validate_issue_new (WRONG_FORMAT,
974           "The format or dimensions of the compared images do not match",
975           "The format or dimensions of the compared images do not match",
976           GST_VALIDATE_REPORT_LEVEL_CRITICAL));
977
978   return NULL;
979 }
980
981 static void
982 gst_validate_ssim_class_init (GstValidateSsimClass * klass)
983 {
984   GObjectClass *oclass = G_OBJECT_CLASS (klass);
985   static GOnce _once = G_ONCE_INIT;
986
987   GST_DEBUG_CATEGORY_INIT (gstvalidatessim_debug, "validatessim", 0,
988       "Validate ssim plugin");
989
990   oclass->get_property = gst_validate_ssim_get_property;
991   oclass->set_property = gst_validate_ssim_set_property;
992   oclass->dispose = gst_validate_ssim_dispose;
993   oclass->finalize = gst_validate_ssim_finalize;
994
995   g_once (&_once, _register_issues, NULL);
996
997   g_object_class_install_property (oclass, PROP_RUNNER,
998       g_param_spec_object ("validate-runner", "VALIDATE Runner",
999           "The Validate runner to report errors to",
1000           GST_TYPE_VALIDATE_RUNNER,
1001           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1002 }
1003
1004 static void
1005 gst_validate_ssim_init (GstValidateSsim * self)
1006 {
1007   self->priv = gst_validate_ssim_get_instance_private (self);
1008
1009   self->priv->ssim = gssim_new ();
1010   self->priv->ref_frames_cache = g_hash_table_new_full (g_str_hash,
1011       g_str_equal, g_free, (GDestroyNotify) g_array_unref);
1012 }
1013
1014 GstValidateSsim *
1015 gst_validate_ssim_new (GstValidateRunner * runner,
1016     gfloat min_avg_similarity, gfloat min_lowest_similarity,
1017     gint fps_n, gint fps_d)
1018 {
1019   GstValidateSsim *self =
1020       g_object_new (GST_VALIDATE_SSIM_TYPE, "validate-runner", runner, NULL);
1021
1022   self->priv->min_avg_similarity = min_avg_similarity;
1023   self->priv->min_lowest_similarity = min_lowest_similarity;
1024   self->priv->fps_n = fps_n;
1025   self->priv->fps_d = fps_d;
1026
1027   gst_validate_reporter_set_name (GST_VALIDATE_REPORTER (self),
1028       g_strdup ("gst-validate-images-checker"));
1029
1030   return self;
1031 }