summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac19
-rw-r--r--docs/plugins/Makefile.am1
-rw-r--r--docs/plugins/gst-plugins-bad-plugins-docs.sgml1
-rw-r--r--docs/plugins/gst-plugins-bad-plugins-sections.txt12
-rw-r--r--ext/Makefile.am7
-rw-r--r--ext/iqa/Makefile.am27
-rw-r--r--ext/iqa/iqa.c343
-rw-r--r--ext/iqa/iqa.h64
8 files changed, 474 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index c48b86cda..35ef31f8a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -358,6 +358,23 @@ AC_SUBST(EXIF_LIBS)
AC_SUBST(EXIF_CFLAGS)
AM_CONDITIONAL(USE_EXIF, test "x$HAVE_EXIF" = "xyes")
+AG_GST_CHECK_FEATURE(IQA, [iqa], iqa , [
+ PKG_CHECK_MODULES(DSSIM, dssim, [
+ HAVE_DSSIM="yes"
+ HAVE_IQA="yes"
+ ], [
+ HAVE_DSSIM="no"
+ HAVE_IQA="no"
+ ])
+
+ AM_CONDITIONAL(HAVE_DSSIM, test "x$HAVE_DSSIM" = "xyes")
+ if test "x$HAVE_DSSIM" = "xyes"; then
+ AC_DEFINE(HAVE_DSSIM, 1, [Define if you have dssim library])
+ fi
+ AC_SUBST(DSSIM_LIBS)
+ AC_SUBST(DSSIM_CFLAGS)
+])
+
dnl Orc
ORC_CHECK([0.4.17])
@@ -3525,6 +3542,7 @@ AM_CONDITIONAL(USE_GSM, false)
AM_CONDITIONAL(USE_GTK3, false)
AM_CONDITIONAL(USE_GTK3_GL, false)
AM_CONDITIONAL(USE_HLS, false)
+AM_CONDITIONAL(USE_IQA, false)
AM_CONDITIONAL(USE_KATE, false)
AM_CONDITIONAL(USE_KMS, false)
AM_CONDITIONAL(USE_TIGER, false)
@@ -3846,6 +3864,7 @@ ext/flite/Makefile
ext/fluidsynth/Makefile
ext/gsm/Makefile
ext/hls/Makefile
+ext/iqa/Makefile
ext/kate/Makefile
ext/ladspa/Makefile
ext/lv2/Makefile
diff --git a/docs/plugins/Makefile.am b/docs/plugins/Makefile.am
index 0dc14f88f..0cce8594a 100644
--- a/docs/plugins/Makefile.am
+++ b/docs/plugins/Makefile.am
@@ -75,6 +75,7 @@ EXTRA_HFILES = \
$(top_srcdir)/ext/dts/gstdtsdec.h \
$(top_srcdir)/ext/faac/gstfaac.h \
$(top_srcdir)/ext/faad/gstfaad.h \
+ $(top_srcdir)/ext/iqa/iqa.h \
$(top_srcdir)/ext/kate/gstkateenc.h \
$(top_srcdir)/ext/kate/gstkatedec.h \
$(top_srcdir)/ext/kate/gstkateparse.h \
diff --git a/docs/plugins/gst-plugins-bad-plugins-docs.sgml b/docs/plugins/gst-plugins-bad-plugins-docs.sgml
index 70b1ea017..199d66fb6 100644
--- a/docs/plugins/gst-plugins-bad-plugins-docs.sgml
+++ b/docs/plugins/gst-plugins-bad-plugins-docs.sgml
@@ -104,6 +104,7 @@
<xi:include href="xml/element-glvideomixerelement.xml" />
<xi:include href="xml/element-glvideomixer.xml" />
<xi:include href="xml/element-glviewconvert.xml" />
+ <xi:include href="xml/element-iqa.xml" />
<xi:include href="xml/element-jpegparse.xml" />
<xi:include href="xml/element-kaleidoscope.xml" />
<xi:include href="xml/element-liveadder.xml" />
diff --git a/docs/plugins/gst-plugins-bad-plugins-sections.txt b/docs/plugins/gst-plugins-bad-plugins-sections.txt
index 811c0e5f0..9596f4389 100644
--- a/docs/plugins/gst-plugins-bad-plugins-sections.txt
+++ b/docs/plugins/gst-plugins-bad-plugins-sections.txt
@@ -2282,6 +2282,18 @@ gst_interlace_get_type
</SECTION>
<SECTION>
+<FILE>element-iqa</FILE>
+<TITLE>IQA</TITLE>
+Iqa
+<SUBSECTION Standard>
+IqaClass
+IQA
+GST_TYPE_IQA
+iqa_get_type
+gst_iqa_plugin_init
+</SECTION>
+
+<SECTION>
<FILE>element-ivfparse</FILE>
<TITLE>ivfparse</TITLE>
GstIvfParse
diff --git a/ext/Makefile.am b/ext/Makefile.am
index 16821e0be..6dfc6bc5a 100644
--- a/ext/Makefile.am
+++ b/ext/Makefile.am
@@ -142,6 +142,12 @@ else
GSM_DIR=
endif
+if USE_IQA
+IQA_DIR = iqa
+else
+IQA_DIR =
+endif
+
if USE_KATE
KATE_DIR=kate
else
@@ -457,6 +463,7 @@ SUBDIRS=\
$(FLUIDSYNTH_DIR) \
$(GSM_DIR) \
$(G729_DIR) \
+ $(IQA_DIR) \
$(KATE_DIR) \
$(LADSPA_DIR) \
$(LV2_DIR) \
diff --git a/ext/iqa/Makefile.am b/ext/iqa/Makefile.am
new file mode 100644
index 000000000..f114d8d09
--- /dev/null
+++ b/ext/iqa/Makefile.am
@@ -0,0 +1,27 @@
+plugin_LTLIBRARIES = libgstiqa.la
+
+libgstiqa_la_SOURCES = \
+ iqa.c
+
+libgstiqa_la_CFLAGS = \
+ -I$(top_srcdir)/gst-libs \
+ -I$(top_builddir)/gst-libs \
+ $(GST_PLUGINS_BASE_CFLAGS) \
+ $(GST_BASE_CFLAGS) $(GST_CFLAGS)
+
+libgstiqa_la_CFLAGS += $(DSSIM_CFLAGS)
+
+libgstiqa_la_LIBADD = \
+ $(top_builddir)/gst-libs/gst/base/libgstbadbase-$(GST_API_VERSION).la \
+ $(top_builddir)/gst-libs/gst/video/libgstbadvideo-$(GST_API_VERSION).la \
+ $(GST_PLUGINS_BASE_LIBS) \
+ $(GST_BASE_LIBS) $(GST_LIBS)
+
+libgstiqa_la_LIBADD += $(DSSIM_LIBS)
+
+libgstiqa_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstiqa_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS)
+
+noinst_HEADERS = \
+ iqa.h
+
diff --git a/ext/iqa/iqa.c b/ext/iqa/iqa.c
new file mode 100644
index 000000000..5ba60b35d
--- /dev/null
+++ b/ext/iqa/iqa.c
@@ -0,0 +1,343 @@
+/* Image Quality Assessment plugin
+ * Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-iqa
+ * @short_description: Image Quality Assessment plugin.
+ *
+ * IQA will perform full reference image quality assessment, with the
+ * first added pad being the reference.
+ *
+ * It will perform comparisons on video streams with the same geometry.
+ *
+ * The image output will be the heat map of differences, between
+ * the two pads with the highest measured difference.
+ *
+ * For each reference frame, IQA will post a message containing
+ * a structure named IQA.
+ *
+ * The only metric supported for now is "dssim", which will be available
+ * if https://github.com/pornel/dssim was installed on the system
+ * at the time that plugin was compiled.
+ *
+ * For each metric activated, this structure will contain another
+ * structure, named after the metric.
+ *
+ * The message will also contain a "time" field.
+ *
+ * For example, if do-dssim is set to true, and there are
+ * two compared streams, the emitted structure will look like this:
+ *
+ * IQA, dssim=(structure)"dssim\,\ sink_1\=\(double\)0.053621271267184856\,\
+ * sink_2\=\(double\)0.0082939683976297474\;",
+ * time=(guint64)0;
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch-1.0 -m uridecodebin uri=file:///test/file/1 ! iqa name=iqa do-dssim=true \
+ * ! videoconvert ! autovideosink uridecodebin uri=file:///test/file/2 ! iqa.
+ * ]| This pipeline will output messages to the console for each set of compared frames.
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "iqa.h"
+
+#ifdef HAVE_DSSIM
+#include "dssim.h"
+#endif
+
+GST_DEBUG_CATEGORY_STATIC (gst_iqa_debug);
+#define GST_CAT_DEFAULT gst_iqa_debug
+
+#define SINK_FORMATS " { AYUV, BGRA, ARGB, RGBA, ABGR, Y444, Y42B, YUY2, UYVY, "\
+ " YVYU, I420, YV12, NV12, NV21, Y41B, RGB, BGR, xRGB, xBGR, "\
+ " RGBx, BGRx } "
+
+#define SRC_FORMAT " { RGBA } "
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (SRC_FORMAT))
+ );
+
+enum
+{
+ PROP_0,
+ PROP_DO_SSIM,
+ PROP_LAST,
+};
+
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (SINK_FORMATS))
+ );
+
+
+/* GstIqa */
+
+#define gst_iqa_parent_class parent_class
+G_DEFINE_TYPE (GstIqa, gst_iqa, GST_TYPE_VIDEO_AGGREGATOR);
+
+#ifdef HAVE_DSSIM
+
+inline static unsigned char
+to_byte (float in)
+{
+ if (in <= 0)
+ return 0;
+ if (in >= 255.f / 256.f)
+ return 255;
+ return in * 256.f;
+}
+
+static void
+do_dssim (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp,
+ GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname)
+{
+ dssim_attr *attr = dssim_create_attr ();
+ gint y;
+ unsigned char **ptrs, **ptrs2;
+ GstMapInfo ref_info;
+ GstMapInfo cmp_info;
+ GstMapInfo out_info;
+ dssim_image *ref_image;
+ dssim_image *cmp_image;
+ double dssim;
+ dssim_ssim_map map_meta;
+ float *map;
+ gint i;
+ dssim_rgba *out;
+ GstStructure *dssim_structure;
+
+ gst_structure_get (msg_structure, "dssim", GST_TYPE_STRUCTURE,
+ &dssim_structure, NULL);
+
+ dssim_set_save_ssim_maps (attr, 1, 1);
+ if (ref->info.width != cmp->info.width ||
+ ref->info.height != cmp->info.height) {
+ GST_WARNING_OBJECT (self,
+ "Cannot compare two images with a different geometry yet");
+ return;
+ }
+
+ gst_buffer_map (ref->buffer, &ref_info, GST_MAP_READ);
+ gst_buffer_map (cmp->buffer, &cmp_info, GST_MAP_READ);
+ gst_buffer_map (outbuf, &out_info, GST_MAP_WRITE);
+ out = (dssim_rgba *) out_info.data;
+
+ ptrs = g_malloc (sizeof (char **) * ref->info.height);
+ ptrs2 = g_malloc (sizeof (char **) * cmp->info.height);
+
+ for (y = 0; y < ref->info.height; y++) {
+ ptrs[y] = ref_info.data + (ref->info.width * 4 * y);
+ }
+
+ ref_image =
+ dssim_create_image (attr, ptrs, DSSIM_RGBA, ref->info.width,
+ ref->info.height, 0.45455);
+
+ ptrs2 = g_malloc (sizeof (char **) * cmp->info.height);
+
+ for (y = 0; y < cmp->info.height; y++) {
+ ptrs2[y] = cmp_info.data + (cmp->info.width * 4 * y);
+ }
+
+ cmp_image =
+ dssim_create_image (attr, ptrs2, DSSIM_RGBA, cmp->info.width,
+ cmp->info.height, 0.45455);
+ dssim = dssim_compare (attr, ref_image, cmp_image);
+
+ map_meta = dssim_pop_ssim_map (attr, 0, 0);
+
+ if (dssim > self->max_dssim) {
+ map = map_meta.data;
+
+ for (i = 0; i < map_meta.width * map_meta.height; i++) {
+ const float max = 1.0 - map[i];
+ const float maxsq = max * max;
+ out[i] = (dssim_rgba) {
+ .r = to_byte (max * 3.0),.g = to_byte (maxsq * 6.0),.b =
+ to_byte (max / ((1.0 - map_meta.dssim) * 4.0)),.a = 255,};
+ }
+ self->max_dssim = dssim;
+ }
+
+ gst_structure_set (dssim_structure, padname, G_TYPE_DOUBLE, dssim, NULL);
+ gst_structure_set (msg_structure, "dssim", GST_TYPE_STRUCTURE,
+ dssim_structure, NULL);
+ gst_structure_free (dssim_structure);
+
+ g_free (ptrs);
+ g_free (ptrs2);
+ gst_buffer_unmap (ref->buffer, &ref_info);
+ gst_buffer_unmap (cmp->buffer, &cmp_info);
+ gst_buffer_unmap (outbuf, &out_info);
+ dssim_dealloc_image (ref_image);
+ dssim_dealloc_image (cmp_image);
+ dssim_dealloc_attr (attr);
+}
+#else
+static void
+do_dssim (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp,
+ GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname)
+{
+}
+#endif
+
+static void
+compare_frames (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp,
+ GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname)
+{
+ if (self->do_dssim)
+ do_dssim (self, ref, cmp, outbuf, msg_structure, padname);
+}
+
+static GstFlowReturn
+gst_iqa_aggregate_frames (GstVideoAggregator * vagg, GstBuffer * outbuf)
+{
+ GList *l;
+ GstVideoFrame *ref_frame = NULL;
+ GstIqa *self = GST_IQA (vagg);
+ GstStructure *msg_structure = gst_structure_new_empty ("IQA");
+ GstMessage *m = gst_message_new_element (GST_OBJECT (self), msg_structure);
+ GstAggregator *agg = GST_AGGREGATOR (vagg);
+
+ if (self->do_dssim) {
+ gst_structure_set (msg_structure, "dssim", GST_TYPE_STRUCTURE,
+ gst_structure_new_empty ("dssim"), NULL);
+ self->max_dssim = 0.0;
+ }
+
+ GST_OBJECT_LOCK (vagg);
+ for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) {
+ GstVideoAggregatorPad *pad = l->data;
+
+ if (pad->aggregated_frame != NULL) {
+ if (!ref_frame) {
+ ref_frame = pad->aggregated_frame;
+ } else {
+ gchar *padname = gst_pad_get_name (pad);
+ GstVideoFrame *cmp_frame = pad->aggregated_frame;
+
+ compare_frames (self, ref_frame, cmp_frame, outbuf, msg_structure,
+ padname);
+ g_free (padname);
+ }
+ }
+ }
+
+ GST_OBJECT_UNLOCK (vagg);
+
+ /* We only post the message here, because we can't post it while the object
+ * is locked.
+ */
+ gst_structure_set (msg_structure, "time", GST_TYPE_CLOCK_TIME,
+ agg->segment.position, NULL);
+ gst_element_post_message (GST_ELEMENT (self), m);
+ return GST_FLOW_OK;
+}
+
+static void
+_set_property (GObject * object, guint prop_id, const GValue * value,
+ GParamSpec * pspec)
+{
+ GstIqa *self = GST_IQA (object);
+
+ switch (prop_id) {
+ case PROP_DO_SSIM:
+ self->do_dssim = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstIqa *self = GST_IQA (object);
+
+ switch (prop_id) {
+ case PROP_DO_SSIM:
+ g_value_set_boolean (value, self->do_dssim);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/* GObject boilerplate */
+static void
+gst_iqa_class_init (GstIqaClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstElementClass *gstelement_class = (GstElementClass *) klass;
+ GstVideoAggregatorClass *videoaggregator_class =
+ (GstVideoAggregatorClass *) klass;
+
+ videoaggregator_class->aggregate_frames = gst_iqa_aggregate_frames;
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&src_factory));
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&sink_factory));
+
+ gobject_class->set_property = _set_property;
+ gobject_class->get_property = _get_property;
+
+#ifdef HAVE_DSSIM
+ g_object_class_install_property (gobject_class, PROP_DO_SSIM,
+ g_param_spec_boolean ("do-dssim", "do-dssim",
+ "Run structural similarity checks", FALSE, G_PARAM_READWRITE));
+#endif
+
+ gst_element_class_set_static_metadata (gstelement_class, "Iqa",
+ "Filter/Analyzer/Video",
+ "Provides various Image Quality Assessment metrics",
+ "Mathieu Duponchelle <mathieu.duponchelle@collabora.co.uk>");
+}
+
+static void
+gst_iqa_init (GstIqa * self)
+{
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ GST_DEBUG_CATEGORY_INIT (gst_iqa_debug, "iqa", 0, "iqa");
+
+ return gst_element_register (plugin, "iqa", GST_RANK_PRIMARY, GST_TYPE_IQA);
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ iqa,
+ "Iqa", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME,
+ GST_PACKAGE_ORIGIN)
diff --git a/ext/iqa/iqa.h b/ext/iqa/iqa.h
new file mode 100644
index 000000000..e879daa26
--- /dev/null
+++ b/ext/iqa/iqa.h
@@ -0,0 +1,64 @@
+/* Image Quality Assessment plugin
+ * Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_IQA_H__
+#define __GST_IQA_H__
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+#include <gst/video/gstvideoaggregator.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_IQA (gst_iqa_get_type())
+#define GST_IQA(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_IQA, GstIqa))
+#define GST_IQA_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_IQA, GstIqaClass))
+#define GST_IS_IQA(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_IQA))
+#define GST_IS_IQA_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_IQA))
+
+typedef struct _GstIqa GstIqa;
+typedef struct _GstIqaClass GstIqaClass;
+
+/**
+ * GstIqa:
+ *
+ * The opaque #GstIqa structure.
+ */
+struct _GstIqa
+{
+ GstVideoAggregator videoaggregator;
+
+ gboolean do_dssim;
+ double max_dssim;
+};
+
+struct _GstIqaClass
+{
+ GstVideoAggregatorClass parent_class;
+};
+
+GType gst_iqa_get_type (void);
+
+G_END_DECLS
+#endif /* __GST_IQA_H__ */
+