From 975deca85b3fb881571203c9a2cad4eb242cd954 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 2 Jan 2013 20:36:28 +0100 Subject: {decoder,player}_thread: convert to C++ --- src/DecoderThread.cxx | 521 +++++++++++++++++++++ src/DecoderThread.hxx | 28 ++ src/Main.cxx | 2 +- src/PlayerThread.cxx | 1198 ++++++++++++++++++++++++++++++++++++++++++++++++ src/PlayerThread.hxx | 45 ++ src/decoder_internal.h | 7 + src/decoder_thread.c | 529 --------------------- src/decoder_thread.h | 28 -- src/player_thread.c | 1197 ----------------------------------------------- src/player_thread.h | 45 -- 10 files changed, 1800 insertions(+), 1800 deletions(-) create mode 100644 src/DecoderThread.cxx create mode 100644 src/DecoderThread.hxx create mode 100644 src/PlayerThread.cxx create mode 100644 src/PlayerThread.hxx delete mode 100644 src/decoder_thread.c delete mode 100644 src/decoder_thread.h delete mode 100644 src/player_thread.c delete mode 100644 src/player_thread.h (limited to 'src') diff --git a/src/DecoderThread.cxx b/src/DecoderThread.cxx new file mode 100644 index 000000000..e5b582598 --- /dev/null +++ b/src/DecoderThread.cxx @@ -0,0 +1,521 @@ +/* + * 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 "DecoderThread.hxx" +#include "decoder_error.h" +#include "decoder_plugin.h" +#include "song.h" +#include "mpd_error.h" + +extern "C" { +#include "decoder_control.h" +#include "decoder_internal.h" +#include "decoder_list.h" +#include "decoder_api.h" +#include "replay_gain_ape.h" +#include "input_stream.h" +#include "tag.h" +#include "mapper.h" +#include "uri.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) +{ + return const_cast(plugin); +} + +/** + * 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) +{ + decoder decoder(dc, dc->start_ms > 0); + 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; + decoder.chunk = 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); + + /* flush the last chunk */ + + if (decoder.chunk != NULL) + decoder_flush_chunk(&decoder); + + 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); + + if (ret) + dc->state = DECODE_STATE_STOP; + else { + dc->state = DECODE_STATE_ERROR; + + const char *error_uri = song->uri; + char *allocated = uri_remove_auth(error_uri); + if (allocated != NULL) + error_uri = allocated; + + dc->error = g_error_new(decoder_quark(), 0, + "Failed to decode %s", error_uri); + g_free(allocated); + } +} + +static void +decoder_run(struct decoder_control *dc) +{ + dc_clear_error(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; + dc->error = g_error_new(decoder_quark(), 0, + "Failed to map song"); + + 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 = (struct decoder_control *)arg; + + decoder_lock(dc); + + do { + assert(dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR); + + switch (dc->command) { + case DECODE_COMMAND_START: + 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/DecoderThread.hxx b/src/DecoderThread.hxx new file mode 100644 index 000000000..8efaa2fca --- /dev/null +++ b/src/DecoderThread.hxx @@ -0,0 +1,28 @@ +/* + * 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_THREAD_HXX +#define MPD_DECODER_THREAD_HXX + +struct decoder_control; + +void +decoder_thread_start(struct decoder_control *dc); + +#endif diff --git a/src/Main.cxx b/src/Main.cxx index 9a5fa1cd9..0d8f66d7e 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -23,6 +23,7 @@ #include "UpdateGlue.hxx" #include "chunk.h" #include "StateFile.hxx" +#include "PlayerThread.hxx" extern "C" { #include "daemon.h" @@ -33,7 +34,6 @@ extern "C" { #include "AllCommands.h" #include "playlist.h" #include "database.h" -#include "player_thread.h" #include "listen.h" #include "cmdline.h" #include "conf.h" diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx new file mode 100644 index 000000000..ad7a008db --- /dev/null +++ b/src/PlayerThread.cxx @@ -0,0 +1,1198 @@ +/* + * 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 "PlayerThread.hxx" +#include "DecoderThread.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" +#include "tag.h" +#include "pipe.h" +#include "chunk.h" +#include "idle.h" +#include "buffer.h" +} + +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "player_thread" + +enum xfade_state { + XFADE_DISABLED = -1, + XFADE_UNKNOWN = 0, + XFADE_ENABLED = 1 +}; + +struct player { + struct player_control *pc; + + struct decoder_control *dc; + + struct music_pipe *pipe; + + /** + * are we waiting for buffered_before_play? + */ + bool buffering; + + /** + * true if the decoder is starting and did not provide data + * yet + */ + bool decoder_starting; + + /** + * is the player paused? + */ + bool paused; + + /** + * is there a new song in pc.next_song? + */ + bool queued; + + /** + * Was any audio output opened successfully? It might have + * failed meanwhile, but was not explicitly closed by the + * player thread. When this flag is unset, some output + * methods must not be called. + */ + bool output_open; + + /** + * the song currently being played + */ + struct song *song; + + /** + * is cross fading enabled? + */ + enum xfade_state xfade; + + /** + * has cross-fading begun? + */ + bool cross_fading; + + /** + * The number of chunks used for crossfading. + */ + unsigned cross_fade_chunks; + + /** + * The tag of the "next" song during cross-fade. It is + * postponed, and sent to the output thread when the new song + * really begins. + */ + struct tag *cross_fade_tag; + + /** + * The current audio format for the audio outputs. + */ + struct audio_format play_audio_format; + + /** + * The time stamp of the chunk most recently sent to the + * output thread. This attribute is only used if + * audio_output_all_get_elapsed_time() didn't return a usable + * value; the output thread can estimate the elapsed time more + * precisely. + */ + float elapsed_time; + + player(player_control *_pc, decoder_control *_dc) + :pc(_pc), dc(_dc), + buffering(false), + decoder_starting(false), + paused(false), + queued(true), + output_open(false), + song(NULL), + xfade(XFADE_UNKNOWN), + cross_fading(false), + cross_fade_chunks(0), + cross_fade_tag(NULL), + elapsed_time(0.0) {} +}; + +static struct music_buffer *player_buffer; + +static void +player_command_finished_locked(struct player_control *pc) +{ + assert(pc->command != PLAYER_COMMAND_NONE); + + pc->command = PLAYER_COMMAND_NONE; + g_cond_signal(main_cond); +} + +static void +player_command_finished(struct player_control *pc) +{ + player_lock(pc); + player_command_finished_locked(pc); + player_unlock(pc); +} + +/** + * Start the decoder. + * + * Player lock is not held. + */ +static void +player_dc_start(struct player *player, struct music_pipe *pipe) +{ + struct player_control *pc = player->pc; + struct decoder_control *dc = player->dc; + + assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); + assert(pc->next_song != NULL); + + unsigned start_ms = pc->next_song->start_ms; + if (pc->command == PLAYER_COMMAND_SEEK) + start_ms += (unsigned)(pc->seek_where * 1000); + + dc_start(dc, song_dup_detached(pc->next_song), + start_ms, pc->next_song->end_ms, + player_buffer, pipe); +} + +/** + * Is the decoder still busy on the same song as the player? + * + * Note: this function does not check if the decoder is already + * finished. + */ +static bool +player_dc_at_current_song(const struct player *player) +{ + assert(player != NULL); + assert(player->pipe != NULL); + + return player->dc->pipe == player->pipe; +} + +/** + * Returns true if the decoder is decoding the next song (or has begun + * decoding it, or has finished doing it), and the player hasn't + * switched to that song yet. + */ +static bool +player_dc_at_next_song(const struct player *player) +{ + return player->dc->pipe != NULL && !player_dc_at_current_song(player); +} + +/** + * Stop the decoder and clears (and frees) its music pipe. + * + * Player lock is not held. + */ +static void +player_dc_stop(struct player *player) +{ + struct decoder_control *dc = player->dc; + + dc_stop(dc); + + if (dc->pipe != NULL) { + /* clear and free the decoder pipe */ + + music_pipe_clear(dc->pipe, player_buffer); + + if (dc->pipe != player->pipe) + music_pipe_free(dc->pipe); + + dc->pipe = NULL; + } +} + +/** + * After the decoder has been started asynchronously, wait for the + * "START" command to finish. The decoder may not be initialized yet, + * i.e. there is no audio_format information yet. + * + * The player lock is not held. + */ +static bool +player_wait_for_decoder(struct player *player) +{ + struct player_control *pc = player->pc; + struct decoder_control *dc = player->dc; + + assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); + assert(pc->next_song != NULL); + + player->queued = false; + + GError *error = dc_lock_get_error(dc); + if (error != NULL) { + player_lock(pc); + pc_set_error(pc, PLAYER_ERROR_DECODER, error); + + song_free(pc->next_song); + pc->next_song = NULL; + + player_unlock(pc); + + return false; + } + + if (player->song != NULL) + song_free(player->song); + + player->song = pc->next_song; + player->elapsed_time = 0.0; + + /* set the "starting" flag, which will be cleared by + player_check_decoder_startup() */ + player->decoder_starting = true; + + player_lock(pc); + + /* update player_control's song information */ + pc->total_time = song_get_duration(pc->next_song); + pc->bit_rate = 0; + audio_format_clear(&pc->audio_format); + + /* clear the queued song */ + pc->next_song = NULL; + + player_unlock(pc); + + /* call syncPlaylistWithQueue() in the main thread */ + event_pipe_emit(PIPE_EVENT_PLAYLIST); + + return true; +} + +/** + * Returns the real duration of the song, comprising the duration + * indicated by the decoder plugin. + */ +static double +real_song_duration(const struct song *song, double decoder_duration) +{ + assert(song != NULL); + + if (decoder_duration <= 0.0) + /* the decoder plugin didn't provide information; fall + back to song_get_duration() */ + return song_get_duration(song); + + if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration) + return (song->end_ms - song->start_ms) / 1000.0; + + return decoder_duration - song->start_ms / 1000.0; +} + +/** + * Wrapper for audio_output_all_open(). Upon failure, it pauses the + * player. + * + * @return true on success + */ +static bool +player_open_output(struct player *player) +{ + struct player_control *pc = player->pc; + + assert(audio_format_defined(&player->play_audio_format)); + assert(pc->state == PLAYER_STATE_PLAY || + pc->state == PLAYER_STATE_PAUSE); + + GError *error = NULL; + if (audio_output_all_open(&player->play_audio_format, player_buffer, + &error)) { + player->output_open = true; + player->paused = false; + + player_lock(pc); + pc->state = PLAYER_STATE_PLAY; + player_unlock(pc); + + return true; + } else { + g_warning("%s", error->message); + + player->output_open = false; + + /* pause: the user may resume playback as soon as an + audio output becomes available */ + player->paused = true; + + player_lock(pc); + pc_set_error(pc, PLAYER_ERROR_OUTPUT, error); + pc->state = PLAYER_STATE_PAUSE; + player_unlock(pc); + + return false; + } +} + +/** + * The decoder has acknowledged the "START" command (see + * player_wait_for_decoder()). This function checks if the decoder + * initialization has completed yet. + * + * The player lock is not held. + */ +static bool +player_check_decoder_startup(struct player *player) +{ + struct player_control *pc = player->pc; + struct decoder_control *dc = player->dc; + + assert(player->decoder_starting); + + decoder_lock(dc); + + GError *error = dc_get_error(dc); + if (error != NULL) { + /* the decoder failed */ + decoder_unlock(dc); + + player_lock(pc); + pc_set_error(pc, PLAYER_ERROR_DECODER, error); + player_unlock(pc); + + return false; + } else if (!decoder_is_starting(dc)) { + /* the decoder is ready and ok */ + + decoder_unlock(dc); + + if (player->output_open && + !audio_output_all_wait(pc, 1)) + /* the output devices havn't finished playing + all chunks yet - wait for that */ + return true; + + player_lock(pc); + pc->total_time = real_song_duration(dc->song, dc->total_time); + pc->audio_format = dc->in_audio_format; + player_unlock(pc); + + player->play_audio_format = dc->out_audio_format; + player->decoder_starting = false; + + if (!player->paused && !player_open_output(player)) { + char *uri = song_get_uri(dc->song); + g_warning("problems opening audio device " + "while playing \"%s\"", uri); + g_free(uri); + + return true; + } + + return true; + } else { + /* the decoder is not yet ready; wait + some more */ + player_wait_decoder(pc, dc); + decoder_unlock(dc); + + return true; + } +} + +/** + * Sends a chunk of silence to the audio outputs. This is called when + * there is not enough decoded data in the pipe yet, to prevent + * underruns in the hardware buffers. + * + * The player lock is not held. + */ +static bool +player_send_silence(struct player *player) +{ + assert(player->output_open); + assert(audio_format_defined(&player->play_audio_format)); + + struct music_chunk *chunk = music_buffer_allocate(player_buffer); + if (chunk == NULL) { + g_warning("Failed to allocate silence buffer"); + return false; + } + +#ifndef NDEBUG + chunk->audio_format = player->play_audio_format; +#endif + + size_t frame_size = + audio_format_frame_size(&player->play_audio_format); + /* this formula ensures that we don't send + partial frames */ + unsigned num_frames = sizeof(chunk->data) / frame_size; + + chunk->times = -1.0; /* undefined time stamp */ + chunk->length = num_frames * frame_size; + memset(chunk->data, 0, chunk->length); + + GError *error = NULL; + if (!audio_output_all_play(chunk, &error)) { + g_warning("%s", error->message); + g_error_free(error); + + music_buffer_return(player_buffer, chunk); + return false; + } + + return true; +} + +/** + * This is the handler for the #PLAYER_COMMAND_SEEK command. + * + * The player lock is not held. + */ +static bool player_seek_decoder(struct player *player) +{ + struct player_control *pc = player->pc; + struct song *song = pc->next_song; + struct decoder_control *dc = player->dc; + + assert(pc->next_song != NULL); + + const unsigned start_ms = song->start_ms; + + if (!decoder_lock_is_current_song(dc, song)) { + /* the decoder is already decoding the "next" song - + stop it and start the previous song again */ + + player_dc_stop(player); + + /* clear music chunks which might still reside in the + pipe */ + music_pipe_clear(player->pipe, player_buffer); + + /* re-start the decoder */ + player_dc_start(player, player->pipe); + if (!player_wait_for_decoder(player)) { + /* decoder failure */ + player_command_finished(pc); + return false; + } + } else { + if (!player_dc_at_current_song(player)) { + /* the decoder is already decoding the "next" song, + but it is the same song file; exchange the pipe */ + music_pipe_clear(player->pipe, player_buffer); + music_pipe_free(player->pipe); + player->pipe = dc->pipe; + } + + song_free(pc->next_song); + pc->next_song = NULL; + player->queued = false; + } + + /* wait for the decoder to complete initialization */ + + while (player->decoder_starting) { + if (!player_check_decoder_startup(player)) { + /* decoder failure */ + player_command_finished(pc); + return false; + } + } + + /* send the SEEK command */ + + double where = pc->seek_where; + if (where > pc->total_time) + where = pc->total_time - 0.1; + if (where < 0.0) + where = 0.0; + + if (!dc_seek(dc, where + start_ms / 1000.0)) { + /* decoder failure */ + player_command_finished(pc); + return false; + } + + player->elapsed_time = where; + + player_command_finished(pc); + + player->xfade = XFADE_UNKNOWN; + + /* re-fill the buffer after seeking */ + player->buffering = true; + + audio_output_all_cancel(); + + return true; +} + +/** + * Player lock must be held before calling. + */ +static void player_process_command(struct player *player) +{ + struct player_control *pc = player->pc; + G_GNUC_UNUSED struct decoder_control *dc = player->dc; + + switch (pc->command) { + case PLAYER_COMMAND_NONE: + case PLAYER_COMMAND_STOP: + case PLAYER_COMMAND_EXIT: + case PLAYER_COMMAND_CLOSE_AUDIO: + break; + + case PLAYER_COMMAND_UPDATE_AUDIO: + player_unlock(pc); + audio_output_all_enable_disable(); + player_lock(pc); + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_QUEUE: + assert(pc->next_song != NULL); + assert(!player->queued); + assert(!player_dc_at_next_song(player)); + + player->queued = true; + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_PAUSE: + player_unlock(pc); + + player->paused = !player->paused; + if (player->paused) { + audio_output_all_pause(); + player_lock(pc); + + pc->state = PLAYER_STATE_PAUSE; + } else if (!audio_format_defined(&player->play_audio_format)) { + /* the decoder hasn't provided an audio format + yet - don't open the audio device yet */ + player_lock(pc); + + pc->state = PLAYER_STATE_PLAY; + } else { + player_open_output(player); + + player_lock(pc); + } + + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_SEEK: + player_unlock(pc); + player_seek_decoder(player); + player_lock(pc); + break; + + case PLAYER_COMMAND_CANCEL: + if (pc->next_song == NULL) { + /* the cancel request arrived too late, we're + already playing the queued song... stop + everything now */ + pc->command = PLAYER_COMMAND_STOP; + return; + } + + if (player_dc_at_next_song(player)) { + /* the decoder is already decoding the song - + stop it and reset the position */ + player_unlock(pc); + player_dc_stop(player); + player_lock(pc); + } + + song_free(pc->next_song); + pc->next_song = NULL; + player->queued = false; + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_REFRESH: + if (player->output_open && !player->paused) { + player_unlock(pc); + audio_output_all_check(); + player_lock(pc); + } + + pc->elapsed_time = audio_output_all_get_elapsed_time(); + if (pc->elapsed_time < 0.0) + pc->elapsed_time = player->elapsed_time; + + player_command_finished_locked(pc); + break; + } +} + +static void +update_song_tag(struct song *song, const struct tag *new_tag) +{ + if (song_is_file(song)) + /* don't update tags of local files, only remote + streams may change tags dynamically */ + return; + + struct tag *old_tag = song->tag; + song->tag = tag_dup(new_tag); + + if (old_tag != NULL) + tag_free(old_tag); + + /* the main thread will update the playlist version when he + receives this event */ + event_pipe_emit(PIPE_EVENT_TAG); + + /* notify all clients that the tag of the current song has + changed */ + idle_add(IDLE_PLAYER); +} + +/** + * Plays a #music_chunk object (after applying software volume). If + * it contains a (stream) tag, copy it to the current song, so MPD's + * playlist reflects the new stream tag. + * + * Player lock is not held. + */ +static bool +play_chunk(struct player_control *pc, + struct song *song, struct music_chunk *chunk, + const struct audio_format *format, + GError **error_r) +{ + assert(music_chunk_check_format(chunk, format)); + + if (chunk->tag != NULL) + update_song_tag(song, chunk->tag); + + if (chunk->length == 0) { + music_buffer_return(player_buffer, chunk); + return true; + } + + player_lock(pc); + pc->bit_rate = chunk->bit_rate; + player_unlock(pc); + + /* send the chunk to the audio outputs */ + + if (!audio_output_all_play(chunk, error_r)) + return false; + + pc->total_play_time += (double)chunk->length / + audio_format_time_to_size(format); + return true; +} + +/** + * Obtains the next chunk from the music pipe, optionally applies + * cross-fading, and sends it to all audio outputs. + * + * @return true on success, false on error (playback will be stopped) + */ +static bool +play_next_chunk(struct player *player) +{ + struct player_control *pc = player->pc; + struct decoder_control *dc = player->dc; + + if (!audio_output_all_wait(pc, 64)) + /* the output pipe is still large enough, don't send + another chunk */ + return true; + + unsigned cross_fade_position; + struct music_chunk *chunk = NULL; + if (player->xfade == XFADE_ENABLED && + player_dc_at_next_song(player) && + (cross_fade_position = music_pipe_size(player->pipe)) + <= player->cross_fade_chunks) { + /* perform cross fade */ + struct music_chunk *other_chunk = + music_pipe_shift(dc->pipe); + + if (!player->cross_fading) { + /* beginning of the cross fade - adjust + crossFadeChunks which might be bigger than + the remaining number of chunks in the old + song */ + player->cross_fade_chunks = cross_fade_position; + player->cross_fading = true; + } + + if (other_chunk != NULL) { + chunk = music_pipe_shift(player->pipe); + assert(chunk != NULL); + assert(chunk->other == NULL); + + /* don't send the tags of the new song (which + is being faded in) yet; postpone it until + the current song is faded out */ + player->cross_fade_tag = + tag_merge_replace(player->cross_fade_tag, + other_chunk->tag); + other_chunk->tag = NULL; + + if (isnan(pc->mixramp_delay_seconds)) { + chunk->mix_ratio = ((float)cross_fade_position) + / player->cross_fade_chunks; + } else { + chunk->mix_ratio = nan(""); + } + + if (music_chunk_is_empty(other_chunk)) { + /* the "other" chunk was a music_chunk + which had only a tag, but no music + data - we cannot cross-fade that; + but since this happens only at the + beginning of the new song, we can + easily recover by throwing it away + now */ + music_buffer_return(player_buffer, + other_chunk); + other_chunk = NULL; + } + + chunk->other = other_chunk; + } else { + /* there are not enough decoded chunks yet */ + + decoder_lock(dc); + + if (decoder_is_idle(dc)) { + /* the decoder isn't running, abort + cross fading */ + decoder_unlock(dc); + + player->xfade = XFADE_DISABLED; + } else { + /* wait for the decoder */ + decoder_signal(dc); + player_wait_decoder(pc, dc); + decoder_unlock(dc); + + return true; + } + } + } + + if (chunk == NULL) + chunk = music_pipe_shift(player->pipe); + + assert(chunk != NULL); + + /* insert the postponed tag if cross-fading is finished */ + + if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) { + chunk->tag = tag_merge_replace(chunk->tag, + player->cross_fade_tag); + player->cross_fade_tag = NULL; + } + + /* play the current chunk */ + + GError *error = NULL; + if (!play_chunk(player->pc, player->song, chunk, + &player->play_audio_format, &error)) { + g_warning("%s", error->message); + + music_buffer_return(player_buffer, chunk); + + player_lock(pc); + + pc_set_error(pc, PLAYER_ERROR_OUTPUT, error); + + /* pause: the user may resume playback as soon as an + audio output becomes available */ + pc->state = PLAYER_STATE_PAUSE; + player->paused = true; + + player_unlock(pc); + + return false; + } + + /* this formula should prevent that the decoder gets woken up + with each chunk; it is more efficient to make it decode a + larger block at a time */ + decoder_lock(dc); + if (!decoder_is_idle(dc) && + music_pipe_size(dc->pipe) <= (pc->buffered_before_play + + music_buffer_size(player_buffer) * 3) / 4) + decoder_signal(dc); + decoder_unlock(dc); + + return true; +} + +/** + * This is called at the border between two songs: the audio output + * has consumed all chunks of the current song, and we should start + * sending chunks from the next one. + * + * The player lock is not held. + * + * @return true on success, false on error (playback will be stopped) + */ +static bool +player_song_border(struct player *player) +{ + player->xfade = XFADE_UNKNOWN; + + char *uri = song_get_uri(player->song); + g_message("played \"%s\"", uri); + g_free(uri); + + music_pipe_free(player->pipe); + player->pipe = player->dc->pipe; + + audio_output_all_song_border(); + + if (!player_wait_for_decoder(player)) + return false; + + struct player_control *const pc = player->pc; + player_lock(pc); + + if (pc->border_pause) { + player->paused = true; + pc->state = PLAYER_STATE_PAUSE; + } + + player_unlock(pc); + + return true; +} + +/* + * The main loop of the player thread, during playback. This is + * basically a state machine, which multiplexes data between the + * decoder thread and the output threads. + */ +static void do_play(struct player_control *pc, struct decoder_control *dc) +{ + player player(pc, dc); + + player_unlock(pc); + + player.pipe = music_pipe_new(); + + player_dc_start(&player, player.pipe); + if (!player_wait_for_decoder(&player)) { + assert(player.song == NULL); + + player_dc_stop(&player); + player_command_finished(pc); + music_pipe_free(player.pipe); + event_pipe_emit(PIPE_EVENT_PLAYLIST); + player_lock(pc); + return; + } + + player_lock(pc); + pc->state = PLAYER_STATE_PLAY; + + if (pc->command == PLAYER_COMMAND_SEEK) + player.elapsed_time = pc->seek_where; + + player_command_finished_locked(pc); + + while (true) { + player_process_command(&player); + if (pc->command == PLAYER_COMMAND_STOP || + pc->command == PLAYER_COMMAND_EXIT || + pc->command == PLAYER_COMMAND_CLOSE_AUDIO) { + player_unlock(pc); + audio_output_all_cancel(); + break; + } + + player_unlock(pc); + + if (player.buffering) { + /* buffering at the start of the song - wait + until the buffer is large enough, to + prevent stuttering on slow machines */ + + if (music_pipe_size(player.pipe) < pc->buffered_before_play && + !decoder_lock_is_idle(dc)) { + /* not enough decoded buffer space yet */ + + if (!player.paused && + player.output_open && + audio_output_all_check() < 4 && + !player_send_silence(&player)) + break; + + decoder_lock(dc); + /* XXX race condition: check decoder again */ + player_wait_decoder(pc, dc); + decoder_unlock(dc); + player_lock(pc); + continue; + } else { + /* buffering is complete */ + player.buffering = false; + } + } + + if (player.decoder_starting) { + /* wait until the decoder is initialized completely */ + + if (!player_check_decoder_startup(&player)) + break; + + player_lock(pc); + continue; + } + +#ifndef NDEBUG + /* + music_pipe_check_format(&play_audio_format, + player.next_song_chunk, + &dc->out_audio_format); + */ +#endif + + if (decoder_lock_is_idle(dc) && player.queued && + dc->pipe == player.pipe) { + /* the decoder has finished the current song; + make it decode the next song */ + + assert(dc->pipe == NULL || dc->pipe == player.pipe); + + player_dc_start(&player, music_pipe_new()); + } + + if (/* no cross-fading if MPD is going to pause at the + end of the current song */ + !pc->border_pause && + player_dc_at_next_song(&player) && + player.xfade == XFADE_UNKNOWN && + !decoder_lock_is_starting(dc)) { + /* enable cross fading in this song? if yes, + calculate how many chunks will be required + for it */ + player.cross_fade_chunks = + cross_fade_calc(pc->cross_fade_seconds, dc->total_time, + pc->mixramp_db, + pc->mixramp_delay_seconds, + dc->replay_gain_db, + dc->replay_gain_prev_db, + dc->mixramp_start, + dc->mixramp_prev_end, + &dc->out_audio_format, + &player.play_audio_format, + music_buffer_size(player_buffer) - + pc->buffered_before_play); + if (player.cross_fade_chunks > 0) { + player.xfade = XFADE_ENABLED; + player.cross_fading = false; + } else + /* cross fading is disabled or the + next song is too short */ + player.xfade = XFADE_DISABLED; + } + + if (player.paused) { + player_lock(pc); + + if (pc->command == PLAYER_COMMAND_NONE) + player_wait(pc); + continue; + } else if (!music_pipe_empty(player.pipe)) { + /* at least one music chunk is ready - send it + to the audio output */ + + play_next_chunk(&player); + } else if (audio_output_all_check() > 0) { + /* not enough data from decoder, but the + output thread is still busy, so it's + okay */ + + /* XXX synchronize in a better way */ + g_usleep(10000); + } else if (player_dc_at_next_song(&player)) { + /* at the beginning of a new song */ + + if (!player_song_border(&player)) + break; + } else if (decoder_lock_is_idle(dc)) { + /* check the size of the pipe again, because + the decoder thread may have added something + since we last checked */ + if (music_pipe_empty(player.pipe)) { + /* wait for the hardware to finish + playback */ + audio_output_all_drain(); + break; + } + } else if (player.output_open) { + /* the decoder is too busy and hasn't provided + new PCM data in time: send silence (if the + output pipe is empty) */ + if (!player_send_silence(&player)) + break; + } + + player_lock(pc); + } + + player_dc_stop(&player); + + music_pipe_clear(player.pipe, player_buffer); + music_pipe_free(player.pipe); + + if (player.cross_fade_tag != NULL) + tag_free(player.cross_fade_tag); + + if (player.song != NULL) + song_free(player.song); + + player_lock(pc); + + if (player.queued) { + assert(pc->next_song != NULL); + song_free(pc->next_song); + pc->next_song = NULL; + } + + pc->state = PLAYER_STATE_STOP; + + player_unlock(pc); + + event_pipe_emit(PIPE_EVENT_PLAYLIST); + + player_lock(pc); +} + +static gpointer +player_task(gpointer arg) +{ + struct player_control *pc = (struct player_control *)arg; + + struct decoder_control *dc = dc_new(pc->cond); + decoder_thread_start(dc); + + player_buffer = music_buffer_new(pc->buffer_chunks); + + player_lock(pc); + + while (1) { + switch (pc->command) { + case PLAYER_COMMAND_SEEK: + case PLAYER_COMMAND_QUEUE: + assert(pc->next_song != NULL); + + do_play(pc, dc); + break; + + case PLAYER_COMMAND_STOP: + player_unlock(pc); + audio_output_all_cancel(); + player_lock(pc); + + /* fall through */ + + case PLAYER_COMMAND_PAUSE: + if (pc->next_song != NULL) { + song_free(pc->next_song); + pc->next_song = NULL; + } + + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_CLOSE_AUDIO: + player_unlock(pc); + + audio_output_all_release(); + + player_lock(pc); + player_command_finished_locked(pc); + +#ifndef NDEBUG + /* in the DEBUG build, check for leaked + music_chunk objects by freeing the + music_buffer */ + music_buffer_free(player_buffer); + player_buffer = music_buffer_new(pc->buffer_chunks); +#endif + + break; + + case PLAYER_COMMAND_UPDATE_AUDIO: + player_unlock(pc); + audio_output_all_enable_disable(); + player_lock(pc); + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_EXIT: + player_unlock(pc); + + dc_quit(dc); + dc_free(dc); + audio_output_all_close(); + music_buffer_free(player_buffer); + + player_command_finished(pc); + return NULL; + + case PLAYER_COMMAND_CANCEL: + if (pc->next_song != NULL) { + song_free(pc->next_song); + pc->next_song = NULL; + } + + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_REFRESH: + /* no-op when not playing */ + player_command_finished_locked(pc); + break; + + case PLAYER_COMMAND_NONE: + player_wait(pc); + break; + } + } +} + +void +player_create(struct player_control *pc) +{ + assert(pc->thread == NULL); + + GError *e = NULL; + pc->thread = g_thread_create(player_task, pc, true, &e); + if (pc->thread == NULL) + MPD_ERROR("Failed to spawn player task: %s", e->message); +} diff --git a/src/PlayerThread.hxx b/src/PlayerThread.hxx new file mode 100644 index 000000000..197669cd6 --- /dev/null +++ b/src/PlayerThread.hxx @@ -0,0 +1,45 @@ +/* + * 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. + */ + +/* \file + * + * The player thread controls the playback. It acts as a bridge + * between the decoder thread and the output thread(s): it receives + * #music_chunk objects from the decoder, optionally mixes them + * (cross-fading), applies software volume, and sends them to the + * audio outputs via audio_output_all_play(). + * + * It is controlled by the main thread (the playlist code), see + * player_control.h. The playlist enqueues new songs into the player + * thread and sends it commands. + * + * The player thread itself does not do any I/O. It synchronizes with + * other threads via #GMutex and #GCond objects, and passes + * #music_chunk instances around in #music_pipe objects. + */ + +#ifndef MPD_PLAYER_THREAD_HXX +#define MPD_PLAYER_THREAD_HXX + +struct player_control; + +void +player_create(struct player_control *pc); + +#endif diff --git a/src/decoder_internal.h b/src/decoder_internal.h index d89e68cfc..5bc7e216d 100644 --- a/src/decoder_internal.h +++ b/src/decoder_internal.h @@ -80,6 +80,13 @@ struct decoder { * 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 }; /** diff --git a/src/decoder_thread.c b/src/decoder_thread.c deleted file mode 100644 index b13f2a46a..000000000 --- a/src/decoder_thread.c +++ /dev/null @@ -1,529 +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_error.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; - decoder.chunk = 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); - - /* flush the last chunk */ - - if (decoder.chunk != NULL) - decoder_flush_chunk(&decoder); - - 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); - - if (ret) - dc->state = DECODE_STATE_STOP; - else { - dc->state = DECODE_STATE_ERROR; - - const char *error_uri = song->uri; - char *allocated = uri_remove_auth(error_uri); - if (allocated != NULL) - error_uri = allocated; - - dc->error = g_error_new(decoder_quark(), 0, - "Failed to decode %s", error_uri); - g_free(allocated); - } -} - -static void -decoder_run(struct decoder_control *dc) -{ - dc_clear_error(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; - dc->error = g_error_new(decoder_quark(), 0, - "Failed to map song"); - - 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: - 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_thread.h b/src/decoder_thread.h deleted file mode 100644 index 78f12a54a..000000000 --- a/src/decoder_thread.h +++ /dev/null @@ -1,28 +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_THREAD_H -#define MPD_DECODER_THREAD_H - -struct decoder_control; - -void -decoder_thread_start(struct decoder_control *dc); - -#endif diff --git a/src/player_thread.c b/src/player_thread.c deleted file mode 100644 index 561c595eb..000000000 --- a/src/player_thread.c +++ /dev/null @@ -1,1197 +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_thread.h" -#include "player_control.h" -#include "decoder_control.h" -#include "decoder_thread.h" -#include "output_all.h" -#include "pcm_volume.h" -#include "path.h" -#include "event_pipe.h" -#include "crossfade.h" -#include "song.h" -#include "tag.h" -#include "pipe.h" -#include "chunk.h" -#include "idle.h" -#include "Main.hxx" -#include "buffer.h" -#include "mpd_error.h" - -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "player_thread" - -enum xfade_state { - XFADE_DISABLED = -1, - XFADE_UNKNOWN = 0, - XFADE_ENABLED = 1 -}; - -struct player { - struct player_control *pc; - - struct decoder_control *dc; - - struct music_pipe *pipe; - - /** - * are we waiting for buffered_before_play? - */ - bool buffering; - - /** - * true if the decoder is starting and did not provide data - * yet - */ - bool decoder_starting; - - /** - * is the player paused? - */ - bool paused; - - /** - * is there a new song in pc.next_song? - */ - bool queued; - - /** - * Was any audio output opened successfully? It might have - * failed meanwhile, but was not explicitly closed by the - * player thread. When this flag is unset, some output - * methods must not be called. - */ - bool output_open; - - /** - * the song currently being played - */ - struct song *song; - - /** - * is cross fading enabled? - */ - enum xfade_state xfade; - - /** - * has cross-fading begun? - */ - bool cross_fading; - - /** - * The number of chunks used for crossfading. - */ - unsigned cross_fade_chunks; - - /** - * The tag of the "next" song during cross-fade. It is - * postponed, and sent to the output thread when the new song - * really begins. - */ - struct tag *cross_fade_tag; - - /** - * The current audio format for the audio outputs. - */ - struct audio_format play_audio_format; - - /** - * The time stamp of the chunk most recently sent to the - * output thread. This attribute is only used if - * audio_output_all_get_elapsed_time() didn't return a usable - * value; the output thread can estimate the elapsed time more - * precisely. - */ - float elapsed_time; -}; - -static struct music_buffer *player_buffer; - -static void -player_command_finished_locked(struct player_control *pc) -{ - assert(pc->command != PLAYER_COMMAND_NONE); - - pc->command = PLAYER_COMMAND_NONE; - g_cond_signal(main_cond); -} - -static void -player_command_finished(struct player_control *pc) -{ - player_lock(pc); - player_command_finished_locked(pc); - player_unlock(pc); -} - -/** - * Start the decoder. - * - * Player lock is not held. - */ -static void -player_dc_start(struct player *player, struct music_pipe *pipe) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); - assert(pc->next_song != NULL); - - unsigned start_ms = pc->next_song->start_ms; - if (pc->command == PLAYER_COMMAND_SEEK) - start_ms += (unsigned)(pc->seek_where * 1000); - - dc_start(dc, song_dup_detached(pc->next_song), - start_ms, pc->next_song->end_ms, - player_buffer, pipe); -} - -/** - * Is the decoder still busy on the same song as the player? - * - * Note: this function does not check if the decoder is already - * finished. - */ -static bool -player_dc_at_current_song(const struct player *player) -{ - assert(player != NULL); - assert(player->pipe != NULL); - - return player->dc->pipe == player->pipe; -} - -/** - * Returns true if the decoder is decoding the next song (or has begun - * decoding it, or has finished doing it), and the player hasn't - * switched to that song yet. - */ -static bool -player_dc_at_next_song(const struct player *player) -{ - return player->dc->pipe != NULL && !player_dc_at_current_song(player); -} - -/** - * Stop the decoder and clears (and frees) its music pipe. - * - * Player lock is not held. - */ -static void -player_dc_stop(struct player *player) -{ - struct decoder_control *dc = player->dc; - - dc_stop(dc); - - if (dc->pipe != NULL) { - /* clear and free the decoder pipe */ - - music_pipe_clear(dc->pipe, player_buffer); - - if (dc->pipe != player->pipe) - music_pipe_free(dc->pipe); - - dc->pipe = NULL; - } -} - -/** - * After the decoder has been started asynchronously, wait for the - * "START" command to finish. The decoder may not be initialized yet, - * i.e. there is no audio_format information yet. - * - * The player lock is not held. - */ -static bool -player_wait_for_decoder(struct player *player) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); - assert(pc->next_song != NULL); - - player->queued = false; - - GError *error = dc_lock_get_error(dc); - if (error != NULL) { - player_lock(pc); - pc_set_error(pc, PLAYER_ERROR_DECODER, error); - - song_free(pc->next_song); - pc->next_song = NULL; - - player_unlock(pc); - - return false; - } - - if (player->song != NULL) - song_free(player->song); - - player->song = pc->next_song; - player->elapsed_time = 0.0; - - /* set the "starting" flag, which will be cleared by - player_check_decoder_startup() */ - player->decoder_starting = true; - - player_lock(pc); - - /* update player_control's song information */ - pc->total_time = song_get_duration(pc->next_song); - pc->bit_rate = 0; - audio_format_clear(&pc->audio_format); - - /* clear the queued song */ - pc->next_song = NULL; - - player_unlock(pc); - - /* call syncPlaylistWithQueue() in the main thread */ - event_pipe_emit(PIPE_EVENT_PLAYLIST); - - return true; -} - -/** - * Returns the real duration of the song, comprising the duration - * indicated by the decoder plugin. - */ -static double -real_song_duration(const struct song *song, double decoder_duration) -{ - assert(song != NULL); - - if (decoder_duration <= 0.0) - /* the decoder plugin didn't provide information; fall - back to song_get_duration() */ - return song_get_duration(song); - - if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration) - return (song->end_ms - song->start_ms) / 1000.0; - - return decoder_duration - song->start_ms / 1000.0; -} - -/** - * Wrapper for audio_output_all_open(). Upon failure, it pauses the - * player. - * - * @return true on success - */ -static bool -player_open_output(struct player *player) -{ - struct player_control *pc = player->pc; - - assert(audio_format_defined(&player->play_audio_format)); - assert(pc->state == PLAYER_STATE_PLAY || - pc->state == PLAYER_STATE_PAUSE); - - GError *error = NULL; - if (audio_output_all_open(&player->play_audio_format, player_buffer, - &error)) { - player->output_open = true; - player->paused = false; - - player_lock(pc); - pc->state = PLAYER_STATE_PLAY; - player_unlock(pc); - - return true; - } else { - g_warning("%s", error->message); - - player->output_open = false; - - /* pause: the user may resume playback as soon as an - audio output becomes available */ - player->paused = true; - - player_lock(pc); - pc_set_error(pc, PLAYER_ERROR_OUTPUT, error); - pc->state = PLAYER_STATE_PAUSE; - player_unlock(pc); - - return false; - } -} - -/** - * The decoder has acknowledged the "START" command (see - * player_wait_for_decoder()). This function checks if the decoder - * initialization has completed yet. - * - * The player lock is not held. - */ -static bool -player_check_decoder_startup(struct player *player) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - assert(player->decoder_starting); - - decoder_lock(dc); - - GError *error = dc_get_error(dc); - if (error != NULL) { - /* the decoder failed */ - decoder_unlock(dc); - - player_lock(pc); - pc_set_error(pc, PLAYER_ERROR_DECODER, error); - player_unlock(pc); - - return false; - } else if (!decoder_is_starting(dc)) { - /* the decoder is ready and ok */ - - decoder_unlock(dc); - - if (player->output_open && - !audio_output_all_wait(pc, 1)) - /* the output devices havn't finished playing - all chunks yet - wait for that */ - return true; - - player_lock(pc); - pc->total_time = real_song_duration(dc->song, dc->total_time); - pc->audio_format = dc->in_audio_format; - player_unlock(pc); - - player->play_audio_format = dc->out_audio_format; - player->decoder_starting = false; - - if (!player->paused && !player_open_output(player)) { - char *uri = song_get_uri(dc->song); - g_warning("problems opening audio device " - "while playing \"%s\"", uri); - g_free(uri); - - return true; - } - - return true; - } else { - /* the decoder is not yet ready; wait - some more */ - player_wait_decoder(pc, dc); - decoder_unlock(dc); - - return true; - } -} - -/** - * Sends a chunk of silence to the audio outputs. This is called when - * there is not enough decoded data in the pipe yet, to prevent - * underruns in the hardware buffers. - * - * The player lock is not held. - */ -static bool -player_send_silence(struct player *player) -{ - assert(player->output_open); - assert(audio_format_defined(&player->play_audio_format)); - - struct music_chunk *chunk = music_buffer_allocate(player_buffer); - if (chunk == NULL) { - g_warning("Failed to allocate silence buffer"); - return false; - } - -#ifndef NDEBUG - chunk->audio_format = player->play_audio_format; -#endif - - size_t frame_size = - audio_format_frame_size(&player->play_audio_format); - /* this formula ensures that we don't send - partial frames */ - unsigned num_frames = sizeof(chunk->data) / frame_size; - - chunk->times = -1.0; /* undefined time stamp */ - chunk->length = num_frames * frame_size; - memset(chunk->data, 0, chunk->length); - - GError *error = NULL; - if (!audio_output_all_play(chunk, &error)) { - g_warning("%s", error->message); - g_error_free(error); - - music_buffer_return(player_buffer, chunk); - return false; - } - - return true; -} - -/** - * This is the handler for the #PLAYER_COMMAND_SEEK command. - * - * The player lock is not held. - */ -static bool player_seek_decoder(struct player *player) -{ - struct player_control *pc = player->pc; - struct song *song = pc->next_song; - struct decoder_control *dc = player->dc; - - assert(pc->next_song != NULL); - - const unsigned start_ms = song->start_ms; - - if (!decoder_lock_is_current_song(dc, song)) { - /* the decoder is already decoding the "next" song - - stop it and start the previous song again */ - - player_dc_stop(player); - - /* clear music chunks which might still reside in the - pipe */ - music_pipe_clear(player->pipe, player_buffer); - - /* re-start the decoder */ - player_dc_start(player, player->pipe); - if (!player_wait_for_decoder(player)) { - /* decoder failure */ - player_command_finished(pc); - return false; - } - } else { - if (!player_dc_at_current_song(player)) { - /* the decoder is already decoding the "next" song, - but it is the same song file; exchange the pipe */ - music_pipe_clear(player->pipe, player_buffer); - music_pipe_free(player->pipe); - player->pipe = dc->pipe; - } - - song_free(pc->next_song); - pc->next_song = NULL; - player->queued = false; - } - - /* wait for the decoder to complete initialization */ - - while (player->decoder_starting) { - if (!player_check_decoder_startup(player)) { - /* decoder failure */ - player_command_finished(pc); - return false; - } - } - - /* send the SEEK command */ - - double where = pc->seek_where; - if (where > pc->total_time) - where = pc->total_time - 0.1; - if (where < 0.0) - where = 0.0; - - if (!dc_seek(dc, where + start_ms / 1000.0)) { - /* decoder failure */ - player_command_finished(pc); - return false; - } - - player->elapsed_time = where; - - player_command_finished(pc); - - player->xfade = XFADE_UNKNOWN; - - /* re-fill the buffer after seeking */ - player->buffering = true; - - audio_output_all_cancel(); - - return true; -} - -/** - * Player lock must be held before calling. - */ -static void player_process_command(struct player *player) -{ - struct player_control *pc = player->pc; - G_GNUC_UNUSED struct decoder_control *dc = player->dc; - - switch (pc->command) { - case PLAYER_COMMAND_NONE: - case PLAYER_COMMAND_STOP: - case PLAYER_COMMAND_EXIT: - case PLAYER_COMMAND_CLOSE_AUDIO: - break; - - case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(pc); - audio_output_all_enable_disable(); - player_lock(pc); - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_QUEUE: - assert(pc->next_song != NULL); - assert(!player->queued); - assert(!player_dc_at_next_song(player)); - - player->queued = true; - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_PAUSE: - player_unlock(pc); - - player->paused = !player->paused; - if (player->paused) { - audio_output_all_pause(); - player_lock(pc); - - pc->state = PLAYER_STATE_PAUSE; - } else if (!audio_format_defined(&player->play_audio_format)) { - /* the decoder hasn't provided an audio format - yet - don't open the audio device yet */ - player_lock(pc); - - pc->state = PLAYER_STATE_PLAY; - } else { - player_open_output(player); - - player_lock(pc); - } - - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_SEEK: - player_unlock(pc); - player_seek_decoder(player); - player_lock(pc); - break; - - case PLAYER_COMMAND_CANCEL: - if (pc->next_song == NULL) { - /* the cancel request arrived too late, we're - already playing the queued song... stop - everything now */ - pc->command = PLAYER_COMMAND_STOP; - return; - } - - if (player_dc_at_next_song(player)) { - /* the decoder is already decoding the song - - stop it and reset the position */ - player_unlock(pc); - player_dc_stop(player); - player_lock(pc); - } - - song_free(pc->next_song); - pc->next_song = NULL; - player->queued = false; - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_REFRESH: - if (player->output_open && !player->paused) { - player_unlock(pc); - audio_output_all_check(); - player_lock(pc); - } - - pc->elapsed_time = audio_output_all_get_elapsed_time(); - if (pc->elapsed_time < 0.0) - pc->elapsed_time = player->elapsed_time; - - player_command_finished_locked(pc); - break; - } -} - -static void -update_song_tag(struct song *song, const struct tag *new_tag) -{ - if (song_is_file(song)) - /* don't update tags of local files, only remote - streams may change tags dynamically */ - return; - - struct tag *old_tag = song->tag; - song->tag = tag_dup(new_tag); - - if (old_tag != NULL) - tag_free(old_tag); - - /* the main thread will update the playlist version when he - receives this event */ - event_pipe_emit(PIPE_EVENT_TAG); - - /* notify all clients that the tag of the current song has - changed */ - idle_add(IDLE_PLAYER); -} - -/** - * Plays a #music_chunk object (after applying software volume). If - * it contains a (stream) tag, copy it to the current song, so MPD's - * playlist reflects the new stream tag. - * - * Player lock is not held. - */ -static bool -play_chunk(struct player_control *pc, - struct song *song, struct music_chunk *chunk, - const struct audio_format *format, - GError **error_r) -{ - assert(music_chunk_check_format(chunk, format)); - - if (chunk->tag != NULL) - update_song_tag(song, chunk->tag); - - if (chunk->length == 0) { - music_buffer_return(player_buffer, chunk); - return true; - } - - player_lock(pc); - pc->bit_rate = chunk->bit_rate; - player_unlock(pc); - - /* send the chunk to the audio outputs */ - - if (!audio_output_all_play(chunk, error_r)) - return false; - - pc->total_play_time += (double)chunk->length / - audio_format_time_to_size(format); - return true; -} - -/** - * Obtains the next chunk from the music pipe, optionally applies - * cross-fading, and sends it to all audio outputs. - * - * @return true on success, false on error (playback will be stopped) - */ -static bool -play_next_chunk(struct player *player) -{ - struct player_control *pc = player->pc; - struct decoder_control *dc = player->dc; - - if (!audio_output_all_wait(pc, 64)) - /* the output pipe is still large enough, don't send - another chunk */ - return true; - - unsigned cross_fade_position; - struct music_chunk *chunk = NULL; - if (player->xfade == XFADE_ENABLED && - player_dc_at_next_song(player) && - (cross_fade_position = music_pipe_size(player->pipe)) - <= player->cross_fade_chunks) { - /* perform cross fade */ - struct music_chunk *other_chunk = - music_pipe_shift(dc->pipe); - - if (!player->cross_fading) { - /* beginning of the cross fade - adjust - crossFadeChunks which might be bigger than - the remaining number of chunks in the old - song */ - player->cross_fade_chunks = cross_fade_position; - player->cross_fading = true; - } - - if (other_chunk != NULL) { - chunk = music_pipe_shift(player->pipe); - assert(chunk != NULL); - assert(chunk->other == NULL); - - /* don't send the tags of the new song (which - is being faded in) yet; postpone it until - the current song is faded out */ - player->cross_fade_tag = - tag_merge_replace(player->cross_fade_tag, - other_chunk->tag); - other_chunk->tag = NULL; - - if (isnan(pc->mixramp_delay_seconds)) { - chunk->mix_ratio = ((float)cross_fade_position) - / player->cross_fade_chunks; - } else { - chunk->mix_ratio = nan(""); - } - - if (music_chunk_is_empty(other_chunk)) { - /* the "other" chunk was a music_chunk - which had only a tag, but no music - data - we cannot cross-fade that; - but since this happens only at the - beginning of the new song, we can - easily recover by throwing it away - now */ - music_buffer_return(player_buffer, - other_chunk); - other_chunk = NULL; - } - - chunk->other = other_chunk; - } else { - /* there are not enough decoded chunks yet */ - - decoder_lock(dc); - - if (decoder_is_idle(dc)) { - /* the decoder isn't running, abort - cross fading */ - decoder_unlock(dc); - - player->xfade = XFADE_DISABLED; - } else { - /* wait for the decoder */ - decoder_signal(dc); - player_wait_decoder(pc, dc); - decoder_unlock(dc); - - return true; - } - } - } - - if (chunk == NULL) - chunk = music_pipe_shift(player->pipe); - - assert(chunk != NULL); - - /* insert the postponed tag if cross-fading is finished */ - - if (player->xfade != XFADE_ENABLED && player->cross_fade_tag != NULL) { - chunk->tag = tag_merge_replace(chunk->tag, - player->cross_fade_tag); - player->cross_fade_tag = NULL; - } - - /* play the current chunk */ - - GError *error = NULL; - if (!play_chunk(player->pc, player->song, chunk, - &player->play_audio_format, &error)) { - g_warning("%s", error->message); - - music_buffer_return(player_buffer, chunk); - - player_lock(pc); - - pc_set_error(pc, PLAYER_ERROR_OUTPUT, error); - - /* pause: the user may resume playback as soon as an - audio output becomes available */ - pc->state = PLAYER_STATE_PAUSE; - player->paused = true; - - player_unlock(pc); - - return false; - } - - /* this formula should prevent that the decoder gets woken up - with each chunk; it is more efficient to make it decode a - larger block at a time */ - decoder_lock(dc); - if (!decoder_is_idle(dc) && - music_pipe_size(dc->pipe) <= (pc->buffered_before_play + - music_buffer_size(player_buffer) * 3) / 4) - decoder_signal(dc); - decoder_unlock(dc); - - return true; -} - -/** - * This is called at the border between two songs: the audio output - * has consumed all chunks of the current song, and we should start - * sending chunks from the next one. - * - * The player lock is not held. - * - * @return true on success, false on error (playback will be stopped) - */ -static bool -player_song_border(struct player *player) -{ - player->xfade = XFADE_UNKNOWN; - - char *uri = song_get_uri(player->song); - g_message("played \"%s\"", uri); - g_free(uri); - - music_pipe_free(player->pipe); - player->pipe = player->dc->pipe; - - audio_output_all_song_border(); - - if (!player_wait_for_decoder(player)) - return false; - - struct player_control *const pc = player->pc; - player_lock(pc); - - if (pc->border_pause) { - player->paused = true; - pc->state = PLAYER_STATE_PAUSE; - } - - player_unlock(pc); - - return true; -} - -/* - * The main loop of the player thread, during playback. This is - * basically a state machine, which multiplexes data between the - * decoder thread and the output threads. - */ -static void do_play(struct player_control *pc, struct decoder_control *dc) -{ - struct player player = { - .pc = pc, - .dc = dc, - .buffering = true, - .decoder_starting = false, - .paused = false, - .queued = true, - .output_open = false, - .song = NULL, - .xfade = XFADE_UNKNOWN, - .cross_fading = false, - .cross_fade_chunks = 0, - .cross_fade_tag = NULL, - .elapsed_time = 0.0, - }; - - player_unlock(pc); - - player.pipe = music_pipe_new(); - - player_dc_start(&player, player.pipe); - if (!player_wait_for_decoder(&player)) { - assert(player.song == NULL); - - player_dc_stop(&player); - player_command_finished(pc); - music_pipe_free(player.pipe); - event_pipe_emit(PIPE_EVENT_PLAYLIST); - player_lock(pc); - return; - } - - player_lock(pc); - pc->state = PLAYER_STATE_PLAY; - - if (pc->command == PLAYER_COMMAND_SEEK) - player.elapsed_time = pc->seek_where; - - player_command_finished_locked(pc); - - while (true) { - player_process_command(&player); - if (pc->command == PLAYER_COMMAND_STOP || - pc->command == PLAYER_COMMAND_EXIT || - pc->command == PLAYER_COMMAND_CLOSE_AUDIO) { - player_unlock(pc); - audio_output_all_cancel(); - break; - } - - player_unlock(pc); - - if (player.buffering) { - /* buffering at the start of the song - wait - until the buffer is large enough, to - prevent stuttering on slow machines */ - - if (music_pipe_size(player.pipe) < pc->buffered_before_play && - !decoder_lock_is_idle(dc)) { - /* not enough decoded buffer space yet */ - - if (!player.paused && - player.output_open && - audio_output_all_check() < 4 && - !player_send_silence(&player)) - break; - - decoder_lock(dc); - /* XXX race condition: check decoder again */ - player_wait_decoder(pc, dc); - decoder_unlock(dc); - player_lock(pc); - continue; - } else { - /* buffering is complete */ - player.buffering = false; - } - } - - if (player.decoder_starting) { - /* wait until the decoder is initialized completely */ - - if (!player_check_decoder_startup(&player)) - break; - - player_lock(pc); - continue; - } - -#ifndef NDEBUG - /* - music_pipe_check_format(&play_audio_format, - player.next_song_chunk, - &dc->out_audio_format); - */ -#endif - - if (decoder_lock_is_idle(dc) && player.queued && - dc->pipe == player.pipe) { - /* the decoder has finished the current song; - make it decode the next song */ - - assert(dc->pipe == NULL || dc->pipe == player.pipe); - - player_dc_start(&player, music_pipe_new()); - } - - if (/* no cross-fading if MPD is going to pause at the - end of the current song */ - !pc->border_pause && - player_dc_at_next_song(&player) && - player.xfade == XFADE_UNKNOWN && - !decoder_lock_is_starting(dc)) { - /* enable cross fading in this song? if yes, - calculate how many chunks will be required - for it */ - player.cross_fade_chunks = - cross_fade_calc(pc->cross_fade_seconds, dc->total_time, - pc->mixramp_db, - pc->mixramp_delay_seconds, - dc->replay_gain_db, - dc->replay_gain_prev_db, - dc->mixramp_start, - dc->mixramp_prev_end, - &dc->out_audio_format, - &player.play_audio_format, - music_buffer_size(player_buffer) - - pc->buffered_before_play); - if (player.cross_fade_chunks > 0) { - player.xfade = XFADE_ENABLED; - player.cross_fading = false; - } else - /* cross fading is disabled or the - next song is too short */ - player.xfade = XFADE_DISABLED; - } - - if (player.paused) { - player_lock(pc); - - if (pc->command == PLAYER_COMMAND_NONE) - player_wait(pc); - continue; - } else if (!music_pipe_empty(player.pipe)) { - /* at least one music chunk is ready - send it - to the audio output */ - - play_next_chunk(&player); - } else if (audio_output_all_check() > 0) { - /* not enough data from decoder, but the - output thread is still busy, so it's - okay */ - - /* XXX synchronize in a better way */ - g_usleep(10000); - } else if (player_dc_at_next_song(&player)) { - /* at the beginning of a new song */ - - if (!player_song_border(&player)) - break; - } else if (decoder_lock_is_idle(dc)) { - /* check the size of the pipe again, because - the decoder thread may have added something - since we last checked */ - if (music_pipe_empty(player.pipe)) { - /* wait for the hardware to finish - playback */ - audio_output_all_drain(); - break; - } - } else if (player.output_open) { - /* the decoder is too busy and hasn't provided - new PCM data in time: send silence (if the - output pipe is empty) */ - if (!player_send_silence(&player)) - break; - } - - player_lock(pc); - } - - player_dc_stop(&player); - - music_pipe_clear(player.pipe, player_buffer); - music_pipe_free(player.pipe); - - if (player.cross_fade_tag != NULL) - tag_free(player.cross_fade_tag); - - if (player.song != NULL) - song_free(player.song); - - player_lock(pc); - - if (player.queued) { - assert(pc->next_song != NULL); - song_free(pc->next_song); - pc->next_song = NULL; - } - - pc->state = PLAYER_STATE_STOP; - - player_unlock(pc); - - event_pipe_emit(PIPE_EVENT_PLAYLIST); - - player_lock(pc); -} - -static gpointer -player_task(gpointer arg) -{ - struct player_control *pc = arg; - - struct decoder_control *dc = dc_new(pc->cond); - decoder_thread_start(dc); - - player_buffer = music_buffer_new(pc->buffer_chunks); - - player_lock(pc); - - while (1) { - switch (pc->command) { - case PLAYER_COMMAND_SEEK: - case PLAYER_COMMAND_QUEUE: - assert(pc->next_song != NULL); - - do_play(pc, dc); - break; - - case PLAYER_COMMAND_STOP: - player_unlock(pc); - audio_output_all_cancel(); - player_lock(pc); - - /* fall through */ - - case PLAYER_COMMAND_PAUSE: - if (pc->next_song != NULL) { - song_free(pc->next_song); - pc->next_song = NULL; - } - - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_CLOSE_AUDIO: - player_unlock(pc); - - audio_output_all_release(); - - player_lock(pc); - player_command_finished_locked(pc); - -#ifndef NDEBUG - /* in the DEBUG build, check for leaked - music_chunk objects by freeing the - music_buffer */ - music_buffer_free(player_buffer); - player_buffer = music_buffer_new(pc->buffer_chunks); -#endif - - break; - - case PLAYER_COMMAND_UPDATE_AUDIO: - player_unlock(pc); - audio_output_all_enable_disable(); - player_lock(pc); - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_EXIT: - player_unlock(pc); - - dc_quit(dc); - dc_free(dc); - audio_output_all_close(); - music_buffer_free(player_buffer); - - player_command_finished(pc); - return NULL; - - case PLAYER_COMMAND_CANCEL: - if (pc->next_song != NULL) { - song_free(pc->next_song); - pc->next_song = NULL; - } - - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_REFRESH: - /* no-op when not playing */ - player_command_finished_locked(pc); - break; - - case PLAYER_COMMAND_NONE: - player_wait(pc); - break; - } - } -} - -void -player_create(struct player_control *pc) -{ - assert(pc->thread == NULL); - - GError *e = NULL; - pc->thread = g_thread_create(player_task, pc, true, &e); - if (pc->thread == NULL) - MPD_ERROR("Failed to spawn player task: %s", e->message); -} diff --git a/src/player_thread.h b/src/player_thread.h deleted file mode 100644 index 7373eb438..000000000 --- a/src/player_thread.h +++ /dev/null @@ -1,45 +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. - */ - -/* \file - * - * The player thread controls the playback. It acts as a bridge - * between the decoder thread and the output thread(s): it receives - * #music_chunk objects from the decoder, optionally mixes them - * (cross-fading), applies software volume, and sends them to the - * audio outputs via audio_output_all_play(). - * - * It is controlled by the main thread (the playlist code), see - * player_control.h. The playlist enqueues new songs into the player - * thread and sends it commands. - * - * The player thread itself does not do any I/O. It synchronizes with - * other threads via #GMutex and #GCond objects, and passes - * #music_chunk instances around in #music_pipe objects. - */ - -#ifndef MPD_PLAYER_THREAD_H -#define MPD_PLAYER_THREAD_H - -struct player_control; - -void -player_create(struct player_control *pc); - -#endif -- cgit v1.2.3