filter/FilterInternal: split class Filter, add class PreparedFilter

For easier state management inside filter plugins.
This commit is contained in:
Max Kellermann 2016-06-22 11:15:49 +02:00
parent 5c75096bcd
commit 3a21241248
26 changed files with 615 additions and 388 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

115
src/filter/Observer.cxx Normal file
View File

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

47
src/filter/Observer.hxx Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
/** /**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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