diff --git a/src/player_thread.c b/src/player_thread.c index e1e2b21a7..07ad8bb61 100644 --- a/src/player_thread.c +++ b/src/player_thread.c @@ -1,6 +1,6 @@ -/* the Music Player Daemon (MPD) - * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) - * This project's homepage is: http://www.musicpd.org +/* + * Copyright (C) 2003-2009 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 @@ -110,12 +110,17 @@ static void player_command_finished(void) notify_signal(&main_notify); } +/** + * Stop the decoder and clears (and frees) its music pipe. + */ static void player_dc_stop(struct player *player) { dc_stop(&pc.notify); if (dc.pipe != NULL) { + /* clear and free the decoder pipe */ + music_pipe_clear(dc.pipe, player_buffer); if (dc.pipe != player->pipe) @@ -125,6 +130,11 @@ player_dc_stop(struct player *player) } } +/** + * 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. + */ static bool player_wait_for_decoder(struct player *player) { @@ -148,6 +158,9 @@ player_wait_for_decoder(struct player *player) pc.next_song = NULL; pc.elapsed_time = 0; player->queued = false; + + /* set the "starting" flag, which will be cleared by + player_check_decoder_startup() */ player->decoder_starting = true; /* call syncPlaylistWithQueue() in the main thread */ @@ -156,6 +169,11 @@ player_wait_for_decoder(struct player *player) return true; } +/** + * The decoder has acknowledged the "START" command (see + * player_wait_for_decoder()). This function checks if the decoder + * initialization has completed yet. + */ static bool player_check_decoder_startup(struct player *player) { @@ -252,6 +270,9 @@ player_send_silence(struct player *player) return true; } +/** + * This is the handler for the #PLAYER_COMMAND_SEEK command. + */ static bool player_seek_decoder(struct player *player) { double where; @@ -260,14 +281,21 @@ static bool player_seek_decoder(struct player *player) assert(pc.next_song != NULL); if (decoder_current_song() != pc.next_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); dc.pipe = player->pipe; - dc_start_async(pc.next_song); + /* re-start the decoder */ + dc_start_async(pc.next_song); ret = player_wait_for_decoder(player); if (!ret) { + /* decoder failure */ player_command_finished(); return false; } @@ -276,6 +304,8 @@ static bool player_seek_decoder(struct player *player) player->queued = false; } + /* send the SEEK command */ + where = pc.seek_where; if (where > pc.total_time) where = pc.total_time - 0.1; @@ -284,6 +314,7 @@ static bool player_seek_decoder(struct player *player) ret = dc_seek(&pc.notify, where); if (!ret) { + /* decoder failure */ player_command_finished(); return false; } @@ -331,8 +362,11 @@ static void player_process_command(struct player *player) pc.state = PLAYER_STATE_PLAY; } else if (audio_output_all_open(&player->play_audio_format, player_buffer)) { + /* unpaused, continue playing */ pc.state = PLAYER_STATE_PLAY; } else { + /* the audio device has failed - rollback to + pause mode */ assert(dc.next_song == NULL || dc.next_song->url != NULL); pc.errored_song = dc.next_song; pc.error = PLAYER_ERROR_AUDIO; @@ -349,7 +383,7 @@ static void player_process_command(struct player *player) case PLAYER_COMMAND_CANCEL: if (pc.next_song == NULL) { - /* the cancel request arrived too later, we're + /* the cancel request arrived too late, we're already playing the queued song... stop everything now */ pc.command = PLAYER_COMMAND_STOP; @@ -368,6 +402,11 @@ static void player_process_command(struct player *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. + */ static bool play_chunk(struct song *song, struct music_chunk *chunk, const struct audio_format *format, double sizeToTime) @@ -399,6 +438,8 @@ play_chunk(struct song *song, struct music_chunk *chunk, } } + /* apply software volume */ + success = pcm_volume(chunk->data, chunk->length, format, pc.software_volume); if (!success) { @@ -409,6 +450,8 @@ play_chunk(struct song *song, struct music_chunk *chunk, return false; } + /* send the chunk to the audio outputs */ + if (!audio_output_all_play(chunk)) { pc.errored_song = dc.current_song; pc.error = PLAYER_ERROR_AUDIO; @@ -433,8 +476,8 @@ play_next_chunk(struct player *player) bool success; if (audio_output_all_check() >= 64) { - /* the output pipe is still large - enough, don't send another chunk */ + /* the output pipe is still large enough, don't send + another chunk */ /* XXX synchronize in a better way */ g_usleep(1000); @@ -450,11 +493,10 @@ play_next_chunk(struct player *player) 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 */ + /* 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; } @@ -469,16 +511,13 @@ play_next_chunk(struct player *player) player->cross_fade_chunks); music_buffer_return(player_buffer, other_chunk); } else { - /* there are not enough - decoded chunks yet */ + /* there are not enough decoded chunks yet */ if (decoder_is_idle()) { - /* the decoder isn't - running, abort + /* the decoder isn't running, abort cross fading */ player->xfade = XFADE_DISABLED; } else { - /* wait for the - decoder */ + /* wait for the decoder */ notify_signal(&dc.notify); notify_wait(&pc.notify); @@ -502,9 +541,8 @@ play_next_chunk(struct player *player) return false; } - /* this formula should prevent that the - decoder gets woken up with each chunk; it - is more efficient to make it decode a + /* 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 */ if (!decoder_is_idle() && music_pipe_size(dc.pipe) <= (pc.buffered_before_play + @@ -535,6 +573,11 @@ player_song_border(struct player *player) 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(void) { struct player player = { @@ -576,6 +619,10 @@ static void do_play(void) } 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_is_idle()) { /* not enough decoded buffer space yet */ @@ -595,6 +642,7 @@ static void do_play(void) } if (player.decoder_starting) { + /* wait until the decoder is initialized completely */ bool success; success = player_check_decoder_startup(&player); diff --git a/src/player_thread.h b/src/player_thread.h index f8b88f65d..6fd62ec7e 100644 --- a/src/player_thread.h +++ b/src/player_thread.h @@ -1,6 +1,6 @@ -/* the Music Player Daemon (MPD) - * Copyright (C) 2008 Max Kellermann - * This project's homepage is: http://www.musicpd.org +/* + * Copyright (C) 2003-2009 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 @@ -16,6 +16,23 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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