aboutsummaryrefslogtreecommitdiffstats
path: root/src/decoder
diff options
context:
space:
mode:
Diffstat (limited to 'src/decoder')
-rw-r--r--src/decoder/DecoderAPI.cxx597
-rw-r--r--src/decoder/DecoderAPI.hxx213
-rw-r--r--src/decoder/DecoderBuffer.cxx168
-rw-r--r--src/decoder/DecoderBuffer.hxx111
-rw-r--r--src/decoder/DecoderCommand.hxx32
-rw-r--r--src/decoder/DecoderControl.cxx140
-rw-r--r--src/decoder/DecoderControl.hxx395
-rw-r--r--src/decoder/DecoderError.cxx23
-rw-r--r--src/decoder/DecoderError.hxx25
-rw-r--r--src/decoder/DecoderInternal.cxx101
-rw-r--r--src/decoder/DecoderInternal.hxx125
-rw-r--r--src/decoder/DecoderList.cxx186
-rw-r--r--src/decoder/DecoderList.hxx89
-rw-r--r--src/decoder/DecoderPlugin.cxx42
-rw-r--r--src/decoder/DecoderPlugin.hxx180
-rw-r--r--src/decoder/DecoderPrint.cxx55
-rw-r--r--src/decoder/DecoderPrint.hxx28
-rw-r--r--src/decoder/DecoderThread.cxx476
-rw-r--r--src/decoder/DecoderThread.hxx28
-rw-r--r--src/decoder/plugins/AdPlugDecoderPlugin.cxx (renamed from src/decoder/AdPlugDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/AdPlugDecoderPlugin.h (renamed from src/decoder/AdPlugDecoderPlugin.h)0
-rw-r--r--src/decoder/plugins/AudiofileDecoderPlugin.cxx (renamed from src/decoder/AudiofileDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/AudiofileDecoderPlugin.hxx (renamed from src/decoder/AudiofileDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/DsdLib.cxx (renamed from src/decoder/DsdLib.cxx)2
-rw-r--r--src/decoder/plugins/DsdLib.hxx (renamed from src/decoder/DsdLib.hxx)0
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.cxx (renamed from src/decoder/DsdiffDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.hxx (renamed from src/decoder/DsdiffDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.cxx (renamed from src/decoder/DsfDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.hxx (renamed from src/decoder/DsfDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/FaadDecoderPlugin.cxx (renamed from src/decoder/FaadDecoderPlugin.cxx)4
-rw-r--r--src/decoder/plugins/FaadDecoderPlugin.hxx (renamed from src/decoder/FaadDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.cxx (renamed from src/decoder/FfmpegDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.hxx (renamed from src/decoder/FfmpegDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/FfmpegMetaData.cxx (renamed from src/decoder/FfmpegMetaData.cxx)0
-rw-r--r--src/decoder/plugins/FfmpegMetaData.hxx (renamed from src/decoder/FfmpegMetaData.hxx)0
-rw-r--r--src/decoder/plugins/FlacCommon.cxx (renamed from src/decoder/FlacCommon.cxx)0
-rw-r--r--src/decoder/plugins/FlacCommon.hxx (renamed from src/decoder/FlacCommon.hxx)2
-rw-r--r--src/decoder/plugins/FlacDecoderPlugin.cxx (renamed from src/decoder/FlacDecoderPlugin.cxx)0
-rw-r--r--src/decoder/plugins/FlacDecoderPlugin.h (renamed from src/decoder/FlacDecoderPlugin.h)0
-rw-r--r--src/decoder/plugins/FlacDomain.cxx (renamed from src/decoder/FlacDomain.cxx)0
-rw-r--r--src/decoder/plugins/FlacDomain.hxx (renamed from src/decoder/FlacDomain.hxx)0
-rw-r--r--src/decoder/plugins/FlacIOHandle.cxx (renamed from src/decoder/FlacIOHandle.cxx)0
-rw-r--r--src/decoder/plugins/FlacIOHandle.hxx (renamed from src/decoder/FlacIOHandle.hxx)0
-rw-r--r--src/decoder/plugins/FlacInput.cxx (renamed from src/decoder/FlacInput.cxx)2
-rw-r--r--src/decoder/plugins/FlacInput.hxx (renamed from src/decoder/FlacInput.hxx)0
-rw-r--r--src/decoder/plugins/FlacMetadata.cxx (renamed from src/decoder/FlacMetadata.cxx)0
-rw-r--r--src/decoder/plugins/FlacMetadata.hxx (renamed from src/decoder/FlacMetadata.hxx)0
-rw-r--r--src/decoder/plugins/FlacPcm.cxx (renamed from src/decoder/FlacPcm.cxx)0
-rw-r--r--src/decoder/plugins/FlacPcm.hxx (renamed from src/decoder/FlacPcm.hxx)0
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.cxx (renamed from src/decoder/FluidsynthDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.hxx (renamed from src/decoder/FluidsynthDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/GmeDecoderPlugin.cxx (renamed from src/decoder/GmeDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/GmeDecoderPlugin.hxx (renamed from src/decoder/GmeDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.cxx (renamed from src/decoder/MadDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.hxx (renamed from src/decoder/MadDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/MikmodDecoderPlugin.cxx (renamed from src/decoder/MikmodDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/MikmodDecoderPlugin.hxx (renamed from src/decoder/MikmodDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/ModplugDecoderPlugin.cxx (renamed from src/decoder/ModplugDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/ModplugDecoderPlugin.hxx (renamed from src/decoder/ModplugDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/MpcdecDecoderPlugin.cxx (renamed from src/decoder/MpcdecDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/MpcdecDecoderPlugin.hxx (renamed from src/decoder/MpcdecDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/Mpg123DecoderPlugin.cxx (renamed from src/decoder/Mpg123DecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/Mpg123DecoderPlugin.hxx (renamed from src/decoder/Mpg123DecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/OggCodec.cxx (renamed from src/decoder/OggCodec.cxx)2
-rw-r--r--src/decoder/plugins/OggCodec.hxx (renamed from src/decoder/OggCodec.hxx)0
-rw-r--r--src/decoder/plugins/OggFind.cxx (renamed from src/decoder/OggFind.cxx)0
-rw-r--r--src/decoder/plugins/OggFind.hxx (renamed from src/decoder/OggFind.hxx)0
-rw-r--r--src/decoder/plugins/OggSyncState.hxx (renamed from src/decoder/OggSyncState.hxx)0
-rw-r--r--src/decoder/plugins/OggUtil.cxx (renamed from src/decoder/OggUtil.cxx)2
-rw-r--r--src/decoder/plugins/OggUtil.hxx (renamed from src/decoder/OggUtil.hxx)0
-rw-r--r--src/decoder/plugins/OpusDecoderPlugin.cxx (renamed from src/decoder/OpusDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/OpusDecoderPlugin.h (renamed from src/decoder/OpusDecoderPlugin.h)0
-rw-r--r--src/decoder/plugins/OpusDomain.cxx (renamed from src/decoder/OpusDomain.cxx)0
-rw-r--r--src/decoder/plugins/OpusDomain.hxx (renamed from src/decoder/OpusDomain.hxx)0
-rw-r--r--src/decoder/plugins/OpusHead.cxx (renamed from src/decoder/OpusHead.cxx)0
-rw-r--r--src/decoder/plugins/OpusHead.hxx (renamed from src/decoder/OpusHead.hxx)0
-rw-r--r--src/decoder/plugins/OpusReader.hxx (renamed from src/decoder/OpusReader.hxx)0
-rw-r--r--src/decoder/plugins/OpusTags.cxx (renamed from src/decoder/OpusTags.cxx)0
-rw-r--r--src/decoder/plugins/OpusTags.hxx (renamed from src/decoder/OpusTags.hxx)0
-rw-r--r--src/decoder/plugins/PcmDecoderPlugin.cxx (renamed from src/decoder/PcmDecoderPlugin.cxx)4
-rw-r--r--src/decoder/plugins/PcmDecoderPlugin.hxx (renamed from src/decoder/PcmDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.cxx (renamed from src/decoder/SidplayDecoderPlugin.cxx)0
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.hxx (renamed from src/decoder/SidplayDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/SndfileDecoderPlugin.cxx (renamed from src/decoder/SndfileDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/SndfileDecoderPlugin.hxx (renamed from src/decoder/SndfileDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/VorbisComments.cxx (renamed from src/decoder/VorbisComments.cxx)0
-rw-r--r--src/decoder/plugins/VorbisComments.hxx (renamed from src/decoder/VorbisComments.hxx)0
-rw-r--r--src/decoder/plugins/VorbisDecoderPlugin.cxx (renamed from src/decoder/VorbisDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/VorbisDecoderPlugin.h (renamed from src/decoder/VorbisDecoderPlugin.h)0
-rw-r--r--src/decoder/plugins/VorbisDomain.cxx (renamed from src/decoder/VorbisDomain.cxx)0
-rw-r--r--src/decoder/plugins/VorbisDomain.hxx (renamed from src/decoder/VorbisDomain.hxx)0
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.cxx (renamed from src/decoder/WavpackDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.hxx (renamed from src/decoder/WavpackDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/WildmidiDecoderPlugin.cxx (renamed from src/decoder/WildmidiDecoderPlugin.cxx)2
-rw-r--r--src/decoder/plugins/WildmidiDecoderPlugin.hxx (renamed from src/decoder/WildmidiDecoderPlugin.hxx)0
-rw-r--r--src/decoder/plugins/XiphTags.cxx (renamed from src/decoder/XiphTags.cxx)0
-rw-r--r--src/decoder/plugins/XiphTags.hxx (renamed from src/decoder/XiphTags.hxx)0
97 files changed, 3040 insertions, 26 deletions
diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx
new file mode 100644
index 000000000..ccb98a230
--- /dev/null
+++ b/src/decoder/DecoderAPI.cxx
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2003-2014 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 "DecoderAPI.hxx"
+#include "DecoderError.hxx"
+#include "pcm/PcmConvert.hxx"
+#include "AudioConfig.hxx"
+#include "ReplayGainConfig.hxx"
+#include "MusicChunk.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicPipe.hxx"
+#include "DecoderControl.hxx"
+#include "DecoderInternal.hxx"
+#include "DetachedSong.hxx"
+#include "InputStream.hxx"
+#include "util/Error.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+
+void
+decoder_initialized(Decoder &decoder,
+ const AudioFormat audio_format,
+ bool seekable, float total_time)
+{
+ DecoderControl &dc = decoder.dc;
+ struct audio_format_string af_string;
+
+ assert(dc.state == DecoderState::START);
+ assert(dc.pipe != nullptr);
+ assert(decoder.convert == nullptr);
+ assert(decoder.stream_tag == nullptr);
+ assert(decoder.decoder_tag == nullptr);
+ assert(!decoder.seeking);
+ assert(audio_format.IsDefined());
+ assert(audio_format.IsValid());
+
+ dc.in_audio_format = audio_format;
+ dc.out_audio_format = getOutputAudioFormat(audio_format);
+
+ dc.seekable = seekable;
+ dc.total_time = total_time;
+
+ FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
+ audio_format_to_string(dc.in_audio_format, &af_string),
+ seekable ? "true" : "false");
+
+ if (dc.in_audio_format != dc.out_audio_format) {
+ FormatDebug(decoder_domain, "converting to %s",
+ audio_format_to_string(dc.out_audio_format,
+ &af_string));
+
+ decoder.convert = new PcmConvert();
+
+ Error error;
+ if (!decoder.convert->Open(dc.in_audio_format,
+ dc.out_audio_format,
+ error))
+ decoder.error = std::move(error);
+ }
+
+ dc.Lock();
+ dc.state = DecoderState::DECODE;
+ dc.client_cond.signal();
+ dc.Unlock();
+}
+
+/**
+ * Checks if we need an "initial seek". If so, then the initial seek
+ * is prepared, and the function returns true.
+ */
+gcc_pure
+static bool
+decoder_prepare_initial_seek(Decoder &decoder)
+{
+ const DecoderControl &dc = decoder.dc;
+ assert(dc.pipe != nullptr);
+
+ if (dc.state != DecoderState::DECODE)
+ /* wait until the decoder has finished initialisation
+ (reading file headers etc.) before emitting the
+ virtual "SEEK" command */
+ return false;
+
+ if (decoder.initial_seek_running)
+ /* initial seek has already begun - override any other
+ command */
+ return true;
+
+ if (decoder.initial_seek_pending) {
+ if (!dc.seekable) {
+ /* seeking is not possible */
+ decoder.initial_seek_pending = false;
+ return false;
+ }
+
+ if (dc.command == DecoderCommand::NONE) {
+ /* begin initial seek */
+
+ decoder.initial_seek_pending = false;
+ decoder.initial_seek_running = true;
+ return true;
+ }
+
+ /* skip initial seek when there's another command
+ (e.g. STOP) */
+
+ decoder.initial_seek_pending = false;
+ }
+
+ return false;
+}
+
+/**
+ * Returns the current decoder command. May return a "virtual"
+ * synthesized command, e.g. to seek to the beginning of the CUE
+ * track.
+ */
+gcc_pure
+static DecoderCommand
+decoder_get_virtual_command(Decoder &decoder)
+{
+ if (decoder.error.IsDefined())
+ /* an error has occurred: stop the decoder plugin */
+ return DecoderCommand::STOP;
+
+ const DecoderControl &dc = decoder.dc;
+ assert(dc.pipe != nullptr);
+
+ if (decoder_prepare_initial_seek(decoder))
+ return DecoderCommand::SEEK;
+
+ return dc.command;
+}
+
+DecoderCommand
+decoder_get_command(Decoder &decoder)
+{
+ return decoder_get_virtual_command(decoder);
+}
+
+void
+decoder_command_finished(Decoder &decoder)
+{
+ DecoderControl &dc = decoder.dc;
+
+ dc.Lock();
+
+ assert(dc.command != DecoderCommand::NONE ||
+ decoder.initial_seek_running);
+ assert(dc.command != DecoderCommand::SEEK ||
+ decoder.initial_seek_running ||
+ dc.seek_error || decoder.seeking);
+ assert(dc.pipe != nullptr);
+
+ if (decoder.initial_seek_running) {
+ assert(!decoder.seeking);
+ assert(decoder.chunk == nullptr);
+ assert(dc.pipe->IsEmpty());
+
+ decoder.initial_seek_running = false;
+ decoder.timestamp = dc.start_ms / 1000.;
+ dc.Unlock();
+ return;
+ }
+
+ if (decoder.seeking) {
+ decoder.seeking = false;
+
+ /* delete frames from the old song position */
+
+ if (decoder.chunk != nullptr) {
+ dc.buffer->Return(decoder.chunk);
+ decoder.chunk = nullptr;
+ }
+
+ dc.pipe->Clear(*dc.buffer);
+
+ decoder.timestamp = dc.seek_where;
+ }
+
+ dc.command = DecoderCommand::NONE;
+ dc.client_cond.signal();
+ dc.Unlock();
+}
+
+double decoder_seek_where(gcc_unused Decoder & decoder)
+{
+ const DecoderControl &dc = decoder.dc;
+
+ assert(dc.pipe != nullptr);
+
+ if (decoder.initial_seek_running)
+ return dc.start_ms / 1000.;
+
+ assert(dc.command == DecoderCommand::SEEK);
+
+ decoder.seeking = true;
+
+ return dc.seek_where;
+}
+
+void decoder_seek_error(Decoder & decoder)
+{
+ DecoderControl &dc = decoder.dc;
+
+ assert(dc.pipe != nullptr);
+
+ if (decoder.initial_seek_running) {
+ /* d'oh, we can't seek to the sub-song start position,
+ what now? - no idea, ignoring the problem for now. */
+ decoder.initial_seek_running = false;
+ return;
+ }
+
+ assert(dc.command == DecoderCommand::SEEK);
+
+ dc.seek_error = true;
+ decoder.seeking = false;
+
+ decoder_command_finished(decoder);
+}
+
+/**
+ * Should be read operation be cancelled? That is the case when the
+ * player thread has sent a command such as "STOP".
+ */
+gcc_pure
+static inline bool
+decoder_check_cancel_read(const Decoder *decoder)
+{
+ if (decoder == nullptr)
+ return false;
+
+ const DecoderControl &dc = decoder->dc;
+ if (dc.command == DecoderCommand::NONE)
+ return false;
+
+ /* ignore the SEEK command during initialization, the plugin
+ should handle that after it has initialized successfully */
+ if (dc.command == DecoderCommand::SEEK &&
+ (dc.state == DecoderState::START || decoder->seeking))
+ return false;
+
+ return true;
+}
+
+size_t
+decoder_read(Decoder *decoder,
+ InputStream &is,
+ void *buffer, size_t length)
+{
+ /* XXX don't allow decoder==nullptr */
+
+ assert(decoder == nullptr ||
+ decoder->dc.state == DecoderState::START ||
+ decoder->dc.state == DecoderState::DECODE);
+ assert(buffer != nullptr);
+
+ if (length == 0)
+ return 0;
+
+ is.Lock();
+
+ while (true) {
+ if (decoder_check_cancel_read(decoder)) {
+ is.Unlock();
+ return 0;
+ }
+
+ if (is.IsAvailable())
+ break;
+
+ is.cond.wait(is.mutex);
+ }
+
+ Error error;
+ size_t nbytes = is.Read(buffer, length, error);
+ assert(nbytes == 0 || !error.IsDefined());
+ assert(nbytes > 0 || error.IsDefined() || is.IsEOF());
+
+ is.Unlock();
+
+ if (gcc_unlikely(nbytes == 0 && error.IsDefined()))
+ LogError(error);
+
+ return nbytes;
+}
+
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+ void *_buffer, size_t size)
+{
+ uint8_t *buffer = (uint8_t *)_buffer;
+
+ while (size > 0) {
+ size_t nbytes = decoder_read(decoder, is, buffer, size);
+ if (nbytes == 0)
+ return false;
+
+ buffer += nbytes;
+ size -= nbytes;
+ }
+
+ return true;
+}
+
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size)
+{
+ while (size > 0) {
+ char buffer[1024];
+ size_t nbytes = decoder_read(decoder, is, buffer,
+ std::min(sizeof(buffer), size));
+ if (nbytes == 0)
+ return false;
+
+ size -= nbytes;
+ }
+
+ return true;
+}
+
+void
+decoder_timestamp(Decoder &decoder, double t)
+{
+ assert(t >= 0);
+
+ decoder.timestamp = t;
+}
+
+/**
+ * Sends a #tag as-is to the music pipe. Flushes the current chunk
+ * (decoder.chunk) if there is one.
+ */
+static DecoderCommand
+do_send_tag(Decoder &decoder, const Tag &tag)
+{
+ struct music_chunk *chunk;
+
+ if (decoder.chunk != nullptr) {
+ /* there is a partial chunk - flush it, we want the
+ tag in a new chunk */
+ decoder.FlushChunk();
+ }
+
+ assert(decoder.chunk == nullptr);
+
+ chunk = decoder.GetChunk();
+ if (chunk == nullptr) {
+ assert(decoder.dc.command != DecoderCommand::NONE);
+ return decoder.dc.command;
+ }
+
+ chunk->tag = new Tag(tag);
+ return DecoderCommand::NONE;
+}
+
+static bool
+update_stream_tag(Decoder &decoder, InputStream *is)
+{
+ Tag *tag;
+
+ tag = is != nullptr
+ ? is->LockReadTag()
+ : nullptr;
+ if (tag == nullptr) {
+ tag = decoder.song_tag;
+ if (tag == nullptr)
+ return false;
+
+ /* no stream tag present - submit the song tag
+ instead */
+ decoder.song_tag = nullptr;
+ }
+
+ delete decoder.stream_tag;
+ decoder.stream_tag = tag;
+ return true;
+}
+
+DecoderCommand
+decoder_data(Decoder &decoder,
+ InputStream *is,
+ const void *data, size_t length,
+ uint16_t kbit_rate)
+{
+ DecoderControl &dc = decoder.dc;
+ DecoderCommand cmd;
+
+ assert(dc.state == DecoderState::DECODE);
+ assert(dc.pipe != nullptr);
+ assert(length % dc.in_audio_format.GetFrameSize() == 0);
+
+ dc.Lock();
+ cmd = decoder_get_virtual_command(decoder);
+ dc.Unlock();
+
+ if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK ||
+ length == 0)
+ return cmd;
+
+ /* send stream tags */
+
+ if (update_stream_tag(decoder, is)) {
+ if (decoder.decoder_tag != nullptr) {
+ /* merge with tag from decoder plugin */
+ Tag *tag = Tag::Merge(*decoder.decoder_tag,
+ *decoder.stream_tag);
+ cmd = do_send_tag(decoder, *tag);
+ delete tag;
+ } else
+ /* send only the stream tag */
+ cmd = do_send_tag(decoder, *decoder.stream_tag);
+
+ if (cmd != DecoderCommand::NONE)
+ return cmd;
+ }
+
+ if (decoder.convert != nullptr) {
+ assert(dc.in_audio_format != dc.out_audio_format);
+
+ Error error;
+ data = decoder.convert->Convert(data, length,
+ &length,
+ error);
+ if (data == nullptr) {
+ /* the PCM conversion has failed - stop
+ playback, since we have no better way to
+ bail out */
+ LogError(error);
+ return DecoderCommand::STOP;
+ }
+ } else {
+ assert(dc.in_audio_format == dc.out_audio_format);
+ }
+
+ while (length > 0) {
+ struct music_chunk *chunk;
+ bool full;
+
+ chunk = decoder.GetChunk();
+ if (chunk == nullptr) {
+ assert(dc.command != DecoderCommand::NONE);
+ return dc.command;
+ }
+
+ const auto dest =
+ chunk->Write(dc.out_audio_format,
+ decoder.timestamp -
+ dc.song->GetStartMS() / 1000.0,
+ kbit_rate);
+ if (dest.IsNull()) {
+ /* the chunk is full, flush it */
+ decoder.FlushChunk();
+ continue;
+ }
+
+ size_t nbytes = dest.size;
+ assert(nbytes > 0);
+
+ if (nbytes > length)
+ nbytes = length;
+
+ /* copy the buffer */
+
+ memcpy(dest.data, data, nbytes);
+
+ /* expand the music pipe chunk */
+
+ full = chunk->Expand(dc.out_audio_format, nbytes);
+ if (full) {
+ /* the chunk is full, flush it */
+ decoder.FlushChunk();
+ }
+
+ data = (const uint8_t *)data + nbytes;
+ length -= nbytes;
+
+ decoder.timestamp += (double)nbytes /
+ dc.out_audio_format.GetTimeToSize();
+
+ if (dc.end_ms > 0 &&
+ decoder.timestamp >= dc.end_ms / 1000.0)
+ /* the end of this range has been reached:
+ stop decoding */
+ return DecoderCommand::STOP;
+ }
+
+ return DecoderCommand::NONE;
+}
+
+DecoderCommand
+decoder_tag(Decoder &decoder, InputStream *is,
+ Tag &&tag)
+{
+ gcc_unused const DecoderControl &dc = decoder.dc;
+ DecoderCommand cmd;
+
+ assert(dc.state == DecoderState::DECODE);
+ assert(dc.pipe != nullptr);
+
+ /* save the tag */
+
+ delete decoder.decoder_tag;
+ decoder.decoder_tag = new Tag(tag);
+
+ /* check for a new stream tag */
+
+ update_stream_tag(decoder, is);
+
+ /* check if we're seeking */
+
+ if (decoder_prepare_initial_seek(decoder))
+ /* during initial seek, no music chunk must be created
+ until seeking is finished; skip the rest of the
+ function here */
+ return DecoderCommand::SEEK;
+
+ /* send tag to music pipe */
+
+ if (decoder.stream_tag != nullptr) {
+ /* merge with tag from input stream */
+ Tag *merged;
+
+ merged = Tag::Merge(*decoder.stream_tag,
+ *decoder.decoder_tag);
+ cmd = do_send_tag(decoder, *merged);
+ delete merged;
+ } else
+ /* send only the decoder tag */
+ cmd = do_send_tag(decoder, *decoder.decoder_tag);
+
+ return cmd;
+}
+
+void
+decoder_replay_gain(Decoder &decoder,
+ const ReplayGainInfo *replay_gain_info)
+{
+ if (replay_gain_info != nullptr) {
+ static unsigned serial;
+ if (++serial == 0)
+ serial = 1;
+
+ if (REPLAY_GAIN_OFF != replay_gain_mode) {
+ ReplayGainMode rgm = replay_gain_mode;
+ if (rgm != REPLAY_GAIN_ALBUM)
+ rgm = REPLAY_GAIN_TRACK;
+
+ const auto &tuple = replay_gain_info->tuples[rgm];
+ const auto scale =
+ tuple.CalculateScale(replay_gain_preamp,
+ replay_gain_missing_preamp,
+ replay_gain_limit);
+ decoder.dc.replay_gain_db = 20.0 * log10f(scale);
+ }
+
+ decoder.replay_gain_info = *replay_gain_info;
+ decoder.replay_gain_serial = serial;
+
+ if (decoder.chunk != nullptr) {
+ /* flush the current chunk because the new
+ replay gain values affect the following
+ samples */
+ decoder.FlushChunk();
+ }
+ } else
+ decoder.replay_gain_serial = 0;
+}
+
+void
+decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp)
+{
+ DecoderControl &dc = decoder.dc;
+
+ dc.SetMixRamp(std::move(mix_ramp));
+}
diff --git a/src/decoder/DecoderAPI.hxx b/src/decoder/DecoderAPI.hxx
new file mode 100644
index 000000000..0ce861fb1
--- /dev/null
+++ b/src/decoder/DecoderAPI.hxx
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2003-2014 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.
+ */
+
+/*! \file
+ * \brief The MPD Decoder API
+ *
+ * This is the public API which is used by decoder plugins to
+ * communicate with the mpd core.
+ */
+
+#ifndef MPD_DECODER_API_HXX
+#define MPD_DECODER_API_HXX
+
+// IWYU pragma: begin_exports
+
+#include "check.h"
+#include "DecoderCommand.hxx"
+#include "DecoderPlugin.hxx"
+#include "ReplayGainInfo.hxx"
+#include "tag/Tag.hxx"
+#include "AudioFormat.hxx"
+#include "MixRampInfo.hxx"
+#include "ConfigData.hxx"
+
+// IWYU pragma: end_exports
+
+/**
+ * Notify the player thread that it has finished initialization and
+ * that it has read the song's meta data.
+ *
+ * @param decoder the decoder object
+ * @param audio_format the audio format which is going to be sent to
+ * decoder_data()
+ * @param seekable true if the song is seekable
+ * @param total_time the total number of seconds in this song; -1 if unknown
+ */
+void
+decoder_initialized(Decoder &decoder,
+ AudioFormat audio_format,
+ bool seekable, float total_time);
+
+/**
+ * Determines the pending decoder command.
+ *
+ * @param decoder the decoder object
+ * @return the current command, or DecoderCommand::NONE if there is no
+ * command pending
+ */
+gcc_pure
+DecoderCommand
+decoder_get_command(Decoder &decoder);
+
+/**
+ * Called by the decoder when it has performed the requested command
+ * (dc->command). This function resets dc->command and wakes up the
+ * player thread.
+ *
+ * @param decoder the decoder object
+ */
+void
+decoder_command_finished(Decoder &decoder);
+
+/**
+ * Call this when you have received the DecoderCommand::SEEK command.
+ *
+ * @param decoder the decoder object
+ * @return the destination position for the week
+ */
+gcc_pure
+double
+decoder_seek_where(Decoder &decoder);
+
+/**
+ * Call this instead of decoder_command_finished() when seeking has
+ * failed.
+ *
+ * @param decoder the decoder object
+ */
+void
+decoder_seek_error(Decoder &decoder);
+
+/**
+ * Blocking read from the input stream.
+ *
+ * @param decoder the decoder object
+ * @param is the input stream to read from
+ * @param buffer the destination buffer
+ * @param length the maximum number of bytes to read
+ * @return the number of bytes read, or 0 if one of the following
+ * occurs: end of file; error; command (like SEEK or STOP).
+ */
+size_t
+decoder_read(Decoder *decoder, InputStream &is,
+ void *buffer, size_t length);
+
+static inline size_t
+decoder_read(Decoder &decoder, InputStream &is,
+ void *buffer, size_t length)
+{
+ return decoder_read(&decoder, is, buffer, length);
+}
+
+/**
+ * Blocking read from the input stream. Attempts to fill the buffer
+ * completely; there is no partial result.
+ *
+ * @return true on success, false on error or command or not enough
+ * data
+ */
+bool
+decoder_read_full(Decoder *decoder, InputStream &is,
+ void *buffer, size_t size);
+
+/**
+ * Skip data on the #InputStream.
+ *
+ * @return true on success, false on error or command
+ */
+bool
+decoder_skip(Decoder *decoder, InputStream &is, size_t size);
+
+/**
+ * Sets the time stamp for the next data chunk [seconds]. The MPD
+ * core automatically counts it up, and a decoder plugin only needs to
+ * use this function if it thinks that adding to the time stamp based
+ * on the buffer size won't work.
+ */
+void
+decoder_timestamp(Decoder &decoder, double t);
+
+/**
+ * This function is called by the decoder plugin when it has
+ * successfully decoded block of input data.
+ *
+ * @param decoder the decoder object
+ * @param is an input stream which is buffering while we are waiting
+ * for the player
+ * @param data the source buffer
+ * @param length the number of bytes in the buffer
+ * @return the current command, or DecoderCommand::NONE if there is no
+ * command pending
+ */
+DecoderCommand
+decoder_data(Decoder &decoder, InputStream *is,
+ const void *data, size_t length,
+ uint16_t kbit_rate);
+
+static inline DecoderCommand
+decoder_data(Decoder &decoder, InputStream &is,
+ const void *data, size_t length,
+ uint16_t kbit_rate)
+{
+ return decoder_data(decoder, &is, data, length, kbit_rate);
+}
+
+/**
+ * This function is called by the decoder plugin when it has
+ * successfully decoded a tag.
+ *
+ * @param decoder the decoder object
+ * @param is an input stream which is buffering while we are waiting
+ * for the player
+ * @param tag the tag to send
+ * @return the current command, or DecoderCommand::NONE if there is no
+ * command pending
+ */
+DecoderCommand
+decoder_tag(Decoder &decoder, InputStream *is, Tag &&tag);
+
+static inline DecoderCommand
+decoder_tag(Decoder &decoder, InputStream &is, Tag &&tag)
+{
+ return decoder_tag(decoder, &is, std::move(tag));
+}
+
+/**
+ * Set replay gain values for the following chunks.
+ *
+ * @param decoder the decoder object
+ * @param rgi the replay_gain_info object; may be nullptr to invalidate
+ * the previous replay gain values
+ */
+void
+decoder_replay_gain(Decoder &decoder,
+ const ReplayGainInfo *replay_gain_info);
+
+/**
+ * Store MixRamp tags.
+ *
+ * @param decoder the decoder object
+ * @param mixramp_start the mixramp_start tag; may be nullptr to invalidate
+ * @param mixramp_end the mixramp_end tag; may be nullptr to invalidate
+ */
+void
+decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp);
+
+#endif
diff --git a/src/decoder/DecoderBuffer.cxx b/src/decoder/DecoderBuffer.cxx
new file mode 100644
index 000000000..47671513e
--- /dev/null
+++ b/src/decoder/DecoderBuffer.cxx
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2003-2014 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 "DecoderBuffer.hxx"
+#include "DecoderAPI.hxx"
+#include "util/ConstBuffer.hxx"
+#include "util/VarSize.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+struct DecoderBuffer {
+ Decoder *decoder;
+ InputStream *is;
+
+ /** the allocated size of the buffer */
+ size_t size;
+
+ /** the current length of the buffer */
+ size_t length;
+
+ /** number of bytes already consumed at the beginning of the
+ buffer */
+ size_t consumed;
+
+ /** the actual buffer (dynamic size) */
+ unsigned char data[sizeof(size_t)];
+
+ DecoderBuffer(Decoder *_decoder, InputStream &_is,
+ size_t _size)
+ :decoder(_decoder), is(&_is),
+ size(_size), length(0), consumed(0) {}
+};
+
+DecoderBuffer *
+decoder_buffer_new(Decoder *decoder, InputStream &is,
+ size_t size)
+{
+ assert(size > 0);
+
+ return NewVarSize<DecoderBuffer>(sizeof(DecoderBuffer::data),
+ size,
+ decoder, is, size);
+}
+
+void
+decoder_buffer_free(DecoderBuffer *buffer)
+{
+ assert(buffer != nullptr);
+
+ DeleteVarSize(buffer);
+}
+
+bool
+decoder_buffer_is_empty(const DecoderBuffer *buffer)
+{
+ return buffer->consumed == buffer->length;
+}
+
+bool
+decoder_buffer_is_full(const DecoderBuffer *buffer)
+{
+ return buffer->consumed == 0 && buffer->length == buffer->size;
+}
+
+void
+decoder_buffer_clear(DecoderBuffer *buffer)
+{
+ buffer->length = buffer->consumed = 0;
+}
+
+static void
+decoder_buffer_shift(DecoderBuffer *buffer)
+{
+ assert(buffer->consumed > 0);
+
+ buffer->length -= buffer->consumed;
+ memmove(buffer->data, buffer->data + buffer->consumed, buffer->length);
+ buffer->consumed = 0;
+}
+
+bool
+decoder_buffer_fill(DecoderBuffer *buffer)
+{
+ size_t nbytes;
+
+ if (buffer->consumed > 0)
+ decoder_buffer_shift(buffer);
+
+ if (buffer->length >= buffer->size)
+ /* buffer is full */
+ return false;
+
+ nbytes = decoder_read(buffer->decoder, *buffer->is,
+ buffer->data + buffer->length,
+ buffer->size - buffer->length);
+ if (nbytes == 0)
+ /* end of file, I/O error or decoder command
+ received */
+ return false;
+
+ buffer->length += nbytes;
+ assert(buffer->length <= buffer->size);
+
+ return true;
+}
+
+ConstBuffer<void>
+decoder_buffer_read(const DecoderBuffer *buffer)
+{
+ return {
+ buffer->data + buffer->consumed,
+ buffer->length - buffer->consumed
+ };
+}
+
+void
+decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes)
+{
+ /* just move the "consumed" pointer - decoder_buffer_shift()
+ will do the real work later (called by
+ decoder_buffer_fill()) */
+ buffer->consumed += nbytes;
+
+ assert(buffer->consumed <= buffer->length);
+}
+
+bool
+decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes)
+{
+ bool success;
+
+ /* this could probably be optimized by seeking */
+
+ while (true) {
+ auto data = decoder_buffer_read(buffer);
+ if (!data.IsEmpty()) {
+ if (data.size > nbytes)
+ data.size = nbytes;
+ decoder_buffer_consume(buffer, data.size);
+ nbytes -= data.size;
+ if (nbytes == 0)
+ return true;
+ }
+
+ success = decoder_buffer_fill(buffer);
+ if (!success)
+ return false;
+ }
+}
diff --git a/src/decoder/DecoderBuffer.hxx b/src/decoder/DecoderBuffer.hxx
new file mode 100644
index 000000000..4cadd7740
--- /dev/null
+++ b/src/decoder/DecoderBuffer.hxx
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2003-2014 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_DECODER_BUFFER_HXX
+#define MPD_DECODER_BUFFER_HXX
+
+#include "Compiler.h"
+
+#include <stddef.h>
+
+/**
+ * This objects handles buffered reads in decoder plugins easily. You
+ * create a buffer object, and use its high-level methods to fill and
+ * read it. It will automatically handle shifting the buffer.
+ */
+struct DecoderBuffer;
+
+struct Decoder;
+struct InputStream;
+
+template<typename T> struct ConstBuffer;
+
+/**
+ * Creates a new buffer.
+ *
+ * @param decoder the decoder object, used for decoder_read(), may be nullptr
+ * @param is the input stream object where we should read from
+ * @param size the maximum size of the buffer
+ * @return the new decoder_buffer object
+ */
+DecoderBuffer *
+decoder_buffer_new(Decoder *decoder, InputStream &is,
+ size_t size);
+
+/**
+ * Frees resources used by the decoder_buffer object.
+ */
+void
+decoder_buffer_free(DecoderBuffer *buffer);
+
+gcc_pure
+bool
+decoder_buffer_is_empty(const DecoderBuffer *buffer);
+
+gcc_pure
+bool
+decoder_buffer_is_full(const DecoderBuffer *buffer);
+
+void
+decoder_buffer_clear(DecoderBuffer *buffer);
+
+/**
+ * Read data from the input_stream and append it to the buffer.
+ *
+ * @return true if data was appended; false if there is no data
+ * available (yet), end of file, I/O error or a decoder command was
+ * received
+ */
+bool
+decoder_buffer_fill(DecoderBuffer *buffer);
+
+/**
+ * Reads data from the buffer. This data is not yet consumed, you
+ * have to call decoder_buffer_consume() to do that. The returned
+ * buffer becomes invalid after a decoder_buffer_fill() or a
+ * decoder_buffer_consume() call.
+ *
+ * @param buffer the decoder_buffer object
+ */
+gcc_pure
+ConstBuffer<void>
+decoder_buffer_read(const DecoderBuffer *buffer);
+
+/**
+ * Consume (delete, invalidate) a part of the buffer. The "nbytes"
+ * parameter must not be larger than the length returned by
+ * decoder_buffer_read().
+ *
+ * @param buffer the decoder_buffer object
+ * @param nbytes the number of bytes to consume
+ */
+void
+decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes);
+
+/**
+ * Skips the specified number of bytes, discarding its data.
+ *
+ * @param buffer the decoder_buffer object
+ * @param nbytes the number of bytes to skip
+ * @return true on success, false on error
+ */
+bool
+decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes);
+
+#endif
diff --git a/src/decoder/DecoderCommand.hxx b/src/decoder/DecoderCommand.hxx
new file mode 100644
index 000000000..a00519644
--- /dev/null
+++ b/src/decoder/DecoderCommand.hxx
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2003-2014 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_DECODER_COMMAND_HXX
+#define MPD_DECODER_COMMAND_HXX
+
+#include <stdint.h>
+
+enum class DecoderCommand : uint8_t {
+ NONE = 0,
+ START,
+ STOP,
+ SEEK
+};
+
+#endif
diff --git a/src/decoder/DecoderControl.cxx b/src/decoder/DecoderControl.cxx
new file mode 100644
index 000000000..d78fc66c9
--- /dev/null
+++ b/src/decoder/DecoderControl.cxx
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2003-2014 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 "DecoderControl.hxx"
+#include "MusicPipe.hxx"
+#include "DetachedSong.hxx"
+
+#include <assert.h>
+
+DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond)
+ :mutex(_mutex), client_cond(_client_cond),
+ state(DecoderState::STOP),
+ command(DecoderCommand::NONE),
+ client_is_waiting(false),
+ song(nullptr),
+ replay_gain_db(0), replay_gain_prev_db(0) {}
+
+DecoderControl::~DecoderControl()
+{
+ ClearError();
+
+ delete song;
+}
+
+void
+DecoderControl::WaitForDecoder()
+{
+ assert(!client_is_waiting);
+ client_is_waiting = true;
+
+ client_cond.wait(mutex);
+
+ assert(client_is_waiting);
+ client_is_waiting = false;
+}
+
+bool
+DecoderControl::IsCurrentSong(const DetachedSong &_song) const
+{
+ switch (state) {
+ case DecoderState::STOP:
+ case DecoderState::ERROR:
+ return false;
+
+ case DecoderState::START:
+ case DecoderState::DECODE:
+ return song->IsSame(_song);
+ }
+
+ assert(false);
+ gcc_unreachable();
+}
+
+void
+DecoderControl::Start(DetachedSong *_song,
+ unsigned _start_ms, unsigned _end_ms,
+ MusicBuffer &_buffer, MusicPipe &_pipe)
+{
+ assert(_song != nullptr);
+ assert(_pipe.IsEmpty());
+
+ delete song;
+ song = _song;
+ start_ms = _start_ms;
+ end_ms = _end_ms;
+ buffer = &_buffer;
+ pipe = &_pipe;
+
+ LockSynchronousCommand(DecoderCommand::START);
+}
+
+void
+DecoderControl::Stop()
+{
+ Lock();
+
+ if (command != DecoderCommand::NONE)
+ /* Attempt to cancel the current command. If it's too
+ late and the decoder thread is already executing
+ the old command, we'll call STOP again in this
+ function (see below). */
+ SynchronousCommandLocked(DecoderCommand::STOP);
+
+ if (state != DecoderState::STOP && state != DecoderState::ERROR)
+ SynchronousCommandLocked(DecoderCommand::STOP);
+
+ Unlock();
+}
+
+bool
+DecoderControl::Seek(double where)
+{
+ assert(state != DecoderState::START);
+ assert(where >= 0.0);
+
+ if (state == DecoderState::STOP ||
+ state == DecoderState::ERROR || !seekable)
+ return false;
+
+ seek_where = where;
+ seek_error = false;
+ LockSynchronousCommand(DecoderCommand::SEEK);
+
+ return !seek_error;
+}
+
+void
+DecoderControl::Quit()
+{
+ assert(thread.IsDefined());
+
+ quit = true;
+ LockAsynchronousCommand(DecoderCommand::STOP);
+
+ thread.Join();
+}
+
+void
+DecoderControl::CycleMixRamp()
+{
+ previous_mix_ramp = std::move(mix_ramp);
+ mix_ramp.Clear();
+}
diff --git a/src/decoder/DecoderControl.hxx b/src/decoder/DecoderControl.hxx
new file mode 100644
index 000000000..4e5c43b5a
--- /dev/null
+++ b/src/decoder/DecoderControl.hxx
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2003-2014 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_DECODER_CONTROL_HXX
+#define MPD_DECODER_CONTROL_HXX
+
+#include "DecoderCommand.hxx"
+#include "AudioFormat.hxx"
+#include "MixRampInfo.hxx"
+#include "thread/Mutex.hxx"
+#include "thread/Cond.hxx"
+#include "thread/Thread.hxx"
+#include "util/Error.hxx"
+
+#include <assert.h>
+#include <stdint.h>
+
+/* damn you, windows.h! */
+#ifdef ERROR
+#undef ERROR
+#endif
+
+class DetachedSong;
+class MusicBuffer;
+class MusicPipe;
+
+enum class DecoderState : uint8_t {
+ STOP = 0,
+ START,
+ DECODE,
+
+ /**
+ * The last "START" command failed, because there was an I/O
+ * error or because no decoder was able to decode the file.
+ * This state will only come after START; once the state has
+ * turned to DECODE, by definition no such error can occur.
+ */
+ ERROR,
+};
+
+struct DecoderControl {
+ /**
+ * The handle of the decoder thread.
+ */
+ Thread thread;
+
+ /**
+ * This lock protects #state and #command.
+ *
+ * This is usually a reference to PlayerControl::mutex, so
+ * that both player thread and decoder thread share a mutex.
+ * This simplifies synchronization with #cond and
+ * #client_cond.
+ */
+ Mutex &mutex;
+
+ /**
+ * Trigger this object after you have modified #command. This
+ * is also used by the decoder thread to notify the caller
+ * when it has finished a command.
+ */
+ Cond cond;
+
+ /**
+ * The trigger of this object's client. It is signalled
+ * whenever an event occurs.
+ *
+ * This is usually a reference to PlayerControl::cond.
+ */
+ Cond &client_cond;
+
+ DecoderState state;
+ DecoderCommand command;
+
+ /**
+ * The error that occurred in the decoder thread. This
+ * attribute is only valid if #state is #DecoderState::ERROR.
+ * The object must be freed when this object transitions to
+ * any other state (usually #DecoderState::START).
+ */
+ Error error;
+
+ bool quit;
+
+ /**
+ * Is the client currently waiting for the DecoderThread? If
+ * false, the DecoderThread may omit invoking Cond::signal(),
+ * reducing the number of system calls.
+ */
+ bool client_is_waiting;
+
+ bool seek_error;
+ bool seekable;
+ double seek_where;
+
+ /** the format of the song file */
+ AudioFormat in_audio_format;
+
+ /** the format being sent to the music pipe */
+ AudioFormat out_audio_format;
+
+ /**
+ * The song currently being decoded. This attribute is set by
+ * the player thread, when it sends the #DecoderCommand::START
+ * command.
+ *
+ * This is a duplicate, and must be freed when this attribute
+ * is cleared.
+ */
+ DetachedSong *song;
+
+ /**
+ * The initial seek position (in milliseconds), e.g. to the
+ * start of a sub-track described by a CUE file.
+ *
+ * This attribute is set by dc_start().
+ */
+ unsigned start_ms;
+
+ /**
+ * The decoder will stop when it reaches this position (in
+ * milliseconds). 0 means don't stop before the end of the
+ * file.
+ *
+ * This attribute is set by dc_start().
+ */
+ unsigned end_ms;
+
+ float total_time;
+
+ /** the #music_chunk allocator */
+ MusicBuffer *buffer;
+
+ /**
+ * The destination pipe for decoded chunks. The caller thread
+ * owns this object, and is responsible for freeing it.
+ */
+ MusicPipe *pipe;
+
+ float replay_gain_db;
+ float replay_gain_prev_db;
+
+ MixRampInfo mix_ramp, previous_mix_ramp;
+
+ /**
+ * @param _mutex see #mutex
+ * @param _client_cond see #client_cond
+ */
+ DecoderControl(Mutex &_mutex, Cond &_client_cond);
+ ~DecoderControl();
+
+ /**
+ * Locks the object.
+ */
+ void Lock() const {
+ mutex.lock();
+ }
+
+ /**
+ * Unlocks the object.
+ */
+ void Unlock() const {
+ mutex.unlock();
+ }
+
+ /**
+ * Signals the object. This function is only valid in the
+ * player thread. The object should be locked prior to
+ * calling this function.
+ */
+ void Signal() {
+ cond.signal();
+ }
+
+ /**
+ * Waits for a signal on the #DecoderControl object. This function
+ * is only valid in the decoder thread. The object must be locked
+ * prior to calling this function.
+ */
+ void Wait() {
+ cond.wait(mutex);
+ }
+
+ /**
+ * Waits for a signal from the decoder thread. This object
+ * must be locked prior to calling this function. This method
+ * is only valid in the player thread.
+ *
+ * Caller must hold the lock.
+ */
+ void WaitForDecoder();
+
+ bool IsIdle() const {
+ return state == DecoderState::STOP ||
+ state == DecoderState::ERROR;
+ }
+
+ gcc_pure
+ bool LockIsIdle() const {
+ Lock();
+ bool result = IsIdle();
+ Unlock();
+ return result;
+ }
+
+ bool IsStarting() const {
+ return state == DecoderState::START;
+ }
+
+ gcc_pure
+ bool LockIsStarting() const {
+ Lock();
+ bool result = IsStarting();
+ Unlock();
+ return result;
+ }
+
+ bool HasFailed() const {
+ assert(command == DecoderCommand::NONE);
+
+ return state == DecoderState::ERROR;
+ }
+
+ gcc_pure
+ bool LockHasFailed() const {
+ Lock();
+ bool result = HasFailed();
+ Unlock();
+ return result;
+ }
+
+ /**
+ * Checks whether an error has occurred, and if so, returns a
+ * copy of the #Error object.
+ *
+ * Caller must lock the object.
+ */
+ gcc_pure
+ Error GetError() const {
+ assert(command == DecoderCommand::NONE);
+ assert(state != DecoderState::ERROR || error.IsDefined());
+
+ Error result;
+ if (state == DecoderState::ERROR)
+ result.Set(error);
+ return result;
+ }
+
+ /**
+ * Like dc_get_error(), but locks and unlocks the object.
+ */
+ gcc_pure
+ Error LockGetError() const {
+ Lock();
+ Error result = GetError();
+ Unlock();
+ return result;
+ }
+
+ /**
+ * Clear the error condition and free the #Error object (if any).
+ *
+ * Caller must lock the object.
+ */
+ void ClearError() {
+ if (state == DecoderState::ERROR) {
+ error.Clear();
+ state = DecoderState::STOP;
+ }
+ }
+
+ /**
+ * Check if the specified song is currently being decoded. If the
+ * decoder is not running currently (or being started), then this
+ * function returns false in any case.
+ *
+ * Caller must lock the object.
+ */
+ gcc_pure
+ bool IsCurrentSong(const DetachedSong &_song) const;
+
+ gcc_pure
+ bool LockIsCurrentSong(const DetachedSong &_song) const {
+ Lock();
+ const bool result = IsCurrentSong(_song);
+ Unlock();
+ return result;
+ }
+
+private:
+ /**
+ * Wait for the command to be finished by the decoder thread.
+ *
+ * To be called from the client thread. Caller must lock the
+ * object.
+ */
+ void WaitCommandLocked() {
+ while (command != DecoderCommand::NONE)
+ WaitForDecoder();
+ }
+
+ /**
+ * Send a command to the decoder thread and synchronously wait
+ * for it to finish.
+ *
+ * To be called from the client thread. Caller must lock the
+ * object.
+ */
+ void SynchronousCommandLocked(DecoderCommand cmd) {
+ command = cmd;
+ Signal();
+ WaitCommandLocked();
+ }
+
+ /**
+ * Send a command to the decoder thread and synchronously wait
+ * for it to finish.
+ *
+ * To be called from the client thread. This method locks the
+ * object.
+ */
+ void LockSynchronousCommand(DecoderCommand cmd) {
+ Lock();
+ ClearError();
+ SynchronousCommandLocked(cmd);
+ Unlock();
+ }
+
+ void LockAsynchronousCommand(DecoderCommand cmd) {
+ Lock();
+ command = cmd;
+ Signal();
+ Unlock();
+ }
+
+public:
+ /**
+ * Start the decoder.
+ *
+ * @param song the song to be decoded; the given instance will be
+ * owned and freed by the decoder
+ * @param start_ms see #DecoderControl
+ * @param end_ms see #DecoderControl
+ * @param pipe the pipe which receives the decoded chunks (owned by
+ * the caller)
+ */
+ void Start(DetachedSong *song, unsigned start_ms, unsigned end_ms,
+ MusicBuffer &buffer, MusicPipe &pipe);
+
+ void Stop();
+
+ bool Seek(double where);
+
+ void Quit();
+
+ const char *GetMixRampStart() const {
+ return mix_ramp.GetStart();
+ }
+
+ const char *GetMixRampEnd() const {
+ return mix_ramp.GetEnd();
+ }
+
+ const char *GetMixRampPreviousEnd() const {
+ return previous_mix_ramp.GetEnd();
+ }
+
+ void SetMixRamp(MixRampInfo &&new_value) {
+ mix_ramp = std::move(new_value);
+ }
+
+ /**
+ * Move mixramp_end to mixramp_prev_end and clear
+ * mixramp_start/mixramp_end.
+ */
+ void CycleMixRamp();
+};
+
+#endif
diff --git a/src/decoder/DecoderError.cxx b/src/decoder/DecoderError.cxx
new file mode 100644
index 000000000..bd3842837
--- /dev/null
+++ b/src/decoder/DecoderError.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2014 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 "DecoderError.hxx"
+#include "util/Domain.hxx"
+
+const Domain decoder_domain("decoder");
diff --git a/src/decoder/DecoderError.hxx b/src/decoder/DecoderError.hxx
new file mode 100644
index 000000000..83cf98204
--- /dev/null
+++ b/src/decoder/DecoderError.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 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_DECODER_ERROR_HXX
+#define MPD_DECODER_ERROR_HXX
+
+extern const class Domain decoder_domain;
+
+#endif
diff --git a/src/decoder/DecoderInternal.cxx b/src/decoder/DecoderInternal.cxx
new file mode 100644
index 000000000..b50fee185
--- /dev/null
+++ b/src/decoder/DecoderInternal.cxx
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2003-2014 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 "DecoderInternal.hxx"
+#include "DecoderControl.hxx"
+#include "pcm/PcmConvert.hxx"
+#include "MusicPipe.hxx"
+#include "MusicBuffer.hxx"
+#include "MusicChunk.hxx"
+#include "tag/Tag.hxx"
+
+#include <assert.h>
+
+Decoder::~Decoder()
+{
+ /* caller must flush the chunk */
+ assert(chunk == nullptr);
+
+ if (convert != nullptr) {
+ convert->Close();
+ delete convert;
+ }
+
+ delete song_tag;
+ delete stream_tag;
+ delete decoder_tag;
+}
+
+/**
+ * All chunks are full of decoded data; wait for the player to free
+ * one.
+ */
+static DecoderCommand
+need_chunks(DecoderControl &dc)
+{
+ if (dc.command == DecoderCommand::NONE)
+ dc.Wait();
+
+ return dc.command;
+}
+
+struct music_chunk *
+Decoder::GetChunk()
+{
+ DecoderCommand cmd;
+
+ if (chunk != nullptr)
+ return chunk;
+
+ do {
+ chunk = dc.buffer->Allocate();
+ if (chunk != nullptr) {
+ chunk->replay_gain_serial = replay_gain_serial;
+ if (replay_gain_serial != 0)
+ chunk->replay_gain_info = replay_gain_info;
+
+ return chunk;
+ }
+
+ dc.Lock();
+ cmd = need_chunks(dc);
+ dc.Unlock();
+ } while (cmd == DecoderCommand::NONE);
+
+ return nullptr;
+}
+
+void
+Decoder::FlushChunk()
+{
+ assert(chunk != nullptr);
+
+ if (chunk->IsEmpty())
+ dc.buffer->Return(chunk);
+ else
+ dc.pipe->Push(chunk);
+
+ chunk = nullptr;
+
+ dc.Lock();
+ if (dc.client_is_waiting)
+ dc.client_cond.signal();
+ dc.Unlock();
+}
diff --git a/src/decoder/DecoderInternal.hxx b/src/decoder/DecoderInternal.hxx
new file mode 100644
index 000000000..bef6f6c13
--- /dev/null
+++ b/src/decoder/DecoderInternal.hxx
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2003-2014 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_DECODER_INTERNAL_HXX
+#define MPD_DECODER_INTERNAL_HXX
+
+#include "ReplayGainInfo.hxx"
+#include "util/Error.hxx"
+
+class PcmConvert;
+struct DecoderControl;
+struct Tag;
+
+struct Decoder {
+ DecoderControl &dc;
+
+ /**
+ * For converting input data to the configured audio format.
+ * nullptr means no conversion necessary.
+ */
+ PcmConvert *convert;
+
+ /**
+ * The time stamp of the next data chunk, in seconds.
+ */
+ double timestamp;
+
+ /**
+ * Is the initial seek (to the start position of the sub-song)
+ * pending, or has it been performed already?
+ */
+ bool initial_seek_pending;
+
+ /**
+ * Is the initial seek currently running? During this time,
+ * the decoder command is SEEK. This flag is set by
+ * decoder_get_virtual_command(), when the virtual SEEK
+ * command is generated for the first time.
+ */
+ bool initial_seek_running;
+
+ /**
+ * This flag is set by decoder_seek_where(), and checked by
+ * decoder_command_finished(). It is used to clean up after
+ * seeking.
+ */
+ bool seeking;
+
+ /**
+ * The tag from the song object. This is only used for local
+ * files, because we expect the stream server to send us a new
+ * tag each time we play it.
+ */
+ Tag *song_tag;
+
+ /** the last tag received from the stream */
+ Tag *stream_tag;
+
+ /** the last tag received from the decoder plugin */
+ Tag *decoder_tag;
+
+ /** the chunk currently being written to */
+ struct music_chunk *chunk;
+
+ ReplayGainInfo replay_gain_info;
+
+ /**
+ * A positive serial number for checking if replay gain info
+ * has changed since the last check.
+ */
+ unsigned replay_gain_serial;
+
+ /**
+ * An error has occurred (in DecoderAPI.cxx), and the plugin
+ * will be asked to stop.
+ */
+ Error error;
+
+ Decoder(DecoderControl &_dc, bool _initial_seek_pending, Tag *_tag)
+ :dc(_dc),
+ convert(nullptr),
+ timestamp(0),
+ initial_seek_pending(_initial_seek_pending),
+ initial_seek_running(false),
+ seeking(false),
+ song_tag(_tag), stream_tag(nullptr), decoder_tag(nullptr),
+ chunk(nullptr),
+ replay_gain_serial(0) {
+ }
+
+ ~Decoder();
+
+ /**
+ * Returns the current chunk the decoder writes to, or allocates a new
+ * chunk if there is none.
+ *
+ * @return the chunk, or NULL if we have received a decoder command
+ */
+ music_chunk *GetChunk();
+
+ /**
+ * Flushes the current chunk.
+ *
+ * Caller must not lock the #DecoderControl object.
+ */
+ void FlushChunk();
+};
+
+#endif
diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx
new file mode 100644
index 000000000..a644d2eae
--- /dev/null
+++ b/src/decoder/DecoderList.cxx
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2003-2014 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 "DecoderList.hxx"
+#include "DecoderPlugin.hxx"
+#include "ConfigGlobal.hxx"
+#include "ConfigData.hxx"
+#include "plugins/AudiofileDecoderPlugin.hxx"
+#include "plugins/PcmDecoderPlugin.hxx"
+#include "plugins/DsdiffDecoderPlugin.hxx"
+#include "plugins/DsfDecoderPlugin.hxx"
+#include "plugins/FlacDecoderPlugin.h"
+#include "plugins/OpusDecoderPlugin.h"
+#include "plugins/VorbisDecoderPlugin.h"
+#include "plugins/AdPlugDecoderPlugin.h"
+#include "plugins/WavpackDecoderPlugin.hxx"
+#include "plugins/FfmpegDecoderPlugin.hxx"
+#include "plugins/GmeDecoderPlugin.hxx"
+#include "plugins/FaadDecoderPlugin.hxx"
+#include "plugins/MadDecoderPlugin.hxx"
+#include "plugins/SndfileDecoderPlugin.hxx"
+#include "plugins/Mpg123DecoderPlugin.hxx"
+#include "plugins/WildmidiDecoderPlugin.hxx"
+#include "plugins/MikmodDecoderPlugin.hxx"
+#include "plugins/ModplugDecoderPlugin.hxx"
+#include "plugins/MpcdecDecoderPlugin.hxx"
+#include "plugins/FluidsynthDecoderPlugin.hxx"
+#include "plugins/SidplayDecoderPlugin.hxx"
+#include "system/FatalError.hxx"
+#include "util/Macros.hxx"
+
+#include <string.h>
+
+const struct DecoderPlugin *const decoder_plugins[] = {
+#ifdef HAVE_MAD
+ &mad_decoder_plugin,
+#endif
+#ifdef HAVE_MPG123
+ &mpg123_decoder_plugin,
+#endif
+#ifdef ENABLE_VORBIS_DECODER
+ &vorbis_decoder_plugin,
+#endif
+#if defined(HAVE_FLAC)
+ &oggflac_decoder_plugin,
+#endif
+#ifdef HAVE_FLAC
+ &flac_decoder_plugin,
+#endif
+#ifdef HAVE_OPUS
+ &opus_decoder_plugin,
+#endif
+#ifdef ENABLE_SNDFILE
+ &sndfile_decoder_plugin,
+#endif
+#ifdef HAVE_AUDIOFILE
+ &audiofile_decoder_plugin,
+#endif
+ &dsdiff_decoder_plugin,
+ &dsf_decoder_plugin,
+#ifdef HAVE_FAAD
+ &faad_decoder_plugin,
+#endif
+#ifdef HAVE_MPCDEC
+ &mpcdec_decoder_plugin,
+#endif
+#ifdef HAVE_WAVPACK
+ &wavpack_decoder_plugin,
+#endif
+#ifdef HAVE_MODPLUG
+ &modplug_decoder_plugin,
+#endif
+#ifdef ENABLE_MIKMOD_DECODER
+ &mikmod_decoder_plugin,
+#endif
+#ifdef ENABLE_SIDPLAY
+ &sidplay_decoder_plugin,
+#endif
+#ifdef ENABLE_WILDMIDI
+ &wildmidi_decoder_plugin,
+#endif
+#ifdef ENABLE_FLUIDSYNTH
+ &fluidsynth_decoder_plugin,
+#endif
+#ifdef HAVE_ADPLUG
+ &adplug_decoder_plugin,
+#endif
+#ifdef HAVE_FFMPEG
+ &ffmpeg_decoder_plugin,
+#endif
+#ifdef HAVE_GME
+ &gme_decoder_plugin,
+#endif
+ &pcm_decoder_plugin,
+ nullptr
+};
+
+static constexpr unsigned num_decoder_plugins =
+ ARRAY_SIZE(decoder_plugins) - 1;
+
+/** which plugins have been initialized successfully? */
+bool decoder_plugins_enabled[num_decoder_plugins];
+
+const struct DecoderPlugin *
+decoder_plugin_from_name(const char *name)
+{
+ return decoder_plugins_find([=](const DecoderPlugin &plugin){
+ return strcmp(plugin.name, name) == 0;
+ });
+}
+
+/**
+ * Find the "decoder" configuration block for the specified plugin.
+ *
+ * @param plugin_name the name of the decoder plugin
+ * @return the configuration block, or nullptr if none was configured
+ */
+static const struct config_param *
+decoder_plugin_config(const char *plugin_name)
+{
+ const struct config_param *param = nullptr;
+
+ while ((param = config_get_next_param(CONF_DECODER, param)) != nullptr) {
+ const char *name = param->GetBlockValue("plugin");
+ if (name == nullptr)
+ FormatFatalError("decoder configuration without 'plugin' name in line %d",
+ param->line);
+
+ if (strcmp(name, plugin_name) == 0)
+ return param;
+ }
+
+ return nullptr;
+}
+
+void decoder_plugin_init_all(void)
+{
+ struct config_param empty;
+
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) {
+ const DecoderPlugin &plugin = *decoder_plugins[i];
+ const struct config_param *param =
+ decoder_plugin_config(plugin.name);
+
+ if (param == nullptr)
+ param = &empty;
+ else if (!param->GetBlockValue("enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ if (plugin.Init(*param))
+ decoder_plugins_enabled[i] = true;
+ }
+}
+
+void decoder_plugin_deinit_all(void)
+{
+ decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){
+ plugin.Finish();
+ });
+}
+
+bool
+decoder_plugins_supports_suffix(const char *suffix)
+{
+ return decoder_plugins_try([suffix](const DecoderPlugin &plugin){
+ return plugin.SupportsSuffix(suffix);
+ });
+}
diff --git a/src/decoder/DecoderList.hxx b/src/decoder/DecoderList.hxx
new file mode 100644
index 000000000..47085d4ae
--- /dev/null
+++ b/src/decoder/DecoderList.hxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2014 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_DECODER_LIST_HXX
+#define MPD_DECODER_LIST_HXX
+
+#include "Compiler.h"
+
+struct DecoderPlugin;
+
+extern const struct DecoderPlugin *const decoder_plugins[];
+extern bool decoder_plugins_enabled[];
+
+/* interface for using plugins */
+
+gcc_pure
+const struct DecoderPlugin *
+decoder_plugin_from_name(const char *name);
+
+/* this is where we "load" all the "plugins" ;-) */
+void decoder_plugin_init_all(void);
+
+/* this is where we "unload" all the "plugins" */
+void decoder_plugin_deinit_all(void);
+
+template<typename F>
+static inline const DecoderPlugin *
+decoder_plugins_find(F f)
+{
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
+ if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))
+ return decoder_plugins[i];
+
+ return nullptr;
+}
+
+template<typename F>
+static inline bool
+decoder_plugins_try(F f)
+{
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
+ if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))
+ return true;
+
+ return false;
+}
+
+template<typename F>
+static inline void
+decoder_plugins_for_each(F f)
+{
+ for (auto i = decoder_plugins; *i != nullptr; ++i)
+ f(**i);
+}
+
+template<typename F>
+static inline void
+decoder_plugins_for_each_enabled(F f)
+{
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
+ if (decoder_plugins_enabled[i])
+ f(*decoder_plugins[i]);
+}
+
+/**
+ * Is there at least once #DecoderPlugin that supports the specified
+ * file name suffix?
+ */
+gcc_pure gcc_nonnull_all
+bool
+decoder_plugins_supports_suffix(const char *suffix);
+
+#endif
diff --git a/src/decoder/DecoderPlugin.cxx b/src/decoder/DecoderPlugin.cxx
new file mode 100644
index 000000000..3be812c3b
--- /dev/null
+++ b/src/decoder/DecoderPlugin.cxx
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2003-2014 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 "DecoderPlugin.hxx"
+#include "util/StringUtil.hxx"
+
+#include <assert.h>
+
+bool
+DecoderPlugin::SupportsSuffix(const char *suffix) const
+{
+ assert(suffix != nullptr);
+
+ return suffixes != nullptr && string_array_contains(suffixes, suffix);
+
+}
+
+bool
+DecoderPlugin::SupportsMimeType(const char *mime_type) const
+{
+ assert(mime_type != nullptr);
+
+ return mime_types != nullptr &&
+ string_array_contains(mime_types, mime_type);
+}
diff --git a/src/decoder/DecoderPlugin.hxx b/src/decoder/DecoderPlugin.hxx
new file mode 100644
index 000000000..3155be04b
--- /dev/null
+++ b/src/decoder/DecoderPlugin.hxx
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2003-2014 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_DECODER_PLUGIN_HXX
+#define MPD_DECODER_PLUGIN_HXX
+
+#include "Compiler.h"
+
+struct config_param;
+struct InputStream;
+struct tag_handler;
+
+/**
+ * Opaque handle which the decoder plugin passes to the functions in
+ * this header.
+ */
+struct Decoder;
+
+struct DecoderPlugin {
+ const char *name;
+
+ /**
+ * Initialize the decoder plugin. Optional method.
+ *
+ * @param param a configuration block for this plugin, or nullptr
+ * if none is configured
+ * @return true if the plugin was initialized successfully,
+ * false if the plugin is not available
+ */
+ bool (*init)(const config_param &param);
+
+ /**
+ * Deinitialize a decoder plugin which was initialized
+ * successfully. Optional method.
+ */
+ void (*finish)(void);
+
+ /**
+ * Decode a stream (data read from an #input_stream object).
+ *
+ * Either implement this method or file_decode(). If
+ * possible, it is recommended to implement this method,
+ * because it is more versatile.
+ */
+ void (*stream_decode)(Decoder &decoder, InputStream &is);
+
+ /**
+ * Decode a local file.
+ *
+ * Either implement this method or stream_decode().
+ */
+ void (*file_decode)(Decoder &decoder, const char *path_fs);
+
+ /**
+ * Scan metadata of a file.
+ *
+ * @return false if the operation has failed
+ */
+ bool (*scan_file)(const char *path_fs,
+ const struct tag_handler *handler,
+ void *handler_ctx);
+
+ /**
+ * Scan metadata of a file.
+ *
+ * @return false if the operation has failed
+ */
+ bool (*scan_stream)(InputStream &is,
+ const struct tag_handler *handler,
+ void *handler_ctx);
+
+ /**
+ * @brief Return a "virtual" filename for subtracks in
+ * container formats like flac
+ * @param const char* pathname full pathname for the file on fs
+ * @param const unsigned int tnum track number
+ *
+ * @return nullptr if there are no multiple files
+ * a filename for every single track according to tnum (param 2)
+ * do not include full pathname here, just the "virtual" file
+ */
+ char* (*container_scan)(const char *path_fs, const unsigned int tnum);
+
+ /* last element in these arrays must always be a nullptr: */
+ const char *const*suffixes;
+ const char *const*mime_types;
+
+ /**
+ * Initialize a decoder plugin.
+ *
+ * @param param a configuration block for this plugin, or nullptr if none
+ * is configured
+ * @return true if the plugin was initialized successfully, false if
+ * the plugin is not available
+ */
+ bool Init(const config_param &param) const {
+ return init != nullptr
+ ? init(param)
+ : true;
+ }
+
+ /**
+ * Deinitialize a decoder plugin which was initialized successfully.
+ */
+ void Finish() const {
+ if (finish != nullptr)
+ finish();
+ }
+
+ /**
+ * Decode a stream.
+ */
+ void StreamDecode(Decoder &decoder, InputStream &is) const {
+ stream_decode(decoder, is);
+ }
+
+ /**
+ * Decode a file.
+ */
+ void FileDecode(Decoder &decoder, const char *path_fs) const {
+ file_decode(decoder, path_fs);
+ }
+
+ /**
+ * Read the tag of a file.
+ */
+ bool ScanFile(const char *path_fs,
+ const tag_handler &handler, void *handler_ctx) const {
+ return scan_file != nullptr
+ ? scan_file(path_fs, &handler, handler_ctx)
+ : false;
+ }
+
+ /**
+ * Read the tag of a stream.
+ */
+ bool ScanStream(InputStream &is,
+ const tag_handler &handler, void *handler_ctx) const {
+ return scan_stream != nullptr
+ ? scan_stream(is, &handler, handler_ctx)
+ : false;
+ }
+
+ /**
+ * return "virtual" tracks in a container
+ */
+ char *ContainerScan(const char *path, const unsigned int tnum) const {
+ return container_scan(path, tnum);
+ }
+
+ /**
+ * Does the plugin announce the specified file name suffix?
+ */
+ gcc_pure gcc_nonnull_all
+ bool SupportsSuffix(const char *suffix) const;
+
+ /**
+ * Does the plugin announce the specified MIME type?
+ */
+ gcc_pure gcc_nonnull_all
+ bool SupportsMimeType(const char *mime_type) const;
+};
+
+#endif
diff --git a/src/decoder/DecoderPrint.cxx b/src/decoder/DecoderPrint.cxx
new file mode 100644
index 000000000..06ef1f05e
--- /dev/null
+++ b/src/decoder/DecoderPrint.cxx
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2003-2014 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 "DecoderPrint.hxx"
+#include "DecoderList.hxx"
+#include "DecoderPlugin.hxx"
+#include "Client.hxx"
+
+#include <functional>
+
+#include <assert.h>
+
+static void
+decoder_plugin_print(Client &client,
+ const DecoderPlugin &plugin)
+{
+ const char *const*p;
+
+ assert(plugin.name != nullptr);
+
+ client_printf(client, "plugin: %s\n", plugin.name);
+
+ if (plugin.suffixes != nullptr)
+ for (p = plugin.suffixes; *p != nullptr; ++p)
+ client_printf(client, "suffix: %s\n", *p);
+
+ if (plugin.mime_types != nullptr)
+ for (p = plugin.mime_types; *p != nullptr; ++p)
+ client_printf(client, "mime_type: %s\n", *p);
+}
+
+void
+decoder_list_print(Client &client)
+{
+ using namespace std::placeholders;
+ const auto f = std::bind(decoder_plugin_print, std::ref(client), _1);
+ decoder_plugins_for_each_enabled(f);
+}
diff --git a/src/decoder/DecoderPrint.hxx b/src/decoder/DecoderPrint.hxx
new file mode 100644
index 000000000..695bd099d
--- /dev/null
+++ b/src/decoder/DecoderPrint.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 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_DECODER_PRINT_HXX
+#define MPD_DECODER_PRINT_HXX
+
+class Client;
+
+void
+decoder_list_print(Client &client);
+
+#endif
diff --git a/src/decoder/DecoderThread.cxx b/src/decoder/DecoderThread.cxx
new file mode 100644
index 000000000..5c0e31034
--- /dev/null
+++ b/src/decoder/DecoderThread.cxx
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2003-2014 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 "DecoderThread.hxx"
+#include "DecoderControl.hxx"
+#include "DecoderInternal.hxx"
+#include "DecoderError.hxx"
+#include "DecoderPlugin.hxx"
+#include "DetachedSong.hxx"
+#include "system/FatalError.hxx"
+#include "Mapper.hxx"
+#include "fs/Traits.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "DecoderAPI.hxx"
+#include "InputStream.hxx"
+#include "DecoderList.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "thread/Name.hxx"
+#include "tag/ApeReplayGain.hxx"
+#include "Log.hxx"
+
+#include <functional>
+
+static constexpr Domain decoder_thread_domain("decoder_thread");
+
+/**
+ * Marks the current decoder command as "finished" and notifies the
+ * player thread.
+ *
+ * @param dc the #DecoderControl object; must be locked
+ */
+static void
+decoder_command_finished_locked(DecoderControl &dc)
+{
+ assert(dc.command != DecoderCommand::NONE);
+
+ dc.command = DecoderCommand::NONE;
+
+ dc.client_cond.signal();
+}
+
+/**
+ * Opens the input stream with input_stream::Open(), and waits until
+ * the stream gets ready. If a decoder STOP command is received
+ * during that, it cancels the operation (but does not close the
+ * stream).
+ *
+ * Unlock the decoder before calling this function.
+ *
+ * @return an input_stream on success or if #DecoderCommand::STOP is
+ * received, nullptr on error
+ */
+static InputStream *
+decoder_input_stream_open(DecoderControl &dc, const char *uri)
+{
+ Error error;
+
+ InputStream *is = InputStream::Open(uri, dc.mutex, dc.cond, error);
+ if (is == nullptr) {
+ if (error.IsDefined())
+ LogError(error);
+
+ return nullptr;
+ }
+
+ /* wait for the input stream to become ready; its metadata
+ will be available then */
+
+ dc.Lock();
+
+ is->Update();
+ while (!is->ready &&
+ dc.command != DecoderCommand::STOP) {
+ dc.Wait();
+
+ is->Update();
+ }
+
+ if (!is->Check(error)) {
+ dc.Unlock();
+
+ LogError(error);
+ return nullptr;
+ }
+
+ dc.Unlock();
+
+ return is;
+}
+
+static bool
+decoder_stream_decode(const DecoderPlugin &plugin,
+ Decoder &decoder,
+ InputStream &input_stream)
+{
+ assert(plugin.stream_decode != nullptr);
+ assert(decoder.stream_tag == nullptr);
+ assert(decoder.decoder_tag == nullptr);
+ assert(input_stream.ready);
+ assert(decoder.dc.state == DecoderState::START);
+
+ FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name);
+
+ if (decoder.dc.command == DecoderCommand::STOP)
+ return true;
+
+ /* rewind the stream, so each plugin gets a fresh start */
+ input_stream.Rewind(IgnoreError());
+
+ decoder.dc.Unlock();
+
+ FormatThreadName("decoder:%s", plugin.name);
+
+ plugin.StreamDecode(decoder, input_stream);
+
+ SetThreadName("decoder");
+
+ decoder.dc.Lock();
+
+ assert(decoder.dc.state == DecoderState::START ||
+ decoder.dc.state == DecoderState::DECODE);
+
+ return decoder.dc.state != DecoderState::START;
+}
+
+static bool
+decoder_file_decode(const DecoderPlugin &plugin,
+ Decoder &decoder, const char *path)
+{
+ assert(plugin.file_decode != nullptr);
+ assert(decoder.stream_tag == nullptr);
+ assert(decoder.decoder_tag == nullptr);
+ assert(path != nullptr);
+ assert(PathTraitsFS::IsAbsolute(path));
+ assert(decoder.dc.state == DecoderState::START);
+
+ FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name);
+
+ if (decoder.dc.command == DecoderCommand::STOP)
+ return true;
+
+ decoder.dc.Unlock();
+
+ FormatThreadName("decoder:%s", plugin.name);
+
+ plugin.FileDecode(decoder, path);
+
+ SetThreadName("decoder");
+
+ decoder.dc.Lock();
+
+ assert(decoder.dc.state == DecoderState::START ||
+ decoder.dc.state == DecoderState::DECODE);
+
+ return decoder.dc.state != DecoderState::START;
+}
+
+gcc_pure
+static bool
+decoder_check_plugin_mime(const DecoderPlugin &plugin, const InputStream &is)
+{
+ assert(plugin.stream_decode != nullptr);
+
+ return !is.mime.empty() && plugin.SupportsMimeType(is.mime.c_str());
+}
+
+gcc_pure
+static bool
+decoder_check_plugin_suffix(const DecoderPlugin &plugin, const char *suffix)
+{
+ assert(plugin.stream_decode != nullptr);
+
+ return suffix != nullptr && plugin.SupportsSuffix(suffix);
+}
+
+gcc_pure
+static bool
+decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is,
+ const char *suffix)
+{
+ return plugin.stream_decode != nullptr &&
+ (decoder_check_plugin_mime(plugin, is) ||
+ decoder_check_plugin_suffix(plugin, suffix));
+}
+
+static bool
+decoder_run_stream_plugin(Decoder &decoder, InputStream &is,
+ const char *suffix,
+ const DecoderPlugin &plugin,
+ bool &tried_r)
+{
+ if (!decoder_check_plugin(plugin, is, suffix))
+ return false;
+
+ tried_r = true;
+ return decoder_stream_decode(plugin, decoder, is);
+}
+
+static bool
+decoder_run_stream_locked(Decoder &decoder, InputStream &is,
+ const char *uri, bool &tried_r)
+{
+ const char *const suffix = uri_get_suffix(uri);
+
+ using namespace std::placeholders;
+ const auto f = std::bind(decoder_run_stream_plugin,
+ std::ref(decoder), std::ref(is), suffix,
+ _1, std::ref(tried_r));
+ return decoder_plugins_try(f);
+}
+
+/**
+ * Try decoding a stream, using the fallback plugin.
+ */
+static bool
+decoder_run_stream_fallback(Decoder &decoder, InputStream &is)
+{
+ const struct DecoderPlugin *plugin;
+
+ plugin = decoder_plugin_from_name("mad");
+ return plugin != nullptr && plugin->stream_decode != nullptr &&
+ decoder_stream_decode(*plugin, decoder, is);
+}
+
+/**
+ * Try decoding a stream.
+ */
+static bool
+decoder_run_stream(Decoder &decoder, const char *uri)
+{
+ DecoderControl &dc = decoder.dc;
+ InputStream *input_stream;
+ bool success;
+
+ dc.Unlock();
+
+ input_stream = decoder_input_stream_open(dc, uri);
+ if (input_stream == nullptr) {
+ dc.Lock();
+ return false;
+ }
+
+ dc.Lock();
+
+ bool tried = false;
+ success = dc.command == DecoderCommand::STOP ||
+ decoder_run_stream_locked(decoder, *input_stream, uri,
+ tried) ||
+ /* fallback to mp3: this is needed for bastard streams
+ that don't have a suffix or set the mimeType */
+ (!tried &&
+ decoder_run_stream_fallback(decoder, *input_stream));
+
+ dc.Unlock();
+ input_stream->Close();
+ dc.Lock();
+
+ return success;
+}
+
+/**
+ * Attempt to load replay gain data, and pass it to
+ * decoder_replay_gain().
+ */
+static void
+decoder_load_replay_gain(Decoder &decoder, const char *path_fs)
+{
+ ReplayGainInfo info;
+ if (replay_gain_ape_read(Path::FromFS(path_fs), info))
+ decoder_replay_gain(decoder, &info);
+}
+
+static bool
+TryDecoderFile(Decoder &decoder, const char *path_fs, const char *suffix,
+ const DecoderPlugin &plugin)
+{
+ if (!plugin.SupportsSuffix(suffix))
+ return false;
+
+ DecoderControl &dc = decoder.dc;
+
+ if (plugin.file_decode != nullptr) {
+ dc.Lock();
+
+ if (decoder_file_decode(plugin, decoder, path_fs))
+ return true;
+
+ dc.Unlock();
+ } else if (plugin.stream_decode != nullptr) {
+ InputStream *input_stream =
+ decoder_input_stream_open(dc, path_fs);
+ if (input_stream == nullptr)
+ return false;
+
+ dc.Lock();
+
+ bool success = decoder_stream_decode(plugin, decoder,
+ *input_stream);
+
+ dc.Unlock();
+
+ input_stream->Close();
+
+ if (success) {
+ dc.Lock();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Try decoding a file.
+ */
+static bool
+decoder_run_file(Decoder &decoder, const char *path_fs)
+{
+ const char *suffix = uri_get_suffix(path_fs);
+ if (suffix == nullptr)
+ return false;
+
+ DecoderControl &dc = decoder.dc;
+ dc.Unlock();
+
+ decoder_load_replay_gain(decoder, path_fs);
+
+ if (decoder_plugins_try([&decoder, path_fs, suffix](const DecoderPlugin &plugin){
+ return TryDecoderFile(decoder, path_fs, suffix,
+ plugin);
+ }))
+ return true;
+
+ dc.Lock();
+ return false;
+}
+
+static void
+decoder_run_song(DecoderControl &dc,
+ const DetachedSong &song, const char *uri)
+{
+ Decoder decoder(dc, dc.start_ms > 0,
+ new Tag(song.GetTag()));
+ int ret;
+
+ dc.state = DecoderState::START;
+
+ decoder_command_finished_locked(dc);
+
+ ret = song.IsFile()
+ ? decoder_run_file(decoder, uri)
+ : decoder_run_stream(decoder, uri);
+
+ dc.Unlock();
+
+ /* flush the last chunk */
+
+ if (decoder.chunk != nullptr)
+ decoder.FlushChunk();
+
+ dc.Lock();
+
+ if (decoder.error.IsDefined()) {
+ /* copy the Error from sruct Decoder to
+ DecoderControl */
+ dc.state = DecoderState::ERROR;
+ dc.error = std::move(decoder.error);
+ } else if (ret)
+ dc.state = DecoderState::STOP;
+ else {
+ dc.state = DecoderState::ERROR;
+
+ const char *error_uri = song.GetURI();
+ const std::string allocated = uri_remove_auth(error_uri);
+ if (!allocated.empty())
+ error_uri = allocated.c_str();
+
+ dc.error.Format(decoder_domain,
+ "Failed to decode %s", error_uri);
+ }
+
+ dc.client_cond.signal();
+}
+
+static void
+decoder_run(DecoderControl &dc)
+{
+ dc.ClearError();
+
+ assert(dc.song != nullptr);
+ const DetachedSong &song = *dc.song;
+
+ const std::string uri = song.IsFile()
+ ? map_song_fs(song).c_str()
+ : song.GetRealURI();
+
+ if (uri.empty()) {
+ dc.state = DecoderState::ERROR;
+ dc.error.Set(decoder_domain, "Failed to map song");
+
+ decoder_command_finished_locked(dc);
+ return;
+ }
+
+ decoder_run_song(dc, song, uri.c_str());
+
+}
+
+static void
+decoder_task(void *arg)
+{
+ DecoderControl &dc = *(DecoderControl *)arg;
+
+ SetThreadName("decoder");
+
+ dc.Lock();
+
+ do {
+ assert(dc.state == DecoderState::STOP ||
+ dc.state == DecoderState::ERROR);
+
+ switch (dc.command) {
+ case DecoderCommand::START:
+ dc.CycleMixRamp();
+ dc.replay_gain_prev_db = dc.replay_gain_db;
+ dc.replay_gain_db = 0;
+
+ /* fall through */
+
+ case DecoderCommand::SEEK:
+ decoder_run(dc);
+ break;
+
+ case DecoderCommand::STOP:
+ decoder_command_finished_locked(dc);
+ break;
+
+ case DecoderCommand::NONE:
+ dc.Wait();
+ break;
+ }
+ } while (dc.command != DecoderCommand::NONE || !dc.quit);
+
+ dc.Unlock();
+}
+
+void
+decoder_thread_start(DecoderControl &dc)
+{
+ assert(!dc.thread.IsDefined());
+
+ dc.quit = false;
+
+ Error error;
+ if (!dc.thread.Start(decoder_task, &dc, error))
+ FatalError(error);
+}
diff --git a/src/decoder/DecoderThread.hxx b/src/decoder/DecoderThread.hxx
new file mode 100644
index 000000000..d5fde281c
--- /dev/null
+++ b/src/decoder/DecoderThread.hxx
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2014 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_DECODER_THREAD_HXX
+#define MPD_DECODER_THREAD_HXX
+
+struct DecoderControl;
+
+void
+decoder_thread_start(DecoderControl &dc);
+
+#endif
diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/plugins/AdPlugDecoderPlugin.cxx
index 89dcea06b..c288d8cf4 100644
--- a/src/decoder/AdPlugDecoderPlugin.cxx
+++ b/src/decoder/plugins/AdPlugDecoderPlugin.cxx
@@ -20,7 +20,7 @@
#include "config.h"
#include "AdPlugDecoderPlugin.h"
#include "tag/TagHandler.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "util/Error.hxx"
#include "util/Macros.hxx"
diff --git a/src/decoder/AdPlugDecoderPlugin.h b/src/decoder/plugins/AdPlugDecoderPlugin.h
index 539dbbf0a..539dbbf0a 100644
--- a/src/decoder/AdPlugDecoderPlugin.h
+++ b/src/decoder/plugins/AdPlugDecoderPlugin.h
diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/plugins/AudiofileDecoderPlugin.cxx
index 9ef9ef263..cad908cfd 100644
--- a/src/decoder/AudiofileDecoderPlugin.cxx
+++ b/src/decoder/plugins/AudiofileDecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "AudiofileDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
diff --git a/src/decoder/AudiofileDecoderPlugin.hxx b/src/decoder/plugins/AudiofileDecoderPlugin.hxx
index 61129076d..61129076d 100644
--- a/src/decoder/AudiofileDecoderPlugin.hxx
+++ b/src/decoder/plugins/AudiofileDecoderPlugin.hxx
diff --git a/src/decoder/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx
index eb3dd5e06..43af7dee1 100644
--- a/src/decoder/DsdLib.cxx
+++ b/src/decoder/plugins/DsdLib.cxx
@@ -25,7 +25,7 @@
#include "config.h"
#include "DsdLib.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "tag/TagId3.hxx"
#include "util/Error.hxx"
diff --git a/src/decoder/DsdLib.hxx b/src/decoder/plugins/DsdLib.hxx
index 4c5e83114..4c5e83114 100644
--- a/src/decoder/DsdLib.hxx
+++ b/src/decoder/plugins/DsdLib.hxx
diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
index 643ee23bd..89ad2ecd0 100644
--- a/src/decoder/DsdiffDecoderPlugin.cxx
+++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
@@ -28,7 +28,7 @@
#include "config.h"
#include "DsdiffDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/bit_reverse.h"
diff --git a/src/decoder/DsdiffDecoderPlugin.hxx b/src/decoder/plugins/DsdiffDecoderPlugin.hxx
index 7aa36752b..7aa36752b 100644
--- a/src/decoder/DsdiffDecoderPlugin.hxx
+++ b/src/decoder/plugins/DsdiffDecoderPlugin.hxx
diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx
index fbc49ab62..bb056bbec 100644
--- a/src/decoder/DsfDecoderPlugin.cxx
+++ b/src/decoder/plugins/DsfDecoderPlugin.cxx
@@ -29,7 +29,7 @@
#include "config.h"
#include "DsfDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/bit_reverse.h"
diff --git a/src/decoder/DsfDecoderPlugin.hxx b/src/decoder/plugins/DsfDecoderPlugin.hxx
index 02bea0b5c..02bea0b5c 100644
--- a/src/decoder/DsfDecoderPlugin.hxx
+++ b/src/decoder/plugins/DsfDecoderPlugin.hxx
diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/plugins/FaadDecoderPlugin.cxx
index 5e7ccda52..79d6db9f7 100644
--- a/src/decoder/FaadDecoderPlugin.cxx
+++ b/src/decoder/plugins/FaadDecoderPlugin.cxx
@@ -19,8 +19,8 @@
#include "config.h"
#include "FaadDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "DecoderBuffer.hxx"
+#include "../DecoderAPI.hxx"
+#include "../DecoderBuffer.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
diff --git a/src/decoder/FaadDecoderPlugin.hxx b/src/decoder/plugins/FaadDecoderPlugin.hxx
index 968433e9b..968433e9b 100644
--- a/src/decoder/FaadDecoderPlugin.hxx
+++ b/src/decoder/plugins/FaadDecoderPlugin.hxx
diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
index 0d4256bd9..461ecb5e4 100644
--- a/src/decoder/FfmpegDecoderPlugin.cxx
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
@@ -22,7 +22,7 @@
#include "config.h"
#include "FfmpegDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "FfmpegMetaData.hxx"
#include "tag/TagHandler.hxx"
#include "InputStream.hxx"
diff --git a/src/decoder/FfmpegDecoderPlugin.hxx b/src/decoder/plugins/FfmpegDecoderPlugin.hxx
index 0a3e78e4b..0a3e78e4b 100644
--- a/src/decoder/FfmpegDecoderPlugin.hxx
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.hxx
diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx
index a39466945..a39466945 100644
--- a/src/decoder/FfmpegMetaData.cxx
+++ b/src/decoder/plugins/FfmpegMetaData.cxx
diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx
index 5eb41db68..5eb41db68 100644
--- a/src/decoder/FfmpegMetaData.hxx
+++ b/src/decoder/plugins/FfmpegMetaData.hxx
diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/plugins/FlacCommon.cxx
index 7b67585a0..7b67585a0 100644
--- a/src/decoder/FlacCommon.cxx
+++ b/src/decoder/plugins/FlacCommon.cxx
diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/plugins/FlacCommon.hxx
index cb9bfef75..34ce0a3fc 100644
--- a/src/decoder/FlacCommon.hxx
+++ b/src/decoder/plugins/FlacCommon.hxx
@@ -25,7 +25,7 @@
#define MPD_FLAC_COMMON_HXX
#include "FlacInput.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "pcm/PcmBuffer.hxx"
#include <FLAC/stream_decoder.h>
diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/plugins/FlacDecoderPlugin.cxx
index 17949f8c5..17949f8c5 100644
--- a/src/decoder/FlacDecoderPlugin.cxx
+++ b/src/decoder/plugins/FlacDecoderPlugin.cxx
diff --git a/src/decoder/FlacDecoderPlugin.h b/src/decoder/plugins/FlacDecoderPlugin.h
index fcdecf869..fcdecf869 100644
--- a/src/decoder/FlacDecoderPlugin.h
+++ b/src/decoder/plugins/FlacDecoderPlugin.h
diff --git a/src/decoder/FlacDomain.cxx b/src/decoder/plugins/FlacDomain.cxx
index fc5cc5498..fc5cc5498 100644
--- a/src/decoder/FlacDomain.cxx
+++ b/src/decoder/plugins/FlacDomain.cxx
diff --git a/src/decoder/FlacDomain.hxx b/src/decoder/plugins/FlacDomain.hxx
index a06c6c6b4..a06c6c6b4 100644
--- a/src/decoder/FlacDomain.hxx
+++ b/src/decoder/plugins/FlacDomain.hxx
diff --git a/src/decoder/FlacIOHandle.cxx b/src/decoder/plugins/FlacIOHandle.cxx
index aecee638b..aecee638b 100644
--- a/src/decoder/FlacIOHandle.cxx
+++ b/src/decoder/plugins/FlacIOHandle.cxx
diff --git a/src/decoder/FlacIOHandle.hxx b/src/decoder/plugins/FlacIOHandle.hxx
index 1f95b83d1..1f95b83d1 100644
--- a/src/decoder/FlacIOHandle.hxx
+++ b/src/decoder/plugins/FlacIOHandle.hxx
diff --git a/src/decoder/FlacInput.cxx b/src/decoder/plugins/FlacInput.cxx
index 51b1b0baf..43350c3cf 100644
--- a/src/decoder/FlacInput.cxx
+++ b/src/decoder/plugins/FlacInput.cxx
@@ -20,7 +20,7 @@
#include "config.h"
#include "FlacInput.hxx"
#include "FlacDomain.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
diff --git a/src/decoder/FlacInput.hxx b/src/decoder/plugins/FlacInput.hxx
index 30ed55fd0..30ed55fd0 100644
--- a/src/decoder/FlacInput.hxx
+++ b/src/decoder/plugins/FlacInput.hxx
diff --git a/src/decoder/FlacMetadata.cxx b/src/decoder/plugins/FlacMetadata.cxx
index b921e8481..b921e8481 100644
--- a/src/decoder/FlacMetadata.cxx
+++ b/src/decoder/plugins/FlacMetadata.cxx
diff --git a/src/decoder/FlacMetadata.hxx b/src/decoder/plugins/FlacMetadata.hxx
index e0449b2a2..e0449b2a2 100644
--- a/src/decoder/FlacMetadata.hxx
+++ b/src/decoder/plugins/FlacMetadata.hxx
diff --git a/src/decoder/FlacPcm.cxx b/src/decoder/plugins/FlacPcm.cxx
index 311500f26..311500f26 100644
--- a/src/decoder/FlacPcm.cxx
+++ b/src/decoder/plugins/FlacPcm.cxx
diff --git a/src/decoder/FlacPcm.hxx b/src/decoder/plugins/FlacPcm.hxx
index 30c318725..30c318725 100644
--- a/src/decoder/FlacPcm.hxx
+++ b/src/decoder/plugins/FlacPcm.hxx
diff --git a/src/decoder/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
index 596aba1fd..3b4703e97 100644
--- a/src/decoder/FluidsynthDecoderPlugin.cxx
+++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "FluidsynthDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
diff --git a/src/decoder/FluidsynthDecoderPlugin.hxx b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx
index cd8ec2d62..cd8ec2d62 100644
--- a/src/decoder/FluidsynthDecoderPlugin.hxx
+++ b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx
diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx
index 506c647bf..53015facf 100644
--- a/src/decoder/GmeDecoderPlugin.cxx
+++ b/src/decoder/plugins/GmeDecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "GmeDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "util/Alloc.hxx"
diff --git a/src/decoder/GmeDecoderPlugin.hxx b/src/decoder/plugins/GmeDecoderPlugin.hxx
index f4885b6e4..f4885b6e4 100644
--- a/src/decoder/GmeDecoderPlugin.hxx
+++ b/src/decoder/plugins/GmeDecoderPlugin.hxx
diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx
index 5a61d7d96..07fd05f1b 100644
--- a/src/decoder/MadDecoderPlugin.cxx
+++ b/src/decoder/plugins/MadDecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "MadDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "ConfigGlobal.hxx"
#include "tag/TagId3.hxx"
diff --git a/src/decoder/MadDecoderPlugin.hxx b/src/decoder/plugins/MadDecoderPlugin.hxx
index eb2a10d6f..eb2a10d6f 100644
--- a/src/decoder/MadDecoderPlugin.hxx
+++ b/src/decoder/plugins/MadDecoderPlugin.hxx
diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/plugins/MikmodDecoderPlugin.cxx
index 313b368ca..aacb5922d 100644
--- a/src/decoder/MikmodDecoderPlugin.cxx
+++ b/src/decoder/plugins/MikmodDecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "MikmodDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "tag/TagHandler.hxx"
#include "system/FatalError.hxx"
#include "util/Domain.hxx"
diff --git a/src/decoder/MikmodDecoderPlugin.hxx b/src/decoder/plugins/MikmodDecoderPlugin.hxx
index 27ba2a823..27ba2a823 100644
--- a/src/decoder/MikmodDecoderPlugin.hxx
+++ b/src/decoder/plugins/MikmodDecoderPlugin.hxx
diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/plugins/ModplugDecoderPlugin.cxx
index eabcd9d02..29c452f73 100644
--- a/src/decoder/ModplugDecoderPlugin.cxx
+++ b/src/decoder/plugins/ModplugDecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "ModplugDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "tag/TagHandler.hxx"
#include "system/FatalError.hxx"
diff --git a/src/decoder/ModplugDecoderPlugin.hxx b/src/decoder/plugins/ModplugDecoderPlugin.hxx
index 08f2ecb12..08f2ecb12 100644
--- a/src/decoder/ModplugDecoderPlugin.hxx
+++ b/src/decoder/plugins/ModplugDecoderPlugin.hxx
diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/plugins/MpcdecDecoderPlugin.cxx
index b27ea6ac2..21596ddf7 100644
--- a/src/decoder/MpcdecDecoderPlugin.cxx
+++ b/src/decoder/plugins/MpcdecDecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "MpcdecDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
diff --git a/src/decoder/MpcdecDecoderPlugin.hxx b/src/decoder/plugins/MpcdecDecoderPlugin.hxx
index 7f71311fa..7f71311fa 100644
--- a/src/decoder/MpcdecDecoderPlugin.hxx
+++ b/src/decoder/plugins/MpcdecDecoderPlugin.hxx
diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/plugins/Mpg123DecoderPlugin.cxx
index 670e44e69..cf8573c85 100644
--- a/src/decoder/Mpg123DecoderPlugin.cxx
+++ b/src/decoder/plugins/Mpg123DecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h" /* must be first for large file support */
#include "Mpg123DecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "util/Error.hxx"
diff --git a/src/decoder/Mpg123DecoderPlugin.hxx b/src/decoder/plugins/Mpg123DecoderPlugin.hxx
index fd089c6a4..fd089c6a4 100644
--- a/src/decoder/Mpg123DecoderPlugin.hxx
+++ b/src/decoder/plugins/Mpg123DecoderPlugin.hxx
diff --git a/src/decoder/OggCodec.cxx b/src/decoder/plugins/OggCodec.cxx
index f9b3c3527..c7f39586e 100644
--- a/src/decoder/OggCodec.cxx
+++ b/src/decoder/plugins/OggCodec.cxx
@@ -23,7 +23,7 @@
#include "config.h"
#include "OggCodec.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include <string.h>
diff --git a/src/decoder/OggCodec.hxx b/src/decoder/plugins/OggCodec.hxx
index 6643d85ad..6643d85ad 100644
--- a/src/decoder/OggCodec.hxx
+++ b/src/decoder/plugins/OggCodec.hxx
diff --git a/src/decoder/OggFind.cxx b/src/decoder/plugins/OggFind.cxx
index df4318da2..df4318da2 100644
--- a/src/decoder/OggFind.cxx
+++ b/src/decoder/plugins/OggFind.cxx
diff --git a/src/decoder/OggFind.hxx b/src/decoder/plugins/OggFind.hxx
index 71b53323b..71b53323b 100644
--- a/src/decoder/OggFind.hxx
+++ b/src/decoder/plugins/OggFind.hxx
diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/plugins/OggSyncState.hxx
index 024902fff..024902fff 100644
--- a/src/decoder/OggSyncState.hxx
+++ b/src/decoder/plugins/OggSyncState.hxx
diff --git a/src/decoder/OggUtil.cxx b/src/decoder/plugins/OggUtil.cxx
index ceacd2962..3d8a26f0b 100644
--- a/src/decoder/OggUtil.cxx
+++ b/src/decoder/plugins/OggUtil.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "OggUtil.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
bool
OggFeed(ogg_sync_state &oy, Decoder *decoder,
diff --git a/src/decoder/OggUtil.hxx b/src/decoder/plugins/OggUtil.hxx
index 3289702a3..3289702a3 100644
--- a/src/decoder/OggUtil.hxx
+++ b/src/decoder/plugins/OggUtil.hxx
diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
index 93b043065..15a17eb01 100644
--- a/src/decoder/OpusDecoderPlugin.cxx
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -24,7 +24,7 @@
#include "OpusTags.hxx"
#include "OggFind.hxx"
#include "OggSyncState.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "OggCodec.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagBuilder.hxx"
diff --git a/src/decoder/OpusDecoderPlugin.h b/src/decoder/plugins/OpusDecoderPlugin.h
index 260dab99a..260dab99a 100644
--- a/src/decoder/OpusDecoderPlugin.h
+++ b/src/decoder/plugins/OpusDecoderPlugin.h
diff --git a/src/decoder/OpusDomain.cxx b/src/decoder/plugins/OpusDomain.cxx
index 1efd64a48..1efd64a48 100644
--- a/src/decoder/OpusDomain.cxx
+++ b/src/decoder/plugins/OpusDomain.cxx
diff --git a/src/decoder/OpusDomain.hxx b/src/decoder/plugins/OpusDomain.hxx
index fb19e0301..fb19e0301 100644
--- a/src/decoder/OpusDomain.hxx
+++ b/src/decoder/plugins/OpusDomain.hxx
diff --git a/src/decoder/OpusHead.cxx b/src/decoder/plugins/OpusHead.cxx
index bfa41d618..bfa41d618 100644
--- a/src/decoder/OpusHead.cxx
+++ b/src/decoder/plugins/OpusHead.cxx
diff --git a/src/decoder/OpusHead.hxx b/src/decoder/plugins/OpusHead.hxx
index c478d8d90..c478d8d90 100644
--- a/src/decoder/OpusHead.hxx
+++ b/src/decoder/plugins/OpusHead.hxx
diff --git a/src/decoder/OpusReader.hxx b/src/decoder/plugins/OpusReader.hxx
index c5b8e9107..c5b8e9107 100644
--- a/src/decoder/OpusReader.hxx
+++ b/src/decoder/plugins/OpusReader.hxx
diff --git a/src/decoder/OpusTags.cxx b/src/decoder/plugins/OpusTags.cxx
index aff5479c0..aff5479c0 100644
--- a/src/decoder/OpusTags.cxx
+++ b/src/decoder/plugins/OpusTags.cxx
diff --git a/src/decoder/OpusTags.hxx b/src/decoder/plugins/OpusTags.hxx
index be3ac3a8d..be3ac3a8d 100644
--- a/src/decoder/OpusTags.hxx
+++ b/src/decoder/plugins/OpusTags.hxx
diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/plugins/PcmDecoderPlugin.cxx
index 35ccc5d12..d92aecd51 100644
--- a/src/decoder/PcmDecoderPlugin.cxx
+++ b/src/decoder/plugins/PcmDecoderPlugin.cxx
@@ -18,8 +18,8 @@
*/
#include "config.h"
-#include "decoder/PcmDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "PcmDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "util/Error.hxx"
#include "util/ByteReverse.hxx"
diff --git a/src/decoder/PcmDecoderPlugin.hxx b/src/decoder/plugins/PcmDecoderPlugin.hxx
index 3582e5856..3582e5856 100644
--- a/src/decoder/PcmDecoderPlugin.hxx
+++ b/src/decoder/plugins/PcmDecoderPlugin.hxx
diff --git a/src/decoder/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx
index 8b3e3f8c0..8b3e3f8c0 100644
--- a/src/decoder/SidplayDecoderPlugin.cxx
+++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx
diff --git a/src/decoder/SidplayDecoderPlugin.hxx b/src/decoder/plugins/SidplayDecoderPlugin.hxx
index 58786e646..58786e646 100644
--- a/src/decoder/SidplayDecoderPlugin.hxx
+++ b/src/decoder/plugins/SidplayDecoderPlugin.hxx
diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/plugins/SndfileDecoderPlugin.cxx
index 941df33ab..8db078504 100644
--- a/src/decoder/SndfileDecoderPlugin.cxx
+++ b/src/decoder/plugins/SndfileDecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "SndfileDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
diff --git a/src/decoder/SndfileDecoderPlugin.hxx b/src/decoder/plugins/SndfileDecoderPlugin.hxx
index d56acdd5a..d56acdd5a 100644
--- a/src/decoder/SndfileDecoderPlugin.hxx
+++ b/src/decoder/plugins/SndfileDecoderPlugin.hxx
diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/plugins/VorbisComments.cxx
index 2a0820ab5..2a0820ab5 100644
--- a/src/decoder/VorbisComments.cxx
+++ b/src/decoder/plugins/VorbisComments.cxx
diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/plugins/VorbisComments.hxx
index 893c89277..893c89277 100644
--- a/src/decoder/VorbisComments.hxx
+++ b/src/decoder/plugins/VorbisComments.hxx
diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/plugins/VorbisDecoderPlugin.cxx
index c69ad4386..35551604e 100644
--- a/src/decoder/VorbisDecoderPlugin.cxx
+++ b/src/decoder/plugins/VorbisDecoderPlugin.cxx
@@ -21,7 +21,7 @@
#include "VorbisDecoderPlugin.h"
#include "VorbisComments.hxx"
#include "VorbisDomain.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "OggCodec.hxx"
#include "util/Error.hxx"
diff --git a/src/decoder/VorbisDecoderPlugin.h b/src/decoder/plugins/VorbisDecoderPlugin.h
index b54df2e97..b54df2e97 100644
--- a/src/decoder/VorbisDecoderPlugin.h
+++ b/src/decoder/plugins/VorbisDecoderPlugin.h
diff --git a/src/decoder/VorbisDomain.cxx b/src/decoder/plugins/VorbisDomain.cxx
index e3d880efa..e3d880efa 100644
--- a/src/decoder/VorbisDomain.cxx
+++ b/src/decoder/plugins/VorbisDomain.cxx
diff --git a/src/decoder/VorbisDomain.hxx b/src/decoder/plugins/VorbisDomain.hxx
index 48715e328..48715e328 100644
--- a/src/decoder/VorbisDomain.hxx
+++ b/src/decoder/plugins/VorbisDomain.hxx
diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/plugins/WavpackDecoderPlugin.cxx
index 3aceb3ae7..c217e513e 100644
--- a/src/decoder/WavpackDecoderPlugin.cxx
+++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "WavpackDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
diff --git a/src/decoder/WavpackDecoderPlugin.hxx b/src/decoder/plugins/WavpackDecoderPlugin.hxx
index 2e5f9bd42..2e5f9bd42 100644
--- a/src/decoder/WavpackDecoderPlugin.hxx
+++ b/src/decoder/plugins/WavpackDecoderPlugin.hxx
diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/plugins/WildmidiDecoderPlugin.cxx
index 3bc433b4b..9a6018022 100644
--- a/src/decoder/WildmidiDecoderPlugin.cxx
+++ b/src/decoder/plugins/WildmidiDecoderPlugin.cxx
@@ -19,7 +19,7 @@
#include "config.h"
#include "WildmidiDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "tag/TagHandler.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
diff --git a/src/decoder/WildmidiDecoderPlugin.hxx b/src/decoder/plugins/WildmidiDecoderPlugin.hxx
index fc87aab80..fc87aab80 100644
--- a/src/decoder/WildmidiDecoderPlugin.hxx
+++ b/src/decoder/plugins/WildmidiDecoderPlugin.hxx
diff --git a/src/decoder/XiphTags.cxx b/src/decoder/plugins/XiphTags.cxx
index a5e534086..a5e534086 100644
--- a/src/decoder/XiphTags.cxx
+++ b/src/decoder/plugins/XiphTags.cxx
diff --git a/src/decoder/XiphTags.hxx b/src/decoder/plugins/XiphTags.hxx
index 48a27425f..48a27425f 100644
--- a/src/decoder/XiphTags.hxx
+++ b/src/decoder/plugins/XiphTags.hxx