| /* 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 ! videoconvert ! glimagesink filesrc |
| * location=orig.avi ! decodebin ! ssim.original filesrc location=compr.avi ! |
| * decodebin ! ssim.modified0 |
| * ]| This pipeline produces a video stream that consists of SSIM frames. |
| * </refsect2> |
| */ |
| /* 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 <stdlib.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) { I420, 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_%u", |
| 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_%u", |
| 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; |
| 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; |
| |
| 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; |
| 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; |
| |
| 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 ('I', '4', '2', '0')); |
| gst_value_list_append_value (&list, &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 ('I', '4', '2', '0'): |
| 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 (live_cur) { |
| if (min_cur > min) |
| min = min_cur; |
| |
| if (max == GST_CLOCK_TIME_NONE) |
| max = max_cur; |
| else if (max_cur < max) |
| max = max_cur; |
| |
| live = TRUE; |
| } |
| } |
| |
| 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) |
| { |
| GstIterator *it; |
| GValue vret = { 0 }; |
| |
| GST_LOG_OBJECT (ssim, "Forwarding event %p (%s)", event, |
| GST_EVENT_TYPE_NAME (event)); |
| |
| 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); |
| |
| return g_value_get_boolean (&vret); |
| } |
| |
| 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_PARAM_STATIC_STRINGS)); |
| |
| 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_PARAM_STATIC_STRINGS)); |
| |
| 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_PARAM_STATIC_STRINGS)); |
| |
| 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 | G_PARAM_STATIC_STRINGS)); |
| |
| 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_static_metadata (gstelement_class, "SSim", |
| "Filter/Analyzer/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; |
| guint 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_", 9) == 0) { |
| const gchar *numstr = &padname[9]; |
| num = strtoul (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), |
| NULL, TRUE); |
| |
| /* 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_add (&ssim->padcount, 1); |
| |
| if (num != -1) { |
| GstSSimOutputContext *c; |
| |
| template = gst_static_pad_template_get (&gst_ssim_src_template); |
| name = g_strdup_printf ("src_%u", 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; |
| 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')\n", padname); |
| return NULL; |
| } |
| unnamed_pad: |
| { |
| g_warning ("gstssim: request new pad without a name (must be " |
| "'modified')\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); |
| |
| static gfloat |
| gst_ssim_weight_func_none (GstSSim * ssim, gint y, gint x) |
| { |
| return 1; |
| } |
| |
| static 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)); |
| } |
| |
| static 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 = gst_ssim_weight_func_none; |
| break; |
| case 1: |
| func = 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 = 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 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; |
| |
| collect_data = (GstCollectData *) collected->data; |
| |
| if (collect_data->pad != ssim->orig) { |
| inbuf = gst_collect_pads_pop (pads, collect_data); |
| |
| indata = GST_BUFFER_DATA (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); |
| |
| /* 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; |
| } |