player_thread: added comments

This commit is contained in:
Max Kellermann 2009-03-11 09:35:16 +01:00
parent 13cd6b2834
commit bc3702a4fd
2 changed files with 89 additions and 24 deletions

View File

@ -1,6 +1,6 @@
/* the Music Player Daemon (MPD) /*
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) * Copyright (C) 2003-2009 The Music Player Daemon Project
* This project's homepage is: http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * 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 * 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); notify_signal(&main_notify);
} }
/**
* Stop the decoder and clears (and frees) its music pipe.
*/
static void static void
player_dc_stop(struct player *player) player_dc_stop(struct player *player)
{ {
dc_stop(&pc.notify); dc_stop(&pc.notify);
if (dc.pipe != NULL) { if (dc.pipe != NULL) {
/* clear and free the decoder pipe */
music_pipe_clear(dc.pipe, player_buffer); music_pipe_clear(dc.pipe, player_buffer);
if (dc.pipe != player->pipe) 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 static bool
player_wait_for_decoder(struct player *player) player_wait_for_decoder(struct player *player)
{ {
@ -148,6 +158,9 @@ player_wait_for_decoder(struct player *player)
pc.next_song = NULL; pc.next_song = NULL;
pc.elapsed_time = 0; pc.elapsed_time = 0;
player->queued = false; player->queued = false;
/* set the "starting" flag, which will be cleared by
player_check_decoder_startup() */
player->decoder_starting = true; player->decoder_starting = true;
/* call syncPlaylistWithQueue() in the main thread */ /* call syncPlaylistWithQueue() in the main thread */
@ -156,6 +169,11 @@ player_wait_for_decoder(struct player *player)
return true; 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 static bool
player_check_decoder_startup(struct player *player) player_check_decoder_startup(struct player *player)
{ {
@ -252,6 +270,9 @@ player_send_silence(struct player *player)
return true; return true;
} }
/**
* This is the handler for the #PLAYER_COMMAND_SEEK command.
*/
static bool player_seek_decoder(struct player *player) static bool player_seek_decoder(struct player *player)
{ {
double where; double where;
@ -260,14 +281,21 @@ static bool player_seek_decoder(struct player *player)
assert(pc.next_song != NULL); assert(pc.next_song != NULL);
if (decoder_current_song() != pc.next_song) { 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); player_dc_stop(player);
/* clear music chunks which might still reside in the
pipe */
music_pipe_clear(player->pipe, player_buffer); music_pipe_clear(player->pipe, player_buffer);
dc.pipe = player->pipe; 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); ret = player_wait_for_decoder(player);
if (!ret) { if (!ret) {
/* decoder failure */
player_command_finished(); player_command_finished();
return false; return false;
} }
@ -276,6 +304,8 @@ static bool player_seek_decoder(struct player *player)
player->queued = false; player->queued = false;
} }
/* send the SEEK command */
where = pc.seek_where; where = pc.seek_where;
if (where > pc.total_time) if (where > pc.total_time)
where = pc.total_time - 0.1; where = pc.total_time - 0.1;
@ -284,6 +314,7 @@ static bool player_seek_decoder(struct player *player)
ret = dc_seek(&pc.notify, where); ret = dc_seek(&pc.notify, where);
if (!ret) { if (!ret) {
/* decoder failure */
player_command_finished(); player_command_finished();
return false; return false;
} }
@ -331,8 +362,11 @@ static void player_process_command(struct player *player)
pc.state = PLAYER_STATE_PLAY; pc.state = PLAYER_STATE_PLAY;
} else if (audio_output_all_open(&player->play_audio_format, player_buffer)) { } else if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
/* unpaused, continue playing */
pc.state = PLAYER_STATE_PLAY; pc.state = PLAYER_STATE_PLAY;
} else { } else {
/* the audio device has failed - rollback to
pause mode */
assert(dc.next_song == NULL || dc.next_song->url != NULL); assert(dc.next_song == NULL || dc.next_song->url != NULL);
pc.errored_song = dc.next_song; pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_AUDIO; pc.error = PLAYER_ERROR_AUDIO;
@ -349,7 +383,7 @@ static void player_process_command(struct player *player)
case PLAYER_COMMAND_CANCEL: case PLAYER_COMMAND_CANCEL:
if (pc.next_song == NULL) { 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 already playing the queued song... stop
everything now */ everything now */
pc.command = PLAYER_COMMAND_STOP; 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 static bool
play_chunk(struct song *song, struct music_chunk *chunk, play_chunk(struct song *song, struct music_chunk *chunk,
const struct audio_format *format, double sizeToTime) 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, success = pcm_volume(chunk->data, chunk->length,
format, pc.software_volume); format, pc.software_volume);
if (!success) { if (!success) {
@ -409,6 +450,8 @@ play_chunk(struct song *song, struct music_chunk *chunk,
return false; return false;
} }
/* send the chunk to the audio outputs */
if (!audio_output_all_play(chunk)) { if (!audio_output_all_play(chunk)) {
pc.errored_song = dc.current_song; pc.errored_song = dc.current_song;
pc.error = PLAYER_ERROR_AUDIO; pc.error = PLAYER_ERROR_AUDIO;
@ -433,8 +476,8 @@ play_next_chunk(struct player *player)
bool success; bool success;
if (audio_output_all_check() >= 64) { if (audio_output_all_check() >= 64) {
/* the output pipe is still large /* the output pipe is still large enough, don't send
enough, don't send another chunk */ another chunk */
/* XXX synchronize in a better way */ /* XXX synchronize in a better way */
g_usleep(1000); g_usleep(1000);
@ -450,11 +493,10 @@ play_next_chunk(struct player *player)
music_pipe_shift(dc.pipe); music_pipe_shift(dc.pipe);
if (!player->cross_fading) { if (!player->cross_fading) {
/* beginning of the cross fade /* beginning of the cross fade - adjust
- adjust crossFadeChunks crossFadeChunks which might be bigger than
which might be bigger than the remaining number of chunks in the old
the remaining number of song */
chunks in the old song */
player->cross_fade_chunks = cross_fade_position; player->cross_fade_chunks = cross_fade_position;
player->cross_fading = true; player->cross_fading = true;
} }
@ -469,16 +511,13 @@ play_next_chunk(struct player *player)
player->cross_fade_chunks); player->cross_fade_chunks);
music_buffer_return(player_buffer, other_chunk); music_buffer_return(player_buffer, other_chunk);
} else { } else {
/* there are not enough /* there are not enough decoded chunks yet */
decoded chunks yet */
if (decoder_is_idle()) { if (decoder_is_idle()) {
/* the decoder isn't /* the decoder isn't running, abort
running, abort
cross fading */ cross fading */
player->xfade = XFADE_DISABLED; player->xfade = XFADE_DISABLED;
} else { } else {
/* wait for the /* wait for the decoder */
decoder */
notify_signal(&dc.notify); notify_signal(&dc.notify);
notify_wait(&pc.notify); notify_wait(&pc.notify);
@ -502,9 +541,8 @@ play_next_chunk(struct player *player)
return false; return false;
} }
/* this formula should prevent that the /* this formula should prevent that the decoder gets woken up
decoder gets woken up with each chunk; it with each chunk; it is more efficient to make it decode a
is more efficient to make it decode a
larger block at a time */ larger block at a time */
if (!decoder_is_idle() && if (!decoder_is_idle() &&
music_pipe_size(dc.pipe) <= (pc.buffered_before_play + music_pipe_size(dc.pipe) <= (pc.buffered_before_play +
@ -535,6 +573,11 @@ player_song_border(struct player *player)
return true; 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) static void do_play(void)
{ {
struct player player = { struct player player = {
@ -576,6 +619,10 @@ static void do_play(void)
} }
if (player.buffering) { 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 && if (music_pipe_size(player.pipe) < pc.buffered_before_play &&
!decoder_is_idle()) { !decoder_is_idle()) {
/* not enough decoded buffer space yet */ /* not enough decoded buffer space yet */
@ -595,6 +642,7 @@ static void do_play(void)
} }
if (player.decoder_starting) { if (player.decoder_starting) {
/* wait until the decoder is initialized completely */
bool success; bool success;
success = player_check_decoder_startup(&player); success = player_check_decoder_startup(&player);

View File

@ -1,6 +1,6 @@
/* the Music Player Daemon (MPD) /*
* Copyright (C) 2008 Max Kellermann <max@duempel.org> * Copyright (C) 2003-2009 The Music Player Daemon Project
* This project's homepage is: http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * 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 * 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 * 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 #ifndef MPD_PLAYER_THREAD_H
#define MPD_PLAYER_THREAD_H #define MPD_PLAYER_THREAD_H