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