decoder/*: move to decoder/plugins/
This commit is contained in:
597
src/decoder/DecoderAPI.cxx
Normal file
597
src/decoder/DecoderAPI.cxx
Normal 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
213
src/decoder/DecoderAPI.hxx
Normal 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
|
168
src/decoder/DecoderBuffer.cxx
Normal file
168
src/decoder/DecoderBuffer.cxx
Normal 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;
|
||||
}
|
||||
}
|
111
src/decoder/DecoderBuffer.hxx
Normal file
111
src/decoder/DecoderBuffer.hxx
Normal 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
|
32
src/decoder/DecoderCommand.hxx
Normal file
32
src/decoder/DecoderCommand.hxx
Normal 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
|
140
src/decoder/DecoderControl.cxx
Normal file
140
src/decoder/DecoderControl.cxx
Normal 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();
|
||||
}
|
395
src/decoder/DecoderControl.hxx
Normal file
395
src/decoder/DecoderControl.hxx
Normal 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
|
23
src/decoder/DecoderError.cxx
Normal file
23
src/decoder/DecoderError.cxx
Normal 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");
|
25
src/decoder/DecoderError.hxx
Normal file
25
src/decoder/DecoderError.hxx
Normal 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
|
101
src/decoder/DecoderInternal.cxx
Normal file
101
src/decoder/DecoderInternal.cxx
Normal 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();
|
||||
}
|
125
src/decoder/DecoderInternal.hxx
Normal file
125
src/decoder/DecoderInternal.hxx
Normal 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
186
src/decoder/DecoderList.cxx
Normal 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 = ∅
|
||||
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);
|
||||
});
|
||||
}
|
89
src/decoder/DecoderList.hxx
Normal file
89
src/decoder/DecoderList.hxx
Normal 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
|
42
src/decoder/DecoderPlugin.cxx
Normal file
42
src/decoder/DecoderPlugin.cxx
Normal 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);
|
||||
}
|
180
src/decoder/DecoderPlugin.hxx
Normal file
180
src/decoder/DecoderPlugin.hxx
Normal 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 ¶m);
|
||||
|
||||
/**
|
||||
* Deinitialize a decoder plugin which was initialized
|
||||
* successfully. Optional method.
|
||||
*/
|
||||
void (*finish)(void);
|
||||
|
||||
/**
|
||||
* Decode a stream (data read from an #input_stream object).
|
||||
*
|
||||
* Either implement this method or file_decode(). If
|
||||
* possible, it is recommended to implement this method,
|
||||
* because it is more versatile.
|
||||
*/
|
||||
void (*stream_decode)(Decoder &decoder, InputStream &is);
|
||||
|
||||
/**
|
||||
* Decode a local file.
|
||||
*
|
||||
* Either implement this method or stream_decode().
|
||||
*/
|
||||
void (*file_decode)(Decoder &decoder, const char *path_fs);
|
||||
|
||||
/**
|
||||
* Scan metadata of a file.
|
||||
*
|
||||
* @return false if the operation has failed
|
||||
*/
|
||||
bool (*scan_file)(const char *path_fs,
|
||||
const struct tag_handler *handler,
|
||||
void *handler_ctx);
|
||||
|
||||
/**
|
||||
* Scan metadata of a file.
|
||||
*
|
||||
* @return false if the operation has failed
|
||||
*/
|
||||
bool (*scan_stream)(InputStream &is,
|
||||
const struct tag_handler *handler,
|
||||
void *handler_ctx);
|
||||
|
||||
/**
|
||||
* @brief Return a "virtual" filename for subtracks in
|
||||
* container formats like flac
|
||||
* @param const char* pathname full pathname for the file on fs
|
||||
* @param const unsigned int tnum track number
|
||||
*
|
||||
* @return nullptr if there are no multiple files
|
||||
* a filename for every single track according to tnum (param 2)
|
||||
* do not include full pathname here, just the "virtual" file
|
||||
*/
|
||||
char* (*container_scan)(const char *path_fs, const unsigned int tnum);
|
||||
|
||||
/* last element in these arrays must always be a nullptr: */
|
||||
const char *const*suffixes;
|
||||
const char *const*mime_types;
|
||||
|
||||
/**
|
||||
* Initialize a decoder plugin.
|
||||
*
|
||||
* @param param a configuration block for this plugin, or nullptr if none
|
||||
* is configured
|
||||
* @return true if the plugin was initialized successfully, false if
|
||||
* the plugin is not available
|
||||
*/
|
||||
bool Init(const config_param ¶m) const {
|
||||
return init != nullptr
|
||||
? init(param)
|
||||
: true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deinitialize a decoder plugin which was initialized successfully.
|
||||
*/
|
||||
void Finish() const {
|
||||
if (finish != nullptr)
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a stream.
|
||||
*/
|
||||
void StreamDecode(Decoder &decoder, InputStream &is) const {
|
||||
stream_decode(decoder, is);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a file.
|
||||
*/
|
||||
void FileDecode(Decoder &decoder, const char *path_fs) const {
|
||||
file_decode(decoder, path_fs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the tag of a file.
|
||||
*/
|
||||
bool ScanFile(const char *path_fs,
|
||||
const tag_handler &handler, void *handler_ctx) const {
|
||||
return scan_file != nullptr
|
||||
? scan_file(path_fs, &handler, handler_ctx)
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the tag of a stream.
|
||||
*/
|
||||
bool ScanStream(InputStream &is,
|
||||
const tag_handler &handler, void *handler_ctx) const {
|
||||
return scan_stream != nullptr
|
||||
? scan_stream(is, &handler, handler_ctx)
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* return "virtual" tracks in a container
|
||||
*/
|
||||
char *ContainerScan(const char *path, const unsigned int tnum) const {
|
||||
return container_scan(path, tnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the plugin announce the specified file name suffix?
|
||||
*/
|
||||
gcc_pure gcc_nonnull_all
|
||||
bool SupportsSuffix(const char *suffix) const;
|
||||
|
||||
/**
|
||||
* Does the plugin announce the specified MIME type?
|
||||
*/
|
||||
gcc_pure gcc_nonnull_all
|
||||
bool SupportsMimeType(const char *mime_type) const;
|
||||
};
|
||||
|
||||
#endif
|
55
src/decoder/DecoderPrint.cxx
Normal file
55
src/decoder/DecoderPrint.cxx
Normal 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);
|
||||
}
|
28
src/decoder/DecoderPrint.hxx
Normal file
28
src/decoder/DecoderPrint.hxx
Normal 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
|
476
src/decoder/DecoderThread.cxx
Normal file
476
src/decoder/DecoderThread.cxx
Normal 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);
|
||||
}
|
28
src/decoder/DecoderThread.hxx
Normal file
28
src/decoder/DecoderThread.hxx
Normal 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
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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>
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -23,7 +23,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "OggCodec.hxx"
|
||||
#include "DecoderAPI.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
|
||||
#include <string.h>
|
||||
|
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "OggUtil.hxx"
|
||||
#include "DecoderAPI.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
|
||||
bool
|
||||
OggFeed(ogg_sync_state &oy, Decoder *decoder,
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
@@ -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"
|
Reference in New Issue
Block a user