diff --git a/Makefile.am b/Makefile.am index f82ff3e32..2b08c1f8e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1333,6 +1333,7 @@ OUTPUT_API_SRC = \ src/output/Wrapper.hxx \ src/output/Registry.cxx src/output/Registry.hxx \ src/output/MultipleOutputs.cxx src/output/MultipleOutputs.hxx \ + src/output/SharedPipeConsumer.cxx src/output/SharedPipeConsumer.hxx \ src/output/OutputThread.cxx \ src/output/Domain.cxx src/output/Domain.hxx \ src/output/OutputControl.cxx \ diff --git a/src/output/Internal.cxx b/src/output/Internal.cxx index 07bb5b128..a6045f487 100644 --- a/src/output/Internal.cxx +++ b/src/output/Internal.cxx @@ -19,10 +19,6 @@ #include "config.h" #include "Internal.hxx" -#include "MusicPipe.hxx" -#include "MusicChunk.hxx" - -#include bool AudioOutput::IsChunkConsumed(const MusicChunk &chunk) const @@ -30,16 +26,5 @@ AudioOutput::IsChunkConsumed(const MusicChunk &chunk) const if (!open) return true; - if (current_chunk == nullptr) - return false; - - assert(&chunk == current_chunk || - pipe->Contains(current_chunk)); - - if (&chunk != current_chunk) { - assert(chunk.next != nullptr); - return true; - } - - return current_chunk_finished && chunk.next == nullptr; + return pipe.IsConsumed(chunk); } diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx index 0832a7012..1d9d5c1f7 100644 --- a/src/output/Internal.hxx +++ b/src/output/Internal.hxx @@ -20,6 +20,7 @@ #ifndef MPD_OUTPUT_INTERNAL_HXX #define MPD_OUTPUT_INTERNAL_HXX +#include "SharedPipeConsumer.hxx" #include "AudioFormat.hxx" #include "pcm/PcmBuffer.hxx" #include "pcm/PcmDither.hxx" @@ -247,13 +248,7 @@ struct AudioOutput { Command command = Command::NONE; /** - * The music pipe which provides music chunks to be played. - */ - const MusicPipe *pipe; - - /** - * This mutex protects #open, #fail_timer, #current_chunk and - * #current_chunk_finished. + * This mutex protects #open, #fail_timer, #pipe. */ Mutex mutex; @@ -270,17 +265,9 @@ struct AudioOutput { PlayerControl *player_control; /** - * The #MusicChunk which is currently being played. All - * chunks before this one may be returned to the - * #music_buffer, because they are not going to be used by - * this output anymore. + * A reference to the #MusicPipe and the current position. */ - const MusicChunk *current_chunk; - - /** - * Has the output finished playing #current_chunk? - */ - bool current_chunk_finished; + SharedPipeConsumer pipe; /** * Throws #std::runtime_error on error. @@ -445,9 +432,6 @@ private: */ bool WaitForDelay(); - gcc_pure - const MusicChunk *GetNextChunk() const; - bool PlayChunk(const MusicChunk *chunk); /** diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx index 2da317eff..a9228b457 100644 --- a/src/output/MultipleOutputs.cxx +++ b/src/output/MultipleOutputs.cxx @@ -262,7 +262,7 @@ MultipleOutputs::IsChunkConsumed(const MusicChunk *chunk) const } inline void -MultipleOutputs::ClearTailChunk(gcc_unused const MusicChunk *chunk, +MultipleOutputs::ClearTailChunk(const MusicChunk *chunk, bool *locked) { assert(chunk->next == nullptr); @@ -281,9 +281,7 @@ MultipleOutputs::ClearTailChunk(gcc_unused const MusicChunk *chunk, continue; } - assert(ao->current_chunk == chunk); - assert(ao->current_chunk_finished); - ao->current_chunk = nullptr; + ao->pipe.ClearTail(*chunk); } } diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx index 15125e2cc..c8b25c101 100644 --- a/src/output/OutputControl.cxx +++ b/src/output/OutputControl.cxx @@ -113,11 +113,10 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) fail_timer.Reset(); if (open && audio_format == in_audio_format) { - assert(pipe == &mp || (always_on && pause)); + assert(&pipe.GetPipe() == &mp || (always_on && pause)); if (pause) { - current_chunk = nullptr; - pipe = ∓ + pipe.Init(mp); /* unpause with the CANCEL command; this is a hack, but suits well for forcing the thread @@ -133,9 +132,8 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) } in_audio_format = audio_format; - current_chunk = nullptr; - pipe = ∓ + pipe.Init(mp); if (!thread.IsDefined()) StartThread(); diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx index b9d7a43a9..18af1923e 100644 --- a/src/output/OutputThread.cxx +++ b/src/output/OutputThread.cxx @@ -134,8 +134,6 @@ AudioOutput::Open() struct audio_format_string af_string; assert(!open); - assert(pipe != nullptr); - assert(current_chunk == nullptr); assert(in_audio_format.IsValid()); fail_timer.Reset(); @@ -239,9 +237,8 @@ AudioOutput::Close(bool drain) { assert(open); - pipe = nullptr; + pipe.Deinit(); - current_chunk = nullptr; open = false; const ScopeUnlock unlock(mutex); @@ -292,9 +289,9 @@ AudioOutput::Reopen() { if (!config_audio_format.IsFullyDefined()) { if (open) { - const MusicPipe *mp = pipe; + const MusicPipe &old_pipe = pipe.GetPipe(); Close(true); - pipe = mp; + pipe.Init(old_pipe); } /* no audio format is configured: copy in->out, let @@ -499,50 +496,28 @@ AudioOutput::PlayChunk(const MusicChunk *chunk) return true; } -inline const MusicChunk * -AudioOutput::GetNextChunk() const -{ - return current_chunk != nullptr - /* continue the previous play() call */ - ? current_chunk->next - /* get the first chunk from the pipe */ - : pipe->Peek(); -} - inline bool AudioOutput::Play() { - assert(pipe != nullptr); - - const MusicChunk *chunk = GetNextChunk(); + const MusicChunk *chunk = pipe.Get(); if (chunk == nullptr) /* no chunk available */ return false; - current_chunk_finished = false; - assert(!in_playback_loop); in_playback_loop = true; while (chunk != nullptr && command == Command::NONE) { - assert(!current_chunk_finished); - - current_chunk = chunk; - - if (!PlayChunk(chunk)) { - assert(current_chunk == nullptr); + if (!PlayChunk(chunk)) break; - } - assert(current_chunk == chunk); - chunk = chunk->next; + pipe.Consume(*chunk); + chunk = pipe.Get(); } assert(in_playback_loop); in_playback_loop = false; - current_chunk_finished = true; - const ScopeUnlock unlock(mutex); player_control->LockSignal(); @@ -626,7 +601,6 @@ AudioOutput::Task() case Command::CLOSE: assert(open); - assert(pipe != nullptr); Close(false); CommandFinished(); @@ -651,8 +625,8 @@ AudioOutput::Task() case Command::DRAIN: if (open) { - assert(current_chunk == nullptr); - assert(pipe->Peek() == nullptr); + assert(pipe.IsInitial()); + assert(pipe.GetPipe().Peek() == nullptr); const ScopeUnlock unlock(mutex); ao_plugin_drain(this); @@ -662,7 +636,7 @@ AudioOutput::Task() continue; case Command::CANCEL: - current_chunk = nullptr; + pipe.Cancel(); if (open) { const ScopeUnlock unlock(mutex); @@ -673,7 +647,7 @@ AudioOutput::Task() continue; case Command::KILL: - current_chunk = nullptr; + pipe.Cancel(); CommandFinished(); return; } diff --git a/src/output/SharedPipeConsumer.cxx b/src/output/SharedPipeConsumer.cxx new file mode 100644 index 000000000..c52e8e02d --- /dev/null +++ b/src/output/SharedPipeConsumer.cxx @@ -0,0 +1,58 @@ +/* + * 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 "SharedPipeConsumer.hxx" +#include "MusicChunk.hxx" +#include "MusicPipe.hxx" + +const MusicChunk * +SharedPipeConsumer::Get() +{ + if (chunk != nullptr) { + if (!consumed) + return chunk; + + if (chunk->next == nullptr) + return nullptr; + + consumed = false; + return chunk = chunk->next; + } else { + /* get the first chunk from the pipe */ + consumed = false; + return chunk = pipe->Peek(); + } +} + +bool +SharedPipeConsumer::IsConsumed(const MusicChunk &_chunk) const +{ + if (chunk == nullptr) + return false; + + assert(&_chunk == chunk || pipe->Contains(chunk)); + + if (&_chunk != chunk) { + assert(_chunk.next != nullptr); + return true; + } + + return consumed && _chunk.next == nullptr; +} diff --git a/src/output/SharedPipeConsumer.hxx b/src/output/SharedPipeConsumer.hxx new file mode 100644 index 000000000..fe5039901 --- /dev/null +++ b/src/output/SharedPipeConsumer.hxx @@ -0,0 +1,97 @@ +/* + * 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 SHARED_PIPE_CONSUMER_HXX +#define SHARED_PIPE_CONSUMER_HXX + +#include "check.h" +#include "Compiler.h" + +#include + +struct MusicChunk; +class MusicPipe; + +/** + * A utility class which helps with consuming data from a #MusicPipe. + */ +class SharedPipeConsumer { + /** + * The music pipe which provides music chunks to be played. + */ + const MusicPipe *pipe = nullptr; + + /** + * The #MusicChunk which is currently being played. All + * chunks before this one may be returned to the #MusicBuffer, + * because they are not going to be used by this output + * anymore. + */ + const MusicChunk *chunk; + + /** + * Has the output finished playing #current_chunk? + */ + bool consumed; + +public: + void Init(const MusicPipe &_pipe) { + pipe = &_pipe; + chunk = nullptr; + } + + void Deinit() { + pipe = nullptr; + chunk = nullptr; + } + + const MusicPipe &GetPipe() { + assert(pipe != nullptr); + + return *pipe; + } + + bool IsInitial() { + return chunk == nullptr; + } + + void Cancel() { + chunk = nullptr; + } + + const MusicChunk *Get(); + + void Consume(gcc_unused const MusicChunk &_chunk) { + assert(chunk != nullptr); + assert(chunk == &_chunk); + + consumed = true; + } + + gcc_pure + bool IsConsumed(const MusicChunk &_chunk) const; + + void ClearTail(gcc_unused const MusicChunk &_chunk) { + assert(chunk == &_chunk); + assert(consumed); + chunk = nullptr; + } +}; + +#endif