summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorРуслан Ижбулатов <lrn1986@gmail.com>2009-09-09 19:14:27 +0400
committerSebastian Dröge <sebastian.droege@collabora.co.uk>2009-09-10 12:03:32 +0200
commite9297ba3eb7af24a81051dfc57f0ff5cbd15735e (patch)
tree224a2292343a3a7b72f60fa19d04c1decc6f7a5f
parent1678e89301d6e72df3be7668a758d10ed8f6cfd5 (diff)
videomeasure: Initial version of videomeasure plugin
This plugin contains elements for calculating metrics of video streams, intended for objective video codec comparison. At the moment only SSIM metric is implemented (why would you need anything else anyway?). Also contains a helper videomeasure_collector element that collects measurement events and outputs them into a file (to be used with gst-launch). Other metrics may be implemented in the future along with a base class for all measurers. Fixes bug #594321.
-rw-r--r--configure.ac2
-rw-r--r--gst/videomeasure/Makefile.am16
-rw-r--r--gst/videomeasure/gstvideomeasure.c70
-rw-r--r--gst/videomeasure/gstvideomeasure.h31
-rw-r--r--gst/videomeasure/gstvideomeasure_collector.c417
-rw-r--r--gst/videomeasure/gstvideomeasure_collector.h80
-rw-r--r--gst/videomeasure/gstvideomeasure_ssim.c1743
-rw-r--r--gst/videomeasure/gstvideomeasure_ssim.h140
8 files changed, 2499 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index 6f060ec70..91d099eca 100644
--- a/configure.ac
+++ b/configure.ac
@@ -296,6 +296,7 @@ AG_GST_CHECK_PLUGIN(subenc)
AG_GST_CHECK_PLUGIN(stereo)
AG_GST_CHECK_PLUGIN(tta)
AG_GST_CHECK_PLUGIN(valve)
+AG_GST_CHECK_PLUGIN(videomeasure)
AG_GST_CHECK_PLUGIN(videosignal)
AG_GST_CHECK_PLUGIN(vmnc)
@@ -1747,6 +1748,7 @@ gst/subenc/Makefile
gst/stereo/Makefile
gst/tta/Makefile
gst/valve/Makefile
+gst/videomeasure/Makefile
gst/videosignal/Makefile
gst/vmnc/Makefile
gst-libs/Makefile
diff --git a/gst/videomeasure/Makefile.am b/gst/videomeasure/Makefile.am
new file mode 100644
index 000000000..c36ad642d
--- /dev/null
+++ b/gst/videomeasure/Makefile.am
@@ -0,0 +1,16 @@
+plugin_LTLIBRARIES = libgstvideomeasure.la
+
+noinst_HEADERS = gstvideomeasure_ssim.h gstvideomeasure_collector.h
+
+libgstvideomeasure_la_SOURCES = \
+ gstvideomeasure.c \
+ gstvideomeasure_ssim.c \
+ gstvideomeasure_collector.c
+
+libgstvideomeasure_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) \
+ $(GST_PLUGINS_BASE_CFLAGS)
+libgstvideomeasure_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) \
+ -lgstvideo-@GST_MAJORMINOR@ $(GST_BASE_LIBS) $(GST_LIBS)
+libgstvideomeasure_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstvideomeasure_la_LIBTOOLFLAGS = --tag=disable-static
+
diff --git a/gst/videomeasure/gstvideomeasure.c b/gst/videomeasure/gstvideomeasure.c
new file mode 100644
index 000000000..52aa80573
--- /dev/null
+++ b/gst/videomeasure/gstvideomeasure.c
@@ -0,0 +1,70 @@
+/* GStreamer
+ * Copyright (C) <2009> LRN <lrn1986 _at_ gmail _dot_ com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstvideomeasure.h"
+#include "gstvideomeasure_ssim.h"
+#include "gstvideomeasure_collector.h"
+
+GstEvent *gst_event_new_measured (guint64 framenumber, GstClockTime timestamp,
+ const gchar *metric, const GValue *mean, const GValue *lowest,
+ const GValue *highest)
+{
+ GstStructure *str = gst_structure_new(
+ GST_EVENT_VIDEO_MEASURE,
+ "event", G_TYPE_STRING, "frame-measured",
+ "offset", G_TYPE_UINT64, framenumber,
+ "timestamp", GST_TYPE_CLOCK_TIME, timestamp,
+ "metric", G_TYPE_STRING, metric,
+ NULL);
+ gst_structure_set_value (str, "mean", mean);
+ gst_structure_set_value (str, "lowest", lowest);
+ gst_structure_set_value (str, "highest", highest);
+ return gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, str);
+}
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ gboolean res;
+
+#if ENABLE_NLS
+ GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE,
+ LOCALEDIR);
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+#endif
+
+ res = gst_element_register (plugin, "ssim", GST_RANK_NONE,
+ GST_TYPE_SSIM);
+
+ res &= gst_element_register (plugin, "measurecollector", GST_RANK_NONE,
+ GST_TYPE_MEASURE_COLLECTOR);
+
+ return res;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ "videomeasure",
+ "Various video measurers",
+ plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
diff --git a/gst/videomeasure/gstvideomeasure.h b/gst/videomeasure/gstvideomeasure.h
new file mode 100644
index 000000000..4db1ee7e1
--- /dev/null
+++ b/gst/videomeasure/gstvideomeasure.h
@@ -0,0 +1,31 @@
+/* GStreamer
+ * Copyright (C) <2009> LRN <lrn1986 _at_ gmail _dot_ com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GST_VIDEO_MEASURE_H__
+#define __GST_VIDEO_MEASURE_H__
+
+#include <gst/video/gstvideofilter.h>
+
+#define GST_EVENT_VIDEO_MEASURE "application/x-videomeasure"
+
+GstEvent *gst_event_new_measured (guint64 framenumber, GstClockTime timestamp,
+ const gchar *metric, const GValue *mean, const GValue *lowest,
+ const GValue *highest);
+
+#endif /* __GST_VIDEO_MEASURE_H__ */
diff --git a/gst/videomeasure/gstvideomeasure_collector.c b/gst/videomeasure/gstvideomeasure_collector.c
new file mode 100644
index 000000000..2a863a95c
--- /dev/null
+++ b/gst/videomeasure/gstvideomeasure_collector.c
@@ -0,0 +1,417 @@
+/* GStreamer
+ * Copyright (C) <2009> LRN <lrn1986 _at_ gmail _dot_ com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:element-measurecollector
+ *
+ * This plugin collects measurements from measuring elemtns and calculates
+ * total measure for the whole sequence and also outputs measurements to a file
+ * <classname>&quot;GstMeasureCollector&quot;</classname>.
+ *
+ *
+ * Last reviewed on 2009-03-15 (0.10.?)
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "../../gst-libs/gst/gst-i18n-plugin.h"
+
+#include "gstvideomeasure_collector.h"
+
+#include <string.h>
+#include <math.h>
+
+#include <gst/video/video.h>
+
+/* GstMeasureCollector signals and args */
+
+enum
+{
+ PROP_0,
+ PROP_FLAGS,
+ PROP_FILENAME
+};
+
+GST_DEBUG_CATEGORY_STATIC (measure_collector_debug);
+#define GST_CAT_DEFAULT measure_collector_debug
+
+static const GstElementDetails measure_collector_details =
+GST_ELEMENT_DETAILS ("Video measure collector",
+ "Filter/Effect/Video",
+ "Collect measurements from a measuring element",
+ "LRN <lrn _at_ gmail _dot_ com>");
+
+static GstStaticPadTemplate gst_measure_collector_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY
+ );
+
+static GstStaticPadTemplate gst_measure_collector_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY
+ );
+
+//static GstBaseTransformClass *parent_class = NULL;
+
+static void gst_measure_collector_finalize (GObject * object);
+static gboolean gst_measure_collector_event (GstBaseTransform * base,
+ GstEvent * event);
+static void gst_measure_collector_save_csv(GstMeasureCollector *mc);
+
+static void gst_measure_collector_post_message (GstMeasureCollector *mc);
+
+GST_BOILERPLATE (GstMeasureCollector, gst_measure_collector, GstBaseTransform,
+ GST_TYPE_BASE_TRANSFORM);
+
+static void
+gst_measure_collector_collect (GstMeasureCollector *mc, GstEvent *gstevent)
+{
+ const GstStructure *str;
+ const gchar *event, *metric;
+ guint64 framenumber = G_MAXUINT64;
+ const GValue *framenumber_v;
+
+ str = gst_event_get_structure (gstevent);
+
+ event = gst_structure_get_string (str, "event");
+ metric = gst_structure_get_string (str, "metric");
+
+ if (strcmp (event, "frame-measured") == 0 && metric != NULL)
+ {
+ GstStructure *cpy;
+ cpy = gst_structure_copy (str);
+
+ framenumber_v = gst_structure_get_value (str, "offset");
+ if (framenumber_v)
+ {
+ if (G_VALUE_TYPE (framenumber_v) == G_TYPE_UINT64)
+ framenumber = g_value_get_uint64 (framenumber_v);
+ else if (G_VALUE_TYPE (framenumber_v) == G_TYPE_INT64)
+ framenumber = g_value_get_int64 (framenumber_v);
+ }
+
+ if (framenumber == G_MAXUINT64)
+ framenumber = mc->nextoffset++;
+
+ if (mc->measurements->len <= framenumber)
+ g_ptr_array_set_size (mc->measurements, framenumber + 1);
+ g_ptr_array_index (mc->measurements, framenumber) = cpy;
+
+ mc->nextoffset = framenumber + 1;
+
+ if (!mc->metric)
+ mc->metric = g_strdup (metric);
+ }
+}
+
+static void
+gst_measure_collector_post_message (GstMeasureCollector *mc)
+{
+ GstBaseTransform *trans;
+ GstMessage *m;
+ guint64 i;
+
+ trans = GST_BASE_TRANSFORM_CAST (mc);
+
+ g_return_if_fail (mc->metric);
+
+ if (strcmp (mc->metric, "SSIM") == 0)
+ {
+ gfloat dresult = 0;
+ g_free (mc->result);
+ mc->result = g_new0 (GValue, 1);
+ g_value_init (mc->result, G_TYPE_FLOAT);
+ for (i = 0; i < mc->measurements->len; i++)
+ {
+ const GValue *v;
+ GstStructure *str = (GstStructure *) g_ptr_array_index (mc->measurements, i);
+ v = gst_structure_get_value (str, "mean");
+ dresult += g_value_get_float (v);
+ }
+ g_value_set_float (mc->result, dresult / mc->measurements->len);
+ }
+
+ m = gst_message_new_element (GST_OBJECT_CAST (mc),
+ gst_structure_new ("GstMeasureCollector",
+ "measure-result", G_TYPE_VALUE, mc->result,
+ NULL));
+
+ gst_element_post_message (GST_ELEMENT_CAST (mc), m);
+}
+
+static void
+gst_measure_collector_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstMeasureCollector *measurecollector;
+
+ measurecollector = GST_MEASURE_COLLECTOR (object);
+
+ switch (prop_id) {
+ case PROP_FLAGS:
+ measurecollector->flags = g_value_get_uint64 (value);
+ break;
+ case PROP_FILENAME:
+ measurecollector->filename = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_measure_collector_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstMeasureCollector *measurecollector;
+
+ measurecollector = GST_MEASURE_COLLECTOR (object);
+
+ switch (prop_id) {
+ case PROP_FLAGS:
+ g_value_set_uint64 (value, measurecollector->flags);
+ break;
+ case PROP_FILENAME:
+ g_value_set_string (value, measurecollector->filename);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_measure_collector_event (GstBaseTransform * base, GstEvent * event)
+{
+ GstMeasureCollector *mc = GST_MEASURE_COLLECTOR (base);
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_CUSTOM_DOWNSTREAM:
+ if (gst_event_has_name (event, GST_EVENT_VIDEO_MEASURE))
+ gst_measure_collector_collect (mc, event);
+ break;
+ case GST_EVENT_EOS:
+ gst_measure_collector_post_message (mc);
+ gst_measure_collector_save_csv (mc);
+ break;
+ default:
+ break;
+ }
+
+ return parent_class->event (base, event);
+}
+
+static void gst_measure_collector_save_csv(GstMeasureCollector *mc)
+{
+ gchar *name_local;
+ FILE *file;
+ guint64 i, j;
+ GstStructure *str;
+ GValue tmp = { 0 };
+ g_value_init (&tmp, G_TYPE_STRING);
+
+ if (!(mc->flags & GST_MEASURE_COLLECTOR_WRITE_CSV))
+ return;
+
+ if (mc->measurements->len <= 0)
+ goto empty;
+
+ /* open the file */
+ if (mc->filename == NULL || mc->filename[0] == '\0')
+ goto no_filename;
+
+ name_local = g_filename_from_utf8 ((const gchar*) mc->filename,
+ -1, NULL, NULL, NULL);
+
+ /* open the file */
+ if (name_local == NULL || name_local[0] == '\0')
+ goto not_good_filename;
+
+
+ /* FIXME, can we use g_fopen here? some people say that the FILE object is
+ * local to the .so that performed the fopen call, which would not be us when
+ * we use g_fopen. */
+ file = fopen (name_local, "wb");
+
+ g_free(name_local);
+
+ if (file == NULL)
+ goto open_failed;
+
+ str = (GstStructure *) g_ptr_array_index (mc->measurements, 0);
+
+ for (j = 0; j < gst_structure_n_fields (str); j++)
+ {
+ const gchar *fieldname;
+ fieldname = gst_structure_nth_field_name (str, j);
+ if (G_LIKELY (j > 0))
+ fprintf(file, ";", fieldname);
+ fprintf(file, "%s", fieldname);
+ }
+
+ for (i = 0; i < mc->measurements->len; i++)
+ {
+ fprintf(file, "\n");
+ str = (GstStructure *) g_ptr_array_index (mc->measurements, i);
+ for (j = 0; j < gst_structure_n_fields (str); j++)
+ {
+ const gchar *fieldname;
+ fieldname = gst_structure_nth_field_name (str, j);
+ if (G_LIKELY (j > 0))
+ fprintf(file, ";", fieldname);
+ if (G_LIKELY (g_value_transform (gst_structure_get_value (str, fieldname), &tmp)))
+ fprintf(file, "%s", g_value_get_string (&tmp));
+ else
+ fprintf(file, "<untranslatable>");
+ }
+ }
+
+ fclose(file);
+
+ /* ERRORS */
+empty:
+ {
+ return;
+ }
+no_filename:
+ {
+ GST_ELEMENT_ERROR (mc, RESOURCE, NOT_FOUND,
+ (_("No file name specified for writing.")), (NULL));
+ return;
+ }
+not_good_filename:
+ {
+ GST_ELEMENT_ERROR (mc, RESOURCE, NOT_FOUND,
+ (_("Given file name \"%s\" can't be converted to local file name \
+encoding."),
+ mc->filename), (NULL));
+ return;
+ }
+open_failed:
+ {
+ GST_ELEMENT_ERROR (mc, RESOURCE, OPEN_WRITE,
+ (_("Could not open file \"%s\" for writing."), mc->filename),
+ GST_ERROR_SYSTEM);
+ return;
+ }
+}
+
+static void
+gst_measure_collector_base_init (gpointer g_class)
+{
+ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+
+ gst_element_class_set_details (element_class, &measure_collector_details);
+
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&gst_measure_collector_sink_template));
+ gst_element_class_add_pad_template (element_class,
+ gst_static_pad_template_get (&gst_measure_collector_src_template));
+}
+
+static void
+gst_measure_collector_class_init (GstMeasureCollectorClass *klass)
+{
+ GObjectClass *gobject_class;
+ GstBaseTransformClass *trans_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ trans_class = GST_BASE_TRANSFORM_CLASS (klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "measurecollect", 0,
+ "measurement collector");
+
+ gobject_class->set_property = gst_measure_collector_set_property;
+ gobject_class->get_property = gst_measure_collector_get_property;
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_measure_collector_finalize);
+
+ g_object_class_install_property (gobject_class, PROP_FLAGS,
+ g_param_spec_uint64 ("flags", "Flags",
+ "Flags that control the operation of the element",
+ 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (gobject_class, PROP_FILENAME,
+ g_param_spec_string ("filename", "Output file name",
+ "A name of a file into which element will write the measurement \
+information",
+ "", G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ trans_class->event =
+ GST_DEBUG_FUNCPTR (gst_measure_collector_event);
+
+ trans_class->passthrough_on_same_caps = TRUE;
+
+}
+
+static void
+gst_measure_collector_init (GstMeasureCollector *instance,
+ GstMeasureCollectorClass *g_class)
+{
+ GstMeasureCollector *measurecollector;
+
+ measurecollector = GST_MEASURE_COLLECTOR (instance);
+
+ GST_DEBUG_OBJECT (measurecollector, "gst_measure_collector_init");
+
+ gst_base_transform_set_qos_enabled (GST_BASE_TRANSFORM (measurecollector),
+ FALSE);
+
+ measurecollector->measurements = g_ptr_array_new ();
+ measurecollector->metric = NULL;
+ measurecollector->inited = TRUE;
+ measurecollector->filename = NULL;
+ measurecollector->flags = 0;
+ measurecollector->nextoffset = 0;
+ measurecollector->result = NULL;
+}
+
+static void
+gst_measure_collector_finalize (GObject * object)
+{
+ gint i;
+ GstMeasureCollector *mc = GST_MEASURE_COLLECTOR (object);
+
+ for (i = 0; i < mc->measurements->len; i++)
+ {
+ gst_structure_free ((GstStructure *) g_ptr_array_index (mc->measurements, i));
+ }
+
+ g_ptr_array_free (mc->measurements, TRUE);
+ mc->measurements = NULL;
+
+ g_free (mc->result);
+ mc->result = NULL;
+
+ g_free (mc->metric);
+ mc->metric = NULL;
+
+ g_free (mc->filename);
+ mc->filename = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
diff --git a/gst/videomeasure/gstvideomeasure_collector.h b/gst/videomeasure/gstvideomeasure_collector.h
new file mode 100644
index 000000000..49d6c17c1
--- /dev/null
+++ b/gst/videomeasure/gstvideomeasure_collector.h
@@ -0,0 +1,80 @@
+/* GStreamer
+ * Copyright (C) <2009> LRN <lrn1986 _at_ gmail _dot_ com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GST_MEASURE_COLLECTOR_H__
+#define __GST_MEASURE_COLLECTOR_H__
+
+#include "gstvideomeasure.h"
+#include <gst/base/gstbasetransform.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GstMeasureCollector GstMeasureCollector;
+typedef struct _GstMeasureCollectorClass GstMeasureCollectorClass;
+
+#define GST_TYPE_MEASURE_COLLECTOR (gst_measure_collector_get_type())
+#define GST_MEASURE_COLLECTOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MEASURE_COLLECTOR, \
+ GstMeasureCollector))
+#define GST_IS_MEASURE_COLLECTOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_MEASURE_COLLECTOR))
+#define GST_MEASURE_COLLECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),\
+ GST_TYPE_MEASURE_COLLECTOR, GstMeasureCollectorClass))
+#define GST_IS_MEASURE_COLLECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),\
+ GST_TYPE_MEASURE_COLLECTOR))
+#define GST_MEASURE_COLLECTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),\
+ GST_TYPE_MEASURE_COLLECTOR, GstMeasureCollectorClass))
+
+typedef enum {
+ GST_MEASURE_COLLECTOR_0 = 0,
+ GST_MEASURE_COLLECTOR_WRITE_CSV = 0x1,
+ GST_MEASURE_COLLECTOR_EMIT_MESSAGE = 0x1 << 1,
+ GST_MEASURE_COLLECTOR_ALL =
+ GST_MEASURE_COLLECTOR_WRITE_CSV |
+ GST_MEASURE_COLLECTOR_EMIT_MESSAGE
+} GstMeasureCollectorFlags;
+
+struct _GstMeasureCollector {
+ GstBaseTransform element;
+
+ guint64 flags;
+
+ gchar *filename;
+
+ /* Array of pointers to GstStructure */
+ GPtrArray *measurements;
+
+ GValue *result;
+
+ guint64 nextoffset;
+
+ gchar *metric;
+
+ gboolean inited;
+};
+
+struct _GstMeasureCollectorClass {
+ GstBaseTransformClass parent_class;
+};
+
+GType gst_measure_collector_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_MEASURE_COLLECTOR_H__ */
diff --git a/gst/videomeasure/gstvideomeasure_ssim.c b/gst/videomeasure/gstvideomeasure_ssim.c
new file mode 100644
index 000000000..4b41fad5c
--- /dev/null
+++ b/gst/videomeasure/gstvideomeasure_ssim.c
@@ -0,0 +1,1743 @@
+/* GStreamer
+ * Copyright (C) <2009> LRN <lrn1986 _at_ gmail _dot_ com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:element-ssim
+ *
+ * The ssim calculates SSIM (Structural SIMilarity) index for two or more
+ * streams, for each frame.
+ * First stream is the original, other streams are modified (compressed) ones.
+ * ssim will calculate SSIM index of each frame of each modified stream, using
+ * original stream as a reference.
+ *
+ * The ssim accepts only YUV planar top-first data and calculates only Y-SSIM.
+ * All streams must have the same width, height and colorspace.
+ * Output streams are greyscale video streams, where bright pixels indicate
+ * high SSIM values, dark pixels - low SSIM values.
+ * The ssim also calculates mean SSIM index for each frame and emits is as a
+ * message.
+ * ssim is intended to be used with videomeasure_collector element to catch the
+ * events (such as mean SSIM index values) and save them into a file.
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch ssim name=ssim ssim.src0 ! ffmpegcolorspace ! glimagesink filesrc
+ * location=orig.avi ! decodebin2 ! ssim.original filesrc location=compr.avi !
+ * decodebin2 ! ssim.modified0
+ * ]| This pipeline produces a video stream that consists of SSIM frames.
+ * </refsect2>
+ *
+ * Last reviewed on 2009-09-06 (0.10.?)
+ */
+/* Element-Checklist-Version: 5 */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstvideomeasure.h"
+#include "gstvideomeasure_ssim.h"
+#include <gst/audio/audio.h>
+#include <string.h>
+#include <math.h>
+
+#define GST_CAT_DEFAULT gst_ssim_debug
+GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
+
+/* elementfactory information */
+
+#define SINK_CAPS \
+ "video/x-raw-yuv, " \
+ "format = (fourcc) { YV12, Y41B, Y42B } "
+
+
+#define SRC_CAPS \
+ "video/x-raw-gray, " \
+ "width = (int) [ 1, MAX ], " \
+ "height = (int) [ 1, MAX ], " \
+ "framerate = (fraction) [ 0/1, MAX ], " \
+ "bpp = (int) 8, " \
+ "depth = (int) 8 "
+
+static GstStaticPadTemplate gst_ssim_src_template =
+GST_STATIC_PAD_TEMPLATE ("src%d",
+ GST_PAD_SRC,
+ GST_PAD_SOMETIMES,
+ GST_STATIC_CAPS (SRC_CAPS)
+ );
+
+static GstStaticPadTemplate gst_ssim_sink_original_template =
+GST_STATIC_PAD_TEMPLATE ("original",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS (SINK_CAPS)
+ );
+
+static GstStaticPadTemplate gst_ssim_sink_modified_template =
+GST_STATIC_PAD_TEMPLATE ("modified%d",
+ GST_PAD_SINK,
+ GST_PAD_REQUEST,
+ GST_STATIC_CAPS (SINK_CAPS)
+ );
+
+static void gst_ssim_class_init (GstSSimClass * klass);
+static void gst_ssim_init (GstSSim * ssim);
+static void gst_ssim_finalize (GObject * object);
+
+static gboolean gst_ssim_setcaps (GstPad * pad, GstCaps * caps);
+static gboolean gst_ssim_query (GstPad * pad, GstQuery * query);
+static gboolean gst_ssim_src_event (GstPad * pad, GstEvent * event);
+static gboolean gst_ssim_sink_event (GstPad * pad, GstEvent * event);
+
+static GstPad *gst_ssim_request_new_pad (GstElement * element,
+ GstPadTemplate * temp, const gchar * unused);
+static void gst_ssim_release_pad (GstElement * element, GstPad * pad);
+
+static GstStateChangeReturn gst_ssim_change_state (GstElement * element,
+ GstStateChange transition);
+
+static GstFlowReturn gst_ssim_collected (GstCollectPads * pads,
+ gpointer user_data);
+
+static GstElementClass *parent_class = NULL;
+
+GType
+gst_ssim_get_type (void)
+{
+ static GType ssim_type = 0;
+
+ if (G_UNLIKELY (ssim_type == 0)) {
+ static const GTypeInfo ssim_info = {
+ sizeof (GstSSimClass), NULL, NULL,
+ (GClassInitFunc) gst_ssim_class_init, NULL, NULL,
+ sizeof (GstSSim), 0,
+ (GInstanceInitFunc) gst_ssim_init,
+ };
+
+ ssim_type = g_type_register_static (GST_TYPE_ELEMENT, "GstSSim",
+ &ssim_info, 0);
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "ssim", 0,
+ "SSIM calculator");
+ }
+ return ssim_type;
+}
+
+static void
+gst_ssim_post_message (GstSSim *ssim, GstBuffer *buffer, gfloat mssim,
+ gfloat lowest, gfloat highest)
+{
+ GstMessage *m;
+ guint64 offset;
+
+ offset = GST_BUFFER_OFFSET (buffer);
+
+ m = gst_message_new_element (GST_OBJECT_CAST (ssim),
+ gst_structure_new ("SSIM",
+ "offset", G_TYPE_UINT64, offset,
+ "timestamp", GST_TYPE_CLOCK_TIME, GST_BUFFER_TIMESTAMP (buffer),
+ "mean", G_TYPE_FLOAT, mssim,
+ "lowest", G_TYPE_FLOAT, lowest,
+ "highest", G_TYPE_FLOAT, highest,
+ NULL));
+
+ GST_DEBUG_OBJECT (GST_OBJECT (ssim), "Frame %" G_GINT64_FORMAT
+ " @ %" GST_TIME_FORMAT " mean SSIM is %f, l-h is %f-%f", offset,
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), mssim, lowest, highest);
+
+ gst_element_post_message (GST_ELEMENT_CAST (ssim), m);
+}
+
+static GstCaps *
+gst_ssim_src_getcaps (GstPad * pad)
+{
+ GstCaps *result;
+ gchar *capstr;
+
+ result = gst_caps_copy( gst_pad_get_pad_template_caps (pad));
+ capstr = gst_caps_to_string (result);
+ GST_DEBUG ("getsrccaps - return static caps: %s", capstr);
+ g_free (capstr);
+ return result;
+}
+
+static GstCaps *
+gst_ssim_sink_getcaps (GstPad * pad)
+{
+ GstCaps *result = NULL;
+ GstSSim *ssim;
+ gchar *capstr;
+
+ ssim = GST_SSIM (GST_PAD_PARENT (pad));
+
+ GST_OBJECT_LOCK (ssim);
+
+ result = gst_pad_get_fixed_caps_func (pad);
+ capstr = gst_caps_to_string (result);
+ GST_DEBUG ("getsinkcaps - return caps: %s", capstr);
+ g_free (capstr);
+
+ GST_OBJECT_UNLOCK (ssim);
+
+ return result;
+}
+
+static void
+calculate_mu (GstSSim *ssim, gfloat *outmu, guint8 *buf)
+{
+ gint oy, ox, iy, ix;
+
+ for (oy = 0; oy < ssim->height; oy++)
+ {
+ for (ox = 0; ox < ssim->width; ox++)
+ {
+ gfloat mu = 0;
+ gfloat elsumm;
+ gint weight_y_base, weight_x_base;
+ gint weight_offset;
+ gint pixel_offset;
+ gint winstart_y;
+ gint wghstart_y;
+ gint winend_y;
+ gint winstart_x;
+ gint wghstart_x;
+ gint winend_x;
+ gint winlen_x;
+ gint winstride_x;
+ gfloat weight;
+ gint source_offset;
+
+ source_offset = oy * ssim->width + ox;
+
+ winstart_x = ssim->windows[source_offset].x_window_start;
+ wghstart_x = ssim->windows[source_offset].x_weight_start;
+ winend_x = ssim->windows[source_offset].x_window_end;
+ winstart_y = ssim->windows[source_offset].y_window_start;
+ wghstart_y = ssim->windows[source_offset].y_weight_start;
+ winend_y = ssim->windows[source_offset].y_window_end;
+ winlen_x = winend_x - winstart_x + 1;
+ winstride_x = sizeof(gfloat) * winlen_x;
+ elsumm = ssim->windows[source_offset].element_summ;
+
+ switch (ssim->windowtype)
+ {
+ case 0:
+ for (iy = winstart_y; iy <= winend_y; iy++)
+ {
+ pixel_offset = iy * ssim->width;
+ for (ix = winstart_x; ix <= winend_x; ix++)
+ mu += buf[pixel_offset + ix];
+ }
+ mu = mu / elsumm;
+ break;
+ case 1:
+
+ weight_y_base = wghstart_y - winstart_y;
+ weight_x_base = wghstart_x - winstart_x;
+
+ for (iy = winstart_y; iy <= winend_y; iy++)
+ {
+ pixel_offset = iy * ssim->width;
+ weight_offset = (weight_y_base + iy) * ssim->windowsize +
+ weight_x_base;
+ for (ix = winstart_x; ix <= winend_x; ix++)
+ {
+ weight = ssim->weights[weight_offset + ix];
+ mu += weight * buf[pixel_offset + ix];
+ }
+ }
+ mu = mu / elsumm;
+ break;
+ }
+ outmu[oy * ssim->width + ox] = mu;
+ }
+ }
+
+}
+
+static void
+calcssim_without_mu (GstSSim *ssim, guint8 *org, gfloat *orgmu, guint8 *mod,
+ guint8 *out, gfloat *mean, gfloat *lowest, gfloat *highest)
+{
+ gint oy, ox, iy, ix;
+ gfloat cumulative_ssim = 0;
+ *lowest = G_MAXFLOAT;
+ *highest = -G_MAXFLOAT;
+
+ for (oy = 0; oy < ssim->height; oy++)
+ {
+ for (ox = 0; ox < ssim->width; ox++)
+ {
+ gfloat mu_o = 128, mu_m = 128;
+ gdouble sigma_o = 0, sigma_m = 0, sigma_om = 0;
+ gfloat tmp1 = 0, tmp2 = 0;
+ gfloat elsumm = 0;
+ gint weight_y_base, weight_x_base;
+ gint weight_offset;
+ gint pixel_offset;
+ gint winstart_y;
+ gint wghstart_y;
+ gint winend_y;
+ gint winstart_x;
+ gint wghstart_x;
+ gint winend_x;
+ gfloat weight;
+ gint source_offset;
+
+ source_offset = oy * ssim->width + ox;
+
+ winstart_x = ssim->windows[source_offset].x_window_start;
+ wghstart_x = ssim->windows[source_offset].x_weight_start;
+ winend_x = ssim->windows[source_offset].x_window_end;
+ winstart_y = ssim->windows[source_offset].y_window_start;
+ wghstart_y = ssim->windows[source_offset].y_weight_start;
+ winend_y = ssim->windows[source_offset].y_window_end;
+ elsumm = ssim->windows[source_offset].element_summ;
+
+ weight_y_base = wghstart_y - winstart_y;
+ weight_x_base = wghstart_x - winstart_x;
+ switch (ssim->windowtype)
+ {
+ case 0:
+ for (iy = winstart_y; iy <= winend_y; iy++)
+ {
+ guint8 *org_with_offset, *mod_with_offset;
+ pixel_offset = iy * ssim->width;
+ org_with_offset = &org[pixel_offset];
+ mod_with_offset = &mod[pixel_offset];
+ for (ix = winstart_x; ix <= winend_x; ix++)
+ {
+ tmp1 = org_with_offset[ix] - mu_o;
+ sigma_o += tmp1 * tmp1;
+ tmp2 = mod_with_offset[ix] - mu_m;
+ sigma_m += tmp2 * tmp2;
+ sigma_om += tmp1 * tmp2;
+ }
+ }
+ break;
+ case 1:
+
+ weight_y_base = wghstart_y - winstart_y;
+ weight_x_base = wghstart_x - winstart_x;
+
+ for (iy = winstart_y; iy <= winend_y; iy++)
+ {
+ guint8 *org_with_offset, *mod_with_offset;
+ gfloat *weights_with_offset;
+ gfloat wt1, wt2;
+ pixel_offset = iy * ssim->width;
+ weight_offset = (weight_y_base + iy) * ssim->windowsize +
+ weight_x_base;
+ org_with_offset = &org[pixel_offset];
+ mod_with_offset = &mod[pixel_offset];
+ weights_with_offset = &ssim->weights[weight_offset];
+ for (ix = winstart_x; ix <= winend_x; ix++)
+ {
+ weight = weights_with_offset[ix];
+ tmp1 = org_with_offset[ix] - mu_o;
+ tmp2 = mod_with_offset[ix] - mu_m;
+ wt1 = weight * tmp1;
+ wt2 = weight * tmp2;
+ sigma_o += wt1 * tmp1;
+ sigma_m += wt2 * tmp2;
+ sigma_om += wt1 * tmp2;
+ }
+ }
+ break;
+ }
+ sigma_o = sqrt (sigma_o / elsumm);
+ sigma_m = sqrt (sigma_m / elsumm);
+ sigma_om = sigma_om / elsumm;
+ tmp1 = (2 * mu_o * mu_m + ssim->const1) * (2 * sigma_om + ssim->const2) /
+ ( (mu_o * mu_o + mu_m * mu_m + ssim->const1) *
+ (sigma_o * sigma_o + sigma_m * sigma_m + ssim->const2) );
+
+ /* SSIM can go negative, that's why it is
+ 127 + index * 128 instead of index * 255 */
+ out[oy * ssim->width + ox] = 127 + tmp1 * 128;
+ *lowest = MIN (*lowest, tmp1);
+ *highest = MAX (*highest, tmp1);
+ cumulative_ssim += tmp1;
+ }
+ }
+ *mean = cumulative_ssim / (ssim->width * ssim->height);
+}
+
+static void
+calcssim_canonical (GstSSim *ssim, guint8 *org, gfloat *orgmu, guint8 *mod,
+ guint8 *out, gfloat *mean, gfloat *lowest, gfloat *highest)
+{
+ gint oy, ox, iy, ix;
+ gfloat cumulative_ssim = 0;
+ *lowest = G_MAXFLOAT;
+ *highest = -G_MAXFLOAT;
+
+ for (oy = 0; oy < ssim->height; oy++)
+ {
+ for (ox = 0; ox < ssim->width; ox++)
+ {
+ gfloat mu_o = 0, mu_m = 0;
+ gdouble sigma_o = 0, sigma_m = 0, sigma_om = 0;
+ gfloat tmp1, tmp2;
+ gfloat elsumm = 0;
+ gint weight_y_base, weight_x_base;
+ gint weight_offset;
+ gint pixel_offset;
+ gint winstart_y;
+ gint wghstart_y;
+ gint winend_y;
+ gint winstart_x;
+ gint wghstart_x;
+ gint winend_x;
+ gint winlen_x;
+ gint winstride_x;
+ gfloat weight;
+ gint source_offset;
+
+ source_offset = oy * ssim->width + ox;
+
+ winstart_x = ssim->windows[source_offset].x_window_start;
+ wghstart_x = ssim->windows[source_offset].x_weight_start;
+ winend_x = ssim->windows[source_offset].x_window_end;
+ winstart_y = ssim->windows[source_offset].y_window_start;
+ wghstart_y = ssim->windows[source_offset].y_weight_start;
+ winend_y = ssim->windows[source_offset].y_window_end;
+ winlen_x = winend_x - winstart_x + 1;
+ winstride_x = sizeof(gfloat) * winlen_x;
+ elsumm = ssim->windows[source_offset].element_summ;
+
+ switch (ssim->windowtype)
+ {
+ case 0:
+ for (iy = winstart_y; iy <= winend_y; iy++)
+ {
+ pixel_offset = iy * ssim->width;
+ for (ix = winstart_x; ix <= winend_x; ix++)
+ {
+ mu_m += mod[pixel_offset + ix];
+ }
+ }
+ mu_m = mu_m / elsumm;
+ mu_o = orgmu[oy * ssim->width + ox];
+ for (iy = winstart_y; iy <= winend_y; iy++)
+ {
+ pixel_offset = iy * ssim->width;
+ for (ix = winstart_x; ix <= winend_x; ix++)
+ {
+ tmp1 = org[pixel_offset + ix] - mu_o;
+ tmp2 = mod[pixel_offset + ix] - mu_m;
+ sigma_o += tmp1 * tmp1;
+ sigma_m += tmp2 * tmp2;
+ sigma_om += tmp1 * tmp2;
+ }
+ }
+ break;
+ case 1:
+
+ weight_y_base = wghstart_y - winstart_y;
+ weight_x_base = wghstart_x - winstart_x;
+
+ for (iy = winstart_y; iy <= winend_y; iy++)
+ {
+ pixel_offset = iy * ssim->width;
+ weight_offset = (weight_y_base + iy) * ssim->windowsize +
+ weight_x_base;
+ for (ix = winstart_x; ix <= winend_x; ix++)
+ {
+ weight = ssim->weights[weight_offset + ix];
+ mu_o += weight * org[pixel_offset + ix];
+ mu_m += weight * mod[pixel_offset + ix];
+ }
+ }
+ mu_m = mu_m / elsumm;
+ mu_o = orgmu[oy * ssim->width + ox];
+ for (iy = winstart_y; iy <= winend_y; iy++)
+ {
+ gfloat *weights_with_offset;
+ guint8 *org_with_offset, *mod_with_offset;
+ gfloat wt1, wt2;
+ pixel_offset = iy * ssim->width;
+ weight_offset = (weight_y_base + iy) * ssim->windowsize +
+ weight_x_base;
+ weights_with_offset = &ssim->weights[weight_offset];
+ org_with_offset = &org[pixel_offset];
+ mod_with_offset = &mod[pixel_offset];
+ for (ix = winstart_x; ix <= winend_x; ix++)
+ {
+ weight = weights_with_offset[ix];
+ tmp1 = org_with_offset[ix] - mu_o;
+ tmp2 = mod_with_offset[ix] - mu_m;
+ wt1 = weight * tmp1;
+ wt2 = weight * tmp2;
+ sigma_o += wt1 * tmp1;
+ sigma_m += wt2 * tmp2;
+ sigma_om += wt1 * tmp2;
+ }
+ }
+ break;
+ }
+ sigma_o = sqrt (sigma_o / elsumm);
+ sigma_m = sqrt (sigma_m / elsumm);
+ sigma_om = sigma_om / elsumm;
+ tmp1 = (2 * mu_o * mu_m + ssim->const1) * (2 * sigma_om + ssim->const2) /
+ ( (mu_o * mu_o + mu_m * mu_m + ssim->const1) *
+ (sigma_o * sigma_o + sigma_m * sigma_m + ssim->const2) );
+
+ /* SSIM can go negative, that's why it is
+ 127 + index * 128 instead of index * 255 */
+ out[oy * ssim->width + ox] = 127 + tmp1 * 128;
+ *lowest = MIN (*lowest, tmp1);
+ *highest = MAX (*highest, tmp1);
+ cumulative_ssim += tmp1;
+ }
+ }
+ *mean = cumulative_ssim / (ssim->width * ssim->height);
+}
+
+
+/* the first caps we receive on any of the sinkpads will define the caps for all
+ * the other sinkpads because we can only measure streams with the same caps.
+ */
+static gboolean
+gst_ssim_setcaps (GstPad * pad, GstCaps * caps)
+{
+ GstSSim *ssim;
+ GList *pads;
+ const char *media_type;
+ GstStructure *capsstr;
+ gint width, height, fps_n, fps_d;
+ guint32 fourcc;
+
+ ssim = GST_SSIM (GST_PAD_PARENT (pad));
+
+ GST_DEBUG_OBJECT (ssim, "setting caps on pad %p,%s to %" GST_PTR_FORMAT, pad,
+ GST_PAD_NAME (pad), caps);
+
+ capsstr = gst_caps_get_structure (caps, 0);
+ gst_structure_get_int (capsstr, "width", &width);
+ gst_structure_get_int (capsstr, "height", &height);
+ gst_structure_get_fraction (capsstr, "framerate", &fps_n, &fps_d);
+ gst_structure_get_fourcc (capsstr, "format", &fourcc);
+
+ GST_OBJECT_LOCK (ssim);
+
+ /* Sink caps are stored only once. At the moment it doesn't feel
+ * right to measure streams with variable caps.
+ */
+ if (G_UNLIKELY (!ssim->sinkcaps))
+ {
+ GstStructure *newstr;
+ GValue list = { 0, }, fourcc = { 0, };
+
+ g_value_init (&list, GST_TYPE_LIST);
+ g_value_init (&fourcc, GST_TYPE_FOURCC);
+
+ gst_value_set_fourcc (&fourcc, GST_MAKE_FOURCC ('Y', 'V', '1', '2'));
+ gst_value_list_append_value (&list, &fourcc);
+ gst_value_set_fourcc (&fourcc, GST_MAKE_FOURCC ('Y', '4', '1', 'B'));
+ gst_value_list_append_value (&list, &fourcc);
+ gst_value_set_fourcc (&fourcc, GST_MAKE_FOURCC ('Y', '4', '2', 'B'));
+ gst_value_list_append_value (&list, &fourcc);
+
+ newstr = gst_structure_new ("video/x-raw-yuv", NULL);
+ gst_structure_set (newstr, "width", G_TYPE_INT, width, NULL);
+ gst_structure_set (newstr, "height", G_TYPE_INT, height, NULL);
+ gst_structure_set_value (newstr, "format", &list);
+
+ ssim->sinkcaps = gst_caps_new_full (newstr, NULL);
+
+ g_value_unset (&list);
+ g_value_unset (&fourcc);
+ }
+
+ if (G_UNLIKELY (!ssim->srccaps))
+ {
+ GstStructure *newstr;
+
+ newstr = gst_structure_new ("video/x-raw-gray", NULL);
+ gst_structure_set (newstr, "width", G_TYPE_INT, width, NULL);
+ gst_structure_set (newstr, "height", G_TYPE_INT, height, NULL);
+ gst_structure_set (newstr, "framerate", GST_TYPE_FRACTION, fps_n, fps_d,
+ NULL);
+ /* Calculates SSIM only for Y channel, hence the output is monochrome.
+ * TODO: an option (a mask?) to calculate SSIM for more than one channel,
+ * will probably output RGB, one metric per channel...that would
+ * look kinda funny :)
+ */
+ gst_structure_set (newstr, "bpp", G_TYPE_INT, 8, "depth", G_TYPE_INT, 8,
+ NULL);
+
+ ssim->srccaps = gst_caps_new_full (newstr, NULL);
+ }
+
+ pads = GST_ELEMENT (ssim)->pads;
+ while (pads) {
+ GstPadDirection direction;
+ GstPad *otherpad = GST_PAD (pads->data);
+ direction = gst_pad_get_direction (otherpad);
+
+ GST_DEBUG_OBJECT (ssim, "checking caps on pad %p", otherpad);
+ if (direction == GST_PAD_SINK)
+ {
+ gchar *capstr;
+ capstr = gst_caps_to_string (GST_PAD_CAPS (otherpad));
+ GST_DEBUG_OBJECT (ssim, "old caps on pad %p,%s were %s", otherpad,
+ GST_PAD_NAME (otherpad), capstr);
+ g_free(capstr);
+ gst_caps_replace (&GST_PAD_CAPS (otherpad), ssim->sinkcaps);
+ capstr = gst_caps_to_string (ssim->sinkcaps);
+ GST_DEBUG_OBJECT (ssim, "new caps on pad %p,%s are %s", otherpad,
+ GST_PAD_NAME (otherpad), capstr);
+ g_free(capstr);
+ }
+ else if (direction == GST_PAD_SRC)
+ {
+ gst_caps_replace (&GST_PAD_CAPS (otherpad), ssim->srccaps);
+ }
+ pads = g_list_next (pads);
+ }
+
+ /* parse caps now */
+ media_type = gst_structure_get_name (capsstr);
+ GST_DEBUG_OBJECT (ssim, "media type is %s", media_type);
+ if (strcmp (media_type, "video/x-raw-yuv") == 0)
+ {
+ ssim->width = width;
+ ssim->height = height;
+ ssim->frame_rate = fps_n;
+ ssim->frame_rate_base = fps_d;
+
+ GST_INFO_OBJECT (ssim, "parse_caps sets ssim to yuv format "
+ "%d, %dx%d, %d/%d fps", fourcc, ssim->width, ssim->height,
+ ssim->frame_rate, ssim->frame_rate_base);
+
+ /* Only planar formats are supported.
+ * TODO: implement support for interleaved formats
+ * Only YUV formats are supported. There's no sense in calculating the
+ * index for R, G or B channels separately.
+ */
+ switch (fourcc)
+ {
+ case GST_MAKE_FOURCC ('Y', 'V', '1', '2'):
+ case GST_MAKE_FOURCC ('Y', '4', '1', 'B'):
+ case GST_MAKE_FOURCC ('Y', '4', '2', 'B'):
+ break;
+ default:
+ goto not_supported;
+ }
+
+ }
+ else
+ {
+ goto not_supported;
+ }
+
+ GST_OBJECT_UNLOCK (ssim);
+ return TRUE;
+ /* ERRORS */
+not_supported:
+ {
+ GST_OBJECT_UNLOCK (ssim);
+ GST_DEBUG_OBJECT (ssim, "unsupported format set as caps");
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_ssim_query_latency (GstSSim * ssim, GstQuery * query)
+{
+ GstClockTime min, max;
+ gboolean live;
+ gboolean res;
+ GstIterator *it;
+ gboolean done;
+
+ res = TRUE;
+ done = FALSE;
+
+ live = FALSE;
+ min = 0;
+ max = GST_CLOCK_TIME_NONE;
+
+ /* Take maximum of all latency values */
+ it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (ssim));
+ while (!done) {
+ GstIteratorResult ires;
+
+ gpointer item;
+
+ ires = gst_iterator_next (it, &item);
+ switch (ires) {
+ case GST_ITERATOR_DONE:
+ done = TRUE;
+ break;
+ case GST_ITERATOR_OK:
+ {
+ GstPad *pad = GST_PAD_CAST (item);
+ GstQuery *peerquery;
+ GstClockTime min_cur, max_cur;
+ gboolean live_cur;
+
+ peerquery = gst_query_new_latency ();
+
+ /* Ask peer for latency */
+ res &= gst_pad_peer_query (pad, peerquery);
+
+ /* take max from all valid return values */
+ if (res) {
+ gst_query_parse_latency (peerquery, &live_cur, &min_cur, &max_cur);
+
+ if (min_cur > min)
+ min = min_cur;
+
+ if (max_cur != GST_CLOCK_TIME_NONE &&
+ ((max != GST_CLOCK_TIME_NONE && max_cur > max) ||
+ (max == GST_CLOCK_TIME_NONE)))
+ max = max_cur;
+
+ live = live || live_cur;
+ }
+
+ gst_query_unref (peerquery);
+ gst_object_unref (pad);
+ break;
+ }
+ case GST_ITERATOR_RESYNC:
+ live = FALSE;
+ min = 0;
+ max = GST_CLOCK_TIME_NONE;
+ res = TRUE;
+ gst_iterator_resync (it);
+ break;
+ default:
+ res = FALSE;
+ done = TRUE;
+ break;
+ }
+ }
+ gst_iterator_free (it);
+
+ if (res) {
+ /* store the results */
+ GST_DEBUG_OBJECT (ssim, "Calculated total latency: live %s, min %"
+ GST_TIME_FORMAT ", max %" GST_TIME_FORMAT,
+ (live ? "yes" : "no"), GST_TIME_ARGS (min), GST_TIME_ARGS (max));
+ gst_query_set_latency (query, live, min, max);
+ }
+
+ return res;
+}
+
+static gboolean
+gst_ssim_query_duration (GstSSim * ssim, GstQuery * query)
+{
+ gint64 max, min;
+ gboolean res;
+ GstFormat format;
+ GstIterator *it;
+ gboolean done;
+
+ /* parse format */
+ gst_query_parse_duration (query, &format, NULL);
+
+ max = -1;
+ min = G_MAXINT64;
+ res = TRUE;
+ done = FALSE;
+
+ it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (ssim));
+ while (!done) {
+ GstIteratorResult ires;
+
+ gpointer item;
+
+ ires = gst_iterator_next (it, &item);
+ switch (ires) {
+ case GST_ITERATOR_DONE:
+ done = TRUE;
+ break;
+ case GST_ITERATOR_OK:
+ {
+ GstPad *pad = GST_PAD_CAST (item);
+
+ gint64 duration;
+
+ /* ask sink peer for duration */
+ res &= gst_pad_query_peer_duration (pad, &format, &duration);
+ /* take min&max from all valid return values */
+ if (res) {
+ /* valid unknown length, stop searching */
+ if (duration == -1) {
+ max = duration;
+ done = TRUE;
+ }
+ /* else see if bigger than current max */
+ else {
+ if (duration > max)
+ max = duration;
+ if (duration < min)
+ min = duration;
+ }
+ }
+ gst_object_unref (pad);
+ break;
+ }
+ case GST_ITERATOR_RESYNC:
+ max = -1;
+ min = G_MAXINT64;
+ res = TRUE;
+ gst_iterator_resync (it);
+ break;
+ default:
+ res = FALSE;
+ done = TRUE;
+ break;
+ }
+ }
+ gst_iterator_free (it);
+
+ if (res) {
+ /* and store the max */
+ GST_DEBUG_OBJECT (ssim, "Total duration in format %s: %"
+ GST_TIME_FORMAT, gst_format_get_name (format), GST_TIME_ARGS (min));
+ gst_query_set_duration (query, format, min);
+ }
+
+ return res;
+}
+
+
+static gboolean
+gst_ssim_query (GstPad * pad, GstQuery * query)
+{
+ GstSSim *ssim = GST_SSIM (gst_pad_get_parent (pad));
+ gboolean res = FALSE;
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_POSITION:
+ {
+ GstFormat format;
+
+ gst_query_parse_position (query, &format, NULL);
+
+ switch (format) {
+ case GST_FORMAT_TIME:
+ /* FIXME, bring to stream time, might be tricky */
+ gst_query_set_position (query, format, ssim->timestamp);
+ res = TRUE;
+ break;
+ case GST_FORMAT_DEFAULT:
+ gst_query_set_position (query, format, ssim->offset);
+ res = TRUE;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case GST_QUERY_DURATION:
+ res = gst_ssim_query_duration (ssim, query);
+ break;
+ case GST_QUERY_LATENCY:
+ res = gst_ssim_query_latency (ssim, query);
+ break;
+ default:
+ /* FIXME, needs a custom query handler because we have multiple
+ * sinkpads
+ */
+ res = gst_pad_query_default (pad, query);
+ break;
+ }
+
+ gst_object_unref (ssim);
+ return res;
+}
+
+static gboolean
+forward_event_func (GstPad * pad, GValue * ret, GstEvent * event)
+{
+ gst_event_ref (event);
+ GST_LOG_OBJECT (pad, "About to send event %s", GST_EVENT_TYPE_NAME (event));
+ if (!gst_pad_push_event (pad, event)) {
+ g_value_set_boolean (ret, FALSE);
+ GST_LOG_OBJECT (pad, "Sending event %p (%s) failed.",
+ event, GST_EVENT_TYPE_NAME (event));
+ } else {
+ GST_LOG_OBJECT (pad, "Sent event %p (%s).",
+ event, GST_EVENT_TYPE_NAME (event));
+ }
+ gst_object_unref (pad);
+ return TRUE;
+}
+
+/* forwards the event to all sinkpads, takes ownership of the
+ * event
+ *
+ * Returns: TRUE if the event could be forwarded on all
+ * sinkpads.
+ */
+static gboolean
+forward_event (GstSSim * ssim, GstEvent * event)
+{
+ gboolean ret;
+ GstIterator *it;
+ GValue vret = { 0 };
+
+ GST_LOG_OBJECT (ssim, "Forwarding event %p (%s)", event,
+ GST_EVENT_TYPE_NAME (event));
+
+ ret = TRUE;
+
+ g_value_init (&vret, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&vret, TRUE);
+ it = gst_element_iterate_sink_pads (GST_ELEMENT_CAST (ssim));
+ gst_iterator_fold (it, (GstIteratorFoldFunction) forward_event_func, &vret,
+ event);
+ gst_iterator_free (it);
+ gst_event_unref (event);
+
+ ret = g_value_get_boolean (&vret);
+
+ return ret;
+}
+
+static gboolean
+gst_ssim_src_event (GstPad * pad, GstEvent * event)
+{
+ GstSSim *ssim;
+ gboolean result;
+
+ ssim = GST_SSIM (gst_pad_get_parent (pad));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_QOS:
+ /* QoS might be tricky */
+ result = FALSE;
+ break;
+ case GST_EVENT_SEEK:
+ {
+ GstSeekFlags flags;
+ GstSeekType curtype;
+ gint64 cur;
+
+ /* parse the seek parameters */
+ gst_event_parse_seek (event, &ssim->segment_rate, NULL, &flags, &curtype,
+ &cur, NULL, NULL);
+
+ /* check if we are flushing */
+ if (flags & GST_SEEK_FLAG_FLUSH) {
+ /* make sure we accept nothing anymore and return WRONG_STATE */
+ gst_collect_pads_set_flushing (ssim->collect, TRUE);
+
+ /* flushing seek, start flush downstream, the flush will be done
+ * when all pads received a FLUSH_STOP. */
+ gst_pad_push_event (pad, gst_event_new_flush_start ());
+ }
+ /* now wait for the collected to be finished and mark a new
+ * segment */
+ GST_OBJECT_LOCK (ssim->collect);
+ if (curtype == GST_SEEK_TYPE_SET)
+ ssim->segment_position = cur;
+ else
+ ssim->segment_position = 0;
+ {
+ GstSSimOutputContext *c;
+ gint i = 0;
+ for (i = 0; i < ssim->src->len; i++)
+ {
+ c = (GstSSimOutputContext *) g_ptr_array_index (ssim->src, i);
+ c->segment_pending = TRUE;
+ }
+ }
+ GST_OBJECT_UNLOCK (ssim->collect);
+
+ result = forward_event (ssim, event);
+ break;
+ }
+ case GST_EVENT_NAVIGATION:
+ /* navigation is rather pointless. */
+ result = FALSE;
+ break;
+ default:
+ /* just forward the rest for now */
+ result = forward_event (ssim, event);
+ break;
+ }
+ gst_object_unref (ssim);
+
+ return result;
+}
+
+static gboolean
+gst_ssim_sink_event (GstPad * pad, GstEvent * event)
+{
+ GstSSim *ssim;
+ gboolean ret;
+
+ ssim = GST_SSIM (gst_pad_get_parent (pad));
+
+ GST_DEBUG ("Got %s event on pad %s:%s", GST_EVENT_TYPE_NAME (event),
+ GST_DEBUG_PAD_NAME (pad));
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_NEWSEGMENT:
+ {
+ gboolean update;
+ gdouble rate;
+ gdouble applied_rate;
+ GstFormat format;
+ gint64 start;
+ gint64 stop;
+ gint64 position;
+ gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate,
+ &format, &start, &stop, &position);
+ GST_DEBUG ("NEWSEGMENTEVENT: update(%d), rate(%f), app_rate(%f), "
+ "format(%d), start(%" GST_TIME_FORMAT ") stop(%" GST_TIME_FORMAT ") "
+ "position(%" GST_TIME_FORMAT ")", update, rate, applied_rate, format,
+ GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS(position));
+ break;
+ }
+ case GST_EVENT_FLUSH_STOP:
+ /* mark a pending new segment. This event is synchronized
+ * with the streaming thread so we can safely update the
+ * variable without races. It's somewhat weird because we
+ * assume the collectpads forwarded the FLUSH_STOP past us
+ * and downstream (using our source pad, the bastard!).
+ */
+ {
+ GstSSimOutputContext *c;
+ gint i = 0;
+ for (i = 0; i < ssim->src->len; i++)
+ {
+ c = (GstSSimOutputContext *) g_ptr_array_index (ssim->src, i);
+ c->segment_pending = TRUE;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* now GstCollectPads can take care of the rest, e.g. EOS */
+ GST_DEBUG ("Dispatching %s event on pad %s:%s", GST_EVENT_TYPE_NAME (event),
+ GST_DEBUG_PAD_NAME (pad));
+ ret = ssim->collect_event (pad, event);
+ GST_DEBUG ("Event %s on pad %s:%s is dispatched", GST_EVENT_TYPE_NAME (event),
+ GST_DEBUG_PAD_NAME (pad));
+ gst_object_unref (ssim);
+ return ret;
+}
+
+static void
+gst_ssim_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstSSim *ssim;
+
+ ssim = GST_SSIM (object);
+
+ switch (prop_id) {
+ case PROP_SSIM_TYPE:
+ ssim->ssimtype = g_value_get_int (value);
+ break;
+ case PROP_WINDOW_TYPE:
+ ssim->windowtype = g_value_get_int (value);
+ g_free (ssim->windows);
+ ssim->windows = NULL;
+ break;
+ case PROP_WINDOW_SIZE:
+ ssim->windowsize = g_value_get_int (value);
+ g_free (ssim->windows);
+ ssim->windows = NULL;
+ break;
+ case PROP_GAUSS_SIGMA:
+ ssim->sigma = g_value_get_float (value);
+ g_free (ssim->windows);
+ ssim->windows = NULL;
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_ssim_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstSSim *ssim;
+
+ ssim = GST_SSIM (object);
+
+ switch (prop_id) {
+ case PROP_SSIM_TYPE:
+ g_value_set_int (value, ssim->ssimtype);
+ break;
+ case PROP_WINDOW_TYPE:
+ g_value_set_int (value, ssim->windowtype);
+ break;
+ case PROP_WINDOW_SIZE:
+ g_value_set_int (value, ssim->windowsize);
+ break;
+ case PROP_GAUSS_SIGMA:
+ g_value_set_float (value, ssim->sigma);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+
+static void
+gst_ssim_class_init (GstSSimClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *gstelement_class = (GstElementClass *) klass;
+
+ gobject_class->set_property = gst_ssim_set_property;
+ gobject_class->get_property = gst_ssim_get_property;
+ gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_ssim_finalize);
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SSIM_TYPE,
+ g_param_spec_int ("ssim-type", "SSIM type",
+ "Type of the SSIM metric. 0 - canonical. 1 - with fixed mu "
+ "(almost the same results, but roughly 20% faster)",
+ 0, 1, 0, G_PARAM_READWRITE));
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WINDOW_TYPE,
+ g_param_spec_int ("window-type", "Window type",
+ "Type of the weighting in the window. "
+ "0 - no weighting. 1 - Gaussian weighting (controlled by \"sigma\")",
+ 0, 1, 1, G_PARAM_READWRITE));
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WINDOW_SIZE,
+ g_param_spec_int ("window-size", "Window size",
+ "Size of a window.",
+ 1, 22, 11, G_PARAM_READWRITE));
+
+ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_GAUSS_SIGMA,
+ g_param_spec_float ("gauss-sigma", "Deviation (for Gauss function)",
+ "Used to calculate Gussian weights "
+ "(only when using Gaussian window).",
+ G_MINFLOAT, 10, 1.5, G_PARAM_READWRITE));
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&gst_ssim_src_template));
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&gst_ssim_sink_original_template));
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&gst_ssim_sink_modified_template));
+ gst_element_class_set_details_simple (gstelement_class, "SSim",
+ "Filter/Converter/Video",
+ "Calculate Y-SSIM for n+2 YUV video streams",
+ "LRN <lrn1986 _at_ gmail _dot_ com>");
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ gstelement_class->request_new_pad =
+ GST_DEBUG_FUNCPTR (gst_ssim_request_new_pad);
+ gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_ssim_release_pad);
+
+ gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_ssim_change_state);
+}
+
+static GstPad *
+gst_ssim_request_new_pad (GstElement * element, GstPadTemplate * templ,
+ const gchar * padname)
+{
+ gchar *name;
+ GstSSim *ssim;
+ GstPad *newpad;
+ GstPad *newsrc;
+ gint padcount;
+ GstPadTemplate *template;
+ gint num = -1;
+
+ if (templ->direction != GST_PAD_SINK)
+ goto not_sink;
+
+ ssim = GST_SSIM (element);
+
+ padcount = ssim->padcount;
+
+ GST_DEBUG_OBJECT (ssim, "number of pads = %d", padcount);
+
+ if (padname)
+ GST_DEBUG_OBJECT (ssim, "reqested pad %s", padname);
+ else
+ goto unnamed_pad;
+
+ if (strcmp (padname, "original") == 0) {
+ newpad = gst_pad_new_from_template (templ, "original");
+ GST_DEBUG_OBJECT (ssim, "request new sink pad original");
+ ssim->orig = newpad;
+ } else if (strncmp (padname, "modified", 8) == 0) {
+ const gchar *numstr = &padname[8];
+ num = strtol (numstr, NULL, 10);
+ if (errno == EINVAL || errno == ERANGE)
+ goto bad_name;
+ newpad = gst_pad_new_from_template (templ, padname);
+ GST_DEBUG_OBJECT (ssim, "request new sink pad %s", padname);
+ } else
+ goto bad_name;
+
+ gst_pad_set_getcaps_function (newpad,
+ GST_DEBUG_FUNCPTR (gst_ssim_sink_getcaps));
+ gst_pad_set_setcaps_function (newpad, GST_DEBUG_FUNCPTR (gst_ssim_setcaps));
+ gst_collect_pads_add_pad (ssim->collect, newpad, sizeof (GstCollectData));
+
+ /* FIXME: hacked way to override/extend the event function of
+ * GstCollectPads; because it sets its own event function giving the
+ * element no access to events
+ */
+ GST_DEBUG_OBJECT (ssim, "Current collect_event is %p, changing to %p",
+ ssim->collect_event, GST_PAD_EVENTFUNC (newpad));
+ ssim->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
+ gst_pad_set_event_function (newpad, GST_DEBUG_FUNCPTR (gst_ssim_sink_event));
+
+ GST_DEBUG_OBJECT (ssim, "Adding a pad...");
+ /* takes ownership of the pad */
+ if (!gst_element_add_pad (GST_ELEMENT (ssim), newpad))
+ goto could_not_add_sink;
+ else
+ /* increment pad counter */
+ padcount = g_atomic_int_exchange_and_add (&ssim->padcount, 1);
+
+ if (num >= 0)
+ {
+ GstSSimOutputContext *c;
+ GObject *asobject;
+ template = gst_static_pad_template_get (&gst_ssim_src_template);
+ name = g_strdup_printf ("src%d", num);
+ newsrc = gst_pad_new_from_template (template, name);
+ GST_DEBUG_OBJECT (ssim, "creating src pad %s", name);
+ g_free (name);
+ gst_object_unref (template);
+
+ gst_pad_set_getcaps_function (newsrc,
+ GST_DEBUG_FUNCPTR (gst_ssim_src_getcaps));
+ gst_pad_set_query_function (newsrc, GST_DEBUG_FUNCPTR (gst_ssim_query));
+ gst_pad_set_event_function (newsrc, GST_DEBUG_FUNCPTR (gst_ssim_src_event));
+
+ if (!gst_element_add_pad (GST_ELEMENT (ssim), newsrc))
+ goto could_not_add_src;
+
+ c = g_new (GstSSimOutputContext, 1);
+ c->pad = newsrc;
+ asobject = G_OBJECT (newsrc);
+ g_object_set_data (G_OBJECT (newpad), "ssim-match-output-context", c);
+ g_ptr_array_add (ssim->src, (gpointer) c);
+ }
+
+ return newpad;
+
+ /* errors */
+bad_name:
+ {
+ g_warning ("gstssim: request new pad with bad name %s (must be "
+ "'modified\%d')\n", padname);
+ return NULL;
+ }
+unnamed_pad:
+ {
+ g_warning ("gstssim: request new pad without a name (must be "
+ "'modified\%d')\n");
+ return NULL;
+ }
+not_sink:
+ {
+ g_warning ("gstssim: request new pad that is not a SINK pad\n");
+ return NULL;
+ }
+could_not_add_src:
+ {
+ GST_DEBUG_OBJECT (ssim, "could not add src pad");
+ gst_object_unref (newsrc);
+ }
+could_not_add_sink:
+ {
+ GST_DEBUG_OBJECT (ssim, "could not add sink pad");
+ gst_collect_pads_remove_pad (ssim->collect, newpad);
+ gst_object_unref (newpad);
+ return NULL;
+ }
+}
+
+static void
+gst_ssim_release_pad (GstElement * element, GstPad * pad)
+{
+ GstSSim *ssim;
+
+ ssim = GST_SSIM (element);
+
+ GST_DEBUG_OBJECT (ssim, "release pad %s:%s", GST_DEBUG_PAD_NAME (pad));
+
+ gst_collect_pads_remove_pad (ssim->collect, pad);
+ gst_element_remove_pad (element, pad);
+}
+
+
+static void
+gst_ssim_init (GstSSim * ssim)
+{
+ ssim->windowsize = 11;
+ ssim->windowtype = 1;
+ ssim->windows = NULL;
+ ssim->sigma = 1.5;
+ ssim->ssimtype = 0;
+ ssim->src = g_ptr_array_new ();
+ ssim->padcount = 0;
+ ssim->collect_event = NULL;
+ ssim->sinkcaps = NULL;
+
+ /* keep track of the sinkpads requested */
+ ssim->collect = gst_collect_pads_new ();
+ gst_collect_pads_set_function (ssim->collect,
+ GST_DEBUG_FUNCPTR (gst_ssim_collected), ssim);
+}
+
+static void
+gst_ssim_finalize (GObject * object)
+{
+ GstSSim *ssim = GST_SSIM (object);
+
+ gst_object_unref (ssim->collect);
+ ssim->collect = NULL;
+
+ g_free (ssim->windows);
+ ssim->windows = NULL;
+
+ g_free (ssim->weights);
+ ssim->weights = NULL;
+
+ if (ssim->sinkcaps)
+ gst_caps_unref (ssim->sinkcaps);
+ if (ssim->srccaps)
+ gst_caps_unref (ssim->srccaps);
+
+ g_ptr_array_free (ssim->src, TRUE);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+typedef gfloat (*GstSSimWeightFunc) (GstSSim *ssim, gint y, gint x);
+
+gfloat gst_ssim_weight_func_none (GstSSim *ssim, gint y, gint x)
+{
+ return 1;
+}
+
+gfloat gst_ssim_weight_func_gauss (GstSSim *ssim, gint y, gint x)
+{
+ gfloat coord = sqrt (x * x + y * y);
+ return exp ( -1 * (coord * coord) / (2 * ssim->sigma * ssim->sigma) ) /
+ (ssim->sigma * sqrt (2 * G_PI));
+}
+
+gboolean gst_ssim_regenerate_windows (GstSSim *ssim)
+{
+ gint windowiseven;
+ gint y,x, y2, x2;
+ GstSSimWeightFunc func;
+ gfloat normal_summ = 0;
+ gint normal_count = 0;
+
+ g_free (ssim->weights);
+
+ ssim->weights = g_new (gfloat, ssim->windowsize * ssim->windowsize);
+
+ windowiseven = ((gint) ssim->windowsize / 2) * 2 == ssim->windowsize ? 1 : 0;
+
+ g_free (ssim->windows);
+
+ ssim->windows = g_new (GstSSimWindowCache, ssim->height * ssim->width);
+
+ switch (ssim->windowtype)
+ {
+ case 0:
+ func = (GstSSimWeightFunc) gst_ssim_weight_func_none;
+ break;
+ case 1:
+ func = (GstSSimWeightFunc) gst_ssim_weight_func_gauss;
+ break;
+ default:
+ GST_WARNING_OBJECT (ssim, "unknown window type - %d. Defaulting to %d",
+ ssim->windowtype, 1);
+ ssim->windowtype = 1;
+ func = (GstSSimWeightFunc) gst_ssim_weight_func_gauss;
+ }
+
+ for (y = 0; y < ssim->windowsize; y++)
+ {
+ gint yoffset = y * ssim->windowsize;
+ for (x = 0; x < ssim->windowsize; x++)
+ {
+ ssim->weights[yoffset + x] = func(ssim, x - ssim->windowsize / 2 +
+ windowiseven, y - ssim->windowsize / 2 + windowiseven);
+ normal_summ += ssim->weights[yoffset + x];
+ normal_count++;
+ }
+ }
+
+ for (y = 0; y < ssim->height; y++)
+ {
+ for (x = 0; x < ssim->width; x++)
+ {
+ GstSSimWindowCache win;
+ gint element_count = 0;
+
+ win.x_window_start = x - ssim->windowsize / 2 + windowiseven;
+ win.x_weight_start = 0;
+ if (win.x_window_start < 0)
+ {
+ win.x_weight_start = -win.x_window_start;
+ win.x_window_start = 0;
+ }
+
+ win.x_window_end = x + ssim->windowsize / 2;
+ if (win.x_window_end >= ssim->width)
+ win.x_window_end = ssim->width - 1;
+
+ win.y_window_start = y - ssim->windowsize / 2 + windowiseven;
+ win.y_weight_start = 0;
+ if (win.y_window_start < 0)
+ {
+ win.y_weight_start = -win.y_window_start;
+ win.y_window_start = 0;
+ }
+
+ win.y_window_end = y + ssim->windowsize / 2;
+ if (win.y_window_end >= ssim->height)
+ win.y_window_end = ssim->height - 1;
+
+ win.element_summ = 0;
+ element_count = (win.y_window_end - win.y_window_start + 1) *
+ (win.x_window_end - win.x_window_start + 1);
+ if (element_count == normal_count)
+ win.element_summ = normal_summ;
+ else
+ {
+ for (y2 = win.y_weight_start; y2 < ssim->windowsize; y2++)
+ {
+ for (x2 = win.x_weight_start; x2 < ssim->windowsize; x2++)
+ {
+ win.element_summ += ssim->weights[y2 * ssim->windowsize + x2];
+ }
+ }
+ }
+ ssim->windows[(y * ssim->width + x)] = win;
+ }
+ }
+
+ /* FIXME: while 0.01 and 0.03 are pretty much static, the 255 implies that
+ * we're working with 8-bit-per-color-component format, which may not be true
+ */
+ ssim->const1 = 0.01 * 255 * 0.01 * 255;
+ ssim->const2 = 0.03 * 255 * 0.03 * 255;
+ return TRUE;
+}
+
+static GstFlowReturn
+gst_ssim_collected (GstCollectPads * pads, gpointer user_data)
+{
+ GstSSim *ssim;
+ GSList *collected;
+ GstFlowReturn ret = GST_FLOW_OK;
+ GstBuffer *orgbuf = NULL;
+ gfloat *orgmu = NULL;
+ GstBuffer *outbuf = NULL;
+ gpointer outdata = NULL;
+ guint outsize = 0;
+ gfloat mssim = 0, lowest = 1, highest = -1;
+ gboolean empty = TRUE;
+ gboolean ready = TRUE;
+ gint padnumber = 0;
+
+ ssim = GST_SSIM (user_data);
+
+ if (G_UNLIKELY (ssim->windows == NULL))
+ {
+ GST_DEBUG_OBJECT (ssim, "Regenerating windows");
+ gst_ssim_regenerate_windows (ssim);
+ }
+
+ switch (ssim->ssimtype)
+ {
+ case 0:
+ ssim->func = (GstSSimFunction) calcssim_canonical;
+ break;
+ case 1:
+ ssim->func = (GstSSimFunction) calcssim_without_mu;
+ break;
+ default:
+ return GST_FLOW_ERROR;
+ }
+
+ for (collected = pads->data; collected; collected =
+ g_slist_next (collected)) {
+ GstCollectData *collect_data;
+ GstBuffer *inbuf;
+
+ collect_data = (GstCollectData *) collected->data;
+
+ inbuf = gst_collect_pads_peek (pads, collect_data);
+
+ if (inbuf == NULL)
+ {
+ GST_LOG_OBJECT (ssim, "channel %p: no bytes available", collect_data);
+ ready = FALSE;
+ }
+ else
+ gst_buffer_unref (inbuf);
+ }
+
+ /* if _collected() was called, all pads should have data, but if
+ * one of them doesn't, it means that it is EOS and we can't go any further
+ *
+ * FIXME, shouldn't we do something about pads that DO have data?
+ * Flush them or something?
+ */
+ if (G_UNLIKELY (!ready))
+ goto eos;
+
+ /* Mu is just a blur, we can calculate it once */
+ if (ssim->ssimtype == 0)
+ {
+ orgmu = g_new (gfloat, ssim->width * ssim->height);
+
+ for (collected = pads->data; collected;
+ collected = g_slist_next (collected)) {
+ GstCollectData *collect_data;
+
+ collect_data = (GstCollectData *) collected->data;
+
+ if (collect_data->pad == ssim->orig)
+ {
+ orgbuf = gst_collect_pads_pop (pads, collect_data);;
+
+ GST_DEBUG_OBJECT (ssim, "Original stream - flags(0x%x), timestamp(%"
+ GST_TIME_FORMAT "), duration(%" GST_TIME_FORMAT ")",
+ GST_BUFFER_FLAGS (orgbuf),
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (orgbuf)),
+ GST_TIME_ARGS(GST_BUFFER_DURATION (orgbuf)));
+ calculate_mu (ssim, orgmu, GST_BUFFER_DATA (orgbuf));
+
+ break;
+ }
+ }
+ }
+
+ GST_LOG_OBJECT (ssim, "starting to cycle through streams");
+
+ for (collected = pads->data; collected;
+ collected = g_slist_next (collected)) {
+ GstCollectData *collect_data;
+ GstBuffer *inbuf;
+ guint8 *indata;
+ guint insize;
+
+ collect_data = (GstCollectData *) collected->data;
+
+ if (collect_data->pad != ssim->orig)
+ {
+ inbuf = gst_collect_pads_pop (pads, collect_data);
+
+ indata = GST_BUFFER_DATA (inbuf);
+ insize = GST_BUFFER_SIZE (inbuf);
+
+ GST_DEBUG_OBJECT (ssim, "Modified stream - flags(0x%x), timestamp(%"
+ GST_TIME_FORMAT "), duration(%" GST_TIME_FORMAT ")",
+ GST_BUFFER_FLAGS (inbuf),
+ GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (inbuf)),
+ GST_TIME_ARGS (GST_BUFFER_DURATION (inbuf)));
+
+ if (!GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_GAP)) {
+ GstSSimOutputContext *c;
+ GstEvent *measured;
+ guint64 offset;
+ GValue vmean = { 0 }, vlowest = { 0 }, vhighest = { 0 };
+
+ c = (GstSSimOutputContext *) g_object_get_data (
+ G_OBJECT (collect_data->pad), "ssim-match-output-context");
+
+ GST_DEBUG_OBJECT (ssim, "Output context is %" GST_PTR_FORMAT
+ ", pad will be %" GST_PTR_FORMAT, c, c->pad);
+
+ outsize = GST_ROUND_UP_4 (ssim->width) * ssim->height;
+ GST_LOG_OBJECT (ssim, "channel %p: making output buffer of %d bytes",
+ collect_data, outsize);
+
+ /* first buffer, alloc outsize.
+ * FIXME: we can easily subbuffer and _make_writable.
+ * FIXME: only create empty buffer for first non-gap buffer, so that we
+ * only use ssim function when really calculating
+ */
+ outbuf = gst_buffer_new_and_alloc (GST_ROUND_UP_4 (ssim->width) *
+ ssim->height);
+ outdata = GST_BUFFER_DATA (outbuf);
+ gst_buffer_set_caps (outbuf, gst_pad_get_fixed_caps_func (c->pad));
+
+ /* Videos should match, so the output video has the same characteristics
+ * as the input video
+ */
+ /* set timestamps on the output buffer */
+ gst_buffer_copy_metadata(outbuf, inbuf, (GstBufferCopyFlags)
+ GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS);
+
+ g_value_init (&vmean, G_TYPE_FLOAT);
+ g_value_init (&vlowest, G_TYPE_FLOAT);
+ g_value_init (&vhighest, G_TYPE_FLOAT);
+
+ GST_LOG_OBJECT (ssim, "channel %p: calculating SSIM", collect_data);
+
+ ssim->func (ssim, GST_BUFFER_DATA (orgbuf), orgmu, indata, outdata,
+ &mssim, &lowest, &highest);
+
+ GST_DEBUG_OBJECT (GST_OBJECT (ssim), "MSSIM is %f, l-h is %f - %f",
+ mssim, lowest, highest);
+
+ gst_ssim_post_message (ssim, outbuf, mssim, lowest, highest);
+
+ g_value_set_float (&vmean, mssim);
+ g_value_set_float (&vlowest, lowest);
+ g_value_set_float (&vhighest, highest);
+ offset = GST_BUFFER_OFFSET (inbuf);
+
+ /* our timestamping is very simple, just an ever incrementing
+ * counter, the new segment time will take care of their respective
+ * stream time.
+ */
+ if (c->segment_pending) {
+ GstEvent *event;
+
+ /* FIXME, use rate/applied_rate as set on all sinkpads.
+ * - currently we just set rate as received from last seek-event
+ * We could potentially figure out the duration as well using
+ * the current segment positions and the stated stop positions.
+ * Also we just start from stream time 0 which is rather
+ * weird. For non-synchronized mixing, the time should be
+ * the min of the stream times of all received segments,
+ * rationale being that the duration is at least going to
+ * be as long as the earliest stream we start mixing. This
+ * would also be correct for synchronized mixing but then
+ * the later streams would be delayed until the stream times`
+ * match.
+ */
+ event = gst_event_new_new_segment_full (FALSE, ssim->segment_rate,
+ 1.0, GST_FORMAT_TIME, ssim->timestamp, -1,
+ ssim->segment_position);
+
+ gst_pad_push_event (c->pad, event);
+ c->segment_pending = FALSE;
+ }
+
+ measured = gst_event_new_measured (offset,
+ GST_BUFFER_TIMESTAMP (inbuf), "SSIM", &vmean, &vlowest, &vhighest);
+ gst_pad_push_event(c->pad, measured);
+
+ empty = FALSE;
+
+ /* send it out */
+ GST_DEBUG_OBJECT (ssim, "pushing outbuf, timestamp %" GST_TIME_FORMAT
+ ", size %d", GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)),
+ GST_BUFFER_SIZE (outbuf));
+ ret &= gst_pad_push (c->pad, outbuf);
+
+ } else {
+ GST_LOG_OBJECT (ssim, "channel %p: skipping", collect_data);
+ }
+ gst_buffer_unref (inbuf);
+ padnumber++;
+ }
+ }
+ gst_buffer_unref (orgbuf);
+
+ if (ssim->ssimtype == 0)
+ g_free (orgmu);
+
+ ssim->segment_position = 0;
+
+ return ret;
+
+ /* ERRORS */
+eos:
+ {
+ gint i;
+ GST_DEBUG_OBJECT (ssim, "no data available, must be EOS");
+ for (i = 0; i < ssim->src->len; i++)
+ {
+ GstSSimOutputContext *c =
+ (GstSSimOutputContext *) g_ptr_array_index (ssim->src, i);
+ gst_pad_push_event (c->pad, gst_event_new_eos ());
+ }
+
+ return GST_FLOW_UNEXPECTED;
+ }
+}
+
+static GstStateChangeReturn
+gst_ssim_change_state (GstElement * element, GstStateChange transition)
+{
+ GstSSim *ssim;
+ GstStateChangeReturn ret;
+
+ ssim = GST_SSIM (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ ssim->timestamp = 0;
+ ssim->offset = 0;
+ {
+ GstSSimOutputContext *c;
+ gint i = 0;
+ for (i = 0; i < ssim->src->len; i++)
+ {
+ c = (GstSSimOutputContext *) g_ptr_array_index (ssim->src, i);
+ c->segment_pending = TRUE;
+ }
+ }
+ ssim->segment_position = 0;
+ ssim->segment_rate = 1.0;
+ gst_segment_init (&ssim->segment, GST_FORMAT_UNDEFINED);
+ gst_collect_pads_start (ssim->collect);
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ /* need to unblock the collectpads before calling the
+ * parent change_state so that streaming can finish
+ */
+ gst_collect_pads_stop (ssim->collect);
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ switch (transition) {
+ default:
+ break;
+ }
+
+ return ret;
+}
diff --git a/gst/videomeasure/gstvideomeasure_ssim.h b/gst/videomeasure/gstvideomeasure_ssim.h
new file mode 100644
index 000000000..e21caf83e
--- /dev/null
+++ b/gst/videomeasure/gstvideomeasure_ssim.h
@@ -0,0 +1,140 @@
+/* GStreamer
+ * Copyright (C) <2009> LRN <lrn1986 _at_ gmail _dot_ com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GST_SSIM_H__
+#define __GST_SSIM_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstcollectpads.h>
+#include <gst/video/video.h>
+
+G_BEGIN_DECLS
+
+enum
+{
+ PROP_0,
+ PROP_SSIM_TYPE,
+ PROP_WINDOW_TYPE,
+ PROP_WINDOW_SIZE,
+ PROP_GAUSS_SIGMA,
+};
+
+
+#define GST_TYPE_SSIM (gst_ssim_get_type())
+#define GST_SSIM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ GST_TYPE_SSIM,GstSSim))
+#define GST_IS_SSIM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \
+ GST_TYPE_SSIM))
+#define GST_SSIM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass) , \
+ GST_TYPE_SSIM,GstSSimClass))
+#define GST_IS_SSIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass) , \
+ GST_TYPE_SSIM))
+#define GST_SSIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) , \
+ GST_TYPE_SSIM,GstSSimClass))
+
+typedef struct _GstSSim GstSSim;
+typedef struct _GstSSimClass GstSSimClass;
+
+typedef struct _GstSSimWindowCache {
+ gint x_window_start;
+ gint x_weight_start;
+ gint x_window_end;
+ gint y_window_start;
+ gint y_weight_start;
+ gint y_window_end;
+ gfloat element_summ;
+} GstSSimWindowCache;
+
+typedef void (*GstSSimFunction) (GstSSim *ssim, guint8 *org, gfloat *orgmu,
+ guint8 *mod, guint8 *out, gfloat *mean, gfloat *lowest, gfloat *highest);
+
+typedef struct _GstSSimOutputContext GstSSimOutputContext;
+
+/* TODO: check if all fields are used */
+struct _GstSSimOutputContext {
+ GstPad *pad;
+ gboolean segment_pending;
+};
+
+/**
+ * GstSSim:
+ *
+ * The ssim object structure.
+ */
+struct _GstSSim {
+ GstElement element;
+
+ /* Array of GstSSimOutputContext */
+ GPtrArray *src;
+
+ gint padcount;
+
+ GstCollectPads *collect;
+ GstPad *orig;
+
+ gint frame_rate;
+ gint frame_rate_base;
+ gint width;
+ gint height;
+ GstCaps *sinkcaps;
+ GstCaps *srccaps;
+
+ /* SSIM type (0 - canonical; 1 - without mu) */
+ gint ssimtype;
+
+ /* Size of a window, windows are square */
+ gint windowsize;
+
+ /* Type of a weight-generator. 0 - no weighting. 1 - Gaussian weighting */
+ gint windowtype;
+
+ /* Array of width*height GstSSimWindowCaches */
+ GstSSimWindowCache *windows;
+
+ /* Array of windowsize*windowsize gfloats */
+ gfloat *weights;
+
+ /* For Gaussian function */
+ gfloat sigma;
+
+ GstSSimFunction func;
+
+ gfloat const1;
+ gfloat const2;
+
+ /* counters to keep track of timestamps */
+ gint64 timestamp;
+ gint64 offset;
+
+ /* sink event handling */
+ GstPadEventFunction collect_event;
+ GstSegment segment;
+ guint64 segment_position;
+ gdouble segment_rate;
+};
+
+struct _GstSSimClass {
+ GstElementClass parent_class;
+};
+
+GType gst_ssim_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_SSIM_H__ */