Files
mpd/src/player_thread.c
Max Kellermann 8fe03b8bce decoder: terminate decoder thread before MPD cleanup
When MPD exits, it should manually free all resources in use, to allow
easy memory leak debugging.  Make the decoder thread terminate during
that.
2008-12-28 19:48:53 +01:00

546 lines
13 KiB
C

/* 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
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "player_thread.h"
#include "player_control.h"
#include "decoder_control.h"
#include "audio.h"
#include "pcm_utils.h"
#include "path.h"
#include "log.h"
#include "main_notify.h"
#include "crossfade.h"
#include "song.h"
#include "pipe.h"
#include "idle.h"
#include <glib.h>
enum xfade_state {
XFADE_DISABLED = -1,
XFADE_UNKNOWN = 0,
XFADE_ENABLED = 1
};
struct player {
/**
* 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;
/**
* the song currently being played
*/
struct song *song;
/**
* is cross fading enabled?
*/
enum xfade_state xfade;
/**
* index of the first chunk of the next song, -1 if there is
* no next song
*/
int next_song_chunk;
};
static void player_command_finished(void)
{
assert(pc.command != PLAYER_COMMAND_NONE);
pc.command = PLAYER_COMMAND_NONE;
wakeup_main_task();
}
static void player_stop_decoder(void)
{
dc_stop(&pc.notify);
pc.state = PLAYER_STATE_STOP;
wakeup_main_task();
}
static int player_wait_for_decoder(struct player *player)
{
dc_command_wait(&pc.notify);
if (decoder_has_failed()) {
assert(dc.next_song == NULL || dc.next_song->url != NULL);
pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_FILE;
return -1;
}
pc.total_time = pc.next_song->tag != NULL
? pc.next_song->tag->time : 0;
pc.bit_rate = 0;
audio_format_clear(&pc.audio_format);
player->song = pc.next_song;
pc.next_song = NULL;
pc.elapsed_time = 0;
player->queued = false;
player->decoder_starting = true;
return 0;
}
static bool player_seek_decoder(struct player *player)
{
double where;
bool ret;
if (decoder_current_song() != pc.next_song) {
dc_stop(&pc.notify);
player->next_song_chunk = -1;
music_pipe_clear();
dc_start_async(pc.next_song);
player_wait_for_decoder(player);
}
where = pc.seek_where;
if (where > pc.total_time)
where = pc.total_time - 0.1;
if (where < 0.0)
where = 0.0;
ret = dc_seek(&pc.notify, where);
if (ret)
pc.elapsed_time = where;
player_command_finished();
return ret;
}
static void player_process_command(struct player *player)
{
switch (pc.command) {
case PLAYER_COMMAND_NONE:
case PLAYER_COMMAND_PLAY:
case PLAYER_COMMAND_STOP:
case PLAYER_COMMAND_EXIT:
case PLAYER_COMMAND_CLOSE_AUDIO:
break;
case PLAYER_COMMAND_QUEUE:
assert(pc.next_song != NULL);
assert(!player->queued);
assert(player->next_song_chunk == -1);
player->queued = true;
player_command_finished();
break;
case PLAYER_COMMAND_PAUSE:
player->paused = !player->paused;
if (player->paused) {
audio_output_pause_all();
pc.state = PLAYER_STATE_PAUSE;
} else {
if (openAudioDevice(NULL)) {
pc.state = PLAYER_STATE_PLAY;
} else {
char tmp[MPD_PATH_MAX];
assert(dc.next_song == NULL || dc.next_song->url != NULL);
pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_AUDIO;
ERROR("problems opening audio device "
"while playing \"%s\"\n",
song_get_url(dc.next_song, tmp));
player->paused = true;
}
}
player_command_finished();
break;
case PLAYER_COMMAND_SEEK:
dropBufferedAudio();
if (player_seek_decoder(player)) {
player->xfade = XFADE_UNKNOWN;
/* abort buffering when the user has requested
a seek */
player->buffering = false;
}
break;
case PLAYER_COMMAND_CANCEL:
if (pc.next_song == NULL) {
/* the cancel request arrived too later, we're
already playing the queued song... stop
everything now */
pc.command = PLAYER_COMMAND_STOP;
return;
}
if (player->next_song_chunk != -1) {
/* the decoder is already decoding the song -
stop it and reset the position */
dc_stop(&pc.notify);
music_pipe_chop(player->next_song_chunk);
player->next_song_chunk = -1;
}
pc.next_song = NULL;
player->queued = false;
player_command_finished();
break;
}
}
static int
play_chunk(struct song *song, struct music_chunk *chunk,
const struct audio_format *format, double sizeToTime)
{
pc.elapsed_time = chunk->times;
pc.bit_rate = chunk->bit_rate;
if (chunk->tag != NULL) {
sendMetadataToAudioDevice(chunk->tag);
if (!song_is_file(song)) {
/* always update the tag of remote streams */
struct tag *old_tag = song->tag;
song->tag = tag_dup(chunk->tag);
if (old_tag != NULL)
tag_free(old_tag);
/* notify all clients that the tag of the
current song has changed */
idle_add(IDLE_PLAYER);
}
}
if (chunk->length == 0)
return 0;
pcm_volume(chunk->data, chunk->length,
format, pc.software_volume);
if (!playAudio(chunk->data, chunk->length))
return -1;
pc.total_play_time += sizeToTime * chunk->length;
return 0;
}
static void do_play(void)
{
struct player player = {
.buffering = true,
.decoder_starting = false,
.paused = false,
.queued = false,
.song = NULL,
.xfade = XFADE_UNKNOWN,
.next_song_chunk = -1,
};
unsigned int crossFadeChunks = 0;
/** the position of the next cross-faded chunk in the next
song */
int nextChunk = 0;
static const char silence[CHUNK_SIZE];
struct audio_format play_audio_format;
double sizeToTime = 0.0;
music_pipe_clear();
music_pipe_set_lazy(false);
dc_start(&pc.notify, pc.next_song);
if (player_wait_for_decoder(&player) < 0) {
player_stop_decoder();
player_command_finished();
return;
}
pc.elapsed_time = 0;
pc.state = PLAYER_STATE_PLAY;
player_command_finished();
while (1) {
player_process_command(&player);
if (pc.command == PLAYER_COMMAND_STOP ||
pc.command == PLAYER_COMMAND_EXIT ||
pc.command == PLAYER_COMMAND_CLOSE_AUDIO) {
dropBufferedAudio();
break;
}
if (player.buffering) {
if (music_pipe_available() < pc.buffered_before_play &&
!decoder_is_idle()) {
/* not enough decoded buffer space yet */
notify_wait(&pc.notify);
continue;
} else {
/* buffering is complete */
player.buffering = false;
music_pipe_set_lazy(true);
}
}
if (player.decoder_starting) {
if (decoder_has_failed()) {
/* the decoder failed */
assert(dc.next_song == NULL || dc.next_song->url != NULL);
pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_FILE;
break;
}
else if (!decoder_is_starting()) {
/* the decoder is ready and ok */
player.decoder_starting = false;
if (!openAudioDevice(&dc.out_audio_format)) {
char tmp[MPD_PATH_MAX];
assert(dc.next_song == NULL || dc.next_song->url != NULL);
pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_AUDIO;
ERROR("problems opening audio device "
"while playing \"%s\"\n",
song_get_url(dc.next_song, tmp));
break;
}
if (player.paused)
closeAudioDevice();
pc.total_time = dc.total_time;
pc.audio_format = dc.in_audio_format;
play_audio_format = dc.out_audio_format;
sizeToTime = audioFormatSizeToTime(&dc.out_audio_format);
}
else {
/* the decoder is not yet ready; wait
some more */
notify_wait(&pc.notify);
continue;
}
}
#ifndef NDEBUG
/*
music_pipe_check_format(&play_audio_format,
player.next_song_chunk,
&dc.out_audio_format);
*/
#endif
if (decoder_is_idle() && !player.queued &&
player.next_song_chunk < 0 &&
pc.next_song != NULL &&
pc.command == PLAYER_COMMAND_NONE) {
/* the decoder has finished the current song;
request the next song from the playlist */
pc.next_song = NULL;
wakeup_main_task();
}
if (decoder_is_idle() && player.queued) {
/* the decoder has finished the current song;
make it decode the next song */
assert(pc.next_song != NULL);
assert(player.next_song_chunk == -1);
player.queued = false;
player.next_song_chunk = music_pipe_tail_index();
dc_start_async(pc.next_song);
}
if (player.next_song_chunk >= 0 &&
player.xfade == XFADE_UNKNOWN &&
!decoder_is_starting()) {
/* enable cross fading in this song? if yes,
calculate how many chunks will be required
for it */
crossFadeChunks =
cross_fade_calc(pc.cross_fade_seconds, dc.total_time,
&dc.out_audio_format,
music_pipe_size() -
pc.buffered_before_play);
if (crossFadeChunks > 0) {
player.xfade = XFADE_ENABLED;
nextChunk = -1;
} else
/* cross fading is disabled or the
next song is too short */
player.xfade = XFADE_DISABLED;
}
if (player.paused)
notify_wait(&pc.notify);
else if (!music_pipe_is_empty() &&
!music_pipe_head_is(player.next_song_chunk)) {
struct music_chunk *beginChunk = music_pipe_peek();
unsigned int fadePosition;
if (player.xfade == XFADE_ENABLED &&
player.next_song_chunk >= 0 &&
(fadePosition = music_pipe_relative(player.next_song_chunk))
<= crossFadeChunks) {
/* perform cross fade */
if (nextChunk < 0) {
/* beginning of the cross fade
- adjust crossFadeChunks
which might be bigger than
the remaining number of
chunks in the old song */
crossFadeChunks = fadePosition;
}
nextChunk = music_pipe_absolute(crossFadeChunks);
if (nextChunk >= 0) {
music_pipe_set_lazy(true);
cross_fade_apply(beginChunk,
music_pipe_get_chunk(nextChunk),
&dc.out_audio_format,
fadePosition,
crossFadeChunks);
} else {
/* there are not enough
decoded chunks yet */
if (decoder_is_idle()) {
/* the decoder isn't
running, abort
cross fading */
player.xfade = XFADE_DISABLED;
} else {
/* wait for the
decoder */
music_pipe_set_lazy(false);
notify_signal(&dc.notify);
notify_wait(&pc.notify);
continue;
}
}
}
/* play the current chunk */
if (play_chunk(player.song, beginChunk,
&play_audio_format, sizeToTime) < 0)
break;
music_pipe_shift();
/* 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 (music_pipe_available() <= (pc.buffered_before_play + music_pipe_size() * 3) / 4)
notify_signal(&dc.notify);
} else if (music_pipe_head_is(player.next_song_chunk)) {
/* at the beginning of a new song */
if (player.xfade == XFADE_ENABLED && nextChunk >= 0) {
/* the cross-fade is finished; skip
the section which was cross-faded
(and thus already played) */
music_pipe_skip(crossFadeChunks);
}
player.xfade = XFADE_UNKNOWN;
player.next_song_chunk = -1;
if (player_wait_for_decoder(&player) < 0)
return;
wakeup_main_task();
} else if (decoder_is_idle()) {
break;
} else {
size_t frame_size =
audio_format_frame_size(&play_audio_format);
/* this formula ensures that we don't send
partial frames */
unsigned num_frames = CHUNK_SIZE / frame_size;
/*DEBUG("waiting for decoded audio, play silence\n");*/
if (!playAudio(silence, num_frames * frame_size))
break;
}
}
player_stop_decoder();
}
static void * player_task(G_GNUC_UNUSED void *arg)
{
while (1) {
switch (pc.command) {
case PLAYER_COMMAND_PLAY:
case PLAYER_COMMAND_QUEUE:
do_play();
break;
case PLAYER_COMMAND_STOP:
case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_PAUSE:
player_command_finished();
break;
case PLAYER_COMMAND_CLOSE_AUDIO:
closeAudioDevice();
player_command_finished();
break;
case PLAYER_COMMAND_EXIT:
dc_quit(&pc.notify);
closeAudioDevice();
player_command_finished();
pthread_exit(NULL);
break;
case PLAYER_COMMAND_CANCEL:
pc.next_song = NULL;
player_command_finished();
break;
case PLAYER_COMMAND_NONE:
notify_wait(&pc.notify);
break;
}
}
return NULL;
}
void player_create(void)
{
pthread_attr_t attr;
pthread_t player_thread;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create(&player_thread, &attr, player_task, NULL))
FATAL("Failed to spawn player task: %s\n", strerror(errno));
}