/* 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 "outputBuffer.h" #include "utils.h" #include "normalize.h" #include "ringbuf.h" #include "condition.h" #include "main_notify.h" #include "player_error.h" #include "log.h" #include "action_status.h" #include "decode.h" #include "metadata_pipe.h" #include "playlist.h" /* typically have 2048-4096 of these structs, so pack tightly */ struct ob_chunk { uint16_t len; /* 0: skip this chunk */ uint16_t bit_rate; float time; uint8_t seq; /* see seq_ok() for explanation */ char data[CHUNK_SIZE]; }; static struct ob_chunk silence; enum ob_xfade_state { XFADE_DISABLED = 0, XFADE_ENABLED }; static struct condition ob_action_cond = STATIC_COND_INITIALIZER; static struct condition ob_halt_cond = STATIC_COND_INITIALIZER; static struct condition ob_seq_cond = STATIC_COND_INITIALIZER; struct output_buffer { struct ringbuf *index; /* index for chunks */ struct ob_chunk *chunks; size_t bpp_max; /* buffer_before_play, user setting, in chunks */ size_t bpp_cur; /* current prebuffer size (in chunks) */ enum ob_state state; /* protected by ob_action_cond */ enum ob_action action; /* protected by ob_action_cond */ enum ob_xfade_state xfade_state; /* thread-internal */ int sw_vol; int bit_rate; float total_time; float elapsed_time; AudioFormat audio_format; size_t xfade_cur; size_t xfade_max; float xfade_time; void *conv_buf; size_t conv_buf_len; pthread_t thread; ConvState conv_state; uint8_t seq_drop; uint8_t seq_player; /* only gets changed by ob.thread */ uint8_t seq_decoder; /* only gets changed by dc.thread */ struct ringbuf preseek_index; enum ob_state preseek_state; uint16_t *preseek_len; }; static struct output_buffer ob; #include "outputBuffer_xfade.h" #include "outputBuffer_accessors.h" static enum action_status ob_do_stop(void); static void stop_playback(void) { assert(pthread_equal(pthread_self(), ob.thread)); cond_enter(&ob_action_cond); ob_do_stop(); cond_leave(&ob_action_cond); } void ob_trigger_action(enum ob_action action) { /* * This can be called by both dc.thread and main_thread, but only one * action can be in progress at once. So we use this private mutex * to protect against simultaneous invocations stepping over * each other */ static pthread_mutex_t trigger_lock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&trigger_lock); DEBUG(__FILE__": %d action: %d\n", __LINE__, action); assert(!pthread_equal(pthread_self(), ob.thread)); DEBUG(__FILE__":%s %d\n", __func__, __LINE__); cond_enter(&ob_action_cond); DEBUG(__FILE__":%s %d\n", __func__, __LINE__); assert(ob.action == OB_ACTION_NONE); if (pthread_equal(pthread_self(), dc.thread)) assert(action == OB_ACTION_PLAY || action == OB_ACTION_SEEK_START || action == OB_ACTION_SEEK_FINISH || action == OB_ACTION_RESET); else assert(action != OB_ACTION_PLAY && action != OB_ACTION_SEEK_START && action != OB_ACTION_SEEK_FINISH); ob.action = action; do { switch (ob.state) { case OB_STATE_PAUSE: case OB_STATE_STOP: case OB_STATE_SEEK: DEBUG(__FILE__":%s %d\n", __func__, __LINE__); cond_signal_sync(&ob_halt_cond); DEBUG(__FILE__":%s %d\n", __func__, __LINE__); break; default: break; } DEBUG(__FILE__":%s %d\n", __func__, __LINE__); cond_wait(&ob_action_cond); } while (ob.action != OB_ACTION_NONE); DEBUG(__FILE__":%s %d\n", __func__, __LINE__); assert(ob.action == OB_ACTION_NONE); cond_leave(&ob_action_cond); DEBUG(__FILE__":%s %d\n", __func__, __LINE__); pthread_mutex_unlock(&trigger_lock); } static enum action_status ob_finalize_action(void) { assert(pthread_equal(pthread_self(), ob.thread)); ob.action = OB_ACTION_NONE; /* DEBUG(__FILE__ ":%s signaling %d\n", __func__, __LINE__); */ cond_signal(&ob_action_cond); cond_leave(&ob_action_cond); return AS_COMPLETE; } /* marks all buffered chunks with sequence number matching `seq' as invalid */ static enum action_status ob_do_drop(void) { struct rbvec vec[2]; long i; uint8_t seq_drop; cond_enter(&ob_seq_cond); seq_drop = ob.seq_drop; /* drop the audio that we've already pushed to the device, too */ if (seq_drop == ob.seq_player) dropBufferedAudio(); cond_leave(&ob_seq_cond); assert(pthread_equal(pthread_self(), ob.thread)); for (i = (long)ringbuf_get_read_vector(ob.index, vec); --i >= 0; ) { struct ob_chunk *c = get_chunk(vec, i); assert(c); if (c->seq == seq_drop) c->len = 0; } return ob_finalize_action(); } static enum action_status ob_do_pause(void) { assert(pthread_equal(pthread_self(), ob.thread)); ob.xfade_state = XFADE_DISABLED; /* * This will eventually set certain outputs (like shout) into 'pause' * state where it'll just play silence instead of disconnecting * listeners */ dropBufferedAudio(); closeAudioDevice(); ob.state = OB_STATE_PAUSE; return AS_INPROGRESS; } static void reader_reset_buffer(void) { struct rbvec vec[2]; size_t nr; long i; assert(pthread_equal(pthread_self(), ob.thread)); nr = ringbuf_get_read_vector(ob.index, vec); for (i = nr; --i >= 0; ) { struct ob_chunk *c = get_chunk(vec, i); assert(c); c->len = 0; } ringbuf_read_advance(ob.index, nr); metadata_pipe_clear(); } static void ob_seq_player_set(uint8_t seq_num) { cond_enter(&ob_seq_cond); ob.seq_player = seq_num; cond_signal(&ob_seq_cond); cond_leave(&ob_seq_cond); } static enum action_status ob_do_reset(int close_audio) { assert(pthread_equal(pthread_self(), ob.thread)); ob.elapsed_time = 0; ob.total_time = 0; reader_reset_buffer(); dropBufferedAudio(); if (close_audio) closeAudioDevice(); ob.xfade_state = XFADE_DISABLED; ob_seq_player_set(ob.seq_decoder); return ob_finalize_action(); } static enum action_status ob_do_stop(void) { assert(pthread_equal(pthread_self(), ob.thread)); if (ob.state == OB_STATE_STOP) return AS_INPROGRESS; ob.state = OB_STATE_STOP; return ob_do_reset(1); } /* * we need to reset the buffer *before* we seek because the decoder * _may_ try to flush out the last remnants of the previously decoded audio, * so we need to ensure there is space available for that */ static enum action_status ob_do_seek_start(void) { int i; assert(pthread_equal(pthread_self(), ob.thread)); /* preserve pre-seek ringbuf and state information */ memcpy(&ob.preseek_index, ob.index, sizeof(struct ringbuf)); for (i = ob.preseek_index.size; --i >= 0; ) ob.preseek_len[i] = ob.chunks[i].len; ob.preseek_state = ob.state; ob.state = OB_STATE_SEEK; reader_reset_buffer(); return AS_INPROGRESS; } static enum action_status ob_do_seek_finish(void) { assert(pthread_equal(pthread_self(), ob.thread)); assert(ob.state == OB_STATE_SEEK); ob.state = ob.preseek_state; if (dc.seek_where < 0) { int i; assert(dc.seek_where == DC_SEEK_MISMATCH || dc.seek_where == DC_SEEK_ERROR); /* restore the old ringbuf index if we failed to seek */ memcpy(ob.index, &ob.preseek_index, sizeof(struct ringbuf)); for (i = ob.preseek_index.size; --i >= 0; ) ob.chunks[i].len = ob.preseek_len[i]; } else { assert(dc.seek_where >= 0); ob.xfade_state = XFADE_DISABLED; ob.elapsed_time = dc.seek_where; ob.total_time = dc.total_time; reader_reset_buffer(); dropBufferedAudio(); ob_seq_player_set(ob.seq_decoder); } return ob_finalize_action(); } static enum action_status ob_take_action(void) { assert(pthread_equal(pthread_self(), ob.thread)); if (mpd_likely(ob.action == OB_ACTION_NONE)) return AS_COMPLETE; DEBUG(__FILE__": %s %d\n", __func__, __LINE__); cond_enter(&ob_action_cond); DEBUG(__FILE__": %s %d action: %d\n", __func__, __LINE__, ob.action); switch (ob.action) { case OB_ACTION_NONE: return ob_finalize_action(); case OB_ACTION_PLAY: ob.state = OB_STATE_PLAY; break; case OB_ACTION_DROP: return ob_do_drop(); case OB_ACTION_SEEK_START: return ob_do_seek_start(); case OB_ACTION_SEEK_FINISH: return ob_do_seek_finish(); case OB_ACTION_PAUSE_SET: if (ob.state == OB_STATE_PLAY) return ob_do_pause(); ob.state = OB_STATE_PAUSE; break; case OB_ACTION_PAUSE_UNSET: if (ob.state == OB_STATE_PAUSE) ob.state = OB_STATE_PLAY; break; case OB_ACTION_PAUSE_FLIP: switch (ob.state) { case OB_STATE_PLAY: return ob_do_pause(); case OB_STATE_PAUSE: ob.state = OB_STATE_PLAY; break; default: break; } break; case OB_ACTION_STOP: return ob_do_stop(); case OB_ACTION_RESET: return ob_do_reset(0); case OB_ACTION_QUIT: dropBufferedAudio(); closeAudioDevice(); ob.state = OB_STATE_QUIT; return AS_INPROGRESS; } return ob_finalize_action(); } /* * looks up the chunk given by index `i', returns NULL if `i' is beyond * the end of the buffer. This allows us to treat our chunks array * like an infinite, rotating buffer. The first available chunk * is always indexed as `0', the second one as `1', and so on... */ static struct ob_chunk *get_chunk(struct rbvec vec[2], size_t i) { if (vec[0].len > i) return &ob.chunks[vec[0].base + i - ob.index->buf]; if (i && vec[1].base) { assert(vec[0].len > 0); i -= vec[0].len; if (vec[1].len > i) return &ob.chunks[vec[1].base + i - ob.index->buf]; } return NULL; } static void prevent_buffer_underrun(void) { if (playAudio(silence.data, sizeof(silence.data)) < 0) stop_playback(); } /* causes ob_do_drop() to be called (and waits for completion) */ void ob_drop_audio(enum ob_drop_type type) { assert(!pthread_equal(pthread_self(), ob.thread)); assert(!pthread_equal(pthread_self(), dc.thread)); assert(dc.state == DC_STATE_STOP); /* not needed, just a good idea */ cond_enter(&ob_seq_cond); switch (type) { case OB_DROP_DECODED: ob.seq_drop = ob.seq_decoder; break; case OB_DROP_PLAYING: ob.seq_drop = ob.seq_player; break; } cond_leave(&ob_seq_cond); /* DEBUG("dropping %u\n", ob.seq_drop); */ ob_trigger_action(OB_ACTION_DROP); /* DEBUG("done dropping %u\n", ob.seq_drop); */ } /* call this exactly once before decoding each song */ void ob_advance_sequence(void) { assert(pthread_equal(pthread_self(), dc.thread)); DEBUG(__FILE__": %s %d\n", __func__, __LINE__); cond_enter(&ob_seq_cond); ++ob.seq_decoder; cond_leave(&ob_seq_cond); DEBUG(__FILE__": %s %d\n", __func__, __LINE__); DEBUG("ob.seq_decoder: %d\n", ob.seq_decoder); } /* * Returns true if output buffer is playing the song we're decoding */ int ob_synced(void) { int ret; assert(!pthread_equal(pthread_self(), dc.thread)); assert(!pthread_equal(pthread_self(), ob.thread)); /* assert(pthread_equal(pthread_self(), main_thread)); */ cond_enter(&ob_seq_cond); ret = (ob.seq_decoder == ob.seq_player); cond_leave(&ob_seq_cond); return ret; } static void new_song_chunk(struct ob_chunk *a) { assert(pthread_equal(pthread_self(), ob.thread)); ob.xfade_state = XFADE_DISABLED; ob.total_time = dc.total_time; /* DEBUG("ob.total_time: %f\n", ob.total_time); */ ob_seq_player_set(a->seq); wakeup_main_task(); /* sync playlist */ } #include "outputBuffer_audio.h" static void send_next_tag(void) { static struct mpd_tag *last_tag; struct mpd_tag *tag; if ((tag = metadata_pipe_recv())) { /* streaming tag */ DEBUG("Caught new metadata! %p\n", tag); sendMetadataToAudioDevice(tag); tag_free(tag); wakeup_main_task(); /* call sync_metadata() in playlist.c */ } else if ((tag = playlist_current_tag())) { /* static file tag */ /* shouldn't need mpdTagsAreEqual here for static tags */ if (last_tag != tag) { sendMetadataToAudioDevice(tag); last_tag = tag; } } } static void play_next_chunk(void) { struct rbvec vec[2]; struct ob_chunk *a; size_t nr; static float last_time; assert(pthread_equal(pthread_self(), ob.thread)); nr = ringbuf_get_read_vector(ob.index, vec); if (mpd_unlikely(!nr)) { if (dc.state == DC_STATE_STOP && ! playlist_playing()) stop_playback(); else prevent_buffer_underrun(); return; } if (ob.xfade_time <= 0) { if (nr < ob.bpp_cur) { prevent_buffer_underrun(); return; } } else if (nr < xfade_chunks_needed(vec)) { if (dc.state != DC_STATE_STOP && playlist_playing()) { prevent_buffer_underrun(); return; } /* nearing end of last track, xfade to silence.. */ } a = get_chunk(vec, 0); assert(a); if (! a->len) goto out; if (ob.xfade_state == XFADE_ENABLED) { struct ob_chunk *b; b = get_chunk(vec, ob.xfade_max); if (!b) { /* xfade to silence */ b = &silence; b->len = a->len; b->seq = a->seq + 1; } xfade_mix(a, b); } last_time = ob.elapsed_time = a->time; ob.bit_rate = a->bit_rate; if (mpd_unlikely(ob.seq_player != a->seq)) { if (open_audio_devices(1) < 0) return; new_song_chunk(a); } send_next_tag(); pcm_volumeChange(a->data, a->len, &ob.audio_format, ob.sw_vol); if (playAudio(a->data, a->len) < 0) stop_playback(); a->len = 0; /* mark the chunk as empty for ob_send() */ out: ringbuf_read_advance(ob.index, 1); /* we've played our first chunk, stop prebuffering */ if (mpd_unlikely(ob.bpp_cur)) ob.bpp_cur = 0; /* unblock ob_send() if it was waiting on a full buffer */ dc_try_unhalt(); } static void * ob_task(mpd_unused void *arg) { enum action_status as; assert(pthread_equal(pthread_self(), ob.thread)); cond_enter(&ob_halt_cond); while (1) { as = ob_take_action(); switch (ob.state) { case OB_STATE_PLAY: assert(as == AS_COMPLETE); if (open_audio_devices(0) >= 0) play_next_chunk(); break; case OB_STATE_STOP: case OB_STATE_PAUSE: case OB_STATE_SEEK: assert(as != AS_DEFERRED); ob.bpp_cur = ob.bpp_max; /* enable prebuffer */ if (as == AS_INPROGRESS) ob_finalize_action(); cond_wait(&ob_halt_cond); break; case OB_STATE_QUIT: goto out; } } out: cond_leave(&ob_halt_cond); assert(ob.state == OB_STATE_QUIT); assert(as == AS_INPROGRESS); ob_finalize_action(); return NULL; } #include "outputBuffer_config_init.h" void ob_seek_start(void) { assert(pthread_equal(pthread_self(), dc.thread)); assert(dc.seek_where >= 0); ob_trigger_action(OB_ACTION_SEEK_START); } void ob_seek_finish(void) { assert(pthread_equal(pthread_self(), dc.thread)); ob_trigger_action(OB_ACTION_SEEK_FINISH); } /* * if there are any partially written chunk, flush them out to * the output process _before_ decoding the next track */ void ob_flush(void) { struct rbvec vec[2]; assert(pthread_equal(pthread_self(), dc.thread)); /* DEBUG(__FILE__":%s %d\n", __func__, __LINE__); */ if (ringbuf_get_write_vector(ob.index, vec)) { /* DEBUG(__FILE__":%s %d\n", __func__, __LINE__); */ struct ob_chunk *c = get_chunk(vec, 0); assert(c); if (c->len) { assert(ob.seq_decoder == c->seq); switch (ob.state) { case OB_STATE_SEEK: assert(0); case OB_STATE_PLAY: case OB_STATE_PAUSE: ringbuf_write_advance(ob.index, 1); break; case OB_STATE_STOP: case OB_STATE_QUIT: c->len = 0; } } } } #include "outputBuffer_ob_send.h"