2012-05-27 18:28:07 +02:00
|
|
|
/* GStreamer spectrum analysis toy
|
|
|
|
* Copyright (C) 2006 Joseph Rabinoff <bobqwatson@yahoo.com>
|
|
|
|
* Some code copyright (C) 2005 Gav Wood
|
|
|
|
*/
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
* *
|
|
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
|
|
* it under the terms of the GNU General Public License as published by *
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
|
|
* (at your option) any later version. *
|
|
|
|
* *
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* SECTION:element-moodbar
|
|
|
|
*
|
|
|
|
* <refsect2>
|
|
|
|
* <title>Example launch line</title>
|
|
|
|
* <para>
|
|
|
|
* <programlisting>
|
|
|
|
* gst-launch filesrc location=test.mp3 ! mad ! audioconvert ! fftwspectrum ! moodbar height=50 ! pngenc ! filesink location=test.png
|
|
|
|
* </programlisting>
|
|
|
|
* </para>
|
|
|
|
* </refsect2>
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* This plugin is based on the Moodbar code in Amarok version 1.4.0a,
|
|
|
|
* written by Gav Wood. The algorithm is basically the same as the
|
|
|
|
* one applied there, and the normalizing code below is taken directly
|
|
|
|
* from Gav Wood's Exscalibar package.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* This plugin takes a frequency-domain stream, does some simple
|
|
|
|
* analysis, and returns a string of (unsigned char) rgb triples
|
|
|
|
* that represent the magnitude of various sections of the stream.
|
|
|
|
* Since we have to perform some normalization, we queue up all
|
|
|
|
* of our analysis until we get an EOS event, at which point we
|
|
|
|
* normalize and do the output. If a max-width is specified, the
|
|
|
|
* output is scaled down to the desired width if necessary.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* More precisely, the analysis performed is as follows:
|
|
|
|
* (1) the spectrum is broken into 24 parts, called "bark bands"
|
|
|
|
* (Gav's terminology), as given in bark_bands below
|
|
|
|
* (2) we compute the size of the first 8 bark bands and store
|
|
|
|
* that as the "red" component; similarly for blue and green
|
|
|
|
* (3) after receiving an EOS, we normalize all of the analysis
|
|
|
|
* done in (1) and (2) and return a stream of rgb triples
|
|
|
|
* (application/x-raw-rgb)
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
# include <config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <gst/gst.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
#include "gstmoodbar.h"
|
|
|
|
#include "spectrum.h"
|
|
|
|
|
|
|
|
GST_DEBUG_CATEGORY (gst_moodbar_debug);
|
|
|
|
#define GST_CAT_DEFAULT gst_moodbar_debug
|
|
|
|
|
|
|
|
/* Filter signals and args */
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
/* FILL ME */
|
|
|
|
LAST_SIGNAL
|
|
|
|
};
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
ARG_0,
|
|
|
|
ARG_HEIGHT,
|
|
|
|
ARG_MAX_WIDTH
|
|
|
|
};
|
|
|
|
|
|
|
|
static GstStaticPadTemplate sink_factory
|
|
|
|
= GST_STATIC_PAD_TEMPLATE ("sink",
|
|
|
|
GST_PAD_SINK,
|
|
|
|
GST_PAD_ALWAYS,
|
|
|
|
GST_STATIC_CAPS
|
|
|
|
( SPECTRUM_FREQ_CAPS )
|
|
|
|
);
|
|
|
|
|
|
|
|
static GstStaticPadTemplate src_factory
|
|
|
|
= GST_STATIC_PAD_TEMPLATE ("src",
|
|
|
|
GST_PAD_SRC,
|
|
|
|
GST_PAD_ALWAYS,
|
|
|
|
GST_STATIC_CAPS
|
|
|
|
( "video/x-raw-rgb, "
|
|
|
|
"bpp = (int) 24, "
|
|
|
|
"depth = (int) 24, "
|
|
|
|
"height = (int) [ 1, MAX ], "
|
|
|
|
"width = (int) [ 1, MAX ], "
|
|
|
|
"framerate = (fraction) 0/1"
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
GST_BOILERPLATE (GstMoodbar, gst_moodbar, GstElement,
|
|
|
|
GST_TYPE_ELEMENT);
|
|
|
|
|
|
|
|
static void gst_moodbar_set_property (GObject *object, guint prop_id,
|
|
|
|
const GValue *value, GParamSpec *pspec);
|
|
|
|
static void gst_moodbar_get_property (GObject *object, guint prop_id,
|
|
|
|
GValue *value, GParamSpec *pspec);
|
|
|
|
|
|
|
|
static gboolean gst_moodbar_set_sink_caps (GstPad *pad, GstCaps *caps);
|
|
|
|
static gboolean gst_moodbar_sink_event (GstPad *pad, GstEvent *event);
|
|
|
|
|
|
|
|
static GstFlowReturn gst_moodbar_chain (GstPad *pad, GstBuffer *buf);
|
|
|
|
static GstStateChangeReturn gst_moodbar_change_state (GstElement *element,
|
|
|
|
GstStateChange transition);
|
|
|
|
|
|
|
|
static void gst_moodbar_finish (GstMoodbar *mood);
|
|
|
|
|
|
|
|
/* This is a failsafe so we don't eat up all of a computer's memory
|
|
|
|
* if we hit an endless stream. */
|
|
|
|
#define MAX_TRIPLES (1024*1024*4)
|
|
|
|
|
|
|
|
#define NUMFREQS(mood) ((mood)->size/2+1)
|
|
|
|
|
|
|
|
/* Allocate mood->r, mood->g, and mood->b in chunks of this many */
|
|
|
|
#define FRAME_CHUNK 1000
|
|
|
|
|
|
|
|
/* Default height of the output image */
|
|
|
|
#define HEIGHT_DEFAULT 1
|
|
|
|
|
|
|
|
/* Default max-width of the output image, or 0 for no rescaling */
|
|
|
|
#define MAX_WIDTH_DEFAULT 0
|
|
|
|
|
|
|
|
/* We use this table to break up the incoming spectrum into segments */
|
|
|
|
static const guint bark_bands[24]
|
|
|
|
= { 100, 200, 300, 400, 510, 630, 770, 920,
|
|
|
|
1080, 1270, 1480, 1720, 2000, 2320, 2700, 3150,
|
|
|
|
3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500 };
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************************/
|
|
|
|
/* GObject boilerplate stuff */
|
|
|
|
/***************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
gst_moodbar_base_init (gpointer gclass)
|
|
|
|
{
|
|
|
|
static GstElementDetails element_details =
|
|
|
|
{
|
|
|
|
"Moodbar analyzer",
|
|
|
|
"Filter/Converter/Moodbar",
|
|
|
|
"Convert a spectrum into a stream of (uchar) rgb triples representing its \"mood\"",
|
|
|
|
"Joe Rabinoff <bobqwatson@yahoo.com>"
|
|
|
|
};
|
|
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
|
|
|
|
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
|
|
gst_static_pad_template_get (&src_factory));
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
|
|
gst_static_pad_template_get (&sink_factory));
|
|
|
|
gst_element_class_set_details (element_class, &element_details);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize the plugin's class */
|
|
|
|
static void
|
|
|
|
gst_moodbar_class_init (GstMoodbarClass * klass)
|
|
|
|
{
|
|
|
|
GObjectClass *gobject_class;
|
|
|
|
GstElementClass *gstelement_class;
|
|
|
|
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
|
|
gstelement_class = (GstElementClass *) klass;
|
|
|
|
|
|
|
|
gobject_class->set_property = gst_moodbar_set_property;
|
|
|
|
gobject_class->get_property = gst_moodbar_get_property;
|
|
|
|
|
|
|
|
g_object_class_install_property (gobject_class, ARG_HEIGHT,
|
|
|
|
g_param_spec_int ("height", "Image height",
|
|
|
|
"The height of the resulting raw image",
|
|
|
|
1, G_MAXINT32, HEIGHT_DEFAULT, G_PARAM_READWRITE));
|
|
|
|
|
|
|
|
g_object_class_install_property (gobject_class, ARG_MAX_WIDTH,
|
|
|
|
g_param_spec_int ("max-width", "Image maximum width",
|
|
|
|
"The maximum width of the resulting raw image, or 0 for no rescaling",
|
|
|
|
0, G_MAXINT32, MAX_WIDTH_DEFAULT, G_PARAM_READWRITE));
|
|
|
|
|
|
|
|
gstelement_class->change_state
|
|
|
|
= GST_DEBUG_FUNCPTR (gst_moodbar_change_state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initialize the new element
|
|
|
|
* instantiate pads and add them to element
|
|
|
|
* set functions
|
|
|
|
* initialize structure
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
gst_moodbar_init (GstMoodbar *mood, GstMoodbarClass *gclass)
|
|
|
|
{
|
|
|
|
GstElementClass *klass = GST_ELEMENT_GET_CLASS (mood);
|
|
|
|
|
|
|
|
mood->sinkpad =
|
|
|
|
gst_pad_new_from_template
|
|
|
|
(gst_element_class_get_pad_template (klass, "sink"), "sink");
|
|
|
|
gst_pad_set_setcaps_function (mood->sinkpad,
|
|
|
|
GST_DEBUG_FUNCPTR (gst_moodbar_set_sink_caps));
|
|
|
|
gst_pad_set_event_function (mood->sinkpad,
|
|
|
|
GST_DEBUG_FUNCPTR (gst_moodbar_sink_event));
|
|
|
|
gst_pad_set_chain_function (mood->sinkpad,
|
|
|
|
GST_DEBUG_FUNCPTR (gst_moodbar_chain));
|
|
|
|
|
|
|
|
mood->srcpad =
|
|
|
|
gst_pad_new_from_template
|
|
|
|
(gst_element_class_get_pad_template (klass, "src"), "src");
|
|
|
|
|
|
|
|
|
|
|
|
gst_element_add_pad (GST_ELEMENT (mood), mood->sinkpad);
|
|
|
|
gst_element_add_pad (GST_ELEMENT (mood), mood->srcpad);
|
|
|
|
|
|
|
|
/* These are set once the (sink) capabilities are determined */
|
|
|
|
mood->rate = 0;
|
|
|
|
mood->size = 0;
|
|
|
|
mood->barkband_table = NULL;
|
|
|
|
|
|
|
|
/* These are allocated when we change to PAUSED */
|
|
|
|
mood->r = NULL;
|
|
|
|
mood->g = NULL;
|
|
|
|
mood->b = NULL;
|
|
|
|
mood->numframes = 0;
|
|
|
|
|
|
|
|
/* Property */
|
|
|
|
mood->height = HEIGHT_DEFAULT;
|
|
|
|
mood->max_width = MAX_WIDTH_DEFAULT;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void gst_moodbar_set_property (GObject *object, guint prop_id,
|
|
|
|
const GValue *value, GParamSpec *pspec)
|
|
|
|
{
|
|
|
|
GstMoodbar *mood = GST_MOODBAR (object);
|
|
|
|
|
|
|
|
switch (prop_id)
|
|
|
|
{
|
|
|
|
case ARG_HEIGHT:
|
|
|
|
mood->height = (guint) g_value_get_int (value);
|
|
|
|
break;
|
|
|
|
case ARG_MAX_WIDTH:
|
|
|
|
mood->max_width = (guint) g_value_get_int (value);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void gst_moodbar_get_property (GObject *object, guint prop_id,
|
|
|
|
GValue *value, GParamSpec *pspec)
|
|
|
|
{
|
|
|
|
GstMoodbar *mood = GST_MOODBAR (object);
|
|
|
|
|
|
|
|
switch (prop_id)
|
|
|
|
{
|
|
|
|
case ARG_HEIGHT:
|
|
|
|
g_value_set_int (value, (int) mood->height);
|
|
|
|
break;
|
|
|
|
case ARG_MAX_WIDTH:
|
|
|
|
g_value_set_int (value, (int) mood->max_width);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************************/
|
|
|
|
/* Pad handling */
|
|
|
|
/***************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
/* This calculates a table that caches which bark band slot each
|
|
|
|
* incoming band is supposed to go in. */
|
|
|
|
static void
|
|
|
|
calc_barkband_table (GstMoodbar *mood)
|
|
|
|
{
|
|
|
|
guint i;
|
|
|
|
guint barkband = 0;
|
|
|
|
|
|
|
|
/* Avoid divide-by-zero */
|
|
|
|
if (!mood->size || !mood->rate)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (mood->barkband_table)
|
|
|
|
g_free (mood->barkband_table);
|
|
|
|
|
|
|
|
mood->barkband_table = g_malloc (NUMFREQS (mood) * sizeof (guint));
|
|
|
|
|
|
|
|
for (i = 0; i < NUMFREQS (mood); ++i)
|
|
|
|
{
|
|
|
|
if (barkband < 23 &&
|
|
|
|
(guint) GST_SPECTRUM_BAND_FREQ (i, mood->size, mood->rate)
|
|
|
|
>= bark_bands[barkband])
|
|
|
|
barkband++;
|
|
|
|
|
|
|
|
mood->barkband_table[i] = barkband;
|
|
|
|
|
|
|
|
/*
|
|
|
|
GST_LOG ("Band %d (frequency %f) -> barkband %d (frequency %d)",
|
|
|
|
i, GST_SPECTRUM_BAND_FREQ (i, mood->size, mood->rate),
|
|
|
|
barkband, bark_bands[barkband]);
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Setting the sink caps just gets the rate and size parameters.
|
|
|
|
* Note that we do not support upstream caps renegotiation, since
|
|
|
|
* we could only possibly scale the height anyway.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gst_moodbar_set_sink_caps (GstPad *pad, GstCaps *caps)
|
|
|
|
{
|
|
|
|
GstMoodbar *mood;
|
|
|
|
GstStructure *newstruct;
|
|
|
|
gint rate, size;
|
|
|
|
gboolean res = FALSE;
|
|
|
|
|
|
|
|
mood = GST_MOODBAR (gst_pad_get_parent (pad));
|
|
|
|
|
|
|
|
newstruct = gst_caps_get_structure (caps, 0);
|
|
|
|
if (!gst_structure_get_int (newstruct, "rate", &rate) ||
|
|
|
|
!gst_structure_get_int (newstruct, "size", &size))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
res = TRUE;
|
|
|
|
|
|
|
|
mood->rate = rate;
|
|
|
|
mood->size = (guint) size;
|
|
|
|
calc_barkband_table (mood);
|
|
|
|
|
|
|
|
out:
|
|
|
|
gst_object_unref (mood);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
gst_moodbar_sink_event (GstPad *pad, GstEvent *event)
|
|
|
|
{
|
|
|
|
GstMoodbar *mood;
|
|
|
|
gboolean res = TRUE;
|
|
|
|
|
|
|
|
mood = GST_MOODBAR (gst_pad_get_parent (pad));
|
|
|
|
|
|
|
|
if (GST_EVENT_TYPE (event) == GST_EVENT_EOS)
|
|
|
|
gst_moodbar_finish (mood);
|
|
|
|
|
|
|
|
res = gst_pad_push_event (mood->srcpad, event);
|
|
|
|
gst_object_unref (mood);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***************************************************************/
|
|
|
|
/* Actual analysis */
|
|
|
|
/***************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
static GstStateChangeReturn
|
|
|
|
gst_moodbar_change_state (GstElement *element, GstStateChange transition)
|
|
|
|
{
|
|
|
|
GstMoodbar *mood = GST_MOODBAR (element);
|
|
|
|
GstStateChangeReturn res;
|
|
|
|
|
|
|
|
switch (transition)
|
|
|
|
{
|
|
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
|
|
calc_barkband_table (mood);
|
|
|
|
break;
|
|
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
2012-05-27 22:39:53 +02:00
|
|
|
mood->r = (gdouble *) g_malloc (FRAME_CHUNK * sizeof(gdouble));
|
|
|
|
mood->g = (gdouble *) g_malloc (FRAME_CHUNK * sizeof(gdouble));
|
|
|
|
mood->b = (gdouble *) g_malloc (FRAME_CHUNK * sizeof(gdouble));
|
2012-05-27 18:28:07 +02:00
|
|
|
mood->numframes = 0;
|
|
|
|
break;
|
|
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = parent_class->change_state (element, transition);
|
|
|
|
|
|
|
|
switch (transition)
|
|
|
|
{
|
|
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
|
|
break;
|
|
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
|
|
g_free (mood->r);
|
|
|
|
g_free (mood->g);
|
|
|
|
g_free (mood->b);
|
|
|
|
mood->r = NULL;
|
|
|
|
mood->g = NULL;
|
|
|
|
mood->b = NULL;
|
|
|
|
mood->numframes = 0;
|
|
|
|
break;
|
|
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
|
|
g_free (mood->barkband_table);
|
|
|
|
mood->barkband_table = NULL;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* We allocate r, g, b frames in chunks of FRAME_CHUNK so we don't
|
|
|
|
* have to realloc every time a buffer comes in.
|
|
|
|
*/
|
|
|
|
static gboolean
|
|
|
|
allocate_another_frame (GstMoodbar *mood)
|
|
|
|
{
|
|
|
|
mood->numframes++;
|
|
|
|
|
|
|
|
/* Failsafe */
|
|
|
|
if (mood->numframes == MAX_TRIPLES)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
if(mood->numframes % FRAME_CHUNK == 0)
|
|
|
|
{
|
2012-05-27 22:39:53 +02:00
|
|
|
guint size = (mood->numframes + FRAME_CHUNK) * sizeof (gdouble);
|
2012-05-27 18:28:07 +02:00
|
|
|
|
2012-05-27 22:39:53 +02:00
|
|
|
mood->r = (gdouble *) g_realloc (mood->r, size);
|
|
|
|
mood->g = (gdouble *) g_realloc (mood->g, size);
|
|
|
|
mood->b = (gdouble *) g_realloc (mood->b, size);
|
2012-05-27 18:28:07 +02:00
|
|
|
|
|
|
|
if (mood->r == NULL || mood->g == NULL || mood->b == NULL)
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* This function does most of the analysis on the spectra we
|
|
|
|
* get as input and caches them. We actually push buffers
|
|
|
|
* once we receive an EOS signal.
|
|
|
|
*/
|
|
|
|
static GstFlowReturn
|
|
|
|
gst_moodbar_chain (GstPad *pad, GstBuffer *buf)
|
|
|
|
{
|
|
|
|
GstMoodbar *mood = GST_MOODBAR (gst_pad_get_parent (pad));
|
|
|
|
guint i;
|
2012-05-27 22:39:53 +02:00
|
|
|
gdouble amplitudes[24], rgb[3] = {0.f, 0.f, 0.f};
|
|
|
|
gdouble *out, real, imag;
|
2012-05-27 18:28:07 +02:00
|
|
|
guint numfreqs = NUMFREQS (mood);
|
|
|
|
|
2012-05-27 22:39:53 +02:00
|
|
|
if (GST_BUFFER_SIZE (buf) != numfreqs * sizeof (gdouble) * 2)
|
2012-05-27 18:28:07 +02:00
|
|
|
{
|
|
|
|
gst_object_unref (mood);
|
|
|
|
return GST_FLOW_ERROR;
|
|
|
|
}
|
|
|
|
|
2012-05-27 22:39:53 +02:00
|
|
|
out = (gdouble *) GST_BUFFER_DATA (buf);
|
2012-05-27 18:28:07 +02:00
|
|
|
|
|
|
|
if (!allocate_another_frame (mood))
|
|
|
|
return GST_FLOW_ERROR;
|
|
|
|
|
|
|
|
/* Calculate total amplitudes for the different bark bands */
|
|
|
|
|
|
|
|
for (i = 0; i < 24; ++i)
|
|
|
|
amplitudes[i] = 0.f;
|
|
|
|
|
|
|
|
for (i = 0; i < numfreqs; ++i)
|
|
|
|
{
|
|
|
|
real = out[2*i]; imag = out[2*i + 1];
|
|
|
|
amplitudes[mood->barkband_table[i]] += sqrtf (real*real + imag*imag);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now divide the bark bands into thirds and compute their total
|
|
|
|
* amplitudes */
|
|
|
|
|
|
|
|
for (i = 0; i < 24; ++i)
|
|
|
|
rgb[i/8] += amplitudes[i] * amplitudes[i];
|
|
|
|
|
|
|
|
rgb[0] = sqrtf (rgb[0]);
|
|
|
|
rgb[1] = sqrtf (rgb[1]);
|
|
|
|
rgb[2] = sqrtf (rgb[2]);
|
|
|
|
|
|
|
|
mood->r[mood->numframes] = rgb[0];
|
|
|
|
mood->g[mood->numframes] = rgb[1];
|
|
|
|
mood->b[mood->numframes] = rgb[2];
|
|
|
|
|
|
|
|
gst_buffer_unref (buf);
|
|
|
|
gst_object_unref (mood);
|
|
|
|
|
|
|
|
return GST_FLOW_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* The normalization code was copied from Gav Wood's Exscalibar
|
|
|
|
* library, normalise.cpp
|
|
|
|
*/
|
|
|
|
static void
|
2012-05-27 22:39:53 +02:00
|
|
|
normalize (gdouble *vals, guint numvals)
|
2012-05-27 18:28:07 +02:00
|
|
|
{
|
2012-05-27 22:39:53 +02:00
|
|
|
gdouble mini, maxi, tu = 0.f, tb = 0.f;
|
|
|
|
gdouble avgu = 0.f, avgb = 0.f, delta, avg = 0.f;
|
|
|
|
gdouble avguu = 0.f, avgbb = 0.f;
|
2012-05-27 18:28:07 +02:00
|
|
|
guint i;
|
|
|
|
gint t = 0;
|
|
|
|
|
|
|
|
if (!numvals)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mini = maxi = vals[0];
|
|
|
|
|
|
|
|
for (i = 1; i < numvals; i++)
|
|
|
|
{
|
|
|
|
if (vals[i] > maxi)
|
|
|
|
maxi = vals[i];
|
|
|
|
else if (vals[i] < mini)
|
|
|
|
mini = vals[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < numvals; i++)
|
|
|
|
{
|
|
|
|
if(vals[i] != mini && vals[i] != maxi)
|
|
|
|
{
|
2012-05-27 22:39:53 +02:00
|
|
|
avg += vals[i] / ((gdouble) numvals);
|
2012-05-27 18:28:07 +02:00
|
|
|
t++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < numvals; i++)
|
|
|
|
{
|
|
|
|
if (vals[i] != mini && vals[i] != maxi)
|
|
|
|
{
|
|
|
|
if (vals[i] > avg)
|
|
|
|
{
|
|
|
|
avgu += vals[i];
|
|
|
|
tu++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
avgb += vals[i];
|
|
|
|
tb++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-27 22:39:53 +02:00
|
|
|
avgu /= (gdouble) tu;
|
|
|
|
avgb /= (gdouble) tb;
|
2012-05-27 18:28:07 +02:00
|
|
|
|
|
|
|
tu = 0.f;
|
|
|
|
tb = 0.f;
|
|
|
|
for (i = 0; i < numvals; i++)
|
|
|
|
{
|
|
|
|
if (vals[i] != mini && vals[i] != maxi)
|
|
|
|
{
|
|
|
|
if (vals[i] > avgu)
|
|
|
|
{
|
|
|
|
avguu += vals[i];
|
|
|
|
tu++;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (vals[i] < avgb)
|
|
|
|
{
|
|
|
|
avgbb += vals[i];
|
|
|
|
tb++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-27 22:39:53 +02:00
|
|
|
avguu /= (gdouble) tu;
|
|
|
|
avgbb /= (gdouble) tb;
|
2012-05-27 18:28:07 +02:00
|
|
|
|
|
|
|
mini = MAX (avg + (avgb - avg) * 2.f, avgbb);
|
|
|
|
maxi = MIN (avg + (avgu - avg) * 2.f, avguu);
|
|
|
|
delta = maxi - mini;
|
|
|
|
|
|
|
|
if (delta == 0.f)
|
|
|
|
delta = 1.f;
|
|
|
|
|
|
|
|
for (i = 0; i < numvals; i++)
|
|
|
|
vals[i] = finite (vals[i]) ? MIN(1.f, MAX(0.f, (vals[i] - mini) / delta))
|
|
|
|
: 0.f;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* This function normalizes all of the cached r,g,b data and
|
|
|
|
* finally pushes a monster buffer with all of our output.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
gst_moodbar_finish (GstMoodbar *mood)
|
|
|
|
{
|
|
|
|
GstBuffer *buf;
|
|
|
|
guchar *data;
|
|
|
|
guint line;
|
|
|
|
guint output_width;
|
|
|
|
|
|
|
|
if (mood->max_width == 0
|
|
|
|
|| mood->numframes <= mood->max_width)
|
|
|
|
output_width = mood->numframes;
|
|
|
|
else
|
|
|
|
output_width = mood->max_width;
|
|
|
|
|
|
|
|
normalize (mood->r, mood->numframes);
|
|
|
|
normalize (mood->g, mood->numframes);
|
|
|
|
normalize (mood->b, mood->numframes);
|
|
|
|
|
|
|
|
buf = gst_buffer_new_and_alloc
|
|
|
|
(output_width * mood->height * 3 * sizeof (guchar));
|
|
|
|
if (!buf)
|
|
|
|
return;
|
|
|
|
/* Don't set the timestamp, duration, etc. since it's irrelevant */
|
|
|
|
GST_BUFFER_OFFSET (buf) = 0;
|
|
|
|
data = (guchar *) GST_BUFFER_DATA (buf);
|
|
|
|
|
2012-05-27 22:39:53 +02:00
|
|
|
gdouble r, g, b;
|
2012-05-27 18:28:07 +02:00
|
|
|
guint i, j, n;
|
|
|
|
guint start, end;
|
|
|
|
for (line = 0; line < mood->height; ++line)
|
|
|
|
{
|
|
|
|
for (i = 0; i < output_width; ++i)
|
|
|
|
{
|
|
|
|
r = 0.f; g = 0.f; b = 0.f;
|
|
|
|
start = i * mood->numframes / output_width;
|
|
|
|
end = (i + 1) * mood->numframes / output_width;
|
|
|
|
if ( start == end )
|
|
|
|
end = start + 1;
|
|
|
|
|
|
|
|
for( j = start; j < end; j++ )
|
|
|
|
{
|
|
|
|
r += mood->r[j] * 255.f;
|
|
|
|
g += mood->g[j] * 255.f;
|
|
|
|
b += mood->b[j] * 255.f;
|
|
|
|
}
|
|
|
|
|
|
|
|
n = end - start;
|
|
|
|
|
2012-05-27 22:39:53 +02:00
|
|
|
*(data++) = (guchar) (r / ((gdouble) n));
|
|
|
|
*(data++) = (guchar) (g / ((gdouble) n));
|
|
|
|
*(data++) = (guchar) (b / ((gdouble) n));
|
2012-05-27 18:28:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{ /* Now we (finally) know the width of the image we're pushing */
|
|
|
|
GstCaps *caps = gst_caps_copy (gst_pad_get_caps (mood->srcpad));
|
|
|
|
gboolean res;
|
|
|
|
gst_caps_set_simple (caps, "width", G_TYPE_INT, output_width, NULL);
|
|
|
|
gst_caps_set_simple (caps, "height", G_TYPE_INT, mood->height, NULL);
|
|
|
|
res = gst_pad_set_caps (mood->srcpad, caps);
|
|
|
|
if (res)
|
|
|
|
gst_buffer_set_caps (buf, caps);
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
if (!res)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
gst_pad_push (mood->srcpad, buf);
|
|
|
|
}
|