From 86d3b25aec8fb42c20aa45a53d2d15c7bf34c7ea Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 26 Dec 2016 13:53:22 +0100 Subject: [PATCH] output/Source: add Fill(), ReadTag(), PeekData(), ConsumeData() Don't expose MusicChunk instances, provide higher-level access to chunk contents. --- src/output/Internal.hxx | 5 +-- src/output/OutputThread.cxx | 67 +++++++++++++++++++------------------ src/output/Source.cxx | 35 +++++++++++++++++++ src/output/Source.hxx | 67 +++++++++++++++++++++++++++++++------ 4 files changed, 130 insertions(+), 44 deletions(-) diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx index b15138fcb..69972524b 100644 --- a/src/output/Internal.hxx +++ b/src/output/Internal.hxx @@ -30,7 +30,6 @@ #include "system/PeriodClock.hxx" class PreparedFilter; -class Filter; class MusicPipe; class EventLoop; class Mixer; @@ -455,7 +454,9 @@ private: */ bool WaitForDelay(); - bool PlayChunk(const MusicChunk &chunk); + bool FillSourceOrClose(); + + bool PlayChunk(); /** * Plays all remaining chunks, until the tail of the pipe has diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx index f9d78bac7..7a343229a 100644 --- a/src/output/OutputThread.cxx +++ b/src/output/OutputThread.cxx @@ -261,36 +261,43 @@ AudioOutput::WaitForDelay() } } +bool +AudioOutput::FillSourceOrClose() +try { + return source.Fill(); +} catch (const std::runtime_error &e) { + FormatError(e, "Failed to filter for output \"%s\" [%s]", + name, plugin.name); + + Close(false); + + /* don't automatically reopen this device for 10 + seconds */ + fail_timer.Update(); + return false; +} + inline bool -AudioOutput::PlayChunk(const MusicChunk &chunk) +AudioOutput::PlayChunk() { - if (tags && gcc_unlikely(chunk.tag != nullptr)) { - const ScopeUnlock unlock(mutex); - try { - ao_plugin_send_tag(this, *chunk.tag); - } catch (const std::runtime_error &e) { - FormatError(e, "Failed to send tag to \"%s\" [%s]", - name, plugin.name); + if (tags) { + const auto *tag = source.ReadTag(); + if (tag != nullptr) { + const ScopeUnlock unlock(mutex); + try { + ao_plugin_send_tag(this, *tag); + } catch (const std::runtime_error &e) { + FormatError(e, "Failed to send tag to \"%s\" [%s]", + name, plugin.name); + } } } - ConstBuffer data; + while (command == Command::NONE) { + const auto data = source.PeekData(); + if (data.IsEmpty()) + break; - try { - data = data.FromVoid(source.FilterChunk(chunk)); - } catch (const std::runtime_error &e) { - FormatError(e, "Failed to filter for output \"%s\" [%s]", - name, plugin.name); - - Close(false); - - /* don't automatically reopen this device for 10 - seconds */ - fail_timer.Update(); - return false; - } - - while (!data.IsEmpty() && command == Command::NONE) { if (!WaitForDelay()) break; @@ -319,7 +326,7 @@ AudioOutput::PlayChunk(const MusicChunk &chunk) assert(nbytes % out_audio_format.GetFrameSize() == 0); - data.skip_front(nbytes); + source.ConsumeData(nbytes); } return true; @@ -328,8 +335,7 @@ AudioOutput::PlayChunk(const MusicChunk &chunk) inline bool AudioOutput::Play() { - const MusicChunk *chunk = source.Get(); - if (chunk == nullptr) + if (!FillSourceOrClose()) /* no chunk available */ return false; @@ -356,12 +362,9 @@ AudioOutput::Play() n = 0; } - if (!PlayChunk(*chunk)) + if (!PlayChunk()) break; - - source.Consume(*chunk); - chunk = source.Get(); - } while (chunk != nullptr); + } while (FillSourceOrClose()); const ScopeUnlock unlock(mutex); client->ChunksConsumed(); diff --git a/src/output/Source.cxx b/src/output/Source.cxx index 728508705..9649c9858 100644 --- a/src/output/Source.cxx +++ b/src/output/Source.cxx @@ -185,3 +185,38 @@ AudioOutputSource::FilterChunk(const MusicChunk &chunk) return filter_instance->FilterPCM(data); } + +bool +AudioOutputSource::Fill() +{ + if (current_chunk != nullptr && pending_tag == nullptr && + pending_data.IsEmpty()) + pipe.Consume(*std::exchange(current_chunk, nullptr)); + + if (current_chunk != nullptr) + return true; + + current_chunk = pipe.Get(); + if (current_chunk == nullptr) + return false; + + pending_tag = current_chunk->tag; + + try { + pending_data = pending_data.FromVoid(FilterChunk(*current_chunk)); + } catch (...) { + current_chunk = nullptr; + throw; + } + + return true; +} + +void +AudioOutputSource::ConsumeData(size_t nbytes) noexcept +{ + pending_data.skip_front(nbytes); + + if (pending_data.IsEmpty()) + pipe.Consume(*std::exchange(current_chunk, nullptr)); +} diff --git a/src/output/Source.hxx b/src/output/Source.hxx index e945b5602..63504a37d 100644 --- a/src/output/Source.hxx +++ b/src/output/Source.hxx @@ -27,11 +27,15 @@ #include "ReplayGainMode.hxx" #include "pcm/PcmBuffer.hxx" #include "pcm/PcmDither.hxx" +#include "util/ConstBuffer.hxx" + +#include #include +#include -template struct ConstBuffer; struct MusicChunk; +struct Tag; class Filter; class PreparedFilter; @@ -96,6 +100,23 @@ class AudioOutputSource { */ Filter *filter_instance = nullptr; + /** + * The #MusicChunk currently being processed (see + * #pending_tag, #pending_data). + */ + const MusicChunk *current_chunk = nullptr; + + /** + * The #Tag to be processed by the #AudioOutput. + */ + const Tag *pending_tag; + + /** + * Filtered #MusicChunk PCM data to be processed by the + * #AudioOutput. + */ + ConstBuffer pending_data; + public: void SetReplayGainMode(ReplayGainMode _mode) { replay_gain_mode = _mode; @@ -117,21 +138,47 @@ public: void Close(); void Cancel() { + current_chunk = nullptr; pipe.Cancel(); } - const MusicChunk *Get() { - assert(IsOpen()); + /** + * Ensure that ReadTag() or PeekData() return any input. + * + * Throws std::runtime_error on error + * + * @return true if any input is available, false if the source + * has (temporarily?) run empty + */ + bool Fill(); - return pipe.Get(); + /** + * Reads the #Tag to be processed. Be sure to call Fill() + * successfully before calling this metohd. + */ + const Tag *ReadTag() noexcept { + assert(current_chunk != nullptr); + + return std::exchange(pending_tag, nullptr); } - void Consume(const MusicChunk &chunk) { - assert(IsOpen()); - - pipe.Consume(chunk); + /** + * Returns the remaining filtered PCM data be played. The + * caller shall use ConsumeData() to mark portions of the + * return value as "consumed". + * + * Be sure to call Fill() successfully before calling this + * metohd. + */ + ConstBuffer PeekData() const noexcept { + return pending_data.ToVoid(); } + /** + * Mark portions of the PeekData() return value as "consumed". + */ + void ConsumeData(size_t nbytes) noexcept; + bool IsChunkConsumed(const MusicChunk &chunk) const { assert(IsOpen()); @@ -142,8 +189,6 @@ public: pipe.ClearTail(chunk); } - ConstBuffer FilterChunk(const MusicChunk &chunk); - private: void OpenFilter(AudioFormat audio_format, PreparedFilter *prepared_replay_gain_filter, @@ -155,6 +200,8 @@ private: ConstBuffer GetChunkData(const MusicChunk &chunk, Filter *replay_gain_filter, unsigned *replay_gain_serial_p); + + ConstBuffer FilterChunk(const MusicChunk &chunk); }; #endif