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