player_thread: added comments
This commit is contained in:
parent
13cd6b2834
commit
bc3702a4fd
@ -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);
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user