Move gstfastspectrum to 3rdparty
This commit is contained in:
parent
9ae0afaaf7
commit
e9684cd1a1
|
@ -26,3 +26,11 @@ getopt included on Windows for command line options parsing with Unicode support
|
||||||
The directory can safely be deleted on other platforms.
|
The directory can safely be deleted on other platforms.
|
||||||
|
|
||||||
URL: https://github.com/ludvikjerabek/getopt-win
|
URL: https://github.com/ludvikjerabek/getopt-win
|
||||||
|
|
||||||
|
gstfastspectrum
|
||||||
|
---------------
|
||||||
|
A GStreamer spectrum plugin using FFTW3.
|
||||||
|
It is needed for moodbar support, and is currently not available
|
||||||
|
in GStreamer.
|
||||||
|
The plan is to submit it to GStreamer, or move it to
|
||||||
|
a seperate repository outside of Strawberry.
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
cmake_minimum_required(VERSION 3.13)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
set(SOURCES gstfastspectrum.cpp gstmoodbarplugin.cpp)
|
set(SOURCES gstfastspectrum.cpp)
|
||||||
|
|
||||||
add_library(gstmoodbar STATIC ${SOURCES})
|
add_library(gstfastspectrum STATIC ${SOURCES})
|
||||||
|
|
||||||
target_include_directories(gstmoodbar SYSTEM PRIVATE
|
target_include_directories(gstfastspectrum SYSTEM PRIVATE
|
||||||
${GLIB_INCLUDE_DIRS}
|
${GLIB_INCLUDE_DIRS}
|
||||||
${GOBJECT_INCLUDE_DIRS}
|
${GOBJECT_INCLUDE_DIRS}
|
||||||
${GSTREAMER_INCLUDE_DIRS}
|
${GSTREAMER_INCLUDE_DIRS}
|
||||||
|
@ -13,9 +13,9 @@ target_include_directories(gstmoodbar SYSTEM PRIVATE
|
||||||
${FFTW3_INCLUDE_DIR}
|
${FFTW3_INCLUDE_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(gstmoodbar PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(gstfastspectrum PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
target_link_directories(gstmoodbar PRIVATE
|
target_link_directories(gstfastspectrum PRIVATE
|
||||||
${GLIB_LIBRARY_DIRS}
|
${GLIB_LIBRARY_DIRS}
|
||||||
${GOBJECT_LIBRARY_DIRS}
|
${GOBJECT_LIBRARY_DIRS}
|
||||||
${GSTREAMER_LIBRARY_DIRS}
|
${GSTREAMER_LIBRARY_DIRS}
|
||||||
|
@ -24,12 +24,11 @@ target_link_directories(gstmoodbar PRIVATE
|
||||||
${FFTW3_LIBRARY_DIRS}
|
${FFTW3_LIBRARY_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(gstmoodbar PRIVATE
|
target_link_libraries(gstfastspectrum PRIVATE
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
${GOBJECT_LIBRARIES}
|
${GOBJECT_LIBRARIES}
|
||||||
${GSTREAMER_LIBRARIES}
|
${GSTREAMER_LIBRARIES}
|
||||||
${GSTREAMER_BASE_LIBRARIES}
|
${GSTREAMER_BASE_LIBRARIES}
|
||||||
${GSTREAMER_AUDIO_LIBRARIES}
|
${GSTREAMER_AUDIO_LIBRARIES}
|
||||||
${FFTW3_FFTW_LIBRARY}
|
${FFTW3_FFTW_LIBRARY}
|
||||||
Qt${QT_VERSION_MAJOR}::Core
|
|
||||||
)
|
)
|
|
@ -0,0 +1,520 @@
|
||||||
|
/* GStreamer
|
||||||
|
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||||
|
* <2006,2011> Stefan Kost <ensonic@users.sf.net>
|
||||||
|
* <2007-2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||||
|
* <2018-2024> Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Library General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Library General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Library General Public
|
||||||
|
* License along with this library; if not, write to the
|
||||||
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <gst/audio/gstaudiofilter.h>
|
||||||
|
|
||||||
|
#include <fftw3.h>
|
||||||
|
|
||||||
|
#include "gstfastspectrum.h"
|
||||||
|
|
||||||
|
GST_DEBUG_CATEGORY_STATIC(gst_strawberry_fastspectrum_debug);
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Spectrum properties
|
||||||
|
constexpr auto DEFAULT_INTERVAL = (GST_SECOND / 10);
|
||||||
|
constexpr auto DEFAULT_BANDS = 128;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_INTERVAL,
|
||||||
|
PROP_BANDS
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||||
|
#endif
|
||||||
|
G_DEFINE_TYPE(GstStrawberryFastSpectrum, gst_strawberry_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_finalize(GObject *object);
|
||||||
|
static void gst_strawberry_fastspectrum_set_property(GObject *object, const guint prop_id, const GValue *value, GParamSpec *pspec);
|
||||||
|
static void gst_strawberry_fastspectrum_get_property(GObject *object, const guint prop_id, GValue *value, GParamSpec *pspec);
|
||||||
|
static gboolean gst_strawberry_fastspectrum_start(GstBaseTransform *transform);
|
||||||
|
static gboolean gst_strawberry_fastspectrum_stop(GstBaseTransform *transform);
|
||||||
|
static GstFlowReturn gst_strawberry_fastspectrum_transform_ip(GstBaseTransform *transform, GstBuffer *buffer);
|
||||||
|
static gboolean gst_strawberry_fastspectrum_setup(GstAudioFilter *audio_filter, const GstAudioInfo *audio_info);
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_class_init(GstStrawberryFastSpectrumClass *klass) {
|
||||||
|
|
||||||
|
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
||||||
|
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
|
||||||
|
GstBaseTransformClass *transform_class = GST_BASE_TRANSFORM_CLASS(klass);
|
||||||
|
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS(klass);
|
||||||
|
|
||||||
|
gobject_class->set_property = gst_strawberry_fastspectrum_set_property;
|
||||||
|
gobject_class->get_property = gst_strawberry_fastspectrum_get_property;
|
||||||
|
gobject_class->finalize = gst_strawberry_fastspectrum_finalize;
|
||||||
|
|
||||||
|
transform_class->start = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_start);
|
||||||
|
transform_class->stop = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_stop);
|
||||||
|
transform_class->transform_ip = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_transform_ip);
|
||||||
|
transform_class->passthrough_on_same_caps = TRUE;
|
||||||
|
|
||||||
|
filter_class->setup = GST_DEBUG_FUNCPTR(gst_strawberry_fastspectrum_setup);
|
||||||
|
|
||||||
|
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||||
|
|
||||||
|
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||||
|
|
||||||
|
GST_DEBUG_CATEGORY_INIT(gst_strawberry_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
||||||
|
|
||||||
|
gst_element_class_set_static_metadata(element_class,
|
||||||
|
"Fast spectrum analyzer using FFTW",
|
||||||
|
"Filter/Analyzer/Audio",
|
||||||
|
"Run an FFT on the audio signal, output spectrum data",
|
||||||
|
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
||||||
|
"Stefan Kost <ensonic@users.sf.net>, "
|
||||||
|
"Sebastian Dröge <sebastian.droege@collabora.co.uk>, "
|
||||||
|
"Jonas Kvinge <jonas@jkvinge.net>");
|
||||||
|
|
||||||
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||||
|
GstCaps *caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16LE, S24LE, S32LE, F32LE, F64LE }") ", layout = (string) interleaved, channels = 1");
|
||||||
|
#else
|
||||||
|
GstCaps *caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16BE, S24BE, S32BE, F32BE, F64BE }") ", layout = (string) interleaved, channels = 1");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gst_audio_filter_class_add_pad_templates(filter_class, caps);
|
||||||
|
gst_caps_unref(caps);
|
||||||
|
|
||||||
|
g_mutex_init(&klass->fftw_lock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_init(GstStrawberryFastSpectrum *fastspectrum) {
|
||||||
|
|
||||||
|
fastspectrum->interval = DEFAULT_INTERVAL;
|
||||||
|
fastspectrum->bands = DEFAULT_BANDS;
|
||||||
|
|
||||||
|
fastspectrum->channel_data_initialized = false;
|
||||||
|
|
||||||
|
g_mutex_init(&fastspectrum->lock);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_alloc_channel_data(GstStrawberryFastSpectrum *fastspectrum) {
|
||||||
|
|
||||||
|
const guint bands = fastspectrum->bands;
|
||||||
|
const guint nfft = 2 * bands - 2;
|
||||||
|
|
||||||
|
fastspectrum->input_ring_buffer = new double[nfft];
|
||||||
|
fastspectrum->fft_input = reinterpret_cast<double*>(fftw_malloc(sizeof(double) * nfft));
|
||||||
|
fastspectrum->fft_output = reinterpret_cast<fftw_complex*>(fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1)));
|
||||||
|
|
||||||
|
fastspectrum->spect_magnitude = new double[bands] {};
|
||||||
|
|
||||||
|
GstStrawberryFastSpectrumClass *klass = reinterpret_cast<GstStrawberryFastSpectrumClass*>(G_OBJECT_GET_CLASS(fastspectrum));
|
||||||
|
{
|
||||||
|
g_mutex_lock(&klass->fftw_lock);
|
||||||
|
fastspectrum->plan = fftw_plan_dft_r2c_1d(static_cast<int>(nfft), fastspectrum->fft_input, fastspectrum->fft_output, FFTW_ESTIMATE);
|
||||||
|
g_mutex_unlock(&klass->fftw_lock);
|
||||||
|
}
|
||||||
|
fastspectrum->channel_data_initialized = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_free_channel_data(GstStrawberryFastSpectrum *fastspectrum) {
|
||||||
|
|
||||||
|
GstStrawberryFastSpectrumClass *klass = reinterpret_cast<GstStrawberryFastSpectrumClass*>(G_OBJECT_GET_CLASS(fastspectrum));
|
||||||
|
|
||||||
|
if (fastspectrum->channel_data_initialized) {
|
||||||
|
{
|
||||||
|
g_mutex_lock(&klass->fftw_lock);
|
||||||
|
fftw_destroy_plan(fastspectrum->plan);
|
||||||
|
g_mutex_unlock(&klass->fftw_lock);
|
||||||
|
}
|
||||||
|
fftw_free(fastspectrum->fft_input);
|
||||||
|
fftw_free(fastspectrum->fft_output);
|
||||||
|
delete[] fastspectrum->input_ring_buffer;
|
||||||
|
delete[] fastspectrum->spect_magnitude;
|
||||||
|
|
||||||
|
fastspectrum->channel_data_initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_flush(GstStrawberryFastSpectrum *fastspectrum) {
|
||||||
|
|
||||||
|
fastspectrum->num_frames = 0;
|
||||||
|
fastspectrum->num_fft = 0;
|
||||||
|
fastspectrum->accumulated_error = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_reset_state(GstStrawberryFastSpectrum *fastspectrum) {
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT(fastspectrum, "resetting state");
|
||||||
|
|
||||||
|
gst_strawberry_fastspectrum_free_channel_data(fastspectrum);
|
||||||
|
gst_strawberry_fastspectrum_flush(fastspectrum);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_finalize(GObject *object) {
|
||||||
|
|
||||||
|
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(object);
|
||||||
|
|
||||||
|
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||||
|
g_mutex_clear(&fastspectrum->lock);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS(gst_strawberry_fastspectrum_parent_class)->finalize(object);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_set_property(GObject *object, const guint prop_id, const GValue *value, GParamSpec *pspec) {
|
||||||
|
|
||||||
|
GstStrawberryFastSpectrum *filter = reinterpret_cast<GstStrawberryFastSpectrum*>(object);
|
||||||
|
|
||||||
|
switch (prop_id) {
|
||||||
|
case PROP_INTERVAL: {
|
||||||
|
const guint64 interval = g_value_get_uint64(value);
|
||||||
|
g_mutex_lock(&filter->lock);
|
||||||
|
if (filter->interval != interval) {
|
||||||
|
filter->interval = interval;
|
||||||
|
gst_strawberry_fastspectrum_reset_state(filter);
|
||||||
|
}
|
||||||
|
g_mutex_unlock(&filter->lock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PROP_BANDS: {
|
||||||
|
const guint bands = g_value_get_uint(value);
|
||||||
|
g_mutex_lock(&filter->lock);
|
||||||
|
if (filter->bands != bands) {
|
||||||
|
filter->bands = bands;
|
||||||
|
gst_strawberry_fastspectrum_reset_state(filter);
|
||||||
|
}
|
||||||
|
g_mutex_unlock(&filter->lock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_get_property(GObject *object, const guint prop_id, GValue *value, GParamSpec *pspec) {
|
||||||
|
|
||||||
|
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(object);
|
||||||
|
|
||||||
|
switch (prop_id) {
|
||||||
|
case PROP_INTERVAL:
|
||||||
|
g_value_set_uint64(value, fastspectrum->interval);
|
||||||
|
break;
|
||||||
|
case PROP_BANDS:
|
||||||
|
g_value_set_uint(value, fastspectrum->bands);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean gst_strawberry_fastspectrum_start(GstBaseTransform *transform) {
|
||||||
|
|
||||||
|
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(transform);
|
||||||
|
|
||||||
|
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean gst_strawberry_fastspectrum_stop(GstBaseTransform *transform) {
|
||||||
|
|
||||||
|
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(transform);
|
||||||
|
|
||||||
|
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixing data readers
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_input_data_mixed_float(const guint8 *_in, double *out, const guint len, const double max_value, guint op, const guint nfft) {
|
||||||
|
|
||||||
|
(void) max_value;
|
||||||
|
|
||||||
|
const gfloat *in = reinterpret_cast<const gfloat*>(_in);
|
||||||
|
guint ip = 0;
|
||||||
|
|
||||||
|
for (guint j = 0; j < len; j++) {
|
||||||
|
out[op] = in[ip++];
|
||||||
|
op = (op + 1) % nfft;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_input_data_mixed_double(const guint8 *_in, double *out, const guint len, const double max_value, guint op, const guint nfft) {
|
||||||
|
|
||||||
|
(void) max_value;
|
||||||
|
|
||||||
|
const gdouble *in = reinterpret_cast<const gdouble*>(_in);
|
||||||
|
guint ip = 0;
|
||||||
|
|
||||||
|
for (guint j = 0; j < len; j++) {
|
||||||
|
out[op] = in[ip++];
|
||||||
|
op = (op + 1) % nfft;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_input_data_mixed_int32_max(const guint8 *_in, double *out, const guint len, const double max_value, guint op, const guint nfft) {
|
||||||
|
|
||||||
|
const gint32 *in = reinterpret_cast<const gint32*>(_in);
|
||||||
|
guint ip = 0;
|
||||||
|
|
||||||
|
for (guint j = 0; j < len; j++) {
|
||||||
|
out[op] = in[ip++] / max_value;
|
||||||
|
op = (op + 1) % nfft;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_input_data_mixed_int24_max(const guint8 *_in, double *out, const guint len, const double max_value, guint op, const guint nfft) {
|
||||||
|
|
||||||
|
for (guint j = 0; j < len; j++) {
|
||||||
|
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||||
|
guint32 value = GST_READ_UINT24_BE(_in);
|
||||||
|
#else
|
||||||
|
guint32 value = GST_READ_UINT24_LE(_in);
|
||||||
|
#endif
|
||||||
|
if (value & 0x00800000) {
|
||||||
|
value |= 0xff000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
out[op] = value / max_value;
|
||||||
|
op = (op + 1) % nfft;
|
||||||
|
_in += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_input_data_mixed_int16_max(const guint8 *_in, double *out, const guint len, const double max_value, guint op, const guint nfft) {
|
||||||
|
|
||||||
|
const gint16 *in = reinterpret_cast<const gint16*>(_in);
|
||||||
|
guint ip = 0;
|
||||||
|
|
||||||
|
for (guint j = 0; j < len; j++) {
|
||||||
|
out[op] = in[ip++] / max_value;
|
||||||
|
op = (op + 1) % nfft;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean gst_strawberry_fastspectrum_setup(GstAudioFilter *audio_filter, const GstAudioInfo *audio_info) {
|
||||||
|
|
||||||
|
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(audio_filter);
|
||||||
|
GstStrawberryFastSpectrumInputData input_data = nullptr;
|
||||||
|
|
||||||
|
g_mutex_lock(&fastspectrum->lock);
|
||||||
|
switch (GST_AUDIO_INFO_FORMAT(audio_info)) {
|
||||||
|
case GST_AUDIO_FORMAT_S16:
|
||||||
|
input_data = gst_strawberry_fastspectrum_input_data_mixed_int16_max;
|
||||||
|
break;
|
||||||
|
case GST_AUDIO_FORMAT_S24:
|
||||||
|
input_data = gst_strawberry_fastspectrum_input_data_mixed_int24_max;
|
||||||
|
break;
|
||||||
|
case GST_AUDIO_FORMAT_S32:
|
||||||
|
input_data = gst_strawberry_fastspectrum_input_data_mixed_int32_max;
|
||||||
|
break;
|
||||||
|
case GST_AUDIO_FORMAT_F32:
|
||||||
|
input_data = gst_strawberry_fastspectrum_input_data_mixed_float;
|
||||||
|
break;
|
||||||
|
case GST_AUDIO_FORMAT_F64:
|
||||||
|
input_data = gst_strawberry_fastspectrum_input_data_mixed_double;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g_assert_not_reached();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fastspectrum->input_data = input_data;
|
||||||
|
|
||||||
|
gst_strawberry_fastspectrum_reset_state(fastspectrum);
|
||||||
|
g_mutex_unlock(&fastspectrum->lock);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gst_strawberry_fastspectrum_run_fft(GstStrawberryFastSpectrum *fastspectrum, const guint input_pos) {
|
||||||
|
|
||||||
|
const guint bands = fastspectrum->bands;
|
||||||
|
const guint nfft = 2 * bands - 2;
|
||||||
|
|
||||||
|
for (guint i = 0; i < nfft; i++) {
|
||||||
|
fastspectrum->fft_input[i] = fastspectrum->input_ring_buffer[(input_pos + i) % nfft];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be safe to execute the same plan multiple times in parallel.
|
||||||
|
fftw_execute(fastspectrum->plan);
|
||||||
|
|
||||||
|
// Calculate magnitude in db
|
||||||
|
for (guint i = 0; i < bands; i++) {
|
||||||
|
gdouble value = fastspectrum->fft_output[i][0] * fastspectrum->fft_output[i][0];
|
||||||
|
value += fastspectrum->fft_output[i][1] * fastspectrum->fft_output[i][1];
|
||||||
|
value /= nfft * nfft;
|
||||||
|
fastspectrum->spect_magnitude[i] += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstFlowReturn gst_strawberry_fastspectrum_transform_ip(GstBaseTransform *transform, GstBuffer *buffer) {
|
||||||
|
|
||||||
|
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(transform);
|
||||||
|
|
||||||
|
const guint rate = GST_AUDIO_FILTER_RATE(fastspectrum);
|
||||||
|
const guint bps = GST_AUDIO_FILTER_BPS(fastspectrum);
|
||||||
|
const guint bpf = GST_AUDIO_FILTER_BPF(fastspectrum);
|
||||||
|
const double max_value = static_cast<double>((1UL << ((bps << 3) - 1)) - 1);
|
||||||
|
const guint bands = fastspectrum->bands;
|
||||||
|
const guint nfft = 2 * bands - 2;
|
||||||
|
|
||||||
|
g_mutex_lock(&fastspectrum->lock);
|
||||||
|
|
||||||
|
GstMapInfo map;
|
||||||
|
gst_buffer_map(buffer, &map, GST_MAP_READ);
|
||||||
|
const guint8 *data = map.data;
|
||||||
|
gsize size = map.size;
|
||||||
|
|
||||||
|
GST_LOG_OBJECT(fastspectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
|
||||||
|
|
||||||
|
if (GST_BUFFER_IS_DISCONT(buffer)) {
|
||||||
|
GST_DEBUG_OBJECT(fastspectrum, "Discontinuity detected -- flushing");
|
||||||
|
gst_strawberry_fastspectrum_flush(fastspectrum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't have a FFT context yet (or it was reset due to parameter changes) get one and allocate memory for everything
|
||||||
|
if (!fastspectrum->channel_data_initialized) {
|
||||||
|
GST_DEBUG_OBJECT(fastspectrum, "allocating for bands %u", bands);
|
||||||
|
|
||||||
|
gst_strawberry_fastspectrum_alloc_channel_data(fastspectrum);
|
||||||
|
|
||||||
|
// Number of sample frames we process before posting a message interval is in ns
|
||||||
|
fastspectrum->frames_per_interval = gst_util_uint64_scale(fastspectrum->interval, rate, GST_SECOND);
|
||||||
|
fastspectrum->frames_todo = fastspectrum->frames_per_interval;
|
||||||
|
// Rounding error for frames_per_interval in ns, aggregated it in accumulated_error
|
||||||
|
fastspectrum->error_per_interval = (fastspectrum->interval * rate) % GST_SECOND;
|
||||||
|
if (fastspectrum->frames_per_interval == 0) {
|
||||||
|
fastspectrum->frames_per_interval = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_INFO_OBJECT(fastspectrum, "interval %" GST_TIME_FORMAT ", fpi %" G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT, GST_TIME_ARGS(fastspectrum->interval), fastspectrum->frames_per_interval, GST_TIME_ARGS(fastspectrum->error_per_interval));
|
||||||
|
|
||||||
|
fastspectrum->input_pos = 0;
|
||||||
|
|
||||||
|
gst_strawberry_fastspectrum_flush(fastspectrum);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fastspectrum->num_frames == 0) {
|
||||||
|
fastspectrum->message_ts = GST_BUFFER_TIMESTAMP(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
guint input_pos = fastspectrum->input_pos;
|
||||||
|
GstStrawberryFastSpectrumInputData input_data = fastspectrum->input_data;
|
||||||
|
|
||||||
|
while (size >= bpf) {
|
||||||
|
// Run input_data for a chunk of data
|
||||||
|
guint fft_todo = nfft - (fastspectrum->num_frames % nfft);
|
||||||
|
guint msg_todo = fastspectrum->frames_todo - fastspectrum->num_frames;
|
||||||
|
GST_LOG_OBJECT(fastspectrum, "message frames todo: %u, fft frames todo: %u, input frames %" G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
|
||||||
|
guint block_size = msg_todo;
|
||||||
|
if (block_size > (size / bpf)) {
|
||||||
|
block_size = (size / bpf);
|
||||||
|
}
|
||||||
|
if (block_size > fft_todo) {
|
||||||
|
block_size = fft_todo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the current frames into our ringbuffers
|
||||||
|
input_data(data, fastspectrum->input_ring_buffer, block_size, max_value, input_pos, nfft);
|
||||||
|
|
||||||
|
data += block_size * bpf;
|
||||||
|
size -= block_size * bpf;
|
||||||
|
input_pos = (input_pos + block_size) % nfft;
|
||||||
|
fastspectrum->num_frames += block_size;
|
||||||
|
|
||||||
|
gboolean have_full_interval = (fastspectrum->num_frames == fastspectrum->frames_todo);
|
||||||
|
|
||||||
|
GST_LOG_OBJECT(fastspectrum, "size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size, (fastspectrum->num_frames % nfft == 0), have_full_interval);
|
||||||
|
|
||||||
|
// If we have enough frames for an FFT or we have all frames required for the interval and we haven't run a FFT, then run an FFT
|
||||||
|
if ((fastspectrum->num_frames % nfft == 0) || (have_full_interval && !fastspectrum->num_fft)) {
|
||||||
|
gst_strawberry_fastspectrum_run_fft(fastspectrum, input_pos);
|
||||||
|
fastspectrum->num_fft++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have the FFTs for one interval?
|
||||||
|
if (have_full_interval) {
|
||||||
|
GST_DEBUG_OBJECT(fastspectrum, "nfft: %u frames: %" G_GUINT64_FORMAT " fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft, fastspectrum->num_frames, fastspectrum->frames_per_interval, GST_TIME_ARGS(fastspectrum->accumulated_error));
|
||||||
|
|
||||||
|
fastspectrum->frames_todo = fastspectrum->frames_per_interval;
|
||||||
|
if (fastspectrum->accumulated_error >= GST_SECOND) {
|
||||||
|
fastspectrum->accumulated_error -= GST_SECOND;
|
||||||
|
fastspectrum->frames_todo++;
|
||||||
|
}
|
||||||
|
fastspectrum->accumulated_error += fastspectrum->error_per_interval;
|
||||||
|
|
||||||
|
if (fastspectrum->output_callback) {
|
||||||
|
// Calculate average
|
||||||
|
for (guint i = 0; i < fastspectrum->bands; i++) {
|
||||||
|
fastspectrum->spect_magnitude[i] /= static_cast<double>(fastspectrum->num_fft);
|
||||||
|
}
|
||||||
|
|
||||||
|
fastspectrum->output_callback(fastspectrum->spect_magnitude, static_cast<int>(fastspectrum->bands));
|
||||||
|
|
||||||
|
// Reset spectrum accumulators
|
||||||
|
memset(fastspectrum->spect_magnitude, 0, fastspectrum->bands * sizeof(double));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GST_CLOCK_TIME_IS_VALID(fastspectrum->message_ts)) {
|
||||||
|
fastspectrum->message_ts += gst_util_uint64_scale(fastspectrum->num_frames, GST_SECOND, rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
fastspectrum->num_frames = 0;
|
||||||
|
fastspectrum->num_fft = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fastspectrum->input_pos = input_pos;
|
||||||
|
|
||||||
|
gst_buffer_unmap(buffer, &map);
|
||||||
|
g_mutex_unlock(&fastspectrum->lock);
|
||||||
|
|
||||||
|
g_assert(size == 0);
|
||||||
|
|
||||||
|
return GST_FLOW_OK;
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
/* GStreamer
|
/* GStreamer
|
||||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
||||||
* Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
* Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||||
|
* Copyright (C) <2018-2024> Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* This library is free software; you can redistribute it and/or
|
* This library is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU Library General Public
|
* modify it under the terms of the GNU Library General Public
|
||||||
|
@ -25,9 +26,8 @@
|
||||||
// - Send output via a callback instead of GST messages (less overhead).
|
// - Send output via a callback instead of GST messages (less overhead).
|
||||||
// - Removed all properties except interval and band.
|
// - Removed all properties except interval and band.
|
||||||
|
|
||||||
|
#ifndef GST_STRAWBERRY_FASTSPECTRUM_H
|
||||||
#ifndef GST_MOODBAR_FASTSPECTRUM_H
|
#define GST_STRAWBERRY_FASTSPECTRUM_H
|
||||||
#define GST_MOODBAR_FASTSPECTRUM_H
|
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
@ -37,19 +37,17 @@
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
#define GST_TYPE_FASTSPECTRUM (gst_fastspectrum_get_type())
|
#define GST_TYPE_STRAWBERRY_FASTSPECTRUM (gst_strawberry_fastspectrum_get_type())
|
||||||
#define GST_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_FASTSPECTRUM, GstFastSpectrum))
|
#define GST_STRAWBERRY_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_FASTSPECTRUM, GstStrawberryFastSpectrum))
|
||||||
#define GST_IS_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_FASTSPECTRUM))
|
#define GST_IS_STRAWBERRY_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_FASTSPECTRUM))
|
||||||
#define GST_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM, GstFastSpectrumClass))
|
#define GST_STRAWBERRY_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM, GstStrawberryFastSpectrumClass))
|
||||||
#define GST_IS_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FASTSPECTRUM))
|
#define GST_IS_STRAWBERRY_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FASTSPECTRUM))
|
||||||
|
|
||||||
class QMutex;
|
typedef void (*GstStrawberryFastSpectrumInputData)(const guint8 *in, double *out, guint len, double max_value, guint op, guint nfft);
|
||||||
|
|
||||||
typedef void (*GstFastSpectrumInputData)(const guint8 *in, double *out, guint len, double max_value, guint op, guint nfft);
|
using GstStrawberryFastSpectrumOutputCallback = std::function<void(double *magnitudes, int size)>;
|
||||||
|
|
||||||
using OutputCallback = std::function<void(double *magnitudes, int size)>;
|
struct GstStrawberryFastSpectrum {
|
||||||
|
|
||||||
struct GstFastSpectrum {
|
|
||||||
GstAudioFilter parent;
|
GstAudioFilter parent;
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
@ -77,20 +75,17 @@ struct GstFastSpectrum {
|
||||||
|
|
||||||
GMutex lock;
|
GMutex lock;
|
||||||
|
|
||||||
GstFastSpectrumInputData input_data;
|
GstStrawberryFastSpectrumInputData input_data;
|
||||||
|
GstStrawberryFastSpectrumOutputCallback output_callback;
|
||||||
OutputCallback output_callback;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GstFastSpectrumClass {
|
struct GstStrawberryFastSpectrumClass {
|
||||||
GstAudioFilterClass parent_class;
|
GstAudioFilterClass parent_class;
|
||||||
|
GMutex fftw_lock;
|
||||||
// Static lock for creating & destroying FFTW plans.
|
|
||||||
QMutex *fftw_lock;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GType gst_fastspectrum_get_type(void);
|
GType gst_strawberry_fastspectrum_get_type(void);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif // GST_MOODBAR_FASTSPECTRUM_H
|
#endif // GST_STRAWBERRY_FASTSPECTRUM_H
|
|
@ -471,7 +471,7 @@ add_subdirectory(ext/libstrawberry-common)
|
||||||
add_subdirectory(ext/libstrawberry-tagreader)
|
add_subdirectory(ext/libstrawberry-tagreader)
|
||||||
add_subdirectory(ext/strawberry-tagreader)
|
add_subdirectory(ext/strawberry-tagreader)
|
||||||
if(HAVE_MOODBAR)
|
if(HAVE_MOODBAR)
|
||||||
add_subdirectory(ext/gstmoodbar)
|
add_subdirectory(3rdparty/gstfastspectrum)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(GTest_FOUND AND GMOCK_LIBRARY AND Qt${QT_VERSION_MAJOR}Test_FOUND)
|
if(GTest_FOUND AND GMOCK_LIBRARY AND Qt${QT_VERSION_MAJOR}Test_FOUND)
|
||||||
|
|
|
@ -1,520 +0,0 @@
|
||||||
/* GStreamer
|
|
||||||
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
|
|
||||||
* <2006,2011> Stefan Kost <ensonic@users.sf.net>
|
|
||||||
* <2007-2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Library General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Library General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Library General Public
|
|
||||||
* License along with this library; if not, write to the
|
|
||||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
||||||
* Boston, MA 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
|
|
||||||
#include <gst/gst.h>
|
|
||||||
#include <gst/audio/gstaudiofilter.h>
|
|
||||||
|
|
||||||
#include <QMutex>
|
|
||||||
|
|
||||||
#include "gstfastspectrum.h"
|
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_STATIC(gst_fastspectrum_debug);
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// Spectrum properties
|
|
||||||
constexpr auto DEFAULT_INTERVAL = (GST_SECOND / 10);
|
|
||||||
constexpr auto DEFAULT_BANDS = 128;
|
|
||||||
|
|
||||||
enum {
|
|
||||||
PROP_0,
|
|
||||||
PROP_INTERVAL,
|
|
||||||
PROP_BANDS
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
#define gst_fastspectrum_parent_class parent_class
|
|
||||||
#ifdef __GNUC__
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
|
||||||
#endif
|
|
||||||
G_DEFINE_TYPE(GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
|
||||||
#ifdef __GNUC__
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void gst_fastspectrum_finalize(GObject *object);
|
|
||||||
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
|
|
||||||
static void gst_fastspectrum_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
|
|
||||||
static gboolean gst_fastspectrum_start(GstBaseTransform *trans);
|
|
||||||
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans);
|
|
||||||
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer);
|
|
||||||
static gboolean gst_fastspectrum_setup(GstAudioFilter *base, const GstAudioInfo *info);
|
|
||||||
|
|
||||||
static void gst_fastspectrum_class_init(GstFastSpectrumClass *klass) {
|
|
||||||
|
|
||||||
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
|
||||||
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
|
|
||||||
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS(klass);
|
|
||||||
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS(klass);
|
|
||||||
GstCaps *caps = nullptr;
|
|
||||||
|
|
||||||
gobject_class->set_property = gst_fastspectrum_set_property;
|
|
||||||
gobject_class->get_property = gst_fastspectrum_get_property;
|
|
||||||
gobject_class->finalize = gst_fastspectrum_finalize;
|
|
||||||
|
|
||||||
trans_class->start = GST_DEBUG_FUNCPTR(gst_fastspectrum_start);
|
|
||||||
trans_class->stop = GST_DEBUG_FUNCPTR(gst_fastspectrum_stop);
|
|
||||||
trans_class->transform_ip = GST_DEBUG_FUNCPTR(gst_fastspectrum_transform_ip);
|
|
||||||
trans_class->passthrough_on_same_caps = TRUE;
|
|
||||||
|
|
||||||
filter_class->setup = GST_DEBUG_FUNCPTR(gst_fastspectrum_setup);
|
|
||||||
|
|
||||||
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
|
||||||
|
|
||||||
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_INIT(gst_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
|
||||||
|
|
||||||
gst_element_class_set_static_metadata(element_class, "Spectrum analyzer",
|
|
||||||
"Filter/Analyzer/Audio",
|
|
||||||
"Run an FFT on the audio signal, output spectrum data",
|
|
||||||
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
|
||||||
"Stefan Kost <ensonic@users.sf.net>, "
|
|
||||||
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
|
|
||||||
|
|
||||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
||||||
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16LE, S24LE, S32LE, F32LE, F64LE }") ", layout = (string) interleaved, channels = 1");
|
|
||||||
#else
|
|
||||||
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16BE, S24BE, S32BE, F32BE, F64BE }") ", layout = (string) interleaved, channels = 1");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
gst_audio_filter_class_add_pad_templates(filter_class, caps);
|
|
||||||
gst_caps_unref(caps);
|
|
||||||
|
|
||||||
klass->fftw_lock = new QMutex;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gst_fastspectrum_init(GstFastSpectrum *spectrum) {
|
|
||||||
|
|
||||||
spectrum->interval = DEFAULT_INTERVAL;
|
|
||||||
spectrum->bands = DEFAULT_BANDS;
|
|
||||||
|
|
||||||
spectrum->channel_data_initialized = false;
|
|
||||||
|
|
||||||
g_mutex_init(&spectrum->lock);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gst_fastspectrum_alloc_channel_data(GstFastSpectrum *spectrum) {
|
|
||||||
|
|
||||||
guint bands = spectrum->bands;
|
|
||||||
guint nfft = 2 * bands - 2;
|
|
||||||
|
|
||||||
spectrum->input_ring_buffer = new double[nfft];
|
|
||||||
spectrum->fft_input = reinterpret_cast<double*>(fftw_malloc(sizeof(double) * nfft));
|
|
||||||
spectrum->fft_output = reinterpret_cast<fftw_complex*>(fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1)));
|
|
||||||
|
|
||||||
spectrum->spect_magnitude = new double[bands] {};
|
|
||||||
|
|
||||||
GstFastSpectrumClass *klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
|
||||||
{
|
|
||||||
QMutexLocker l(klass->fftw_lock);
|
|
||||||
spectrum->plan = fftw_plan_dft_r2c_1d(static_cast<int>(nfft), spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
|
|
||||||
}
|
|
||||||
spectrum->channel_data_initialized = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gst_fastspectrum_free_channel_data(GstFastSpectrum *spectrum) {
|
|
||||||
|
|
||||||
GstFastSpectrumClass *klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
|
||||||
if (spectrum->channel_data_initialized) {
|
|
||||||
{
|
|
||||||
QMutexLocker l(klass->fftw_lock);
|
|
||||||
fftw_destroy_plan(spectrum->plan);
|
|
||||||
}
|
|
||||||
fftw_free(spectrum->fft_input);
|
|
||||||
fftw_free(spectrum->fft_output);
|
|
||||||
delete[] spectrum->input_ring_buffer;
|
|
||||||
delete[] spectrum->spect_magnitude;
|
|
||||||
|
|
||||||
spectrum->channel_data_initialized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gst_fastspectrum_flush(GstFastSpectrum *spectrum) {
|
|
||||||
|
|
||||||
spectrum->num_frames = 0;
|
|
||||||
spectrum->num_fft = 0;
|
|
||||||
|
|
||||||
spectrum->accumulated_error = 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gst_fastspectrum_reset_state(GstFastSpectrum *spectrum) {
|
|
||||||
|
|
||||||
GST_DEBUG_OBJECT(spectrum, "resetting state");
|
|
||||||
|
|
||||||
gst_fastspectrum_free_channel_data(spectrum);
|
|
||||||
gst_fastspectrum_flush(spectrum);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gst_fastspectrum_finalize(GObject *object) {
|
|
||||||
|
|
||||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(object);
|
|
||||||
|
|
||||||
gst_fastspectrum_reset_state(spectrum);
|
|
||||||
g_mutex_clear(&spectrum->lock);
|
|
||||||
|
|
||||||
G_OBJECT_CLASS(parent_class)->finalize(object);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
|
|
||||||
|
|
||||||
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
|
|
||||||
|
|
||||||
switch (prop_id) {
|
|
||||||
case PROP_INTERVAL: {
|
|
||||||
guint64 interval = g_value_get_uint64(value);
|
|
||||||
g_mutex_lock(&filter->lock);
|
|
||||||
if (filter->interval != interval) {
|
|
||||||
filter->interval = interval;
|
|
||||||
gst_fastspectrum_reset_state(filter);
|
|
||||||
}
|
|
||||||
g_mutex_unlock(&filter->lock);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PROP_BANDS: {
|
|
||||||
guint bands = g_value_get_uint(value);
|
|
||||||
g_mutex_lock(&filter->lock);
|
|
||||||
if (filter->bands != bands) {
|
|
||||||
filter->bands = bands;
|
|
||||||
gst_fastspectrum_reset_state(filter);
|
|
||||||
}
|
|
||||||
g_mutex_unlock(&filter->lock);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gst_fastspectrum_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
|
|
||||||
|
|
||||||
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
|
|
||||||
|
|
||||||
switch (prop_id) {
|
|
||||||
case PROP_INTERVAL:
|
|
||||||
g_value_set_uint64(value, filter->interval);
|
|
||||||
break;
|
|
||||||
case PROP_BANDS:
|
|
||||||
g_value_set_uint(value, filter->bands);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean gst_fastspectrum_start(GstBaseTransform *trans) {
|
|
||||||
|
|
||||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
|
||||||
|
|
||||||
gst_fastspectrum_reset_state(spectrum);
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans) {
|
|
||||||
|
|
||||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
|
||||||
|
|
||||||
gst_fastspectrum_reset_state(spectrum);
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mixing data readers
|
|
||||||
|
|
||||||
static void input_data_mixed_float(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
|
||||||
|
|
||||||
Q_UNUSED(max_value);
|
|
||||||
|
|
||||||
const gfloat *in = reinterpret_cast<const gfloat*>(_in);
|
|
||||||
guint ip = 0;
|
|
||||||
|
|
||||||
for (guint j = 0; j < len; j++) {
|
|
||||||
out[op] = in[ip++];
|
|
||||||
op = (op + 1) % nfft;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void input_data_mixed_double(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
|
||||||
|
|
||||||
Q_UNUSED(max_value);
|
|
||||||
|
|
||||||
const gdouble *in = reinterpret_cast<const gdouble*>(_in);
|
|
||||||
guint ip = 0;
|
|
||||||
|
|
||||||
for (guint j = 0; j < len; j++) {
|
|
||||||
out[op] = in[ip++];
|
|
||||||
op = (op + 1) % nfft;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void input_data_mixed_int32_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
|
||||||
|
|
||||||
const gint32 *in = reinterpret_cast<const gint32*>(_in);
|
|
||||||
guint ip = 0;
|
|
||||||
|
|
||||||
for (guint j = 0; j < len; j++) {
|
|
||||||
out[op] = in[ip++] / max_value;
|
|
||||||
op = (op + 1) % nfft;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void input_data_mixed_int24_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
|
||||||
|
|
||||||
for (guint j = 0; j < len; j++) {
|
|
||||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
|
||||||
guint32 value = GST_READ_UINT24_BE(_in);
|
|
||||||
#else
|
|
||||||
guint32 value = GST_READ_UINT24_LE(_in);
|
|
||||||
#endif
|
|
||||||
if (value & 0x00800000) {
|
|
||||||
value |= 0xff000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
out[op] = value / max_value;
|
|
||||||
op = (op + 1) % nfft;
|
|
||||||
_in += 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void input_data_mixed_int16_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
|
||||||
|
|
||||||
const gint16 *in = reinterpret_cast<const gint16*>(_in);
|
|
||||||
guint ip = 0;
|
|
||||||
|
|
||||||
for (guint j = 0; j < len; j++) {
|
|
||||||
out[op] = in[ip++] / max_value;
|
|
||||||
op = (op + 1) % nfft;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean gst_fastspectrum_setup(GstAudioFilter *base, const GstAudioInfo *info) {
|
|
||||||
|
|
||||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(base);
|
|
||||||
GstFastSpectrumInputData input_data = nullptr;
|
|
||||||
|
|
||||||
g_mutex_lock(&spectrum->lock);
|
|
||||||
switch (GST_AUDIO_INFO_FORMAT(info)) {
|
|
||||||
case GST_AUDIO_FORMAT_S16:
|
|
||||||
input_data = input_data_mixed_int16_max;
|
|
||||||
break;
|
|
||||||
case GST_AUDIO_FORMAT_S24:
|
|
||||||
input_data = input_data_mixed_int24_max;
|
|
||||||
break;
|
|
||||||
case GST_AUDIO_FORMAT_S32:
|
|
||||||
input_data = input_data_mixed_int32_max;
|
|
||||||
break;
|
|
||||||
case GST_AUDIO_FORMAT_F32:
|
|
||||||
input_data = input_data_mixed_float;
|
|
||||||
break;
|
|
||||||
case GST_AUDIO_FORMAT_F64:
|
|
||||||
input_data = input_data_mixed_double;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
g_assert_not_reached();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
spectrum->input_data = input_data;
|
|
||||||
|
|
||||||
gst_fastspectrum_reset_state(spectrum);
|
|
||||||
g_mutex_unlock(&spectrum->lock);
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void gst_fastspectrum_run_fft(GstFastSpectrum *spectrum, guint input_pos) {
|
|
||||||
|
|
||||||
guint bands = spectrum->bands;
|
|
||||||
guint nfft = 2 * bands - 2;
|
|
||||||
|
|
||||||
for (guint i = 0; i < nfft; i++) {
|
|
||||||
spectrum->fft_input[i] = spectrum->input_ring_buffer[(input_pos + i) % nfft];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be safe to execute the same plan multiple times in parallel.
|
|
||||||
fftw_execute(spectrum->plan);
|
|
||||||
|
|
||||||
// Calculate magnitude in db
|
|
||||||
for (guint i = 0; i < bands; i++) {
|
|
||||||
gdouble val = spectrum->fft_output[i][0] * spectrum->fft_output[i][0];
|
|
||||||
val += spectrum->fft_output[i][1] * spectrum->fft_output[i][1];
|
|
||||||
val /= nfft * nfft;
|
|
||||||
spectrum->spect_magnitude[i] += val;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer) {
|
|
||||||
|
|
||||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
|
||||||
guint rate = GST_AUDIO_FILTER_RATE(spectrum);
|
|
||||||
guint bps = GST_AUDIO_FILTER_BPS(spectrum);
|
|
||||||
guint bpf = GST_AUDIO_FILTER_BPF(spectrum);
|
|
||||||
double max_value = static_cast<double>((1UL << ((bps << 3) - 1)) - 1);
|
|
||||||
guint bands = spectrum->bands;
|
|
||||||
guint nfft = 2 * bands - 2;
|
|
||||||
guint input_pos = 0;
|
|
||||||
GstMapInfo map;
|
|
||||||
const guint8 *data = nullptr;
|
|
||||||
gsize size = 0;
|
|
||||||
GstFastSpectrumInputData input_data = nullptr;
|
|
||||||
|
|
||||||
g_mutex_lock(&spectrum->lock);
|
|
||||||
gst_buffer_map(buffer, &map, GST_MAP_READ);
|
|
||||||
data = map.data;
|
|
||||||
size = map.size;
|
|
||||||
|
|
||||||
GST_LOG_OBJECT(spectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
|
|
||||||
|
|
||||||
if (GST_BUFFER_IS_DISCONT(buffer)) {
|
|
||||||
GST_DEBUG_OBJECT(spectrum, "Discontinuity detected -- flushing");
|
|
||||||
gst_fastspectrum_flush(spectrum);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have a FFT context yet (or it was reset due to parameter changes) get one and allocate memory for everything
|
|
||||||
if (!spectrum->channel_data_initialized) {
|
|
||||||
GST_DEBUG_OBJECT(spectrum, "allocating for bands %u", bands);
|
|
||||||
|
|
||||||
gst_fastspectrum_alloc_channel_data(spectrum);
|
|
||||||
|
|
||||||
// Number of sample frames we process before posting a message interval is in ns
|
|
||||||
spectrum->frames_per_interval = gst_util_uint64_scale(spectrum->interval, rate, GST_SECOND);
|
|
||||||
spectrum->frames_todo = spectrum->frames_per_interval;
|
|
||||||
// Rounding error for frames_per_interval in ns, aggregated it in accumulated_error
|
|
||||||
spectrum->error_per_interval = (spectrum->interval * rate) % GST_SECOND;
|
|
||||||
if (spectrum->frames_per_interval == 0) {
|
|
||||||
spectrum->frames_per_interval = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
GST_INFO_OBJECT(spectrum, "interval %" GST_TIME_FORMAT ", fpi %" G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT, GST_TIME_ARGS(spectrum->interval), spectrum->frames_per_interval, GST_TIME_ARGS(spectrum->error_per_interval));
|
|
||||||
|
|
||||||
spectrum->input_pos = 0;
|
|
||||||
|
|
||||||
gst_fastspectrum_flush(spectrum);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spectrum->num_frames == 0) {
|
|
||||||
spectrum->message_ts = GST_BUFFER_TIMESTAMP(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
input_pos = spectrum->input_pos;
|
|
||||||
input_data = spectrum->input_data;
|
|
||||||
|
|
||||||
while (size >= bpf) {
|
|
||||||
// Run input_data for a chunk of data
|
|
||||||
guint fft_todo = nfft - (spectrum->num_frames % nfft);
|
|
||||||
guint msg_todo = spectrum->frames_todo - spectrum->num_frames;
|
|
||||||
GST_LOG_OBJECT(spectrum, "message frames todo: %u, fft frames todo: %u, input frames %" G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
|
|
||||||
guint block_size = msg_todo;
|
|
||||||
if (block_size > (size / bpf)) {
|
|
||||||
block_size = (size / bpf);
|
|
||||||
}
|
|
||||||
if (block_size > fft_todo) {
|
|
||||||
block_size = fft_todo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the current frames into our ringbuffers
|
|
||||||
input_data(data, spectrum->input_ring_buffer, block_size, max_value, input_pos, nfft);
|
|
||||||
|
|
||||||
data += block_size * bpf;
|
|
||||||
size -= block_size * bpf;
|
|
||||||
input_pos = (input_pos + block_size) % nfft;
|
|
||||||
spectrum->num_frames += block_size;
|
|
||||||
|
|
||||||
gboolean have_full_interval = (spectrum->num_frames == spectrum->frames_todo);
|
|
||||||
|
|
||||||
GST_LOG_OBJECT(spectrum, "size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size, (spectrum->num_frames % nfft == 0), have_full_interval);
|
|
||||||
|
|
||||||
// If we have enough frames for an FFT or we have all frames required for the interval and we haven't run a FFT, then run an FFT
|
|
||||||
if ((spectrum->num_frames % nfft == 0) || (have_full_interval && !spectrum->num_fft)) {
|
|
||||||
gst_fastspectrum_run_fft(spectrum, input_pos);
|
|
||||||
spectrum->num_fft++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we have the FFTs for one interval?
|
|
||||||
if (have_full_interval) {
|
|
||||||
GST_DEBUG_OBJECT(spectrum, "nfft: %u frames: %" G_GUINT64_FORMAT " fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft, spectrum->num_frames, spectrum->frames_per_interval, GST_TIME_ARGS(spectrum->accumulated_error));
|
|
||||||
|
|
||||||
spectrum->frames_todo = spectrum->frames_per_interval;
|
|
||||||
if (spectrum->accumulated_error >= GST_SECOND) {
|
|
||||||
spectrum->accumulated_error -= GST_SECOND;
|
|
||||||
spectrum->frames_todo++;
|
|
||||||
}
|
|
||||||
spectrum->accumulated_error += spectrum->error_per_interval;
|
|
||||||
|
|
||||||
if (spectrum->output_callback) {
|
|
||||||
// Calculate average
|
|
||||||
for (guint i = 0; i < spectrum->bands; i++) {
|
|
||||||
spectrum->spect_magnitude[i] /= static_cast<double>(spectrum->num_fft);
|
|
||||||
}
|
|
||||||
|
|
||||||
spectrum->output_callback(spectrum->spect_magnitude, static_cast<int>(spectrum->bands));
|
|
||||||
|
|
||||||
// Reset spectrum accumulators
|
|
||||||
memset(spectrum->spect_magnitude, 0, spectrum->bands * sizeof(double));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GST_CLOCK_TIME_IS_VALID(spectrum->message_ts)) {
|
|
||||||
spectrum->message_ts += gst_util_uint64_scale(spectrum->num_frames, GST_SECOND, rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
spectrum->num_frames = 0;
|
|
||||||
spectrum->num_fft = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spectrum->input_pos = input_pos;
|
|
||||||
|
|
||||||
gst_buffer_unmap(buffer, &map);
|
|
||||||
g_mutex_unlock(&spectrum->lock);
|
|
||||||
|
|
||||||
g_assert(size == 0);
|
|
||||||
|
|
||||||
return GST_FLOW_OK;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
/* This file was part of Clementine.
|
|
||||||
Copyright 2014, David Sansome <me@davidsansome.com>
|
|
||||||
|
|
||||||
Strawberry 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 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Strawberry is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <gst/gst.h>
|
|
||||||
|
|
||||||
#include "gstfastspectrum.h"
|
|
||||||
#include "gstmoodbarplugin.h"
|
|
||||||
|
|
||||||
static gboolean gst_moodbar_plugin_init(GstPlugin *plugin) {
|
|
||||||
|
|
||||||
if (!gst_element_register(plugin, "fastspectrum", GST_RANK_NONE, GST_TYPE_FASTSPECTRUM)) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int gstfastspectrum_register_static() {
|
|
||||||
|
|
||||||
return gst_plugin_register_static(
|
|
||||||
GST_VERSION_MAJOR,
|
|
||||||
GST_VERSION_MINOR,
|
|
||||||
"fastspectrum",
|
|
||||||
"Fast spectrum analyzer for generating Moodbars",
|
|
||||||
gst_moodbar_plugin_init,
|
|
||||||
"0.1",
|
|
||||||
"GPL",
|
|
||||||
"FastSpectrum",
|
|
||||||
"FastSpectrum",
|
|
||||||
"https://www.strawberrymusicplayer.org");
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
/* This file was part of Clementine.
|
|
||||||
Copyright 2014, David Sansome <me@davidsansome.com>
|
|
||||||
|
|
||||||
Strawberry 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 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Strawberry is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef GST_MOODBAR_PLUGIN_H
|
|
||||||
#define GST_MOODBAR_PLUGIN_H
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
int gstfastspectrum_register_static();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // GST_MOODBAR_PLUGIN_H
|
|
|
@ -988,6 +988,7 @@ optional_source(HAVE_MOODBAR
|
||||||
moodbar/moodbarpipeline.cpp
|
moodbar/moodbarpipeline.cpp
|
||||||
moodbar/moodbarproxystyle.cpp
|
moodbar/moodbarproxystyle.cpp
|
||||||
moodbar/moodbarrenderer.cpp
|
moodbar/moodbarrenderer.cpp
|
||||||
|
moodbar/gstfastspectrumplugin.cpp
|
||||||
settings/moodbarsettingspage.cpp
|
settings/moodbarsettingspage.cpp
|
||||||
HEADERS
|
HEADERS
|
||||||
moodbar/moodbarcontroller.h
|
moodbar/moodbarcontroller.h
|
||||||
|
@ -1154,7 +1155,8 @@ if(HAVE_GSTREAMER)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_MOODBAR)
|
if(HAVE_MOODBAR)
|
||||||
target_link_libraries(strawberry_lib PRIVATE gstmoodbar)
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${CMAKE_SOURCE_DIR}/3rdparty/gstfastspectrum)
|
||||||
|
target_link_libraries(strawberry_lib PRIVATE gstfastspectrum)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_VLC)
|
if(HAVE_VLC)
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
#include "utilities/envutils.h"
|
#include "utilities/envutils.h"
|
||||||
|
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
# include "ext/gstmoodbar/gstmoodbarplugin.h"
|
# include "moodbar/gstfastspectrumplugin.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "gststartup.h"
|
#include "gststartup.h"
|
||||||
|
@ -83,7 +83,7 @@ void GstStartup::InitializeGStreamer() {
|
||||||
gst_pb_utils_init();
|
gst_pb_utils_init();
|
||||||
|
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
gstfastspectrum_register_static();
|
gst_strawberry_fastspectrum_register_static();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry 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 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <gst/gst.h>
|
||||||
|
|
||||||
|
#include "gstfastspectrum.h"
|
||||||
|
#include "gstfastspectrumplugin.h"
|
||||||
|
|
||||||
|
static gboolean gst_strawberry_fastspectrum_plugin_init(GstPlugin *plugin) {
|
||||||
|
|
||||||
|
GstRegistry *reg = gst_registry_get();
|
||||||
|
if (reg) {
|
||||||
|
GstPluginFeature *fastspectrum = gst_registry_lookup_feature(reg, "fastspectrum");
|
||||||
|
if (fastspectrum) {
|
||||||
|
gst_object_unref(fastspectrum);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gst_element_register(plugin, "fastspectrum", GST_RANK_NONE, GST_TYPE_STRAWBERRY_FASTSPECTRUM);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int gst_strawberry_fastspectrum_register_static() {
|
||||||
|
|
||||||
|
return gst_plugin_register_static(
|
||||||
|
GST_VERSION_MAJOR,
|
||||||
|
GST_VERSION_MINOR,
|
||||||
|
"fastspectrum",
|
||||||
|
"Fast spectrum analyzer for generating Moodbars",
|
||||||
|
gst_strawberry_fastspectrum_plugin_init,
|
||||||
|
"0.1",
|
||||||
|
"GPL",
|
||||||
|
"FastSpectrum",
|
||||||
|
"gst-strawberry-fastspectrum",
|
||||||
|
"https://www.strawberrymusicplayer.org");
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry 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 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GST_STRAWBERRY_FASTSPECTRUM_PLUGIN_H
|
||||||
|
#define GST_STRAWBERRY_FASTSPECTRUM_PLUGIN_H
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
int gst_strawberry_fastspectrum_register_static();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // GST_STRAWBERRY_FASTSPECTRUM_PLUGIN_H
|
|
@ -37,7 +37,7 @@
|
||||||
#include "utilities/threadutils.h"
|
#include "utilities/threadutils.h"
|
||||||
#include "moodbar/moodbarbuilder.h"
|
#include "moodbar/moodbarbuilder.h"
|
||||||
|
|
||||||
#include "ext/gstmoodbar/gstfastspectrum.h"
|
#include "gstfastspectrum.h"
|
||||||
|
|
||||||
using namespace Qt::StringLiterals;
|
using namespace Qt::StringLiterals;
|
||||||
using std::make_unique;
|
using std::make_unique;
|
||||||
|
@ -122,7 +122,7 @@ void MoodbarPipeline::Start() {
|
||||||
g_object_set(decodebin, "uri", gst_url.constData(), nullptr);
|
g_object_set(decodebin, "uri", gst_url.constData(), nullptr);
|
||||||
g_object_set(spectrum, "bands", kBands, nullptr);
|
g_object_set(spectrum, "bands", kBands, nullptr);
|
||||||
|
|
||||||
GstFastSpectrum *fast_spectrum = reinterpret_cast<GstFastSpectrum*>(spectrum);
|
GstStrawberryFastSpectrum *fast_spectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(spectrum);
|
||||||
fast_spectrum->output_callback = [this](double *magnitudes, int size) { builder_->AddFrame(magnitudes, size); };
|
fast_spectrum->output_callback = [this](double *magnitudes, int size) { builder_->AddFrame(magnitudes, size); };
|
||||||
|
|
||||||
// Connect signals
|
// Connect signals
|
||||||
|
|
Loading…
Reference in New Issue