Move gstfastspectrum to 3rdparty

This commit is contained in:
Jonas Kvinge 2024-09-12 19:42:03 +02:00
parent 9ae0afaaf7
commit e9684cd1a1
13 changed files with 640 additions and 626 deletions

8
3rdparty/README.md vendored
View File

@ -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.

View File

@ -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
) )

View File

@ -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;
}

View File

@ -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

View File

@ -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)

View File

@ -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;
}

View File

@ -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");
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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");
}

View File

@ -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

View File

@ -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