summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--src/filter/Filter.cxx27
-rw-r--r--src/filter/Filter.hxx6
-rw-r--r--src/filter/Observer.cxx4
-rw-r--r--src/filter/plugins/ChainFilterPlugin.cxx49
-rw-r--r--src/output/Source.cxx8
-rw-r--r--src/output/Source.hxx5
-rw-r--r--src/output/Thread.cxx31
-rw-r--r--test/run_convert.cxx9
9 files changed, 134 insertions, 7 deletions
diff --git a/Makefile.am b/Makefile.am
index 42611701d..15c829861 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1644,7 +1644,7 @@ libfilter_api_a_SOURCES = \
src/filter/Observer.cxx src/filter/Observer.hxx \
src/filter/FilterPlugin.hxx \
src/filter/Prepared.hxx \
- src/filter/Filter.hxx
+ src/filter/Filter.cxx src/filter/Filter.hxx
libfilter_plugins_a_SOURCES = \
src/AudioCompress/config.h \
diff --git a/src/filter/Filter.cxx b/src/filter/Filter.cxx
new file mode 100644
index 000000000..6a291cde9
--- /dev/null
+++ b/src/filter/Filter.cxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2003-2017 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 "Filter.hxx"
+#include "util/ConstBuffer.hxx"
+
+ConstBuffer<void>
+Filter::Flush()
+{
+ return nullptr;
+}
diff --git a/src/filter/Filter.hxx b/src/filter/Filter.hxx
index 6af002adc..7d714df79 100644
--- a/src/filter/Filter.hxx
+++ b/src/filter/Filter.hxx
@@ -62,6 +62,12 @@ public:
* or Reset() call)
*/
virtual ConstBuffer<void> FilterPCM(ConstBuffer<void> src) = 0;
+
+ /**
+ * Flush pending data and return it. This should be called
+ * repepatedly until it returns nullptr.
+ */
+ virtual ConstBuffer<void> Flush();
};
#endif
diff --git a/src/filter/Observer.cxx b/src/filter/Observer.cxx
index d51d6329e..a56efc70f 100644
--- a/src/filter/Observer.cxx
+++ b/src/filter/Observer.cxx
@@ -75,6 +75,10 @@ public:
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override {
return filter->FilterPCM(src);
}
+
+ ConstBuffer<void> Flush() noexcept override {
+ return filter->Flush();
+ }
};
Filter *
diff --git a/src/filter/plugins/ChainFilterPlugin.cxx b/src/filter/plugins/ChainFilterPlugin.cxx
index 6fa273729..d95ae3768 100644
--- a/src/filter/plugins/ChainFilterPlugin.cxx
+++ b/src/filter/plugins/ChainFilterPlugin.cxx
@@ -43,6 +43,11 @@ class ChainFilter final : public Filter {
std::list<Child> children;
+ /**
+ * The child which will be flushed in the next Flush() call.
+ */
+ std::list<Child>::iterator flushing = children.end();
+
public:
explicit ChainFilter(AudioFormat _audio_format)
:Filter(_audio_format) {}
@@ -54,11 +59,20 @@ public:
assert(out_audio_format.IsValid());
children.emplace_back(name, std::move(filter));
+
+ RewindFlush();
}
/* virtual methods from class Filter */
void Reset() noexcept override;
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
+ ConstBuffer<void> Flush() override;
+
+private:
+ void RewindFlush() {
+ flushing = children.begin();
+ }
+
};
class PreparedChainFilter final : public PreparedFilter {
@@ -118,23 +132,46 @@ PreparedChainFilter::Open(AudioFormat &in_audio_format)
void
ChainFilter::Reset() noexcept
{
+ RewindFlush();
+
for (auto &child : children)
child.filter->Reset();
}
-ConstBuffer<void>
-ChainFilter::FilterPCM(ConstBuffer<void> src)
+template<typename I>
+static ConstBuffer<void>
+ApplyFilterChain(I begin, I end, ConstBuffer<void> src)
{
- for (auto &child : children) {
+ for (auto i = begin; i != end; ++i)
/* feed the output of the previous filter as input
into the current one */
- src = child.filter->FilterPCM(src);
- }
+ src = i->filter->FilterPCM(src);
- /* return the output of the last filter */
return src;
}
+ConstBuffer<void>
+ChainFilter::FilterPCM(ConstBuffer<void> src)
+{
+ RewindFlush();
+
+ /* return the output of the last filter */
+ return ApplyFilterChain(children.begin(), children.end(), src);
+}
+
+ConstBuffer<void>
+ChainFilter::Flush()
+{
+ for (auto end = children.end(); flushing != end; ++flushing) {
+ auto data = flushing->filter->Flush();
+ if (!data.IsNull())
+ return ApplyFilterChain(std::next(flushing), end,
+ data);
+ }
+
+ return nullptr;
+}
+
std::unique_ptr<PreparedFilter>
filter_chain_new() noexcept
{
diff --git a/src/output/Source.cxx b/src/output/Source.cxx
index d6c110a38..fdaff77f2 100644
--- a/src/output/Source.cxx
+++ b/src/output/Source.cxx
@@ -244,3 +244,11 @@ AudioOutputSource::ConsumeData(size_t nbytes) noexcept
if (pending_data.empty())
pipe.Consume(*std::exchange(current_chunk, nullptr));
}
+
+ConstBuffer<void>
+AudioOutputSource::Flush()
+{
+ return filter
+ ? filter->Flush()
+ : nullptr;
+}
diff --git a/src/output/Source.hxx b/src/output/Source.hxx
index 1b26a3378..435ce52b2 100644
--- a/src/output/Source.hxx
+++ b/src/output/Source.hxx
@@ -195,6 +195,11 @@ public:
pipe.ClearTail(chunk);
}
+ /**
+ * Wrapper for Filter::Flush().
+ */
+ ConstBuffer<void> Flush();
+
private:
void OpenFilter(AudioFormat audio_format,
PreparedFilter *prepared_replay_gain_filter,
diff --git a/src/output/Thread.cxx b/src/output/Thread.cxx
index ac382864a..bad9e0a73 100644
--- a/src/output/Thread.cxx
+++ b/src/output/Thread.cxx
@@ -362,11 +362,42 @@ AudioOutputControl::InternalPause() noexcept
skip_delay = true;
}
+static void
+PlayFull(FilteredAudioOutput &output, ConstBuffer<void> _buffer)
+{
+ auto buffer = ConstBuffer<uint8_t>::FromVoid(_buffer);
+
+ while (!buffer.empty()) {
+ size_t nbytes = output.Play(buffer.data, buffer.size);
+ assert(nbytes > 0);
+
+ buffer.skip_front(nbytes);
+ }
+
+}
+
inline void
AudioOutputControl::InternalDrain() noexcept
{
const ScopeUnlock unlock(mutex);
+ try {
+ /* flush the filter and play its remaining output */
+
+ while (true) {
+ auto buffer = source.Flush();
+ if (buffer.IsNull())
+ break;
+
+ PlayFull(*output, buffer);
+ }
+ } catch (...) {
+ FormatError(std::current_exception(),
+ "Failed to flush filter on %s", GetLogName());
+ InternalCloseError(std::current_exception());
+ return;
+ }
+
output->Drain();
}
diff --git a/test/run_convert.cxx b/test/run_convert.cxx
index 1d11e44d0..64ca98bcc 100644
--- a/test/run_convert.cxx
+++ b/test/run_convert.cxx
@@ -86,6 +86,15 @@ try {
output.size);
}
+ while (true) {
+ auto output = state.Flush();
+ if (output.IsNull())
+ break;
+
+ gcc_unused ssize_t ignored = write(1, output.data,
+ output.size);
+ }
+
state.Close();
return EXIT_SUCCESS;