Refactoring

This commit is contained in:
Jonas Kvinge
2024-10-22 18:12:33 +02:00
parent dfcf715291
commit 8da2b9cd94
623 changed files with 9071 additions and 5126 deletions

View File

@@ -31,14 +31,15 @@
#include <QSettings>
#include "utilities/envutils.h"
#include "utilities/timeconstants.h"
#include "core/networkproxyfactory.h"
#include "constants/timeconstants.h"
#include "core/logging.h"
#include "core/settings.h"
#include "core/networkproxyfactory.h"
#include "enginebase.h"
#include "settings/backendsettingspage.h"
#include "settings/networkproxysettingspage.h"
#include "constants/backendsettings.h"
#include "constants/networkproxysettings.h"
#ifdef HAVE_SPOTIFY
# include "settings/spotifysettingspage.h"
# include "constants/spotifysettings.h"
#endif
using namespace Qt::Literals::StringLiterals;
@@ -61,9 +62,9 @@ EngineBase::EngineBase(QObject *parent)
rg_compression_(true),
ebur128_loudness_normalization_(false),
ebur128_target_level_lufs_(-23.0),
buffer_duration_nanosec_(BackendSettingsPage::kDefaultBufferDuration * kNsecPerMsec),
buffer_low_watermark_(BackendSettingsPage::kDefaultBufferLowWatermark),
buffer_high_watermark_(BackendSettingsPage::kDefaultBufferHighWatermark),
buffer_duration_nanosec_(BackendSettings::kDefaultBufferDuration * kNsecPerMsec),
buffer_low_watermark_(BackendSettings::kDefaultBufferLowWatermark),
buffer_high_watermark_(BackendSettings::kDefaultBufferHighWatermark),
fadeout_enabled_(true),
crossfade_enabled_(true),
autocrossfade_enabled_(false),
@@ -167,59 +168,59 @@ void EngineBase::ReloadSettings() {
Settings s;
s.beginGroup(BackendSettingsPage::kSettingsGroup);
s.beginGroup(BackendSettings::kSettingsGroup);
output_ = s.value("output").toString();
device_ = s.value("device");
output_ = s.value(BackendSettings::kOutput).toString();
device_ = s.value(BackendSettings::kDevice);
exclusive_mode_ = s.value("exclusive_mode", false).toBool();
exclusive_mode_ = s.value(BackendSettings::kExclusiveMode, false).toBool();
volume_control_ = s.value("volume_control", true).toBool();
volume_control_ = s.value(BackendSettings::kVolumeControl, true).toBool();
channels_enabled_ = s.value("channels_enabled", false).toBool();
channels_ = s.value("channels", 0).toInt();
channels_enabled_ = s.value(BackendSettings::kChannelsEnabled, false).toBool();
channels_ = s.value(BackendSettings::kChannels, 0).toInt();
buffer_duration_nanosec_ = s.value("bufferduration", BackendSettingsPage::kDefaultBufferDuration).toLongLong() * kNsecPerMsec;
buffer_low_watermark_ = s.value("bufferlowwatermark", BackendSettingsPage::kDefaultBufferLowWatermark).toDouble();
buffer_high_watermark_ = s.value("bufferhighwatermark", BackendSettingsPage::kDefaultBufferHighWatermark).toDouble();
buffer_duration_nanosec_ = s.value(BackendSettings::kBufferDuration, BackendSettings::kDefaultBufferDuration).toLongLong() * kNsecPerMsec;
buffer_low_watermark_ = s.value(BackendSettings::kBufferLowWatermark, BackendSettings::kDefaultBufferLowWatermark).toDouble();
buffer_high_watermark_ = s.value(BackendSettings::kBufferHighWatermark, BackendSettings::kDefaultBufferHighWatermark).toDouble();
rg_enabled_ = s.value("rgenabled", false).toBool();
rg_mode_ = s.value("rgmode", 0).toInt();
rg_preamp_ = s.value("rgpreamp", 0.0).toDouble();
rg_fallbackgain_ = s.value("rgfallbackgain", 0.0).toDouble();
rg_compression_ = s.value("rgcompression", true).toBool();
rg_enabled_ = s.value(BackendSettings::kRgEnabled, false).toBool();
rg_mode_ = s.value(BackendSettings::kRgMode, 0).toInt();
rg_preamp_ = s.value(BackendSettings::kRgPreamp, 0.0).toDouble();
rg_fallbackgain_ = s.value(BackendSettings::kRgFallbackGain, 0.0).toDouble();
rg_compression_ = s.value(BackendSettings::kRgCompression, true).toBool();
ebur128_loudness_normalization_ = s.value("ebur128_loudness_normalization", false).toBool();
ebur128_target_level_lufs_ = s.value("ebur128_target_level_lufs", -23.0).toDouble();
ebur128_loudness_normalization_ = s.value(BackendSettings::kEBUR128LoudnessNormalization, false).toBool();
ebur128_target_level_lufs_ = s.value(BackendSettings::kEBUR128TargetLevelLUFS, -23.0).toDouble();
fadeout_enabled_ = s.value("FadeoutEnabled", false).toBool();
crossfade_enabled_ = s.value("CrossfadeEnabled", false).toBool();
autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool();
crossfade_same_album_ = !s.value("NoCrossfadeSameAlbum", true).toBool();
fadeout_pause_enabled_ = s.value("FadeoutPauseEnabled", false).toBool();
fadeout_duration_ = s.value("FadeoutDuration", 2000).toLongLong();
fadeout_enabled_ = s.value(BackendSettings::kFadeoutEnabled, false).toBool();
crossfade_enabled_ = s.value(BackendSettings::kCrossfadeEnabled, false).toBool();
autocrossfade_enabled_ = s.value(BackendSettings::kAutoCrossfadeEnabled, false).toBool();
crossfade_same_album_ = !s.value(BackendSettings::kNoCrossfadeSameAlbum, true).toBool();
fadeout_pause_enabled_ = s.value(BackendSettings::kFadeoutPauseEnabled, false).toBool();
fadeout_duration_ = s.value(BackendSettings::kFadeoutDuration, 2000).toLongLong();
fadeout_duration_nanosec_ = (fadeout_duration_ * kNsecPerMsec);
fadeout_pause_duration_ = s.value("FadeoutPauseDuration", 250).toLongLong();
fadeout_pause_duration_ = s.value(BackendSettings::kFadeoutPauseDuration, 250).toLongLong();
fadeout_pause_duration_nanosec_ = (fadeout_pause_duration_ * kNsecPerMsec);
bs2b_enabled_ = s.value("bs2b", false).toBool();
bs2b_enabled_ = s.value(BackendSettings::kBS2B, false).toBool();
bool http2_enabled = s.value("http2", false).toBool();
bool http2_enabled = s.value(BackendSettings::kHTTP2, false).toBool();
if (http2_enabled != http2_enabled_) {
http2_enabled_ = http2_enabled;
Utilities::SetEnv("SOUP_FORCE_HTTP1", http2_enabled_ ? ""_L1 : u"1"_s);
qLog(Debug) << "SOUP_FORCE_HTTP1:" << (http2_enabled_ ? "OFF" : "ON");
}
strict_ssl_enabled_ = s.value("strict_ssl", false).toBool();
strict_ssl_enabled_ = s.value(BackendSettings::kStrictSSL, false).toBool();
s.endGroup();
s.beginGroup(NetworkProxySettingsPage::kSettingsGroup);
s.beginGroup(NetworkProxySettings::kSettingsGroup);
const NetworkProxyFactory::Mode proxy_mode = static_cast<NetworkProxyFactory::Mode>(s.value("mode", static_cast<int>(NetworkProxyFactory::Mode::System)).toInt());
if (proxy_mode == NetworkProxyFactory::Mode::Manual && s.contains("engine") && s.value("engine").toBool()) {
QString proxy_host = s.value("hostname").toString();
int proxy_port = s.value("port").toInt();
if (proxy_mode == NetworkProxyFactory::Mode::Manual && s.contains(NetworkProxySettings::kEngine) && s.value(NetworkProxySettings::kEngine).toBool()) {
QString proxy_host = s.value(NetworkProxySettings::kHostname).toString();
int proxy_port = s.value(NetworkProxySettings::kPort).toInt();
if (proxy_host.isEmpty() || proxy_port <= 0) {
proxy_address_.clear();
proxy_authentication_ = false;
@@ -228,9 +229,9 @@ void EngineBase::ReloadSettings() {
}
else {
proxy_address_ = QStringLiteral("%1:%2").arg(proxy_host).arg(proxy_port);
proxy_authentication_ = s.value("use_authentication").toBool();
proxy_user_ = s.value("username").toString();
proxy_pass_ = s.value("password").toString();
proxy_authentication_ = s.value(NetworkProxySettings::kUseAuthentication).toBool();
proxy_user_ = s.value(NetworkProxySettings::kUsername).toString();
proxy_pass_ = s.value(NetworkProxySettings::kPassword).toString();
}
}
else {
@@ -243,8 +244,8 @@ void EngineBase::ReloadSettings() {
s.endGroup();
#ifdef HAVE_SPOTIFY
s.beginGroup(SpotifySettingsPage::kSettingsGroup);
spotify_access_token_ = s.value("access_token").toString();
s.beginGroup(SpotifySettings::kSettingsGroup);
spotify_access_token_ = s.value(SpotifySettings::kAccessToken).toString();
s.endGroup();
#endif

View File

@@ -39,7 +39,7 @@
#include <QUrl>
#include "devicefinders.h"
#include "enginemetadata.h"
#include "core/enginemetadata.h"
#include "core/song.h"
class EngineBase : public QObject {

View File

@@ -1,31 +0,0 @@
/*
* Strawberry Music Player
* Copyright 2018-2023, 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 "enginemetadata.h"
#include "core/song.h"
EngineMetadata::EngineMetadata()
: type(Type::Any),
length(-1),
year(-1),
track(-1),
filetype(Song::FileType::Unknown),
samplerate(-1),
bitdepth(-1),
bitrate(-1) {}

View File

@@ -1,56 +0,0 @@
/*
* Strawberry Music Player
* Copyright 2018-2023, 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 ENGINEMETADATA_H
#define ENGINEMETADATA_H
#include <QMetaType>
#include <QString>
#include <QUrl>
#include "core/song.h"
class EngineMetadata {
public:
EngineMetadata();
enum class Type {
Any,
Current,
Next
};
Type type;
QUrl media_url;
QUrl stream_url;
QString title;
QString artist;
QString album;
QString comment;
QString genre;
qint64 length;
int year;
int track;
Song::FileType filetype;
int samplerate;
int bitdepth;
int bitrate;
QString lyrics;
};
Q_DECLARE_METATYPE(EngineMetadata)
#endif // ENGINEMETADATA_H

View File

@@ -50,16 +50,16 @@
#include <QMetaObject>
#include <QTimerEvent>
#include "core/shared_ptr.h"
#include "includes/shared_ptr.h"
#include "core/logging.h"
#include "core/taskmanager.h"
#include "core/signalchecker.h"
#include "utilities/timeconstants.h"
#include "core/enginemetadata.h"
#include "constants/timeconstants.h"
#include "enginebase.h"
#include "gstengine.h"
#include "gstenginepipeline.h"
#include "gstbufferconsumer.h"
#include "enginemetadata.h"
using namespace Qt::Literals::StringLiterals;
using std::make_shared;

View File

@@ -39,7 +39,7 @@
#include <QString>
#include <QUrl>
#include "core/shared_ptr.h"
#include "includes/shared_ptr.h"
#include "enginebase.h"
#include "gststartup.h"
#include "gstenginepipeline.h"

View File

@@ -61,8 +61,8 @@
#include "core/logging.h"
#include "core/signalchecker.h"
#include "utilities/timeconstants.h"
#include "settings/backendsettingspage.h"
#include "constants/timeconstants.h"
#include "constants/backendsettings.h"
#include "gstengine.h"
#include "gstenginepipeline.h"
#include "gstbufferconsumer.h"
@@ -103,9 +103,9 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
volume_enabled_(true),
fading_enabled_(false),
strict_ssl_enabled_(false),
buffer_duration_nanosec_(BackendSettingsPage::kDefaultBufferDuration * kNsecPerMsec),
buffer_low_watermark_(BackendSettingsPage::kDefaultBufferLowWatermark),
buffer_high_watermark_(BackendSettingsPage::kDefaultBufferHighWatermark),
buffer_duration_nanosec_(BackendSettings::kDefaultBufferDuration * kNsecPerMsec),
buffer_low_watermark_(BackendSettings::kDefaultBufferLowWatermark),
buffer_high_watermark_(BackendSettings::kDefaultBufferHighWatermark),
proxy_authentication_(false),
channels_enabled_(false),
channels_(0),

View File

@@ -43,9 +43,9 @@
#include <QString>
#include <QUrl>
#include "core/shared_ptr.h"
#include "core/mutex_protected.h"
#include "enginemetadata.h"
#include "includes/shared_ptr.h"
#include "includes/mutex_protected.h"
#include "core/enginemetadata.h"
class QTimer;
class QTimerEvent;

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 guint64 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 (guint64 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 guint64 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 (guint64 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 guint64 len, const double max_value, guint op, const guint nfft) {
const gint32 *in = reinterpret_cast<const gint32*>(_in);
guint ip = 0;
for (guint64 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 guint64 len, const double max_value, guint op, const guint nfft) {
for (guint64 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 guint64 len, const double max_value, guint op, const guint nfft) {
const gint16 *in = reinterpret_cast<const gint16*>(_in);
guint ip = 0;
for (guint64 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 guint64 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
guint64 fft_todo = nfft - (fastspectrum->num_frames % nfft);
guint64 msg_todo = fastspectrum->frames_todo - fastspectrum->num_frames;
GST_LOG_OBJECT(fastspectrum, "message frames todo: %" G_GUINT64_FORMAT ", fft frames todo: %" G_GUINT64_FORMAT ", input frames %" G_GSIZE_FORMAT, msg_todo, fft_todo, static_cast<gsize>(size / bpf));
guint64 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

@@ -0,0 +1,91 @@
/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* 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
* 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.
*/
// Adapted from gstspectrum for Clementine with the following changes:
// - Uses fftw instead of kiss fft (2x faster).
// - Hardcoded to 1 channel (use an audioconvert element to do the work
// instead, simplifies this code a lot).
// - Send output via a callback instead of GST messages (less overhead).
// - Removed all properties except interval and band.
#ifndef GST_STRAWBERRY_FASTSPECTRUM_H
#define GST_STRAWBERRY_FASTSPECTRUM_H
#include <functional>
#include <gst/gst.h>
#include <gst/audio/gstaudiofilter.h>
#include <fftw3.h>
G_BEGIN_DECLS
#define GST_TYPE_STRAWBERRY_FASTSPECTRUM (gst_strawberry_fastspectrum_get_type())
#define GST_STRAWBERRY_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_FASTSPECTRUM, GstStrawberryFastSpectrum))
#define GST_IS_STRAWBERRY_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_FASTSPECTRUM))
#define GST_STRAWBERRY_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM, GstStrawberryFastSpectrumClass))
#define GST_IS_STRAWBERRY_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FASTSPECTRUM))
typedef void (*GstStrawberryFastSpectrumInputData)(const guint8 *in, double *out, guint64 len, double max_value, guint op, guint nfft);
using GstStrawberryFastSpectrumOutputCallback = std::function<void(double *magnitudes, int size)>;
struct GstStrawberryFastSpectrum {
GstAudioFilter parent;
// Properties
guint64 interval; // How many nanoseconds between emits
guint64 frames_per_interval; // How many frames per interval
guint64 frames_todo;
guint bands; // Number of spectrum bands
gboolean multi_channel; // Send separate channel results
guint64 num_frames; // Frame count (1 sample per channel) since last emit
guint64 num_fft; // Number of FFTs since last emit
GstClockTime message_ts; // Starttime for next message
// <private>
bool channel_data_initialized;
double *input_ring_buffer;
double *fft_input;
fftw_complex *fft_output;
double *spect_magnitude;
fftw_plan plan;
guint input_pos;
guint64 error_per_interval;
guint64 accumulated_error;
GMutex lock;
GstStrawberryFastSpectrumInputData input_data;
GstStrawberryFastSpectrumOutputCallback output_callback;
};
struct GstStrawberryFastSpectrumClass {
GstAudioFilterClass parent_class;
GMutex fftw_lock;
};
GType gst_strawberry_fastspectrum_get_type(void);
G_END_DECLS
#endif // GST_STRAWBERRY_FASTSPECTRUM_H

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, "strawberry-fastspectrum");
if (fastspectrum) {
gst_object_unref(fastspectrum);
return TRUE;
}
}
return gst_element_register(plugin, "strawberry-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,
"strawberry-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

@@ -39,7 +39,7 @@
#include "utilities/envutils.h"
#ifdef HAVE_MOODBAR
# include "moodbar/gstfastspectrumplugin.h"
# include "gstfastspectrumplugin.h"
#endif
#include "gststartup.h"

View File

@@ -27,8 +27,8 @@
#include <QString>
#include "includes/scoped_cftyperef.h"
#include "core/logging.h"
#include "core/scoped_cftyperef.h"
#include "macosdevicefinder.h"
#include "enginedevice.h"