From 44d9f62f34e0561d83ea32941f0ea1b529b1490d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 16 Aug 2008 09:28:15 -0700 Subject: core rewrite (decode,player,outputBuffer,playlist) This is a huge refactoring of the core mpd process. The queueing/buffering mechanism is heavily reworked. The player.c code has been merged into outputBuffer (the actual ring buffering logic is handled by ringbuf.c); and decode.c actually handles decoding stuff. The end result is several hundreds of lines shorter, even though we still have a lot of DEBUG statements left in there for tracing and a lot of assertions, too. --- src/Makefile.am | 11 +- src/action_status.h | 16 + src/command.c | 50 +-- src/condition.c | 54 ++- src/condition.h | 42 ++- src/decode.c | 666 +++++++++++------------------------- src/decode.h | 59 ++-- src/inputPlugins/_flac_common.c | 11 +- src/inputPlugins/_flac_common.h | 16 +- src/inputPlugins/aac_plugin.c | 44 +-- src/inputPlugins/audiofile_plugin.c | 46 +-- src/inputPlugins/flac_plugin.c | 53 ++- src/inputPlugins/mod_plugin.c | 30 +- src/inputPlugins/mp3_plugin.c | 146 ++++---- src/inputPlugins/mp4_plugin.c | 55 ++- src/inputPlugins/mpc_plugin.c | 59 ++-- src/inputPlugins/oggflac_plugin.c | 55 ++- src/inputPlugins/oggvorbis_plugin.c | 59 ++-- src/inputPlugins/wavpack_plugin.c | 83 ++--- src/inputStream_http.c | 8 +- src/interface.c | 3 - src/log.c | 1 + src/main.c | 7 +- src/main_notify.c | 83 +---- src/main_notify.h | 6 - src/myfprintf.c | 2 +- src/notify.c | 68 ---- src/notify.h | 60 ---- src/outputBuffer.c | 661 +++++++++++++++++++++++++---------- src/outputBuffer.h | 135 ++++---- src/outputBuffer_accessors.h | 77 +++++ src/outputBuffer_audio.h | 35 ++ src/outputBuffer_ob_send.h | 133 +++++++ src/outputBuffer_sequence.h | 30 ++ src/outputBuffer_xfade.h | 105 ++++++ src/player.c | 345 ------------------- src/player.h | 139 -------- src/playerData.c | 19 +- src/playerData.h | 6 - src/player_error.c | 53 +++ src/player_error.h | 40 +++ src/playlist.c | 542 +++++++++++++++-------------- src/playlist.h | 23 +- src/ringbuf.h | 6 +- src/sig_handlers.c | 1 - src/stats.c | 4 +- src/volume.c | 3 +- 47 files changed, 1951 insertions(+), 2199 deletions(-) create mode 100644 src/action_status.h delete mode 100644 src/notify.c delete mode 100644 src/notify.h create mode 100644 src/outputBuffer_accessors.h create mode 100644 src/outputBuffer_audio.h create mode 100644 src/outputBuffer_ob_send.h create mode 100644 src/outputBuffer_sequence.h create mode 100644 src/outputBuffer_xfade.h delete mode 100644 src/player.c delete mode 100644 src/player.h create mode 100644 src/player_error.c create mode 100644 src/player_error.h diff --git a/src/Makefile.am b/src/Makefile.am index d79bda29f..93943db23 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -29,8 +29,8 @@ mpd_inputPlugins = \ mpd_headers = \ - notify.h \ ack.h \ + action_status.h \ audio.h \ audioOutput.h \ buffer2array.h \ @@ -61,10 +61,14 @@ mpd_headers = \ compress.h \ os_compat.h \ outputBuffer.h \ + outputBuffer_accessors.h \ + outputBuffer_audio.h \ + outputBuffer_ob_send.h \ + outputBuffer_xfade.h \ path.h \ pcm_utils.h \ permission.h \ - player.h \ + player_error.h \ playerData.h \ playlist.h \ replayGain.h \ @@ -92,7 +96,6 @@ mpd_SOURCES = \ $(mpd_headers) \ $(mpd_audioOutputs) \ $(mpd_inputPlugins) \ - notify.c \ audio.c \ audioOutput.c \ buffer2array.c \ @@ -122,7 +125,7 @@ mpd_SOURCES = \ path.c \ pcm_utils.c \ permission.c \ - player.c \ + player_error.c \ playerData.c \ playlist.c \ replayGain.c \ diff --git a/src/action_status.h b/src/action_status.h new file mode 100644 index 000000000..d645435c3 --- /dev/null +++ b/src/action_status.h @@ -0,0 +1,16 @@ +#ifndef ACTION_STATUS_H +#define ACTION_STATUS_H + +/* should probably be used in other places (decoder/input buffering), too */ +enum action_status { + /* all mutexes for conditions are unlocked and caller signaled */ + AS_COMPLETE = 0, + + /* mutexes are locked and caller has not been signaled, yet */ + AS_INPROGRESS, + + /* mutexes are unlocked and caller has not been signaled */ + AS_DEFERRED +}; + +#endif /* !ACTION_STATUS_H */ diff --git a/src/command.c b/src/command.c index a7e5b38fd..a8c49e8c9 100644 --- a/src/command.c +++ b/src/command.c @@ -17,7 +17,6 @@ */ #include "command.h" -#include "player.h" #include "playlist.h" #include "ls.h" #include "directory.h" @@ -33,6 +32,8 @@ #include "sllist.h" #include "ack.h" #include "os_compat.h" +#include "player_error.h" +#include "outputBuffer.h" #define COMMAND_PLAY "play" #define COMMAND_PLAYID "playid" @@ -280,13 +281,15 @@ static int handleCurrentSong(int fd, int *permission, int argc, char *argv[]) static int handlePause(int fd, int *permission, int argc, char *argv[]) { + enum ob_action action = OB_ACTION_PAUSE_FLIP; if (argc == 2) { - int pause_flag; - if (check_int(fd, &pause_flag, argv[1], check_boolean, argv[1]) < 0) + int set; + if (check_int(fd, &set, argv[1], check_boolean, argv[1]) < 0) return -1; - return playerSetPause(fd, pause_flag); + action = set ? OB_ACTION_PAUSE_SET : OB_ACTION_PAUSE_UNSET; } - return playerPause(fd); + ob_trigger_action(action); + return 0; } static int commandStatus(int fd, int *permission, int argc, char *argv[]) @@ -295,15 +298,16 @@ static int commandStatus(int fd, int *permission, int argc, char *argv[]) int updateJobId; int song; - playPlaylistIfPlayerStopped(); - switch (getPlayerState()) { - case PLAYER_STATE_STOP: + switch (ob_get_state()) { + case OB_STATE_STOP: + case OB_STATE_QUIT: state = COMMAND_STOP; break; - case PLAYER_STATE_PAUSE: + case OB_STATE_PAUSE: state = COMMAND_PAUSE; break; - case PLAYER_STATE_PLAY: + case OB_STATE_PLAY: + case OB_STATE_SEEK: state = COMMAND_PLAY; break; } @@ -318,7 +322,7 @@ static int commandStatus(int fd, int *permission, int argc, char *argv[]) fdprintf(fd, "%s: %i\n", COMMAND_STATUS_PLAYLIST_LENGTH, getPlaylistLength()); fdprintf(fd, "%s: %i\n", COMMAND_STATUS_CROSSFADE, - (int)(getPlayerCrossFade() + 0.5)); + (int)(ob_get_xfade() + 0.5)); fdprintf(fd, "%s: %s\n", COMMAND_STATUS_STATE, state); @@ -328,14 +332,14 @@ static int commandStatus(int fd, int *permission, int argc, char *argv[]) fdprintf(fd, "%s: %i\n", COMMAND_STATUS_SONGID, getPlaylistSongId(song)); } - if (getPlayerState() != PLAYER_STATE_STOP) { - fdprintf(fd, "%s: %i:%i\n", COMMAND_STATUS_TIME, - getPlayerElapsedTime(), getPlayerTotalTime()); - fdprintf(fd, "%s: %li\n", COMMAND_STATUS_BITRATE, - getPlayerBitRate()); - fdprintf(fd, "%s: %u:%i:%i\n", COMMAND_STATUS_AUDIO, - getPlayerSampleRate(), getPlayerBits(), - getPlayerChannels()); + if (ob_get_state() != OB_STATE_STOP) { + fdprintf(fd, "%s: %lu:%lu\n", COMMAND_STATUS_TIME, + ob_get_elapsed_time(), ob_get_total_time()); + fdprintf(fd, "%s: %u\n", COMMAND_STATUS_BITRATE, + ob_get_bit_rate()); + fdprintf(fd, "%s: %u:%u:%u\n", COMMAND_STATUS_AUDIO, + ob_get_sample_rate(), ob_get_bits(), + ob_get_channels()); } if ((updateJobId = isUpdatingDB())) { @@ -343,9 +347,9 @@ static int commandStatus(int fd, int *permission, int argc, char *argv[]) updateJobId); } - if (getPlayerError() != PLAYER_ERROR_NOERROR) { + if (player_errno != PLAYER_ERROR_NONE) { fdprintf(fd, "%s: %s\n", COMMAND_STATUS_ERROR, - getPlayerErrorStr()); + player_strerror()); } return 0; @@ -741,7 +745,7 @@ static int handleStats(int fd, int *permission, int argc, char *argv[]) static int handleClearError(int fd, int *permission, int argc, char *argv[]) { - clearPlayerError(); + player_clearerror(); return 0; } @@ -890,7 +894,7 @@ static int handleCrossfade(int fd, int *permission, int argc, char *argv[]) if (check_int(fd, &xfade_time, argv[1], check_non_negative, argv[1]) < 0) return -1; - setPlayerCrossFade(xfade_time); + ob_set_xfade(xfade_time); return 0; } diff --git a/src/condition.c b/src/condition.c index e71dcdd88..ca63ba537 100644 --- a/src/condition.c +++ b/src/condition.c @@ -27,54 +27,44 @@ void cond_init(struct condition *cond) xpthread_cond_init(&cond->cond, NULL); } -void cond_enter(struct condition *cond) -{ - pthread_mutex_lock(&cond->mutex); -} - -void cond_leave(struct condition *cond) -{ - pthread_mutex_unlock(&cond->mutex); -} - -void cond_wait(struct condition *cond) -{ - pthread_cond_wait(&cond->cond, &cond->mutex); -} - -static struct timespec * ts_timeout(struct timespec *ts, const long sec) +int cond_timedwait(struct condition *cond, const long msec) { + static const long nsec_per_msec = 1000000; + static const long nsec_per_usec = 1000; + static const long nsec_per_sec = 1000000000; + struct timespec ts; struct timeval tv; + int ret; + gettimeofday(&tv, NULL); - ts->tv_sec = tv.tv_sec + sec; - ts->tv_nsec = tv.tv_usec * 1000; - return ts; -} + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = (tv.tv_usec * nsec_per_usec) + (msec * nsec_per_msec); + if (ts.tv_nsec >= nsec_per_sec) { + ts.tv_nsec -= nsec_per_sec; + ts.tv_sec++; + } -int cond_timedwait(struct condition *cond, const long sec) -{ - struct timespec ts; - int ret = pthread_cond_timedwait(&cond->cond, &cond->mutex, - ts_timeout(&ts, sec)); + ret = pthread_cond_timedwait(&cond->cond, &cond->mutex, &ts); if (!ret || ret == ETIMEDOUT) return ret; FATAL("cond_timedwait: %s\n", strerror(ret)); return ret; } -int cond_signal_async(struct condition *cond) +int cond_signal_trysync(struct condition *cond) { - if (!pthread_mutex_trylock(&cond->mutex)) { - pthread_cond_signal(&cond->cond); - pthread_mutex_unlock(&cond->mutex); - return 0; - } - return EBUSY; + if (pthread_mutex_trylock(&cond->mutex) == EBUSY) + return EBUSY; + pthread_cond_signal(&cond->cond); + pthread_mutex_unlock(&cond->mutex); + return 0; } void cond_signal_sync(struct condition *cond) { + pthread_mutex_lock(&cond->mutex); pthread_cond_signal(&cond->cond); + pthread_mutex_unlock(&cond->mutex); } void cond_destroy(struct condition *cond) diff --git a/src/condition.h b/src/condition.h index 576ff9dc3..75e9cfad5 100644 --- a/src/condition.h +++ b/src/condition.h @@ -27,40 +27,62 @@ struct condition { pthread_cond_t cond; }; +#define STATIC_COND_INITIALIZER \ + { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER } + void cond_init(struct condition *cond); /** * The thread which shall be notified by this object must call this * function before any cond_wait() invocation. It locks the mutex. */ -void cond_enter(struct condition *cond); +static inline void cond_enter(struct condition *cond) +{ + pthread_mutex_lock(&cond->mutex); +} + +/** + * Will try to enter a condition where it can eventually wait. + * + * Returns immediately, 0 if successful, EBUSY if failed + */ +static inline int cond_tryenter(struct condition *cond) +{ + return pthread_mutex_trylock(&cond->mutex); +} /** * Neutralize cond_leave(). */ -void cond_leave(struct condition *cond); +static inline void cond_leave(struct condition *cond) +{ + pthread_mutex_unlock(&cond->mutex); +} /** * Wait for a conditio. Return immediately if we have already * been notified since we last returned from cond_wait(). */ -void cond_wait(struct condition *cond); +static inline void cond_wait(struct condition *cond) +{ + pthread_cond_wait(&cond->cond, &cond->mutex); +} /** * Wait for a condition with timeout * - * @param sec number of seconds to wait for (subject to change) + * @param sec number of milliseconds to wait for * * @return ETIMEDOUT if timed out, 0 if notification was received */ -int cond_timedwait(struct condition *cond, const long sec); +int cond_timedwait(struct condition *cond, const long msec); /** * Notify the thread there is a waiter. This function never blocks. * * @return EBUSY if it was unable to lock the mutex, 0 on success */ -int cond_signal_async(struct condition *cond); +int cond_signal_trysync(struct condition *cond); /** * Notify the thread synchronously, i.e. wait until it can deliver @@ -68,6 +90,14 @@ int cond_signal_async(struct condition *cond); */ void cond_signal_sync(struct condition *cond); +/** + * Signal the thread without caring about delivery success/failure + */ +static inline void cond_signal(struct condition *cond) +{ + pthread_cond_signal(&cond->cond); +} + /** * cond_destroy - destroy the cond and internal structures */ diff --git a/src/decode.c b/src/decode.c index 6b57239e3..12c1b8746 100644 --- a/src/decode.c +++ b/src/decode.c @@ -17,241 +17,210 @@ */ #include "decode.h" - -#include "player.h" +#include "outputBuffer.h" +#include "player_error.h" +#include "playlist.h" #include "playerData.h" #include "pcm_utils.h" #include "path.h" #include "log.h" #include "ls.h" -#include "main_notify.h" +#include "condition.h" -enum xfade_state { - XFADE_DISABLED = -1, - XFADE_UNKNOWN = 0, - XFADE_ENABLED = 1 -}; +static struct condition dc_action_cond = STATIC_COND_INITIALIZER; +static struct condition dc_halt_cond = STATIC_COND_INITIALIZER; -/* called inside decoder_task (inputPlugins) */ -void decoder_wakeup_player(void) -{ - wakeup_player_nb(); -} +struct decoder_control dc; /* ugh, global for now... */ -void decoder_sleep(void) +/* blocking, waits until the signaled thread has replied */ +void dc_trigger_action(enum dc_action action, float seek_where) { - notify_wait(&dc.notify); - wakeup_player_nb(); + assert(!pthread_equal(pthread_self(), dc.thread)); + /* assert(pthread_equal(pthread_self(), main_thread)); */ + assert(action != DC_ACTION_NONE); + + /* DEBUG(__FILE__ ":%s %d\n", __func__,__LINE__); */ + cond_enter(&dc_action_cond); + assert(dc.action == DC_ACTION_NONE); + if (action == DC_ACTION_SEEK) + dc.seek_where = seek_where; /* usually 0 */ + dc.action = action; + do { + /* DEBUG(__FILE__ ":%s %d\n", __func__,__LINE__); */ + cond_signal(&dc_halt_cond); /* blind signal w/o lock */ + /* DEBUG(__FILE__ ":%s %d\n", __func__,__LINE__); */ + cond_timedwait(&dc_action_cond, 10); + /* DEBUG(__FILE__ ":%s %d\n", __func__,__LINE__); */ + } while (dc.action != DC_ACTION_NONE); /* spurious wakeup protection */ + /* DEBUG(__FILE__ ":%s %d\n", __func__,__LINE__); */ + cond_leave(&dc_action_cond); + /* DEBUG(__FILE__ ":%s %d\n", __func__,__LINE__); */ } -static void player_wakeup_decoder_nb(void) +static void take_action(void) { - notify_signal(&dc.notify); + assert(pthread_equal(pthread_self(), dc.thread)); + assert(dc.state == DC_STATE_STOP); + /* DEBUG("%s dc.action(%d): %d\n", __func__,__LINE__, dc.action); */ + cond_enter(&dc_action_cond); + /* DEBUG("%s dc.action(%d): %d\n", __func__,__LINE__, dc.action); */ + + switch (dc.action) { + case DC_ACTION_NONE: goto out; + case DC_ACTION_START: + case DC_ACTION_SEEK: + dc.state = DC_STATE_DECODE; + return; + case DC_ACTION_STOP: dc.state = DC_STATE_STOP; break; + case DC_ACTION_QUIT: dc.state = DC_STATE_QUIT; + } + dc.action = DC_ACTION_NONE; + cond_signal(&dc_action_cond); +out: + assert(dc.action == DC_ACTION_NONE); + cond_leave(&dc_action_cond); } -/* called from player_task */ -static void player_wakeup_decoder(void) +/* + * This will grab an action, but will not signal the calling thread. + * dc_action_end() is required to signal the calling thread + */ +void dc_action_begin(void) { - notify_signal(&dc.notify); - player_sleep(); -} + enum dc_action ret = dc.action; -static void stopDecode(void) -{ - if (dc.start || dc.state != DECODE_STATE_STOP) { - dc.stop = 1; - do { player_wakeup_decoder_nb(); } while (dc.stop); + assert(pthread_equal(pthread_self(), dc.thread)); + + if (ret != DC_ACTION_NONE) { + /* DEBUG(__FILE__ ":%s %d\n", __func__,__LINE__); */ + cond_enter(&dc_action_cond); + /* dc.action can't get set to NONE outside this thread */ + assert(dc.action == ret); + if (ret == DC_ACTION_SEEK) + ob_seek_start(); } } -static void quitDecode(void) +void dc_action_end(void) { - stopDecode(); - pc.state = PLAYER_STATE_STOP; - dc.seek = 0; - pc.play = 0; - pc.stop = 0; - pc.pause = 0; - wakeup_main_task(); + assert(pthread_equal(pthread_self(), dc.thread)); + assert(dc.action != DC_ACTION_NONE); + /* DEBUG("DONE ACTION %d\n", dc.action); */ + if (dc.action == DC_ACTION_SEEK) + ob_seek_finish(); + dc.action = DC_ACTION_NONE; + + cond_signal(&dc_action_cond); + cond_leave(&dc_action_cond); } -static unsigned calculateCrossFadeChunks(AudioFormat * af, float totalTime) +void dc_action_seek_fail(enum seek_err_type err_type) { - unsigned int buffered_chunks, chunks; - - if (pc.crossFade == 0 || pc.crossFade >= totalTime || - !isCurrentAudioFormat(af)) - return 0; - - assert(pc.crossFade > 0); - assert(af->bits > 0); - assert(af->channels > 0); - assert(af->sampleRate > 0); - - chunks = (af->sampleRate * af->bits * af->channels / 8.0 / CHUNK_SIZE); - chunks = (chunks * pc.crossFade + 0.5); - - buffered_chunks = ob.size; - assert(buffered_chunks >= buffered_before_play); - if (chunks > (buffered_chunks - buffered_before_play)) - chunks = buffered_chunks - buffered_before_play; - - return chunks; + assert(pthread_equal(pthread_self(), dc.thread)); + cond_enter(&dc_action_cond); + assert(dc.action == DC_ACTION_SEEK); + dc.action = DC_ACTION_NONE; + dc.seek_where = err_type; + cond_signal(&dc_action_cond); + cond_leave(&dc_action_cond); } -static int waitOnDecode(int *decodeWaitedOn) +/* Returns true if we need to interrupt the decoding inside an inputPlugin */ +int dc_intr(void) { - while (dc.start) - player_wakeup_decoder(); - - if (dc.error != DECODE_ERROR_NOERROR) { - pc.errored_song = pc.current_song; - pc.error = PLAYER_ERROR_FILE; - quitDecode(); - return -1; + if (!pthread_equal(pthread_self(), dc.thread)) + return 0; + switch (dc.action) { + case DC_ACTION_NONE: + case DC_ACTION_SEEK: + return 0; + default: + /* DEBUG(__FILE__": %s %d\n", __func__, __LINE__); */ + /* DEBUG("dc.action: %d\n", (int)dc.action); */ + return 1; } - - pc.totalTime = pc.fileTime; - pc.bitRate = 0; - pc.sampleRate = 0; - pc.bits = 0; - pc.channels = 0; - *decodeWaitedOn = 1; - - return 0; } -static int decodeSeek(int *decodeWaitedOn, int *next) +int dc_seek(void) { - int ret = -1; - - if (dc.state == DECODE_STATE_STOP || - dc.error != DECODE_ERROR_NOERROR || - dc.current_song != pc.current_song) { - stopDecode(); - *next = -1; - ob_clear(); - dc.error = DECODE_ERROR_NOERROR; - dc.start = 1; - waitOnDecode(decodeWaitedOn); - } - if (dc.state != DECODE_STATE_STOP && dc.seekable) { - *next = -1; - dc.seekWhere = pc.seekWhere > pc.totalTime - 0.1 ? - pc.totalTime - 0.1 : pc.seekWhere; - dc.seekWhere = 0 > dc.seekWhere ? 0 : dc.seekWhere; - dc.seekError = 0; - dc.seek = 1; - do { player_wakeup_decoder(); } while (dc.seek); - if (!dc.seekError) { - pc.elapsedTime = dc.seekWhere; - ret = 0; - } - } - pc.seek = 0; - wakeup_main_task(); - - return ret; + if (pthread_equal(pthread_self(), dc.thread)) + return (dc.action == DC_ACTION_SEEK); + return 0; } -static void processDecodeInput(int *pause_r, unsigned int *bbp_r, - enum xfade_state *do_xfade_r, - int *decodeWaitedOn_r, - int *next_r) +static void finalize_per_track_actions(void) { - if(pc.lockQueue) { - pc.queueLockState = PLAYER_QUEUE_LOCKED; - pc.lockQueue = 0; - wakeup_main_task(); - } - if(pc.unlockQueue) { - pc.queueLockState = PLAYER_QUEUE_UNLOCKED; - pc.unlockQueue = 0; - wakeup_main_task(); - } - if(pc.pause) { - *pause_r = !*pause_r; - if (*pause_r) { - pc.state = PLAYER_STATE_PAUSE; - } else { - if (openAudioDevice(NULL) >= 0) { - pc.state = PLAYER_STATE_PLAY; - } else { - char tmp[MPD_PATH_MAX]; - pc.errored_song = pc.current_song; - pc.error = PLAYER_ERROR_AUDIO; - ERROR("problems opening audio device " - "while playing \"%s\"\n", - get_song_url(tmp, pc.current_song)); - *pause_r = -1; - } - } - pc.pause = 0; - wakeup_main_task(); - if (*pause_r == -1) { - *pause_r = 1; - } else if (*pause_r) { - dropBufferedAudio(); - closeAudioDevice(); - } - } - if(pc.seek) { - dropBufferedAudio(); - if (decodeSeek(decodeWaitedOn_r, next_r) == 0) { - *do_xfade_r = XFADE_UNKNOWN; - *bbp_r = 0; - } + enum dc_action action; + /* DEBUG(":%s dc.action(%d): %d\n", __func__,__LINE__, dc.action); */ + assert(pthread_equal(pthread_self(), dc.thread)); + cond_enter(&dc_action_cond); + dc.state = DC_STATE_STOP; + action = dc.action; + dc.action = DC_ACTION_NONE; + + if (action == DC_ACTION_STOP) { + cond_signal(&dc_action_cond); + } else if (action == DC_ACTION_SEEK) { + dc.seek_where = DC_SEEK_MISMATCH; + cond_signal(&dc_action_cond); } + cond_leave(&dc_action_cond); + /* DEBUG(":%s dc.action(%d): %d\n", __func__,__LINE__, dc.action); */ } -static void decodeStart(void) +static void decode_start(void) { - int ret; + int err = -1; int close_instream = 1; - InputStream inStream; + InputStream is; InputPlugin *plugin = NULL; char path_max_fs[MPD_PATH_MAX]; char path_max_utf8[MPD_PATH_MAX]; - - if (!get_song_url(path_max_utf8, pc.current_song)) { - dc.error = DECODE_ERROR_FILE; - goto stop_no_close; + assert(pthread_equal(pthread_self(), dc.thread)); + assert(dc.state == DC_STATE_DECODE); + assert(dc.current_song); + get_song_url(path_max_utf8, dc.current_song); + assert(*path_max_utf8); + + switch (dc.action) { + case DC_ACTION_START: + dc_action_end(); + break; + case DC_ACTION_SEEK: + /* DEBUG("dc.seek_where(%d): %f\n", __LINE__, dc.seek_where); */ + /* make sure dc_action_start() works inside inputPlugins: */ + cond_leave(&dc_action_cond); + /* DEBUG("dc.action(%d) %d\n", __LINE__, dc.action); */ + break; + default: assert("unknown action!" && 0); } - if (!isRemoteUrl(path_max_utf8)) { + + if (isRemoteUrl(path_max_utf8)) { + pathcpy_trunc(path_max_fs, path_max_utf8); + } else { rmp2amp_r(path_max_fs, utf8_to_fs_charset(path_max_fs, path_max_utf8)); - } else - pathcpy_trunc(path_max_fs, path_max_utf8); - - dc.current_song = pc.current_song; /* NEED LOCK */ - if (openInputStream(&inStream, path_max_fs) < 0) { - dc.error = DECODE_ERROR_FILE; - goto stop_no_close; } - dc.state = DECODE_STATE_START; - dc.start = 0; - - /* for http streams, seekable is determined in bufferInputStream */ - dc.seekable = inStream.seekable; - - if (dc.stop) - goto stop; + if (openInputStream(&is, path_max_fs) < 0) { + DEBUG("couldn't open song: %s\n", path_max_fs); + player_seterror(PLAYER_ERROR_FILENOTFOUND, dc.current_song); + return; + } - ret = DECODE_ERROR_UNKTYPE; if (isRemoteUrl(path_max_utf8)) { unsigned int next = 0; /* first we try mime types: */ - while (ret && (plugin = getInputPluginFromMimeType(inStream.mime, next++))) { + while (err && (plugin = getInputPluginFromMimeType(is.mime, next++))) { if (!plugin->streamDecodeFunc) continue; if (!(plugin->streamTypes & INPUT_PLUGIN_STREAM_URL)) continue; if (plugin->tryDecodeFunc - && !plugin->tryDecodeFunc(&inStream)) + && !plugin->tryDecodeFunc(&is)) continue; - ret = plugin->streamDecodeFunc(&inStream); + err = plugin->streamDecodeFunc(&is); break; } @@ -259,16 +228,16 @@ static void decodeStart(void) if (plugin == NULL) { const char *s = getSuffix(path_max_utf8); next = 0; - while (ret && (plugin = getInputPluginFromSuffix(s, next++))) { + while (err && (plugin = getInputPluginFromSuffix(s, next++))) { if (!plugin->streamDecodeFunc) continue; if (!(plugin->streamTypes & INPUT_PLUGIN_STREAM_URL)) continue; if (plugin->tryDecodeFunc && - !plugin->tryDecodeFunc(&inStream)) + !plugin->tryDecodeFunc(&is)) continue; - ret = plugin->streamDecodeFunc(&inStream); + err = plugin->streamDecodeFunc(&is); break; } } @@ -278,330 +247,101 @@ static void decodeStart(void) if (plugin == NULL) { /* we already know our mp3Plugin supports streams, no * need to check for stream{Types,DecodeFunc} */ - if ((plugin = getInputPluginFromName("mp3"))) { - ret = plugin->streamDecodeFunc(&inStream); - } + if ((plugin = getInputPluginFromName("mp3"))) + err = plugin->streamDecodeFunc(&is); } } else { unsigned int next = 0; const char *s = getSuffix(path_max_utf8); - while (ret && (plugin = getInputPluginFromSuffix(s, next++))) { + while (err && (plugin = getInputPluginFromSuffix(s, next++))) { if (!plugin->streamTypes & INPUT_PLUGIN_STREAM_FILE) continue; if (plugin->tryDecodeFunc && - !plugin->tryDecodeFunc(&inStream)) + !plugin->tryDecodeFunc(&is)) continue; if (plugin->fileDecodeFunc) { - closeInputStream(&inStream); + closeInputStream(&is); close_instream = 0; - ret = plugin->fileDecodeFunc(path_max_fs); + err = plugin->fileDecodeFunc(path_max_fs); break; } else if (plugin->streamDecodeFunc) { - ret = plugin->streamDecodeFunc(&inStream); + err = plugin->streamDecodeFunc(&is); break; } } } - if (ret < 0 || ret == DECODE_ERROR_UNKTYPE) { - pc.errored_song = pc.current_song; - if (ret != DECODE_ERROR_UNKTYPE) - dc.error = DECODE_ERROR_FILE; + if (err) { + if (plugin) + player_seterror(PLAYER_ERROR_SYSTEM, dc.current_song); else - dc.error = DECODE_ERROR_UNKTYPE; + player_seterror(PLAYER_ERROR_UNKTYPE, dc.current_song); } - -stop: + if (player_errno) + ERROR("player_error: %s\n", player_strerror()); if (close_instream) - closeInputStream(&inStream); -stop_no_close: - dc.state = DECODE_STATE_STOP; - dc.stop = 0; + closeInputStream(&is); } static void * decoder_task(mpd_unused void *arg) { - notify_enter(&dc.notify); - + assert(pthread_equal(pthread_self(), dc.thread)); + cond_enter(&dc_halt_cond); while (1) { - assert(dc.state == DECODE_STATE_STOP); - - if (dc.start || dc.seek) { - decodeStart(); - } else if (dc.stop) { - dc.stop = 0; - decoder_wakeup_player(); - } else { - decoder_sleep(); + take_action(); + switch (dc.state) { + case DC_STATE_STOP: + /* DEBUG(__FILE__": halted %d\n", __LINE__); */ + cond_wait(&dc_halt_cond); + /* DEBUG(__FILE__": unhalted %d\n", __LINE__); */ + break; + case DC_STATE_DECODE: + /* DEBUG(__FILE__": %s %d\n", __func__, __LINE__); */ + /* DEBUG("dc.action: %d\n", (int)dc.action); */ + if ((dc.current_song = playlist_queued_song())) { + char p[MPD_PATH_MAX]; + ob_advance_sequence(); + get_song_url(p, dc.current_song); + DEBUG("decoding song: %s\n", p); + decode_start(); + DEBUG("DONE decoding song: %s\n", p); + ob_flush(); + dc.current_song = NULL; + } + finalize_per_track_actions(); + playlist_queue_next(); + break; + case DC_STATE_QUIT: + goto out; } } - +out: + cond_leave(&dc_halt_cond); + assert(dc.state == DC_STATE_QUIT); return NULL; } -void decoderInit(void) +void decoder_init(void) { pthread_attr_t attr; - pthread_t decoder_thread; + assert(!dc.thread); pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - if (pthread_create(&decoder_thread, &attr, decoder_task, NULL)) + if (pthread_create(&dc.thread, &attr, decoder_task, NULL)) FATAL("Failed to spawn decoder task: %s\n", strerror(errno)); } -static void crossFade(ob_chunk * a, ob_chunk * b, - AudioFormat * format, - unsigned int fadePosition, unsigned int crossFadeChunks) +int dc_try_unhalt(void) { - assert(fadePosition <= crossFadeChunks); - - pcm_mix(a->data, - b->data, - a->chunkSize, - b->chunkSize, - format, - ((float)fadePosition) / - crossFadeChunks); - if (b->chunkSize > a->chunkSize) - a->chunkSize = b->chunkSize; + assert(!pthread_equal(pthread_self(), dc.thread)); + return cond_signal_trysync(&dc_halt_cond); } -static int playChunk(ob_chunk * chunk, - const AudioFormat * format, double sizeToTime) +void dc_halt(void) { - pc.elapsedTime = chunk->times; - pc.bitRate = chunk->bitRate; - - pcm_volumeChange(chunk->data, chunk->chunkSize, - format, pc.softwareVolume); - - if (playAudio(chunk->data, - chunk->chunkSize) < 0) - return -1; - - pc.totalPlayTime += sizeToTime * chunk->chunkSize; - return 0; -} - -static void decodeParent(void) -{ - int do_pause = 0; - int buffering = 1; - unsigned int bbp = buffered_before_play; - enum xfade_state do_xfade = XFADE_UNKNOWN; - unsigned int crossFadeChunks = 0; - /** the position of the next cross-faded chunk in the next - song */ - int nextChunk = 0; - int decodeWaitedOn = 0; - static const char silence[CHUNK_SIZE]; - double sizeToTime = 0.0; - /** the position of the first chunk in the next song */ - int next = -1; - - ob_set_lazy(0); - - if (waitOnDecode(&decodeWaitedOn) < 0) - return; - - pc.elapsedTime = 0; - pc.state = PLAYER_STATE_PLAY; - pc.play = 0; - wakeup_main_task(); - - while (1) { - processDecodeInput(&do_pause, &bbp, &do_xfade, - &decodeWaitedOn, &next); - if (pc.stop) { - dropBufferedAudio(); - break; - } - - if (buffering) { - if (ob_available() < bbp) { - /* not enough decoded buffer space yet */ - player_sleep(); - continue; - } else { - /* buffering is complete */ - buffering = 0; - ob_set_lazy(1); - } - } - - if (decodeWaitedOn) { - if(dc.state!=DECODE_STATE_START && - dc.error==DECODE_ERROR_NOERROR) { - /* the decoder is ready and ok */ - decodeWaitedOn = 0; - if(openAudioDevice(&(ob.audioFormat))<0) { - char tmp[MPD_PATH_MAX]; - pc.errored_song = pc.current_song; - pc.error = PLAYER_ERROR_AUDIO; - ERROR("problems opening audio device " - "while playing \"%s\"\n", - get_song_url(tmp, pc.current_song)); - break; - } else { - player_wakeup_decoder(); - } - if (do_pause) { - dropBufferedAudio(); - closeAudioDevice(); - } - pc.totalTime = dc.totalTime; - pc.sampleRate = dc.audioFormat.sampleRate; - pc.bits = dc.audioFormat.bits; - pc.channels = dc.audioFormat.channels; - sizeToTime = audioFormatSizeToTime(&ob.audioFormat); - } - else if(dc.state!=DECODE_STATE_START) { - /* the decoder failed */ - pc.errored_song = pc.current_song; - pc.error = PLAYER_ERROR_FILE; - break; - } - else { - /* the decoder is not yet ready; wait - some more */ - player_sleep(); - continue; - } - } - - if (dc.state == DECODE_STATE_STOP && - pc.queueState == PLAYER_QUEUE_FULL && - pc.queueLockState == PLAYER_QUEUE_UNLOCKED) { - /* the decoder has finished the current song; - make it decode the next song */ - next = ob.end; - dc.start = 1; - pc.queueState = PLAYER_QUEUE_DECODE; - wakeup_main_task(); - player_wakeup_decoder_nb(); - } - if (next >= 0 && do_xfade == XFADE_UNKNOWN && !dc.start && - dc.state != DECODE_STATE_START) { - /* enable cross fading in this song? if yes, - calculate how many chunks will be required - for it */ - crossFadeChunks = - calculateCrossFadeChunks(&(ob.audioFormat), - dc.totalTime); - if (crossFadeChunks > 0) { - do_xfade = XFADE_ENABLED; - nextChunk = -1; - } else - /* cross fading is disabled or the - next song is too short */ - do_xfade = XFADE_DISABLED; - } - - if (do_pause) - player_sleep(); - else if (!ob_is_empty() && (int)ob.begin != next) { - ob_chunk *beginChunk = ob_get_chunk(ob.begin); - unsigned int fadePosition; - if (do_xfade == XFADE_ENABLED && next >= 0 && - (fadePosition = ob_relative(next)) - <= 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 = ob_absolute(crossFadeChunks); - if (nextChunk >= 0) { - ob_set_lazy(1); - crossFade(beginChunk, - ob_get_chunk(nextChunk), - &(ob.audioFormat), - fadePosition, - crossFadeChunks); - } else { - /* there are not enough - decoded chunks yet */ - if (dc.state == DECODE_STATE_STOP) { - /* the decoder isn't - running, abort - cross fading */ - do_xfade = XFADE_DISABLED; - } else { - /* wait for the - decoder */ - ob_set_lazy(0); - player_sleep(); - continue; - } - } - } - - /* play the current chunk */ - if (playChunk(beginChunk, &(ob.audioFormat), - sizeToTime) < 0) - break; - ob_shift(); - player_wakeup_decoder_nb(); - } else if (!ob_is_empty() && (int)ob.begin == next) { - /* at the beginning of a new song */ - - if (do_xfade == XFADE_ENABLED && nextChunk >= 0) { - /* the cross-fade is finished; skip - the section which was cross-faded - (and thus already played) */ - ob_skip(crossFadeChunks); - } - - do_xfade = XFADE_UNKNOWN; - - /* wait for the decoder to work on the new song */ - if (pc.queueState == PLAYER_QUEUE_DECODE || - pc.queueLockState == PLAYER_QUEUE_LOCKED) { - player_sleep(); - continue; - } - if (pc.queueState != PLAYER_QUEUE_PLAY) - break; - - next = -1; - if (waitOnDecode(&decodeWaitedOn) < 0) - return; - - pc.queueState = PLAYER_QUEUE_EMPTY; - wakeup_main_task(); - } else if (dc.state == DECODE_STATE_STOP && !dc.start) { - break; - } else { - /*DEBUG("waiting for decoded audio, play silence\n");*/ - if (playAudio(silence, CHUNK_SIZE) < 0) - break; - } - } - - quitDecode(); -} - -/* decode w/ buffering - * this will fork another process - * child process does decoding - * parent process does playing audio - */ -void decode(void) -{ - ob_clear(); - - dc.error = DECODE_ERROR_NOERROR; - dc.seek = 0; - dc.stop = 0; - dc.start = 1; - do { player_wakeup_decoder(); } while (dc.start); - - decodeParent(); + assert(pthread_equal(pthread_self(), dc.thread)); + cond_wait(&dc_halt_cond); } diff --git a/src/decode.h b/src/decode.h index 8ee2a095f..b1ba138e2 100644 --- a/src/decode.h +++ b/src/decode.h @@ -22,43 +22,50 @@ #include "song.h" #include "audio.h" -#include "notify.h" +#include "condition.h" #define DECODE_TYPE_FILE 0 #define DECODE_TYPE_URL 1 -enum decoder_state { - DECODE_STATE_STOP = 0, - DECODE_STATE_START, - DECODE_STATE_DECODE +enum dc_action { + DC_ACTION_NONE = 0, + DC_ACTION_START, + DC_ACTION_SEEK, /* like start, but clears previously decoded audio */ + DC_ACTION_STOP, + DC_ACTION_QUIT }; -#define DECODE_ERROR_NOERROR 0 -#define DECODE_ERROR_UNKTYPE 10 -#define DECODE_ERROR_FILE 20 +/* only changeable by dc.thread */ +enum dc_state { + DC_STATE_STOP = 0, /* decoder stopped (no song) */ + DC_STATE_DECODE, /* open() + {file,stream}DecodeFunc (+ paused) */ + DC_STATE_QUIT /* NIH, the pthread cancellation API blows... */ +}; -typedef struct _DecoderControl { - Notify notify; +struct decoder_control { + Song * current_song; /* only needed for wavpack, remove? */ + enum dc_state state; /* rw=dc.thread, r=main */ + enum dc_action action; /* rw protected by action_cond */ + float total_time; /* w=dc.thread, r=main */ + float seek_where; /* -1 == error, rw protected by action_cond */ + pthread_t thread; + AudioFormat audio_format; /* w=dc.thread, r=all */ +}; - volatile enum decoder_state state; - volatile mpd_sint8 stop; - volatile mpd_sint8 start; - volatile mpd_uint16 error; - volatile mpd_sint8 seek; - volatile mpd_sint8 seekError; - volatile mpd_sint8 seekable; - volatile double seekWhere; - AudioFormat audioFormat; - Song *current_song; - volatile float totalTime; -} DecoderControl; +extern struct decoder_control dc; -void decode(void); +void dc_trigger_action(enum dc_action action, float seek_where); +void decoder_init(void); +int dc_try_unhalt(void); +void dc_halt(void); -void decoder_wakeup_player(void); +void dc_action_begin(void); +void dc_action_end(void); -void decoder_sleep(void); +int dc_intr(void); +int dc_seek(void); -void decoderInit(void); +enum seek_err_type { DC_SEEK_MISMATCH = -2, DC_SEEK_ERROR = -1 }; +void dc_action_seek_fail(enum seek_err_type); #endif diff --git a/src/inputPlugins/_flac_common.c b/src/inputPlugins/_flac_common.c index cf23a5e8c..3401a8b4f 100644 --- a/src/inputPlugins/_flac_common.c +++ b/src/inputPlugins/_flac_common.c @@ -166,11 +166,10 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, switch (block->type) { case FLAC__METADATA_TYPE_STREAMINFO: - dc.audioFormat.bits = (mpd_sint8)si->bits_per_sample; - dc.audioFormat.sampleRate = si->sample_rate; - dc.audioFormat.channels = (mpd_sint8)si->channels; - dc.totalTime = ((float)si->total_samples) / (si->sample_rate); - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); + dc.audio_format.bits = (mpd_sint8)si->bits_per_sample; + dc.audio_format.sampleRate = si->sample_rate; + dc.audio_format.channels = (mpd_sint8)si->channels; + dc.total_time = ((float)si->total_samples) / (si->sample_rate); break; case FLAC__METADATA_TYPE_VORBIS_COMMENT: flacParseReplayGain(block, data); @@ -183,7 +182,7 @@ void flac_error_common_cb(const char *plugin, const FLAC__StreamDecoderErrorStatus status, FlacData * data) { - if (dc.stop) + if (dc_intr()) return; switch (status) { diff --git a/src/inputPlugins/_flac_common.h b/src/inputPlugins/_flac_common.h index 10c2f3d38..5c147a064 100644 --- a/src/inputPlugins/_flac_common.h +++ b/src/inputPlugins/_flac_common.h @@ -165,17 +165,13 @@ MpdTag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, MpdTag * tag); /* keep this inlined, this is just macro but prettier :) */ -static inline int flacSendChunk(FlacData * data) +static inline enum dc_action flacSendChunk(FlacData * data) { - if (ob_send(data->inStream, - 1, data->chunk, - data->chunk_length, data->time, - data->bitRate, - data->replayGainInfo) == - OUTPUT_BUFFER_DC_STOP) - return -1; - - return 0; + enum dc_action ret = ob_send(data->chunk, data->chunk_length, + data->time, data->bitRate, + data->replayGainInfo); + data->chunk_length = 0; + return ret; } #endif /* HAVE_FLAC || HAVE_OGGFLAC */ diff --git a/src/inputPlugins/aac_plugin.c b/src/inputPlugins/aac_plugin.c index 6e53c6420..98329a4b3 100644 --- a/src/inputPlugins/aac_plugin.c +++ b/src/inputPlugins/aac_plugin.c @@ -339,9 +339,8 @@ static int aac_decode(char *path) return -1; } - dc.audioFormat.bits = 16; - - dc.totalTime = totalTime; + dc.audio_format.bits = 16; + dc.total_time = totalTime; file_time = 0.0; @@ -372,13 +371,8 @@ static int aac_decode(char *path) sampleRate = frameInfo.samplerate; #endif - if (dc.state != DECODE_STATE_DECODE) { - dc.audioFormat.channels = frameInfo.channels; - dc.audioFormat.sampleRate = sampleRate; - getOutputAudioFormat(&(dc.audioFormat), - &(ob.audioFormat)); - dc.state = DECODE_STATE_DECODE; - } + dc.audio_format.channels = frameInfo.channels; + dc.audio_format.sampleRate = sampleRate; advanceAacBuffer(&b, frameInfo.bytesconsumed); @@ -395,34 +389,24 @@ static int aac_decode(char *path) sampleBufferLen = sampleCount * 2; - ob_send(NULL, 0, sampleBuffer, - sampleBufferLen, file_time, - bitRate, NULL); - if (dc.seek) { - dc.seekError = 1; - dc.seek = 0; - decoder_wakeup_player(); - } else if (dc.stop) { - eof = 1; + switch (ob_send(sampleBuffer, sampleBufferLen, + file_time, bitRate, NULL)) { + case DC_ACTION_NONE: break; + case DC_ACTION_SEEK: + /* + * this plugin doesn't support seek because nobody + * has bothered, yet... + */ + dc_action_seek_fail(DC_SEEK_ERROR); break; + default: eof = 1; } } - ob_flush(); - faacDecClose(decoder); if (b.buffer) free(b.buffer); - if (dc.state != DECODE_STATE_DECODE) - return -1; - - if (dc.seek) { - dc.seekError = 1; - dc.seek = 0; - decoder_wakeup_player(); - } - return 0; } diff --git a/src/inputPlugins/audiofile_plugin.c b/src/inputPlugins/audiofile_plugin.c index 558731dd3..8178972ff 100644 --- a/src/inputPlugins/audiofile_plugin.c +++ b/src/inputPlugins/audiofile_plugin.c @@ -67,42 +67,39 @@ static int audiofile_decode(char *path) afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, AF_SAMPFMT_TWOSCOMP, 16); afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - dc.audioFormat.bits = (mpd_uint8)bits; - dc.audioFormat.sampleRate = + dc.audio_format.bits = (mpd_uint8)bits; + dc.audio_format.sampleRate = (unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK); - dc.audioFormat.channels = + dc.audio_format.channels = (mpd_uint8)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK); - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); - dc.totalTime = - ((float)frame_count / (float)dc.audioFormat.sampleRate); + dc.total_time = ((float)frame_count / + (float)dc.audio_format.sampleRate); - bitRate = (mpd_uint16)(st.st_size * 8.0 / dc.totalTime / 1000.0 + 0.5); + bitRate = (mpd_uint16)(st.st_size * 8.0 / dc.total_time / 1000.0 + 0.5); - if (dc.audioFormat.bits != 8 && dc.audioFormat.bits != 16) { + if (dc.audio_format.bits != 8 && dc.audio_format.bits != 16) { ERROR("Only 8 and 16-bit files are supported. %s is %i-bit\n", - path, dc.audioFormat.bits); + path, dc.audio_format.bits); afCloseFile(af_fp); return -1; } fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); - dc.state = DECODE_STATE_DECODE; { int ret, eof = 0, current = 0; char chunk[CHUNK_SIZE]; while (!eof) { - if (dc.seek) { - ob_clear(); - current = dc.seekWhere * - dc.audioFormat.sampleRate; + if (dc_seek()) { + dc_action_begin(); + current = dc.seek_where * + dc.audio_format.sampleRate; afSeekFrame(af_fp, AF_DEFAULT_TRACK, current); - dc.seek = 0; - decoder_wakeup_player(); + dc_action_end(); } ret = @@ -112,20 +109,15 @@ static int audiofile_decode(char *path) eof = 1; else { current += ret; - ob_send(NULL, - 1, - chunk, - ret * fs, - (float)current / - (float)dc.audioFormat. - sampleRate, bitRate, - NULL); - if (dc.stop) + ob_send(chunk, ret * fs, + (float)current / + (float)dc.audio_format.sampleRate, + bitRate, + NULL); + if (dc_intr()) break; } } - - ob_flush(); } afCloseFile(af_fp); diff --git a/src/inputPlugins/flac_plugin.c b/src/inputPlugins/flac_plugin.c index 38131bac9..4a4ac627c 100644 --- a/src/inputPlugins/flac_plugin.c +++ b/src/inputPlugins/flac_plugin.c @@ -41,14 +41,14 @@ static flac_read_status flacRead(const flac_decoder * flacDec, while (1) { r = readFromInputStream(data->inStream, (void *)buf, 1, *bytes); - if (r == 0 && !inputStreamAtEOF(data->inStream) && !dc.stop) - my_usleep(10000); + if (r == 0 && !inputStreamAtEOF(data->inStream) && !dc_intr()) + my_usleep(10000); /* FIXME */ else break; } *bytes = r; - if (r == 0 && !dc.stop) { + if (r == 0 && !dc_intr()) { if (inputStreamAtEOF(data->inStream)) return flac_read_status_eof; else @@ -247,7 +247,7 @@ static FLAC__StreamDecoderWriteStatus flacWrite(const flac_decoder *dec, FLAC__uint32 samples = frame->header.blocksize; unsigned int c_samp; const unsigned int num_channels = frame->header.channels; - const unsigned int bytes_per_sample = (dc.audioFormat.bits / 8); + const unsigned int bytes_per_sample = (dc.audio_format.bits / 8); const unsigned int bytes_per_channel = bytes_per_sample * frame->header.channels; const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel; @@ -255,7 +255,7 @@ static FLAC__StreamDecoderWriteStatus flacWrite(const flac_decoder *dec, float timeChange; FLAC__uint64 newPosition = 0; - assert(dc.audioFormat.bits > 0); + assert(dc.audio_format.bits > 0); timeChange = ((float)samples) / frame->header.sample_rate; data->time += timeChange; @@ -286,14 +286,12 @@ static FLAC__StreamDecoderWriteStatus flacWrite(const flac_decoder *dec, c_samp, c_samp + num_samples); data->chunk_length = num_samples * bytes_per_channel; - if (flacSendChunk(data) < 0) { - return - FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - data->chunk_length = 0; - if (dc.seek) { - return - FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + switch (flacSendChunk(data)) { + case DC_ACTION_STOP: + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + case DC_ACTION_SEEK: + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + default: break; /* compilers are complainers */ } } @@ -419,36 +417,35 @@ static int flac_decode_internal(InputStream * inStream, int is_ogg) } } - dc.state = DECODE_STATE_DECODE; - while (1) { if (!flac_process_single(flacDec)) break; if (flac_get_state(flacDec) == flac_decoder_eof) break; - if (dc.seek) { - FLAC__uint64 sampleToSeek = dc.seekWhere * - dc.audioFormat.sampleRate + 0.5; + if (dc_seek()) { + FLAC__uint64 sampleToSeek; + dc_action_begin(); + assert(dc.action == DC_ACTION_SEEK); + sampleToSeek = dc.seek_where * + dc.audio_format.sampleRate + 0.5; if (flac_seek_absolute(flacDec, sampleToSeek)) { - ob_clear(); data.time = ((float)sampleToSeek) / - dc.audioFormat.sampleRate; + dc.audio_format.sampleRate; data.position = 0; - } else - dc.seekError = 1; - dc.seek = 0; - decoder_wakeup_player(); + data.chunk_length = 0; + } else { + dc.seek_where = DC_SEEK_ERROR; + } + dc_action_end(); } } - if (!dc.stop) { + if (!dc_intr()) { flacPrintErroredState(flac_get_state(flacDec)); flac_finish(flacDec); } /* send last little bit */ - if (data.chunk_length > 0 && !dc.stop) { + if (data.chunk_length > 0 && !dc_intr()) flacSendChunk(&data); - ob_flush(); - } fail: if (data.replayGainInfo) diff --git a/src/inputPlugins/mod_plugin.c b/src/inputPlugins/mod_plugin.c index 23b16fa23..cfa7d6e3d 100644 --- a/src/inputPlugins/mod_plugin.c +++ b/src/inputPlugins/mod_plugin.c @@ -179,25 +179,20 @@ static int mod_decode(char *path) return -1; } - dc.totalTime = 0; - dc.audioFormat.bits = 16; - dc.audioFormat.sampleRate = 44100; - dc.audioFormat.channels = 2; - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); + dc.total_time = 0; + dc.audio_format.bits = 16; + dc.audio_format.sampleRate = 44100; + dc.audio_format.channels = 2; secPerByte = - 1.0 / ((dc.audioFormat.bits * dc.audioFormat.channels / 8.0) * - (float)dc.audioFormat.sampleRate); + 1.0 / ((dc.audio_format.bits * dc.audio_format.channels / 8.0) * + (float)dc.audio_format.sampleRate); - dc.state = DECODE_STATE_DECODE; while (1) { - if (dc.seek) { - dc.seekError = 1; - dc.seek = 0; - decoder_wakeup_player(); - } + if (dc_seek()) + dc_action_seek_fail(DC_SEEK_ERROR); - if (dc.stop) + if (dc_intr()) break; if (!Player_Active()) @@ -205,13 +200,8 @@ static int mod_decode(char *path) ret = VC_WriteBytes(data->audio_buffer, MIKMOD_FRAME_SIZE); total_time += ret * secPerByte; - ob_send(NULL, 0, - (char *)data->audio_buffer, ret, - total_time, 0, NULL); + ob_send((char *)data->audio_buffer, ret, total_time, 0, NULL); } - - ob_flush(); - mod_close(data); MikMod_Exit(); diff --git a/src/inputPlugins/mp3_plugin.c b/src/inputPlugins/mp3_plugin.c index dcfc25cdc..10a914a91 100644 --- a/src/inputPlugins/mp3_plugin.c +++ b/src/inputPlugins/mp3_plugin.c @@ -314,10 +314,8 @@ static void mp3_parseId3Tag(mp3DecodeData * data, size_t tagsize, len = readFromInputStream(data->inStream, allocated + count, (size_t) 1, tagsize - count); - if (len <= 0 && inputStreamAtEOF(data->inStream)) { + if (len <= 0 && inputStreamAtEOF(data->inStream)) break; - } else if (len <= 0) - my_usleep(10000); else count += len; } @@ -689,13 +687,13 @@ static int decodeFirstFrame(mp3DecodeData * data, while (1) { while ((ret = decodeNextFrameHeader(data, tag, replayGainInfo)) == DECODE_CONT && - !dc.stop); - if (ret == DECODE_BREAK || dc.stop) return -1; + !dc_intr()); + if (ret == DECODE_BREAK || dc_intr()) return -1; if (ret == DECODE_SKIP) continue; while ((ret = decodeNextFrame(data)) == DECODE_CONT && - !dc.stop); - if (ret == DECODE_BREAK || dc.stop) return -1; + !dc_intr()); + if (ret == DECODE_BREAK || dc_intr()) return -1; if (ret == DECODE_OK) break; } @@ -813,6 +811,35 @@ static int openMp3FromInputStream(InputStream * inStream, mp3DecodeData * data, return 0; } +static float frame_time(mp3DecodeData * data, long j) +{ + return (((float)mad_timer_count(data->times[j], + MAD_UNITS_MILLISECONDS)) / 1000); +} + +static void mp3Read_seek(mp3DecodeData * data) +{ + long j = 0; + data->muteFrame = MUTEFRAME_SEEK; + + assert(pthread_equal(pthread_self(), dc.thread)); + assert(dc.action == DC_ACTION_SEEK); + + while (j < data->highestFrame && dc.seek_where > frame_time(data, j)) + j++; + if (j < data->highestFrame) { + dc_action_begin(); + if (seekMp3InputBuffer(data, data->frameOffset[j]) < 0) { + dc.seek_where = DC_SEEK_ERROR; + } else { + data->outputPtr = data->outputBuffer; + data->currentFrame = j; + } + data->muteFrame = 0; + dc_action_end(); + } +} + static int mp3Read(mp3DecodeData * data, ReplayGainInfo ** replayGainInfo) { int samplesPerFrame; @@ -821,6 +848,8 @@ static int mp3Read(mp3DecodeData * data, ReplayGainInfo ** replayGainInfo) int ret; int skip; + assert(pthread_equal(pthread_self(), dc.thread)); + if (data->currentFrame >= data->highestFrame) { mad_timer_add(&data->timer, (data->frame).header.duration); data->bitRate = (data->frame).header.bitrate; @@ -851,13 +880,13 @@ static int mp3Read(mp3DecodeData * data, ReplayGainInfo ** replayGainInfo) data->muteFrame = 0; break; case MUTEFRAME_SEEK: - if (dc.seekWhere <= data->elapsedTime) { + dc_action_begin(); + assert(dc.action == DC_ACTION_SEEK); + if (dc.seek_where <= data->elapsedTime) { data->outputPtr = data->outputBuffer; - ob_clear(); data->muteFrame = 0; - dc.seek = 0; - decoder_wakeup_player(); } + dc_action_end(); break; default: mad_synth_frame(&data->synth, &data->frame); @@ -928,53 +957,33 @@ static int mp3Read(mp3DecodeData * data, ReplayGainInfo ** replayGainInfo) } if (data->outputPtr >= data->outputBufferEnd) { - ret = ob_send(data->inStream, - data->inStream->seekable, - data->outputBuffer, - data->outputPtr - data->outputBuffer, - data->elapsedTime, - data->bitRate / 1000, - (replayGainInfo != NULL) ? *replayGainInfo : NULL); - if (ret == OUTPUT_BUFFER_DC_STOP) { + enum dc_action action = ob_send( + data->outputBuffer, + data->outputPtr - + data->outputBuffer, + data->elapsedTime, + data->bitRate / 1000, + replayGainInfo ? *replayGainInfo + : NULL); + + if (action == DC_ACTION_STOP) { data->flush = 0; return DECODE_BREAK; } - data->outputPtr = data->outputBuffer; - if (ret == OUTPUT_BUFFER_DC_SEEK) + if (action == DC_ACTION_SEEK) break; } } data->decodedFirstFrame = 1; - if (dc.seek && data->inStream->seekable) { - long j = 0; - data->muteFrame = MUTEFRAME_SEEK; - while (j < data->highestFrame && dc.seekWhere > - ((float)mad_timer_count(data->times[j], - MAD_UNITS_MILLISECONDS)) - / 1000) { - j++; - } - if (j < data->highestFrame) { - if (seekMp3InputBuffer(data, - data->frameOffset[j]) == - 0) { - data->outputPtr = data->outputBuffer; - ob_clear(); - data->currentFrame = j; - } else - dc.seekError = 1; - data->muteFrame = 0; - dc.seek = 0; - decoder_wakeup_player(); - } - } else if (dc.seek && !data->inStream->seekable) { - dc.seek = 0; - dc.seekError = 1; - decoder_wakeup_player(); + if (dc_seek()) { + if (data->inStream->seekable) + mp3Read_seek(data); + else + dc_action_seek_fail(DC_SEEK_ERROR); } } @@ -983,22 +992,22 @@ static int mp3Read(mp3DecodeData * data, ReplayGainInfo ** replayGainInfo) while ((ret = decodeNextFrameHeader(data, NULL, replayGainInfo)) == DECODE_CONT - && !dc.stop) ; - if (ret == DECODE_BREAK || dc.stop || dc.seek) + && dc_intr()) ; + if (ret == DECODE_BREAK || dc_intr() || dc_seek()) break; else if (ret == DECODE_SKIP) skip = 1; if (!data->muteFrame) { while ((ret = decodeNextFrame(data)) == DECODE_CONT && - !dc.stop && !dc.seek) ; - if (ret == DECODE_BREAK || dc.stop || dc.seek) + !dc_intr() && dc_seek()) ; + if (ret == DECODE_BREAK || dc_intr() || dc_seek()) break; } if (!skip && ret == DECODE_OK) break; } - if (dc.stop) + if (dc_intr()) return DECODE_BREAK; return ret; @@ -1018,9 +1027,8 @@ static int mp3_decode(InputStream * inStream) MpdTag *tag = NULL; ReplayGainInfo *replayGainInfo = NULL; - if (openMp3FromInputStream(inStream, &data, &tag, &replayGainInfo) < - 0) { - if (!dc.stop) { + if (openMp3FromInputStream(inStream, &data, &tag, &replayGainInfo) < 0) { + if (!dc_intr()) { ERROR ("Input does not appear to be a mp3 bit stream.\n"); return -1; @@ -1028,10 +1036,9 @@ static int mp3_decode(InputStream * inStream) return 0; } - initAudioFormatFromMp3DecodeData(&data, &(dc.audioFormat)); - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); + initAudioFormatFromMp3DecodeData(&data, &(dc.audio_format)); - dc.totalTime = data.totalTime; + dc.total_time = data.totalTime; if (inStream->metaTitle) { if (tag) @@ -1058,31 +1065,16 @@ static int mp3_decode(InputStream * inStream) freeMpdTag(tag); } - dc.state = DECODE_STATE_DECODE; - while (mp3Read(&data, &replayGainInfo) != DECODE_BREAK) ; - /* send last little bit if not dc.stop */ - if (!dc.stop && data.outputPtr != data.outputBuffer && data.flush) { - ob_send(NULL, - data.inStream->seekable, - data.outputBuffer, - data.outputPtr - data.outputBuffer, - data.elapsedTime, data.bitRate / 1000, - replayGainInfo); + /* send last little bit if not dc_intr() */ + if (!dc_intr() && data.outputPtr != data.outputBuffer && data.flush) { + ob_send(data.outputBuffer, data.outputPtr - data.outputBuffer, + data.elapsedTime, data.bitRate / 1000, replayGainInfo); } if (replayGainInfo) freeReplayGainInfo(replayGainInfo); - - if (dc.seek && data.muteFrame == MUTEFRAME_SEEK) { - ob_clear(); - dc.seek = 0; - decoder_wakeup_player(); - } - - ob_flush(); mp3DecodeDataFinalize(&data); - return 0; } diff --git a/src/inputPlugins/mp4_plugin.c b/src/inputPlugins/mp4_plugin.c index 7f13ca344..bf200c534 100644 --- a/src/inputPlugins/mp4_plugin.c +++ b/src/inputPlugins/mp4_plugin.c @@ -145,7 +145,7 @@ static int mp4_decode(InputStream * inStream) #endif faacDecSetConfiguration(decoder, config); - dc.audioFormat.bits = 16; + dc.audio_format.bits = 16; mp4Buffer = NULL; mp4BufferSize = 0; @@ -160,8 +160,8 @@ static int mp4_decode(InputStream * inStream) return -1; } - dc.audioFormat.sampleRate = sampleRate; - dc.audioFormat.channels = channels; + dc.audio_format.sampleRate = sampleRate; + dc.audio_format.channels = channels; file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); scale = mp4ff_time_scale(mp4fh, track); @@ -175,7 +175,7 @@ static int mp4_decode(InputStream * inStream) free(mp4cb); return -1; } - dc.totalTime = ((float)file_time) / scale; + dc.total_time = ((float)file_time) / scale; numSamples = mp4ff_num_samples(mp4fh, track); @@ -184,13 +184,16 @@ static int mp4_decode(InputStream * inStream) seekTable = xmalloc(sizeof(float) * numSamples); for (sampleId = 0; sampleId < numSamples && !eof; sampleId++) { - if (dc.seek) + if (!seeking && dc_seek()) { + dc_action_begin(); + assert(dc.action == DC_ACTION_SEEK); seeking = 1; + } if (seeking && seekTableEnd > 1 && - seekTable[seekTableEnd] >= dc.seekWhere) { + seekTable[seekTableEnd] >= dc.seek_where) { int i = 2; - while (seekTable[i] < dc.seekWhere) + while (seekTable[i] < dc.seek_where) i++; sampleId = i - 1; file_time = seekTable[sampleId]; @@ -212,15 +215,14 @@ static int mp4_decode(InputStream * inStream) dur -= offset; file_time += ((float)dur) / scale; - if (seeking && file_time > dc.seekWhere) + if (seeking && file_time > dc.seek_where) seekPositionFound = 1; if (seeking && seekPositionFound) { seekPositionFound = 0; - ob_clear(); seeking = 0; - dc.seek = 0; - decoder_wakeup_player(); + assert(dc.action == DC_ACTION_SEEK); + dc_action_end(); } if (seeking) @@ -247,17 +249,12 @@ static int mp4_decode(InputStream * inStream) break; } - if (dc.state != DECODE_STATE_DECODE) { - channels = frameInfo.channels; + channels = frameInfo.channels; #ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE - scale = frameInfo.samplerate; + scale = frameInfo.samplerate; #endif - dc.audioFormat.sampleRate = scale; - dc.audioFormat.channels = frameInfo.channels; - getOutputAudioFormat(&(dc.audioFormat), - &(ob.audioFormat)); - dc.state = DECODE_STATE_DECODE; - } + dc.audio_format.sampleRate = scale; + dc.audio_format.channels = frameInfo.channels; if (channels * (unsigned long)(dur + offset) > frameInfo.samples) { dur = frameInfo.samples / channels; @@ -277,10 +274,9 @@ static int mp4_decode(InputStream * inStream) sampleBuffer += offset * channels * 2; - ob_send(inStream, 1, sampleBuffer, - sampleBufferLen, file_time, - bitRate, NULL); - if (dc.stop) { + ob_send(sampleBuffer, sampleBufferLen, file_time, + bitRate, NULL); + if (dc_intr()) { eof = 1; break; } @@ -291,15 +287,10 @@ static int mp4_decode(InputStream * inStream) mp4ff_close(mp4fh); free(mp4cb); - if (dc.state != DECODE_STATE_DECODE) - return -1; - - if (dc.seek && seeking) { - ob_clear(); - dc.seek = 0; - decoder_wakeup_player(); + if (seeking) { + dc.seek_where = DC_SEEK_ERROR; + dc_action_end(); } - ob_flush(); return 0; } diff --git a/src/inputPlugins/mpc_plugin.c b/src/inputPlugins/mpc_plugin.c index 1003f15d5..116e471ff 100644 --- a/src/inputPlugins/mpc_plugin.c +++ b/src/inputPlugins/mpc_plugin.c @@ -42,8 +42,8 @@ static mpc_int32_t mpc_read_cb(void *vdata, void *ptr, mpc_int32_t size) while (1) { ret = readFromInputStream(data->inStream, ptr, 1, size); - if (ret == 0 && !inputStreamAtEOF(data->inStream) && !dc.stop) - my_usleep(10000); + if (ret == 0 && !inputStreamAtEOF(data->inStream) && !dc_intr()) + my_usleep(10000); /* FIXME */ else break; } @@ -147,7 +147,7 @@ static int mpc_decode(InputStream * inStream) mpc_streaminfo_init(&info); if ((ret = mpc_streaminfo_read(&info, &reader)) != ERROR_CODE_OK) { - if (!dc.stop) { + if (!dc_intr()) { ERROR("Not a valid musepack stream\n"); return -1; } @@ -157,20 +157,18 @@ static int mpc_decode(InputStream * inStream) mpc_decoder_setup(&decoder, &reader); if (!mpc_decoder_initialize(&decoder, &info)) { - if (!dc.stop) { + if (!dc_intr()) { ERROR("Not a valid musepack stream\n"); return -1; } return 0; } - dc.totalTime = mpc_streaminfo_get_length(&info); + dc.total_time = mpc_streaminfo_get_length(&info); - dc.audioFormat.bits = 16; - dc.audioFormat.channels = info.channels; - dc.audioFormat.sampleRate = info.sample_freq; - - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); + dc.audio_format.bits = 16; + dc.audio_format.channels = info.channels; + dc.audio_format.sampleRate = info.sample_freq; replayGainInfo = newReplayGainInfo(); replayGainInfo->albumGain = info.gain_album * 0.01; @@ -178,19 +176,16 @@ static int mpc_decode(InputStream * inStream) replayGainInfo->trackGain = info.gain_title * 0.01; replayGainInfo->trackPeak = info.peak_title / 32767.0; - dc.state = DECODE_STATE_DECODE; - while (!eof) { - if (dc.seek) { - samplePos = dc.seekWhere * dc.audioFormat.sampleRate; + if (dc_seek()) { + dc_action_begin(); + samplePos = dc.seek_where * dc.audio_format.sampleRate; if (mpc_decoder_seek_sample(&decoder, samplePos)) { - ob_clear(); s16 = (mpd_sint16 *) chunk; chunkpos = 0; } else - dc.seekError = 1; - dc.seek = 0; - decoder_wakeup_player(); + dc.seek_where = DC_SEEK_ERROR; + dc_action_end(); } vbrUpdateAcc = 0; @@ -198,7 +193,7 @@ static int mpc_decode(InputStream * inStream) ret = mpc_decoder_decode(&decoder, sample_buffer, &vbrUpdateAcc, &vbrUpdateBits); - if (ret <= 0 || dc.stop) { + if (ret <= 0 || dc_intr()) { eof = 1; break; } @@ -216,20 +211,17 @@ static int mpc_decode(InputStream * inStream) if (chunkpos >= MPC_CHUNK_SIZE) { total_time = ((float)samplePos) / - dc.audioFormat.sampleRate; + dc.audio_format.sampleRate; bitRate = vbrUpdateBits * - dc.audioFormat.sampleRate / 1152 / 1000; + dc.audio_format.sampleRate / 1152 / 1000; - ob_send(inStream, - inStream->seekable, - chunk, chunkpos, - total_time, - bitRate, replayGainInfo); + ob_send(chunk, chunkpos, total_time, + bitRate, replayGainInfo); chunkpos = 0; s16 = (mpd_sint16 *) chunk; - if (dc.stop) { + if (dc_intr()) { eof = 1; break; } @@ -237,19 +229,14 @@ static int mpc_decode(InputStream * inStream) } } - if (!dc.stop && chunkpos > 0) { - total_time = ((float)samplePos) / dc.audioFormat.sampleRate; + if (!dc_intr() && chunkpos > 0) { + total_time = ((float)samplePos) / dc.audio_format.sampleRate; bitRate = - vbrUpdateBits * dc.audioFormat.sampleRate / 1152 / 1000; + vbrUpdateBits * dc.audio_format.sampleRate / 1152 / 1000; - ob_send(NULL, inStream->seekable, - chunk, chunkpos, total_time, bitRate, - replayGainInfo); + ob_send(chunk, chunkpos, total_time, bitRate, replayGainInfo); } - - ob_flush(); - freeReplayGainInfo(replayGainInfo); return 0; diff --git a/src/inputPlugins/oggflac_plugin.c b/src/inputPlugins/oggflac_plugin.c index 379ff6466..6c5998afe 100644 --- a/src/inputPlugins/oggflac_plugin.c +++ b/src/inputPlugins/oggflac_plugin.c @@ -55,15 +55,14 @@ static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(const while (1) { r = readFromInputStream(data->inStream, (void *)buf, 1, *bytes); - if (r == 0 && !inputStreamAtEOF(data->inStream) && - !dc.stop) - my_usleep(10000); + if (r == 0 && !inputStreamAtEOF(data->inStream) && !dc_intr()) + my_usleep(10000); /* FIXME */ else break; } *bytes = r; - if (r == 0 && !inputStreamAtEOF(data->inStream) && !dc.stop) + if (r == 0 && !inputStreamAtEOF(data->inStream) && !dc_intr()) return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; @@ -193,16 +192,17 @@ static FLAC__StreamDecoderWriteStatus oggflacWrite(const c_chan++) { u16 = buf[c_chan][c_samp]; uc = (unsigned char *)&u16; - for (i = 0; i < (dc.audioFormat.bits / 8); i++) { + for (i = 0; i < (dc.audio_format.bits / 8); i++) { if (data->chunk_length >= FLAC_CHUNK_SIZE) { - if (flacSendChunk(data) < 0) { - return - FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - data->chunk_length = 0; - if (dc.seek) { - return - FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + /* FIXME: line wrapping */ + switch (flacSendChunk(data)) { + case DC_ACTION_STOP: + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + case DC_ACTION_SEEK: + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + default: + /* compilers are complainers */ + break; } } data->chunk[data->chunk_length++] = *(uc++); @@ -349,40 +349,39 @@ static int oggflac_decode(InputStream * inStream) goto fail; } - dc.state = DECODE_STATE_DECODE; - while (1) { OggFLAC__seekable_stream_decoder_process_single(decoder); if (OggFLAC__seekable_stream_decoder_get_state(decoder) != OggFLAC__SEEKABLE_STREAM_DECODER_OK) { break; } - if (dc.seek) { - FLAC__uint64 sampleToSeek = dc.seekWhere * - dc.audioFormat.sampleRate + 0.5; + if (dc_seek()) { + FLAC__uint64 sampleToSeek; + dc_action_begin(); + assert(dc.action == DC_ACTION_SEEK); + sampleToSeek = dc.seek_where * + dc.audio_format.sampleRate + 0.5; if (OggFLAC__seekable_stream_decoder_seek_absolute (decoder, sampleToSeek)) { - ob_clear(); data.time = ((float)sampleToSeek) / - dc.audioFormat.sampleRate; + dc.audio_format.sampleRate; data.position = 0; - } else - dc.seekError = 1; - dc.seek = 0; - decoder_wakeup_player(); + data.chunk_length = 0; + } else { + dc.seek_where = DC_SEEK_ERROR; + } + dc_action_end(); } } - if (!dc.stop) { + if (!dc_intr()) { oggflacPrintErroredState (OggFLAC__seekable_stream_decoder_get_state(decoder)); OggFLAC__seekable_stream_decoder_finish(decoder); } /* send last little bit */ - if (data.chunk_length > 0 && !dc.stop) { + if (data.chunk_length > 0 && !dc_intr()) flacSendChunk(&data); - ob_flush(); - } fail: oggflac_cleanup(&data, decoder); diff --git a/src/inputPlugins/oggvorbis_plugin.c b/src/inputPlugins/oggvorbis_plugin.c index 16040b388..fcedda54a 100644 --- a/src/inputPlugins/oggvorbis_plugin.c +++ b/src/inputPlugins/oggvorbis_plugin.c @@ -66,8 +66,8 @@ static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) while (1) { ret = readFromInputStream(data->inStream, ptr, size, nmemb); if (ret == 0 && !inputStreamAtEOF(data->inStream) && - !dc.stop) { - my_usleep(10000); + !dc_intr()) { + my_usleep(10000); /* FIXME */ } else break; } @@ -80,7 +80,7 @@ static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) { const OggCallbackData *data = (const OggCallbackData *) vdata; - if (dc.stop) + if (dc_intr()) return -1; return seekInputStream(data->inStream, offset, whence); } @@ -240,7 +240,7 @@ static int oggvorbis_decode(InputStream * inStream) callbacks.close_func = ogg_close_cb; callbacks.tell_func = ogg_tell_cb; if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) { - if (!dc.stop) { + if (!dc_intr()) { switch (ret) { case OV_EREAD: errorStr = "read error"; @@ -267,20 +267,19 @@ static int oggvorbis_decode(InputStream * inStream) } return 0; } - dc.totalTime = ov_time_total(&vf, -1); - if (dc.totalTime < 0) - dc.totalTime = 0; - dc.audioFormat.bits = 16; + dc.total_time = ov_time_total(&vf, -1); + if (dc.total_time < 0) + dc.total_time = 0; + dc.audio_format.bits = 16; while (1) { - if (dc.seek) { - if (0 == ov_time_seek_page(&vf, dc.seekWhere)) { - ob_clear(); + if (dc_seek()) { + dc_action_begin(); + if (0 == ov_time_seek_page(&vf, dc.seek_where)) chunkpos = 0; - } else - dc.seekError = 1; - dc.seek = 0; - decoder_wakeup_player(); + else + dc.seek_where = DC_SEEK_ERROR; + dc_action_end(); } ret = ov_read(&vf, chunk + chunkpos, OGG_CHUNK_SIZE - chunkpos, @@ -288,13 +287,8 @@ static int oggvorbis_decode(InputStream * inStream) if (current_section != prev_section) { /*printf("new song!\n"); */ vorbis_info *vi = ov_info(&vf, -1); - dc.audioFormat.channels = vi->channels; - dc.audioFormat.sampleRate = vi->rate; - if (dc.state == DECODE_STATE_START) { - getOutputAudioFormat(&(dc.audioFormat), - &(ob.audioFormat)); - dc.state = DECODE_STATE_DECODE; - } + dc.audio_format.channels = vi->channels; + dc.audio_format.sampleRate = vi->rate; comments = ov_comment(&vf, -1)->user_comments; putOggCommentsIntoOutputBuffer(inStream->metaName, comments); @@ -316,23 +310,18 @@ static int oggvorbis_decode(InputStream * inStream) if ((test = ov_bitrate_instant(&vf)) > 0) { bitRate = test / 1000; } - ob_send(inStream, - inStream->seekable, - chunk, chunkpos, - ov_pcm_tell(&vf) / - dc.audioFormat.sampleRate, - bitRate, replayGainInfo); + ob_send(chunk, chunkpos, + ov_pcm_tell(&vf) / dc.audio_format.sampleRate, + bitRate, replayGainInfo); chunkpos = 0; - if (dc.stop) + if (dc_intr()) break; } } - if (!dc.stop && chunkpos > 0) { - ob_send(NULL, inStream->seekable, - chunk, chunkpos, - ov_time_tell(&vf), bitRate, - replayGainInfo); + if (!dc_intr() && chunkpos > 0) { + ob_send(chunk, chunkpos, ov_time_tell(&vf), bitRate, + replayGainInfo); } if (replayGainInfo) @@ -340,8 +329,6 @@ static int oggvorbis_decode(InputStream * inStream) ov_clear(&vf); - ob_flush(); - return 0; } diff --git a/src/inputPlugins/wavpack_plugin.c b/src/inputPlugins/wavpack_plugin.c index 7a7145141..ef02712d7 100644 --- a/src/inputPlugins/wavpack_plugin.c +++ b/src/inputPlugins/wavpack_plugin.c @@ -139,12 +139,12 @@ static void wavpack_decode(WavpackContext *wpc, int canseek, int position, outsamplesize; int Bps; - dc.audioFormat.sampleRate = WavpackGetSampleRate(wpc); - dc.audioFormat.channels = WavpackGetReducedChannels(wpc); - dc.audioFormat.bits = WavpackGetBitsPerSample(wpc); + dc.audio_format.sampleRate = WavpackGetSampleRate(wpc); + dc.audio_format.channels = WavpackGetReducedChannels(wpc); + dc.audio_format.bits = WavpackGetBitsPerSample(wpc); - if (dc.audioFormat.bits > 16) - dc.audioFormat.bits = 16; + if (dc.audio_format.bits > 16) + dc.audio_format.bits = 16; if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT) format_samples = format_samples_float; @@ -162,40 +162,33 @@ static void wavpack_decode(WavpackContext *wpc, int canseek, outsamplesize = Bps; if (outsamplesize > 2) outsamplesize = 2; - outsamplesize *= dc.audioFormat.channels; + outsamplesize *= dc.audio_format.channels; - samplesreq = sizeof(chunk) / (4 * dc.audioFormat.channels); + samplesreq = sizeof(chunk) / (4 * dc.audio_format.channels); - getOutputAudioFormat(&(dc.audioFormat), &(ob.audioFormat)); - - dc.totalTime = (float)allsamples / dc.audioFormat.sampleRate; - dc.state = DECODE_STATE_DECODE; - dc.seekable = canseek; + dc.total_time = (float)allsamples / dc.audio_format.sampleRate; position = 0; do { - if (dc.seek) { + if (dc_seek()) { + dc_action_begin(); + assert(dc.action == DC_ACTION_SEEK); if (canseek) { int where; - - ob_clear(); - - where = dc.seekWhere * - dc.audioFormat.sampleRate; + where = dc.seek_where * + dc.audio_format.sampleRate; if (WavpackSeekSample(wpc, where)) position = where; else - dc.seekError = 1; + dc.seek_where = DC_SEEK_ERROR; } else { - dc.seekError = 1; + dc.seek_where = DC_SEEK_ERROR; } - - dc.seek = 0; - decoder_wakeup_player(); + dc_action_end(); } - if (dc.stop) + if (dc_intr()) break; samplesgot = WavpackUnpackSamples(wpc, @@ -205,19 +198,15 @@ static void wavpack_decode(WavpackContext *wpc, int canseek, 1000 + 0.5); position += samplesgot; file_time = (float)position / - dc.audioFormat.sampleRate; + dc.audio_format.sampleRate; format_samples(Bps, chunk, - samplesgot * dc.audioFormat.channels); + samplesgot * dc.audio_format.channels); - ob_send(NULL, 0, chunk, - samplesgot * outsamplesize, - file_time, bitrate, - replayGainInfo); + ob_send(chunk, samplesgot * outsamplesize, + file_time, bitrate, replayGainInfo); } } while (samplesgot == samplesreq); - - ob_flush(); } static char *wavpack_tag(WavpackContext *wpc, char *key) @@ -398,6 +387,7 @@ static int can_seek(void *id) return ((InputStreamPlus *)id)->is->seekable; } +/* FIXME: remove C99 initializers */ static WavpackStreamReader mpd_is_reader = { .read_bytes = read_bytes, .get_pos = get_pos, @@ -453,38 +443,27 @@ static int wavpack_streamdecode(InputStream *is) int canseek; /* Try to find wvc */ + /* wvc being the "correction" file to supplement the original .wv */ do { char tmp[MPD_PATH_MAX]; const char *utf8url; size_t len; err = 1; - /* - * As we use dc.utf8url, this function will be bad for - * single files. utf8url is not absolute file path :/ - */ - utf8url = get_song_url(tmp, dc.current_song); - if (utf8url == NULL) { - break; - } - - len = strlen(utf8url); - if (!len) { + /* This is the only reader of dc.current_song */ + if (!(utf8url = get_song_url(tmp, dc.current_song))) break; - } - wvc_url = (char *)xmalloc(len + 2); /* +2: 'c' and EOS */ - if (wvc_url == NULL) { + if (!(len = strlen(utf8url))) break; - } + wvc_url = (char *)xmalloc(len + sizeof("c")); memcpy(wvc_url, utf8url, len); wvc_url[len] = 'c'; wvc_url[len + 1] = '\0'; - if (openInputStream(&is_wvc, wvc_url)) { + if (openInputStream(&is_wvc, wvc_url)) break; - } /* * And we try to buffer in order to get know @@ -500,17 +479,17 @@ static int wavpack_streamdecode(InputStream *is) break; } + /* FIXME: replace with future "peek" function */ if (bufferInputStream(&is_wvc) >= 0) { err = 0; break; } - if (dc.stop) { + if (dc_intr()) break; - } /* Save some CPU */ - my_usleep(1000); + my_usleep(1000); /* FIXME: remove */ } if (err) { closeInputStream(&is_wvc); diff --git a/src/inputStream_http.c b/src/inputStream_http.c index 48972ac8b..a6c715459 100644 --- a/src/inputStream_http.c +++ b/src/inputStream_http.c @@ -252,7 +252,7 @@ static int trigger_action(struct http_data *data, goto out; } if (nonblocking) - cond_timedwait(&data->action_cond, 1); + cond_timedwait(&data->action_cond, 100); else cond_wait(&data->action_cond); ret = 0; @@ -279,7 +279,7 @@ static int take_action(struct http_data *data) xclose(data->fd); data->fd = -1; data->action = CONN_ACTION_NONE; - cond_signal_sync(&data->action_cond); + cond_signal(&data->action_cond); cond_leave(&data->action_cond); return 1; } @@ -436,7 +436,7 @@ static void await_buffer_space(struct http_data *data) static void feed_starved(struct http_data *data) { assert(pthread_equal(data->io_thread, pthread_self())); - cond_signal_async(&data->empty_cond); + cond_signal(&data->empty_cond); } static int starved_wait(struct http_data *data, const long sec) @@ -449,7 +449,7 @@ static int awaken_buffer_task(struct http_data *data) { assert(!pthread_equal(data->io_thread, pthread_self())); - return ! cond_signal_async(&data->full_cond); + return ! cond_signal_trysync(&data->full_cond); } static ssize_t buffer_data(InputStream *is) diff --git a/src/interface.c b/src/interface.c index db5959f3a..45b81a2f7 100644 --- a/src/interface.c +++ b/src/interface.c @@ -495,10 +495,7 @@ int doIOForInterfaces(void) registered_IO_add_fds(&fdmax, &rfds, &wfds, &efds); - main_notify_lock(); selret = select(fdmax + 1, &rfds, &wfds, &efds, NULL); - main_notify_unlock(); - if (selret < 0 && errno == EINTR) break; diff --git a/src/log.c b/src/log.c index 8a2d48410..aac7349d1 100644 --- a/src/log.c +++ b/src/log.c @@ -75,6 +75,7 @@ static void do_log(FILE *fp, const char *fmt, va_list args) { if (!stdout_mode) fwrite(log_date(), LOG_DATE_LEN, 1, fp); + fprintf(fp, "%08x: ", pthread_self()); vfprintf(fp, fmt, args); } diff --git a/src/main.c b/src/main.c index a838d0349..e02085294 100644 --- a/src/main.c +++ b/src/main.c @@ -20,7 +20,6 @@ #include "command.h" #include "playlist.h" #include "directory.h" -#include "player.h" #include "listen.h" #include "conf.h" #include "path.h" @@ -45,6 +44,7 @@ #include "zeroconf.h" #include "main_notify.h" #include "os_compat.h" +#include "outputBuffer.h" #define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf" #define USER_CONFIG_FILE_LOCATION "/.mpdconf" @@ -431,8 +431,7 @@ int main(int argc, char *argv[]) initZeroconf(); openVolumeDevice(); - decoderInit(); - playerInit(); + decoder_init(); read_state_file(); while (COMMAND_RETURN_KILL != doIOForInterfaces() && @@ -443,7 +442,7 @@ int main(int argc, char *argv[]) } write_state_file(); - playerKill(); + ob_trigger_action(OB_ACTION_PAUSE_SET); finishZeroconf(); freeAllInterfaces(); closeAllListenSockets(); diff --git a/src/main_notify.c b/src/main_notify.c index 978a401ad..2c546633d 100644 --- a/src/main_notify.c +++ b/src/main_notify.c @@ -19,7 +19,6 @@ */ #include "main_notify.h" -#include "notify.h" #include "utils.h" #include "ioops.h" #include "gcc.h" @@ -27,11 +26,6 @@ static struct ioOps main_notify_IO; static int main_pipe[2]; -static pthread_t main_task; -static pthread_cond_t main_wakeup = PTHREAD_COND_INITIALIZER; -static pthread_mutex_t main_wakeup_mutex = PTHREAD_MUTEX_INITIALIZER; -static volatile int pending; -static pthread_mutex_t select_mutex = PTHREAD_MUTEX_INITIALIZER; static int ioops_fdset(fd_set * rfds, mpd_unused fd_set * wfds, mpd_unused fd_set * efds) @@ -40,20 +34,19 @@ static int ioops_fdset(fd_set * rfds, return main_pipe[0]; } -static void consume_pipe(void) -{ - char buffer[2]; - ssize_t r = read(main_pipe[0], buffer, sizeof(buffer)); - - if (r < 0 && errno != EAGAIN && errno != EINTR) - FATAL("error reading from pipe: %s\n", strerror(errno)); -} - static int ioops_consume(int fd_count, fd_set * rfds, mpd_unused fd_set * wfds, mpd_unused fd_set * efds) { + char buffer[4096]; + ssize_t r; + if (FD_ISSET(main_pipe[0], rfds)) { - consume_pipe(); + do { + r = read(main_pipe[0], buffer, sizeof(buffer)); + } while (r > 0); + + if (r < 0 && errno != EAGAIN && errno != EINTR) + FATAL("error reading from pipe: %s\n", strerror(errno)); FD_CLR(main_pipe[0], rfds); fd_count--; } @@ -62,65 +55,17 @@ static int ioops_consume(int fd_count, fd_set * rfds, void init_main_notify(void) { - if (pipe(main_pipe) < 0) - FATAL("Couldn't open pipe: %s", strerror(errno)); - if (set_nonblocking(main_pipe[0]) < 0) - FATAL("Couldn't set non-blocking on main_notify fd: %s", - strerror(errno)); - if (set_nonblocking(main_pipe[1]) < 0) - FATAL("Couldn't set non-blocking on main_notify fd: %s", - strerror(errno)); + init_async_pipe(main_pipe); main_notify_IO.fdset = ioops_fdset; main_notify_IO.consume = ioops_consume; registerIO(&main_notify_IO); - main_task = pthread_self(); -} - -static int wakeup_via_pipe(void) -{ - int ret = pthread_mutex_trylock(&select_mutex); - if (ret == EBUSY) { - ssize_t w = write(main_pipe[1], "", 1); - if (w < 0 && errno != EAGAIN && errno != EINTR) - FATAL("error writing to pipe: %s\n", - strerror(errno)); - return 1; - } else { - pthread_mutex_unlock(&select_mutex); - return 0; - } } void wakeup_main_task(void) { - assert(!pthread_equal(main_task, pthread_self())); - - pending = 1; + ssize_t w = write(main_pipe[1], "", 1); - if (!wakeup_via_pipe()) - pthread_cond_signal(&main_wakeup); -} - -void main_notify_lock(void) -{ - assert(pthread_equal(main_task, pthread_self())); - pthread_mutex_lock(&select_mutex); -} - -void main_notify_unlock(void) -{ - assert(pthread_equal(main_task, pthread_self())); - pthread_mutex_unlock(&select_mutex); -} - -void wait_main_task(void) -{ - assert(pthread_equal(main_task, pthread_self())); - - pthread_mutex_lock(&main_wakeup_mutex); - if (!pending) - pthread_cond_wait(&main_wakeup, &main_wakeup_mutex); - pending = 0; - pthread_mutex_unlock(&main_wakeup_mutex); + if (w < 0 && errno != EAGAIN && errno != EINTR) + FATAL("error writing to pipe: %s\n", + strerror(errno)); } - diff --git a/src/main_notify.h b/src/main_notify.h index c7bba4440..db36042a7 100644 --- a/src/main_notify.h +++ b/src/main_notify.h @@ -25,10 +25,4 @@ void init_main_notify(void); void wakeup_main_task(void); -void wait_main_task(void); - -void main_notify_lock(void); - -void main_notify_unlock(void); - #endif /* MAIN_NOTIFY_H */ diff --git a/src/myfprintf.c b/src/myfprintf.c index 0732caf02..7e4f4678d 100644 --- a/src/myfprintf.c +++ b/src/myfprintf.c @@ -41,7 +41,7 @@ static void blockingWrite(const int fd, const char *string, size_t len) void vfdprintf(const int fd, const char *fmt, va_list args) { - static char buffer[BUFFER_LENGTH]; + char buffer[BUFFER_LENGTH]; char *buf = buffer; size_t len; diff --git a/src/notify.c b/src/notify.c deleted file mode 100644 index ed46829ae..000000000 --- a/src/notify.c +++ /dev/null @@ -1,68 +0,0 @@ -/* the Music Player Daemon (MPD) - * Copyright (C) 2008 Max Kellermann - * 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 "notify.h" - -int notify_init(Notify *notify) -{ - int ret; - - ret = pthread_mutex_init(¬ify->mutex, NULL); - if (ret != 0) - return ret; - - ret = pthread_cond_init(¬ify->cond, NULL); - if (ret != 0) { - pthread_mutex_destroy(¬ify->mutex); - return ret; - } - - notify->pending = 0; - - return 0; -} - -void notify_enter(Notify *notify) -{ - pthread_mutex_lock(¬ify->mutex); -} - -void notify_leave(Notify *notify) -{ - pthread_mutex_unlock(¬ify->mutex); -} - -void notify_wait(Notify *notify) -{ - if (!notify->pending) - pthread_cond_wait(¬ify->cond, ¬ify->mutex); - notify->pending = 0; -} - -void notify_signal(Notify *notify) -{ - notify->pending = 1; - pthread_cond_signal(¬ify->cond); -} - -void notify_signal_sync(Notify *notify) -{ - pthread_mutex_lock(¬ify->mutex); - notify_signal(notify); - pthread_mutex_unlock(¬ify->mutex); -} diff --git a/src/notify.h b/src/notify.h deleted file mode 100644 index 0fbc74479..000000000 --- a/src/notify.h +++ /dev/null @@ -1,60 +0,0 @@ -/* the Music Player Daemon (MPD) - * Copyright (C) 2008 Max Kellermann - * 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 - */ - -#ifndef NOTIFY_H -#define NOTIFY_H - -#include "os_compat.h" - -typedef struct _Notify { - pthread_mutex_t mutex; - pthread_cond_t cond; - int pending; -} Notify; - -int notify_init(Notify *notify); - -/** - * The thread which shall be notified by this object must call this - * function before any notify_wait() invocation. It locks the mutex. - */ -void notify_enter(Notify *notify); - -/** - * Neutralize notify_leave(). - */ -void notify_leave(Notify *notify); - -/** - * Wait for a notification. Return immediately if we have already - * been notified since we last returned from notify_wait(). - */ -void notify_wait(Notify *notify); - -/** - * Notify the thread. This function never blocks. - */ -void notify_signal(Notify *notify); - -/** - * Notify the thread synchonously, i.e. wait until it has received the - * notification. - */ -void notify_signal_sync(Notify *notify); - -#endif diff --git a/src/outputBuffer.c b/src/outputBuffer.c index b0cfc00df..26e6077f2 100644 --- a/src/outputBuffer.c +++ b/src/outputBuffer.c @@ -20,252 +20,543 @@ #include "utils.h" #include "normalize.h" -#include "playerData.h" - -void ob_init(unsigned int size) +#include "ringbuf.h" +#include "condition.h" +#include "song.h" +#include "main_notify.h" +#include "player_error.h" +#include "log.h" +#include "action_status.h" + +/* typically have 2048-4096 of these structs, so pack tightly */ +struct ob_chunk { + mpd_uint16 len; /* 0: skip this chunk */ + mpd_uint16 bit_rate; + float time; + mpd_uint8 seq; /* see seq_ok() for explanation */ + char data[CHUNK_SIZE]; +}; + +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 nr_bpp; /* nr (chunks) buffered before play */ + 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; + unsigned int seq_drop; + unsigned int seq_player; /* only gets changed by ob.thread */ + mpd_uint8 seq_decoder; /* only gets changed by dc.thread */ + struct ringbuf preseek_index; + enum ob_state preseek_state; + mpd_uint16 *preseek_len; +}; + +static struct output_buffer ob; + +#include "outputBuffer_xfade.h" +#include "outputBuffer_accessors.h" + +static void ob_free(void) { - assert(size > 0); - - memset(&ob.convState, 0, sizeof(ConvState)); - ob.chunks = xmalloc(size * sizeof(*ob.chunks)); - ob.size = size; - ob.begin = 0; - ob.end = 0; - ob.lazy = 0; - ob.chunks[0].chunkSize = 0; + free(ob.chunks); + ringbuf_free(ob.index); } -void ob_free(void) +static enum action_status ob_do_stop(void); +static void stop_playback(void) { - assert(ob.chunks != NULL); - free(ob.chunks); + assert(pthread_equal(pthread_self(), ob.thread)); + cond_enter(&ob_action_cond); + ob_do_stop(); + cond_leave(&ob_action_cond); } -void ob_clear(void) +void ob_trigger_action(enum ob_action action) { - ob.end = ob.begin; - ob.chunks[ob.end].chunkSize = 0; + /* + * 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); + 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); } -/** return the index of the chunk after i */ -static inline unsigned successor(unsigned i) +static enum action_status ob_finalize_action(void) { - assert(i <= ob.size); - - ++i; - return i == ob.size ? 0 : i; + 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; } -/** - * Mark the tail chunk as "full" and wake up the player if is waiting - * for the decoder. - */ -static void output_buffer_expand(unsigned i) +/* marks all buffered chunks with sequence number matching `seq' as invalid */ +static enum action_status ob_do_drop(void) { - int was_empty = !ob.lazy || ob_is_empty(); - - assert(i == (ob.end + 1) % ob.size); - assert(i != ob.end); - - ob.end = i; - ob.chunks[i].chunkSize = 0; - if (was_empty) - /* if the buffer was empty, the player thread might be - waiting for us; wake it up now that another decoded - buffer has become available. */ - decoder_wakeup_player(); + struct iovec vec[2]; + long i; + unsigned int 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(); } -void ob_flush(void) +static void close_audio_devices(void); +static enum action_status ob_do_pause(void) { - ob_chunk *chunk = ob_get_chunk(ob.end); - - if (chunk->chunkSize > 0) { - unsigned int next = successor(ob.end); - if (next == ob.begin) - /* all buffers are full; we have to wait for - the player to free one, so don't flush - right now */ - return; + 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 + */ + close_audio_devices(); + ob.state = OB_STATE_PAUSE; + return AS_INPROGRESS; +} - output_buffer_expand(next); +static void reader_reset_buffer(void) +{ + struct iovec 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); } -void ob_set_lazy(int lazy) +static void ob_seq_player_set(unsigned int seq_num) { - ob.lazy = lazy; + cond_enter(&ob_seq_cond); + ob.seq_player = seq_num; + cond_signal(&ob_seq_cond); + cond_leave(&ob_seq_cond); } -int ob_is_empty(void) +static enum action_status ob_do_stop(void) { - return ob.begin == ob.end; + assert(pthread_equal(pthread_self(), ob.thread)); + if (ob.state != OB_STATE_STOP) { + ob.elapsed_time = 0; + reader_reset_buffer(); + ob.xfade_state = XFADE_DISABLED; + close_audio_devices(); + ob.state = OB_STATE_STOP; + ob_seq_player_set((unsigned int)ob.seq_decoder); + } + return AS_INPROGRESS; } -void ob_shift(void) +/* + * 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) { - assert(ob.begin != ob.end); - assert(ob.begin < ob.size); - - ob.begin = successor(ob.begin); + 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; } -unsigned int ob_relative(const unsigned i) +static enum action_status ob_do_seek_finish(void) { - if (i >= ob.begin) - return i - ob.begin; - else - return i + ob.size - ob.begin; + 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((unsigned int)ob.seq_decoder); + } + return ob_finalize_action(); } -unsigned ob_available(void) +static enum action_status ob_take_action(void) { - return ob_relative(ob.end); + 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_QUIT: + close_audio_devices(); + ob.state = OB_STATE_QUIT; + return AS_INPROGRESS; + } + return ob_finalize_action(); } -int ob_absolute(const unsigned relative) +/* + * 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 iovec vec[2], size_t i) { - unsigned i, max; - - max = ob.end; - if (max < ob.begin) - max += ob.size; - i = (unsigned)ob.begin + relative; - if (i >= max) - return -1; + if (vec[0].iov_len > i) + return &ob.chunks[vec[0].iov_base + i - ob.index->buf]; + if (vec[1].iov_base) { + assert(vec[0].iov_len > 0); + i -= vec[0].iov_len; + if (vec[1].iov_len > i) + return &ob.chunks[vec[1].iov_base + i - ob.index->buf]; + } + return NULL; +} - if (i >= ob.size) - i -= ob.size; +static void prevent_buffer_underrun(void) +{ + static const char silence[CHUNK_SIZE]; + if (playAudio(silence, sizeof(silence)) < 0) + stop_playback(); +} - return (int)i; +/* 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); */ } -ob_chunk * ob_get_chunk(const unsigned i) +void ob_wait_sync(void) { - assert(i < ob.size); + assert(!pthread_equal(pthread_self(), dc.thread)); + assert(!pthread_equal(pthread_self(), ob.thread)); + + /* DEBUG(__FILE__": %s %d\n", __func__, __LINE__); */ + cond_enter(&ob_seq_cond); + while (ob.seq_player != ob.seq_decoder) { + /* DEBUG("%s %d seq_player:%u seq_decoder:%u\n", */ + /* __func__, __LINE__, ob.seq_player, ob.seq_decoder); */ + cond_wait(&ob_seq_cond); + /* DEBUG("%s %d seq_player:%u seq_decoder:%u\n", */ + /* __func__, __LINE__, ob.seq_player, ob.seq_decoder); */ + } + cond_leave(&ob_seq_cond); + /* DEBUG(__FILE__": %s %d\n", __func__, __LINE__); */ +} - return &ob.chunks[i]; +/* 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); } -/** - * Return the tail chunk which has room for additional data. If there - * is no room in the queue, this function blocks until the player - * thread has finished playing its current chunk. - * - * @return the positive index of the new chunk; OUTPUT_BUFFER_DC_SEEK - * if another thread requested seeking; OUTPUT_BUFFER_DC_STOP if - * another thread requested stopping the decoder. +/* + * Returns true if output buffer is playing the song we're decoding */ -static int tailChunk(InputStream * inStream, - int seekable, float data_time, mpd_uint16 bitRate) +int ob_synced(void) { - unsigned int next; - ob_chunk *chunk; - - chunk = ob_get_chunk(ob.end); - assert(chunk->chunkSize <= sizeof(chunk->data)); - if (chunk->chunkSize == sizeof(chunk->data)) { - /* this chunk is full; allocate a new chunk */ - next = successor(ob.end); - while (ob.begin == next) { - /* all chunks are full of decoded data; wait - for the player to free one */ - - if (dc.stop) - return OUTPUT_BUFFER_DC_STOP; - - if (dc.seek) { - if (seekable) { - return OUTPUT_BUFFER_DC_SEEK; - } else { - dc.seekError = 1; - dc.seek = 0; - decoder_wakeup_player(); - } - } - if (!inStream || bufferInputStream(inStream) <= 0) { - decoder_sleep(); - } - } + 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((unsigned int)a->seq); + wakeup_main_task(); /* sync playlist */ +} + +#include "outputBuffer_audio.h" - output_buffer_expand(next); - chunk = ob_get_chunk(next); - assert(chunk->chunkSize == 0); +static void play_next_chunk(void) +{ + struct iovec 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 && + (dc.state == DC_STATE_STOP) && + ! playlist_playing())) { + stop_playback(); + return; + } + if (nr < ((ob.xfade_time <= 0) ? ob.nr_bpp : xfade_chunks_needed(vec))) + { + prevent_buffer_underrun(); + return; } - if (chunk->chunkSize == 0) { - /* if the chunk is empty, nobody has set bitRate and - times yet */ + a = get_chunk(vec, 0); + assert(a); + if (! a->len) + goto out; - chunk->bitRate = bitRate; - chunk->times = data_time; + if (nr > 1 && ob.xfade_state == XFADE_ENABLED) { + struct ob_chunk *b = get_chunk(vec, ob.xfade_max); + xfade_mix(a, b); } - return ob.end; + 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); + } + /* 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); + + /* unblock ob_send() if it was waiting on a full buffer */ + dc_try_unhalt(); } -int ob_send(InputStream * inStream, - int seekable, void *dataIn, - size_t dataInLen, float data_time, mpd_uint16 bitRate, - ReplayGainInfo * replayGainInfo) +static void * ob_task(mpd_unused void *arg) { - size_t dataToSend; - char *data; - size_t datalen; - static char *convBuffer; - static size_t convBufferLen; - ob_chunk *chunk = NULL; - - if (cmpAudioFormat(&(ob.audioFormat), &(dc.audioFormat)) == 0) { - data = dataIn; - datalen = dataInLen; - } else { - datalen = pcm_sizeOfConvBuffer(&(dc.audioFormat), dataInLen, - &(ob.audioFormat)); - if (datalen > convBufferLen) { - if (convBuffer != NULL) - free(convBuffer); - convBuffer = xmalloc(datalen); - convBufferLen = datalen; + 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); + if (as == AS_INPROGRESS) + ob_finalize_action(); + cond_wait(&ob_halt_cond); + break; + case OB_STATE_QUIT: goto out; } - data = convBuffer; - datalen = pcm_convertAudioFormat(&(dc.audioFormat), dataIn, - dataInLen, &(ob.audioFormat), - data, &(ob.convState)); } +out: + cond_leave(&ob_halt_cond); + assert(ob.state == OB_STATE_QUIT); + assert(as == AS_INPROGRESS); + ob_finalize_action(); + return NULL; +} - if (replayGainInfo && (replayGainState != REPLAYGAIN_OFF)) - doReplayGain(replayGainInfo, data, datalen, &ob.audioFormat); - else if (normalizationEnabled) - normalizeData(data, datalen, &ob.audioFormat); - - while (datalen) { - int chunk_index = tailChunk(inStream, seekable, - data_time, bitRate); - if (chunk_index < 0) - return chunk_index; - - chunk = ob_get_chunk(chunk_index); - - dataToSend = sizeof(chunk->data) - chunk->chunkSize; - if (dataToSend > datalen) - dataToSend = datalen; - - memcpy(chunk->data + chunk->chunkSize, data, dataToSend); - chunk->chunkSize += dataToSend; - datalen -= dataToSend; - data += dataToSend; - } +void ob_init(size_t size) +{ + pthread_attr_t attr; + assert(size > 0 && !ob.index && !ob.chunks); + ob.index = ringbuf_create(size); + ob.chunks = xcalloc(ob.index->size, sizeof(struct ob_chunk)); + ob.preseek_len = xmalloc(ob.index->size * sizeof(ob.chunks[0].len)); + ob.nr_bpp = 1; + ob.state = OB_STATE_STOP; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (pthread_create(&ob.thread, &attr, ob_task, NULL)) + FATAL("Failed to spawn player task: %s\n", strerror(errno)); + atexit(ob_free); +} - if (chunk != NULL && chunk->chunkSize == sizeof(chunk->data)) - ob_flush(); +void ob_seek_start(void) +{ + assert(pthread_equal(pthread_self(), dc.thread)); + assert(dc.seek_where >= 0); + ob_trigger_action(OB_ACTION_SEEK_START); +} - return 0; +void ob_seek_finish(void) +{ + assert(pthread_equal(pthread_self(), dc.thread)); + ob_trigger_action(OB_ACTION_SEEK_FINISH); } -void ob_skip(unsigned num) +/* + * if there are any partially written chunk, flush them out to + * the output process _before_ decoding the next track + */ +void ob_flush(void) { - int i = ob_absolute(num); - if (i >= 0) - ob.begin = i; + struct iovec 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: /* should we assert if paused? */ + ringbuf_write_advance(ob.index, 1); + break; + case OB_STATE_STOP: + case OB_STATE_QUIT: + c->len = 0; + } + } + } } + +#include "outputBuffer_ob_send.h" diff --git a/src/outputBuffer.h b/src/outputBuffer.h index 4aea59120..833a29e77 100644 --- a/src/outputBuffer.h +++ b/src/outputBuffer.h @@ -25,89 +25,84 @@ #include "inputStream.h" #include "replayGain.h" -#define OUTPUT_BUFFER_DC_STOP -1 -#define OUTPUT_BUFFER_DC_SEEK -2 - -/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ -#define CHUNK_SIZE 1020 - -typedef struct _OutputBufferChunk { - mpd_uint16 chunkSize; - mpd_uint16 bitRate; - float times; - char data[CHUNK_SIZE]; -} ob_chunk; - -/** - * A ring set of buffers where the decoder appends data after the end, - * and the player consumes data from the beginning. +/* + * As far as audio output is concerned, `stop' is a superset of `pause' + * That is, `stop' will drop decoded audio chunks from the buffer + * and `pause' will not. Both will stop audio playback immediately + * and close audio playback devices (TODO: close mixer devices). */ -typedef struct _OutputBuffer { - ob_chunk *chunks; - - unsigned int size; - - /** the index of the first decoded chunk */ - unsigned int volatile begin; - - /** the index after the last decoded chunk */ - unsigned int volatile end; - - /** non-zero if the player thread should only we woken up if - the buffer becomes non-empty */ - int lazy; - - AudioFormat audioFormat; - ConvState convState; -} OutputBuffer; - -void ob_init(unsigned int size); +enum ob_action { + OB_ACTION_NONE = 0, + OB_ACTION_PLAY, + OB_ACTION_DROP, + OB_ACTION_SEEK_START, + OB_ACTION_SEEK_FINISH, + OB_ACTION_PAUSE_SET, + OB_ACTION_PAUSE_UNSET, + OB_ACTION_PAUSE_FLIP, + OB_ACTION_STOP, + OB_ACTION_QUIT +}; + +/* 1020 bytes since its divisible for 8, 16, 24, and 32-bit audio */ +#define CHUNK_SIZE 1020 -void ob_free(void); +void ob_init(size_t size); -void ob_clear(void); +enum ob_drop_type { OB_DROP_DECODED, OB_DROP_PLAYING }; +void ob_drop_audio(enum ob_drop_type type); -void ob_flush(void); +/* + * Returns true if output buffer is playing the song we're decoding + */ +int ob_synced(void); -/** - * When a chunk is decoded, we wake up the player thread to tell him - * about it. In "lazy" mode, we only wake him up when the buffer was - * previously empty, i.e. when the player thread has really been - * waiting for us. +/* + * analogous to send(2) or write(2), it will put @data into + * the output buffer (like writing to a pipe, the consumer of + * which will read and play the contents of the output buffer + * + * Future direction: + * zero-copy functions using vectors: + * vec = ob_getv(); decode_to(&vec, ...); ob_vmsplice(&vec, ...); */ -void ob_set_lazy(int lazy); +enum dc_action ob_send(void *data, size_t len, float time, + mpd_uint16 bit_rate, ReplayGainInfo *rgi); -/** is the buffer empty? */ -int ob_is_empty(void); +/* synchronous and blocking (the only way it should be) */ +void ob_trigger_action(enum ob_action action); -void ob_shift(void); +/* synchronous and blocking, called from dc.thread */ +void ob_seek_start(void); +void ob_seek_finish(void); -/** - * what is the position of the specified chunk number, relative to - * the first chunk in use? - */ -unsigned int ob_relative(const unsigned i); +/* boring accessor functions, only called by main-thread */ +unsigned long ob_get_elapsed_time(void); +unsigned long ob_get_total_time(void); +unsigned int ob_get_channels(void); +unsigned int ob_get_bit_rate(void); +unsigned int ob_get_sample_rate(void); +unsigned int ob_get_bits(void); +void ob_set_sw_volume(int volume); +void ob_set_xfade(float xfade_seconds); +float ob_get_xfade(void); -/** determine the number of decoded chunks */ -unsigned ob_available(void); +enum ob_state { + OB_STATE_PLAY = 0, + OB_STATE_STOP, + OB_STATE_PAUSE, + OB_STATE_SEEK, + OB_STATE_QUIT +}; -/** - * Get the absolute index of the nth used chunk after the first one. - * Returns -1 if there is no such chunk. - */ -int ob_absolute(const unsigned relative); +enum ob_state ob_get_state(void); -ob_chunk * ob_get_chunk(const unsigned i); +AudioFormat *ob_audio_format(void); -/* we send inStream for buffering the inputStream while waiting to - send the next chunk */ -int ob_send(InputStream * inStream, - int seekable, - void *data, - size_t datalen, - float data_time, - mpd_uint16 bitRate, ReplayGainInfo * replayGainInfo); +void ob_advance_sequence(void); -void ob_skip(unsigned num); +void ob_wait_sync(void); + +void ob_flush(void); #endif diff --git a/src/outputBuffer_accessors.h b/src/outputBuffer_accessors.h new file mode 100644 index 000000000..11c8887c8 --- /dev/null +++ b/src/outputBuffer_accessors.h @@ -0,0 +1,77 @@ +/* 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 + */ + +/* + * Some of the most boring code in the world that I didn't want cluttering + * up outputBuffer.c + */ + +unsigned long ob_get_elapsed_time(void) +{ + return (unsigned long)(ob.elapsed_time + 0.5); +} + +unsigned long ob_get_total_time(void) +{ + return (unsigned long)(ob.total_time + 0.5); +} + +unsigned int ob_get_bit_rate(void) +{ + return (unsigned int)ob.bit_rate; +} + +unsigned int ob_get_channels(void) +{ + return (unsigned int)ob.audio_format.channels; +} + +unsigned int ob_get_sample_rate(void) +{ + return (unsigned int)ob.audio_format.sampleRate; +} + +unsigned int ob_get_bits(void) +{ + return (unsigned int)ob.audio_format.bits; +} + +void ob_set_sw_volume(int volume) +{ + ob.sw_vol = (volume > 1000) ? 1000 : (volume < 0 ? 0 : volume); +} + +void ob_set_xfade(float xfade_sec) +{ + ob.xfade_time = (xfade_sec < 0) ? 0 : xfade_sec; +} + +float ob_get_xfade(void) +{ + return ob.xfade_time; +} + +enum ob_state ob_get_state(void) +{ + return ob.state; +} + +AudioFormat *ob_audio_format(void) +{ + return &ob.audio_format; +} diff --git a/src/outputBuffer_audio.h b/src/outputBuffer_audio.h new file mode 100644 index 000000000..2af40c95f --- /dev/null +++ b/src/outputBuffer_audio.h @@ -0,0 +1,35 @@ +/* This is where audio devices are managed inside the output buffer thread */ + +static int audio_opened; + +/* + * reopen is set when we get a new song and there's a difference + * in audio format + */ +static int open_audio_devices(int reopen) +{ + assert(pthread_equal(pthread_self(), ob.thread)); + + if (!reopen && audio_opened) + return 0; + if (openAudioDevice(&ob.audio_format) >= 0) { + audio_opened = 1; + return 0; + } + audio_opened = 0; + stop_playback(); + player_seterror(PLAYER_ERROR_AUDIO, NULL); + ERROR("problems opening audio device\n"); + return -1; +} + +static void close_audio_devices(void) +{ + assert(pthread_equal(pthread_self(), ob.thread)); + DEBUG(__FILE__":%s %d\n", __func__, __LINE__); + dropBufferedAudio(); + closeAudioDevice(); + audio_opened = 0; + /* DEBUG(__FILE__":%s %d\n", __func__, __LINE__); */ +} + diff --git a/src/outputBuffer_ob_send.h b/src/outputBuffer_ob_send.h new file mode 100644 index 000000000..2a4a84763 --- /dev/null +++ b/src/outputBuffer_ob_send.h @@ -0,0 +1,133 @@ +/* + * This file only contains ob_send() and private functions + * needed to implement ob_send() + */ + +/* + * This is one of two places where dc.thread can block, + * the other is readFromInputStream + */ +static enum dc_action await_buffer_space(void) +{ + assert(pthread_equal(pthread_self(), dc.thread)); + /* DEBUG("Waiting for buffer space\n"); */ + assert(dc.state != DC_STATE_STOP); + + dc_halt(); + /* DEBUG("done waiting for buffer space\n"); */ + return dc.action; +} + +/* This will modify its input */ +static void do_audio_conversion(void **data, size_t *len) +{ + size_t newlen; + + assert(pthread_equal(pthread_self(), dc.thread)); + newlen = pcm_sizeOfConvBuffer(&dc.audio_format, *len, &ob.audio_format); + if (newlen > ob.conv_buf_len) { + ob.conv_buf = xrealloc(ob.conv_buf, newlen); + ob.conv_buf_len = newlen; + } + *len = pcm_convertAudioFormat(&dc.audio_format, *data, *len, + &ob.audio_format, ob.conv_buf, + &ob.conv_state); + *data = ob.conv_buf; +} + +static void ensure_audio_format_sanity(void **data, size_t *len) +{ + static mpd_uint8 seq_last; + + assert(pthread_equal(pthread_self(), dc.thread)); + if (mpd_unlikely(seq_last != ob.seq_decoder)) { + seq_last = ob.seq_decoder; + if (cmpAudioFormat(&dc.audio_format, &ob.audio_format)) + getOutputAudioFormat(&dc.audio_format, + &ob.audio_format); + } + if (cmpAudioFormat(&ob.audio_format, &dc.audio_format)) + do_audio_conversion(data, len); +} + +static void start_playback(void) +{ + assert(pthread_equal(pthread_self(), dc.thread)); + if (mpd_unlikely(ob.state == OB_STATE_STOP && + player_errno == PLAYER_ERROR_NONE)) { + ob_trigger_action(OB_ACTION_PLAY); + } +} + +enum dc_action +ob_send(void *data, size_t len, + float decode_time, mpd_uint16 bit_rate, ReplayGainInfo * rgi) +{ + struct iovec vec[2]; + struct ob_chunk *c; + size_t idx; + size_t i, j; + + assert(pthread_equal(pthread_self(), dc.thread)); + + ensure_audio_format_sanity(&data, &len); + + if (rgi && (replayGainState != REPLAYGAIN_OFF)) + doReplayGain(rgi, data, len, &ob.audio_format); + else if (normalizationEnabled) + normalizeData(data, len, &ob.audio_format); + + while (1) { + /* full buffer, loop check in case of spurious wakeups */ + while (!ringbuf_get_write_vector(ob.index, vec)) { + enum dc_action rv = await_buffer_space(); + if (rv != DC_ACTION_NONE) + return rv; + } + + for (i = 0; i < ARRAY_SIZE(vec); i++) { + for (j = 0; j < vec[i].iov_len; j++) { + size_t c_len; + idx = vec[i].iov_base - ob.index->buf + j; + c = &(ob.chunks[idx]); + + if (!c->len) { /* populate empty chunk */ + c->seq = ob.seq_decoder; + c->time = decode_time; + c->bit_rate = bit_rate; + c_len = len > CHUNK_SIZE ? CHUNK_SIZE + : len; + c->len = (mpd_uint16)c_len; + memcpy(c->data, data, c_len); + } else { /* partially filled chunk */ + size_t max = CHUNK_SIZE - c->len; + assert(c->seq == ob.seq_decoder); + c_len = len > max ? max : len; + assert(c_len <= CHUNK_SIZE); + memcpy(c->data + c->len, data, c_len); + c->len += c_len; + assert(c->len <= CHUNK_SIZE); + } + + /* + * feed ob.thread ASAP, otherwise ob.thread + * will just play silence + */ + if (c->len == CHUNK_SIZE) + ringbuf_write_advance(ob.index, 1); + + assert(len >= c_len); + len -= c_len; + if (!len) { + start_playback(); + return dc.action; + } + data += c_len; + } + } + } + assert(__FILE__ && __LINE__ && "We should never get here" && 0); + return DC_ACTION_NONE; +} + + diff --git a/src/outputBuffer_sequence.h b/src/outputBuffer_sequence.h new file mode 100644 index 000000000..25181425f --- /dev/null +++ b/src/outputBuffer_sequence.h @@ -0,0 +1,30 @@ +/* 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 + */ + +#ifndef OUTPUT_BUFFER_SEQUENCE_H +#define OUTPUT_BUFFER_SEQUENCE_H + +/* + * prevent the decoder thread from being more than one song + * ahead of the output buffer + */ +void ob_seq_enter(void); +void ob_seq_leave(void); +void ob_seq_wait(void); + +#endif /* OUTPUT_BUFFER_SEQUENCE_H */ diff --git a/src/outputBuffer_xfade.h b/src/outputBuffer_xfade.h new file mode 100644 index 000000000..0f3a4d5a7 --- /dev/null +++ b/src/outputBuffer_xfade.h @@ -0,0 +1,105 @@ +#ifndef OUTPUT_BUFFER_XFADE_H +#define OUTPUT_BUFFER_XFADE_H + +#include "os_compat.h" +#include "audio.h" +#include "pcm_utils.h" + +static struct ob_chunk *get_chunk(struct iovec vec[2], size_t i); +static size_t calculate_xfade_chunks(struct iovec vec[2]) +{ + size_t chunks; + struct ob_chunk *c; + size_t nr; + AudioFormat *af = &ob.audio_format; + + assert(pthread_equal(ob.thread, pthread_self())); + + if (!ob.total_time || + (ob.elapsed_time + ob.xfade_time) < ob.total_time || + !isCurrentAudioFormat(af)) + return ob.nr_bpp; /* too early, don't enable xfade yet */ + + assert(af->bits > 0); + assert(af->channels > 0); + assert(af->sampleRate > 0); + + chunks = af->sampleRate * af->bits * af->channels / 8.0 / CHUNK_SIZE; + chunks = chunks * (ob.xfade_time + 0.5); + + assert(ob.index->size >= ob.nr_bpp); + if (chunks > (ob.index->size - ob.nr_bpp)) + chunks = ob.index->size - ob.nr_bpp; + DEBUG("calculated xfade chunks: %d\n", chunks); + nr = vec[0].iov_len + vec[1].iov_len; + + if (chunks > nr) + return chunks; /* not enough work with */ + + c = get_chunk(vec, chunks); + assert(c); + if (c->seq == ob.seq_player) { + do { + if (!(c = get_chunk(vec, ++chunks))) + return chunks; /* not enough to work with */ + } while (c->seq == ob.seq_player); + } else { + do { + c = get_chunk(vec, --chunks); + assert(c); + } while (c->seq == ob.seq_decoder); + assert((c = get_chunk(vec, chunks))); + assert(c->seq != ob.seq_decoder); + ++chunks; + assert((c = get_chunk(vec, chunks))); + assert(c->seq == ob.seq_decoder); + } + DEBUG("adjusted xfade chunks: %d\n", chunks); + + ob.xfade_cur = chunks; + ob.xfade_max = chunks; + assert(ob.xfade_state == XFADE_DISABLED); + ob.xfade_state = XFADE_ENABLED; + return chunks; +} + +static size_t xfade_chunks_needed(struct iovec vec[2]) +{ + assert(pthread_equal(ob.thread, pthread_self())); + + if (ob.xfade_state == XFADE_DISABLED) + return calculate_xfade_chunks(vec); + assert(ob.xfade_state == XFADE_ENABLED); + return ob.xfade_max; +} + +static void xfade_mix(struct ob_chunk *a, struct ob_chunk *b) +{ + assert(pthread_equal(ob.thread, pthread_self())); + assert(ob.xfade_state == XFADE_ENABLED); + assert(ob.xfade_cur <= ob.xfade_max); + assert(b); + assert(a != b); + assert(a->len <= CHUNK_SIZE); + assert(b->len <= CHUNK_SIZE); + if (b->seq == a->seq) { + /* deal with small rounding errors */ + DEBUG("seq_match: %d == %d\n", a->seq, b->seq); + return; + } + + /* as xfade_cur increases, b is scaled more and a is scaled less */ + pcm_mix(a->data, b->data, a->len, b->len, + &ob.audio_format, ((float)ob.xfade_cur) / ob.xfade_max); + + /* next time we fade more until we have nothing to fade */ + if (ob.xfade_cur) + --ob.xfade_cur; + if (b->len > a->len) { + DEBUG("reassign len: %d => %d\n", a->len, b->len); + a->len = b->len; + } + b->len = 0; /* invalidate the chunk we already mixed in */ +} + +#endif /* OUTPUT_BUFFER_XFADE_H */ diff --git a/src/player.c b/src/player.c deleted file mode 100644 index 4d7762a0f..000000000 --- a/src/player.c +++ /dev/null @@ -1,345 +0,0 @@ -/* 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.h" -#include "path.h" -#include "decode.h" -#include "command.h" -#include "interface.h" -#include "playlist.h" -#include "ls.h" -#include "listen.h" -#include "log.h" -#include "utils.h" -#include "directory.h" -#include "volume.h" -#include "playerData.h" -#include "permission.h" -#include "ack.h" -#include "os_compat.h" -#include "main_notify.h" - -static void playerCloseAudio(void); - -void wakeup_player_nb(void) -{ - notify_signal(&pc.notify); -} - -static void wakeup_player(void) -{ - notify_signal(&pc.notify); - wait_main_task(); -} - -void player_sleep(void) -{ - notify_wait(&pc.notify); -} - -static void * player_task(mpd_unused void *arg) -{ - notify_enter(&pc.notify); - - while (1) { - if (pc.play) { - decode(); - continue; /* decode() calls wakeup_main_task */ - } else if (pc.stop) { - pc.stop = 0; - } else if (pc.seek) { - pc.seek = 0; - } else if (pc.pause) { - pc.pause = 0; - } else if (pc.closeAudio) { - closeAudioDevice(); - pc.closeAudio = 0; - } else if (pc.lockQueue) { - pc.queueLockState = PLAYER_QUEUE_LOCKED; - pc.lockQueue = 0; - } else if (pc.unlockQueue) { - pc.queueLockState = PLAYER_QUEUE_UNLOCKED; - pc.unlockQueue = 0; - } else { - player_sleep(); - continue; - } - /* we did something, tell the main task about it */ - wakeup_main_task(); - } - return NULL; -} - -void playerInit(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)); -} - -int playerWait(int fd) -{ - if (playerStop(fd) < 0) - return -1; - - playerCloseAudio(); - - return 0; -} - -static void set_current_song(Song *song) -{ - pc.fileTime = song->tag ? song->tag->time : 0; - pc.current_song = song; -} - -int playerPlay(int fd, Song * song) -{ - if (playerStop(fd) < 0) - return -1; - - set_current_song(song); - - pc.play = 1; - /* FIXME: _nb() variant is probably wrong here, and everywhere... */ - do { wakeup_player_nb(); } while (pc.play); - - return 0; -} - -int playerStop(int fd) -{ - if (pc.state != PLAYER_STATE_STOP) { - pc.stop = 1; - do { wakeup_player(); } while (pc.stop); - } - - pc.queueState = PLAYER_QUEUE_BLANK; - playerQueueUnlock(); - - return 0; -} - -void playerKill(void) /* deprecated */ -{ - playerPause(STDERR_FILENO); -} - -int playerPause(int fd) -{ - if (pc.state != PLAYER_STATE_STOP) { - pc.pause = 1; - do { wakeup_player(); } while (pc.pause); - } - - return 0; -} - -int playerSetPause(int fd, int pause_flag) -{ - switch (pc.state) { - case PLAYER_STATE_PLAY: - if (pause_flag) - playerPause(fd); - break; - case PLAYER_STATE_PAUSE: - if (!pause_flag) - playerPause(fd); - break; - } - - return 0; -} - -int getPlayerElapsedTime(void) -{ - return (int)(pc.elapsedTime + 0.5); -} - -unsigned long getPlayerBitRate(void) -{ - return pc.bitRate; -} - -int getPlayerTotalTime(void) -{ - return (int)(pc.totalTime + 0.5); -} - -int getPlayerState(void) -{ - return pc.state; -} - -void clearPlayerError(void) -{ - pc.error = 0; -} - -int getPlayerError(void) -{ - return pc.error; -} - -char *getPlayerErrorStr(void) -{ - /* static OK here, only one user in main task */ - static char error[MPD_PATH_MAX + 64]; /* still too much */ - static const size_t errorlen = sizeof(error); - char path_max_tmp[MPD_PATH_MAX]; - *error = '\0'; /* likely */ - - switch (pc.error) { - case PLAYER_ERROR_FILENOTFOUND: - snprintf(error, errorlen, - "file \"%s\" does not exist or is inaccessible", - get_song_url(path_max_tmp, pc.errored_song)); - break; - case PLAYER_ERROR_FILE: - snprintf(error, errorlen, "problems decoding \"%s\"", - get_song_url(path_max_tmp, pc.errored_song)); - break; - case PLAYER_ERROR_AUDIO: - strcpy(error, "problems opening audio device"); - break; - case PLAYER_ERROR_SYSTEM: - strcpy(error, "system error occured"); - break; - case PLAYER_ERROR_UNKTYPE: - snprintf(error, errorlen, "file type of \"%s\" is unknown", - get_song_url(path_max_tmp, pc.errored_song)); - } - return *error ? error : NULL; -} - -static void playerCloseAudio(void) -{ - if (playerStop(STDERR_FILENO) < 0) - return; - pc.closeAudio = 1; - do { wakeup_player(); } while (pc.closeAudio); -} - -int queueSong(Song * song) -{ - if (pc.queueState == PLAYER_QUEUE_BLANK) { - set_current_song(song); - pc.queueState = PLAYER_QUEUE_FULL; - return 0; - } - - return -1; -} - -int getPlayerQueueState(void) -{ - return pc.queueState; -} - -void setQueueState(int queueState) -{ - pc.queueState = queueState; - wakeup_player_nb(); -} - -void playerQueueLock(void) -{ - if (pc.queueLockState == PLAYER_QUEUE_UNLOCKED) { - pc.lockQueue = 1; - do { wakeup_player(); } while (pc.lockQueue); - } -} - -void playerQueueUnlock(void) -{ - if (pc.queueLockState == PLAYER_QUEUE_LOCKED) { - pc.unlockQueue = 1; - do { wakeup_player(); } while (pc.unlockQueue); - } -} - -int playerSeek(int fd, Song * song, float seek_time) -{ - assert(song != NULL); - - if (pc.state == PLAYER_STATE_STOP) { - commandError(fd, ACK_ERROR_PLAYER_SYNC, - "player not currently playing"); - return -1; - } - - if (pc.current_song != song) - set_current_song(song); - - if (pc.error == PLAYER_ERROR_NOERROR) { - pc.seekWhere = seek_time; - pc.seek = 1; - /* FIXME: _nb() is probably wrong here, too */ - do { wakeup_player_nb(); } while (pc.seek); - } - - return 0; -} - -float getPlayerCrossFade(void) -{ - return pc.crossFade; -} - -void setPlayerCrossFade(float crossFadeInSeconds) -{ - if (crossFadeInSeconds < 0) - crossFadeInSeconds = 0; - pc.crossFade = crossFadeInSeconds; -} - -void setPlayerSoftwareVolume(int volume) -{ - volume = (volume > 1000) ? 1000 : (volume < 0 ? 0 : volume); - pc.softwareVolume = volume; -} - -double getPlayerTotalPlayTime(void) -{ - return pc.totalPlayTime; -} - -unsigned int getPlayerSampleRate(void) -{ - return pc.sampleRate; -} - -int getPlayerBits(void) -{ - return pc.bits; -} - -int getPlayerChannels(void) -{ - return pc.channels; -} - -/* this actually creates a dupe of the current metadata */ -Song *playerCurrentDecodeSong(void) -{ - return NULL; -} diff --git a/src/player.h b/src/player.h deleted file mode 100644 index 623ae1fb0..000000000 --- a/src/player.h +++ /dev/null @@ -1,139 +0,0 @@ -/* 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 - */ - -#ifndef PLAYER_H -#define PLAYER_H - -#include "decode.h" -#include "mpd_types.h" -#include "song.h" -#include "os_compat.h" - -#define PLAYER_STATE_STOP 0 -#define PLAYER_STATE_PAUSE 1 -#define PLAYER_STATE_PLAY 2 - -#define PLAYER_ERROR_NOERROR 0 -#define PLAYER_ERROR_FILE 1 -#define PLAYER_ERROR_AUDIO 2 -#define PLAYER_ERROR_SYSTEM 3 -#define PLAYER_ERROR_UNKTYPE 4 -#define PLAYER_ERROR_FILENOTFOUND 5 - -/* 0->1->2->3->5 regular playback - * ->4->0 don't play queued song - */ -#define PLAYER_QUEUE_BLANK 0 -#define PLAYER_QUEUE_FULL 1 -#define PLAYER_QUEUE_DECODE 2 -#define PLAYER_QUEUE_PLAY 3 -#define PLAYER_QUEUE_STOP 4 -#define PLAYER_QUEUE_EMPTY 5 - -#define PLAYER_QUEUE_UNLOCKED 0 -#define PLAYER_QUEUE_LOCKED 1 - -typedef struct _PlayerControl { - Notify notify; - volatile mpd_sint8 stop; - volatile mpd_sint8 play; - volatile mpd_sint8 pause; - volatile mpd_sint8 state; - volatile mpd_sint8 closeAudio; - volatile mpd_sint8 error; - volatile mpd_uint16 bitRate; - volatile mpd_sint8 bits; - volatile mpd_sint8 channels; - volatile mpd_uint32 sampleRate; - volatile float totalTime; - volatile float elapsedTime; - volatile float fileTime; - Song *current_song; - Song *errored_song; - volatile mpd_sint8 queueState; - volatile mpd_sint8 queueLockState; - volatile mpd_sint8 lockQueue; - volatile mpd_sint8 unlockQueue; - volatile mpd_sint8 seek; - volatile double seekWhere; - volatile float crossFade; - volatile mpd_uint16 softwareVolume; - volatile double totalPlayTime; -} PlayerControl; - -void wakeup_player_nb(void); - -void player_sleep(void); - -int playerPlay(int fd, Song * song); - -int playerSetPause(int fd, int pause_flag); - -int playerPause(int fd); - -int playerStop(int fd); - -void playerKill(void); - -int getPlayerTotalTime(void); - -int getPlayerElapsedTime(void); - -unsigned long getPlayerBitRate(void); - -int getPlayerState(void); - -void clearPlayerError(void); - -char *getPlayerErrorStr(void); - -int getPlayerError(void); - -int playerWait(int fd); - -int queueSong(Song * song); - -int getPlayerQueueState(void); - -void setQueueState(int queueState); - -void playerQueueLock(void); - -void playerQueueUnlock(void); - -int playerSeek(int fd, Song * song, float seek_time); - -void setPlayerCrossFade(float crossFadeInSeconds); - -float getPlayerCrossFade(void); - -void setPlayerSoftwareVolume(int volume); - -double getPlayerTotalPlayTime(void); - -unsigned int getPlayerSampleRate(void); - -int getPlayerBits(void); - -int getPlayerChannels(void); - -Song *playerCurrentDecodeSong(void); - -void playerInit(void); - -#endif diff --git a/src/playerData.c b/src/playerData.c index 3934d0c6f..113a71f35 100644 --- a/src/playerData.c +++ b/src/playerData.c @@ -24,19 +24,14 @@ #define DEFAULT_BUFFER_SIZE 2048 #define DEFAULT_BUFFER_BEFORE_PLAY 10 -unsigned int buffered_before_play; -PlayerControl pc; -DecoderControl dc; -OutputBuffer ob; - void initPlayerData(void) { float perc = DEFAULT_BUFFER_BEFORE_PLAY; char *test; - int crossfade = 0; size_t bufferSize = DEFAULT_BUFFER_SIZE; unsigned int buffered_chunks; ConfigParam *param; + unsigned int buffered_before_play; param = getConfigParam(CONF_AUDIO_BUFFER_SIZE); @@ -73,18 +68,6 @@ void initPlayerData(void) } ob_init(buffered_chunks); - - notify_init(&pc.notify); - pc.error = PLAYER_ERROR_NOERROR; - pc.state = PLAYER_STATE_STOP; - pc.queueState = PLAYER_QUEUE_BLANK; - pc.queueLockState = PLAYER_QUEUE_UNLOCKED; - pc.crossFade = crossfade; - pc.softwareVolume = 1000; - - notify_init(&dc.notify); - dc.state = DECODE_STATE_STOP; - dc.error = DECODE_ERROR_NOERROR; } diff --git a/src/playerData.h b/src/playerData.h index cf7f72cf0..ea8ea13e8 100644 --- a/src/playerData.h +++ b/src/playerData.h @@ -20,16 +20,10 @@ #define PLAYER_DATA_H #include "audio.h" -#include "player.h" #include "decode.h" #include "mpd_types.h" #include "outputBuffer.h" -extern unsigned int buffered_before_play; -extern PlayerControl pc; -extern DecoderControl dc; -extern OutputBuffer ob; - void initPlayerData(void); #endif diff --git a/src/player_error.c b/src/player_error.c new file mode 100644 index 000000000..4c7f7b9de --- /dev/null +++ b/src/player_error.c @@ -0,0 +1,53 @@ +#include "player_error.h" +#include "os_compat.h" +#include "log.h" +#include "path.h" + +enum player_error player_errno; +Song *player_errsong; + +void player_clearerror(void) +{ + player_errno = PLAYER_ERROR_NONE; + player_errsong = NULL; +} + +void player_seterror(enum player_error err, Song *song) +{ + if (player_errno) + ERROR("Clobbering existing error: %s\n", player_strerror()); + player_errno = err; + player_errsong = song; +} + +const char *player_strerror(void) +{ + /* static OK here, only one user in main task */ + static char error[MPD_PATH_MAX + 64]; /* still too much */ + char path_max_tmp[MPD_PATH_MAX]; + *error = '\0'; /* likely */ + + switch (player_errno) { + case PLAYER_ERROR_NONE: break; + case PLAYER_ERROR_FILE: + snprintf(error, sizeof(error), "problems decoding \"%s\"", + get_song_url(path_max_tmp, player_errsong)); + break; + case PLAYER_ERROR_AUDIO: + strcpy(error, "problems opening audio device"); + break; + case PLAYER_ERROR_SYSTEM: + strcpy(error, "system error occured"); + break; + case PLAYER_ERROR_UNKTYPE: + snprintf(error, sizeof(error), "file type of \"%s\" is unknown", + get_song_url(path_max_tmp, player_errsong)); + case PLAYER_ERROR_FILENOTFOUND: + snprintf(error, sizeof(error), + "file \"%s\" does not exist or is inaccessible", + get_song_url(path_max_tmp, player_errsong)); + break; + } + return *error ? error : NULL; +} + diff --git a/src/player_error.h b/src/player_error.h new file mode 100644 index 000000000..c90c98420 --- /dev/null +++ b/src/player_error.h @@ -0,0 +1,40 @@ +/* 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 + */ + +#ifndef PLAYER_ERROR_H +#define PLAYER_ERROR_H + +#include "song.h" + +enum player_error { + PLAYER_ERROR_NONE = 0, + PLAYER_ERROR_FILE, + PLAYER_ERROR_AUDIO, + PLAYER_ERROR_SYSTEM, + PLAYER_ERROR_UNKTYPE, + PLAYER_ERROR_FILENOTFOUND +}; + +extern enum player_error player_errno; +extern Song *player_errsong; + +void player_clearerror(void); +void player_seterror(enum player_error err, Song *song); +const char *player_strerror(void); + +#endif /* PLAYER_ERROR_H */ diff --git a/src/playlist.c b/src/playlist.c index ce029cc65..abe151488 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -17,7 +17,7 @@ */ #include "playlist.h" -#include "player.h" +#include "player_error.h" #include "command.h" #include "ls.h" #include "tag.h" @@ -31,9 +31,28 @@ #include "ack.h" #include "myfprintf.h" #include "os_compat.h" - -#define PLAYLIST_STATE_STOP 0 -#define PLAYLIST_STATE_PLAY 1 +#include "main_notify.h" + +enum _playlist_state { + PLAYLIST_STATE_STOP = 0, + PLAYLIST_STATE_PLAY = 1 +}; +static enum _playlist_state playlist_state; + +struct _playlist { + Song **songs; + /* holds version a song was modified on */ + mpd_uint32 *songMod; + int *order; + int *positionToId; + int *idToPosition; + int length; + int current; + int queued; /* to be decoded */ + int repeat; + int random; + mpd_uint32 version; +}; #define PLAYLIST_PREV_UNLESS_ELAPSED 10 @@ -57,13 +76,19 @@ #define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) #define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS 0 -static Playlist playlist; -static int playlist_state = PLAYLIST_STATE_STOP; +static struct _playlist playlist; int playlist_max_length = DEFAULT_PLAYLIST_MAX_LENGTH; static int playlist_stopOnError; static int playlist_errorCount; -static int playlist_queueError; -static int playlist_noGoToNext; + +/* + * queue_lock is to prevent ourselves from modifying playlist.queued + * while the decoder is decoding the song. The main_thread in mpd is + * the only modifier of playlist.queued. However, we may modify + * playlist.queued "in-place" without locking if it points to the same + * song (during move or shuffle). + */ +static pthread_mutex_t queue_lock = PTHREAD_MUTEX_INITIALIZER; int playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; @@ -238,8 +263,8 @@ void savePlaylistState(FILE *fp) fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE); switch (playlist_state) { case PLAYLIST_STATE_PLAY: - switch (getPlayerState()) { - case PLAYER_STATE_PAUSE: + switch (ob_get_state()) { + case OB_STATE_PAUSE: fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE); break; default: @@ -247,8 +272,8 @@ void savePlaylistState(FILE *fp) } fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT, playlist.order[playlist.current]); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME, - getPlayerElapsedTime()); + fprintf(fp, "%s%lu\n", PLAYLIST_STATE_FILE_TIME, + ob_get_elapsed_time()); break; default: fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP); @@ -257,7 +282,7 @@ void savePlaylistState(FILE *fp) fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM, playlist.random); fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT, playlist.repeat); fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE, - (int)(getPlayerCrossFade())); + (int)(ob_get_xfade())); fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN); fflush(fp); showPlaylist(fileno(fp)); @@ -265,7 +290,8 @@ void savePlaylistState(FILE *fp) } static void loadPlaylistFromStateFile(FILE *fp, char *buffer, - int state, int current, int seek_time) + enum ob_state state, + int current, int seek_time) { char *temp; int song; @@ -281,14 +307,9 @@ static void loadPlaylistFromStateFile(FILE *fp, char *buffer, state_file_fatal(); if (!addToPlaylist(STDERR_FILENO, temp, NULL) && current == song) { - if (state != PLAYER_STATE_STOP) { - playPlaylist(STDERR_FILENO, - playlist.length - 1, 0); - } - if (state == PLAYER_STATE_PAUSE) { - playerPause(STDERR_FILENO); - } - if (state != PLAYER_STATE_STOP) { + if (state == OB_STATE_PAUSE) + ob_trigger_action(OB_ACTION_PAUSE_SET); + if (state != OB_STATE_STOP) { seekSongInPlaylist(STDERR_FILENO, playlist.length - 1, seek_time); @@ -303,7 +324,7 @@ void readPlaylistState(FILE *fp) { int current = -1; int seek_time = 0; - int state = PLAYER_STATE_STOP; + enum ob_state state = OB_STATE_STOP; char buffer[PLAYLIST_BUFFER_SIZE]; while (myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) { @@ -311,13 +332,13 @@ void readPlaylistState(FILE *fp) strlen(PLAYLIST_STATE_FILE_STATE)) == 0) { if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]), PLAYLIST_STATE_FILE_STATE_PLAY) == 0) { - state = PLAYER_STATE_PLAY; + state = OB_STATE_PLAY; } else if (strcmp (&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]), PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) { - state = PLAYER_STATE_PAUSE; + state = OB_STATE_PAUSE; } } else if (strncmp(buffer, PLAYLIST_STATE_FILE_TIME, strlen(PLAYLIST_STATE_FILE_TIME)) == 0) { @@ -337,9 +358,7 @@ void readPlaylistState(FILE *fp) if (strncmp (buffer, PLAYLIST_STATE_FILE_CROSSFADE, strlen(PLAYLIST_STATE_FILE_CROSSFADE)) == 0) { - setPlayerCrossFade(atoi - (& - (buffer + ob_set_xfade(atoi(&(buffer [strlen (PLAYLIST_STATE_FILE_CROSSFADE)]))); } else @@ -368,7 +387,7 @@ void readPlaylistState(FILE *fp) (buffer, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN, strlen(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN) ) == 0) { - if (state == PLAYER_STATE_STOP) + if (state == OB_STATE_STOP) current = -1; loadPlaylistFromStateFile(fp, buffer, state, current, seek_time); @@ -483,95 +502,95 @@ static void swapSongs(int song1, int song2) playlist.positionToId[song2] = iTemp; } -static void queueNextSongInPlaylist(void) +static Song *song_at(int order_num) { - char path_max_tmp[MPD_PATH_MAX]; + if (order_num >= 0 && order_num < playlist.length) { + assert(playlist.songs[playlist.order[order_num]]); + return playlist.songs[playlist.order[order_num]]; + } + return NULL; +} +static int next_order_num(void) +{ if (playlist.current < playlist.length - 1) { - playlist.queued = playlist.current + 1; - DEBUG("playlist: queue song %i:\"%s\"\n", - playlist.queued, - get_song_url(path_max_tmp, - playlist. - songs[playlist.order[playlist.queued]])); - if (queueSong(playlist.songs[playlist.order[playlist.queued]]) < - 0) { - playlist.queued = -1; - playlist_queueError = 1; - } + return playlist.current + 1; } else if (playlist.length && playlist.repeat) { - if (playlist.length > 1 && playlist.random) { + if (playlist.length > 1 && playlist.random) randomizeOrder(0, playlist.length - 1); - } - playlist.queued = 0; - DEBUG("playlist: queue song %i:\"%s\"\n", - playlist.queued, - get_song_url(path_max_tmp, - playlist. - songs[playlist.order[playlist.queued]])); - if (queueSong(playlist.songs[playlist.order[playlist.queued]]) < - 0) { - playlist.queued = -1; - playlist_queueError = 1; - } + return 0; } + return -1; } -static void syncPlaylistWithQueue(int queue) +static void queueNextSongInPlaylist(void) { - if (queue && getPlayerQueueState() == PLAYER_QUEUE_BLANK) { - queueNextSongInPlaylist(); - } else if (getPlayerQueueState() == PLAYER_QUEUE_DECODE) { - if (playlist.queued != -1) - setQueueState(PLAYER_QUEUE_PLAY); - else - setQueueState(PLAYER_QUEUE_STOP); - } else if (getPlayerQueueState() == PLAYER_QUEUE_EMPTY) { - setQueueState(PLAYER_QUEUE_BLANK); - if (playlist.queued >= 0) { - DEBUG("playlist: now playing queued song\n"); - playlist.current = playlist.queued; - } - playlist.queued = -1; - if (queue) - queueNextSongInPlaylist(); + assert(playlist_state == PLAYLIST_STATE_PLAY); + /* DEBUG("%s:%d\n", __func__, __LINE__); */ + if (pthread_mutex_trylock(&queue_lock) == EBUSY) + return; /* still decoding */ + DEBUG("%s:%d\n", __func__, __LINE__); + playlist.queued = next_order_num(); + pthread_mutex_unlock(&queue_lock); + if (playlist.queued < 0) { + playlist_state = PLAYLIST_STATE_STOP; + } else if (dc.state == DC_STATE_STOP) { + /* DEBUG("%s:%d (%d)\n", __func__, __LINE__, playlist.queued);*/ + dc_trigger_action(DC_ACTION_START, 0); } } -static void lockPlaylistInteraction(void) +static void syncPlaylistWithQueue(void) { - if (getPlayerQueueState() == PLAYER_QUEUE_PLAY || - getPlayerQueueState() == PLAYER_QUEUE_FULL) { - playerQueueLock(); - syncPlaylistWithQueue(0); + assert(playlist_state == PLAYLIST_STATE_PLAY); + + if (!ob_synced()) + return; + + if (playlist.queued >= 0 && + playlist.current != playlist.queued) { + DEBUG("playlist: now playing queued song\n"); + DEBUG("%s:%d queued: %d\n",__func__,__LINE__,playlist.queued); + playlist.current = playlist.queued; } + queueNextSongInPlaylist(); } -static void unlockPlaylistInteraction(void) +void playlist_queue_next(void) { - playerQueueUnlock(); + assert(pthread_equal(pthread_self(), dc.thread)); + pthread_mutex_unlock(&queue_lock); + wakeup_main_task(); } -static void clearPlayerQueue(void) +Song *playlist_queued_song(void) { - playlist.queued = -1; - switch (getPlayerQueueState()) { - case PLAYER_QUEUE_FULL: - DEBUG("playlist: dequeue song\n"); - setQueueState(PLAYER_QUEUE_BLANK); - break; - case PLAYER_QUEUE_PLAY: - DEBUG("playlist: stop decoding queued song\n"); - setQueueState(PLAYER_QUEUE_STOP); - break; - } + assert(pthread_equal(pthread_self(), dc.thread)); + pthread_mutex_lock(&queue_lock); + return song_at(playlist.queued); } -static void clearPlayerQueueLocked(void) +static void queue_song_locked(int order_num) +{ + pthread_mutex_lock(&queue_lock); + playlist.queued = order_num; + pthread_mutex_unlock(&queue_lock); +} + +/* + * stops decoder iff we're decoding a song we haven't played yet + * Returns the currently queued song, -1 if we cleared the queue + * This will not affect the currently playing song + */ +static int clear_queue(void) { - lockPlaylistInteraction(); - clearPlayerQueue(); - unlockPlaylistInteraction(); + if (playlist.queued >= 0 && playlist.current != playlist.queued) { + dc_trigger_action(DC_ACTION_STOP, 0); + assert(dc.state == DC_STATE_STOP); + ob_drop_audio(OB_DROP_DECODED); + queue_song_locked(-1); + } + return playlist.queued; } int addToPlaylist(int fd, char *url, int *added_id) @@ -633,7 +652,7 @@ int addSongToPlaylist(int fd, Song * song, int *added_id) if (playlist_state == PLAYLIST_STATE_PLAY) { if (playlist.queued >= 0 && playlist.current == playlist.length - 1) - clearPlayerQueueLocked(); + clear_queue(); } id = getNextId(); @@ -649,8 +668,7 @@ int addSongToPlaylist(int fd, Song * song, int *added_id) if (playlist.random) { int swap; int start; - /*if(playlist_state==PLAYLIST_STATE_STOP) start = 0; - else */ if (playlist.queued >= 0) + if (playlist.queued >= 0) start = playlist.queued + 1; else start = playlist.current + 1; @@ -689,11 +707,13 @@ int swapSongsInPlaylist(int fd, int song1, int song2) if (playlist.queued >= 0) { queuedSong = playlist.order[playlist.queued]; } + assert(playlist.current >= 0 && + playlist.current < playlist.length); currentSong = playlist.order[playlist.current]; if (queuedSong == song1 || queuedSong == song2 || currentSong == song1 || currentSong == song2) - clearPlayerQueueLocked(); + clear_queue(); } swapSongs(song1, song2); @@ -742,6 +762,8 @@ int deleteFromPlaylist(int fd, int song) { int i; int songOrder; + int stopped_current = 0; + int prev_queued = playlist.queued; if (song < 0 || song >= playlist.length) { commandError(fd, ACK_ERROR_NO_EXIST, @@ -750,10 +772,12 @@ int deleteFromPlaylist(int fd, int song) } if (playlist_state == PLAYLIST_STATE_PLAY) { - if (playlist.queued >= 0 - && (playlist.order[playlist.queued] == song - || playlist.order[playlist.current] == song)) - clearPlayerQueueLocked(); + if (prev_queued >= 0 + && (playlist.order[prev_queued] == song + || playlist.order[playlist.current] == song)) { + /* DEBUG(__FILE__": %d (clearing)\n", __LINE__); */ + clear_queue(); + } } if (playlist.songs[song]->type == SONG_TYPE_URL) { @@ -786,12 +810,15 @@ int deleteFromPlaylist(int fd, int song) incrPlaylistVersion(); + /* DEBUG("current: %d, songOrder: %d\n", playlist.current, songOrder); */ + /* DEBUG("playlist_state: %d\n", playlist_state); */ if (playlist_state != PLAYLIST_STATE_STOP && playlist.current == songOrder) { - /*if(playlist.current>=playlist.length) return playerStop(fd); - else return playPlaylistOrderNumber(fd,playlist.current); */ - playerWait(STDERR_FILENO); - playlist_noGoToNext = 1; + dc_trigger_action(DC_ACTION_STOP, 0); + /* drop all audio, but don't actually close the device */ + ob_drop_audio(OB_DROP_DECODED); + ob_drop_audio(OB_DROP_PLAYING); + stopped_current = 1; } if (playlist.current > songOrder) { @@ -799,9 +826,15 @@ int deleteFromPlaylist(int fd, int song) } else if (playlist.current >= playlist.length) { incrPlaylistCurrent(); } - - if (playlist.queued > songOrder) { - playlist.queued--; + if (stopped_current) { + /* DEBUG(__FILE__": %d\n", __LINE__); */ + if (playlist.current >= 0) + playPlaylistOrderNumber(STDERR_FILENO,playlist.current); + else + playlist_state = PLAYLIST_STATE_STOP; + } else { + /* DEBUG(__FILE__": %d\n", __LINE__); */ + queueNextSongInPlaylist(); } return 0; @@ -831,11 +864,18 @@ void deleteASongFromPlaylist(Song * song) int stopPlaylist(int fd) { DEBUG("playlist: stop\n"); - if (playerWait(fd) < 0) - return -1; - playlist.queued = -1; + + DEBUG("%s:%d\n", __func__, __LINE__); + dc_trigger_action(DC_ACTION_STOP, 0); + DEBUG("%s:%d\n", __func__, __LINE__); + assert(dc.state == DC_STATE_STOP); + DEBUG("%s:%d\n", __func__, __LINE__); + ob_trigger_action(OB_ACTION_STOP); + assert(ob_get_state() == OB_STATE_STOP); + + DEBUG("%s:%d\n", __func__, __LINE__); + queue_song_locked(-1); playlist_state = PLAYLIST_STATE_STOP; - playlist_noGoToNext = 0; if (playlist.random) randomizeOrder(0, playlist.length - 1); return 0; @@ -843,27 +883,21 @@ int stopPlaylist(int fd) static int playPlaylistOrderNumber(int fd, int orderNum) { - char path_max_tmp[MPD_PATH_MAX]; - - if (playerStop(fd) < 0) - return -1; + char path[MPD_PATH_MAX]; playlist_state = PLAYLIST_STATE_PLAY; - playlist_noGoToNext = 0; - playlist.queued = -1; - playlist_queueError = 0; + assert(orderNum >= 0); DEBUG("playlist: play %i:\"%s\"\n", orderNum, - get_song_url(path_max_tmp, - playlist.songs[playlist.order[orderNum]])); - - if (playerPlay(fd, (playlist.songs[playlist.order[orderNum]])) < 0) { - stopPlaylist(fd); - return -1; - } - + get_song_url(path, song_at(orderNum))); + dc_trigger_action(DC_ACTION_STOP, 0); + ob_drop_audio(OB_DROP_DECODED); + queue_song_locked(orderNum); + ob_drop_audio(OB_DROP_PLAYING); + ob_trigger_action(OB_ACTION_PAUSE_UNSET); + dc_trigger_action(DC_ACTION_START, 0); + ob_wait_sync(); /* HACK: REMOVE */ playlist.current = orderNum; - return 0; } @@ -871,14 +905,17 @@ int playPlaylist(int fd, int song, int stopOnError) { int i = song; - clearPlayerError(); + DEBUG("%s %d song(%d)\n", __func__, __LINE__, song); + + player_clearerror(); if (song == -1) { if (playlist.length == 0) return 0; if (playlist_state == PLAYLIST_STATE_PLAY) { - return playerSetPause(fd, 0); + ob_trigger_action(OB_ACTION_PAUSE_UNSET); + return 0; } if (playlist.current >= 0 && playlist.current < playlist.length) { i = playlist.current; @@ -892,14 +929,12 @@ int playPlaylist(int fd, int song, int stopOnError) } if (playlist.random) { - if (song == -1 && playlist_state == PLAYLIST_STATE_PLAY) { + if (song == -1) { randomizeOrder(0, playlist.length - 1); } else { if (song >= 0) for (i = 0; song != playlist.order[i]; i++) ; - if (playlist_state == PLAYLIST_STATE_STOP) { - playlist.current = 0; - } + playlist.current = 0; swapOrder(i, playlist.current); i = playlist.current; } @@ -908,6 +943,7 @@ int playPlaylist(int fd, int song, int stopOnError) playlist_stopOnError = stopOnError; playlist_errorCount = 0; + ERROR(__FILE__ ": %d current:%d\n", __LINE__, playlist.current); return playPlaylistOrderNumber(fd, i); } @@ -924,7 +960,7 @@ int playPlaylistById(int fd, int id, int stopOnError) static void syncCurrentPlayerDecodeMetadata(void) { - Song *songPlayer = playerCurrentDecodeSong(); + Song *songPlayer = song_at(playlist.current); Song *song; int songNum; char path_max_tmp[MPD_PATH_MAX]; @@ -953,72 +989,31 @@ void syncPlayerAndPlaylist(void) { if (playlist_state != PLAYLIST_STATE_PLAY) return; - - if (getPlayerState() == PLAYER_STATE_STOP) - playPlaylistIfPlayerStopped(); - else - syncPlaylistWithQueue(!playlist_queueError); - - syncCurrentPlayerDecodeMetadata(); -} - -static int currentSongInPlaylist(int fd) -{ - if (playlist_state != PLAYLIST_STATE_PLAY) - return 0; - - playlist_stopOnError = 0; - - syncPlaylistWithQueue(0); - - if (playlist.current >= 0 && playlist.current < playlist.length) { - return playPlaylistOrderNumber(fd, playlist.current); - } else - return stopPlaylist(fd); + syncPlaylistWithQueue(); + /* DEBUG("queued:%d current:%d\n", playlist.queued, playlist.current); */ + if (playlist_state == PLAYLIST_STATE_PLAY && + playlist.queued >= 0 && + playlist.queued != playlist.current && + ob_synced() && + dc.state == DC_STATE_STOP && + ob_get_state() != OB_STATE_PAUSE) { + dc_trigger_action(DC_ACTION_START, 0); + } } int nextSongInPlaylist(int fd) { + int next; if (playlist_state != PLAYLIST_STATE_PLAY) return 0; - - syncPlaylistWithQueue(0); - playlist_stopOnError = 0; - - if (playlist.current < playlist.length - 1) { - return playPlaylistOrderNumber(fd, playlist.current + 1); - } else if (playlist.length && playlist.repeat) { - if (playlist.random) - randomizeOrder(0, playlist.length - 1); - return playPlaylistOrderNumber(fd, 0); - } else { + next = next_order_num(); + if (next < 0) { + /* we were already at last song w/o repeat: */ incrPlaylistCurrent(); return stopPlaylist(fd); } -} - -void playPlaylistIfPlayerStopped(void) -{ - if (getPlayerState() == PLAYER_STATE_STOP) { - int error = getPlayerError(); - - if (error == PLAYER_ERROR_NOERROR) - playlist_errorCount = 0; - else - playlist_errorCount++; - - if (playlist_state == PLAYLIST_STATE_PLAY - && ((playlist_stopOnError && error != PLAYER_ERROR_NOERROR) - || error == PLAYER_ERROR_AUDIO - || error == PLAYER_ERROR_SYSTEM - || playlist_errorCount >= playlist.length)) { - stopPlaylist(STDERR_FILENO); - } else if (playlist_noGoToNext) - currentSongInPlaylist(STDERR_FILENO); - else - nextSongInPlaylist(STDERR_FILENO); - } + return playPlaylistOrderNumber(fd, next); } int getPlaylistRepeatStatus(void) @@ -1040,7 +1035,7 @@ int setPlaylistRepeatStatus(int fd, int status) if (playlist_state == PLAYLIST_STATE_PLAY) { if (playlist.repeat && !status && playlist.queued == 0) - clearPlayerQueueLocked(); + clear_queue(); } playlist.repeat = status; @@ -1090,7 +1085,7 @@ int moveSongInPlaylist(int fd, int from, int to) queuedSong = playlist.order[playlist.queued]; if (queuedSong == from || queuedSong == to || currentSong == from || currentSong == to) - clearPlayerQueueLocked(); + clear_queue(); } tmpSong = playlist.songs[from]; @@ -1120,28 +1115,16 @@ int moveSongInPlaylist(int fd, int from, int to) playlist.order[i] = to; } } - } - else - { - if (playlist.current == from) + } else { + if (playlist.current == from) { playlist.current = to; - else if (playlist.current > from && playlist.current <= to) { + } else if (playlist.current > from && playlist.current <= to) { playlist.current--; } else if (playlist.current >= to && playlist.current < from) { playlist.current++; } - - /* this first if statement isn't necessary since the queue - * would have been cleared out if queued == from */ - if (playlist.queued == from) - playlist.queued = to; - else if (playlist.queued > from && playlist.queued <= to) { - playlist.queued--; - } else if (playlist.queued>= to && playlist.queued < from) { - playlist.queued++; - } } - + queueNextSongInPlaylist(); incrPlaylistVersion(); return 0; @@ -1157,20 +1140,19 @@ int moveSongInPlaylistById(int fd, int id1, int to) static void orderPlaylist(void) { int i; + int queued_is_current = (playlist.queued == playlist.current); + if (!queued_is_current && + playlist_state == PLAYLIST_STATE_PLAY && + playlist.queued >= 0) + clear_queue(); if (playlist.current >= 0 && playlist.current < playlist.length) { playlist.current = playlist.order[playlist.current]; + if (queued_is_current) + playlist.queued = playlist.current; } - - if (playlist_state == PLAYLIST_STATE_PLAY) { - if (playlist.queued >= 0) - clearPlayerQueueLocked(); - } - - for (i = 0; i < playlist.length; i++) { + for (i = 0; i < playlist.length; i++) playlist.order[i] = i; - } - } static void swapOrder(int a, int b) @@ -1184,13 +1166,16 @@ static void randomizeOrder(int start, int end) { int i; int ri; + int queued_is_current = (playlist.queued == playlist.current); DEBUG("playlist: randomize from %i to %i\n", start, end); + DEBUG("%s:%d current: %d\n", __func__, __LINE__, playlist.current); - if (playlist_state == PLAYLIST_STATE_PLAY) { - if (playlist.queued >= start && playlist.queued <= end) - clearPlayerQueueLocked(); - } + if (!queued_is_current && + playlist_state == PLAYLIST_STATE_PLAY && + playlist.queued >= start && + playlist.queued <= end) + clear_queue(); for (i = start; i <= end; i++) { ri = random() % (end - start + 1) + start; @@ -1200,6 +1185,9 @@ static void randomizeOrder(int start, int end) playlist.current = ri; swapOrder(i, ri); } + if (queued_is_current) + playlist.queued = playlist.current; + DEBUG("%s:%d current: %d\n", __func__, __LINE__, playlist.current); } int setPlaylistRandomStatus(int fd, int status) @@ -1214,19 +1202,15 @@ int setPlaylistRandomStatus(int fd, int status) playlist.random = status; if (status != statusWas) { - if (playlist.random) { - /*if(playlist_state==PLAYLIST_STATE_PLAY) { - randomizeOrder(playlist.current+1, - playlist.length-1); - } - else */ randomizeOrder(0, playlist.length - 1); - if (playlist.current >= 0 && - playlist.current < playlist.length) { - swapOrder(playlist.current, 0); - playlist.current = 0; - } - } else + if (playlist.random) + randomizeOrder(0, playlist.length - 1); + else orderPlaylist(); + if (playlist_state == PLAYLIST_STATE_PLAY) { + queueNextSongInPlaylist(); + DEBUG("%s:%d queued: %d\n", + __func__,__LINE__,playlist.queued); + } } return 0; @@ -1236,36 +1220,40 @@ int previousSongInPlaylist(int fd) { static time_t lastTime; time_t diff = time(NULL) - lastTime; + int prev_order_num; lastTime += diff; if (playlist_state != PLAYLIST_STATE_PLAY) return 0; - syncPlaylistWithQueue(0); + syncPlaylistWithQueue(); - if (diff && getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) { - return playPlaylistOrderNumber(fd, playlist.current); + if (diff && ob_get_elapsed_time() > PLAYLIST_PREV_UNLESS_ELAPSED) { + prev_order_num = playlist.current; } else { - if (playlist.current > 0) { - return playPlaylistOrderNumber(fd, - playlist.current - 1); - } else if (playlist.repeat) { - return playPlaylistOrderNumber(fd, playlist.length - 1); - } else { - return playPlaylistOrderNumber(fd, playlist.current); - } + if (playlist.current > 0) + prev_order_num = playlist.current - 1; + else if (playlist.repeat) + prev_order_num = playlist.length - 1; + else + prev_order_num = playlist.current; } + return playPlaylistOrderNumber(fd, prev_order_num); } int shufflePlaylist(int fd) { int i; int ri; + int playing_queued = 0; if (playlist.length > 1) { if (playlist_state == PLAYLIST_STATE_PLAY) { - clearPlayerQueueLocked(); + if (playlist.queued == playlist.current) + playing_queued = 1; + else + clear_queue(); /* put current playing song first */ swapSongs(0, playlist.order[playlist.current]); if (playlist.random) { @@ -1274,6 +1262,8 @@ int shufflePlaylist(int fd) playlist.current = j; } else playlist.current = 0; + if (playing_queued) + playlist.queued = playlist.current; i = 1; } else { i = 0; @@ -1284,8 +1274,9 @@ int shufflePlaylist(int fd) ri = random() % (playlist.length - 1) + 1; swapSongs(i, ri); } - incrPlaylistVersion(); + if (playlist_state == PLAYLIST_STATE_PLAY) + queueNextSongInPlaylist(); } return 0; @@ -1356,6 +1347,7 @@ int savePlaylist(int fd, char *utf8file) int getPlaylistCurrentSong(void) { + DEBUG("%s:%d current: %d\n", __func__, __LINE__, playlist.current); if (playlist.current >= 0 && playlist.current < playlist.length) { return playlist.order[playlist.current]; } @@ -1373,9 +1365,14 @@ int getPlaylistLength(void) return playlist.length; } +/* + * This command will always return 0 regardless of whether or + * not the seek succeeded (it's always been the case, apparently) + */ int seekSongInPlaylist(int fd, int song, float seek_time) { int i = song; + char path[MPD_PATH_MAX]; if (song < 0 || song >= playlist.length) { commandError(fd, ACK_ERROR_NO_EXIST, @@ -1386,22 +1383,42 @@ int seekSongInPlaylist(int fd, int song, float seek_time) if (playlist.random) for (i = 0; song != playlist.order[i]; i++) ; - clearPlayerError(); + player_clearerror(); playlist_stopOnError = 1; playlist_errorCount = 0; - if (playlist_state == PLAYLIST_STATE_PLAY) { - if (playlist.queued >= 0) - clearPlayerQueueLocked(); - } else if (playPlaylistOrderNumber(fd, i) < 0) - return -1; - - if (playlist.current != i) { - if (playPlaylistOrderNumber(fd, i) < 0) - return -1; + if (playlist_state == PLAYLIST_STATE_PLAY && + (playlist.current == i && playlist.queued == i)) { + dc_trigger_action(DC_ACTION_SEEK, seek_time); + if (dc.seek_where != DC_SEEK_MISMATCH) + return 0; + /* + * if near end of decoding can cause seek to fail (since we're + * already on another song) (leading to DC_SEEK_MISMATCH), + * so fall through to restarting the decoder below. + */ } - return playerSeek(fd, playlist.songs[playlist.order[i]], seek_time); + DEBUG("playlist: seek %i:\"%s\"\n", i, get_song_url(path, song_at(i))); + playlist_state = PLAYLIST_STATE_PLAY; + DEBUG("%s:%d\n", __func__, __LINE__); + dc_trigger_action(DC_ACTION_STOP, 0); + DEBUG("%s:%d\n", __func__, __LINE__); + ob_drop_audio(OB_DROP_DECODED); + DEBUG("%s:%d\n", __func__, __LINE__); + queue_song_locked(i); + DEBUG("%s:%d\n", __func__, __LINE__); + ob_drop_audio(OB_DROP_PLAYING); + DEBUG("%s:%d\n", __func__, __LINE__); + DEBUG("seek_time: %f\n", seek_time); + dc_trigger_action(DC_ACTION_SEEK, seek_time); + DEBUG("%s:%d\n", __func__, __LINE__); + if (dc.seek_where >= 0) { + playlist.current = i; + DEBUG("%s:%d\n", __func__, __LINE__); + ob_wait_sync(); + } + return 0; } int seekSongInPlaylistById(int fd, int id, float seek_time) @@ -1538,4 +1555,7 @@ int valid_playlist_name(int err_fd, const char *utf8path) return 1; } - +int playlist_playing(void) +{ + return (playlist_state == PLAYLIST_STATE_PLAY); +} diff --git a/src/playlist.h b/src/playlist.h index db5243ba9..2ba28f9e4 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -24,21 +24,6 @@ #define PLAYLIST_FILE_SUFFIX "m3u" #define PLAYLIST_COMMENT '#' -typedef struct _Playlist { - Song **songs; - /* holds version a song was modified on */ - mpd_uint32 *songMod; - int *order; - int *positionToId; - int *idToPosition; - int length; - int current; - int queued; - int repeat; - int random; - mpd_uint32 version; -} Playlist; - extern int playlist_saveAbsolutePaths; extern int playlist_max_length; @@ -71,6 +56,12 @@ int playlistInfo(int fd, int song); int playlistId(int fd, int song); +Song *playlist_queued_song(void); + +void playlist_queue_next(void); + +int playlist_playing(void); + int stopPlaylist(int fd); int playPlaylist(int fd, int song, int stopOnError); @@ -119,8 +110,6 @@ int getPlaylistLength(void); unsigned long getPlaylistVersion(void); -void playPlaylistIfPlayerStopped(void); - int seekSongInPlaylist(int fd, int song, float seek_time); int seekSongInPlaylistById(int fd, int id, float seek_time); diff --git a/src/ringbuf.h b/src/ringbuf.h index d4baffdbd..43ba83a7c 100644 --- a/src/ringbuf.h +++ b/src/ringbuf.h @@ -175,7 +175,8 @@ void ringbuf_reset(struct ringbuf * rb); /** * Reset the write pointer to the read pointer, making an empty buffer. * - * This should only be called by the writer + * This should only be called by the writer and only very carefully + * (it is subject to race conditions with vectors or peek) * * @param rb a pointer to the ringbuffer structure. */ @@ -184,7 +185,8 @@ void ringbuf_writer_reset(struct ringbuf * rb); /** * Reset the read pointer to the write pointer, making an empty buffer. * - * This should only be called by the reader + * This should only be called by the writer and only very carefully + * (it is subject to race conditions with vectors or peek) * * @param rb a pointer to the ringbuffer structure. */ diff --git a/src/sig_handlers.c b/src/sig_handlers.c index 88159f24f..0ad35b779 100644 --- a/src/sig_handlers.c +++ b/src/sig_handlers.c @@ -23,7 +23,6 @@ #include "command.h" #include "signal_check.h" #include "log.h" -#include "player.h" #include "decode.h" int handlePendingSignals(void) diff --git a/src/stats.c b/src/stats.c index 254595472..39ba39e9a 100644 --- a/src/stats.c +++ b/src/stats.c @@ -20,7 +20,7 @@ #include "directory.h" #include "myfprintf.h" -#include "player.h" +#include "outputBuffer.h" #include "tagTracker.h" #include "os_compat.h" @@ -39,7 +39,7 @@ int printStats(int fd) fdprintf(fd, "songs: %i\n", stats.numberOfSongs); fdprintf(fd, "uptime: %li\n", time(NULL) - stats.daemonStart); fdprintf(fd, "playtime: %li\n", - (long)(getPlayerTotalPlayTime() + 0.5)); + (long)(ob_get_total_time() + 0.5)); fdprintf(fd, "db_playtime: %li\n", stats.dbPlayTime); fdprintf(fd, "db_update: %li\n", getDbModTime()); return 0; diff --git a/src/volume.c b/src/volume.c index 5b720651a..0da7d0360 100644 --- a/src/volume.c +++ b/src/volume.c @@ -20,7 +20,6 @@ #include "command.h" #include "conf.h" #include "log.h" -#include "player.h" #include "gcc.h" #include "utils.h" #include "ack.h" @@ -492,7 +491,7 @@ static int changeSoftwareVolume(int fd, int change, int rel) new = 1000.0 * (exp(new / 25.0) - 1) / (54.5981500331F - 1) + 0.5; - setPlayerSoftwareVolume(new); + ob_set_sw_volume(new); return 0; } -- cgit v1.2.3 From 3befb84a6a7a95de7e0e94c2f0bd9936ecb60668 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 19 Aug 2008 03:01:39 -0700 Subject: log.c: thread-safety for warning log I'm really no fan of the warning log, it's too complex for how little it gets used; but fixing it is another problem. --- src/log.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/log.c b/src/log.c index aac7349d1..2f4c1af0f 100644 --- a/src/log.c +++ b/src/log.c @@ -29,6 +29,7 @@ static unsigned int logLevel = LOG_LEVEL_LOW; static int warningFlushed; static int stdout_mode = 1; static char *warningBuffer; +static pthread_mutex_t warning_buffer_lock = PTHREAD_MUTEX_INITIALIZER; static int out_fd = -1; static int err_fd = -1; static const char *out_filename; @@ -81,7 +82,10 @@ static void do_log(FILE *fp, const char *fmt, va_list args) void flushWarningLog(void) { - char *s = warningBuffer; + char *s; + + pthread_mutex_lock(&warning_buffer_lock); + s = warningBuffer; DEBUG("flushing warning messages\n"); @@ -98,8 +102,8 @@ void flushWarningLog(void) warningBuffer = NULL; } - warningFlushed = 1; + pthread_mutex_unlock(&warning_buffer_lock); DEBUG("done flushing warning messages\n"); } @@ -189,10 +193,14 @@ void WARNING(const char *fmt, ...) { va_list args; va_start(args, fmt); - if (warningFlushed) { + + pthread_mutex_lock(&warning_buffer_lock); + if (warningFlushed) do_log(stderr, fmt, args); - } else + else buffer_warning(fmt, args); + pthread_mutex_unlock(&warning_buffer_lock); + va_end(args); } -- cgit v1.2.3 From 548385ac6cc0bc344762e19117f94258e7ea2251 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 19 Aug 2008 03:31:25 -0700 Subject: fix output buffer deadlock when daemonizing We spawned the output buffer thread before daemonizing in initPlayerData(), which is ultra bad because daemonizes forks and threads are not preserved on exit. Since playerData has been stripped bare by this core-rewrite anyways, move this code into the outputBuffer_* group and drop playerData.[ch] completely I completely forgot to test this :< --- src/Makefile.am | 3 +- src/audio.c | 1 - src/decode.c | 1 - src/inputPlugin.h | 1 - src/inputPlugins/audiofile_plugin.c | 1 - src/inputPlugins/mod_plugin.c | 1 - src/inputPlugins/wavpack_plugin.c | 1 - src/main.c | 4 +- src/outputBuffer.c | 23 +--------- src/outputBuffer.h | 7 +-- src/outputBuffer_config_init.h | 87 +++++++++++++++++++++++++++++++++++++ src/playerData.c | 73 ------------------------------- src/playerData.h | 29 ------------- 13 files changed, 95 insertions(+), 137 deletions(-) create mode 100644 src/outputBuffer_config_init.h delete mode 100644 src/playerData.c delete mode 100644 src/playerData.h diff --git a/src/Makefile.am b/src/Makefile.am index 93943db23..6c52dde1e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -61,6 +61,7 @@ mpd_headers = \ compress.h \ os_compat.h \ outputBuffer.h \ + outputBuffer_config_init.h \ outputBuffer_accessors.h \ outputBuffer_audio.h \ outputBuffer_ob_send.h \ @@ -69,7 +70,6 @@ mpd_headers = \ pcm_utils.h \ permission.h \ player_error.h \ - playerData.h \ playlist.h \ replayGain.h \ ringbuf.h \ @@ -126,7 +126,6 @@ mpd_SOURCES = \ pcm_utils.c \ permission.c \ player_error.c \ - playerData.c \ playlist.c \ replayGain.c \ ringbuf.c \ diff --git a/src/audio.c b/src/audio.c index 9f6bedc33..34b74e6e1 100644 --- a/src/audio.c +++ b/src/audio.c @@ -20,7 +20,6 @@ #include "audioOutput.h" #include "log.h" #include "command.h" -#include "playerData.h" #include "path.h" #include "ack.h" #include "myfprintf.h" diff --git a/src/decode.c b/src/decode.c index 12c1b8746..150a0a58b 100644 --- a/src/decode.c +++ b/src/decode.c @@ -20,7 +20,6 @@ #include "outputBuffer.h" #include "player_error.h" #include "playlist.h" -#include "playerData.h" #include "pcm_utils.h" #include "path.h" #include "log.h" diff --git a/src/inputPlugin.h b/src/inputPlugin.h index 169781931..61cef9bef 100644 --- a/src/inputPlugin.h +++ b/src/inputPlugin.h @@ -21,7 +21,6 @@ #include "inputStream.h" #include "outputBuffer.h" -#include "playerData.h" /* valid values for streamTypes in the InputPlugin struct: */ #define INPUT_PLUGIN_STREAM_FILE 0x01 diff --git a/src/inputPlugins/audiofile_plugin.c b/src/inputPlugins/audiofile_plugin.c index 8178972ff..114a87786 100644 --- a/src/inputPlugins/audiofile_plugin.c +++ b/src/inputPlugins/audiofile_plugin.c @@ -26,7 +26,6 @@ #include "../audio.h" #include "../log.h" #include "../pcm_utils.h" -#include "../playerData.h" #include "../os_compat.h" #include diff --git a/src/inputPlugins/mod_plugin.c b/src/inputPlugins/mod_plugin.c index cfa7d6e3d..9f9da6273 100644 --- a/src/inputPlugins/mod_plugin.c +++ b/src/inputPlugins/mod_plugin.c @@ -24,7 +24,6 @@ #include "../audio.h" #include "../log.h" #include "../pcm_utils.h" -#include "../playerData.h" #include "../os_compat.h" #include diff --git a/src/inputPlugins/wavpack_plugin.c b/src/inputPlugins/wavpack_plugin.c index ef02712d7..2538be326 100644 --- a/src/inputPlugins/wavpack_plugin.c +++ b/src/inputPlugins/wavpack_plugin.c @@ -26,7 +26,6 @@ #include "../audio.h" #include "../log.h" #include "../pcm_utils.h" -#include "../playerData.h" #include "../outputBuffer.h" #include "../os_compat.h" #include "../path.h" diff --git a/src/main.c b/src/main.c index e02085294..239296501 100644 --- a/src/main.c +++ b/src/main.c @@ -23,7 +23,6 @@ #include "listen.h" #include "conf.h" #include "path.h" -#include "playerData.h" #include "stats.h" #include "sig_handlers.h" #include "audio.h" @@ -412,7 +411,7 @@ int main(int argc, char *argv[]) openDB(&options, argv[0]); initCommands(); - initPlayerData(); + config_output_buffer(); initAudioConfig(); initAudioDriver(); initVolume(); @@ -424,6 +423,7 @@ int main(int argc, char *argv[]) daemonize(&options); init_main_notify(); + init_output_buffer(); setup_log_output(options.stdOutput); initSigHandlers(); diff --git a/src/outputBuffer.c b/src/outputBuffer.c index 26e6077f2..26d31b2cd 100644 --- a/src/outputBuffer.c +++ b/src/outputBuffer.c @@ -78,12 +78,6 @@ static struct output_buffer ob; #include "outputBuffer_xfade.h" #include "outputBuffer_accessors.h" -static void ob_free(void) -{ - free(ob.chunks); - ringbuf_free(ob.index); -} - static enum action_status ob_do_stop(void); static void stop_playback(void) { @@ -497,22 +491,7 @@ out: return NULL; } -void ob_init(size_t size) -{ - pthread_attr_t attr; - assert(size > 0 && !ob.index && !ob.chunks); - ob.index = ringbuf_create(size); - ob.chunks = xcalloc(ob.index->size, sizeof(struct ob_chunk)); - ob.preseek_len = xmalloc(ob.index->size * sizeof(ob.chunks[0].len)); - ob.nr_bpp = 1; - ob.state = OB_STATE_STOP; - - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - if (pthread_create(&ob.thread, &attr, ob_task, NULL)) - FATAL("Failed to spawn player task: %s\n", strerror(errno)); - atexit(ob_free); -} +#include "outputBuffer_config_init.h" void ob_seek_start(void) { diff --git a/src/outputBuffer.h b/src/outputBuffer.h index 833a29e77..187846ef3 100644 --- a/src/outputBuffer.h +++ b/src/outputBuffer.h @@ -47,7 +47,7 @@ enum ob_action { /* 1020 bytes since its divisible for 8, 16, 24, and 32-bit audio */ #define CHUNK_SIZE 1020 -void ob_init(size_t size); +void ob_flush(void); enum ob_drop_type { OB_DROP_DECODED, OB_DROP_PLAYING }; void ob_drop_audio(enum ob_drop_type type); @@ -101,8 +101,9 @@ AudioFormat *ob_audio_format(void); void ob_advance_sequence(void); -void ob_wait_sync(void); - void ob_flush(void); +void config_output_buffer(void); +void init_output_buffer(void); + #endif diff --git a/src/outputBuffer_config_init.h b/src/outputBuffer_config_init.h new file mode 100644 index 000000000..ba5b7f137 --- /dev/null +++ b/src/outputBuffer_config_init.h @@ -0,0 +1,87 @@ +/* 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 "conf.h" +#include "log.h" +#include "utils.h" + +#define DEFAULT_BUFFER_SIZE 2048 +#define DEFAULT_BUFFER_BEFORE_PLAY 10 + + +void config_output_buffer(void) +{ + float perc = DEFAULT_BUFFER_BEFORE_PLAY; + char *test; + size_t buffer_size = DEFAULT_BUFFER_SIZE; + ConfigParam *param; + unsigned int buffered_before_play; + unsigned int buffered_chunks; + + if ((param = getConfigParam(CONF_AUDIO_BUFFER_SIZE))) { + buffer_size = strtol(param->value, &test, 10); + if (*test != '\0' || buffer_size <= 0) + FATAL(CONF_AUDIO_BUFFER_SIZE + " \"%s\" is not a positive integer, " + "line %i\n", param->value, param->line); + } + + buffer_size *= 1024; + buffered_chunks = buffer_size / CHUNK_SIZE; + + if (buffered_chunks >= 1 << 15) + FATAL("buffer size \"%li\" is too big\n", (long)buffer_size); + + if ((param = getConfigParam(CONF_BUFFER_BEFORE_PLAY))) { + perc = strtod(param->value, &test); + if (*test != '%' || perc < 0 || perc > 100) + FATAL(CONF_BUFFER_BEFORE_PLAY + " \"%s\" is not a positive " + "percentage and less than 100 percent, line %i" + "\n", param->value, param->line); + } + + buffered_before_play = (perc / 100) * buffered_chunks; + if (buffered_before_play > buffered_chunks) + buffered_before_play = buffered_chunks; + ob.nr_bpp = buffered_before_play; + + assert(buffered_chunks > 0 && !ob.index && !ob.chunks); + ob.index = ringbuf_create(buffered_chunks); + ob.chunks = xcalloc(ob.index->size, sizeof(struct ob_chunk)); + ob.preseek_len = xmalloc(ob.index->size * sizeof(ob.chunks[0].len)); + ob.state = OB_STATE_STOP; +} + +static void ob_free(void) +{ + free(ob.chunks); + ringbuf_free(ob.index); +} + +void init_output_buffer(void) +{ + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (pthread_create(&ob.thread, &attr, ob_task, NULL)) + FATAL("Failed to spawn player task: %s\n", strerror(errno)); + + atexit(ob_free); +} diff --git a/src/playerData.c b/src/playerData.c deleted file mode 100644 index 113a71f35..000000000 --- a/src/playerData.c +++ /dev/null @@ -1,73 +0,0 @@ -/* 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 "playerData.h" -#include "conf.h" -#include "log.h" -#include "utils.h" - -#define DEFAULT_BUFFER_SIZE 2048 -#define DEFAULT_BUFFER_BEFORE_PLAY 10 - -void initPlayerData(void) -{ - float perc = DEFAULT_BUFFER_BEFORE_PLAY; - char *test; - size_t bufferSize = DEFAULT_BUFFER_SIZE; - unsigned int buffered_chunks; - ConfigParam *param; - unsigned int buffered_before_play; - - param = getConfigParam(CONF_AUDIO_BUFFER_SIZE); - - if (param) { - bufferSize = strtol(param->value, &test, 10); - if (*test != '\0' || bufferSize <= 0) { - FATAL("buffer size \"%s\" is not a positive integer, " - "line %i\n", param->value, param->line); - } - } - - bufferSize *= 1024; - - buffered_chunks = bufferSize / CHUNK_SIZE; - - if (buffered_chunks >= 1 << 15) { - FATAL("buffer size \"%li\" is too big\n", (long)bufferSize); - } - - param = getConfigParam(CONF_BUFFER_BEFORE_PLAY); - - if (param) { - perc = strtod(param->value, &test); - if (*test != '%' || perc < 0 || perc > 100) { - FATAL("buffered before play \"%s\" is not a positive " - "percentage and less than 100 percent, line %i" - "\n", param->value, param->line); - } - } - - buffered_before_play = (perc / 100) * buffered_chunks; - if (buffered_before_play > buffered_chunks) { - buffered_before_play = buffered_chunks; - } - - ob_init(buffered_chunks); -} - - diff --git a/src/playerData.h b/src/playerData.h deleted file mode 100644 index ea8ea13e8..000000000 --- a/src/playerData.h +++ /dev/null @@ -1,29 +0,0 @@ -/* 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 - */ - -#ifndef PLAYER_DATA_H -#define PLAYER_DATA_H - -#include "audio.h" -#include "decode.h" -#include "mpd_types.h" -#include "outputBuffer.h" - -void initPlayerData(void); - -#endif -- cgit v1.2.3 From 508ae1c18d3bdc99a1bb06181762e5ec859cf072 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 19 Aug 2008 04:19:49 -0700 Subject: Remove ob_wait_sync and cleanup triggering in playlist ob_wait_sync was a gross hack anyways. We are one step closer to being able to trigger actions in our worker threads asynchronously. Just need to make input (file opening) in decoder happen _after_ our decoder returns an ACK. --- src/outputBuffer.c | 45 +++++++++++++--------------------- src/outputBuffer.h | 1 + src/playlist.c | 72 +++++++++++++++++++++--------------------------------- 3 files changed, 46 insertions(+), 72 deletions(-) diff --git a/src/outputBuffer.c b/src/outputBuffer.c index 26d31b2cd..aca84d07f 100644 --- a/src/outputBuffer.c +++ b/src/outputBuffer.c @@ -207,18 +207,24 @@ static void ob_seq_player_set(unsigned int seq_num) cond_leave(&ob_seq_cond); } +static enum action_status ob_do_reset(void) +{ + assert(pthread_equal(pthread_self(), ob.thread)); + ob.elapsed_time = 0; + ob.total_time = 0; + reader_reset_buffer(); + ob.xfade_state = XFADE_DISABLED; + ob_seq_player_set((unsigned int)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) { - ob.elapsed_time = 0; - reader_reset_buffer(); - ob.xfade_state = XFADE_DISABLED; - close_audio_devices(); - ob.state = OB_STATE_STOP; - ob_seq_player_set((unsigned int)ob.seq_decoder); - } - return AS_INPROGRESS; + if (ob.state == OB_STATE_STOP) + return AS_INPROGRESS; + ob.state = OB_STATE_STOP; + return ob_do_reset(); } /* @@ -299,6 +305,7 @@ static enum action_status ob_take_action(void) } break; case OB_ACTION_STOP: return ob_do_stop(); + case OB_ACTION_RESET: return ob_do_reset(); case OB_ACTION_QUIT: close_audio_devices(); ob.state = OB_STATE_QUIT; @@ -350,24 +357,6 @@ void ob_drop_audio(enum ob_drop_type type) /* DEBUG("done dropping %u\n", ob.seq_drop); */ } -void ob_wait_sync(void) -{ - assert(!pthread_equal(pthread_self(), dc.thread)); - assert(!pthread_equal(pthread_self(), ob.thread)); - - /* DEBUG(__FILE__": %s %d\n", __func__, __LINE__); */ - cond_enter(&ob_seq_cond); - while (ob.seq_player != ob.seq_decoder) { - /* DEBUG("%s %d seq_player:%u seq_decoder:%u\n", */ - /* __func__, __LINE__, ob.seq_player, ob.seq_decoder); */ - cond_wait(&ob_seq_cond); - /* DEBUG("%s %d seq_player:%u seq_decoder:%u\n", */ - /* __func__, __LINE__, ob.seq_player, ob.seq_decoder); */ - } - cond_leave(&ob_seq_cond); - /* DEBUG(__FILE__": %s %d\n", __func__, __LINE__); */ -} - /* call this exactly once before decoding each song */ void ob_advance_sequence(void) { @@ -527,7 +516,7 @@ void ob_flush(void) case OB_STATE_SEEK: assert(0); case OB_STATE_PLAY: - case OB_STATE_PAUSE: /* should we assert if paused? */ + case OB_STATE_PAUSE: ringbuf_write_advance(ob.index, 1); break; case OB_STATE_STOP: diff --git a/src/outputBuffer.h b/src/outputBuffer.h index 187846ef3..15e46da60 100644 --- a/src/outputBuffer.h +++ b/src/outputBuffer.h @@ -40,6 +40,7 @@ enum ob_action { OB_ACTION_PAUSE_SET, OB_ACTION_PAUSE_UNSET, OB_ACTION_PAUSE_FLIP, + OB_ACTION_RESET, OB_ACTION_STOP, OB_ACTION_QUIT }; diff --git a/src/playlist.c b/src/playlist.c index abe151488..9839cc346 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -93,7 +93,7 @@ static pthread_mutex_t queue_lock = PTHREAD_MUTEX_INITIALIZER; int playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; static void swapOrder(int a, int b); -static int playPlaylistOrderNumber(int fd, int orderNum); +static int play_order_num(int fd, int order_num, float seek_time); static void randomizeOrder(int start, int end); static void incrPlaylistVersion(void) @@ -762,7 +762,7 @@ int deleteFromPlaylist(int fd, int song) { int i; int songOrder; - int stopped_current = 0; + int stop_current = 0; int prev_queued = playlist.queued; if (song < 0 || song >= playlist.length) { @@ -813,25 +813,20 @@ int deleteFromPlaylist(int fd, int song) /* DEBUG("current: %d, songOrder: %d\n", playlist.current, songOrder); */ /* DEBUG("playlist_state: %d\n", playlist_state); */ if (playlist_state != PLAYLIST_STATE_STOP - && playlist.current == songOrder) { - dc_trigger_action(DC_ACTION_STOP, 0); - /* drop all audio, but don't actually close the device */ - ob_drop_audio(OB_DROP_DECODED); - ob_drop_audio(OB_DROP_PLAYING); - stopped_current = 1; - } + && playlist.current == songOrder) + stop_current = 1; if (playlist.current > songOrder) { playlist.current--; } else if (playlist.current >= playlist.length) { incrPlaylistCurrent(); } - if (stopped_current) { + if (stop_current) { /* DEBUG(__FILE__": %d\n", __LINE__); */ if (playlist.current >= 0) - playPlaylistOrderNumber(STDERR_FILENO,playlist.current); + play_order_num(fd, playlist.current, 0); else - playlist_state = PLAYLIST_STATE_STOP; + stopPlaylist(fd); } else { /* DEBUG(__FILE__": %d\n", __LINE__); */ queueNextSongInPlaylist(); @@ -881,23 +876,26 @@ int stopPlaylist(int fd) return 0; } -static int playPlaylistOrderNumber(int fd, int orderNum) +static int play_order_num(int fd, int order_num, float seek_time) { char path[MPD_PATH_MAX]; + enum dc_action action = seek_time ? DC_ACTION_SEEK : DC_ACTION_START; playlist_state = PLAYLIST_STATE_PLAY; - assert(orderNum >= 0); + assert(order_num >= 0); + assert(seek_time >= 0); - DEBUG("playlist: play %i:\"%s\"\n", orderNum, - get_song_url(path, song_at(orderNum))); + DEBUG("playlist: play %i:\"%s\"\n", order_num, + get_song_url(path, song_at(order_num))); dc_trigger_action(DC_ACTION_STOP, 0); - ob_drop_audio(OB_DROP_DECODED); - queue_song_locked(orderNum); - ob_drop_audio(OB_DROP_PLAYING); - ob_trigger_action(OB_ACTION_PAUSE_UNSET); - dc_trigger_action(DC_ACTION_START, 0); - ob_wait_sync(); /* HACK: REMOVE */ - playlist.current = orderNum; + queue_song_locked(order_num); + + ob_trigger_action(OB_ACTION_RESET); + + dc_trigger_action(action, seek_time); + if (dc.seek_where >= 0) + playlist.current = order_num; + return 0; } @@ -944,7 +942,8 @@ int playPlaylist(int fd, int song, int stopOnError) playlist_errorCount = 0; ERROR(__FILE__ ": %d current:%d\n", __LINE__, playlist.current); - return playPlaylistOrderNumber(fd, i); + ob_trigger_action(OB_ACTION_PAUSE_UNSET); + return play_order_num(fd, i, 0); } int playPlaylistById(int fd, int id, int stopOnError) @@ -1013,7 +1012,8 @@ int nextSongInPlaylist(int fd) incrPlaylistCurrent(); return stopPlaylist(fd); } - return playPlaylistOrderNumber(fd, next); + ob_trigger_action(OB_ACTION_PAUSE_UNSET); + return play_order_num(fd, next, 0); } int getPlaylistRepeatStatus(void) @@ -1239,7 +1239,8 @@ int previousSongInPlaylist(int fd) else prev_order_num = playlist.current; } - return playPlaylistOrderNumber(fd, prev_order_num); + ob_trigger_action(OB_ACTION_PAUSE_UNSET); + return play_order_num(fd, prev_order_num, 0); } int shufflePlaylist(int fd) @@ -1400,24 +1401,7 @@ int seekSongInPlaylist(int fd, int song, float seek_time) } DEBUG("playlist: seek %i:\"%s\"\n", i, get_song_url(path, song_at(i))); - playlist_state = PLAYLIST_STATE_PLAY; - DEBUG("%s:%d\n", __func__, __LINE__); - dc_trigger_action(DC_ACTION_STOP, 0); - DEBUG("%s:%d\n", __func__, __LINE__); - ob_drop_audio(OB_DROP_DECODED); - DEBUG("%s:%d\n", __func__, __LINE__); - queue_song_locked(i); - DEBUG("%s:%d\n", __func__, __LINE__); - ob_drop_audio(OB_DROP_PLAYING); - DEBUG("%s:%d\n", __func__, __LINE__); - DEBUG("seek_time: %f\n", seek_time); - dc_trigger_action(DC_ACTION_SEEK, seek_time); - DEBUG("%s:%d\n", __func__, __LINE__); - if (dc.seek_where >= 0) { - playlist.current = i; - DEBUG("%s:%d\n", __func__, __LINE__); - ob_wait_sync(); - } + play_order_num(fd, i, seek_time); return 0; } -- cgit v1.2.3