decoder/*: move to decoder/plugins/

This commit is contained in:
Max Kellermann
2014-01-24 00:02:24 +01:00
parent ea5b901bcc
commit 51adaf2c47
111 changed files with 142 additions and 142 deletions

597
src/decoder/DecoderAPI.cxx Normal file
View File

@@ -0,0 +1,597 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "DecoderAPI.hxx"
#include "DecoderError.hxx"
#include "pcm/PcmConvert.hxx"
#include "AudioConfig.hxx"
#include "ReplayGainConfig.hxx"
#include "MusicChunk.hxx"
#include "MusicBuffer.hxx"
#include "MusicPipe.hxx"
#include "DecoderControl.hxx"
#include "DecoderInternal.hxx"
#include "DetachedSong.hxx"
#include "InputStream.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
#include <assert.h>
#include <string.h>
#include <math.h>
void
decoder_initialized(Decoder &decoder,
const AudioFormat audio_format,
bool seekable, float total_time)
{
DecoderControl &dc = decoder.dc;
struct audio_format_string af_string;
assert(dc.state == DecoderState::START);
assert(dc.pipe != nullptr);
assert(decoder.convert == nullptr);
assert(decoder.stream_tag == nullptr);
assert(decoder.decoder_tag == nullptr);
assert(!decoder.seeking);
assert(audio_format.IsDefined());
assert(audio_format.IsValid());
dc.in_audio_format = audio_format;
dc.out_audio_format = getOutputAudioFormat(audio_format);
dc.seekable = seekable;
dc.total_time = total_time;
FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
audio_format_to_string(dc.in_audio_format, &af_string),
seekable ? "true" : "false");
if (dc.in_audio_format != dc.out_audio_format) {
FormatDebug(decoder_domain, "converting to %s",
audio_format_to_string(dc.out_audio_format,
&af_string));
decoder.convert = new PcmConvert();
Error error;
if (!decoder.convert->Open(dc.in_audio_format,
dc.out_audio_format,
error))
decoder.error = std::move(error);
}
dc.Lock();
dc.state = DecoderState::DECODE;
dc.client_cond.signal();
dc.Unlock();
}
/**
* Checks if we need an "initial seek". If so, then the initial seek
* is prepared, and the function returns true.
*/
gcc_pure
static bool
decoder_prepare_initial_seek(Decoder &decoder)
{
const DecoderControl &dc = decoder.dc;
assert(dc.pipe != nullptr);
if (dc.state != DecoderState::DECODE)
/* wait until the decoder has finished initialisation
(reading file headers etc.) before emitting the
virtual "SEEK" command */
return false;
if (decoder.initial_seek_running)
/* initial seek has already begun - override any other
command */
return true;
if (decoder.initial_seek_pending) {
if (!dc.seekable) {
/* seeking is not possible */
decoder.initial_seek_pending = false;
return false;
}
if (dc.command == DecoderCommand::NONE) {
/* begin initial seek */
decoder.initial_seek_pending = false;
decoder.initial_seek_running = true;
return true;
}
/* skip initial seek when there's another command
(e.g. STOP) */
decoder.initial_seek_pending = false;
}
return false;
}
/**
* Returns the current decoder command. May return a "virtual"
* synthesized command, e.g. to seek to the beginning of the CUE
* track.
*/
gcc_pure
static DecoderCommand
decoder_get_virtual_command(Decoder &decoder)
{
if (decoder.error.IsDefined())
/* an error has occurred: stop the decoder plugin */
return DecoderCommand::STOP;
const DecoderControl &dc = decoder.dc;
assert(dc.pipe != nullptr);
if (decoder_prepare_initial_seek(decoder))
return DecoderCommand::SEEK;
return dc.command;
}
DecoderCommand
decoder_get_command(Decoder &decoder)
{
return decoder_get_virtual_command(decoder);
}
void
decoder_command_finished(Decoder &decoder)
{
DecoderControl &dc = decoder.dc;
dc.Lock();
assert(dc.command != DecoderCommand::NONE ||
decoder.initial_seek_running);
assert(dc.command != DecoderCommand::SEEK ||
decoder.initial_seek_running ||
dc.seek_error || decoder.seeking);
assert(dc.pipe != nullptr);
if (decoder.initial_seek_running) {
assert(!decoder.seeking);
assert(decoder.chunk == nullptr);
assert(dc.pipe->IsEmpty());
decoder.initial_seek_running = false;
decoder.timestamp = dc.start_ms / 1000.;
dc.Unlock();
return;
}
if (decoder.seeking) {
decoder.seeking = false;
/* delete frames from the old song position */
if (decoder.chunk != nullptr) {
dc.buffer->Return(decoder.chunk);
decoder.chunk = nullptr;
}
dc.pipe->Clear(*dc.buffer);
decoder.timestamp = dc.seek_where;
}
dc.command = DecoderCommand::NONE;
dc.client_cond.signal();
dc.Unlock();
}
double decoder_seek_where(gcc_unused Decoder & decoder)
{
const DecoderControl &dc = decoder.dc;
assert(dc.pipe != nullptr);
if (decoder.initial_seek_running)
return dc.start_ms / 1000.;
assert(dc.command == DecoderCommand::SEEK);
decoder.seeking = true;
return dc.seek_where;
}
void decoder_seek_error(Decoder & decoder)
{
DecoderControl &dc = decoder.dc;
assert(dc.pipe != nullptr);
if (decoder.initial_seek_running) {
/* d'oh, we can't seek to the sub-song start position,
what now? - no idea, ignoring the problem for now. */
decoder.initial_seek_running = false;
return;
}
assert(dc.command == DecoderCommand::SEEK);
dc.seek_error = true;
decoder.seeking = false;
decoder_command_finished(decoder);
}
/**
* Should be read operation be cancelled? That is the case when the
* player thread has sent a command such as "STOP".
*/
gcc_pure
static inline bool
decoder_check_cancel_read(const Decoder *decoder)
{
if (decoder == nullptr)
return false;
const DecoderControl &dc = decoder->dc;
if (dc.command == DecoderCommand::NONE)
return false;
/* ignore the SEEK command during initialization, the plugin
should handle that after it has initialized successfully */
if (dc.command == DecoderCommand::SEEK &&
(dc.state == DecoderState::START || decoder->seeking))
return false;
return true;
}
size_t
decoder_read(Decoder *decoder,
InputStream &is,
void *buffer, size_t length)
{
/* XXX don't allow decoder==nullptr */
assert(decoder == nullptr ||
decoder->dc.state == DecoderState::START ||
decoder->dc.state == DecoderState::DECODE);
assert(buffer != nullptr);
if (length == 0)
return 0;
is.Lock();
while (true) {
if (decoder_check_cancel_read(decoder)) {
is.Unlock();
return 0;
}
if (is.IsAvailable())
break;
is.cond.wait(is.mutex);
}
Error error;
size_t nbytes = is.Read(buffer, length, error);
assert(nbytes == 0 || !error.IsDefined());
assert(nbytes > 0 || error.IsDefined() || is.IsEOF());
is.Unlock();
if (gcc_unlikely(nbytes == 0 && error.IsDefined()))
LogError(error);
return nbytes;
}
bool
decoder_read_full(Decoder *decoder, InputStream &is,
void *_buffer, size_t size)
{
uint8_t *buffer = (uint8_t *)_buffer;
while (size > 0) {
size_t nbytes = decoder_read(decoder, is, buffer, size);
if (nbytes == 0)
return false;
buffer += nbytes;
size -= nbytes;
}
return true;
}
bool
decoder_skip(Decoder *decoder, InputStream &is, size_t size)
{
while (size > 0) {
char buffer[1024];
size_t nbytes = decoder_read(decoder, is, buffer,
std::min(sizeof(buffer), size));
if (nbytes == 0)
return false;
size -= nbytes;
}
return true;
}
void
decoder_timestamp(Decoder &decoder, double t)
{
assert(t >= 0);
decoder.timestamp = t;
}
/**
* Sends a #tag as-is to the music pipe. Flushes the current chunk
* (decoder.chunk) if there is one.
*/
static DecoderCommand
do_send_tag(Decoder &decoder, const Tag &tag)
{
struct music_chunk *chunk;
if (decoder.chunk != nullptr) {
/* there is a partial chunk - flush it, we want the
tag in a new chunk */
decoder.FlushChunk();
}
assert(decoder.chunk == nullptr);
chunk = decoder.GetChunk();
if (chunk == nullptr) {
assert(decoder.dc.command != DecoderCommand::NONE);
return decoder.dc.command;
}
chunk->tag = new Tag(tag);
return DecoderCommand::NONE;
}
static bool
update_stream_tag(Decoder &decoder, InputStream *is)
{
Tag *tag;
tag = is != nullptr
? is->LockReadTag()
: nullptr;
if (tag == nullptr) {
tag = decoder.song_tag;
if (tag == nullptr)
return false;
/* no stream tag present - submit the song tag
instead */
decoder.song_tag = nullptr;
}
delete decoder.stream_tag;
decoder.stream_tag = tag;
return true;
}
DecoderCommand
decoder_data(Decoder &decoder,
InputStream *is,
const void *data, size_t length,
uint16_t kbit_rate)
{
DecoderControl &dc = decoder.dc;
DecoderCommand cmd;
assert(dc.state == DecoderState::DECODE);
assert(dc.pipe != nullptr);
assert(length % dc.in_audio_format.GetFrameSize() == 0);
dc.Lock();
cmd = decoder_get_virtual_command(decoder);
dc.Unlock();
if (cmd == DecoderCommand::STOP || cmd == DecoderCommand::SEEK ||
length == 0)
return cmd;
/* send stream tags */
if (update_stream_tag(decoder, is)) {
if (decoder.decoder_tag != nullptr) {
/* merge with tag from decoder plugin */
Tag *tag = Tag::Merge(*decoder.decoder_tag,
*decoder.stream_tag);
cmd = do_send_tag(decoder, *tag);
delete tag;
} else
/* send only the stream tag */
cmd = do_send_tag(decoder, *decoder.stream_tag);
if (cmd != DecoderCommand::NONE)
return cmd;
}
if (decoder.convert != nullptr) {
assert(dc.in_audio_format != dc.out_audio_format);
Error error;
data = decoder.convert->Convert(data, length,
&length,
error);
if (data == nullptr) {
/* the PCM conversion has failed - stop
playback, since we have no better way to
bail out */
LogError(error);
return DecoderCommand::STOP;
}
} else {
assert(dc.in_audio_format == dc.out_audio_format);
}
while (length > 0) {
struct music_chunk *chunk;
bool full;
chunk = decoder.GetChunk();
if (chunk == nullptr) {
assert(dc.command != DecoderCommand::NONE);
return dc.command;
}
const auto dest =
chunk->Write(dc.out_audio_format,
decoder.timestamp -
dc.song->GetStartMS() / 1000.0,
kbit_rate);
if (dest.IsNull()) {
/* the chunk is full, flush it */
decoder.FlushChunk();
continue;
}
size_t nbytes = dest.size;
assert(nbytes > 0);
if (nbytes > length)
nbytes = length;
/* copy the buffer */
memcpy(dest.data, data, nbytes);
/* expand the music pipe chunk */
full = chunk->Expand(dc.out_audio_format, nbytes);
if (full) {
/* the chunk is full, flush it */
decoder.FlushChunk();
}
data = (const uint8_t *)data + nbytes;
length -= nbytes;
decoder.timestamp += (double)nbytes /
dc.out_audio_format.GetTimeToSize();
if (dc.end_ms > 0 &&
decoder.timestamp >= dc.end_ms / 1000.0)
/* the end of this range has been reached:
stop decoding */
return DecoderCommand::STOP;
}
return DecoderCommand::NONE;
}
DecoderCommand
decoder_tag(Decoder &decoder, InputStream *is,
Tag &&tag)
{
gcc_unused const DecoderControl &dc = decoder.dc;
DecoderCommand cmd;
assert(dc.state == DecoderState::DECODE);
assert(dc.pipe != nullptr);
/* save the tag */
delete decoder.decoder_tag;
decoder.decoder_tag = new Tag(tag);
/* check for a new stream tag */
update_stream_tag(decoder, is);
/* check if we're seeking */
if (decoder_prepare_initial_seek(decoder))
/* during initial seek, no music chunk must be created
until seeking is finished; skip the rest of the
function here */
return DecoderCommand::SEEK;
/* send tag to music pipe */
if (decoder.stream_tag != nullptr) {
/* merge with tag from input stream */
Tag *merged;
merged = Tag::Merge(*decoder.stream_tag,
*decoder.decoder_tag);
cmd = do_send_tag(decoder, *merged);
delete merged;
} else
/* send only the decoder tag */
cmd = do_send_tag(decoder, *decoder.decoder_tag);
return cmd;
}
void
decoder_replay_gain(Decoder &decoder,
const ReplayGainInfo *replay_gain_info)
{
if (replay_gain_info != nullptr) {
static unsigned serial;
if (++serial == 0)
serial = 1;
if (REPLAY_GAIN_OFF != replay_gain_mode) {
ReplayGainMode rgm = replay_gain_mode;
if (rgm != REPLAY_GAIN_ALBUM)
rgm = REPLAY_GAIN_TRACK;
const auto &tuple = replay_gain_info->tuples[rgm];
const auto scale =
tuple.CalculateScale(replay_gain_preamp,
replay_gain_missing_preamp,
replay_gain_limit);
decoder.dc.replay_gain_db = 20.0 * log10f(scale);
}
decoder.replay_gain_info = *replay_gain_info;
decoder.replay_gain_serial = serial;
if (decoder.chunk != nullptr) {
/* flush the current chunk because the new
replay gain values affect the following
samples */
decoder.FlushChunk();
}
} else
decoder.replay_gain_serial = 0;
}
void
decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp)
{
DecoderControl &dc = decoder.dc;
dc.SetMixRamp(std::move(mix_ramp));
}

