From e12cc01aa4b25798a2887ac6c5e01309e744ea8b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 4 Jan 2013 08:41:16 +0100 Subject: decoder_*: convert to C++ --- src/DecoderAPI.cxx | 569 ++++++++++++++++++++++++++++++++++++++++++++++++ src/DecoderControl.cxx | 226 +++++++++++++++++++ src/DecoderControl.hxx | 339 +++++++++++++++++++++++++++++ src/DecoderInternal.cxx | 100 +++++++++ src/DecoderInternal.hxx | 107 +++++++++ src/DecoderThread.cxx | 4 +- src/PlayerControl.cxx | 344 +++++++++++++++++++++++++++++ src/PlayerThread.cxx | 2 +- src/decoder_api.c | 567 ----------------------------------------------- src/decoder_control.c | 223 ------------------- src/decoder_control.h | 339 ----------------------------- src/decoder_internal.c | 96 -------- src/decoder_internal.h | 107 --------- src/player_control.c | 344 ----------------------------- 14 files changed, 1688 insertions(+), 1679 deletions(-) create mode 100644 src/DecoderAPI.cxx create mode 100644 src/DecoderControl.cxx create mode 100644 src/DecoderControl.hxx create mode 100644 src/DecoderInternal.cxx create mode 100644 src/DecoderInternal.hxx create mode 100644 src/PlayerControl.cxx delete mode 100644 src/decoder_api.c delete mode 100644 src/decoder_control.c delete mode 100644 src/decoder_control.h delete mode 100644 src/decoder_internal.c delete mode 100644 src/decoder_internal.h delete mode 100644 src/player_control.c (limited to 'src') diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx new file mode 100644 index 000000000..738e71842 --- /dev/null +++ b/src/DecoderAPI.cxx @@ -0,0 +1,569 @@ +/* + * 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" + +extern "C" { +#include "decoder_api.h" +#include "audio_config.h" +#include "buffer.h" +#include "pipe.h" +#include "chunk.h" +#include "replay_gain_config.h" +} + +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" +#include "song.h" + +#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. + */ +G_GNUC_PURE +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. + */ +G_GNUC_PURE +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(decoder->chunk == NULL); + 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; + + /* delete frames from the old song position */ + + if (decoder->chunk != NULL) { + music_buffer_return(dc->buffer, decoder->chunk); + decoder->chunk = NULL; + } + + music_pipe_clear(dc->pipe, dc->buffer); + + decoder->timestamp = dc->seek_where; + } + + dc->command = DECODE_COMMAND_NONE; + g_cond_signal(dc->client_cond); + decoder_unlock(dc); +} + +double decoder_seek_where(G_GNUC_UNUSED 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". + */ +G_GNUC_PURE +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; + + if (decoder->chunk != NULL) { + /* there is a partial chunk - flush it, we want the + tag in a new chunk */ + decoder_flush_chunk(decoder); + g_cond_signal(decoder->dc->client_cond); + } + + assert(decoder->chunk == NULL); + + 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, + const void *data, size_t length, + uint16_t kbit_rate) +{ + struct decoder_control *dc = decoder->dc; + GError *error = NULL; + enum decoder_command cmd; + + assert(dc->state == DECODE_STATE_DECODE); + assert(dc->pipe != NULL); + assert(length % audio_format_frame_size(&dc->in_audio_format) == 0); + + decoder_lock(dc); + cmd = decoder_get_virtual_command(decoder); + decoder_unlock(dc); + + if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK || + length == 0) + return cmd; + + /* 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) + return cmd; + } + + if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) { + data = pcm_convert(&decoder->conv_state, + &dc->in_audio_format, data, length, + &dc->out_audio_format, &length, + &error); + if (data == NULL) { + /* the PCM conversion has failed - stop + playback, since we have no better way to + bail out */ + g_warning("%s", error->message); + return DECODE_COMMAND_STOP; + } + } + + while (length > 0) { + struct music_chunk *chunk; + size_t nbytes; + bool full; + + chunk = decoder_get_chunk(decoder); + if (chunk == NULL) { + assert(dc->command != DECODE_COMMAND_NONE); + return dc->command; + } + + void *dest = music_chunk_write(chunk, &dc->out_audio_format, + decoder->timestamp - + dc->song->start_ms / 1000.0, + kbit_rate, &nbytes); + if (dest == NULL) { + /* the chunk is full, flush it */ + decoder_flush_chunk(decoder); + g_cond_signal(dc->client_cond); + continue; + } + + assert(nbytes > 0); + + if (nbytes > length) + nbytes = length; + + /* copy the buffer */ + + memcpy(dest, data, nbytes); + + /* expand the music pipe chunk */ + + full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes); + if (full) { + /* the chunk is full, flush it */ + decoder_flush_chunk(decoder); + g_cond_signal(dc->client_cond); + } + + data = (const uint8_t *)data + nbytes; + length -= nbytes; + + decoder->timestamp += (double)nbytes / + audio_format_time_to_size(&dc->out_audio_format); + + if (dc->end_ms > 0 && + decoder->timestamp >= dc->end_ms / 1000.0) + /* the end of this range has been reached: + stop decoding */ + return DECODE_COMMAND_STOP; + } + + return DECODE_COMMAND_NONE; +} + +enum decoder_command +decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, + const struct tag *tag) +{ + G_GNUC_UNUSED 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; + + if (decoder->chunk != NULL) { + /* flush the current chunk because the new + replay gain values affect the following + samples */ + decoder_flush_chunk(decoder); + g_cond_signal(decoder->dc->client_cond); + } + } 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/DecoderControl.cxx b/src/DecoderControl.cxx new file mode 100644 index 000000000..bd075ed71 --- /dev/null +++ b/src/DecoderControl.cxx @@ -0,0 +1,226 @@ +/* + * 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 "DecoderControl.hxx" +#include "song.h" + +extern "C" { +#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->song = NULL; + + 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) +{ + dc_clear_error(dc); + + if (dc->song != NULL) + song_free(dc->song); + + 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_clear_error(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); +} + +bool +decoder_is_current_song(const struct decoder_control *dc, + const struct song *song) +{ + assert(dc != NULL); + assert(song != NULL); + + switch (dc->state) { + case DECODE_STATE_STOP: + case DECODE_STATE_ERROR: + return false; + + case DECODE_STATE_START: + case DECODE_STATE_DECODE: + return song_equals(dc->song, song); + } + + assert(false); + return false; +} + +void +dc_start(struct decoder_control *dc, struct song *song, + unsigned start_ms, unsigned end_ms, + struct music_buffer *buffer, struct music_pipe *pipe) +{ + assert(song != NULL); + assert(buffer != NULL); + assert(pipe != NULL); + assert(music_pipe_empty(pipe)); + + if (dc->song != NULL) + song_free(dc->song); + + dc->song = song; + dc->start_ms = start_ms; + dc->end_ms = end_ms; + dc->buffer = buffer; + 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; +} + +void +dc_mixramp_end(struct decoder_control *dc, char *mixramp_end) +{ + assert(dc != NULL); + + g_free(dc->mixramp_end); + dc->mixramp_end = mixramp_end; +} + +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; +} diff --git a/src/DecoderControl.hxx b/src/DecoderControl.hxx new file mode 100644 index 000000000..f98a604fa --- /dev/null +++ b/src/DecoderControl.hxx @@ -0,0 +1,339 @@ +/* + * 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. + */ + +#ifndef MPD_DECODER_CONTROL_HXX +#define MPD_DECODER_CONTROL_HXX + +#include "decoder_command.h" +#include "audio_format.h" + +#include + +#include + +enum decoder_state { + DECODE_STATE_STOP = 0, + DECODE_STATE_START, + DECODE_STATE_DECODE, + + /** + * The last "START" command failed, because there was an I/O + * error or because no decoder was able to decode the file. + * This state will only come after START; once the state has + * turned to DECODE, by definition no such error can occur. + */ + DECODE_STATE_ERROR, +}; + +struct decoder_control { + /** the handle of the decoder thread, or NULL if the decoder + thread isn't running */ + GThread *thread; + + /** + * This lock protects #state and #command. + */ + GMutex *mutex; + + /** + * Trigger this object after you have modified #command. This + * is also used by the decoder thread to notify the caller + * when it has finished a command. + */ + GCond *cond; + + /** + * The trigger of this object's client. It is signalled + * whenever an event occurs. + */ + GCond *client_cond; + + enum decoder_state state; + enum decoder_command command; + + /** + * The error that occurred in the decoder thread. This + * attribute is only valid if #state is #DECODE_STATE_ERROR. + * The object must be freed when this object transitions to + * any other state (usually #DECODE_STATE_START). + */ + GError *error; + + bool quit; + bool seek_error; + bool seekable; + double seek_where; + + /** the format of the song file */ + struct audio_format in_audio_format; + + /** the format being sent to the music pipe */ + struct audio_format out_audio_format; + + /** + * The song currently being decoded. This attribute is set by + * the player thread, when it sends the #DECODE_COMMAND_START + * command. + * + * This is a duplicate, and must be freed when this attribute + * is cleared. + */ + struct song *song; + + /** + * The initial seek position (in milliseconds), e.g. to the + * start of a sub-track described by a CUE file. + * + * This attribute is set by dc_start(). + */ + unsigned start_ms; + + /** + * The decoder will stop when it reaches this position (in + * milliseconds). 0 means don't stop before the end of the + * file. + * + * This attribute is set by dc_start(). + */ + unsigned end_ms; + + float total_time; + + /** the #music_chunk allocator */ + struct music_buffer *buffer; + + /** + * The destination pipe for decoded chunks. The caller thread + * owns this object, and is responsible for freeing it. + */ + struct music_pipe *pipe; + + float replay_gain_db; + float replay_gain_prev_db; + char *mixramp_start; + char *mixramp_end; + char *mixramp_prev_end; +}; + +G_GNUC_MALLOC +struct decoder_control * +dc_new(GCond *client_cond); + +void +dc_free(struct decoder_control *dc); + +/** + * Locks the #decoder_control object. + */ +static inline void +decoder_lock(struct decoder_control *dc) +{ + g_mutex_lock(dc->mutex); +} + +/** + * Unlocks the #decoder_control object. + */ +static inline void +decoder_unlock(struct decoder_control *dc) +{ + g_mutex_unlock(dc->mutex); +} + +/** + * Waits for a signal on the #decoder_control object. This function + * is only valid in the decoder thread. The object must be locked + * prior to calling this function. + */ +static inline void +decoder_wait(struct decoder_control *dc) +{ + g_cond_wait(dc->cond, dc->mutex); +} + +/** + * Signals the #decoder_control object. This function is only valid + * in the player thread. The object should be locked prior to calling + * this function. + */ +static inline void +decoder_signal(struct decoder_control *dc) +{ + g_cond_signal(dc->cond); +} + +static inline bool +decoder_is_idle(const struct decoder_control *dc) +{ + return dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR; +} + +static inline bool +decoder_is_starting(const struct decoder_control *dc) +{ + return dc->state == DECODE_STATE_START; +} + +static inline bool +decoder_has_failed(const struct decoder_control *dc) +{ + assert(dc->command == DECODE_COMMAND_NONE); + + return dc->state == DECODE_STATE_ERROR; +} + +/** + * Checks whether an error has occurred, and if so, returns a newly + * allocated copy of the #GError object. + * + * Caller must lock the object. + */ +static inline GError * +dc_get_error(const struct decoder_control *dc) +{ + assert(dc != NULL); + assert(dc->command == DECODE_COMMAND_NONE); + assert(dc->state != DECODE_STATE_ERROR || dc->error != NULL); + + return dc->state == DECODE_STATE_ERROR + ? g_error_copy(dc->error) + : NULL; +} + +/** + * Like dc_get_error(), but locks and unlocks the object. + */ +static inline GError * +dc_lock_get_error(struct decoder_control *dc) +{ + decoder_lock(dc); + GError *error = dc_get_error(dc); + decoder_unlock(dc); + return error; +} + +/** + * Clear the error condition and free the #GError object (if any). + * + * Caller must lock the object. + */ +static inline void +dc_clear_error(struct decoder_control *dc) +{ + if (dc->state == DECODE_STATE_ERROR) { + g_error_free(dc->error); + dc->state = DECODE_STATE_STOP; + } +} + +static inline bool +decoder_lock_is_idle(struct decoder_control *dc) +{ + bool ret; + + decoder_lock(dc); + ret = decoder_is_idle(dc); + decoder_unlock(dc); + + return ret; +} + +static inline bool +decoder_lock_is_starting(struct decoder_control *dc) +{ + bool ret; + + decoder_lock(dc); + ret = decoder_is_starting(dc); + decoder_unlock(dc); + + return ret; +} + +static inline bool +decoder_lock_has_failed(struct decoder_control *dc) +{ + bool ret; + + decoder_lock(dc); + ret = decoder_has_failed(dc); + decoder_unlock(dc); + + return ret; +} + +/** + * Check if the specified song is currently being decoded. If the + * decoder is not running currently (or being started), then this + * function returns false in any case. + * + * Caller must lock the object. + */ +gcc_pure +bool +decoder_is_current_song(const struct decoder_control *dc, + const struct song *song); + +gcc_pure +static inline bool +decoder_lock_is_current_song(struct decoder_control *dc, + const struct song *song) +{ + decoder_lock(dc); + const bool result = decoder_is_current_song(dc, song); + decoder_unlock(dc); + return result; +} + +/** + * Start the decoder. + * + * @param the decoder + * @param song the song to be decoded; the given instance will be + * owned and freed by the decoder + * @param start_ms see #decoder_control + * @param end_ms see #decoder_control + * @param pipe the pipe which receives the decoded chunks (owned by + * the caller) + */ +void +dc_start(struct decoder_control *dc, struct song *song, + unsigned start_ms, unsigned end_ms, + struct music_buffer *buffer, struct music_pipe *pipe); + +void +dc_stop(struct decoder_control *dc); + +bool +dc_seek(struct decoder_control *dc, double where); + +void +dc_quit(struct decoder_control *dc); + +void +dc_mixramp_start(struct decoder_control *dc, char *mixramp_start); + +void +dc_mixramp_end(struct decoder_control *dc, char *mixramp_end); + +void +dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end); + +#endif diff --git a/src/DecoderInternal.cxx b/src/DecoderInternal.cxx new file mode 100644 index 000000000..ab496dbd3 --- /dev/null +++ b/src/DecoderInternal.cxx @@ -0,0 +1,100 @@ +/* + * 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 "DecoderInternal.hxx" +#include "DecoderControl.hxx" + +extern "C" { +#include "pipe.h" +#include "buffer.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; + enum decoder_command cmd; + + assert(decoder != NULL); + + if (decoder->chunk != NULL) + return decoder->chunk; + + do { + decoder->chunk = music_buffer_allocate(dc->buffer); + if (decoder->chunk != NULL) { + decoder->chunk->replay_gain_serial = + decoder->replay_gain_serial; + if (decoder->replay_gain_serial != 0) + decoder->chunk->replay_gain_info = + decoder->replay_gain_info; + + return decoder->chunk; + } + + decoder_lock(dc); + cmd = need_chunks(dc, true); + decoder_unlock(dc); + } while (cmd == DECODE_COMMAND_NONE); + + return NULL; +} + +void +decoder_flush_chunk(struct decoder *decoder) +{ + struct decoder_control *dc = decoder->dc; + + assert(decoder != NULL); + assert(decoder->chunk != NULL); + + if (music_chunk_is_empty(decoder->chunk)) + music_buffer_return(dc->buffer, decoder->chunk); + else + music_pipe_push(dc->pipe, decoder->chunk); + + decoder->chunk = NULL; +} diff --git a/src/DecoderInternal.hxx b/src/DecoderInternal.hxx new file mode 100644 index 000000000..698e90bf1 --- /dev/null +++ b/src/DecoderInternal.hxx @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#ifndef MPD_DECODER_INTERNAL_HXX +#define MPD_DECODER_INTERNAL_HXX + +#include "decoder_command.h" +#include "pcm_convert.h" +#include "replay_gain_info.h" + +struct input_stream; + +struct decoder { + struct decoder_control *dc; + + struct pcm_convert_state conv_state; + + /** + * The time stamp of the next data chunk, in seconds. + */ + double timestamp; + + /** + * Is the initial seek (to the start position of the sub-song) + * pending, or has it been performed already? + */ + bool initial_seek_pending; + + /** + * Is the initial seek currently running? During this time, + * the decoder command is SEEK. This flag is set by + * decoder_get_virtual_command(), when the virtual SEEK + * command is generated for the first time. + */ + bool initial_seek_running; + + /** + * This flag is set by decoder_seek_where(), and checked by + * decoder_command_finished(). It is used to clean up after + * seeking. + */ + bool seeking; + + /** + * The tag from the song object. This is only used for local + * files, because we expect the stream server to send us a new + * tag each time we play it. + */ + struct tag *song_tag; + + /** the last tag received from the stream */ + struct tag *stream_tag; + + /** the last tag received from the decoder plugin */ + struct tag *decoder_tag; + + /** the chunk currently being written to */ + struct music_chunk *chunk; + + struct replay_gain_info replay_gain_info; + + /** + * A positive serial number for checking if replay gain info + * has changed since the last check. + */ + unsigned replay_gain_serial; + +#ifdef __cplusplus + decoder(decoder_control *_dc, bool _initial_seek_pending) + :dc(_dc), + initial_seek_pending(_initial_seek_pending), + initial_seek_running(false) {} +#endif +}; + +/** + * Returns the current chunk the decoder writes to, or allocates a new + * chunk if there is none. + * + * @return the chunk, or NULL if we have received a decoder command + */ +struct music_chunk * +decoder_get_chunk(struct decoder *decoder); + +/** + * Flushes the current chunk. + */ +void +decoder_flush_chunk(struct decoder *decoder); + +#endif diff --git a/src/DecoderThread.cxx b/src/DecoderThread.cxx index cbdb19188..f6f8437ab 100644 --- a/src/DecoderThread.cxx +++ b/src/DecoderThread.cxx @@ -19,6 +19,8 @@ #include "config.h" #include "DecoderThread.hxx" +#include "DecoderControl.hxx" +#include "DecoderInternal.hxx" #include "decoder_error.h" #include "decoder_plugin.h" #include "song.h" @@ -26,8 +28,6 @@ #include "Mapper.hxx" extern "C" { -#include "decoder_control.h" -#include "decoder_internal.h" #include "decoder_list.h" #include "decoder_api.h" #include "replay_gain_ape.h" diff --git a/src/PlayerControl.cxx b/src/PlayerControl.cxx new file mode 100644 index 000000000..07749b7f0 --- /dev/null +++ b/src/PlayerControl.cxx @@ -0,0 +1,344 @@ +/* + * 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" + +extern "C" { +#include "player_control.h" +#include "idle.h" +} + +#include "song.h" +#include "DecoderControl.hxx" +#include "Main.hxx" + +#include +#include +#include + +static void +pc_enqueue_song_locked(struct player_control *pc, struct song *song); + +struct player_control * +pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) +{ + struct player_control *pc = g_new0(struct player_control, 1); + + pc->buffer_chunks = buffer_chunks; + pc->buffered_before_play = buffered_before_play; + + pc->mutex = g_mutex_new(); + pc->cond = g_cond_new(); + + pc->command = PLAYER_COMMAND_NONE; + pc->error_type = PLAYER_ERROR_NONE; + pc->state = PLAYER_STATE_STOP; + pc->cross_fade_seconds = 0; + pc->mixramp_db = 0; + pc->mixramp_delay_seconds = nanf(""); + + return pc; +} + +void +pc_free(struct player_control *pc) +{ + if (pc->next_song != NULL) + song_free(pc->next_song); + + g_cond_free(pc->cond); + g_mutex_free(pc->mutex); + g_free(pc); +} + +void +player_wait_decoder(struct player_control *pc, struct decoder_control *dc) +{ + assert(pc != NULL); + assert(dc != NULL); + assert(dc->client_cond == pc->cond); + + /* during this function, the decoder lock is held, because + we're waiting for the decoder thread */ + g_cond_wait(pc->cond, dc->mutex); +} + +static void +player_command_wait_locked(struct player_control *pc) +{ + while (pc->command != PLAYER_COMMAND_NONE) + g_cond_wait(main_cond, pc->mutex); +} + +static void +player_command_locked(struct player_control *pc, enum player_command cmd) +{ + assert(pc->command == PLAYER_COMMAND_NONE); + + pc->command = cmd; + player_signal(pc); + player_command_wait_locked(pc); +} + +static void +player_command(struct player_control *pc, enum player_command cmd) +{ + player_lock(pc); + player_command_locked(pc, cmd); + player_unlock(pc); +} + +void +pc_play(struct player_control *pc, struct song *song) +{ + assert(song != NULL); + + player_lock(pc); + + if (pc->state != PLAYER_STATE_STOP) + player_command_locked(pc, PLAYER_COMMAND_STOP); + + assert(pc->next_song == NULL); + + pc_enqueue_song_locked(pc, song); + + assert(pc->next_song == NULL); + + player_unlock(pc); + + idle_add(IDLE_PLAYER); +} + +void +pc_cancel(struct player_control *pc) +{ + player_command(pc, PLAYER_COMMAND_CANCEL); + assert(pc->next_song == NULL); +} + +void +pc_stop(struct player_control *pc) +{ + player_command(pc, PLAYER_COMMAND_CLOSE_AUDIO); + assert(pc->next_song == NULL); + + idle_add(IDLE_PLAYER); +} + +void +pc_update_audio(struct player_control *pc) +{ + player_command(pc, PLAYER_COMMAND_UPDATE_AUDIO); +} + +void +pc_kill(struct player_control *pc) +{ + assert(pc->thread != NULL); + + player_command(pc, PLAYER_COMMAND_EXIT); + g_thread_join(pc->thread); + pc->thread = NULL; + + idle_add(IDLE_PLAYER); +} + +void +pc_pause(struct player_control *pc) +{ + player_lock(pc); + + if (pc->state != PLAYER_STATE_STOP) { + player_command_locked(pc, PLAYER_COMMAND_PAUSE); + idle_add(IDLE_PLAYER); + } + + player_unlock(pc); +} + +static void +pc_pause_locked(struct player_control *pc) +{ + if (pc->state != PLAYER_STATE_STOP) { + player_command_locked(pc, PLAYER_COMMAND_PAUSE); + idle_add(IDLE_PLAYER); + } +} + +void +pc_set_pause(struct player_control *pc, bool pause_flag) +{ + player_lock(pc); + + switch (pc->state) { + case PLAYER_STATE_STOP: + break; + + case PLAYER_STATE_PLAY: + if (pause_flag) + pc_pause_locked(pc); + break; + + case PLAYER_STATE_PAUSE: + if (!pause_flag) + pc_pause_locked(pc); + break; + } + + player_unlock(pc); +} + +void +pc_set_border_pause(struct player_control *pc, bool border_pause) +{ + player_lock(pc); + pc->border_pause = border_pause; + player_unlock(pc); +} + +void +pc_get_status(struct player_control *pc, struct player_status *status) +{ + player_lock(pc); + player_command_locked(pc, PLAYER_COMMAND_REFRESH); + + status->state = pc->state; + + if (pc->state != PLAYER_STATE_STOP) { + status->bit_rate = pc->bit_rate; + status->audio_format = pc->audio_format; + status->total_time = pc->total_time; + status->elapsed_time = pc->elapsed_time; + } + + player_unlock(pc); +} + +void +pc_set_error(struct player_control *pc, enum player_error type, + GError *error) +{ + assert(pc != NULL); + assert(type != PLAYER_ERROR_NONE); + assert(error != NULL); + + if (pc->error_type != PLAYER_ERROR_NONE) + g_error_free(pc->error); + + pc->error_type = type; + pc->error = error; +} + +void +pc_clear_error(struct player_control *pc) +{ + player_lock(pc); + + if (pc->error_type != PLAYER_ERROR_NONE) { + pc->error_type = PLAYER_ERROR_NONE; + g_error_free(pc->error); + } + + player_unlock(pc); +} + +char * +pc_get_error_message(struct player_control *pc) +{ + player_lock(pc); + char *message = pc->error_type != PLAYER_ERROR_NONE + ? g_strdup(pc->error->message) + : NULL; + player_unlock(pc); + return message; +} + +static void +pc_enqueue_song_locked(struct player_control *pc, struct song *song) +{ + assert(song != NULL); + assert(pc->next_song == NULL); + + pc->next_song = song; + player_command_locked(pc, PLAYER_COMMAND_QUEUE); +} + +void +pc_enqueue_song(struct player_control *pc, struct song *song) +{ + assert(song != NULL); + + player_lock(pc); + pc_enqueue_song_locked(pc, song); + player_unlock(pc); +} + +bool +pc_seek(struct player_control *pc, struct song *song, float seek_time) +{ + assert(song != NULL); + + player_lock(pc); + + if (pc->next_song != NULL) + song_free(pc->next_song); + + pc->next_song = song; + pc->seek_where = seek_time; + player_command_locked(pc, PLAYER_COMMAND_SEEK); + player_unlock(pc); + + assert(pc->next_song == NULL); + + idle_add(IDLE_PLAYER); + + return true; +} + +float +pc_get_cross_fade(const struct player_control *pc) +{ + return pc->cross_fade_seconds; +} + +void +pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds) +{ + if (cross_fade_seconds < 0) + cross_fade_seconds = 0; + pc->cross_fade_seconds = cross_fade_seconds; + + idle_add(IDLE_OPTIONS); +} + +void +pc_set_mixramp_db(struct player_control *pc, float mixramp_db) +{ + pc->mixramp_db = mixramp_db; + + idle_add(IDLE_OPTIONS); +} + +void +pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds) +{ + pc->mixramp_delay_seconds = mixramp_delay_seconds; + + idle_add(IDLE_OPTIONS); +} diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx index ad7a008db..9e5c6042b 100644 --- a/src/PlayerThread.cxx +++ b/src/PlayerThread.cxx @@ -20,13 +20,13 @@ #include "config.h" #include "PlayerThread.hxx" #include "DecoderThread.hxx" +#include "DecoderControl.hxx" #include "song.h" #include "Main.hxx" #include "mpd_error.h" extern "C" { #include "player_control.h" -#include "decoder_control.h" #include "output_all.h" #include "event_pipe.h" #include "crossfade.h" diff --git a/src/decoder_api.c b/src/decoder_api.c deleted file mode 100644 index a45d0f1e6..000000000 --- a/src/decoder_api.c +++ /dev/null @@ -1,567 +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 "buffer.h" -#include "pipe.h" -#include "chunk.h" -#include "replay_gain_config.h" - -#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. - */ -G_GNUC_PURE -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. - */ -G_GNUC_PURE -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(decoder->chunk == NULL); - 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; - - /* delete frames from the old song position */ - - if (decoder->chunk != NULL) { - music_buffer_return(dc->buffer, decoder->chunk); - decoder->chunk = NULL; - } - - music_pipe_clear(dc->pipe, dc->buffer); - - decoder->timestamp = dc->seek_where; - } - - dc->command = DECODE_COMMAND_NONE; - g_cond_signal(dc->client_cond); - decoder_unlock(dc); -} - -double decoder_seek_where(G_GNUC_UNUSED 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". - */ -G_GNUC_PURE -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; - - if (decoder->chunk != NULL) { - /* there is a partial chunk - flush it, we want the - tag in a new chunk */ - decoder_flush_chunk(decoder); - g_cond_signal(decoder->dc->client_cond); - } - - assert(decoder->chunk == NULL); - - 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, - const void *_data, size_t length, - uint16_t kbit_rate) -{ - struct decoder_control *dc = decoder->dc; - const char *data = _data; - GError *error = NULL; - enum decoder_command cmd; - - assert(dc->state == DECODE_STATE_DECODE); - assert(dc->pipe != NULL); - assert(length % audio_format_frame_size(&dc->in_audio_format) == 0); - - decoder_lock(dc); - cmd = decoder_get_virtual_command(decoder); - decoder_unlock(dc); - - if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK || - length == 0) - return cmd; - - /* 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) - return cmd; - } - - if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) { - data = pcm_convert(&decoder->conv_state, - &dc->in_audio_format, data, length, - &dc->out_audio_format, &length, - &error); - if (data == NULL) { - /* the PCM conversion has failed - stop - playback, since we have no better way to - bail out */ - g_warning("%s", error->message); - return DECODE_COMMAND_STOP; - } - } - - while (length > 0) { - struct music_chunk *chunk; - char *dest; - size_t nbytes; - bool full; - - chunk = decoder_get_chunk(decoder); - if (chunk == NULL) { - assert(dc->command != DECODE_COMMAND_NONE); - return dc->command; - } - - dest = music_chunk_write(chunk, &dc->out_audio_format, - decoder->timestamp - - dc->song->start_ms / 1000.0, - kbit_rate, &nbytes); - if (dest == NULL) { - /* the chunk is full, flush it */ - decoder_flush_chunk(decoder); - g_cond_signal(dc->client_cond); - continue; - } - - assert(nbytes > 0); - - if (nbytes > length) - nbytes = length; - - /* copy the buffer */ - - memcpy(dest, data, nbytes); - - /* expand the music pipe chunk */ - - full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes); - if (full) { - /* the chunk is full, flush it */ - decoder_flush_chunk(decoder); - g_cond_signal(dc->client_cond); - } - - data += nbytes; - length -= nbytes; - - decoder->timestamp += (double)nbytes / - audio_format_time_to_size(&dc->out_audio_format); - - if (dc->end_ms > 0 && - decoder->timestamp >= dc->end_ms / 1000.0) - /* the end of this range has been reached: - stop decoding */ - return DECODE_COMMAND_STOP; - } - - return DECODE_COMMAND_NONE; -} - -enum decoder_command -decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, - const struct tag *tag) -{ - G_GNUC_UNUSED 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; - - if (decoder->chunk != NULL) { - /* flush the current chunk because the new - replay gain values affect the following - samples */ - decoder_flush_chunk(decoder); - g_cond_signal(decoder->dc->client_cond); - } - } 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 33d4e4d44..000000000 --- a/src/decoder_control.c +++ /dev/null @@ -1,223 +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 "song.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->song = NULL; - - 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) -{ - dc_clear_error(dc); - - if (dc->song != NULL) - song_free(dc->song); - - 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_clear_error(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); -} - -bool -decoder_is_current_song(const struct decoder_control *dc, - const struct song *song) -{ - assert(dc != NULL); - assert(song != NULL); - - switch (dc->state) { - case DECODE_STATE_STOP: - case DECODE_STATE_ERROR: - return false; - - case DECODE_STATE_START: - case DECODE_STATE_DECODE: - return song_equals(dc->song, song); - } - - assert(false); - return false; -} - -void -dc_start(struct decoder_control *dc, struct song *song, - unsigned start_ms, unsigned end_ms, - struct music_buffer *buffer, struct music_pipe *pipe) -{ - assert(song != NULL); - assert(buffer != NULL); - assert(pipe != NULL); - assert(music_pipe_empty(pipe)); - - if (dc->song != NULL) - song_free(dc->song); - - dc->song = song; - dc->start_ms = start_ms; - dc->end_ms = end_ms; - dc->buffer = buffer; - 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; -} - -void -dc_mixramp_end(struct decoder_control *dc, char *mixramp_end) -{ - assert(dc != NULL); - - g_free(dc->mixramp_end); - dc->mixramp_end = mixramp_end; -} - -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; -} diff --git a/src/decoder_control.h b/src/decoder_control.h deleted file mode 100644 index 9ecbde73e..000000000 --- a/src/decoder_control.h +++ /dev/null @@ -1,339 +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. - */ - -#ifndef MPD_DECODER_CONTROL_H -#define MPD_DECODER_CONTROL_H - -#include "decoder_command.h" -#include "audio_format.h" - -#include - -#include - -enum decoder_state { - DECODE_STATE_STOP = 0, - DECODE_STATE_START, - DECODE_STATE_DECODE, - - /** - * The last "START" command failed, because there was an I/O - * error or because no decoder was able to decode the file. - * This state will only come after START; once the state has - * turned to DECODE, by definition no such error can occur. - */ - DECODE_STATE_ERROR, -}; - -struct decoder_control { - /** the handle of the decoder thread, or NULL if the decoder - thread isn't running */ - GThread *thread; - - /** - * This lock protects #state and #command. - */ - GMutex *mutex; - - /** - * Trigger this object after you have modified #command. This - * is also used by the decoder thread to notify the caller - * when it has finished a command. - */ - GCond *cond; - - /** - * The trigger of this object's client. It is signalled - * whenever an event occurs. - */ - GCond *client_cond; - - enum decoder_state state; - enum decoder_command command; - - /** - * The error that occurred in the decoder thread. This - * attribute is only valid if #state is #DECODE_STATE_ERROR. - * The object must be freed when this object transitions to - * any other state (usually #DECODE_STATE_START). - */ - GError *error; - - bool quit; - bool seek_error; - bool seekable; - double seek_where; - - /** the format of the song file */ - struct audio_format in_audio_format; - - /** the format being sent to the music pipe */ - struct audio_format out_audio_format; - - /** - * The song currently being decoded. This attribute is set by - * the player thread, when it sends the #DECODE_COMMAND_START - * command. - * - * This is a duplicate, and must be freed when this attribute - * is cleared. - */ - struct song *song; - - /** - * The initial seek position (in milliseconds), e.g. to the - * start of a sub-track described by a CUE file. - * - * This attribute is set by dc_start(). - */ - unsigned start_ms; - - /** - * The decoder will stop when it reaches this position (in - * milliseconds). 0 means don't stop before the end of the - * file. - * - * This attribute is set by dc_start(). - */ - unsigned end_ms; - - float total_time; - - /** the #music_chunk allocator */ - struct music_buffer *buffer; - - /** - * The destination pipe for decoded chunks. The caller thread - * owns this object, and is responsible for freeing it. - */ - struct music_pipe *pipe; - - float replay_gain_db; - float replay_gain_prev_db; - char *mixramp_start; - char *mixramp_end; - char *mixramp_prev_end; -}; - -G_GNUC_MALLOC -struct decoder_control * -dc_new(GCond *client_cond); - -void -dc_free(struct decoder_control *dc); - -/** - * Locks the #decoder_control object. - */ -static inline void -decoder_lock(struct decoder_control *dc) -{ - g_mutex_lock(dc->mutex); -} - -/** - * Unlocks the #decoder_control object. - */ -static inline void -decoder_unlock(struct decoder_control *dc) -{ - g_mutex_unlock(dc->mutex); -} - -/** - * Waits for a signal on the #decoder_control object. This function - * is only valid in the decoder thread. The object must be locked - * prior to calling this function. - */ -static inline void -decoder_wait(struct decoder_control *dc) -{ - g_cond_wait(dc->cond, dc->mutex); -} - -/** - * Signals the #decoder_control object. This function is only valid - * in the player thread. The object should be locked prior to calling - * this function. - */ -static inline void -decoder_signal(struct decoder_control *dc) -{ - g_cond_signal(dc->cond); -} - -static inline bool -decoder_is_idle(const struct decoder_control *dc) -{ - return dc->state == DECODE_STATE_STOP || - dc->state == DECODE_STATE_ERROR; -} - -static inline bool -decoder_is_starting(const struct decoder_control *dc) -{ - return dc->state == DECODE_STATE_START; -} - -static inline bool -decoder_has_failed(const struct decoder_control *dc) -{ - assert(dc->command == DECODE_COMMAND_NONE); - - return dc->state == DECODE_STATE_ERROR; -} - -/** - * Checks whether an error has occurred, and if so, returns a newly - * allocated copy of the #GError object. - * - * Caller must lock the object. - */ -static inline GError * -dc_get_error(const struct decoder_control *dc) -{ - assert(dc != NULL); - assert(dc->command == DECODE_COMMAND_NONE); - assert(dc->state != DECODE_STATE_ERROR || dc->error != NULL); - - return dc->state == DECODE_STATE_ERROR - ? g_error_copy(dc->error) - : NULL; -} - -/** - * Like dc_get_error(), but locks and unlocks the object. - */ -static inline GError * -dc_lock_get_error(struct decoder_control *dc) -{ - decoder_lock(dc); - GError *error = dc_get_error(dc); - decoder_unlock(dc); - return error; -} - -/** - * Clear the error condition and free the #GError object (if any). - * - * Caller must lock the object. - */ -static inline void -dc_clear_error(struct decoder_control *dc) -{ - if (dc->state == DECODE_STATE_ERROR) { - g_error_free(dc->error); - dc->state = DECODE_STATE_STOP; - } -} - -static inline bool -decoder_lock_is_idle(struct decoder_control *dc) -{ - bool ret; - - decoder_lock(dc); - ret = decoder_is_idle(dc); - decoder_unlock(dc); - - return ret; -} - -static inline bool -decoder_lock_is_starting(struct decoder_control *dc) -{ - bool ret; - - decoder_lock(dc); - ret = decoder_is_starting(dc); - decoder_unlock(dc); - - return ret; -} - -static inline bool -decoder_lock_has_failed(struct decoder_control *dc) -{ - bool ret; - - decoder_lock(dc); - ret = decoder_has_failed(dc); - decoder_unlock(dc); - - return ret; -} - -/** - * Check if the specified song is currently being decoded. If the - * decoder is not running currently (or being started), then this - * function returns false in any case. - * - * Caller must lock the object. - */ -gcc_pure -bool -decoder_is_current_song(const struct decoder_control *dc, - const struct song *song); - -gcc_pure -static inline bool -decoder_lock_is_current_song(struct decoder_control *dc, - const struct song *song) -{ - decoder_lock(dc); - const bool result = decoder_is_current_song(dc, song); - decoder_unlock(dc); - return result; -} - -/** - * Start the decoder. - * - * @param the decoder - * @param song the song to be decoded; the given instance will be - * owned and freed by the decoder - * @param start_ms see #decoder_control - * @param end_ms see #decoder_control - * @param pipe the pipe which receives the decoded chunks (owned by - * the caller) - */ -void -dc_start(struct decoder_control *dc, struct song *song, - unsigned start_ms, unsigned end_ms, - struct music_buffer *buffer, struct music_pipe *pipe); - -void -dc_stop(struct decoder_control *dc); - -bool -dc_seek(struct decoder_control *dc, double where); - -void -dc_quit(struct decoder_control *dc); - -void -dc_mixramp_start(struct decoder_control *dc, char *mixramp_start); - -void -dc_mixramp_end(struct decoder_control *dc, char *mixramp_end); - -void -dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end); - -#endif diff --git a/src/decoder_internal.c b/src/decoder_internal.c deleted file mode 100644 index bc349f2ff..000000000 --- a/src/decoder_internal.c +++ /dev/null @@ -1,96 +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 "buffer.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; - enum decoder_command cmd; - - assert(decoder != NULL); - - if (decoder->chunk != NULL) - return decoder->chunk; - - do { - decoder->chunk = music_buffer_allocate(dc->buffer); - if (decoder->chunk != NULL) { - decoder->chunk->replay_gain_serial = - decoder->replay_gain_serial; - if (decoder->replay_gain_serial != 0) - decoder->chunk->replay_gain_info = - decoder->replay_gain_info; - - return decoder->chunk; - } - - decoder_lock(dc); - cmd = need_chunks(dc, true); - decoder_unlock(dc); - } while (cmd == DECODE_COMMAND_NONE); - - return NULL; -} - -void -decoder_flush_chunk(struct decoder *decoder) -{ - struct decoder_control *dc = decoder->dc; - - assert(decoder != NULL); - assert(decoder->chunk != NULL); - - if (music_chunk_is_empty(decoder->chunk)) - music_buffer_return(dc->buffer, decoder->chunk); - else - music_pipe_push(dc->pipe, decoder->chunk); - - decoder->chunk = NULL; -} diff --git a/src/decoder_internal.h b/src/decoder_internal.h deleted file mode 100644 index 5bc7e216d..000000000 --- a/src/decoder_internal.h +++ /dev/null @@ -1,107 +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. - */ - -#ifndef MPD_DECODER_INTERNAL_H -#define MPD_DECODER_INTERNAL_H - -#include "decoder_command.h" -#include "pcm_convert.h" -#include "replay_gain_info.h" - -struct input_stream; - -struct decoder { - struct decoder_control *dc; - - struct pcm_convert_state conv_state; - - /** - * The time stamp of the next data chunk, in seconds. - */ - double timestamp; - - /** - * Is the initial seek (to the start position of the sub-song) - * pending, or has it been performed already? - */ - bool initial_seek_pending; - - /** - * Is the initial seek currently running? During this time, - * the decoder command is SEEK. This flag is set by - * decoder_get_virtual_command(), when the virtual SEEK - * command is generated for the first time. - */ - bool initial_seek_running; - - /** - * This flag is set by decoder_seek_where(), and checked by - * decoder_command_finished(). It is used to clean up after - * seeking. - */ - bool seeking; - - /** - * The tag from the song object. This is only used for local - * files, because we expect the stream server to send us a new - * tag each time we play it. - */ - struct tag *song_tag; - - /** the last tag received from the stream */ - struct tag *stream_tag; - - /** the last tag received from the decoder plugin */ - struct tag *decoder_tag; - - /** the chunk currently being written to */ - struct music_chunk *chunk; - - struct replay_gain_info replay_gain_info; - - /** - * A positive serial number for checking if replay gain info - * has changed since the last check. - */ - unsigned replay_gain_serial; - -#ifdef __cplusplus - decoder(decoder_control *_dc, bool _initial_seek_pending) - :dc(_dc), - initial_seek_pending(_initial_seek_pending), - initial_seek_running(false) {} -#endif -}; - -/** - * Returns the current chunk the decoder writes to, or allocates a new - * chunk if there is none. - * - * @return the chunk, or NULL if we have received a decoder command - */ -struct music_chunk * -decoder_get_chunk(struct decoder *decoder); - -/** - * Flushes the current chunk. - */ -void -decoder_flush_chunk(struct decoder *decoder); - -#endif diff --git a/src/player_control.c b/src/player_control.c deleted file mode 100644 index 5a16520c2..000000000 --- a/src/player_control.c +++ /dev/null @@ -1,344 +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 "player_control.h" -#include "decoder_control.h" -#include "path.h" -#include "log.h" -#include "tag.h" -#include "song.h" -#include "idle.h" -#include "pcm_volume.h" -#include "Main.hxx" - -#include -#include -#include - -static void -pc_enqueue_song_locked(struct player_control *pc, struct song *song); - -struct player_control * -pc_new(unsigned buffer_chunks, unsigned int buffered_before_play) -{ - struct player_control *pc = g_new0(struct player_control, 1); - - pc->buffer_chunks = buffer_chunks; - pc->buffered_before_play = buffered_before_play; - - pc->mutex = g_mutex_new(); - pc->cond = g_cond_new(); - - pc->command = PLAYER_COMMAND_NONE; - pc->error_type = PLAYER_ERROR_NONE; - pc->state = PLAYER_STATE_STOP; - pc->cross_fade_seconds = 0; - pc->mixramp_db = 0; - pc->mixramp_delay_seconds = nanf(""); - - return pc; -} - -void -pc_free(struct player_control *pc) -{ - if (pc->next_song != NULL) - song_free(pc->next_song); - - g_cond_free(pc->cond); - g_mutex_free(pc->mutex); - g_free(pc); -} - -void -player_wait_decoder(struct player_control *pc, struct decoder_control *dc) -{ - assert(pc != NULL); - assert(dc != NULL); - assert(dc->client_cond == pc->cond); - - /* during this function, the decoder lock is held, because - we're waiting for the decoder thread */ - g_cond_wait(pc->cond, dc->mutex); -} - -static void -player_command_wait_locked(struct player_control *pc) -{ - while (pc->command != PLAYER_COMMAND_NONE) - g_cond_wait(main_cond, pc->mutex); -} - -static void -player_command_locked(struct player_control *pc, enum player_command cmd) -{ - assert(pc->command == PLAYER_COMMAND_NONE); - - pc->command = cmd; - player_signal(pc); - player_command_wait_locked(pc); -} - -static void -player_command(struct player_control *pc, enum player_command cmd) -{ - player_lock(pc); - player_command_locked(pc, cmd); - player_unlock(pc); -} - -void -pc_play(struct player_control *pc, struct song *song) -{ - assert(song != NULL); - - player_lock(pc); - - if (pc->state != PLAYER_STATE_STOP) - player_command_locked(pc, PLAYER_COMMAND_STOP); - - assert(pc->next_song == NULL); - - pc_enqueue_song_locked(pc, song); - - assert(pc->next_song == NULL); - - player_unlock(pc); - - idle_add(IDLE_PLAYER); -} - -void -pc_cancel(struct player_control *pc) -{ - player_command(pc, PLAYER_COMMAND_CANCEL); - assert(pc->next_song == NULL); -} - -void -pc_stop(struct player_control *pc) -{ - player_command(pc, PLAYER_COMMAND_CLOSE_AUDIO); - assert(pc->next_song == NULL); - - idle_add(IDLE_PLAYER); -} - -void -pc_update_audio(struct player_control *pc) -{ - player_command(pc, PLAYER_COMMAND_UPDATE_AUDIO); -} - -void -pc_kill(struct player_control *pc) -{ - assert(pc->thread != NULL); - - player_command(pc, PLAYER_COMMAND_EXIT); - g_thread_join(pc->thread); - pc->thread = NULL; - - idle_add(IDLE_PLAYER); -} - -void -pc_pause(struct player_control *pc) -{ - player_lock(pc); - - if (pc->state != PLAYER_STATE_STOP) { - player_command_locked(pc, PLAYER_COMMAND_PAUSE); - idle_add(IDLE_PLAYER); - } - - player_unlock(pc); -} - -static void -pc_pause_locked(struct player_control *pc) -{ - if (pc->state != PLAYER_STATE_STOP) { - player_command_locked(pc, PLAYER_COMMAND_PAUSE); - idle_add(IDLE_PLAYER); - } -} - -void -pc_set_pause(struct player_control *pc, bool pause_flag) -{ - player_lock(pc); - - switch (pc->state) { - case PLAYER_STATE_STOP: - break; - - case PLAYER_STATE_PLAY: - if (pause_flag) - pc_pause_locked(pc); - break; - - case PLAYER_STATE_PAUSE: - if (!pause_flag) - pc_pause_locked(pc); - break; - } - - player_unlock(pc); -} - -void -pc_set_border_pause(struct player_control *pc, bool border_pause) -{ - player_lock(pc); - pc->border_pause = border_pause; - player_unlock(pc); -} - -void -pc_get_status(struct player_control *pc, struct player_status *status) -{ - player_lock(pc); - player_command_locked(pc, PLAYER_COMMAND_REFRESH); - - status->state = pc->state; - - if (pc->state != PLAYER_STATE_STOP) { - status->bit_rate = pc->bit_rate; - status->audio_format = pc->audio_format; - status->total_time = pc->total_time; - status->elapsed_time = pc->elapsed_time; - } - - player_unlock(pc); -} - -void -pc_set_error(struct player_control *pc, enum player_error type, - GError *error) -{ - assert(pc != NULL); - assert(type != PLAYER_ERROR_NONE); - assert(error != NULL); - - if (pc->error_type != PLAYER_ERROR_NONE) - g_error_free(pc->error); - - pc->error_type = type; - pc->error = error; -} - -void -pc_clear_error(struct player_control *pc) -{ - player_lock(pc); - - if (pc->error_type != PLAYER_ERROR_NONE) { - pc->error_type = PLAYER_ERROR_NONE; - g_error_free(pc->error); - } - - player_unlock(pc); -} - -char * -pc_get_error_message(struct player_control *pc) -{ - player_lock(pc); - char *message = pc->error_type != PLAYER_ERROR_NONE - ? g_strdup(pc->error->message) - : NULL; - player_unlock(pc); - return message; -} - -static void -pc_enqueue_song_locked(struct player_control *pc, struct song *song) -{ - assert(song != NULL); - assert(pc->next_song == NULL); - - pc->next_song = song; - player_command_locked(pc, PLAYER_COMMAND_QUEUE); -} - -void -pc_enqueue_song(struct player_control *pc, struct song *song) -{ - assert(song != NULL); - - player_lock(pc); - pc_enqueue_song_locked(pc, song); - player_unlock(pc); -} - -bool -pc_seek(struct player_control *pc, struct song *song, float seek_time) -{ - assert(song != NULL); - - player_lock(pc); - - if (pc->next_song != NULL) - song_free(pc->next_song); - - pc->next_song = song; - pc->seek_where = seek_time; - player_command_locked(pc, PLAYER_COMMAND_SEEK); - player_unlock(pc); - - assert(pc->next_song == NULL); - - idle_add(IDLE_PLAYER); - - return true; -} - -float -pc_get_cross_fade(const struct player_control *pc) -{ - return pc->cross_fade_seconds; -} - -void -pc_set_cross_fade(struct player_control *pc, float cross_fade_seconds) -{ - if (cross_fade_seconds < 0) - cross_fade_seconds = 0; - pc->cross_fade_seconds = cross_fade_seconds; - - idle_add(IDLE_OPTIONS); -} - -void -pc_set_mixramp_db(struct player_control *pc, float mixramp_db) -{ - pc->mixramp_db = mixramp_db; - - idle_add(IDLE_OPTIONS); -} - -void -pc_set_mixramp_delay(struct player_control *pc, float mixramp_delay_seconds) -{ - pc->mixramp_delay_seconds = mixramp_delay_seconds; - - idle_add(IDLE_OPTIONS); -} -- cgit v1.2.3