aboutsummaryrefslogtreecommitdiffstats
path: root/src/decoder
diff options
context:
space:
mode:
Diffstat (limited to 'src/decoder')
-rw-r--r--src/decoder/DecoderAPI.cxx641
-rw-r--r--src/decoder/DecoderAPI.hxx237
-rw-r--r--src/decoder/DecoderBuffer.cxx71
-rw-r--r--src/decoder/DecoderBuffer.hxx117
-rw-r--r--src/decoder/DecoderCommand.hxx32
-rw-r--r--src/decoder/DecoderControl.cxx139
-rw-r--r--src/decoder/DecoderControl.hxx395
-rw-r--r--src/decoder/DecoderError.cxx23
-rw-r--r--src/decoder/DecoderError.hxx25
-rw-r--r--src/decoder/DecoderInternal.cxx104
-rw-r--r--src/decoder/DecoderInternal.hxx126
-rw-r--r--src/decoder/DecoderList.cxx163
-rw-r--r--src/decoder/DecoderList.hxx89
-rw-r--r--src/decoder/DecoderPlugin.cxx42
-rw-r--r--src/decoder/DecoderPlugin.hxx186
-rw-r--r--src/decoder/DecoderPrint.cxx55
-rw-r--r--src/decoder/DecoderPrint.hxx28
-rw-r--r--src/decoder/DecoderThread.cxx509
-rw-r--r--src/decoder/DecoderThread.hxx28
-rw-r--r--src/decoder/plugins/AdPlugDecoderPlugin.cxx (renamed from src/decoder/AdPlugDecoderPlugin.cxx)27
-rw-r--r--src/decoder/plugins/AdPlugDecoderPlugin.h (renamed from src/decoder/AdPlugDecoderPlugin.h)2
-rw-r--r--src/decoder/plugins/AudiofileDecoderPlugin.cxx (renamed from src/decoder/AudiofileDecoderPlugin.cxx)146
-rw-r--r--src/decoder/plugins/AudiofileDecoderPlugin.hxx (renamed from src/decoder/AudiofileDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/DsdLib.cxx (renamed from src/decoder/DsdLib.cxx)85
-rw-r--r--src/decoder/plugins/DsdLib.hxx (renamed from src/decoder/DsdLib.hxx)16
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.cxx (renamed from src/decoder/DsdiffDecoderPlugin.cxx)166
-rw-r--r--src/decoder/plugins/DsdiffDecoderPlugin.hxx (renamed from src/decoder/DsdiffDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.cxx (renamed from src/decoder/DsfDecoderPlugin.cxx)210
-rw-r--r--src/decoder/plugins/DsfDecoderPlugin.hxx (renamed from src/decoder/DsfDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/FaadDecoderPlugin.cxx (renamed from src/decoder/FaadDecoderPlugin.cxx)296
-rw-r--r--src/decoder/plugins/FaadDecoderPlugin.hxx (renamed from src/decoder/FaadDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.cxx (renamed from src/decoder/FfmpegDecoderPlugin.cxx)85
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.hxx (renamed from src/decoder/FfmpegDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/FfmpegMetaData.cxx (renamed from src/decoder/FfmpegMetaData.cxx)2
-rw-r--r--src/decoder/plugins/FfmpegMetaData.hxx (renamed from src/decoder/FfmpegMetaData.hxx)2
-rw-r--r--src/decoder/plugins/FlacCommon.cxx (renamed from src/decoder/FlacCommon.cxx)23
-rw-r--r--src/decoder/plugins/FlacCommon.hxx (renamed from src/decoder/FlacCommon.hxx)5
-rw-r--r--src/decoder/plugins/FlacDecoderPlugin.cxx (renamed from src/decoder/FlacDecoderPlugin.cxx)30
-rw-r--r--src/decoder/plugins/FlacDecoderPlugin.h (renamed from src/decoder/FlacDecoderPlugin.h)2
-rw-r--r--src/decoder/plugins/FlacDomain.cxx (renamed from src/decoder/FlacDomain.cxx)2
-rw-r--r--src/decoder/plugins/FlacDomain.hxx (renamed from src/decoder/FlacDomain.hxx)2
-rw-r--r--src/decoder/plugins/FlacIOHandle.cxx (renamed from src/decoder/FlacIOHandle.cxx)30
-rw-r--r--src/decoder/plugins/FlacIOHandle.hxx (renamed from src/decoder/FlacIOHandle.hxx)6
-rw-r--r--src/decoder/plugins/FlacInput.cxx (renamed from src/decoder/FlacInput.cxx)18
-rw-r--r--src/decoder/plugins/FlacInput.hxx (renamed from src/decoder/FlacInput.hxx)4
-rw-r--r--src/decoder/plugins/FlacMetadata.cxx (renamed from src/decoder/FlacMetadata.cxx)152
-rw-r--r--src/decoder/plugins/FlacMetadata.hxx (renamed from src/decoder/FlacMetadata.hxx)26
-rw-r--r--src/decoder/plugins/FlacPcm.cxx (renamed from src/decoder/FlacPcm.cxx)2
-rw-r--r--src/decoder/plugins/FlacPcm.hxx (renamed from src/decoder/FlacPcm.hxx)2
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.cxx (renamed from src/decoder/FluidsynthDecoderPlugin.cxx)16
-rw-r--r--src/decoder/plugins/FluidsynthDecoderPlugin.hxx (renamed from src/decoder/FluidsynthDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/GmeDecoderPlugin.cxx (renamed from src/decoder/GmeDecoderPlugin.cxx)47
-rw-r--r--src/decoder/plugins/GmeDecoderPlugin.hxx (renamed from src/decoder/GmeDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.cxx (renamed from src/decoder/MadDecoderPlugin.cxx)325
-rw-r--r--src/decoder/plugins/MadDecoderPlugin.hxx (renamed from src/decoder/MadDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/MikmodDecoderPlugin.cxx (renamed from src/decoder/MikmodDecoderPlugin.cxx)22
-rw-r--r--src/decoder/plugins/MikmodDecoderPlugin.hxx (renamed from src/decoder/MikmodDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/ModplugDecoderPlugin.cxx (renamed from src/decoder/ModplugDecoderPlugin.cxx)48
-rw-r--r--src/decoder/plugins/ModplugDecoderPlugin.hxx (renamed from src/decoder/ModplugDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/MpcdecDecoderPlugin.cxx (renamed from src/decoder/MpcdecDecoderPlugin.cxx)32
-rw-r--r--src/decoder/plugins/MpcdecDecoderPlugin.hxx (renamed from src/decoder/MpcdecDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/Mpg123DecoderPlugin.cxx (renamed from src/decoder/Mpg123DecoderPlugin.cxx)151
-rw-r--r--src/decoder/plugins/Mpg123DecoderPlugin.hxx (renamed from src/decoder/Mpg123DecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/OggCodec.cxx (renamed from src/decoder/OggCodec.cxx)3
-rw-r--r--src/decoder/plugins/OggCodec.hxx (renamed from src/decoder/OggCodec.hxx)5
-rw-r--r--src/decoder/plugins/OggFind.cxx (renamed from src/decoder/OggFind.cxx)16
-rw-r--r--src/decoder/plugins/OggFind.hxx (renamed from src/decoder/OggFind.hxx)8
-rw-r--r--src/decoder/plugins/OggSyncState.hxx (renamed from src/decoder/OggSyncState.hxx)2
-rw-r--r--src/decoder/plugins/OggUtil.cxx (renamed from src/decoder/OggUtil.cxx)4
-rw-r--r--src/decoder/plugins/OggUtil.hxx (renamed from src/decoder/OggUtil.hxx)4
-rw-r--r--src/decoder/plugins/OpusDecoderPlugin.cxx (renamed from src/decoder/OpusDecoderPlugin.cxx)154
-rw-r--r--src/decoder/plugins/OpusDecoderPlugin.h (renamed from src/decoder/OpusDecoderPlugin.h)2
-rw-r--r--src/decoder/plugins/OpusDomain.cxx (renamed from src/decoder/OpusDomain.cxx)2
-rw-r--r--src/decoder/plugins/OpusDomain.hxx (renamed from src/decoder/OpusDomain.hxx)2
-rw-r--r--src/decoder/plugins/OpusHead.cxx (renamed from src/decoder/OpusHead.cxx)3
-rw-r--r--src/decoder/plugins/OpusHead.hxx (renamed from src/decoder/OpusHead.hxx)2
-rw-r--r--src/decoder/plugins/OpusReader.hxx (renamed from src/decoder/OpusReader.hxx)2
-rw-r--r--src/decoder/plugins/OpusTags.cxx (renamed from src/decoder/OpusTags.cxx)2
-rw-r--r--src/decoder/plugins/OpusTags.hxx (renamed from src/decoder/OpusTags.hxx)2
-rw-r--r--src/decoder/plugins/PcmDecoderPlugin.cxx (renamed from src/decoder/PcmDecoderPlugin.cxx)27
-rw-r--r--src/decoder/plugins/PcmDecoderPlugin.hxx (renamed from src/decoder/PcmDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.cxx (renamed from src/decoder/SidplayDecoderPlugin.cxx)70
-rw-r--r--src/decoder/plugins/SidplayDecoderPlugin.hxx (renamed from src/decoder/SidplayDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/SndfileDecoderPlugin.cxx (renamed from src/decoder/SndfileDecoderPlugin.cxx)172
-rw-r--r--src/decoder/plugins/SndfileDecoderPlugin.hxx (renamed from src/decoder/SndfileDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/VorbisComments.cxx (renamed from src/decoder/VorbisComments.cxx)55
-rw-r--r--src/decoder/plugins/VorbisComments.hxx (renamed from src/decoder/VorbisComments.hxx)4
-rw-r--r--src/decoder/plugins/VorbisDecoderPlugin.cxx (renamed from src/decoder/VorbisDecoderPlugin.cxx)125
-rw-r--r--src/decoder/plugins/VorbisDecoderPlugin.h (renamed from src/decoder/VorbisDecoderPlugin.h)2
-rw-r--r--src/decoder/plugins/VorbisDomain.cxx (renamed from src/decoder/VorbisDomain.cxx)2
-rw-r--r--src/decoder/plugins/VorbisDomain.hxx (renamed from src/decoder/VorbisDomain.hxx)6
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.cxx (renamed from src/decoder/WavpackDecoderPlugin.cxx)178
-rw-r--r--src/decoder/plugins/WavpackDecoderPlugin.hxx (renamed from src/decoder/WavpackDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/WildmidiDecoderPlugin.cxx (renamed from src/decoder/WildmidiDecoderPlugin.cxx)28
-rw-r--r--src/decoder/plugins/WildmidiDecoderPlugin.hxx (renamed from src/decoder/WildmidiDecoderPlugin.hxx)2
-rw-r--r--src/decoder/plugins/XiphTags.cxx (renamed from src/decoder/XiphTags.cxx)7
-rw-r--r--src/decoder/plugins/XiphTags.hxx (renamed from src/decoder/XiphTags.hxx)2
97 files changed, 4541 insertions, 1376 deletions
diff --git a/src/decoder/DecoderAPI.cxx b/src/decoder/DecoderAPI.cxx
new file mode 100644
index 000000000..4794d60e7
--- /dev/null
+++ b/src/decoder/DecoderAPI.cxx
@@ -0,0 +1,641 @@
+/*
+ * 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 "input/InputStream.hxx"
+#include "util/Error.hxx"
+#include "util/ConstBuffer.hxx"
+#include "Log.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+
+void
+decoder_initialized(Decoder &decoder,
+ const AudioFormat audio_format,
+ bool seekable, SignedSongTime duration)
+{
+ DecoderControl &dc = decoder.dc;
+ struct audio_format_string af_string;
+
+ assert(dc.state == DecoderState::START);
+ assert(dc.pipe != nullptr);
+ assert(dc.pipe->IsEmpty());
+ 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 = duration;
+
+ 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_time.ToDoubleS();
+ 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_time.ToDoubleS();
+ }
+
+ dc.command = DecoderCommand::NONE;
+ dc.client_cond.signal();
+ dc.Unlock();
+}
+
+SongTime
+decoder_seek_time(Decoder &decoder)
+{
+ const DecoderControl &dc = decoder.dc;
+
+ assert(dc.pipe != nullptr);
+
+ if (decoder.initial_seek_running)
+ return dc.start_time;
+
+ assert(dc.command == DecoderCommand::SEEK);
+
+ decoder.seeking = true;
+
+ return dc.seek_time;
+}
+
+uint64_t
+decoder_seek_where_frame(Decoder &decoder)
+{
+ const DecoderControl &dc = decoder.dc;
+
+ return decoder_seek_time(decoder).ToScale<uint64_t>(dc.in_audio_format.sample_rate);
+}
+
+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);
+}
+
+InputStream *
+decoder_open_uri(Decoder &decoder, const char *uri, Error &error)
+{
+ assert(decoder.dc.state == DecoderState::START ||
+ decoder.dc.state == DecoderState::DECODE);
+
+ DecoderControl &dc = decoder.dc;
+ Mutex &mutex = dc.mutex;
+ Cond &cond = dc.cond;
+
+ InputStream *is = InputStream::Open(uri, mutex, cond, error);
+ if (is == nullptr)
+ return nullptr;
+
+ mutex.lock();
+ while (true) {
+ is->Update();
+ if (is->IsReady()) {
+ mutex.unlock();
+ return is;
+ }
+
+ if (dc.command == DecoderCommand::STOP) {
+ mutex.unlock();
+ delete is;
+ return nullptr;
+ }
+
+ cond.wait(mutex);
+ }
+}
+
+/**
+ * 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)
+{
+ MusicChunk *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;
+
+ assert(!decoder.initial_seek_pending);
+ assert(!decoder.initial_seek_running);
+
+ /* 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;
+ auto result = decoder.convert->Convert({data, 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;
+ }
+
+ data = result.data;
+ length = result.size;
+ } else {
+ assert(dc.in_audio_format == dc.out_audio_format);
+ }
+
+ while (length > 0) {
+ MusicChunk *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,
+ SongTime::FromS(decoder.timestamp) -
+ dc.song->GetStartTime(),
+ kbit_rate);
+ if (dest.IsEmpty()) {
+ /* the chunk is full, flush it */
+ decoder.FlushChunk();
+ continue;
+ }
+
+ const size_t nbytes = std::min(dest.size, 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_time.IsPositive() &&
+ decoder.timestamp >= dc.end_time.ToDoubleS())
+ /* 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..b756331d9
--- /dev/null
+++ b/src/decoder/DecoderAPI.hxx
@@ -0,0 +1,237 @@
+/*
+ * 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 "config/ConfigData.hxx"
+#include "Chrono.hxx"
+
+// IWYU pragma: end_exports
+
+#include <stdint.h>
+
+class Error;
+
+/**
+ * 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 duration the total duration of this song; negative if
+ * unknown
+ */
+void
+decoder_initialized(Decoder &decoder,
+ AudioFormat audio_format,
+ bool seekable, SignedSongTime duration);
+
+/**
+ * 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 seek in milliseconds
+ */
+gcc_pure
+SongTime
+decoder_seek_time(Decoder &decoder);
+
+/**
+ * Call this when you have received the DecoderCommand::SEEK command.
+ *
+ * @param decoder the decoder object
+ * @return the destination position for the seek in frames
+ */
+gcc_pure
+uint64_t
+decoder_seek_where_frame(Decoder &decoder);
+
+/**
+ * Call this instead of decoder_command_finished() when seeking has
+ * failed.
+ *
+ * @param decoder the decoder object
+ */
+void
+decoder_seek_error(Decoder &decoder);
+
+/**
+ * Open a new #InputStream and wait until it's ready. Can get
+ * cancelled by DecoderCommand::STOP (returns nullptr without setting
+ * #Error).
+ */
+InputStream *
+decoder_open_uri(Decoder &decoder, const char *uri, Error &error);
+
+/**
+ * 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..a8958d6fd
--- /dev/null
+++ b/src/decoder/DecoderBuffer.cxx
@@ -0,0 +1,71 @@
+/*
+ * 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 <assert.h>
+
+bool
+DecoderBuffer::Fill()
+{
+ auto w = buffer.Write();
+ if (w.IsEmpty())
+ /* buffer is full */
+ return false;
+
+ size_t nbytes = decoder_read(decoder, is,
+ w.data, w.size);
+ if (nbytes == 0)
+ /* end of file, I/O error or decoder command
+ received */
+ return false;
+
+ buffer.Append(nbytes);
+ return true;
+}
+
+ConstBuffer<void>
+DecoderBuffer::Need(size_t min_size)
+{
+ while (true) {
+ const auto r = Read();
+ if (r.size >= min_size)
+ return r;
+
+ if (!Fill())
+ return nullptr;
+ }
+}
+
+bool
+DecoderBuffer::Skip(size_t nbytes)
+{
+ const auto r = buffer.Read();
+ if (r.size >= nbytes) {
+ buffer.Consume(nbytes);
+ return true;
+ }
+
+ buffer.Clear();
+ nbytes -= r.size;
+
+ return decoder_skip(decoder, is, nbytes);
+}
diff --git a/src/decoder/DecoderBuffer.hxx b/src/decoder/DecoderBuffer.hxx
new file mode 100644
index 000000000..9cf47d915
--- /dev/null
+++ b/src/decoder/DecoderBuffer.hxx
@@ -0,0 +1,117 @@
+/*
+ * 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 "util/DynamicFifoBuffer.hxx"
+#include "util/ConstBuffer.hxx"
+
+#include <stddef.h>
+
+struct Decoder;
+class InputStream;
+
+/**
+ * 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.
+ */
+class DecoderBuffer {
+ Decoder *const decoder;
+ InputStream &is;
+
+ DynamicFifoBuffer<uint8_t> buffer;
+
+public:
+ /**
+ * 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
+ */
+ DecoderBuffer(Decoder *_decoder, InputStream &_is,
+ size_t _size)
+ :decoder(_decoder), is(_is), buffer(_size) {}
+
+ const InputStream &GetStream() const {
+ return is;
+ }
+
+ void Clear() {
+ buffer.Clear();
+ }
+
+ /**
+ * Read data from the #InputStream 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 Fill();
+
+ /**
+ * How many bytes are stored in the buffer?
+ */
+ gcc_pure
+ size_t GetAvailable() const {
+ return buffer.GetAvailable();
+ }
+
+ /**
+ * Reads data from the buffer. This data is not yet consumed,
+ * you have to call Consume() to do that. The returned buffer
+ * becomes invalid after a Fill() or a Consume() call.
+ */
+ ConstBuffer<void> Read() const {
+ auto r = buffer.Read();
+ return { r.data, r.size };
+ }
+
+ /**
+ * Wait until this number of bytes are available. Returns nullptr on
+ * error.
+ */
+ ConstBuffer<void> Need(size_t min_size);
+
+ /**
+ * Consume (delete, invalidate) a part of the buffer. The
+ * "nbytes" parameter must not be larger than the length
+ * returned by Read().
+ *
+ * @param nbytes the number of bytes to consume
+ */
+ void Consume(size_t nbytes) {
+ buffer.Consume(nbytes);
+ }
+
+ /**
+ * Skips the specified number of bytes, discarding its data.
+ *
+ * @param nbytes the number of bytes to skip
+ * @return true on success, false on error
+ */
+ bool Skip(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..c30da6214
--- /dev/null
+++ b/src/decoder/DecoderControl.cxx
@@ -0,0 +1,139 @@
+/*
+ * 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,
+ SongTime _start_time, SongTime _end_time,
+ MusicBuffer &_buffer, MusicPipe &_pipe)
+{
+ assert(_song != nullptr);
+ assert(_pipe.IsEmpty());
+
+ delete song;
+ song = _song;
+ start_time = _start_time;
+ end_time = _end_time;
+ 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(SongTime t)
+{
+ assert(state != DecoderState::START);
+
+ if (state == DecoderState::STOP ||
+ state == DecoderState::ERROR || !seekable)
+ return false;
+
+ seek_time = t;
+ 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..ed2b8c538
--- /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 "Chrono.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;
+ SongTime seek_time;
+
+ /** 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, e.g. to the start of a sub-track
+ * described by a CUE file.
+ *
+ * This attribute is set by Start().
+ */
+ SongTime start_time;
+
+ /**
+ * The decoder will stop when it reaches this position. 0
+ * means don't stop before the end of the file.
+ *
+ * This attribute is set by Start().
+ */
+ SongTime end_time;
+
+ SignedSongTime total_time;
+
+ /** the #MusicChunk 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 GetError(), 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_time see #DecoderControl
+ * @param end_time see #DecoderControl
+ * @param pipe the pipe which receives the decoded chunks (owned by
+ * the caller)
+ */
+ void Start(DetachedSong *song, SongTime start_time, SongTime end_time,
+ MusicBuffer &buffer, MusicPipe &pipe);
+
+ void Stop();
+
+ bool Seek(SongTime t);
+
+ 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..f35878682
--- /dev/null
+++ b/src/decoder/DecoderInternal.cxx
@@ -0,0 +1,104 @@
+/*
+ * 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;
+}
+
+MusicChunk *
+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(!seeking);
+ assert(!initial_seek_running);
+ assert(!initial_seek_pending);
+ 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..24b665e85
--- /dev/null
+++ b/src/decoder/DecoderInternal.hxx
@@ -0,0 +1,126 @@
+/*
+ * 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 MusicChunk;
+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_time(), 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 */
+ MusicChunk *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
+ */
+ MusicChunk *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..cd6881ce2
--- /dev/null
+++ b/src/decoder/DecoderList.cxx
@@ -0,0 +1,163 @@
+/*
+ * 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 "config/ConfigGlobal.hxx"
+#include "config/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 "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
+#ifdef ENABLE_DSD
+ &dsdiff_decoder_plugin,
+ &dsf_decoder_plugin,
+#endif
+#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;
+ });
+}
+
+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 =
+ config_find_block(CONF_DECODER, "plugin", plugin.name);
+
+ if (param == nullptr)
+ param = &empty;
+ else if (!param->GetBlockValue("enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ if (plugin.Init(*param))
+ decoder_plugins_enabled[i] = true;
+ }
+}
+
+void decoder_plugin_deinit_all(void)
+{
+ decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){
+ plugin.Finish();
+ });
+}
+
+bool
+decoder_plugins_supports_suffix(const char *suffix)
+{
+ return decoder_plugins_try([suffix](const DecoderPlugin &plugin){
+ return plugin.SupportsSuffix(suffix);
+ });
+}
diff --git a/src/decoder/DecoderList.hxx b/src/decoder/DecoderList.hxx
new file mode 100644
index 000000000..47085d4ae
--- /dev/null
+++ b/src/decoder/DecoderList.hxx
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_LIST_HXX
+#define MPD_DECODER_LIST_HXX
+
+#include "Compiler.h"
+
+struct DecoderPlugin;
+
+extern const struct DecoderPlugin *const decoder_plugins[];
+extern bool decoder_plugins_enabled[];
+
+/* interface for using plugins */
+
+gcc_pure
+const struct DecoderPlugin *
+decoder_plugin_from_name(const char *name);
+
+/* this is where we "load" all the "plugins" ;-) */
+void decoder_plugin_init_all(void);
+
+/* this is where we "unload" all the "plugins" */
+void decoder_plugin_deinit_all(void);
+
+template<typename F>
+static inline const DecoderPlugin *
+decoder_plugins_find(F f)
+{
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
+ if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))
+ return decoder_plugins[i];
+
+ return nullptr;
+}
+
+template<typename F>
+static inline bool
+decoder_plugins_try(F f)
+{
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
+ if (decoder_plugins_enabled[i] && f(*decoder_plugins[i]))
+ return true;
+
+ return false;
+}
+
+template<typename F>
+static inline void
+decoder_plugins_for_each(F f)
+{
+ for (auto i = decoder_plugins; *i != nullptr; ++i)
+ f(**i);
+}
+
+template<typename F>
+static inline void
+decoder_plugins_for_each_enabled(F f)
+{
+ for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i)
+ if (decoder_plugins_enabled[i])
+ f(*decoder_plugins[i]);
+}
+
+/**
+ * Is there at least once #DecoderPlugin that supports the specified
+ * file name suffix?
+ */
+gcc_pure gcc_nonnull_all
+bool
+decoder_plugins_supports_suffix(const char *suffix);
+
+#endif
diff --git a/src/decoder/DecoderPlugin.cxx b/src/decoder/DecoderPlugin.cxx
new file mode 100644
index 000000000..3be812c3b
--- /dev/null
+++ b/src/decoder/DecoderPlugin.cxx
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "DecoderPlugin.hxx"
+#include "util/StringUtil.hxx"
+
+#include <assert.h>
+
+bool
+DecoderPlugin::SupportsSuffix(const char *suffix) const
+{
+ assert(suffix != nullptr);
+
+ return suffixes != nullptr && string_array_contains(suffixes, suffix);
+
+}
+
+bool
+DecoderPlugin::SupportsMimeType(const char *mime_type) const
+{
+ assert(mime_type != nullptr);
+
+ return mime_types != nullptr &&
+ string_array_contains(mime_types, mime_type);
+}
diff --git a/src/decoder/DecoderPlugin.hxx b/src/decoder/DecoderPlugin.hxx
new file mode 100644
index 000000000..dbf3db9aa
--- /dev/null
+++ b/src/decoder/DecoderPlugin.hxx
@@ -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.
+ */
+
+#ifndef MPD_DECODER_PLUGIN_HXX
+#define MPD_DECODER_PLUGIN_HXX
+
+#include "Compiler.h"
+
+struct config_param;
+class InputStream;
+struct tag_handler;
+class Path;
+
+/**
+ * Opaque handle which the decoder plugin passes to the functions in
+ * this header.
+ */
+struct Decoder;
+
+struct DecoderPlugin {
+ const char *name;
+
+ /**
+ * Initialize the decoder plugin. Optional method.
+ *
+ * @param param a configuration block for this plugin, or nullptr
+ * if none is configured
+ * @return true if the plugin was initialized successfully,
+ * false if the plugin is not available
+ */
+ bool (*init)(const config_param &param);
+
+ /**
+ * Deinitialize a decoder plugin which was initialized
+ * successfully. Optional method.
+ */
+ void (*finish)(void);
+
+ /**
+ * Decode a stream (data read from an #input_stream object).
+ *
+ * Either implement this method or file_decode(). If
+ * possible, it is recommended to implement this method,
+ * because it is more versatile.
+ */
+ void (*stream_decode)(Decoder &decoder, InputStream &is);
+
+ /**
+ * Decode a local file.
+ *
+ * Either implement this method or stream_decode().
+ */
+ void (*file_decode)(Decoder &decoder, Path path_fs);
+
+ /**
+ * Scan metadata of a file.
+ *
+ * @return false if the operation has failed
+ */
+ bool (*scan_file)(Path 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
+ *
+ * Free the return value with delete[].
+ */
+ char* (*container_scan)(Path path_fs, const unsigned int tnum);
+
+ /* last element in these arrays must always be a nullptr: */
+ const char *const*suffixes;
+ const char *const*mime_types;
+
+ /**
+ * Initialize a decoder plugin.
+ *
+ * @param param a configuration block for this plugin, or nullptr if none
+ * is configured
+ * @return true if the plugin was initialized successfully, false if
+ * the plugin is not available
+ */
+ bool Init(const config_param &param) const {
+ return init != nullptr
+ ? init(param)
+ : true;
+ }
+
+ /**
+ * Deinitialize a decoder plugin which was initialized successfully.
+ */
+ void Finish() const {
+ if (finish != nullptr)
+ finish();
+ }
+
+ /**
+ * Decode a stream.
+ */
+ void StreamDecode(Decoder &decoder, InputStream &is) const {
+ stream_decode(decoder, is);
+ }
+
+ /**
+ * Decode a file.
+ */
+ template<typename P>
+ void FileDecode(Decoder &decoder, P path_fs) const {
+ file_decode(decoder, path_fs);
+ }
+
+ /**
+ * Read the tag of a file.
+ */
+ template<typename P>
+ bool ScanFile(P 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
+ */
+ template<typename P>
+ char *ContainerScan(P 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..54b89c36c
--- /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/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..dd5518b98
--- /dev/null
+++ b/src/decoder/DecoderThread.cxx
@@ -0,0 +1,509 @@
+/*
+ * 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 "MusicPipe.hxx"
+#include "fs/Traits.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "input/LocalOpen.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->IsReady() &&
+ dc.command != DecoderCommand::STOP) {
+ dc.Wait();
+
+ is->Update();
+ }
+
+ if (!is->Check(error)) {
+ dc.Unlock();
+
+ LogError(error);
+ return nullptr;
+ }
+
+ dc.Unlock();
+
+ return is;
+}
+
+static InputStream *
+decoder_input_stream_open(DecoderControl &dc, Path path)
+{
+ Error error;
+
+ InputStream *is = OpenLocalInputStream(path, dc.mutex, dc.cond, error);
+ if (is == nullptr) {
+ LogError(error);
+ return nullptr;
+ }
+
+ assert(is->IsReady());
+
+ 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.IsReady());
+ 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, Path path)
+{
+ assert(plugin.file_decode != nullptr);
+ assert(decoder.stream_tag == nullptr);
+ assert(decoder.decoder_tag == nullptr);
+ assert(!path.IsNull());
+ assert(path.IsAbsolute());
+ 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);
+
+ const char *mime_type = is.GetMimeType();
+ return mime_type != nullptr && plugin.SupportsMimeType(mime_type);
+}
+
+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)
+{
+ UriSuffixBuffer suffix_buffer;
+ const char *const suffix = uri_get_suffix(uri, suffix_buffer);
+
+ 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();
+ delete input_stream;
+ 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, Path path_fs)
+{
+ ReplayGainInfo info;
+ if (replay_gain_ape_read(path_fs, info))
+ decoder_replay_gain(decoder, &info);
+}
+
+static bool
+TryDecoderFile(Decoder &decoder, Path 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();
+
+ delete input_stream;
+
+ if (success) {
+ dc.Lock();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Try decoding a file.
+ */
+static bool
+decoder_run_file(Decoder &decoder, const char *uri_utf8, Path path_fs)
+{
+ const char *suffix = uri_get_suffix(uri_utf8);
+ 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, Path path_fs)
+{
+ Decoder decoder(dc, dc.start_time.IsPositive(),
+ new Tag(song.GetTag()));
+ int ret;
+
+ dc.state = DecoderState::START;
+
+ decoder_command_finished_locked(dc);
+
+ ret = !path_fs.IsNull()
+ ? decoder_run_file(decoder, uri, path_fs)
+ : 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 char *const uri_utf8 = song.GetRealURI();
+
+ Path path_fs = Path::Null();
+ AllocatedPath path_buffer = AllocatedPath::Null();
+ if (PathTraitsUTF8::IsAbsolute(uri_utf8)) {
+ path_buffer = AllocatedPath::FromUTF8(uri_utf8, dc.error);
+ if (path_buffer.IsNull()) {
+ dc.state = DecoderState::ERROR;
+ decoder_command_finished_locked(dc);
+ return;
+ }
+
+ path_fs = path_buffer;
+ }
+
+ decoder_run_song(dc, song, uri_utf8, path_fs);
+
+}
+
+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;
+
+ decoder_run(dc);
+ break;
+
+ case DecoderCommand::SEEK:
+ /* this seek was too late, and the decoder had
+ already finished; start a new decoder */
+
+ /* we need to clear the pipe here; usually the
+ PlayerThread is responsible, but it is not
+ aware that the decoder has finished */
+ dc.pipe->Clear(*dc.buffer);
+
+ 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 c79fca5f9..9cc37ade4 100644
--- a/src/decoder/AdPlugDecoderPlugin.cxx
+++ b/src/decoder/plugins/AdPlugDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,9 +20,11 @@
#include "config.h"
#include "AdPlugDecoderPlugin.h"
#include "tag/TagHandler.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
+#include "fs/Path.hxx"
#include "util/Error.hxx"
+#include "util/Domain.hxx"
#include "util/Macros.hxx"
#include "Log.hxx"
@@ -31,11 +33,16 @@
#include <assert.h>
+static constexpr Domain adplug_domain("adplug");
+
static unsigned sample_rate;
static bool
adplug_init(const config_param &param)
{
+ FormatDebug(adplug_domain, "adplug %s",
+ CAdPlug::get_version().c_str());
+
Error error;
sample_rate = param.GetBlockValue("sample_rate", 48000u);
@@ -48,12 +55,12 @@ adplug_init(const config_param &param)
}
static void
-adplug_file_decode(Decoder &decoder, const char *path_fs)
+adplug_file_decode(Decoder &decoder, Path path_fs)
{
CEmuopl opl(sample_rate, true, true);
opl.init();
- CPlayer *player = CAdPlug::factory(path_fs, &opl);
+ CPlayer *player = CAdPlug::factory(path_fs.c_str(), &opl);
if (player == nullptr)
return;
@@ -61,16 +68,16 @@ adplug_file_decode(Decoder &decoder, const char *path_fs)
assert(audio_format.IsValid());
decoder_initialized(decoder, audio_format, false,
- player->songlength() / 1000.);
+ SongTime::FromMS(player->songlength()));
- int16_t buffer[2048];
- const unsigned frames_per_buffer = ARRAY_SIZE(buffer) / 2;
DecoderCommand cmd;
do {
if (!player->update())
break;
+ int16_t buffer[2048];
+ constexpr unsigned frames_per_buffer = ARRAY_SIZE(buffer) / 2;
opl.update(buffer, frames_per_buffer);
cmd = decoder_data(decoder, nullptr,
buffer, sizeof(buffer),
@@ -90,18 +97,18 @@ adplug_scan_tag(TagType type, const std::string &value,
}
static bool
-adplug_scan_file(const char *path_fs,
+adplug_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
CEmuopl opl(sample_rate, true, true);
opl.init();
- CPlayer *player = CAdPlug::factory(path_fs, &opl);
+ CPlayer *player = CAdPlug::factory(path_fs.c_str(), &opl);
if (player == nullptr)
return false;
tag_handler_invoke_duration(handler, handler_ctx,
- player->songlength() / 1000);
+ SongTime::FromMS(player->songlength()));
if (handler->tag != nullptr) {
adplug_scan_tag(TAG_TITLE, player->gettitle(),
diff --git a/src/decoder/AdPlugDecoderPlugin.h b/src/decoder/plugins/AdPlugDecoderPlugin.h
index a827fdc7d..539dbbf0a 100644
--- a/src/decoder/AdPlugDecoderPlugin.h
+++ b/src/decoder/plugins/AdPlugDecoderPlugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/plugins/AudiofileDecoderPlugin.cxx
index b1b8bf613..a0ef71e49 100644
--- a/src/decoder/AudiofileDecoderPlugin.cxx
+++ b/src/decoder/plugins/AudiofileDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,10 +19,11 @@
#include "config.h"
#include "AudiofileDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
+#include "fs/Path.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -33,11 +34,21 @@
#include <assert.h>
#include <stdio.h>
-/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
-#define CHUNK_SIZE 1020
-
static constexpr Domain audiofile_domain("audiofile");
+static void
+audiofile_error_func(long, const char *msg)
+{
+ LogWarning(audiofile_domain, msg);
+}
+
+static bool
+audiofile_init(const config_param &)
+{
+ afSetErrorHandler(audiofile_error_func);
+ return true;
+}
+
struct AudioFileInputStream {
Decoder *const decoder;
InputStream &is;
@@ -52,18 +63,12 @@ struct AudioFileInputStream {
}
};
-static int audiofile_get_duration(const char *file)
+gcc_pure
+static SongTime
+audiofile_get_duration(AFfilehandle fh)
{
- int total_time;
- AFfilehandle af_fp = afOpenFile(file, "r", nullptr);
- if (af_fp == AF_NULL_FILEHANDLE) {
- return -1;
- }
- total_time = (int)
- ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK)
- / afGetRate(af_fp, AF_DEFAULT_TRACK));
- afCloseFile(af_fp);
- return total_time;
+ return SongTime::FromScale<uint64_t>(afGetFrameCount(fh, AF_DEFAULT_TRACK),
+ afGetRate(fh, AF_DEFAULT_TRACK));
}
static ssize_t
@@ -101,15 +106,18 @@ audiofile_file_destroy(AFvirtualfile *vfile)
}
static AFfileoffset
-audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset offset, int is_relative)
+audiofile_file_seek(AFvirtualfile *vfile, AFfileoffset _offset,
+ int is_relative)
{
AudioFileInputStream &afis = *(AudioFileInputStream *)vfile->closure;
InputStream &is = afis.is;
- int whence = (is_relative ? SEEK_CUR : SEEK_SET);
+ offset_type offset = _offset;
+ if (is_relative)
+ offset += is.GetOffset();
Error error;
- if (is.LockSeek(offset, whence, error)) {
+ if (is.LockSeek(offset, error)) {
return is.GetOffset();
} else {
LogError(error, "Seek failed");
@@ -165,7 +173,7 @@ audiofile_setup_sample_format(AFfilehandle af_fp)
}
afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
- AF_SAMPFMT_TWOSCOMP, bits);
+ AF_SAMPFMT_TWOSCOMP, bits);
afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
return audiofile_bits_to_sample_format(bits);
@@ -174,88 +182,94 @@ audiofile_setup_sample_format(AFfilehandle af_fp)
static void
audiofile_stream_decode(Decoder &decoder, InputStream &is)
{
- AFvirtualfile *vf;
- int fs, frame_count;
- AFfilehandle af_fp;
- AudioFormat audio_format;
- float total_time;
- uint16_t bit_rate;
- int ret;
- char chunk[CHUNK_SIZE];
-
- if (!is.IsSeekable()) {
+ if (!is.IsSeekable() || !is.KnownSize()) {
LogWarning(audiofile_domain, "not seekable");
return;
}
AudioFileInputStream afis{&decoder, is};
- vf = setup_virtual_fops(afis);
+ AFvirtualfile *const vf = setup_virtual_fops(afis);
- af_fp = afOpenVirtualFile(vf, "r", nullptr);
- if (af_fp == AF_NULL_FILEHANDLE) {
- LogWarning(audiofile_domain, "failed to input stream");
+ const AFfilehandle fh = afOpenVirtualFile(vf, "r", nullptr);
+ if (fh == AF_NULL_FILEHANDLE)
return;
- }
Error error;
+ AudioFormat audio_format;
if (!audio_format_init_checked(audio_format,
- afGetRate(af_fp, AF_DEFAULT_TRACK),
- audiofile_setup_sample_format(af_fp),
- afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK),
+ afGetRate(fh, AF_DEFAULT_TRACK),
+ audiofile_setup_sample_format(fh),
+ afGetVirtualChannels(fh, AF_DEFAULT_TRACK),
error)) {
LogError(error);
- afCloseFile(af_fp);
+ afCloseFile(fh);
return;
}
- frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK);
-
- total_time = ((float)frame_count / (float)audio_format.sample_rate);
+ const auto total_time = audiofile_get_duration(fh);
- bit_rate = (uint16_t)(is.GetSize() * 8.0 / total_time / 1000.0 + 0.5);
+ const uint16_t kbit_rate = (uint16_t)
+ (is.GetSize() * uint64_t(8) / total_time.ToMS());
- fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1);
+ const unsigned frame_size = (unsigned)
+ afGetVirtualFrameSize(fh, AF_DEFAULT_TRACK, true);
decoder_initialized(decoder, audio_format, true, total_time);
DecoderCommand cmd;
do {
- ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk,
- CHUNK_SIZE / fs);
- if (ret <= 0)
+ /* pick 1020 since its divisible for 8,16,24, and
+ 32-bit audio */
+ char chunk[1020];
+ const int nframes =
+ afReadFrames(fh, AF_DEFAULT_TRACK, chunk,
+ sizeof(chunk) / frame_size);
+ if (nframes <= 0)
break;
cmd = decoder_data(decoder, nullptr,
- chunk, ret * fs,
- bit_rate);
+ chunk, nframes * frame_size,
+ kbit_rate);
if (cmd == DecoderCommand::SEEK) {
- AFframecount frame = decoder_seek_where(decoder) *
- audio_format.sample_rate;
- afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame);
+ AFframecount frame = decoder_seek_where_frame(decoder);
+ afSeekFrame(fh, AF_DEFAULT_TRACK, frame);
decoder_command_finished(decoder);
cmd = DecoderCommand::NONE;
}
} while (cmd == DecoderCommand::NONE);
- afCloseFile(af_fp);
+ afCloseFile(fh);
}
-static bool
-audiofile_scan_file(const char *file,
- const struct tag_handler *handler, void *handler_ctx)
+gcc_pure
+static SignedSongTime
+audiofile_get_duration(InputStream &is)
{
- int total_time = audiofile_get_duration(file);
+ if (!is.IsSeekable() || !is.KnownSize())
+ return SignedSongTime::Negative();
+
+ AudioFileInputStream afis{nullptr, is};
+ AFvirtualfile *vf = setup_virtual_fops(afis);
+ AFfilehandle fh = afOpenVirtualFile(vf, "r", nullptr);
+ if (fh == AF_NULL_FILEHANDLE)
+ return SignedSongTime::Negative();
+
+ const auto duration = audiofile_get_duration(fh);
+ afCloseFile(fh);
+ return duration;
+}
- if (total_time < 0) {
- FormatWarning(audiofile_domain,
- "Failed to get total song time from: %s",
- file);
+static bool
+audiofile_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const auto duration = audiofile_get_duration(is);
+ if (duration.IsNegative())
return false;
- }
- tag_handler_invoke_duration(handler, handler_ctx, total_time);
+ tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration));
return true;
}
@@ -271,12 +285,12 @@ static const char *const audiofile_mime_types[] = {
const struct DecoderPlugin audiofile_decoder_plugin = {
"audiofile",
- nullptr,
+ audiofile_init,
nullptr,
audiofile_stream_decode,
nullptr,
- audiofile_scan_file,
nullptr,
+ audiofile_scan_stream,
nullptr,
audiofile_suffixes,
audiofile_mime_types,
diff --git a/src/decoder/AudiofileDecoderPlugin.hxx b/src/decoder/plugins/AudiofileDecoderPlugin.hxx
index 5a17281b0..61129076d 100644
--- a/src/decoder/AudiofileDecoderPlugin.hxx
+++ b/src/decoder/plugins/AudiofileDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx
index eafedda8f..8892ed387 100644
--- a/src/decoder/DsdLib.cxx
+++ b/src/decoder/plugins/DsdLib.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -25,16 +25,12 @@
#include "config.h"
#include "DsdLib.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "util/bit_reverse.h"
-#include "tag/TagHandler.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "tag/TagId3.hxx"
#include "util/Error.hxx"
-#include <unistd.h>
#include <string.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
#ifdef HAVE_ID3TAG
#include <id3tag.h>
@@ -54,27 +50,15 @@ DsdId::Equals(const char *s) const
*/
bool
dsdlib_skip_to(Decoder *decoder, InputStream &is,
- int64_t offset)
+ offset_type offset)
{
if (is.IsSeekable())
- return is.Seek(offset, SEEK_SET, IgnoreError());
+ return is.LockSeek(offset, IgnoreError());
if (is.GetOffset() > offset)
return false;
- char buffer[8192];
- while (is.GetOffset() < offset) {
- size_t length = sizeof(buffer);
- if (offset - is.GetOffset() < (int64_t)length)
- length = offset - is.GetOffset();
-
- size_t nbytes = decoder_read(decoder, is, buffer, length);
- if (nbytes == 0)
- return false;
- }
-
- assert(is.GetOffset() == offset);
- return true;
+ return dsdlib_skip(decoder, is, offset - is.GetOffset());
}
/**
@@ -82,30 +66,39 @@ dsdlib_skip_to(Decoder *decoder, InputStream &is,
*/
bool
dsdlib_skip(Decoder *decoder, InputStream &is,
- int64_t delta)
+ offset_type delta)
{
- assert(delta >= 0);
-
if (delta == 0)
return true;
if (is.IsSeekable())
- return is.Seek(delta, SEEK_CUR, IgnoreError());
+ return is.LockSeek(is.GetOffset() + delta, IgnoreError());
- char buffer[8192];
- while (delta > 0) {
- size_t length = sizeof(buffer);
- if ((int64_t)length > delta)
- length = delta;
+ if (delta > 1024 * 1024)
+ /* don't skip more than one megabyte; it would be too
+ expensive */
+ return false;
- size_t nbytes = decoder_read(decoder, is, buffer, length);
- if (nbytes == 0)
- return false;
+ return decoder_skip(decoder, is, delta);
+}
- delta -= nbytes;
- }
+bool
+dsdlib_valid_freq(uint32_t samplefreq)
+{
+ switch (samplefreq) {
+ case 2822400: /* DSD64, 64xFs, Fs = 44.100kHz */
+ case 3072000: /* DSD64 with Fs = 48.000 kHz */
+ case 5644800:
+ case 6144000:
+ case 11289600:
+ case 12288000:
+ case 22579200:/* DSD512 */
+ case 24576000:
+ return true;
- return true;
+ default:
+ return false;
+ }
}
#ifdef HAVE_ID3TAG
@@ -116,35 +109,29 @@ dsdlib_tag_id3(InputStream &is,
{
assert(tagoffset >= 0);
- if (tagoffset == 0)
+ if (tagoffset == 0 || !is.KnownSize())
return;
if (!dsdlib_skip_to(nullptr, is, tagoffset))
return;
- struct id3_tag *id3_tag = nullptr;
- id3_length_t count;
-
/* Prevent broken files causing problems */
const auto size = is.GetSize();
const auto offset = is.GetOffset();
if (offset >= size)
return;
- count = size - offset;
+ const id3_length_t count = size - offset;
/* Check and limit id3 tag size to prevent a stack overflow */
- if (count == 0 || count > 4096)
+ id3_byte_t dsdid3[4096];
+ if (count == 0 || count > sizeof(dsdid3))
return;
- id3_byte_t dsdid3[count];
- id3_byte_t *dsdid3data;
- dsdid3data = dsdid3;
-
- if (!decoder_read_full(nullptr, is, dsdid3data, count))
+ if (!decoder_read_full(nullptr, is, dsdid3, count))
return;
- id3_tag = id3_tag_parse(dsdid3data, count);
+ struct id3_tag *id3_tag = id3_tag_parse(dsdid3, count);
if (id3_tag == nullptr)
return;
diff --git a/src/decoder/DsdLib.hxx b/src/decoder/plugins/DsdLib.hxx
index 5c6127149..8295bcbf6 100644
--- a/src/decoder/DsdLib.hxx
+++ b/src/decoder/plugins/DsdLib.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -21,13 +21,14 @@
#define MPD_DECODER_DSDLIB_HXX
#include "system/ByteOrder.hxx"
+#include "input/Offset.hxx"
#include "Compiler.h"
#include <stddef.h>
#include <stdint.h>
struct Decoder;
-struct InputStream;
+class InputStream;
struct DsdId {
char value[4];
@@ -60,11 +61,18 @@ public:
bool
dsdlib_skip_to(Decoder *decoder, InputStream &is,
- int64_t offset);
+ offset_type offset);
bool
dsdlib_skip(Decoder *decoder, InputStream &is,
- int64_t delta);
+ offset_type delta);
+
+/**
+ * Check if the sample frequency is a valid DSD frequency.
+ **/
+gcc_const
+bool
+dsdlib_valid_freq(uint32_t samplefreq);
/**
* Add tags from ID3 tag. All tags commonly found in the ID3 tags of
diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
index 767395215..b6c79e11e 100644
--- a/src/decoder/DsdiffDecoderPlugin.cxx
+++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -28,8 +28,8 @@
#include "config.h"
#include "DsdiffDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/bit_reverse.h"
#include "util/Error.hxx"
@@ -38,9 +38,6 @@
#include "DsdLib.hxx"
#include "Log.hxx"
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
-
struct DsdiffHeader {
DsdId id;
DffDsdUint64 size;
@@ -69,15 +66,7 @@ struct dsdiff_native_tag {
struct DsdiffMetaData {
unsigned sample_rate, channels;
bool bitreverse;
- uint64_t chunk_size;
-#ifdef HAVE_ID3TAG
- InputStream::offset_type id3_offset;
- uint64_t id3_size;
-#endif
- /** offset for artist tag */
- InputStream::offset_type diar_offset;
- /** offset for title tag */
- InputStream::offset_type diti_offset;
+ offset_type chunk_size;
};
static bool lsbitfirst;
@@ -121,14 +110,14 @@ dsdiff_read_payload(Decoder *decoder, InputStream &is,
static bool
dsdiff_read_prop_snd(Decoder *decoder, InputStream &is,
DsdiffMetaData *metadata,
- InputStream::offset_type end_offset)
+ offset_type end_offset)
{
DsdiffChunkHeader header;
- while ((InputStream::offset_type)(is.GetOffset() + sizeof(header)) <= end_offset) {
+ while (is.GetOffset() + sizeof(header) <= end_offset) {
if (!dsdiff_read_chunk_header(decoder, is, &header))
return false;
- InputStream::offset_type chunk_end_offset = is.GetOffset()
+ offset_type chunk_end_offset = is.GetOffset()
+ header.GetSize();
if (chunk_end_offset > end_offset)
return false;
@@ -182,7 +171,7 @@ dsdiff_read_prop(Decoder *decoder, InputStream &is,
const DsdiffChunkHeader *prop_header)
{
uint64_t prop_size = prop_header->GetSize();
- InputStream::offset_type end_offset = is.GetOffset() + prop_size;
+ const offset_type end_offset = is.GetOffset() + prop_size;
DsdId prop_id;
if (prop_size < sizeof(prop_id) ||
@@ -199,7 +188,7 @@ dsdiff_read_prop(Decoder *decoder, InputStream &is,
static void
dsdiff_handle_native_tag(InputStream &is,
const struct tag_handler *handler,
- void *handler_ctx, InputStream::offset_type tagoffset,
+ void *handler_ctx, offset_type tagoffset,
TagType type)
{
if (!dsdlib_skip_to(nullptr, is, tagoffset))
@@ -250,18 +239,20 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
return false;
- metadata->diar_offset = 0;
- metadata->diti_offset = 0;
+ /** offset for artist tag */
+ offset_type artist_offset = 0;
+ /** offset for title tag */
+ offset_type title_offset = 0;
#ifdef HAVE_ID3TAG
- metadata->id3_offset = 0;
+ offset_type id3_offset = 0;
#endif
/* Now process all the remaining chunk headers in the stream
and record their position and size */
do {
- uint64_t chunk_size = chunk_header->GetSize();
+ offset_type chunk_size = chunk_header->GetSize();
/* DIIN chunk, is directly followed by other chunks */
if (chunk_header->id.Equals("DIIN"))
@@ -270,20 +261,19 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
/* DIAR chunk - DSDIFF native tag for Artist */
if (chunk_header->id.Equals("DIAR")) {
chunk_size = chunk_header->GetSize();
- metadata->diar_offset = is.GetOffset();
+ artist_offset = is.GetOffset();
}
/* DITI chunk - DSDIFF native tag for Title */
if (chunk_header->id.Equals("DITI")) {
chunk_size = chunk_header->GetSize();
- metadata->diti_offset = is.GetOffset();
+ title_offset = is.GetOffset();
}
#ifdef HAVE_ID3TAG
/* 'ID3 ' chunk, offspec. Used by sacdextract */
if (chunk_header->id.Equals("ID3 ")) {
chunk_size = chunk_header->GetSize();
- metadata->id3_offset = is.GetOffset();
- metadata->id3_size = chunk_size;
+ id3_offset = is.GetOffset();
}
#endif
@@ -294,22 +284,21 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is,
/* done processing chunk headers, process tags if any */
#ifdef HAVE_ID3TAG
- if (metadata->id3_offset != 0)
- {
+ if (id3_offset != 0) {
/* a ID3 tag has preference over the other tags, do not process
other tags if we have one */
- dsdlib_tag_id3(is, handler, handler_ctx, metadata->id3_offset);
+ dsdlib_tag_id3(is, handler, handler_ctx, id3_offset);
return true;
}
#endif
- if (metadata->diar_offset != 0)
+ if (artist_offset != 0)
dsdiff_handle_native_tag(is, handler, handler_ctx,
- metadata->diar_offset, TAG_ARTIST);
+ artist_offset, TAG_ARTIST);
- if (metadata->diti_offset != 0)
+ if (title_offset != 0)
dsdiff_handle_native_tag(is, handler, handler_ctx,
- metadata->diti_offset, TAG_TITLE);
+ title_offset, TAG_TITLE);
return true;
}
@@ -339,13 +328,13 @@ dsdiff_read_metadata(Decoder *decoder, InputStream &is,
chunk_header))
return false;
} else if (chunk_header->id.Equals("DSD ")) {
- const uint64_t chunk_size = chunk_header->GetSize();
+ const offset_type chunk_size = chunk_header->GetSize();
metadata->chunk_size = chunk_size;
return true;
} else {
/* ignore unknown chunk */
- const uint64_t chunk_size = chunk_header->GetSize();
- InputStream::offset_type chunk_end_offset =
+ const offset_type chunk_size = chunk_header->GetSize();
+ const offset_type chunk_end_offset =
is.GetOffset() + chunk_size;
if (!dsdlib_skip_to(decoder, is, chunk_end_offset))
@@ -361,29 +350,53 @@ bit_reverse_buffer(uint8_t *p, uint8_t *end)
*p = bit_reverse(*p);
}
+static offset_type
+FrameToOffset(uint64_t frame, unsigned channels)
+{
+ return frame * channels;
+}
+
/**
* Decode one "DSD" chunk.
*/
static bool
dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
- unsigned channels,
- uint64_t chunk_size)
+ unsigned channels, unsigned sample_rate,
+ const offset_type total_bytes)
{
+ const offset_type start_offset = is.GetOffset();
+
uint8_t buffer[8192];
const size_t sample_size = sizeof(buffer[0]);
const size_t frame_size = channels * sample_size;
const unsigned buffer_frames = sizeof(buffer) / frame_size;
- const unsigned buffer_samples = buffer_frames * frame_size;
- const size_t buffer_size = buffer_samples * sample_size;
+ const size_t buffer_size = buffer_frames * frame_size;
+
+ auto cmd = decoder_get_command(decoder);
+ for (offset_type remaining_bytes = total_bytes;
+ remaining_bytes >= frame_size && cmd != DecoderCommand::STOP;) {
+ if (cmd == DecoderCommand::SEEK) {
+ uint64_t frame = decoder_seek_where_frame(decoder);
+ offset_type offset = FrameToOffset(frame, channels);
+ if (offset >= total_bytes) {
+ decoder_command_finished(decoder);
+ break;
+ }
+
+ if (dsdlib_skip_to(&decoder, is,
+ start_offset + offset)) {
+ decoder_command_finished(decoder);
+ remaining_bytes = total_bytes - offset;
+ } else
+ decoder_seek_error(decoder);
+ }
- while (chunk_size >= frame_size) {
/* see how much aligned data from the remaining chunk
fits into the local buffer */
size_t now_size = buffer_size;
- if (chunk_size < (uint64_t)now_size) {
- unsigned now_frames =
- (unsigned)chunk_size / frame_size;
+ if (remaining_bytes < (offset_type)now_size) {
+ unsigned now_frames = remaining_bytes / frame_size;
now_size = now_frames * frame_size;
}
@@ -391,28 +404,16 @@ dsdiff_decode_chunk(Decoder &decoder, InputStream &is,
return false;
const size_t nbytes = now_size;
- chunk_size -= nbytes;
+ remaining_bytes -= nbytes;
if (lsbitfirst)
bit_reverse_buffer(buffer, buffer + nbytes);
- const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0);
- switch (cmd) {
- case DecoderCommand::NONE:
- break;
-
- case DecoderCommand::START:
- case DecoderCommand::STOP:
- return false;
-
- case DecoderCommand::SEEK:
-
- /* Not implemented yet */
- decoder_seek_error(decoder);
- break;
- }
+ cmd = decoder_data(decoder, is, buffer, nbytes,
+ sample_rate / 1000);
}
- return dsdlib_skip(&decoder, is, chunk_size);
+
+ return true;
}
static void
@@ -435,36 +436,22 @@ dsdiff_stream_decode(Decoder &decoder, InputStream &is)
}
/* calculate song time from DSD chunk size and sample frequency */
- uint64_t chunk_size = metadata.chunk_size;
- float songtime = ((chunk_size / metadata.channels) * 8) /
- (float) metadata.sample_rate;
+ offset_type chunk_size = metadata.chunk_size;
+
+ uint64_t n_frames = chunk_size / audio_format.channels;
+ auto songtime = SongTime::FromScale<uint64_t>(n_frames,
+ audio_format.sample_rate);
/* success: file was recognized */
- decoder_initialized(decoder, audio_format, false, songtime);
+ decoder_initialized(decoder, audio_format, is.IsSeekable(), songtime);
/* every iteration of the following loop decodes one "DSD"
chunk from a DFF file */
- while (true) {
- chunk_size = chunk_header.GetSize();
-
- if (chunk_header.id.Equals("DSD ")) {
- if (!dsdiff_decode_chunk(decoder, is,
- metadata.channels,
- chunk_size))
- break;
- } else {
- /* ignore other chunks */
- if (!dsdlib_skip(&decoder, is, chunk_size))
- break;
- }
-
- /* read next chunk header; the first one was read by
- dsdiff_read_metadata() */
- if (!dsdiff_read_chunk_header(&decoder,
- is, &chunk_header))
- break;
- }
+ dsdiff_decode_chunk(decoder, is,
+ metadata.channels,
+ metadata.sample_rate,
+ chunk_size);
}
static bool
@@ -487,8 +474,9 @@ dsdiff_scan_stream(InputStream &is,
return false;
/* calculate song time and add as tag */
- unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) /
- metadata.sample_rate;
+ uint64_t n_frames = metadata.chunk_size / audio_format.channels;
+ auto songtime = SongTime::FromScale<uint64_t>(n_frames,
+ audio_format.sample_rate);
tag_handler_invoke_duration(handler, handler_ctx, songtime);
/* Read additional metadata and created tags if available */
diff --git a/src/decoder/DsdiffDecoderPlugin.hxx b/src/decoder/plugins/DsdiffDecoderPlugin.hxx
index be14fc9cd..7aa36752b 100644
--- a/src/decoder/DsdiffDecoderPlugin.hxx
+++ b/src/decoder/plugins/DsdiffDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx
index 9fbfe9cda..690616d15 100644
--- a/src/decoder/DsfDecoderPlugin.cxx
+++ b/src/decoder/plugins/DsfDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -29,8 +29,8 @@
#include "config.h"
#include "DsfDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/bit_reverse.h"
#include "util/Error.hxx"
@@ -39,16 +39,16 @@
#include "tag/TagHandler.hxx"
#include "Log.hxx"
-#include <unistd.h>
-#include <stdio.h> /* for SEEK_SET, SEEK_CUR */
+#include <string.h>
+
+static constexpr unsigned DSF_BLOCK_SIZE = 4096;
struct DsfMetaData {
unsigned sample_rate, channels;
bool bitreverse;
- uint64_t chunk_size;
+ offset_type n_blocks;
#ifdef HAVE_ID3TAG
- InputStream::offset_type id3_offset;
- uint64_t id3_size;
+ offset_type id3_offset;
#endif
};
@@ -107,12 +107,12 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
!dsf_header.id.Equals("DSD "))
return false;
- const uint64_t chunk_size = dsf_header.size.Read();
+ const offset_type chunk_size = dsf_header.size.Read();
if (sizeof(dsf_header) != chunk_size)
return false;
#ifdef HAVE_ID3TAG
- const uint64_t metadata_offset = dsf_header.pmeta.Read();
+ const offset_type metadata_offset = dsf_header.pmeta.Read();
#endif
/* read the 'fmt ' chunk of the DSF file */
@@ -127,19 +127,20 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
return false;
uint32_t samplefreq = FromLE32(dsf_fmt_chunk.sample_freq);
+ const unsigned channels = FromLE32(dsf_fmt_chunk.channelnum);
/* for now, only support version 1 of the standard, DSD raw stereo
files with a sample freq of 2822400 or 5644800 Hz */
- if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0
- || dsf_fmt_chunk.channeltype != 2
- || dsf_fmt_chunk.channelnum != 2
- || (samplefreq != 2822400 && samplefreq != 5644800))
+ if (FromLE32(dsf_fmt_chunk.version) != 1 ||
+ FromLE32(dsf_fmt_chunk.formatid) != 0 ||
+ !audio_valid_channel_count(channels) ||
+ !dsdlib_valid_freq(samplefreq))
return false;
uint32_t chblksize = FromLE32(dsf_fmt_chunk.block_size);
/* according to the spec block size should always be 4096 */
- if (chblksize != 4096)
+ if (chblksize != DSF_BLOCK_SIZE)
return false;
/* read the 'data' chunk of the DSF file */
@@ -151,37 +152,33 @@ dsf_read_metadata(Decoder *decoder, InputStream &is,
/* data size of DSF files are padded to multiple of 4096,
we use the actual data size as chunk size */
- uint64_t data_size = data_chunk.size.Read();
+ offset_type data_size = data_chunk.size.Read();
if (data_size < sizeof(data_chunk))
return false;
data_size -= sizeof(data_chunk);
/* data_size cannot be bigger or equal to total file size */
- const uint64_t size = (uint64_t)is.GetSize();
- if (data_size >= size)
+ if (is.KnownSize() && data_size > is.GetRest())
return false;
/* use the sample count from the DSF header as the upper
bound, because some DSF files contain junk at the end of
the "data" chunk */
const uint64_t samplecnt = dsf_fmt_chunk.scnt.Read();
- const uint64_t playable_size = samplecnt * 2 / 8;
+ const offset_type playable_size = samplecnt * channels / 8;
if (data_size > playable_size)
data_size = playable_size;
- metadata->chunk_size = data_size;
- metadata->channels = (unsigned) dsf_fmt_chunk.channelnum;
+ const size_t block_size = channels * DSF_BLOCK_SIZE;
+ metadata->n_blocks = data_size / block_size;
+ metadata->channels = channels;
metadata->sample_rate = samplefreq;
#ifdef HAVE_ID3TAG
- /* metada_offset cannot be bigger then or equal to total file size */
- if (metadata_offset >= size)
- metadata->id3_offset = 0;
- else
- metadata->id3_offset = (InputStream::offset_type)metadata_offset;
+ metadata->id3_offset = metadata_offset;
#endif
/* check bits per sample format, determine if bitreverse is needed */
- metadata->bitreverse = dsf_fmt_chunk.bitssample == 1;
+ metadata->bitreverse = FromLE32(dsf_fmt_chunk.bitssample) == 1;
return true;
}
@@ -192,6 +189,13 @@ bit_reverse_buffer(uint8_t *p, uint8_t *end)
*p = bit_reverse(*p);
}
+static void
+InterleaveDsfBlockMono(uint8_t *gcc_restrict dest,
+ const uint8_t *gcc_restrict src)
+{
+ memcpy(dest, src, DSF_BLOCK_SIZE);
+}
+
/**
* DSF data is build up of alternating 4096 blocks of DSD samples for left and
* right. Convert the buffer holding 1 block of 4096 DSD left samples and 1
@@ -199,22 +203,49 @@ bit_reverse_buffer(uint8_t *p, uint8_t *end)
* order.
*/
static void
-dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes)
+InterleaveDsfBlockStereo(uint8_t *gcc_restrict dest,
+ const uint8_t *gcc_restrict src)
{
- for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) {
- scratch[i] = *(dest+j);
- j++;
+ for (size_t i = 0; i < DSF_BLOCK_SIZE; ++i) {
+ dest[2 * i] = src[i];
+ dest[2 * i + 1] = src[DSF_BLOCK_SIZE + i];
}
+}
- for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) {
- scratch[i] = *(dest+4096+j);
- j++;
- }
+static void
+InterleaveDsfBlockChannel(uint8_t *gcc_restrict dest,
+ const uint8_t *gcc_restrict src,
+ unsigned channels)
+{
+ for (size_t i = 0; i < DSF_BLOCK_SIZE; ++i, dest += channels, ++src)
+ *dest = *src;
+}
- for (unsigned i = 0; i < (unsigned)nrbytes; i++) {
- *dest = scratch[i];
- dest++;
- }
+static void
+InterleaveDsfBlockGeneric(uint8_t *gcc_restrict dest,
+ const uint8_t *gcc_restrict src,
+ unsigned channels)
+{
+ for (unsigned c = 0; c < channels; ++c, ++dest, src += DSF_BLOCK_SIZE)
+ InterleaveDsfBlockChannel(dest, src, channels);
+}
+
+static void
+InterleaveDsfBlock(uint8_t *gcc_restrict dest, const uint8_t *gcc_restrict src,
+ unsigned channels)
+{
+ if (channels == 1)
+ InterleaveDsfBlockMono(dest, src);
+ else if (channels == 2)
+ InterleaveDsfBlockStereo(dest, src);
+ else
+ InterleaveDsfBlockGeneric(dest, src, channels);
+}
+
+static offset_type
+FrameToBlock(uint64_t frame)
+{
+ return frame / DSF_BLOCK_SIZE;
}
/**
@@ -222,60 +253,50 @@ dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes)
*/
static bool
dsf_decode_chunk(Decoder &decoder, InputStream &is,
- unsigned channels,
- uint64_t chunk_size,
- bool bitreverse)
+ unsigned channels, unsigned sample_rate,
+ offset_type n_blocks,
+ bool bitreverse)
{
- uint8_t buffer[8192];
-
- /* scratch buffer for DSF samples to convert to the needed
- normal left/right regime of samples */
- uint8_t dsf_scratch_buffer[8192];
-
- const size_t sample_size = sizeof(buffer[0]);
- const size_t frame_size = channels * sample_size;
- const unsigned buffer_frames = sizeof(buffer) / frame_size;
- const unsigned buffer_samples = buffer_frames * frame_size;
- const size_t buffer_size = buffer_samples * sample_size;
-
- while (chunk_size >= frame_size) {
- /* see how much aligned data from the remaining chunk
- fits into the local buffer */
- size_t now_size = buffer_size;
- if (chunk_size < (uint64_t)now_size) {
- unsigned now_frames =
- (unsigned)chunk_size / frame_size;
- now_size = now_frames * frame_size;
+ const size_t block_size = channels * DSF_BLOCK_SIZE;
+ const offset_type start_offset = is.GetOffset();
+
+ auto cmd = decoder_get_command(decoder);
+ for (offset_type i = 0; i < n_blocks && cmd != DecoderCommand::STOP;) {
+ if (cmd == DecoderCommand::SEEK) {
+ uint64_t frame = decoder_seek_where_frame(decoder);
+ offset_type block = FrameToBlock(frame);
+ if (block >= n_blocks) {
+ decoder_command_finished(decoder);
+ break;
+ }
+
+ offset_type offset =
+ start_offset + block * block_size;
+ if (dsdlib_skip_to(&decoder, is, offset)) {
+ decoder_command_finished(decoder);
+ i = block;
+ } else
+ decoder_seek_error(decoder);
}
- if (!decoder_read_full(&decoder, is, buffer, now_size))
+ /* worst-case buffer size */
+ uint8_t buffer[MAX_CHANNELS * DSF_BLOCK_SIZE];
+ if (!decoder_read_full(&decoder, is, buffer, block_size))
return false;
- const size_t nbytes = now_size;
- chunk_size -= nbytes;
-
if (bitreverse)
- bit_reverse_buffer(buffer, buffer + nbytes);
+ bit_reverse_buffer(buffer, buffer + block_size);
- dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes);
+ uint8_t interleaved_buffer[MAX_CHANNELS * DSF_BLOCK_SIZE];
+ InterleaveDsfBlock(interleaved_buffer, buffer, channels);
- const auto cmd = decoder_data(decoder, is, buffer, nbytes, 0);
- switch (cmd) {
- case DecoderCommand::NONE:
- break;
-
- case DecoderCommand::START:
- case DecoderCommand::STOP:
- return false;
-
- case DecoderCommand::SEEK:
-
- /* not implemented yet */
- decoder_seek_error(decoder);
- break;
- }
+ cmd = decoder_data(decoder, is,
+ interleaved_buffer, block_size,
+ sample_rate / 1000);
+ ++i;
}
- return dsdlib_skip(&decoder, is, chunk_size);
+
+ return true;
}
static void
@@ -295,17 +316,17 @@ dsf_stream_decode(Decoder &decoder, InputStream &is)
return;
}
/* Calculate song time from DSD chunk size and sample frequency */
- uint64_t chunk_size = metadata.chunk_size;
- float songtime = ((chunk_size / metadata.channels) * 8) /
- (float) metadata.sample_rate;
+ const auto n_blocks = metadata.n_blocks;
+ auto songtime = SongTime::FromScale<uint64_t>(n_blocks * DSF_BLOCK_SIZE,
+ audio_format.sample_rate);
/* success: file was recognized */
- decoder_initialized(decoder, audio_format, false, songtime);
+ decoder_initialized(decoder, audio_format, is.IsSeekable(), songtime);
- if (!dsf_decode_chunk(decoder, is, metadata.channels,
- chunk_size,
- metadata.bitreverse))
- return;
+ dsf_decode_chunk(decoder, is, metadata.channels,
+ metadata.sample_rate,
+ n_blocks,
+ metadata.bitreverse);
}
static bool
@@ -326,8 +347,9 @@ dsf_scan_stream(InputStream &is,
return false;
/* calculate song time and add as tag */
- unsigned songtime = ((metadata.chunk_size / metadata.channels) * 8) /
- metadata.sample_rate;
+ const auto n_blocks = metadata.n_blocks;
+ auto songtime = SongTime::FromScale<uint64_t>(n_blocks * DSF_BLOCK_SIZE,
+ audio_format.sample_rate);
tag_handler_invoke_duration(handler, handler_ctx, songtime);
#ifdef HAVE_ID3TAG
diff --git a/src/decoder/DsfDecoderPlugin.hxx b/src/decoder/plugins/DsfDecoderPlugin.hxx
index 921c94698..02bea0b5c 100644
--- a/src/decoder/DsfDecoderPlugin.hxx
+++ b/src/decoder/plugins/DsfDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/plugins/FaadDecoderPlugin.cxx
index ae1181b4c..add23aaa4 100644
--- a/src/decoder/FaadDecoderPlugin.cxx
+++ b/src/decoder/plugins/FaadDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,11 +19,12 @@
#include "config.h"
#include "FaadDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "DecoderBuffer.hxx"
-#include "InputStream.hxx"
+#include "../DecoderAPI.hxx"
+#include "../DecoderBuffer.hxx"
+#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
+#include "util/ConstBuffer.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
@@ -62,55 +63,42 @@ adts_check_frame(const unsigned char *data)
* found or if not enough data is available.
*/
static size_t
-adts_find_frame(DecoderBuffer *buffer)
+adts_find_frame(DecoderBuffer &buffer)
{
while (true) {
- size_t length;
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr || length < 8) {
- /* not enough data yet */
- if (!decoder_buffer_fill(buffer))
- /* failed */
- return 0;
-
- continue;
- }
+ auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(8));
+ if (data.IsNull())
+ /* failed */
+ return 0;
/* find the 0xff marker */
- const uint8_t *p = (const uint8_t *)memchr(data, 0xff, length);
+ const uint8_t *p = (const uint8_t *)
+ memchr(data.data, 0xff, data.size);
if (p == nullptr) {
/* no marker - discard the buffer */
- decoder_buffer_clear(buffer);
+ buffer.Clear();
continue;
}
- if (p > data) {
+ if (p > data.data) {
/* discard data before 0xff */
- decoder_buffer_consume(buffer, p - data);
+ buffer.Consume(p - data.data);
continue;
}
/* is it a frame? */
- const size_t frame_length = adts_check_frame(data);
+ const size_t frame_length = adts_check_frame(data.data);
if (frame_length == 0) {
/* it's just some random 0xff byte; discard it
and continue searching */
- decoder_buffer_consume(buffer, 1);
+ buffer.Consume(1);
continue;
}
- if (length < frame_length) {
- /* available buffer size is smaller than the
- frame will be - attempt to read more
- data */
- if (!decoder_buffer_fill(buffer)) {
- /* not enough data; discard this frame
- to prevent a possible buffer
- overflow */
- decoder_buffer_clear(buffer);
- }
-
+ if (buffer.Need(frame_length).IsNull()) {
+ /* not enough data; discard this frame to
+ prevent a possible buffer overflow */
+ buffer.Clear();
continue;
}
@@ -119,38 +107,34 @@ adts_find_frame(DecoderBuffer *buffer)
}
}
-static float
-adts_song_duration(DecoderBuffer *buffer)
+static SignedSongTime
+adts_song_duration(DecoderBuffer &buffer)
{
- const InputStream &is = decoder_buffer_get_stream(buffer);
+ const InputStream &is = buffer.GetStream();
const bool estimate = !is.CheapSeeking();
- const auto file_size = is.GetSize();
- if (estimate && file_size <= 0)
- return -1;
+ if (estimate && !is.KnownSize())
+ return SignedSongTime::Negative();
unsigned sample_rate = 0;
/* Read all frames to ensure correct time and bitrate */
- unsigned frames;
- for (frames = 0;; frames++) {
+ unsigned frames = 0;
+ for (;; frames++) {
const unsigned frame_length = adts_find_frame(buffer);
if (frame_length == 0)
break;
-
if (frames == 0) {
- size_t buffer_length;
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &buffer_length);
- assert(data != nullptr);
- assert(frame_length <= buffer_length);
+ auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read());
+ assert(!data.IsEmpty());
+ assert(frame_length <= data.size);
- sample_rate = adts_sample_rates[(data[2] & 0x3c) >> 2];
+ sample_rate = adts_sample_rates[(data.data[2] & 0x3c) >> 2];
if (sample_rate == 0)
break;
}
- decoder_buffer_consume(buffer, frame_length);
+ buffer.Consume(frame_length);
if (estimate && frames == 128) {
/* if this is a remote file, don't slurp the
@@ -160,88 +144,85 @@ adts_song_duration(DecoderBuffer *buffer)
have until now */
const auto offset = is.GetOffset()
- - decoder_buffer_available(buffer);
+ - buffer.GetAvailable();
if (offset <= 0)
- return -1;
+ return SignedSongTime::Negative();
+ const auto file_size = is.GetSize();
frames = (frames * file_size) / offset;
break;
}
}
if (sample_rate == 0)
- return -1;
-
- float frames_per_second = (float)sample_rate / 1024.0;
- assert(frames_per_second > 0);
+ return SignedSongTime::Negative();
- return (float)frames / frames_per_second;
+ return SignedSongTime::FromScale<uint64_t>(frames * uint64_t(1024),
+ sample_rate);
}
-static float
-faad_song_duration(DecoderBuffer *buffer, InputStream &is)
+static SignedSongTime
+faad_song_duration(DecoderBuffer &buffer, InputStream &is)
{
- const auto size = is.GetSize();
- const size_t fileread = size >= 0 ? size : 0;
-
- decoder_buffer_fill(buffer);
- size_t length;
- const uint8_t *data = (const uint8_t *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr)
- return -1;
+ auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(5));
+ if (data.IsNull())
+ return SignedSongTime::Negative();
size_t tagsize = 0;
- if (length >= 10 && !memcmp(data, "ID3", 3)) {
+ if (data.size >= 10 && !memcmp(data.data, "ID3", 3)) {
/* skip the ID3 tag */
- tagsize = (data[6] << 21) | (data[7] << 14) |
- (data[8] << 7) | (data[9] << 0);
+ tagsize = (data.data[6] << 21) | (data.data[7] << 14) |
+ (data.data[8] << 7) | (data.data[9] << 0);
tagsize += 10;
- const bool success = decoder_buffer_skip(buffer, tagsize) &&
- decoder_buffer_fill(buffer);
- if (!success)
- return -1;
+ if (!buffer.Skip(tagsize))
+ return SignedSongTime::Negative();
- data = (const uint8_t *)decoder_buffer_read(buffer, &length);
- if (data == nullptr)
- return -1;
+ data = ConstBuffer<uint8_t>::FromVoid(buffer.Need(5));
+ if (data.IsNull())
+ return SignedSongTime::Negative();
}
- if (length >= 8 && adts_check_frame(data) > 0) {
+ if (data.size >= 8 && adts_check_frame(data.data) > 0) {
/* obtain the duration from the ADTS header */
if (!is.IsSeekable())
- return -1;
+ return SignedSongTime::Negative();
+
+ auto song_length = adts_song_duration(buffer);
- float song_length = adts_song_duration(buffer);
+ is.LockSeek(tagsize, IgnoreError());
- is.LockSeek(tagsize, SEEK_SET, IgnoreError());
- decoder_buffer_clear(buffer);
+ buffer.Clear();
return song_length;
- } else if (length >= 5 && memcmp(data, "ADIF", 4) == 0) {
+ } else if (data.size >= 5 && memcmp(data.data, "ADIF", 4) == 0) {
/* obtain the duration from the ADIF header */
- size_t skip_size = (data[4] & 0x80) ? 9 : 0;
- if (8 + skip_size > length)
+ if (!is.KnownSize())
+ return SignedSongTime::Negative();
+
+ size_t skip_size = (data.data[4] & 0x80) ? 9 : 0;
+
+ if (8 + skip_size > data.size)
/* not enough data yet; skip parsing this
header */
- return -1;
+ return SignedSongTime::Negative();
+
+ unsigned bit_rate = ((data.data[4 + skip_size] & 0x0F) << 19) |
+ (data.data[5 + skip_size] << 11) |
+ (data.data[6 + skip_size] << 3) |
+ (data.data[7 + skip_size] & 0xE0);
- unsigned bit_rate = ((data[4 + skip_size] & 0x0F) << 19) |
- (data[5 + skip_size] << 11) |
- (data[6 + skip_size] << 3) |
- (data[7 + skip_size] & 0xE0);
+ const auto size = is.GetSize();
+ if (bit_rate == 0)
+ return SignedSongTime::Negative();
- if (fileread != 0 && bit_rate != 0)
- return fileread * 8.0 / bit_rate;
- else
- return fileread;
+ return SongTime::FromScale(size, bit_rate / 8);
} else
- return -1;
+ return SignedSongTime::Negative();
}
static NeAACDecHandle
@@ -264,14 +245,11 @@ faad_decoder_new()
* inconsistencies in libfaad.
*/
static bool
-faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
+faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer &buffer,
AudioFormat &audio_format, Error &error)
{
-
- size_t length;
- const unsigned char *data = (const unsigned char *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr) {
+ auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read());
+ if (data.IsEmpty()) {
error.Set(faad_decoder_domain, "Empty file");
return false;
}
@@ -280,15 +258,15 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
unsigned long sample_rate;
long nbytes = NeAACDecInit(decoder,
/* deconst hack, libfaad requires this */
- const_cast<unsigned char *>(data),
- length,
+ const_cast<unsigned char *>(data.data),
+ data.size,
&sample_rate, &channels);
if (nbytes < 0) {
error.Set(faad_decoder_domain, "Not an AAC stream");
return false;
}
- decoder_buffer_consume(buffer, nbytes);
+ buffer.Consume(nbytes);
return audio_format_init_checked(audio_format, sample_rate,
SampleFormat::S16, channels, error);
@@ -299,87 +277,57 @@ faad_decoder_init(NeAACDecHandle decoder, DecoderBuffer *buffer,
* inconsistencies in libfaad.
*/
static const void *
-faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer *buffer,
+faad_decoder_decode(NeAACDecHandle decoder, DecoderBuffer &buffer,
NeAACDecFrameInfo *frame_info)
{
- size_t length;
- const unsigned char *data = (const unsigned char *)
- decoder_buffer_read(buffer, &length);
- if (data == nullptr)
+ auto data = ConstBuffer<uint8_t>::FromVoid(buffer.Read());
+ if (data.IsEmpty())
return nullptr;
return NeAACDecDecode(decoder, frame_info,
/* deconst hack, libfaad requires this */
- const_cast<unsigned char *>(data),
- length);
+ const_cast<uint8_t *>(data.data),
+ data.size);
}
/**
- * Get a song file's total playing time in seconds, as a float.
- * Returns 0 if the duration is unknown, and a negative value if the
- * file is invalid.
+ * Determine a song file's total playing time.
+ *
+ * The first return value specifies whether the file was recognized.
+ * The second return value is the duration.
*/
-static float
-faad_get_file_time_float(InputStream &is)
+static std::pair<bool, SignedSongTime>
+faad_get_file_time(InputStream &is)
{
- DecoderBuffer *const buffer =
- decoder_buffer_new(nullptr, is,
- FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
-
- float length = faad_song_duration(buffer, is);
-
- if (length < 0) {
- AudioFormat audio_format;
+ DecoderBuffer buffer(nullptr, is,
+ FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
+ auto duration = faad_song_duration(buffer, is);
+ bool recognized = !duration.IsNegative();
+ if (!recognized) {
NeAACDecHandle decoder = faad_decoder_new();
- decoder_buffer_fill(buffer);
+ buffer.Fill();
+ AudioFormat audio_format;
if (faad_decoder_init(decoder, buffer, audio_format,
IgnoreError()))
- length = 0;
+ recognized = true;
NeAACDecClose(decoder);
}
- decoder_buffer_free(buffer);
-
- return length;
-}
-
-/**
- * Get a song file's total playing time in seconds, as an int.
- * Returns 0 if the duration is unknown, and a negative value if the
- * file is invalid.
- */
-static int
-faad_get_file_time(InputStream &is)
-{
- float length = faad_get_file_time_float(is);
- if (length < 0)
- return -1;
-
- return int(length + 0.5);
+ return std::make_pair(recognized, duration);
}
static void
-faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
+faad_stream_decode(Decoder &mpd_decoder, InputStream &is,
+ DecoderBuffer &buffer, const NeAACDecHandle decoder)
{
- DecoderBuffer *const buffer =
- decoder_buffer_new(&mpd_decoder, is,
- FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
-
- const float total_time = faad_song_duration(buffer, is);
+ const auto total_time = faad_song_duration(buffer, is);
- /* create the libfaad decoder */
-
- const NeAACDecHandle decoder = faad_decoder_new();
-
- while (!decoder_buffer_is_full(buffer) && !is.LockIsEOF() &&
- decoder_get_command(mpd_decoder) == DecoderCommand::NONE) {
- adts_find_frame(buffer);
- decoder_buffer_fill(buffer);
- }
+ if (adts_find_frame(buffer) == 0)
+ return;
/* initialize it */
@@ -387,8 +335,6 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
AudioFormat audio_format;
if (!faad_decoder_init(decoder, buffer, audio_format, error)) {
LogError(error);
- NeAACDecClose(decoder);
- decoder_buffer_free(buffer);
return;
}
@@ -399,7 +345,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
/* the decoder loop */
DecoderCommand cmd;
- uint16_t bit_rate = 0;
+ unsigned bit_rate = 0;
do {
/* find the next frame */
@@ -436,7 +382,7 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
break;
}
- decoder_buffer_consume(buffer, frame_info.bytesconsumed);
+ buffer.Consume(frame_info.bytesconsumed);
/* update bit rate and position */
@@ -452,22 +398,36 @@ faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
(size_t)frame_info.samples * 2,
bit_rate);
} while (cmd != DecoderCommand::STOP);
+}
+
+static void
+faad_stream_decode(Decoder &mpd_decoder, InputStream &is)
+{
+ DecoderBuffer buffer(&mpd_decoder, is,
+ FAAD_MIN_STREAMSIZE * MAX_CHANNELS);
+
+ /* create the libfaad decoder */
+
+ const NeAACDecHandle decoder = faad_decoder_new();
+
+ faad_stream_decode(mpd_decoder, is, buffer, decoder);
/* cleanup */
NeAACDecClose(decoder);
- decoder_buffer_free(buffer);
}
static bool
faad_scan_stream(InputStream &is,
const struct tag_handler *handler, void *handler_ctx)
{
- int file_time = faad_get_file_time(is);
- if (file_time < 0)
+ auto result = faad_get_file_time(is);
+ if (!result.first)
return false;
- tag_handler_invoke_duration(handler, handler_ctx, file_time);
+ if (!result.second.IsNegative())
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime(result.second));
return true;
}
@@ -476,7 +436,7 @@ static const char *const faad_mime_types[] = {
"audio/aac", "audio/aacp", nullptr
};
-const struct DecoderPlugin faad_decoder_plugin = {
+const DecoderPlugin faad_decoder_plugin = {
"faad",
nullptr,
nullptr,
diff --git a/src/decoder/FaadDecoderPlugin.hxx b/src/decoder/plugins/FaadDecoderPlugin.hxx
index 817927d5e..968433e9b 100644
--- a/src/decoder/FaadDecoderPlugin.hxx
+++ b/src/decoder/plugins/FaadDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
index 9a00bf3c4..494f4048b 100644
--- a/src/decoder/FfmpegDecoderPlugin.cxx
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -22,10 +22,11 @@
#include "config.h"
#include "FfmpegDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "lib/ffmpeg/Domain.hxx"
+#include "../DecoderAPI.hxx"
#include "FfmpegMetaData.hxx"
#include "tag/TagHandler.hxx"
-#include "InputStream.hxx"
+#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
@@ -47,8 +48,6 @@ extern "C" {
#include <assert.h>
#include <string.h>
-static constexpr Domain ffmpeg_domain("ffmpeg");
-
/* suppress the ffmpeg compatibility macro */
#ifdef SampleFormat
#undef SampleFormat
@@ -120,13 +119,35 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
{
AvioStream *stream = (AvioStream *)opaque;
- if (whence == AVSEEK_SIZE)
- return stream->input.size;
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ pos += stream->input.GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!stream->input.KnownSize())
+ return -1;
- if (!stream->input.LockSeek(pos, whence, IgnoreError()))
+ pos += stream->input.GetSize();
+ break;
+
+ case AVSEEK_SIZE:
+ if (!stream->input.KnownSize())
+ return -1;
+
+ return stream->input.GetSize();
+
+ default:
+ return -1;
+ }
+
+ if (!stream->input.LockSeek(pos, IgnoreError()))
return -1;
- return stream->input.offset;
+ return stream->input.GetOffset();
}
bool
@@ -135,7 +156,7 @@ AvioStream::Open()
io = avio_alloc_context(buffer, sizeof(buffer),
false, this,
mpd_ffmpeg_stream_read, nullptr,
- input.seekable
+ input.IsSeekable()
? mpd_ffmpeg_stream_seek : nullptr);
return io != nullptr;
}
@@ -189,11 +210,19 @@ time_from_ffmpeg(int64_t t, const AVRational time_base)
/ (double)1024;
}
+template<typename Ratio>
+static constexpr AVRational
+RatioToAVRational()
+{
+ return { Ratio::num, Ratio::den };
+}
+
gcc_const
static int64_t
-time_to_ffmpeg(double t, const AVRational time_base)
+time_to_ffmpeg(SongTime t, const AVRational time_base)
{
- return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024},
+ return av_rescale_q(t.count(),
+ RatioToAVRational<SongTime::period>(),
time_base);
}
@@ -394,7 +423,7 @@ ffmpeg_probe(Decoder *decoder, InputStream &is)
avpd.buf = buffer;
avpd.buf_size = nbytes;
- avpd.filename = is.uri.c_str();
+ avpd.filename = is.GetURI();
#ifdef AVPROBE_SCORE_MIME
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(56, 5, 1)
@@ -430,7 +459,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
//ffmpeg works with ours "fileops" helper
AVFormatContext *format_context = nullptr;
if (mpd_ffmpeg_open_input(&format_context, stream.io,
- input.uri.c_str(),
+ input.GetURI(),
input_format) != 0) {
LogError(ffmpeg_domain, "Open failed");
return;
@@ -477,8 +506,11 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
const SampleFormat sample_format =
ffmpeg_sample_format(codec_context->sample_fmt);
- if (sample_format == SampleFormat::UNDEFINED)
+ if (sample_format == SampleFormat::UNDEFINED) {
+ // (error message already done by ffmpeg_sample_format())
+ avformat_close_input(&format_context);
return;
+ }
Error error;
AudioFormat audio_format;
@@ -503,12 +535,14 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
return;
}
- int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE
- ? format_context->duration / AV_TIME_BASE
- : 0;
+ const SignedSongTime total_time =
+ format_context->duration != (int64_t)AV_NOPTS_VALUE
+ ? SignedSongTime::FromScale<uint64_t>(format_context->duration,
+ AV_TIME_BASE)
+ : SignedSongTime::Negative();
decoder_initialized(decoder, audio_format,
- input.seekable, total_time);
+ input.IsSeekable(), total_time);
#if LIBAVUTIL_VERSION_MAJOR >= 53
AVFrame *frame = av_frame_alloc();
@@ -544,7 +578,7 @@ ffmpeg_decode(Decoder &decoder, InputStream &input)
if (cmd == DecoderCommand::SEEK) {
int64_t where =
- time_to_ffmpeg(decoder_seek_where(decoder),
+ time_to_ffmpeg(decoder_seek_time(decoder),
av_stream->time_base) +
start_time_fallback(*av_stream);
@@ -585,7 +619,7 @@ ffmpeg_scan_stream(InputStream &is,
return false;
AVFormatContext *f = nullptr;
- if (mpd_ffmpeg_open_input(&f, stream.io, is.uri.c_str(),
+ if (mpd_ffmpeg_open_input(&f, stream.io, is.GetURI(),
input_format) != 0)
return false;
@@ -596,9 +630,12 @@ ffmpeg_scan_stream(InputStream &is,
return false;
}
- if (f->duration != (int64_t)AV_NOPTS_VALUE)
- tag_handler_invoke_duration(handler, handler_ctx,
- f->duration / AV_TIME_BASE);
+ if (f->duration != (int64_t)AV_NOPTS_VALUE) {
+ const auto duration =
+ SongTime::FromScale<uint64_t>(f->duration,
+ AV_TIME_BASE);
+ tag_handler_invoke_duration(handler, handler_ctx, duration);
+ }
ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx);
int idx = ffmpeg_find_audio_stream(f);
diff --git a/src/decoder/FfmpegDecoderPlugin.hxx b/src/decoder/plugins/FfmpegDecoderPlugin.hxx
index 23bf74fce..0a3e78e4b 100644
--- a/src/decoder/FfmpegDecoderPlugin.hxx
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx
index 6e92b4a13..a39466945 100644
--- a/src/decoder/FfmpegMetaData.cxx
+++ b/src/decoder/plugins/FfmpegMetaData.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx
index 998cdf5a8..5eb41db68 100644
--- a/src/decoder/FfmpegMetaData.hxx
+++ b/src/decoder/plugins/FfmpegMetaData.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/plugins/FlacCommon.cxx
index e4b906c12..e86f85569 100644
--- a/src/decoder/FlacCommon.cxx
+++ b/src/decoder/plugins/FlacCommon.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -25,14 +25,10 @@
#include "FlacCommon.hxx"
#include "FlacMetadata.hxx"
#include "FlacPcm.hxx"
-#include "MixRampInfo.hxx"
#include "CheckAudioFormat.hxx"
#include "util/Error.hxx"
-#include "util/Domain.hxx"
#include "Log.hxx"
-#include <assert.h>
-
flac_data::flac_data(Decoder &_decoder,
InputStream &_input_stream)
:FlacInput(_input_stream, &_decoder),
@@ -102,13 +98,14 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
break;
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
- if (flac_parse_replay_gain(rgi, block))
+ if (flac_parse_replay_gain(rgi, block->data.vorbis_comment))
decoder_replay_gain(data->decoder, &rgi);
- decoder_mixramp(data->decoder, flac_parse_mixramp(block));
+ decoder_mixramp(data->decoder,
+ flac_parse_mixramp(block->data.vorbis_comment));
- flac_vorbis_comments_to_tag(data->tag,
- &block->data.vorbis_comment);
+ data->tag = flac_vorbis_comments_to_tag(&block->data.vorbis_comment);
+ break;
default:
break;
@@ -140,10 +137,12 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
data->frame_size = data->audio_format.GetFrameSize();
+ const auto duration = SongTime::FromScale<uint64_t>(data->total_frames,
+ data->audio_format.sample_rate);
+
decoder_initialized(data->decoder, data->audio_format,
- data->input_stream.seekable,
- (float)data->total_frames /
- (float)data->audio_format.sample_rate);
+ data->input_stream.IsSeekable(),
+ duration);
data->initialized = true;
diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/plugins/FlacCommon.hxx
index de000dfa1..34ce0a3fc 100644
--- a/src/decoder/FlacCommon.hxx
+++ b/src/decoder/plugins/FlacCommon.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -25,11 +25,10 @@
#define MPD_FLAC_COMMON_HXX
#include "FlacInput.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "pcm/PcmBuffer.hxx"
#include <FLAC/stream_decoder.h>
-#include <FLAC/metadata.h>
struct flac_data : public FlacInput {
PcmBuffer buffer;
diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/plugins/FlacDecoderPlugin.cxx
index 1b5734434..eea813401 100644
--- a/src/decoder/FlacDecoderPlugin.cxx
+++ b/src/decoder/plugins/FlacDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -23,13 +23,10 @@
#include "FlacCommon.hxx"
#include "FlacMetadata.hxx"
#include "OggCodec.hxx"
+#include "fs/Path.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
-#include <assert.h>
-
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
#error libFLAC is too old
#endif
@@ -83,11 +80,11 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
}
static bool
-flac_scan_file(const char *file,
+flac_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
FlacMetadataChain chain;
- if (!chain.Read(file)) {
+ if (!chain.Read(path_fs.c_str())) {
FormatDebug(flac_domain,
"Failed to read FLAC tags: %s",
chain.GetStatusString());
@@ -147,14 +144,18 @@ flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
if (data->initialized) {
/* done */
+
+ const auto duration2 =
+ SongTime::FromScale<uint64_t>(data->total_frames,
+ data->audio_format.sample_rate);
+
decoder_initialized(data->decoder, data->audio_format,
- data->input_stream.seekable,
- (float)data->total_frames /
- (float)data->audio_format.sample_rate);
+ data->input_stream.IsSeekable(),
+ duration2);
return true;
}
- if (data->input_stream.seekable)
+ if (data->input_stream.IsSeekable())
/* allow the workaround below only for nonseekable
streams*/
return false;
@@ -184,8 +185,7 @@ flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
if (cmd == DecoderCommand::SEEK) {
FLAC__uint64 seek_sample = t_start +
- decoder_seek_where(decoder) *
- data->audio_format.sample_rate;
+ decoder_seek_where_frame(decoder);
if (seek_sample >= t_start &&
(t_end == 0 || seek_sample <= t_end) &&
FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
@@ -297,11 +297,11 @@ oggflac_init(gcc_unused const config_param &param)
}
static bool
-oggflac_scan_file(const char *file,
+oggflac_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
FlacMetadataChain chain;
- if (!chain.ReadOgg(file)) {
+ if (!chain.ReadOgg(path_fs.c_str())) {
FormatDebug(flac_domain,
"Failed to read OggFLAC tags: %s",
chain.GetStatusString());
diff --git a/src/decoder/FlacDecoderPlugin.h b/src/decoder/plugins/FlacDecoderPlugin.h
index 936423fbf..fcdecf869 100644
--- a/src/decoder/FlacDecoderPlugin.h
+++ b/src/decoder/plugins/FlacDecoderPlugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/FlacDomain.cxx b/src/decoder/plugins/FlacDomain.cxx
index 5858004de..fc5cc5498 100644
--- a/src/decoder/FlacDomain.cxx
+++ b/src/decoder/plugins/FlacDomain.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/FlacDomain.hxx b/src/decoder/plugins/FlacDomain.hxx
index cf357332f..a06c6c6b4 100644
--- a/src/decoder/FlacDomain.hxx
+++ b/src/decoder/plugins/FlacDomain.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/FlacIOHandle.cxx b/src/decoder/plugins/FlacIOHandle.cxx
index b471ecf64..0dd895798 100644
--- a/src/decoder/FlacIOHandle.cxx
+++ b/src/decoder/plugins/FlacIOHandle.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -23,6 +23,7 @@
#include "Compiler.h"
#include <errno.h>
+#include <stdio.h>
static size_t
FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle)
@@ -62,12 +63,31 @@ FlacIORead(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle handle)
}
static int
-FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 offset, int whence)
+FlacIOSeek(FLAC__IOHandle handle, FLAC__int64 _offset, int whence)
{
InputStream *is = (InputStream *)handle;
- Error error;
- return is->LockSeek(offset, whence, error) ? 0 : -1;
+ offset_type offset = _offset;
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ offset += is->GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!is->KnownSize())
+ return -1;
+
+ offset += is->GetSize();
+ break;
+
+ default:
+ return -1;
+ }
+
+ return is->LockSeek(offset, IgnoreError()) ? 0 : -1;
}
static FLAC__int64
@@ -75,7 +95,7 @@ FlacIOTell(FLAC__IOHandle handle)
{
InputStream *is = (InputStream *)handle;
- return is->offset;
+ return is->GetOffset();
}
static int
diff --git a/src/decoder/FlacIOHandle.hxx b/src/decoder/plugins/FlacIOHandle.hxx
index b6e563fa3..90acc66af 100644
--- a/src/decoder/FlacIOHandle.hxx
+++ b/src/decoder/plugins/FlacIOHandle.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -21,7 +21,7 @@
#define MPD_FLAC_IO_HANDLE_HXX
#include "Compiler.h"
-#include "InputStream.hxx"
+#include "input/InputStream.hxx"
#include <FLAC/callback.h>
@@ -37,7 +37,7 @@ ToFlacIOHandle(InputStream &is)
static inline const FLAC__IOCallbacks &
GetFlacIOCallbacks(const InputStream &is)
{
- return is.seekable
+ return is.IsSeekable()
? flac_io_callbacks_seekable
: flac_io_callbacks;
}
diff --git a/src/decoder/FlacInput.cxx b/src/decoder/plugins/FlacInput.cxx
index ce193101d..5b4c3104d 100644
--- a/src/decoder/FlacInput.cxx
+++ b/src/decoder/plugins/FlacInput.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,8 +20,8 @@
#include "config.h"
#include "FlacInput.hxx"
#include "FlacDomain.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
#include "Compiler.h"
@@ -47,11 +47,11 @@ FlacInput::Read(FLAC__byte buffer[], size_t *bytes)
FLAC__StreamDecoderSeekStatus
FlacInput::Seek(FLAC__uint64 absolute_byte_offset)
{
- if (!input_stream.seekable)
+ if (!input_stream.IsSeekable())
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
::Error error;
- if (!input_stream.LockSeek(absolute_byte_offset, SEEK_SET, error)) {
+ if (!input_stream.LockSeek(absolute_byte_offset, error)) {
LogError(error);
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
}
@@ -62,20 +62,20 @@ FlacInput::Seek(FLAC__uint64 absolute_byte_offset)
FLAC__StreamDecoderTellStatus
FlacInput::Tell(FLAC__uint64 *absolute_byte_offset)
{
- if (!input_stream.seekable)
+ if (!input_stream.IsSeekable())
return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
- *absolute_byte_offset = (FLAC__uint64)input_stream.offset;
+ *absolute_byte_offset = (FLAC__uint64)input_stream.GetOffset();
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}
FLAC__StreamDecoderLengthStatus
FlacInput::Length(FLAC__uint64 *stream_length)
{
- if (input_stream.size < 0)
+ if (!input_stream.KnownSize())
return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
- *stream_length = (FLAC__uint64)input_stream.size;
+ *stream_length = (FLAC__uint64)input_stream.GetSize();
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}
diff --git a/src/decoder/FlacInput.hxx b/src/decoder/plugins/FlacInput.hxx
index ddd5649f8..427abccb4 100644
--- a/src/decoder/FlacInput.hxx
+++ b/src/decoder/plugins/FlacInput.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -23,7 +23,7 @@
#include <FLAC/stream_decoder.h>
struct Decoder;
-struct InputStream;
+class InputStream;
/**
* This class wraps an #InputStream in libFLAC stream decoder
diff --git a/src/decoder/FlacMetadata.cxx b/src/decoder/plugins/FlacMetadata.cxx
index 17cc4cd8d..03e276dce 100644
--- a/src/decoder/FlacMetadata.cxx
+++ b/src/decoder/plugins/FlacMetadata.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -21,122 +21,56 @@
#include "FlacMetadata.hxx"
#include "XiphTags.hxx"
#include "MixRampInfo.hxx"
-#include "tag/Tag.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagTable.hxx"
#include "tag/TagBuilder.hxx"
+#include "tag/Tag.hxx"
+#include "tag/VorbisComment.hxx"
+#include "tag/ReplayGain.hxx"
+#include "tag/MixRamp.hxx"
#include "ReplayGainInfo.hxx"
#include "util/ASCII.hxx"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
-
-static bool
-flac_find_float_comment(const FLAC__StreamMetadata *block,
- const char *cmnt, float *fl)
-{
- int offset;
- size_t pos;
- int len;
- unsigned char tmp, *p;
-
- offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
- cmnt);
- if (offset < 0)
- return false;
-
- pos = strlen(cmnt) + 1; /* 1 is for '=' */
- len = block->data.vorbis_comment.comments[offset].length - pos;
- if (len <= 0)
- return false;
-
- p = &block->data.vorbis_comment.comments[offset].entry[pos];
- tmp = p[len];
- p[len] = '\0';
- *fl = (float)atof((char *)p);
- p[len] = tmp;
-
- return true;
-}
+#include "util/SplitString.hxx"
bool
flac_parse_replay_gain(ReplayGainInfo &rgi,
- const FLAC__StreamMetadata *block)
+ const FLAC__StreamMetadata_VorbisComment &vc)
{
rgi.Clear();
bool found = false;
- if (flac_find_float_comment(block, "replaygain_album_gain",
- &rgi.tuples[REPLAY_GAIN_ALBUM].gain))
- found = true;
- if (flac_find_float_comment(block, "replaygain_album_peak",
- &rgi.tuples[REPLAY_GAIN_ALBUM].peak))
- found = true;
- if (flac_find_float_comment(block, "replaygain_track_gain",
- &rgi.tuples[REPLAY_GAIN_TRACK].gain))
- found = true;
- if (flac_find_float_comment(block, "replaygain_track_peak",
- &rgi.tuples[REPLAY_GAIN_TRACK].peak))
- found = true;
- return found;
-}
+ const auto *comments = vc.comments;
+ for (FLAC__uint32 i = 0, n = vc.num_comments; i < n; ++i)
+ if (ParseReplayGainVorbis(rgi,
+ (const char *)comments[i].entry))
+ found = true;
-gcc_pure
-static std::string
-flac_find_string_comment(const FLAC__StreamMetadata *block, const char *cmnt)
-{
- int offset;
- size_t pos;
- int len;
- const unsigned char *p;
-
- offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
- cmnt);
- if (offset < 0)
- return std::string();
-
- pos = strlen(cmnt) + 1; /* 1 is for '=' */
- len = block->data.vorbis_comment.comments[offset].length - pos;
- if (len <= 0)
- return std::string();
-
- p = &block->data.vorbis_comment.comments[offset].entry[pos];
- return std::string((const char *)p, len);
+ return found;
}
MixRampInfo
-flac_parse_mixramp(const FLAC__StreamMetadata *block)
+flac_parse_mixramp(const FLAC__StreamMetadata_VorbisComment &vc)
{
MixRampInfo mix_ramp;
- mix_ramp.SetStart(flac_find_string_comment(block, "mixramp_start"));
- mix_ramp.SetEnd(flac_find_string_comment(block, "mixramp_end"));
+
+ const auto *comments = vc.comments;
+ for (FLAC__uint32 i = 0, n = vc.num_comments; i < n; ++i)
+ ParseMixRampVorbis(mix_ramp,
+ (const char *)comments[i].entry);
+
return mix_ramp;
}
/**
* Checks if the specified name matches the entry's name, and if yes,
- * returns the comment value (not null-temrinated).
+ * returns the comment value;
*/
static const char *
flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
- const char *name, size_t *length_r)
+ const char *name)
{
- size_t name_length = strlen(name);
- const char *comment = (const char*)entry->entry;
-
- if (entry->length <= name_length ||
- !StringEqualsCaseASCII(comment, name, name_length))
- return nullptr;
-
- if (comment[name_length] == '=') {
- *length_r = entry->length - name_length - 1;
- return comment + name_length + 1;
- }
-
- return nullptr;
+ return vorbis_comment_value((const char *)entry->entry, name);
}
/**
@@ -148,14 +82,9 @@ flac_copy_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
const char *name, TagType tag_type,
const struct tag_handler *handler, void *handler_ctx)
{
- const char *value;
- size_t value_length;
-
- value = flac_comment_value(entry, name, &value_length);
+ const char *value = flac_comment_value(entry, name);
if (value != nullptr) {
- char *p = g_strndup(value, value_length);
- tag_handler_invoke_tag(handler, handler_ctx, tag_type, p);
- g_free(p);
+ tag_handler_invoke_tag(handler, handler_ctx, tag_type, value);
return true;
}
@@ -167,16 +96,12 @@ flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
const struct tag_handler *handler, void *handler_ctx)
{
if (handler->pair != nullptr) {
- char *name = g_strdup((const char*)entry->entry);
- char *value = strchr(name, '=');
-
- if (value != nullptr && value > name) {
- *value++ = 0;
+ const char *comment = (const char *)entry->entry;
+ const SplitString split(comment, '=');
+ if (split.IsDefined() && !split.IsEmpty())
tag_handler_invoke_pair(handler, handler_ctx,
- name, value);
- }
-
- g_free(name);
+ split.GetFirst(),
+ split.GetSecond());
}
for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i)
@@ -200,6 +125,16 @@ flac_scan_comments(const FLAC__StreamMetadata_VorbisComment *comment,
handler, handler_ctx);
}
+gcc_pure
+static inline SongTime
+flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
+{
+ assert(stream_info->sample_rate > 0);
+
+ return SongTime::FromScale<uint64_t>(stream_info->total_samples,
+ stream_info->sample_rate);
+}
+
void
flac_scan_metadata(const FLAC__StreamMetadata *block,
const struct tag_handler *handler, void *handler_ctx)
@@ -221,13 +156,12 @@ flac_scan_metadata(const FLAC__StreamMetadata *block,
}
}
-void
-flac_vorbis_comments_to_tag(Tag &tag,
- const FLAC__StreamMetadata_VorbisComment *comment)
+Tag
+flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment)
{
TagBuilder tag_builder;
flac_scan_comments(comment, &add_tag_handler, &tag_builder);
- tag_builder.Commit(tag);
+ return tag_builder.Commit();
}
void
diff --git a/src/decoder/FlacMetadata.hxx b/src/decoder/plugins/FlacMetadata.hxx
index 96c61b8e6..d791fa34e 100644
--- a/src/decoder/FlacMetadata.hxx
+++ b/src/decoder/plugins/FlacMetadata.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -27,6 +27,7 @@
#include <assert.h>
+struct tag_handler;
class MixRampInfo;
class FlacMetadataChain {
@@ -81,7 +82,7 @@ public:
return FLAC__Metadata_ChainStatusString[GetStatus()];
}
- void Scan(const struct tag_handler *handler, void *handler_ctx);
+ void Scan(const tag_handler *handler, void *handler_ctx);
};
class FLACMetadataIterator {
@@ -110,32 +111,21 @@ public:
}
};
-struct tag_handler;
struct Tag;
struct ReplayGainInfo;
-static inline unsigned
-flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
-{
- assert(stream_info->sample_rate > 0);
-
- return (stream_info->total_samples + stream_info->sample_rate - 1) /
- stream_info->sample_rate;
-}
-
bool
flac_parse_replay_gain(ReplayGainInfo &rgi,
- const FLAC__StreamMetadata *block);
+ const FLAC__StreamMetadata_VorbisComment &vc);
MixRampInfo
-flac_parse_mixramp(const FLAC__StreamMetadata *block);
+flac_parse_mixramp(const FLAC__StreamMetadata_VorbisComment &vc);
-void
-flac_vorbis_comments_to_tag(Tag &tag,
- const FLAC__StreamMetadata_VorbisComment *comment);
+Tag
+flac_vorbis_comments_to_tag(const FLAC__StreamMetadata_VorbisComment *comment);
void
flac_scan_metadata(const FLAC__StreamMetadata *block,
- const struct tag_handler *handler, void *handler_ctx);
+ const tag_handler *handler, void *handler_ctx);
#endif
diff --git a/src/decoder/FlacPcm.cxx b/src/decoder/plugins/FlacPcm.cxx
index 569879371..311500f26 100644
--- a/src/decoder/FlacPcm.cxx
+++ b/src/decoder/plugins/FlacPcm.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/FlacPcm.hxx b/src/decoder/plugins/FlacPcm.hxx
index 6cb2d5062..30c318725 100644
--- a/src/decoder/FlacPcm.hxx
+++ b/src/decoder/plugins/FlacPcm.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
index fa946f219..f19ac5bf4 100644
--- a/src/decoder/FluidsynthDecoderPlugin.cxx
+++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,8 +19,9 @@
#include "config.h"
#include "FluidsynthDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
+#include "fs/Path.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "util/Macros.hxx"
@@ -92,7 +93,7 @@ fluidsynth_init(const config_param &param)
}
static void
-fluidsynth_file_decode(Decoder &decoder, const char *path_fs)
+fluidsynth_file_decode(Decoder &decoder, Path path_fs)
{
char setting_sample_rate[] = "synth.sample-rate";
/*
@@ -141,7 +142,7 @@ fluidsynth_file_decode(Decoder &decoder, const char *path_fs)
return;
}
- ret = fluid_player_add(player, path_fs);
+ ret = fluid_player_add(player, path_fs.c_str());
if (ret != 0) {
LogWarning(fluidsynth_domain, "fluid_player_add() failed");
delete_fluid_player(player);
@@ -165,7 +166,8 @@ fluidsynth_file_decode(Decoder &decoder, const char *path_fs)
MPD core */
const AudioFormat audio_format(sample_rate, SampleFormat::S16, 2);
- decoder_initialized(decoder, audio_format, false, -1);
+ decoder_initialized(decoder, audio_format, false,
+ SignedSongTime::Negative());
DecoderCommand cmd;
while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) {
@@ -198,11 +200,11 @@ fluidsynth_file_decode(Decoder &decoder, const char *path_fs)
}
static bool
-fluidsynth_scan_file(const char *file,
+fluidsynth_scan_file(Path path_fs,
gcc_unused const struct tag_handler *handler,
gcc_unused void *handler_ctx)
{
- return fluid_is_midifile(file);
+ return fluid_is_midifile(path_fs.c_str());
}
static const char *const fluidsynth_suffixes[] = {
diff --git a/src/decoder/FluidsynthDecoderPlugin.hxx b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx
index 9771898a5..cd8ec2d62 100644
--- a/src/decoder/FluidsynthDecoderPlugin.hxx
+++ b/src/decoder/plugins/FluidsynthDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx
index 9c9b19478..cc6ce5e5d 100644
--- a/src/decoder/GmeDecoderPlugin.cxx
+++ b/src/decoder/plugins/GmeDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,9 +19,11 @@
#include "config.h"
#include "GmeDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
+#include "fs/Path.hxx"
+#include "util/Alloc.hxx"
#include "util/FormatString.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
@@ -50,10 +52,10 @@ static constexpr unsigned GME_BUFFER_SAMPLES =
* suffix
*/
static char *
-get_container_name(const char *path_fs)
+get_container_name(Path path_fs)
{
- const char *subtune_suffix = uri_get_suffix(path_fs);
- char *path_container = g_strdup(path_fs);
+ const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
+ char *path_container = xstrdup(path_fs.c_str());
char pat[64];
snprintf(pat, sizeof(pat), "%s%s",
@@ -79,9 +81,9 @@ get_container_name(const char *path_fs)
* is appended.
*/
static int
-get_song_num(const char *path_fs)
+get_song_num(Path path_fs)
{
- const char *subtune_suffix = uri_get_suffix(path_fs);
+ const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
char pat[64];
snprintf(pat, sizeof(pat), "%s%s",
@@ -90,8 +92,8 @@ get_song_num(const char *path_fs)
GPatternSpec *path_with_subtune = g_pattern_spec_new(pat);
if (g_pattern_match(path_with_subtune,
- strlen(path_fs), path_fs, nullptr)) {
- char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
+ path_fs.length(), path_fs.data(), nullptr)) {
+ char *sub = g_strrstr(path_fs.c_str(), "/" SUBTUNE_PREFIX);
g_pattern_spec_free(path_with_subtune);
if (!sub)
return 0;
@@ -107,10 +109,11 @@ get_song_num(const char *path_fs)
}
static char *
-gme_container_scan(const char *path_fs, const unsigned int tnum)
+gme_container_scan(Path path_fs, const unsigned int tnum)
{
Music_Emu *emu;
- const char *gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
+ const char *gme_err = gme_open_file(path_fs.c_str(), &emu,
+ GME_SAMPLE_RATE);
if (gme_err != nullptr) {
LogWarning(gme_domain, gme_err);
return nullptr;
@@ -122,7 +125,7 @@ gme_container_scan(const char *path_fs, const unsigned int tnum)
if (num_songs < 2)
return nullptr;
- const char *subtune_suffix = uri_get_suffix(path_fs);
+ const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
if (tnum <= num_songs){
return FormatNew(SUBTUNE_PREFIX "%03u.%s",
tnum, subtune_suffix);
@@ -131,14 +134,14 @@ gme_container_scan(const char *path_fs, const unsigned int tnum)
}
static void
-gme_file_decode(Decoder &decoder, const char *path_fs)
+gme_file_decode(Decoder &decoder, Path path_fs)
{
char *path_container = get_container_name(path_fs);
Music_Emu *emu;
const char *gme_err =
gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
- g_free(path_container);
+ free(path_container);
if (gme_err != nullptr) {
LogWarning(gme_domain, gme_err);
return;
@@ -153,9 +156,9 @@ gme_file_decode(Decoder &decoder, const char *path_fs)
return;
}
- const float song_len = ti->length > 0
- ? ti->length / 1000.0
- : -1.0;
+ const SignedSongTime song_len = ti->length > 0
+ ? SignedSongTime::FromMS(ti->length)
+ : SignedSongTime::Negative();
/* initialize the MPD decoder */
@@ -191,8 +194,8 @@ gme_file_decode(Decoder &decoder, const char *path_fs)
cmd = decoder_data(decoder, nullptr, buf, sizeof(buf), 0);
if (cmd == DecoderCommand::SEEK) {
- float where = decoder_seek_where(decoder);
- gme_err = gme_seek(emu, int(where * 1000));
+ unsigned where = decoder_seek_time(decoder).ToMS();
+ gme_err = gme_seek(emu, where);
if (gme_err != nullptr)
LogWarning(gme_domain, gme_err);
decoder_command_finished(decoder);
@@ -207,7 +210,7 @@ gme_file_decode(Decoder &decoder, const char *path_fs)
}
static bool
-gme_scan_file(const char *path_fs,
+gme_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
char *path_container = get_container_name(path_fs);
@@ -215,7 +218,7 @@ gme_scan_file(const char *path_fs,
Music_Emu *emu;
const char *gme_err =
gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
- g_free(path_container);
+ free(path_container);
if (gme_err != nullptr) {
LogWarning(gme_domain, gme_err);
return false;
@@ -235,7 +238,7 @@ gme_scan_file(const char *path_fs,
if (ti->length > 0)
tag_handler_invoke_duration(handler, handler_ctx,
- ti->length / 1000);
+ SongTime::FromMS(ti->length));
if (ti->song != nullptr) {
if (gme_track_count(emu) > 1) {
diff --git a/src/decoder/GmeDecoderPlugin.hxx b/src/decoder/plugins/GmeDecoderPlugin.hxx
index e46dc766a..f4885b6e4 100644
--- a/src/decoder/GmeDecoderPlugin.hxx
+++ b/src/decoder/plugins/GmeDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx
index 6f619b34b..de6c9b127 100644
--- a/src/decoder/MadDecoderPlugin.cxx
+++ b/src/decoder/plugins/MadDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,33 +19,33 @@
#include "config.h"
#include "MadDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
-#include "ConfigGlobal.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
+#include "config/ConfigGlobal.hxx"
#include "tag/TagId3.hxx"
#include "tag/TagRva2.hxx"
#include "tag/TagHandler.hxx"
+#include "tag/ReplayGain.hxx"
+#include "tag/MixRamp.hxx"
#include "CheckAudioFormat.hxx"
+#include "util/StringUtil.hxx"
#include "util/ASCII.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <assert.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <glib.h>
#include <mad.h>
#ifdef HAVE_ID3TAG
#include <id3tag.h>
#endif
-#define FRAMES_CUSHION 2000
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
-#define READ_BUFFER_SIZE 40960
+static constexpr unsigned long FRAMES_CUSHION = 2000;
enum mp3_action {
DECODE_SKIP = -3,
@@ -61,22 +61,27 @@ enum muteframe {
};
/* the number of samples of silence the decoder inserts at start */
-#define DECODERDELAY 529
+static constexpr unsigned DECODERDELAY = 529;
-#define DEFAULT_GAPLESS_MP3_PLAYBACK true
+static constexpr bool DEFAULT_GAPLESS_MP3_PLAYBACK = true;
static constexpr Domain mad_domain("mad");
static bool gapless_playback;
+gcc_const
+static SongTime
+ToSongTime(mad_timer_t t)
+{
+ return SongTime::FromMS(mad_timer_count(t, MAD_UNITS_MILLISECONDS));
+}
+
static inline int32_t
mad_fixed_to_24_sample(mad_fixed_t sample)
{
- enum {
- bits = 24,
- MIN = -MAD_F_ONE,
- MAX = MAD_F_ONE - 1
- };
+ static constexpr unsigned bits = 24;
+ static constexpr mad_fixed_t MIN = -MAD_F_ONE;
+ static constexpr mad_fixed_t MAX = MAD_F_ONE - 1;
/* round */
sample = sample + (1L << (MAD_F_FRACBITS - bits));
@@ -96,12 +101,9 @@ mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth,
unsigned int start, unsigned int end,
unsigned int num_channels)
{
- unsigned int i, c;
-
- for (i = start; i < end; ++i) {
- for (c = 0; c < num_channels; ++c)
+ for (unsigned i = start; i < end; ++i)
+ for (unsigned c = 0; c < num_channels; ++c)
*dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]);
- }
}
static bool
@@ -112,18 +114,19 @@ mp3_plugin_init(gcc_unused const config_param &param)
return true;
}
-#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048
-
struct MadDecoder {
+ static constexpr size_t READ_BUFFER_SIZE = 40960;
+ static constexpr size_t MP3_DATA_OUTPUT_BUFFER_SIZE = 2048;
+
struct mad_stream stream;
struct mad_frame frame;
struct mad_synth synth;
mad_timer_t timer;
unsigned char input_buffer[READ_BUFFER_SIZE];
int32_t output_buffer[MP3_DATA_OUTPUT_BUFFER_SIZE];
- float total_time;
- float elapsed_time;
- float seek_where;
+ SignedSongTime total_time;
+ SongTime elapsed_time;
+ SongTime seek_time;
enum muteframe mute_frame;
long *frame_offsets;
mad_timer_t *times;
@@ -135,7 +138,6 @@ struct MadDecoder {
unsigned int drop_start_samples;
unsigned int drop_end_samples;
bool found_replay_gain;
- bool found_xing;
bool found_first_frame;
bool decoded_first_frame;
unsigned long bit_rate;
@@ -153,10 +155,10 @@ struct MadDecoder {
enum mp3_action DecodeNextFrame();
gcc_pure
- InputStream::offset_type ThisFrameOffset() const;
+ offset_type ThisFrameOffset() const;
gcc_pure
- InputStream::offset_type RestIncludingThisFrame() const;
+ offset_type RestIncludingThisFrame() const;
/**
* Attempt to calulcate the length of the song from filesize
@@ -166,7 +168,7 @@ struct MadDecoder {
bool DecodeFirstFrame(Tag **tag);
gcc_pure
- long TimeToFrame(double t) const;
+ long TimeToFrame(SongTime t) const;
void UpdateTimerNextFrame();
@@ -192,7 +194,7 @@ MadDecoder::MadDecoder(Decoder *_decoder,
highest_frame(0), max_frames(0), current_frame(0),
drop_start_frames(0), drop_end_frames(0),
drop_start_samples(0), drop_end_samples(0),
- found_replay_gain(false), found_xing(false),
+ found_replay_gain(false),
found_first_frame(false), decoded_first_frame(false),
decoder(_decoder), input_stream(_input_stream),
layer(mad_layer(0))
@@ -208,7 +210,7 @@ inline bool
MadDecoder::Seek(long offset)
{
Error error;
- if (!input_stream.LockSeek(offset, SEEK_SET, error))
+ if (!input_stream.LockSeek(offset, error))
return false;
mad_stream_buffer(&stream, input_buffer, 0);
@@ -254,38 +256,24 @@ static bool
parse_id3_replay_gain_info(ReplayGainInfo &rgi,
struct id3_tag *tag)
{
- int i;
- char *key;
- char *value;
- struct id3_frame *frame;
bool found = false;
rgi.Clear();
- for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
+ struct id3_frame *frame;
+ for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
if (frame->nfields < 3)
continue;
- key = (char *)
+ char *const key = (char *)
id3_ucs4_latin1duplicate(id3_field_getstring
(&frame->fields[1]));
- value = (char *)
+ char *const value = (char *)
id3_ucs4_latin1duplicate(id3_field_getstring
(&frame->fields[2]));
- if (StringEqualsCaseASCII(key, "replaygain_track_gain")) {
- rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(value);
- found = true;
- } else if (StringEqualsCaseASCII(key, "replaygain_album_gain")) {
- rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(value);
- found = true;
- } else if (StringEqualsCaseASCII(key, "replaygain_track_peak")) {
- rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(value);
+ if (ParseReplayGainTag(rgi, key, value))
found = true;
- } else if (StringEqualsCaseASCII(key, "replaygain_album_peak")) {
- rgi.tuples[REPLAY_GAIN_ALBUM].peak = atof(value);
- found = true;
- }
free(key);
free(value);
@@ -302,29 +290,21 @@ gcc_pure
static MixRampInfo
parse_id3_mixramp(struct id3_tag *tag)
{
- int i;
- char *key;
- char *value;
- struct id3_frame *frame;
-
MixRampInfo result;
- for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
+ struct id3_frame *frame;
+ for (unsigned i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
if (frame->nfields < 3)
continue;
- key = (char *)
+ char *const key = (char *)
id3_ucs4_latin1duplicate(id3_field_getstring
(&frame->fields[1]));
- value = (char *)
+ char *const value = (char *)
id3_ucs4_latin1duplicate(id3_field_getstring
(&frame->fields[2]));
- if (StringEqualsCaseASCII(key, "mixramp_start")) {
- result.SetStart(value);
- } else if (StringEqualsCaseASCII(key, "mixramp_end")) {
- result.SetEnd(value);
- }
+ ParseMixRampTag(result, key, value);
free(key);
free(value);
@@ -338,34 +318,32 @@ inline void
MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
{
#ifdef HAVE_ID3TAG
- struct id3_tag *id3_tag = nullptr;
- id3_length_t count;
- id3_byte_t const *id3_data;
id3_byte_t *allocated = nullptr;
- count = stream.bufend - stream.this_frame;
+ const id3_length_t count = stream.bufend - stream.this_frame;
+ const id3_byte_t *id3_data;
if (tagsize <= count) {
id3_data = stream.this_frame;
mad_stream_skip(&(stream), tagsize);
} else {
- allocated = (id3_byte_t *)g_malloc(tagsize);
+ allocated = new id3_byte_t[tagsize];
memcpy(allocated, stream.this_frame, count);
mad_stream_skip(&(stream), count);
if (!decoder_read_full(decoder, input_stream,
allocated + count, tagsize - count)) {
LogDebug(mad_domain, "error parsing ID3 tag");
- g_free(allocated);
+ delete[] allocated;
return;
}
id3_data = allocated;
}
- id3_tag = id3_tag_parse(id3_data, tagsize);
+ struct id3_tag *const id3_tag = id3_tag_parse(id3_data, tagsize);
if (id3_tag == nullptr) {
- g_free(allocated);
+ delete[] allocated;
return;
}
@@ -390,7 +368,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
id3_tag_delete(id3_tag);
- g_free(allocated);
+ delete[] allocated;
#else /* !HAVE_ID3TAG */
(void)mpd_tag;
@@ -426,6 +404,20 @@ id3_tag_query(const void *p0, size_t length)
}
#endif /* !HAVE_ID3TAG */
+static enum mp3_action
+RecoverFrameError(struct mad_stream &stream)
+{
+ if (MAD_RECOVERABLE(stream.error))
+ return DECODE_SKIP;
+ else if (stream.error == MAD_ERROR_BUFLEN)
+ return DECODE_CONT;
+
+ FormatWarning(mad_domain,
+ "unrecoverable frame level error: %s",
+ mad_stream_errorstr(&stream));
+ return DECODE_BREAK;
+}
+
enum mp3_action
MadDecoder::DecodeNextFrameHeader(Tag **tag)
{
@@ -448,18 +440,8 @@ MadDecoder::DecodeNextFrameHeader(Tag **tag)
return DECODE_CONT;
}
}
- if (MAD_RECOVERABLE(stream.error)) {
- return DECODE_SKIP;
- } else {
- if (stream.error == MAD_ERROR_BUFLEN)
- return DECODE_CONT;
- else {
- FormatWarning(mad_domain,
- "unrecoverable frame level error: %s",
- mad_stream_errorstr(&stream));
- return DECODE_BREAK;
- }
- }
+
+ return RecoverFrameError(stream);
}
enum mad_layer new_layer = frame.header.layer;
@@ -495,28 +477,18 @@ MadDecoder::DecodeNextFrame()
return DECODE_CONT;
}
}
- if (MAD_RECOVERABLE(stream.error)) {
- return DECODE_SKIP;
- } else {
- if (stream.error == MAD_ERROR_BUFLEN)
- return DECODE_CONT;
- else {
- FormatWarning(mad_domain,
- "unrecoverable frame level error: %s",
- mad_stream_errorstr(&stream));
- return DECODE_BREAK;
- }
- }
+
+ return RecoverFrameError(stream);
}
return DECODE_OK;
}
/* xing stuff stolen from alsaplayer, and heavily modified by jat */
-#define XI_MAGIC (('X' << 8) | 'i')
-#define NG_MAGIC (('n' << 8) | 'g')
-#define IN_MAGIC (('I' << 8) | 'n')
-#define FO_MAGIC (('f' << 8) | 'o')
+static constexpr unsigned XI_MAGIC = (('X' << 8) | 'i');
+static constexpr unsigned NG_MAGIC = (('n' << 8) | 'g');
+static constexpr unsigned IN_MAGIC = (('I' << 8) | 'n');
+static constexpr unsigned FO_MAGIC = (('f' << 8) | 'o');
enum xing_magic {
XING_MAGIC_XING, /* VBR */
@@ -532,12 +504,10 @@ struct xing {
enum xing_magic magic; /* header magic */
};
-enum {
- XING_FRAMES = 0x00000001L,
- XING_BYTES = 0x00000002L,
- XING_TOC = 0x00000004L,
- XING_SCALE = 0x00000008L
-};
+static const unsigned XING_FRAMES = 1;
+static const unsigned XING_BYTES = 2;
+static const unsigned XING_TOC = 4;
+static const unsigned XING_SCALE = 8;
struct lame_version {
unsigned major;
@@ -558,17 +528,12 @@ struct lame {
static bool
parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
{
- unsigned long bits;
- int bitlen;
- int bitsleft;
- int i;
-
- bitlen = *oldbitlen;
+ int bitlen = *oldbitlen;
if (bitlen < 16)
return false;
- bits = mad_bit_read(ptr, 16);
+ const unsigned long bits = mad_bit_read(ptr, 16);
bitlen -= 16;
if (bits == XI_MAGIC) {
@@ -617,7 +582,8 @@ parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
if (xing->flags & XING_TOC) {
if (bitlen < 800)
return false;
- for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8);
+ for (unsigned i = 0; i < 100; ++i)
+ xing->toc[i] = mad_bit_read(ptr, 8);
bitlen -= 800;
}
@@ -630,7 +596,7 @@ parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
/* Make sure we consume no less than 120 bytes (960 bits) in hopes that
* the LAME tag is found there, and not right after the Xing header */
- bitsleft = 960 - ((*oldbitlen) - bitlen);
+ const int bitsleft = 960 - (*oldbitlen - bitlen);
if (bitsleft < 0)
return false;
else if (bitsleft > 0) {
@@ -646,19 +612,12 @@ parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen)
static bool
parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
{
- int adj = 0;
- int name;
- int orig;
- int sign;
- int gain;
- int i;
-
/* Unlike the xing header, the lame tag has a fixed length. Fail if
* not all 36 bytes (288 bits) are there. */
if (*bitlen < 288)
return false;
- for (i = 0; i < 9; i++)
+ for (unsigned i = 0; i < 9; i++)
lame->encoder[i] = (char)mad_bit_read(ptr, 8);
lame->encoder[9] = '\0';
@@ -667,7 +626,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
/* This is technically incorrect, since the encoder might not be lame.
* But there's no other way to determine if this is a lame tag, and we
* wouldn't want to go reading a tag that's not there. */
- if (!g_str_has_prefix(lame->encoder, "LAME"))
+ if (!StringStartsWith(lame->encoder, "LAME"))
return false;
if (sscanf(lame->encoder+4, "%u.%u",
@@ -684,6 +643,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
* it's impossible to make the proper adjustment for 3.95.
* Fortunately, 3.95 was only out for about a day before 3.95.1 was
* released. -- tmz */
+ int adj = 0;
if (lame->version.major < 3 ||
(lame->version.major == 3 && lame->version.minor < 95))
adj = 6;
@@ -694,10 +654,10 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
FormatDebug(mad_domain, "LAME peak found: %f", lame->peak);
lame->track_gain = 0;
- name = mad_bit_read(ptr, 3); /* gain name */
- orig = mad_bit_read(ptr, 3); /* gain originator */
- sign = mad_bit_read(ptr, 1); /* sign bit */
- gain = mad_bit_read(ptr, 9); /* gain*10 */
+ unsigned name = mad_bit_read(ptr, 3); /* gain name */
+ unsigned orig = mad_bit_read(ptr, 3); /* gain originator */
+ unsigned sign = mad_bit_read(ptr, 1); /* sign bit */
+ int gain = mad_bit_read(ptr, 9); /* gain*10 */
if (gain && name == 1 && orig != 0) {
lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj;
FormatDebug(mad_domain, "LAME track gain found: %f",
@@ -740,14 +700,13 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
return true;
}
-static inline float
+static inline SongTime
mp3_frame_duration(const struct mad_frame *frame)
{
- return mad_timer_count(frame->header.duration,
- MAD_UNITS_MILLISECONDS) / 1000.0;
+ return ToSongTime(frame->header.duration);
}
-inline InputStream::offset_type
+inline offset_type
MadDecoder::ThisFrameOffset() const
{
auto offset = input_stream.GetOffset();
@@ -760,7 +719,7 @@ MadDecoder::ThisFrameOffset() const
return offset;
}
-inline InputStream::offset_type
+inline offset_type
MadDecoder::RestIncludingThisFrame() const
{
return input_stream.GetSize() - ThisFrameOffset();
@@ -769,16 +728,22 @@ MadDecoder::RestIncludingThisFrame() const
inline void
MadDecoder::FileSizeToSongLength()
{
- InputStream::offset_type rest = RestIncludingThisFrame();
-
- if (rest > 0) {
- float frame_duration = mp3_frame_duration(&frame);
-
- total_time = (rest * 8.0) / frame.header.bitrate;
- max_frames = total_time / frame_duration + FRAMES_CUSHION;
+ if (input_stream.KnownSize()) {
+ offset_type rest = RestIncludingThisFrame();
+
+ const SongTime frame_duration = mp3_frame_duration(&frame);
+ const SongTime duration =
+ SongTime::FromScale<uint64_t>(rest,
+ frame.header.bitrate / 8);
+ total_time = duration;
+
+ max_frames = (frame_duration.IsPositive()
+ ? duration.count() / frame_duration.count()
+ : 0)
+ + FRAMES_CUSHION;
} else {
max_frames = FRAMES_CUSHION;
- total_time = 0;
+ total_time = SignedSongTime::Negative();
}
}
@@ -786,16 +751,10 @@ inline bool
MadDecoder::DecodeFirstFrame(Tag **tag)
{
struct xing xing;
- struct lame lame;
- struct mad_bitptr ptr;
- int bitlen;
- enum mp3_action ret;
-
- /* stfu gcc */
- memset(&xing, 0, sizeof(struct xing));
- xing.flags = 0;
+ xing.frames = 0;
while (true) {
+ enum mp3_action ret;
do {
ret = DecodeNextFrameHeader(tag);
} while (ret == DECODE_CONT);
@@ -811,8 +770,8 @@ MadDecoder::DecodeFirstFrame(Tag **tag)
if (ret == DECODE_OK) break;
}
- ptr = stream.anc_ptr;
- bitlen = stream.anc_bitlen;
+ struct mad_bitptr ptr = stream.anc_ptr;
+ int bitlen = stream.anc_bitlen;
FileSizeToSongLength();
@@ -820,16 +779,16 @@ MadDecoder::DecodeFirstFrame(Tag **tag)
* if an xing tag exists, use that!
*/
if (parse_xing(&xing, &ptr, &bitlen)) {
- found_xing = true;
mute_frame = MUTEFRAME_SKIP;
if ((xing.flags & XING_FRAMES) && xing.frames) {
mad_timer_t duration = frame.header.duration;
mad_timer_multiply(&duration, xing.frames);
- total_time = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000;
+ total_time = ToSongTime(duration);
max_frames = xing.frames;
}
+ struct lame lame;
if (parse_lame(&lame, &ptr, &bitlen)) {
if (gapless_playback && input_stream.IsSeekable()) {
drop_start_samples = lame.encoder_delay +
@@ -877,24 +836,22 @@ MadDecoder::~MadDecoder()
}
/* this is primarily used for getting total time for tags */
-static int
+static std::pair<bool, SignedSongTime>
mad_decoder_total_file_time(InputStream &is)
{
MadDecoder data(nullptr, is);
return data.DecodeFirstFrame(nullptr)
- ? data.total_time + 0.5
- : -1;
+ ? std::make_pair(true, data.total_time)
+ : std::make_pair(false, SignedSongTime::Negative());
}
long
-MadDecoder::TimeToFrame(double t) const
+MadDecoder::TimeToFrame(SongTime t) const
{
unsigned long i;
for (i = 0; i < highest_frame; ++i) {
- double frame_time =
- mad_timer_count(times[i],
- MAD_UNITS_MILLISECONDS) / 1000.;
+ auto frame_time = ToSongTime(times[i]);
if (frame_time >= t)
break;
}
@@ -925,15 +882,13 @@ MadDecoder::UpdateTimerNextFrame()
timer = times[current_frame];
current_frame++;
- elapsed_time = mad_timer_count(timer, MAD_UNITS_MILLISECONDS) / 1000.0;
+ elapsed_time = ToSongTime(timer);
}
DecoderCommand
MadDecoder::SendPCM(unsigned i, unsigned pcm_length)
{
- unsigned max_samples;
-
- max_samples = sizeof(output_buffer) /
+ unsigned max_samples = sizeof(output_buffer) /
sizeof(output_buffer[0]) /
MAD_NCHANNELS(&frame.header);
@@ -1014,8 +969,6 @@ MadDecoder::SyncAndSend()
inline bool
MadDecoder::Read()
{
- enum mp3_action ret;
-
UpdateTimerNextFrame();
switch (mute_frame) {
@@ -1025,17 +978,16 @@ MadDecoder::Read()
mute_frame = MUTEFRAME_NONE;
break;
case MUTEFRAME_SEEK:
- if (elapsed_time >= seek_where)
+ if (elapsed_time >= seek_time)
mute_frame = MUTEFRAME_NONE;
break;
case MUTEFRAME_NONE:
cmd = SyncAndSend();
if (cmd == DecoderCommand::SEEK) {
- unsigned long j;
-
assert(input_stream.IsSeekable());
- j = TimeToFrame(decoder_seek_where(*decoder));
+ unsigned long j =
+ TimeToFrame(decoder_seek_time(*decoder));
if (j < highest_frame) {
if (Seek(frame_offsets[j])) {
current_frame = j;
@@ -1043,7 +995,7 @@ MadDecoder::Read()
} else
decoder_seek_error(*decoder);
} else {
- seek_where = decoder_seek_where(*decoder);
+ seek_time = decoder_seek_time(*decoder);
mute_frame = MUTEFRAME_SEEK;
decoder_command_finished(*decoder);
}
@@ -1052,8 +1004,7 @@ MadDecoder::Read()
}
while (true) {
- bool skip = false;
-
+ enum mp3_action ret;
do {
Tag *tag = nullptr;
@@ -1067,8 +1018,8 @@ MadDecoder::Read()
} while (ret == DECODE_CONT);
if (ret == DECODE_BREAK)
return false;
- else if (ret == DECODE_SKIP)
- skip = true;
+
+ const bool skip = ret == DECODE_SKIP;
if (mute_frame == MUTEFRAME_NONE) {
do {
@@ -1079,10 +1030,8 @@ MadDecoder::Read()
}
if (!skip && ret == DECODE_OK)
- break;
+ return true;
}
-
- return ret != DECODE_BREAK;
}
static void
@@ -1096,7 +1045,7 @@ mp3_decode(Decoder &decoder, InputStream &input_stream)
if (decoder_get_command(decoder) == DecoderCommand::NONE)
LogError(mad_domain,
- "Input does not appear to be a mp3 bit stream");
+ "input/Input does not appear to be a mp3 bit stream");
return;
}
@@ -1128,13 +1077,13 @@ static bool
mad_decoder_scan_stream(InputStream &is,
const struct tag_handler *handler, void *handler_ctx)
{
- int total_time;
-
- total_time = mad_decoder_total_file_time(is);
- if (total_time < 0)
+ const auto result = mad_decoder_total_file_time(is);
+ if (!result.first)
return false;
- tag_handler_invoke_duration(handler, handler_ctx, total_time);
+ if (!result.second.IsNegative())
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime(result.second));
return true;
}
diff --git a/src/decoder/MadDecoderPlugin.hxx b/src/decoder/plugins/MadDecoderPlugin.hxx
index 450323670..eb2a10d6f 100644
--- a/src/decoder/MadDecoderPlugin.hxx
+++ b/src/decoder/plugins/MadDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/plugins/MikmodDecoderPlugin.cxx
index 34381aafa..85633f1fc 100644
--- a/src/decoder/MikmodDecoderPlugin.cxx
+++ b/src/decoder/plugins/MikmodDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,14 +19,15 @@
#include "config.h"
#include "MikmodDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "tag/TagHandler.hxx"
#include "system/FatalError.hxx"
+#include "fs/Path.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
#include <mikmod.h>
+
#include <assert.h>
static constexpr Domain mikmod_domain("mikmod");
@@ -146,11 +147,11 @@ mikmod_decoder_finish(void)
}
static void
-mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs)
+mikmod_decoder_file_decode(Decoder &decoder, Path path_fs)
{
/* deconstify the path because libmikmod wants a non-const
string pointer */
- char *const path2 = const_cast<char *>(path_fs);
+ char *const path2 = const_cast<char *>(path_fs.c_str());
MODULE *handle;
int ret;
@@ -160,7 +161,7 @@ mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs)
if (handle == nullptr) {
FormatError(mikmod_domain,
- "failed to open mod: %s", path_fs);
+ "failed to open mod: %s", path_fs.c_str());
return;
}
@@ -169,7 +170,8 @@ mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs)
const AudioFormat audio_format(mikmod_sample_rate, SampleFormat::S16, 2);
assert(audio_format.IsValid());
- decoder_initialized(decoder, audio_format, false, 0);
+ decoder_initialized(decoder, audio_format, false,
+ SignedSongTime::Negative());
Player_Start(handle);
@@ -184,18 +186,18 @@ mikmod_decoder_file_decode(Decoder &decoder, const char *path_fs)
}
static bool
-mikmod_decoder_scan_file(const char *path_fs,
+mikmod_decoder_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
/* deconstify the path because libmikmod wants a non-const
string pointer */
- char *const path2 = const_cast<char *>(path_fs);
+ char *const path2 = const_cast<char *>(path_fs.c_str());
MODULE *handle = Player_Load(path2, 128, 0);
if (handle == nullptr) {
FormatDebug(mikmod_domain,
- "Failed to open file: %s", path_fs);
+ "Failed to open file: %s", path_fs.c_str());
return false;
}
diff --git a/src/decoder/MikmodDecoderPlugin.hxx b/src/decoder/plugins/MikmodDecoderPlugin.hxx
index d25c5f6e7..27ba2a823 100644
--- a/src/decoder/MikmodDecoderPlugin.hxx
+++ b/src/decoder/plugins/MikmodDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/plugins/ModplugDecoderPlugin.cxx
index e75f5479c..3e0a41550 100644
--- a/src/decoder/ModplugDecoderPlugin.cxx
+++ b/src/decoder/plugins/ModplugDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,8 +19,8 @@
#include "config.h"
#include "ModplugDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "tag/TagHandler.hxx"
#include "system/FatalError.hxx"
#include "util/WritableBuffer.hxx"
@@ -36,7 +36,7 @@ static constexpr Domain modplug_domain("modplug");
static constexpr size_t MODPLUG_FRAME_SIZE = 4096;
static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024;
-static constexpr InputStream::offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024;
+static constexpr offset_type MODPLUG_FILE_LIMIT = 100 * 1024 * 1024;
static int modplug_loop_count;
@@ -54,24 +54,29 @@ modplug_decoder_init(const config_param &param)
static WritableBuffer<uint8_t>
mod_loadfile(Decoder *decoder, InputStream &is)
{
- const InputStream::offset_type size = is.GetSize();
+ //known/unknown size, preallocate array, lets read in chunks
- if (size == 0) {
- LogWarning(modplug_domain, "file is empty");
- return { nullptr, 0 };
- }
+ const bool is_stream = !is.KnownSize();
- if (size > MODPLUG_FILE_LIMIT) {
- LogWarning(modplug_domain, "file too large");
- return { nullptr, 0 };
- }
+ WritableBuffer<uint8_t> buffer;
+ if (is_stream)
+ buffer.size = MODPLUG_PREALLOC_BLOCK;
+ else {
+ const auto size = is.GetSize();
+
+ if (size == 0) {
+ LogWarning(modplug_domain, "file is empty");
+ return { nullptr, 0 };
+ }
- //known/unknown size, preallocate array, lets read in chunks
+ if (size > MODPLUG_FILE_LIMIT) {
+ LogWarning(modplug_domain, "file too large");
+ return { nullptr, 0 };
+ }
- const bool is_stream = size < 0;
+ buffer.size = size;
+ }
- WritableBuffer<uint8_t> buffer;
- buffer.size = is_stream ? MODPLUG_PREALLOC_BLOCK : size;
buffer.data = new uint8_t[buffer.size];
uint8_t *const end = buffer.end();
@@ -148,7 +153,7 @@ mod_decode(Decoder &decoder, InputStream &is)
decoder_initialized(decoder, audio_format,
is.IsSeekable(),
- ModPlug_GetLength(f) / 1000.0);
+ SongTime::FromMS(ModPlug_GetLength(f)));
DecoderCommand cmd;
do {
@@ -161,10 +166,7 @@ mod_decode(Decoder &decoder, InputStream &is)
0);
if (cmd == DecoderCommand::SEEK) {
- float where = decoder_seek_where(decoder);
-
- ModPlug_Seek(f, (int)(where * 1000.0));
-
+ ModPlug_Seek(f, decoder_seek_time(decoder).ToMS());
decoder_command_finished(decoder);
}
@@ -182,7 +184,7 @@ modplug_scan_stream(InputStream &is,
return false;
tag_handler_invoke_duration(handler, handler_ctx,
- ModPlug_GetLength(f) / 1000);
+ SongTime::FromMS(ModPlug_GetLength(f)));
const char *title = ModPlug_GetName(f);
if (title != nullptr)
diff --git a/src/decoder/ModplugDecoderPlugin.hxx b/src/decoder/plugins/ModplugDecoderPlugin.hxx
index 4cd9f5b25..08f2ecb12 100644
--- a/src/decoder/ModplugDecoderPlugin.hxx
+++ b/src/decoder/plugins/ModplugDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/plugins/MpcdecDecoderPlugin.cxx
index dc258623c..befed0f3b 100644
--- a/src/decoder/MpcdecDecoderPlugin.cxx
+++ b/src/decoder/plugins/MpcdecDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,8 +19,8 @@
#include "config.h"
#include "MpcdecDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "util/Error.hxx"
@@ -30,8 +30,6 @@
#include <mpc/mpcdec.h>
-#include <assert.h>
-#include <unistd.h>
#include <math.h>
struct mpc_decoder_data {
@@ -59,7 +57,7 @@ mpc_seek_cb(mpc_reader *reader, mpc_int32_t offset)
struct mpc_decoder_data *data =
(struct mpc_decoder_data *)reader->data;
- return data->is.LockSeek(offset, SEEK_SET, IgnoreError());
+ return data->is.LockSeek(offset, IgnoreError());
}
static mpc_int32_t
@@ -86,6 +84,9 @@ mpc_getsize_cb(mpc_reader *reader)
struct mpc_decoder_data *data =
(struct mpc_decoder_data *)reader->data;
+ if (!data->is.KnownSize())
+ return -1;
+
return data->is.GetSize();
}
@@ -179,13 +180,13 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
decoder_initialized(mpd_decoder, audio_format,
is.IsSeekable(),
- mpc_streaminfo_get_length(&info));
+ SongTime::FromS(mpc_streaminfo_get_length(&info)));
DecoderCommand cmd = DecoderCommand::NONE;
do {
if (cmd == DecoderCommand::SEEK) {
- mpc_int64_t where = decoder_seek_where(mpd_decoder) *
- audio_format.sample_rate;
+ mpc_int64_t where =
+ decoder_seek_where_frame(mpd_decoder);
bool success;
success = mpc_demux_seek_sample(demux, where)
@@ -227,7 +228,7 @@ mpcdec_decode(Decoder &mpd_decoder, InputStream &is)
mpc_demux_exit(demux);
}
-static float
+static SignedSongTime
mpcdec_get_file_duration(InputStream &is)
{
mpc_decoder_data data(is, nullptr);
@@ -242,25 +243,24 @@ mpcdec_get_file_duration(InputStream &is)
mpc_demux *demux = mpc_demux_init(&reader);
if (demux == nullptr)
- return -1;
+ return SignedSongTime::Negative();
mpc_streaminfo info;
mpc_demux_get_info(demux, &info);
mpc_demux_exit(demux);
- return mpc_streaminfo_get_length(&info);
+ return SongTime::FromS(mpc_streaminfo_get_length(&info));
}
static bool
mpcdec_scan_stream(InputStream &is,
const struct tag_handler *handler, void *handler_ctx)
{
- float total_time = mpcdec_get_file_duration(is);
-
- if (total_time < 0)
+ const auto duration = mpcdec_get_file_duration(is);
+ if (duration.IsNegative())
return false;
- tag_handler_invoke_duration(handler, handler_ctx, total_time);
+ tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration));
return true;
}
diff --git a/src/decoder/MpcdecDecoderPlugin.hxx b/src/decoder/plugins/MpcdecDecoderPlugin.hxx
index 23ecc801e..7f71311fa 100644
--- a/src/decoder/MpcdecDecoderPlugin.hxx
+++ b/src/decoder/plugins/MpcdecDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/plugins/Mpg123DecoderPlugin.cxx
index df23f7847..166529a4d 100644
--- a/src/decoder/Mpg123DecoderPlugin.cxx
+++ b/src/decoder/plugins/Mpg123DecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,16 +19,19 @@
#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 "tag/TagBuilder.hxx"
+#include "tag/ReplayGain.hxx"
+#include "tag/MixRamp.hxx"
+#include "fs/Path.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <mpg123.h>
+
#include <stdio.h>
static constexpr Domain mpg123_domain("mpg123");
@@ -60,14 +63,10 @@ static bool
mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
AudioFormat &audio_format)
{
- int error;
- int channels, encoding;
- long rate;
-
/* mpg123_open() wants a writable string :-( */
char *const path2 = const_cast<char *>(path_fs);
- error = mpg123_open(handle, path2);
+ int error = mpg123_open(handle, path2);
if (error != MPG123_OK) {
FormatWarning(mpg123_domain,
"libmpg123 failed to open %s: %s",
@@ -77,6 +76,8 @@ mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
/* obtain the audio format */
+ long rate;
+ int channels, encoding;
error = mpg123_getformat(handle, &rate, &channels, &encoding);
if (error != MPG123_OK) {
FormatWarning(mpg123_domain,
@@ -104,16 +105,96 @@ mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
}
static void
-mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs)
+AddTagItem(TagBuilder &tag, TagType type, const mpg123_string &s)
{
- mpg123_handle *handle;
- int error;
- off_t num_samples;
- struct mpg123_frameinfo info;
+ assert(s.p != nullptr);
+ assert(s.size >= s.fill);
+ assert(s.fill > 0);
+
+ tag.AddItem(type, s.p, s.fill - 1);
+}
+
+static void
+AddTagItem(TagBuilder &tag, TagType type, const mpg123_string *s)
+{
+ if (s != nullptr)
+ AddTagItem(tag, type, *s);
+}
+
+static void
+mpd_mpg123_id3v2_tag(Decoder &decoder, const mpg123_id3v2 &id3v2)
+{
+ TagBuilder tag;
+
+ AddTagItem(tag, TAG_TITLE, id3v2.title);
+ AddTagItem(tag, TAG_ARTIST, id3v2.artist);
+ AddTagItem(tag, TAG_ALBUM, id3v2.album);
+ AddTagItem(tag, TAG_DATE, id3v2.year);
+ AddTagItem(tag, TAG_GENRE, id3v2.genre);
+
+ for (size_t i = 0, n = id3v2.comments; i < n; ++i)
+ AddTagItem(tag, TAG_COMMENT, id3v2.comment_list[i].text);
+
+ decoder_tag(decoder, nullptr, tag.Commit());
+}
+
+static void
+mpd_mpg123_id3v2_extras(Decoder &decoder, const mpg123_id3v2 &id3v2)
+{
+ ReplayGainInfo replay_gain;
+ replay_gain.Clear();
+
+ MixRampInfo mix_ramp;
+
+ bool found_replay_gain = false, found_mixramp = false;
+
+ for (size_t i = 0, n = id3v2.extras; i < n; ++i) {
+ if (ParseReplayGainTag(replay_gain,
+ id3v2.extra[i].description.p,
+ id3v2.extra[i].text.p))
+ found_replay_gain = true;
+ else if (ParseMixRampTag(mix_ramp,
+ id3v2.extra[i].description.p,
+ id3v2.extra[i].text.p))
+ found_mixramp = true;
+ }
+ if (found_replay_gain)
+ decoder_replay_gain(decoder, &replay_gain);
+
+ if (found_mixramp)
+ decoder_mixramp(decoder, std::move(mix_ramp));
+}
+
+static void
+mpd_mpg123_id3v2(Decoder &decoder, const mpg123_id3v2 &id3v2)
+{
+ mpd_mpg123_id3v2_tag(decoder, id3v2);
+ mpd_mpg123_id3v2_extras(decoder, id3v2);
+}
+
+static void
+mpd_mpg123_meta(Decoder &decoder, mpg123_handle *const handle)
+{
+ if ((mpg123_meta_check(handle) & MPG123_NEW_ID3) == 0)
+ return;
+
+ mpg123_id3v1 *v1;
+ mpg123_id3v2 *v2;
+ if (mpg123_id3(handle, &v1, &v2) != MPG123_OK)
+ return;
+
+ if (v2 != nullptr)
+ mpd_mpg123_id3v2(decoder, *v2);
+}
+
+static void
+mpd_mpg123_file_decode(Decoder &decoder, Path path_fs)
+{
/* open the file */
- handle = mpg123_new(nullptr, &error);
+ int error;
+ mpg123_handle *const handle = mpg123_new(nullptr, &error);
if (handle == nullptr) {
FormatError(mpg123_domain,
"mpg123_new() failed: %s",
@@ -122,19 +203,22 @@ mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs)
}
AudioFormat audio_format;
- if (!mpd_mpg123_open(handle, path_fs, audio_format)) {
+ if (!mpd_mpg123_open(handle, path_fs.c_str(), audio_format)) {
mpg123_delete(handle);
return;
}
- num_samples = mpg123_length(handle);
+ const off_t num_samples = mpg123_length(handle);
/* tell MPD core we're ready */
- decoder_initialized(decoder, audio_format, true,
- (float)num_samples /
- (float)audio_format.sample_rate);
+ const auto duration =
+ SongTime::FromScale<uint64_t>(num_samples,
+ audio_format.sample_rate);
+ decoder_initialized(decoder, audio_format, true, duration);
+
+ struct mpg123_frameinfo info;
if (mpg123_info(handle, &info) != MPG123_OK) {
info.vbr = MPG123_CBR;
info.bitrate = 0;
@@ -151,14 +235,15 @@ mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs)
}
/* the decoder main loop */
-
DecoderCommand cmd;
do {
- unsigned char buffer[8192];
- size_t nbytes;
+ /* read metadata */
+ mpd_mpg123_meta(decoder, handle);
/* decode */
+ unsigned char buffer[8192];
+ size_t nbytes;
error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes);
if (error != MPG123_OK) {
if (error != MPG123_DONE)
@@ -181,7 +266,7 @@ mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs)
cmd = decoder_data(decoder, nullptr, buffer, nbytes, info.bitrate);
if (cmd == DecoderCommand::SEEK) {
- off_t c = decoder_seek_where(decoder)*audio_format.sample_rate;
+ off_t c = decoder_seek_where_frame(decoder);
c = mpg123_seek(handle, c, SEEK_SET);
if (c < 0)
decoder_seek_error(decoder);
@@ -200,14 +285,11 @@ mpd_mpg123_file_decode(Decoder &decoder, const char *path_fs)
}
static bool
-mpd_mpg123_scan_file(const char *path_fs,
+mpd_mpg123_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
- mpg123_handle *handle;
int error;
- off_t num_samples;
-
- handle = mpg123_new(nullptr, &error);
+ mpg123_handle *const handle = mpg123_new(nullptr, &error);
if (handle == nullptr) {
FormatError(mpg123_domain,
"mpg123_new() failed: %s",
@@ -216,12 +298,12 @@ mpd_mpg123_scan_file(const char *path_fs,
}
AudioFormat audio_format;
- if (!mpd_mpg123_open(handle, path_fs, audio_format)) {
+ if (!mpd_mpg123_open(handle, path_fs.c_str(), audio_format)) {
mpg123_delete(handle);
return false;
}
- num_samples = mpg123_length(handle);
+ const off_t num_samples = mpg123_length(handle);
if (num_samples <= 0) {
mpg123_delete(handle);
return false;
@@ -231,8 +313,11 @@ mpd_mpg123_scan_file(const char *path_fs,
mpg123_delete(handle);
- tag_handler_invoke_duration(handler, handler_ctx,
- num_samples / audio_format.sample_rate);
+ const auto duration =
+ SongTime::FromScale<uint64_t>(num_samples,
+ audio_format.sample_rate);
+
+ tag_handler_invoke_duration(handler, handler_ctx, duration);
return true;
}
diff --git a/src/decoder/Mpg123DecoderPlugin.hxx b/src/decoder/plugins/Mpg123DecoderPlugin.hxx
index 10f7c37f5..fd089c6a4 100644
--- a/src/decoder/Mpg123DecoderPlugin.hxx
+++ b/src/decoder/plugins/Mpg123DecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/OggCodec.cxx b/src/decoder/plugins/OggCodec.cxx
index 565dbafcf..c7f39586e 100644
--- a/src/decoder/OggCodec.cxx
+++ b/src/decoder/plugins/OggCodec.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -23,6 +23,7 @@
#include "config.h"
#include "OggCodec.hxx"
+#include "../DecoderAPI.hxx"
#include <string.h>
diff --git a/src/decoder/OggCodec.hxx b/src/decoder/plugins/OggCodec.hxx
index 857871607..3b096561c 100644
--- a/src/decoder/OggCodec.hxx
+++ b/src/decoder/plugins/OggCodec.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -24,7 +24,8 @@
#ifndef MPD_OGG_CODEC_HXX
#define MPD_OGG_CODEC_HXX
-#include "DecoderAPI.hxx"
+struct Decoder;
+class InputStream;
enum ogg_codec {
OGG_CODEC_UNKNOWN,
diff --git a/src/decoder/OggFind.cxx b/src/decoder/plugins/OggFind.cxx
index 65c7fa3ce..978e1d7cf 100644
--- a/src/decoder/OggFind.cxx
+++ b/src/decoder/plugins/OggFind.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,10 +20,9 @@
#include "config.h"
#include "OggFind.hxx"
#include "OggSyncState.hxx"
+#include "input/InputStream.hxx"
#include "util/Error.hxx"
-#include <stdio.h>
-
bool
OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet)
{
@@ -41,7 +40,7 @@ OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet)
bool
OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
- InputStream::offset_type offset, int whence)
+ offset_type offset)
{
oy.Reset();
@@ -49,7 +48,7 @@ OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
data */
ogg_stream_reset(&os);
- return is.LockSeek(offset, whence, IgnoreError()) &&
+ return is.LockSeek(offset, IgnoreError()) &&
oy.ExpectPageSeekIn(os);
}
@@ -57,12 +56,15 @@ bool
OggSeekFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet,
InputStream &is)
{
- if (is.size > 0 && is.size - is.offset < 65536)
+ if (!is.KnownSize())
+ return false;
+
+ if (is.GetRest() < 65536)
return OggFindEOS(oy, os, packet);
if (!is.CheapSeeking())
return false;
- return OggSeekPageAtOffset(oy, os, is, -65536, SEEK_END) &&
+ return OggSeekPageAtOffset(oy, os, is, is.GetSize() - 65536) &&
OggFindEOS(oy, os, packet);
}
diff --git a/src/decoder/OggFind.hxx b/src/decoder/plugins/OggFind.hxx
index ad51ccdf3..2aa4f6c06 100644
--- a/src/decoder/OggFind.hxx
+++ b/src/decoder/plugins/OggFind.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -21,12 +21,12 @@
#define MPD_OGG_FIND_HXX
#include "check.h"
-#include "InputStream.hxx"
+#include "input/Offset.hxx"
#include <ogg/ogg.h>
-struct InputStream;
class OggSyncState;
+class InputStream;
/**
* Skip all pages/packets until an end-of-stream (EOS) packet for the
@@ -42,7 +42,7 @@ OggFindEOS(OggSyncState &oy, ogg_stream_state &os, ogg_packet &packet);
*/
bool
OggSeekPageAtOffset(OggSyncState &oy, ogg_stream_state &os, InputStream &is,
- InputStream::offset_type offset, int whence);
+ offset_type offset);
/**
* Try to find the end-of-stream (EOS) packet. Seek to the end of the
diff --git a/src/decoder/OggSyncState.hxx b/src/decoder/plugins/OggSyncState.hxx
index 5235c1bd8..024902fff 100644
--- a/src/decoder/OggSyncState.hxx
+++ b/src/decoder/plugins/OggSyncState.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/OggUtil.cxx b/src/decoder/plugins/OggUtil.cxx
index 8f181ce57..6341b0008 100644
--- a/src/decoder/OggUtil.cxx
+++ b/src/decoder/plugins/OggUtil.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -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 41fc755ba..94c380ef4 100644
--- a/src/decoder/OggUtil.hxx
+++ b/src/decoder/plugins/OggUtil.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -26,7 +26,7 @@
#include <stddef.h>
-struct InputStream;
+class InputStream;
struct Decoder;
/**
diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/plugins/OpusDecoderPlugin.cxx
index 01ea3687a..e14827e38 100644
--- a/src/decoder/OpusDecoderPlugin.cxx
+++ b/src/decoder/plugins/OpusDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -22,28 +22,30 @@
#include "OpusDomain.hxx"
#include "OpusHead.hxx"
#include "OpusTags.hxx"
-#include "OggUtil.hxx"
#include "OggFind.hxx"
#include "OggSyncState.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "OggCodec.hxx"
-#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagBuilder.hxx"
-#include "InputStream.hxx"
+#include "input/InputStream.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
#include <opus.h>
#include <ogg/ogg.h>
-#include <glib.h>
-
#include <string.h>
#include <stdio.h>
static constexpr opus_int32 opus_sample_rate = 48000;
+/**
+ * Allocate an output buffer for 16 bit PCM samples big enough to hold
+ * a quarter second, larger than 120ms required by libopus.
+ */
+static constexpr unsigned opus_output_buffer_frames = opus_sample_rate / 4;
+
gcc_pure
static bool
IsOpusHead(const ogg_packet &packet)
@@ -74,10 +76,16 @@ class MPDOpusDecoder {
OpusDecoder *opus_decoder;
opus_int16 *output_buffer;
- unsigned output_size;
+
+ /**
+ * If non-zero, then a previous Opus stream has been found
+ * already with this number of channels. If opus_decoder is
+ * nullptr, then its end-of-stream packet has been found
+ * already.
+ */
+ unsigned previous_channels;
bool os_initialized;
- bool found_opus;
int opus_serialno;
@@ -90,8 +98,9 @@ public:
InputStream &_input_stream)
:decoder(_decoder), input_stream(_input_stream),
opus_decoder(nullptr),
- output_buffer(nullptr), output_size(0),
- os_initialized(false), found_opus(false) {}
+ output_buffer(nullptr),
+ previous_channels(0),
+ os_initialized(false) {}
~MPDOpusDecoder();
bool ReadFirstPage(OggSyncState &oy);
@@ -100,15 +109,16 @@ public:
DecoderCommand HandlePackets();
DecoderCommand HandlePacket(const ogg_packet &packet);
DecoderCommand HandleBOS(const ogg_packet &packet);
+ DecoderCommand HandleEOS();
DecoderCommand HandleTags(const ogg_packet &packet);
DecoderCommand HandleAudio(const ogg_packet &packet);
- bool Seek(OggSyncState &oy, double where);
+ bool Seek(OggSyncState &oy, uint64_t where_frame);
};
MPDOpusDecoder::~MPDOpusDecoder()
{
- g_free(output_buffer);
+ delete[] output_buffer;
if (opus_decoder != nullptr)
opus_decoder_destroy(opus_decoder);
@@ -163,12 +173,14 @@ inline DecoderCommand
MPDOpusDecoder::HandlePacket(const ogg_packet &packet)
{
if (packet.e_o_s)
- return DecoderCommand::STOP;
+ return HandleEOS();
if (packet.b_o_s)
return HandleBOS(packet);
- else if (!found_opus)
+ else if (opus_decoder == nullptr) {
+ LogDebug(opus_domain, "BOS packet expected");
return DecoderCommand::STOP;
+ }
if (IsOpusTags(packet))
return HandleTags(packet);
@@ -188,11 +200,9 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
/* we do this for local files only, because seeking
around remote files is expensive and not worth the
troubl */
- return -1;
+ return false;
- const auto old_offset = is.offset;
- if (old_offset < 0)
- return -1;
+ const auto old_offset = is.GetOffset();
/* create temporary Ogg objects for seeking and parsing the
EOS packet */
@@ -204,7 +214,7 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
ogg_stream_clear(&os);
/* restore the previous file position */
- is.Seek(old_offset, SEEK_SET, IgnoreError());
+ is.LockSeek(old_offset, IgnoreError());
return result;
}
@@ -231,19 +241,29 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
{
assert(packet.b_o_s);
- if (found_opus || !IsOpusHead(packet))
+ if (opus_decoder != nullptr || !IsOpusHead(packet)) {
+ LogDebug(opus_domain, "BOS packet must be OpusHead");
return DecoderCommand::STOP;
+ }
unsigned channels;
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
- !audio_valid_channel_count(channels))
+ !audio_valid_channel_count(channels)) {
+ LogDebug(opus_domain, "Malformed BOS packet");
return DecoderCommand::STOP;
+ }
assert(opus_decoder == nullptr);
- assert(output_buffer == nullptr);
+ assert((previous_channels == 0) == (output_buffer == nullptr));
+
+ if (previous_channels != 0 && channels != previous_channels) {
+ FormatWarning(opus_domain,
+ "Next stream has different channels (%u -> %u)",
+ previous_channels, channels);
+ return DecoderCommand::STOP;
+ }
opus_serialno = os.serialno;
- found_opus = true;
/* TODO: parse attributes from the OpusHead (sample rate,
channels, ...) */
@@ -257,30 +277,51 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
return DecoderCommand::STOP;
}
+ if (previous_channels != 0) {
+ /* decoder was already initialized by the previous
+ stream; skip the rest of this method */
+ LogDebug(opus_domain, "Found another stream");
+ return decoder_get_command(decoder);
+ }
+
eos_granulepos = LoadEOSGranulePos(input_stream, &decoder,
opus_serialno);
- const double duration = eos_granulepos >= 0
- ? double(eos_granulepos) / opus_sample_rate
- : -1.0;
+ const auto duration = eos_granulepos >= 0
+ ? SignedSongTime::FromScale<uint64_t>(eos_granulepos,
+ opus_sample_rate)
+ : SignedSongTime::Negative();
+ previous_channels = channels;
const AudioFormat audio_format(opus_sample_rate,
SampleFormat::S16, channels);
decoder_initialized(decoder, audio_format,
eos_granulepos > 0, duration);
frame_size = audio_format.GetFrameSize();
- /* allocate an output buffer for 16 bit PCM samples big enough
- to hold a quarter second, larger than 120ms required by
- libopus */
- output_size = audio_format.sample_rate / 4;
- output_buffer = (opus_int16 *)
- g_malloc(sizeof(*output_buffer) * output_size *
- audio_format.channels);
+ output_buffer = new opus_int16[opus_output_buffer_frames
+ * audio_format.channels];
return decoder_get_command(decoder);
}
inline DecoderCommand
+MPDOpusDecoder::HandleEOS()
+{
+ if (eos_granulepos < 0 && previous_channels != 0) {
+ /* allow chaining of (unseekable) streams */
+ assert(opus_decoder != nullptr);
+ assert(output_buffer != nullptr);
+
+ opus_decoder_destroy(opus_decoder);
+ opus_decoder = nullptr;
+
+ return decoder_get_command(decoder);
+ }
+
+ return DecoderCommand::STOP;
+}
+
+inline DecoderCommand
MPDOpusDecoder::HandleTags(const ogg_packet &packet)
{
ReplayGainInfo rgi;
@@ -295,8 +336,7 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet)
!tag_builder.IsEmpty()) {
decoder_replay_gain(decoder, &rgi);
- Tag tag;
- tag_builder.Commit(tag);
+ Tag tag = tag_builder.Commit();
cmd = decoder_tag(decoder, input_stream, std::move(tag));
} else
cmd = decoder_get_command(decoder);
@@ -312,10 +352,11 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
int nframes = opus_decode(opus_decoder,
(const unsigned char*)packet.packet,
packet.bytes,
- output_buffer, output_size,
+ output_buffer, opus_output_buffer_frames,
0);
if (nframes < 0) {
- LogError(opus_domain, opus_strerror(nframes));
+ FormatError(opus_domain, "libopus error: %s",
+ opus_strerror(nframes));
return DecoderCommand::STOP;
}
@@ -337,26 +378,21 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
}
bool
-MPDOpusDecoder::Seek(OggSyncState &oy, double where_s)
+MPDOpusDecoder::Seek(OggSyncState &oy, uint64_t where_frame)
{
assert(eos_granulepos > 0);
- assert(input_stream.seekable);
- assert(input_stream.size > 0);
- assert(input_stream.offset >= 0);
+ assert(input_stream.IsSeekable());
+ assert(input_stream.KnownSize());
- const ogg_int64_t where_granulepos(where_s * opus_sample_rate);
+ const ogg_int64_t where_granulepos(where_frame);
/* interpolate the file offset where we expect to find the
given granule position */
/* TODO: implement binary search */
- InputStream::offset_type offset(where_granulepos * input_stream.size
- / eos_granulepos);
-
- if (!OggSeekPageAtOffset(oy, os, input_stream, offset, SEEK_SET))
- return false;
+ offset_type offset(where_granulepos * input_stream.GetSize()
+ / eos_granulepos);
- decoder_timestamp(decoder, where_s);
- return true;
+ return OggSeekPageAtOffset(oy, os, input_stream, offset);
}
static void
@@ -379,7 +415,7 @@ mpd_opus_stream_decode(Decoder &decoder,
while (true) {
auto cmd = d.HandlePackets();
if (cmd == DecoderCommand::SEEK) {
- if (d.Seek(oy, decoder_seek_where(decoder)))
+ if (d.Seek(oy, decoder_seek_where_frame(decoder)))
decoder_command_finished(decoder);
else
decoder_seek_error(decoder);
@@ -454,9 +490,12 @@ mpd_opus_scan_stream(InputStream &is,
}
}
- if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is))
- tag_handler_invoke_duration(handler, handler_ctx,
- packet.granulepos / opus_sample_rate);
+ if (packet.e_o_s || OggSeekFindEOS(oy, os, packet, is)) {
+ const auto duration =
+ SongTime::FromScale<uint64_t>(packet.granulepos,
+ opus_sample_rate);
+ tag_handler_invoke_duration(handler, handler_ctx, duration);
+ }
ogg_stream_clear(&os);
@@ -471,6 +510,13 @@ static const char *const opus_suffixes[] = {
};
static const char *const opus_mime_types[] = {
+ /* the official MIME type (RFC 5334) */
+ "audio/ogg",
+
+ /* deprecated (RFC 5334) */
+ "application/ogg",
+
+ /* deprecated; from an early draft */
"audio/opus",
nullptr
};
diff --git a/src/decoder/OpusDecoderPlugin.h b/src/decoder/plugins/OpusDecoderPlugin.h
index 263ac6e2d..260dab99a 100644
--- a/src/decoder/OpusDecoderPlugin.h
+++ b/src/decoder/plugins/OpusDecoderPlugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/OpusDomain.cxx b/src/decoder/plugins/OpusDomain.cxx
index b00e2a553..1efd64a48 100644
--- a/src/decoder/OpusDomain.cxx
+++ b/src/decoder/plugins/OpusDomain.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/OpusDomain.hxx b/src/decoder/plugins/OpusDomain.hxx
index 2b56c427c..fb19e0301 100644
--- a/src/decoder/OpusDomain.hxx
+++ b/src/decoder/plugins/OpusDomain.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/OpusHead.cxx b/src/decoder/plugins/OpusHead.cxx
index 0417d3905..bfa41d618 100644
--- a/src/decoder/OpusHead.cxx
+++ b/src/decoder/plugins/OpusHead.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -21,7 +21,6 @@
#include "OpusHead.hxx"
#include <stdint.h>
-#include <string.h>
struct OpusHead {
char signature[8];
diff --git a/src/decoder/OpusHead.hxx b/src/decoder/plugins/OpusHead.hxx
index fa6a2b666..c478d8d90 100644
--- a/src/decoder/OpusHead.hxx
+++ b/src/decoder/plugins/OpusHead.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/OpusReader.hxx b/src/decoder/plugins/OpusReader.hxx
index 2bb39b748..c5b8e9107 100644
--- a/src/decoder/OpusReader.hxx
+++ b/src/decoder/plugins/OpusReader.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/OpusTags.cxx b/src/decoder/plugins/OpusTags.cxx
index f7729e5ad..aff5479c0 100644
--- a/src/decoder/OpusTags.cxx
+++ b/src/decoder/plugins/OpusTags.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/OpusTags.hxx b/src/decoder/plugins/OpusTags.hxx
index e1f1a1ff1..be3ac3a8d 100644
--- a/src/decoder/OpusTags.hxx
+++ b/src/decoder/plugins/OpusTags.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/plugins/PcmDecoderPlugin.cxx
index dbc38fb76..c07a7b9b1 100644
--- a/src/decoder/PcmDecoderPlugin.cxx
+++ b/src/decoder/plugins/PcmDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -18,17 +18,14 @@
*/
#include "config.h"
-#include "decoder/PcmDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "PcmDecoderPlugin.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "util/Error.hxx"
#include "util/ByteReverse.hxx"
#include "Log.hxx"
-#include <glib.h>
-#include <unistd.h>
#include <string.h>
-#include <stdio.h> /* for SEEK_SET */
static void
pcm_stream_decode(Decoder &decoder, InputStream &is)
@@ -43,12 +40,12 @@ pcm_stream_decode(Decoder &decoder, InputStream &is)
const bool reverse_endian = mime != nullptr &&
strcmp(mime, "audio/x-mpd-cdda-pcm-reverse") == 0;
- const double time_to_size = audio_format.GetTimeToSize();
+ const auto frame_size = audio_format.GetFrameSize();
- float total_time = -1;
- const auto size = is.GetSize();
- if (size >= 0)
- total_time = size / time_to_size;
+ const auto total_time = is.KnownSize()
+ ? SignedSongTime::FromScale<uint64_t>(is.GetSize() / frame_size,
+ audio_format.sample_rate)
+ : SignedSongTime::Negative();
decoder_initialized(decoder, audio_format,
is.IsSeekable(), total_time);
@@ -74,11 +71,11 @@ pcm_stream_decode(Decoder &decoder, InputStream &is)
buffer, nbytes, 0)
: decoder_get_command(decoder);
if (cmd == DecoderCommand::SEEK) {
- InputStream::offset_type offset(time_to_size *
- decoder_seek_where(decoder));
+ uint64_t frame = decoder_seek_where_frame(decoder);
+ offset_type offset = frame * frame_size;
Error error;
- if (is.LockSeek(offset, SEEK_SET, error)) {
+ if (is.LockSeek(offset, error)) {
decoder_command_finished(decoder);
} else {
LogError(error);
diff --git a/src/decoder/PcmDecoderPlugin.hxx b/src/decoder/plugins/PcmDecoderPlugin.hxx
index 38e4a5020..3582e5856 100644
--- a/src/decoder/PcmDecoderPlugin.hxx
+++ b/src/decoder/plugins/PcmDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx
index 160337594..8435f095f 100644
--- a/src/decoder/SidplayDecoderPlugin.cxx
+++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -21,6 +21,8 @@
#include "SidplayDecoderPlugin.hxx"
#include "../DecoderAPI.hxx"
#include "tag/TagHandler.hxx"
+#include "fs/Path.hxx"
+#include "util/FormatString.hxx"
#include "util/Domain.hxx"
#include "system/ByteOrder.hxx"
#include "Log.hxx"
@@ -119,9 +121,9 @@ sidplay_finish()
* suffix
*/
static char *
-get_container_name(const char *path_fs)
+get_container_name(Path path_fs)
{
- char *path_container=g_strdup(path_fs);
+ char *path_container = strdup(path_fs.c_str());
if(!g_pattern_match(path_with_subtune,
strlen(path_container), path_container, nullptr))
@@ -157,31 +159,31 @@ get_song_num(const char *path_fs)
}
/* get the song length in seconds */
-static int
-get_song_length(const char *path_fs)
+static SignedSongTime
+get_song_length(Path path_fs)
{
if (songlength_database == nullptr)
- return -1;
+ return SignedSongTime::Negative();
- gchar *sid_file=get_container_name(path_fs);
+ char *sid_file = get_container_name(path_fs);
SidTuneMod tune(sid_file);
- g_free(sid_file);
+ free(sid_file);
if(!tune) {
LogWarning(sidplay_domain,
"failed to load file for calculating md5 sum");
- return -1;
+ return SignedSongTime::Negative();
}
char md5sum[SIDTUNE_MD5_LENGTH+1];
tune.createMD5(md5sum);
- const unsigned song_num = get_song_num(path_fs);
+ const unsigned song_num = get_song_num(path_fs.c_str());
gsize num_items;
gchar **values=g_key_file_get_string_list(songlength_database,
"Database", md5sum, &num_items, nullptr);
if(!values || song_num>num_items) {
g_strfreev(values);
- return -1;
+ return SignedSongTime::Negative();
}
int minutes=strtol(values[song_num-1], nullptr, 10);
@@ -197,11 +199,11 @@ get_song_length(const char *path_fs)
g_strfreev(values);
- return (minutes*60)+seconds;
+ return SignedSongTime::FromS((minutes * 60) + seconds);
}
static void
-sidplay_file_decode(Decoder &decoder, const char *path_fs)
+sidplay_file_decode(Decoder &decoder, Path path_fs)
{
int channels;
@@ -209,17 +211,18 @@ sidplay_file_decode(Decoder &decoder, const char *path_fs)
char *path_container=get_container_name(path_fs);
SidTune tune(path_container, nullptr, true);
- g_free(path_container);
+ free(path_container);
if (!tune) {
LogWarning(sidplay_domain, "failed to load file");
return;
}
- int song_num=get_song_num(path_fs);
+ const int song_num = get_song_num(path_fs.c_str());
tune.selectSong(song_num);
- int song_len=get_song_length(path_fs);
- if(song_len==-1) song_len=default_songlength;
+ auto duration = get_song_length(path_fs);
+ if (duration.IsNegative() && default_songlength > 0)
+ duration = SongTime::FromS(default_songlength);
/* initialize the player */
@@ -290,12 +293,14 @@ sidplay_file_decode(Decoder &decoder, const char *path_fs)
const AudioFormat audio_format(48000, SampleFormat::S16, channels);
assert(audio_format.IsValid());
- decoder_initialized(decoder, audio_format, true, (float)song_len);
+ decoder_initialized(decoder, audio_format, true, duration);
/* .. and play */
const unsigned timebase = player.timebase();
- song_len *= timebase;
+ const unsigned end = duration.IsNegative()
+ ? 0u
+ : duration.ToScale<uint64_t>(timebase);
DecoderCommand cmd;
do {
@@ -312,8 +317,8 @@ sidplay_file_decode(Decoder &decoder, const char *path_fs)
if (cmd == DecoderCommand::SEEK) {
unsigned data_time = player.time();
- unsigned target_time = (unsigned)
- (decoder_seek_where(decoder) * timebase);
+ unsigned target_time =
+ decoder_seek_time(decoder).ToScale(timebase);
/* can't rewind so return to zero and seek forward */
if(target_time<data_time) {
@@ -332,21 +337,21 @@ sidplay_file_decode(Decoder &decoder, const char *path_fs)
decoder_command_finished(decoder);
}
- if (song_len > 0 && player.time() >= (unsigned)song_len)
+ if (end > 0 && player.time() >= end)
break;
} while (cmd != DecoderCommand::STOP);
}
static bool
-sidplay_scan_file(const char *path_fs,
+sidplay_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
- int song_num=get_song_num(path_fs);
+ const int song_num = get_song_num(path_fs.c_str());
char *path_container=get_container_name(path_fs);
SidTune tune(path_container, nullptr, true);
- g_free(path_container);
+ free(path_container);
if (!tune)
return false;
@@ -380,17 +385,18 @@ sidplay_scan_file(const char *path_fs,
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
/* time */
- int song_len=get_song_length(path_fs);
- if (song_len >= 0)
- tag_handler_invoke_duration(handler, handler_ctx, song_len);
+ const auto duration = get_song_length(path_fs);
+ if (!duration.IsNegative())
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime(duration));
return true;
}
static char *
-sidplay_container_scan(const char *path_fs, const unsigned int tnum)
+sidplay_container_scan(Path path_fs, const unsigned int tnum)
{
- SidTune tune(path_fs, nullptr, true);
+ SidTune tune(path_fs.c_str(), nullptr, true);
if (!tune)
return nullptr;
@@ -404,9 +410,7 @@ sidplay_container_scan(const char *path_fs, const unsigned int tnum)
/* Construct container/tune path names, eg.
Delta.sid/tune_001.sid */
if(tnum<=info.songs) {
- char *subtune= g_strdup_printf(
- SUBTUNE_PREFIX "%03u.sid", tnum);
- return subtune;
+ return FormatNew(SUBTUNE_PREFIX "%03u.sid", tnum);
} else
return nullptr;
}
diff --git a/src/decoder/SidplayDecoderPlugin.hxx b/src/decoder/plugins/SidplayDecoderPlugin.hxx
index 16544801f..58786e646 100644
--- a/src/decoder/SidplayDecoderPlugin.hxx
+++ b/src/decoder/plugins/SidplayDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/plugins/SndfileDecoderPlugin.cxx
index bcdf6d7ca..5518c70be 100644
--- a/src/decoder/SndfileDecoderPlugin.cxx
+++ b/src/decoder/plugins/SndfileDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,8 +19,8 @@
#include "config.h"
#include "SndfileDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "util/Error.hxx"
@@ -31,6 +31,13 @@
static constexpr Domain sndfile_domain("sndfile");
+static bool
+sndfile_init(gcc_unused const config_param &param)
+{
+ LogDebug(sndfile_domain, sf_version_string());
+ return true;
+}
+
struct SndfileInputStream {
Decoder *const decoder;
InputStream &is;
@@ -50,17 +57,40 @@ sndfile_vio_get_filelen(void *user_data)
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
const InputStream &is = sis.is;
+ if (!is.KnownSize())
+ return -1;
+
return is.GetSize();
}
static sf_count_t
-sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
+sndfile_vio_seek(sf_count_t _offset, int whence, void *user_data)
{
SndfileInputStream &sis = *(SndfileInputStream *)user_data;
InputStream &is = sis.is;
+ offset_type offset = _offset;
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ offset += is.GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!is.KnownSize())
+ return -1;
+
+ offset += is.GetSize();
+ break;
+
+ default:
+ return -1;
+ }
+
Error error;
- if (!is.LockSeek(offset, whence, error)) {
+ if (!is.LockSeek(offset, error)) {
LogError(error, "Seek failed");
return -1;
}
@@ -109,60 +139,89 @@ static SF_VIRTUAL_IO vio = {
/**
* Converts a frame number to a timestamp (in seconds).
*/
-static float
-frame_to_time(sf_count_t frame, const AudioFormat *audio_format)
+static constexpr SongTime
+sndfile_duration(const SF_INFO &info)
{
- return (float)frame / (float)audio_format->sample_rate;
+ return SongTime::FromScale<uint64_t>(info.frames, info.samplerate);
+}
+
+gcc_pure
+static SampleFormat
+sndfile_sample_format(const SF_INFO &info)
+{
+ switch (info.format & SF_FORMAT_SUBMASK) {
+ case SF_FORMAT_PCM_S8:
+ case SF_FORMAT_PCM_U8:
+ case SF_FORMAT_PCM_16:
+ return SampleFormat::S16;
+
+ case SF_FORMAT_FLOAT:
+ case SF_FORMAT_DOUBLE:
+ return SampleFormat::FLOAT;
+
+ default:
+ return SampleFormat::S32;
+ }
}
-/**
- * Converts a timestamp (in seconds) to a frame number.
- */
static sf_count_t
-time_to_frame(float t, const AudioFormat *audio_format)
+sndfile_read_frames(SNDFILE *sf, SampleFormat format,
+ void *buffer, sf_count_t n_frames)
{
- return (sf_count_t)(t * audio_format->sample_rate);
+ switch (format) {
+ case SampleFormat::S16:
+ return sf_readf_short(sf, (short *)buffer, n_frames);
+
+ case SampleFormat::S32:
+ return sf_readf_int(sf, (int *)buffer, n_frames);
+
+ case SampleFormat::FLOAT:
+ return sf_readf_float(sf, (float *)buffer, n_frames);
+
+ default:
+ assert(false);
+ gcc_unreachable();
+ }
}
static void
sndfile_stream_decode(Decoder &decoder, InputStream &is)
{
- SNDFILE *sf;
SF_INFO info;
- size_t frame_size;
- sf_count_t read_frames, num_frames;
- int buffer[4096];
info.format = 0;
SndfileInputStream sis{&decoder, is};
- sf = sf_open_virtual(&vio, SFM_READ, &info, &sis);
+ SNDFILE *const sf = sf_open_virtual(&vio, SFM_READ, &info, &sis);
if (sf == nullptr) {
- LogWarning(sndfile_domain, "sf_open_virtual() failed");
+ FormatWarning(sndfile_domain, "sf_open_virtual() failed: %s",
+ sf_strerror(nullptr));
return;
}
- /* for now, always read 32 bit samples. Later, we could lower
- MPD's CPU usage by reading 16 bit samples with
- sf_readf_short() on low-quality source files. */
Error error;
AudioFormat audio_format;
if (!audio_format_init_checked(audio_format, info.samplerate,
- SampleFormat::S32,
+ sndfile_sample_format(info),
info.channels, error)) {
LogError(error);
return;
}
decoder_initialized(decoder, audio_format, info.seekable,
- frame_to_time(info.frames, &audio_format));
+ sndfile_duration(info));
- frame_size = audio_format.GetFrameSize();
- read_frames = sizeof(buffer) / frame_size;
+ char buffer[16384];
+
+ const size_t frame_size = audio_format.GetFrameSize();
+ const sf_count_t read_frames = sizeof(buffer) / frame_size;
DecoderCommand cmd;
do {
- num_frames = sf_readf_int(sf, buffer, read_frames);
+ sf_count_t num_frames =
+ sndfile_read_frames(sf,
+ audio_format.format,
+ buffer, read_frames);
if (num_frames <= 0)
break;
@@ -170,9 +229,7 @@ sndfile_stream_decode(Decoder &decoder, InputStream &is)
buffer, num_frames * frame_size,
0);
if (cmd == DecoderCommand::SEEK) {
- sf_count_t c =
- time_to_frame(decoder_seek_where(decoder),
- &audio_format);
+ sf_count_t c = decoder_seek_where_frame(decoder);
c = sf_seek(sf, c, SEEK_SET);
if (c < 0)
decoder_seek_error(decoder);
@@ -185,44 +242,53 @@ sndfile_stream_decode(Decoder &decoder, InputStream &is)
sf_close(sf);
}
+static void
+sndfile_handle_tag(SNDFILE *sf, int str, TagType tag,
+ const struct tag_handler *handler, void *handler_ctx)
+{
+ const char *value = sf_get_string(sf, str);
+ if (value != nullptr)
+ tag_handler_invoke_tag(handler, handler_ctx, tag, value);
+}
+
+static constexpr struct {
+ int8_t str;
+ TagType tag;
+} sndfile_tags[] = {
+ { SF_STR_TITLE, TAG_TITLE },
+ { SF_STR_ARTIST, TAG_ARTIST },
+ { SF_STR_COMMENT, TAG_COMMENT },
+ { SF_STR_DATE, TAG_DATE },
+ { SF_STR_ALBUM, TAG_ALBUM },
+ { SF_STR_TRACKNUMBER, TAG_TRACK },
+ { SF_STR_GENRE, TAG_GENRE },
+};
+
static bool
-sndfile_scan_file(const char *path_fs,
- const struct tag_handler *handler, void *handler_ctx)
+sndfile_scan_stream(InputStream &is,
+ const struct tag_handler *handler, void *handler_ctx)
{
- SNDFILE *sf;
SF_INFO info;
- const char *p;
info.format = 0;
- sf = sf_open(path_fs, SFM_READ, &info);
+ SndfileInputStream sis{nullptr, is};
+ SNDFILE *const sf = sf_open_virtual(&vio, SFM_READ, &info, &sis);
if (sf == nullptr)
return false;
if (!audio_valid_sample_rate(info.samplerate)) {
sf_close(sf);
FormatWarning(sndfile_domain,
- "Invalid sample rate in %s", path_fs);
+ "Invalid sample rate in %s", is.GetURI());
return false;
}
tag_handler_invoke_duration(handler, handler_ctx,
- info.frames / info.samplerate);
-
- p = sf_get_string(sf, SF_STR_TITLE);
- if (p != nullptr)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_TITLE, p);
+ sndfile_duration(info));
- p = sf_get_string(sf, SF_STR_ARTIST);
- if (p != nullptr)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_ARTIST, p);
-
- p = sf_get_string(sf, SF_STR_DATE);
- if (p != nullptr)
- tag_handler_invoke_tag(handler, handler_ctx,
- TAG_DATE, p);
+ for (auto i : sndfile_tags)
+ sndfile_handle_tag(sf, i.str, i.tag, handler, handler_ctx);
sf_close(sf);
@@ -261,12 +327,12 @@ static const char *const sndfile_mime_types[] = {
const struct DecoderPlugin sndfile_decoder_plugin = {
"sndfile",
- nullptr,
+ sndfile_init,
nullptr,
sndfile_stream_decode,
nullptr,
- sndfile_scan_file,
nullptr,
+ sndfile_scan_stream,
nullptr,
sndfile_suffixes,
sndfile_mime_types,
diff --git a/src/decoder/SndfileDecoderPlugin.hxx b/src/decoder/plugins/SndfileDecoderPlugin.hxx
index f8aa65680..d56acdd5a 100644
--- a/src/decoder/SndfileDecoderPlugin.hxx
+++ b/src/decoder/plugins/SndfileDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/VorbisComments.cxx b/src/decoder/plugins/VorbisComments.cxx
index d4f019b58..062f46acf 100644
--- a/src/decoder/VorbisComments.cxx
+++ b/src/decoder/plugins/VorbisComments.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -20,58 +20,28 @@
#include "config.h"
#include "VorbisComments.hxx"
#include "XiphTags.hxx"
-#include "tag/Tag.hxx"
#include "tag/TagTable.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagBuilder.hxx"
+#include "tag/VorbisComment.hxx"
+#include "tag/ReplayGain.hxx"
#include "ReplayGainInfo.hxx"
#include "util/ASCII.hxx"
+#include "util/SplitString.hxx"
-#include <glib.h>
-
-#include <assert.h>
#include <stddef.h>
-#include <string.h>
#include <stdlib.h>
-static const char *
-vorbis_comment_value(const char *comment, const char *needle)
-{
- size_t len = strlen(needle);
-
- if (StringEqualsCaseASCII(comment, needle, len) &&
- comment[len] == '=')
- return comment + len + 1;
-
- return nullptr;
-}
-
bool
vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments)
{
rgi.Clear();
- const char *temp;
bool found = false;
while (*comments) {
- if ((temp =
- vorbis_comment_value(*comments, "replaygain_track_gain"))) {
- rgi.tuples[REPLAY_GAIN_TRACK].gain = atof(temp);
+ if (ParseReplayGainVorbis(rgi, *comments))
found = true;
- } else if ((temp = vorbis_comment_value(*comments,
- "replaygain_album_gain"))) {
- rgi.tuples[REPLAY_GAIN_ALBUM].gain = atof(temp);
- found = true;
- } else if ((temp = vorbis_comment_value(*comments,
- "replaygain_track_peak"))) {
- rgi.tuples[REPLAY_GAIN_TRACK].peak = atof(temp);
- found = true;
- } else if ((temp = vorbis_comment_value(*comments,
- "replaygain_album_peak"))) {
- rgi.tuples[REPLAY_GAIN_ALBUM].peak = atof(temp);
- found = true;
- }
comments++;
}
@@ -104,16 +74,11 @@ vorbis_scan_comment(const char *comment,
const struct tag_handler *handler, void *handler_ctx)
{
if (handler->pair != nullptr) {
- char *name = g_strdup((const char*)comment);
- char *value = strchr(name, '=');
-
- if (value != nullptr && value > name) {
- *value++ = 0;
+ const SplitString split(comment, '=');
+ if (split.IsDefined() && !split.IsEmpty())
tag_handler_invoke_pair(handler, handler_ctx,
- name, value);
- }
-
- g_free(name);
+ split.GetFirst(),
+ split.GetSecond());
}
for (const struct tag_table *i = xiph_tags; i->name != nullptr; ++i)
@@ -145,5 +110,5 @@ vorbis_comments_to_tag(char **comments)
vorbis_comments_scan(comments, &add_tag_handler, &tag_builder);
return tag_builder.IsEmpty()
? nullptr
- : tag_builder.Commit();
+ : tag_builder.CommitNew();
}
diff --git a/src/decoder/VorbisComments.hxx b/src/decoder/plugins/VorbisComments.hxx
index e5a48ef6b..893c89277 100644
--- a/src/decoder/VorbisComments.hxx
+++ b/src/decoder/plugins/VorbisComments.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -31,7 +31,7 @@ vorbis_comments_to_replay_gain(ReplayGainInfo &rgi, char **comments);
void
vorbis_comments_scan(char **comments,
- const struct tag_handler *handler, void *handler_ctx);
+ const tag_handler *handler, void *handler_ctx);
Tag *
vorbis_comments_to_tag(char **comments);
diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/plugins/VorbisDecoderPlugin.cxx
index 4d3e48528..e0d3d1374 100644
--- a/src/decoder/VorbisDecoderPlugin.cxx
+++ b/src/decoder/plugins/VorbisDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -21,13 +21,11 @@
#include "VorbisDecoderPlugin.h"
#include "VorbisComments.hxx"
#include "VorbisDomain.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "OggCodec.hxx"
#include "util/Error.hxx"
-#include "util/UriUtil.hxx"
#include "util/Macros.hxx"
-#include "system/ByteOrder.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "Log.hxx"
@@ -48,20 +46,23 @@
#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000))
#endif /* HAVE_TREMOR */
-#include <assert.h>
#include <errno.h>
-struct vorbis_input_stream {
- Decoder *decoder;
+struct VorbisInputStream {
+ Decoder *const decoder;
- InputStream *input_stream;
+ InputStream &input_stream;
bool seekable;
+
+ VorbisInputStream(Decoder *_decoder, InputStream &_is)
+ :decoder(_decoder), input_stream(_is),
+ seekable(input_stream.CheapSeeking()) {}
};
static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data)
{
- struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data;
- size_t ret = decoder_read(vis->decoder, *vis->input_stream,
+ VorbisInputStream *vis = (VorbisInputStream *)data;
+ size_t ret = decoder_read(vis->decoder, vis->input_stream,
ptr, size * nmemb);
errno = 0;
@@ -69,15 +70,37 @@ static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data)
return ret / size;
}
-static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence)
+static int ogg_seek_cb(void *data, ogg_int64_t _offset, int whence)
{
- struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data;
+ VorbisInputStream *vis = (VorbisInputStream *)data;
+ InputStream &is = vis->input_stream;
- Error error;
- return vis->seekable &&
- (vis->decoder == nullptr ||
- decoder_get_command(*vis->decoder) != DecoderCommand::STOP) &&
- vis->input_stream->LockSeek(offset, whence, error)
+ if (!vis->seekable ||
+ (vis->decoder != nullptr &&
+ decoder_get_command(*vis->decoder) == DecoderCommand::STOP))
+ return -1;
+
+ offset_type offset = _offset;
+ switch (whence) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ offset += is.GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!is.KnownSize())
+ return -1;
+
+ offset += is.GetSize();
+ break;
+
+ default:
+ return -1;
+ }
+
+ return is.LockSeek(offset, IgnoreError())
? 0 : -1;
}
@@ -89,9 +112,9 @@ static int ogg_close_cb(gcc_unused void *data)
static long ogg_tell_cb(void *data)
{
- struct vorbis_input_stream *vis = (struct vorbis_input_stream *)data;
+ VorbisInputStream *vis = (VorbisInputStream *)data;
- return (long)vis->input_stream->offset;
+ return (long)vis->input_stream.GetOffset();
}
static const ov_callbacks vorbis_is_callbacks = {
@@ -126,17 +149,12 @@ vorbis_strerror(int code)
}
static bool
-vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf,
- Decoder *decoder, InputStream &input_stream)
+vorbis_is_open(VorbisInputStream *vis, OggVorbis_File *vf)
{
- vis->decoder = decoder;
- vis->input_stream = &input_stream;
- vis->seekable = input_stream.CheapSeeking();
-
int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks);
if (ret < 0) {
- if (decoder == nullptr ||
- decoder_get_command(*decoder) == DecoderCommand::NONE)
+ if (vis->decoder == nullptr ||
+ decoder_get_command(*vis->decoder) == DecoderCommand::NONE)
FormatWarning(vorbis_domain,
"Failed to open Ogg Vorbis stream: %s",
vorbis_strerror(ret));
@@ -165,8 +183,8 @@ vorbis_interleave(float *dest, const float *const*src,
{
for (const float *const*src_end = src + channels;
src != src_end; ++src, ++dest) {
- float *d = dest;
- for (const float *s = *src, *s_end = s + nframes;
+ float *gcc_restrict d = dest;
+ for (const float *gcc_restrict s = *src, *s_end = s + nframes;
s != s_end; ++s, d += channels)
*d = *s;
}
@@ -174,6 +192,26 @@ vorbis_interleave(float *dest, const float *const*src,
#endif
/* public */
+
+static bool
+vorbis_init(gcc_unused const config_param &param)
+{
+#ifndef HAVE_TREMOR
+ LogDebug(vorbis_domain, vorbis_version_string());
+#endif
+ return true;
+}
+
+gcc_pure
+static SignedSongTime
+vorbis_duration(OggVorbis_File &vf)
+{
+ auto total = ov_time_total(&vf, -1);
+ return total >= 0
+ ? SignedSongTime::FromS(total)
+ : SignedSongTime::Negative();
+}
+
static void
vorbis_stream_decode(Decoder &decoder,
InputStream &input_stream)
@@ -185,9 +223,9 @@ vorbis_stream_decode(Decoder &decoder,
moved it */
input_stream.LockRewind(IgnoreError());
- struct vorbis_input_stream vis;
+ VorbisInputStream vis(&decoder, input_stream);
OggVorbis_File vf;
- if (!vorbis_is_open(&vis, &vf, &decoder, input_stream))
+ if (!vorbis_is_open(&vis, &vf))
return;
const vorbis_info *vi = ov_info(&vf, -1);
@@ -209,11 +247,8 @@ vorbis_stream_decode(Decoder &decoder,
return;
}
- float total_time = ov_time_total(&vf, -1);
- if (total_time < 0)
- total_time = 0;
-
- decoder_initialized(decoder, audio_format, vis.seekable, total_time);
+ decoder_initialized(decoder, audio_format, vis.seekable,
+ vorbis_duration(vf));
#ifdef HAVE_TREMOR
char buffer[4096];
@@ -230,8 +265,8 @@ vorbis_stream_decode(Decoder &decoder,
DecoderCommand cmd = decoder_get_command(decoder);
do {
if (cmd == DecoderCommand::SEEK) {
- double seek_where = decoder_seek_where(decoder);
- if (0 == ov_time_seek_page(&vf, seek_where)) {
+ auto seek_where = decoder_seek_where_frame(decoder);
+ if (0 == ov_pcm_seek_page(&vf, seek_where)) {
decoder_command_finished(decoder);
} else
decoder_seek_error(decoder);
@@ -306,14 +341,16 @@ static bool
vorbis_scan_stream(InputStream &is,
const struct tag_handler *handler, void *handler_ctx)
{
- struct vorbis_input_stream vis;
+ VorbisInputStream vis(nullptr, is);
OggVorbis_File vf;
- if (!vorbis_is_open(&vis, &vf, nullptr, is))
+ if (!vorbis_is_open(&vis, &vf))
return false;
- tag_handler_invoke_duration(handler, handler_ctx,
- (int)(ov_time_total(&vf, -1) + 0.5));
+ const auto total = ov_time_total(&vf, -1);
+ if (total >= 0)
+ tag_handler_invoke_duration(handler, handler_ctx,
+ SongTime::FromS(total));
vorbis_comments_scan(ov_comment(&vf, -1)->user_comments,
handler, handler_ctx);
@@ -340,7 +377,7 @@ static const char *const vorbis_mime_types[] = {
const struct DecoderPlugin vorbis_decoder_plugin = {
"vorbis",
- nullptr,
+ vorbis_init,
nullptr,
vorbis_stream_decode,
nullptr,
diff --git a/src/decoder/VorbisDecoderPlugin.h b/src/decoder/plugins/VorbisDecoderPlugin.h
index 54953d83a..b54df2e97 100644
--- a/src/decoder/VorbisDecoderPlugin.h
+++ b/src/decoder/plugins/VorbisDecoderPlugin.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/VorbisDomain.cxx b/src/decoder/plugins/VorbisDomain.cxx
index 32ff4d6b7..e3d880efa 100644
--- a/src/decoder/VorbisDomain.cxx
+++ b/src/decoder/plugins/VorbisDomain.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/VorbisDomain.hxx b/src/decoder/plugins/VorbisDomain.hxx
index a35edd041..48715e328 100644
--- a/src/decoder/VorbisDomain.hxx
+++ b/src/decoder/plugins/VorbisDomain.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -22,6 +22,8 @@
#include "check.h"
-extern const class Domain vorbis_domain;
+class Domain;
+
+extern const Domain vorbis_domain;
#endif
diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/plugins/WavpackDecoderPlugin.cxx
index 98555c5e8..67859bbd2 100644
--- a/src/decoder/WavpackDecoderPlugin.cxx
+++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,11 +19,12 @@
#include "config.h"
#include "WavpackDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
-#include "InputStream.hxx"
+#include "../DecoderAPI.hxx"
+#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "tag/ApeTag.hxx"
+#include "fs/Path.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "util/Macros.hxx"
@@ -159,8 +160,9 @@ wavpack_decode(Decoder &decoder, WavpackContext *wpc, bool can_seek)
? format_samples_float
: format_samples_int;
- const float total_time = float(WavpackGetNumSamples(wpc))
- / audio_format.sample_rate;
+ const auto total_time =
+ SongTime::FromScale<uint64_t>(WavpackGetNumSamples(wpc),
+ audio_format.sample_rate);
const int bytes_per_sample = WavpackGetBytesPerSample(wpc);
const int output_sample_size = audio_format.GetFrameSize();
@@ -176,8 +178,7 @@ wavpack_decode(Decoder &decoder, WavpackContext *wpc, bool can_seek)
while (cmd != DecoderCommand::STOP) {
if (cmd == DecoderCommand::SEEK) {
if (can_seek) {
- unsigned where = decoder_seek_where(decoder) *
- audio_format.sample_rate;
+ auto where = decoder_seek_where_frame(decoder);
if (WavpackSeekSample(wpc, where)) {
decoder_command_finished(decoder);
@@ -269,21 +270,23 @@ wavpack_scan_pair(WavpackContext *wpc, const char *name,
* Reads metainfo from the specified file.
*/
static bool
-wavpack_scan_file(const char *fname,
+wavpack_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
char error[ERRORLEN];
- WavpackContext *wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0);
+ WavpackContext *wpc = WavpackOpenFileInput(path_fs.c_str(), error,
+ OPEN_TAGS, 0);
if (wpc == nullptr) {
FormatError(wavpack_domain,
"failed to open WavPack file \"%s\": %s",
- fname, error);
+ path_fs.c_str(), error);
return false;
}
- tag_handler_invoke_duration(handler, handler_ctx,
- WavpackGetNumSamples(wpc) /
- WavpackGetSampleRate(wpc));
+ const auto duration =
+ SongTime::FromScale<uint64_t>(WavpackGetNumSamples(wpc),
+ WavpackGetSampleRate(wpc));
+ tag_handler_invoke_duration(handler, handler_ctx, duration);
/* the WavPack format implies APEv2 tags, which means we can
reuse the mapping from tag_ape.c */
@@ -323,32 +326,43 @@ wavpack_scan_file(const char *fname,
*/
/* This struct is needed for per-stream last_byte storage. */
-struct wavpack_input {
- Decoder *decoder;
- InputStream *is;
+struct WavpackInput {
+ Decoder &decoder;
+ InputStream &is;
/* Needed for push_back_byte() */
int last_byte;
+
+ constexpr WavpackInput(Decoder &_decoder, InputStream &_is)
+ :decoder(_decoder), is(_is), last_byte(EOF) {}
+
+ int32_t ReadBytes(void *data, size_t bcount);
};
/**
- * Little wrapper for struct wavpack_input to cast from void *.
+ * Little wrapper for struct WavpackInput to cast from void *.
*/
-static struct wavpack_input *
+static WavpackInput *
wpin(void *id)
{
assert(id);
- return (struct wavpack_input *)id;
+ return (WavpackInput *)id;
}
static int32_t
wavpack_input_read_bytes(void *id, void *data, int32_t bcount)
{
+ return wpin(id)->ReadBytes(data, bcount);
+}
+
+int32_t
+WavpackInput::ReadBytes(void *data, size_t bcount)
+{
uint8_t *buf = (uint8_t *)data;
int32_t i = 0;
- if (wpin(id)->last_byte != EOF) {
- *buf++ = wpin(id)->last_byte;
- wpin(id)->last_byte = EOF;
+ if (last_byte != EOF) {
+ *buf++ = last_byte;
+ last_byte = EOF;
--bcount;
++i;
}
@@ -356,9 +370,7 @@ wavpack_input_read_bytes(void *id, void *data, int32_t bcount)
/* wavpack fails if we return a partial read, so we just wait
until the buffer is full */
while (bcount > 0) {
- size_t nbytes = decoder_read(
- wpin(id)->decoder, *wpin(id)->is, buf, bcount
- );
+ size_t nbytes = decoder_read(&decoder, is, buf, bcount);
if (nbytes == 0) {
/* EOF, error or a decoder command */
break;
@@ -375,26 +387,55 @@ wavpack_input_read_bytes(void *id, void *data, int32_t bcount)
static uint32_t
wavpack_input_get_pos(void *id)
{
- return wpin(id)->is->offset;
+ WavpackInput &wpi = *wpin(id);
+
+ return wpi.is.GetOffset();
}
static int
wavpack_input_set_pos_abs(void *id, uint32_t pos)
{
- return wpin(id)->is->LockSeek(pos, SEEK_SET, IgnoreError()) ? 0 : -1;
+ WavpackInput &wpi = *wpin(id);
+
+ return wpi.is.LockSeek(pos, IgnoreError()) ? 0 : -1;
}
static int
wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
{
- return wpin(id)->is->LockSeek(delta, mode, IgnoreError()) ? 0 : -1;
+ WavpackInput &wpi = *wpin(id);
+ InputStream &is = wpi.is;
+
+ offset_type offset = delta;
+ switch (mode) {
+ case SEEK_SET:
+ break;
+
+ case SEEK_CUR:
+ offset += is.GetOffset();
+ break;
+
+ case SEEK_END:
+ if (!is.KnownSize())
+ return -1;
+
+ offset += is.GetSize();
+ break;
+
+ default:
+ return -1;
+ }
+
+ return is.LockSeek(offset, IgnoreError()) ? 0 : -1;
}
static int
wavpack_input_push_back_byte(void *id, int c)
{
- if (wpin(id)->last_byte == EOF) {
- wpin(id)->last_byte = c;
+ WavpackInput &wpi = *wpin(id);
+
+ if (wpi.last_byte == EOF) {
+ wpi.last_byte = c;
return c;
} else {
return EOF;
@@ -404,16 +445,22 @@ wavpack_input_push_back_byte(void *id, int c)
static uint32_t
wavpack_input_get_length(void *id)
{
- if (wpin(id)->is->size < 0)
+ WavpackInput &wpi = *wpin(id);
+ InputStream &is = wpi.is;
+
+ if (!is.KnownSize())
return 0;
- return wpin(id)->is->size;
+ return is.GetSize();
}
static int
wavpack_input_can_seek(void *id)
{
- return wpin(id)->is->seekable;
+ WavpackInput &wpi = *wpin(id);
+ InputStream &is = wpi.is;
+
+ return is.IsSeekable();
}
static WavpackStreamReader mpd_is_reader = {
@@ -427,19 +474,8 @@ static WavpackStreamReader mpd_is_reader = {
nullptr /* no need to write edited tags */
};
-static void
-wavpack_input_init(struct wavpack_input *isp, Decoder &decoder,
- InputStream &is)
-{
- isp->decoder = &decoder;
- isp->is = &is;
- isp->last_byte = EOF;
-}
-
-static InputStream *
-wavpack_open_wvc(Decoder &decoder, const char *uri,
- Mutex &mutex, Cond &cond,
- struct wavpack_input *wpi)
+static WavpackInput *
+wavpack_open_wvc(Decoder &decoder, const char *uri)
{
/*
* As we use dc->utf8url, this function will be bad for
@@ -450,29 +486,13 @@ wavpack_open_wvc(Decoder &decoder, const char *uri,
char *wvc_url = g_strconcat(uri, "c", nullptr);
- InputStream *is_wvc = InputStream::Open(wvc_url, mutex, cond,
- IgnoreError());
+ InputStream *is_wvc = decoder_open_uri(decoder, uri, IgnoreError());
g_free(wvc_url);
if (is_wvc == nullptr)
return nullptr;
- /*
- * And we try to buffer in order to get know
- * about a possible 404 error.
- */
- char first_byte;
- size_t nbytes = decoder_read(decoder, *is_wvc,
- &first_byte, sizeof(first_byte));
- if (nbytes == 0) {
- is_wvc->Close();
- return nullptr;
- }
-
- /* push it back */
- wavpack_input_init(wpi, decoder, *is_wvc);
- wpi->last_byte = first_byte;
- return is_wvc;
+ return new WavpackInput(decoder, *is_wvc);
}
/*
@@ -482,29 +502,23 @@ static void
wavpack_streamdecode(Decoder &decoder, InputStream &is)
{
int open_flags = OPEN_NORMALIZE;
- bool can_seek = is.seekable;
+ bool can_seek = is.IsSeekable();
- wavpack_input isp_wvc;
- InputStream *is_wvc = wavpack_open_wvc(decoder, is.uri.c_str(),
- is.mutex, is.cond,
- &isp_wvc);
- if (is_wvc != nullptr) {
+ WavpackInput *wvc = wavpack_open_wvc(decoder, is.GetURI());
+ if (wvc != nullptr) {
open_flags |= OPEN_WVC;
- can_seek &= is_wvc->seekable;
+ can_seek &= wvc->is.IsSeekable();
}
if (!can_seek) {
open_flags |= OPEN_STREAMING;
}
- wavpack_input isp;
- wavpack_input_init(&isp, decoder, is);
+ WavpackInput isp(decoder, is);
char error[ERRORLEN];
WavpackContext *wpc =
- WavpackOpenFileInputEx(&mpd_is_reader, &isp,
- open_flags & OPEN_WVC
- ? &isp_wvc : nullptr,
+ WavpackOpenFileInputEx(&mpd_is_reader, &isp, wvc,
error, open_flags, 23);
if (wpc == nullptr) {
@@ -516,8 +530,10 @@ wavpack_streamdecode(Decoder &decoder, InputStream &is)
wavpack_decode(decoder, wpc, can_seek);
WavpackCloseFile(wpc);
- if (open_flags & OPEN_WVC) {
- is_wvc->Close();
+
+ if (wvc != nullptr) {
+ delete &wvc->is;
+ delete wvc;
}
}
@@ -525,16 +541,16 @@ wavpack_streamdecode(Decoder &decoder, InputStream &is)
* Decodes a file.
*/
static void
-wavpack_filedecode(Decoder &decoder, const char *fname)
+wavpack_filedecode(Decoder &decoder, Path path_fs)
{
char error[ERRORLEN];
- WavpackContext *wpc = WavpackOpenFileInput(fname, error,
+ WavpackContext *wpc = WavpackOpenFileInput(path_fs.c_str(), error,
OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE,
23);
if (wpc == nullptr) {
FormatWarning(wavpack_domain,
"failed to open WavPack file \"%s\": %s",
- fname, error);
+ path_fs.c_str(), error);
return;
}
diff --git a/src/decoder/WavpackDecoderPlugin.hxx b/src/decoder/plugins/WavpackDecoderPlugin.hxx
index 3a2d94532..2e5f9bd42 100644
--- a/src/decoder/WavpackDecoderPlugin.hxx
+++ b/src/decoder/plugins/WavpackDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/plugins/WildmidiDecoderPlugin.cxx
index 3da3f1387..fc58f0977 100644
--- a/src/decoder/WildmidiDecoderPlugin.cxx
+++ b/src/decoder/plugins/WildmidiDecoderPlugin.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -19,12 +19,13 @@
#include "config.h"
#include "WildmidiDecoderPlugin.hxx"
-#include "DecoderAPI.hxx"
+#include "../DecoderAPI.hxx"
#include "tag/TagHandler.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/FileSystem.hxx"
+#include "fs/Path.hxx"
#include "system/FatalError.hxx"
#include "Log.hxx"
@@ -65,7 +66,7 @@ wildmidi_finish(void)
}
static void
-wildmidi_file_decode(Decoder &decoder, const char *path_fs)
+wildmidi_file_decode(Decoder &decoder, Path path_fs)
{
static constexpr AudioFormat audio_format = {
WILDMIDI_SAMPLE_RATE,
@@ -75,7 +76,7 @@ wildmidi_file_decode(Decoder &decoder, const char *path_fs)
midi *wm;
const struct _WM_Info *info;
- wm = WildMidi_Open(path_fs);
+ wm = WildMidi_Open(path_fs.c_str());
if (wm == nullptr)
return;
@@ -85,8 +86,11 @@ wildmidi_file_decode(Decoder &decoder, const char *path_fs)
return;
}
- decoder_initialized(decoder, audio_format, true,
- info->approx_total_samples / WILDMIDI_SAMPLE_RATE);
+ const auto duration =
+ SongTime::FromScale<uint64_t>(info->approx_total_samples,
+ WILDMIDI_SAMPLE_RATE);
+
+ decoder_initialized(decoder, audio_format, true, duration);
DecoderCommand cmd;
do {
@@ -104,8 +108,8 @@ wildmidi_file_decode(Decoder &decoder, const char *path_fs)
cmd = decoder_data(decoder, nullptr, buffer, len, 0);
if (cmd == DecoderCommand::SEEK) {
- unsigned long seek_where = WILDMIDI_SAMPLE_RATE *
- decoder_seek_where(decoder);
+ unsigned long seek_where =
+ decoder_seek_where_frame(decoder);
WildMidi_FastSeek(wm, &seek_where);
decoder_command_finished(decoder);
@@ -118,10 +122,10 @@ wildmidi_file_decode(Decoder &decoder, const char *path_fs)
}
static bool
-wildmidi_scan_file(const char *path_fs,
+wildmidi_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
- midi *wm = WildMidi_Open(path_fs);
+ midi *wm = WildMidi_Open(path_fs.c_str());
if (wm == nullptr)
return false;
@@ -131,7 +135,9 @@ wildmidi_scan_file(const char *path_fs,
return false;
}
- int duration = info->approx_total_samples / WILDMIDI_SAMPLE_RATE;
+ const auto duration =
+ SongTime::FromScale<uint64_t>(info->approx_total_samples,
+ WILDMIDI_SAMPLE_RATE);
tag_handler_invoke_duration(handler, handler_ctx, duration);
WildMidi_Close(wm);
diff --git a/src/decoder/WildmidiDecoderPlugin.hxx b/src/decoder/plugins/WildmidiDecoderPlugin.hxx
index a6289612e..fc87aab80 100644
--- a/src/decoder/WildmidiDecoderPlugin.hxx
+++ b/src/decoder/plugins/WildmidiDecoderPlugin.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
diff --git a/src/decoder/XiphTags.cxx b/src/decoder/plugins/XiphTags.cxx
index b9958a19a..11a0bcd42 100644
--- a/src/decoder/XiphTags.cxx
+++ b/src/decoder/plugins/XiphTags.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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
@@ -17,6 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+/* This File contains additional Tags for Xiph-Based Formats like Ogg-Vorbis,
+ * Flac and Opus which will be used in addition to the Tags in tag/TagNames.c
+ * see https://www.xiph.org/vorbis/doc/v-comment.html for further Info
+ */
#include "config.h"
#include "XiphTags.hxx"
@@ -24,5 +28,6 @@ const struct tag_table xiph_tags[] = {
{ "tracknumber", TAG_TRACK },
{ "discnumber", TAG_DISC },
{ "album artist", TAG_ALBUM_ARTIST },
+ { "description", TAG_COMMENT },
{ nullptr, TAG_NUM_OF_ITEM_TYPES }
};
diff --git a/src/decoder/XiphTags.hxx b/src/decoder/plugins/XiphTags.hxx
index 606dfef10..48a27425f 100644
--- a/src/decoder/XiphTags.hxx
+++ b/src/decoder/plugins/XiphTags.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * 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