From bdbd944c5270e6037d79b6ebf43b5db96a107b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 10 Sep 2009 11:58:02 +0200 Subject: [PATCH] videomeasure: Fix indention, line endings and use LRN's real name --- gst/videomeasure/gstvideomeasure.c | 139 +- gst/videomeasure/gstvideomeasure.h | 2 +- gst/videomeasure/gstvideomeasure_collector.c | 824 +++-- gst/videomeasure/gstvideomeasure_collector.h | 2 +- gst/videomeasure/gstvideomeasure_ssim.c | 3437 +++++++++--------- gst/videomeasure/gstvideomeasure_ssim.h | 2 +- 6 files changed, 2173 insertions(+), 2233 deletions(-) diff --git a/gst/videomeasure/gstvideomeasure.c b/gst/videomeasure/gstvideomeasure.c index 52aa80573a..9cbfb60ef1 100644 --- a/gst/videomeasure/gstvideomeasure.c +++ b/gst/videomeasure/gstvideomeasure.c @@ -1,70 +1,69 @@ -/* GStreamer - * Copyright (C) <2009> LRN - * - * 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); +/* GStreamer + * Copyright (C) <2009> Руслан Ижбулатов + * + * 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 index 4db1ee7e1b..e385a8d1d5 100644 --- a/gst/videomeasure/gstvideomeasure.h +++ b/gst/videomeasure/gstvideomeasure.h @@ -1,5 +1,5 @@ /* GStreamer - * Copyright (C) <2009> LRN + * Copyright (C) <2009> Руслан Ижбулатов * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/gst/videomeasure/gstvideomeasure_collector.c b/gst/videomeasure/gstvideomeasure_collector.c index 2a863a95cc..7a7edb03a7 100644 --- a/gst/videomeasure/gstvideomeasure_collector.c +++ b/gst/videomeasure/gstvideomeasure_collector.c @@ -1,417 +1,407 @@ -/* GStreamer - * Copyright (C) <2009> LRN - * - * 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 - * "GstMeasureCollector". - * - * - * 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 -#include - -#include - -/* 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 "); - -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, ""); - } - } - - 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); -} +/* GStreamer + * Copyright (C) <2009> Руслан Ижбулатов + * + * 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 + * "GstMeasureCollector". + * + * + * 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 +#include + +#include + +/* 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", + "Руслан Ижбулатов "); + +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, ""); + } + } + + 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 index 49d6c17c16..49a4e9318d 100644 --- a/gst/videomeasure/gstvideomeasure_collector.h +++ b/gst/videomeasure/gstvideomeasure_collector.h @@ -1,5 +1,5 @@ /* GStreamer - * Copyright (C) <2009> LRN + * Copyright (C) <2009> Руслан Ижбулатов * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/gst/videomeasure/gstvideomeasure_ssim.c b/gst/videomeasure/gstvideomeasure_ssim.c index 4b41fad5c9..7a0d0ed0e9 100644 --- a/gst/videomeasure/gstvideomeasure_ssim.c +++ b/gst/videomeasure/gstvideomeasure_ssim.c @@ -1,1743 +1,1694 @@ -/* GStreamer - * Copyright (C) <2009> LRN - * - * 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. - * - * - * Example launch line - * |[ - * 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. - * - * - * 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 -#include -#include - -#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 "); - - 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; -} +/* GStreamer + * Copyright (C) <2009> Руслан Ижбулатов + * + * 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. + * + * + * Example launch line + * |[ + * 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. + * + * + * 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 +#include +#include + +#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", + "Руслан Ижбулатов "); + + 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 index e21caf83e4..57e0907fbf 100644 --- a/gst/videomeasure/gstvideomeasure_ssim.h +++ b/gst/videomeasure/gstvideomeasure_ssim.h @@ -1,5 +1,5 @@ /* GStreamer - * Copyright (C) <2009> LRN + * Copyright (C) <2009> Руслан Ижбулатов * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public