322b061632
From now on, struct Song will be used by the database only, and DetachedSong will be used by everybody else. DetachedSong is easier to use, but Song has lower overhead.
598 lines
13 KiB
C++
598 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2003-2013 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));
|
|
}
|