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 (limited to 'src') 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