diff options
Diffstat (limited to '')
-rw-r--r-- | trunk/src/decode.c | 706 |
1 files changed, 706 insertions, 0 deletions
diff --git a/trunk/src/decode.c b/trunk/src/decode.c new file mode 100644 index 000000000..82eba19b9 --- /dev/null +++ b/trunk/src/decode.c @@ -0,0 +1,706 @@ +/* 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 "decode.h" + +#include "player.h" +#include "playerData.h" +#include "utils.h" +#include "pcm_utils.h" +#include "audio.h" +#include "path.h" +#include "log.h" +#include "sig_handlers.h" +#include "ls.h" +#include "utf8.h" + +#include <signal.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/wait.h> +#include <unistd.h> +#include <string.h> + +static int decode_pid; + +void decodeSigHandler(int sig, siginfo_t * si, void *v) +{ + if (sig == SIGCHLD) { + int status; + if (decode_pid == wait3(&status, WNOHANG, NULL)) { + if (WIFSIGNALED(status)) { + if (WTERMSIG(status) != SIGTERM) { + ERROR("decode process died from " + "signal: %i\n", WTERMSIG(status)); + } + } + decode_pid = 0; + getPlayerData()->playerControl.decode_pid = 0; + } + } else if (sig == SIGTERM) { + int pid = decode_pid; + if (pid > 0) { + DEBUG("player (or child) got SIGTERM\n"); + kill(pid, SIGTERM); + } else + DEBUG("decoder (or child) got SIGTERM\n"); + exit(EXIT_SUCCESS); + } +} + +static void stopDecode(DecoderControl * dc) +{ + if (decode_pid > 0 && (dc->start || dc->state != DECODE_STATE_STOP)) { + dc->stop = 1; + while (decode_pid > 0 && dc->stop) + my_usleep(10000); + } +} + +static void quitDecode(PlayerControl * pc, DecoderControl * dc) +{ + stopDecode(dc); + pc->state = PLAYER_STATE_STOP; + dc->seek = 0; + pc->play = 0; + pc->stop = 0; + pc->pause = 0; + kill(getppid(), SIGUSR1); +} + +static int calculateCrossFadeChunks(PlayerControl * pc, AudioFormat * af) +{ + long chunks; + + if (pc->crossFade <= 0) + return 0; + + chunks = (af->sampleRate * af->bits * af->channels / 8.0 / CHUNK_SIZE); + chunks = (chunks * pc->crossFade + 0.5); + + if (chunks > (buffered_chunks - buffered_before_play)) { + chunks = buffered_chunks - buffered_before_play; + } + + if (chunks < 0) + chunks = 0; + + return (int)chunks; +} + +#define handleDecodeStart() \ + if(decodeWaitedOn) { \ + if(dc->state!=DECODE_STATE_START && decode_pid > 0 && \ + dc->error==DECODE_ERROR_NOERROR) \ + { \ + decodeWaitedOn = 0; \ + if(openAudioDevice(&(cb->audioFormat))<0) { \ + pathcpy_trunc(pc->erroredUrl, pc->utf8url); \ + pc->error = PLAYER_ERROR_AUDIO; \ + ERROR("problems opening audio device while playing \"%s\"\n", pc->utf8url); \ + quitDecode(pc,dc); \ + return; \ + } \ + pc->totalTime = dc->totalTime; \ + pc->sampleRate = dc->audioFormat.sampleRate; \ + pc->bits = dc->audioFormat.bits; \ + pc->channels = dc->audioFormat.channels; \ + sizeToTime = 8.0/cb->audioFormat.bits/ \ + cb->audioFormat.channels/ \ + cb->audioFormat.sampleRate; \ + } \ + else if(dc->state!=DECODE_STATE_START || decode_pid <= 0) { \ + pathcpy_trunc(pc->erroredUrl, pc->utf8url); \ + pc->error = PLAYER_ERROR_FILE; \ + quitDecode(pc,dc); \ + return; \ + } \ + else { \ + my_usleep(10000); \ + continue; \ + } \ + } + +static int waitOnDecode(PlayerControl * pc, DecoderControl * dc, + OutputBuffer * cb, int *decodeWaitedOn) +{ + MpdTag *tag = NULL; + pathcpy_trunc(pc->currentUrl, pc->utf8url); + + while (decode_pid > 0 && dc->start) + my_usleep(10000); + + if (dc->start || dc->error != DECODE_ERROR_NOERROR) { + pathcpy_trunc(pc->erroredUrl, pc->utf8url); + pc->error = PLAYER_ERROR_FILE; + quitDecode(pc, dc); + return -1; + } + + if ((tag = metadataChunkToMpdTagDup(&(pc->fileMetadataChunk)))) { + sendMetadataToAudioDevice(tag); + freeMpdTag(tag); + } + + pc->totalTime = pc->fileTime; + pc->bitRate = 0; + pc->sampleRate = 0; + pc->bits = 0; + pc->channels = 0; + *decodeWaitedOn = 1; + + return 0; +} + +static int decodeSeek(PlayerControl * pc, DecoderControl * dc, + OutputBuffer * cb, int *decodeWaitedOn, int *next) +{ + int ret = -1; + + if (decode_pid > 0) { + if (dc->state == DECODE_STATE_STOP || dc->error || + strcmp(dc->utf8url, pc->utf8url) != 0) { + stopDecode(dc); + *next = -1; + cb->begin = 0; + cb->end = 0; + dc->error = 0; + dc->start = 1; + waitOnDecode(pc, dc, cb, decodeWaitedOn); + } + if (decode_pid > 0 && 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; + while (decode_pid > 0 && dc->seek) + my_usleep(10000); + if (!dc->seekError) { + pc->elapsedTime = dc->seekWhere; + ret = 0; + } + } + } + pc->seek = 0; + + return ret; +} + +#define processDecodeInput() \ + if(pc->cycleLogFiles) { \ + cycle_log_files(); \ + pc->cycleLogFiles = 0; \ + } \ + if(pc->lockQueue) { \ + pc->queueLockState = PLAYER_QUEUE_LOCKED; \ + pc->lockQueue = 0; \ + } \ + if(pc->unlockQueue) { \ + pc->queueLockState = PLAYER_QUEUE_UNLOCKED; \ + pc->unlockQueue = 0; \ + } \ + if(pc->pause) { \ + pause = !pause; \ + if (pause) pc->state = PLAYER_STATE_PAUSE; \ + else { \ + if (openAudioDevice(NULL) >= 0) pc->state = PLAYER_STATE_PLAY; \ + else { \ + pathcpy_trunc(pc->erroredUrl, pc->utf8url); \ + pc->error = PLAYER_ERROR_AUDIO; \ + ERROR("problems opening audio device while playing \"%s\"\n", pc->utf8url); \ + pause = -1; \ + } \ + } \ + pc->pause = 0; \ + kill(getppid(), SIGUSR1); \ + if (pause == -1) pause = 1; \ + else if (pause) { \ + dropBufferedAudio(); \ + closeAudioDevice(); \ + } \ + } \ + if(pc->seek) { \ + dropBufferedAudio(); \ + if(decodeSeek(pc,dc,cb,&decodeWaitedOn,&next) == 0) { \ + doCrossFade = 0; \ + nextChunk = -1; \ + bbp = 0; \ + } \ + } \ + if(pc->stop) { \ + dropBufferedAudio(); \ + quitDecode(pc,dc); \ + return; \ + } + +static void decodeStart(PlayerControl * pc, OutputBuffer * cb, + DecoderControl * dc) +{ + int ret; + InputStream inStream; + InputPlugin *plugin = NULL; + char *path; + + if (isRemoteUrl(pc->utf8url)) + path = utf8StrToLatin1Dup(pc->utf8url); + else + path = xstrdup(rmp2amp(utf8ToFsCharset(pc->utf8url))); + + if (!path) { + dc->error = DECODE_ERROR_FILE; + dc->state = DECODE_STATE_STOP; + dc->start = 0; + return; + } + + copyMpdTagToOutputBuffer(cb, NULL); + + pathcpy_trunc(dc->utf8url, pc->utf8url); + + if (openInputStream(&inStream, path) < 0) { + dc->error = DECODE_ERROR_FILE; + dc->state = DECODE_STATE_STOP; + dc->start = 0; + free(path); + return; + } + + dc->state = DECODE_STATE_START; + dc->start = 0; + + while (!inputStreamAtEOF(&inStream) && bufferInputStream(&inStream) < 0 + && !dc->stop) { + /* sleep so we don't consume 100% of the cpu */ + my_usleep(1000); + } + + /* for http streams, seekable is determined in bufferInputStream */ + dc->seekable = inStream.seekable; + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + free(path); + return; + } + + /*if(inStream.metaName) { + MpdTag * tag = newMpdTag(); + tag->name = xstrdup(inStream.metaName); + copyMpdTagToOutputBuffer(cb, tag); + freeMpdTag(tag); + } */ + + /* reset Metadata in OutputBuffer */ + + ret = DECODE_ERROR_UNKTYPE; + if (isRemoteUrl(dc->utf8url)) { + unsigned int next = 0; + cb->acceptMetadata = 1; + + /* first we try mime types: */ + while (ret + && (plugin = + getInputPluginFromMimeType(inStream.mime, next++))) { + if (!plugin->streamDecodeFunc) + continue; + if (!(plugin->streamTypes & INPUT_PLUGIN_STREAM_URL)) + continue; + if (plugin->tryDecodeFunc + && !plugin->tryDecodeFunc(&inStream)) + continue; + ret = plugin->streamDecodeFunc(cb, dc, &inStream); + break; + } + + /* if that fails, try suffix matching the URL: */ + if (plugin == NULL) { + char *s = getSuffix(dc->utf8url); + next = 0; + while (ret + && (plugin = + getInputPluginFromSuffix(s, next++))) { + if (!plugin->streamDecodeFunc) + continue; + if (!(plugin->streamTypes & + INPUT_PLUGIN_STREAM_URL)) + continue; + if (plugin->tryDecodeFunc && + !plugin->tryDecodeFunc(&inStream)) + continue; + ret = + plugin->streamDecodeFunc(cb, dc, &inStream); + break; + } + } + /* fallback to mp3: */ + /* this is needed for bastard streams that don't have a suffix + or set the mimeType */ + 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(cb, dc, + &inStream); + } + } else { + unsigned int next = 0; + char *s = getSuffix(dc->utf8url); + cb->acceptMetadata = 0; + while (ret && (plugin = getInputPluginFromSuffix(s, next++))) { + if (!plugin->streamTypes & INPUT_PLUGIN_STREAM_FILE) + continue; + if (plugin->tryDecodeFunc + && !plugin->tryDecodeFunc(&inStream)) + continue; + + if (plugin->streamDecodeFunc) { + ret = + plugin->streamDecodeFunc(cb, dc, &inStream); + break; + } else if (plugin->fileDecodeFunc) { + closeInputStream(&inStream); + ret = plugin->fileDecodeFunc(cb, dc, path); + } + } + } + + if (ret < 0 || ret == DECODE_ERROR_UNKTYPE) { + pathcpy_trunc(pc->erroredUrl, dc->utf8url); + if (ret != DECODE_ERROR_UNKTYPE) + dc->error = DECODE_ERROR_FILE; + else { + dc->error = DECODE_ERROR_UNKTYPE; + closeInputStream(&inStream); + } + dc->stop = 0; + dc->state = DECODE_STATE_STOP; + } + + free(path); +} + +static int decoderInit(PlayerControl * pc, OutputBuffer * cb, + DecoderControl * dc) +{ + blockSignals(); + getPlayerData()->playerControl.decode_pid = 0; + decode_pid = fork(); + + if (decode_pid == 0) { + /* CHILD */ + unblockSignals(); + + while (1) { + if (dc->cycleLogFiles) { + cycle_log_files(); + dc->cycleLogFiles = 0; + } else if (dc->start || dc->seek) + decodeStart(pc, cb, dc); + else if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + my_usleep(10000); + } + + exit(EXIT_SUCCESS); + /* END OF CHILD */ + } else if (decode_pid < 0) { + unblockSignals(); + pathcpy_trunc(pc->erroredUrl, pc->utf8url); + pc->error = PLAYER_ERROR_SYSTEM; + return -1; + } + DEBUG("decoder PID: %d\n", decode_pid); + getPlayerData()->playerControl.decode_pid = decode_pid; + unblockSignals(); + + return 0; +} + +static void handleMetadata(OutputBuffer * cb, PlayerControl * pc, int *previous, + int *currentChunkSent, MetadataChunk * currentChunk) +{ + if (cb->begin != cb->end) { + int meta = cb->metaChunk[cb->begin]; + if (meta != *previous) { + DEBUG("player: metadata change\n"); + if (meta >= 0 && cb->metaChunkSet[meta]) { + DEBUG("player: new metadata from decoder!\n"); + memcpy(currentChunk, + cb->metadataChunks + meta, + sizeof(MetadataChunk)); + *currentChunkSent = 0; + cb->metaChunkSet[meta] = 0; + } + } + *previous = meta; + } + if (!(*currentChunkSent) && pc->metadataState == + PLAYER_METADATA_STATE_WRITE) { + MpdTag *tag = NULL; + + *currentChunkSent = 1; + + if ((tag = metadataChunkToMpdTagDup(currentChunk))) { + sendMetadataToAudioDevice(tag); + freeMpdTag(tag); + } + + memcpy(&(pc->metadataChunk), currentChunk, + sizeof(MetadataChunk)); + pc->metadataState = PLAYER_METADATA_STATE_READ; + kill(getppid(), SIGUSR1); + } +} + +static void advanceOutputBufferTo(OutputBuffer * cb, PlayerControl * pc, + int *previous, int *currentChunkSent, + MetadataChunk * currentChunk, int to) +{ + while (cb->begin != to) { + handleMetadata(cb, pc, previous, currentChunkSent, + currentChunk); + if (cb->begin + 1 >= buffered_chunks) { + cb->begin = 0; + } + else cb->begin++; + } +} + +static void decodeParent(PlayerControl * pc, DecoderControl * dc, OutputBuffer * cb) +{ + int pause = 0; + int quit = 0; + int bbp = buffered_before_play; + int doCrossFade = 0; + int crossFadeChunks = 0; + int fadePosition; + int nextChunk = -1; + int test; + int decodeWaitedOn = 0; + char silence[CHUNK_SIZE]; + double sizeToTime = 0.0; + int previousMetadataChunk = -1; + MetadataChunk currentMetadataChunk; + int currentChunkSent = 1; + int end; + int next = -1; + + memset(silence, 0, CHUNK_SIZE); + + if (waitOnDecode(pc, dc, cb, &decodeWaitedOn) < 0) + return; + + pc->elapsedTime = 0; + pc->state = PLAYER_STATE_PLAY; + pc->play = 0; + kill(getppid(), SIGUSR1); + + while (decode_pid > 0 && + cb->end - cb->begin < bbp && + cb->end != buffered_chunks - 1 && + dc->state != DECODE_STATE_STOP) { + processDecodeInput(); + my_usleep(1000); + } + + while (!quit) { + processDecodeInput(); + handleDecodeStart(); + handleMetadata(cb, pc, &previousMetadataChunk, + ¤tChunkSent, ¤tMetadataChunk); + if (dc->state == DECODE_STATE_STOP && + pc->queueState == PLAYER_QUEUE_FULL && + pc->queueLockState == PLAYER_QUEUE_UNLOCKED) { + next = cb->end; + dc->start = 1; + pc->queueState = PLAYER_QUEUE_DECODE; + kill(getppid(), SIGUSR1); + } + if (next >= 0 && doCrossFade == 0 && !dc->start && + dc->state != DECODE_STATE_START) { + nextChunk = -1; + if (isCurrentAudioFormat(&(cb->audioFormat))) { + doCrossFade = 1; + crossFadeChunks = + calculateCrossFadeChunks(pc, + &(cb-> + audioFormat)); + if (!crossFadeChunks + || pc->crossFade >= dc->totalTime) { + doCrossFade = -1; + } + } else + doCrossFade = -1; + } + + /* copy these to local variables to prevent any potential + race conditions and weirdness */ + end = cb->end; + + if (pause) + my_usleep(10000); + else if (cb->begin != end && cb->begin != next) { + if (doCrossFade == 1 && next >= 0 && + ((next > cb->begin && + (fadePosition = next - cb->begin) + <= crossFadeChunks) || + (cb->begin > next && + (fadePosition = next - cb->begin + + buffered_chunks) <= crossFadeChunks))) { + if (nextChunk < 0) { + crossFadeChunks = fadePosition; + } + test = end; + if (end < cb->begin) + test += buffered_chunks; + nextChunk = cb->begin + crossFadeChunks; + if (nextChunk < test) { + if (nextChunk >= buffered_chunks) { + nextChunk -= buffered_chunks; + } + pcm_mix(cb->chunks + + cb->begin * CHUNK_SIZE, + cb->chunks + + nextChunk * CHUNK_SIZE, + cb->chunkSize[cb->begin], + cb->chunkSize[nextChunk], + &(cb->audioFormat), + ((float)fadePosition) / + crossFadeChunks); + if (cb->chunkSize[nextChunk] > + cb->chunkSize[cb->begin] + ) { + cb->chunkSize[cb->begin] + = cb->chunkSize[nextChunk]; + } + } else { + if (dc->state == DECODE_STATE_STOP) { + doCrossFade = -1; + } else + continue; + } + } + pc->elapsedTime = cb->times[cb->begin]; + pc->bitRate = cb->bitRate[cb->begin]; + pcm_volumeChange(cb->chunks + cb->begin * + CHUNK_SIZE, + cb->chunkSize[cb->begin], + &(cb->audioFormat), + pc->softwareVolume); + if (playAudio(cb->chunks + cb->begin * CHUNK_SIZE, + cb->chunkSize[cb->begin]) < 0) { + quit = 1; + } + pc->totalPlayTime += + sizeToTime * cb->chunkSize[cb->begin]; + if (cb->begin + 1 >= buffered_chunks) { + cb->begin = 0; + } else + cb->begin++; + } else if (cb->begin != end && cb->begin == next) { + if (doCrossFade == 1 && nextChunk >= 0) { + nextChunk = cb->begin + crossFadeChunks; + test = end; + if (end < cb->begin) + test += buffered_chunks; + if (nextChunk < test) { + if (nextChunk >= buffered_chunks) { + nextChunk -= buffered_chunks; + } + advanceOutputBufferTo(cb, pc, + &previousMetadataChunk, + ¤tChunkSent, + ¤tMetadataChunk, + nextChunk); + } + } + while (pc->queueState == PLAYER_QUEUE_DECODE || + pc->queueLockState == PLAYER_QUEUE_LOCKED) { + processDecodeInput(); + if (quit) { + quitDecode(pc, dc); + return; + } + my_usleep(10000); + } + if (pc->queueState != PLAYER_QUEUE_PLAY) { + quit = 1; + break; + } else { + next = -1; + if (waitOnDecode(pc, dc, cb, &decodeWaitedOn) < + 0) { + return; + } + nextChunk = -1; + doCrossFade = 0; + crossFadeChunks = 0; + pc->queueState = PLAYER_QUEUE_EMPTY; + kill(getppid(), SIGUSR1); + } + } else if (decode_pid <= 0 || + (dc->state == DECODE_STATE_STOP && !dc->start)) { + quit = 1; + break; + } else { + /*DEBUG("waiting for decoded audio, play silence\n");*/ + if (playAudio(silence, CHUNK_SIZE) < 0) + quit = 1; + } + } + + quitDecode(pc, dc); +} + +/* decode w/ buffering + * this will fork another process + * child process does decoding + * parent process does playing audio + */ +void decode(void) +{ + OutputBuffer *cb; + PlayerControl *pc; + DecoderControl *dc; + + cb = &(getPlayerData()->buffer); + + clearAllMetaChunkSets(cb); + cb->begin = 0; + cb->end = 0; + pc = &(getPlayerData()->playerControl); + dc = &(getPlayerData()->decoderControl); + dc->error = 0; + dc->seek = 0; + dc->stop = 0; + dc->start = 1; + + if (decode_pid <= 0) { + if (decoderInit(pc, cb, dc) < 0) + return; + } + + decodeParent(pc, dc, cb); +} |