diff options
Diffstat (limited to 'src/decoder')
-rw-r--r-- | src/decoder/DecoderAPI.cxx | 597 | ||||
-rw-r--r-- | src/decoder/DecoderAPI.hxx | 213 | ||||
-rw-r--r-- | src/decoder/DecoderBuffer.cxx | 168 | ||||
-rw-r--r-- | src/decoder/DecoderBuffer.hxx | 111 | ||||
-rw-r--r-- | src/decoder/DecoderCommand.hxx | 32 | ||||
-rw-r--r-- | src/decoder/DecoderControl.cxx | 140 | ||||
-rw-r--r-- | src/decoder/DecoderControl.hxx | 395 | ||||
-rw-r--r-- | src/decoder/DecoderError.cxx | 23 | ||||
-rw-r--r-- | src/decoder/DecoderError.hxx | 25 | ||||
-rw-r--r-- | src/decoder/DecoderInternal.cxx | 101 | ||||
-rw-r--r-- | src/decoder/DecoderInternal.hxx | 125 | ||||
-rw-r--r-- | src/decoder/DecoderList.cxx | 186 | ||||
-rw-r--r-- | src/decoder/DecoderList.hxx | 89 | ||||
-rw-r--r-- | src/decoder/DecoderPlugin.cxx | 42 | ||||
-rw-r--r-- | src/decoder/DecoderPlugin.hxx | 180 | ||||
-rw-r--r-- | src/decoder/DecoderPrint.cxx | 55 | ||||
-rw-r--r-- | src/decoder/DecoderPrint.hxx | 28 | ||||
-rw-r--r-- | src/decoder/DecoderThread.cxx | 476 | ||||
-rw-r--r-- | src/decoder/DecoderThread.hxx | 28 | ||||
-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 = ∅ + 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 ¶m); + + /** + * 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 ¶m) 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 |