filter/FilterInternal: split class Filter, add class PreparedFilter
For easier state management inside filter plugins.
This commit is contained in:
parent
5c75096bcd
commit
3a21241248
|
@ -108,6 +108,7 @@ libmpd_a_SOURCES = \
|
||||||
src/filter/FilterPlugin.cxx src/filter/FilterPlugin.hxx \
|
src/filter/FilterPlugin.cxx src/filter/FilterPlugin.hxx \
|
||||||
src/filter/FilterInternal.hxx \
|
src/filter/FilterInternal.hxx \
|
||||||
src/filter/FilterRegistry.cxx src/filter/FilterRegistry.hxx \
|
src/filter/FilterRegistry.cxx src/filter/FilterRegistry.hxx \
|
||||||
|
src/filter/Observer.cxx src/filter/Observer.hxx \
|
||||||
src/client/Client.cxx src/client/Client.hxx \
|
src/client/Client.cxx src/client/Client.hxx \
|
||||||
src/client/ClientInternal.hxx \
|
src/client/ClientInternal.hxx \
|
||||||
src/client/ClientEvent.cxx \
|
src/client/ClientEvent.cxx \
|
||||||
|
@ -2068,6 +2069,7 @@ test_run_output_SOURCES = test/run_output.cxx \
|
||||||
src/mixer/MixerType.cxx \
|
src/mixer/MixerType.cxx \
|
||||||
src/filter/FilterPlugin.cxx \
|
src/filter/FilterPlugin.cxx \
|
||||||
src/filter/FilterConfig.cxx \
|
src/filter/FilterConfig.cxx \
|
||||||
|
src/filter/Observer.cxx \
|
||||||
src/ReplayGainInfo.cxx
|
src/ReplayGainInfo.cxx
|
||||||
|
|
||||||
test_read_mixer_LDADD = \
|
test_read_mixer_LDADD = \
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
filter_chain_append_new(Filter &chain, const char *template_name, Error &error)
|
filter_chain_append_new(PreparedFilter &chain, const char *template_name, Error &error)
|
||||||
{
|
{
|
||||||
const auto *cfg = config_find_block(ConfigBlockOption::AUDIO_FILTER,
|
const auto *cfg = config_find_block(ConfigBlockOption::AUDIO_FILTER,
|
||||||
"name", template_name);
|
"name", template_name);
|
||||||
|
@ -45,7 +45,7 @@ filter_chain_append_new(Filter &chain, const char *template_name, Error &error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate one of those filter plugins with the template name as a hint
|
// Instantiate one of those filter plugins with the template name as a hint
|
||||||
Filter *f = filter_configured_new(*cfg, error);
|
PreparedFilter *f = filter_configured_new(*cfg, error);
|
||||||
if (f == nullptr)
|
if (f == nullptr)
|
||||||
// The error has already been set, just stop.
|
// The error has already been set, just stop.
|
||||||
return false;
|
return false;
|
||||||
|
@ -58,7 +58,7 @@ filter_chain_append_new(Filter &chain, const char *template_name, Error &error)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
filter_chain_parse(Filter &chain, const char *spec, Error &error)
|
filter_chain_parse(PreparedFilter &chain, const char *spec, Error &error)
|
||||||
{
|
{
|
||||||
const char *const end = spec + strlen(spec);
|
const char *const end = spec + strlen(spec);
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
#ifndef MPD_FILTER_CONFIG_HXX
|
#ifndef MPD_FILTER_CONFIG_HXX
|
||||||
#define MPD_FILTER_CONFIG_HXX
|
#define MPD_FILTER_CONFIG_HXX
|
||||||
|
|
||||||
class Filter;
|
class PreparedFilter;
|
||||||
class Error;
|
class Error;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,6 +38,6 @@ class Error;
|
||||||
* @return true on success
|
* @return true on success
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
filter_chain_parse(Filter &chain, const char *spec, Error &error);
|
filter_chain_parse(PreparedFilter &chain, const char *spec, Error &error);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
#ifndef MPD_FILTER_INTERNAL_HXX
|
#ifndef MPD_FILTER_INTERNAL_HXX
|
||||||
#define MPD_FILTER_INTERNAL_HXX
|
#define MPD_FILTER_INTERNAL_HXX
|
||||||
|
|
||||||
|
#include "AudioFormat.hxx"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
struct AudioFormat;
|
struct AudioFormat;
|
||||||
|
@ -32,9 +34,39 @@ class Error;
|
||||||
template<typename T> struct ConstBuffer;
|
template<typename T> struct ConstBuffer;
|
||||||
|
|
||||||
class Filter {
|
class Filter {
|
||||||
|
protected:
|
||||||
|
AudioFormat out_audio_format;
|
||||||
|
|
||||||
|
Filter() = default;
|
||||||
|
explicit Filter(AudioFormat _out_audio_format)
|
||||||
|
:out_audio_format(_out_audio_format) {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~Filter() {}
|
virtual ~Filter() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the #AudioFormat produced by FilterPCM().
|
||||||
|
*/
|
||||||
|
const AudioFormat &GetOutAudioFormat() const {
|
||||||
|
return out_audio_format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters a block of PCM data.
|
||||||
|
*
|
||||||
|
* @param src the input buffer
|
||||||
|
* @param error location to store the error occurring
|
||||||
|
* @return the destination buffer on success (will be
|
||||||
|
* invalidated by deleting this object or the next FilterPCM()
|
||||||
|
* call), nullptr on error
|
||||||
|
*/
|
||||||
|
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, Error &error) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PreparedFilter {
|
||||||
|
public:
|
||||||
|
virtual ~PreparedFilter() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the filter, preparing it for FilterPCM().
|
* Opens the filter, preparing it for FilterPCM().
|
||||||
*
|
*
|
||||||
|
@ -45,23 +77,7 @@ public:
|
||||||
* @return the format of outgoing data or
|
* @return the format of outgoing data or
|
||||||
* AudioFormat::Undefined() on error
|
* AudioFormat::Undefined() on error
|
||||||
*/
|
*/
|
||||||
virtual AudioFormat Open(AudioFormat &af, Error &error) = 0;
|
virtual Filter *Open(AudioFormat &af, Error &error) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the filter. After that, you may call Open() again.
|
|
||||||
*/
|
|
||||||
virtual void Close() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters a block of PCM data.
|
|
||||||
*
|
|
||||||
* @param src the input buffer
|
|
||||||
* @param error location to store the error occurring
|
|
||||||
* @return the destination buffer on success (will be
|
|
||||||
* invalidated by Close() or FilterPCM()), nullptr on
|
|
||||||
* error
|
|
||||||
*/
|
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src, Error &error) = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
Filter *
|
PreparedFilter *
|
||||||
filter_new(const struct filter_plugin *plugin,
|
filter_new(const struct filter_plugin *plugin,
|
||||||
const ConfigBlock &block, Error &error)
|
const ConfigBlock &block, Error &error)
|
||||||
{
|
{
|
||||||
|
@ -36,7 +36,7 @@ filter_new(const struct filter_plugin *plugin,
|
||||||
return plugin->init(block, error);
|
return plugin->init(block, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
Filter *
|
PreparedFilter *
|
||||||
filter_configured_new(const ConfigBlock &block, Error &error)
|
filter_configured_new(const ConfigBlock &block, Error &error)
|
||||||
{
|
{
|
||||||
assert(!error.IsDefined());
|
assert(!error.IsDefined());
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
#define MPD_FILTER_PLUGIN_HXX
|
#define MPD_FILTER_PLUGIN_HXX
|
||||||
|
|
||||||
struct ConfigBlock;
|
struct ConfigBlock;
|
||||||
class Filter;
|
class PreparedFilter;
|
||||||
class Error;
|
class Error;
|
||||||
|
|
||||||
struct filter_plugin {
|
struct filter_plugin {
|
||||||
|
@ -36,7 +36,7 @@ struct filter_plugin {
|
||||||
/**
|
/**
|
||||||
* Allocates and configures a filter.
|
* Allocates and configures a filter.
|
||||||
*/
|
*/
|
||||||
Filter *(*init)(const ConfigBlock &block, Error &error);
|
PreparedFilter *(*init)(const ConfigBlock &block, Error &error);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,7 +48,7 @@ struct filter_plugin {
|
||||||
* ignore errors.
|
* ignore errors.
|
||||||
* @return a new filter object, or nullptr on error
|
* @return a new filter object, or nullptr on error
|
||||||
*/
|
*/
|
||||||
Filter *
|
PreparedFilter *
|
||||||
filter_new(const struct filter_plugin *plugin,
|
filter_new(const struct filter_plugin *plugin,
|
||||||
const ConfigBlock &block, Error &error);
|
const ConfigBlock &block, Error &error);
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ filter_new(const struct filter_plugin *plugin,
|
||||||
* ignore errors.
|
* ignore errors.
|
||||||
* @return a new filter object, or nullptr on error
|
* @return a new filter object, or nullptr on error
|
||||||
*/
|
*/
|
||||||
Filter *
|
PreparedFilter *
|
||||||
filter_configured_new(const ConfigBlock &block, Error &error);
|
filter_configured_new(const ConfigBlock &block, Error &error);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2003-2016 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 "Observer.hxx"
|
||||||
|
#include "FilterInternal.hxx"
|
||||||
|
#include "util/ConstBuffer.hxx"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
class FilterObserver::PreparedProxy final : public PreparedFilter {
|
||||||
|
FilterObserver &observer;
|
||||||
|
|
||||||
|
PreparedFilter *const prepared_filter;
|
||||||
|
Proxy *child = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PreparedProxy(FilterObserver &_observer,
|
||||||
|
PreparedFilter *_prepared_filter)
|
||||||
|
:observer(_observer),
|
||||||
|
prepared_filter(_prepared_filter) {}
|
||||||
|
|
||||||
|
~PreparedProxy() {
|
||||||
|
assert(child == nullptr);
|
||||||
|
assert(observer.proxy == this);
|
||||||
|
|
||||||
|
observer.proxy = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clear(gcc_unused Proxy *_child) {
|
||||||
|
assert(child == _child);
|
||||||
|
child = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter *Get();
|
||||||
|
|
||||||
|
Filter *Open(AudioFormat &af, Error &error) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FilterObserver::Proxy final : public Filter {
|
||||||
|
PreparedProxy &parent;
|
||||||
|
|
||||||
|
Filter *const filter;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Proxy(PreparedProxy &_parent, Filter *_filter)
|
||||||
|
:Filter(_filter->GetOutAudioFormat()),
|
||||||
|
parent(_parent), filter(_filter) {}
|
||||||
|
|
||||||
|
~Proxy() {
|
||||||
|
parent.Clear(this);
|
||||||
|
delete filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter *Get() {
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
|
Error &error) override {
|
||||||
|
return filter->FilterPCM(src, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter *
|
||||||
|
FilterObserver::PreparedProxy::Get()
|
||||||
|
{
|
||||||
|
return child != nullptr
|
||||||
|
? child->Get()
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter *
|
||||||
|
FilterObserver::PreparedProxy::Open(AudioFormat &af, Error &error)
|
||||||
|
{
|
||||||
|
assert(child == nullptr);
|
||||||
|
|
||||||
|
Filter *f = prepared_filter->Open(af, error);
|
||||||
|
if (f == nullptr)
|
||||||
|
return f;
|
||||||
|
|
||||||
|
return child = new Proxy(*this, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
PreparedFilter *
|
||||||
|
FilterObserver::Set(PreparedFilter *pf)
|
||||||
|
{
|
||||||
|
assert(proxy == nullptr);
|
||||||
|
|
||||||
|
return proxy = new PreparedProxy(*this, pf);
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter *
|
||||||
|
FilterObserver::Get()
|
||||||
|
{
|
||||||
|
return proxy != nullptr
|
||||||
|
? proxy->Get()
|
||||||
|
: nullptr;
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2003-2016 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_FILTER_OBSERVER_HXX
|
||||||
|
#define MPD_FILTER_OBSERVER_HXX
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
|
class PreparedFilter;
|
||||||
|
class Filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class which observes calls to a #PreparedFilter and allows
|
||||||
|
* the caller to access the #Filter instances created by it.
|
||||||
|
*/
|
||||||
|
class FilterObserver {
|
||||||
|
class PreparedProxy;
|
||||||
|
class Proxy;
|
||||||
|
|
||||||
|
PreparedProxy *proxy = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @return a proxy object
|
||||||
|
*/
|
||||||
|
PreparedFilter *Set(PreparedFilter *pf);
|
||||||
|
|
||||||
|
Filter *Get();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -24,7 +24,6 @@
|
||||||
#include "filter/FilterInternal.hxx"
|
#include "filter/FilterInternal.hxx"
|
||||||
#include "filter/FilterRegistry.hxx"
|
#include "filter/FilterRegistry.hxx"
|
||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
#include "config/Block.hxx"
|
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
@ -33,82 +32,70 @@ class AutoConvertFilter final : public Filter {
|
||||||
/**
|
/**
|
||||||
* The underlying filter.
|
* The underlying filter.
|
||||||
*/
|
*/
|
||||||
Filter *filter;
|
Filter *const filter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A convert_filter, just in case conversion is needed. nullptr
|
* A convert_filter, just in case conversion is needed. nullptr
|
||||||
* if unused.
|
* if unused.
|
||||||
*/
|
*/
|
||||||
Filter *convert;
|
Filter *const convert;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AutoConvertFilter(Filter *_filter):filter(_filter) {}
|
AutoConvertFilter(Filter *_filter, Filter *_convert)
|
||||||
|
:filter(_filter), convert(_convert) {}
|
||||||
|
|
||||||
~AutoConvertFilter() {
|
~AutoConvertFilter() {
|
||||||
|
delete convert;
|
||||||
delete filter;
|
delete filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
|
||||||
virtual void Close() override;
|
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
Error &error) override;
|
Error &error) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioFormat
|
class PreparedAutoConvertFilter final : public PreparedFilter {
|
||||||
AutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error)
|
/**
|
||||||
|
* The underlying filter.
|
||||||
|
*/
|
||||||
|
PreparedFilter *const filter;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PreparedAutoConvertFilter(PreparedFilter *_filter):filter(_filter) {}
|
||||||
|
~PreparedAutoConvertFilter() {
|
||||||
|
delete filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Filter *Open(AudioFormat &af, Error &error) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
Filter *
|
||||||
|
PreparedAutoConvertFilter::Open(AudioFormat &in_audio_format, Error &error)
|
||||||
{
|
{
|
||||||
assert(in_audio_format.IsValid());
|
assert(in_audio_format.IsValid());
|
||||||
|
|
||||||
/* open the "real" filter */
|
/* open the "real" filter */
|
||||||
|
|
||||||
AudioFormat child_audio_format = in_audio_format;
|
AudioFormat child_audio_format = in_audio_format;
|
||||||
AudioFormat out_audio_format = filter->Open(child_audio_format, error);
|
auto *new_filter = filter->Open(child_audio_format, error);
|
||||||
if (!out_audio_format.IsDefined())
|
if (new_filter == nullptr)
|
||||||
return out_audio_format;
|
return nullptr;
|
||||||
|
|
||||||
/* need to convert? */
|
/* need to convert? */
|
||||||
|
|
||||||
|
Filter *convert = nullptr;
|
||||||
if (in_audio_format != child_audio_format) {
|
if (in_audio_format != child_audio_format) {
|
||||||
/* yes - create a convert_filter */
|
/* yes - create a convert_filter */
|
||||||
|
|
||||||
const ConfigBlock empty;
|
convert = convert_filter_new(in_audio_format,
|
||||||
convert = filter_new(&convert_filter_plugin, empty, error);
|
child_audio_format,
|
||||||
|
error);
|
||||||
if (convert == nullptr) {
|
if (convert == nullptr) {
|
||||||
filter->Close();
|
delete new_filter;
|
||||||
return AudioFormat::Undefined();
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
return new AutoConvertFilter(new_filter, convert);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
|
@ -123,8 +110,8 @@ AutoConvertFilter::FilterPCM(ConstBuffer<void> src, Error &error)
|
||||||
return filter->FilterPCM(src, error);
|
return filter->FilterPCM(src, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
Filter *
|
PreparedFilter *
|
||||||
autoconvert_filter_new(Filter *filter)
|
autoconvert_filter_new(PreparedFilter *filter)
|
||||||
{
|
{
|
||||||
return new AutoConvertFilter(filter);
|
return new PreparedAutoConvertFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
|
#ifndef MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
|
||||||
#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
|
#define MPD_AUTOCONVERT_FILTER_PLUGIN_HXX
|
||||||
|
|
||||||
class Filter;
|
class PreparedFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new "autoconvert" filter. When opened, it ensures that
|
* Creates a new "autoconvert" filter. When opened, it ensures that
|
||||||
|
@ -28,7 +28,7 @@ class Filter;
|
||||||
* requests a different format, it automatically creates a
|
* requests a different format, it automatically creates a
|
||||||
* convert_filter.
|
* convert_filter.
|
||||||
*/
|
*/
|
||||||
Filter *
|
PreparedFilter *
|
||||||
autoconvert_filter_new(Filter *filter);
|
autoconvert_filter_new(PreparedFilter *filter);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
@ -49,100 +50,98 @@ class ChainFilter final : public Filter {
|
||||||
std::list<Child> children;
|
std::list<Child> children;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
explicit ChainFilter(AudioFormat _audio_format)
|
||||||
|
:Filter(_audio_format) {}
|
||||||
|
|
||||||
void Append(const char *name, Filter *filter) {
|
void Append(const char *name, Filter *filter) {
|
||||||
|
assert(out_audio_format.IsValid());
|
||||||
|
out_audio_format = filter->GetOutAudioFormat();
|
||||||
|
assert(out_audio_format.IsValid());
|
||||||
|
|
||||||
children.emplace_back(name, filter);
|
children.emplace_back(name, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* virtual methods from class Filter */
|
/* virtual methods from class Filter */
|
||||||
AudioFormat Open(AudioFormat &af, Error &error) override;
|
|
||||||
void Close() override;
|
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
Error &error) override;
|
Error &error) override;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
class PreparedChainFilter final : public PreparedFilter {
|
||||||
/**
|
struct Child {
|
||||||
* Close all filters in the chain until #until is reached.
|
const char *name;
|
||||||
* #until itself is not closed.
|
PreparedFilter *filter;
|
||||||
*/
|
|
||||||
void CloseUntil(const Filter *until);
|
Child(const char *_name, PreparedFilter *_filter)
|
||||||
|
:name(_name), filter(_filter) {}
|
||||||
|
~Child() {
|
||||||
|
delete filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
Child(const Child &) = delete;
|
||||||
|
Child &operator=(const Child &) = delete;
|
||||||
|
|
||||||
|
Filter *Open(const AudioFormat &prev_audio_format,
|
||||||
|
Error &error);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::list<Child> children;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void Append(const char *name, PreparedFilter *filter) {
|
||||||
|
children.emplace_back(name, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* virtual methods from class PreparedFilter */
|
||||||
|
Filter *Open(AudioFormat &af, Error &error) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr Domain chain_filter_domain("chain_filter");
|
static constexpr Domain chain_filter_domain("chain_filter");
|
||||||
|
|
||||||
static Filter *
|
static PreparedFilter *
|
||||||
chain_filter_init(gcc_unused const ConfigBlock &block,
|
chain_filter_init(gcc_unused const ConfigBlock &block,
|
||||||
gcc_unused Error &error)
|
gcc_unused Error &error)
|
||||||
{
|
{
|
||||||
return new ChainFilter();
|
return new PreparedChainFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
Filter *
|
||||||
ChainFilter::CloseUntil(const Filter *until)
|
PreparedChainFilter::Child::Open(const AudioFormat &prev_audio_format,
|
||||||
{
|
Error &error)
|
||||||
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;
|
AudioFormat conv_audio_format = prev_audio_format;
|
||||||
const AudioFormat next_audio_format =
|
Filter *new_filter = filter->Open(conv_audio_format, error);
|
||||||
filter->Open(conv_audio_format, error);
|
if (new_filter == nullptr)
|
||||||
if (!next_audio_format.IsDefined())
|
return nullptr;
|
||||||
return next_audio_format;
|
|
||||||
|
|
||||||
if (conv_audio_format != prev_audio_format) {
|
if (conv_audio_format != prev_audio_format) {
|
||||||
|
delete new_filter;
|
||||||
|
|
||||||
struct audio_format_string s;
|
struct audio_format_string s;
|
||||||
|
|
||||||
filter->Close();
|
|
||||||
|
|
||||||
error.Format(chain_filter_domain,
|
error.Format(chain_filter_domain,
|
||||||
"Audio format not supported by filter '%s': %s",
|
"Audio format not supported by filter '%s': %s",
|
||||||
name,
|
name,
|
||||||
audio_format_to_string(prev_audio_format, &s));
|
audio_format_to_string(prev_audio_format, &s));
|
||||||
return AudioFormat::Undefined();
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return next_audio_format;
|
return new_filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioFormat
|
Filter *
|
||||||
ChainFilter::Open(AudioFormat &in_audio_format, Error &error)
|
PreparedChainFilter::Open(AudioFormat &in_audio_format, Error &error)
|
||||||
{
|
{
|
||||||
AudioFormat audio_format = in_audio_format;
|
std::unique_ptr<ChainFilter> chain(new ChainFilter(in_audio_format));
|
||||||
|
|
||||||
for (auto &child : children) {
|
for (auto &child : children) {
|
||||||
audio_format = chain_open_child(child.name, child.filter,
|
AudioFormat audio_format = chain->GetOutAudioFormat();
|
||||||
audio_format, error);
|
auto *filter = child.Open(audio_format, error);
|
||||||
if (!audio_format.IsDefined()) {
|
if (filter == nullptr)
|
||||||
/* rollback, close all children */
|
return nullptr;
|
||||||
CloseUntil(child.filter);
|
|
||||||
break;
|
chain->Append(child.name, filter);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* return the output format of the last filter */
|
return chain.release();
|
||||||
return audio_format;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ChainFilter::Close()
|
|
||||||
{
|
|
||||||
for (auto &child : children)
|
|
||||||
child.filter->Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
|
@ -165,16 +164,17 @@ const struct filter_plugin chain_filter_plugin = {
|
||||||
chain_filter_init,
|
chain_filter_init,
|
||||||
};
|
};
|
||||||
|
|
||||||
Filter *
|
PreparedFilter *
|
||||||
filter_chain_new(void)
|
filter_chain_new(void)
|
||||||
{
|
{
|
||||||
return new ChainFilter();
|
return new PreparedChainFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
filter_chain_append(Filter &_chain, const char *name, Filter *filter)
|
filter_chain_append(PreparedFilter &_chain, const char *name,
|
||||||
|
PreparedFilter *filter)
|
||||||
{
|
{
|
||||||
ChainFilter &chain = (ChainFilter &)_chain;
|
PreparedChainFilter &chain = (PreparedChainFilter &)_chain;
|
||||||
|
|
||||||
chain.Append(name, filter);
|
chain.Append(name, filter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,12 @@
|
||||||
#ifndef MPD_FILTER_CHAIN_HXX
|
#ifndef MPD_FILTER_CHAIN_HXX
|
||||||
#define MPD_FILTER_CHAIN_HXX
|
#define MPD_FILTER_CHAIN_HXX
|
||||||
|
|
||||||
class Filter;
|
class PreparedFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new filter chain.
|
* Creates a new filter chain.
|
||||||
*/
|
*/
|
||||||
Filter *
|
PreparedFilter *
|
||||||
filter_chain_new();
|
filter_chain_new();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,6 +43,7 @@ filter_chain_new();
|
||||||
* @param filter the filter to be appended to #chain
|
* @param filter the filter to be appended to #chain
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
filter_chain_append(Filter &chain, const char *name, Filter *filter);
|
filter_chain_append(PreparedFilter &chain, const char *name,
|
||||||
|
PreparedFilter *filter);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -38,31 +38,33 @@ class ConvertFilter final : public Filter {
|
||||||
AudioFormat in_audio_format;
|
AudioFormat in_audio_format;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The output audio format; the consumer of this plugin
|
* This object is only "open" if #in_audio_format !=
|
||||||
* expects PCM data in this format.
|
* #out_audio_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;
|
PcmConvert state;
|
||||||
|
|
||||||
Manual<PcmConvert> state;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
ConvertFilter(const AudioFormat &audio_format);
|
||||||
|
~ConvertFilter();
|
||||||
|
|
||||||
bool Set(const AudioFormat &_out_audio_format, Error &error);
|
bool Set(const AudioFormat &_out_audio_format, Error &error);
|
||||||
|
|
||||||
virtual AudioFormat Open(AudioFormat &af, Error &error) override;
|
|
||||||
virtual void Close() override;
|
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
Error &error) override;
|
Error &error) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Filter *
|
class PreparedConvertFilter final : public PreparedFilter {
|
||||||
|
public:
|
||||||
|
bool Set(const AudioFormat &_out_audio_format, Error &error);
|
||||||
|
|
||||||
|
Filter *Open(AudioFormat &af, Error &error) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
static PreparedFilter *
|
||||||
convert_filter_init(gcc_unused const ConfigBlock &block,
|
convert_filter_init(gcc_unused const ConfigBlock &block,
|
||||||
gcc_unused Error &error)
|
gcc_unused Error &error)
|
||||||
{
|
{
|
||||||
return new ConvertFilter();
|
return new PreparedConvertFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -75,47 +77,41 @@ ConvertFilter::Set(const AudioFormat &_out_audio_format, Error &error)
|
||||||
/* no change */
|
/* no change */
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (out_audio_format.IsValid()) {
|
if (out_audio_format != in_audio_format) {
|
||||||
out_audio_format.Clear();
|
out_audio_format = in_audio_format;
|
||||||
state->Close();
|
state.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_out_audio_format == in_audio_format)
|
if (_out_audio_format == in_audio_format)
|
||||||
/* optimized special case: no-op */
|
/* optimized special case: no-op */
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!state->Open(in_audio_format, _out_audio_format, error))
|
if (!state.Open(in_audio_format, _out_audio_format, error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
out_audio_format = _out_audio_format;
|
out_audio_format = _out_audio_format;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioFormat
|
ConvertFilter::ConvertFilter(const AudioFormat &audio_format)
|
||||||
ConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
|
:Filter(audio_format), in_audio_format(audio_format)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter *
|
||||||
|
PreparedConvertFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
|
||||||
{
|
{
|
||||||
assert(audio_format.IsValid());
|
assert(audio_format.IsValid());
|
||||||
|
|
||||||
in_audio_format = audio_format;
|
return new ConvertFilter(audio_format);
|
||||||
out_audio_format.Clear();
|
|
||||||
|
|
||||||
state.Construct();
|
|
||||||
|
|
||||||
return in_audio_format;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
ConvertFilter::~ConvertFilter()
|
||||||
ConvertFilter::Close()
|
|
||||||
{
|
{
|
||||||
assert(in_audio_format.IsValid());
|
assert(in_audio_format.IsValid());
|
||||||
|
|
||||||
if (out_audio_format.IsValid())
|
if (out_audio_format.IsValid())
|
||||||
state->Close();
|
state.Close();
|
||||||
|
|
||||||
state.Destruct();
|
|
||||||
|
|
||||||
poison_undefined(&in_audio_format, sizeof(in_audio_format));
|
|
||||||
poison_undefined(&out_audio_format, sizeof(out_audio_format));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
|
@ -127,7 +123,7 @@ ConvertFilter::FilterPCM(ConstBuffer<void> src, Error &error)
|
||||||
/* optimized special case: no-op */
|
/* optimized special case: no-op */
|
||||||
return src;
|
return src;
|
||||||
|
|
||||||
return state->Convert(src, error);
|
return state.Convert(src, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct filter_plugin convert_filter_plugin = {
|
const struct filter_plugin convert_filter_plugin = {
|
||||||
|
@ -135,6 +131,20 @@ const struct filter_plugin convert_filter_plugin = {
|
||||||
convert_filter_init,
|
convert_filter_init,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Filter *
|
||||||
|
convert_filter_new(const AudioFormat in_audio_format,
|
||||||
|
const AudioFormat out_audio_format,
|
||||||
|
Error &error)
|
||||||
|
{
|
||||||
|
auto *filter = new ConvertFilter(in_audio_format);
|
||||||
|
if (!filter->Set(out_audio_format, error)) {
|
||||||
|
delete filter;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
convert_filter_set(Filter *_filter, AudioFormat out_audio_format,
|
convert_filter_set(Filter *_filter, AudioFormat out_audio_format,
|
||||||
Error &error)
|
Error &error)
|
||||||
|
|
|
@ -24,6 +24,11 @@ class Filter;
|
||||||
class Error;
|
class Error;
|
||||||
struct AudioFormat;
|
struct AudioFormat;
|
||||||
|
|
||||||
|
Filter *
|
||||||
|
convert_filter_new(AudioFormat in_audio_format,
|
||||||
|
AudioFormat out_audio_format,
|
||||||
|
Error &error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the output audio format for the specified filter. You must
|
* Sets the output audio format for the specified filter. You must
|
||||||
* call this after the filter has been opened. Since this audio
|
* call this after the filter has been opened. Since this audio
|
||||||
|
|
|
@ -29,40 +29,43 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
class NormalizeFilter final : public Filter {
|
class NormalizeFilter final : public Filter {
|
||||||
struct Compressor *compressor;
|
Compressor *const compressor;
|
||||||
|
|
||||||
PcmBuffer buffer;
|
PcmBuffer buffer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
NormalizeFilter(const AudioFormat &audio_format)
|
||||||
|
:Filter(audio_format), compressor(Compressor_new(0)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~NormalizeFilter() {
|
||||||
|
Compressor_delete(compressor);
|
||||||
|
}
|
||||||
|
|
||||||
/* virtual methods from class Filter */
|
/* virtual methods from class Filter */
|
||||||
AudioFormat Open(AudioFormat &af, Error &error) override;
|
|
||||||
void Close() override;
|
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
Error &error) override;
|
Error &error) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Filter *
|
class PreparedNormalizeFilter final : public PreparedFilter {
|
||||||
|
public:
|
||||||
|
/* virtual methods from class PreparedFilter */
|
||||||
|
Filter *Open(AudioFormat &af, Error &error) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
static PreparedFilter *
|
||||||
normalize_filter_init(gcc_unused const ConfigBlock &block,
|
normalize_filter_init(gcc_unused const ConfigBlock &block,
|
||||||
gcc_unused Error &error)
|
gcc_unused Error &error)
|
||||||
{
|
{
|
||||||
return new NormalizeFilter();
|
return new PreparedNormalizeFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioFormat
|
Filter *
|
||||||
NormalizeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
|
PreparedNormalizeFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
|
||||||
{
|
{
|
||||||
audio_format.format = SampleFormat::S16;
|
audio_format.format = SampleFormat::S16;
|
||||||
|
|
||||||
compressor = Compressor_new(0);
|
return new NormalizeFilter(audio_format);
|
||||||
|
|
||||||
return audio_format;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
NormalizeFilter::Close()
|
|
||||||
{
|
|
||||||
buffer.Clear();
|
|
||||||
Compressor_delete(compressor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
|
|
|
@ -34,12 +34,7 @@
|
||||||
|
|
||||||
class NullFilter final : public Filter {
|
class NullFilter final : public Filter {
|
||||||
public:
|
public:
|
||||||
virtual AudioFormat Open(AudioFormat &af,
|
explicit NullFilter(const AudioFormat &af):Filter(af) {}
|
||||||
gcc_unused Error &error) override {
|
|
||||||
return af;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Close() override {}
|
|
||||||
|
|
||||||
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
gcc_unused Error &error) override {
|
gcc_unused Error &error) override {
|
||||||
|
@ -47,11 +42,19 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static Filter *
|
class PreparedNullFilter final : public PreparedFilter {
|
||||||
|
public:
|
||||||
|
virtual Filter *Open(AudioFormat &af,
|
||||||
|
gcc_unused Error &error) override {
|
||||||
|
return new NullFilter(af);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static PreparedFilter *
|
||||||
null_filter_init(gcc_unused const ConfigBlock &block,
|
null_filter_init(gcc_unused const ConfigBlock &block,
|
||||||
gcc_unused Error &error)
|
gcc_unused Error &error)
|
||||||
{
|
{
|
||||||
return new NullFilter();
|
return new PreparedNullFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct filter_plugin null_filter_plugin = {
|
const struct filter_plugin null_filter_plugin = {
|
||||||
|
|
|
@ -41,13 +41,13 @@ class ReplayGainFilter final : public Filter {
|
||||||
* If set, then this hardware mixer is used for applying
|
* If set, then this hardware mixer is used for applying
|
||||||
* replay gain, instead of the software volume library.
|
* replay gain, instead of the software volume library.
|
||||||
*/
|
*/
|
||||||
Mixer *mixer = nullptr;
|
Mixer *const mixer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base volume level for scale=1.0, between 1 and 100
|
* The base volume level for scale=1.0, between 1 and 100
|
||||||
* (including).
|
* (including).
|
||||||
*/
|
*/
|
||||||
unsigned base;
|
const unsigned base;
|
||||||
|
|
||||||
ReplayGainMode mode = REPLAY_GAIN_OFF;
|
ReplayGainMode mode = REPLAY_GAIN_OFF;
|
||||||
|
|
||||||
|
@ -68,17 +68,15 @@ class ReplayGainFilter final : public Filter {
|
||||||
PcmVolume pv;
|
PcmVolume pv;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ReplayGainFilter() {
|
ReplayGainFilter(const AudioFormat &audio_format,
|
||||||
|
Mixer *_mixer, unsigned _base)
|
||||||
|
:Filter(audio_format),
|
||||||
|
mixer(_mixer), base(_base), mode(REPLAY_GAIN_OFF) {
|
||||||
info.Clear();
|
info.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetMixer(Mixer *_mixer, unsigned _base) {
|
bool Open(Error &error) {
|
||||||
assert(_mixer == nullptr || (_base > 0 && _base <= 100));
|
return pv.Open(out_audio_format.format, error);
|
||||||
|
|
||||||
mixer = _mixer;
|
|
||||||
base = _base;
|
|
||||||
|
|
||||||
Update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetInfo(const ReplayGainInfo *_info) {
|
void SetInfo(const ReplayGainInfo *_info) {
|
||||||
|
@ -110,12 +108,35 @@ public:
|
||||||
void Update();
|
void Update();
|
||||||
|
|
||||||
/* virtual methods from class Filter */
|
/* virtual methods from class Filter */
|
||||||
AudioFormat Open(AudioFormat &af, Error &error) override;
|
|
||||||
void Close() override;
|
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
Error &error) override;
|
Error &error) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PreparedReplayGainFilter final : public PreparedFilter {
|
||||||
|
/**
|
||||||
|
* If set, then this hardware mixer is used for applying
|
||||||
|
* replay gain, instead of the software volume library.
|
||||||
|
*/
|
||||||
|
Mixer *mixer = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base volume level for scale=1.0, between 1 and 100
|
||||||
|
* (including).
|
||||||
|
*/
|
||||||
|
unsigned base;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void SetMixer(Mixer *_mixer, unsigned _base) {
|
||||||
|
assert(_mixer == nullptr || (_base > 0 && _base <= 100));
|
||||||
|
|
||||||
|
mixer = _mixer;
|
||||||
|
base = _base;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* virtual methods from class Filter */
|
||||||
|
Filter *Open(AudioFormat &af, Error &error) override;
|
||||||
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
ReplayGainFilter::Update()
|
ReplayGainFilter::Update()
|
||||||
{
|
{
|
||||||
|
@ -146,26 +167,23 @@ ReplayGainFilter::Update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Filter *
|
static PreparedFilter *
|
||||||
replay_gain_filter_init(gcc_unused const ConfigBlock &block,
|
replay_gain_filter_init(gcc_unused const ConfigBlock &block,
|
||||||
gcc_unused Error &error)
|
gcc_unused Error &error)
|
||||||
{
|
{
|
||||||
return new ReplayGainFilter();
|
return new PreparedReplayGainFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioFormat
|
Filter *
|
||||||
ReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error)
|
PreparedReplayGainFilter::Open(AudioFormat &af, gcc_unused Error &error)
|
||||||
{
|
{
|
||||||
if (!pv.Open(af.format, error))
|
auto *filter = new ReplayGainFilter(af, mixer, base);
|
||||||
return AudioFormat::Undefined();
|
if (!filter->Open(error)) {
|
||||||
|
delete filter;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
return af;
|
return filter;
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ReplayGainFilter::Close()
|
|
||||||
{
|
|
||||||
pv.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
|
@ -180,10 +198,10 @@ const struct filter_plugin replay_gain_filter_plugin = {
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer,
|
replay_gain_filter_set_mixer(PreparedFilter *_filter, Mixer *mixer,
|
||||||
unsigned base)
|
unsigned base)
|
||||||
{
|
{
|
||||||
ReplayGainFilter *filter = (ReplayGainFilter *)_filter;
|
PreparedReplayGainFilter *filter = (PreparedReplayGainFilter *)_filter;
|
||||||
|
|
||||||
filter->SetMixer(mixer, base);
|
filter->SetMixer(mixer, base);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "ReplayGainInfo.hxx"
|
#include "ReplayGainInfo.hxx"
|
||||||
|
|
||||||
class Filter;
|
class Filter;
|
||||||
|
class PreparedFilter;
|
||||||
class Mixer;
|
class Mixer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,7 +35,7 @@ class Mixer;
|
||||||
* (including).
|
* (including).
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
replay_gain_filter_set_mixer(Filter *_filter, Mixer *mixer,
|
replay_gain_filter_set_mixer(PreparedFilter *_filter, Mixer *mixer,
|
||||||
unsigned base);
|
unsigned base);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -59,6 +59,46 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
class RouteFilter final : public Filter {
|
class RouteFilter final : public Filter {
|
||||||
|
/**
|
||||||
|
* 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"
|
||||||
|
*/
|
||||||
|
const std::array<int8_t, MAX_CHANNELS> sources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual input format of our signal, once opened
|
||||||
|
*/
|
||||||
|
const AudioFormat input_format;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size, in bytes, of each multichannel frame in the
|
||||||
|
* input buffer
|
||||||
|
*/
|
||||||
|
const 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:
|
||||||
|
RouteFilter(const AudioFormat &audio_format, unsigned out_channels,
|
||||||
|
const std::array<int8_t, MAX_CHANNELS> &_sources);
|
||||||
|
|
||||||
|
/* virtual methods from class Filter */
|
||||||
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
|
Error &error) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PreparedRouteFilter final : public PreparedFilter {
|
||||||
/**
|
/**
|
||||||
* The minimum number of channels we need for output
|
* The minimum number of channels we need for output
|
||||||
* to be able to perform all the copies the user has specified
|
* to be able to perform all the copies the user has specified
|
||||||
|
@ -81,33 +121,6 @@ class RouteFilter final : public Filter {
|
||||||
*/
|
*/
|
||||||
std::array<int8_t, MAX_CHANNELS> sources;
|
std::array<int8_t, MAX_CHANNELS> sources;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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:
|
public:
|
||||||
/**
|
/**
|
||||||
* Parse the "routes" section, a string on the form
|
* Parse the "routes" section, a string on the form
|
||||||
|
@ -120,16 +133,13 @@ public:
|
||||||
*/
|
*/
|
||||||
bool Configure(const ConfigBlock &block, Error &error);
|
bool Configure(const ConfigBlock &block, Error &error);
|
||||||
|
|
||||||
/* virtual methods from class Filter */
|
/* virtual methods from class PreparedFilter */
|
||||||
AudioFormat Open(AudioFormat &af, Error &error) override;
|
Filter *Open(AudioFormat &af, Error &error) override;
|
||||||
void Close() override;
|
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
|
||||||
Error &error) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
RouteFilter::Configure(const ConfigBlock &block, Error &error) {
|
PreparedRouteFilter::Configure(const ConfigBlock &block, Error &error)
|
||||||
|
{
|
||||||
/* TODO:
|
/* TODO:
|
||||||
* With a more clever way of marking "don't copy to output N",
|
* With a more clever way of marking "don't copy to output N",
|
||||||
* This could easily be merged into a single loop with some
|
* This could easily be merged into a single loop with some
|
||||||
|
@ -204,10 +214,10 @@ RouteFilter::Configure(const ConfigBlock &block, Error &error) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Filter *
|
static PreparedFilter *
|
||||||
route_filter_init(const ConfigBlock &block, Error &error)
|
route_filter_init(const ConfigBlock &block, Error &error)
|
||||||
{
|
{
|
||||||
RouteFilter *filter = new RouteFilter();
|
auto *filter = new PreparedRouteFilter();
|
||||||
if (!filter->Configure(block, error)) {
|
if (!filter->Configure(block, error)) {
|
||||||
delete filter;
|
delete filter;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -216,28 +226,24 @@ route_filter_init(const ConfigBlock &block, Error &error)
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioFormat
|
RouteFilter::RouteFilter(const AudioFormat &audio_format,
|
||||||
RouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
|
unsigned out_channels,
|
||||||
|
const std::array<int8_t, MAX_CHANNELS> &_sources)
|
||||||
|
:Filter(audio_format), sources(_sources), input_format(audio_format),
|
||||||
|
input_frame_size(input_format.GetFrameSize())
|
||||||
{
|
{
|
||||||
// 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,
|
// Decide on an output format which has enough channels,
|
||||||
// and is otherwise identical
|
// and is otherwise identical
|
||||||
output_format = audio_format;
|
out_audio_format.channels = out_channels;
|
||||||
output_format.channels = min_output_channels;
|
|
||||||
|
|
||||||
// Precalculate this simple value, to speed up allocation later
|
// Precalculate this simple value, to speed up allocation later
|
||||||
output_frame_size = output_format.GetFrameSize();
|
output_frame_size = out_audio_format.GetFrameSize();
|
||||||
|
|
||||||
return output_format;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
Filter *
|
||||||
RouteFilter::Close()
|
PreparedRouteFilter::Open(AudioFormat &audio_format, gcc_unused Error &error)
|
||||||
{
|
{
|
||||||
output_buffer.Clear();
|
return new RouteFilter(audio_format, min_output_channels, sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
|
@ -261,7 +267,7 @@ RouteFilter::FilterPCM(ConstBuffer<void> src, gcc_unused Error &error)
|
||||||
for (unsigned int s=0; s<number_of_frames; ++s) {
|
for (unsigned int s=0; s<number_of_frames; ++s) {
|
||||||
|
|
||||||
// Need to perform one copy per output channel
|
// Need to perform one copy per output channel
|
||||||
for (unsigned int c=0; c<min_output_channels; ++c) {
|
for (unsigned c = 0; c < out_audio_format.channels; ++c) {
|
||||||
if (sources[c] == -1 ||
|
if (sources[c] == -1 ||
|
||||||
(unsigned)sources[c] >= input_format.channels) {
|
(unsigned)sources[c] >= input_format.channels) {
|
||||||
// No source for this destination output,
|
// No source for this destination output,
|
||||||
|
|
|
@ -30,6 +30,13 @@ class VolumeFilter final : public Filter {
|
||||||
PcmVolume pv;
|
PcmVolume pv;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
explicit VolumeFilter(const AudioFormat &audio_format)
|
||||||
|
:Filter(audio_format) {}
|
||||||
|
|
||||||
|
bool Open(Error &error) {
|
||||||
|
return pv.Open(out_audio_format.format, error);
|
||||||
|
}
|
||||||
|
|
||||||
unsigned GetVolume() const {
|
unsigned GetVolume() const {
|
||||||
return pv.GetVolume();
|
return pv.GetVolume();
|
||||||
}
|
}
|
||||||
|
@ -39,32 +46,35 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/* virtual methods from class Filter */
|
/* virtual methods from class Filter */
|
||||||
AudioFormat Open(AudioFormat &af, Error &error) override;
|
|
||||||
void Close() override;
|
|
||||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
ConstBuffer<void> FilterPCM(ConstBuffer<void> src,
|
||||||
Error &error) override;
|
Error &error) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Filter *
|
class PreparedVolumeFilter final : public PreparedFilter {
|
||||||
|
PcmVolume pv;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/* virtual methods from class Filter */
|
||||||
|
Filter *Open(AudioFormat &af, Error &error) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
static PreparedFilter *
|
||||||
volume_filter_init(gcc_unused const ConfigBlock &block,
|
volume_filter_init(gcc_unused const ConfigBlock &block,
|
||||||
gcc_unused Error &error)
|
gcc_unused Error &error)
|
||||||
{
|
{
|
||||||
return new VolumeFilter();
|
return new PreparedVolumeFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioFormat
|
Filter *
|
||||||
VolumeFilter::Open(AudioFormat &audio_format, Error &error)
|
PreparedVolumeFilter::Open(AudioFormat &audio_format, Error &error)
|
||||||
{
|
{
|
||||||
if (!pv.Open(audio_format.format, error))
|
auto *filter = new VolumeFilter(audio_format);
|
||||||
return AudioFormat::Undefined();
|
if (!filter->Open(error)) {
|
||||||
|
delete filter;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
return audio_format;
|
return filter;
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
VolumeFilter::Close()
|
|
||||||
{
|
|
||||||
pv.Close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstBuffer<void>
|
ConstBuffer<void>
|
||||||
|
|
|
@ -34,9 +34,9 @@ AudioOutput::~AudioOutput()
|
||||||
if (mixer != nullptr)
|
if (mixer != nullptr)
|
||||||
mixer_free(mixer);
|
mixer_free(mixer);
|
||||||
|
|
||||||
delete replay_gain_filter;
|
delete prepared_replay_gain_filter;
|
||||||
delete other_replay_gain_filter;
|
delete prepared_other_replay_gain_filter;
|
||||||
delete filter;
|
delete prepared_filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -102,7 +102,7 @@ audio_output_mixer_type(const ConfigBlock &block)
|
||||||
"hardware"));
|
"hardware"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static Filter *
|
static PreparedFilter *
|
||||||
CreateVolumeFilter()
|
CreateVolumeFilter()
|
||||||
{
|
{
|
||||||
return filter_new(&volume_filter_plugin, ConfigBlock(),
|
return filter_new(&volume_filter_plugin, ConfigBlock(),
|
||||||
|
@ -113,12 +113,10 @@ static Mixer *
|
||||||
audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao,
|
audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao,
|
||||||
const ConfigBlock &block,
|
const ConfigBlock &block,
|
||||||
const MixerPlugin *plugin,
|
const MixerPlugin *plugin,
|
||||||
Filter &filter_chain,
|
PreparedFilter &filter_chain,
|
||||||
MixerListener &listener,
|
MixerListener &listener,
|
||||||
Error &error)
|
Error &error)
|
||||||
{
|
{
|
||||||
assert(ao.volume_filter == nullptr);
|
|
||||||
|
|
||||||
Mixer *mixer;
|
Mixer *mixer;
|
||||||
|
|
||||||
switch (audio_output_mixer_type(block)) {
|
switch (audio_output_mixer_type(block)) {
|
||||||
|
@ -144,13 +142,8 @@ audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao,
|
||||||
IgnoreError());
|
IgnoreError());
|
||||||
assert(mixer != nullptr);
|
assert(mixer != nullptr);
|
||||||
|
|
||||||
ao.volume_filter = CreateVolumeFilter();
|
|
||||||
assert(ao.volume_filter != nullptr);
|
|
||||||
|
|
||||||
filter_chain_append(filter_chain, "software_mixer",
|
filter_chain_append(filter_chain, "software_mixer",
|
||||||
ao.volume_filter);
|
ao.volume_filter.Set(CreateVolumeFilter()));
|
||||||
|
|
||||||
software_mixer_set_filter(*mixer, ao.volume_filter);
|
|
||||||
return mixer;
|
return mixer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,23 +183,23 @@ AudioOutput::Configure(const ConfigBlock &block, Error &error)
|
||||||
|
|
||||||
/* set up the filter chain */
|
/* set up the filter chain */
|
||||||
|
|
||||||
filter = filter_chain_new();
|
prepared_filter = filter_chain_new();
|
||||||
assert(filter != nullptr);
|
assert(prepared_filter != nullptr);
|
||||||
|
|
||||||
/* create the normalization filter (if configured) */
|
/* create the normalization filter (if configured) */
|
||||||
|
|
||||||
if (config_get_bool(ConfigOption::VOLUME_NORMALIZATION, false)) {
|
if (config_get_bool(ConfigOption::VOLUME_NORMALIZATION, false)) {
|
||||||
Filter *normalize_filter =
|
auto *normalize_filter =
|
||||||
filter_new(&normalize_filter_plugin, ConfigBlock(),
|
filter_new(&normalize_filter_plugin, ConfigBlock(),
|
||||||
IgnoreError());
|
IgnoreError());
|
||||||
assert(normalize_filter != nullptr);
|
assert(normalize_filter != nullptr);
|
||||||
|
|
||||||
filter_chain_append(*filter, "normalize",
|
filter_chain_append(*prepared_filter, "normalize",
|
||||||
autoconvert_filter_new(normalize_filter));
|
autoconvert_filter_new(normalize_filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
Error filter_error;
|
Error filter_error;
|
||||||
filter_chain_parse(*filter,
|
filter_chain_parse(*prepared_filter,
|
||||||
block.GetBlockValue(AUDIO_FILTERS, ""),
|
block.GetBlockValue(AUDIO_FILTERS, ""),
|
||||||
filter_error);
|
filter_error);
|
||||||
|
|
||||||
|
@ -235,21 +228,21 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao,
|
||||||
block.GetBlockValue("replay_gain_handler", "software");
|
block.GetBlockValue("replay_gain_handler", "software");
|
||||||
|
|
||||||
if (strcmp(replay_gain_handler, "none") != 0) {
|
if (strcmp(replay_gain_handler, "none") != 0) {
|
||||||
ao.replay_gain_filter = filter_new(&replay_gain_filter_plugin,
|
ao.prepared_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
|
||||||
block, IgnoreError());
|
block, IgnoreError());
|
||||||
assert(ao.replay_gain_filter != nullptr);
|
assert(ao.prepared_replay_gain_filter != nullptr);
|
||||||
|
|
||||||
ao.replay_gain_serial = 0;
|
ao.replay_gain_serial = 0;
|
||||||
|
|
||||||
ao.other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
|
ao.prepared_other_replay_gain_filter = filter_new(&replay_gain_filter_plugin,
|
||||||
block,
|
block,
|
||||||
IgnoreError());
|
IgnoreError());
|
||||||
assert(ao.other_replay_gain_filter != nullptr);
|
assert(ao.prepared_other_replay_gain_filter != nullptr);
|
||||||
|
|
||||||
ao.other_replay_gain_serial = 0;
|
ao.other_replay_gain_serial = 0;
|
||||||
} else {
|
} else {
|
||||||
ao.replay_gain_filter = nullptr;
|
ao.prepared_replay_gain_filter = nullptr;
|
||||||
ao.other_replay_gain_filter = nullptr;
|
ao.prepared_other_replay_gain_filter = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* set up the mixer */
|
/* set up the mixer */
|
||||||
|
@ -257,7 +250,7 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao,
|
||||||
Error mixer_error;
|
Error mixer_error;
|
||||||
ao.mixer = audio_output_load_mixer(event_loop, ao, block,
|
ao.mixer = audio_output_load_mixer(event_loop, ao, block,
|
||||||
ao.plugin.mixer_plugin,
|
ao.plugin.mixer_plugin,
|
||||||
*ao.filter,
|
*ao.prepared_filter,
|
||||||
mixer_listener,
|
mixer_listener,
|
||||||
mixer_error);
|
mixer_error);
|
||||||
if (ao.mixer == nullptr && mixer_error.IsDefined())
|
if (ao.mixer == nullptr && mixer_error.IsDefined())
|
||||||
|
@ -269,13 +262,13 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao,
|
||||||
|
|
||||||
if (strcmp(replay_gain_handler, "mixer") == 0) {
|
if (strcmp(replay_gain_handler, "mixer") == 0) {
|
||||||
if (ao.mixer != nullptr)
|
if (ao.mixer != nullptr)
|
||||||
replay_gain_filter_set_mixer(ao.replay_gain_filter,
|
replay_gain_filter_set_mixer(ao.prepared_replay_gain_filter,
|
||||||
ao.mixer, 100);
|
ao.mixer, 100);
|
||||||
else
|
else
|
||||||
FormatError(output_domain,
|
FormatError(output_domain,
|
||||||
"No such mixer for output '%s'", ao.name);
|
"No such mixer for output '%s'", ao.name);
|
||||||
} else if (strcmp(replay_gain_handler, "software") != 0 &&
|
} else if (strcmp(replay_gain_handler, "software") != 0 &&
|
||||||
ao.replay_gain_filter != nullptr) {
|
ao.prepared_replay_gain_filter != nullptr) {
|
||||||
error.Set(config_domain,
|
error.Set(config_domain,
|
||||||
"Invalid \"replay_gain_handler\" value");
|
"Invalid \"replay_gain_handler\" value");
|
||||||
return false;
|
return false;
|
||||||
|
@ -283,11 +276,12 @@ audio_output_setup(EventLoop &event_loop, AudioOutput &ao,
|
||||||
|
|
||||||
/* the "convert" filter must be the last one in the chain */
|
/* the "convert" filter must be the last one in the chain */
|
||||||
|
|
||||||
ao.convert_filter = filter_new(&convert_filter_plugin, ConfigBlock(),
|
auto *f = filter_new(&convert_filter_plugin, ConfigBlock(),
|
||||||
IgnoreError());
|
IgnoreError());
|
||||||
assert(ao.convert_filter != nullptr);
|
assert(f != nullptr);
|
||||||
|
|
||||||
filter_chain_append(*ao.filter, "convert", ao.convert_filter);
|
filter_chain_append(*ao.prepared_filter, "convert",
|
||||||
|
ao.convert_filter.Set(f));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,12 +24,14 @@
|
||||||
#include "pcm/PcmBuffer.hxx"
|
#include "pcm/PcmBuffer.hxx"
|
||||||
#include "pcm/PcmDither.hxx"
|
#include "pcm/PcmDither.hxx"
|
||||||
#include "ReplayGainInfo.hxx"
|
#include "ReplayGainInfo.hxx"
|
||||||
|
#include "filter/Observer.hxx"
|
||||||
#include "thread/Mutex.hxx"
|
#include "thread/Mutex.hxx"
|
||||||
#include "thread/Cond.hxx"
|
#include "thread/Cond.hxx"
|
||||||
#include "thread/Thread.hxx"
|
#include "thread/Thread.hxx"
|
||||||
#include "system/PeriodClock.hxx"
|
#include "system/PeriodClock.hxx"
|
||||||
|
|
||||||
class Error;
|
class Error;
|
||||||
|
class PreparedFilter;
|
||||||
class Filter;
|
class Filter;
|
||||||
class MusicPipe;
|
class MusicPipe;
|
||||||
class EventLoop;
|
class EventLoop;
|
||||||
|
@ -147,6 +149,8 @@ struct AudioOutput {
|
||||||
*/
|
*/
|
||||||
bool woken_for_play = false;
|
bool woken_for_play = false;
|
||||||
|
|
||||||
|
ReplayGainMode replay_gain_mode = REPLAY_GAIN_OFF;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If not nullptr, the device has failed, and this timer is used
|
* If not nullptr, the device has failed, and this timer is used
|
||||||
* to estimate how long it should stay disabled (unless
|
* to estimate how long it should stay disabled (unless
|
||||||
|
@ -187,19 +191,21 @@ struct AudioOutput {
|
||||||
* The filter object of this audio output. This is an
|
* The filter object of this audio output. This is an
|
||||||
* instance of chain_filter_plugin.
|
* instance of chain_filter_plugin.
|
||||||
*/
|
*/
|
||||||
Filter *filter = nullptr;
|
PreparedFilter *prepared_filter = nullptr;
|
||||||
|
Filter *filter_instance;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The #VolumeFilter instance of this audio output. It is
|
* The #VolumeFilter instance of this audio output. It is
|
||||||
* used by the #SoftwareMixer.
|
* used by the #SoftwareMixer.
|
||||||
*/
|
*/
|
||||||
Filter *volume_filter = nullptr;
|
FilterObserver volume_filter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The replay_gain_filter_plugin instance of this audio
|
* The replay_gain_filter_plugin instance of this audio
|
||||||
* output.
|
* output.
|
||||||
*/
|
*/
|
||||||
Filter *replay_gain_filter = nullptr;
|
PreparedFilter *prepared_replay_gain_filter = nullptr;
|
||||||
|
Filter *replay_gain_filter_instance;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The serial number of the last replay gain info. 0 means no
|
* The serial number of the last replay gain info. 0 means no
|
||||||
|
@ -212,7 +218,8 @@ struct AudioOutput {
|
||||||
* output, to be applied to the second chunk during
|
* output, to be applied to the second chunk during
|
||||||
* cross-fading.
|
* cross-fading.
|
||||||
*/
|
*/
|
||||||
Filter *other_replay_gain_filter = nullptr;
|
PreparedFilter *prepared_other_replay_gain_filter = nullptr;
|
||||||
|
Filter *other_replay_gain_filter_instance;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The serial number of the last replay gain info by the
|
* The serial number of the last replay gain info by the
|
||||||
|
@ -226,7 +233,7 @@ struct AudioOutput {
|
||||||
* for converting the input data into the appropriate format
|
* for converting the input data into the appropriate format
|
||||||
* for this audio output.
|
* for this audio output.
|
||||||
*/
|
*/
|
||||||
Filter *convert_filter;
|
FilterObserver convert_filter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The thread handle, or nullptr if the output thread isn't
|
* The thread handle, or nullptr if the output thread isn't
|
||||||
|
@ -346,7 +353,9 @@ struct AudioOutput {
|
||||||
*/
|
*/
|
||||||
void LockRelease();
|
void LockRelease();
|
||||||
|
|
||||||
void SetReplayGainMode(ReplayGainMode mode);
|
void SetReplayGainMode(ReplayGainMode _mode) {
|
||||||
|
replay_gain_mode = _mode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caller must lock the mutex.
|
* Caller must lock the mutex.
|
||||||
|
|
|
@ -68,15 +68,6 @@ AudioOutput::LockCommandWait(Command cmd)
|
||||||
CommandWait(cmd);
|
CommandWait(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
AudioOutput::SetReplayGainMode(ReplayGainMode mode)
|
|
||||||
{
|
|
||||||
if (replay_gain_filter != nullptr)
|
|
||||||
replay_gain_filter_set_mode(replay_gain_filter, mode);
|
|
||||||
if (other_replay_gain_filter != nullptr)
|
|
||||||
replay_gain_filter_set_mode(other_replay_gain_filter, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::LockEnableWait()
|
AudioOutput::LockEnableWait()
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
#include "filter/FilterInternal.hxx"
|
#include "filter/FilterInternal.hxx"
|
||||||
#include "filter/plugins/ConvertFilterPlugin.hxx"
|
#include "filter/plugins/ConvertFilterPlugin.hxx"
|
||||||
#include "filter/plugins/ReplayGainFilterPlugin.hxx"
|
#include "filter/plugins/ReplayGainFilterPlugin.hxx"
|
||||||
|
#include "mixer/MixerInternal.hxx"
|
||||||
|
#include "mixer/plugins/SoftwareMixerPlugin.hxx"
|
||||||
#include "player/Control.hxx"
|
#include "player/Control.hxx"
|
||||||
#include "MusicPipe.hxx"
|
#include "MusicPipe.hxx"
|
||||||
#include "MusicChunk.hxx"
|
#include "MusicChunk.hxx"
|
||||||
|
@ -94,37 +96,44 @@ AudioOutput::OpenFilter(AudioFormat &format, Error &error_r)
|
||||||
assert(format.IsValid());
|
assert(format.IsValid());
|
||||||
|
|
||||||
/* the replay_gain filter cannot fail here */
|
/* the replay_gain filter cannot fail here */
|
||||||
if (replay_gain_filter != nullptr &&
|
if (prepared_replay_gain_filter != nullptr) {
|
||||||
!replay_gain_filter->Open(format, error_r).IsDefined())
|
replay_gain_filter_instance =
|
||||||
return AudioFormat::Undefined();
|
prepared_replay_gain_filter->Open(format, error_r);
|
||||||
|
if (replay_gain_filter_instance == nullptr)
|
||||||
|
return AudioFormat::Undefined();
|
||||||
|
}
|
||||||
|
|
||||||
if (other_replay_gain_filter != nullptr &&
|
if (prepared_other_replay_gain_filter != nullptr) {
|
||||||
!other_replay_gain_filter->Open(format, error_r).IsDefined()) {
|
other_replay_gain_filter_instance =
|
||||||
if (replay_gain_filter != nullptr)
|
prepared_other_replay_gain_filter->Open(format, error_r);
|
||||||
replay_gain_filter->Close();
|
if (other_replay_gain_filter_instance == nullptr) {
|
||||||
|
delete replay_gain_filter_instance;
|
||||||
|
return AudioFormat::Undefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter_instance = prepared_filter->Open(format, error_r);
|
||||||
|
if (filter_instance == nullptr) {
|
||||||
|
delete other_replay_gain_filter_instance;
|
||||||
|
delete replay_gain_filter_instance;
|
||||||
return AudioFormat::Undefined();
|
return AudioFormat::Undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
const AudioFormat af = filter->Open(format, error_r);
|
if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
|
||||||
if (!af.IsDefined()) {
|
software_mixer_set_filter(*mixer, volume_filter.Get());
|
||||||
if (replay_gain_filter != nullptr)
|
|
||||||
replay_gain_filter->Close();
|
|
||||||
if (other_replay_gain_filter != nullptr)
|
|
||||||
other_replay_gain_filter->Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return af;
|
return filter_instance->GetOutAudioFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioOutput::CloseFilter()
|
AudioOutput::CloseFilter()
|
||||||
{
|
{
|
||||||
if (replay_gain_filter != nullptr)
|
if (mixer != nullptr && mixer->IsPlugin(software_mixer_plugin))
|
||||||
replay_gain_filter->Close();
|
software_mixer_set_filter(*mixer, nullptr);
|
||||||
if (other_replay_gain_filter != nullptr)
|
|
||||||
other_replay_gain_filter->Close();
|
|
||||||
|
|
||||||
filter->Close();
|
delete replay_gain_filter_instance;
|
||||||
|
delete other_replay_gain_filter_instance;
|
||||||
|
delete filter_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
|
@ -186,7 +195,7 @@ AudioOutput::Open()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!convert_filter_set(convert_filter, out_audio_format,
|
if (!convert_filter_set(convert_filter.Get(), out_audio_format,
|
||||||
error)) {
|
error)) {
|
||||||
FormatError(error, "Failed to convert for \"%s\" [%s]",
|
FormatError(error, "Failed to convert for \"%s\" [%s]",
|
||||||
name, plugin.name);
|
name, plugin.name);
|
||||||
|
@ -282,7 +291,7 @@ AudioOutput::ReopenFilter()
|
||||||
const AudioFormat filter_audio_format =
|
const AudioFormat filter_audio_format =
|
||||||
OpenFilter(in_audio_format, error);
|
OpenFilter(in_audio_format, error);
|
||||||
if (!filter_audio_format.IsDefined() ||
|
if (!filter_audio_format.IsDefined() ||
|
||||||
!convert_filter_set(convert_filter, out_audio_format,
|
!convert_filter_set(convert_filter.Get(), out_audio_format,
|
||||||
error)) {
|
error)) {
|
||||||
FormatError(error,
|
FormatError(error,
|
||||||
"Failed to open filter for \"%s\" [%s]",
|
"Failed to open filter for \"%s\" [%s]",
|
||||||
|
@ -368,6 +377,9 @@ ao_chunk_data(AudioOutput *ao, const MusicChunk *chunk,
|
||||||
assert(data.size % ao->in_audio_format.GetFrameSize() == 0);
|
assert(data.size % ao->in_audio_format.GetFrameSize() == 0);
|
||||||
|
|
||||||
if (!data.IsEmpty() && replay_gain_filter != nullptr) {
|
if (!data.IsEmpty() && replay_gain_filter != nullptr) {
|
||||||
|
replay_gain_filter_set_mode(replay_gain_filter,
|
||||||
|
ao->replay_gain_mode);
|
||||||
|
|
||||||
if (chunk->replay_gain_serial != *replay_gain_serial_p) {
|
if (chunk->replay_gain_serial != *replay_gain_serial_p) {
|
||||||
replay_gain_filter_set_info(replay_gain_filter,
|
replay_gain_filter_set_info(replay_gain_filter,
|
||||||
chunk->replay_gain_serial != 0
|
chunk->replay_gain_serial != 0
|
||||||
|
@ -390,7 +402,7 @@ static ConstBuffer<void>
|
||||||
ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
|
ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
|
||||||
{
|
{
|
||||||
ConstBuffer<void> data =
|
ConstBuffer<void> data =
|
||||||
ao_chunk_data(ao, chunk, ao->replay_gain_filter,
|
ao_chunk_data(ao, chunk, ao->replay_gain_filter_instance,
|
||||||
&ao->replay_gain_serial);
|
&ao->replay_gain_serial);
|
||||||
if (data.IsEmpty())
|
if (data.IsEmpty())
|
||||||
return data;
|
return data;
|
||||||
|
@ -400,7 +412,7 @@ ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
|
||||||
if (chunk->other != nullptr) {
|
if (chunk->other != nullptr) {
|
||||||
ConstBuffer<void> other_data =
|
ConstBuffer<void> other_data =
|
||||||
ao_chunk_data(ao, chunk->other,
|
ao_chunk_data(ao, chunk->other,
|
||||||
ao->other_replay_gain_filter,
|
ao->other_replay_gain_filter_instance,
|
||||||
&ao->other_replay_gain_serial);
|
&ao->other_replay_gain_serial);
|
||||||
if (other_data.IsNull())
|
if (other_data.IsNull())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -443,7 +455,7 @@ ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
|
||||||
/* apply filter chain */
|
/* apply filter chain */
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
data = ao->filter->FilterPCM(data, error);
|
data = ao->filter_instance->FilterPCM(data, error);
|
||||||
if (data.IsNull()) {
|
if (data.IsNull()) {
|
||||||
FormatError(error, "\"%s\" [%s] failed to filter",
|
FormatError(error, "\"%s\" [%s] failed to filter",
|
||||||
ao->name, ao->plugin.name);
|
ao->name, ao->plugin.name);
|
||||||
|
@ -456,7 +468,7 @@ ao_filter_chunk(AudioOutput *ao, const MusicChunk *chunk)
|
||||||
inline bool
|
inline bool
|
||||||
AudioOutput::PlayChunk(const MusicChunk *chunk)
|
AudioOutput::PlayChunk(const MusicChunk *chunk)
|
||||||
{
|
{
|
||||||
assert(filter != nullptr);
|
assert(filter_instance != nullptr);
|
||||||
|
|
||||||
if (tags && gcc_unlikely(chunk->tag != nullptr)) {
|
if (tags && gcc_unlikely(chunk->tag != nullptr)) {
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
#include "system/FatalError.hxx"
|
#include "system/FatalError.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
@ -46,7 +48,7 @@ mixer_set_volume(gcc_unused Mixer *mixer,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Filter *
|
static PreparedFilter *
|
||||||
load_filter(const char *name)
|
load_filter(const char *name)
|
||||||
{
|
{
|
||||||
const auto *param = config_find_block(ConfigBlockOption::AUDIO_FILTER,
|
const auto *param = config_find_block(ConfigBlockOption::AUDIO_FILTER,
|
||||||
|
@ -57,7 +59,7 @@ load_filter(const char *name)
|
||||||
}
|
}
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
Filter *filter = filter_configured_new(*param, error);
|
auto *filter = filter_configured_new(*param, error);
|
||||||
if (filter == NULL) {
|
if (filter == NULL) {
|
||||||
LogError(error, "Failed to load filter");
|
LogError(error, "Failed to load filter");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -97,20 +99,22 @@ try {
|
||||||
|
|
||||||
/* initialize the filter */
|
/* initialize the filter */
|
||||||
|
|
||||||
Filter *filter = load_filter(argv[2]);
|
std::unique_ptr<PreparedFilter> prepared_filter(load_filter(argv[2]));
|
||||||
if (filter == NULL)
|
if (!prepared_filter)
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
/* open the filter */
|
/* open the filter */
|
||||||
|
|
||||||
Error error;
|
Error error;
|
||||||
const AudioFormat out_audio_format = filter->Open(audio_format, error);
|
std::unique_ptr<Filter> filter(prepared_filter->Open(audio_format,
|
||||||
if (!out_audio_format.IsDefined()) {
|
error));
|
||||||
|
if (!filter) {
|
||||||
LogError(error, "Failed to open filter");
|
LogError(error, "Failed to open filter");
|
||||||
delete filter;
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AudioFormat out_audio_format = filter->GetOutAudioFormat();
|
||||||
|
|
||||||
fprintf(stderr, "audio_format=%s\n",
|
fprintf(stderr, "audio_format=%s\n",
|
||||||
audio_format_to_string(out_audio_format, &af_string));
|
audio_format_to_string(out_audio_format, &af_string));
|
||||||
|
|
||||||
|
@ -127,8 +131,6 @@ try {
|
||||||
error);
|
error);
|
||||||
if (dest.IsNull()) {
|
if (dest.IsNull()) {
|
||||||
LogError(error, "filter/Filter failed");
|
LogError(error, "filter/Filter failed");
|
||||||
filter->Close();
|
|
||||||
delete filter;
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,17 +138,12 @@ try {
|
||||||
if (nbytes < 0) {
|
if (nbytes < 0) {
|
||||||
fprintf(stderr, "Failed to write: %s\n",
|
fprintf(stderr, "Failed to write: %s\n",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
filter->Close();
|
|
||||||
delete filter;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* cleanup and exit */
|
/* cleanup and exit */
|
||||||
|
|
||||||
filter->Close();
|
|
||||||
delete filter;
|
|
||||||
|
|
||||||
config_global_finish();
|
config_global_finish();
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
|
|
Loading…
Reference in New Issue