aboutsummaryrefslogtreecommitdiffstats
path: root/src/player_thread.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/player_thread.c466
1 files changed, 466 insertions, 0 deletions
diff --git a/src/player_thread.c b/src/player_thread.c
new file mode 100644
index 000000000..0937fb3ca
--- /dev/null
+++ b/src/player_thread.c
@@ -0,0 +1,466 @@
+/* the Music Player Daemon (MPD)
+ * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "player_thread.h"
+#include "playerData.h"
+#include "audio.h"
+#include "pcm_utils.h"
+#include "path.h"
+#include "log.h"
+#include "main_notify.h"
+#include "crossfade.h"
+
+enum xfade_state {
+ XFADE_DISABLED = -1,
+ XFADE_UNKNOWN = 0,
+ XFADE_ENABLED = 1
+};
+
+static void dc_command_wait(void)
+{
+ while (dc.command != DECODE_COMMAND_NONE) {
+ notify_signal(&dc.notify);
+ notify_wait(&pc.notify);
+ }
+}
+
+static void dc_command(enum decoder_command cmd)
+{
+ dc.command = cmd;
+ dc_command_wait();
+}
+
+static void stopDecode(void)
+{
+ if (dc.command == DECODE_COMMAND_START ||
+ dc.state != DECODE_STATE_STOP)
+ dc_command(DECODE_COMMAND_STOP);
+}
+
+static void quitDecode(void)
+{
+ stopDecode();
+ pc.state = PLAYER_STATE_STOP;
+ dc.command = DECODE_COMMAND_NONE;
+ pc.command = PLAYER_COMMAND_NONE;
+ wakeup_main_task();
+}
+
+static int waitOnDecode(int *decodeWaitedOn)
+{
+ while (dc.command == DECODE_COMMAND_START) {
+ notify_signal(&dc.notify);
+ notify_wait(&pc.notify);
+ }
+
+ if (dc.error != DECODE_ERROR_NOERROR) {
+ pc.errored_song = dc.next_song;
+ pc.error = PLAYER_ERROR_FILE;
+ quitDecode();
+ 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 ret = -1;
+
+ if (dc.state == DECODE_STATE_STOP ||
+ dc.error != DECODE_ERROR_NOERROR ||
+ dc.current_song != pc.next_song) {
+ stopDecode();
+ *next = -1;
+ ob_clear();
+ dc.next_song = pc.next_song;
+ dc.error = DECODE_ERROR_NOERROR;
+ dc.command = DECODE_COMMAND_START;
+ 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_command(DECODE_COMMAND_SEEK);
+ if (!dc.seekError) {
+ pc.elapsedTime = dc.seekWhere;
+ ret = 0;
+ }
+ }
+
+ player_command_finished();
+
+ return ret;
+}
+
+static void processDecodeInput(int *pause_r, unsigned int *bbp_r,
+ enum xfade_state *do_xfade_r,
+ int *decodeWaitedOn_r,
+ int *next_r)
+{
+ switch (pc.command) {
+ case PLAYER_COMMAND_NONE:
+ case PLAYER_COMMAND_PLAY:
+ case PLAYER_COMMAND_STOP:
+ case PLAYER_COMMAND_CLOSE_AUDIO:
+ break;
+
+ case PLAYER_COMMAND_LOCK_QUEUE:
+ pc.queueLockState = PLAYER_QUEUE_LOCKED;
+ player_command_finished();
+ break;
+
+ case PLAYER_COMMAND_UNLOCK_QUEUE:
+ pc.queueLockState = PLAYER_QUEUE_UNLOCKED;
+ player_command_finished();
+ break;
+
+ case PLAYER_COMMAND_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 = dc.next_song;
+ pc.error = PLAYER_ERROR_AUDIO;
+ ERROR("problems opening audio device "
+ "while playing \"%s\"\n",
+ get_song_url(tmp, dc.next_song));
+ *pause_r = -1;
+ }
+ }
+ player_command_finished();
+ if (*pause_r == -1) {
+ *pause_r = 1;
+ } else if (*pause_r) {
+ dropBufferedAudio();
+ closeAudioDevice();
+ }
+ break;
+
+ case PLAYER_COMMAND_SEEK:
+ dropBufferedAudio();
+ if (decodeSeek(decodeWaitedOn_r, next_r) == 0) {
+ *do_xfade_r = XFADE_UNKNOWN;
+ *bbp_r = 0;
+ }
+ break;
+ }
+}
+
+static int playChunk(ob_chunk * chunk,
+ const AudioFormat * format, double sizeToTime)
+{
+ 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;
+ player_command_finished();
+
+ while (1) {
+ processDecodeInput(&do_pause, &bbp, &do_xfade,
+ &decodeWaitedOn, &next);
+ if (pc.command == PLAYER_COMMAND_STOP) {
+ dropBufferedAudio();
+ break;
+ }
+
+ if (buffering) {
+ if (ob_available() < bbp) {
+ /* not enough decoded buffer space yet */
+ notify_wait(&pc.notify);
+ 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 = dc.next_song;
+ pc.error = PLAYER_ERROR_AUDIO;
+ ERROR("problems opening audio device "
+ "while playing \"%s\"\n",
+ get_song_url(tmp, dc.next_song));
+ break;
+ }
+
+ 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 = dc.next_song;
+ pc.error = PLAYER_ERROR_FILE;
+ break;
+ }
+ else {
+ /* the decoder is not yet ready; wait
+ some more */
+ notify_wait(&pc.notify);
+ 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.next_song = pc.next_song;
+ dc.error = DECODE_ERROR_NOERROR;
+ dc.command = DECODE_COMMAND_START;
+ pc.queueState = PLAYER_QUEUE_DECODE;
+ wakeup_main_task();
+ notify_signal(&dc.notify);
+ }
+ if (next >= 0 && do_xfade == XFADE_UNKNOWN &&
+ dc.command != DECODE_COMMAND_START &&
+ dc.state != DECODE_STATE_START) {
+ /* enable cross fading in this song? if yes,
+ calculate how many chunks will be required
+ for it */
+ crossFadeChunks =
+ cross_fade_calc(pc.crossFade, dc.totalTime,
+ &(ob.audioFormat),
+ ob.size -
+ buffered_before_play);
+ 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)
+ notify_wait(&pc.notify);
+ 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);
+ cross_fade_apply(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);
+ notify_wait(&pc.notify);
+ continue;
+ }
+ }
+ }
+
+ /* play the current chunk */
+ if (playChunk(beginChunk, &(ob.audioFormat),
+ sizeToTime) < 0)
+ break;
+ ob_shift();
+ notify_signal(&dc.notify);
+ } 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) {
+ notify_wait(&pc.notify);
+ 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.command != DECODE_COMMAND_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
+ */
+static void decode(void)
+{
+ ob_clear();
+
+ dc.next_song = pc.next_song;
+ dc.error = DECODE_ERROR_NOERROR;
+ dc_command(DECODE_COMMAND_START);
+
+ decodeParent();
+}
+
+static void * player_task(mpd_unused void *arg)
+{
+ notify_enter(&pc.notify);
+
+ while (1) {
+ switch (pc.command) {
+ case PLAYER_COMMAND_PLAY:
+ decode();
+ break;
+
+ case PLAYER_COMMAND_STOP:
+ case PLAYER_COMMAND_SEEK:
+ case PLAYER_COMMAND_PAUSE:
+ player_command_finished();
+ break;
+
+ case PLAYER_COMMAND_CLOSE_AUDIO:
+ closeAudioDevice();
+ player_command_finished();
+ break;
+
+ case PLAYER_COMMAND_LOCK_QUEUE:
+ pc.queueLockState = PLAYER_QUEUE_LOCKED;
+ player_command_finished();
+ break;
+
+ case PLAYER_COMMAND_UNLOCK_QUEUE:
+ pc.queueLockState = PLAYER_QUEUE_UNLOCKED;
+ player_command_finished();
+ break;
+
+ case PLAYER_COMMAND_NONE:
+ notify_wait(&pc.notify);
+ break;
+ }
+ }
+ return NULL;
+}
+
+void player_create(void)
+{
+ pthread_attr_t attr;
+ pthread_t player_thread;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ if (pthread_create(&player_thread, &attr, player_task, NULL))
+ FATAL("Failed to spawn player task: %s\n", strerror(errno));
+}