aboutsummaryrefslogtreecommitdiffstats
path: root/src/filter/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/filter/plugins')
-rw-r--r--src/filter/plugins/AutoConvertFilterPlugin.cxx131
-rw-r--r--src/filter/plugins/AutoConvertFilterPlugin.hxx34
-rw-r--r--src/filter/plugins/ChainFilterPlugin.cxx181
-rw-r--r--src/filter/plugins/ChainFilterPlugin.hxx48
-rw-r--r--src/filter/plugins/ConvertFilterPlugin.cxx149
-rw-r--r--src/filter/plugins/ConvertFilterPlugin.hxx37
-rw-r--r--src/filter/plugins/NormalizeFilterPlugin.cxx82
-rw-r--r--src/filter/plugins/NullFilterPlugin.cxx61
-rw-r--r--src/filter/plugins/ReplayGainFilterPlugin.cxx210
-rw-r--r--src/filter/plugins/ReplayGainFilterPlugin.hxx52
-rw-r--r--src/filter/plugins/RouteFilterPlugin.cxx296
-rw-r--r--src/filter/plugins/VolumeFilterPlugin.cxx106
-rw-r--r--src/filter/plugins/VolumeFilterPlugin.hxx31
13 files changed, 1418 insertions, 0 deletions
diff --git a/src/filter/plugins/AutoConvertFilterPlugin.cxx b/src/filter/plugins/AutoConvertFilterPlugin.cxx
new file mode 100644
index 000000000..cdeeefdc6
--- /dev/null
+++ b/src/filter/plugins/AutoConvertFilterPlugin.cxx
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AutoConvertFilterPlugin.hxx"
+#include "ConvertFilterPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "config/ConfigData.hxx"
+
+#include <assert.h>
+
+class AutoConvertFilter final : public Filter {
+ /**
+ * The underlying filter.
+ */
+ Filter *filter;
+
+ /**
+ * A convert_filter, just in case conversion is needed. nullptr
+ * if unused.
+ */
+ Filter *convert;
+
+public:
+ AutoConvertFilter(Filter *_filter):filter(_filter) {}
+ ~AutoConvertFilter() {
+ delete filter;
+ }
+
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close() override;
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r,
+ Error &error) override;
+};
+
+AudioFormat
+AutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error)
+{
+ assert(in_audio_format.IsValid());
+
+ /* open the "real" filter */
+
+ AudioFormat child_audio_format = in_audio_format;
+ AudioFormat out_audio_format = filter->Open(child_audio_format, error);
+ if (!out_audio_format.IsDefined())
+ return out_audio_format;
+
+ /* need to convert? */
+
+ if (in_audio_format != child_audio_format) {
+ /* yes - create a convert_filter */
+
+ const config_param empty;
+ convert = filter_new(&convert_filter_plugin, empty, error);
+ if (convert == nullptr) {
+ filter->Close();
+ return AudioFormat::Undefined();
+ }
+
+ AudioFormat audio_format2 = in_audio_format;
+ AudioFormat audio_format3 =
+ convert->Open(audio_format2, error);
+ if (!audio_format3.IsDefined()) {
+ delete convert;
+ filter->Close();
+ return AudioFormat::Undefined();
+ }
+
+ assert(audio_format2 == in_audio_format);
+
+ if (!convert_filter_set(convert, child_audio_format, error)) {
+ delete convert;
+ filter->Close();
+ return AudioFormat::Undefined();
+ }
+ } else
+ /* no */
+ convert = nullptr;
+
+ return out_audio_format;
+}
+
+void
+AutoConvertFilter::Close()
+{
+ if (convert != nullptr) {
+ convert->Close();
+ delete convert;
+ }
+
+ filter->Close();
+}
+
+const void *
+AutoConvertFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error)
+{
+ if (convert != nullptr) {
+ src = convert->FilterPCM(src, src_size, &src_size, error);
+ if (src == nullptr)
+ return nullptr;
+ }
+
+ return filter->FilterPCM(src, src_size, dest_size_r, error);
+}
+
+Filter *
+autoconvert_filter_new(Filter *filter)
+{
+ return new AutoConvertFilter(filter);
+}
diff --git a/src/filter/plugins/AutoConvertFilterPlugin.hxx b/src/filter/plugins/AutoConvertFilterPlugin.hxx
new file mode 100644
index 000000000..c5dfdd2f6
--- /dev/null
+++ b/src/filter/plugins/AutoConvertFilterPlugin.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
+#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
+
+class Filter;
+
+/**
+ * Creates a new "autoconvert" filter. When opened, it ensures that
+ * the input audio format isn't changed. If the underlying filter
+ * requests a different format, it automatically creates a
+ * convert_filter.
+ */
+Filter *
+autoconvert_filter_new(Filter *filter);
+
+#endif
diff --git a/src/filter/plugins/ChainFilterPlugin.cxx b/src/filter/plugins/ChainFilterPlugin.cxx
new file mode 100644
index 000000000..7dc6db667
--- /dev/null
+++ b/src/filter/plugins/ChainFilterPlugin.cxx
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ChainFilterPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <list>
+
+#include <assert.h>
+
+class ChainFilter final : public Filter {
+ struct Child {
+ const char *name;
+ Filter *filter;
+
+ Child(const char *_name, Filter *_filter)
+ :name(_name), filter(_filter) {}
+ ~Child() {
+ delete filter;
+ }
+
+ Child(const Child &) = delete;
+ Child &operator=(const Child &) = delete;
+ };
+
+ std::list<Child> children;
+
+public:
+ void Append(const char *name, Filter *filter) {
+ children.emplace_back(name, filter);
+ }
+
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+
+private:
+ /**
+ * Close all filters in the chain until #until is reached.
+ * #until itself is not closed.
+ */
+ void CloseUntil(const Filter *until);
+};
+
+static constexpr Domain chain_filter_domain("chain_filter");
+
+static Filter *
+chain_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new ChainFilter();
+}
+
+void
+ChainFilter::CloseUntil(const Filter *until)
+{
+ for (auto &child : children) {
+ if (child.filter == until)
+ /* don't close this filter */
+ return;
+
+ /* close this filter */
+ child.filter->Close();
+ }
+
+ /* this assertion fails if #until does not exist (anymore) */
+ assert(false);
+ gcc_unreachable();
+}
+
+static AudioFormat
+chain_open_child(const char *name, Filter *filter,
+ const AudioFormat &prev_audio_format,
+ Error &error)
+{
+ AudioFormat conv_audio_format = prev_audio_format;
+ const AudioFormat next_audio_format =
+ filter->Open(conv_audio_format, error);
+ if (!next_audio_format.IsDefined())
+ return next_audio_format;
+
+ if (conv_audio_format != prev_audio_format) {
+ struct audio_format_string s;
+
+ filter->Close();
+
+ error.Format(chain_filter_domain,
+ "Audio format not supported by filter '%s': %s",
+ name,
+ audio_format_to_string(prev_audio_format, &s));
+ return AudioFormat::Undefined();
+ }
+
+ return next_audio_format;
+}
+
+AudioFormat
+ChainFilter::Open(AudioFormat &in_audio_format, Error &error)
+{
+ AudioFormat audio_format = in_audio_format;
+
+ for (auto &child : children) {
+ audio_format = chain_open_child(child.name, child.filter,
+ audio_format, error);
+ if (!audio_format.IsDefined()) {
+ /* rollback, close all children */
+ CloseUntil(child.filter);
+ break;
+ }
+ }
+
+ /* return the output format of the last filter */
+ return audio_format;
+}
+
+void
+ChainFilter::Close()
+{
+ for (auto &child : children)
+ child.filter->Close();
+}
+
+const void *
+ChainFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error)
+{
+ for (auto &child : children) {
+ /* feed the output of the previous filter as input
+ into the current one */
+ src = child.filter->FilterPCM(src, src_size, &src_size,
+ error);
+ if (src == nullptr)
+ return nullptr;
+ }
+
+ /* return the output of the last filter */
+ *dest_size_r = src_size;
+ return src;
+}
+
+const struct filter_plugin chain_filter_plugin = {
+ "chain",
+ chain_filter_init,
+};
+
+Filter *
+filter_chain_new(void)
+{
+ return new ChainFilter();
+}
+
+void
+filter_chain_append(Filter &_chain, const char *name, Filter *filter)
+{
+ ChainFilter &chain = (ChainFilter &)_chain;
+
+ chain.Append(name, filter);
+}
diff --git a/src/filter/plugins/ChainFilterPlugin.hxx b/src/filter/plugins/ChainFilterPlugin.hxx
new file mode 100644
index 000000000..b36aa3322
--- /dev/null
+++ b/src/filter/plugins/ChainFilterPlugin.hxx
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * A filter chain is a container for several filters. They are
+ * chained together, i.e. called in a row, one filter passing its
+ * output to the next one.
+ */
+
+#ifndef MPD_FILTER_CHAIN_HXX
+#define MPD_FILTER_CHAIN_HXX
+
+class Filter;
+
+/**
+ * Creates a new filter chain.
+ */
+Filter *
+filter_chain_new(void);
+
+/**
+ * Appends a new filter at the end of the filter chain. You must call
+ * this function before the first filter_open() call.
+ *
+ * @param chain the filter chain created with filter_chain_new()
+ * @param filter the filter to be appended to #chain
+ */
+void
+filter_chain_append(Filter &chain, const char *name, Filter *filter);
+
+#endif
diff --git a/src/filter/plugins/ConvertFilterPlugin.cxx b/src/filter/plugins/ConvertFilterPlugin.cxx
new file mode 100644
index 000000000..27e6774f8
--- /dev/null
+++ b/src/filter/plugins/ConvertFilterPlugin.cxx
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ConvertFilterPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/PcmConvert.hxx"
+#include "util/Manual.hxx"
+#include "AudioFormat.hxx"
+#include "poison.h"
+
+#include <assert.h>
+
+class ConvertFilter final : public Filter {
+ /**
+ * The input audio format; PCM data is passed to the filter()
+ * method in this format.
+ */
+ AudioFormat in_audio_format;
+
+ /**
+ * The output audio format; the consumer of this plugin
+ * expects PCM data in this format.
+ *
+ * If this is AudioFormat::Undefined(), then the #PcmConvert
+ * attribute is not open. This can mean that Set() has failed
+ * or that no conversion is necessary.
+ */
+ AudioFormat out_audio_format;
+
+ Manual<PcmConvert> state;
+
+public:
+ bool Set(const AudioFormat &_out_audio_format, Error &error);
+
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close() override;
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r,
+ Error &error) override;
+};
+
+static Filter *
+convert_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new ConvertFilter();
+}
+
+bool
+ConvertFilter::Set(const AudioFormat &_out_audio_format, Error &error)
+{
+ assert(in_audio_format.IsValid());
+ assert(_out_audio_format.IsValid());
+
+ if (_out_audio_format == out_audio_format)
+ /* no change */
+ return true;
+
+ if (out_audio_format.IsValid()) {
+ out_audio_format.Clear();
+ state->Close();
+ }
+
+ if (_out_audio_format == in_audio_format)
+ /* optimized special case: no-op */
+ return true;
+
+ if (!state->Open(in_audio_format, _out_audio_format, error))
+ return false;
+
+ out_audio_format = _out_audio_format;
+ return true;
+}
+
+AudioFormat
+ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+{
+ assert(audio_format.IsValid());
+
+ in_audio_format = audio_format;
+ out_audio_format.Clear();
+
+ state.Construct();
+
+ return in_audio_format;
+}
+
+void
+ConvertFilter::Close()
+{
+ assert(in_audio_format.IsValid());
+
+ if (out_audio_format.IsValid())
+ state->Close();
+
+ state.Destruct();
+
+ poison_undefined(&in_audio_format, sizeof(in_audio_format));
+ poison_undefined(&out_audio_format, sizeof(out_audio_format));
+}
+
+const void *
+ConvertFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error)
+{
+ assert(in_audio_format.IsValid());
+
+ if (!out_audio_format.IsValid()) {
+ /* optimized special case: no-op */
+ *dest_size_r = src_size;
+ return src;
+ }
+
+ return state->Convert(src, src_size, dest_size_r,
+ error);
+}
+
+const struct filter_plugin convert_filter_plugin = {
+ "convert",
+ convert_filter_init,
+};
+
+bool
+convert_filter_set(Filter *_filter, AudioFormat out_audio_format,
+ Error &error)
+{
+ ConvertFilter *filter = (ConvertFilter *)_filter;
+
+ return filter->Set(out_audio_format, error);
+}
diff --git a/src/filter/plugins/ConvertFilterPlugin.hxx b/src/filter/plugins/ConvertFilterPlugin.hxx
new file mode 100644
index 000000000..bb4673651
--- /dev/null
+++ b/src/filter/plugins/ConvertFilterPlugin.hxx
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_CONVERT_FILTER_PLUGIN_HXX
+#define MPD_CONVERT_FILTER_PLUGIN_HXX
+
+class Filter;
+class Error;
+struct AudioFormat;
+
+/**
+ * Sets the output audio format for the specified filter. You must
+ * call this after the filter has been opened. Since this audio
+ * format switch is a violation of the filter API, this filter must be
+ * the last in a chain.
+ */
+bool
+convert_filter_set(Filter *filter, AudioFormat out_audio_format,
+ Error &error);
+
+#endif
diff --git a/src/filter/plugins/NormalizeFilterPlugin.cxx b/src/filter/plugins/NormalizeFilterPlugin.cxx
new file mode 100644
index 000000000..58eb0c6a2
--- /dev/null
+++ b/src/filter/plugins/NormalizeFilterPlugin.cxx
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "AudioFormat.hxx"
+#include "AudioCompress/compress.h"
+
+#include <string.h>
+
+class NormalizeFilter final : public Filter {
+ struct Compressor *compressor;
+
+ PcmBuffer buffer;
+
+public:
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+};
+
+static Filter *
+normalize_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new NormalizeFilter();
+}
+
+AudioFormat
+NormalizeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+{
+ audio_format.format = SampleFormat::S16;
+
+ compressor = Compressor_new(0);
+
+ return audio_format;
+}
+
+void
+NormalizeFilter::Close()
+{
+ buffer.Clear();
+ Compressor_delete(compressor);
+}
+
+const void *
+NormalizeFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, gcc_unused Error &error)
+{
+ int16_t *dest = (int16_t *)buffer.Get(src_size);
+ memcpy(dest, src, src_size);
+
+ Compressor_Process_int16(compressor, dest, src_size / 2);
+
+ *dest_size_r = src_size;
+ return dest;
+}
+
+const struct filter_plugin normalize_filter_plugin = {
+ "normalize",
+ normalize_filter_init,
+};
diff --git a/src/filter/plugins/NullFilterPlugin.cxx b/src/filter/plugins/NullFilterPlugin.cxx
new file mode 100644
index 000000000..f79aa19f7
--- /dev/null
+++ b/src/filter/plugins/NullFilterPlugin.cxx
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * This filter plugin does nothing. That is not quite useful, except
+ * for testing the filter core, or as a template for new filter
+ * plugins.
+ */
+
+#include "config.h"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "Compiler.h"
+
+class NullFilter final : public Filter {
+public:
+ virtual AudioFormat Open(AudioFormat &af,
+ gcc_unused Error &error) override {
+ return af;
+ }
+
+ virtual void Close() override {}
+
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r,
+ gcc_unused Error &error) override {
+ *dest_size_r = src_size;
+ return src;
+ }
+};
+
+static Filter *
+null_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new NullFilter();
+}
+
+const struct filter_plugin null_filter_plugin = {
+ "null",
+ null_filter_init,
+};
diff --git a/src/filter/plugins/ReplayGainFilterPlugin.cxx b/src/filter/plugins/ReplayGainFilterPlugin.cxx
new file mode 100644
index 000000000..d89e79480
--- /dev/null
+++ b/src/filter/plugins/ReplayGainFilterPlugin.cxx
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "ReplayGainFilterPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "AudioFormat.hxx"
+#include "ReplayGainInfo.hxx"
+#include "ReplayGainConfig.hxx"
+#include "MixerControl.hxx"
+#include "pcm/Volume.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+static constexpr Domain replay_gain_domain("replay_gain");
+
+class ReplayGainFilter final : public Filter {
+ /**
+ * If set, then this hardware mixer is used for applying
+ * replay gain, instead of the software volume library.
+ */
+ Mixer *mixer;
+
+ /**
+ * The base volume level for scale=1.0, between 1 and 100
+ * (including).
+ */
+ unsigned base;
+
+ ReplayGainMode mode;
+
+ ReplayGainInfo info;
+
+ /**
+ * About the current volume: it is between 0 and a value that
+ * may or may not exceed #PCM_VOLUME_1.
+ *
+ * If the default value of true is used for replaygain_limit, the
+ * application of the volume to the signal will never cause clipping.
+ *
+ * On the other hand, if the user has set replaygain_limit to false,
+ * the chance of clipping is explicitly preferred if that's required to
+ * maintain a consistent audio level. Whether clipping will actually
+ * occur depends on what value the user is using for replaygain_preamp.
+ */
+ PcmVolume pv;
+
+public:
+ ReplayGainFilter()
+ :mixer(nullptr), mode(REPLAY_GAIN_OFF) {
+ info.Clear();
+ }
+
+ void SetMixer(Mixer *_mixer, unsigned _base) {
+ assert(_mixer == nullptr || (_base > 0 && _base <= 100));
+
+ mixer = _mixer;
+ base = _base;
+
+ Update();
+ }
+
+ void SetInfo(const ReplayGainInfo *_info) {
+ if (_info != nullptr) {
+ info = *_info;
+ info.Complete();
+ } else
+ info.Clear();
+
+ Update();
+ }
+
+ void SetMode(ReplayGainMode _mode) {
+ if (_mode == mode)
+ /* no change */
+ return;
+
+ FormatDebug(replay_gain_domain,
+ "replay gain mode has changed %d->%d\n",
+ mode, _mode);
+
+ mode = _mode;
+ Update();
+ }
+
+ /**
+ * Recalculates the new volume after a property was changed.
+ */
+ void Update();
+
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+};
+
+void
+ReplayGainFilter::Update()
+{
+ unsigned volume = PCM_VOLUME_1;
+ if (mode != REPLAY_GAIN_OFF) {
+ const auto &tuple = info.tuples[mode];
+ float scale = tuple.CalculateScale(replay_gain_preamp,
+ replay_gain_missing_preamp,
+ replay_gain_limit);
+ FormatDebug(replay_gain_domain,
+ "scale=%f\n", (double)scale);
+
+ volume = pcm_float_to_volume(scale);
+ }
+
+ pv.SetVolume(volume);
+
+ if (mixer != nullptr) {
+ /* update the hardware mixer volume */
+
+ unsigned _volume = (volume * base) / PCM_VOLUME_1;
+ if (_volume > 100)
+ _volume = 100;
+
+ Error error;
+ if (!mixer_set_volume(mixer, _volume, error))
+ LogError(error, "Failed to update hardware mixer");
+ }
+}
+
+static Filter *
+replay_gain_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new ReplayGainFilter();
+}
+
+AudioFormat
+ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error)
+{
+ if (!pv.Open(af.format, error))
+ return AudioFormat::Undefined();
+
+ return af;
+}
+
+void
+ReplayGainFilter::Close()
+{
+ pv.Close();
+}
+
+const void *
+ReplayGainFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, gcc_unused Error &error)
+{
+ const auto dest = pv.Apply({src, src_size});
+ *dest_size_r = dest.size;
+ return dest.data;
+}
+
+const struct filter_plugin replay_gain_filter_plugin = {
+ "replay_gain",
+ replay_gain_filter_init,
+};
+
+void
+replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer,
+ unsigned base)
+{
+ ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
+
+ filter->SetMixer(mixer, base);
+}
+
+void
+replay_gain_filter_set_info(Filter *_filter, const ReplayGainInfo *info)
+{
+ ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
+
+ filter->SetInfo(info);
+}
+
+void
+replay_gain_filter_set_mode(Filter *_filter, ReplayGainMode mode)
+{
+ ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
+
+ filter->SetMode(mode);
+}
diff --git a/src/filter/plugins/ReplayGainFilterPlugin.hxx b/src/filter/plugins/ReplayGainFilterPlugin.hxx
new file mode 100644
index 000000000..346541b97
--- /dev/null
+++ b/src/filter/plugins/ReplayGainFilterPlugin.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
+#define MPD_REPLAY_GAIN_FILTER_PLUGIN_HXX
+
+#include "ReplayGainInfo.hxx"
+
+class Filter;
+class Mixer;
+
+/**
+ * Enables or disables the hardware mixer for applying replay gain.
+ *
+ * @param mixer the hardware mixer, or nullptr to fall back to software
+ * volume
+ * @param base the base volume level for scale=1.0, between 1 and 100
+ * (including).
+ */
+void
+replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer,
+ unsigned base);
+
+/**
+ * Sets a new #replay_gain_info at the beginning of a new song.
+ *
+ * @param info the new #replay_gain_info value, or nullptr if no replay
+ * gain data is available for the current song
+ */
+void
+replay_gain_filter_set_info(Filter *filter, const ReplayGainInfo *info);
+
+void
+replay_gain_filter_set_mode(Filter *filter, ReplayGainMode mode);
+
+#endif
diff --git a/src/filter/plugins/RouteFilterPlugin.cxx b/src/filter/plugins/RouteFilterPlugin.cxx
new file mode 100644
index 000000000..38c4ec43b
--- /dev/null
+++ b/src/filter/plugins/RouteFilterPlugin.cxx
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/** \file
+ *
+ * This filter copies audio data between channels. Useful for
+ * upmixing mono/stereo audio to surround speaker configurations.
+ *
+ * Its configuration consists of a "filter" section with a single
+ * "routes" entry, formatted as: \\
+ * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\
+ * where each pair of numbers signifies a set of channels.
+ * Each source>dest pair leads to the data from channel #source
+ * being copied to channel #dest in the output.
+ *
+ * Example: \\
+ * routes "0>0, 1>1, 0>2, 1>3"\\
+ * upmixes stereo audio to a 4-speaker system, copying the front-left
+ * (0) to front left (0) and rear left (2), copying front-right (1) to
+ * front-right (1) and rear-right (3).
+ *
+ * If multiple sources are copied to the same destination channel, only
+ * one of them takes effect.
+ */
+
+#include "config.h"
+#include "config/ConfigError.hxx"
+#include "config/ConfigData.hxx"
+#include "AudioFormat.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/PcmBuffer.hxx"
+#include "util/StringUtil.hxx"
+#include "util/Error.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+class RouteFilter final : public Filter {
+ /**
+ * The minimum number of channels we need for output
+ * to be able to perform all the copies the user has specified
+ */
+ unsigned min_output_channels;
+
+ /**
+ * The minimum number of input channels we need to
+ * copy all the data the user has requested. If fewer
+ * than this many are supplied by the input, undefined
+ * copy operations are given zeroed sources in stead.
+ */
+ unsigned min_input_channels;
+
+ /**
+ * The set of copy operations to perform on each sample
+ * The index is an output channel to use, the value is
+ * a corresponding input channel from which to take the
+ * data. A -1 means "no source"
+ */
+ int8_t sources[MAX_CHANNELS];
+
+ /**
+ * The actual input format of our signal, once opened
+ */
+ AudioFormat input_format;
+
+ /**
+ * The decided upon output format, once opened
+ */
+ AudioFormat output_format;
+
+ /**
+ * The size, in bytes, of each multichannel frame in the
+ * input buffer
+ */
+ size_t input_frame_size;
+
+ /**
+ * The size, in bytes, of each multichannel frame in the
+ * output buffer
+ */
+ size_t output_frame_size;
+
+ /**
+ * The output buffer used last time around, can be reused if the size doesn't differ.
+ */
+ PcmBuffer output_buffer;
+
+public:
+ /**
+ * Parse the "routes" section, a string on the form
+ * a>b, c>d, e>f, ...
+ * where a... are non-unique, non-negative integers
+ * and input channel a gets copied to output channel b, etc.
+ * @param param the configuration block to read
+ * @param filter a route_filter whose min_channels and sources[] to set
+ * @return true on success, false on error
+ */
+ bool Configure(const config_param &param, Error &error);
+
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+};
+
+bool
+RouteFilter::Configure(const config_param &param, Error &error) {
+
+ /* TODO:
+ * With a more clever way of marking "don't copy to output N",
+ * This could easily be merged into a single loop with some
+ * dynamic realloc() instead of one count run and one malloc().
+ */
+
+ std::fill_n(sources, MAX_CHANNELS, -1);
+
+ min_input_channels = 0;
+ min_output_channels = 0;
+
+ // A cowardly default, just passthrough stereo
+ const char *routes = param.GetBlockValue("routes", "0>0, 1>1");
+ while (true) {
+ routes = strchug_fast(routes);
+
+ char *endptr;
+ const unsigned source = strtoul(routes, &endptr, 10);
+ endptr = strchug_fast(endptr);
+ if (endptr == routes || *endptr != '>') {
+ error.Set(config_domain,
+ "Malformed 'routes' specification");
+ return false;
+ }
+
+ if (source >= MAX_CHANNELS) {
+ error.Format(config_domain,
+ "Invalid source channel number: %u",
+ source);
+ return false;
+ }
+
+ if (source >= min_input_channels)
+ min_input_channels = source + 1;
+
+ routes = strchug_fast(endptr + 1);
+
+ unsigned dest = strtoul(routes, &endptr, 10);
+ endptr = strchug_fast(endptr);
+ if (endptr == routes) {
+ error.Set(config_domain,
+ "Malformed 'routes' specification");
+ return false;
+ }
+
+ if (dest >= MAX_CHANNELS) {
+ error.Format(config_domain,
+ "Invalid destination channel number: %u",
+ dest);
+ return false;
+ }
+
+ if (dest >= min_output_channels)
+ min_output_channels = dest + 1;
+
+ sources[dest] = source;
+
+ routes = endptr;
+
+ if (*routes == 0)
+ break;
+
+ if (*routes != ',') {
+ error.Set(config_domain,
+ "Malformed 'routes' specification");
+ return false;
+ }
+
+ ++routes;
+ }
+
+ return true;
+}
+
+static Filter *
+route_filter_init(const config_param &param, Error &error)
+{
+ RouteFilter *filter = new RouteFilter();
+ if (!filter->Configure(param, error)) {
+ delete filter;
+ return nullptr;
+ }
+
+ return filter;
+}
+
+AudioFormat
+RouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
+{
+ // Copy the input format for later reference
+ input_format = audio_format;
+ input_frame_size = input_format.GetFrameSize();
+
+ // Decide on an output format which has enough channels,
+ // and is otherwise identical
+ output_format = audio_format;
+ output_format.channels = min_output_channels;
+
+ // Precalculate this simple value, to speed up allocation later
+ output_frame_size = output_format.GetFrameSize();
+
+ return output_format;
+}
+
+void
+RouteFilter::Close()
+{
+ output_buffer.Clear();
+}
+
+const void *
+RouteFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, gcc_unused Error &error)
+{
+ size_t number_of_frames = src_size / input_frame_size;
+
+ const size_t bytes_per_frame_per_channel = input_format.GetSampleSize();
+
+ // A moving pointer that always refers to channel 0 in the input, at the currently handled frame
+ const uint8_t *base_source = (const uint8_t *)src;
+
+ // Grow our reusable buffer, if needed, and set the moving pointer
+ *dest_size_r = number_of_frames * output_frame_size;
+ void *const result = output_buffer.Get(*dest_size_r);
+
+ // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output
+ uint8_t *chan_destination = (uint8_t *)result;
+
+ // Perform our copy operations, with N input channels and M output channels
+ for (unsigned int s=0; s<number_of_frames; ++s) {
+
+ // Need to perform one copy per output channel
+ for (unsigned int c=0; c<min_output_channels; ++c) {
+ if (sources[c] == -1 ||
+ (unsigned)sources[c] >= input_format.channels) {
+ // No source for this destination output,
+ // give it zeroes as input
+ memset(chan_destination,
+ 0x00,
+ bytes_per_frame_per_channel);
+ } else {
+ // Get the data from channel sources[c]
+ // and copy it to the output
+ const uint8_t *data = base_source +
+ (sources[c] * bytes_per_frame_per_channel);
+ memcpy(chan_destination,
+ data,
+ bytes_per_frame_per_channel);
+ }
+ // Move on to the next output channel
+ chan_destination += bytes_per_frame_per_channel;
+ }
+
+
+ // Go on to the next N input samples
+ base_source += input_frame_size;
+ }
+
+ // Here it is, ladies and gentlemen! Rerouted data!
+ return result;
+}
+
+const struct filter_plugin route_filter_plugin = {
+ "route",
+ route_filter_init,
+};
diff --git a/src/filter/plugins/VolumeFilterPlugin.cxx b/src/filter/plugins/VolumeFilterPlugin.cxx
new file mode 100644
index 000000000..c9b7aa89e
--- /dev/null
+++ b/src/filter/plugins/VolumeFilterPlugin.cxx
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "VolumeFilterPlugin.hxx"
+#include "filter/FilterPlugin.hxx"
+#include "filter/FilterInternal.hxx"
+#include "filter/FilterRegistry.hxx"
+#include "pcm/Volume.hxx"
+#include "AudioFormat.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+#include <assert.h>
+#include <string.h>
+
+class VolumeFilter final : public Filter {
+ PcmVolume pv;
+
+public:
+ unsigned GetVolume() const {
+ return pv.GetVolume();
+ }
+
+ void SetVolume(unsigned _volume) {
+ pv.SetVolume(_volume);
+ }
+
+ virtual AudioFormat Open(AudioFormat &af, Error &error) override;
+ virtual void Close();
+ virtual const void *FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, Error &error);
+};
+
+static constexpr Domain volume_domain("pcm_volume");
+
+static Filter *
+volume_filter_init(gcc_unused const config_param &param,
+ gcc_unused Error &error)
+{
+ return new VolumeFilter();
+}
+
+AudioFormat
+VolumeFilter::Open(AudioFormat &audio_format, Error &error)
+{
+ if (!pv.Open(audio_format.format, error))
+ return AudioFormat::Undefined();
+
+ return audio_format;
+}
+
+void
+VolumeFilter::Close()
+{
+ pv.Close();
+}
+
+const void *
+VolumeFilter::FilterPCM(const void *src, size_t src_size,
+ size_t *dest_size_r, gcc_unused Error &error)
+{
+ const auto dest = pv.Apply({src, src_size});
+ *dest_size_r = dest.size;
+ return dest.data;
+}
+
+const struct filter_plugin volume_filter_plugin = {
+ "volume",
+ volume_filter_init,
+};
+
+unsigned
+volume_filter_get(const Filter *_filter)
+{
+ const VolumeFilter *filter =
+ (const VolumeFilter *)_filter;
+
+ return filter->GetVolume();
+}
+
+void
+volume_filter_set(Filter *_filter, unsigned volume)
+{
+ VolumeFilter *filter = (VolumeFilter *)_filter;
+
+ filter->SetVolume(volume);
+}
+
diff --git a/src/filter/plugins/VolumeFilterPlugin.hxx b/src/filter/plugins/VolumeFilterPlugin.hxx
new file mode 100644
index 000000000..b5317dc6f
--- /dev/null
+++ b/src/filter/plugins/VolumeFilterPlugin.hxx
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_VOLUME_FILTER_PLUGIN_HXX
+#define MPD_VOLUME_FILTER_PLUGIN_HXX
+
+class Filter;
+
+unsigned
+volume_filter_get(const Filter *filter);
+
+void
+volume_filter_set(Filter *filter, unsigned volume);
+
+#endif