Bundle the gstreamer moodbar plugin, with a patch that protects calls to fftwf_plan from multiple threads.
This commit is contained in:
parent
19c3e1d5ec
commit
638a4b9739
|
@ -52,7 +52,6 @@ set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests/)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
|
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
|
||||||
find_package(FFmpeg)
|
find_package(FFmpeg)
|
||||||
find_package(FFTW3)
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
find_library(ACCELERATE_LIBRARIES Accelerate)
|
find_library(ACCELERATE_LIBRARIES Accelerate)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -8,6 +8,7 @@ include(cmake/Deb.cmake)
|
||||||
include(cmake/Rpm.cmake)
|
include(cmake/Rpm.cmake)
|
||||||
include(cmake/SpotifyVersion.cmake)
|
include(cmake/SpotifyVersion.cmake)
|
||||||
include(cmake/OptionalSource.cmake)
|
include(cmake/OptionalSource.cmake)
|
||||||
|
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
if (UNIX AND NOT APPLE)
|
if (UNIX AND NOT APPLE)
|
||||||
set(LINUX 1)
|
set(LINUX 1)
|
||||||
|
@ -48,6 +49,7 @@ find_package(Boost REQUIRED)
|
||||||
find_package(Gettext REQUIRED)
|
find_package(Gettext REQUIRED)
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
find_package(Protobuf REQUIRED)
|
find_package(Protobuf REQUIRED)
|
||||||
|
find_package(FFTW3)
|
||||||
|
|
||||||
pkg_check_modules(TAGLIB REQUIRED taglib>=1.6)
|
pkg_check_modules(TAGLIB REQUIRED taglib>=1.6)
|
||||||
pkg_check_modules(QJSON REQUIRED QJson)
|
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_BLOB "Build the spotify non-GPL binary" ON)
|
||||||
option(ENABLE_SPOTIFY "Enable spotify support" ON)
|
option(ENABLE_SPOTIFY "Enable spotify support" ON)
|
||||||
option(ENABLE_PLASMARUNNER "Enable plasma krunner global search" OFF)
|
option(ENABLE_PLASMARUNNER "Enable plasma krunner global search" OFF)
|
||||||
|
option(ENABLE_MOODBAR "Enable moodbar" ON)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
option(ENABLE_WIN32_CONSOLE "Show the windows console even outside Debug mode" OFF)
|
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)
|
set(HAVE_QCA ON)
|
||||||
endif(QCA_FOUND AND HAVE_SPOTIFY)
|
endif(QCA_FOUND AND HAVE_SPOTIFY)
|
||||||
|
|
||||||
|
if(ENABLE_MOODBAR AND FFTW3_FOUND)
|
||||||
|
set(HAVE_MOODBAR ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
if(ENABLE_VISUALISATIONS)
|
if(ENABLE_VISUALISATIONS)
|
||||||
# When/if upstream accepts our patches then these options can be used to link
|
# 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)
|
add_subdirectory(ext/clementine-spotifyblob)
|
||||||
endif(HAVE_SPOTIFY_BLOB)
|
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
|
# This goes after everything else because KDE fucks everything else up with its
|
||||||
# cmake includes.
|
# cmake includes.
|
||||||
find_package(KDE4 4.3.60)
|
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("Devices: GIO backend" HAVE_GIO)
|
||||||
summary_add("Gnome sound menu integration" HAVE_LIBINDICATE)
|
summary_add("Gnome sound menu integration" HAVE_LIBINDICATE)
|
||||||
summary_add("Last.fm support" HAVE_LIBLASTFM)
|
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: core code" HAVE_SPOTIFY)
|
||||||
summary_add("Spotify support: non-GPL binary helper" HAVE_SPOTIFY_BLOB)
|
summary_add("Spotify support: non-GPL binary helper" HAVE_SPOTIFY_BLOB)
|
||||||
summary_add("Visualisations" ENABLE_VISUALISATIONS)
|
summary_add("Visualisations" ENABLE_VISUALISATIONS)
|
||||||
|
|
|
@ -64,14 +64,14 @@ FIND_LIBRARY(FFTW3_FFTW_LIBRARY
|
||||||
#MESSAGE("DBG FFTW3_FFTW_LIBRARY=${FFTW3_FFTW_LIBRARY}")
|
#MESSAGE("DBG FFTW3_FFTW_LIBRARY=${FFTW3_FFTW_LIBRARY}")
|
||||||
|
|
||||||
FIND_LIBRARY(FFTW3_FFTWF_LIBRARY
|
FIND_LIBRARY(FFTW3_FFTWF_LIBRARY
|
||||||
NAMES fftwf3 fftwf libfftwf libfftwf3 libfftw3f-3
|
NAMES fftwf3 fftw3f fftwf libfftwf libfftwf3 libfftw3f-3
|
||||||
PATHS
|
PATHS
|
||||||
${FFTW3_POSSIBLE_LIBRARY_PATH}
|
${FFTW3_POSSIBLE_LIBRARY_PATH}
|
||||||
)
|
)
|
||||||
#MESSAGE("DBG FFTW3_FFTWF_LIBRARY=${FFTW3_FFTWF_LIBRARY}")
|
#MESSAGE("DBG FFTW3_FFTWF_LIBRARY=${FFTW3_FFTWF_LIBRARY}")
|
||||||
|
|
||||||
FIND_LIBRARY(FFTW3_FFTWL_LIBRARY
|
FIND_LIBRARY(FFTW3_FFTWL_LIBRARY
|
||||||
NAMES fftwl3 fftwl libfftwl libfftwl3 libfftw3l-3
|
NAMES fftwl3 fftw3l fftwl libfftwl libfftwl3 libfftw3l-3
|
||||||
PATHS
|
PATHS
|
||||||
${FFTW3_POSSIBLE_LIBRARY_PATH}
|
${FFTW3_POSSIBLE_LIBRARY_PATH}
|
||||||
)
|
)
|
|
@ -51,6 +51,12 @@ Files: src/core/scoped_nsobject.h
|
||||||
Copyright: 2011, The Chromium Authors
|
Copyright: 2011, The Chromium Authors
|
||||||
License: BSD-Google
|
License: BSD-Google
|
||||||
|
|
||||||
|
Files: gst/moodbar/gstfftwspectrum.*
|
||||||
|
gst/moodbar/gstmoodbar.*
|
||||||
|
gst/moodbar/spectrum.*
|
||||||
|
Copyright: 2006, Joseph Rabinoff <bobqwatson@yahoo.com>
|
||||||
|
License: GPL-2+
|
||||||
|
|
||||||
Files: 3rdparty/gmock/*
|
Files: 3rdparty/gmock/*
|
||||||
Copyright: 2008, Google Inc.
|
Copyright: 2008, Google Inc.
|
||||||
License: BSD-Google
|
License: BSD-Google
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
From 0b62ebc38d1cc0202c6f57c4e096fa0b68a41baf Mon Sep 17 00:00:00 2001
|
||||||
|
From: David Sansome <me@davidsansome.com>
|
||||||
|
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
|
||||||
|
|
|
@ -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}
|
||||||
|
)
|
|
@ -0,0 +1,638 @@
|
||||||
|
/* GStreamer FFTW-based signal-to-spectrum converter
|
||||||
|
* Copyright (C) 2006 Joseph Rabinoff <bobqwatson@yahoo.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* <refsect2>
|
||||||
|
* <title>Example launch line</title>
|
||||||
|
* <para>
|
||||||
|
* <programlisting>
|
||||||
|
* gst-launch audiotestsrc ! audioconvert ! fftwspectrum ! fftwunspectrum ! audioconvert ! alsasink
|
||||||
|
* </programlisting>
|
||||||
|
* </para>
|
||||||
|
* </refsect2>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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 <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <fftw3.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#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 <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_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;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/* GStreamer FFTW-based signal-to-spectrum converter
|
||||||
|
* Copyright (C) 2006 Joseph Rabinoff <bobqwatson@yahoo.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* 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 <gst/gst.h>
|
||||||
|
#include <fftw3.h>
|
||||||
|
|
||||||
|
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__ */
|
|
@ -0,0 +1,673 @@
|
||||||
|
/* 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:
|
||||||
|
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);
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* 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. *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __GST_MOODBAR_H__
|
||||||
|
#define __GST_MOODBAR_H__
|
||||||
|
|
||||||
|
#include <gst/gst.h>
|
||||||
|
|
||||||
|
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__ */
|
|
@ -0,0 +1,71 @@
|
||||||
|
/* GStreamer moodbar plugin globals
|
||||||
|
* Copyright (C) 2006 Joseph Rabinoff <bobqwatson@yahoo.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* 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 <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <gst/gst.h>
|
||||||
|
|
||||||
|
#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");
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* GStreamer moodbar plugin globals
|
||||||
|
* Copyright (C) 2006 Joseph Rabinoff <bobqwatson@yahoo.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* 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__ */
|
|
@ -1085,6 +1085,10 @@ if(HAVE_IMOBILEDEVICE)
|
||||||
link_directories(${USBMUXD_LIBRARY_DIRS})
|
link_directories(${USBMUXD_LIBRARY_DIRS})
|
||||||
endif(HAVE_IMOBILEDEVICE)
|
endif(HAVE_IMOBILEDEVICE)
|
||||||
|
|
||||||
|
if(HAVE_MOODBAR)
|
||||||
|
target_link_libraries(clementine_lib gstmoodbar)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(HAVE_LIBMTP)
|
if(HAVE_LIBMTP)
|
||||||
target_link_libraries(clementine_lib ${LIBMTP_LIBRARIES})
|
target_link_libraries(clementine_lib ${LIBMTP_LIBRARIES})
|
||||||
endif(HAVE_LIBMTP)
|
endif(HAVE_LIBMTP)
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#cmakedefine HAVE_LIBINDICATE
|
#cmakedefine HAVE_LIBINDICATE
|
||||||
#cmakedefine HAVE_LIBLASTFM
|
#cmakedefine HAVE_LIBLASTFM
|
||||||
#cmakedefine HAVE_LIBMTP
|
#cmakedefine HAVE_LIBMTP
|
||||||
|
#cmakedefine HAVE_MOODBAR
|
||||||
#cmakedefine HAVE_QCA
|
#cmakedefine HAVE_QCA
|
||||||
#cmakedefine HAVE_SAC
|
#cmakedefine HAVE_SAC
|
||||||
#cmakedefine HAVE_SPARKLE
|
#cmakedefine HAVE_SPARKLE
|
||||||
|
|
|
@ -32,6 +32,10 @@
|
||||||
# include "gst/afcsrc/gstafcsrc.h"
|
# include "gst/afcsrc/gstafcsrc.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_MOODBAR
|
||||||
|
# include "gst/moodbar/spectrum.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -154,6 +158,10 @@ void GstEngine::InitialiseGstreamer() {
|
||||||
#ifdef HAVE_IMOBILEDEVICE
|
#ifdef HAVE_IMOBILEDEVICE
|
||||||
afcsrc_register_static();
|
afcsrc_register_static();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_MOODBAR
|
||||||
|
gstmoodbar_register_static();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::ReloadSettings() {
|
void GstEngine::ReloadSettings() {
|
||||||
|
|
Loading…
Reference in New Issue