diff --git a/3rdparty/chromaprint/CMakeLists.txt b/3rdparty/chromaprint/CMakeLists.txt index 78a054024..12452de00 100644 --- a/3rdparty/chromaprint/CMakeLists.txt +++ b/3rdparty/chromaprint/CMakeLists.txt @@ -52,7 +52,6 @@ set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests/) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) find_package(FFmpeg) -find_package(FFTW3) if(APPLE) find_library(ACCELERATE_LIBRARIES Accelerate) endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index d1d6fcade..ca139c2cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ include(cmake/Deb.cmake) include(cmake/Rpm.cmake) include(cmake/SpotifyVersion.cmake) include(cmake/OptionalSource.cmake) +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) if (UNIX AND NOT APPLE) set(LINUX 1) @@ -48,6 +49,7 @@ find_package(Boost REQUIRED) find_package(Gettext REQUIRED) find_package(PkgConfig REQUIRED) find_package(Protobuf REQUIRED) +find_package(FFTW3) pkg_check_modules(TAGLIB REQUIRED taglib>=1.6) pkg_check_modules(QJSON REQUIRED QJson) @@ -193,6 +195,7 @@ option(ENABLE_BREAKPAD "Enable crash reporting" OFF) option(ENABLE_SPOTIFY_BLOB "Build the spotify non-GPL binary" ON) option(ENABLE_SPOTIFY "Enable spotify support" ON) option(ENABLE_PLASMARUNNER "Enable plasma krunner global search" OFF) +option(ENABLE_MOODBAR "Enable moodbar" ON) if(WIN32) option(ENABLE_WIN32_CONSOLE "Show the windows console even outside Debug mode" OFF) @@ -255,6 +258,10 @@ if(QCA_FOUND AND HAVE_SPOTIFY) set(HAVE_QCA ON) endif(QCA_FOUND AND HAVE_SPOTIFY) +if(ENABLE_MOODBAR AND FFTW3_FOUND) + set(HAVE_MOODBAR ON) +endif() + if(ENABLE_VISUALISATIONS) # When/if upstream accepts our patches then these options can be used to link @@ -402,6 +409,10 @@ if(HAVE_SPOTIFY_BLOB) add_subdirectory(ext/clementine-spotifyblob) endif(HAVE_SPOTIFY_BLOB) +if(HAVE_MOODBAR) + add_subdirectory(gst/moodbar) +endif() + # This goes after everything else because KDE fucks everything else up with its # cmake includes. find_package(KDE4 4.3.60) @@ -433,6 +444,7 @@ summary_add("Devices: MTP support" HAVE_LIBMTP) summary_add("Devices: GIO backend" HAVE_GIO) summary_add("Gnome sound menu integration" HAVE_LIBINDICATE) summary_add("Last.fm support" HAVE_LIBLASTFM) +summary_add("Moodbar support" HAVE_MOODBAR) summary_add("Spotify support: core code" HAVE_SPOTIFY) summary_add("Spotify support: non-GPL binary helper" HAVE_SPOTIFY_BLOB) summary_add("Visualisations" ENABLE_VISUALISATIONS) diff --git a/3rdparty/chromaprint/cmake/modules/FindFFTW3.cmake b/cmake/FindFFTW3.cmake similarity index 96% rename from 3rdparty/chromaprint/cmake/modules/FindFFTW3.cmake rename to cmake/FindFFTW3.cmake index fb5cef88d..e711dd3ab 100644 --- a/3rdparty/chromaprint/cmake/modules/FindFFTW3.cmake +++ b/cmake/FindFFTW3.cmake @@ -64,14 +64,14 @@ FIND_LIBRARY(FFTW3_FFTW_LIBRARY #MESSAGE("DBG FFTW3_FFTW_LIBRARY=${FFTW3_FFTW_LIBRARY}") FIND_LIBRARY(FFTW3_FFTWF_LIBRARY - NAMES fftwf3 fftwf libfftwf libfftwf3 libfftw3f-3 + NAMES fftwf3 fftw3f fftwf libfftwf libfftwf3 libfftw3f-3 PATHS ${FFTW3_POSSIBLE_LIBRARY_PATH} ) #MESSAGE("DBG FFTW3_FFTWF_LIBRARY=${FFTW3_FFTWF_LIBRARY}") FIND_LIBRARY(FFTW3_FFTWL_LIBRARY - NAMES fftwl3 fftwl libfftwl libfftwl3 libfftw3l-3 + NAMES fftwl3 fftw3l fftwl libfftwl libfftwl3 libfftw3l-3 PATHS ${FFTW3_POSSIBLE_LIBRARY_PATH} ) diff --git a/debian/copyright b/debian/copyright index 375016791..f901e107e 100644 --- a/debian/copyright +++ b/debian/copyright @@ -51,6 +51,12 @@ Files: src/core/scoped_nsobject.h Copyright: 2011, The Chromium Authors License: BSD-Google +Files: gst/moodbar/gstfftwspectrum.* + gst/moodbar/gstmoodbar.* + gst/moodbar/spectrum.* +Copyright: 2006, Joseph Rabinoff +License: GPL-2+ + Files: 3rdparty/gmock/* Copyright: 2008, Google Inc. License: BSD-Google diff --git a/gst/moodbar/01-fftw-plan-mutex.patch b/gst/moodbar/01-fftw-plan-mutex.patch new file mode 100644 index 000000000..8e4862939 --- /dev/null +++ b/gst/moodbar/01-fftw-plan-mutex.patch @@ -0,0 +1,31 @@ +From 0b62ebc38d1cc0202c6f57c4e096fa0b68a41baf Mon Sep 17 00:00:00 2001 +From: David Sansome +Date: Sun, 27 May 2012 17:00:32 +0100 +Subject: [PATCH] Protect calls to fftwf_plan_dft_r2c_1d with a mutex + +--- + plugin/gstfftwspectrum.c | 4 ++++ + 1 files changed, 4 insertions(+), 0 deletions(-) + +diff --git a/plugin/gstfftwspectrum.c b/plugin/gstfftwspectrum.c +index 147e606..f6e2427 100644 +--- a/plugin/gstfftwspectrum.c ++++ b/plugin/gstfftwspectrum.c +@@ -302,10 +302,14 @@ alloc_fftw_data (GstFFTWSpectrum *conv) + * outputs are the hermetian conjugates). This should be optimal for + * implementing filters. + */ ++ ++ static GStaticMutex mutex = G_STATIC_MUTEX_INIT; ++ g_static_mutex_lock(&mutex); + conv->fftw_plan + = fftwf_plan_dft_r2c_1d(conv->size, conv->fftw_in, + (fftwf_complex *) conv->fftw_out, + conv->hi_q ? FFTW_MEASURE : FFTW_ESTIMATE); ++ g_static_mutex_unlock(&mutex); + } + + +-- +1.7.5.4 + diff --git a/gst/moodbar/CMakeLists.txt b/gst/moodbar/CMakeLists.txt new file mode 100644 index 000000000..222c81f9c --- /dev/null +++ b/gst/moodbar/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 2.6) + +set(CMAKE_C_FLAGS "-Wall") +set(CMAKE_CXX_FLAGS "-Woverloaded-virtual -Wall") + +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GOBJECT_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${FFTW3_INCLUDE_DIR}) + +set(SOURCES + gstfftwspectrum.c + gstmoodbar.c + spectrum.c +) + +add_library(gstmoodbar STATIC + ${SOURCES} +) + +target_link_libraries(gstmoodbar + ${GOBJECT_LIBRARIES} + ${GLIB_LIBRARIES} + ${GSTREAMER_LIBRARIES} + ${GSTREAMER_BASE_LIBRARIES} + ${FFTW3_FFTWF_LIBRARY} +) diff --git a/gst/moodbar/gstfftwspectrum.c b/gst/moodbar/gstfftwspectrum.c new file mode 100644 index 000000000..f6e2427a5 --- /dev/null +++ b/gst/moodbar/gstfftwspectrum.c @@ -0,0 +1,638 @@ +/* GStreamer FFTW-based signal-to-spectrum converter + * Copyright (C) 2006 Joseph Rabinoff + */ + +/*************************************************************************** + * * + * 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-fftwspectrum + * + * + * Example launch line + * + * + * gst-launch audiotestsrc ! audioconvert ! fftwspectrum ! fftwunspectrum ! audioconvert ! alsasink + * + * + * + */ + +/* This is a simple plugin to take an audio signal and return its + * Fourier transform, using fftw3. It takes a specified number N of + * samples and returns the first N/2+1 (complex) Fourier transform + * values (the other half of the values being the complex conjugates + * of the first). The modulus of these values correspond to the + * strength of the signal in their various bands, and the phase gives + * information about the phase of the signal. The step by which the + * transform increments is also variable, so it can return redundant + * data (to reduce artifacts when converting back into a signal). + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include "gstfftwspectrum.h" +#include "spectrum.h" + +GST_DEBUG_CATEGORY (gst_fftwspectrum_debug); +#define GST_CAT_DEFAULT gst_fftwspectrum_debug + +/* Filter signals and args */ +enum +{ + /* FILL ME */ + LAST_SIGNAL +}; + +/* The size and step arguments are actually only default values + * used to fixate the size and step properties of the source cap. + */ +enum +{ + ARG_0, + ARG_DEF_SIZE, + ARG_DEF_STEP, + ARG_HIQUALITY +}; + +#define DEF_SIZE_DEFAULT 1024 +#define DEF_STEP_DEFAULT 512 +#define HIQUALITY_DEFAULT TRUE + +static GstStaticPadTemplate sink_factory + = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS + ( SPECTRUM_SIGNAL_CAPS ) + ); + +/* See spectrum.h for a definition of the frequency caps */ +static GstStaticPadTemplate src_factory + = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS + ( SPECTRUM_FREQ_CAPS ) + ); + +GST_BOILERPLATE (GstFFTWSpectrum, gst_fftwspectrum, GstElement, + GST_TYPE_ELEMENT); + +static void gst_fftwspectrum_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static void gst_fftwspectrum_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); + +static gboolean gst_fftwspectrum_set_sink_caps (GstPad *pad, GstCaps *caps); +static gboolean gst_fftwspectrum_set_src_caps (GstPad *pad, GstCaps *caps); +static void gst_fftwspectrum_fixatecaps (GstPad *pad, GstCaps *caps); +static GstCaps *gst_fftwspectrum_getcaps (GstPad *pad); + +static GstFlowReturn gst_fftwspectrum_chain (GstPad *pad, GstBuffer *buf); +static GstStateChangeReturn gst_fftwspectrum_change_state (GstElement *element, + GstStateChange transition); + + +#define OUTPUT_SIZE(conv) (((conv)->size/2+1)*sizeof(fftwf_complex)) + + +/***************************************************************/ +/* GObject boilerplate stuff */ +/***************************************************************/ + + +static void +gst_fftwspectrum_base_init (gpointer gclass) +{ + static GstElementDetails element_details = + { + "FFTW-based Fourier transform", + "Filter/Converter/Spectrum", + "Convert a raw audio stream into a frequency spectrum", + "Joe Rabinoff " + }; + 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_fftwspectrum_class_init (GstFFTWSpectrumClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + + gobject_class->set_property = gst_fftwspectrum_set_property; + gobject_class->get_property = gst_fftwspectrum_get_property; + + g_object_class_install_property (gobject_class, ARG_DEF_SIZE, + g_param_spec_int ("def-size", "Default Size", + "Apply a Fourier transform to this many samples at a time (default value)", + 1, G_MAXINT32, DEF_SIZE_DEFAULT, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_DEF_STEP, + g_param_spec_int ("def-step", "Default Step", + "Advance the stream this many samples each time (default value)", + 1, G_MAXINT32, DEF_STEP_DEFAULT, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, ARG_HIQUALITY, + g_param_spec_boolean ("hiquality", "High Quality", + "Use a more time-consuming, higher quality algorithm chooser", + HIQUALITY_DEFAULT, G_PARAM_READWRITE)); + + gstelement_class->change_state + = GST_DEBUG_FUNCPTR (gst_fftwspectrum_change_state); +} + +/* initialize the new element + * instantiate pads and add them to element + * set functions + * initialize structure + */ +static void +gst_fftwspectrum_init (GstFFTWSpectrum * conv, + GstFFTWSpectrumClass * gclass) +{ + GstElementClass *klass = GST_ELEMENT_GET_CLASS (conv); + + conv->sinkpad = + gst_pad_new_from_template + (gst_element_class_get_pad_template (klass, "sink"), "sink"); + gst_pad_set_setcaps_function (conv->sinkpad, + GST_DEBUG_FUNCPTR (gst_fftwspectrum_set_sink_caps)); + gst_pad_set_getcaps_function (conv->sinkpad, + GST_DEBUG_FUNCPTR (gst_fftwspectrum_getcaps)); + gst_pad_set_chain_function (conv->sinkpad, + GST_DEBUG_FUNCPTR (gst_fftwspectrum_chain)); + + conv->srcpad = + gst_pad_new_from_template + (gst_element_class_get_pad_template (klass, "src"), "src"); + gst_pad_set_setcaps_function (conv->srcpad, + GST_DEBUG_FUNCPTR (gst_fftwspectrum_set_src_caps)); + gst_pad_set_getcaps_function (conv->srcpad, + GST_DEBUG_FUNCPTR (gst_fftwspectrum_getcaps)); + gst_pad_set_fixatecaps_function (conv->srcpad, + GST_DEBUG_FUNCPTR (gst_fftwspectrum_fixatecaps)); + + + gst_element_add_pad (GST_ELEMENT (conv), conv->sinkpad); + gst_element_add_pad (GST_ELEMENT (conv), conv->srcpad); + + /* These are set once the (source) capabilities are determined */ + conv->rate = 0; + conv->size = 0; + conv->step = 0; + + /* These are set when we change to READY */ + conv->fftw_in = NULL; + conv->fftw_out = NULL; + conv->fftw_plan = NULL; + + /* These are set when we start receiving data */ + conv->samples = NULL; + conv->numsamples = 0; + conv->timestamp = 0; + conv->offset = 0; + + /* Properties */ + conv->def_size = DEF_SIZE_DEFAULT; + conv->def_step = DEF_STEP_DEFAULT; + conv->hi_q = HIQUALITY_DEFAULT; +} + +static void +gst_fftwspectrum_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstFFTWSpectrum *conv = GST_FFTWSPECTRUM (object); + + switch (prop_id) + { + case ARG_DEF_SIZE: + conv->def_size = g_value_get_int (value); + break; + case ARG_DEF_STEP: + conv->def_step = g_value_get_int (value); + break; + case ARG_HIQUALITY: + conv->hi_q = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_fftwspectrum_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstFFTWSpectrum *conv = GST_FFTWSPECTRUM (object); + + switch (prop_id) + { + case ARG_DEF_SIZE: + g_value_set_int (value, conv->def_size); + break; + case ARG_DEF_STEP: + g_value_set_int (value, conv->def_step); + break; + case ARG_HIQUALITY: + g_value_set_boolean (value, conv->hi_q); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +/* Allocate and deallocate fftw state data */ +static void +free_fftw_data (GstFFTWSpectrum *conv) +{ + if(conv->fftw_plan != NULL) + fftwf_destroy_plan (conv->fftw_plan); + if(conv->fftw_in != NULL) + fftwf_free (conv->fftw_in); + if(conv->fftw_out != NULL) + fftwf_free (conv->fftw_out); + + conv->fftw_in = NULL; + conv->fftw_out = NULL; + conv->fftw_plan = NULL; +} + +static void +alloc_fftw_data (GstFFTWSpectrum *conv) +{ + free_fftw_data (conv); + + GST_DEBUG ("Allocating data for size = %d and step = %d", + conv->size, conv->step); + + conv->fftw_in = (float *) fftwf_malloc (sizeof(float) * conv->size); + conv->fftw_out = (float *) fftwf_malloc (OUTPUT_SIZE (conv)); + + /* We use the simplest real-to-complex algorithm, which takes n real + * inputs and returns floor(n/2) + 1 complex outputs (the other n/2 + * outputs are the hermetian conjugates). This should be optimal for + * implementing filters. + */ + + static GStaticMutex mutex = G_STATIC_MUTEX_INIT; + g_static_mutex_lock(&mutex); + conv->fftw_plan + = fftwf_plan_dft_r2c_1d(conv->size, conv->fftw_in, + (fftwf_complex *) conv->fftw_out, + conv->hi_q ? FFTW_MEASURE : FFTW_ESTIMATE); + g_static_mutex_unlock(&mutex); +} + + +/***************************************************************/ +/* Capabilities negotiation */ +/***************************************************************/ + +/* The input and output capabilities are only related by the "rate" + * parameter, which is propagated so that an audio signal can be + * reconstructed eventually. This module does no rate conversion. + * + * The way I understand it, there are two times when caps negotiation + * takes place: (1) when a sink pad receives either its first buffer, + * or a buffer with a new caps type, and (2) when a source pad request + * a buffer from something downstream, and the returned allocated + * buffer has different caps from the ones already negotiated. In the + * first case, _set_sink_caps is called, and in the second, _set_src_caps + * is called. + * When (1) occurs, we remember the rate (the only variable parameter + * in the source) and set the source caps. Then _set_src_caps is called. + * In _set_src_caps, we check that the rate hasn't changed, and figure out + * or remember appropriate size and step attributes. If _set_src_caps is + * called from _set_sink_caps, this completes our setting up our internal + * configuration; if it is called from (2), we reconfigure just the source + * part of the internal configuration. + */ + +static gboolean +gst_fftwspectrum_set_sink_caps (GstPad * pad, GstCaps * caps) +{ + GstFFTWSpectrum *conv; + GstCaps *srccaps, *newsrccaps; + GstStructure *newstruct; + gint rate; + gboolean res; + + conv = GST_FFTWSPECTRUM (gst_pad_get_parent (pad)); + + srccaps = gst_pad_get_allowed_caps (conv->srcpad); + newsrccaps = gst_caps_copy_nth (srccaps, 0); + gst_caps_unref (srccaps); + + newstruct = gst_caps_get_structure (caps, 0); + if (!gst_structure_get_int (newstruct, "rate", &rate)) + { + gst_caps_unref (newsrccaps); + gst_object_unref (conv); + return FALSE; + } + + /* Fixate the source caps with the given rate */ + gst_caps_set_simple (newsrccaps, "rate", G_TYPE_INT, rate, NULL); + gst_pad_fixate_caps (conv->srcpad, newsrccaps); + conv->rate = rate; + res = gst_pad_set_caps (conv->srcpad, newsrccaps); + if (!res) + conv->rate = 0; + + gst_caps_unref (newsrccaps); + gst_object_unref (conv); + + return res; +} + +static gboolean +gst_fftwspectrum_set_src_caps (GstPad * pad, GstCaps * caps) +{ + GstFFTWSpectrum *conv; + gboolean res = FALSE; + GstStructure *newstruct; + gint rate; + + conv = GST_FFTWSPECTRUM (gst_pad_get_parent (pad)); + + newstruct = gst_caps_get_structure (caps, 0); + if (!gst_structure_get_int (newstruct, "rate", &rate)) + goto out; + + /* Assume caps negotiation has already taken place */ + if (rate == conv->rate) + { + gint size, step; + + if (!gst_structure_get_int (newstruct, "size", &size)) + goto out; + if (!gst_structure_get_int (newstruct, "step", &step)) + goto out; + + if (conv->size != size || conv->step != step) + { + conv->size = size; + conv->step = step; + + /* Re-allocate the fftw data */ + if (GST_STATE (GST_ELEMENT (conv)) >= GST_STATE_READY) + alloc_fftw_data (conv); + } + + res = TRUE; + } + + out: + gst_object_unref (conv); + + return res; +} + + +/* The only thing that can constrain the caps is the rate. */ +static GstCaps * +gst_fftwspectrum_getcaps (GstPad *pad) +{ + GstFFTWSpectrum *conv; + GstCaps *tmplcaps; + + conv = GST_FFTWSPECTRUM (gst_pad_get_parent (pad)); + tmplcaps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); + + if(conv->rate != 0) + { + /* Assumes the template caps are simple */ + gst_caps_set_simple (tmplcaps, "rate", G_TYPE_INT, conv->rate, NULL); + } + + gst_object_unref (conv); + + return tmplcaps; +} + + +/* This is called when the source pad needs to choose its capabilities + * when it has a choice and nobody's forcing its hand. In this case + * we take our hint from the def_size and def_step properties. + */ +static void +gst_fftwspectrum_fixatecaps (GstPad *pad, GstCaps *caps) +{ + GstFFTWSpectrum *conv; + GstStructure *s; + const GValue *val; + + conv = GST_FFTWSPECTRUM (gst_pad_get_parent (pad)); + s = gst_caps_get_structure (caps, 0); + + val = gst_structure_get_value (s, "size"); + if (val == NULL) + gst_caps_set_simple (caps, "size", G_TYPE_INT, conv->def_size, NULL); + else if (G_VALUE_TYPE (val) == GST_TYPE_INT_RANGE) + { + gint sizemin, sizemax; + sizemin = gst_value_get_int_range_min (val); + sizemax = gst_value_get_int_range_max (val); + gst_caps_set_simple (caps, "size", G_TYPE_INT, + CLAMP (conv->def_size, sizemin, sizemax), NULL); + } + /* else it should be already fixed */ + + val = gst_structure_get_value (s, "step"); + if (val == NULL) + gst_caps_set_simple (caps, "step", G_TYPE_INT, conv->def_step, NULL); + else if (G_VALUE_TYPE (val) == GST_TYPE_INT_RANGE) + { + gint stepmin, stepmax; + stepmin = gst_value_get_int_range_min (val); + stepmax = gst_value_get_int_range_max (val); + gst_caps_set_simple (caps, "step", G_TYPE_INT, + CLAMP (conv->def_step, stepmin, stepmax), NULL); + } + /* else it should be already fixed */ + + /* Assume rate is already fixed (if not it'll be fixed by default) */ + + gst_object_unref (conv); +} + + +/***************************************************************/ +/* Actual conversion */ +/***************************************************************/ + + +static GstStateChangeReturn +gst_fftwspectrum_change_state (GstElement * element, + GstStateChange transition) +{ + GstFFTWSpectrum *conv = GST_FFTWSPECTRUM (element); + GstStateChangeReturn res; + + switch (transition) + { + case GST_STATE_CHANGE_NULL_TO_READY: + alloc_fftw_data (conv); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + conv->samples = (gfloat *) g_malloc (sizeof(gfloat)); + conv->numsamples = 0; + conv->timestamp = 0; + conv->offset = 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(conv->samples); + conv->samples = NULL; + conv->numsamples = 0; + conv->timestamp = 0; + conv->offset = 0; + break; + case GST_STATE_CHANGE_READY_TO_NULL: + free_fftw_data (conv); + break; + default: + break; + } + + return res; +} + + +/* Adds the samples contained in buf to the end of conv->samples, + * updating conv->numsamples. + */ +static void +push_samples (GstFFTWSpectrum *conv, GstBuffer *buf) +{ + gint newsamples = GST_BUFFER_SIZE (buf) / sizeof (gfloat); + gint oldsamples = conv->numsamples; + + conv->numsamples += newsamples; + conv->samples = g_realloc (conv->samples, conv->numsamples * sizeof (gfloat)); + memcpy (&conv->samples[oldsamples], GST_BUFFER_DATA (buf), + newsamples * sizeof (gfloat)); + + /* GST_LOG ("Added %d samples", newsamples); */ +} + +/* This basically does the opposite of push_samples, but takes samples + * off the front. + */ +static void +shift_samples (GstFFTWSpectrum *conv, gint toshift) +{ + gfloat *oldsamples = conv->samples; + + conv->numsamples -= toshift; + conv->samples = g_malloc (MAX (conv->numsamples, 1) * sizeof (float)); + memcpy (conv->samples, &oldsamples[toshift], + conv->numsamples * sizeof (gfloat)); + g_free (oldsamples); + + /* Fix the timestamp and offset */ + conv->timestamp + += gst_util_uint64_scale_int (GST_SECOND, toshift, conv->rate); + conv->offset += toshift; + + /* GST_LOG ("Disposed of %d samples (time: %" GST_TIME_FORMAT " offset: %llu)", + toshift, GST_TIME_ARGS(conv->timestamp), conv->offset); */ +} + + +/* This function queues samples until there are at least + * max (conv->size, conv->step) samples to process. We + * then process samples in chunks of conv->size and increment + * by conv->step. + */ +static GstFlowReturn +gst_fftwspectrum_chain (GstPad * pad, GstBuffer * buf) +{ + GstFFTWSpectrum *conv; + GstBuffer *outbuf; + GstFlowReturn res = GST_FLOW_OK; + + conv = GST_FFTWSPECTRUM (gst_pad_get_parent (pad)); + + push_samples (conv, buf); + gst_buffer_unref (buf); + + while (conv->numsamples >= MAX (conv->size, conv->step)) + { + res = gst_pad_alloc_buffer_and_set_caps + (conv->srcpad, conv->offset, OUTPUT_SIZE (conv), + GST_PAD_CAPS(conv->srcpad), &outbuf); + if (res != GST_FLOW_OK) + break; + + GST_BUFFER_SIZE (outbuf) = OUTPUT_SIZE (conv); + GST_BUFFER_OFFSET (outbuf) = conv->offset; + GST_BUFFER_OFFSET_END (outbuf) = conv->offset + conv->step; + GST_BUFFER_TIMESTAMP (outbuf) = conv->timestamp; + GST_BUFFER_DURATION (outbuf) + = gst_util_uint64_scale_int (GST_SECOND, conv->step, conv->rate); + + /* Do the Fourier transform */ + memcpy (conv->fftw_in, conv->samples, conv->size * sizeof (float)); + fftwf_execute (conv->fftw_plan); + { /* Normalize */ + gint i; + gfloat root = sqrtf (conv->size); + for (i = 0; i < 2*(conv->size/2+1); ++i) + conv->fftw_out[i] /= root; + } + memcpy (GST_BUFFER_DATA (outbuf), conv->fftw_out, OUTPUT_SIZE (conv)); + + res = gst_pad_push (conv->srcpad, outbuf); + + shift_samples (conv, conv->step); + + if (res != GST_FLOW_OK) + break; + } + + gst_object_unref (conv); + + return res; +} + diff --git a/gst/moodbar/gstfftwspectrum.h b/gst/moodbar/gstfftwspectrum.h new file mode 100644 index 000000000..8e72ae553 --- /dev/null +++ b/gst/moodbar/gstfftwspectrum.h @@ -0,0 +1,68 @@ +/* GStreamer FFTW-based signal-to-spectrum converter + * Copyright (C) 2006 Joseph Rabinoff + */ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + + +#ifndef __GST_FFTWSPECTRUM_H__ +#define __GST_FFTWSPECTRUM_H__ + +#include +#include + +G_BEGIN_DECLS + +/* #defines don't like whitespacey bits */ +#define GST_TYPE_FFTWSPECTRUM \ + (gst_fftwspectrum_get_type()) +#define GST_FFTWSPECTRUM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FFTWSPECTRUM,GstFFTWSpectrum)) +#define GST_FFTWSPECTRUM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_FFTWSPECTRUM,GstFFTWSpectrumClass)) + +typedef struct _GstFFTWSpectrum GstFFTWSpectrum; +typedef struct _GstFFTWSpectrumClass GstFFTWSpectrumClass; + +struct _GstFFTWSpectrum +{ + GstElement element; + + GstPad *sinkpad, *srcpad; + + /* Stream data */ + gint rate, size, step; + + /* Actual queued (incoming) stream */ + gfloat *samples; + gint numsamples; + GstClockTime timestamp; /* Timestamp of the first sample */ + guint64 offset; /* Offset of the first sample */ + + /* State data for fftw */ + float *fftw_in; + float *fftw_out; + fftwf_plan fftw_plan; + + /* Properties */ + gint32 def_size, def_step; + gboolean hi_q; +}; + +struct _GstFFTWSpectrumClass +{ + GstElementClass parent_class; +}; + +GType gst_fftwspectrum_get_type (void); + +G_END_DECLS + +#endif /* __GST_FFTWSPECTRUM_H__ */ diff --git a/gst/moodbar/gstmoodbar.c b/gst/moodbar/gstmoodbar.c new file mode 100644 index 000000000..7954328ab --- /dev/null +++ b/gst/moodbar/gstmoodbar.c @@ -0,0 +1,673 @@ +/* GStreamer spectrum analysis toy + * Copyright (C) 2006 Joseph Rabinoff + * 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 + * + * + * Example launch line + * + * + * gst-launch filesrc location=test.mp3 ! mad ! audioconvert ! fftwspectrum ! moodbar height=50 ! pngenc ! filesink location=test.png + * + * + * + */ + +/* 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 +#endif + +#include +#include +#include + +#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 " + }; + 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: + mood->r = (gfloat *) g_malloc (FRAME_CHUNK * sizeof(gfloat)); + mood->g = (gfloat *) g_malloc (FRAME_CHUNK * sizeof(gfloat)); + mood->b = (gfloat *) g_malloc (FRAME_CHUNK * sizeof(gfloat)); + 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) + { + guint size = (mood->numframes + FRAME_CHUNK) * sizeof (gfloat); + + mood->r = (gfloat *) g_realloc (mood->r, size); + mood->g = (gfloat *) g_realloc (mood->g, size); + mood->b = (gfloat *) g_realloc (mood->b, size); + + 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; + gfloat amplitudes[24], rgb[3] = {0.f, 0.f, 0.f}; + gfloat *out, real, imag; + guint numfreqs = NUMFREQS (mood); + + if (GST_BUFFER_SIZE (buf) != numfreqs * sizeof (gfloat) * 2) + { + gst_object_unref (mood); + return GST_FLOW_ERROR; + } + + out = (gfloat *) GST_BUFFER_DATA (buf); + + 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 +normalize (gfloat *vals, guint numvals) +{ + gfloat mini, maxi, tu = 0.f, tb = 0.f; + gfloat avgu = 0.f, avgb = 0.f, delta, avg = 0.f; + gfloat avguu = 0.f, avgbb = 0.f; + 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) + { + avg += vals[i] / ((gfloat) numvals); + 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++; + } + } + } + + avgu /= (gfloat) tu; + avgb /= (gfloat) tb; + + 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++; + } + } + } + + avguu /= (gfloat) tu; + avgbb /= (gfloat) tb; + + 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); + + gfloat r, g, b; + 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; + + *(data++) = (guchar) (r / ((gfloat) n)); + *(data++) = (guchar) (g / ((gfloat) n)); + *(data++) = (guchar) (b / ((gfloat) n)); + } + } + + { /* 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); +} diff --git a/gst/moodbar/gstmoodbar.h b/gst/moodbar/gstmoodbar.h new file mode 100644 index 000000000..1f2a472ff --- /dev/null +++ b/gst/moodbar/gstmoodbar.h @@ -0,0 +1,63 @@ +/* GStreamer spectrum analysis toy + * Copyright (C) 2006 Joseph Rabinoff + * 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. * + * * + ***************************************************************************/ + +#ifndef __GST_MOODBAR_H__ +#define __GST_MOODBAR_H__ + +#include + +G_BEGIN_DECLS + +/* #defines don't like whitespacey bits */ +#define GST_TYPE_MOODBAR \ + (gst_moodbar_get_type()) +#define GST_MOODBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MOODBAR,GstMoodbar)) +#define GST_MOODBAR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MOODBAR,GstMoodbarClass)) + +typedef struct _GstMoodbar GstMoodbar; +typedef struct _GstMoodbarClass GstMoodbarClass; + +struct _GstMoodbar +{ + GstElement element; + + GstPad *sinkpad, *srcpad; + + /* Stream data */ + gint rate, size; + + /* Cached band -> bark band table */ + guint *barkband_table; + + /* Queued moodbar data */ + gfloat *r, *g, *b; + guint numframes; + + /* Property */ + guint height; + guint max_width; +}; + +struct _GstMoodbarClass +{ + GstElementClass parent_class; +}; + +GType gst_moodbar_get_type (void); + +G_END_DECLS + +#endif /* __GST_MOODBAR_H__ */ diff --git a/gst/moodbar/spectrum.c b/gst/moodbar/spectrum.c new file mode 100644 index 000000000..f82f3932f --- /dev/null +++ b/gst/moodbar/spectrum.c @@ -0,0 +1,71 @@ +/* GStreamer moodbar plugin globals + * Copyright (C) 2006 Joseph Rabinoff + */ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "gstfftwspectrum.h" +#include "gstmoodbar.h" +#include "spectrum.h" + + +/***************************************************************/ +/* Plugin managing */ +/***************************************************************/ + +GST_DEBUG_CATEGORY_EXTERN (gst_fftwspectrum_debug); +GST_DEBUG_CATEGORY_EXTERN (gst_moodbar_debug); + + +/* entry point to initialize the plug-in + * initialize the plug-in itself + * register the element factories and pad templates + * register the features + * + * exchange the string 'plugin' with your elemnt name + */ +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "fftwspectrum", + GST_RANK_NONE, GST_TYPE_FFTWSPECTRUM)) + return FALSE; + + if (!gst_element_register (plugin, "moodbar", + GST_RANK_NONE, GST_TYPE_MOODBAR)) + return FALSE; + + GST_DEBUG_CATEGORY_INIT (gst_fftwspectrum_debug, "fftwspectrum", + 0, "FFTW Sample-to-Spectrum Converter Plugin"); + GST_DEBUG_CATEGORY_INIT (gst_moodbar_debug, "moodbar", + 0, "Moodbar analyzer"); + + return TRUE; +} + +void gstmoodbar_register_static() { + gst_plugin_register_static( + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "moodbar", + "Frequency analyzer and converter plugin", + plugin_init, + "0.1.2", + "GPL", + "Moodbar", + "Moodbar", + "http://amarok.kde.org/wiki/Moodbar"); +} diff --git a/gst/moodbar/spectrum.h b/gst/moodbar/spectrum.h new file mode 100644 index 000000000..14ca2f118 --- /dev/null +++ b/gst/moodbar/spectrum.h @@ -0,0 +1,67 @@ +/* GStreamer moodbar plugin globals + * Copyright (C) 2006 Joseph Rabinoff + */ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef __SPECTRUM_H__ +#define __SPECTRUM_H__ + + +/* Since fftwspectrum and fftwunspectrum are supposed to be + * opposites, they'll be using the same caps: */ + +#define SPECTRUM_SIGNAL_CAPS "audio/x-raw-float, " \ + "rate = (int) [ 1, MAX ], " \ + "channels = (int) 1, " \ + "endianness = (int) BYTE_ORDER, " \ + "width = (int) 32, " \ + "signed = (boolean) true" + +/* audio/x-spectrum-complex-float is an array of complex floats. A + * complex float is just a pair (r, i) of a real float and an + * imaginary float, each with the specified width. The properties + * are as follows: + * rate: the rate of the original signal + * size: the number of signals processed to make the current buffer + * step: the number of signals advanced after the current buffer + * width: the size of the real & imaginary parts of the data + * endianness: ditto + * + * Each audio/x-spectrum-complex-float buffer represents the Fourier + * transform of size samples, and hence _must_ have exactly + * floor(size/2) + 1 complex floats in it; in other words, its + * buffer size must be (floor(size/2) + 1) * 2 * sizeof(gfloat) + */ + +#define SPECTRUM_FREQ_CAPS "audio/x-spectrum-complex-float, " \ + "rate = (int) [ 1, MAX ], " \ + "endianness = (int) BYTE_ORDER, " \ + "width = (int) 32, " \ + "size = (int) [ 1, MAX ], " \ + "step = (int) [ 1, MAX ]" + + +/* Given a band number from a spectrum made from size audio + * samples at the given rate, return the frequency that band + * corresponds to. + */ +#define GST_SPECTRUM_BAND_FREQ(band, size, rate) \ + (((gfloat)(band))*((gfloat)(rate))/((gfloat)(size))) + +#ifdef __cplusplus +extern "C" { +#endif + void gstmoodbar_register_static(); +#ifdef __cplusplus +} +#endif + +#endif /* __SPECTRUM_H__ */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7418112a6..0aebe4aa5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1085,6 +1085,10 @@ if(HAVE_IMOBILEDEVICE) link_directories(${USBMUXD_LIBRARY_DIRS}) endif(HAVE_IMOBILEDEVICE) +if(HAVE_MOODBAR) + target_link_libraries(clementine_lib gstmoodbar) +endif() + if(HAVE_LIBMTP) target_link_libraries(clementine_lib ${LIBMTP_LIBRARIES}) endif(HAVE_LIBMTP) diff --git a/src/config.h.in b/src/config.h.in index 9ef910354..a3f0329f6 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -32,6 +32,7 @@ #cmakedefine HAVE_LIBINDICATE #cmakedefine HAVE_LIBLASTFM #cmakedefine HAVE_LIBMTP +#cmakedefine HAVE_MOODBAR #cmakedefine HAVE_QCA #cmakedefine HAVE_SAC #cmakedefine HAVE_SPARKLE diff --git a/src/engines/gstengine.cpp b/src/engines/gstengine.cpp index 76d392a81..28b320a31 100644 --- a/src/engines/gstengine.cpp +++ b/src/engines/gstengine.cpp @@ -32,6 +32,10 @@ # include "gst/afcsrc/gstafcsrc.h" #endif +#ifdef HAVE_MOODBAR +# include "gst/moodbar/spectrum.h" +#endif + #include #include #include @@ -154,6 +158,10 @@ void GstEngine::InitialiseGstreamer() { #ifdef HAVE_IMOBILEDEVICE afcsrc_register_static(); #endif + +#ifdef HAVE_MOODBAR + gstmoodbar_register_static(); +#endif } void GstEngine::ReloadSettings() {