213
src/decoder/DecoderAPI.hxx Normal file
View File

@@ -0,0 +1,213 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \file
* \brief The MPD Decoder API
*
* This is the public API which is used by decoder plugins to
* communicate with the mpd core.
*/
#ifndef MPD_DECODER_API_HXX
#define MPD_DECODER_API_HXX
// IWYU pragma: begin_exports
#include "check.h"
#include "DecoderCommand.hxx"
#include "DecoderPlugin.hxx"
#include "ReplayGainInfo.hxx"
#include "tag/Tag.hxx"
#include "AudioFormat.hxx"
#include "MixRampInfo.hxx"
#include "ConfigData.hxx"
// IWYU pragma: end_exports
/**
* Notify the player thread that it has finished initialization and
* that it has read the song's meta data.
*
* @param decoder the decoder object
* @param audio_format the audio format which is going to be sent to
* decoder_data()
* @param seekable true if the song is seekable
* @param total_time the total number of seconds in this song; -1 if unknown
*/
void
decoder_initialized(Decoder &decoder,
AudioFormat audio_format,
bool seekable, float total_time);
/**
* Determines the pending decoder command.
*
* @param decoder the decoder object
* @return the current command, or DecoderCommand::NONE if there is no
* command pending
*/
gcc_pure
DecoderCommand
decoder_get_command(Decoder &decoder);
/**
* Called by the decoder when it has performed the requested command
* (dc->command). This function resets dc->command and wakes up the
* player thread.
*
* @param decoder the decoder object
*/
void
decoder_command_finished(Decoder &decoder);
/**
* Call this when you have received the DecoderCommand::SEEK command.
*
* @param decoder the decoder object
* @return the destination position for the week
*/
gcc_pure
double
decoder_seek_where(Decoder &decoder);
/**
* Call this instead of decoder_command_finished() when seeking has
* failed.
*
* @param decoder the decoder object
*/
void
decoder_seek_error(Decoder &decoder);
/**
* Blocking read from the input stream.
*
* @param decoder the decoder object
* @param is the input stream to read from
* @param buffer the destination buffer
* @param length the maximum number of bytes to read
* @return the number of bytes read, or 0 if one of the following
* occurs: end of file; error; command (like SEEK or STOP).
*/
size_t
decoder_read(Decoder *decoder, InputStream &is,
void *buffer, size_t length);
static inline size_t
decoder_read(Decoder &decoder, InputStream &is,
void *buffer, size_t length)
{
return decoder_read(&decoder, is, buffer, length);
}
/**
* Blocking read from the input stream. Attempts to fill the buffer
* completely; there is no partial result.
*
* @return true on success, false on error or command or not enough
* data
*/
bool
decoder_read_full(Decoder *decoder, InputStream &is,
void *buffer, size_t size);
/**
* Skip data on the #InputStream.
*
* @return true on success, false on error or command
*/
bool
decoder_skip(Decoder *decoder, InputStream &is, size_t size);
/**
* Sets the time stamp for the next data chunk [seconds]. The MPD
* core automatically counts it up, and a decoder plugin only needs to
* use this function if it thinks that adding to the time stamp based
* on the buffer size won't work.
*/
void
decoder_timestamp(Decoder &decoder, double t);
/**
* This function is called by the decoder plugin when it has
* successfully decoded block of input data.
*
* @param decoder the decoder object
* @param is an input stream which is buffering while we are waiting
* for the player
* @param data the source buffer
* @param length the number of bytes in the buffer
* @return the current command, or DecoderCommand::NONE if there is no
* command pending
*/
DecoderCommand
decoder_data(Decoder &decoder, InputStream *is,
const void *data, size_t length,
uint16_t kbit_rate);
static inline DecoderCommand
decoder_data(Decoder &decoder, InputStream &is,
const void *data, size_t length,
uint16_t kbit_rate)
{
return decoder_data(decoder, &is, data, length, kbit_rate);
}
/**
* This function is called by the decoder plugin when it has
* successfully decoded a tag.
*
* @param decoder the decoder object
* @param is an input stream which is buffering while we are waiting
* for the player
* @param tag the tag to send
* @return the current command, or DecoderCommand::NONE if there is no
* command pending
*/
DecoderCommand
decoder_tag(Decoder &decoder, InputStream *is, Tag &&tag);
static inline DecoderCommand
decoder_tag(Decoder &decoder, InputStream &is, Tag &&tag)
{
return decoder_tag(decoder, &is, std::move(tag));
}
/**
* Set replay gain values for the following chunks.
*
* @param decoder the decoder object
* @param rgi the replay_gain_info object; may be nullptr to invalidate
* the previous replay gain values
*/
void
decoder_replay_gain(Decoder &decoder,
const ReplayGainInfo *replay_gain_info);
/**
* Store MixRamp tags.
*
* @param decoder the decoder object
* @param mixramp_start the mixramp_start tag; may be nullptr to invalidate
* @param mixramp_end the mixramp_end tag; may be nullptr to invalidate
*/
void
decoder_mixramp(Decoder &decoder, MixRampInfo &&mix_ramp);
#endif

