diff options
author | Max Kellermann <max@duempel.org> | 2014-01-24 16:31:52 +0100 |
---|---|---|
committer | Max Kellermann <max@duempel.org> | 2014-01-24 16:43:57 +0100 |
commit | 7c52a1c04bccac68f4220c8bf3d3a59c16ed58db (patch) | |
tree | 6c1f8b99225343974584817b3278958599e2e12a /src/filter/plugins | |
parent | 9d34fc394ce30a28ec0e43f2ad7172b8de8b3be6 (diff) | |
download | mpd-7c52a1c04bccac68f4220c8bf3d3a59c16ed58db.tar.gz mpd-7c52a1c04bccac68f4220c8bf3d3a59c16ed58db.tar.xz mpd-7c52a1c04bccac68f4220c8bf3d3a59c16ed58db.zip |
Filter*: move to filter/
Diffstat (limited to 'src/filter/plugins')
-rw-r--r-- | src/filter/plugins/AutoConvertFilterPlugin.cxx | 131 | ||||
-rw-r--r-- | src/filter/plugins/AutoConvertFilterPlugin.hxx | 34 | ||||
-rw-r--r-- | src/filter/plugins/ChainFilterPlugin.cxx | 181 | ||||
-rw-r--r-- | src/filter/plugins/ChainFilterPlugin.hxx | 48 | ||||
-rw-r--r-- | src/filter/plugins/ConvertFilterPlugin.cxx | 149 | ||||
-rw-r--r-- | src/filter/plugins/ConvertFilterPlugin.hxx | 37 | ||||
-rw-r--r-- | src/filter/plugins/NormalizeFilterPlugin.cxx | 82 | ||||
-rw-r--r-- | src/filter/plugins/NullFilterPlugin.cxx | 61 | ||||
-rw-r--r-- | src/filter/plugins/ReplayGainFilterPlugin.cxx | 210 | ||||
-rw-r--r-- | src/filter/plugins/ReplayGainFilterPlugin.hxx | 52 | ||||
-rw-r--r-- | src/filter/plugins/RouteFilterPlugin.cxx | 296 | ||||
-rw-r--r-- | src/filter/plugins/VolumeFilterPlugin.cxx | 106 | ||||
-rw-r--r-- | src/filter/plugins/VolumeFilterPlugin.hxx | 31 |
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 ¶m, + 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 ¶m, + 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 ¶m, + 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 ¶m, + 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 ¶m, + 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 ¶m, 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 ¶m, 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 ¶m, 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 ¶m, + 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 |