From d40d9e7c5f718936f3a126a8b7c3c9e1bb1c6290 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Tue, 23 Apr 2013 15:48:41 +0200 Subject: Move all decoder c files under decoder/ --- src/Makefile | 14 +- src/decoder/decoder_api.c | 505 +++++++++++++++++++++++++++++++++++++++++ src/decoder/decoder_control.c | 191 ++++++++++++++++ src/decoder/decoder_internal.c | 77 +++++++ src/decoder/decoder_list.c | 159 +++++++++++++ src/decoder/decoder_plugin.c | 47 ++++ src/decoder/decoder_print.c | 53 +++++ src/decoder/decoder_thread.c | 505 +++++++++++++++++++++++++++++++++++++++++ src/decoder_api.c | 505 ----------------------------------------- src/decoder_control.c | 191 ---------------- src/decoder_internal.c | 77 ------- src/decoder_list.c | 159 ------------- src/decoder_plugin.c | 47 ---- src/decoder_print.c | 53 ----- src/decoder_thread.c | 505 ----------------------------------------- 15 files changed, 1544 insertions(+), 1544 deletions(-) create mode 100644 src/decoder/decoder_api.c create mode 100644 src/decoder/decoder_control.c create mode 100644 src/decoder/decoder_internal.c create mode 100644 src/decoder/decoder_list.c create mode 100644 src/decoder/decoder_plugin.c create mode 100644 src/decoder/decoder_print.c create mode 100644 src/decoder/decoder_thread.c delete mode 100644 src/decoder_api.c delete mode 100644 src/decoder_control.c delete mode 100644 src/decoder_internal.c delete mode 100644 src/decoder_list.c delete mode 100644 src/decoder_plugin.c delete mode 100644 src/decoder_print.c delete mode 100644 src/decoder_thread.c diff --git a/src/Makefile b/src/Makefile index 3a64e173..e056717f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -31,13 +31,6 @@ OBJS = aiff.o \ db_print.o \ db_save.o \ dbUtils.o \ - decoder_api.o \ - decoder_control.o \ - decoder_internal.o \ - decoder_list.o \ - decoder_plugin.o \ - decoder_print.o \ - decoder_thread.o \ directory.o \ directory_save.o \ encoder_list.o \ @@ -160,6 +153,13 @@ OBJS = aiff.o \ AudioCompress/compress.o \ cue/cue_parser.o \ db/simple_db_plugin.o \ + decoder/decoder_api.o \ + decoder/decoder_control.o \ + decoder/decoder_internal.o \ + decoder/decoder_list.o \ + decoder/decoder_plugin.o \ + decoder/decoder_print.o \ + decoder/decoder_thread.o \ decoder/libav_decoder_plugin.o \ filter/autoconvert_filter_plugin.o \ filter/chain_filter_plugin.o \ diff --git a/src/decoder/decoder_api.c b/src/decoder/decoder_api.c new file mode 100644 index 00000000..662b9d8e --- /dev/null +++ b/src/decoder/decoder_api.c @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2003-2011 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 "decoder_api.h" +#include "decoder_internal.h" +#include "decoder_control.h" +#include "audio_config.h" +#include "song.h" +#include "pipe.h" +#include "chunk.h" +#include "replay_gain_config.h" + +#include + +#include +#include +#include + +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "decoder" + +void decoder_initialized(struct decoder *decoder, + const struct audio_format *audio_format, + bool seekable, float total_time) +{ + struct decoder_control *dc = decoder->dc; + struct audio_format_string af_string; + + assert(dc->state == DECODE_STATE_START); + assert(dc->pipe != NULL); + assert(decoder != NULL); + assert(decoder->stream_tag == NULL); + assert(decoder->decoder_tag == NULL); + assert(!decoder->seeking); + assert(audio_format != NULL); + assert(audio_format_defined(audio_format)); + assert(audio_format_valid(audio_format)); + + dc->in_audio_format = *audio_format; + getOutputAudioFormat(audio_format, &dc->out_audio_format); + + dc->seekable = seekable; + dc->total_time = total_time; + + decoder_lock(dc); + dc->state = DECODE_STATE_DECODE; + g_cond_signal(dc->client_cond); + decoder_unlock(dc); + + g_debug("audio_format=%s, seekable=%s", + audio_format_to_string(&dc->in_audio_format, &af_string), + seekable ? "true" : "false"); + + if (!audio_format_equals(&dc->in_audio_format, + &dc->out_audio_format)) + g_debug("converting to %s", + audio_format_to_string(&dc->out_audio_format, + &af_string)); +} + +/** + * Checks if we need an "initial seek". If so, then the initial seek + * is prepared, and the function returns true. + */ +static bool decoder_prepare_initial_seek(struct decoder *decoder) +{ + const struct decoder_control *dc = decoder->dc; + assert(dc->pipe != NULL); + + if (dc->state != DECODE_STATE_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 == DECODE_COMMAND_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. + */ +static enum decoder_command decoder_get_virtual_command(struct decoder *decoder) +{ + const struct decoder_control *dc = decoder->dc; + assert(dc->pipe != NULL); + + if (decoder_prepare_initial_seek(decoder)) + return DECODE_COMMAND_SEEK; + + return dc->command; +} + +enum decoder_command decoder_get_command(struct decoder *decoder) +{ + return decoder_get_virtual_command(decoder); +} + +void decoder_command_finished(struct decoder *decoder) +{ + struct decoder_control *dc = decoder->dc; + + decoder_lock(dc); + + assert(dc->command != DECODE_COMMAND_NONE || + decoder->initial_seek_running); + assert(dc->command != DECODE_COMMAND_SEEK || + decoder->initial_seek_running || + dc->seek_error || decoder->seeking); + assert(dc->pipe != NULL); + + if (decoder->initial_seek_running) { + assert(!decoder->seeking); + assert(music_pipe_empty(dc->pipe)); + + decoder->initial_seek_running = false; + decoder->timestamp = dc->start_ms / 1000.; + decoder_unlock(dc); + return; + } + + if (decoder->seeking) { + decoder->seeking = false; + + music_pipe_clear(dc->pipe); + + decoder->timestamp = dc->seek_where; + } + + dc->command = DECODE_COMMAND_NONE; + g_cond_signal(dc->client_cond); + decoder_unlock(dc); +} + +double decoder_seek_where(struct decoder * decoder) +{ + const struct decoder_control *dc = decoder->dc; + + assert(dc->pipe != NULL); + + if (decoder->initial_seek_running) + return dc->start_ms / 1000.; + + assert(dc->command == DECODE_COMMAND_SEEK); + + decoder->seeking = true; + + return dc->seek_where; +} + +void decoder_seek_error(struct decoder * decoder) +{ + struct decoder_control *dc = decoder->dc; + + assert(dc->pipe != NULL); + + 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 == DECODE_COMMAND_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". + */ +static inline bool decoder_check_cancel_read(const struct decoder *decoder) +{ + if (decoder == NULL) + return false; + + const struct decoder_control *dc = decoder->dc; + if (dc->command == DECODE_COMMAND_NONE) + return false; + + /* ignore the SEEK command during initialization, the plugin + should handle that after it has initialized successfully */ + if (dc->command == DECODE_COMMAND_SEEK && + (dc->state == DECODE_STATE_START || decoder->seeking)) + return false; + + return true; +} + +size_t decoder_read(struct decoder *decoder, + struct input_stream *is, + void *buffer, size_t length) +{ + /* XXX don't allow decoder==NULL */ + GError *error = NULL; + size_t nbytes; + + assert(decoder == NULL || + decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); + assert(is != NULL); + assert(buffer != NULL); + + if (length == 0) + return 0; + + input_stream_lock(is); + + while (true) { + if (decoder_check_cancel_read(decoder)) { + input_stream_unlock(is); + return 0; + } + + if (input_stream_available(is)) + break; + + g_cond_wait(is->cond, is->mutex); + } + + nbytes = input_stream_read(is, buffer, length, &error); + assert(nbytes == 0 || error == NULL); + assert(nbytes > 0 || error != NULL || input_stream_eof(is)); + + if (G_UNLIKELY(nbytes == 0 && error != NULL)) { + g_warning("%s", error->message); + g_error_free(error); + } + + input_stream_unlock(is); + + return nbytes; +} + +void decoder_timestamp(struct decoder *decoder, double t) +{ + assert(decoder != NULL); + 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 enum decoder_command do_send_tag(struct decoder *decoder, const struct tag *tag) +{ + struct music_chunk *chunk; + + chunk = decoder_get_chunk(decoder); + if (chunk == NULL) { + assert(decoder->dc->command != DECODE_COMMAND_NONE); + return decoder->dc->command; + } + + chunk->tag = tag_dup(tag); + return DECODE_COMMAND_NONE; +} + +static bool update_stream_tag(struct decoder *decoder, struct input_stream *is) +{ + struct tag *tag; + + tag = is != NULL ? input_stream_lock_tag(is) : NULL; + if (tag == NULL) { + tag = decoder->song_tag; + if (tag == NULL) + return false; + + /* no stream tag present - submit the song tag + instead */ + decoder->song_tag = NULL; + } + + if (decoder->stream_tag != NULL) + tag_free(decoder->stream_tag); + + decoder->stream_tag = tag; + return true; +} + +enum decoder_command decoder_data(struct decoder *decoder, + struct input_stream *is, + AVFrame *frame, + uint16_t kbit_rate) +{ + struct decoder_control *dc = decoder->dc; + enum decoder_command cmd = DECODE_COMMAND_NONE; + int nb_samples = frame->nb_samples; + struct music_chunk *chunk; + + assert(dc->state == DECODE_STATE_DECODE); + assert(dc->pipe != NULL); + + decoder_lock(dc); + cmd = decoder_get_virtual_command(decoder); + decoder_unlock(dc); + + if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK || + !frame->nb_samples) + goto finish; + + /* send stream tags */ + + if (update_stream_tag(decoder, is)) { + if (decoder->decoder_tag != NULL) { + /* merge with tag from decoder plugin */ + struct tag *tag; + + tag = tag_merge(decoder->decoder_tag, + decoder->stream_tag); + cmd = do_send_tag(decoder, tag); + tag_free(tag); + } else + /* send only the stream tag */ + cmd = do_send_tag(decoder, decoder->stream_tag); + + if (cmd != DECODE_COMMAND_NONE) + goto finish; + } + + if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) { + AVFrame *tmp = pcm_convert(&decoder->conv_state, + &dc->in_audio_format, &dc->out_audio_format, frame); + if (!tmp) { + /* the PCM conversion has failed - stop + playback, since we have no better way to + bail out */ + g_warning("Error converting to output format.\n"); + cmd = DECODE_COMMAND_STOP; + goto finish; + } + av_frame_free(&frame); + frame = tmp; + } + + // FIXME support planar formats everywhere + assert(!av_sample_fmt_is_planar(frame->format)); + + chunk = decoder_get_chunk(decoder); + if (!chunk) { + cmd = (dc->command == DECODE_COMMAND_NONE) ? + DECODE_COMMAND_STOP : dc->command; + goto finish; + } + chunk->frame = frame; + chunk->bit_rate = kbit_rate; + chunk->times = decoder->timestamp - dc->song->start_ms / 1000.0; +#ifndef NDEBUG + chunk->audio_format = dc->out_audio_format; +#endif + + frame = NULL; + + music_pipe_push(dc->pipe, chunk); + g_cond_signal(dc->client_cond); + + decoder->timestamp += (double)nb_samples / dc->in_audio_format.sample_rate; + + if (dc->end_ms > 0 && + decoder->timestamp >= dc->end_ms / 1000.0) { + /* the end of this range has been reached: + stop decoding */ + cmd = DECODE_COMMAND_STOP; + } + +finish: + av_frame_free(&frame); + return cmd; +} + +enum decoder_command decoder_tag(struct decoder *decoder, struct input_stream *is, + const struct tag *tag) +{ + const struct decoder_control *dc = decoder->dc; + enum decoder_command cmd; + + assert(dc->state == DECODE_STATE_DECODE); + assert(dc->pipe != NULL); + assert(tag != NULL); + + /* save the tag */ + + if (decoder->decoder_tag != NULL) + tag_free(decoder->decoder_tag); + decoder->decoder_tag = tag_dup(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 DECODE_COMMAND_SEEK; + + /* send tag to music pipe */ + + if (decoder->stream_tag != NULL) { + /* merge with tag from input stream */ + struct tag *merged; + + merged = tag_merge(decoder->stream_tag, decoder->decoder_tag); + cmd = do_send_tag(decoder, merged); + tag_free(merged); + } else + /* send only the decoder tag */ + cmd = do_send_tag(decoder, tag); + + return cmd; +} + +float decoder_replay_gain(struct decoder *decoder, + const struct replay_gain_info *replay_gain_info) +{ + float return_db = 0; + assert(decoder != NULL); + + if (replay_gain_info != NULL) { + static unsigned serial; + if (++serial == 0) + serial = 1; + + if (REPLAY_GAIN_OFF != replay_gain_mode) { + return_db = 20.0 * log10f( + replay_gain_tuple_scale( + &replay_gain_info->tuples[replay_gain_get_real_mode()], + replay_gain_preamp, replay_gain_missing_preamp, + replay_gain_limit)); + } + + decoder->replay_gain_info = *replay_gain_info; + decoder->replay_gain_serial = serial; + } else + decoder->replay_gain_serial = 0; + + return return_db; +} + +void decoder_mixramp(struct decoder *decoder, float replay_gain_db, + char *mixramp_start, char *mixramp_end) +{ + assert(decoder != NULL); + struct decoder_control *dc = decoder->dc; + assert(dc != NULL); + + dc->replay_gain_db = replay_gain_db; + dc_mixramp_start(dc, mixramp_start); + dc_mixramp_end(dc, mixramp_end); +} diff --git a/src/decoder/decoder_control.c b/src/decoder/decoder_control.c new file mode 100644 index 00000000..5c952d9d --- /dev/null +++ b/src/decoder/decoder_control.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2003-2011 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 "decoder_control.h" +#include "pipe.h" + +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "decoder_control" + +struct decoder_control * +dc_new(GCond *client_cond) +{ + struct decoder_control *dc = g_new(struct decoder_control, 1); + + dc->thread = NULL; + + dc->mutex = g_mutex_new(); + dc->cond = g_cond_new(); + dc->client_cond = client_cond; + + dc->state = DECODE_STATE_STOP; + dc->command = DECODE_COMMAND_NONE; + + dc->replay_gain_db = 0; + dc->replay_gain_prev_db = 0; + dc->mixramp_start = NULL; + dc->mixramp_end = NULL; + dc->mixramp_prev_end = NULL; + + return dc; +} + +void +dc_free(struct decoder_control *dc) +{ + g_cond_free(dc->cond); + g_mutex_free(dc->mutex); + g_free(dc->mixramp_start); + g_free(dc->mixramp_end); + g_free(dc->mixramp_prev_end); + g_free(dc); +} + +static void +dc_command_wait_locked(struct decoder_control *dc) +{ + while (dc->command != DECODE_COMMAND_NONE) + g_cond_wait(dc->client_cond, dc->mutex); +} + +static void +dc_command_locked(struct decoder_control *dc, enum decoder_command cmd) +{ + dc->command = cmd; + decoder_signal(dc); + dc_command_wait_locked(dc); +} + +static void +dc_command(struct decoder_control *dc, enum decoder_command cmd) +{ + decoder_lock(dc); + dc_command_locked(dc, cmd); + decoder_unlock(dc); +} + +static void +dc_command_async(struct decoder_control *dc, enum decoder_command cmd) +{ + decoder_lock(dc); + + dc->command = cmd; + decoder_signal(dc); + + decoder_unlock(dc); +} + +void dc_start(struct decoder_control *dc, struct song *song, + unsigned start_ms, unsigned end_ms, + int buffer_samples, struct music_pipe *pipe) +{ + assert(song != NULL); + assert(pipe != NULL); + assert(music_pipe_empty(pipe)); + + dc->song = song; + dc->start_ms = start_ms; + dc->end_ms = end_ms; + dc->buffer_samples = buffer_samples; + dc->pipe = pipe; + dc_command(dc, DECODE_COMMAND_START); +} + +void +dc_stop(struct decoder_control *dc) +{ + decoder_lock(dc); + + if (dc->command != DECODE_COMMAND_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). */ + dc_command_locked(dc, DECODE_COMMAND_STOP); + + if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR) + dc_command_locked(dc, DECODE_COMMAND_STOP); + + decoder_unlock(dc); +} + +bool +dc_seek(struct decoder_control *dc, double where) +{ + assert(dc->state != DECODE_STATE_START); + assert(where >= 0.0); + + if (dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR || !dc->seekable) + return false; + + dc->seek_where = where; + dc->seek_error = false; + dc_command(dc, DECODE_COMMAND_SEEK); + + if (dc->seek_error) + return false; + + return true; +} + +void +dc_quit(struct decoder_control *dc) +{ + assert(dc->thread != NULL); + + dc->quit = true; + dc_command_async(dc, DECODE_COMMAND_STOP); + + g_thread_join(dc->thread); + dc->thread = NULL; +} + +void +dc_mixramp_start(struct decoder_control *dc, char *mixramp_start) +{ + assert(dc != NULL); + + g_free(dc->mixramp_start); + dc->mixramp_start = mixramp_start; + g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL"); +} + +void +dc_mixramp_end(struct decoder_control *dc, char *mixramp_end) +{ + assert(dc != NULL); + + g_free(dc->mixramp_end); + dc->mixramp_end = mixramp_end; + g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL"); +} + +void +dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end) +{ + assert(dc != NULL); + + g_free(dc->mixramp_prev_end); + dc->mixramp_prev_end = mixramp_prev_end; + g_debug("mixramp_prev_end = %s", mixramp_prev_end ? mixramp_prev_end : "NULL"); +} diff --git a/src/decoder/decoder_internal.c b/src/decoder/decoder_internal.c new file mode 100644 index 00000000..cd8f03c1 --- /dev/null +++ b/src/decoder/decoder_internal.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2011 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 "decoder_internal.h" +#include "decoder_control.h" +#include "pipe.h" +#include "input_stream.h" +#include "chunk.h" + +#include + +/** + * All chunks are full of decoded data; wait for the player to free + * one. + */ +static enum decoder_command +need_chunks(struct decoder_control *dc, bool do_wait) +{ + if (dc->command == DECODE_COMMAND_STOP || + dc->command == DECODE_COMMAND_SEEK) + return dc->command; + + if (do_wait) { + decoder_wait(dc); + g_cond_signal(dc->client_cond); + + return dc->command; + } + + return DECODE_COMMAND_NONE; +} + +struct music_chunk *decoder_get_chunk(struct decoder *decoder) +{ + struct decoder_control *dc = decoder->dc; + struct music_chunk *chunk; + enum decoder_command cmd; + + assert(decoder != NULL); + + do { + if (music_pipe_size(dc->pipe) <= dc->buffer_samples) { + chunk = music_chunk_alloc(); + if (!chunk) + return NULL; + + chunk->replay_gain_serial = decoder->replay_gain_serial; + if (decoder->replay_gain_serial != 0) + chunk->replay_gain_info = decoder->replay_gain_info; + + return chunk; + } + + decoder_lock(dc); + cmd = need_chunks(dc, true); + decoder_unlock(dc); + } while (cmd == DECODE_COMMAND_NONE); + + return NULL; +} diff --git a/src/decoder/decoder_list.c b/src/decoder/decoder_list.c new file mode 100644 index 00000000..d9437e86 --- /dev/null +++ b/src/decoder/decoder_list.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2003-2012 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 "decoder_list.h" +#include "decoder_plugin.h" +#include "utils.h" +#include "conf.h" +#include "mpd_error.h" + +#include + +#include + +extern const struct decoder_plugin libav_decoder_plugin; + +const struct decoder_plugin *const decoder_plugins[] = { + &libav_decoder_plugin, + NULL +}; + +enum { + num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1, +}; + +/** which plugins have been initialized successfully? */ +bool decoder_plugins_enabled[num_decoder_plugins]; + +static unsigned +decoder_plugin_index(const struct decoder_plugin *plugin) +{ + unsigned i = 0; + + while (decoder_plugins[i] != plugin) + ++i; + + return i; +} + +static unsigned +decoder_plugin_next_index(const struct decoder_plugin *plugin) +{ + return plugin == 0 + ? 0 /* start with first plugin */ + : decoder_plugin_index(plugin) + 1; +} + +const struct decoder_plugin * +decoder_plugin_from_suffix(const char *suffix, + const struct decoder_plugin *plugin) +{ + if (suffix == NULL) + return NULL; + + for (unsigned i = decoder_plugin_next_index(plugin); + decoder_plugins[i] != NULL; ++i) { + plugin = decoder_plugins[i]; + if (decoder_plugins_enabled[i] && + decoder_plugin_supports_suffix(plugin, suffix)) + return plugin; + } + + return NULL; +} + +const struct decoder_plugin * +decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) +{ + static unsigned i = num_decoder_plugins; + + if (mimeType == NULL) + return NULL; + + if (!next) + i = 0; + for (; decoder_plugins[i] != NULL; ++i) { + const struct decoder_plugin *plugin = decoder_plugins[i]; + if (decoder_plugins_enabled[i] && + decoder_plugin_supports_mime_type(plugin, mimeType)) { + ++i; + return plugin; + } + } + + return NULL; +} + +const struct decoder_plugin * +decoder_plugin_from_name(const char *name) +{ + decoder_plugins_for_each_enabled(plugin) + if (strcmp(plugin->name, name) == 0) + return plugin; + + return NULL; +} + +/** + * Find the "decoder" configuration block for the specified plugin. + * + * @param plugin_name the name of the decoder plugin + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +decoder_plugin_config(const char *plugin_name) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_DECODER, param)) != NULL) { + const char *name = + config_get_block_string(param, "plugin", NULL); + if (name == NULL) + MPD_ERROR("decoder configuration without 'plugin' name in line %d", + param->line); + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return NULL; +} + +void decoder_plugin_init_all(void) +{ + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { + const struct decoder_plugin *plugin = decoder_plugins[i]; + const struct config_param *param = + decoder_plugin_config(plugin->name); + + if (!config_get_block_bool(param, "enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (decoder_plugin_init(plugin, param)) + decoder_plugins_enabled[i] = true; + } +} + +void decoder_plugin_deinit_all(void) +{ + decoder_plugins_for_each_enabled(plugin) + decoder_plugin_finish(plugin); +} diff --git a/src/decoder/decoder_plugin.c b/src/decoder/decoder_plugin.c new file mode 100644 index 00000000..d32043f0 --- /dev/null +++ b/src/decoder/decoder_plugin.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2011 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 "decoder_plugin.h" +#include "string_util.h" + +#include + +bool +decoder_plugin_supports_suffix(const struct decoder_plugin *plugin, + const char *suffix) +{ + assert(plugin != NULL); + assert(suffix != NULL); + + return plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix); + +} + +bool +decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin, + const char *mime_type) +{ + assert(plugin != NULL); + assert(mime_type != NULL); + + return plugin->mime_types != NULL && + string_array_contains(plugin->mime_types, mime_type); +} diff --git a/src/decoder/decoder_print.c b/src/decoder/decoder_print.c new file mode 100644 index 00000000..e14477ed --- /dev/null +++ b/src/decoder/decoder_print.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2011 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 "decoder_print.h" +#include "decoder_list.h" +#include "decoder_plugin.h" +#include "client.h" + +#include + +static void +decoder_plugin_print(struct client *client, + const struct decoder_plugin *plugin) +{ + const char *const*p; + + assert(plugin != NULL); + assert(plugin->name != NULL); + + client_printf(client, "plugin: %s\n", plugin->name); + + if (plugin->suffixes != NULL) + for (p = plugin->suffixes; *p != NULL; ++p) + client_printf(client, "suffix: %s\n", *p); + + if (plugin->mime_types != NULL) + for (p = plugin->mime_types; *p != NULL; ++p) + client_printf(client, "mime_type: %s\n", *p); +} + +void +decoder_list_print(struct client *client) +{ + decoder_plugins_for_each_enabled(plugin) + decoder_plugin_print(client, plugin); +} diff --git a/src/decoder/decoder_thread.c b/src/decoder/decoder_thread.c new file mode 100644 index 00000000..964f1318 --- /dev/null +++ b/src/decoder/decoder_thread.c @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2003-2011 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 "decoder_thread.h" +#include "decoder_control.h" +#include "decoder_internal.h" +#include "decoder_list.h" +#include "decoder_plugin.h" +#include "decoder_api.h" +#include "replay_gain_ape.h" +#include "input_stream.h" +#include "pipe.h" +#include "song.h" +#include "tag.h" +#include "mapper.h" +#include "path.h" +#include "uri.h" +#include "mpd_error.h" + +#include + +#include +#include /* for SEEK_SET */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "decoder_thread" + +/** + * Marks the current decoder command as "finished" and notifies the + * player thread. + * + * @param dc the #decoder_control object; must be locked + */ +static void +decoder_command_finished_locked(struct decoder_control *dc) +{ + assert(dc->command != DECODE_COMMAND_NONE); + + dc->command = DECODE_COMMAND_NONE; + + g_cond_signal(dc->client_cond); +} + +/** + * 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 #DECODE_COMMAND_STOP is + * received, NULL on error + */ +static struct input_stream * +decoder_input_stream_open(struct decoder_control *dc, const char *uri) +{ + GError *error = NULL; + struct input_stream *is; + + is = input_stream_open(uri, dc->mutex, dc->cond, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + /* wait for the input stream to become ready; its metadata + will be available then */ + + decoder_lock(dc); + + input_stream_update(is); + while (!is->ready && + dc->command != DECODE_COMMAND_STOP) { + decoder_wait(dc); + + input_stream_update(is); + } + + if (!input_stream_check(is, &error)) { + decoder_unlock(dc); + + g_warning("%s", error->message); + g_error_free(error); + + return NULL; + } + + decoder_unlock(dc); + + return is; +} + +static bool +decoder_stream_decode(const struct decoder_plugin *plugin, + struct decoder *decoder, + struct input_stream *input_stream) +{ + assert(plugin != NULL); + assert(plugin->stream_decode != NULL); + assert(decoder != NULL); + assert(decoder->stream_tag == NULL); + assert(decoder->decoder_tag == NULL); + assert(input_stream != NULL); + assert(input_stream->ready); + assert(decoder->dc->state == DECODE_STATE_START); + + g_debug("probing plugin %s", plugin->name); + + if (decoder->dc->command == DECODE_COMMAND_STOP) + return true; + + /* rewind the stream, so each plugin gets a fresh start */ + input_stream_seek(input_stream, 0, SEEK_SET, NULL); + + decoder_unlock(decoder->dc); + + decoder_plugin_stream_decode(plugin, decoder, input_stream); + + decoder_lock(decoder->dc); + + assert(decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); + + return decoder->dc->state != DECODE_STATE_START; +} + +static bool +decoder_file_decode(const struct decoder_plugin *plugin, + struct decoder *decoder, const char *path) +{ + assert(plugin != NULL); + assert(plugin->file_decode != NULL); + assert(decoder != NULL); + assert(decoder->stream_tag == NULL); + assert(decoder->decoder_tag == NULL); + assert(path != NULL); + assert(g_path_is_absolute(path)); + assert(decoder->dc->state == DECODE_STATE_START); + + g_debug("probing plugin %s", plugin->name); + + if (decoder->dc->command == DECODE_COMMAND_STOP) + return true; + + decoder_unlock(decoder->dc); + + decoder_plugin_file_decode(plugin, decoder, path); + + decoder_lock(decoder->dc); + + assert(decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); + + return decoder->dc->state != DECODE_STATE_START; +} + +/** + * Hack to allow tracking const decoder plugins in a GSList. + */ +static inline gpointer +deconst_plugin(const struct decoder_plugin *plugin) +{ + union { + const struct decoder_plugin *in; + gpointer out; + } u = { .in = plugin }; + + return u.out; +} + +/** + * Try decoding a stream, using plugins matching the stream's MIME type. + * + * @param tried_r a list of plugins which were tried + */ +static bool +decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is, + GSList **tried_r) +{ + assert(tried_r != NULL); + + const struct decoder_plugin *plugin; + unsigned int next = 0; + + if (is->mime == NULL) + return false; + + while ((plugin = decoder_plugin_from_mime_type(is->mime, next++))) { + if (plugin->stream_decode == NULL) + continue; + + if (g_slist_find(*tried_r, plugin) != NULL) + /* don't try a plugin twice */ + continue; + + if (decoder_stream_decode(plugin, decoder, is)) + return true; + + *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); + } + + return false; +} + +/** + * Try decoding a stream, using plugins matching the stream's URI + * suffix. + * + * @param tried_r a list of plugins which were tried + */ +static bool +decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is, + const char *uri, GSList **tried_r) +{ + assert(tried_r != NULL); + + const char *suffix = uri_get_suffix(uri); + const struct decoder_plugin *plugin = NULL; + + if (suffix == NULL) + return false; + + while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { + if (plugin->stream_decode == NULL) + continue; + + if (g_slist_find(*tried_r, plugin) != NULL) + /* don't try a plugin twice */ + continue; + + if (decoder_stream_decode(plugin, decoder, is)) + return true; + + *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); + } + + return false; +} + +/** + * Try decoding a stream, using the fallback plugin. + */ +static bool +decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is) +{ + const struct decoder_plugin *plugin; + + plugin = decoder_plugin_from_name("mad"); + return plugin != NULL && plugin->stream_decode != NULL && + decoder_stream_decode(plugin, decoder, is); +} + +/** + * Try decoding a stream. + */ +static bool +decoder_run_stream(struct decoder *decoder, const char *uri) +{ + struct decoder_control *dc = decoder->dc; + struct input_stream *input_stream; + bool success; + + decoder_unlock(dc); + + input_stream = decoder_input_stream_open(dc, uri); + if (input_stream == NULL) { + decoder_lock(dc); + return false; + } + + decoder_lock(dc); + + GSList *tried = NULL; + + success = dc->command == DECODE_COMMAND_STOP || + /* first we try mime types: */ + decoder_run_stream_mime_type(decoder, input_stream, &tried) || + /* if that fails, try suffix matching the URL: */ + decoder_run_stream_suffix(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 == NULL && + decoder_run_stream_fallback(decoder, input_stream)); + + g_slist_free(tried); + + decoder_unlock(dc); + input_stream_close(input_stream); + decoder_lock(dc); + + return success; +} + +/** + * Attempt to load replay gain data, and pass it to + * decoder_replay_gain(). + */ +static void +decoder_load_replay_gain(struct decoder *decoder, const char *path_fs) +{ + struct replay_gain_info info; + if (replay_gain_ape_read(path_fs, &info)) + decoder_replay_gain(decoder, &info); +} + +/** + * Try decoding a file. + */ +static bool +decoder_run_file(struct decoder *decoder, const char *path_fs) +{ + struct decoder_control *dc = decoder->dc; + const char *suffix = uri_get_suffix(path_fs); + const struct decoder_plugin *plugin = NULL; + + if (suffix == NULL) + return false; + + decoder_unlock(dc); + + decoder_load_replay_gain(decoder, path_fs); + + while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { + if (plugin->file_decode != NULL) { + decoder_lock(dc); + + if (decoder_file_decode(plugin, decoder, path_fs)) + return true; + + decoder_unlock(dc); + } else if (plugin->stream_decode != NULL) { + struct input_stream *input_stream; + bool success; + + input_stream = decoder_input_stream_open(dc, path_fs); + if (input_stream == NULL) + continue; + + decoder_lock(dc); + + success = decoder_stream_decode(plugin, decoder, + input_stream); + + decoder_unlock(dc); + + input_stream_close(input_stream); + + if (success) { + decoder_lock(dc); + return true; + } + } + } + + decoder_lock(dc); + return false; +} + +static void +decoder_run_song(struct decoder_control *dc, + const struct song *song, const char *uri) +{ + struct decoder decoder = { + .dc = dc, + .initial_seek_pending = dc->start_ms > 0, + .initial_seek_running = false, + }; + int ret; + + decoder.timestamp = 0.0; + decoder.seeking = false; + decoder.song_tag = song->tag != NULL && song_is_file(song) + ? tag_dup(song->tag) : NULL; + decoder.stream_tag = NULL; + decoder.decoder_tag = NULL; + + dc->state = DECODE_STATE_START; + + decoder_command_finished_locked(dc); + + pcm_convert_init(&decoder.conv_state); + + ret = song_is_file(song) + ? decoder_run_file(&decoder, uri) + : decoder_run_stream(&decoder, uri); + + decoder_unlock(dc); + + pcm_convert_deinit(&decoder.conv_state); + + if (decoder.song_tag != NULL) + tag_free(decoder.song_tag); + + if (decoder.stream_tag != NULL) + tag_free(decoder.stream_tag); + + if (decoder.decoder_tag != NULL) + tag_free(decoder.decoder_tag); + + decoder_lock(dc); + + dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; +} + +static void +decoder_run(struct decoder_control *dc) +{ + const struct song *song = dc->song; + char *uri; + + assert(song != NULL); + + if (song_is_file(song)) + uri = map_song_fs(song); + else + uri = song_get_uri(song); + + if (uri == NULL) { + dc->state = DECODE_STATE_ERROR; + decoder_command_finished_locked(dc); + return; + } + + decoder_run_song(dc, song, uri); + g_free(uri); + +} + +static gpointer +decoder_task(gpointer arg) +{ + struct decoder_control *dc = arg; + + decoder_lock(dc); + + do { + assert(dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR); + + switch (dc->command) { + case DECODE_COMMAND_START: + g_debug("clearing mixramp tags"); + dc_mixramp_start(dc, NULL); + dc_mixramp_prev_end(dc, dc->mixramp_end); + dc->mixramp_end = NULL; /* Don't free, it's copied above. */ + dc->replay_gain_prev_db = dc->replay_gain_db; + dc->replay_gain_db = 0; + + /* fall through */ + + case DECODE_COMMAND_SEEK: + decoder_run(dc); + break; + + case DECODE_COMMAND_STOP: + decoder_command_finished_locked(dc); + break; + + case DECODE_COMMAND_NONE: + decoder_wait(dc); + break; + } + } while (dc->command != DECODE_COMMAND_NONE || !dc->quit); + + decoder_unlock(dc); + + return NULL; +} + +void +decoder_thread_start(struct decoder_control *dc) +{ + GError *e = NULL; + + assert(dc->thread == NULL); + + dc->quit = false; + + dc->thread = g_thread_create(decoder_task, dc, true, &e); + if (dc->thread == NULL) + MPD_ERROR("Failed to spawn decoder task: %s", e->message); +} diff --git a/src/decoder_api.c b/src/decoder_api.c deleted file mode 100644 index 662b9d8e..00000000 --- a/src/decoder_api.c +++ /dev/null @@ -1,505 +0,0 @@ -/* - * Copyright (C) 2003-2011 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 "decoder_api.h" -#include "decoder_internal.h" -#include "decoder_control.h" -#include "audio_config.h" -#include "song.h" -#include "pipe.h" -#include "chunk.h" -#include "replay_gain_config.h" - -#include - -#include -#include -#include - -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "decoder" - -void decoder_initialized(struct decoder *decoder, - const struct audio_format *audio_format, - bool seekable, float total_time) -{ - struct decoder_control *dc = decoder->dc; - struct audio_format_string af_string; - - assert(dc->state == DECODE_STATE_START); - assert(dc->pipe != NULL); - assert(decoder != NULL); - assert(decoder->stream_tag == NULL); - assert(decoder->decoder_tag == NULL); - assert(!decoder->seeking); - assert(audio_format != NULL); - assert(audio_format_defined(audio_format)); - assert(audio_format_valid(audio_format)); - - dc->in_audio_format = *audio_format; - getOutputAudioFormat(audio_format, &dc->out_audio_format); - - dc->seekable = seekable; - dc->total_time = total_time; - - decoder_lock(dc); - dc->state = DECODE_STATE_DECODE; - g_cond_signal(dc->client_cond); - decoder_unlock(dc); - - g_debug("audio_format=%s, seekable=%s", - audio_format_to_string(&dc->in_audio_format, &af_string), - seekable ? "true" : "false"); - - if (!audio_format_equals(&dc->in_audio_format, - &dc->out_audio_format)) - g_debug("converting to %s", - audio_format_to_string(&dc->out_audio_format, - &af_string)); -} - -/** - * Checks if we need an "initial seek". If so, then the initial seek - * is prepared, and the function returns true. - */ -static bool decoder_prepare_initial_seek(struct decoder *decoder) -{ - const struct decoder_control *dc = decoder->dc; - assert(dc->pipe != NULL); - - if (dc->state != DECODE_STATE_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 == DECODE_COMMAND_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. - */ -static enum decoder_command decoder_get_virtual_command(struct decoder *decoder) -{ - const struct decoder_control *dc = decoder->dc; - assert(dc->pipe != NULL); - - if (decoder_prepare_initial_seek(decoder)) - return DECODE_COMMAND_SEEK; - - return dc->command; -} - -enum decoder_command decoder_get_command(struct decoder *decoder) -{ - return decoder_get_virtual_command(decoder); -} - -void decoder_command_finished(struct decoder *decoder) -{ - struct decoder_control *dc = decoder->dc; - - decoder_lock(dc); - - assert(dc->command != DECODE_COMMAND_NONE || - decoder->initial_seek_running); - assert(dc->command != DECODE_COMMAND_SEEK || - decoder->initial_seek_running || - dc->seek_error || decoder->seeking); - assert(dc->pipe != NULL); - - if (decoder->initial_seek_running) { - assert(!decoder->seeking); - assert(music_pipe_empty(dc->pipe)); - - decoder->initial_seek_running = false; - decoder->timestamp = dc->start_ms / 1000.; - decoder_unlock(dc); - return; - } - - if (decoder->seeking) { - decoder->seeking = false; - - music_pipe_clear(dc->pipe); - - decoder->timestamp = dc->seek_where; - } - - dc->command = DECODE_COMMAND_NONE; - g_cond_signal(dc->client_cond); - decoder_unlock(dc); -} - -double decoder_seek_where(struct decoder * decoder) -{ - const struct decoder_control *dc = decoder->dc; - - assert(dc->pipe != NULL); - - if (decoder->initial_seek_running) - return dc->start_ms / 1000.; - - assert(dc->command == DECODE_COMMAND_SEEK); - - decoder->seeking = true; - - return dc->seek_where; -} - -void decoder_seek_error(struct decoder * decoder) -{ - struct decoder_control *dc = decoder->dc; - - assert(dc->pipe != NULL); - - 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 == DECODE_COMMAND_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". - */ -static inline bool decoder_check_cancel_read(const struct decoder *decoder) -{ - if (decoder == NULL) - return false; - - const struct decoder_control *dc = decoder->dc; - if (dc->command == DECODE_COMMAND_NONE) - return false; - - /* ignore the SEEK command during initialization, the plugin - should handle that after it has initialized successfully */ - if (dc->command == DECODE_COMMAND_SEEK && - (dc->state == DECODE_STATE_START || decoder->seeking)) - return false; - - return true; -} - -size_t decoder_read(struct decoder *decoder, - struct input_stream *is, - void *buffer, size_t length) -{ - /* XXX don't allow decoder==NULL */ - GError *error = NULL; - size_t nbytes; - - assert(decoder == NULL || - decoder->dc->state == DECODE_STATE_START || - decoder->dc->state == DECODE_STATE_DECODE); - assert(is != NULL); - assert(buffer != NULL); - - if (length == 0) - return 0; - - input_stream_lock(is); - - while (true) { - if (decoder_check_cancel_read(decoder)) { - input_stream_unlock(is); - return 0; - } - - if (input_stream_available(is)) - break; - - g_cond_wait(is->cond, is->mutex); - } - - nbytes = input_stream_read(is, buffer, length, &error); - assert(nbytes == 0 || error == NULL); - assert(nbytes > 0 || error != NULL || input_stream_eof(is)); - - if (G_UNLIKELY(nbytes == 0 && error != NULL)) { - g_warning("%s", error->message); - g_error_free(error); - } - - input_stream_unlock(is); - - return nbytes; -} - -void decoder_timestamp(struct decoder *decoder, double t) -{ - assert(decoder != NULL); - 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 enum decoder_command do_send_tag(struct decoder *decoder, const struct tag *tag) -{ - struct music_chunk *chunk; - - chunk = decoder_get_chunk(decoder); - if (chunk == NULL) { - assert(decoder->dc->command != DECODE_COMMAND_NONE); - return decoder->dc->command; - } - - chunk->tag = tag_dup(tag); - return DECODE_COMMAND_NONE; -} - -static bool update_stream_tag(struct decoder *decoder, struct input_stream *is) -{ - struct tag *tag; - - tag = is != NULL ? input_stream_lock_tag(is) : NULL; - if (tag == NULL) { - tag = decoder->song_tag; - if (tag == NULL) - return false; - - /* no stream tag present - submit the song tag - instead */ - decoder->song_tag = NULL; - } - - if (decoder->stream_tag != NULL) - tag_free(decoder->stream_tag); - - decoder->stream_tag = tag; - return true; -} - -enum decoder_command decoder_data(struct decoder *decoder, - struct input_stream *is, - AVFrame *frame, - uint16_t kbit_rate) -{ - struct decoder_control *dc = decoder->dc; - enum decoder_command cmd = DECODE_COMMAND_NONE; - int nb_samples = frame->nb_samples; - struct music_chunk *chunk; - - assert(dc->state == DECODE_STATE_DECODE); - assert(dc->pipe != NULL); - - decoder_lock(dc); - cmd = decoder_get_virtual_command(decoder); - decoder_unlock(dc); - - if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK || - !frame->nb_samples) - goto finish; - - /* send stream tags */ - - if (update_stream_tag(decoder, is)) { - if (decoder->decoder_tag != NULL) { - /* merge with tag from decoder plugin */ - struct tag *tag; - - tag = tag_merge(decoder->decoder_tag, - decoder->stream_tag); - cmd = do_send_tag(decoder, tag); - tag_free(tag); - } else - /* send only the stream tag */ - cmd = do_send_tag(decoder, decoder->stream_tag); - - if (cmd != DECODE_COMMAND_NONE) - goto finish; - } - - if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) { - AVFrame *tmp = pcm_convert(&decoder->conv_state, - &dc->in_audio_format, &dc->out_audio_format, frame); - if (!tmp) { - /* the PCM conversion has failed - stop - playback, since we have no better way to - bail out */ - g_warning("Error converting to output format.\n"); - cmd = DECODE_COMMAND_STOP; - goto finish; - } - av_frame_free(&frame); - frame = tmp; - } - - // FIXME support planar formats everywhere - assert(!av_sample_fmt_is_planar(frame->format)); - - chunk = decoder_get_chunk(decoder); - if (!chunk) { - cmd = (dc->command == DECODE_COMMAND_NONE) ? - DECODE_COMMAND_STOP : dc->command; - goto finish; - } - chunk->frame = frame; - chunk->bit_rate = kbit_rate; - chunk->times = decoder->timestamp - dc->song->start_ms / 1000.0; -#ifndef NDEBUG - chunk->audio_format = dc->out_audio_format; -#endif - - frame = NULL; - - music_pipe_push(dc->pipe, chunk); - g_cond_signal(dc->client_cond); - - decoder->timestamp += (double)nb_samples / dc->in_audio_format.sample_rate; - - if (dc->end_ms > 0 && - decoder->timestamp >= dc->end_ms / 1000.0) { - /* the end of this range has been reached: - stop decoding */ - cmd = DECODE_COMMAND_STOP; - } - -finish: - av_frame_free(&frame); - return cmd; -} - -enum decoder_command decoder_tag(struct decoder *decoder, struct input_stream *is, - const struct tag *tag) -{ - const struct decoder_control *dc = decoder->dc; - enum decoder_command cmd; - - assert(dc->state == DECODE_STATE_DECODE); - assert(dc->pipe != NULL); - assert(tag != NULL); - - /* save the tag */ - - if (decoder->decoder_tag != NULL) - tag_free(decoder->decoder_tag); - decoder->decoder_tag = tag_dup(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 DECODE_COMMAND_SEEK; - - /* send tag to music pipe */ - - if (decoder->stream_tag != NULL) { - /* merge with tag from input stream */ - struct tag *merged; - - merged = tag_merge(decoder->stream_tag, decoder->decoder_tag); - cmd = do_send_tag(decoder, merged); - tag_free(merged); - } else - /* send only the decoder tag */ - cmd = do_send_tag(decoder, tag); - - return cmd; -} - -float decoder_replay_gain(struct decoder *decoder, - const struct replay_gain_info *replay_gain_info) -{ - float return_db = 0; - assert(decoder != NULL); - - if (replay_gain_info != NULL) { - static unsigned serial; - if (++serial == 0) - serial = 1; - - if (REPLAY_GAIN_OFF != replay_gain_mode) { - return_db = 20.0 * log10f( - replay_gain_tuple_scale( - &replay_gain_info->tuples[replay_gain_get_real_mode()], - replay_gain_preamp, replay_gain_missing_preamp, - replay_gain_limit)); - } - - decoder->replay_gain_info = *replay_gain_info; - decoder->replay_gain_serial = serial; - } else - decoder->replay_gain_serial = 0; - - return return_db; -} - -void decoder_mixramp(struct decoder *decoder, float replay_gain_db, - char *mixramp_start, char *mixramp_end) -{ - assert(decoder != NULL); - struct decoder_control *dc = decoder->dc; - assert(dc != NULL); - - dc->replay_gain_db = replay_gain_db; - dc_mixramp_start(dc, mixramp_start); - dc_mixramp_end(dc, mixramp_end); -} diff --git a/src/decoder_control.c b/src/decoder_control.c deleted file mode 100644 index 5c952d9d..00000000 --- a/src/decoder_control.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2003-2011 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 "decoder_control.h" -#include "pipe.h" - -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "decoder_control" - -struct decoder_control * -dc_new(GCond *client_cond) -{ - struct decoder_control *dc = g_new(struct decoder_control, 1); - - dc->thread = NULL; - - dc->mutex = g_mutex_new(); - dc->cond = g_cond_new(); - dc->client_cond = client_cond; - - dc->state = DECODE_STATE_STOP; - dc->command = DECODE_COMMAND_NONE; - - dc->replay_gain_db = 0; - dc->replay_gain_prev_db = 0; - dc->mixramp_start = NULL; - dc->mixramp_end = NULL; - dc->mixramp_prev_end = NULL; - - return dc; -} - -void -dc_free(struct decoder_control *dc) -{ - g_cond_free(dc->cond); - g_mutex_free(dc->mutex); - g_free(dc->mixramp_start); - g_free(dc->mixramp_end); - g_free(dc->mixramp_prev_end); - g_free(dc); -} - -static void -dc_command_wait_locked(struct decoder_control *dc) -{ - while (dc->command != DECODE_COMMAND_NONE) - g_cond_wait(dc->client_cond, dc->mutex); -} - -static void -dc_command_locked(struct decoder_control *dc, enum decoder_command cmd) -{ - dc->command = cmd; - decoder_signal(dc); - dc_command_wait_locked(dc); -} - -static void -dc_command(struct decoder_control *dc, enum decoder_command cmd) -{ - decoder_lock(dc); - dc_command_locked(dc, cmd); - decoder_unlock(dc); -} - -static void -dc_command_async(struct decoder_control *dc, enum decoder_command cmd) -{ - decoder_lock(dc); - - dc->command = cmd; - decoder_signal(dc); - - decoder_unlock(dc); -} - -void dc_start(struct decoder_control *dc, struct song *song, - unsigned start_ms, unsigned end_ms, - int buffer_samples, struct music_pipe *pipe) -{ - assert(song != NULL); - assert(pipe != NULL); - assert(music_pipe_empty(pipe)); - - dc->song = song; - dc->start_ms = start_ms; - dc->end_ms = end_ms; - dc->buffer_samples = buffer_samples; - dc->pipe = pipe; - dc_command(dc, DECODE_COMMAND_START); -} - -void -dc_stop(struct decoder_control *dc) -{ - decoder_lock(dc); - - if (dc->command != DECODE_COMMAND_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). */ - dc_command_locked(dc, DECODE_COMMAND_STOP); - - if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR) - dc_command_locked(dc, DECODE_COMMAND_STOP); - - decoder_unlock(dc); -} - -bool -dc_seek(struct decoder_control *dc, double where) -{ - assert(dc->state != DECODE_STATE_START); - assert(where >= 0.0); - - if (dc->state == DECODE_STATE_STOP || - dc->state == DECODE_STATE_ERROR || !dc->seekable) - return false; - - dc->seek_where = where; - dc->seek_error = false; - dc_command(dc, DECODE_COMMAND_SEEK); - - if (dc->seek_error) - return false; - - return true; -} - -void -dc_quit(struct decoder_control *dc) -{ - assert(dc->thread != NULL); - - dc->quit = true; - dc_command_async(dc, DECODE_COMMAND_STOP); - - g_thread_join(dc->thread); - dc->thread = NULL; -} - -void -dc_mixramp_start(struct decoder_control *dc, char *mixramp_start) -{ - assert(dc != NULL); - - g_free(dc->mixramp_start); - dc->mixramp_start = mixramp_start; - g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL"); -} - -void -dc_mixramp_end(struct decoder_control *dc, char *mixramp_end) -{ - assert(dc != NULL); - - g_free(dc->mixramp_end); - dc->mixramp_end = mixramp_end; - g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL"); -} - -void -dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end) -{ - assert(dc != NULL); - - g_free(dc->mixramp_prev_end); - dc->mixramp_prev_end = mixramp_prev_end; - g_debug("mixramp_prev_end = %s", mixramp_prev_end ? mixramp_prev_end : "NULL"); -} diff --git a/src/decoder_internal.c b/src/decoder_internal.c deleted file mode 100644 index cd8f03c1..00000000 --- a/src/decoder_internal.c +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2003-2011 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 "decoder_internal.h" -#include "decoder_control.h" -#include "pipe.h" -#include "input_stream.h" -#include "chunk.h" - -#include - -/** - * All chunks are full of decoded data; wait for the player to free - * one. - */ -static enum decoder_command -need_chunks(struct decoder_control *dc, bool do_wait) -{ - if (dc->command == DECODE_COMMAND_STOP || - dc->command == DECODE_COMMAND_SEEK) - return dc->command; - - if (do_wait) { - decoder_wait(dc); - g_cond_signal(dc->client_cond); - - return dc->command; - } - - return DECODE_COMMAND_NONE; -} - -struct music_chunk *decoder_get_chunk(struct decoder *decoder) -{ - struct decoder_control *dc = decoder->dc; - struct music_chunk *chunk; - enum decoder_command cmd; - - assert(decoder != NULL); - - do { - if (music_pipe_size(dc->pipe) <= dc->buffer_samples) { - chunk = music_chunk_alloc(); - if (!chunk) - return NULL; - - chunk->replay_gain_serial = decoder->replay_gain_serial; - if (decoder->replay_gain_serial != 0) - chunk->replay_gain_info = decoder->replay_gain_info; - - return chunk; - } - - decoder_lock(dc); - cmd = need_chunks(dc, true); - decoder_unlock(dc); - } while (cmd == DECODE_COMMAND_NONE); - - return NULL; -} diff --git a/src/decoder_list.c b/src/decoder_list.c deleted file mode 100644 index d9437e86..00000000 --- a/src/decoder_list.c +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2003-2012 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 "decoder_list.h" -#include "decoder_plugin.h" -#include "utils.h" -#include "conf.h" -#include "mpd_error.h" - -#include - -#include - -extern const struct decoder_plugin libav_decoder_plugin; - -const struct decoder_plugin *const decoder_plugins[] = { - &libav_decoder_plugin, - NULL -}; - -enum { - num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1, -}; - -/** which plugins have been initialized successfully? */ -bool decoder_plugins_enabled[num_decoder_plugins]; - -static unsigned -decoder_plugin_index(const struct decoder_plugin *plugin) -{ - unsigned i = 0; - - while (decoder_plugins[i] != plugin) - ++i; - - return i; -} - -static unsigned -decoder_plugin_next_index(const struct decoder_plugin *plugin) -{ - return plugin == 0 - ? 0 /* start with first plugin */ - : decoder_plugin_index(plugin) + 1; -} - -const struct decoder_plugin * -decoder_plugin_from_suffix(const char *suffix, - const struct decoder_plugin *plugin) -{ - if (suffix == NULL) - return NULL; - - for (unsigned i = decoder_plugin_next_index(plugin); - decoder_plugins[i] != NULL; ++i) { - plugin = decoder_plugins[i]; - if (decoder_plugins_enabled[i] && - decoder_plugin_supports_suffix(plugin, suffix)) - return plugin; - } - - return NULL; -} - -const struct decoder_plugin * -decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) -{ - static unsigned i = num_decoder_plugins; - - if (mimeType == NULL) - return NULL; - - if (!next) - i = 0; - for (; decoder_plugins[i] != NULL; ++i) { - const struct decoder_plugin *plugin = decoder_plugins[i]; - if (decoder_plugins_enabled[i] && - decoder_plugin_supports_mime_type(plugin, mimeType)) { - ++i; - return plugin; - } - } - - return NULL; -} - -const struct decoder_plugin * -decoder_plugin_from_name(const char *name) -{ - decoder_plugins_for_each_enabled(plugin) - if (strcmp(plugin->name, name) == 0) - return plugin; - - return NULL; -} - -/** - * Find the "decoder" configuration block for the specified plugin. - * - * @param plugin_name the name of the decoder plugin - * @return the configuration block, or NULL if none was configured - */ -static const struct config_param * -decoder_plugin_config(const char *plugin_name) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_DECODER, param)) != NULL) { - const char *name = - config_get_block_string(param, "plugin", NULL); - if (name == NULL) - MPD_ERROR("decoder configuration without 'plugin' name in line %d", - param->line); - - if (strcmp(name, plugin_name) == 0) - return param; - } - - return NULL; -} - -void decoder_plugin_init_all(void) -{ - for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { - const struct decoder_plugin *plugin = decoder_plugins[i]; - const struct config_param *param = - decoder_plugin_config(plugin->name); - - if (!config_get_block_bool(param, "enabled", true)) - /* the plugin is disabled in mpd.conf */ - continue; - - if (decoder_plugin_init(plugin, param)) - decoder_plugins_enabled[i] = true; - } -} - -void decoder_plugin_deinit_all(void) -{ - decoder_plugins_for_each_enabled(plugin) - decoder_plugin_finish(plugin); -} diff --git a/src/decoder_plugin.c b/src/decoder_plugin.c deleted file mode 100644 index d32043f0..00000000 --- a/src/decoder_plugin.c +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2003-2011 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 "decoder_plugin.h" -#include "string_util.h" - -#include - -bool -decoder_plugin_supports_suffix(const struct decoder_plugin *plugin, - const char *suffix) -{ - assert(plugin != NULL); - assert(suffix != NULL); - - return plugin->suffixes != NULL && - string_array_contains(plugin->suffixes, suffix); - -} - -bool -decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin, - const char *mime_type) -{ - assert(plugin != NULL); - assert(mime_type != NULL); - - return plugin->mime_types != NULL && - string_array_contains(plugin->mime_types, mime_type); -} diff --git a/src/decoder_print.c b/src/decoder_print.c deleted file mode 100644 index e14477ed..00000000 --- a/src/decoder_print.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2003-2011 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 "decoder_print.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "client.h" - -#include - -static void -decoder_plugin_print(struct client *client, - const struct decoder_plugin *plugin) -{ - const char *const*p; - - assert(plugin != NULL); - assert(plugin->name != NULL); - - client_printf(client, "plugin: %s\n", plugin->name); - - if (plugin->suffixes != NULL) - for (p = plugin->suffixes; *p != NULL; ++p) - client_printf(client, "suffix: %s\n", *p); - - if (plugin->mime_types != NULL) - for (p = plugin->mime_types; *p != NULL; ++p) - client_printf(client, "mime_type: %s\n", *p); -} - -void -decoder_list_print(struct client *client) -{ - decoder_plugins_for_each_enabled(plugin) - decoder_plugin_print(client, plugin); -} diff --git a/src/decoder_thread.c b/src/decoder_thread.c deleted file mode 100644 index 964f1318..00000000 --- a/src/decoder_thread.c +++ /dev/null @@ -1,505 +0,0 @@ -/* - * Copyright (C) 2003-2011 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 "decoder_thread.h" -#include "decoder_control.h" -#include "decoder_internal.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "decoder_api.h" -#include "replay_gain_ape.h" -#include "input_stream.h" -#include "pipe.h" -#include "song.h" -#include "tag.h" -#include "mapper.h" -#include "path.h" -#include "uri.h" -#include "mpd_error.h" - -#include - -#include -#include /* for SEEK_SET */ - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "decoder_thread" - -/** - * Marks the current decoder command as "finished" and notifies the - * player thread. - * - * @param dc the #decoder_control object; must be locked - */ -static void -decoder_command_finished_locked(struct decoder_control *dc) -{ - assert(dc->command != DECODE_COMMAND_NONE); - - dc->command = DECODE_COMMAND_NONE; - - g_cond_signal(dc->client_cond); -} - -/** - * 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 #DECODE_COMMAND_STOP is - * received, NULL on error - */ -static struct input_stream * -decoder_input_stream_open(struct decoder_control *dc, const char *uri) -{ - GError *error = NULL; - struct input_stream *is; - - is = input_stream_open(uri, dc->mutex, dc->cond, &error); - if (is == NULL) { - if (error != NULL) { - g_warning("%s", error->message); - g_error_free(error); - } - - return NULL; - } - - /* wait for the input stream to become ready; its metadata - will be available then */ - - decoder_lock(dc); - - input_stream_update(is); - while (!is->ready && - dc->command != DECODE_COMMAND_STOP) { - decoder_wait(dc); - - input_stream_update(is); - } - - if (!input_stream_check(is, &error)) { - decoder_unlock(dc); - - g_warning("%s", error->message); - g_error_free(error); - - return NULL; - } - - decoder_unlock(dc); - - return is; -} - -static bool -decoder_stream_decode(const struct decoder_plugin *plugin, - struct decoder *decoder, - struct input_stream *input_stream) -{ - assert(plugin != NULL); - assert(plugin->stream_decode != NULL); - assert(decoder != NULL); - assert(decoder->stream_tag == NULL); - assert(decoder->decoder_tag == NULL); - assert(input_stream != NULL); - assert(input_stream->ready); - assert(decoder->dc->state == DECODE_STATE_START); - - g_debug("probing plugin %s", plugin->name); - - if (decoder->dc->command == DECODE_COMMAND_STOP) - return true; - - /* rewind the stream, so each plugin gets a fresh start */ - input_stream_seek(input_stream, 0, SEEK_SET, NULL); - - decoder_unlock(decoder->dc); - - decoder_plugin_stream_decode(plugin, decoder, input_stream); - - decoder_lock(decoder->dc); - - assert(decoder->dc->state == DECODE_STATE_START || - decoder->dc->state == DECODE_STATE_DECODE); - - return decoder->dc->state != DECODE_STATE_START; -} - -static bool -decoder_file_decode(const struct decoder_plugin *plugin, - struct decoder *decoder, const char *path) -{ - assert(plugin != NULL); - assert(plugin->file_decode != NULL); - assert(decoder != NULL); - assert(decoder->stream_tag == NULL); - assert(decoder->decoder_tag == NULL); - assert(path != NULL); - assert(g_path_is_absolute(path)); - assert(decoder->dc->state == DECODE_STATE_START); - - g_debug("probing plugin %s", plugin->name); - - if (decoder->dc->command == DECODE_COMMAND_STOP) - return true; - - decoder_unlock(decoder->dc); - - decoder_plugin_file_decode(plugin, decoder, path); - - decoder_lock(decoder->dc); - - assert(decoder->dc->state == DECODE_STATE_START || - decoder->dc->state == DECODE_STATE_DECODE); - - return decoder->dc->state != DECODE_STATE_START; -} - -/** - * Hack to allow tracking const decoder plugins in a GSList. - */ -static inline gpointer -deconst_plugin(const struct decoder_plugin *plugin) -{ - union { - const struct decoder_plugin *in; - gpointer out; - } u = { .in = plugin }; - - return u.out; -} - -/** - * Try decoding a stream, using plugins matching the stream's MIME type. - * - * @param tried_r a list of plugins which were tried - */ -static bool -decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is, - GSList **tried_r) -{ - assert(tried_r != NULL); - - const struct decoder_plugin *plugin; - unsigned int next = 0; - - if (is->mime == NULL) - return false; - - while ((plugin = decoder_plugin_from_mime_type(is->mime, next++))) { - if (plugin->stream_decode == NULL) - continue; - - if (g_slist_find(*tried_r, plugin) != NULL) - /* don't try a plugin twice */ - continue; - - if (decoder_stream_decode(plugin, decoder, is)) - return true; - - *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); - } - - return false; -} - -/** - * Try decoding a stream, using plugins matching the stream's URI - * suffix. - * - * @param tried_r a list of plugins which were tried - */ -static bool -decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is, - const char *uri, GSList **tried_r) -{ - assert(tried_r != NULL); - - const char *suffix = uri_get_suffix(uri); - const struct decoder_plugin *plugin = NULL; - - if (suffix == NULL) - return false; - - while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { - if (plugin->stream_decode == NULL) - continue; - - if (g_slist_find(*tried_r, plugin) != NULL) - /* don't try a plugin twice */ - continue; - - if (decoder_stream_decode(plugin, decoder, is)) - return true; - - *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); - } - - return false; -} - -/** - * Try decoding a stream, using the fallback plugin. - */ -static bool -decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is) -{ - const struct decoder_plugin *plugin; - - plugin = decoder_plugin_from_name("mad"); - return plugin != NULL && plugin->stream_decode != NULL && - decoder_stream_decode(plugin, decoder, is); -} - -/** - * Try decoding a stream. - */ -static bool -decoder_run_stream(struct decoder *decoder, const char *uri) -{ - struct decoder_control *dc = decoder->dc; - struct input_stream *input_stream; - bool success; - - decoder_unlock(dc); - - input_stream = decoder_input_stream_open(dc, uri); - if (input_stream == NULL) { - decoder_lock(dc); - return false; - } - - decoder_lock(dc); - - GSList *tried = NULL; - - success = dc->command == DECODE_COMMAND_STOP || - /* first we try mime types: */ - decoder_run_stream_mime_type(decoder, input_stream, &tried) || - /* if that fails, try suffix matching the URL: */ - decoder_run_stream_suffix(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 == NULL && - decoder_run_stream_fallback(decoder, input_stream)); - - g_slist_free(tried); - - decoder_unlock(dc); - input_stream_close(input_stream); - decoder_lock(dc); - - return success; -} - -/** - * Attempt to load replay gain data, and pass it to - * decoder_replay_gain(). - */ -static void -decoder_load_replay_gain(struct decoder *decoder, const char *path_fs) -{ - struct replay_gain_info info; - if (replay_gain_ape_read(path_fs, &info)) - decoder_replay_gain(decoder, &info); -} - -/** - * Try decoding a file. - */ -static bool -decoder_run_file(struct decoder *decoder, const char *path_fs) -{ - struct decoder_control *dc = decoder->dc; - const char *suffix = uri_get_suffix(path_fs); - const struct decoder_plugin *plugin = NULL; - - if (suffix == NULL) - return false; - - decoder_unlock(dc); - - decoder_load_replay_gain(decoder, path_fs); - - while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { - if (plugin->file_decode != NULL) { - decoder_lock(dc); - - if (decoder_file_decode(plugin, decoder, path_fs)) - return true; - - decoder_unlock(dc); - } else if (plugin->stream_decode != NULL) { - struct input_stream *input_stream; - bool success; - - input_stream = decoder_input_stream_open(dc, path_fs); - if (input_stream == NULL) - continue; - - decoder_lock(dc); - - success = decoder_stream_decode(plugin, decoder, - input_stream); - - decoder_unlock(dc); - - input_stream_close(input_stream); - - if (success) { - decoder_lock(dc); - return true; - } - } - } - - decoder_lock(dc); - return false; -} - -static void -decoder_run_song(struct decoder_control *dc, - const struct song *song, const char *uri) -{ - struct decoder decoder = { - .dc = dc, - .initial_seek_pending = dc->start_ms > 0, - .initial_seek_running = false, - }; - int ret; - - decoder.timestamp = 0.0; - decoder.seeking = false; - decoder.song_tag = song->tag != NULL && song_is_file(song) - ? tag_dup(song->tag) : NULL; - decoder.stream_tag = NULL; - decoder.decoder_tag = NULL; - - dc->state = DECODE_STATE_START; - - decoder_command_finished_locked(dc); - - pcm_convert_init(&decoder.conv_state); - - ret = song_is_file(song) - ? decoder_run_file(&decoder, uri) - : decoder_run_stream(&decoder, uri); - - decoder_unlock(dc); - - pcm_convert_deinit(&decoder.conv_state); - - if (decoder.song_tag != NULL) - tag_free(decoder.song_tag); - - if (decoder.stream_tag != NULL) - tag_free(decoder.stream_tag); - - if (decoder.decoder_tag != NULL) - tag_free(decoder.decoder_tag); - - decoder_lock(dc); - - dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; -} - -static void -decoder_run(struct decoder_control *dc) -{ - const struct song *song = dc->song; - char *uri; - - assert(song != NULL); - - if (song_is_file(song)) - uri = map_song_fs(song); - else - uri = song_get_uri(song); - - if (uri == NULL) { - dc->state = DECODE_STATE_ERROR; - decoder_command_finished_locked(dc); - return; - } - - decoder_run_song(dc, song, uri); - g_free(uri); - -} - -static gpointer -decoder_task(gpointer arg) -{ - struct decoder_control *dc = arg; - - decoder_lock(dc); - - do { - assert(dc->state == DECODE_STATE_STOP || - dc->state == DECODE_STATE_ERROR); - - switch (dc->command) { - case DECODE_COMMAND_START: - g_debug("clearing mixramp tags"); - dc_mixramp_start(dc, NULL); - dc_mixramp_prev_end(dc, dc->mixramp_end); - dc->mixramp_end = NULL; /* Don't free, it's copied above. */ - dc->replay_gain_prev_db = dc->replay_gain_db; - dc->replay_gain_db = 0; - - /* fall through */ - - case DECODE_COMMAND_SEEK: - decoder_run(dc); - break; - - case DECODE_COMMAND_STOP: - decoder_command_finished_locked(dc); - break; - - case DECODE_COMMAND_NONE: - decoder_wait(dc); - break; - } - } while (dc->command != DECODE_COMMAND_NONE || !dc->quit); - - decoder_unlock(dc); - - return NULL; -} - -void -decoder_thread_start(struct decoder_control *dc) -{ - GError *e = NULL; - - assert(dc->thread == NULL); - - dc->quit = false; - - dc->thread = g_thread_create(decoder_task, dc, true, &e); - if (dc->thread == NULL) - MPD_ERROR("Failed to spawn decoder task: %s", e->message); -} -- cgit v1.2.3