View File

@@ -0,0 +1,168 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "DecoderBuffer.hxx"
#include "DecoderAPI.hxx"
#include "util/ConstBuffer.hxx"
#include "util/VarSize.hxx"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
struct DecoderBuffer {
Decoder *decoder;
InputStream *is;
/** the allocated size of the buffer */
size_t size;
/** the current length of the buffer */
size_t length;
/** number of bytes already consumed at the beginning of the
buffer */
size_t consumed;
/** the actual buffer (dynamic size) */
unsigned char data[sizeof(size_t)];
DecoderBuffer(Decoder *_decoder, InputStream &_is,
size_t _size)
:decoder(_decoder), is(&_is),
size(_size), length(0), consumed(0) {}
};
DecoderBuffer *
decoder_buffer_new(Decoder *decoder, InputStream &is,
size_t size)
{
assert(size > 0);
return NewVarSize<DecoderBuffer>(sizeof(DecoderBuffer::data),
size,
decoder, is, size);
}
void
decoder_buffer_free(DecoderBuffer *buffer)
{
assert(buffer != nullptr);
DeleteVarSize(buffer);
}
bool
decoder_buffer_is_empty(const DecoderBuffer *buffer)
{
return buffer->consumed == buffer->length;
}
bool
decoder_buffer_is_full(const DecoderBuffer *buffer)
{
return buffer->consumed == 0 && buffer->length == buffer->size;
}
void
decoder_buffer_clear(DecoderBuffer *buffer)
{
buffer->length = buffer->consumed = 0;
}
static void
decoder_buffer_shift(DecoderBuffer *buffer)
{
assert(buffer->consumed > 0);
buffer->length -= buffer->consumed;
memmove(buffer->data, buffer->data + buffer->consumed, buffer->length);
buffer->consumed = 0;
}
bool
decoder_buffer_fill(DecoderBuffer *buffer)
{
size_t nbytes;
if (buffer->consumed > 0)
decoder_buffer_shift(buffer);
if (buffer->length >= buffer->size)
/* buffer is full */
return false;
nbytes = decoder_read(buffer->decoder, *buffer->is,
buffer->data + buffer->length,
buffer->size - buffer->length);
if (nbytes == 0)
/* end of file, I/O error or decoder command
received */
return false;
buffer->length += nbytes;
assert(buffer->length <= buffer->size);
return true;
}
ConstBuffer<void>
decoder_buffer_read(const DecoderBuffer *buffer)
{
return {
buffer->data + buffer->consumed,
buffer->length - buffer->consumed
};
}
void
decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes)
{
/* just move the "consumed" pointer - decoder_buffer_shift()
will do the real work later (called by
decoder_buffer_fill()) */
buffer->consumed += nbytes;
assert(buffer->consumed <= buffer->length);
}
bool
decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes)
{
bool success;
/* this could probably be optimized by seeking */
while (true) {
auto data = decoder_buffer_read(buffer);
if (!data.IsEmpty()) {
if (data.size > nbytes)
data.size = nbytes;
decoder_buffer_consume(buffer, data.size);
nbytes -= data.size;
if (nbytes == 0)
return true;
}
success = decoder_buffer_fill(buffer);
if (!success)
return false;
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DECODER_BUFFER_HXX
#define MPD_DECODER_BUFFER_HXX
#include "Compiler.h"
#include <stddef.h>
/**
* This objects handles buffered reads in decoder plugins easily. You
* create a buffer object, and use its high-level methods to fill and
* read it. It will automatically handle shifting the buffer.
*/
struct DecoderBuffer;
struct Decoder;
struct InputStream;
template<typename T> struct ConstBuffer;
/**
* Creates a new buffer.
*
* @param decoder the decoder object, used for decoder_read(), may be nullptr
* @param is the input stream object where we should read from
* @param size the maximum size of the buffer
* @return the new decoder_buffer object
*/
DecoderBuffer *
decoder_buffer_new(Decoder *decoder, InputStream &is,
size_t size);
/**
* Frees resources used by the decoder_buffer object.
*/
void
decoder_buffer_free(DecoderBuffer *buffer);
gcc_pure
bool
decoder_buffer_is_empty(const DecoderBuffer *buffer);
gcc_pure
bool
decoder_buffer_is_full(const DecoderBuffer *buffer);
void
decoder_buffer_clear(DecoderBuffer *buffer);
/**
* Read data from the input_stream and append it to the buffer.
*
* @return true if data was appended; false if there is no data
* available (yet), end of file, I/O error or a decoder command was
* received
*/
bool
decoder_buffer_fill(DecoderBuffer *buffer);
/**
* Reads data from the buffer. This data is not yet consumed, you
* have to call decoder_buffer_consume() to do that. The returned
* buffer becomes invalid after a decoder_buffer_fill() or a
* decoder_buffer_consume() call.
*
* @param buffer the decoder_buffer object
*/
gcc_pure
ConstBuffer<void>
decoder_buffer_read(const DecoderBuffer *buffer);
/**
* Consume (delete, invalidate) a part of the buffer. The "nbytes"
* parameter must not be larger than the length returned by
* decoder_buffer_read().
*
* @param buffer the decoder_buffer object
* @param nbytes the number of bytes to consume
*/
void
decoder_buffer_consume(DecoderBuffer *buffer, size_t nbytes);
/**
* Skips the specified number of bytes, discarding its data.
*
* @param buffer the decoder_buffer object
* @param nbytes the number of bytes to skip
* @return true on success, false on error
*/
bool
decoder_buffer_skip(DecoderBuffer *buffer, size_t nbytes);
#endif

View File

@@ -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

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "DecoderControl.hxx"
#include "MusicPipe.hxx"
#include "DetachedSong.hxx"
#include <assert.h>
DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond)
:mutex(_mutex), client_cond(_client_cond),
state(DecoderState::STOP),
command(DecoderCommand::NONE),
client_is_waiting(false),
song(nullptr),
replay_gain_db(0), replay_gain_prev_db(0) {}
DecoderControl::~DecoderControl()
{
ClearError();
delete song;
}
void
DecoderControl::WaitForDecoder()
{
assert(!client_is_waiting);
client_is_waiting = true;
client_cond.wait(mutex);
assert(client_is_waiting);
client_is_waiting = false;
}
bool
DecoderControl::IsCurrentSong(const DetachedSong &_song) const
{
switch (state) {
case DecoderState::STOP:
case DecoderState::ERROR:
return false;
case DecoderState::START:
case DecoderState::DECODE:
return song->IsSame(_song);
}
assert(false);
gcc_unreachable();
}
void
DecoderControl::Start(DetachedSong *_song,
unsigned _start_ms, unsigned _end_ms,
MusicBuffer &_buffer, MusicPipe &_pipe)
{
assert(_song != nullptr);
assert(_pipe.IsEmpty());
delete song;
song = _song;
start_ms = _start_ms;
end_ms = _end_ms;
buffer = &_buffer;
pipe = &_pipe;
LockSynchronousCommand(DecoderCommand::START);
}
void
DecoderControl::Stop()
{
Lock();
if (command != DecoderCommand::NONE)
/* Attempt to cancel the current command. If it's too
late and the decoder thread is already executing
the old command, we'll call STOP again in this
function (see below). */
SynchronousCommandLocked(DecoderCommand::STOP);
if (state != DecoderState::STOP && state != DecoderState::ERROR)
SynchronousCommandLocked(DecoderCommand::STOP);
Unlock();
}
bool
DecoderControl::Seek(double where)
{
assert(state != DecoderState::START);
assert(where >= 0.0);
if (state == DecoderState::STOP ||
state == DecoderState::ERROR || !seekable)
return false;
seek_where = where;
seek_error = false;
LockSynchronousCommand(DecoderCommand::SEEK);
return !seek_error;
}
void
DecoderControl::Quit()
{
assert(thread.IsDefined());
quit = true;
LockAsynchronousCommand(DecoderCommand::STOP);
thread.Join();
}
void
DecoderControl::CycleMixRamp()
{
previous_mix_ramp = std::move(mix_ramp);
mix_ramp.Clear();
}

View File

@@ -0,0 +1,395 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DECODER_CONTROL_HXX
#define MPD_DECODER_CONTROL_HXX
#include "DecoderCommand.hxx"
#include "AudioFormat.hxx"
#include "MixRampInfo.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
#include "thread/Thread.hxx"
#include "util/Error.hxx"
#include <assert.h>
#include <stdint.h>
/* damn you, windows.h! */
#ifdef ERROR
#undef ERROR
#endif
class DetachedSong;
class MusicBuffer;
class MusicPipe;
enum class DecoderState : uint8_t {
STOP = 0,
START,
DECODE,
/**
* The last "START" command failed, because there was an I/O
* error or because no decoder was able to decode the file.
* This state will only come after START; once the state has
* turned to DECODE, by definition no such error can occur.
*/
ERROR,
};
struct DecoderControl {
/**
* The handle of the decoder thread.
*/
Thread thread;
/**
* This lock protects #state and #command.
*
* This is usually a reference to PlayerControl::mutex, so
* that both player thread and decoder thread share a mutex.
* This simplifies synchronization with #cond and
* #client_cond.
*/
Mutex &mutex;
/**
* Trigger this object after you have modified #command. This
* is also used by the decoder thread to notify the caller
* when it has finished a command.
*/
Cond cond;
/**
* The trigger of this object's client. It is signalled
* whenever an event occurs.
*
* This is usually a reference to PlayerControl::cond.
*/
Cond &client_cond;
DecoderState state;
DecoderCommand command;
/**
* The error that occurred in the decoder thread. This
* attribute is only valid if #state is #DecoderState::ERROR.
* The object must be freed when this object transitions to
* any other state (usually #DecoderState::START).
*/
Error error;
bool quit;
/**
* Is the client currently waiting for the DecoderThread? If
* false, the DecoderThread may omit invoking Cond::signal(),
* reducing the number of system calls.
*/
bool client_is_waiting;
bool seek_error;
bool seekable;
double seek_where;
/** the format of the song file */
AudioFormat in_audio_format;
/** the format being sent to the music pipe */
AudioFormat out_audio_format;
/**
* The song currently being decoded. This attribute is set by
* the player thread, when it sends the #DecoderCommand::START
* command.
*
* This is a duplicate, and must be freed when this attribute
* is cleared.
*/
DetachedSong *song;
/**
* The initial seek position (in milliseconds), e.g. to the
* start of a sub-track described by a CUE file.
*
* This attribute is set by dc_start().
*/
unsigned start_ms;
/**
* The decoder will stop when it reaches this position (in
* milliseconds). 0 means don't stop before the end of the
* file.
*
* This attribute is set by dc_start().
*/
unsigned end_ms;
float total_time;
/** the #music_chunk allocator */
MusicBuffer *buffer;
/**
* The destination pipe for decoded chunks. The caller thread
* owns this object, and is responsible for freeing it.
*/
MusicPipe *pipe;
float replay_gain_db;
float replay_gain_prev_db;
MixRampInfo mix_ramp, previous_mix_ramp;
/**
* @param _mutex see #mutex
* @param _client_cond see #client_cond
*/
DecoderControl(Mutex &_mutex, Cond &_client_cond);
~DecoderControl();
/**
* Locks the object.
*/
void Lock() const {
mutex.lock();
}
/**
* Unlocks the object.
*/
void Unlock() const {
mutex.unlock();
}
/**
* Signals the object. This function is only valid in the
* player thread. The object should be locked prior to
* calling this function.
*/
void Signal() {
cond.signal();
}
/**
* Waits for a signal on the #DecoderControl object. This function
* is only valid in the decoder thread. The object must be locked
* prior to calling this function.
*/
void Wait() {
cond.wait(mutex);
}
/**
* Waits for a signal from the decoder thread. This object
* must be locked prior to calling this function. This method
* is only valid in the player thread.
*
* Caller must hold the lock.
*/
void WaitForDecoder();
bool IsIdle() const {
return state == DecoderState::STOP ||
state == DecoderState::ERROR;
}
gcc_pure
bool LockIsIdle() const {
Lock();
bool result = IsIdle();
Unlock();
return result;
}
bool IsStarting() const {
return state == DecoderState::START;
}
gcc_pure
bool LockIsStarting() const {
Lock();
bool result = IsStarting();
Unlock();
return result;
}
bool HasFailed() const {
assert(command == DecoderCommand::NONE);
return state == DecoderState::ERROR;
}
gcc_pure
bool LockHasFailed() const {
Lock();
bool result = HasFailed();
Unlock();
return result;
}
/**
* Checks whether an error has occurred, and if so, returns a
* copy of the #Error object.
*
* Caller must lock the object.
*/
gcc_pure
Error GetError() const {
assert(command == DecoderCommand::NONE);
assert(state != DecoderState::ERROR || error.IsDefined());
Error result;
if (state == DecoderState::ERROR)
result.Set(error);
return result;
}
/**
* Like dc_get_error(), but locks and unlocks the object.
*/
gcc_pure
Error LockGetError() const {
Lock();
Error result = GetError();
Unlock();
return result;
}
/**
* Clear the error condition and free the #Error object (if any).
*
* Caller must lock the object.
*/
void ClearError() {
if (state == DecoderState::ERROR) {
error.Clear();
state = DecoderState::STOP;
}
}
/**
* Check if the specified song is currently being decoded. If the
* decoder is not running currently (or being started), then this
* function returns false in any case.
*
* Caller must lock the object.
*/
gcc_pure
bool IsCurrentSong(const DetachedSong &_song) const;
gcc_pure
bool LockIsCurrentSong(const DetachedSong &_song) const {
Lock();
const bool result = IsCurrentSong(_song);
Unlock();
return result;
}
private:
/**
* Wait for the command to be finished by the decoder thread.
*
* To be called from the client thread. Caller must lock the
* object.
*/
void WaitCommandLocked() {
while (command != DecoderCommand::NONE)
WaitForDecoder();
}
/**
* Send a command to the decoder thread and synchronously wait
* for it to finish.
*
* To be called from the client thread. Caller must lock the
* object.
*/
void SynchronousCommandLocked(DecoderCommand cmd) {
command = cmd;
Signal();
WaitCommandLocked();
}
/**
* Send a command to the decoder thread and synchronously wait
* for it to finish.
*
* To be called from the client thread. This method locks the
* object.
*/
void LockSynchronousCommand(DecoderCommand cmd) {
Lock();
ClearError();
SynchronousCommandLocked(cmd);
Unlock();
}
void LockAsynchronousCommand(DecoderCommand cmd) {
Lock();
command = cmd;
Signal();
Unlock();
}
public:
/**
* Start the decoder.
*
* @param song the song to be decoded; the given instance will be
* owned and freed by the decoder
* @param start_ms see #DecoderControl
* @param end_ms see #DecoderControl
* @param pipe the pipe which receives the decoded chunks (owned by
* the caller)
*/
void Start(DetachedSong *song, unsigned start_ms, unsigned end_ms,
MusicBuffer &buffer, MusicPipe &pipe);
void Stop();
bool Seek(double where);
void Quit();
const char *GetMixRampStart() const {
return mix_ramp.GetStart();
}
const char *GetMixRampEnd() const {
return mix_ramp.GetEnd();
}
const char *GetMixRampPreviousEnd() const {
return previous_mix_ramp.GetEnd();
}
void SetMixRamp(MixRampInfo &&new_value) {
mix_ramp = std::move(new_value);
}
/**
* Move mixramp_end to mixramp_prev_end and clear
* mixramp_start/mixramp_end.
*/
void CycleMixRamp();
};
#endif

View File

@@ -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");

View File

@@ -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

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "DecoderInternal.hxx"
#include "DecoderControl.hxx"
#include "pcm/PcmConvert.hxx"
#include "MusicPipe.hxx"
#include "MusicBuffer.hxx"
#include "MusicChunk.hxx"
#include "tag/Tag.hxx"
#include <assert.h>
Decoder::~Decoder()
{
/* caller must flush the chunk */
assert(chunk == nullptr);
if (convert != nullptr) {
convert->Close();
delete convert;
}
delete song_tag;
delete stream_tag;
delete decoder_tag;
}
/**
* All chunks are full of decoded data; wait for the player to free
* one.
*/
static DecoderCommand
need_chunks(DecoderControl &dc)
{
if (dc.command == DecoderCommand::NONE)
dc.Wait();
return dc.command;
}
struct music_chunk *
Decoder::GetChunk()
{
DecoderCommand cmd;
if (chunk != nullptr)
return chunk;
do {
chunk = dc.buffer->Allocate();
if (chunk != nullptr) {
chunk->replay_gain_serial = replay_gain_serial;
if (replay_gain_serial != 0)
chunk->replay_gain_info = replay_gain_info;
return chunk;
}
dc.Lock();
cmd = need_chunks(dc);
dc.Unlock();
} while (cmd == DecoderCommand::NONE);
return nullptr;
}
void
Decoder::FlushChunk()
{
assert(chunk != nullptr);
if (chunk->IsEmpty())
dc.buffer->Return(chunk);
else
dc.pipe->Push(chunk);
chunk = nullptr;
dc.Lock();
if (dc.client_is_waiting)
dc.client_cond.signal();
dc.Unlock();
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DECODER_INTERNAL_HXX
#define MPD_DECODER_INTERNAL_HXX
#include "ReplayGainInfo.hxx"
#include "util/Error.hxx"
class PcmConvert;
struct DecoderControl;
struct Tag;
struct Decoder {
DecoderControl &dc;
/**
* For converting input data to the configured audio format.
* nullptr means no conversion necessary.
*/
PcmConvert *convert;
/**
* The time stamp of the next data chunk, in seconds.
*/
double timestamp;
/**
* Is the initial seek (to the start position of the sub-song)
* pending, or has it been performed already?
*/
bool initial_seek_pending;
/**
* Is the initial seek currently running? During this time,
* the decoder command is SEEK. This flag is set by
* decoder_get_virtual_command(), when the virtual SEEK
* command is generated for the first time.
*/
bool initial_seek_running;
/**
* This flag is set by decoder_seek_where(), and checked by
* decoder_command_finished(). It is used to clean up after
* seeking.
*/
bool seeking;
/**
* The tag from the song object. This is only used for local
* files, because we expect the stream server to send us a new
* tag each time we play it.
*/
Tag *song_tag;
/** the last tag received from the stream */
Tag *stream_tag;
/** the last tag received from the decoder plugin */
Tag *decoder_tag;
/** the chunk currently being written to */
struct music_chunk *chunk;
ReplayGainInfo replay_gain_info;
/**
* A positive serial number for checking if replay gain info
* has changed since the last check.
*/
unsigned replay_gain_serial;
/**
* An error has occurred (in DecoderAPI.cxx), and the plugin
* will be asked to stop.
*/
Error error;
Decoder(DecoderControl &_dc, bool _initial_seek_pending, Tag *_tag)
:dc(_dc),
convert(nullptr),
timestamp(0),
initial_seek_pending(_initial_seek_pending),
initial_seek_running(false),
seeking(false),
song_tag(_tag), stream_tag(nullptr), decoder_tag(nullptr),
chunk(nullptr),
replay_gain_serial(0) {
}
~Decoder();
/**
* Returns the current chunk the decoder writes to, or allocates a new
* chunk if there is none.
*
* @return the chunk, or NULL if we have received a decoder command
*/
music_chunk *GetChunk();
/**
* Flushes the current chunk.
*
* Caller must not lock the #DecoderControl object.
*/
void FlushChunk();
};
#endif

186
src/decoder/DecoderList.cxx Normal file
View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "DecoderList.hxx"
#include "DecoderPlugin.hxx"
#include "ConfigGlobal.hxx"
#include "ConfigData.hxx"
#include "plugins/AudiofileDecoderPlugin.hxx"
#include "plugins/PcmDecoderPlugin.hxx"
#include "plugins/DsdiffDecoderPlugin.hxx"
#include "plugins/DsfDecoderPlugin.hxx"
#include "plugins/FlacDecoderPlugin.h"
#include "plugins/OpusDecoderPlugin.h"
#include "plugins/VorbisDecoderPlugin.h"
#include "plugins/AdPlugDecoderPlugin.h"
#include "plugins/WavpackDecoderPlugin.hxx"
#include "plugins/FfmpegDecoderPlugin.hxx"
#include "plugins/GmeDecoderPlugin.hxx"
#include "plugins/FaadDecoderPlugin.hxx"
#include "plugins/MadDecoderPlugin.hxx"
#include "plugins/SndfileDecoderPlugin.hxx"
#include "plugins/Mpg123DecoderPlugin.hxx"
#include "plugins/WildmidiDecoderPlugin.hxx"
#include "plugins/MikmodDecoderPlugin.hxx"
#include "plugins/ModplugDecoderPlugin.hxx"
#include "plugins/MpcdecDecoderPlugin.hxx"
#include "plugins/FluidsynthDecoderPlugin.hxx"
#include "plugins/SidplayDecoderPlugin.hxx"
#include "system/FatalError.hxx"
#include "util/Macros.hxx"
#include <string.h>
const struct DecoderPlugin *const decoder_plugins[] = {
#ifdef HAVE_MAD
&mad_decoder_plugin,
#endif
#ifdef HAVE_MPG123
&mpg123_decoder_plugin,
#endif
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif
#if defined(HAVE_FLAC)
&oggflac_decoder_plugin,
#endif
#ifdef HAVE_FLAC
&flac_decoder_plugin,
#endif
#ifdef HAVE_OPUS
&opus_decoder_plugin,
#endif
#ifdef ENABLE_SNDFILE
&sndfile_decoder_plugin,
#endif
#ifdef HAVE_AUDIOFILE
&audiofile_decoder_plugin,
#endif
&dsdiff_decoder_plugin,
&dsf_decoder_plugin,
#ifdef HAVE_FAAD
&faad_decoder_plugin,
#endif
#ifdef HAVE_MPCDEC
&mpcdec_decoder_plugin,
#endif
#ifdef HAVE_WAVPACK
&wavpack_decoder_plugin,
#endif
#ifdef HAVE_MODPLUG
&modplug_decoder_plugin,
#endif
#ifdef ENABLE_MIKMOD_DECODER
&mikmod_decoder_plugin,
#endif
#ifdef ENABLE_SIDPLAY
&sidplay_decoder_plugin,
#endif
#ifdef ENABLE_WILDMIDI
&wildmidi_decoder_plugin,
#endif
#ifdef ENABLE_FLUIDSYNTH
&fluidsynth_decoder_plugin,
#endif
#ifdef HAVE_ADPLUG
&adplug_decoder_plugin,
#endif
#ifdef HAVE_FFMPEG
&ffmpeg_decoder_plugin,
#endif
#ifdef HAVE_GME
&gme_decoder_plugin,
#endif
&pcm_decoder_plugin,
nullptr
};
static constexpr unsigned num_decoder_plugins =
ARRAY_SIZE(decoder_plugins) - 1;
/** which plugins have been initialized successfully? */
bool decoder_plugins_enabled[num_decoder_plugins];
const struct DecoderPlugin *
decoder_plugin_from_name(const char *name)
{
return decoder_plugins_find([=](const DecoderPlugin &plugin){
return strcmp(plugin.name, name) == 0;
});
}
/**
* Find the "decoder" configuration block for the specified plugin.
*
* @param plugin_name the name of the decoder plugin
* @return the configuration block, or nullptr if none was configured
*/
static const struct config_param *
decoder_plugin_config(const char *plugin_name)
{
const struct config_param *param = nullptr;
while ((param = config_get_next_param(CONF_DECODER, param)) != nullptr) {
const char *name = param->GetBlockValue("plugin");
if (name == nullptr)
FormatFatalError("decoder configuration without 'plugin' name in line %d",
param->line);
if (strcmp(name, plugin_name) == 0)
return param;
}
return nullptr;
}
void decoder_plugin_init_all(void)
{
struct config_param empty;
for (unsigned i = 0; decoder_plugins[i] != nullptr; ++i) {
const DecoderPlugin &plugin = *decoder_plugins[i];
const struct config_param *param =
decoder_plugin_config(plugin.name);
if (param == nullptr)
param = &empty;
else if (!param->GetBlockValue("enabled", true))
/* the plugin is disabled in mpd.conf */
continue;
if (plugin.Init(*param))
decoder_plugins_enabled[i] = true;
}
}
void decoder_plugin_deinit_all(void)
{
decoder_plugins_for_each_enabled([=](const DecoderPlugin &plugin){
plugin.Finish();
});
}
bool
decoder_plugins_supports_suffix(const char *suffix)
{
return decoder_plugins_try([suffix](const DecoderPlugin &plugin){
return plugin.SupportsSuffix(suffix);
});
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -0,0 +1,180 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DECODER_PLUGIN_HXX
#define MPD_DECODER_PLUGIN_HXX
#include "Compiler.h"
struct config_param;
struct InputStream;
struct tag_handler;
/**
* Opaque handle which the decoder plugin passes to the functions in
* this header.
*/
struct Decoder;
struct DecoderPlugin {
const char *name;
/**
* Initialize the decoder plugin. Optional method.
*
* @param param a configuration block for this plugin, or nullptr
* if none is configured
* @return true if the plugin was initialized successfully,
* false if the plugin is not available
*/
bool (*init)(const config_param &param);
/**
* Deinitialize a decoder plugin which was initialized
* successfully. Optional method.
*/
void (*finish)(void);
/**
* Decode a stream (data read from an #input_stream object).
*
* Either implement this method or file_decode(). If
* possible, it is recommended to implement this method,
* because it is more versatile.
*/
void (*stream_decode)(Decoder &decoder, InputStream &is);
/**
* Decode a local file.
*
* Either implement this method or stream_decode().
*/
void (*file_decode)(Decoder &decoder, const char *path_fs);
/**
* Scan metadata of a file.
*
* @return false if the operation has failed
*/
bool (*scan_file)(const char *path_fs,
const struct tag_handler *handler,
void *handler_ctx);
/**
* Scan metadata of a file.
*
* @return false if the operation has failed
*/
bool (*scan_stream)(InputStream &is,
const struct tag_handler *handler,
void *handler_ctx);
/**
* @brief Return a "virtual" filename for subtracks in
* container formats like flac
* @param const char* pathname full pathname for the file on fs
* @param const unsigned int tnum track number
*
* @return nullptr if there are no multiple files
* a filename for every single track according to tnum (param 2)
* do not include full pathname here, just the "virtual" file
*/
char* (*container_scan)(const char *path_fs, const unsigned int tnum);
/* last element in these arrays must always be a nullptr: */
const char *const*suffixes;
const char *const*mime_types;
/**
* Initialize a decoder plugin.
*
* @param param a configuration block for this plugin, or nullptr if none
* is configured
* @return true if the plugin was initialized successfully, false if
* the plugin is not available
*/
bool Init(const config_param &param) const {
return init != nullptr
? init(param)
: true;
}
/**
* Deinitialize a decoder plugin which was initialized successfully.
*/
void Finish() const {
if (finish != nullptr)
finish();
}
/**
* Decode a stream.
*/
void StreamDecode(Decoder &decoder, InputStream &is) const {
stream_decode(decoder, is);
}
/**
* Decode a file.
*/
void FileDecode(Decoder &decoder, const char *path_fs) const {
file_decode(decoder, path_fs);
}
/**
* Read the tag of a file.
*/
bool ScanFile(const char *path_fs,
const tag_handler &handler, void *handler_ctx) const {
return scan_file != nullptr
? scan_file(path_fs, &handler, handler_ctx)
: false;
}
/**
* Read the tag of a stream.
*/
bool ScanStream(InputStream &is,
const tag_handler &handler, void *handler_ctx) const {
return scan_stream != nullptr
? scan_stream(is, &handler, handler_ctx)
: false;
}
/**
* return "virtual" tracks in a container
*/
char *ContainerScan(const char *path, const unsigned int tnum) const {
return container_scan(path, tnum);
}
/**
* Does the plugin announce the specified file name suffix?
*/
gcc_pure gcc_nonnull_all
bool SupportsSuffix(const char *suffix) const;
/**
* Does the plugin announce the specified MIME type?
*/
gcc_pure gcc_nonnull_all
bool SupportsMimeType(const char *mime_type) const;
};
#endif

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "DecoderPrint.hxx"
#include "DecoderList.hxx"
#include "DecoderPlugin.hxx"
#include "Client.hxx"
#include <functional>
#include <assert.h>
static void
decoder_plugin_print(Client &client,
const DecoderPlugin &plugin)
{
const char *const*p;
assert(plugin.name != nullptr);
client_printf(client, "plugin: %s\n", plugin.name);
if (plugin.suffixes != nullptr)
for (p = plugin.suffixes; *p != nullptr; ++p)
client_printf(client, "suffix: %s\n", *p);
if (plugin.mime_types != nullptr)
for (p = plugin.mime_types; *p != nullptr; ++p)
client_printf(client, "mime_type: %s\n", *p);
}
void
decoder_list_print(Client &client)
{
using namespace std::placeholders;
const auto f = std::bind(decoder_plugin_print, std::ref(client), _1);
decoder_plugins_for_each_enabled(f);
}

View File

@@ -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

View File

@@ -0,0 +1,476 @@
/*
* Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "DecoderThread.hxx"
#include "DecoderControl.hxx"
#include "DecoderInternal.hxx"
#include "DecoderError.hxx"
#include "DecoderPlugin.hxx"
#include "DetachedSong.hxx"
#include "system/FatalError.hxx"
#include "Mapper.hxx"
#include "fs/Traits.hxx"
#include "fs/AllocatedPath.hxx"
#include "DecoderAPI.hxx"
#include "InputStream.hxx"
#include "DecoderList.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "thread/Name.hxx"
#include "tag/ApeReplayGain.hxx"
#include "Log.hxx"
#include <functional>
static constexpr Domain decoder_thread_domain("decoder_thread");
/**
* Marks the current decoder command as "finished" and notifies the
* player thread.
*
* @param dc the #DecoderControl object; must be locked
*/
static void
decoder_command_finished_locked(DecoderControl &dc)
{
assert(dc.command != DecoderCommand::NONE);
dc.command = DecoderCommand::NONE;
dc.client_cond.signal();
}
/**
* Opens the input stream with input_stream::Open(), and waits until
* the stream gets ready. If a decoder STOP command is received
* during that, it cancels the operation (but does not close the
* stream).
*
* Unlock the decoder before calling this function.
*
* @return an input_stream on success or if #DecoderCommand::STOP is
* received, nullptr on error
*/
static InputStream *
decoder_input_stream_open(DecoderControl &dc, const char *uri)
{
Error error;
InputStream *is = InputStream::Open(uri, dc.mutex, dc.cond, error);
if (is == nullptr) {
if (error.IsDefined())
LogError(error);
return nullptr;
}
/* wait for the input stream to become ready; its metadata
will be available then */
dc.Lock();
is->Update();
while (!is->ready &&
dc.command != DecoderCommand::STOP) {
dc.Wait();
is->Update();
}
if (!is->Check(error)) {
dc.Unlock();
LogError(error);
return nullptr;
}
dc.Unlock();
return is;
}
static bool
decoder_stream_decode(const DecoderPlugin &plugin,
Decoder &decoder,
InputStream &input_stream)
{
assert(plugin.stream_decode != nullptr);
assert(decoder.stream_tag == nullptr);
assert(decoder.decoder_tag == nullptr);
assert(input_stream.ready);
assert(decoder.dc.state == DecoderState::START);
FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name);
if (decoder.dc.command == DecoderCommand::STOP)
return true;
/* rewind the stream, so each plugin gets a fresh start */
input_stream.Rewind(IgnoreError());
decoder.dc.Unlock();
FormatThreadName("decoder:%s", plugin.name);
plugin.StreamDecode(decoder, input_stream);
SetThreadName("decoder");
decoder.dc.Lock();
assert(decoder.dc.state == DecoderState::START ||
decoder.dc.state == DecoderState::DECODE);
return decoder.dc.state != DecoderState::START;
}
static bool
decoder_file_decode(const DecoderPlugin &plugin,
Decoder &decoder, const char *path)
{
assert(plugin.file_decode != nullptr);
assert(decoder.stream_tag == nullptr);
assert(decoder.decoder_tag == nullptr);
assert(path != nullptr);
assert(PathTraitsFS::IsAbsolute(path));
assert(decoder.dc.state == DecoderState::START);
FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name);
if (decoder.dc.command == DecoderCommand::STOP)
return true;
decoder.dc.Unlock();
FormatThreadName("decoder:%s", plugin.name);
plugin.FileDecode(decoder, path);
SetThreadName("decoder");
decoder.dc.Lock();
assert(decoder.dc.state == DecoderState::START ||
decoder.dc.state == DecoderState::DECODE);
return decoder.dc.state != DecoderState::START;
}
gcc_pure
static bool
decoder_check_plugin_mime(const DecoderPlugin &plugin, const InputStream &is)
{
assert(plugin.stream_decode != nullptr);
return !is.mime.empty() && plugin.SupportsMimeType(is.mime.c_str());
}
gcc_pure
static bool
decoder_check_plugin_suffix(const DecoderPlugin &plugin, const char *suffix)
{
assert(plugin.stream_decode != nullptr);
return suffix != nullptr && plugin.SupportsSuffix(suffix);
}
gcc_pure
static bool
decoder_check_plugin(const DecoderPlugin &plugin, const InputStream &is,
const char *suffix)
{
return plugin.stream_decode != nullptr &&
(decoder_check_plugin_mime(plugin, is) ||
decoder_check_plugin_suffix(plugin, suffix));
}
static bool
decoder_run_stream_plugin(Decoder &decoder, InputStream &is,
const char *suffix,
const DecoderPlugin &plugin,
bool &tried_r)
{
if (!decoder_check_plugin(plugin, is, suffix))
return false;
tried_r = true;
return decoder_stream_decode(plugin, decoder, is);
}
static bool
decoder_run_stream_locked(Decoder &decoder, InputStream &is,
const char *uri, bool &tried_r)
{
const char *const suffix = uri_get_suffix(uri);
using namespace std::placeholders;
const auto f = std::bind(decoder_run_stream_plugin,
std::ref(decoder), std::ref(is), suffix,
_1, std::ref(tried_r));
return decoder_plugins_try(f);
}
/**
* Try decoding a stream, using the fallback plugin.
*/
static bool
decoder_run_stream_fallback(Decoder &decoder, InputStream &is)
{
const struct DecoderPlugin *plugin;
plugin = decoder_plugin_from_name("mad");
return plugin != nullptr && plugin->stream_decode != nullptr &&
decoder_stream_decode(*plugin, decoder, is);
}
/**
* Try decoding a stream.
*/
static bool
decoder_run_stream(Decoder &decoder, const char *uri)
{
DecoderControl &dc = decoder.dc;
InputStream *input_stream;
bool success;
dc.Unlock();
input_stream = decoder_input_stream_open(dc, uri);
if (input_stream == nullptr) {
dc.Lock();
return false;
}
dc.Lock();
bool tried = false;
success = dc.command == DecoderCommand::STOP ||
decoder_run_stream_locked(decoder, *input_stream, uri,
tried) ||
/* fallback to mp3: this is needed for bastard streams
that don't have a suffix or set the mimeType */
(!tried &&
decoder_run_stream_fallback(decoder, *input_stream));
dc.Unlock();
input_stream->Close();
dc.Lock();
return success;
}
/**
* Attempt to load replay gain data, and pass it to
* decoder_replay_gain().
*/
static void
decoder_load_replay_gain(Decoder &decoder, const char *path_fs)
{
ReplayGainInfo info;
if (replay_gain_ape_read(Path::FromFS(path_fs), info))
decoder_replay_gain(decoder, &info);
}
static bool
TryDecoderFile(Decoder &decoder, const char *path_fs, const char *suffix,
const DecoderPlugin &plugin)
{
if (!plugin.SupportsSuffix(suffix))
return false;
DecoderControl &dc = decoder.dc;
if (plugin.file_decode != nullptr) {
dc.Lock();
if (decoder_file_decode(plugin, decoder, path_fs))
return true;
dc.Unlock();
} else if (plugin.stream_decode != nullptr) {
InputStream *input_stream =
decoder_input_stream_open(dc, path_fs);
if (input_stream == nullptr)
return false;
dc.Lock();
bool success = decoder_stream_decode(plugin, decoder,
*input_stream);
dc.Unlock();
input_stream->Close();
if (success) {
dc.Lock();
return true;
}
}
return false;
}
/**
* Try decoding a file.
*/
static bool
decoder_run_file(Decoder &decoder, const char *path_fs)
{
const char *suffix = uri_get_suffix(path_fs);
if (suffix == nullptr)
return false;
DecoderControl &dc = decoder.dc;
dc.Unlock();
decoder_load_replay_gain(decoder, path_fs);
if (decoder_plugins_try([&decoder, path_fs, suffix](const DecoderPlugin &plugin){
return TryDecoderFile(decoder, path_fs, suffix,
plugin);
}))
return true;
dc.Lock();
return false;
}
static void
decoder_run_song(DecoderControl &dc,
const DetachedSong &song, const char *uri)
{
Decoder decoder(dc, dc.start_ms > 0,
new Tag(song.GetTag()));
int ret;
dc.state = DecoderState::START;
decoder_command_finished_locked(dc);
ret = song.IsFile()
? decoder_run_file(decoder, uri)
: decoder_run_stream(decoder, uri);
dc.Unlock();
/* flush the last chunk */
if (decoder.chunk != nullptr)
decoder.FlushChunk();
dc.Lock();
if (decoder.error.IsDefined()) {
/* copy the Error from sruct Decoder to
DecoderControl */
dc.state = DecoderState::ERROR;
dc.error = std::move(decoder.error);
} else if (ret)
dc.state = DecoderState::STOP;
else {
dc.state = DecoderState::ERROR;
const char *error_uri = song.GetURI();
const std::string allocated = uri_remove_auth(error_uri);
if (!allocated.empty())
error_uri = allocated.c_str();
dc.error.Format(decoder_domain,
"Failed to decode %s", error_uri);
}
dc.client_cond.signal();
}
static void
decoder_run(DecoderControl &dc)
{
dc.ClearError();
assert(dc.song != nullptr);
const DetachedSong &song = *dc.song;
const std::string uri = song.IsFile()
? map_song_fs(song).c_str()
: song.GetRealURI();
if (uri.empty()) {
dc.state = DecoderState::ERROR;
dc.error.Set(decoder_domain, "Failed to map song");
decoder_command_finished_locked(dc);
return;
}
decoder_run_song(dc, song, uri.c_str());
}
static void
decoder_task(void *arg)
{
DecoderControl &dc = *(DecoderControl *)arg;
SetThreadName("decoder");
dc.Lock();
do {
assert(dc.state == DecoderState::STOP ||
dc.state == DecoderState::ERROR);
switch (dc.command) {
case DecoderCommand::START:
dc.CycleMixRamp();
dc.replay_gain_prev_db = dc.replay_gain_db;
dc.replay_gain_db = 0;
/* fall through */
case DecoderCommand::SEEK:
decoder_run(dc);
break;
case DecoderCommand::STOP:
decoder_command_finished_locked(dc);
break;
case DecoderCommand::NONE:
dc.Wait();
break;
}
} while (dc.command != DecoderCommand::NONE || !dc.quit);
dc.Unlock();
}
void
decoder_thread_start(DecoderControl &dc)
{
assert(!dc.thread.IsDefined());
dc.quit = false;
Error error;
if (!dc.thread.Start(decoder_task, &dc, error))
FatalError(error);
}

View File

@@ -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

View File

@@ -20,7 +20,7 @@
#include "config.h"
#include "AdPlugDecoderPlugin.h"
#include "tag/TagHandler.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "util/Error.hxx"
#include "util/Macros.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "AudiofileDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"

View File

@@ -25,7 +25,7 @@
#include "config.h"
#include "DsdLib.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "tag/TagId3.hxx"
#include "util/Error.hxx"

View File

@@ -28,7 +28,7 @@
#include "config.h"
#include "DsdiffDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/bit_reverse.h"

View File

@@ -29,7 +29,7 @@
#include "config.h"
#include "DsfDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/bit_reverse.h"

View File

@@ -19,8 +19,8 @@
#include "config.h"
#include "FaadDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "DecoderBuffer.hxx"
#include "../DecoderAPI.hxx"
#include "../DecoderBuffer.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"

View File

@@ -22,7 +22,7 @@
#include "config.h"
#include "FfmpegDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "FfmpegMetaData.hxx"
#include "tag/TagHandler.hxx"
#include "InputStream.hxx"

View File

@@ -25,7 +25,7 @@
#define MPD_FLAC_COMMON_HXX
#include "FlacInput.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "pcm/PcmBuffer.hxx"
#include <FLAC/stream_decoder.h>

View File

@@ -20,7 +20,7 @@
#include "config.h"
#include "FlacInput.hxx"
#include "FlacDomain.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "util/Error.hxx"
#include "Log.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "FluidsynthDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "GmeDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "util/Alloc.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "MadDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "ConfigGlobal.hxx"
#include "tag/TagId3.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "MikmodDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "tag/TagHandler.hxx"
#include "system/FatalError.hxx"
#include "util/Domain.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "ModplugDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "tag/TagHandler.hxx"
#include "system/FatalError.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "MpcdecDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h" /* must be first for large file support */
#include "Mpg123DecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "util/Error.hxx"

View File

@@ -23,7 +23,7 @@
#include "config.h"
#include "OggCodec.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include <string.h>

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "OggUtil.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
bool
OggFeed(ogg_sync_state &oy, Decoder *decoder,

View File

@@ -24,7 +24,7 @@
#include "OpusTags.hxx"
#include "OggFind.hxx"
#include "OggSyncState.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "OggCodec.hxx"
#include "tag/TagHandler.hxx"
#include "tag/TagBuilder.hxx"

View File

@@ -18,8 +18,8 @@
*/
#include "config.h"
#include "decoder/PcmDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "PcmDecoderPlugin.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "util/Error.hxx"
#include "util/ByteReverse.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "SndfileDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"

View File

@@ -21,7 +21,7 @@
#include "VorbisDecoderPlugin.h"
#include "VorbisComments.hxx"
#include "VorbisDomain.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "OggCodec.hxx"
#include "util/Error.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "WavpackDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "WildmidiDecoderPlugin.hxx"
#include "DecoderAPI.hxx"
#include "../DecoderAPI.hxx"
#include "tag/TagHandler.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"