diff options
Diffstat (limited to 'trunk/src')
131 files changed, 33405 insertions, 0 deletions
diff --git a/trunk/src/Makefile.am b/trunk/src/Makefile.am new file mode 100644 index 000000000..b9c6fa0c7 --- /dev/null +++ b/trunk/src/Makefile.am @@ -0,0 +1,148 @@ +bin_PROGRAMS = mpd +SUBDIRS = $(MP4FF_SUBDIR) + +mpd_audioOutputs = \ + audioOutputs/audioOutput_alsa.c \ + audioOutputs/audioOutput_ao.c \ + audioOutputs/audioOutput_oss.c \ + audioOutputs/audioOutput_osx.c \ + audioOutputs/audioOutput_pulse.c \ + audioOutputs/audioOutput_mvp.c \ + audioOutputs/audioOutput_shout.c \ + audioOutputs/audioOutput_jack.c + +mpd_inputPlugins = \ + inputPlugins/_flac_common.c \ + inputPlugins/_ogg_common.c \ + inputPlugins/oggflac_plugin.c \ + inputPlugins/oggvorbis_plugin.c \ + inputPlugins/aac_plugin.c \ + inputPlugins/audiofile_plugin.c \ + inputPlugins/flac_plugin.c \ + inputPlugins/mod_plugin.c \ + inputPlugins/mp3_plugin.c \ + inputPlugins/mp4_plugin.c \ + inputPlugins/mpc_plugin.c + + +mpd_headers = \ + ack.h \ + audio.h \ + audioOutput.h \ + buffer2array.h \ + charConv.h \ + command.h \ + conf.h \ + dbUtils.h \ + decode.h \ + directory.h \ + gcc.h \ + inputPlugin.h \ + inputPlugins/_flac_common.h \ + inputPlugins/_ogg_common.h \ + inputStream.h \ + inputStream_file.h \ + inputStream_http.h \ + interface.h \ + list.h \ + listen.h \ + log.h \ + ls.h \ + metadataChunk.h \ + mpd_types.h \ + myfprintf.h \ + normalize.h \ + compress.h \ + outputBuffer.h \ + path.h \ + pcm_utils.h \ + permission.h \ + player.h \ + playerData.h \ + playlist.h \ + replayGain.h \ + signal_check.h \ + sig_handlers.h \ + sllist.h \ + song.h \ + state_file.h \ + stats.h \ + tag.h \ + tagTracker.h \ + tree.h \ + utf8.h \ + utils.h \ + volume.h \ + ioops.h \ + zeroconf.h \ + locate.h \ + storedPlaylist.h + + +mpd_SOURCES = \ + $(mpd_headers) \ + $(mpd_audioOutputs) \ + $(mpd_inputPlugins) \ + audio.c \ + audioOutput.c \ + buffer2array.c \ + charConv.c \ + command.c \ + conf.c \ + dbUtils.c \ + decode.c \ + directory.c \ + inputPlugin.c \ + inputStream.c \ + inputStream_file.c \ + inputStream_http.c \ + interface.c \ + list.c \ + listen.c \ + log.c \ + ls.c \ + main.c \ + metadataChunk.c \ + myfprintf.c \ + normalize.c \ + compress.c \ + outputBuffer.c \ + path.c \ + pcm_utils.c \ + permission.c \ + player.c \ + playerData.c \ + playlist.c \ + replayGain.c \ + sig_handlers.c \ + signal_check.c \ + sllist.c \ + song.c \ + state_file.c \ + stats.c \ + tag.c \ + tagTracker.c \ + tree.c \ + utils.c \ + volume.c \ + utf8.c \ + zeroconf.c \ + locate.c \ + storedPlaylist.c + + +mpd_CFLAGS = $(MPD_CFLAGS) +mpd_LDADD = $(MPD_LIBS) $(MP4FF_LIB) + +DIST_SUBDIRS = mp4ff + +# sparse is a semantic parser +# URL: git://www.kernel.org/pub/scm/devel/sparse/sparse.git +SPARSE = sparse +SPARSE_FLAGS = +sparse-check: + for i in $(mpd_SOURCES); \ + do \ + $(SPARSE) -I. $(mpd_CFLAGS) $(SPARSE_FLAGS) $(srcdir)/$$i || exit; \ + done + diff --git a/trunk/src/ack.h b/trunk/src/ack.h new file mode 100644 index 000000000..1b2950e20 --- /dev/null +++ b/trunk/src/ack.h @@ -0,0 +1,37 @@ +/* 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 ACK_H +#define ACK_H + +/* Common Errors */ +#define ACK_ERROR_NOT_LIST 1 +#define ACK_ERROR_ARG 2 +#define ACK_ERROR_PASSWORD 3 +#define ACK_ERROR_PERMISSION 4 +#define ACK_ERROR_UNKNOWN 5 + +#define ACK_ERROR_NO_EXIST 50 +#define ACK_ERROR_PLAYLIST_MAX 51 +#define ACK_ERROR_SYSTEM 52 +#define ACK_ERROR_PLAYLIST_LOAD 53 +#define ACK_ERROR_UPDATE_ALREADY 54 +#define ACK_ERROR_PLAYER_SYNC 55 +#define ACK_ERROR_EXIST 56 + +#endif diff --git a/trunk/src/audio.c b/trunk/src/audio.c new file mode 100644 index 000000000..912e46ffa --- /dev/null +++ b/trunk/src/audio.c @@ -0,0 +1,518 @@ +/* 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 "audio.h" +#include "audioOutput.h" +#include "conf.h" +#include "log.h" +#include "sig_handlers.h" +#include "command.h" +#include "playerData.h" +#include "utils.h" +#include "state_file.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <errno.h> +#include <unistd.h> + +#define AUDIO_DEVICE_STATE "audio_device_state:" +#define AUDIO_DEVICE_STATE_LEN 19 /* strlen(AUDIO_DEVICE_STATE) */ +#define AUDIO_BUFFER_SIZE 2*MAXPATHLEN + +static AudioFormat audio_format; + +static AudioFormat *audio_configFormat; + +static AudioOutput *audioOutputArray; +static mpd_uint8 audioOutputArraySize; + +#define DEVICE_OFF 0x00 +#define DEVICE_ENABLE 0x01 /* currently off, but to be turned on */ +#define DEVICE_ON 0x03 +#define DEVICE_DISABLE 0x04 /* currently on, but to be turned off */ + +/* the audioEnabledArray should be stuck into shared memory, and then disable + and enable in playAudio() routine */ +static mpd_uint8 *audioDeviceStates; + +static mpd_uint8 audioOpened; + +static mpd_sint32 audioBufferSize; +static char *audioBuffer; +static mpd_sint32 audioBufferPos; + +size_t audio_device_count(void) +{ + size_t nr = 0; + ConfigParam *param = NULL; + + while ((param = getNextConfigParam(CONF_AUDIO_OUTPUT, param))) + nr++; + if (!nr) + nr = 1; /* we'll always have at least one device */ + return nr; +} + +void copyAudioFormat(AudioFormat * dest, AudioFormat * src) +{ + if (!src) + return; + + memcpy(dest, src, sizeof(AudioFormat)); +} + +int cmpAudioFormat(AudioFormat * f1, AudioFormat * f2) +{ + if (f1 && f2 && (f1->sampleRate == f2->sampleRate) && + (f1->bits == f2->bits) && (f1->channels == f2->channels)) + return 0; + return 1; +} + +void loadAudioDrivers(void) +{ + initAudioOutputPlugins(); + loadAudioOutputPlugin(&alsaPlugin); + loadAudioOutputPlugin(&aoPlugin); + loadAudioOutputPlugin(&ossPlugin); + loadAudioOutputPlugin(&osxPlugin); + loadAudioOutputPlugin(&pulsePlugin); + loadAudioOutputPlugin(&mvpPlugin); + loadAudioOutputPlugin(&shoutPlugin); + loadAudioOutputPlugin(&jackPlugin); +} + +/* make sure initPlayerData is called before this function!! */ +void initAudioDriver(void) +{ + ConfigParam *param = NULL; + int i; + + loadAudioDrivers(); + + audioOutputArraySize = audio_device_count(); + audioDeviceStates = (getPlayerData())->audioDeviceStates; + audioOutputArray = xmalloc(sizeof(AudioOutput) * audioOutputArraySize); + + for (i = 0; i < audioOutputArraySize; i++) + { + AudioOutput *output = &audioOutputArray[i]; + int j; + + param = getNextConfigParam(CONF_AUDIO_OUTPUT, param); + + /* only allow param to be NULL if there just one audioOutput */ + assert(param || (audioOutputArraySize == 1)); + + if (!initAudioOutput(output, param)) { + if (param) + { + FATAL("problems configuring output device " + "defined at line %i\n", param->line); + } + else + { + FATAL("No audio_output specified and unable to " + "detect a default audio output device\n"); + } + } + + /* require output names to be unique: */ + for (j = 0; j < i; j++) { + if (!strcmp(output->name, audioOutputArray[j].name)) { + FATAL("output devices with identical " + "names: %s\n", output->name); + } + } + audioDeviceStates[i] = DEVICE_ENABLE; + } +} + +void getOutputAudioFormat(AudioFormat * inAudioFormat, + AudioFormat * outAudioFormat) +{ + if (audio_configFormat) { + copyAudioFormat(outAudioFormat, audio_configFormat); + } else + copyAudioFormat(outAudioFormat, inAudioFormat); +} + +void initAudioConfig(void) +{ + ConfigParam *param = getConfigParam(CONF_AUDIO_OUTPUT_FORMAT); + + if (NULL == param || NULL == param->value) + return; + + audio_configFormat = xmalloc(sizeof(AudioFormat)); + + if (0 != parseAudioConfig(audio_configFormat, param->value)) { + FATAL("error parsing \"%s\" at line %i\n", + CONF_AUDIO_OUTPUT_FORMAT, param->line); + } +} + +int parseAudioConfig(AudioFormat * audioFormat, char *conf) +{ + char *test; + + memset(audioFormat, 0, sizeof(AudioFormat)); + + audioFormat->sampleRate = strtol(conf, &test, 10); + + if (*test != ':') { + ERROR("error parsing audio output format: %s\n", conf); + return -1; + } + + /*switch(audioFormat->sampleRate) { + case 48000: + case 44100: + case 32000: + case 16000: + break; + default: + ERROR("sample rate %i can not be used for audio output\n", + (int)audioFormat->sampleRate); + return -1 + } */ + + if (audioFormat->sampleRate <= 0) { + ERROR("sample rate %i is not >= 0\n", + (int)audioFormat->sampleRate); + return -1; + } + + audioFormat->bits = strtol(test + 1, &test, 10); + + if (*test != ':') { + ERROR("error parsing audio output format: %s\n", conf); + return -1; + } + + switch (audioFormat->bits) { + case 16: + break; + default: + ERROR("bits %i can not be used for audio output\n", + (int)audioFormat->bits); + return -1; + } + + audioFormat->channels = strtol(test + 1, &test, 10); + + if (*test != '\0') { + ERROR("error parsing audio output format: %s\n", conf); + return -1; + } + + switch (audioFormat->channels) { + case 1: + case 2: + break; + default: + ERROR("channels %i can not be used for audio output\n", + (int)audioFormat->channels); + return -1; + } + + return 0; +} + +void finishAudioConfig(void) +{ + if (audio_configFormat) + free(audio_configFormat); +} + +void finishAudioDriver(void) +{ + int i; + + for (i = 0; i < audioOutputArraySize; i++) { + finishAudioOutput(&audioOutputArray[i]); + } + + free(audioOutputArray); + audioOutputArray = NULL; + audioOutputArraySize = 0; +} + +int isCurrentAudioFormat(AudioFormat * audioFormat) +{ + if (!audioFormat) + return 1; + + if (cmpAudioFormat(audioFormat, &audio_format) != 0) + return 0; + + return 1; +} + +static void syncAudioDeviceStates(void) +{ + int i; + + if (!audio_format.channels) + return; + for (i = 0; i < audioOutputArraySize; ++i ) { + switch (audioDeviceStates[i]) { + case DEVICE_ON: + /* This will reopen only if the audio format changed */ + openAudioOutput(&audioOutputArray[i], &audio_format); + break; + case DEVICE_ENABLE: + openAudioOutput(&audioOutputArray[i], &audio_format); + audioDeviceStates[i] = DEVICE_ON; + break; + case DEVICE_DISABLE: + dropBufferedAudioOutput(&audioOutputArray[i]); + closeAudioOutput(&audioOutputArray[i]); + audioDeviceStates[i] = DEVICE_OFF; + break; + } + } +} + +static int flushAudioBuffer(void) +{ + int ret = -1; + int i, err; + + if (audioBufferPos == 0) + return 0; + + syncAudioDeviceStates(); + + for (i = 0; i < audioOutputArraySize; ++i) { + if (audioDeviceStates[i] != DEVICE_ON) + continue; + err = playAudioOutput(&audioOutputArray[i], audioBuffer, + audioBufferPos); + if (!err) + ret = 0; + else if (err < 0) + /* device should already be closed if the play + * func returned an error */ + audioDeviceStates[i] = DEVICE_ENABLE; + } + + audioBufferPos = 0; + + return ret; +} + +int openAudioDevice(AudioFormat * audioFormat) +{ + int ret = -1; + int i; + + if (!audioOutputArray) + return -1; + + if (!audioOpened || !isCurrentAudioFormat(audioFormat)) { + flushAudioBuffer(); + copyAudioFormat(&audio_format, audioFormat); + audioBufferSize = (audio_format.bits >> 3) * + audio_format.channels; + audioBufferSize *= audio_format.sampleRate >> 5; + audioBuffer = xrealloc(audioBuffer, audioBufferSize); + } + + syncAudioDeviceStates(); + + for (i = 0; i < audioOutputArraySize; ++i) { + if (audioOutputArray[i].open) + ret = 0; + } + + if (ret == 0) + audioOpened = 1; + else { + /* close all devices if there was an error */ + for (i = 0; i < audioOutputArraySize; ++i) { + closeAudioOutput(&audioOutputArray[i]); + } + + audioOpened = 0; + } + + return ret; +} + +int playAudio(char *playChunk, int size) +{ + int send; + + while (size > 0) { + send = audioBufferSize - audioBufferPos; + send = send < size ? send : size; + + memcpy(audioBuffer + audioBufferPos, playChunk, send); + audioBufferPos += send; + size -= send; + playChunk += send; + + if (audioBufferPos == audioBufferSize) { + if (flushAudioBuffer() < 0) + return -1; + } + } + + return 0; +} + +int isAudioDeviceOpen(void) +{ + return audioOpened; +} + +void dropBufferedAudio(void) +{ + int i; + + syncAudioDeviceStates(); + audioBufferPos = 0; + + for (i = 0; i < audioOutputArraySize; ++i) { + if (audioDeviceStates[i] == DEVICE_ON) + dropBufferedAudioOutput(&audioOutputArray[i]); + } +} + +void closeAudioDevice(void) +{ + int i; + + flushAudioBuffer(); + + free(audioBuffer); + audioBuffer = NULL; + audioBufferSize = 0; + + for (i = 0; i < audioOutputArraySize; ++i) { + if (audioDeviceStates[i] == DEVICE_ON) + audioDeviceStates[i] = DEVICE_ENABLE; + closeAudioOutput(&audioOutputArray[i]); + } + + audioOpened = 0; +} + +void sendMetadataToAudioDevice(MpdTag * tag) +{ + int i; + + for (i = 0; i < audioOutputArraySize; ++i) { + sendMetadataToAudioOutput(&audioOutputArray[i], tag); + } +} + +int enableAudioDevice(int fd, int device) +{ + if (device < 0 || device >= audioOutputArraySize) { + commandError(fd, ACK_ERROR_ARG, "audio output device id %i " + "doesn't exist\n", device); + return -1; + } + + if (!(audioDeviceStates[device] & 0x01)) + audioDeviceStates[device] = DEVICE_ENABLE; + + return 0; +} + +int disableAudioDevice(int fd, int device) +{ + if (device < 0 || device >= audioOutputArraySize) { + commandError(fd, ACK_ERROR_ARG, "audio output device id %i " + "doesn't exist\n", device); + return -1; + } + if (audioDeviceStates[device] & 0x01) + audioDeviceStates[device] = DEVICE_DISABLE; + + return 0; +} + +void printAudioDevices(int fd) +{ + int i; + + for (i = 0; i < audioOutputArraySize; i++) { + fdprintf(fd, + "outputid: %i\noutputname: %s\noutputenabled: %i\n", + i, + audioOutputArray[i].name, + audioDeviceStates[i] & 0x01); + } +} + +void saveAudioDevicesState(FILE *fp) +{ + int i; + + assert(audioOutputArraySize != 0); + for (i = 0; i < audioOutputArraySize; i++) { + fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n", + audioDeviceStates[i] & 0x01, + audioOutputArray[i].name); + } +} + +void readAudioDevicesState(FILE *fp) +{ + char buffer[AUDIO_BUFFER_SIZE]; + int i; + + assert(audioOutputArraySize != 0); + + while (myFgets(buffer, AUDIO_BUFFER_SIZE, fp)) { + char *c, *name; + + if (strncmp(buffer, AUDIO_DEVICE_STATE, AUDIO_DEVICE_STATE_LEN)) + continue; + + c = strchr(buffer, ':'); + if (!c || !(++c)) + goto errline; + + name = strchr(c, ':'); + if (!name || !(++name)) + goto errline; + + for (i = 0; i < audioOutputArraySize; ++i) { + if (!strcmp(name, audioOutputArray[i].name)) { + /* devices default to on */ + if (!atoi(c)) + audioDeviceStates[i] = DEVICE_DISABLE; + break; + } + } + continue; +errline: + /* nonfatal */ + ERROR("invalid line in state_file: %s\n", buffer); + } +} + diff --git a/trunk/src/audio.h b/trunk/src/audio.h new file mode 100644 index 000000000..cc4ac0752 --- /dev/null +++ b/trunk/src/audio.h @@ -0,0 +1,83 @@ +/* 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 AUDIO_H +#define AUDIO_H + +#include "../config.h" + +#include "mpd_types.h" +#include "tag.h" + +#include <stdio.h> + +#define AUDIO_AO_DRIVER_DEFAULT "default" + +typedef struct _AudioFormat { + volatile mpd_sint8 channels; + volatile mpd_uint32 sampleRate; + volatile mpd_sint8 bits; +} AudioFormat; + +size_t audio_device_count(void); + +void copyAudioFormat(AudioFormat * dest, AudioFormat * src); + +int cmpAudioFormat(AudioFormat * dest, AudioFormat * src); + +void getOutputAudioFormat(AudioFormat * inFormat, AudioFormat * outFormat); + +int parseAudioConfig(AudioFormat * audioFormat, char *conf); + +/* make sure initPlayerData is called before this function!! */ +void initAudioConfig(void); + +void finishAudioConfig(void); + +void initAudioDriver(void); + +void finishAudioDriver(void); + +int openAudioDevice(AudioFormat * audioFormat); + +int playAudio(char *playChunk, int size); + +void dropBufferedAudio(void); + +void closeAudioDevice(void); + +int isAudioDeviceOpen(void); + +int isCurrentAudioFormat(AudioFormat * audioFormat); + +void sendMetadataToAudioDevice(MpdTag * tag); + +/* these functions are called in the main parent process while the child + process is busy playing to the audio */ +int enableAudioDevice(int fd, int device); + +int disableAudioDevice(int fd, int device); + +void printAudioDevices(int fd); + +void readAudioDevicesState(FILE *fp); + +void saveAudioDevicesState(FILE *fp); + +void loadAudioDrivers(void); +#endif diff --git a/trunk/src/audioOutput.c b/trunk/src/audioOutput.c new file mode 100644 index 000000000..49a7ce258 --- /dev/null +++ b/trunk/src/audioOutput.c @@ -0,0 +1,269 @@ +/* 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 "audioOutput.h" + +#include "list.h" +#include "log.h" +#include "pcm_utils.h" + +#include <string.h> + +#define AUDIO_OUTPUT_TYPE "type" +#define AUDIO_OUTPUT_NAME "name" +#define AUDIO_OUTPUT_FORMAT "format" + +static List *audioOutputPluginList; + +void loadAudioOutputPlugin(AudioOutputPlugin * audioOutputPlugin) +{ + if (!audioOutputPlugin->name) + return; + insertInList(audioOutputPluginList, audioOutputPlugin->name, + audioOutputPlugin); +} + +void unloadAudioOutputPlugin(AudioOutputPlugin * audioOutputPlugin) +{ + if (!audioOutputPlugin->name) + return; + deleteFromList(audioOutputPluginList, audioOutputPlugin->name); +} + +void initAudioOutputPlugins(void) +{ + audioOutputPluginList = makeList(NULL, 0); +} + +void finishAudioOutputPlugins(void) +{ + freeList(audioOutputPluginList); +} + +#define getBlockParam(name, str, force) { \ + bp = getBlockParam(param, name); \ + if(force && bp == NULL) { \ + FATAL("couldn't find parameter \"%s\" in audio output " \ + "definition beginning at %i\n", \ + name, param->line); \ + } \ + if(bp) str = bp->value; \ +} + +int initAudioOutput(AudioOutput *ao, ConfigParam * param) +{ + void *data = NULL; + char *name = NULL; + char *format = NULL; + char *type = NULL; + BlockParam *bp = NULL; + AudioOutputPlugin *plugin = NULL; + + if (param) { + getBlockParam(AUDIO_OUTPUT_NAME, name, 1); + getBlockParam(AUDIO_OUTPUT_TYPE, type, 1); + getBlockParam(AUDIO_OUTPUT_FORMAT, format, 0); + + if (!findInList(audioOutputPluginList, type, &data)) { + FATAL("couldn't find audio output plugin for type " + "\"%s\" at line %i\n", type, param->line); + } + + plugin = (AudioOutputPlugin *) data; + } else { + ListNode *node = audioOutputPluginList->firstNode; + + WARNING("No \"%s\" defined in config file\n", + CONF_AUDIO_OUTPUT); + WARNING("Attempt to detect audio output device\n"); + + while (node) { + plugin = (AudioOutputPlugin *) node->data; + if (plugin->testDefaultDeviceFunc) { + WARNING("Attempting to detect a %s audio " + "device\n", plugin->name); + if (plugin->testDefaultDeviceFunc() == 0) { + WARNING("Successfully detected a %s " + "audio device\n", plugin->name); + break; + } + } + node = node->nextNode; + } + + if (!node) { + WARNING("Unable to detect an audio device\n"); + return 0; + } + + name = "default detected output"; + type = plugin->name; + } + + ao->name = name; + ao->type = type; + ao->finishDriverFunc = plugin->finishDriverFunc; + ao->openDeviceFunc = plugin->openDeviceFunc; + ao->playFunc = plugin->playFunc; + ao->dropBufferedAudioFunc = plugin->dropBufferedAudioFunc; + ao->closeDeviceFunc = plugin->closeDeviceFunc; + ao->sendMetdataFunc = plugin->sendMetdataFunc; + ao->open = 0; + + ao->convertAudioFormat = 0; + ao->sameInAndOutFormats = 0; + ao->convBuffer = NULL; + ao->convBufferLen = 0; + + memset(&ao->inAudioFormat, 0, sizeof(AudioFormat)); + memset(&ao->outAudioFormat, 0, sizeof(AudioFormat)); + memset(&ao->reqAudioFormat, 0, sizeof(AudioFormat)); + memset(&ao->convState, 0, sizeof(ConvState)); + + if (format) { + ao->convertAudioFormat = 1; + + if (0 != parseAudioConfig(&ao->reqAudioFormat, format)) { + FATAL("error parsing format at line %i\n", bp->line); + } + + copyAudioFormat(&ao->outAudioFormat, &ao->reqAudioFormat); + } + + if (plugin->initDriverFunc(ao, param) != 0) + return 0; + + return 1; +} + +int openAudioOutput(AudioOutput * audioOutput, AudioFormat * audioFormat) +{ + int ret = 0; + + if (audioOutput->open) + { + if (0==cmpAudioFormat(audioFormat, &audioOutput->inAudioFormat)) + { + return 0; + } + } + + copyAudioFormat(&audioOutput->inAudioFormat, audioFormat); + + if (audioOutput->convertAudioFormat) + { + copyAudioFormat(&audioOutput->outAudioFormat, + &audioOutput->reqAudioFormat); + } + else + { + copyAudioFormat(&audioOutput->outAudioFormat, + &audioOutput->inAudioFormat); + if (audioOutput->open) closeAudioOutput(audioOutput); + } + + if (!audioOutput->open) + { + ret = audioOutput->openDeviceFunc(audioOutput); + } + + audioOutput->sameInAndOutFormats = + !cmpAudioFormat(&audioOutput->inAudioFormat, + &audioOutput->outAudioFormat); + + return ret; +} + +static void convertAudioFormat(AudioOutput * audioOutput, char **chunkArgPtr, + int *sizeArgPtr) +{ + int size = pcm_sizeOfConvBuffer(&(audioOutput->inAudioFormat), + *sizeArgPtr, + &(audioOutput->outAudioFormat)); + + if (size > audioOutput->convBufferLen) { + audioOutput->convBuffer = + xrealloc(audioOutput->convBuffer, size); + audioOutput->convBufferLen = size; + } + + *sizeArgPtr = pcm_convertAudioFormat(&(audioOutput->inAudioFormat), + *chunkArgPtr, *sizeArgPtr, + &(audioOutput->outAudioFormat), + audioOutput->convBuffer, + &audioOutput->convState); + + *chunkArgPtr = audioOutput->convBuffer; +} + +int playAudioOutput(AudioOutput * audioOutput, char *playChunk, int size) +{ + int ret; + + if (!audioOutput->open) + return -1; + + if (!audioOutput->sameInAndOutFormats) { + convertAudioFormat(audioOutput, &playChunk, &size); + } + + ret = audioOutput->playFunc(audioOutput, playChunk, size); + + return ret; +} + +void dropBufferedAudioOutput(AudioOutput * audioOutput) +{ + if (audioOutput->open) + audioOutput->dropBufferedAudioFunc(audioOutput); +} + +void closeAudioOutput(AudioOutput * audioOutput) +{ + if (audioOutput->open) + audioOutput->closeDeviceFunc(audioOutput); +} + +void finishAudioOutput(AudioOutput * audioOutput) +{ + closeAudioOutput(audioOutput); + audioOutput->finishDriverFunc(audioOutput); + if (audioOutput->convBuffer) + free(audioOutput->convBuffer); +} + +void sendMetadataToAudioOutput(AudioOutput * audioOutput, MpdTag * tag) +{ + if (!audioOutput->sendMetdataFunc) + return; + audioOutput->sendMetdataFunc(audioOutput, tag); +} + +void printAllOutputPluginTypes(FILE * fp) +{ + ListNode *node = audioOutputPluginList->firstNode; + AudioOutputPlugin *plugin; + + while (node) { + plugin = (AudioOutputPlugin *) node->data; + fprintf(fp, "%s ", plugin->name); + node = node->nextNode; + } + fprintf(fp, "\n"); + fflush(fp); +} diff --git a/trunk/src/audioOutput.h b/trunk/src/audioOutput.h new file mode 100644 index 000000000..bcbe7997d --- /dev/null +++ b/trunk/src/audioOutput.h @@ -0,0 +1,117 @@ +/* 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 AUDIO_OUTPUT_H +#define AUDIO_OUTPUT_H + +#include "../config.h" + +#include "pcm_utils.h" +#include "mpd_types.h" +#include "audio.h" +#include "tag.h" +#include "conf.h" +#include "utils.h" + +#define DISABLED_AUDIO_OUTPUT_PLUGIN(plugin) AudioOutputPlugin plugin; + +typedef struct _AudioOutput AudioOutput; + +typedef int (*AudioOutputTestDefaultDeviceFunc) (); + +typedef int (*AudioOutputInitDriverFunc) (AudioOutput * audioOutput, + ConfigParam * param); + +typedef void (*AudioOutputFinishDriverFunc) (AudioOutput * audioOutput); + +typedef int (*AudioOutputOpenDeviceFunc) (AudioOutput * audioOutput); + +typedef int (*AudioOutputPlayFunc) (AudioOutput * audioOutput, + char *playChunk, int size); + +typedef void (*AudioOutputDropBufferedAudioFunc) (AudioOutput * audioOutput); + +typedef void (*AudioOutputCloseDeviceFunc) (AudioOutput * audioOutput); + +typedef void (*AudioOutputSendMetadataFunc) (AudioOutput * audioOutput, + MpdTag * tag); + +struct _AudioOutput { + int open; + char *name; + char *type; + + AudioOutputFinishDriverFunc finishDriverFunc; + AudioOutputOpenDeviceFunc openDeviceFunc; + AudioOutputPlayFunc playFunc; + AudioOutputDropBufferedAudioFunc dropBufferedAudioFunc; + AudioOutputCloseDeviceFunc closeDeviceFunc; + AudioOutputSendMetadataFunc sendMetdataFunc; + + int convertAudioFormat; + AudioFormat inAudioFormat; + AudioFormat outAudioFormat; + AudioFormat reqAudioFormat; + ConvState convState; + char *convBuffer; + int convBufferLen; + int sameInAndOutFormats; + + void *data; +}; + +typedef struct _AudioOutputPlugin { + char *name; + + AudioOutputTestDefaultDeviceFunc testDefaultDeviceFunc; + AudioOutputInitDriverFunc initDriverFunc; + AudioOutputFinishDriverFunc finishDriverFunc; + AudioOutputOpenDeviceFunc openDeviceFunc; + AudioOutputPlayFunc playFunc; + AudioOutputDropBufferedAudioFunc dropBufferedAudioFunc; + AudioOutputCloseDeviceFunc closeDeviceFunc; + AudioOutputSendMetadataFunc sendMetdataFunc; +} AudioOutputPlugin; + +void initAudioOutputPlugins(void); +void finishAudioOutputPlugins(void); + +void loadAudioOutputPlugin(AudioOutputPlugin * audioOutputPlugin); +void unloadAudioOutputPlugin(AudioOutputPlugin * audioOutputPlugin); + +int initAudioOutput(AudioOutput *, ConfigParam * param); +int openAudioOutput(AudioOutput * audioOutput, AudioFormat * audioFormat); +int playAudioOutput(AudioOutput * audioOutput, char *playChunk, int size); +void dropBufferedAudioOutput(AudioOutput * audioOutput); +void closeAudioOutput(AudioOutput * audioOutput); +void finishAudioOutput(AudioOutput * audioOutput); +int keepAudioOutputAlive(AudioOutput * audioOutput, int ms); +void sendMetadataToAudioOutput(AudioOutput * audioOutput, MpdTag * tag); + +void printAllOutputPluginTypes(FILE * fp); + +extern AudioOutputPlugin alsaPlugin; +extern AudioOutputPlugin aoPlugin; +extern AudioOutputPlugin ossPlugin; +extern AudioOutputPlugin osxPlugin; +extern AudioOutputPlugin pulsePlugin; +extern AudioOutputPlugin mvpPlugin; +extern AudioOutputPlugin shoutPlugin; +extern AudioOutputPlugin jackPlugin; + +#endif diff --git a/trunk/src/audioOutputs/audioOutput_alsa.c b/trunk/src/audioOutputs/audioOutput_alsa.c new file mode 100644 index 000000000..3ade3df46 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_alsa.c @@ -0,0 +1,427 @@ +/* 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 "../audioOutput.h" + +#include <stdlib.h> + +#ifdef HAVE_ALSA + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +#define MPD_ALSA_BUFFER_TIME_US 500000 +/* the default period time of xmms is 50 ms, so let's use that as well. + * a user can tweak this parameter via the "period_time" config parameter. + */ +#define MPD_ALSA_PERIOD_TIME_US 50000 +#define MPD_ALSA_RETRY_NR 5 + +#include "../conf.h" +#include "../log.h" + +#include <string.h> + +#include <alsa/asoundlib.h> + +typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, + snd_pcm_uframes_t size); + +typedef struct _AlsaData { + char *device; + snd_pcm_t *pcmHandle; + alsa_writei_t *writei; + unsigned int buffer_time; + unsigned int period_time; + int sampleSize; + int useMmap; + int canPause; + int canResume; +} AlsaData; + +static AlsaData *newAlsaData(void) +{ + AlsaData *ret = xmalloc(sizeof(AlsaData)); + + ret->device = NULL; + ret->pcmHandle = NULL; + ret->writei = snd_pcm_writei; + ret->useMmap = 0; + ret->buffer_time = MPD_ALSA_BUFFER_TIME_US; + ret->period_time = MPD_ALSA_PERIOD_TIME_US; + + return ret; +} + +static void freeAlsaData(AlsaData * ad) +{ + if (ad->device) + free(ad->device); + + free(ad); +} + +static int alsa_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + AlsaData *ad = newAlsaData(); + + if (param) { + BlockParam *bp = getBlockParam(param, "device"); + ad->device = bp ? xstrdup(bp->value) : xstrdup("default"); + + if ((bp = getBlockParam(param, "use_mmap")) && + !strcasecmp(bp->value, "yes")) + ad->useMmap = 1; + if ((bp = getBlockParam(param, "buffer_time"))) + ad->buffer_time = atoi(bp->value); + if ((bp = getBlockParam(param, "period_time"))) + ad->period_time = atoi(bp->value); + } else + ad->device = xstrdup("default"); + audioOutput->data = ad; + + return 0; +} + +static void alsa_finishDriver(AudioOutput * audioOutput) +{ + AlsaData *ad = audioOutput->data; + + freeAlsaData(ad); +} + +static int alsa_testDefault(void) +{ + snd_pcm_t *handle; + + int ret = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK); + snd_config_update_free_global(); + + if (ret) { + WARNING("Error opening default alsa device: %s\n", + snd_strerror(-ret)); + return -1; + } else + snd_pcm_close(handle); + + return 0; +} + +static int alsa_openDevice(AudioOutput * audioOutput) +{ + AlsaData *ad = audioOutput->data; + AudioFormat *audioFormat = &audioOutput->outAudioFormat; + snd_pcm_format_t bitformat; + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + unsigned int sampleRate = audioFormat->sampleRate; + unsigned int channels = audioFormat->channels; + snd_pcm_uframes_t alsa_buffer_size; + snd_pcm_uframes_t alsa_period_size; + int err; + const char *cmd = NULL; + int retry = MPD_ALSA_RETRY_NR; + unsigned int period_time, period_time_ro; + unsigned int buffer_time; + + switch (audioFormat->bits) { + case 8: + bitformat = SND_PCM_FORMAT_S8; + break; + case 16: + bitformat = SND_PCM_FORMAT_S16; + break; + case 24: + bitformat = SND_PCM_FORMAT_S24; + break; + case 32: + bitformat = SND_PCM_FORMAT_S32; + break; + default: + ERROR("ALSA device \"%s\" doesn't support %i bit audio\n", + ad->device, audioFormat->bits); + return -1; + } + + err = snd_pcm_open(&ad->pcmHandle, ad->device, + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + snd_config_update_free_global(); + if (err < 0) { + ad->pcmHandle = NULL; + goto error; + } + + cmd = "snd_pcm_nonblock"; + err = snd_pcm_nonblock(ad->pcmHandle, 0); + if (err < 0) + goto error; + + period_time_ro = period_time = ad->period_time; +configure_hw: + /* configure HW params */ + snd_pcm_hw_params_alloca(&hwparams); + + cmd = "snd_pcm_hw_params_any"; + err = snd_pcm_hw_params_any(ad->pcmHandle, hwparams); + if (err < 0) + goto error; + + if (ad->useMmap) { + err = snd_pcm_hw_params_set_access(ad->pcmHandle, hwparams, + SND_PCM_ACCESS_MMAP_INTERLEAVED); + if (err < 0) { + ERROR("Cannot set mmap'ed mode on alsa device \"%s\": " + " %s\n", ad->device, snd_strerror(-err)); + ERROR("Falling back to direct write mode\n"); + ad->useMmap = 0; + } else + ad->writei = snd_pcm_mmap_writei; + } + + if (!ad->useMmap) { + cmd = "snd_pcm_hw_params_set_access"; + err = snd_pcm_hw_params_set_access(ad->pcmHandle, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) + goto error; + ad->writei = snd_pcm_writei; + } + + err = snd_pcm_hw_params_set_format(ad->pcmHandle, hwparams, bitformat); + if (err < 0) { + ERROR("ALSA device \"%s\" does not support %i bit audio: " + "%s\n", ad->device, audioFormat->bits, snd_strerror(-err)); + goto fail; + } + + err = snd_pcm_hw_params_set_channels_near(ad->pcmHandle, hwparams, + &channels); + if (err < 0) { + ERROR("ALSA device \"%s\" does not support %i channels: " + "%s\n", ad->device, (int)audioFormat->channels, + snd_strerror(-err)); + goto fail; + } + audioFormat->channels = channels; + + err = snd_pcm_hw_params_set_rate_near(ad->pcmHandle, hwparams, + &sampleRate, NULL); + if (err < 0 || sampleRate == 0) { + ERROR("ALSA device \"%s\" does not support %i Hz audio\n", + ad->device, (int)audioFormat->sampleRate); + goto fail; + } + audioFormat->sampleRate = sampleRate; + + buffer_time = ad->buffer_time; + cmd = "snd_pcm_hw_params_set_buffer_time_near"; + err = snd_pcm_hw_params_set_buffer_time_near(ad->pcmHandle, hwparams, + &buffer_time, NULL); + if (err < 0) + goto error; + + period_time = period_time_ro; + cmd = "snd_pcm_hw_params_set_period_time_near"; + err = snd_pcm_hw_params_set_period_time_near(ad->pcmHandle, hwparams, + &period_time, NULL); + if (err < 0) + goto error; + + cmd = "snd_pcm_hw_params"; + err = snd_pcm_hw_params(ad->pcmHandle, hwparams); + if (err == -EPIPE && --retry > 0) { + period_time_ro = period_time_ro >> 1; + goto configure_hw; + } else if (err < 0) + goto error; + if (retry != MPD_ALSA_RETRY_NR) + DEBUG("ALSA period_time set to %d\n", period_time); + + cmd = "snd_pcm_hw_params_get_buffer_size"; + err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_hw_params_get_period_size"; + err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, + NULL); + if (err < 0) + goto error; + + ad->canPause = snd_pcm_hw_params_can_pause(hwparams); + ad->canResume = snd_pcm_hw_params_can_resume(hwparams); + + /* configure SW params */ + snd_pcm_sw_params_alloca(&swparams); + + cmd = "snd_pcm_sw_params_current"; + err = snd_pcm_sw_params_current(ad->pcmHandle, swparams); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_start_threshold"; + err = snd_pcm_sw_params_set_start_threshold(ad->pcmHandle, swparams, + alsa_buffer_size - + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_avail_min"; + err = snd_pcm_sw_params_set_avail_min(ad->pcmHandle, swparams, + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_xfer_align"; + err = snd_pcm_sw_params_set_xfer_align(ad->pcmHandle, swparams, 1); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params"; + err = snd_pcm_sw_params(ad->pcmHandle, swparams); + if (err < 0) + goto error; + + ad->sampleSize = (audioFormat->bits / 8) * audioFormat->channels; + + audioOutput->open = 1; + + DEBUG("alsa device \"%s\" will be playing %i bit, %i channel audio at " + "%i Hz\n", ad->device, (int)audioFormat->bits, + channels, sampleRate); + + return 0; + +error: + if (cmd) { + ERROR("Error opening alsa device \"%s\" (%s): %s\n", + ad->device, cmd, snd_strerror(-err)); + } else { + ERROR("Error opening alsa device \"%s\": %s\n", ad->device, + snd_strerror(-err)); + } +fail: + if (ad->pcmHandle) + snd_pcm_close(ad->pcmHandle); + ad->pcmHandle = NULL; + audioOutput->open = 0; + return -1; +} + +static int alsa_errorRecovery(AlsaData * ad, int err) +{ + if (err == -EPIPE) { + DEBUG("Underrun on alsa device \"%s\"\n", ad->device); + } else if (err == -ESTRPIPE) { + DEBUG("alsa device \"%s\" was suspended\n", ad->device); + } + + switch (snd_pcm_state(ad->pcmHandle)) { + case SND_PCM_STATE_PAUSED: + err = snd_pcm_pause(ad->pcmHandle, /* disable */ 0); + break; + case SND_PCM_STATE_SUSPENDED: + err = ad->canResume ? + snd_pcm_resume(ad->pcmHandle) : + snd_pcm_prepare(ad->pcmHandle); + break; + case SND_PCM_STATE_SETUP: + case SND_PCM_STATE_XRUN: + err = snd_pcm_prepare(ad->pcmHandle); + break; + case SND_PCM_STATE_DISCONNECTED: + /* so alsa_closeDevice won't try to drain: */ + snd_pcm_close(ad->pcmHandle); + ad->pcmHandle = NULL; + break; + default: + /* unknown state, do nothing */ + break; + } + + return err; +} + +static void alsa_dropBufferedAudio(AudioOutput * audioOutput) +{ + AlsaData *ad = audioOutput->data; + + alsa_errorRecovery(ad, snd_pcm_drop(ad->pcmHandle)); +} + +static void alsa_closeDevice(AudioOutput * audioOutput) +{ + AlsaData *ad = audioOutput->data; + + if (ad->pcmHandle) { + snd_pcm_drain(ad->pcmHandle); + snd_pcm_close(ad->pcmHandle); + ad->pcmHandle = NULL; + } + + audioOutput->open = 0; +} + +static int alsa_playAudio(AudioOutput * audioOutput, char *playChunk, int size) +{ + AlsaData *ad = audioOutput->data; + int ret; + + size /= ad->sampleSize; + + while (size > 0) { + ret = ad->writei(ad->pcmHandle, playChunk, size); + + if (ret == -EAGAIN || ret == -EINTR) + continue; + + if (ret < 0) { + if (alsa_errorRecovery(ad, ret) < 0) { + ERROR("closing alsa device \"%s\" due to write " + "error: %s\n", ad->device, + snd_strerror(-errno)); + alsa_closeDevice(audioOutput); + return -1; + } + continue; + } + + playChunk += ret * ad->sampleSize; + size -= ret; + } + + return 0; +} + +AudioOutputPlugin alsaPlugin = { + "alsa", + alsa_testDefault, + alsa_initDriver, + alsa_finishDriver, + alsa_openDevice, + alsa_playAudio, + alsa_dropBufferedAudio, + alsa_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else /* HAVE ALSA */ + +DISABLED_AUDIO_OUTPUT_PLUGIN(alsaPlugin) +#endif /* HAVE_ALSA */ diff --git a/trunk/src/audioOutputs/audioOutput_ao.c b/trunk/src/audioOutputs/audioOutput_ao.c new file mode 100644 index 000000000..a7f437ef4 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_ao.c @@ -0,0 +1,246 @@ +/* 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 "../audioOutput.h" + +#ifdef HAVE_AO + +#include "../conf.h" +#include "../log.h" + +#include <string.h> + +#include <ao/ao.h> + +static int driverInitCount; + +typedef struct _AoData { + int writeSize; + int driverId; + ao_option *options; + ao_device *device; +} AoData; + +static AoData *newAoData(void) +{ + AoData *ret = xmalloc(sizeof(AoData)); + ret->device = NULL; + ret->options = NULL; + + return ret; +} + +static void audioOutputAo_error(void) +{ + if (errno == AO_ENOTLIVE) { + ERROR("not a live ao device\n"); + } else if (errno == AO_EOPENDEVICE) { + ERROR("not able to open audio device\n"); + } else if (errno == AO_EBADOPTION) { + ERROR("bad driver option\n"); + } +} + +static int audioOutputAo_initDriver(AudioOutput * audioOutput, + ConfigParam * param) +{ + ao_info *ai; + char *dup; + char *stk1; + char *stk2; + char *n1; + char *key; + char *value; + char *test; + AoData *ad = newAoData(); + BlockParam *blockParam; + + audioOutput->data = ad; + + if ((blockParam = getBlockParam(param, "write_size"))) { + ad->writeSize = strtol(blockParam->value, &test, 10); + if (*test != '\0') { + FATAL("\"%s\" is not a valid write size at line %i\n", + blockParam->value, blockParam->line); + } + } else + ad->writeSize = 1024; + + if (driverInitCount == 0) { + ao_initialize(); + } + driverInitCount++; + + blockParam = getBlockParam(param, "driver"); + + if (!blockParam || 0 == strcmp(blockParam->value, "default")) { + ad->driverId = ao_default_driver_id(); + } else if ((ad->driverId = ao_driver_id(blockParam->value)) < 0) { + FATAL("\"%s\" is not a valid ao driver at line %i\n", + blockParam->value, blockParam->line); + } + + if ((ai = ao_driver_info(ad->driverId)) == NULL) { + FATAL("problems getting driver info for device defined at line %i\n" + "you may not have permission to the audio device\n", param->line); + } + + DEBUG("using ao driver \"%s\" for \"%s\"\n", ai->short_name, + audioOutput->name); + + blockParam = getBlockParam(param, "options"); + + if (blockParam) { + dup = xstrdup(blockParam->value); + } else + dup = xstrdup(""); + + if (strlen(dup)) { + stk1 = NULL; + n1 = strtok_r(dup, ";", &stk1); + while (n1) { + stk2 = NULL; + key = strtok_r(n1, "=", &stk2); + if (!key) + FATAL("problems parsing options \"%s\"\n", n1); + /*found = 0; + for(i=0;i<ai->option_count;i++) { + if(strcmp(ai->options[i],key)==0) { + found = 1; + break; + } + } + if(!found) { + FATAL("\"%s\" is not an option for " + "\"%s\" ao driver\n",key, + ai->short_name); + } */ + value = strtok_r(NULL, "", &stk2); + if (!value) + FATAL("problems parsing options \"%s\"\n", n1); + ao_append_option(&ad->options, key, value); + n1 = strtok_r(NULL, ";", &stk1); + } + } + free(dup); + + return 0; +} + +static void freeAoData(AoData * ad) +{ + ao_free_options(ad->options); + free(ad); +} + +static void audioOutputAo_finishDriver(AudioOutput * audioOutput) +{ + AoData *ad = (AoData *) audioOutput->data; + freeAoData(ad); + + driverInitCount--; + + if (driverInitCount == 0) + ao_shutdown(); +} + +static void audioOutputAo_dropBufferedAudio(AudioOutput * audioOutput) +{ + /* not supported by libao */ +} + +static void audioOutputAo_closeDevice(AudioOutput * audioOutput) +{ + AoData *ad = (AoData *) audioOutput->data; + + if (ad->device) { + ao_close(ad->device); + ad->device = NULL; + } + + audioOutput->open = 0; +} + +static int audioOutputAo_openDevice(AudioOutput * audioOutput) +{ + ao_sample_format format; + AoData *ad = (AoData *) audioOutput->data; + + if (ad->device) { + audioOutputAo_closeDevice(audioOutput); + } + + format.bits = audioOutput->outAudioFormat.bits; + format.rate = audioOutput->outAudioFormat.sampleRate; + format.byte_format = AO_FMT_NATIVE; + format.channels = audioOutput->outAudioFormat.channels; + + ad->device = ao_open_live(ad->driverId, &format, ad->options); + + if (ad->device == NULL) + return -1; + + audioOutput->open = 1; + + return 0; +} + +static int audioOutputAo_play(AudioOutput * audioOutput, char *playChunk, + int size) +{ + int send; + AoData *ad = (AoData *) audioOutput->data; + + if (ad->device == NULL) + return -1; + + while (size > 0) { + send = ad->writeSize > size ? size : ad->writeSize; + + if (ao_play(ad->device, playChunk, send) == 0) { + audioOutputAo_error(); + ERROR("closing audio device due to write error\n"); + audioOutputAo_closeDevice(audioOutput); + return -1; + } + + playChunk += send; + size -= send; + } + + return 0; +} + +AudioOutputPlugin aoPlugin = { + "ao", + NULL, + audioOutputAo_initDriver, + audioOutputAo_finishDriver, + audioOutputAo_openDevice, + audioOutputAo_play, + audioOutputAo_dropBufferedAudio, + audioOutputAo_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else + +#include <stdio.h> + +DISABLED_AUDIO_OUTPUT_PLUGIN(aoPlugin) +#endif diff --git a/trunk/src/audioOutputs/audioOutput_jack.c b/trunk/src/audioOutputs/audioOutput_jack.c new file mode 100644 index 000000000..1fdfaf4bb --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_jack.c @@ -0,0 +1,440 @@ +/* jack plug in for the Music Player Daemon (MPD) + * (c)2006 by anarch(anarchsss@gmail.com) + * + * 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 "../audioOutput.h" + +#ifdef HAVE_JACK + +#include <stdlib.h> +#include <errno.h> + +#include "../conf.h" +#include "../log.h" + +#include <string.h> +#include <pthread.h> + +#include <jack/jack.h> +#include <jack/types.h> +#include <jack/ringbuffer.h> + +pthread_mutex_t play_audio_lock = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t play_audio = PTHREAD_COND_INITIALIZER; + +/*#include "dmalloc.h"*/ + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +/*#define SAMPLE_SIZE sizeof(jack_default_audio_sample_t);*/ + + +static char *name = "mpd"; +static char *output_ports[2]; +static int ringbuf_sz = 32768; +size_t sample_size = sizeof(jack_default_audio_sample_t); + +typedef struct _JackData { + jack_port_t *ports[2]; + jack_client_t *client; + jack_ringbuffer_t *ringbuffer[2]; + int bps; + int shutdown; +} JackData; + +/*JackData *jd = NULL;*/ + +static JackData *newJackData(void) +{ + JackData *ret; + ret = xcalloc(sizeof(JackData), 1); + + return ret; +} + +static void freeJackData(AudioOutput *audioOutput) +{ + JackData *jd = audioOutput->data; + if (jd) { + if (jd->ringbuffer[0]) + jack_ringbuffer_free(jd->ringbuffer[0]); + if (jd->ringbuffer[1]) + jack_ringbuffer_free(jd->ringbuffer[1]); + free(jd); + audioOutput->data = NULL; + } +} + +static void jack_finishDriver(AudioOutput *audioOutput) +{ + JackData *jd = audioOutput->data; + int i; + + if ( jd && jd->client ) { + jack_deactivate(jd->client); + jack_client_close(jd->client); + } + DEBUG("disconnect_jack (pid=%d)\n", getpid ()); + + if ( strcmp(name, "mpd") ) { + free(name); + name = "mpd"; + } + + for ( i = ARRAY_SIZE(output_ports); --i >= 0; ) { + if (!output_ports[i]) + continue; + free(output_ports[i]); + output_ports[i] = NULL; + } + + freeJackData(audioOutput); +} + +static int srate(jack_nframes_t rate, void *data) +{ + JackData *jd = (JackData *) ((AudioOutput*) data)->data; + AudioFormat *audioFormat = &(((AudioOutput*) data)->outAudioFormat); + + audioFormat->sampleRate = (int)jack_get_sample_rate(jd->client); + + return 0; +} + +static int process(jack_nframes_t nframes, void *arg) +{ + size_t i; + JackData *jd = (JackData *) arg; + jack_default_audio_sample_t *out[2]; + size_t avail_data, avail_frames; + + if ( nframes <= 0 ) + return 0; + + out[0] = jack_port_get_buffer(jd->ports[0], nframes); + out[1] = jack_port_get_buffer(jd->ports[1], nframes); + + while ( nframes ) { + avail_data = jack_ringbuffer_read_space(jd->ringbuffer[1]); + + if ( avail_data > 0 ) { + avail_frames = avail_data / sample_size; + + if (avail_frames > nframes) { + avail_frames = nframes; + avail_data = nframes*sample_size; + } + + jack_ringbuffer_read(jd->ringbuffer[0], (char *)out[0], + avail_data); + jack_ringbuffer_read(jd->ringbuffer[1], (char *)out[1], + avail_data); + + nframes -= avail_frames; + out[0] += avail_data; + out[1] += avail_data; + } else { + for (i = 0; i < nframes; i++) + out[0][i] = out[1][i] = 0.0; + nframes = 0; + } + + if (pthread_mutex_trylock (&play_audio_lock) == 0) { + pthread_cond_signal (&play_audio); + pthread_mutex_unlock (&play_audio_lock); + } + } + + + /*DEBUG("process (pid=%d)\n", getpid());*/ + return 0; +} + +static void shutdown_callback(void *arg) +{ + JackData *jd = (JackData *) arg; + jd->shutdown = 1; +} + +static void set_audioformat(AudioOutput *audioOutput) +{ + JackData *jd = audioOutput->data; + AudioFormat *audioFormat = &audioOutput->outAudioFormat; + + audioFormat->sampleRate = (int) jack_get_sample_rate(jd->client); + DEBUG("samplerate = %d\n", audioFormat->sampleRate); + audioFormat->channels = 2; + audioFormat->bits = 16; + jd->bps = audioFormat->channels + * sizeof(jack_default_audio_sample_t) + * audioFormat->sampleRate; +} + +static void error_callback(const char *msg) +{ + ERROR("jack: %s\n", msg); +} + +static int jack_initDriver(AudioOutput *audioOutput, ConfigParam *param) +{ + BlockParam *bp; + char *endptr; + int val; + char *cp = NULL; + + DEBUG("jack_initDriver (pid=%d)\n", getpid()); + if ( ! param ) return 0; + + if ( (bp = getBlockParam(param, "ports")) ) { + DEBUG("output_ports=%s\n", bp->value); + + if (!(cp = strchr(bp->value, ','))) + FATAL("expected comma and a second value for '%s' " + "at line %d: %s\n", + bp->name, bp->line, bp->value); + + *cp = '\0'; + output_ports[0] = xstrdup(bp->value); + *cp++ = ','; + + if (!*cp) + FATAL("expected a second value for '%s' at line %d: " + "%s\n", bp->name, bp->line, bp->value); + + output_ports[1] = xstrdup(cp); + + if (strchr(cp,',')) + FATAL("Only %d values are supported for '%s' " + "at line %d\n", (int)ARRAY_SIZE(output_ports), + bp->name, bp->line); + } + + if ( (bp = getBlockParam(param, "ringbuffer_size")) ) { + errno = 0; + val = strtol(bp->value, &endptr, 10); + + if ( errno == 0 && endptr != bp->value) { + ringbuf_sz = val < 32768 ? 32768 : val; + DEBUG("ringbuffer_size=%d\n", ringbuf_sz); + } else { + FATAL("%s is not a number; ringbuf_size=%d\n", + bp->value, ringbuf_sz); + } + } + + if ( (bp = getBlockParam(param, "name")) + && (strcmp(bp->value, "mpd") != 0) ) { + name = xstrdup(bp->value); + DEBUG("name=%s\n", name); + } + + return 0; +} + +static int jack_testDefault(void) +{ + return 0; +} + +static int connect_jack(AudioOutput *audioOutput) +{ + JackData *jd = audioOutput->data; + char **jports; + char *port_name; + + if ( (jd->client = jack_client_new(name)) == NULL ) { + ERROR("jack server not running?\n"); + freeJackData(audioOutput); + return -1; + } + + jack_set_error_function(error_callback); + jack_set_process_callback(jd->client, process, (void *)jd); + jack_set_sample_rate_callback(jd->client, (JackProcessCallback)srate, + (void *)audioOutput); + jack_on_shutdown(jd->client, shutdown_callback, (void *)jd); + + if ( jack_activate(jd->client) ) { + ERROR("cannot activate client"); + freeJackData(audioOutput); + return -1; + } + + jd->ports[0] = jack_port_register(jd->client, "left", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if ( !jd->ports[0] ) { + ERROR("Cannot register left output port.\n"); + freeJackData(audioOutput); + return -1; + } + + jd->ports[1] = jack_port_register(jd->client, "right", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if ( !jd->ports[1] ) { + ERROR("Cannot register right output port.\n"); + freeJackData(audioOutput); + return -1; + } + + /* hay que buscar que hay */ + if ( !output_ports[1] + && (jports = (char **)jack_get_ports(jd->client, NULL, NULL, + JackPortIsPhysical| + JackPortIsInput)) ) { + output_ports[0] = jports[0]; + output_ports[1] = jports[1] ? jports[1] : jports[0]; + DEBUG("output_ports: %s %s\n", output_ports[0], output_ports[1]); + free(jports); + } + + if ( output_ports[1] ) { + jd->ringbuffer[0] = jack_ringbuffer_create(ringbuf_sz); + jd->ringbuffer[1] = jack_ringbuffer_create(ringbuf_sz); + memset(jd->ringbuffer[0]->buf, 0, jd->ringbuffer[0]->size); + memset(jd->ringbuffer[1]->buf, 0, jd->ringbuffer[1]->size); + + port_name = xmalloc(sizeof(char)*(7+strlen(name))); + + sprintf(port_name, "%s:left", name); + if ( (jack_connect(jd->client, port_name, + output_ports[0])) != 0 ) { + ERROR("%s is not a valid Jack Client / Port ", + output_ports[0]); + freeJackData(audioOutput); + free(port_name); + return -1; + } + sprintf(port_name, "%s:right", name); + if ( (jack_connect(jd->client, port_name, + output_ports[1])) != 0 ) { + ERROR("%s is not a valid Jack Client / Port ", + output_ports[1]); + freeJackData(audioOutput); + free(port_name); + return -1; + } + free(port_name); + } + + DEBUG("connect_jack (pid=%d)\n", getpid()); + return 1; +} + +static int jack_openDevice(AudioOutput *audioOutput) +{ + JackData *jd = audioOutput->data; + + if ( !jd ) { + DEBUG("connect!\n"); + jd = newJackData(); + audioOutput->data = jd; + + if (connect_jack(audioOutput) < 0) { + freeJackData(audioOutput); + audioOutput->open = 0; + return -1; + } + } + + set_audioformat(audioOutput); + audioOutput->open = 1; + + DEBUG("jack_openDevice (pid=%d)!\n", getpid ()); + return 0; +} + + +static void jack_closeDevice(AudioOutput * audioOutput) +{ + /*jack_finishDriver(audioOutput);*/ + audioOutput->open = 0; + DEBUG("jack_closeDevice (pid=%d)\n", getpid()); +} + +static void jack_dropBufferedAudio (AudioOutput * audioOutput) +{ +} + +static int jack_playAudio(AudioOutput * audioOutput, char *buff, int size) +{ + JackData *jd = audioOutput->data; + size_t space; + int i; + short *buffer = (short *) buff; + jack_default_audio_sample_t sample; + size_t samples = size/4; + + /*DEBUG("jack_playAudio: (pid=%d)!\n", getpid());*/ + + if ( jd->shutdown ) { + ERROR("Refusing to play, because there is no client thread.\n"); + freeJackData(audioOutput); + audioOutput->open = 0; + return 0; + } + + while ( samples && !jd->shutdown ) { + + if ( (space = jack_ringbuffer_write_space(jd->ringbuffer[0])) + >= samples*sample_size ) { + + /*space = MIN(space, samples*sample_size);*/ + /*space = samples*sample_size;*/ + + /*for(i=0; i<space/sample_size; i++) {*/ + for(i=0; i<samples; i++) { + sample = (jack_default_audio_sample_t) *(buffer++)/32768.0; + + jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample, + sample_size); + + sample = (jack_default_audio_sample_t) *(buffer++)/32768.0; + + jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample, + sample_size); + + /*samples--;*/ + } + samples=0; + + } else { + pthread_mutex_lock(&play_audio_lock); + pthread_cond_wait(&play_audio, &play_audio_lock); + pthread_mutex_unlock(&play_audio_lock); + } + + } + return 0; +} + +AudioOutputPlugin jackPlugin = { + "jack", + jack_testDefault, + jack_initDriver, + jack_finishDriver, + jack_openDevice, + jack_playAudio, + jack_dropBufferedAudio, + jack_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else /* HAVE JACK */ + +DISABLED_AUDIO_OUTPUT_PLUGIN(jackPlugin) + +#endif /* HAVE_JACK */ diff --git a/trunk/src/audioOutputs/audioOutput_mvp.c b/trunk/src/audioOutputs/audioOutput_mvp.c new file mode 100644 index 000000000..ea365c657 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_mvp.c @@ -0,0 +1,284 @@ +/* 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 + * + * Media MVP audio output based on code from MVPMC project: + * http://mvpmc.sourceforge.net/ + * + * 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 "../audioOutput.h" + +#include <stdlib.h> + +#ifdef HAVE_MVP + +#include "../conf.h" +#include "../log.h" + +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +typedef struct { + unsigned long dsp_status; + unsigned long stream_decode_type; + unsigned long sample_rate; + unsigned long bit_rate; + unsigned long raw[64 / sizeof(unsigned long)]; +} aud_status_t; + +#define MVP_SET_AUD_STOP _IOW('a',1,int) +#define MVP_SET_AUD_PLAY _IOW('a',2,int) +#define MVP_SET_AUD_PAUSE _IOW('a',3,int) +#define MVP_SET_AUD_UNPAUSE _IOW('a',4,int) +#define MVP_SET_AUD_SRC _IOW('a',5,int) +#define MVP_SET_AUD_MUTE _IOW('a',6,int) +#define MVP_SET_AUD_BYPASS _IOW('a',8,int) +#define MVP_SET_AUD_CHANNEL _IOW('a',9,int) +#define MVP_GET_AUD_STATUS _IOR('a',10,aud_status_t) +#define MVP_SET_AUD_VOLUME _IOW('a',13,int) +#define MVP_GET_AUD_VOLUME _IOR('a',14,int) +#define MVP_SET_AUD_STREAMTYPE _IOW('a',15,int) +#define MVP_SET_AUD_FORMAT _IOW('a',16,int) +#define MVP_GET_AUD_SYNC _IOR('a',21,pts_sync_data_t*) +#define MVP_SET_AUD_STC _IOW('a',22,long long int *) +#define MVP_SET_AUD_SYNC _IOW('a',23,int) +#define MVP_SET_AUD_END_STREAM _IOW('a',25,int) +#define MVP_SET_AUD_RESET _IOW('a',26,int) +#define MVP_SET_AUD_DAC_CLK _IOW('a',27,int) +#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*) + +typedef struct _MvpData { + int fd; +} MvpData; + +static int pcmfrequencies[][3] = { + {9, 8000, 32000}, + {10, 11025, 44100}, + {11, 12000, 48000}, + {1, 16000, 32000}, + {2, 22050, 44100}, + {3, 24000, 48000}, + {5, 32000, 32000}, + {0, 44100, 44100}, + {7, 48000, 48000}, + {13, 64000, 32000}, + {14, 88200, 44100}, + {15, 96000, 48000} +}; + +static int numfrequencies = sizeof(pcmfrequencies) / 12; + +static int mvp_testDefault(void) +{ + int fd; + + fd = open("/dev/adec_pcm", O_WRONLY); + + if (fd) { + close(fd); + return 0; + } + + WARNING("Error opening PCM device \"/dev/adec_pcm\": %s\n", + strerror(errno)); + + return -1; +} + +static int mvp_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + MvpData *md = xmalloc(sizeof(MvpData)); + md->fd = -1; + audioOutput->data = md; + + return 0; +} + +static void mvp_finishDriver(AudioOutput * audioOutput) +{ + MvpData *md = audioOutput->data; + free(md); +} + +static int mvp_setPcmParams(MvpData * md, unsigned long rate, int channels, + int big_endian, int bits) +{ + int iloop; + int mix[5]; + + if (channels == 1) + mix[0] = 1; + else if (channels == 2) + mix[0] = 0; + else + return -1; + + /* 0,1=24bit(24) , 2,3=16bit */ + if (bits == 16) + mix[1] = 2; + else if (bits == 24) + mix[1] = 0; + else + return -1; + + mix[3] = 0; /* stream type? */ + + if (big_endian == 1) + mix[4] = 1; + else if (big_endian == 0) + mix[4] = 0; + else + return -1; + + /* + * if there is an exact match for the frequency, use it. + */ + for (iloop = 0; iloop < numfrequencies; iloop++) { + if (rate == pcmfrequencies[iloop][1]) { + mix[2] = pcmfrequencies[iloop][0]; + break; + } + } + + if (iloop >= numfrequencies) { + ERROR("Can not find suitable output frequency for %ld\n", rate); + return -1; + } + + if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { + ERROR("Can not set audio format\n"); + return -1; + } + + if (ioctl(md->fd, MVP_SET_AUD_SYNC, 2) != 0) { + ERROR("Can not set audio sync\n"); + return -1; + } + + if (ioctl(md->fd, MVP_SET_AUD_PLAY, 0) < 0) { + ERROR("Can not set audio play mode\n"); + return -1; + } + + return 0; +} + +static int mvp_openDevice(AudioOutput * audioOutput) +{ + long long int stc = 0; + MvpData *md = audioOutput->data; + AudioFormat *audioFormat = &audioOutput->outAudioFormat; + int mix[5] = { 0, 2, 7, 1, 0 }; + + if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) { + ERROR("Error opening /dev/adec_pcm: %s\n", strerror(errno)); + return -1; + } + if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) { + ERROR("Error setting audio source: %s\n", strerror(errno)); + return -1; + } + if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) { + ERROR("Error setting audio streamtype: %s\n", strerror(errno)); + return -1; + } + if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { + ERROR("Error setting audio format: %s\n", strerror(errno)); + return -1; + } + ioctl(md->fd, MVP_SET_AUD_STC, &stc); + if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) { + ERROR("Error setting audio streamtype: %s\n", strerror(errno)); + return -1; + } +#ifdef WORDS_BIGENDIAN + mvp_setPcmParams(md, audioFormat->sampleRate, audioFormat->channels, 0, + audioFormat->bits); +#else + mvp_setPcmParams(md, audioFormat->sampleRate, audioFormat->channels, 1, + audioFormat->bits); +#endif + audioOutput->open = 1; + return 0; +} + +static void mvp_closeDevice(AudioOutput * audioOutput) +{ + MvpData *md = audioOutput->data; + if (md->fd >= 0) + close(md->fd); + md->fd = -1; + audioOutput->open = 0; +} + +static void mvp_dropBufferedAudio(AudioOutput * audioOutput) +{ + MvpData *md = audioOutput->data; + if (md->fd >= 0) { + ioctl(md->fd, MVP_SET_AUD_RESET, 0x11); + close(md->fd); + md->fd = -1; + audioOutput->open = 0; + } +} + +static int mvp_playAudio(AudioOutput * audioOutput, char *playChunk, int size) +{ + MvpData *md = audioOutput->data; + int ret; + + /* reopen the device since it was closed by dropBufferedAudio */ + if (md->fd < 0) + mvp_openDevice(audioOutput); + + while (size > 0) { + ret = write(md->fd, playChunk, size); + if (ret < 0) { + if (errno == EINTR) + continue; + ERROR("closing mvp PCM device due to write error: " + "%s\n", strerror(errno)); + mvp_closeDevice(audioOutput); + return -1; + } + playChunk += ret; + size -= ret; + } + return 0; +} + +AudioOutputPlugin mvpPlugin = { + "mvp", + mvp_testDefault, + mvp_initDriver, + mvp_finishDriver, + mvp_openDevice, + mvp_playAudio, + mvp_dropBufferedAudio, + mvp_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else /* HAVE_MVP */ + +DISABLED_AUDIO_OUTPUT_PLUGIN(mvpPlugin) +#endif /* HAVE_MVP */ diff --git a/trunk/src/audioOutputs/audioOutput_oss.c b/trunk/src/audioOutputs/audioOutput_oss.c new file mode 100644 index 000000000..01293cbd1 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_oss.c @@ -0,0 +1,575 @@ +/* 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 + * + * OSS audio output (c) 2004, 2005, 2006, 2007 by Eric Wong <eric@petta-tech.com> + * and Warren Dukes <warren.dukes@gmail.com> + * + * 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 "../audioOutput.h" + +#include <stdlib.h> + +#ifdef HAVE_OSS + +#include "../conf.h" +#include "../log.h" + +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include <soundcard.h> +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include <sys/soundcard.h> +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +#ifdef WORDS_BIGENDIAN +# define AFMT_S16_MPD AFMT_S16_BE +#else +# define AFMT_S16_MPD AFMT_S16_LE +#endif /* WORDS_BIGENDIAN */ + +typedef struct _OssData { + int fd; + const char *device; + int channels; + int sampleRate; + int bitFormat; + int bits; + int *supported[3]; + int numSupported[3]; + int *unsupported[3]; + int numUnsupported[3]; +} OssData; + +#define OSS_SUPPORTED 1 +#define OSS_UNSUPPORTED 0 +#define OSS_UNKNOWN -1 + +#define OSS_RATE 0 +#define OSS_CHANNELS 1 +#define OSS_BITS 2 + +static int getIndexForParam(int param) +{ + int index = 0; + + switch (param) { + case SNDCTL_DSP_SPEED: + index = OSS_RATE; + break; + case SNDCTL_DSP_CHANNELS: + index = OSS_CHANNELS; + break; + case SNDCTL_DSP_SAMPLESIZE: + index = OSS_BITS; + break; + } + + return index; +} + +static int findSupportedParam(OssData * od, int param, int val) +{ + int i; + int index = getIndexForParam(param); + + for (i = 0; i < od->numSupported[index]; i++) { + if (od->supported[index][i] == val) + return 1; + } + + return 0; +} + +static int canConvert(int index, int val) +{ + switch (index) { + case OSS_BITS: + if (val != 16) + return 0; + break; + case OSS_CHANNELS: + if (val != 2) + return 0; + break; + } + + return 1; +} + +static int getSupportedParam(OssData * od, int param, int val) +{ + int i; + int index = getIndexForParam(param); + int ret = -1; + int least = val; + int diff; + + for (i = 0; i < od->numSupported[index]; i++) { + diff = od->supported[index][i] - val; + if (diff < 0) + diff = -diff; + if (diff < least) { + if (!canConvert(index, od->supported[index][i])) { + continue; + } + least = diff; + ret = od->supported[index][i]; + } + } + + return ret; +} + +static int findUnsupportedParam(OssData * od, int param, int val) +{ + int i; + int index = getIndexForParam(param); + + for (i = 0; i < od->numUnsupported[index]; i++) { + if (od->unsupported[index][i] == val) + return 1; + } + + return 0; +} + +static void addSupportedParam(OssData * od, int param, int val) +{ + int index = getIndexForParam(param); + + od->numSupported[index]++; + od->supported[index] = xrealloc(od->supported[index], + od->numSupported[index] * sizeof(int)); + od->supported[index][od->numSupported[index] - 1] = val; +} + +static void addUnsupportedParam(OssData * od, int param, int val) +{ + int index = getIndexForParam(param); + + od->numUnsupported[index]++; + od->unsupported[index] = xrealloc(od->unsupported[index], + od->numUnsupported[index] * + sizeof(int)); + od->unsupported[index][od->numUnsupported[index] - 1] = val; +} + +static void removeSupportedParam(OssData * od, int param, int val) +{ + int i = 0; + int j = 0; + int index = getIndexForParam(param); + + for (i = 0; i < od->numSupported[index] - 1; i++) { + if (od->supported[index][i] == val) + j = 1; + od->supported[index][i] = od->supported[index][i + j]; + } + + od->numSupported[index]--; + od->supported[index] = xrealloc(od->supported[index], + od->numSupported[index] * sizeof(int)); +} + +static void removeUnsupportedParam(OssData * od, int param, int val) +{ + int i = 0; + int j = 0; + int index = getIndexForParam(param); + + for (i = 0; i < od->numUnsupported[index] - 1; i++) { + if (od->unsupported[index][i] == val) + j = 1; + od->unsupported[index][i] = od->unsupported[index][i + j]; + } + + od->numUnsupported[index]--; + od->unsupported[index] = xrealloc(od->unsupported[index], + od->numUnsupported[index] * + sizeof(int)); +} + +static int isSupportedParam(OssData * od, int param, int val) +{ + if (findSupportedParam(od, param, val)) + return OSS_SUPPORTED; + if (findUnsupportedParam(od, param, val)) + return OSS_UNSUPPORTED; + return OSS_UNKNOWN; +} + +static void supportParam(OssData * od, int param, int val) +{ + int supported = isSupportedParam(od, param, val); + + if (supported == OSS_SUPPORTED) + return; + + if (supported == OSS_UNSUPPORTED) { + removeUnsupportedParam(od, param, val); + } + + addSupportedParam(od, param, val); +} + +static void unsupportParam(OssData * od, int param, int val) +{ + int supported = isSupportedParam(od, param, val); + + if (supported == OSS_UNSUPPORTED) + return; + + if (supported == OSS_SUPPORTED) { + removeSupportedParam(od, param, val); + } + + addUnsupportedParam(od, param, val); +} + +static OssData *newOssData(void) +{ + OssData *ret = xmalloc(sizeof(OssData)); + + ret->device = NULL; + ret->fd = -1; + + ret->supported[OSS_RATE] = NULL; + ret->supported[OSS_CHANNELS] = NULL; + ret->supported[OSS_BITS] = NULL; + ret->unsupported[OSS_RATE] = NULL; + ret->unsupported[OSS_CHANNELS] = NULL; + ret->unsupported[OSS_BITS] = NULL; + + ret->numSupported[OSS_RATE] = 0; + ret->numSupported[OSS_CHANNELS] = 0; + ret->numSupported[OSS_BITS] = 0; + ret->numUnsupported[OSS_RATE] = 0; + ret->numUnsupported[OSS_CHANNELS] = 0; + ret->numUnsupported[OSS_BITS] = 0; + + supportParam(ret, SNDCTL_DSP_SPEED, 48000); + supportParam(ret, SNDCTL_DSP_SPEED, 44100); + supportParam(ret, SNDCTL_DSP_CHANNELS, 2); + supportParam(ret, SNDCTL_DSP_SAMPLESIZE, 16); + + return ret; +} + +static void freeOssData(OssData * od) +{ + if (od->supported[OSS_RATE]) + free(od->supported[OSS_RATE]); + if (od->supported[OSS_CHANNELS]) + free(od->supported[OSS_CHANNELS]); + if (od->supported[OSS_BITS]) + free(od->supported[OSS_BITS]); + if (od->unsupported[OSS_RATE]) + free(od->unsupported[OSS_RATE]); + if (od->unsupported[OSS_CHANNELS]) + free(od->unsupported[OSS_CHANNELS]); + if (od->unsupported[OSS_BITS]) + free(od->unsupported[OSS_BITS]); + + free(od); +} + +#define OSS_STAT_NO_ERROR 0 +#define OSS_STAT_NOT_CHAR_DEV -1 +#define OSS_STAT_NO_PERMS -2 +#define OSS_STAT_DOESN_T_EXIST -3 +#define OSS_STAT_OTHER -4 + +static int oss_statDevice(const char *device, int *stErrno) +{ + struct stat st; + + if (0 == stat(device, &st)) { + if (!S_ISCHR(st.st_mode)) { + return OSS_STAT_NOT_CHAR_DEV; + } + } else { + *stErrno = errno; + + switch (errno) { + case ENOENT: + case ENOTDIR: + return OSS_STAT_DOESN_T_EXIST; + case EACCES: + return OSS_STAT_NO_PERMS; + default: + return OSS_STAT_OTHER; + } + } + + return 0; +} + +static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; + +static int oss_testDefault(void) +{ + int fd, i; + + for (i = ARRAY_SIZE(default_devices); --i >= 0; ) { + if ((fd = open(default_devices[i], O_WRONLY)) >= 0) { + xclose(fd); + return 0; + } + WARNING("Error opening OSS device \"%s\": %s\n", + default_devices[i], strerror(errno)); + } + + return -1; +} + +static int oss_open_default(AudioOutput *ao, ConfigParam *param, OssData *od) +{ + int i; + int err[ARRAY_SIZE(default_devices)]; + int ret[ARRAY_SIZE(default_devices)]; + + for (i = ARRAY_SIZE(default_devices); --i >= 0; ) { + ret[i] = oss_statDevice(default_devices[i], &err[i]); + if (ret[i] == 0) { + od->device = default_devices[i]; + return 0; + } + } + + if (param) + ERROR("error trying to open specified OSS device" + " at line %i\n", param->line); + else + ERROR("error trying to open default OSS device\n"); + + for (i = ARRAY_SIZE(default_devices); --i >= 0; ) { + const char *dev = default_devices[i]; + switch(ret[i]) { + case OSS_STAT_DOESN_T_EXIST: + ERROR("%s not found\n", dev); + break; + case OSS_STAT_NOT_CHAR_DEV: + ERROR("%s is not a character device\n", dev); + break; + case OSS_STAT_NO_PERMS: + ERROR("%s: permission denied\n", dev); + break; + default: + ERROR("Error accessing %s: %s", dev, strerror(err[i])); + } + } + exit(EXIT_FAILURE); + return 0; /* some compilers can be dumb... */ +} + +static int oss_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + OssData *od = newOssData(); + audioOutput->data = od; + if (param) { + BlockParam *bp = getBlockParam(param, "device"); + if (bp) { + od->device = bp->value; + return 0; + } + } + return oss_open_default(audioOutput, param, od); +} + +static void oss_finishDriver(AudioOutput * audioOutput) +{ + OssData *od = audioOutput->data; + + freeOssData(od); +} + +static int setParam(OssData * od, int param, int *value) +{ + int val = *value; + int copy; + int supported = isSupportedParam(od, param, val); + + do { + if (supported == OSS_UNSUPPORTED) { + val = getSupportedParam(od, param, val); + if (copy < 0) + return -1; + } + copy = val; + if (ioctl(od->fd, param, ©)) { + unsupportParam(od, param, val); + supported = OSS_UNSUPPORTED; + } else { + if (supported == OSS_UNKNOWN) { + supportParam(od, param, val); + supported = OSS_SUPPORTED; + } + val = copy; + } + } while (supported == OSS_UNSUPPORTED); + + *value = val; + + return 0; +} + +static void oss_close(OssData * od) +{ + if (od->fd >= 0) + while (close(od->fd) && errno == EINTR) ; + od->fd = -1; +} + +static int oss_open(AudioOutput * audioOutput) +{ + int tmp; + OssData *od = audioOutput->data; + + if ((od->fd = open(od->device, O_WRONLY)) < 0) { + ERROR("Error opening OSS device \"%s\": %s\n", od->device, + strerror(errno)); + goto fail; + } + + if (setParam(od, SNDCTL_DSP_CHANNELS, &od->channels)) { + ERROR("OSS device \"%s\" does not support %i channels: %s\n", + od->device, od->channels, strerror(errno)); + goto fail; + } + + if (setParam(od, SNDCTL_DSP_SPEED, &od->sampleRate)) { + ERROR("OSS device \"%s\" does not support %i Hz audio: %s\n", + od->device, od->sampleRate, strerror(errno)); + goto fail; + } + + switch (od->bits) { + case 8: + tmp = AFMT_S8; + break; + case 16: + tmp = AFMT_S16_MPD; + } + + if (setParam(od, SNDCTL_DSP_SAMPLESIZE, &tmp)) { + ERROR("OSS device \"%s\" does not support %i bit audio: %s\n", + od->device, tmp, strerror(errno)); + goto fail; + } + + audioOutput->open = 1; + + return 0; + +fail: + oss_close(od); + audioOutput->open = 0; + return -1; +} + +static int oss_openDevice(AudioOutput * audioOutput) +{ + int ret = -1; + OssData *od = audioOutput->data; + AudioFormat *audioFormat = &audioOutput->outAudioFormat; + + od->channels = audioFormat->channels; + od->sampleRate = audioFormat->sampleRate; + od->bits = audioFormat->bits; + + if ((ret = oss_open(audioOutput)) < 0) + return ret; + + audioFormat->channels = od->channels; + audioFormat->sampleRate = od->sampleRate; + audioFormat->bits = od->bits; + + DEBUG("oss device \"%s\" will be playing %i bit %i channel audio at " + "%i Hz\n", od->device, od->bits, od->channels, od->sampleRate); + + return ret; +} + +static void oss_closeDevice(AudioOutput * audioOutput) +{ + OssData *od = audioOutput->data; + + oss_close(od); + + audioOutput->open = 0; +} + +static void oss_dropBufferedAudio(AudioOutput * audioOutput) +{ + OssData *od = audioOutput->data; + + if (od->fd >= 0) { + ioctl(od->fd, SNDCTL_DSP_RESET, 0); + oss_close(od); + } +} + +static int oss_playAudio(AudioOutput * audioOutput, char *playChunk, int size) +{ + OssData *od = audioOutput->data; + int ret; + + /* reopen the device since it was closed by dropBufferedAudio */ + if (od->fd < 0 && oss_open(audioOutput) < 0) + return -1; + + while (size > 0) { + ret = write(od->fd, playChunk, size); + if (ret < 0) { + if (errno == EINTR) + continue; + ERROR("closing oss device \"%s\" due to write error: " + "%s\n", od->device, strerror(errno)); + oss_closeDevice(audioOutput); + return -1; + } + playChunk += ret; + size -= ret; + } + + return 0; +} + +AudioOutputPlugin ossPlugin = { + "oss", + oss_testDefault, + oss_initDriver, + oss_finishDriver, + oss_openDevice, + oss_playAudio, + oss_dropBufferedAudio, + oss_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else /* HAVE OSS */ + +DISABLED_AUDIO_OUTPUT_PLUGIN(ossPlugin) +#endif /* HAVE_OSS */ diff --git a/trunk/src/audioOutputs/audioOutput_osx.c b/trunk/src/audioOutputs/audioOutput_osx.c new file mode 100644 index 000000000..1caebade5 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_osx.c @@ -0,0 +1,374 @@ +/* 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 "../audioOutput.h" + +#ifdef HAVE_OSX + +#include <AudioUnit/AudioUnit.h> +#include <stdlib.h> +#include <pthread.h> + +#include "../log.h" + +typedef struct _OsxData { + AudioUnit au; + pthread_mutex_t mutex; + pthread_cond_t condition; + char *buffer; + int bufferSize; + int pos; + int len; + int started; +} OsxData; + +static OsxData *newOsxData() +{ + OsxData *ret = xmalloc(sizeof(OsxData)); + + pthread_mutex_init(&ret->mutex, NULL); + pthread_cond_init(&ret->condition, NULL); + + ret->pos = 0; + ret->len = 0; + ret->started = 0; + ret->buffer = NULL; + ret->bufferSize = 0; + + return ret; +} + +static int osx_testDefault() +{ + /*AudioUnit au; + ComponentDescription desc; + Component comp; + + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_Output; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + comp = FindNextComponent(NULL, &desc); + if(!comp) { + ERROR("Unable to open default OS X defice\n"); + return -1; + } + + if(OpenAComponent(comp, &au) != noErr) { + ERROR("Unable to open default OS X defice\n"); + return -1; + } + + CloseComponent(au); */ + + return 0; +} + +static int osx_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + OsxData *od = newOsxData(); + + audioOutput->data = od; + + return 0; +} + +static void freeOsxData(OsxData * od) +{ + if (od->buffer) + free(od->buffer); + pthread_mutex_destroy(&od->mutex); + pthread_cond_destroy(&od->condition); + free(od); +} + +static void osx_finishDriver(AudioOutput * audioOutput) +{ + OsxData *od = (OsxData *) audioOutput->data; + freeOsxData(od); +} + +static void osx_dropBufferedAudio(AudioOutput * audioOutput) +{ + OsxData *od = (OsxData *) audioOutput->data; + + pthread_mutex_lock(&od->mutex); + od->len = 0; + pthread_mutex_unlock(&od->mutex); +} + +static void osx_closeDevice(AudioOutput * audioOutput) +{ + OsxData *od = (OsxData *) audioOutput->data; + + pthread_mutex_lock(&od->mutex); + while (od->len) { + pthread_cond_wait(&od->condition, &od->mutex); + } + pthread_mutex_unlock(&od->mutex); + + if (od->started) { + AudioOutputUnitStop(od->au); + od->started = 0; + } + + CloseComponent(od->au); + AudioUnitUninitialize(od->au); + + audioOutput->open = 0; +} + +static OSStatus osx_render(void *vdata, + AudioUnitRenderActionFlags * ioActionFlags, + const AudioTimeStamp * inTimeStamp, + UInt32 inBusNumber, UInt32 inNumberFrames, + AudioBufferList * bufferList) +{ + OsxData *od = (OsxData *) vdata; + AudioBuffer *buffer = &bufferList->mBuffers[0]; + int bufferSize = buffer->mDataByteSize; + int bytesToCopy; + int curpos = 0; + + /*DEBUG("osx_render: enter : %i\n", (int)bufferList->mNumberBuffers); + DEBUG("osx_render: ioActionFlags: %p\n", ioActionFlags); + if(ioActionFlags) { + if(*ioActionFlags & kAudioUnitRenderAction_PreRender) { + DEBUG("prerender\n"); + } + if(*ioActionFlags & kAudioUnitRenderAction_PostRender) { + DEBUG("post render\n"); + } + if(*ioActionFlags & kAudioUnitRenderAction_OutputIsSilence) { + DEBUG("post render\n"); + } + if(*ioActionFlags & kAudioOfflineUnitRenderAction_Preflight) { + DEBUG("prefilight\n"); + } + if(*ioActionFlags & kAudioOfflineUnitRenderAction_Render) { + DEBUG("render\n"); + } + if(*ioActionFlags & kAudioOfflineUnitRenderAction_Complete) { + DEBUG("complete\n"); + } + } */ + + /* while(bufferSize) { + DEBUG("osx_render: lock\n"); */ + pthread_mutex_lock(&od->mutex); + /* + DEBUG("%i:%i\n", bufferSize, od->len); + while(od->go && od->len < bufferSize && + od->len < od->bufferSize) + { + DEBUG("osx_render: wait\n"); + pthread_cond_wait(&od->condition, &od->mutex); + } + */ + + bytesToCopy = od->len < bufferSize ? od->len : bufferSize; + bufferSize = bytesToCopy; + od->len -= bytesToCopy; + + if (od->pos + bytesToCopy > od->bufferSize) { + int bytes = od->bufferSize - od->pos; + memcpy(buffer->mData + curpos, od->buffer + od->pos, bytes); + od->pos = 0; + curpos += bytes; + bytesToCopy -= bytes; + } + + memcpy(buffer->mData + curpos, od->buffer + od->pos, bytesToCopy); + od->pos += bytesToCopy; + curpos += bytesToCopy; + + if (od->pos >= od->bufferSize) + od->pos = 0; + /* DEBUG("osx_render: unlock\n"); */ + pthread_mutex_unlock(&od->mutex); + pthread_cond_signal(&od->condition); + /* } */ + + buffer->mDataByteSize = bufferSize; + + if (!bufferSize) { + my_usleep(1000); + } + + /* DEBUG("osx_render: leave\n"); */ + return 0; +} + +static int osx_openDevice(AudioOutput * audioOutput) +{ + OsxData *od = (OsxData *) audioOutput->data; + ComponentDescription desc; + Component comp; + AURenderCallbackStruct callback; + AudioFormat *audioFormat = &audioOutput->outAudioFormat; + AudioStreamBasicDescription streamDesc; + + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + comp = FindNextComponent(NULL, &desc); + if (comp == 0) { + ERROR("Error finding OS X component\n"); + return -1; + } + + if (OpenAComponent(comp, &od->au) != noErr) { + ERROR("Unable to open OS X component\n"); + return -1; + } + + if (AudioUnitInitialize(od->au) != 0) { + CloseComponent(od->au); + ERROR("Unable to initialize OS X audio unit\n"); + return -1; + } + + callback.inputProc = osx_render; + callback.inputProcRefCon = od; + + if (AudioUnitSetProperty(od->au, kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, + &callback, sizeof(callback)) != 0) { + AudioUnitUninitialize(od->au); + CloseComponent(od->au); + ERROR("unable to set callback for OS X audio unit\n"); + return -1; + } + + streamDesc.mSampleRate = audioFormat->sampleRate; + streamDesc.mFormatID = kAudioFormatLinearPCM; + streamDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; +#ifdef WORDS_BIGENDIAN + streamDesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; +#endif + + streamDesc.mBytesPerPacket = + audioFormat->channels * audioFormat->bits / 8; + streamDesc.mFramesPerPacket = 1; + streamDesc.mBytesPerFrame = streamDesc.mBytesPerPacket; + streamDesc.mChannelsPerFrame = audioFormat->channels; + streamDesc.mBitsPerChannel = audioFormat->bits; + + if (AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, + &streamDesc, sizeof(streamDesc)) != 0) { + AudioUnitUninitialize(od->au); + CloseComponent(od->au); + ERROR("Unable to set format on OS X device\n"); + return -1; + } + + /* create a buffer of 1s */ + od->bufferSize = (audioFormat->sampleRate) * + (audioFormat->bits >> 3) * (audioFormat->channels); + od->buffer = xrealloc(od->buffer, od->bufferSize); + + od->pos = 0; + od->len = 0; + + audioOutput->open = 1; + + return 0; +} + +static int osx_play(AudioOutput * audioOutput, char *playChunk, int size) +{ + OsxData *od = (OsxData *) audioOutput->data; + int bytesToCopy; + int curpos; + + /* DEBUG("osx_play: enter\n"); */ + + if (!od->started) { + int err; + od->started = 1; + err = AudioOutputUnitStart(od->au); + if (err) { + ERROR("unable to start audio output: %i\n", err); + return -1; + } + } + + pthread_mutex_lock(&od->mutex); + + while (size) { + /* DEBUG("osx_play: lock\n"); */ + curpos = od->pos + od->len; + if (curpos >= od->bufferSize) + curpos -= od->bufferSize; + + bytesToCopy = od->bufferSize < size ? od->bufferSize : size; + + while (od->len > od->bufferSize - bytesToCopy) { + /* DEBUG("osx_play: wait\n"); */ + pthread_cond_wait(&od->condition, &od->mutex); + } + + bytesToCopy = od->bufferSize - od->len; + bytesToCopy = bytesToCopy < size ? bytesToCopy : size; + size -= bytesToCopy; + od->len += bytesToCopy; + + if (curpos + bytesToCopy > od->bufferSize) { + int bytes = od->bufferSize - curpos; + memcpy(od->buffer + curpos, playChunk, bytes); + curpos = 0; + playChunk += bytes; + bytesToCopy -= bytes; + } + + memcpy(od->buffer + curpos, playChunk, bytesToCopy); + curpos += bytesToCopy; + playChunk += bytesToCopy; + + } + /* DEBUG("osx_play: unlock\n"); */ + pthread_mutex_unlock(&od->mutex); + + /* DEBUG("osx_play: leave\n"); */ + return 0; +} + +AudioOutputPlugin osxPlugin = { + "osx", + osx_testDefault, + osx_initDriver, + osx_finishDriver, + osx_openDevice, + osx_play, + osx_dropBufferedAudio, + osx_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else + +#include <stdio.h> + +DISABLED_AUDIO_OUTPUT_PLUGIN(osxPlugin) +#endif diff --git a/trunk/src/audioOutputs/audioOutput_pulse.c b/trunk/src/audioOutputs/audioOutput_pulse.c new file mode 100644 index 000000000..8948e0263 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_pulse.c @@ -0,0 +1,221 @@ +/* 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 "../audioOutput.h" + +#include <stdlib.h> + +#ifdef HAVE_PULSE + +#include "../conf.h" +#include "../log.h" + +#include <string.h> +#include <time.h> + +#include <pulse/simple.h> +#include <pulse/error.h> + +#define MPD_PULSE_NAME "mpd" +#define CONN_ATTEMPT_INTERVAL 60 + +typedef struct _PulseData { + pa_simple *s; + char *server; + char *sink; + int connAttempts; + time_t lastAttempt; +} PulseData; + +static PulseData *newPulseData(void) +{ + PulseData *ret; + + ret = xmalloc(sizeof(PulseData)); + + ret->s = NULL; + ret->server = NULL; + ret->sink = NULL; + ret->connAttempts = 0; + ret->lastAttempt = 0; + + return ret; +} + +static void freePulseData(PulseData * pd) +{ + if (pd->server) + free(pd->server); + if (pd->sink) + free(pd->sink); + free(pd); +} + +static int pulse_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + BlockParam *server = NULL; + BlockParam *sink = NULL; + PulseData *pd; + + if (param) { + server = getBlockParam(param, "server"); + sink = getBlockParam(param, "sink"); + } + + pd = newPulseData(); + pd->server = server ? xstrdup(server->value) : NULL; + pd->sink = sink ? xstrdup(sink->value) : NULL; + audioOutput->data = pd; + + return 0; +} + +static void pulse_finishDriver(AudioOutput * audioOutput) +{ + freePulseData((PulseData *) audioOutput->data); +} + +static int pulse_testDefault(void) +{ + pa_simple *s; + pa_sample_spec ss; + int error; + + ss.format = PA_SAMPLE_S16NE; + ss.rate = 44100; + ss.channels = 2; + + s = pa_simple_new(NULL, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, NULL, + MPD_PULSE_NAME, &ss, NULL, NULL, &error); + if (!s) { + WARNING("Cannot connect to default PulseAudio server: %s\n", + pa_strerror(error)); + return -1; + } + + pa_simple_free(s); + + return 0; +} + +static int pulse_openDevice(AudioOutput * audioOutput) +{ + PulseData *pd; + AudioFormat *audioFormat; + pa_sample_spec ss; + time_t t; + int error; + + t = time(NULL); + pd = audioOutput->data; + audioFormat = &audioOutput->outAudioFormat; + + if (pd->connAttempts != 0 && + (t - pd->lastAttempt) < CONN_ATTEMPT_INTERVAL) + return -1; + + pd->connAttempts++; + pd->lastAttempt = t; + + if (audioFormat->bits != 16) { + ERROR("PulseAudio doesn't support %i bit audio\n", + audioFormat->bits); + return -1; + } + + ss.format = PA_SAMPLE_S16NE; + ss.rate = audioFormat->sampleRate; + ss.channels = audioFormat->channels; + + pd->s = pa_simple_new(pd->server, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, + pd->sink, audioOutput->name, &ss, NULL, NULL, + &error); + if (!pd->s) { + ERROR("Cannot connect to server in PulseAudio output " + "\"%s\" (attempt %i): %s\n", audioOutput->name, + pd->connAttempts, pa_strerror(error)); + return -1; + } + + pd->connAttempts = 0; + audioOutput->open = 1; + + DEBUG("PulseAudio output \"%s\" connected and playing %i bit, %i " + "channel audio at %i Hz\n", audioOutput->name, audioFormat->bits, + audioFormat->channels, audioFormat->sampleRate); + + return 0; +} + +static void pulse_dropBufferedAudio(AudioOutput * audioOutput) +{ + PulseData *pd; + int error; + + pd = audioOutput->data; + if (pa_simple_flush(pd->s, &error) < 0) + WARNING("Flush failed in PulseAudio output \"%s\": %s\n", + audioOutput->name, pa_strerror(error)); +} + +static void pulse_closeDevice(AudioOutput * audioOutput) +{ + PulseData *pd; + + pd = audioOutput->data; + if (pd->s) { + pa_simple_drain(pd->s, NULL); + pa_simple_free(pd->s); + } + + audioOutput->open = 0; +} + +static int pulse_playAudio(AudioOutput * audioOutput, char *playChunk, int size) +{ + PulseData *pd; + int error; + + pd = audioOutput->data; + + if (pa_simple_write(pd->s, playChunk, size, &error) < 0) { + ERROR("PulseAudio output \"%s\" disconnecting due to write " + "error: %s\n", audioOutput->name, pa_strerror(error)); + pulse_closeDevice(audioOutput); + return -1; + } + + return 0; +} + +AudioOutputPlugin pulsePlugin = { + "pulse", + pulse_testDefault, + pulse_initDriver, + pulse_finishDriver, + pulse_openDevice, + pulse_playAudio, + pulse_dropBufferedAudio, + pulse_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else /* HAVE_PULSE */ + +DISABLED_AUDIO_OUTPUT_PLUGIN(pulsePlugin) +#endif /* HAVE_PULSE */ diff --git a/trunk/src/audioOutputs/audioOutput_shout.c b/trunk/src/audioOutputs/audioOutput_shout.c new file mode 100644 index 000000000..7d93f8f85 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_shout.c @@ -0,0 +1,636 @@ +/* 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 "../audioOutput.h" + +#include <stdlib.h> + +#ifdef HAVE_SHOUT + +#include "../conf.h" +#include "../log.h" +#include "../pcm_utils.h" + +#include <string.h> +#include <time.h> + +#include <shout/shout.h> +#include <vorbis/vorbisenc.h> + +#define CONN_ATTEMPT_INTERVAL 60 + +static int shoutInitCount; + +/* lots of this code blatantly stolent from bossogg/bossao2 */ + +typedef struct _ShoutData { + shout_t *shoutConn; + int shoutError; + + ogg_stream_state os; + ogg_page og; + ogg_packet op; + ogg_packet header_main; + ogg_packet header_comments; + ogg_packet header_codebooks; + + vorbis_dsp_state vd; + vorbis_block vb; + vorbis_info vi; + vorbis_comment vc; + + float quality; + int bitrate; + + int opened; + + MpdTag *tag; + int tagToSend; + + int connAttempts; + time_t lastAttempt; + int last_err; + + /* just a pointer to audioOutput->outAudioFormat */ + AudioFormat *audioFormat; +} ShoutData; + +static ShoutData *newShoutData(void) +{ + ShoutData *ret = xmalloc(sizeof(ShoutData)); + + ret->shoutConn = shout_new(); + ret->opened = 0; + ret->tag = NULL; + ret->tagToSend = 0; + ret->bitrate = -1; + ret->quality = -2.0; + ret->connAttempts = 0; + ret->lastAttempt = 0; + ret->audioFormat = NULL; + ret->last_err = SHOUTERR_UNCONNECTED; + + return ret; +} + +static void freeShoutData(ShoutData * sd) +{ + if (sd->shoutConn) + shout_free(sd->shoutConn); + if (sd->tag) + freeMpdTag(sd->tag); + + free(sd); +} + +#define checkBlockParam(name) { \ + blockParam = getBlockParam(param, name); \ + if (!blockParam) { \ + FATAL("no \"%s\" defined for shout device defined at line " \ + "%i\n", name, param->line); \ + } \ +} + +static int myShout_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + ShoutData *sd; + char *test; + int port; + char *host; + char *mount; + char *passwd; + char *user; + char *name; + BlockParam *blockParam; + unsigned int public = 0; + + sd = newShoutData(); + + if (shoutInitCount == 0) + shout_init(); + + shoutInitCount++; + + checkBlockParam("host"); + host = blockParam->value; + + checkBlockParam("mount"); + mount = blockParam->value; + + checkBlockParam("port"); + + port = strtol(blockParam->value, &test, 10); + + if (*test != '\0' || port <= 0) { + FATAL("shout port \"%s\" is not a positive integer, line %i\n", + blockParam->value, blockParam->line); + } + + checkBlockParam("password"); + passwd = blockParam->value; + + checkBlockParam("name"); + name = blockParam->value; + + blockParam = getBlockParam(param, "public"); + if (blockParam) { + if (0 == strcmp(blockParam->value, "yes")) { + public = 1; + } else if (0 == strcmp(blockParam->value, "no")) { + public = 0; + } else { + FATAL("public \"%s\" is not \"yes\" or \"no\" at line " + "%i\n", param->value, param->line); + } + } + + blockParam = getBlockParam(param, "user"); + if (blockParam) + user = blockParam->value; + else + user = "source"; + + blockParam = getBlockParam(param, "quality"); + + if (blockParam) { + int line = blockParam->line; + + sd->quality = strtod(blockParam->value, &test); + + if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) { + FATAL("shout quality \"%s\" is not a number in the " + "range -1 to 10, line %i\n", blockParam->value, + blockParam->line); + } + + blockParam = getBlockParam(param, "bitrate"); + + if (blockParam) { + FATAL("quality (line %i) and bitrate (line %i) are " + "both defined for shout output\n", line, + blockParam->line); + } + } else { + blockParam = getBlockParam(param, "bitrate"); + + if (!blockParam) { + FATAL("neither bitrate nor quality defined for shout " + "output at line %i\n", param->line); + } + + sd->bitrate = strtol(blockParam->value, &test, 10); + + if (*test != '\0' || sd->bitrate <= 0) { + FATAL("bitrate at line %i should be a positive integer " + "\n", blockParam->line); + } + } + + checkBlockParam("format"); + sd->audioFormat = &audioOutput->outAudioFormat; + + if (shout_set_host(sd->shoutConn, host) != SHOUTERR_SUCCESS || + shout_set_port(sd->shoutConn, port) != SHOUTERR_SUCCESS || + shout_set_password(sd->shoutConn, passwd) != SHOUTERR_SUCCESS || + shout_set_mount(sd->shoutConn, mount) != SHOUTERR_SUCCESS || + shout_set_name(sd->shoutConn, name) != SHOUTERR_SUCCESS || + shout_set_user(sd->shoutConn, user) != SHOUTERR_SUCCESS || + shout_set_public(sd->shoutConn, public) != SHOUTERR_SUCCESS || + shout_set_nonblocking(sd->shoutConn, 1) != SHOUTERR_SUCCESS || + shout_set_format(sd->shoutConn, SHOUT_FORMAT_VORBIS) + != SHOUTERR_SUCCESS || + shout_set_protocol(sd->shoutConn, SHOUT_PROTOCOL_HTTP) + != SHOUTERR_SUCCESS || + shout_set_agent(sd->shoutConn, "MPD") != SHOUTERR_SUCCESS) { + FATAL("error configuring shout defined at line %i: %s\n", + param->line, shout_get_error(sd->shoutConn)); + } + + /* optional paramters */ + blockParam = getBlockParam(param, "genre"); + if (blockParam && shout_set_genre(sd->shoutConn, blockParam->value)) { + FATAL("error configuring shout defined at line %i: %s\n", + param->line, shout_get_error(sd->shoutConn)); + } + + blockParam = getBlockParam(param, "description"); + if (blockParam && shout_set_description(sd->shoutConn, + blockParam->value)) { + FATAL("error configuring shout defined at line %i: %s\n", + param->line, shout_get_error(sd->shoutConn)); + } + + { + char temp[11]; + memset(temp, 0, sizeof(temp)); + + snprintf(temp, sizeof(temp), "%d", sd->audioFormat->channels); + shout_set_audio_info(sd->shoutConn, SHOUT_AI_CHANNELS, temp); + + snprintf(temp, sizeof(temp), "%d", sd->audioFormat->sampleRate); + + shout_set_audio_info(sd->shoutConn, SHOUT_AI_SAMPLERATE, temp); + + if (sd->quality >= -1.0) { + snprintf(temp, sizeof(temp), "%2.2f", sd->quality); + shout_set_audio_info(sd->shoutConn, SHOUT_AI_QUALITY, + temp); + } else { + snprintf(temp, sizeof(temp), "%d", sd->bitrate); + shout_set_audio_info(sd->shoutConn, SHOUT_AI_BITRATE, + temp); + } + } + + audioOutput->data = sd; + + return 0; +} + +static int myShout_handleError(ShoutData * sd, int err) +{ + switch (err) { + case SHOUTERR_SUCCESS: + break; + case SHOUTERR_UNCONNECTED: + case SHOUTERR_SOCKET: + ERROR("Lost shout connection to %s:%i : %s\n", + shout_get_host(sd->shoutConn), + shout_get_port(sd->shoutConn), + shout_get_error(sd->shoutConn)); + sd->shoutError = 1; + return -1; + default: + ERROR("shout: connection to %s:%i error : %s\n", + shout_get_host(sd->shoutConn), + shout_get_port(sd->shoutConn), + shout_get_error(sd->shoutConn)); + sd->shoutError = 1; + return -1; + } + + return 0; +} + +static int write_page(ShoutData * sd) +{ + int err = 0; + + /*DEBUG("shout_delay: %i\n", shout_delay(sd->shoutConn)); */ + shout_sync(sd->shoutConn); + err = shout_send(sd->shoutConn, sd->og.header, sd->og.header_len); + if (myShout_handleError(sd, err) < 0) + return -1; + err = shout_send(sd->shoutConn, sd->og.body, sd->og.body_len); + if (myShout_handleError(sd, err) < 0) + return -1; + + return 0; +} + +static void finishEncoder(ShoutData * sd) +{ + vorbis_analysis_wrote(&sd->vd, 0); + + while (vorbis_analysis_blockout(&sd->vd, &sd->vb) == 1) { + vorbis_analysis(&sd->vb, NULL); + vorbis_bitrate_addblock(&sd->vb); + while (vorbis_bitrate_flushpacket(&sd->vd, &sd->op)) { + ogg_stream_packetin(&sd->os, &sd->op); + } + } +} + +static int flushEncoder(ShoutData * sd) +{ + return (ogg_stream_pageout(&sd->os, &sd->og) > 0); +} + +static void clearEncoder(ShoutData * sd) +{ + finishEncoder(sd); + while (1 == flushEncoder(sd)) { + if (!sd->shoutError) + write_page(sd); + } + + vorbis_comment_clear(&sd->vc); + ogg_stream_clear(&sd->os); + vorbis_block_clear(&sd->vb); + vorbis_dsp_clear(&sd->vd); + vorbis_info_clear(&sd->vi); +} + +static void myShout_closeShoutConn(ShoutData * sd) +{ + if (sd->opened) { + clearEncoder(sd); + + if (shout_close(sd->shoutConn) != SHOUTERR_SUCCESS) { + ERROR("problem closing connection to shout server: " + "%s\n", shout_get_error(sd->shoutConn)); + } + } + + sd->last_err = SHOUTERR_UNCONNECTED; + sd->opened = 0; +} + +static void myShout_finishDriver(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + myShout_closeShoutConn(sd); + + freeShoutData(sd); + + shoutInitCount--; + + if (shoutInitCount == 0) + shout_shutdown(); +} + +static void myShout_dropBufferedAudio(AudioOutput * audioOutput) +{ + /* needs to be implemented */ +} + +static void myShout_closeDevice(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + myShout_closeShoutConn(sd); + + audioOutput->open = 0; +} + +#define addTag(name, value) { \ + if(value) vorbis_comment_add_tag(&(sd->vc), name, value); \ +} + +static void copyTagToVorbisComment(ShoutData * sd) +{ + if (sd->tag) { + int i; + + for (i = 0; i < sd->tag->numOfItems; i++) { + switch (sd->tag->items[i].type) { + case TAG_ITEM_ARTIST: + addTag("ARTIST", sd->tag->items[i].value); + break; + case TAG_ITEM_ALBUM: + addTag("ALBUM", sd->tag->items[i].value); + break; + case TAG_ITEM_TITLE: + addTag("TITLE", sd->tag->items[i].value); + break; + } + } + } +} + +static int initEncoder(ShoutData * sd) +{ + vorbis_info_init(&(sd->vi)); + + if (sd->quality >= -1.0) { + if (0 != vorbis_encode_init_vbr(&(sd->vi), + sd->audioFormat->channels, + sd->audioFormat->sampleRate, + sd->quality * 0.1)) { + ERROR("problem setting up vorbis encoder for shout\n"); + vorbis_info_clear(&(sd->vi)); + return -1; + } + } else { + if (0 != vorbis_encode_init(&(sd->vi), + sd->audioFormat->channels, + sd->audioFormat->sampleRate, -1.0, + sd->bitrate * 1000, -1.0)) { + ERROR("problem setting up vorbis encoder for shout\n"); + vorbis_info_clear(&(sd->vi)); + return -1; + } + } + + vorbis_analysis_init(&(sd->vd), &(sd->vi)); + vorbis_block_init(&(sd->vd), &(sd->vb)); + + ogg_stream_init(&(sd->os), rand()); + + vorbis_comment_init(&(sd->vc)); + + return 0; +} + +static int myShout_openShoutConn(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + time_t t = time(NULL); + + if (sd->connAttempts != 0 && + (t - sd->lastAttempt) < CONN_ATTEMPT_INTERVAL) { + return -1; + } + + sd->connAttempts++; + + if (sd->last_err == SHOUTERR_UNCONNECTED) + sd->last_err = shout_open(sd->shoutConn); + switch (sd->last_err) { + case SHOUTERR_SUCCESS: + case SHOUTERR_CONNECTED: + break; + case SHOUTERR_BUSY: + sd->last_err = shout_get_connected(sd->shoutConn); + if (sd->last_err == SHOUTERR_CONNECTED) + break; + return -1; + default: + sd->lastAttempt = t; + ERROR("problem opening connection to shout server %s:%i " + "(attempt %i): %s\n", + shout_get_host(sd->shoutConn), + shout_get_port(sd->shoutConn), + sd->connAttempts, shout_get_error(sd->shoutConn)); + return -1; + } + + if (initEncoder(sd) < 0) { + shout_close(sd->shoutConn); + return -1; + } + + sd->shoutError = 0; + + copyTagToVorbisComment(sd); + + vorbis_analysis_headerout(&(sd->vd), &(sd->vc), &(sd->header_main), + &(sd->header_comments), + &(sd->header_codebooks)); + + ogg_stream_packetin(&(sd->os), &(sd->header_main)); + ogg_stream_packetin(&(sd->os), &(sd->header_comments)); + ogg_stream_packetin(&(sd->os), &(sd->header_codebooks)); + + sd->opened = 1; + sd->tagToSend = 0; + + while (ogg_stream_flush(&(sd->os), &(sd->og))) { + if (write_page(sd) < 0) { + myShout_closeShoutConn(sd); + return -1; + } + } + + sd->connAttempts = 0; + + return 0; +} + +static int myShout_openDevice(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + audioOutput->open = 1; + + if (sd->opened) + return 0; + + if (myShout_openShoutConn(audioOutput) < 0) { + audioOutput->open = 0; + return -1; + } + + return 0; +} + +static void myShout_sendMetadata(ShoutData * sd) +{ + if (!sd->opened || !sd->tag) + return; + + clearEncoder(sd); + if (initEncoder(sd) < 0) + return; + + copyTagToVorbisComment(sd); + + vorbis_analysis_headerout(&(sd->vd), &(sd->vc), &(sd->header_main), + &(sd->header_comments), + &(sd->header_codebooks)); + + ogg_stream_packetin(&(sd->os), &(sd->header_main)); + ogg_stream_packetin(&(sd->os), &(sd->header_comments)); + ogg_stream_packetin(&(sd->os), &(sd->header_codebooks)); + + while (ogg_stream_flush(&(sd->os), &(sd->og))) { + if (write_page(sd) < 0) { + myShout_closeShoutConn(sd); + return; + } + } + + /*if(sd->tag) freeMpdTag(sd->tag); + sd->tag = NULL; */ + sd->tagToSend = 0; +} + +static int myShout_play(AudioOutput * audioOutput, char *playChunk, int size) +{ + int i, j; + ShoutData *sd = (ShoutData *) audioOutput->data; + float **vorbbuf; + int samples; + int bytes = sd->audioFormat->bits / 8; + + if (sd->opened && sd->tagToSend) + myShout_sendMetadata(sd); + + if (!sd->opened) { + if (myShout_openShoutConn(audioOutput) < 0) { + return -1; + } + } + + samples = size / (bytes * sd->audioFormat->channels); + + /* this is for only 16-bit audio */ + + vorbbuf = vorbis_analysis_buffer(&(sd->vd), samples); + + for (i = 0; i < samples; i++) { + for (j = 0; j < sd->audioFormat->channels; j++) { + vorbbuf[j][i] = (*((mpd_sint16 *) playChunk)) / 32768.0; + playChunk += bytes; + } + } + + vorbis_analysis_wrote(&(sd->vd), samples); + + while (1 == vorbis_analysis_blockout(&(sd->vd), &(sd->vb))) { + vorbis_analysis(&(sd->vb), NULL); + vorbis_bitrate_addblock(&(sd->vb)); + + while (vorbis_bitrate_flushpacket(&(sd->vd), &(sd->op))) { + ogg_stream_packetin(&(sd->os), &(sd->op)); + } + } + + while (ogg_stream_pageout(&(sd->os), &(sd->og)) != 0) { + if (write_page(sd) < 0) { + myShout_closeShoutConn(sd); + return -1; + } + } + + return 0; +} + +static void myShout_setTag(AudioOutput * audioOutput, MpdTag * tag) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + if (sd->tag) + freeMpdTag(sd->tag); + sd->tag = NULL; + sd->tagToSend = 0; + + if (!tag) + return; + + sd->tag = mpdTagDup(tag); + sd->tagToSend = 1; +} + +AudioOutputPlugin shoutPlugin = { + "shout", + NULL, + myShout_initDriver, + myShout_finishDriver, + myShout_openDevice, + myShout_play, + myShout_dropBufferedAudio, + myShout_closeDevice, + myShout_setTag, +}; + +#else + +DISABLED_AUDIO_OUTPUT_PLUGIN(shoutPlugin) +#endif diff --git a/trunk/src/buffer2array.c b/trunk/src/buffer2array.c new file mode 100644 index 000000000..d7bfc4561 --- /dev/null +++ b/trunk/src/buffer2array.c @@ -0,0 +1,132 @@ +/* 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 "buffer2array.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + + +static inline +int +isWhiteSpace(char c) +{ + return (c == ' ' || c == '\t'); +} + +int buffer2array(char *buffer, char *array[], const int max) +{ + int i = 0; + char *c = buffer; + + while (*c != '\0' && i < max) { + if (*c == '\"') { + array[i++] = ++c; + while (*c != '\0') { + if (*c == '\"') { + *(c++) = '\0'; + break; + } + else if (*(c++) == '\\' && *c != '\0') { + memmove(c - 1, c, strlen(c) + 1); + } + } + } else { + while (isWhiteSpace(*c)) + ++c; + array[i++] = c++; + if (*c == '\0') + return i; + while (!isWhiteSpace(*c) && *c != '\0') + ++c; + } + if (*c == '\0') + return i; + *(c++) = '\0'; + while (isWhiteSpace(*c)) + ++c; + } + return i; +} + +#ifdef UNIT_TEST + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +int main() +{ + char *a[4] = { NULL }; + char *b; + int max; + + b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("/some/dir/name \"test\"", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("/some/dir/name \"test\" something else", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"/some/dir\\\\name\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("/some/dir\\name", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"/some/dir name\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("/some/dir name", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"\\\"/some/dir\\\"\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("\"/some/dir\"", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"\\\"/some/dir\\\" x\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("\"/some/dir\" x", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"single quote\\'d from php magicquotes\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("single quote\'d from php magicquotes", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"double quote\\\"d from php magicquotes\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("double quote\"d from php magicquotes", a[1]) ); + assert( !a[2] ); + + return 0; +} + +#endif diff --git a/trunk/src/buffer2array.h b/trunk/src/buffer2array.h new file mode 100644 index 000000000..ece663994 --- /dev/null +++ b/trunk/src/buffer2array.h @@ -0,0 +1,32 @@ +/* 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 BUFFER_2_ARRAY_H +#define BUFFER_2_ARRAY_H + +#include "../config.h" + +/* tokenizes up to max elements in buffer (a null-terminated string) and + * stores the result in array (which must be capable of holding up to + * max elements). Tokenization is based on C string quoting rules. + * The arguments buffer and array are modified. + * Returns the number of elements tokenized. + */ +int buffer2array(char *buffer, char *array[], const int max); + +#endif diff --git a/trunk/src/charConv.c b/trunk/src/charConv.c new file mode 100644 index 000000000..69777c47a --- /dev/null +++ b/trunk/src/charConv.c @@ -0,0 +1,168 @@ +/* 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 "charConv.h" +#include "mpd_types.h" +#include "utf8.h" +#include "utils.h" + +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#ifdef HAVE_ICONV +#include <iconv.h> +static iconv_t char_conv_iconv; +#endif + +static char *char_conv_to; +static char *char_conv_from; +static mpd_sint8 char_conv_same; +static mpd_sint8 char_conv_use_iconv; + +/* 1 is to use latin1ToUtf8 + 0 is not to use latin1/utf8 converter + -1 is to use utf8ToLatin1*/ +static mpd_sint8 char_conv_latin1ToUtf8; + +#define BUFFER_SIZE 1024 + +static void closeCharSetConversion(void); + +int setCharSetConversion(char *to, char *from) +{ + if (char_conv_to && char_conv_from) { + if (char_conv_latin1ToUtf8 && + !strcmp(from, char_conv_to) && + !strcmp(to, char_conv_from)) { + char *swap = char_conv_from; + char_conv_from = char_conv_to; + char_conv_to = swap; + char_conv_latin1ToUtf8 *= -1; + return 0; + } else if (!strcmp(to, char_conv_to) && + !strcmp(from,char_conv_from)) { + return 0; + } + } + + closeCharSetConversion(); + + if (0 == strcmp(to, from)) { + char_conv_same = 1; + char_conv_to = xstrdup(to); + char_conv_from = xstrdup(from); + return 0; + } + + if (strcmp(to, "UTF-8") == 0 && strcmp(from, "ISO-8859-1") == 0) { + char_conv_latin1ToUtf8 = 1; + } else if (strcmp(to, "ISO-8859-1") == 0 && strcmp(from, "UTF-8") == 0) { + char_conv_latin1ToUtf8 = -1; + } + + if (char_conv_latin1ToUtf8 != 0) { + char_conv_to = xstrdup(to); + char_conv_from = xstrdup(from); + return 0; + } +#ifdef HAVE_ICONV + if ((char_conv_iconv = iconv_open(to, from)) == (iconv_t) (-1)) + return -1; + + char_conv_to = xstrdup(to); + char_conv_from = xstrdup(from); + char_conv_use_iconv = 1; + + return 0; +#endif + + return -1; +} + +char *convStrDup(char *string) +{ + if (!char_conv_to) + return NULL; + + if (char_conv_same) + return xstrdup(string); + +#ifdef HAVE_ICONV + if (char_conv_use_iconv) { + char buffer[BUFFER_SIZE]; + size_t inleft = strlen(string); + char *ret; + size_t outleft; + size_t retlen = 0; + size_t err; + char *bufferPtr; + + ret = xmalloc(1); + ret[0] = '\0'; + + while (inleft) { + bufferPtr = buffer; + outleft = BUFFER_SIZE; + err = + iconv(char_conv_iconv, &string, &inleft, &bufferPtr, + &outleft); + if (outleft == BUFFER_SIZE + || (err == -1L && errno != E2BIG)) { + free(ret); + return NULL; + } + + ret = xrealloc(ret, retlen + BUFFER_SIZE - outleft + 1); + memcpy(ret + retlen, buffer, BUFFER_SIZE - outleft); + retlen += BUFFER_SIZE - outleft; + ret[retlen] = '\0'; + } + + return ret; + } +#endif + + switch (char_conv_latin1ToUtf8) { + case 1: + return latin1StrToUtf8Dup(string); + break; + case -1: + return utf8StrToLatin1Dup(string); + break; + } + + return NULL; +} + +static void closeCharSetConversion(void) +{ + if (char_conv_to) { +#ifdef HAVE_ICONV + if (char_conv_use_iconv) + iconv_close(char_conv_iconv); +#endif + free(char_conv_to); + free(char_conv_from); + char_conv_to = NULL; + char_conv_from = NULL; + char_conv_same = 0; + char_conv_latin1ToUtf8 = 0; + char_conv_use_iconv = 0; + } +} diff --git a/trunk/src/charConv.h b/trunk/src/charConv.h new file mode 100644 index 000000000..4b1ed4237 --- /dev/null +++ b/trunk/src/charConv.h @@ -0,0 +1,28 @@ +/* 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 CHAR_CONV_H +#define CHAR_CONV_H + +#include "../config.h" + +int setCharSetConversion(char *to, char *from); + +char *convStrDup(char *string); + +#endif diff --git a/trunk/src/command.c b/trunk/src/command.c new file mode 100644 index 000000000..84a30db2b --- /dev/null +++ b/trunk/src/command.c @@ -0,0 +1,1299 @@ +/* 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 "command.h" +#include "player.h" +#include "playlist.h" +#include "ls.h" +#include "directory.h" +#include "volume.h" +#include "stats.h" +#include "myfprintf.h" +#include "list.h" +#include "permission.h" +#include "buffer2array.h" +#include "log.h" +#include "tag.h" +#include "utils.h" +#include "storedPlaylist.h" + +#include <assert.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define COMMAND_PLAY "play" +#define COMMAND_PLAYID "playid" +#define COMMAND_STOP "stop" +#define COMMAND_PAUSE "pause" +#define COMMAND_STATUS "status" +#define COMMAND_KILL "kill" +#define COMMAND_CLOSE "close" +#define COMMAND_ADD "add" +#define COMMAND_ADDID "addid" +#define COMMAND_DELETE "delete" +#define COMMAND_DELETEID "deleteid" +#define COMMAND_PLAYLIST "playlist" +#define COMMAND_SHUFFLE "shuffle" +#define COMMAND_CLEAR "clear" +#define COMMAND_SAVE "save" +#define COMMAND_LOAD "load" +#define COMMAND_LISTPLAYLIST "listplaylist" +#define COMMAND_LISTPLAYLISTINFO "listplaylistinfo" +#define COMMAND_LSINFO "lsinfo" +#define COMMAND_RM "rm" +#define COMMAND_PLAYLISTINFO "playlistinfo" +#define COMMAND_PLAYLISTID "playlistid" +#define COMMAND_FIND "find" +#define COMMAND_SEARCH "search" +#define COMMAND_UPDATE "update" +#define COMMAND_NEXT "next" +#define COMMAND_PREVIOUS "previous" +#define COMMAND_LISTALL "listall" +#define COMMAND_VOLUME "volume" +#define COMMAND_REPEAT "repeat" +#define COMMAND_RANDOM "random" +#define COMMAND_STATS "stats" +#define COMMAND_CLEAR_ERROR "clearerror" +#define COMMAND_LIST "list" +#define COMMAND_MOVE "move" +#define COMMAND_MOVEID "moveid" +#define COMMAND_SWAP "swap" +#define COMMAND_SWAPID "swapid" +#define COMMAND_SEEK "seek" +#define COMMAND_SEEKID "seekid" +#define COMMAND_LISTALLINFO "listallinfo" +#define COMMAND_PING "ping" +#define COMMAND_SETVOL "setvol" +#define COMMAND_PASSWORD "password" +#define COMMAND_CROSSFADE "crossfade" +#define COMMAND_URL_HANDLERS "urlhandlers" +#define COMMAND_PLCHANGES "plchanges" +#define COMMAND_PLCHANGESPOSID "plchangesposid" +#define COMMAND_CURRENTSONG "currentsong" +#define COMMAND_ENABLE_DEV "enableoutput" +#define COMMAND_DISABLE_DEV "disableoutput" +#define COMMAND_DEVICES "outputs" +#define COMMAND_COMMANDS "commands" +#define COMMAND_NOTCOMMANDS "notcommands" +#define COMMAND_PLAYLISTCLEAR "playlistclear" +#define COMMAND_PLAYLISTADD "playlistadd" +#define COMMAND_PLAYLISTFIND "playlistfind" +#define COMMAND_PLAYLISTSEARCH "playlistsearch" +#define COMMAND_PLAYLISTMOVE "playlistmove" +#define COMMAND_PLAYLISTDELETE "playlistdelete" +#define COMMAND_TAGTYPES "tagtypes" +#define COMMAND_COUNT "count" +#define COMMAND_RENAME "rename" + +#define COMMAND_STATUS_VOLUME "volume" +#define COMMAND_STATUS_STATE "state" +#define COMMAND_STATUS_REPEAT "repeat" +#define COMMAND_STATUS_RANDOM "random" +#define COMMAND_STATUS_PLAYLIST "playlist" +#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength" +#define COMMAND_STATUS_SONG "song" +#define COMMAND_STATUS_SONGID "songid" +#define COMMAND_STATUS_TIME "time" +#define COMMAND_STATUS_BITRATE "bitrate" +#define COMMAND_STATUS_ERROR "error" +#define COMMAND_STATUS_CROSSFADE "xfade" +#define COMMAND_STATUS_AUDIO "audio" +#define COMMAND_STATUS_UPDATING_DB "updating_db" + +/* + * The most we ever use is for search/find, and that limits it to the + * number of tags we can have. Add one for the command, and one extra + * to catch errors clients may send us + */ +#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2)) + +typedef struct _CommandEntry CommandEntry; + +typedef int (*CommandHandlerFunction) (int, int *, int, char **); +typedef int (*CommandListHandlerFunction) + (int, int *, int, char **, struct strnode *, CommandEntry *); + +/* if min: -1 don't check args * + * if max: -1 no max args */ +struct _CommandEntry { + char *cmd; + int min; + int max; + int reqPermission; + CommandHandlerFunction handler; + CommandListHandlerFunction listHandler; +}; + +static char *current_command; +static int command_listNum; + +static CommandEntry *getCommandEntryFromString(char *string, int *permission); + +static List *commandList; + +static CommandEntry *newCommandEntry(void) +{ + CommandEntry *cmd = xmalloc(sizeof(CommandEntry)); + cmd->cmd = NULL; + cmd->min = 0; + cmd->max = 0; + cmd->handler = NULL; + cmd->listHandler = NULL; + cmd->reqPermission = 0; + return cmd; +} + +static void addCommand(char *name, + int reqPermission, + int minargs, + int maxargs, + CommandHandlerFunction handler_func, + CommandListHandlerFunction listHandler_func) +{ + CommandEntry *cmd = newCommandEntry(); + cmd->cmd = name; + cmd->min = minargs; + cmd->max = maxargs; + cmd->handler = handler_func; + cmd->listHandler = listHandler_func; + cmd->reqPermission = reqPermission; + + insertInList(commandList, cmd->cmd, cmd); +} + +static int handleUrlHandlers(int fd, int *permission, int argc, char *argv[]) +{ + return printRemoteUrlHandlers(fd); +} + +static int handleTagTypes(int fd, int *permission, int argc, char *argv[]) +{ + printTagTypes(fd); + return 0; +} + +static int handlePlay(int fd, int *permission, int argc, char *argv[]) +{ + int song = -1; + char *test; + + if (argc == 2) { + song = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "need a positive integer"); + return -1; + } + } + return playPlaylist(fd, song, 0); +} + +static int handlePlayId(int fd, int *permission, int argc, char *argv[]) +{ + int id = -1; + char *test; + + if (argc == 2) { + id = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "need a positive integer"); + return -1; + } + } + return playPlaylistById(fd, id, 0); +} + +static int handleStop(int fd, int *permission, int argc, char *argv[]) +{ + return stopPlaylist(fd); +} + +static int handleCurrentSong(int fd, int *permission, int argc, char *argv[]) +{ + int song = getPlaylistCurrentSong(); + + if (song >= 0) { + return playlistInfo(fd, song); + } else + return 0; +} + +static int handlePause(int fd, int *permission, int argc, char *argv[]) +{ + if (argc == 2) { + char *test; + int pause = strtol(argv[1], &test, 10); + if (*test != '\0' || (pause != 0 && pause != 1)) { + commandError(fd, ACK_ERROR_ARG, "\"%s\" is not 0 or 1", + argv[1]); + return -1; + } + return playerSetPause(fd, pause); + } + return playerPause(fd); +} + +static int commandStatus(int fd, int *permission, int argc, char *argv[]) +{ + char *state = NULL; + int updateJobId; + int song; + + /*syncPlayerAndPlaylist(); */ + playPlaylistIfPlayerStopped(); + switch (getPlayerState()) { + case PLAYER_STATE_STOP: + state = COMMAND_STOP; + break; + case PLAYER_STATE_PAUSE: + state = COMMAND_PAUSE; + break; + case PLAYER_STATE_PLAY: + state = COMMAND_PLAY; + break; + } + + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_VOLUME, getVolumeLevel()); + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_REPEAT, + getPlaylistRepeatStatus()); + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_RANDOM, + getPlaylistRandomStatus()); + fdprintf(fd, "%s: %li\n", COMMAND_STATUS_PLAYLIST, + getPlaylistVersion()); + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_PLAYLIST_LENGTH, + getPlaylistLength()); + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_CROSSFADE, + (int)(getPlayerCrossFade() + 0.5)); + + fdprintf(fd, "%s: %s\n", COMMAND_STATUS_STATE, state); + + song = getPlaylistCurrentSong(); + if (song >= 0) { + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_SONG, song); + 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 ((updateJobId = isUpdatingDB())) { + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_UPDATING_DB, + updateJobId); + } + + if (getPlayerError() != PLAYER_ERROR_NOERROR) { + fdprintf(fd, "%s: %s\n", COMMAND_STATUS_ERROR, + getPlayerErrorStr()); + } + + return 0; +} + +static int handleKill(int fd, int *permission, int argc, char *argv[]) +{ + return COMMAND_RETURN_KILL; +} + +static int handleClose(int fd, int *permission, int argc, char *argv[]) +{ + return COMMAND_RETURN_CLOSE; +} + +static int handleAdd(int fd, int *permission, int argc, char *argv[]) +{ + char *path = argv[1]; + + if (isRemoteUrl(path)) + return addToPlaylist(fd, path, 0); + + return addAllIn(fd, path); +} + +static int handleAddId(int fd, int *permission, int argc, char *argv[]) +{ + return addToPlaylist(fd, argv[1], 1); +} + +static int handleDelete(int fd, int *permission, int argc, char *argv[]) +{ + int song; + char *test; + + song = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need a positive integer"); + return -1; + } + return deleteFromPlaylist(fd, song); +} + +static int handleDeleteId(int fd, int *permission, int argc, char *argv[]) +{ + int id; + char *test; + + id = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need a positive integer"); + return -1; + } + return deleteFromPlaylistById(fd, id); +} + +static int handlePlaylist(int fd, int *permission, int argc, char *argv[]) +{ + return showPlaylist(fd); +} + +static int handleShuffle(int fd, int *permission, int argc, char *argv[]) +{ + return shufflePlaylist(fd); +} + +static int handleClear(int fd, int *permission, int argc, char *argv[]) +{ + return clearPlaylist(fd); +} + +static int handleSave(int fd, int *permission, int argc, char *argv[]) +{ + return savePlaylist(fd, argv[1]); +} + +static int handleLoad(int fd, int *permission, int argc, char *argv[]) +{ + return loadPlaylist(fd, argv[1]); +} + +static int handleListPlaylist(int fd, int *permission, int argc, char *argv[]) +{ + return PlaylistInfo(fd, argv[1], 0); +} + +static int handleListPlaylistInfo(int fd, int *permission, + int argc, char *argv[]) +{ + return PlaylistInfo(fd, argv[1], 1); +} + +static int handleLsInfo(int fd, int *permission, int argc, char *argv[]) +{ + char *path = ""; + + if (argc == 2) + path = argv[1]; + + if (printDirectoryInfo(fd, path) < 0) + return -1; + + if (isRootDirectory(path)) + return lsPlaylists(fd, path); + + return 0; +} + +static int handleRm(int fd, int *permission, int argc, char *argv[]) +{ + return deletePlaylist(fd, argv[1]); +} + +static int handleRename(int fd, int *permission, int argc, char *argv[]) +{ + return renameStoredPlaylist(fd, argv[1], argv[2]); +} + +static int handlePlaylistChanges(int fd, int *permission, + int argc, char *argv[]) +{ + unsigned long version; + char *test; + + version = strtoul(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need a positive integer"); + return -1; + } + return playlistChanges(fd, version); +} + +static int handlePlaylistChangesPosId(int fd, int *permission, + int argc, char *argv[]) +{ + unsigned long version; + char *test; + + version = strtoul(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need a positive integer"); + return -1; + } + return playlistChangesPosId(fd, version); +} + +static int handlePlaylistInfo(int fd, int *permission, int argc, char *argv[]) +{ + int song = -1; + char *test; + + if (argc == 2) { + song = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "need a positive integer"); + return -1; + } + } + return playlistInfo(fd, song); +} + +static int handlePlaylistId(int fd, int *permission, int argc, char *argv[]) +{ + int id = -1; + char *test; + + if (argc == 2) { + id = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "need a positive integer"); + return -1; + } + } + return playlistId(fd, id); +} + +static int handleFind(int fd, int *permission, int argc, char *argv[]) +{ + int ret; + + LocateTagItem *items; + int numItems = newLocateTagItemArrayFromArgArray(argv + 1, + argc - 1, + &items); + + if (numItems <= 0) { + commandError(fd, ACK_ERROR_ARG, "incorrect arguments"); + return -1; + } + + ret = findSongsIn(fd, NULL, numItems, items); + + freeLocateTagItemArray(numItems, items); + + return ret; +} + +static int handleSearch(int fd, int *permission, int argc, char *argv[]) +{ + int ret; + + LocateTagItem *items; + int numItems = newLocateTagItemArrayFromArgArray(argv + 1, + argc - 1, + &items); + + if (numItems <= 0) { + commandError(fd, ACK_ERROR_ARG, "incorrect arguments"); + return -1; + } + + ret = searchForSongsIn(fd, NULL, numItems, items); + + freeLocateTagItemArray(numItems, items); + + return ret; +} + +static int handleCount(int fd, int *permission, int argc, char *argv[]) +{ + int ret; + + LocateTagItem *items; + int numItems = newLocateTagItemArrayFromArgArray(argv + 1, + argc - 1, + &items); + + if (numItems <= 0) { + commandError(fd, ACK_ERROR_ARG, "incorrect arguments"); + return -1; + } + + ret = searchStatsForSongsIn(fd, NULL, numItems, items); + + freeLocateTagItemArray(numItems, items); + + return ret; +} + +static int handlePlaylistFind(int fd, int *permission, int argc, char *argv[]) +{ + LocateTagItem *items; + int numItems = newLocateTagItemArrayFromArgArray(argv + 1, + argc - 1, + &items); + + if (numItems <= 0) { + commandError(fd, ACK_ERROR_ARG, "incorrect arguments"); + return -1; + } + + findSongsInPlaylist(fd, numItems, items); + + freeLocateTagItemArray(numItems, items); + + return 0; +} + +static int handlePlaylistSearch(int fd, int *permission, int argc, char *argv[]) +{ + LocateTagItem *items; + int numItems = newLocateTagItemArrayFromArgArray(argv + 1, + argc - 1, + &items); + + if (numItems <= 0) { + commandError(fd, ACK_ERROR_ARG, "incorrect arguments"); + return -1; + } + + searchForSongsInPlaylist(fd, numItems, items); + + freeLocateTagItemArray(numItems, items); + + return 0; +} + +static int handlePlaylistDelete(int fd, int *permission, int argc, char *argv[]) { + char *playlist = argv[1]; + int from; + char *test; + + from = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + + return removeOneSongFromStoredPlaylistByPath(fd, playlist, from); +} + +static int handlePlaylistMove(int fd, int *permission, int argc, char *argv[]) +{ + char *playlist = argv[1]; + int from, to; + char *test; + + from = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + to = strtol(argv[3], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[3]); + return -1; + } + + return moveSongInStoredPlaylistByPath(fd, playlist, from, to); +} + +static int listHandleUpdate(int fd, + int *permission, + int argc, + char *argv[], + struct strnode *cmdnode, CommandEntry * cmd) +{ + static List *pathList; + CommandEntry *nextCmd = NULL; + struct strnode *next = cmdnode->next; + + if (!pathList) + pathList = makeList(NULL, 1); + + if (argc == 2) + insertInList(pathList, argv[1], NULL); + else + insertInList(pathList, "", NULL); + + if (next) + nextCmd = getCommandEntryFromString(next->data, permission); + + if (cmd != nextCmd) { + int ret = updateInit(fd, pathList); + freeList(pathList); + pathList = NULL; + return ret; + } + + return 0; +} + +static int handleUpdate(int fd, int *permission, int argc, char *argv[]) +{ + if (argc == 2) { + int ret; + List *pathList = makeList(NULL, 1); + insertInList(pathList, argv[1], NULL); + ret = updateInit(fd, pathList); + freeList(pathList); + return ret; + } + return updateInit(fd, NULL); +} + +static int handleNext(int fd, int *permission, int argc, char *argv[]) +{ + return nextSongInPlaylist(fd); +} + +static int handlePrevious(int fd, int *permission, int argc, char *argv[]) +{ + return previousSongInPlaylist(fd); +} + +static int handleListAll(int fd, int *permission, int argc, char *argv[]) +{ + char *directory = NULL; + + if (argc == 2) + directory = argv[1]; + return printAllIn(fd, directory); +} + +static int handleVolume(int fd, int *permission, int argc, char *argv[]) +{ + int change; + char *test; + + change = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need an integer"); + return -1; + } + return changeVolumeLevel(fd, change, 1); +} + +static int handleSetVol(int fd, int *permission, int argc, char *argv[]) +{ + int level; + char *test; + + level = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need an integer"); + return -1; + } + return changeVolumeLevel(fd, level, 0); +} + +static int handleRepeat(int fd, int *permission, int argc, char *argv[]) +{ + int status; + char *test; + + status = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need an integer"); + return -1; + } + return setPlaylistRepeatStatus(fd, status); +} + +static int handleRandom(int fd, int *permission, int argc, char *argv[]) +{ + int status; + char *test; + + status = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need an integer"); + return -1; + } + return setPlaylistRandomStatus(fd, status); +} + +static int handleStats(int fd, int *permission, int argc, char *argv[]) +{ + return printStats(fd); +} + +static int handleClearError(int fd, int *permission, int argc, char *argv[]) +{ + clearPlayerError(); + return 0; +} + +static int handleList(int fd, int *permission, int argc, char *argv[]) +{ + int numConditionals = 0; + LocateTagItem *conditionals = NULL; + int tagType = getLocateTagItemType(argv[1]); + int ret; + + if (tagType < 0) { + commandError(fd, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); + return -1; + } + + if (tagType == LOCATE_TAG_ANY_TYPE) { + commandError(fd, ACK_ERROR_ARG, + "\"any\" is not a valid return tag type"); + return -1; + } + + /* for compatibility with < 0.12.0 */ + if (argc == 3) { + if (tagType != TAG_ITEM_ALBUM) { + commandError(fd, ACK_ERROR_ARG, + "should be \"%s\" for 3 arguments", + mpdTagItemKeys[TAG_ITEM_ALBUM]); + return -1; + } + conditionals = newLocateTagItem(mpdTagItemKeys[TAG_ITEM_ARTIST], + argv[2]); + numConditionals = 1; + } else { + numConditionals = + newLocateTagItemArrayFromArgArray(argv + 2, + argc - 2, &conditionals); + + if (numConditionals < 0) { + commandError(fd, ACK_ERROR_ARG, + "not able to parse args"); + return -1; + } + } + + ret = listAllUniqueTags(fd, tagType, numConditionals, conditionals); + + if (conditionals) + freeLocateTagItemArray(numConditionals, conditionals); + + return ret; +} + +static int handleMove(int fd, int *permission, int argc, char *argv[]) +{ + int from; + int to; + char *test; + + from = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + to = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + return moveSongInPlaylist(fd, from, to); +} + +static int handleMoveId(int fd, int *permission, int argc, char *argv[]) +{ + int id; + int to; + char *test; + + id = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + to = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + return moveSongInPlaylistById(fd, id, to); +} + +static int handleSwap(int fd, int *permission, int argc, char *argv[]) +{ + int song1; + int song2; + char *test; + + song1 = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + song2 = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "\"%s\" is not a integer", + argv[2]); + return -1; + } + return swapSongsInPlaylist(fd, song1, song2); +} + +static int handleSwapId(int fd, int *permission, int argc, char *argv[]) +{ + int id1; + int id2; + char *test; + + id1 = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + id2 = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "\"%s\" is not a integer", + argv[2]); + return -1; + } + return swapSongsInPlaylistById(fd, id1, id2); +} + +static int handleSeek(int fd, int *permission, int argc, char *argv[]) +{ + int song; + int time; + char *test; + + song = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + time = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + return seekSongInPlaylist(fd, song, time); +} + +static int handleSeekId(int fd, int *permission, int argc, char *argv[]) +{ + int id; + int time; + char *test; + + id = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + time = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + return seekSongInPlaylistById(fd, id, time); +} + +static int handleListAllInfo(int fd, int *permission, int argc, char *argv[]) +{ + char *directory = NULL; + + if (argc == 2) + directory = argv[1]; + return printInfoForAllIn(fd, directory); +} + +static int handlePing(int fd, int *permission, int argc, char *argv[]) +{ + return 0; +} + +static int handlePassword(int fd, int *permission, int argc, char *argv[]) +{ + if (getPermissionFromPassword(argv[1], permission) < 0) { + commandError(fd, ACK_ERROR_PASSWORD, "incorrect password"); + return -1; + } + + return 0; +} + +static int handleCrossfade(int fd, int *permission, int argc, char *argv[]) +{ + int time; + char *test; + + time = strtol(argv[1], &test, 10); + if (*test != '\0' || time < 0) { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer >= 0", argv[1]); + return -1; + } + + setPlayerCrossFade(time); + + return 0; +} + +static int handleEnableDevice(int fd, int *permission, int argc, char *argv[]) +{ + int device; + char *test; + + device = strtol(argv[1], &test, 10); + if (*test != '\0' || device < 0) { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer >= 0", argv[1]); + return -1; + } + + return enableAudioDevice(fd, device); +} + +static int handleDisableDevice(int fd, int *permission, int argc, char *argv[]) +{ + int device; + char *test; + + device = strtol(argv[1], &test, 10); + if (*test != '\0' || device < 0) { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer >= 0", argv[1]); + return -1; + } + + return disableAudioDevice(fd, device); +} + +static int handleDevices(int fd, int *permission, int argc, char *argv[]) +{ + printAudioDevices(fd); + + return 0; +} + +/* don't be fooled, this is the command handler for "commands" command */ +static int handleCommands(int fd, int *permission, int argc, char *argv[]) +{ + ListNode *node = commandList->firstNode; + CommandEntry *cmd; + + while (node != NULL) { + cmd = (CommandEntry *) node->data; + if (cmd->reqPermission == (*permission & cmd->reqPermission)) { + fdprintf(fd, "command: %s\n", cmd->cmd); + } + + node = node->nextNode; + } + + return 0; +} + +static int handleNotcommands(int fd, int *permission, int argc, char *argv[]) +{ + ListNode *node = commandList->firstNode; + CommandEntry *cmd; + + while (node != NULL) { + cmd = (CommandEntry *) node->data; + + if (cmd->reqPermission != (*permission & cmd->reqPermission)) { + fdprintf(fd, "command: %s\n", cmd->cmd); + } + + node = node->nextNode; + } + + return 0; +} + +static int handlePlaylistClear(int fd, int *permission, int argc, char *argv[]) +{ + return clearStoredPlaylist(fd, argv[1]); +} + +static int handlePlaylistAdd(int fd, int *permission, int argc, char *argv[]) +{ + char *playlist = argv[1]; + char *path = argv[2]; + + if (isRemoteUrl(path)) + return addToStoredPlaylist(fd, path, playlist); + + return addAllInToStoredPlaylist(fd, path, playlist); +} + +void initCommands(void) +{ + commandList = makeList(free, 1); + + /* addCommand(name, permission, min, max, handler, list handler); */ + addCommand(COMMAND_PLAY, PERMISSION_CONTROL, 0, 1, handlePlay, NULL); + addCommand(COMMAND_PLAYID, PERMISSION_CONTROL, 0, 1, handlePlayId, NULL); + addCommand(COMMAND_STOP, PERMISSION_CONTROL, 0, 0, handleStop, NULL); + addCommand(COMMAND_CURRENTSONG, PERMISSION_READ, 0, 0, handleCurrentSong, NULL); + addCommand(COMMAND_PAUSE, PERMISSION_CONTROL, 0, 1, handlePause, NULL); + addCommand(COMMAND_STATUS, PERMISSION_READ, 0, 0, commandStatus, NULL); + addCommand(COMMAND_KILL, PERMISSION_ADMIN, -1, -1, handleKill, NULL); + addCommand(COMMAND_CLOSE, PERMISSION_NONE, -1, -1, handleClose, NULL); + addCommand(COMMAND_ADD, PERMISSION_ADD, 1, 1, handleAdd, NULL); + addCommand(COMMAND_ADDID, PERMISSION_ADD, 1, 1, handleAddId, NULL); + addCommand(COMMAND_DELETE, PERMISSION_CONTROL, 1, 1, handleDelete, NULL); + addCommand(COMMAND_DELETEID, PERMISSION_CONTROL, 1, 1, handleDeleteId, NULL); + addCommand(COMMAND_PLAYLIST, PERMISSION_READ, 0, 0, handlePlaylist, NULL); + addCommand(COMMAND_PLAYLISTID, PERMISSION_READ, 0, 1, handlePlaylistId, NULL); + addCommand(COMMAND_SHUFFLE, PERMISSION_CONTROL, 0, 0, handleShuffle, NULL); + addCommand(COMMAND_CLEAR, PERMISSION_CONTROL, 0, 0, handleClear, NULL); + addCommand(COMMAND_SAVE, PERMISSION_CONTROL, 1, 1, handleSave, NULL); + addCommand(COMMAND_LOAD, PERMISSION_ADD, 1, 1, handleLoad, NULL); + addCommand(COMMAND_LISTPLAYLIST, PERMISSION_READ, 1, 1, handleListPlaylist, NULL); + addCommand(COMMAND_LISTPLAYLISTINFO, PERMISSION_READ, 1, 1, handleListPlaylistInfo, NULL); + addCommand(COMMAND_LSINFO, PERMISSION_READ, 0, 1, handleLsInfo, NULL); + addCommand(COMMAND_RM, PERMISSION_CONTROL, 1, 1, handleRm, NULL); + addCommand(COMMAND_PLAYLISTINFO, PERMISSION_READ, 0, 1, handlePlaylistInfo, NULL); + addCommand(COMMAND_FIND, PERMISSION_READ, 2, -1, handleFind, NULL); + addCommand(COMMAND_SEARCH, PERMISSION_READ, 2, -1, handleSearch, NULL); + addCommand(COMMAND_UPDATE, PERMISSION_ADMIN, 0, 1, handleUpdate, listHandleUpdate); + addCommand(COMMAND_NEXT, PERMISSION_CONTROL, 0, 0, handleNext, NULL); + addCommand(COMMAND_PREVIOUS, PERMISSION_CONTROL, 0, 0, handlePrevious, NULL); + addCommand(COMMAND_LISTALL, PERMISSION_READ, 0, 1, handleListAll, NULL); + addCommand(COMMAND_VOLUME, PERMISSION_CONTROL, 1, 1, handleVolume, NULL); + addCommand(COMMAND_REPEAT, PERMISSION_CONTROL, 1, 1, handleRepeat, NULL); + addCommand(COMMAND_RANDOM, PERMISSION_CONTROL, 1, 1, handleRandom, NULL); + addCommand(COMMAND_STATS, PERMISSION_READ, 0, 0, handleStats, NULL); + addCommand(COMMAND_CLEAR_ERROR, PERMISSION_CONTROL, 0, 0, handleClearError, NULL); + addCommand(COMMAND_LIST, PERMISSION_READ, 1, -1, handleList, NULL); + addCommand(COMMAND_MOVE, PERMISSION_CONTROL, 2, 2, handleMove, NULL); + addCommand(COMMAND_MOVEID, PERMISSION_CONTROL, 2, 2, handleMoveId, NULL); + addCommand(COMMAND_SWAP, PERMISSION_CONTROL, 2, 2, handleSwap, NULL); + addCommand(COMMAND_SWAPID, PERMISSION_CONTROL, 2, 2, handleSwapId, NULL); + addCommand(COMMAND_SEEK, PERMISSION_CONTROL, 2, 2, handleSeek, NULL); + addCommand(COMMAND_SEEKID, PERMISSION_CONTROL, 2, 2, handleSeekId, NULL); + addCommand(COMMAND_LISTALLINFO, PERMISSION_READ, 0, 1, handleListAllInfo, NULL); + addCommand(COMMAND_PING, PERMISSION_NONE, 0, 0, handlePing, NULL); + addCommand(COMMAND_SETVOL, PERMISSION_CONTROL, 1, 1, handleSetVol, NULL); + addCommand(COMMAND_PASSWORD, PERMISSION_NONE, 1, 1, handlePassword, NULL); + addCommand(COMMAND_CROSSFADE, PERMISSION_CONTROL, 1, 1, handleCrossfade, NULL); + addCommand(COMMAND_URL_HANDLERS, PERMISSION_READ, 0, 0, handleUrlHandlers, NULL); + addCommand(COMMAND_PLCHANGES, PERMISSION_READ, 1, 1, handlePlaylistChanges, NULL); + addCommand(COMMAND_PLCHANGESPOSID, PERMISSION_READ, 1, 1, handlePlaylistChangesPosId, NULL); + addCommand(COMMAND_ENABLE_DEV, PERMISSION_ADMIN, 1, 1, handleEnableDevice, NULL); + addCommand(COMMAND_DISABLE_DEV, PERMISSION_ADMIN, 1, 1, handleDisableDevice, NULL); + addCommand(COMMAND_DEVICES, PERMISSION_READ, 0, 0, handleDevices, NULL); + addCommand(COMMAND_COMMANDS, PERMISSION_NONE, 0, 0, handleCommands, NULL); + addCommand(COMMAND_NOTCOMMANDS, PERMISSION_NONE, 0, 0, handleNotcommands, NULL); + addCommand(COMMAND_PLAYLISTCLEAR, PERMISSION_CONTROL, 1, 1, handlePlaylistClear, NULL); + addCommand(COMMAND_PLAYLISTADD, PERMISSION_CONTROL, 2, 2, handlePlaylistAdd, NULL); + addCommand(COMMAND_PLAYLISTFIND, PERMISSION_READ, 2, -1, handlePlaylistFind, NULL); + addCommand(COMMAND_PLAYLISTSEARCH, PERMISSION_READ, 2, -1, handlePlaylistSearch, NULL); + addCommand(COMMAND_PLAYLISTMOVE, PERMISSION_CONTROL, 3, 3, handlePlaylistMove, NULL); + addCommand(COMMAND_PLAYLISTDELETE, PERMISSION_CONTROL, 2, 2, handlePlaylistDelete, NULL); + addCommand(COMMAND_TAGTYPES, PERMISSION_READ, 0, 0, handleTagTypes, NULL); + addCommand(COMMAND_COUNT, PERMISSION_READ, 2, -1, handleCount, NULL); + addCommand(COMMAND_RENAME, PERMISSION_CONTROL, 2, 2, handleRename, NULL); + + sortList(commandList); +} + +void finishCommands(void) +{ + freeList(commandList); +} + +static int checkArgcAndPermission(CommandEntry * cmd, int fd, + int permission, int argc, char *argv[]) +{ + int min = cmd->min + 1; + int max = cmd->max + 1; + + if (cmd->reqPermission != (permission & cmd->reqPermission)) { + if (fd) { + commandError(fd, ACK_ERROR_PERMISSION, + "you don't have permission for \"%s\"", + cmd->cmd); + } + return -1; + } + + if (min == 0) + return 0; + + if (min == max && max != argc) { + if (fd) { + commandError(fd, ACK_ERROR_ARG, + "wrong number of arguments for \"%s\"", + argv[0]); + } + return -1; + } else if (argc < min) { + if (fd) { + commandError(fd, ACK_ERROR_ARG, + "too few arguments for \"%s\"", argv[0]); + } + return -1; + } else if (argc > max && max /* != 0 */ ) { + if (fd) { + commandError(fd, ACK_ERROR_ARG, + "too many arguments for \"%s\"", argv[0]); + } + return -1; + } else + return 0; +} + +static CommandEntry *getCommandEntryAndCheckArgcAndPermission(int fd, + int *permission, + int argc, + char *argv[]) +{ + static char unknown[] = ""; + CommandEntry *cmd; + + current_command = unknown; + + if (argc == 0) + return NULL; + + if (!findInList(commandList, argv[0], (void *)&cmd)) { + if (fd) { + commandError(fd, ACK_ERROR_UNKNOWN, + "unknown command \"%s\"", argv[0]); + } + return NULL; + } + + current_command = cmd->cmd; + + if (checkArgcAndPermission(cmd, fd, *permission, argc, argv) < 0) { + return NULL; + } + + return cmd; +} + +static CommandEntry *getCommandEntryFromString(char *string, int *permission) +{ + CommandEntry *cmd = NULL; + char *argv[COMMAND_ARGV_MAX] = { NULL }; + int argc = buffer2array(string, argv, COMMAND_ARGV_MAX); + + if (0 == argc) + return NULL; + + cmd = getCommandEntryAndCheckArgcAndPermission(0, permission, + argc, argv); + + return cmd; +} + +static int processCommandInternal(int fd, int *permission, + char *commandString, struct strnode *cmdnode) +{ + int argc; + char *argv[COMMAND_ARGV_MAX] = { NULL }; + CommandEntry *cmd; + int ret = -1; + + argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX); + + if (argc == 0) + return 0; + + if ((cmd = getCommandEntryAndCheckArgcAndPermission(fd, permission, + argc, argv))) { + if (!cmdnode || !cmd->listHandler) { + ret = cmd->handler(fd, permission, argc, argv); + } else { + ret = cmd->listHandler(fd, permission, argc, argv, + cmdnode, cmd); + } + } + + current_command = NULL; + + return ret; +} + +int processListOfCommands(int fd, int *permission, int *expired, + int listOK, struct strnode *list) +{ + struct strnode *cur = list; + int ret = 0; + + command_listNum = 0; + + while (cur) { + DEBUG("processListOfCommands: process command \"%s\"\n", + cur->data); + ret = processCommandInternal(fd, permission, cur->data, cur); + DEBUG("processListOfCommands: command returned %i\n", ret); + if (ret != 0 || (*expired) != 0) + goto out; + else if (listOK) + fdprintf(fd, "list_OK\n"); + command_listNum++; + cur = cur->next; + } +out: + command_listNum = 0; + return ret; +} + +int processCommand(int fd, int *permission, char *commandString) +{ + return processCommandInternal(fd, permission, commandString, NULL); +} + +mpd_fprintf_ void commandError(int fd, int error, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + if (current_command && fd != STDERR_FILENO) { + fdprintf(fd, "ACK [%i@%i] {%s} ", + (int)error, command_listNum, current_command); + vfdprintf(fd, fmt, args); + fdprintf(fd, "\n"); + current_command = NULL; + } else { + fdprintf(STDERR_FILENO, "ACK [%i@%i] ", + (int)error, command_listNum); + vfdprintf(STDERR_FILENO, fmt, args); + fdprintf(STDERR_FILENO, "\n"); + } + + va_end(args); +} diff --git a/trunk/src/command.h b/trunk/src/command.h new file mode 100644 index 000000000..a560b9484 --- /dev/null +++ b/trunk/src/command.h @@ -0,0 +1,50 @@ +/* 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 COMMAND_H +#define COMMAND_H + +#include "../config.h" + +#include "list.h" +#include "myfprintf.h" +#include "log.h" +#include "ack.h" +#include "sllist.h" + +#include <unistd.h> +#include <stdio.h> + +#define COMMAND_RETURN_KILL 10 +#define COMMAND_RETURN_CLOSE 20 +#define COMMAND_MASTER_READY 30 + +int processListOfCommands(int fd, int *permission, int *expired, + int listOK, struct strnode *list); + +int processCommand(int fd, int *permission, char *commandString); + +void initCommands(void); + +void finishCommands(void); + +#define commandSuccess(fd) fdprintf(fd, "OK\n") + +mpd_fprintf_ void commandError(int fd, int error, const char *fmt, ...); + +#endif diff --git a/trunk/src/compress.c b/trunk/src/compress.c new file mode 100644 index 000000000..d8db7ab64 --- /dev/null +++ b/trunk/src/compress.c @@ -0,0 +1,411 @@ +/* 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 + * + * Compressor logic by + * (c)2003-6 fluffy@beesbuzz.biz + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "compress.h" +#include "utils.h" + +#ifdef USE_X +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +static Display *display; +static Window window; +static Visual *visual; +static int screen; +static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC; +#endif + +static int *peaks; +static int gainCurrent, gainTarget; + +static struct { + int show_mon; + int anticlip; + int target; + int gainmax; + int gainsmooth; + int buckets; +} prefs; + +#ifdef USE_X +static int mon_init; +#endif + +void CompressCfg(int show_mon, int anticlip, int target, int gainmax, + int gainsmooth, int buckets) +{ + static int lastsize; + + prefs.show_mon = show_mon; + prefs.anticlip = anticlip; + prefs.target = target; + prefs.gainmax = gainmax; + prefs.gainsmooth = gainsmooth; + prefs.buckets = buckets; + + /* Allocate the peak structure */ + peaks = xrealloc(peaks, sizeof(int)*prefs.buckets); + + if (prefs.buckets > lastsize) + memset(peaks + lastsize, 0, sizeof(int)*(prefs.buckets + - lastsize)); + lastsize = prefs.buckets; + +#ifdef USE_X + /* Configure the monitor window if needed */ + if (show_mon && !mon_init) + { + display = XOpenDisplay(getenv("DISPLAY")); + + /* We really shouldn't try to init X if there's no X */ + if (!display) + { + fprintf(stderr, + "X not detected; disabling monitor window\n"); + show_mon = prefs.show_mon = 0; + } + } + + if (show_mon && !mon_init) + { + XGCValues gcv; + XColor col; + + gainCurrent = gainTarget = (1 << GAINSHIFT); + + + + screen = DefaultScreen(display); + visual = DefaultVisual(display, screen); + window = XCreateSimpleWindow(display, + RootWindow(display, screen), + 0, 0, prefs.buckets, 128 + 8, 0, + BlackPixel(display, screen), + WhitePixel(display, screen)); + XStoreName(display, window, "AudioCompress monitor"); + + gcv.foreground = BlackPixel(display, screen); + blackGC = XCreateGC(display, window, GCForeground, &gcv); + gcv.foreground = WhitePixel(display, screen); + whiteGC = XCreateGC(display, window, GCForeground, &gcv); + col.red = 0; + col.green = 0; + col.blue = 65535; + XAllocColor(display, DefaultColormap(display, screen), &col); + gcv.foreground = col.pixel; + blueGC = XCreateGC(display, window, GCForeground, &gcv); + col.red = 65535; + col.green = 65535; + col.blue = 0; + XAllocColor(display, DefaultColormap(display, screen), &col); + gcv.foreground = col.pixel; + yellowGC = XCreateGC(display, window, GCForeground, &gcv); + col.red = 32767; + col.green = 32767; + col.blue = 0; + XAllocColor(display, DefaultColormap(display, screen), &col); + gcv.foreground = col.pixel; + dkyellowGC = XCreateGC(display, window, GCForeground, &gcv); + col.red = 65535; + col.green = 0; + col.blue = 0; + XAllocColor(display, DefaultColormap(display, screen), &col); + gcv.foreground = col.pixel; + redGC = XCreateGC(display, window, GCForeground, &gcv); + mon_init = 1; + } + + if (mon_init) + { + if (show_mon) + XMapWindow(display, window); + else + XUnmapWindow(display, window); + XResizeWindow(display, window, prefs.buckets, 128 + 8); + XFlush(display); + } +#endif +} + +void CompressFree(void) +{ +#ifdef USE_X + if (mon_init) + { + XFreeGC(display, blackGC); + XFreeGC(display, whiteGC); + XFreeGC(display, blueGC); + XFreeGC(display, yellowGC); + XFreeGC(display, dkyellowGC); + XFreeGC(display, redGC); + XDestroyWindow(display, window); + XCloseDisplay(display); + } +#endif + + if (peaks) + free(peaks); +} + +void CompressDo(void *data, unsigned int length) +{ + int16_t *audio = (int16_t *)data, *ap; + int peak, pos; + int i; + int gr, gf, gn; + static int pn = -1; +#ifdef STATS + static int clip; +#endif + static int clipped; + + if (!peaks) + return; + + if (pn == -1) + { + for (i = 0; i < prefs.buckets; i++) + peaks[i] = 0; + } + pn = (pn + 1)%prefs.buckets; + +#ifdef DEBUG + fprintf(stderr, "modifyNative16(0x%08x, %d)\n",(unsigned int)data, + length); +#endif + + /* Determine peak's value and position */ + peak = 1; + pos = 0; + +#ifdef DEBUG + fprintf(stderr, "finding peak(b=%d)\n", pn); +#endif + + ap = audio; + for (i = 0; i < length/2; i++) + { + int val = *ap; + if (val > peak) + { + peak = val; + pos = i; + } else if (-val > peak) + { + peak = -val; + pos = i; + } + ap++; + } + peaks[pn] = peak; + + /* Only draw if needed, of course */ +#ifdef USE_X + if (prefs.show_mon) + { + /* current amplitude */ + XDrawLine(display, window, whiteGC, + pn, 0, + pn, + 127 - + (peaks[pn]*gainCurrent >> (GAINSHIFT + 8))); + + /* amplification */ + XDrawLine(display, window, yellowGC, + pn, + 127 - (peaks[pn]*gainCurrent + >> (GAINSHIFT + 8)), + pn, 127); + + /* peak */ + XDrawLine(display, window, blackGC, + pn, 127 - (peaks[pn] >> 8), pn, 127); + + /* clip indicator */ + if (clipped) + XDrawLine(display, window, redGC, + (pn + prefs.buckets - 1)%prefs.buckets, + 126 - clipped/(length*512), + (pn + prefs.buckets - 1)%prefs.buckets, + 127); + clipped = 0; + + /* target line */ + /* XDrawPoint(display, window, redGC, */ + /* pn, 127 - TARGET/256); */ + /* amplification edge */ + XDrawLine(display, window, dkyellowGC, + pn, + 127 - (peaks[pn]*gainCurrent + >> (GAINSHIFT + 8)), + pn - 1, + 127 - + (peaks[(pn + prefs.buckets + - 1)%prefs.buckets]*gainCurrent + >> (GAINSHIFT + 8))); + } +#endif + + for (i = 0; i < prefs.buckets; i++) + { + if (peaks[i] > peak) + { + peak = peaks[i]; + pos = 0; + } + } + + /* Determine target gain */ + gn = (1 << GAINSHIFT)*prefs.target/peak; + + if (gn <(1 << GAINSHIFT)) + gn = 1 << GAINSHIFT; + + gainTarget = (gainTarget *((1 << prefs.gainsmooth) - 1) + gn) + >> prefs.gainsmooth; + + /* Give it an extra insignifigant nudge to counteract possible + ** rounding error + */ + + if (gn < gainTarget) + gainTarget--; + else if (gn > gainTarget) + gainTarget++; + + if (gainTarget > prefs.gainmax << GAINSHIFT) + gainTarget = prefs.gainmax << GAINSHIFT; + + +#ifdef USE_X + if (prefs.show_mon) + { + int x; + + /* peak*gain */ + XDrawPoint(display, window, redGC, + pn, + 127 - (peak*gainCurrent + >> (GAINSHIFT + 8))); + + /* gain indicator */ + XFillRectangle(display, window, whiteGC, 0, 128, + prefs.buckets, 8); + x = (gainTarget - (1 << GAINSHIFT))*prefs.buckets + / ((prefs.gainmax - 1) << GAINSHIFT); + XDrawLine(display, window, redGC, x, + 128, x, 128 + 8); + + x = (gn - (1 << GAINSHIFT))*prefs.buckets + / ((prefs.gainmax - 1) << GAINSHIFT); + + XDrawLine(display, window, blackGC, + x, 132 - 1, + x, 132 + 1); + + /* blue peak line */ + XDrawLine(display, window, blueGC, + 0, 127 - (peak >> 8), prefs.buckets, + 127 - (peak >> 8)); + XFlush(display); + XDrawLine(display, window, whiteGC, + 0, 127 - (peak >> 8), prefs.buckets, + 127 - (peak >> 8)); + } +#endif + + /* See if a peak is going to clip */ + gn = (1 << GAINSHIFT)*32768/peak; + + if (gn < gainTarget) + { + gainTarget = gn; + + if (prefs.anticlip) + pos = 0; + + } else + { + /* We're ramping up, so draw it out over the whole frame */ + pos = length; + } + + /* Determine gain rate necessary to make target */ + if (!pos) + pos = 1; + + gr = ((gainTarget - gainCurrent) << 16)/pos; + + /* Do the shiznit */ + gf = gainCurrent << 16; + +#ifdef STATS + fprintf(stderr, "\rgain = %2.2f%+.2e ", + gainCurrent*1.0/(1 << GAINSHIFT), + (gainTarget - gainCurrent)*1.0/(1 << GAINSHIFT)); +#endif + + ap = audio; + for (i = 0; i < length/2; i++) + { + int sample; + + /* Interpolate the gain */ + gainCurrent = gf >> 16; + if (i < pos) + gf += gr; + else if (i == pos) + gf = gainTarget << 16; + + /* Amplify */ + sample = (*ap)*gainCurrent >> GAINSHIFT; + if (sample < -32768) + { +#ifdef STATS + clip++; +#endif + clipped += -32768 - sample; + sample = -32768; + } else if (sample > 32767) + { +#ifdef STATS + clip++; +#endif + clipped += sample - 32767; + sample = 32767; + } + *ap++ = sample; + } +#ifdef STATS + fprintf(stderr, "clip %d b%-3d ", clip, pn); +#endif + +#ifdef DEBUG + fprintf(stderr, "\ndone\n"); +#endif +} + diff --git a/trunk/src/compress.h b/trunk/src/compress.h new file mode 100644 index 000000000..42638f788 --- /dev/null +++ b/trunk/src/compress.h @@ -0,0 +1,47 @@ +/* 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 + * + * interface to audio compression + * (c)2003-6 fluffy@beesbuzz.biz + * + * 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 COMPRESS_H +#define COMPRESS_H + +/* These are copied from the AudioCompress config.h, mainly because CompressDo + * needs GAINSHIFT defined. The rest are here so they can be used as defaults + * to pass to CompressCfg(). -- jat */ +#define ANTICLIP 0 /* Strict clipping protection */ +#define TARGET 25000 /* Target level */ +#define GAINMAX 32 /* The maximum amount to amplify by */ +#define GAINSHIFT 10 /* How fine-grained the gain is */ +#define GAINSMOOTH 8 /* How much inertia ramping has*/ +#define BUCKETS 400 /* How long of a history to store */ + +void CompressCfg(int monitor, + int anticlip, + int target, + int maxgain, + int smooth, + int buckets); + +void CompressDo(void *data, unsigned int numSamples); + +void CompressFree(void); + +#endif diff --git a/trunk/src/conf.c b/trunk/src/conf.c new file mode 100644 index 000000000..8ab59a505 --- /dev/null +++ b/trunk/src/conf.c @@ -0,0 +1,450 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "conf.h" + +#include "log.h" + +#include "utils.h" +#include "buffer2array.h" +#include "list.h" + +#include <sys/param.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> +#include <errno.h> + +#define MAX_STRING_SIZE MAXPATHLEN+80 + +#define CONF_COMMENT '#' +#define CONF_BLOCK_BEGIN "{" +#define CONF_BLOCK_END "}" + +#define CONF_REPEATABLE_MASK 0x01 +#define CONF_BLOCK_MASK 0x02 +#define CONF_LINE_TOKEN_MAX 3 + +typedef struct _configEntry { + unsigned char mask; + List *configParamList; +} ConfigEntry; + +static List *configEntriesList; + +static ConfigParam *newConfigParam(char *value, int line) +{ + ConfigParam *ret = xmalloc(sizeof(ConfigParam)); + + if (!value) + ret->value = NULL; + else + ret->value = xstrdup(value); + + ret->line = line; + + ret->numberOfBlockParams = 0; + ret->blockParams = NULL; + + return ret; +} + +static void freeConfigParam(ConfigParam * param) +{ + int i; + + if (param->value) + free(param->value); + + for (i = 0; i < param->numberOfBlockParams; i++) { + if (param->blockParams[i].name) { + free(param->blockParams[i].name); + } + if (param->blockParams[i].value) { + free(param->blockParams[i].value); + } + } + + if (param->numberOfBlockParams) + free(param->blockParams); + + free(param); +} + +static ConfigEntry *newConfigEntry(int repeatable, int block) +{ + ConfigEntry *ret = xmalloc(sizeof(ConfigEntry)); + + ret->mask = 0; + ret->configParamList = + makeList((ListFreeDataFunc *) freeConfigParam, 1); + + if (repeatable) + ret->mask |= CONF_REPEATABLE_MASK; + if (block) + ret->mask |= CONF_BLOCK_MASK; + + return ret; +} + +static void freeConfigEntry(ConfigEntry * entry) +{ + freeList(entry->configParamList); + free(entry); +} + +static void registerConfigParam(char *name, int repeatable, int block) +{ + ConfigEntry *entry; + + if (findInList(configEntriesList, name, NULL)) + FATAL("config parameter \"%s\" already registered\n", name); + + entry = newConfigEntry(repeatable, block); + + insertInList(configEntriesList, name, entry); +} + +void finishConf(void) +{ + freeList(configEntriesList); +} + +void initConf(void) +{ + configEntriesList = makeList((ListFreeDataFunc *) freeConfigEntry, 1); + + /* registerConfigParam(name, repeatable, block); */ + registerConfigParam(CONF_MUSIC_DIR, 0, 0); + registerConfigParam(CONF_PLAYLIST_DIR, 0, 0); + registerConfigParam(CONF_DB_FILE, 0, 0); + registerConfigParam(CONF_LOG_FILE, 0, 0); + registerConfigParam(CONF_ERROR_FILE, 0, 0); + registerConfigParam(CONF_PID_FILE, 0, 0); + registerConfigParam(CONF_STATE_FILE, 0, 0); + registerConfigParam(CONF_USER, 0, 0); + registerConfigParam(CONF_BIND_TO_ADDRESS, 1, 0); + registerConfigParam(CONF_PORT, 0, 0); + registerConfigParam(CONF_LOG_LEVEL, 0, 0); + registerConfigParam(CONF_ZEROCONF_NAME, 0, 0); + registerConfigParam(CONF_PASSWORD, 1, 0); + registerConfigParam(CONF_DEFAULT_PERMS, 0, 0); + registerConfigParam(CONF_AUDIO_OUTPUT, 1, 1); + registerConfigParam(CONF_AUDIO_OUTPUT_FORMAT, 0, 0); + registerConfigParam(CONF_MIXER_TYPE, 0, 0); + registerConfigParam(CONF_MIXER_DEVICE, 0, 0); + registerConfigParam(CONF_MIXER_CONTROL, 0, 0); + registerConfigParam(CONF_REPLAYGAIN, 0, 0); + registerConfigParam(CONF_REPLAYGAIN_PREAMP, 0, 0); + registerConfigParam(CONF_VOLUME_NORMALIZATION, 0, 0); + registerConfigParam(CONF_SAMPLERATE_CONVERTER, 0, 0); + registerConfigParam(CONF_AUDIO_BUFFER_SIZE, 0, 0); + registerConfigParam(CONF_BUFFER_BEFORE_PLAY, 0, 0); + registerConfigParam(CONF_HTTP_BUFFER_SIZE, 0, 0); + registerConfigParam(CONF_HTTP_PREBUFFER_SIZE, 0, 0); + registerConfigParam(CONF_HTTP_PROXY_HOST, 0, 0); + registerConfigParam(CONF_HTTP_PROXY_PORT, 0, 0); + registerConfigParam(CONF_HTTP_PROXY_USER, 0, 0); + registerConfigParam(CONF_HTTP_PROXY_PASSWORD, 0, 0); + registerConfigParam(CONF_CONN_TIMEOUT, 0, 0); + registerConfigParam(CONF_MAX_CONN, 0, 0); + registerConfigParam(CONF_MAX_PLAYLIST_LENGTH, 0, 0); + registerConfigParam(CONF_MAX_COMMAND_LIST_SIZE, 0, 0); + registerConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE, 0, 0); + registerConfigParam(CONF_FS_CHARSET, 0, 0); + registerConfigParam(CONF_ID3V1_ENCODING, 0, 0); + registerConfigParam(CONF_METADATA_TO_USE, 0, 0); + registerConfigParam(CONF_SAVE_ABSOLUTE_PATHS, 0, 0); + registerConfigParam(CONF_GAPLESS_MP3_PLAYBACK, 0, 0); +} + +static void addBlockParam(ConfigParam * param, char *name, char *value, + int line) +{ + param->numberOfBlockParams++; + + param->blockParams = xrealloc(param->blockParams, + param->numberOfBlockParams * + sizeof(BlockParam)); + + param->blockParams[param->numberOfBlockParams - 1].name = xstrdup(name); + param->blockParams[param->numberOfBlockParams - 1].value = + xstrdup(value); + param->blockParams[param->numberOfBlockParams - 1].line = line; +} + +static ConfigParam *readConfigBlock(FILE * fp, int *count, char *string) +{ + ConfigParam *ret = newConfigParam(NULL, *count); + + int i; + int numberOfArgs; + int argsMinusComment; + + while (myFgets(string, MAX_STRING_SIZE, fp)) { + char *array[CONF_LINE_TOKEN_MAX] = { NULL }; + + (*count)++; + + numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX); + + for (i = 0; i < numberOfArgs; i++) { + if (array[i][0] == CONF_COMMENT) + break; + } + + argsMinusComment = i; + + if (0 == argsMinusComment) { + continue; + } + + if (1 == argsMinusComment && + 0 == strcmp(array[0], CONF_BLOCK_END)) { + break; + } + + if (2 != argsMinusComment) { + FATAL("improperly formatted config file at line %i:" + " %s\n", *count, string); + } + + if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) || + 0 == strcmp(array[1], CONF_BLOCK_BEGIN) || + 0 == strcmp(array[0], CONF_BLOCK_END) || + 0 == strcmp(array[1], CONF_BLOCK_END)) { + FATAL("improperly formatted config file at line %i: %s\n" + "in block beginning at line %i\n", + *count, string, ret->line);; + } + + addBlockParam(ret, array[0], array[1], *count); + } + + return ret; +} + +void readConf(char *file) +{ + FILE *fp; + char string[MAX_STRING_SIZE + 1]; + int i; + int numberOfArgs; + int argsMinusComment; + int count = 0; + ConfigEntry *entry; + void *voidPtr; + ConfigParam *param; + + if (!(fp = fopen(file, "r"))) { + FATAL("problems opening file %s for reading: %s\n", file, + strerror(errno)); + } + + while (myFgets(string, MAX_STRING_SIZE, fp)) { + char *array[CONF_LINE_TOKEN_MAX] = { NULL }; + count++; + + numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX); + + for (i = 0; i < numberOfArgs; i++) { + if (array[i][0] == CONF_COMMENT) + break; + } + + argsMinusComment = i; + + if (0 == argsMinusComment) { + continue; + } + + if (2 != argsMinusComment) { + FATAL("improperly formatted config file at line %i:" + " %s\n", count, string); + } + + if (!findInList(configEntriesList, array[0], &voidPtr)) { + FATAL("unrecognized parameter in config file at line " + "%i: %s\n", count, string); + } + + entry = (ConfigEntry *) voidPtr; + + if (!(entry->mask & CONF_REPEATABLE_MASK) && + entry->configParamList->numberOfNodes) { + param = entry->configParamList->firstNode->data; + FATAL("config parameter \"%s\" is first defined on line " + "%i and redefined on line %i\n", array[0], + param->line, count); + } + + if (entry->mask & CONF_BLOCK_MASK) { + if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) { + FATAL("improperly formatted config file at " + "line %i: %s\n", count, string); + } + param = readConfigBlock(fp, &count, string); + } else + param = newConfigParam(array[1], count); + + insertInListWithoutKey(entry->configParamList, param); + } + fclose(fp); +} + +ConfigParam *getNextConfigParam(char *name, ConfigParam * last) +{ + void *voidPtr; + ConfigEntry *entry; + ListNode *node; + ConfigParam *param; + + if (!findInList(configEntriesList, name, &voidPtr)) + return NULL; + + entry = voidPtr; + + node = entry->configParamList->firstNode; + + if (last) { + while (node != NULL) { + param = node->data; + node = node->nextNode; + if (param == last) + break; + } + } + + if (node == NULL) + return NULL; + + param = node->data; + + return param; +} + +char *getConfigParamValue(char *name) +{ + ConfigParam *param = getConfigParam(name); + + if (!param) + return NULL; + + return param->value; +} + +int getBoolConfigParam(char *name) +{ + ConfigParam *param; + + param = getConfigParam(name); + if (!param) return -1; + + if (strcmp("yes", param->value) == 0) return 1; + else if (strcmp("no", param->value) == 0) return 0; + + ERROR("%s is not \"yes\" or \"no\" on line %i\n", name, param->line); + + return -2; +} + +BlockParam *getBlockParam(ConfigParam * param, char *name) +{ + BlockParam *ret = NULL; + int i; + + for (i = 0; i < param->numberOfBlockParams; i++) { + if (0 == strcmp(name, param->blockParams[i].name)) { + if (ret) { + ERROR("\"%s\" first defined on line %i, and " + "redefined on line %i\n", name, + ret->line, param->blockParams[i].line); + } + ret = param->blockParams + i; + } + } + + return ret; +} + +ConfigParam *parseConfigFilePath(char *name, int force) +{ + ConfigParam *param = getConfigParam(name); + char *path; + + if (!param && force) + FATAL("config parameter \"%s\" not found\n", name); + + if (!param) + return NULL; + + path = param->value; + + if (path[0] != '/' && path[0] != '~') { + FATAL("\"%s\" is not an absolute path at line %i\n", + param->value, param->line); + } + /* Parse ~ in path */ + else if (path[0] == '~') { + struct passwd *pwd = NULL; + char *newPath; + int pos = 1; + if (path[1] == '/' || path[1] == '\0') { + ConfigParam *userParam = getConfigParam(CONF_USER); + + if (userParam) { + pwd = getpwnam(userParam->value); + if (!pwd) { + FATAL("no such user %s at line %i\n", + userParam->value, + userParam->line); + } + } else { + uid_t uid = geteuid(); + if ((pwd = getpwuid(uid)) == NULL) { + FATAL("problems getting passwd entry " + "for current user\n"); + } + } + } else { + int foundSlash = 0; + char *ch = path + 1; + for (; *ch != '\0' && *ch != '/'; ch++) ; + if (*ch == '/') + foundSlash = 1; + *ch = '\0'; + pos += ch - path - 1; + if ((pwd = getpwnam(path + 1)) == NULL) { + FATAL("user \"%s\" not found at line %i\n", + path + 1, param->line); + } + if (foundSlash) + *ch = '/'; + } + newPath = xmalloc(strlen(pwd->pw_dir) + strlen(path + pos) + 1); + strcpy(newPath, pwd->pw_dir); + strcat(newPath, path + pos); + free(param->value); + param->value = newPath; + } + + return param; +} diff --git a/trunk/src/conf.h b/trunk/src/conf.h new file mode 100644 index 000000000..7059eaa90 --- /dev/null +++ b/trunk/src/conf.h @@ -0,0 +1,98 @@ +/* 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 CONF_H +#define CONF_H + +#include "../config.h" + +#define CONF_MUSIC_DIR "music_directory" +#define CONF_PLAYLIST_DIR "playlist_directory" +#define CONF_DB_FILE "db_file" +#define CONF_LOG_FILE "log_file" +#define CONF_ERROR_FILE "error_file" +#define CONF_PID_FILE "pid_file" +#define CONF_STATE_FILE "state_file" +#define CONF_USER "user" +#define CONF_BIND_TO_ADDRESS "bind_to_address" +#define CONF_PORT "port" +#define CONF_LOG_LEVEL "log_level" +#define CONF_ZEROCONF_NAME "zeroconf_name" +#define CONF_PASSWORD "password" +#define CONF_DEFAULT_PERMS "default_permissions" +#define CONF_AUDIO_OUTPUT "audio_output" +#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format" +#define CONF_MIXER_TYPE "mixer_type" +#define CONF_MIXER_DEVICE "mixer_device" +#define CONF_MIXER_CONTROL "mixer_control" +#define CONF_REPLAYGAIN "replaygain" +#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp" +#define CONF_VOLUME_NORMALIZATION "volume_normalization" +#define CONF_SAMPLERATE_CONVERTER "samplerate_converter" +#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size" +#define CONF_BUFFER_BEFORE_PLAY "buffer_before_play" +#define CONF_HTTP_BUFFER_SIZE "http_buffer_size" +#define CONF_HTTP_PREBUFFER_SIZE "http_prebuffer_size" +#define CONF_HTTP_PROXY_HOST "http_proxy_host" +#define CONF_HTTP_PROXY_PORT "http_proxy_port" +#define CONF_HTTP_PROXY_USER "http_proxy_user" +#define CONF_HTTP_PROXY_PASSWORD "http_proxy_password" +#define CONF_CONN_TIMEOUT "connection_timeout" +#define CONF_MAX_CONN "max_connections" +#define CONF_MAX_PLAYLIST_LENGTH "max_playlist_length" +#define CONF_MAX_COMMAND_LIST_SIZE "max_command_list_size" +#define CONF_MAX_OUTPUT_BUFFER_SIZE "max_output_buffer_size" +#define CONF_FS_CHARSET "filesystem_charset" +#define CONF_ID3V1_ENCODING "id3v1_encoding" +#define CONF_METADATA_TO_USE "metadata_to_use" +#define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists" +#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback" + +typedef struct _BlockParam { + char *name; + char *value; + int line; +} BlockParam; + +typedef struct _ConfigParam { + char *value; + unsigned int line; + BlockParam *blockParams; + int numberOfBlockParams; +} ConfigParam; + +void initConf(void); +void finishConf(void); + +void readConf(char *file); + +/* don't free the returned value + set _last_ to NULL to get first entry */ +ConfigParam *getNextConfigParam(char *name, ConfigParam * last); + +#define getConfigParam(name) getNextConfigParam(name, NULL) + +char *getConfigParamValue(char *name); + +int getBoolConfigParam(char *name); + +BlockParam *getBlockParam(ConfigParam * param, char *name); + +ConfigParam *parseConfigFilePath(char *name, int force); + +#endif diff --git a/trunk/src/dbUtils.c b/trunk/src/dbUtils.c new file mode 100644 index 000000000..f83ae4c21 --- /dev/null +++ b/trunk/src/dbUtils.c @@ -0,0 +1,341 @@ +/* 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 "dbUtils.h" + +#include "directory.h" +#include "myfprintf.h" +#include "utils.h" +#include "playlist.h" +#include "song.h" +#include "tag.h" +#include "tagTracker.h" +#include "log.h" +#include "storedPlaylist.h" + +typedef struct _ListCommandItem { + mpd_sint8 tagType; + int numConditionals; + LocateTagItem *conditionals; +} ListCommandItem; + +typedef struct _LocateTagItemArray { + int numItems; + LocateTagItem *items; +} LocateTagItemArray; + +typedef struct _SearchStats { + LocateTagItemArray locateArray; + int numberOfSongs; + unsigned long playTime; +} SearchStats; + +static int countSongsInDirectory(int fd, Directory * directory, void *data) +{ + int *count = (int *)data; + + *count += directory->songs->numberOfNodes; + + return 0; +} + +static int printDirectoryInDirectory(int fd, Directory * directory, + void *data) +{ + if (directory->path) { + fdprintf(fd, "directory: %s\n", getDirectoryPath(directory)); + } + return 0; +} + +static int printSongInDirectory(int fd, Song * song, void *data) +{ + printSongUrl(fd, song); + return 0; +} + +static int searchInDirectory(int fd, Song * song, void *data) +{ + LocateTagItemArray *array = data; + + if (strstrSearchTags(song, array->numItems, array->items)) + printSongInfo(fd, song); + + return 0; +} + +int searchForSongsIn(int fd, char *name, int numItems, LocateTagItem * items) +{ + int ret = -1; + int i; + + char **originalNeedles = xmalloc(numItems * sizeof(char *)); + LocateTagItemArray array; + + for (i = 0; i < numItems; i++) { + originalNeedles[i] = items[i].needle; + items[i].needle = strDupToUpper(originalNeedles[i]); + } + + array.numItems = numItems; + array.items = items; + + ret = traverseAllIn(fd, name, searchInDirectory, NULL, &array); + + for (i = 0; i < numItems; i++) { + free(items[i].needle); + items[i].needle = originalNeedles[i]; + } + + free(originalNeedles); + + return ret; +} + +static int findInDirectory(int fd, Song * song, void *data) +{ + LocateTagItemArray *array = data; + + if (tagItemsFoundAndMatches(song, array->numItems, array->items)) + printSongInfo(fd, song); + + return 0; +} + +int findSongsIn(int fd, char *name, int numItems, LocateTagItem * items) +{ + LocateTagItemArray array; + + array.numItems = numItems; + array.items = items; + + return traverseAllIn(fd, name, findInDirectory, NULL, (void *)&array); +} + +static void printSearchStats(int fd, SearchStats *stats) +{ + fdprintf(fd, "songs: %i\n", stats->numberOfSongs); + fdprintf(fd, "playtime: %li\n", stats->playTime); +} + +static int searchStatsInDirectory(int fd, Song * song, void *data) +{ + SearchStats *stats = data; + + if (tagItemsFoundAndMatches(song, stats->locateArray.numItems, + stats->locateArray.items)) { + stats->numberOfSongs++; + if (song->tag->time > 0) + stats->playTime += song->tag->time; + } + + return 0; +} + +int searchStatsForSongsIn(int fd, char *name, int numItems, + LocateTagItem * items) +{ + SearchStats stats; + int ret; + + stats.locateArray.numItems = numItems; + stats.locateArray.items = items; + stats.numberOfSongs = 0; + stats.playTime = 0; + + ret = traverseAllIn(fd, name, searchStatsInDirectory, NULL, &stats); + if (ret == 0) + printSearchStats(fd, &stats); + + return ret; +} + +int printAllIn(int fd, char *name) +{ + return traverseAllIn(fd, name, printSongInDirectory, + printDirectoryInDirectory, NULL); +} + +static int directoryAddSongToPlaylist(int fd, Song * song, void *data) +{ + return addSongToPlaylist(fd, song, 0); +} + +static int directoryAddSongToStoredPlaylist(int fd, Song *song, void *data) +{ + if (appendSongToStoredPlaylistByPath(fd, (char *)data, song) != 0) + return -1; + return 0; +} + +int addAllIn(int fd, char *name) +{ + return traverseAllIn(fd, name, directoryAddSongToPlaylist, NULL, NULL); +} + +int addAllInToStoredPlaylist(int fd, char *name, char *utf8file) +{ + return traverseAllIn(fd, name, directoryAddSongToStoredPlaylist, NULL, + (void *)utf8file); +} + +static int directoryPrintSongInfo(int fd, Song * song, void *data) +{ + return printSongInfo(fd, song); +} + +static int sumSongTime(int fd, Song * song, void *data) +{ + unsigned long *time = (unsigned long *)data; + + if (song->tag && song->tag->time >= 0) + *time += song->tag->time; + + return 0; +} + +int printInfoForAllIn(int fd, char *name) +{ + return traverseAllIn(fd, name, directoryPrintSongInfo, + printDirectoryInDirectory, NULL); +} + +int countSongsIn(int fd, char *name) +{ + int count = 0; + void *ptr = (void *)&count; + + traverseAllIn(fd, name, NULL, countSongsInDirectory, ptr); + + return count; +} + +unsigned long sumSongTimesIn(int fd, char *name) +{ + unsigned long dbPlayTime = 0; + void *ptr = (void *)&dbPlayTime; + + traverseAllIn(fd, name, sumSongTime, NULL, ptr); + + return dbPlayTime; +} + +static ListCommandItem *newListCommandItem(int tagType, int numConditionals, + LocateTagItem * conditionals) +{ + ListCommandItem *item = xmalloc(sizeof(ListCommandItem)); + + item->tagType = tagType; + item->numConditionals = numConditionals; + item->conditionals = conditionals; + + return item; +} + +static void freeListCommandItem(ListCommandItem * item) +{ + free(item); +} + +static void visitTag(int fd, Song * song, int tagType) +{ + int i; + MpdTag *tag = song->tag; + + if (tagType == LOCATE_TAG_FILE_TYPE) { + printSongUrl(fd, song); + return; + } + + if (!tag) + return; + + for (i = 0; i < tag->numOfItems; i++) { + if (tag->items[i].type == tagType) { + visitInTagTracker(tagType, tag->items[i].value); + } + } +} + +static int listUniqueTagsInDirectory(int fd, Song * song, void *data) +{ + ListCommandItem *item = data; + + if (tagItemsFoundAndMatches(song, item->numConditionals, + item->conditionals)) { + visitTag(fd, song, item->tagType); + } + + return 0; +} + +int listAllUniqueTags(int fd, int type, int numConditionals, + LocateTagItem * conditionals) +{ + int ret; + ListCommandItem *item = newListCommandItem(type, numConditionals, + conditionals); + + if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { + resetVisitedFlagsInTagTracker(type); + } + + ret = traverseAllIn(fd, NULL, listUniqueTagsInDirectory, NULL, + (void *)item); + + if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { + printVisitedInTagTracker(fd, type); + } + + freeListCommandItem(item); + + return ret; +} + +static int sumSavedFilenameMemoryInDirectory(int fd, Directory * dir, + void *data) +{ + int *sum = data; + + if (!dir->path) + return 0; + + *sum += (strlen(getDirectoryPath(dir)) + 1 - sizeof(Directory *)) * + dir->songs->numberOfNodes; + + return 0; +} + +static int sumSavedFilenameMemoryInSong(int fd, Song * song, void *data) +{ + int *sum = data; + + *sum += strlen(song->url) + 1; + + return 0; +} + +void printSavedMemoryFromFilenames(void) +{ + int sum = 0; + + traverseAllIn(STDERR_FILENO, NULL, sumSavedFilenameMemoryInSong, + sumSavedFilenameMemoryInDirectory, (void *)&sum); + + DEBUG("saved memory from filenames: %i\n", sum); +} diff --git a/trunk/src/dbUtils.h b/trunk/src/dbUtils.h new file mode 100644 index 000000000..0607bc3b5 --- /dev/null +++ b/trunk/src/dbUtils.h @@ -0,0 +1,51 @@ +/* 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 DB_UTILS_H +#define DB_UTILS_H + +#include <stdio.h> + +#include "locate.h" + +int printAllIn(int fd, char *name); + +int addAllIn(int fd, char *name); + +int addAllInToStoredPlaylist(int fd, char *name, char *utf8file); + +int printInfoForAllIn(int fd, char *name); + +int searchForSongsIn(int fd, char *name, int numItems, + LocateTagItem * items); + +int findSongsIn(int fd, char *name, int numItems, LocateTagItem * items); + +int searchStatsForSongsIn(int fd, char *name, int numItems, + LocateTagItem * items); + +int countSongsIn(int fd, char *name); + +unsigned long sumSongTimesIn(int fd, char *name); + +int listAllUniqueTags(int fd, int type, int numConditiionals, + LocateTagItem * conditionals); + +void printSavedMemoryFromFilenames(void); + +#endif 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); +} diff --git a/trunk/src/decode.h b/trunk/src/decode.h new file mode 100644 index 000000000..f073c0d55 --- /dev/null +++ b/trunk/src/decode.h @@ -0,0 +1,69 @@ +/* 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 DECODE_H +#define DECODE_H + +#include "../config.h" +#include "tag.h" + +#include "mpd_types.h" +#include "audio.h" + +#include <stdio.h> +#include <sys/param.h> +#include <signal.h> + +#define DECODE_TYPE_FILE 0 +#define DECODE_TYPE_URL 1 + +#define DECODE_STATE_STOP 0 +#define DECODE_STATE_START 1 +#define DECODE_STATE_DECODE 2 + +#define DECODE_ERROR_NOERROR 0 +#define DECODE_ERROR_UNKTYPE 10 +#define DECODE_ERROR_FILE 20 + +#define DECODE_SUFFIX_MP3 1 +#define DECODE_SUFFIX_OGG 2 +#define DECODE_SUFFIX_FLAC 3 +#define DECODE_SUFFIX_AAC 4 +#define DECODE_SUFFIX_MP4 5 +#define DECODE_SUFFIX_WAVE 6 + +typedef struct _DecoderControl { + volatile mpd_sint8 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 mpd_sint8 cycleLogFiles; + volatile double seekWhere; + AudioFormat audioFormat; + char utf8url[MAXPATHLEN + 1]; + volatile float totalTime; +} DecoderControl; + +void decodeSigHandler(int sig, siginfo_t * siginfo, void *v); + +void decode(void); + +#endif diff --git a/trunk/src/directory.c b/trunk/src/directory.c new file mode 100644 index 000000000..560c04b7b --- /dev/null +++ b/trunk/src/directory.c @@ -0,0 +1,1362 @@ +/* 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 "directory.h" + +#include "command.h" +#include "conf.h" +#include "dbUtils.h" +#include "interface.h" +#include "list.h" +#include "listen.h" +#include "log.h" +#include "ls.h" +#include "mpd_types.h" +#include "path.h" +#include "player.h" +#include "playlist.h" +#include "sig_handlers.h" +#include "stats.h" +#include "tagTracker.h" +#include "utils.h" +#include "volume.h" + +#include <sys/wait.h> +#include <dirent.h> +#include <errno.h> +#include <assert.h> +#include <libgen.h> + +#define DIRECTORY_DIR "directory: " +#define DIRECTORY_MTIME "mtime: " +#define DIRECTORY_BEGIN "begin: " +#define DIRECTORY_END "end: " +#define DIRECTORY_INFO_BEGIN "info_begin" +#define DIRECTORY_INFO_END "info_end" +#define DIRECTORY_MPD_VERSION "mpd_version: " +#define DIRECTORY_FS_CHARSET "fs_charset: " + +#define DIRECTORY_UPDATE_EXIT_NOUPDATE 0 +#define DIRECTORY_UPDATE_EXIT_UPDATE 1 +#define DIRECTORY_UPDATE_EXIT_ERROR 2 + +#define DIRECTORY_RETURN_NOUPDATE 0 +#define DIRECTORY_RETURN_UPDATE 1 +#define DIRECTORY_RETURN_ERROR -1 + +static Directory *mp3rootDirectory; + +static time_t directory_dbModTime; + +static volatile int directory_updatePid; + +static volatile int directory_reReadDB; + +static volatile mpd_uint16 directory_updateJobId; + +static DirectoryList *newDirectoryList(); + +static int addToDirectory(Directory * directory, char *shortname, char *name); + +static void freeDirectoryList(DirectoryList * list); + +static void freeDirectory(Directory * directory); + +static int exploreDirectory(Directory * directory); + +static int updateDirectory(Directory * directory); + +static void deleteEmptyDirectoriesInDirectory(Directory * directory); + +static void removeSongFromDirectory(Directory * directory, char *shortname); + +static int addSubDirectoryToDirectory(Directory * directory, char *shortname, + char *name, struct stat *st); + +static Directory *getDirectoryDetails(char *name, char **shortname); + +static Directory *getDirectory(char *name); + +static Song *getSongDetails(char *file, char **shortnameRet, + Directory ** directoryRet); + +static int updatePath(char *utf8path); + +static void sortDirectory(Directory * directory); + +static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device); + +static int statDirectory(Directory * dir); + +static char *getDbFile(void) +{ + ConfigParam *param = parseConfigFilePath(CONF_DB_FILE, 1); + + assert(param); + assert(param->value); + + return param->value; +} + +static void clearUpdatePid(void) +{ + directory_updatePid = 0; +} + +int isUpdatingDB(void) +{ + if (directory_updatePid > 0 || directory_reReadDB) { + return directory_updateJobId; + } + return 0; +} + +void directory_sigChldHandler(int pid, int status) +{ + if (directory_updatePid == pid) { + if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) { + ERROR("update process died from a " + "non-TERM signal: %i\n", WTERMSIG(status)); + } else if (!WIFSIGNALED(status)) { + switch (WEXITSTATUS(status)) { + case DIRECTORY_UPDATE_EXIT_UPDATE: + directory_reReadDB = 1; + DEBUG("directory_sigChldHandler: " + "updated db\n"); + case DIRECTORY_UPDATE_EXIT_NOUPDATE: + DEBUG("directory_sigChldHandler: " + "update exited succesffully\n"); + break; + default: + ERROR("error updating db\n"); + } + } + clearUpdatePid(); + } +} + +void readDirectoryDBIfUpdateIsFinished(void) +{ + if (directory_reReadDB && 0 == directory_updatePid) { + DEBUG("readDirectoryDB since update finished successfully\n"); + readDirectoryDB(); + playlistVersionChange(); + directory_reReadDB = 0; + } +} + +int updateInit(int fd, List * pathList) +{ + if (directory_updatePid > 0) { + commandError(fd, ACK_ERROR_UPDATE_ALREADY, "already updating"); + return -1; + } + + /* need to block CHLD signal, cause it can exit before we + even get a chance to assign directory_updatePID */ + blockSignals(); + directory_updatePid = fork(); + if (directory_updatePid == 0) { + /* child */ + int dbUpdated = 0; + clearPlayerPid(); + + unblockSignals(); + + finishSigHandlers(); + closeAllListenSockets(); + freeAllInterfaces(); + finishPlaylist(); + finishVolume(); + + if (pathList) { + ListNode *node = pathList->firstNode; + + while (node) { + switch (updatePath(node->key)) { + case 1: + dbUpdated = 1; + break; + case 0: + break; + default: + exit(DIRECTORY_UPDATE_EXIT_ERROR); + } + node = node->nextNode; + } + } else { + if ((dbUpdated = updateDirectory(mp3rootDirectory)) < 0) { + exit(DIRECTORY_UPDATE_EXIT_ERROR); + } + } + + if (!dbUpdated) + exit(DIRECTORY_UPDATE_EXIT_NOUPDATE); + + /* ignore signals since we don't want them to corrupt the db */ + ignoreSignals(); + if (writeDirectoryDB() < 0) { + exit(DIRECTORY_UPDATE_EXIT_ERROR); + } + exit(DIRECTORY_UPDATE_EXIT_UPDATE); + } else if (directory_updatePid < 0) { + unblockSignals(); + ERROR("updateInit: Problems forking()'ing\n"); + commandError(fd, ACK_ERROR_SYSTEM, + "problems trying to update"); + directory_updatePid = 0; + return -1; + } + unblockSignals(); + + directory_updateJobId++; + if (directory_updateJobId > 1 << 15) + directory_updateJobId = 1; + DEBUG("updateInit: fork()'d update child for update job id %i\n", + (int)directory_updateJobId); + fdprintf(fd, "updating_db: %i\n", (int)directory_updateJobId); + + return 0; +} + +static DirectoryStat *newDirectoryStat(struct stat *st) +{ + DirectoryStat *ret = xmalloc(sizeof(DirectoryStat)); + ret->inode = st->st_ino; + ret->device = st->st_dev; + return ret; +} + +static void freeDirectoryStatFromDirectory(Directory * dir) +{ + if (dir->stat) + free(dir->stat); + dir->stat = NULL; +} + +static DirectoryList *newDirectoryList(void) +{ + return makeList((ListFreeDataFunc *) freeDirectory, 1); +} + +static Directory *newDirectory(char *dirname, Directory * parent) +{ + Directory *directory; + + directory = xmalloc(sizeof(Directory)); + + if (dirname && strlen(dirname)) + directory->path = xstrdup(dirname); + else + directory->path = NULL; + directory->subDirectories = newDirectoryList(); + directory->songs = newSongList(); + directory->stat = NULL; + directory->parent = parent; + + return directory; +} + +static void freeDirectory(Directory * directory) +{ + freeDirectoryList(directory->subDirectories); + freeSongList(directory->songs); + if (directory->path) + free(directory->path); + freeDirectoryStatFromDirectory(directory); + free(directory); + /* this resets last dir returned */ + /*getDirectoryPath(NULL); */ +} + +static void freeDirectoryList(DirectoryList * directoryList) +{ + freeList(directoryList); +} + +static void removeSongFromDirectory(Directory * directory, char *shortname) +{ + void *song; + + if (findInList(directory->songs, shortname, &song)) { + LOG("removing: %s\n", getSongUrl((Song *) song)); + deleteFromList(directory->songs, shortname); + } +} + +static void deleteEmptyDirectoriesInDirectory(Directory * directory) +{ + ListNode *node = directory->subDirectories->firstNode; + ListNode *nextNode; + Directory *subDir; + + while (node) { + subDir = (Directory *) node->data; + deleteEmptyDirectoriesInDirectory(subDir); + nextNode = node->nextNode; + if (subDir->subDirectories->numberOfNodes == 0 && + subDir->songs->numberOfNodes == 0) { + deleteNodeFromList(directory->subDirectories, node); + } + node = nextNode; + } +} + +/* return values: + -1 -> error + 0 -> no error, but nothing updated + 1 -> no error, and stuff updated + */ +static int updateInDirectory(Directory * directory, char *shortname, char *name) +{ + void *song; + void *subDir; + struct stat st; + + if (myStat(name, &st)) + return -1; + + if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) { + if (0 == findInList(directory->songs, shortname, &song)) { + addToDirectory(directory, shortname, name); + return DIRECTORY_RETURN_UPDATE; + } else if (st.st_mtime != ((Song *) song)->mtime) { + LOG("updating %s\n", name); + if (updateSongInfo((Song *) song) < 0) { + removeSongFromDirectory(directory, shortname); + } + return 1; + } + } else if (S_ISDIR(st.st_mode)) { + if (findInList + (directory->subDirectories, shortname, (void **)&subDir)) { + freeDirectoryStatFromDirectory(subDir); + ((Directory *) subDir)->stat = newDirectoryStat(&st); + return updateDirectory((Directory *) subDir); + } else { + return addSubDirectoryToDirectory(directory, shortname, + name, &st); + } + } + + return 0; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing removed + 1 -> no error, and stuff removed + */ +static int removeDeletedFromDirectory(Directory * directory, DIR * dir) +{ + char cwd[2]; + struct dirent *ent; + char *dirname = getDirectoryPath(directory); + List *entList = makeList(free, 1); + void *name; + char *s; + char *utf8; + ListNode *node; + ListNode *tmpNode; + int ret = 0; + + cwd[0] = '.'; + cwd[1] = '\0'; + if (dirname == NULL) + dirname = cwd; + + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') + continue; /* hide hidden stuff */ + if (strchr(ent->d_name, '\n')) + continue; + + utf8 = fsCharsetToUtf8(ent->d_name); + + if (!utf8) + continue; + + if (directory->path) { + s = xmalloc(strlen(getDirectoryPath(directory)) + + strlen(utf8) + 2); + sprintf(s, "%s/%s", getDirectoryPath(directory), utf8); + } else + s = xstrdup(utf8); + insertInList(entList, utf8, s); + } + + node = directory->subDirectories->firstNode; + while (node) { + tmpNode = node->nextNode; + if (findInList(entList, node->key, &name)) { + if (!isDir((char *)name)) { + LOG("removing directory: %s\n", (char *)name); + deleteFromList(directory->subDirectories, + node->key); + ret = 1; + } + } else { + LOG("removing directory: %s/%s\n", + getDirectoryPath(directory), node->key); + deleteFromList(directory->subDirectories, node->key); + ret = 1; + } + node = tmpNode; + } + + node = directory->songs->firstNode; + while (node) { + tmpNode = node->nextNode; + if (findInList(entList, node->key, (void **)&name)) { + if (!isMusic(name, NULL, 0)) { + removeSongFromDirectory(directory, node->key); + ret = 1; + } + } else { + removeSongFromDirectory(directory, node->key); + ret = 1; + } + node = tmpNode; + } + + freeList(entList); + + return ret; +} + +static Directory *addDirectoryPathToDB(char *utf8path, char **shortname) +{ + char *parent; + Directory *parentDirectory; + void *directory; + + parent = xstrdup(parentPath(utf8path)); + + if (strlen(parent) == 0) + parentDirectory = (void *)mp3rootDirectory; + else + parentDirectory = addDirectoryPathToDB(parent, shortname); + + if (!parentDirectory) { + free(parent); + return NULL; + } + + *shortname = utf8path + strlen(parent); + while (*(*shortname) && *(*shortname) == '/') + (*shortname)++; + + if (!findInList + (parentDirectory->subDirectories, *shortname, &directory)) { + struct stat st; + if (myStat(utf8path, &st) < 0 || + inodeFoundInParent(parentDirectory, st.st_ino, st.st_dev)) { + free(parent); + return NULL; + } else { + directory = newDirectory(utf8path, parentDirectory); + insertInList(parentDirectory->subDirectories, + *shortname, directory); + } + } + + /* if we're adding directory paths, make sure to delete filenames + with potentially the same name */ + removeSongFromDirectory(parentDirectory, *shortname); + + free(parent); + + return (Directory *) directory; +} + +static Directory *addParentPathToDB(char *utf8path, char **shortname) +{ + char *parent; + Directory *parentDirectory; + + parent = xstrdup(parentPath(utf8path)); + + if (strlen(parent) == 0) + parentDirectory = (void *)mp3rootDirectory; + else + parentDirectory = addDirectoryPathToDB(parent, shortname); + + if (!parentDirectory) { + free(parent); + return NULL; + } + + *shortname = utf8path + strlen(parent); + while (*(*shortname) && *(*shortname) == '/') + (*shortname)++; + + free(parent); + + return (Directory *) parentDirectory; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing updated + 1 -> no error, and stuff updated + */ +static int updatePath(char *utf8path) +{ + Directory *directory; + Directory *parentDirectory; + Song *song; + char *shortname; + char *path = sanitizePathDup(utf8path); + time_t mtime; + int ret = 0; + + if (NULL == path) + return -1; + + /* if path is in the DB try to update it, or else delete it */ + if ((directory = getDirectoryDetails(path, &shortname))) { + parentDirectory = directory->parent; + + /* if this update directory is successfull, we are done */ + if ((ret = updateDirectory(directory)) >= 0) { + free(path); + sortDirectory(directory); + return ret; + } + /* we don't want to delete the root directory */ + else if (directory == mp3rootDirectory) { + free(path); + return 0; + } + /* if updateDirectory fails, means we should delete it */ + else { + LOG("removing directory: %s\n", path); + deleteFromList(parentDirectory->subDirectories, + shortname); + ret = 1; + /* don't return, path maybe a song now */ + } + } else if ((song = getSongDetails(path, &shortname, &parentDirectory))) { + if (!parentDirectory->stat + && statDirectory(parentDirectory) < 0) { + free(path); + return 0; + } + /* if this song update is successfull, we are done */ + else if (0 == inodeFoundInParent(parentDirectory->parent, + parentDirectory->stat->inode, + parentDirectory->stat->device) + && song && isMusic(getSongUrl(song), &mtime, 0)) { + free(path); + if (song->mtime == mtime) + return 0; + else if (updateSongInfo(song) == 0) + return 1; + else { + removeSongFromDirectory(parentDirectory, + shortname); + return 1; + } + } + /* if updateDirectory fails, means we should delete it */ + else { + removeSongFromDirectory(parentDirectory, shortname); + ret = 1; + /* don't return, path maybe a directory now */ + } + } + + /* path not found in the db, see if it actually exists on the fs. + * Also, if by chance a directory was replaced by a file of the same + * name or vice versa, we need to add it to the db + */ + if (isDir(path) || isMusic(path, NULL, 0)) { + parentDirectory = addParentPathToDB(path, &shortname); + if (!parentDirectory || (!parentDirectory->stat && + statDirectory(parentDirectory) < 0)) { + } else if (0 == inodeFoundInParent(parentDirectory->parent, + parentDirectory->stat->inode, + parentDirectory->stat-> + device) + && addToDirectory(parentDirectory, shortname, path) + > 0) { + ret = 1; + } + } + + free(path); + + return ret; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing updated + 1 -> no error, and stuff updated + */ +static int updateDirectory(Directory * directory) +{ + DIR *dir; + char cwd[2]; + struct dirent *ent; + char *s; + char *utf8; + char *dirname = getDirectoryPath(directory); + int ret = 0; + + { + if (!directory->stat && statDirectory(directory) < 0) { + return -1; + } else if (inodeFoundInParent(directory->parent, + directory->stat->inode, + directory->stat->device)) { + return -1; + } + } + + cwd[0] = '.'; + cwd[1] = '\0'; + if (dirname == NULL) + dirname = cwd; + + if ((dir = opendir(rmp2amp(utf8ToFsCharset(dirname)))) == NULL) + return -1; + + if (removeDeletedFromDirectory(directory, dir) > 0) + ret = 1; + + rewinddir(dir); + + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') + continue; /* hide hidden stuff */ + if (strchr(ent->d_name, '\n')) + continue; + + utf8 = fsCharsetToUtf8(ent->d_name); + + if (!utf8) + continue; + + utf8 = xstrdup(utf8); + + if (directory->path) { + s = xmalloc(strlen(getDirectoryPath(directory)) + + strlen(utf8) + 2); + sprintf(s, "%s/%s", getDirectoryPath(directory), utf8); + } else + s = xstrdup(utf8); + if (updateInDirectory(directory, utf8, s) > 0) + ret = 1; + free(utf8); + free(s); + } + + closedir(dir); + + return ret; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing found + 1 -> no error, and stuff found + */ +static int exploreDirectory(Directory * directory) +{ + DIR *dir; + char cwd[2]; + struct dirent *ent; + char *s; + char *utf8; + char *dirname = getDirectoryPath(directory); + int ret = 0; + + cwd[0] = '.'; + cwd[1] = '\0'; + if (dirname == NULL) + dirname = cwd; + + DEBUG("explore: attempting to opendir: %s\n", dirname); + if ((dir = opendir(rmp2amp(utf8ToFsCharset(dirname)))) == NULL) + return -1; + + DEBUG("explore: %s\n", dirname); + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') + continue; /* hide hidden stuff */ + if (strchr(ent->d_name, '\n')) + continue; + + utf8 = fsCharsetToUtf8(ent->d_name); + + if (!utf8) + continue; + + utf8 = xstrdup(utf8); + + DEBUG("explore: found: %s (%s)\n", ent->d_name, utf8); + + if (directory->path) { + s = xmalloc(strlen(getDirectoryPath(directory)) + + strlen(utf8) + 2); + sprintf(s, "%s/%s", getDirectoryPath(directory), utf8); + } else + s = xstrdup(utf8); + if (addToDirectory(directory, utf8, s) > 0) + ret = 1; + free(utf8); + free(s); + } + + closedir(dir); + + return ret; +} + +static int statDirectory(Directory * dir) +{ + struct stat st; + + if (myStat(getDirectoryPath(dir) ? getDirectoryPath(dir) : "", &st) < 0) + { + return -1; + } + + dir->stat = newDirectoryStat(&st); + + return 0; +} + +static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device) +{ + while (parent) { + if (!parent->stat) { + if (statDirectory(parent) < 0) + return -1; + } + if (parent->stat->inode == inode && + parent->stat->device == device) { + DEBUG("recursive directory found\n"); + return 1; + } + parent = parent->parent; + } + + return 0; +} + +static int addSubDirectoryToDirectory(Directory * directory, char *shortname, + char *name, struct stat *st) +{ + Directory *subDirectory; + + if (inodeFoundInParent(directory, st->st_ino, st->st_dev)) + return 0; + + subDirectory = newDirectory(name, directory); + subDirectory->stat = newDirectoryStat(st); + + if (exploreDirectory(subDirectory) < 1) { + freeDirectory(subDirectory); + return 0; + } + + insertInList(directory->subDirectories, shortname, subDirectory); + + return 1; +} + +static int addToDirectory(Directory * directory, char *shortname, char *name) +{ + struct stat st; + + if (myStat(name, &st)) { + DEBUG("failed to stat %s: %s\n", name, strerror(errno)); + return -1; + } + + if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) { + Song *song; + song = addSongToList(directory->songs, shortname, name, + SONG_TYPE_FILE, directory); + if (!song) + return -1; + LOG("added %s\n", name); + return 1; + } else if (S_ISDIR(st.st_mode)) { + return addSubDirectoryToDirectory(directory, shortname, name, + &st); + } + + DEBUG("addToDirectory: %s is not a directory or music\n", name); + + return -1; +} + +void closeMp3Directory(void) +{ + freeDirectory(mp3rootDirectory); +} + +static Directory *findSubDirectory(Directory * directory, char *name) +{ + void *subDirectory; + char *dup = xstrdup(name); + char *key; + + key = strtok(dup, "/"); + if (!key) { + free(dup); + return NULL; + } + + if (findInList(directory->subDirectories, key, &subDirectory)) { + free(dup); + return (Directory *) subDirectory; + } + + free(dup); + return NULL; +} + +int isRootDirectory(char *name) +{ + if (name == NULL || name[0] == '\0' || strcmp(name, "/") == 0) { + return 1; + } + return 0; +} + +static Directory *getSubDirectory(Directory * directory, char *name, + char **shortname) +{ + Directory *subDirectory; + int len; + + if (isRootDirectory(name)) { + return directory; + } + + if ((subDirectory = findSubDirectory(directory, name)) == NULL) + return NULL; + + *shortname = name; + + len = 0; + while (name[len] != '/' && name[len] != '\0') + len++; + while (name[len] == '/') + len++; + + return getSubDirectory(subDirectory, &(name[len]), shortname); +} + +static Directory *getDirectoryDetails(char *name, char **shortname) +{ + *shortname = NULL; + + return getSubDirectory(mp3rootDirectory, name, shortname); +} + +static Directory *getDirectory(char *name) +{ + char *shortname; + + return getSubDirectory(mp3rootDirectory, name, &shortname); +} + +static int printDirectoryList(int fd, DirectoryList * directoryList) +{ + ListNode *node = directoryList->firstNode; + Directory *directory; + + while (node != NULL) { + directory = (Directory *) node->data; + fdprintf(fd, "%s%s\n", DIRECTORY_DIR, + getDirectoryPath(directory)); + node = node->nextNode; + } + + return 0; +} + +int printDirectoryInfo(int fd, char *name) +{ + Directory *directory; + + if ((directory = getDirectory(name)) == NULL) { + commandError(fd, ACK_ERROR_NO_EXIST, "directory not found"); + return -1; + } + + printDirectoryList(fd, directory->subDirectories); + printSongInfoFromList(fd, directory->songs); + + return 0; +} + +static void writeDirectoryInfo(FILE * fp, Directory * directory) +{ + ListNode *node = (directory->subDirectories)->firstNode; + Directory *subDirectory; + + if (directory->path) { + fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, + getDirectoryPath(directory)); + } + + while (node != NULL) { + subDirectory = (Directory *) node->data; + fprintf(fp, "%s%s\n", DIRECTORY_DIR, node->key); + writeDirectoryInfo(fp, subDirectory); + node = node->nextNode; + } + + writeSongInfoFromList(fp, directory->songs); + + if (directory->path) { + fprintf(fp, "%s%s\n", DIRECTORY_END, + getDirectoryPath(directory)); + } +} + +static void readDirectoryInfo(FILE * fp, Directory * directory) +{ + char buffer[MAXPATHLEN * 2]; + int bufferSize = MAXPATHLEN * 2; + char *key; + Directory *subDirectory; + int strcmpRet; + char *name; + ListNode *nextDirNode = directory->subDirectories->firstNode; + ListNode *nodeTemp; + + while (myFgets(buffer, bufferSize, fp) + && 0 != strncmp(DIRECTORY_END, buffer, strlen(DIRECTORY_END))) { + if (0 == strncmp(DIRECTORY_DIR, buffer, strlen(DIRECTORY_DIR))) { + key = xstrdup(&(buffer[strlen(DIRECTORY_DIR)])); + if (!myFgets(buffer, bufferSize, fp)) + FATAL("Error reading db, fgets\n"); + /* for compatibility with db's prior to 0.11 */ + if (0 == strncmp(DIRECTORY_MTIME, buffer, + strlen(DIRECTORY_MTIME))) { + if (!myFgets(buffer, bufferSize, fp)) + FATAL("Error reading db, fgets\n"); + } + if (strncmp + (DIRECTORY_BEGIN, buffer, + strlen(DIRECTORY_BEGIN))) { + FATAL("Error reading db at line: %s\n", buffer); + } + name = xstrdup(&(buffer[strlen(DIRECTORY_BEGIN)])); + + while (nextDirNode && (strcmpRet = + strcmp(key, + nextDirNode->key)) > 0) { + nodeTemp = nextDirNode->nextNode; + deleteNodeFromList(directory->subDirectories, + nextDirNode); + nextDirNode = nodeTemp; + } + + if (NULL == nextDirNode) { + subDirectory = newDirectory(name, directory); + insertInList(directory->subDirectories, + key, (void *)subDirectory); + } else if (strcmpRet == 0) { + subDirectory = (Directory *) nextDirNode->data; + nextDirNode = nextDirNode->nextNode; + } else { + subDirectory = newDirectory(name, directory); + insertInListBeforeNode(directory-> + subDirectories, + nextDirNode, -1, key, + (void *)subDirectory); + } + + free(name); + free(key); + readDirectoryInfo(fp, subDirectory); + } else if (0 == strncmp(SONG_BEGIN, buffer, strlen(SONG_BEGIN))) { + readSongInfoIntoList(fp, directory->songs, directory); + } else { + FATAL("Unknown line in db: %s\n", buffer); + } + } + + while (nextDirNode) { + nodeTemp = nextDirNode->nextNode; + deleteNodeFromList(directory->subDirectories, nextDirNode); + nextDirNode = nodeTemp; + } +} + +static void sortDirectory(Directory * directory) +{ + ListNode *node = directory->subDirectories->firstNode; + Directory *subDir; + + sortList(directory->subDirectories); + sortList(directory->songs); + + while (node != NULL) { + subDir = (Directory *) node->data; + sortDirectory(subDir); + node = node->nextNode; + } +} + +int checkDirectoryDB(void) +{ + struct stat st; + char *dbFile; + char *dirPath; + char *dbPath; + + dbFile = getDbFile(); + + /* Check if the file exists */ + if (access(dbFile, F_OK)) { + /* If the file doesn't exist, we can't check if we can write + * it, so we are going to try to get the directory path, and + * see if we can write a file in that */ + dbPath = xstrdup(dbFile); + dirPath = dirname(dbPath); + + /* Check that the parent part of the path is a directory */ + if (stat(dirPath, &st) < 0) { + ERROR("Couldn't stat parent directory of db file " + "\"%s\": %s\n", dbFile, strerror(errno)); + free(dbPath); + return -1; + } + + if (!S_ISDIR(st.st_mode)) { + ERROR("Couldn't create db file \"%s\" because the " + "parent path is not a directory\n", dbFile); + free(dbPath); + return -1; + } + + /* Check if we can write to the directory */ + if (access(dirPath, R_OK | W_OK)) { + ERROR("Can't create db file in \"%s\": %s\n", dirPath, + strerror(errno)); + free(dbPath); + return -1; + + } + + free(dbPath); + return 0; + } + + /* Path exists, now check if it's a regular file */ + if (stat(dbFile, &st) < 0) { + ERROR("Couldn't stat db file \"%s\": %s\n", dbFile, + strerror(errno)); + return -1; + } + + if (!S_ISREG(st.st_mode)) { + ERROR("db file \"%s\" is not a regular file\n", dbFile); + return -1; + } + + /* And check that we can write to it */ + if (access(dbFile, R_OK | W_OK)) { + ERROR("Can't open db file \"%s\" for reading/writing: %s\n", + dbFile, strerror(errno)); + return -1; + } + + return 0; +} + +int writeDirectoryDB(void) +{ + FILE *fp; + char *dbFile = getDbFile(); + struct stat st; + + DEBUG("removing empty directories from DB\n"); + deleteEmptyDirectoriesInDirectory(mp3rootDirectory); + + DEBUG("sorting DB\n"); + + sortDirectory(mp3rootDirectory); + + DEBUG("writing DB\n"); + + while (!(fp = fopen(dbFile, "w")) && errno == EINTR); + if (!fp) { + ERROR("unable to write to db file \"%s\": %s\n", + dbFile, strerror(errno)); + return -1; + } + + /* block signals when writing the db so we don't get a corrupted db */ + fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); + fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); + fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, getFsCharset()); + fprintf(fp, "%s\n", DIRECTORY_INFO_END); + + writeDirectoryInfo(fp, mp3rootDirectory); + + while (fclose(fp) && errno == EINTR); + + if (stat(dbFile, &st) == 0) + directory_dbModTime = st.st_mtime; + + return 0; +} + +int readDirectoryDB(void) +{ + FILE *fp = NULL; + char *dbFile = getDbFile(); + struct stat st; + + if (!mp3rootDirectory) + mp3rootDirectory = newDirectory(NULL, NULL); + while (!(fp = fopen(dbFile, "r")) && errno == EINTR) ; + if (fp == NULL) { + ERROR("unable to open db file \"%s\": %s\n", + dbFile, strerror(errno)); + return -1; + } + + /* get initial info */ + { + char buffer[100]; + int bufferSize = 100; + int foundFsCharset = 0; + int foundVersion = 0; + + if (!myFgets(buffer, bufferSize, fp)) + FATAL("Error reading db, fgets\n"); + if (0 == strcmp(DIRECTORY_INFO_BEGIN, buffer)) { + while (myFgets(buffer, bufferSize, fp) && + 0 != strcmp(DIRECTORY_INFO_END, buffer)) { + if (0 == strncmp(DIRECTORY_MPD_VERSION, buffer, + strlen(DIRECTORY_MPD_VERSION))) + { + if (foundVersion) + FATAL("already found version in db\n"); + foundVersion = 1; + } else if (0 == + strncmp(DIRECTORY_FS_CHARSET, buffer, + strlen + (DIRECTORY_FS_CHARSET))) { + char *fsCharset; + char *tempCharset; + + if (foundFsCharset) + FATAL("already found fs charset in db\n"); + + foundFsCharset = 1; + + fsCharset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]); + if ((tempCharset = getConfigParamValue(CONF_FS_CHARSET)) + && strcmp(fsCharset, tempCharset)) { + WARNING("Using \"%s\" for the " + "filesystem charset " + "instead of \"%s\"\n", + fsCharset, tempCharset); + WARNING("maybe you need to " + "recreate the db?\n"); + setFsCharset(fsCharset); + } + } else { + FATAL("directory: unknown line in db info: %s\n", + buffer); + } + } + } else { + ERROR("db info not found in db file\n"); + ERROR("you should recreate the db using --create-db\n"); + fseek(fp, 0, SEEK_SET); + return -1; + } + } + + DEBUG("reading DB\n"); + + readDirectoryInfo(fp, mp3rootDirectory); + while (fclose(fp) && errno == EINTR) ; + + stats.numberOfSongs = countSongsIn(STDERR_FILENO, NULL); + stats.dbPlayTime = sumSongTimesIn(STDERR_FILENO, NULL); + + if (stat(dbFile, &st) == 0) + directory_dbModTime = st.st_mtime; + + return 0; +} + +void updateMp3Directory(void) +{ + switch (updateDirectory(mp3rootDirectory)) { + case 0: + /* nothing updated */ + return; + case 1: + if (writeDirectoryDB() < 0) + exit(EXIT_FAILURE); + break; + default: + /* something was updated and db should be written */ + FATAL("problems updating music db\n"); + } + + return; +} + +static int traverseAllInSubDirectory(int fd, Directory * directory, + int (*forEachSong) (int, Song *, + void *), + int (*forEachDir) (int, Directory *, + void *), void *data) +{ + ListNode *node = directory->songs->firstNode; + Song *song; + Directory *dir; + int errFlag = 0; + + if (forEachDir) { + errFlag = forEachDir(fd, directory, data); + if (errFlag) + return errFlag; + } + + if (forEachSong) { + while (node != NULL && !errFlag) { + song = (Song *) node->data; + errFlag = forEachSong(fd, song, data); + node = node->nextNode; + } + if (errFlag) + return errFlag; + } + + node = directory->subDirectories->firstNode; + + while (node != NULL && !errFlag) { + dir = (Directory *) node->data; + errFlag = traverseAllInSubDirectory(fd, dir, forEachSong, + forEachDir, data); + node = node->nextNode; + } + + return errFlag; +} + +int traverseAllIn(int fd, char *name, + int (*forEachSong) (int, Song *, void *), + int (*forEachDir) (int, Directory *, void *), void *data) +{ + Directory *directory; + + if ((directory = getDirectory(name)) == NULL) { + Song *song; + if ((song = getSongFromDB(name)) && forEachSong) { + return forEachSong(fd, song, data); + } + commandError(fd, ACK_ERROR_NO_EXIST, + "directory or file not found"); + return -1; + } + + return traverseAllInSubDirectory(fd, directory, forEachSong, forEachDir, + data); +} + +static void freeAllDirectoryStats(Directory * directory) +{ + ListNode *node = directory->subDirectories->firstNode; + + while (node != NULL) { + freeAllDirectoryStats((Directory *) node->data); + node = node->nextNode; + } + + freeDirectoryStatFromDirectory(directory); +} + +void initMp3Directory(void) +{ + mp3rootDirectory = newDirectory(NULL, NULL); + exploreDirectory(mp3rootDirectory); + freeAllDirectoryStats(mp3rootDirectory); + stats.numberOfSongs = countSongsIn(STDERR_FILENO, NULL); + stats.dbPlayTime = sumSongTimesIn(STDERR_FILENO, NULL); +} + +static Song *getSongDetails(char *file, char **shortnameRet, + Directory ** directoryRet) +{ + void *song = NULL; + Directory *directory; + char *dir = NULL; + char *dup = xstrdup(file); + char *shortname = dup; + char *c = strtok(dup, "/"); + + DEBUG("get song: %s\n", file); + + while (c) { + shortname = c; + c = strtok(NULL, "/"); + } + + if (shortname != dup) { + for (c = dup; c < shortname - 1; c++) { + if (*c == '\0') + *c = '/'; + } + dir = dup; + } + + if (!(directory = getDirectory(dir))) { + free(dup); + return NULL; + } + + if (!findInList(directory->songs, shortname, &song)) { + free(dup); + return NULL; + } + + free(dup); + if (shortnameRet) + *shortnameRet = shortname; + if (directoryRet) + *directoryRet = directory; + return (Song *) song; +} + +Song *getSongFromDB(char *file) +{ + return getSongDetails(file, NULL, NULL); +} + +time_t getDbModTime(void) +{ + return directory_dbModTime; +} diff --git a/trunk/src/directory.h b/trunk/src/directory.h new file mode 100644 index 000000000..b1482988f --- /dev/null +++ b/trunk/src/directory.h @@ -0,0 +1,76 @@ +/* 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 DIRECTORY_H +#define DIRECTORY_H + +#include "../config.h" + +#include "song.h" +#include "list.h" + +typedef List DirectoryList; + +typedef struct _DirectoryStat { + ino_t inode; + dev_t device; +} DirectoryStat; + +typedef struct _Directory { + char *path; + DirectoryList *subDirectories; + SongList *songs; + struct _Directory *parent; + DirectoryStat *stat; +} Directory; + +void readDirectoryDBIfUpdateIsFinished(void); + +int isUpdatingDB(void); + +void directory_sigChldHandler(int pid, int status); + +int updateInit(int fd, List * pathList); + +void initMp3Directory(void); + +void closeMp3Directory(void); + +int isRootDirectory(char *name); + +int printDirectoryInfo(int fd, char *dirname); + +int checkDirectoryDB(void); + +int writeDirectoryDB(void); + +int readDirectoryDB(void); + +void updateMp3Directory(void); + +Song *getSongFromDB(char *file); + +time_t getDbModTime(void); + +int traverseAllIn(int fd, char *name, + int (*forEachSong) (int, Song *, void *), + int (*forEachDir) (int, Directory *, void *), void *data); + +#define getDirectoryPath(dir) ((dir && dir->path) ? dir->path : "") + +#endif diff --git a/trunk/src/gcc.h b/trunk/src/gcc.h new file mode 100644 index 000000000..4ae18c46b --- /dev/null +++ b/trunk/src/gcc.h @@ -0,0 +1,67 @@ +/* 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 MPD_GCC_H +#define MPD_GCC_H + +/* this allows us to take advantage of special gcc features while still + * allowing other compilers to compile: + * + * example taken from: http://rlove.org/log/2005102601 + */ + +#if defined(__GNUC__) && (__GNUC__ >= 3) +# define mpd_const __attribute__ ((const)) +# define mpd_deprecated __attribute__ ((deprecated)) +# define mpd_malloc __attribute__ ((malloc)) +# define mpd_must_check __attribute__ ((warn_unused_result)) +# define mpd_noreturn __attribute__ ((noreturn)) +# define mpd_packed __attribute__ ((packed)) +/* these are very useful for type checking */ +# define mpd_printf __attribute__ ((format(printf,1,2))) +# define mpd_fprintf __attribute__ ((format(printf,2,3))) +# define mpd_fprintf_ __attribute__ ((format(printf,3,4))) +# define mpd_pure __attribute__ ((pure)) +# define mpd_scanf __attribute__ ((format(scanf,1,2))) +# define mpd_unused __attribute__ ((unused)) +# define mpd_used __attribute__ ((used)) +/* # define inline inline __attribute__ ((always_inline)) */ +# define mpd_noinline __attribute__ ((noinline)) +# define mpd_likely(x) __builtin_expect (!!(x), 1) +# define mpd_unlikely(x) __builtin_expect (!!(x), 0) +#else +# define mpd_const +# define mpd_deprecated +# define mpd_malloc +# define mpd_must_check +# define mpd_noreturn +# define mpd_packed +# define mpd_printf +# define mpd_fprintf +# define mpd_fprintf_ +# define mpd_pure +# define mpd_scanf +# define mpd_unused +# define mpd_used +/* # define inline */ +# define mpd_noinline +# define mpd_likely(x) (x) +# define mpd_unlikely(x) (x) +#endif + +#endif /* MPD_GCC_H */ diff --git a/trunk/src/inputPlugin.c b/trunk/src/inputPlugin.c new file mode 100644 index 000000000..60e60947b --- /dev/null +++ b/trunk/src/inputPlugin.c @@ -0,0 +1,158 @@ +/* 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 "inputPlugin.h" + +#include "list.h" +#include "myfprintf.h" + +#include <stdlib.h> +#include <string.h> + +static List *inputPlugin_list; + +void loadInputPlugin(InputPlugin * inputPlugin) +{ + if (!inputPlugin) + return; + if (!inputPlugin->name) + return; + + if (inputPlugin->initFunc && inputPlugin->initFunc() < 0) + return; + + insertInList(inputPlugin_list, inputPlugin->name, (void *)inputPlugin); +} + +void unloadInputPlugin(InputPlugin * inputPlugin) +{ + if (inputPlugin->finishFunc) + inputPlugin->finishFunc(); + deleteFromList(inputPlugin_list, inputPlugin->name); +} + +static int stringFoundInStringArray(char **array, char *suffix) +{ + while (array && *array) { + if (strcasecmp(*array, suffix) == 0) + return 1; + array++; + } + + return 0; +} + +InputPlugin *getInputPluginFromSuffix(char *suffix, unsigned int next) +{ + static ListNode *pos; + ListNode *node; + InputPlugin *plugin; + + if (suffix == NULL) + return NULL; + + if (next) { + if (pos) + node = pos; + else + return NULL; + } else + node = inputPlugin_list->firstNode; + + while (node != NULL) { + plugin = node->data; + if (stringFoundInStringArray(plugin->suffixes, suffix)) { + pos = node->nextNode; + return plugin; + } + node = node->nextNode; + } + + return NULL; +} + +InputPlugin *getInputPluginFromMimeType(char *mimeType, unsigned int next) +{ + static ListNode *pos; + ListNode *node; + InputPlugin *plugin; + + if (mimeType == NULL) + return NULL; + + node = (next && pos) ? pos : inputPlugin_list->firstNode; + + while (node != NULL) { + plugin = node->data; + if (stringFoundInStringArray(plugin->mimeTypes, mimeType)) { + pos = node->nextNode; + return plugin; + } + node = node->nextNode; + } + + return NULL; +} + +InputPlugin *getInputPluginFromName(char *name) +{ + void *plugin = NULL; + + findInList(inputPlugin_list, name, &plugin); + + return (InputPlugin *) plugin; +} + +void printAllInputPluginSuffixes(FILE * fp) +{ + ListNode *node = inputPlugin_list->firstNode; + InputPlugin *plugin; + char **suffixes; + + while (node) { + plugin = (InputPlugin *) node->data; + suffixes = plugin->suffixes; + while (suffixes && *suffixes) { + fprintf(fp, "%s ", *suffixes); + suffixes++; + } + node = node->nextNode; + } + fprintf(fp, "\n"); + fflush(fp); +} + +void initInputPlugins(void) +{ + inputPlugin_list = makeList(NULL, 1); + + /* load plugins here */ + loadInputPlugin(&mp3Plugin); + loadInputPlugin(&oggvorbisPlugin); + loadInputPlugin(&oggflacPlugin); + loadInputPlugin(&flacPlugin); + loadInputPlugin(&audiofilePlugin); + loadInputPlugin(&mp4Plugin); + loadInputPlugin(&mpcPlugin); + loadInputPlugin(&modPlugin); +} + +void finishInputPlugins(void) +{ + freeList(inputPlugin_list); +} diff --git a/trunk/src/inputPlugin.h b/trunk/src/inputPlugin.h new file mode 100644 index 000000000..398ddc1cb --- /dev/null +++ b/trunk/src/inputPlugin.h @@ -0,0 +1,109 @@ +/* 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 INPUT_PLUGIN_H +#define INPUT_PLUGIN_H + +#include "../config.h" +#include "inputStream.h" +#include "decode.h" +#include "outputBuffer.h" +#include "tag.h" + +/* valid values for streamTypes in the InputPlugin struct: */ +#define INPUT_PLUGIN_STREAM_FILE 0x01 +#define INPUT_PLUGIN_STREAM_URL 0x02 + +/* optional, set this to NULL if the InputPlugin doesn't have/need one + * this must return < 0 if there is an error and >= 0 otherwise */ +typedef int (*InputPlugin_initFunc) (); + +/* optional, set this to NULL if the InputPlugin doesn't have/need one */ +typedef void (*InputPlugin_finishFunc) (); + +/* boolean return value, returns 1 if the InputStream is decodable by + * the InputPlugin, 0 if not */ +typedef unsigned int (*InputPlugin_tryDecodeFunc) (InputStream *); + +/* this will be used to decode InputStreams, and is recommended for files + * and networked (HTTP) connections. + * + * returns -1 on error, 0 on success */ +typedef int (*InputPlugin_streamDecodeFunc) (OutputBuffer *, DecoderControl *, + InputStream *); + +/* use this if and only if your InputPlugin can only be passed a filename or + * handle as input, and will not allow callbacks to be set (like Ogg-Vorbis + * and FLAC libraries allow) + * + * returns -1 on error, 0 on success */ +typedef int (*InputPlugin_fileDecodeFunc) (OutputBuffer *, DecoderControl *, + char *path); + +/* file should be the full path! Returns NULL if a tag cannot be found + * or read */ +typedef MpdTag *(*InputPlugin_tagDupFunc) (char *file); + +typedef struct _InputPlugin { + char *name; + InputPlugin_initFunc initFunc; + InputPlugin_finishFunc finishFunc; + InputPlugin_tryDecodeFunc tryDecodeFunc; + InputPlugin_streamDecodeFunc streamDecodeFunc; + InputPlugin_fileDecodeFunc fileDecodeFunc; + InputPlugin_tagDupFunc tagDupFunc; + + /* one or more of the INPUT_PLUGIN_STREAM_* values OR'd together */ + unsigned char streamTypes; + + /* last element in these arrays must always be a NULL: */ + char **suffixes; + char **mimeTypes; +} InputPlugin; + +/* individual functions to load/unload plugins */ +void loadInputPlugin(InputPlugin * inputPlugin); +void unloadInputPlugin(InputPlugin * inputPlugin); + +/* interface for using plugins */ + +InputPlugin *getInputPluginFromSuffix(char *suffix, unsigned int next); + +InputPlugin *getInputPluginFromMimeType(char *mimeType, unsigned int next); + +InputPlugin *getInputPluginFromName(char *name); + +void printAllInputPluginSuffixes(FILE * fp); + +/* this is where we "load" all the "plugins" ;-) */ +void initInputPlugins(void); + +/* this is where we "unload" all the "plugins" */ +void finishInputPlugins(void); + +extern InputPlugin mp3Plugin; +extern InputPlugin oggvorbisPlugin; +extern InputPlugin flacPlugin; +extern InputPlugin oggflacPlugin; +extern InputPlugin audiofilePlugin; +extern InputPlugin mp4Plugin; +extern InputPlugin mpcPlugin; +extern InputPlugin aacPlugin; +extern InputPlugin modPlugin; + +#endif diff --git a/trunk/src/inputPlugins/_flac_common.c b/trunk/src/inputPlugins/_flac_common.c new file mode 100644 index 000000000..11126cd1b --- /dev/null +++ b/trunk/src/inputPlugins/_flac_common.c @@ -0,0 +1,211 @@ +/* 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 + * + * Common data structures and functions used by FLAC and OggFLAC + * (c) 2005 by Eric Wong <normalperson@yhbt.net> + * + * 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 "../inputPlugin.h" + +#if defined(HAVE_FLAC) || defined(HAVE_OGGFLAC) + +#include "_flac_common.h" + +#include "../log.h" +#include "../tag.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../decode.h" +#include "../replayGain.h" + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <FLAC/format.h> +#include <FLAC/metadata.h> + +void init_FlacData(FlacData * data, OutputBuffer * cb, + DecoderControl * dc, InputStream * inStream) +{ + data->chunk_length = 0; + data->time = 0; + data->position = 0; + data->bitRate = 0; + data->cb = cb; + data->dc = dc; + data->inStream = inStream; + data->replayGainInfo = NULL; + data->tag = NULL; +} + +static int flacFindVorbisCommentFloat(const FLAC__StreamMetadata * block, + const char *cmnt, float *fl) +{ + int offset = + FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, cmnt); + + if (offset >= 0) { + size_t pos = strlen(cmnt) + 1; /* 1 is for '=' */ + int len = block->data.vorbis_comment.comments[offset].length + - pos; + if (len > 0) { + unsigned char tmp; + unsigned char *dup = &(block->data.vorbis_comment. + comments[offset].entry[pos]); + tmp = dup[len]; + dup[len] = '\0'; + *fl = atof((char *)dup); + dup[len] = tmp; + + return 1; + } + } + + return 0; +} + +/* replaygain stuff by AliasMrJones */ +static void flacParseReplayGain(const FLAC__StreamMetadata * block, + FlacData * data) +{ + int found = 0; + + if (data->replayGainInfo) + freeReplayGainInfo(data->replayGainInfo); + + data->replayGainInfo = newReplayGainInfo(); + + found |= flacFindVorbisCommentFloat(block, "replaygain_album_gain", + &data->replayGainInfo->albumGain); + found |= flacFindVorbisCommentFloat(block, "replaygain_album_peak", + &data->replayGainInfo->albumPeak); + found |= flacFindVorbisCommentFloat(block, "replaygain_track_gain", + &data->replayGainInfo->trackGain); + found |= flacFindVorbisCommentFloat(block, "replaygain_track_peak", + &data->replayGainInfo->trackPeak); + + if (!found) { + freeReplayGainInfo(data->replayGainInfo); + data->replayGainInfo = NULL; + } +} + +/* tracknumber is used in VCs, MPD uses "track" ..., all the other + * tag names match */ +static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; +static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; + +static unsigned int commentMatchesAddToTag(const + FLAC__StreamMetadata_VorbisComment_Entry + * entry, unsigned int itemType, + MpdTag ** tag) +{ + const char *str; + size_t slen; + int vlen; + + switch (itemType) { + case TAG_ITEM_TRACK: + str = VORBIS_COMMENT_TRACK_KEY; + break; + case TAG_ITEM_DISC: + str = VORBIS_COMMENT_DISC_KEY; + break; + default: + str = mpdTagItemKeys[itemType]; + } + slen = strlen(str); + vlen = entry->length - slen - 1; + + if ((vlen > 0) && (0 == strncasecmp(str, (char *)entry->entry, slen)) + && (*(entry->entry + slen) == '=')) { + if (!*tag) + *tag = newMpdTag(); + + addItemToMpdTagWithLen(*tag, itemType, + (char *)(entry->entry + slen + 1), vlen); + + return 1; + } + + return 0; +} + +MpdTag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, + MpdTag * tag) +{ + unsigned int i, j; + FLAC__StreamMetadata_VorbisComment_Entry *comments; + + comments = block->data.vorbis_comment.comments; + + for (i = block->data.vorbis_comment.num_comments; i != 0; --i) { + for (j = TAG_NUM_OF_ITEM_TYPES; j--;) { + if (commentMatchesAddToTag(comments, j, &tag)) + break; + } + comments++; + } + + return tag; +} + +void flac_metadata_common_cb(const FLAC__StreamMetadata * block, + FlacData * data) +{ + DecoderControl *dc = data->dc; + const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info); + + switch (block->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + dc->audioFormat.bits = si->bits_per_sample; + dc->audioFormat.sampleRate = si->sample_rate; + dc->audioFormat.channels = si->channels; + dc->totalTime = ((float)si->total_samples) / (si->sample_rate); + getOutputAudioFormat(&(dc->audioFormat), + &(data->cb->audioFormat)); + break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + flacParseReplayGain(block, data); + default: + break; + } +} + +void flac_error_common_cb(const char *plugin, + const FLAC__StreamDecoderErrorStatus status, + FlacData * data) +{ + if (data->dc->stop) + return; + + switch (status) { + case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: + ERROR("%s lost sync\n", plugin); + break; + case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: + ERROR("bad %s header\n", plugin); + break; + case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: + ERROR("%s crc mismatch\n", plugin); + break; + default: + ERROR("unknown %s error\n", plugin); + } +} + +#endif /* HAVE_FLAC || HAVE_OGGFLAC */ diff --git a/trunk/src/inputPlugins/_flac_common.h b/trunk/src/inputPlugins/_flac_common.h new file mode 100644 index 000000000..e04e70693 --- /dev/null +++ b/trunk/src/inputPlugins/_flac_common.h @@ -0,0 +1,187 @@ +/* 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 + * + * Common data structures and functions used by FLAC and OggFLAC + * (c) 2005 by Eric Wong <normalperson@yhbt.net> + * + * 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 _FLAC_COMMON_H +#define _FLAC_COMMON_H + +#include "../inputPlugin.h" + +#if defined(HAVE_FLAC) || defined(HAVE_OGGFLAC) + +#include "../tag.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../decode.h" +#include <FLAC/export.h> +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +# include <FLAC/seekable_stream_decoder.h> +# define flac_decoder FLAC__SeekableStreamDecoder +# define flac_new() FLAC__seekable_stream_decoder_new() + +# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) (0) + +# define flac_get_decode_position(x,y) \ + FLAC__seekable_stream_decoder_get_decode_position(x,y) +# define flac_get_state(x) FLAC__seekable_stream_decoder_get_state(x) +# define flac_process_single(x) FLAC__seekable_stream_decoder_process_single(x) +# define flac_process_metadata(x) \ + FLAC__seekable_stream_decoder_process_until_end_of_metadata(x) +# define flac_seek_absolute(x,y) \ + FLAC__seekable_stream_decoder_seek_absolute(x,y) +# define flac_finish(x) FLAC__seekable_stream_decoder_finish(x) +# define flac_delete(x) FLAC__seekable_stream_decoder_delete(x) + +# define flac_decoder_eof FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM + +typedef unsigned flac_read_status_size_t; +# define flac_read_status FLAC__SeekableStreamDecoderReadStatus +# define flac_read_status_continue \ + FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK +# define flac_read_status_eof FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK +# define flac_read_status_abort \ + FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR + +# define flac_seek_status FLAC__SeekableStreamDecoderSeekStatus +# define flac_seek_status_ok FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK +# define flac_seek_status_error FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR + +# define flac_tell_status FLAC__SeekableStreamDecoderTellStatus +# define flac_tell_status_ok FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK +# define flac_tell_status_error \ + FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR +# define flac_tell_status_unsupported \ + FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR + +# define flac_length_status FLAC__SeekableStreamDecoderLengthStatus +# define flac_length_status_ok FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK +# define flac_length_status_error \ + FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR +# define flac_length_status_unsupported \ + FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR + +# ifdef HAVE_OGGFLAC +# include <OggFLAC/seekable_stream_decoder.h> +# endif +#else /* FLAC_API_VERSION_CURRENT >= 7 */ + + /* OggFLAC support is handled by our flac_plugin already, and + * thus we *can* always have it if libFLAC was compiled with it */ +# ifndef HAVE_OGGFLAC +# define HAVE_OGGFLAC 1 +# endif +# include "_ogg_common.h" +# undef HAVE_OGGFLAC /* we don't need this defined anymore */ + +# include <FLAC/stream_decoder.h> +# define flac_decoder FLAC__StreamDecoder +# define flac_new() FLAC__stream_decoder_new() + +# define flac_init(a,b,c,d,e,f,g,h,i,j) \ + (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \ + == FLAC__STREAM_DECODER_INIT_STATUS_OK) +# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) \ + (FLAC__stream_decoder_init_ogg_stream(a,b,c,d,e,f,g,h,i,j) \ + == FLAC__STREAM_DECODER_INIT_STATUS_OK) + +# define flac_get_decode_position(x,y) \ + FLAC__stream_decoder_get_decode_position(x,y) +# define flac_get_state(x) FLAC__stream_decoder_get_state(x) +# define flac_process_single(x) FLAC__stream_decoder_process_single(x) +# define flac_process_metadata(x) \ + FLAC__stream_decoder_process_until_end_of_metadata(x) +# define flac_seek_absolute(x,y) FLAC__stream_decoder_seek_absolute(x,y) +# define flac_finish(x) FLAC__stream_decoder_finish(x) +# define flac_delete(x) FLAC__stream_decoder_delete(x) + +# define flac_decoder_eof FLAC__STREAM_DECODER_END_OF_STREAM + +typedef size_t flac_read_status_size_t; +# define flac_read_status FLAC__StreamDecoderReadStatus +# define flac_read_status_continue \ + FLAC__STREAM_DECODER_READ_STATUS_CONTINUE +# define flac_read_status_eof FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM +# define flac_read_status_abort FLAC__STREAM_DECODER_READ_STATUS_ABORT + +# define flac_seek_status FLAC__StreamDecoderSeekStatus +# define flac_seek_status_ok FLAC__STREAM_DECODER_SEEK_STATUS_OK +# define flac_seek_status_error FLAC__STREAM_DECODER_SEEK_STATUS_ERROR +# define flac_seek_status_unsupported \ + FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED + +# define flac_tell_status FLAC__StreamDecoderTellStatus +# define flac_tell_status_ok FLAC__STREAM_DECODER_TELL_STATUS_OK +# define flac_tell_status_error FLAC__STREAM_DECODER_TELL_STATUS_ERROR +# define flac_tell_status_unsupported \ + FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED + +# define flac_length_status FLAC__StreamDecoderLengthStatus +# define flac_length_status_ok FLAC__STREAM_DECODER_LENGTH_STATUS_OK +# define flac_length_status_error FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR +# define flac_length_status_unsupported \ + FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +#include <FLAC/metadata.h> + +#define FLAC_CHUNK_SIZE 4080 + +typedef struct { + unsigned char chunk[FLAC_CHUNK_SIZE]; + int chunk_length; + float time; + int bitRate; + FLAC__uint64 position; + OutputBuffer *cb; + DecoderControl *dc; + InputStream *inStream; + ReplayGainInfo *replayGainInfo; + MpdTag *tag; +} FlacData; + +/* initializes a given FlacData struct */ +void init_FlacData(FlacData * data, OutputBuffer * cb, + DecoderControl * dc, InputStream * inStream); +void flac_metadata_common_cb(const FLAC__StreamMetadata * block, + FlacData * data); +void flac_error_common_cb(const char *plugin, + FLAC__StreamDecoderErrorStatus status, + FlacData * data); + +MpdTag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, + MpdTag * tag); + +/* keep this inlined, this is just macro but prettier :) */ +static inline int flacSendChunk(FlacData * data) +{ + if (sendDataToOutputBuffer(data->cb, NULL, data->dc, 1, data->chunk, + data->chunk_length, data->time, + data->bitRate, + data->replayGainInfo) == + OUTPUT_BUFFER_DC_STOP) + return -1; + + return 0; +} + +#endif /* HAVE_FLAC || HAVE_OGGFLAC */ + +#endif /* _FLAC_COMMON_H */ diff --git a/trunk/src/inputPlugins/_ogg_common.c b/trunk/src/inputPlugins/_ogg_common.c new file mode 100644 index 000000000..c83e46103 --- /dev/null +++ b/trunk/src/inputPlugins/_ogg_common.c @@ -0,0 +1,73 @@ +/* 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 + * + * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + * (c) 2005 by Eric Wong <normalperson@yhbt.net> + * + * 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 "../inputPlugin.h" + +#if defined(HAVE_OGGFLAC) || defined(HAVE_OGGVORBIS) + +#include "../utils.h" +#include "_ogg_common.h" + +#include <string.h> + +ogg_stream_type ogg_stream_type_detect(InputStream * inStream) +{ + /* oggflac detection based on code in ogg123 and this post + * http://lists.xiph.org/pipermail/flac/2004-December/000393.html + * ogg123 trunk still doesn't have this patch as of June 2005 */ + unsigned char buf[41]; + size_t r, to_read = 41; + + seekInputStream(inStream, 0, SEEK_SET); + + while (to_read) { + r = readFromInputStream(inStream, buf, 1, to_read); + if (inStream->error) + break; + to_read -= r; + if (!r && !inputStreamAtEOF(inStream)) + my_usleep(10000); + else + break; + } + + seekInputStream(inStream, 0, SEEK_SET); + + if (r >= 32 && memcmp(buf, "OggS", 4) == 0 && ((memcmp + (buf + 29, "FLAC", + 4) == 0 + && memcmp(buf + 37, + "fLaC", + 4) == 0) + || + (memcmp + (buf + 28, "FLAC", + 4) == 0) + || + (memcmp + (buf + 28, "fLaC", + 4) == 0))) { + return FLAC; + } + return VORBIS; +} + +#endif /* defined(HAVE_OGGFLAC || defined(HAVE_OGGVORBIS) */ diff --git a/trunk/src/inputPlugins/_ogg_common.h b/trunk/src/inputPlugins/_ogg_common.h new file mode 100644 index 000000000..5821e6641 --- /dev/null +++ b/trunk/src/inputPlugins/_ogg_common.h @@ -0,0 +1,35 @@ +/* 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 + * + * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + * (c) 2005 by Eric Wong <normalperson@yhbt.net> + * + * 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 _OGG_COMMON_H +#define _OGG_COMMON_H + +#include "../inputPlugin.h" + +#if defined(HAVE_OGGFLAC) || defined(HAVE_OGGVORBIS) + +typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; + +ogg_stream_type ogg_stream_type_detect(InputStream * inStream); + +#endif /* defined(HAVE_OGGFLAC || defined(HAVE_OGGVORBIS) */ + +#endif /* _OGG_COMMON_H */ diff --git a/trunk/src/inputPlugins/aac_plugin.c b/trunk/src/inputPlugins/aac_plugin.c new file mode 100644 index 000000000..529689706 --- /dev/null +++ b/trunk/src/inputPlugins/aac_plugin.c @@ -0,0 +1,475 @@ +/* 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 "../inputPlugin.h" + +#ifdef HAVE_FAAD + +#define AAC_MAX_CHANNELS 6 + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../inputStream.h" +#include "../outputBuffer.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <faad.h> + +/* all code here is either based on or copied from FAAD2's frontend code */ +typedef struct { + InputStream *inStream; + long bytesIntoBuffer; + long bytesConsumed; + long fileOffset; + unsigned char *buffer; + int atEof; +} AacBuffer; + +static void fillAacBuffer(AacBuffer * b) +{ + if (b->bytesConsumed > 0) { + int bread; + + if (b->bytesIntoBuffer) { + memmove((void *)b->buffer, (void *)(b->buffer + + b->bytesConsumed), + b->bytesIntoBuffer); + } + + if (!b->atEof) { + bread = readFromInputStream(b->inStream, + (void *)(b->buffer + + b-> + bytesIntoBuffer), + 1, b->bytesConsumed); + if (bread != b->bytesConsumed) + b->atEof = 1; + b->bytesIntoBuffer += bread; + } + + b->bytesConsumed = 0; + + if (b->bytesIntoBuffer > 3) { + if (memcmp(b->buffer, "TAG", 3) == 0) + b->bytesIntoBuffer = 0; + } + if (b->bytesIntoBuffer > 11) { + if (memcmp(b->buffer, "LYRICSBEGIN", 11) == 0) { + b->bytesIntoBuffer = 0; + } + } + if (b->bytesIntoBuffer > 8) { + if (memcmp(b->buffer, "APETAGEX", 8) == 0) { + b->bytesIntoBuffer = 0; + } + } + } +} + +static void advanceAacBuffer(AacBuffer * b, int bytes) +{ + b->fileOffset += bytes; + b->bytesConsumed = bytes; + b->bytesIntoBuffer -= bytes; +} + +static int adtsSampleRates[] = + { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 +}; + +static int adtsParse(AacBuffer * b, float *length) +{ + int frames, frameLength; + int tFrameLength = 0; + int sampleRate = 0; + float framesPerSec, bytesPerFrame; + + /* Read all frames to ensure correct time and bitrate */ + for (frames = 0;; frames++) { + fillAacBuffer(b); + + if (b->bytesIntoBuffer > 7) { + /* check syncword */ + if (!((b->buffer[0] == 0xFF) && + ((b->buffer[1] & 0xF6) == 0xF0))) { + break; + } + + if (frames == 0) { + sampleRate = adtsSampleRates[(b-> + buffer[2] & 0x3c) + >> 2]; + } + + frameLength = ((((unsigned int)b->buffer[3] & 0x3)) + << 11) | (((unsigned int)b->buffer[4]) + << 3) | (b->buffer[5] >> 5); + + tFrameLength += frameLength; + + if (frameLength > b->bytesIntoBuffer) + break; + + advanceAacBuffer(b, frameLength); + } else + break; + } + + framesPerSec = (float)sampleRate / 1024.0; + if (frames != 0) { + bytesPerFrame = (float)tFrameLength / (float)(frames * 1000); + } else + bytesPerFrame = 0; + if (framesPerSec != 0) + *length = (float)frames / framesPerSec; + + return 1; +} + +static void initAacBuffer(InputStream * inStream, AacBuffer * b, float *length, + size_t * retFileread, size_t * retTagsize) +{ + size_t fileread; + size_t bread; + size_t tagsize; + + if (length) + *length = -1; + + memset(b, 0, sizeof(AacBuffer)); + + b->inStream = inStream; + + fileread = inStream->size; + + b->buffer = xmalloc(FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + memset(b->buffer, 0, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + + bread = readFromInputStream(inStream, b->buffer, 1, + FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + b->bytesIntoBuffer = bread; + b->bytesConsumed = 0; + b->fileOffset = 0; + + if (bread != FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS) + b->atEof = 1; + + tagsize = 0; + if (!memcmp(b->buffer, "ID3", 3)) { + tagsize = (b->buffer[6] << 21) | (b->buffer[7] << 14) | + (b->buffer[8] << 7) | (b->buffer[9] << 0); + + tagsize += 10; + advanceAacBuffer(b, tagsize); + fillAacBuffer(b); + } + + if (retFileread) + *retFileread = fileread; + if (retTagsize) + *retTagsize = tagsize; + + if (length == NULL) + return; + + if ((b->buffer[0] == 0xFF) && ((b->buffer[1] & 0xF6) == 0xF0)) { + adtsParse(b, length); + seekInputStream(b->inStream, tagsize, SEEK_SET); + + bread = readFromInputStream(b->inStream, b->buffer, 1, + FAAD_MIN_STREAMSIZE * + AAC_MAX_CHANNELS); + if (bread != FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS) + b->atEof = 1; + else + b->atEof = 0; + b->bytesIntoBuffer = bread; + b->bytesConsumed = 0; + b->fileOffset = tagsize; + } else if (memcmp(b->buffer, "ADIF", 4) == 0) { + int bitRate; + int skipSize = (b->buffer[4] & 0x80) ? 9 : 0; + bitRate = + ((unsigned int)(b-> + buffer[4 + + skipSize] & 0x0F) << 19) | ((unsigned + int)b-> + buffer[5 + + + skipSize] + << 11) | + ((unsigned int)b-> + buffer[6 + skipSize] << 3) | ((unsigned int)b->buffer[7 + + skipSize] + & 0xE0); + + if (fileread != 0 && bitRate != 0) + *length = fileread * 8.0 / bitRate; + else + *length = fileread; + } +} + +static float getAacFloatTotalTime(char *file) +{ + AacBuffer b; + float length; + size_t fileread, tagsize; + faacDecHandle decoder; + faacDecConfigurationPtr config; + unsigned long sampleRate; + unsigned char channels; + InputStream inStream; + long bread; + + if (openInputStream(&inStream, file) < 0) + return -1; + + initAacBuffer(&inStream, &b, &length, &fileread, &tagsize); + + if (length < 0) { + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + faacDecSetConfiguration(decoder, config); + + fillAacBuffer(&b); +#ifdef HAVE_FAAD_BUFLEN_FUNCS + bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer, + &sampleRate, &channels); +#else + bread = faacDecInit(decoder, b.buffer, &sampleRate, &channels); +#endif + if (bread >= 0 && sampleRate > 0 && channels > 0) + length = 0; + + faacDecClose(decoder); + } + + if (b.buffer) + free(b.buffer); + closeInputStream(&inStream); + + return length; +} + +static int getAacTotalTime(char *file) +{ + int time = -1; + float length; + + if ((length = getAacFloatTotalTime(file)) >= 0) + time = length + 0.5; + + return time; +} + +static int aac_decode(OutputBuffer * cb, DecoderControl * dc, char *path) +{ + float time; + float totalTime; + faacDecHandle decoder; + faacDecFrameInfo frameInfo; + faacDecConfigurationPtr config; + long bread; + unsigned long sampleRate; + unsigned char channels; + int eof = 0; + unsigned int sampleCount; + char *sampleBuffer; + size_t sampleBufferLen; + /*float * seekTable; + long seekTableEnd = -1; + int seekPositionFound = 0; */ + mpd_uint16 bitRate = 0; + AacBuffer b; + InputStream inStream; + + if ((totalTime = getAacFloatTotalTime(path)) < 0) + return -1; + + if (openInputStream(&inStream, path) < 0) + return -1; + + initAacBuffer(&inStream, &b, NULL, NULL, NULL); + + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; +#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX + config->downMatrix = 1; +#endif +#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR + config->dontUpSampleImplicitSBR = 0; +#endif + faacDecSetConfiguration(decoder, config); + + fillAacBuffer(&b); + +#ifdef HAVE_FAAD_BUFLEN_FUNCS + bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer, + &sampleRate, &channels); +#else + bread = faacDecInit(decoder, b.buffer, &sampleRate, &channels); +#endif + if (bread < 0) { + ERROR("Error not a AAC stream.\n"); + faacDecClose(decoder); + closeInputStream(b.inStream); + if (b.buffer) + free(b.buffer); + return -1; + } + + dc->audioFormat.bits = 16; + + dc->totalTime = totalTime; + + time = 0.0; + + advanceAacBuffer(&b, bread); + + while (!eof) { + fillAacBuffer(&b); + + if (b.bytesIntoBuffer == 0) { + eof = 1; + break; + } +#ifdef HAVE_FAAD_BUFLEN_FUNCS + sampleBuffer = faacDecDecode(decoder, &frameInfo, b.buffer, + b.bytesIntoBuffer); +#else + sampleBuffer = faacDecDecode(decoder, &frameInfo, b.buffer); +#endif + + if (frameInfo.error > 0) { + ERROR("error decoding AAC file: %s\n", path); + ERROR("faad2 error: %s\n", + faacDecGetErrorMessage(frameInfo.error)); + eof = 1; + break; + } +#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE + sampleRate = frameInfo.samplerate; +#endif + + if (dc->state != DECODE_STATE_DECODE) { + dc->audioFormat.channels = frameInfo.channels; + dc->audioFormat.sampleRate = sampleRate; + getOutputAudioFormat(&(dc->audioFormat), + &(cb->audioFormat)); + dc->state = DECODE_STATE_DECODE; + } + + advanceAacBuffer(&b, frameInfo.bytesconsumed); + + sampleCount = (unsigned long)(frameInfo.samples); + + if (sampleCount > 0) { + bitRate = frameInfo.bytesconsumed * 8.0 * + frameInfo.channels * sampleRate / + frameInfo.samples / 1000 + 0.5; + time += + (float)(frameInfo.samples) / frameInfo.channels / + sampleRate; + } + + sampleBufferLen = sampleCount * 2; + + sendDataToOutputBuffer(cb, NULL, dc, 0, sampleBuffer, + sampleBufferLen, time, bitRate, NULL); + if (dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } else if (dc->stop) { + eof = 1; + break; + } + } + + flushOutputBuffer(cb); + + faacDecClose(decoder); + closeInputStream(b.inStream); + if (b.buffer) + free(b.buffer); + + if (dc->state != DECODE_STATE_DECODE) + return -1; + + if (dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *aacTagDup(char *file) +{ + MpdTag *ret = NULL; + int time; + + time = getAacTotalTime(file); + + if (time >= 0) { + if ((ret = id3Dup(file)) == NULL) + ret = newMpdTag(); + ret->time = time; + } else { + DEBUG("aacTagDup: Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static char *aacSuffixes[] = { "aac", NULL }; + +InputPlugin aacPlugin = { + "aac", + NULL, + NULL, + NULL, + NULL, + aac_decode, + aacTagDup, + INPUT_PLUGIN_STREAM_FILE, + aacSuffixes, + NULL +}; + +#else + +InputPlugin aacPlugin; + +#endif /* HAVE_FAAD */ diff --git a/trunk/src/inputPlugins/audiofile_plugin.c b/trunk/src/inputPlugins/audiofile_plugin.c new file mode 100644 index 000000000..35fb48b8a --- /dev/null +++ b/trunk/src/inputPlugins/audiofile_plugin.c @@ -0,0 +1,188 @@ +/* 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 + * + * libaudiofile (wave) support added by Eric Wong <normalperson@yhbt.net> + * + * 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 "../inputPlugin.h" + +#ifdef HAVE_AUDIOFILE + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../playerData.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <audiofile.h> + +static int getAudiofileTotalTime(char *file) +{ + int time; + AFfilehandle af_fp = afOpenFile(file, "r", NULL); + if (af_fp == AF_NULL_FILEHANDLE) { + return -1; + } + time = (int) + ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK) + / afGetRate(af_fp, AF_DEFAULT_TRACK)); + afCloseFile(af_fp); + return time; +} + +static int audiofile_decode(OutputBuffer * cb, DecoderControl * dc, char *path) +{ + int fs, frame_count; + AFfilehandle af_fp; + int bits; + mpd_uint16 bitRate; + struct stat st; + + if (stat(path, &st) < 0) { + ERROR("failed to stat: %s\n", path); + return -1; + } + + af_fp = afOpenFile(path, "r", NULL); + if (af_fp == AF_NULL_FILEHANDLE) { + ERROR("failed to open: %s\n", path); + return -1; + } + + afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, + AF_SAMPFMT_TWOSCOMP, 16); + afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); + dc->audioFormat.bits = bits; + dc->audioFormat.sampleRate = afGetRate(af_fp, AF_DEFAULT_TRACK); + dc->audioFormat.channels = afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK); + getOutputAudioFormat(&(dc->audioFormat), &(cb->audioFormat)); + + frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); + + dc->totalTime = + ((float)frame_count / (float)dc->audioFormat.sampleRate); + + bitRate = st.st_size * 8.0 / dc->totalTime / 1000.0 + 0.5; + + if (dc->audioFormat.bits != 8 && dc->audioFormat.bits != 16) { + ERROR("Only 8 and 16-bit files are supported. %s is %i-bit\n", + path, dc->audioFormat.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) { + clearOutputBuffer(cb); + current = dc->seekWhere * + dc->audioFormat.sampleRate; + afSeekFrame(af_fp, AF_DEFAULT_TRACK, current); + dc->seek = 0; + } + + ret = + afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, + CHUNK_SIZE / fs); + if (ret <= 0) + eof = 1; + else { + current += ret; + sendDataToOutputBuffer(cb, + NULL, + dc, + 1, + chunk, + ret * fs, + (float)current / + (float)dc->audioFormat. + sampleRate, bitRate, + NULL); + if (dc->stop) + break; + } + } + + flushOutputBuffer(cb); + + /*if(dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } */ + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + } + afCloseFile(af_fp); + + return 0; +} + +static MpdTag *audiofileTagDup(char *file) +{ + MpdTag *ret = NULL; + int time = getAudiofileTotalTime(file); + + if (time >= 0) { + if (!ret) + ret = newMpdTag(); + ret->time = time; + } else { + DEBUG + ("audiofileTagDup: Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static char *audiofileSuffixes[] = { "wav", "au", "aiff", "aif", NULL }; + +InputPlugin audiofilePlugin = { + "audiofile", + NULL, + NULL, + NULL, + NULL, + audiofile_decode, + audiofileTagDup, + INPUT_PLUGIN_STREAM_FILE, + audiofileSuffixes, + NULL +}; + +#else + +InputPlugin audiofilePlugin; + +#endif /* HAVE_AUDIOFILE */ diff --git a/trunk/src/inputPlugins/flac_plugin.c b/trunk/src/inputPlugins/flac_plugin.c new file mode 100644 index 000000000..3f3a4b4f1 --- /dev/null +++ b/trunk/src/inputPlugins/flac_plugin.c @@ -0,0 +1,530 @@ +/* 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 "_flac_common.h" + +#ifdef HAVE_FLAC + +#include "../utils.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../replayGain.h" +#include "../audio.h" + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> + +/* this code was based on flac123, from flac-tools */ + +static flac_read_status flacRead(const flac_decoder * flacDec, + FLAC__byte buf[], + flac_read_status_size_t *bytes, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + size_t r; + + while (1) { + r = readFromInputStream(data->inStream, (void *)buf, 1, *bytes); + if (r == 0 && !inputStreamAtEOF(data->inStream) && + !data->dc->stop) + my_usleep(10000); + else + break; + } + *bytes = r; + + if (r == 0 && !data->dc->stop) { + if (inputStreamAtEOF(data->inStream)) + return flac_read_status_eof; + else + return flac_read_status_abort; + } + return flac_read_status_continue; +} + +static flac_seek_status flacSeek(const flac_decoder * flacDec, + FLAC__uint64 offset, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + if (seekInputStream(data->inStream, offset, SEEK_SET) < 0) { + return flac_seek_status_error; + } + + return flac_seek_status_ok; +} + +static flac_tell_status flacTell(const flac_decoder * flacDec, + FLAC__uint64 * offset, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + *offset = (long)(data->inStream->offset); + + return flac_tell_status_ok; +} + +static flac_length_status flacLength(const flac_decoder * flacDec, + FLAC__uint64 * length, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + *length = (size_t) (data->inStream->size); + + return flac_length_status_ok; +} + +static FLAC__bool flacEOF(const flac_decoder * flacDec, void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + if (inputStreamAtEOF(data->inStream) == 1) + return true; + return false; +} + +static void flacError(const flac_decoder *dec, + FLAC__StreamDecoderErrorStatus status, void *fdata) +{ + flac_error_common_cb("flac", status, (FlacData *) fdata); +} + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) +{ + const char *str = ""; /* "" to silence compiler warning */ + switch (state) { + case FLAC__SEEKABLE_STREAM_DECODER_OK: + case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: + case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: + return; + case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + str = "allocation error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: + str = "read error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: + str = "seek error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: + str = "seekable stream error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: + str = "decoder already initialized"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: + str = "invalid callback"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: + str = "decoder uninitialized"; + } + ERROR("flac %s\n", str); +} + +static int flac_init(FLAC__SeekableStreamDecoder *dec, + FLAC__SeekableStreamDecoderReadCallback read_cb, + FLAC__SeekableStreamDecoderSeekCallback seek_cb, + FLAC__SeekableStreamDecoderTellCallback tell_cb, + FLAC__SeekableStreamDecoderLengthCallback length_cb, + FLAC__SeekableStreamDecoderEofCallback eof_cb, + FLAC__SeekableStreamDecoderWriteCallback write_cb, + FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, + FLAC__SeekableStreamDecoderErrorCallback error_cb, + void *data) +{ + int s = 1; + s &= FLAC__seekable_stream_decoder_set_read_callback(dec, read_cb); + s &= FLAC__seekable_stream_decoder_set_seek_callback(dec, seek_cb); + s &= FLAC__seekable_stream_decoder_set_tell_callback(dec, tell_cb); + s &= FLAC__seekable_stream_decoder_set_length_callback(dec, length_cb); + s &= FLAC__seekable_stream_decoder_set_eof_callback(dec, eof_cb); + s &= FLAC__seekable_stream_decoder_set_write_callback(dec, write_cb); + s &= FLAC__seekable_stream_decoder_set_metadata_callback(dec, + metadata_cb); + s &= FLAC__seekable_stream_decoder_set_metadata_respond(dec, + FLAC__METADATA_TYPE_VORBIS_COMMENT); + s &= FLAC__seekable_stream_decoder_set_error_callback(dec, error_cb); + s &= FLAC__seekable_stream_decoder_set_client_data(dec, data); + if (!s || (FLAC__seekable_stream_decoder_init(dec) != + FLAC__SEEKABLE_STREAM_DECODER_OK)) + return 0; + return 1; +} +#else /* FLAC_API_VERSION_CURRENT >= 7 */ +static void flacPrintErroredState(FLAC__StreamDecoderState state) +{ + const char *str = ""; /* "" to silence compiler warning */ + switch (state) { + case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: + case FLAC__STREAM_DECODER_READ_METADATA: + case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: + case FLAC__STREAM_DECODER_READ_FRAME: + case FLAC__STREAM_DECODER_END_OF_STREAM: + return; + case FLAC__STREAM_DECODER_OGG_ERROR: + str = "error in the Ogg layer"; + break; + case FLAC__STREAM_DECODER_SEEK_ERROR: + str = "seek error"; + break; + case FLAC__STREAM_DECODER_ABORTED: + str = "decoder aborted by read"; + break; + case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + str = "allocation error"; + break; + case FLAC__STREAM_DECODER_UNINITIALIZED: + str = "decoder uninitialized"; + } + ERROR("flac %s\n", str); +} +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +static void flacMetadata(const flac_decoder * dec, + const FLAC__StreamMetadata * block, void *vdata) +{ + flac_metadata_common_cb(block, (FlacData *) vdata); +} + +static FLAC__StreamDecoderWriteStatus flacWrite(const flac_decoder *dec, + const FLAC__Frame * frame, + const FLAC__int32 * const buf[], + void *vdata) +{ + FlacData *data = (FlacData *) vdata; + FLAC__uint32 samples = frame->header.blocksize; + FLAC__uint16 u16; + unsigned char *uc; + int c_samp, c_chan, d_samp; + int i; + float timeChange; + FLAC__uint64 newPosition = 0; + + timeChange = ((float)samples) / frame->header.sample_rate; + data->time += timeChange; + + flac_get_decode_position(dec, &newPosition); + if (data->position) { + data->bitRate = + ((newPosition - data->position) * 8.0 / timeChange) + / 1000 + 0.5; + } + data->position = newPosition; + + for (c_samp = d_samp = 0; c_samp < frame->header.blocksize; c_samp++) { + for (c_chan = 0; c_chan < frame->header.channels; + c_chan++, d_samp++) { + u16 = buf[c_chan][c_samp]; + uc = (unsigned char *)&u16; + for (i = 0; i < (data->dc->audioFormat.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 (data->dc->seek) { + return + FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + } + data->chunk[data->chunk_length++] = *(uc++); + } + } + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static MpdTag *flacMetadataDup(char *file, int *vorbisCommentFound) +{ + MpdTag *ret = NULL; + FLAC__Metadata_SimpleIterator *it; + FLAC__StreamMetadata *block = NULL; + + *vorbisCommentFound = 0; + + it = FLAC__metadata_simple_iterator_new(); + if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { + switch (FLAC__metadata_simple_iterator_status(it)) { + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: + DEBUG + ("flacMetadataDup: Reading '%s' metadata gave the following error: Illegal Input\n", + file); + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: + DEBUG + ("flacMetadataDup: Reading '%s' metadata gave the following error: Error Opening File\n", + file); + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: + DEBUG + ("flacMetadataDup: Reading '%s' metadata gave the following error: Not A Flac File\n", + file); + break; + default: + DEBUG("flacMetadataDup: Reading '%s' metadata failed\n", + file); + } + FLAC__metadata_simple_iterator_delete(it); + return ret; + } + + do { + block = FLAC__metadata_simple_iterator_get_block(it); + if (!block) + break; + if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { + ret = copyVorbisCommentBlockToMpdTag(block, ret); + + if (ret) + *vorbisCommentFound = 1; + } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { + if (!ret) + ret = newMpdTag(); + ret->time = ((float)block->data.stream_info. + total_samples) / + block->data.stream_info.sample_rate + 0.5; + } + FLAC__metadata_object_delete(block); + } while (FLAC__metadata_simple_iterator_next(it)); + + FLAC__metadata_simple_iterator_delete(it); + return ret; +} + +static MpdTag *flacTagDup(char *file) +{ + MpdTag *ret = NULL; + int foundVorbisComment = 0; + + ret = flacMetadataDup(file, &foundVorbisComment); + if (!ret) { + DEBUG("flacTagDup: Failed to grab information from: %s\n", + file); + return NULL; + } + if (!foundVorbisComment) { + MpdTag *temp = id3Dup(file); + if (temp) { + temp->time = ret->time; + freeMpdTag(ret); + ret = temp; + } + } + + return ret; +} + +static int flac_decode_internal(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream, int is_ogg) +{ + flac_decoder *flacDec; + FlacData data; + const char *err = NULL; + + if (!(flacDec = flac_new())) + return -1; + init_FlacData(&data, cb, dc, inStream); + if (is_ogg) { + if (!flac_ogg_init(flacDec, flacRead, flacSeek, flacTell, + flacLength, flacEOF, flacWrite, flacMetadata, + flacError, (void *)&data)) { + err = "doing Ogg init()"; + goto fail; + } + } else { + if (!flac_init(flacDec, flacRead, flacSeek, flacTell, + flacLength, flacEOF, flacWrite, flacMetadata, + flacError, (void *)&data)) { + err = "doing init()"; + goto fail; + } + if (!flac_process_metadata(flacDec)) { + err = "problem reading metadata"; + goto fail; + } + } + + 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 (flac_seek_absolute(flacDec, sampleToSeek)) { + clearOutputBuffer(cb); + data.time = ((float)sampleToSeek) / + dc->audioFormat.sampleRate; + data.position = 0; + } else + dc->seekError = 1; + dc->seek = 0; + } + } + if (!dc->stop) { + flacPrintErroredState(flac_get_state(flacDec)); + flac_finish(flacDec); + } + /* send last little bit */ + if (data.chunk_length > 0 && !dc->stop) { + flacSendChunk(&data); + flushOutputBuffer(data.cb); + } + + /*if(dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } */ + + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + +fail: + if (data.replayGainInfo) + freeReplayGainInfo(data.replayGainInfo); + + if (flacDec) + flac_delete(flacDec); + + closeInputStream(inStream); + + if (err) { + ERROR("flac %s\n", err); + return -1; + } + return 0; +} + +static int flac_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + return flac_decode_internal(cb, dc, inStream, 0); +} + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +# define flac_plugin_init NULL +#else /* FLAC_API_VERSION_CURRENT >= 7 */ +/* some of this stuff is duplicated from oggflac_plugin.c */ +extern InputPlugin oggflacPlugin; + +static MpdTag *oggflac_tag_dup(char *file) +{ + MpdTag *ret = NULL; + FLAC__Metadata_Iterator *it; + FLAC__StreamMetadata *block; + FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); + + if (!(FLAC__metadata_chain_read_ogg(chain, file))) + goto out; + it = FLAC__metadata_iterator_new(); + FLAC__metadata_iterator_init(it, chain); + do { + if (!(block = FLAC__metadata_iterator_get_block(it))) + break; + if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { + ret = copyVorbisCommentBlockToMpdTag(block, ret); + } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { + if (!ret) + ret = newMpdTag(); + ret->time = ((float)block->data.stream_info. + total_samples) / + block->data.stream_info.sample_rate + 0.5; + } + } while (FLAC__metadata_iterator_next(it)); + FLAC__metadata_iterator_delete(it); +out: + FLAC__metadata_chain_delete(chain); + return ret; +} + +static int oggflac_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + return flac_decode_internal(cb, dc, inStream, 1); +} + +static unsigned int oggflac_try_decode(InputStream * inStream) +{ + return (ogg_stream_type_detect(inStream) == FLAC) ? 1 : 0; +} + +static char *oggflac_suffixes[] = { "ogg", NULL }; +static char *oggflac_mime_types[] = { "audio/x-flac+ogg", + "application/ogg", + "application/x-ogg", + NULL }; + +static int flac_plugin_init(void) +{ + if (!FLAC_API_SUPPORTS_OGG_FLAC) { + DEBUG("libFLAC does not support OggFLAC\n"); + return 1; + } + DEBUG("libFLAC supports OggFLAC, initializing OggFLAC support\n"); + assert(oggflacPlugin.name == NULL); + oggflacPlugin.name = "oggflac"; + oggflacPlugin.tryDecodeFunc = oggflac_try_decode; + oggflacPlugin.streamDecodeFunc = oggflac_decode; + oggflacPlugin.tagDupFunc = oggflac_tag_dup; + oggflacPlugin.streamTypes = INPUT_PLUGIN_STREAM_URL | + INPUT_PLUGIN_STREAM_FILE; + oggflacPlugin.suffixes = oggflac_suffixes; + oggflacPlugin.mimeTypes = oggflac_mime_types; + loadInputPlugin(&oggflacPlugin); + return 1; +} + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +static char *flacSuffixes[] = { "flac", NULL }; +static char *flac_mime_types[] = { "audio/x-flac", + "application/x-flac", + NULL }; + +InputPlugin flacPlugin = { + "flac", + flac_plugin_init, + NULL, + NULL, + flac_decode, + NULL, + flacTagDup, + INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + flacSuffixes, + flac_mime_types +}; + +#else /* !HAVE_FLAC */ + +InputPlugin flacPlugin; + +#endif /* HAVE_FLAC */ diff --git a/trunk/src/inputPlugins/mod_plugin.c b/trunk/src/inputPlugins/mod_plugin.c new file mode 100644 index 000000000..800abc95f --- /dev/null +++ b/trunk/src/inputPlugins/mod_plugin.c @@ -0,0 +1,299 @@ +/* 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 "../inputPlugin.h" + +#ifdef HAVE_MIKMOD + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../playerData.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <mikmod.h> + +/* this is largely copied from alsaplayer */ + +#define MIKMOD_FRAME_SIZE 4096 + +static BOOL mod_mpd_Init(void) +{ + return VC_Init(); +} + +static void mod_mpd_Exit(void) +{ + VC_Exit(); +} + +static void mod_mpd_Update(void) +{ +} + +static BOOL mod_mpd_IsThere(void) +{ + return 1; +} + +static MDRIVER drv_mpd = { + NULL, + "MPD", + "MPD Output Driver v0.1", + 0, + 255, +#if (LIBMIKMOD_VERSION > 0x030106) + "mpd", /* Alias */ +#if (LIBMIKMOD_VERSION > 0x030200) + NULL, /* CmdLineHelp */ +#endif + NULL, /* CommandLine */ +#endif + mod_mpd_IsThere, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + mod_mpd_Init, + mod_mpd_Exit, + NULL, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + mod_mpd_Update, + NULL, + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + +static int mod_mikModInitiated; +static int mod_mikModInitError; + +static int mod_initMikMod(void) +{ + if (mod_mikModInitError) + return -1; + + if (!mod_mikModInitiated) { + mod_mikModInitiated = 1; + + md_device = 0; + md_reverb = 0; + + MikMod_RegisterDriver(&drv_mpd); + MikMod_RegisterAllLoaders(); + } + + md_pansep = 64; + md_mixfreq = 44100; + md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO | + DMODE_16BITS); + + if (MikMod_Init("")) { + ERROR("Could not init MikMod: %s\n", + MikMod_strerror(MikMod_errno)); + mod_mikModInitError = 1; + return -1; + } + + return 0; +} + +static void mod_finishMikMod(void) +{ + MikMod_Exit(); +} + +typedef struct _mod_Data { + MODULE *moduleHandle; + SBYTE *audio_buffer; +} mod_Data; + +static mod_Data *mod_open(char *path) +{ + MODULE *moduleHandle; + mod_Data *data; + + if (!(moduleHandle = Player_Load(path, 128, 0))) + return NULL; + + /* Prevent module from looping forever */ + moduleHandle->loop = 0; + + data = xmalloc(sizeof(mod_Data)); + + data->audio_buffer = xmalloc(MIKMOD_FRAME_SIZE); + data->moduleHandle = moduleHandle; + + Player_Start(data->moduleHandle); + + return data; +} + +static void mod_close(mod_Data * data) +{ + Player_Stop(); + Player_Free(data->moduleHandle); + free(data->audio_buffer); + free(data); +} + +static int mod_decode(OutputBuffer * cb, DecoderControl * dc, char *path) +{ + mod_Data *data; + float time = 0.0; + int ret; + float secPerByte; + + if (mod_initMikMod() < 0) + return -1; + + if (!(data = mod_open(path))) { + ERROR("failed to open mod: %s\n", path); + MikMod_Exit(); + return -1; + } + + dc->totalTime = 0; + dc->audioFormat.bits = 16; + dc->audioFormat.sampleRate = 44100; + dc->audioFormat.channels = 2; + getOutputAudioFormat(&(dc->audioFormat), &(cb->audioFormat)); + + secPerByte = + 1.0 / ((dc->audioFormat.bits * dc->audioFormat.channels / 8.0) * + (float)dc->audioFormat.sampleRate); + + dc->state = DECODE_STATE_DECODE; + while (1) { + if (dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } + + if (dc->stop) + break; + + if (!Player_Active()) + break; + + ret = VC_WriteBytes(data->audio_buffer, MIKMOD_FRAME_SIZE); + time += ret * secPerByte; + sendDataToOutputBuffer(cb, NULL, dc, 0, + (char *)data->audio_buffer, ret, time, + 0, NULL); + } + + flushOutputBuffer(cb); + + mod_close(data); + + MikMod_Exit(); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *modTagDup(char *file) +{ + MpdTag *ret = NULL; + MODULE *moduleHandle; + char *title; + + if (mod_initMikMod() < 0) { + DEBUG("modTagDup: Failed to initialize MikMod\n"); + return NULL; + } + + if (!(moduleHandle = Player_Load(file, 128, 0))) { + DEBUG("modTagDup: Failed to open file: %s\n", file); + MikMod_Exit(); + return NULL; + + } + Player_Free(moduleHandle); + + ret = newMpdTag(); + + ret->time = 0; + title = xstrdup(Player_LoadTitle(file)); + if (title) + addItemToMpdTag(ret, TAG_ITEM_TITLE, title); + + MikMod_Exit(); + + return ret; +} + +static char *modSuffixes[] = { "amf", + "dsm", + "far", + "gdm", + "imf", + "it", + "med", + "mod", + "mtm", + "s3m", + "stm", + "stx", + "ult", + "uni", + "xm", + NULL +}; + +InputPlugin modPlugin = { + "mod", + NULL, + mod_finishMikMod, + NULL, + NULL, + mod_decode, + modTagDup, + INPUT_PLUGIN_STREAM_FILE, + modSuffixes, + NULL +}; + +#else + +InputPlugin modPlugin; + +#endif /* HAVE_MIKMOD */ diff --git a/trunk/src/inputPlugins/mp3_plugin.c b/trunk/src/inputPlugins/mp3_plugin.c new file mode 100644 index 000000000..a920b98a1 --- /dev/null +++ b/trunk/src/inputPlugins/mp3_plugin.c @@ -0,0 +1,1092 @@ +/* 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 "../inputPlugin.h" + +#ifdef HAVE_MAD + +#include "../pcm_utils.h" +#include <mad.h> + +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +#include "../log.h" +#include "../utils.h" +#include "../replayGain.h" +#include "../tag.h" +#include "../conf.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#define FRAMES_CUSHION 2000 + +#define READ_BUFFER_SIZE 40960 + +#define DECODE_SKIP -3 +#define DECODE_BREAK -2 +#define DECODE_CONT -1 +#define DECODE_OK 0 + +#define MUTEFRAME_SKIP 1 +#define MUTEFRAME_SEEK 2 + +/* the number of samples of silence the decoder inserts at start */ +#define DECODERDELAY 529 + +#define DEFAULT_GAPLESS_MP3_PLAYBACK 1 + +static int gaplessPlayback; + +/* this is stolen from mpg321! */ +struct audio_dither { + mad_fixed_t error[3]; + mad_fixed_t random; +}; + +static unsigned long prng(unsigned long state) +{ + return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; +} + +static signed long audio_linear_dither(unsigned int bits, mad_fixed_t sample, + struct audio_dither *dither) +{ + unsigned int scalebits; + mad_fixed_t output, mask, random; + + enum { + MIN = -MAD_F_ONE, + MAX = MAD_F_ONE - 1 + }; + + sample += dither->error[0] - dither->error[1] + dither->error[2]; + + dither->error[2] = dither->error[1]; + dither->error[1] = dither->error[0] / 2; + + output = sample + (1L << (MAD_F_FRACBITS + 1 - bits - 1)); + + scalebits = MAD_F_FRACBITS + 1 - bits; + mask = (1L << scalebits) - 1; + + random = prng(dither->random); + output += (random & mask) - (dither->random & mask); + + dither->random = random; + + if (output > MAX) { + output = MAX; + + if (sample > MAX) + sample = MAX; + } else if (output < MIN) { + output = MIN; + + if (sample < MIN) + sample = MIN; + } + + output &= ~mask; + + dither->error[0] = sample - output; + + return output >> scalebits; +} + +/* end of stolen stuff from mpg321 */ + +static int mp3_plugin_init(void) +{ + gaplessPlayback = getBoolConfigParam(CONF_GAPLESS_MP3_PLAYBACK); + if (gaplessPlayback == -1) gaplessPlayback = DEFAULT_GAPLESS_MP3_PLAYBACK; + else if (gaplessPlayback < 0) exit(EXIT_FAILURE); + return 1; +} + +/* decoder stuff is based on madlld */ + +#define MP3_DATA_OUTPUT_BUFFER_SIZE 4096 + +typedef struct _mp3DecodeData { + struct mad_stream stream; + struct mad_frame frame; + struct mad_synth synth; + mad_timer_t timer; + unsigned char readBuffer[READ_BUFFER_SIZE]; + char outputBuffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; + char *outputPtr; + char *outputBufferEnd; + float totalTime; + float elapsedTime; + int muteFrame; + long *frameOffset; + mad_timer_t *times; + long highestFrame; + long maxFrames; + long currentFrame; + int dropFramesAtStart; + int dropFramesAtEnd; + int dropSamplesAtStart; + int dropSamplesAtEnd; + int foundXing; + int foundFirstFrame; + int decodedFirstFrame; + int flush; + unsigned long bitRate; + InputStream *inStream; + struct audio_dither dither; + enum mad_layer layer; +} mp3DecodeData; + +static void initMp3DecodeData(mp3DecodeData * data, InputStream * inStream) +{ + data->outputPtr = data->outputBuffer; + data->outputBufferEnd = + data->outputBuffer + MP3_DATA_OUTPUT_BUFFER_SIZE; + data->muteFrame = 0; + data->highestFrame = 0; + data->maxFrames = 0; + data->frameOffset = NULL; + data->times = NULL; + data->currentFrame = 0; + data->dropFramesAtStart = 0; + data->dropFramesAtEnd = 0; + data->dropSamplesAtStart = 0; + data->dropSamplesAtEnd = 0; + data->foundXing = 0; + data->foundFirstFrame = 0; + data->decodedFirstFrame = 0; + data->flush = 1; + data->inStream = inStream; + data->layer = 0; + memset(&(data->dither), 0, sizeof(struct audio_dither)); + + mad_stream_init(&data->stream); + mad_stream_options(&data->stream, MAD_OPTION_IGNORECRC); + mad_frame_init(&data->frame); + mad_synth_init(&data->synth); + mad_timer_reset(&data->timer); +} + +static int seekMp3InputBuffer(mp3DecodeData * data, long offset) +{ + if (seekInputStream(data->inStream, offset, SEEK_SET) < 0) { + return -1; + } + + mad_stream_buffer(&data->stream, data->readBuffer, 0); + (data->stream).error = 0; + + return 0; +} + +static int fillMp3InputBuffer(mp3DecodeData * data) +{ + size_t readSize; + size_t remaining; + size_t readed; + unsigned char *readStart; + + if ((data->stream).next_frame != NULL) { + remaining = (data->stream).bufend - (data->stream).next_frame; + memmove(data->readBuffer, (data->stream).next_frame, remaining); + readStart = (data->readBuffer) + remaining; + readSize = READ_BUFFER_SIZE - remaining; + } else { + readSize = READ_BUFFER_SIZE; + readStart = data->readBuffer, remaining = 0; + } + + /* we've exhausted the read buffer, so give up!, these potential + * mp3 frames are way too big, and thus unlikely to be mp3 frames */ + if (readSize == 0) + return -1; + + readed = readFromInputStream(data->inStream, readStart, (size_t) 1, + readSize); + if (readed <= 0 && inputStreamAtEOF(data->inStream)) + return -1; + /* sleep for a fraction of a second! */ + else if (readed <= 0) { + readed = 0; + my_usleep(10000); + } + + mad_stream_buffer(&data->stream, data->readBuffer, readed + remaining); + (data->stream).error = 0; + + return 0; +} + +#ifdef HAVE_ID3TAG +static ReplayGainInfo *parseId3ReplayGainInfo(struct id3_tag *tag) +{ + int i; + char *key; + char *value; + struct id3_frame *frame; + int found = 0; + ReplayGainInfo *replayGainInfo; + + replayGainInfo = newReplayGainInfo(); + + for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + if (frame->nfields < 3) + continue; + + key = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[1])); + value = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[2])); + + if (strcasecmp(key, "replaygain_track_gain") == 0) { + replayGainInfo->trackGain = atof(value); + found = 1; + } else if (strcasecmp(key, "replaygain_album_gain") == 0) { + replayGainInfo->albumGain = atof(value); + found = 1; + } else if (strcasecmp(key, "replaygain_track_peak") == 0) { + replayGainInfo->trackPeak = atof(value); + found = 1; + } else if (strcasecmp(key, "replaygain_album_peak") == 0) { + replayGainInfo->albumPeak = atof(value); + found = 1; + } + + free(key); + free(value); + } + + if (found) + return replayGainInfo; + freeReplayGainInfo(replayGainInfo); + return NULL; +} +#endif + +#ifdef HAVE_ID3TAG +static void mp3_parseId3Tag(mp3DecodeData * data, signed long tagsize, + MpdTag ** mpdTag, ReplayGainInfo ** replayGainInfo) +{ + struct id3_tag *id3Tag = NULL; + id3_length_t count; + id3_byte_t const *id3_data; + id3_byte_t *allocated = NULL; + MpdTag *tmpMpdTag; + ReplayGainInfo *tmpReplayGainInfo; + + count = data->stream.bufend - data->stream.this_frame; + + if (tagsize <= count) { + id3_data = data->stream.this_frame; + mad_stream_skip(&(data->stream), tagsize); + } else { + allocated = xmalloc(tagsize); + if (!allocated) + goto fail; + + memcpy(allocated, data->stream.this_frame, count); + mad_stream_skip(&(data->stream), count); + + while (count < tagsize) { + int len; + + len = readFromInputStream(data->inStream, + allocated + count, (size_t) 1, + tagsize - count); + if (len <= 0 && inputStreamAtEOF(data->inStream)) { + break; + } else if (len <= 0) + my_usleep(10000); + else + count += len; + } + + if (count != tagsize) { + DEBUG("mp3_decode: error parsing ID3 tag\n"); + goto fail; + } + + id3_data = allocated; + } + + id3Tag = id3_tag_parse(id3_data, tagsize); + if (!id3Tag) + goto fail; + + if (mpdTag) { + tmpMpdTag = parseId3Tag(id3Tag); + if (tmpMpdTag) { + if (*mpdTag) + freeMpdTag(*mpdTag); + *mpdTag = tmpMpdTag; + } + } + + if (replayGainInfo) { + tmpReplayGainInfo = parseId3ReplayGainInfo(id3Tag); + if (tmpReplayGainInfo) { + if (*replayGainInfo) + freeReplayGainInfo(*replayGainInfo); + *replayGainInfo = tmpReplayGainInfo; + } + } + + id3_tag_delete(id3Tag); +fail: + if (allocated) + free(allocated); +} +#endif + +static int decodeNextFrameHeader(mp3DecodeData * data, MpdTag ** tag, + ReplayGainInfo ** replayGainInfo) +{ + enum mad_layer layer; + + if ((data->stream).buffer == NULL + || (data->stream).error == MAD_ERROR_BUFLEN) { + if (fillMp3InputBuffer(data) < 0) { + return DECODE_BREAK; + } + } + if (mad_header_decode(&data->frame.header, &data->stream)) { +#ifdef HAVE_ID3TAG + if ((data->stream).error == MAD_ERROR_LOSTSYNC && + (data->stream).this_frame) { + signed long tagsize = id3_tag_query((data->stream). + this_frame, + (data->stream). + bufend - + (data->stream). + this_frame); + + if (tagsize > 0) { + if (tag && !(*tag)) { + mp3_parseId3Tag(data, tagsize, tag, + replayGainInfo); + } else { + mad_stream_skip(&(data->stream), + tagsize); + } + return DECODE_CONT; + } + } +#endif + if (MAD_RECOVERABLE((data->stream).error)) { + return DECODE_SKIP; + } else { + if ((data->stream).error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + else { + ERROR("unrecoverable frame level error " + "(%s).\n", + mad_stream_errorstr(&data->stream)); + data->flush = 0; + return DECODE_BREAK; + } + } + } + + layer = data->frame.header.layer; + if (!data->layer) { + if (layer != MAD_LAYER_II && layer != MAD_LAYER_III) { + /* Only layer 2 and 3 have been tested to work */ + return DECODE_SKIP; + } + data->layer = layer; + } else if (layer != data->layer) { + /* Don't decode frames with a different layer than the first */ + return DECODE_SKIP; + } + + return DECODE_OK; +} + +static int decodeNextFrame(mp3DecodeData * data) +{ + if ((data->stream).buffer == NULL + || (data->stream).error == MAD_ERROR_BUFLEN) { + if (fillMp3InputBuffer(data) < 0) { + return DECODE_BREAK; + } + } + if (mad_frame_decode(&data->frame, &data->stream)) { +#ifdef HAVE_ID3TAG + if ((data->stream).error == MAD_ERROR_LOSTSYNC) { + signed long tagsize = id3_tag_query((data->stream). + this_frame, + (data->stream). + bufend - + (data->stream). + this_frame); + if (tagsize > 0) { + mad_stream_skip(&(data->stream), tagsize); + return DECODE_CONT; + } + } +#endif + if (MAD_RECOVERABLE((data->stream).error)) { + return DECODE_SKIP; + } else { + if ((data->stream).error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + else { + ERROR("unrecoverable frame level error " + "(%s).\n", + mad_stream_errorstr(&data->stream)); + data->flush = 0; + return DECODE_BREAK; + } + } + } + + return DECODE_OK; +} + +/* xing stuff stolen from alsaplayer, and heavily modified by jat */ +#define XI_MAGIC (('X' << 8) | 'i') +#define NG_MAGIC (('n' << 8) | 'g') +#define IN_MAGIC (('I' << 8) | 'n') +#define FO_MAGIC (('f' << 8) | 'o') + +enum xing_magic { + XING_MAGIC_XING, /* VBR */ + XING_MAGIC_INFO /* CBR */ +}; + +struct xing { + long flags; /* valid fields (see below) */ + unsigned long frames; /* total number of frames */ + unsigned long bytes; /* total number of bytes */ + unsigned char toc[100]; /* 100-point seek table */ + long scale; /* VBR quality */ + enum xing_magic magic; /* header magic */ +}; + +enum { + XING_FRAMES = 0x00000001L, + XING_BYTES = 0x00000002L, + XING_TOC = 0x00000004L, + XING_SCALE = 0x00000008L +}; + +struct lame { + char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ +#if 0 + /* See related comment in parse_lame() */ + float peak; /* replaygain peak */ + float trackGain; /* replaygain track gain */ + float albumGain; /* replaygain album gain */ +#endif + int encoderDelay; /* # of added samples at start of mp3 */ + int encoderPadding; /* # of added samples at end of mp3 */ +}; + +static int parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) +{ + unsigned long bits; + int bitlen; + int bitsleft; + int i; + + bitlen = *oldbitlen; + + if (bitlen < 16) goto fail; + bits = mad_bit_read(ptr, 16); + bitlen -= 16; + + if (bits == XI_MAGIC) { + if (bitlen < 16) goto fail; + if (mad_bit_read(ptr, 16) != NG_MAGIC) goto fail; + bitlen -= 16; + xing->magic = XING_MAGIC_XING; + } else if (bits == IN_MAGIC) { + if (bitlen < 16) goto fail; + if (mad_bit_read(ptr, 16) != FO_MAGIC) goto fail; + bitlen -= 16; + xing->magic = XING_MAGIC_INFO; + } + else if (bits == NG_MAGIC) xing->magic = XING_MAGIC_XING; + else if (bits == FO_MAGIC) xing->magic = XING_MAGIC_INFO; + else goto fail; + + if (bitlen < 32) goto fail; + xing->flags = mad_bit_read(ptr, 32); + bitlen -= 32; + + if (xing->flags & XING_FRAMES) { + if (bitlen < 32) goto fail; + xing->frames = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + if (xing->flags & XING_BYTES) { + if (bitlen < 32) goto fail; + xing->bytes = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + if (xing->flags & XING_TOC) { + if (bitlen < 800) goto fail; + for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8); + bitlen -= 800; + } + + if (xing->flags & XING_SCALE) { + if (bitlen < 32) goto fail; + xing->scale = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + /* Make sure we consume no less than 120 bytes (960 bits) in hopes that + * the LAME tag is found there, and not right after the Xing header */ + bitsleft = 960 - ((*oldbitlen) - bitlen); + if (bitsleft < 0) goto fail; + else if (bitsleft > 0) { + mad_bit_read(ptr, bitsleft); + bitlen -= bitsleft; + } + + *oldbitlen = bitlen; + + return 1; +fail: + xing->flags = 0; + return 0; +} + +static int parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) +{ + int i; + + /* Unlike the xing header, the lame tag has a fixed length. Fail if + * not all 36 bytes (288 bits) are there. */ + if (*bitlen < 288) return 0; + + for (i = 0; i < 9; i++) lame->encoder[i] = (char)mad_bit_read(ptr, 8); + lame->encoder[9] = '\0'; + + /* This is technically incorrect, since the encoder might not be lame. + * But there's no other way to determine if this is a lame tag, and we + * wouldn't want to go reading a tag that's not there. */ + if (strncmp(lame->encoder, "LAME", 4) != 0) return 0; + +#if 0 + /* Apparently lame versions <3.97b1 do not calculate replaygain. I'm + * using lame 3.97b2, and while it does calculate replaygain, it's + * setting the values to 0. Using --replaygain-(fast|accurate) doesn't + * make any difference. Leaving this code unused until we have a way + * of testing it. -- jat */ + + mad_bit_read(ptr, 16); + + mad_bit_read(ptr, 32); /* peak */ + + mad_bit_read(ptr, 6); /* header */ + bits = mad_bit_read(ptr, 1); /* sign bit */ + lame->trackGain = mad_bit_read(ptr, 9); /* gain*10 */ + lame->trackGain = (bits ? -lame->trackGain : lame->trackGain) / 10; + + mad_bit_read(ptr, 6); /* header */ + bits = mad_bit_read(ptr, 1); /* sign bit */ + lame->albumGain = mad_bit_read(ptr, 9); /* gain*10 */ + lame->albumGain = (bits ? -lame->albumGain : lame->albumGain) / 10; + + mad_bit_read(ptr, 16); +#else + mad_bit_read(ptr, 96); +#endif + + lame->encoderDelay = mad_bit_read(ptr, 12); + lame->encoderPadding = mad_bit_read(ptr, 12); + + mad_bit_read(ptr, 96); + + *bitlen -= 288; + + return 1; +} + +static int decodeFirstFrame(mp3DecodeData * data, DecoderControl * dc, + MpdTag ** tag, ReplayGainInfo ** replayGainInfo) +{ + struct xing xing; + struct lame lame; + struct mad_bitptr ptr; + int bitlen; + int ret; + + /* stfu gcc */ + memset(&xing, 0, sizeof(struct xing)); + xing.flags = 0; + + while (1) { + while ((ret = decodeNextFrameHeader(data, tag, replayGainInfo)) == DECODE_CONT && + (!dc || !dc->stop)); + if (ret == DECODE_BREAK || (dc && dc->stop)) return -1; + if (ret == DECODE_SKIP) continue; + + while ((ret = decodeNextFrame(data)) == DECODE_CONT && + (!dc || !dc->stop)); + if (ret == DECODE_BREAK || (dc && dc->stop)) return -1; + if (ret == DECODE_OK) break; + } + + ptr = data->stream.anc_ptr; + bitlen = data->stream.anc_bitlen; + + /* + * Attempt to calulcate the length of the song from filesize + */ + { + size_t offset = data->inStream->offset; + mad_timer_t duration = data->frame.header.duration; + float frameTime = ((float)mad_timer_count(duration, + MAD_UNITS_MILLISECONDS)) / 1000; + + if (data->stream.this_frame != NULL) + offset -= data->stream.bufend - data->stream.this_frame; + else + offset -= data->stream.bufend - data->stream.buffer; + + if (data->inStream->size >= offset) { + data->totalTime = ((data->inStream->size - offset) * + 8.0) / (data->frame).header.bitrate; + data->maxFrames = data->totalTime / frameTime + + FRAMES_CUSHION; + } else { + data->maxFrames = FRAMES_CUSHION; + data->totalTime = 0; + } + } + /* + * if an xing tag exists, use that! + */ + if (parse_xing(&xing, &ptr, &bitlen)) { + data->foundXing = 1; + data->muteFrame = MUTEFRAME_SKIP; + + if (gaplessPlayback && data->inStream->seekable && + parse_lame(&lame, &ptr, &bitlen)) { + data->dropSamplesAtStart = lame.encoderDelay + DECODERDELAY; + data->dropSamplesAtEnd = lame.encoderPadding; + } + + if ((xing.flags & XING_FRAMES) && xing.frames) { + mad_timer_t duration = data->frame.header.duration; + mad_timer_multiply(&duration, xing.frames); + data->totalTime = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000; + data->maxFrames = xing.frames; + } + } + + if (!data->maxFrames) return -1; + + data->frameOffset = xmalloc(sizeof(long) * data->maxFrames); + data->times = xmalloc(sizeof(mad_timer_t) * data->maxFrames); + + return 0; +} + +static void mp3DecodeDataFinalize(mp3DecodeData * data) +{ + mad_synth_finish(&data->synth); + mad_frame_finish(&data->frame); + mad_stream_finish(&data->stream); + + if (data->frameOffset) free(data->frameOffset); + if (data->times) free(data->times); +} + +/* this is primarily used for getting total time for tags */ +static int getMp3TotalTime(char *file) +{ + InputStream inStream; + mp3DecodeData data; + int ret; + + if (openInputStream(&inStream, file) < 0) + return -1; + initMp3DecodeData(&data, &inStream); + if (decodeFirstFrame(&data, NULL, NULL, NULL) < 0) + ret = -1; + else + ret = data.totalTime + 0.5; + mp3DecodeDataFinalize(&data); + closeInputStream(&inStream); + + return ret; +} + +static int openMp3FromInputStream(InputStream * inStream, mp3DecodeData * data, + DecoderControl * dc, MpdTag ** tag, + ReplayGainInfo ** replayGainInfo) +{ + initMp3DecodeData(data, inStream); + *tag = NULL; + if (decodeFirstFrame(data, dc, tag, replayGainInfo) < 0) { + mp3DecodeDataFinalize(data); + if (tag && *tag) + freeMpdTag(*tag); + return -1; + } + + return 0; +} + +static int mp3Read(mp3DecodeData * data, OutputBuffer * cb, DecoderControl * dc, + ReplayGainInfo ** replayGainInfo) +{ + int samplesPerFrame; + int samplesLeft; + int i; + int ret; + int skip; + + if (data->currentFrame >= data->highestFrame) { + mad_timer_add(&data->timer, (data->frame).header.duration); + data->bitRate = (data->frame).header.bitrate; + if (data->currentFrame >= data->maxFrames) { + data->currentFrame = data->maxFrames - 1; + } else { + data->highestFrame++; + } + data->frameOffset[data->currentFrame] = data->inStream->offset; + if (data->stream.this_frame != NULL) { + data->frameOffset[data->currentFrame] -= + data->stream.bufend - data->stream.this_frame; + } else { + data->frameOffset[data->currentFrame] -= + data->stream.bufend - data->stream.buffer; + } + data->times[data->currentFrame] = data->timer; + } else { + data->timer = data->times[data->currentFrame]; + } + data->currentFrame++; + data->elapsedTime = + ((float)mad_timer_count(data->timer, MAD_UNITS_MILLISECONDS)) / + 1000; + + switch (data->muteFrame) { + case MUTEFRAME_SKIP: + data->muteFrame = 0; + break; + case MUTEFRAME_SEEK: + if (dc->seekWhere <= data->elapsedTime) { + data->outputPtr = data->outputBuffer; + clearOutputBuffer(cb); + data->muteFrame = 0; + dc->seek = 0; + } + break; + default: + mad_synth_frame(&data->synth, &data->frame); + + if (!data->foundFirstFrame) { + samplesPerFrame = (data->synth).pcm.length; + data->dropFramesAtStart = data->dropSamplesAtStart / samplesPerFrame; + data->dropFramesAtEnd = data->dropSamplesAtEnd / samplesPerFrame; + data->dropSamplesAtStart = data->dropSamplesAtStart % samplesPerFrame; + data->dropSamplesAtEnd = data->dropSamplesAtEnd % samplesPerFrame; + data->foundFirstFrame = 1; + } + + if (data->dropFramesAtStart > 0) { + data->dropFramesAtStart--; + break; + } else if ((data->dropFramesAtEnd > 0) && + (data->currentFrame == (data->maxFrames + 1 - data->dropFramesAtEnd))) { + /* stop decoding, effectively dropping all remaining + * frames */ + return DECODE_BREAK; + } + + if (data->inStream->metaTitle) { + MpdTag *tag = newMpdTag(); + if (data->inStream->metaName) { + addItemToMpdTag(tag, + TAG_ITEM_NAME, + data->inStream->metaName); + } + addItemToMpdTag(tag, TAG_ITEM_TITLE, + data->inStream->metaTitle); + free(data->inStream->metaTitle); + data->inStream->metaTitle = NULL; + copyMpdTagToOutputBuffer(cb, tag); + freeMpdTag(tag); + } + + samplesLeft = (data->synth).pcm.length; + + for (i = 0; i < (data->synth).pcm.length; i++) { + mpd_sint16 *sample; + + samplesLeft--; + + if (!data->decodedFirstFrame && + (i < data->dropSamplesAtStart)) { + continue; + } else if (data->dropSamplesAtEnd && + (data->currentFrame == (data->maxFrames - data->dropFramesAtEnd)) && + (samplesLeft < data->dropSamplesAtEnd)) { + /* stop decoding, effectively dropping + * all remaining samples */ + return DECODE_BREAK; + } + + sample = (mpd_sint16 *) data->outputPtr; + *sample = (mpd_sint16) audio_linear_dither(16, + (data->synth).pcm.samples[0][i], + &(data->dither)); + data->outputPtr += 2; + + if (MAD_NCHANNELS(&(data->frame).header) == 2) { + sample = (mpd_sint16 *) data->outputPtr; + *sample = (mpd_sint16) audio_linear_dither(16, + (data->synth).pcm.samples[1][i], + &(data->dither)); + data->outputPtr += 2; + } + + if (data->outputPtr >= data->outputBufferEnd) { + ret = sendDataToOutputBuffer(cb, + data->inStream, + dc, + data->inStream->seekable, + data->outputBuffer, + data->outputPtr - data->outputBuffer, + data->elapsedTime, + data->bitRate / 1000, + (replayGainInfo != NULL) ? *replayGainInfo : NULL); + if (ret == OUTPUT_BUFFER_DC_STOP) { + data->flush = 0; + return DECODE_BREAK; + } + + data->outputPtr = data->outputBuffer; + + if (ret == OUTPUT_BUFFER_DC_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; + clearOutputBuffer(cb); + data->currentFrame = j; + } else + dc->seekError = 1; + data->muteFrame = 0; + dc->seek = 0; + } + } else if (dc->seek && !data->inStream->seekable) { + dc->seek = 0; + dc->seekError = 1; + } + } + + while (1) { + skip = 0; + while ((ret = + decodeNextFrameHeader(data, NULL, + replayGainInfo)) == DECODE_CONT + && !dc->stop) ; + if (ret == DECODE_BREAK || dc->stop || 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) + break; + } + if (!skip && ret == DECODE_OK) + break; + } + + if (dc->stop) + return DECODE_BREAK; + + return ret; +} + +static void initAudioFormatFromMp3DecodeData(mp3DecodeData * data, + AudioFormat * af) +{ + af->bits = 16; + af->sampleRate = (data->frame).header.samplerate; + af->channels = MAD_NCHANNELS(&(data->frame).header); +} + +static int mp3_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + mp3DecodeData data; + MpdTag *tag = NULL; + ReplayGainInfo *replayGainInfo = NULL; + + if (openMp3FromInputStream(inStream, &data, dc, &tag, &replayGainInfo) < + 0) { + closeInputStream(inStream); + if (!dc->stop) { + ERROR + ("Input does not appear to be a mp3 bit stream.\n"); + return -1; + } else { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } + return 0; + } + + initAudioFormatFromMp3DecodeData(&data, &(dc->audioFormat)); + getOutputAudioFormat(&(dc->audioFormat), &(cb->audioFormat)); + + dc->totalTime = data.totalTime; + + if (inStream->metaTitle) { + if (tag) + freeMpdTag(tag); + tag = newMpdTag(); + addItemToMpdTag(tag, TAG_ITEM_TITLE, inStream->metaTitle); + free(inStream->metaTitle); + inStream->metaTitle = NULL; + if (inStream->metaName) { + addItemToMpdTag(tag, TAG_ITEM_NAME, inStream->metaName); + } + copyMpdTagToOutputBuffer(cb, tag); + freeMpdTag(tag); + } else if (tag) { + if (inStream->metaName) { + clearItemsFromMpdTag(tag, TAG_ITEM_NAME); + addItemToMpdTag(tag, TAG_ITEM_NAME, inStream->metaName); + } + copyMpdTagToOutputBuffer(cb, tag); + freeMpdTag(tag); + } else if (inStream->metaName) { + tag = newMpdTag(); + if (inStream->metaName) { + addItemToMpdTag(tag, TAG_ITEM_NAME, inStream->metaName); + } + copyMpdTagToOutputBuffer(cb, tag); + freeMpdTag(tag); + } + + dc->state = DECODE_STATE_DECODE; + + while (mp3Read(&data, cb, dc, &replayGainInfo) != DECODE_BREAK) ; + /* send last little bit if not dc->stop */ + if (!dc->stop && data.outputPtr != data.outputBuffer && data.flush) { + sendDataToOutputBuffer(cb, NULL, dc, + data.inStream->seekable, + data.outputBuffer, + data.outputPtr - data.outputBuffer, + data.elapsedTime, data.bitRate / 1000, + replayGainInfo); + } + + if (replayGainInfo) + freeReplayGainInfo(replayGainInfo); + + closeInputStream(inStream); + + if (dc->seek && data.muteFrame == MUTEFRAME_SEEK) { + clearOutputBuffer(cb); + dc->seek = 0; + } + + flushOutputBuffer(cb); + mp3DecodeDataFinalize(&data); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *mp3_tagDup(char *file) +{ + MpdTag *ret = NULL; + int time; + + ret = id3Dup(file); + + time = getMp3TotalTime(file); + + if (time >= 0) { + if (!ret) + ret = newMpdTag(); + ret->time = time; + } else { + DEBUG("mp3_tagDup: Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static char *mp3_suffixes[] = { "mp3", "mp2", NULL }; +static char *mp3_mimeTypes[] = { "audio/mpeg", NULL }; + +InputPlugin mp3Plugin = { + "mp3", + mp3_plugin_init, + NULL, + NULL, + mp3_decode, + NULL, + mp3_tagDup, + INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, + mp3_suffixes, + mp3_mimeTypes +}; +#else + +InputPlugin mp3Plugin; + +#endif /* HAVE_MAD */ diff --git a/trunk/src/inputPlugins/mp4_plugin.c b/trunk/src/inputPlugins/mp4_plugin.c new file mode 100644 index 000000000..1ebf556c6 --- /dev/null +++ b/trunk/src/inputPlugins/mp4_plugin.c @@ -0,0 +1,455 @@ +/* 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 "../inputPlugin.h" + +#ifdef HAVE_FAAD + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../decode.h" + +#include "../mp4ff/mp4ff.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <faad.h> + +/* all code here is either based on or copied from FAAD2's frontend code */ + +static int mp4_getAACTrack(mp4ff_t * infile) +{ + /* find AAC track */ + int i, rc; + int numTracks = mp4ff_total_tracks(infile); + + for (i = 0; i < numTracks; i++) { + unsigned char *buff = NULL; + unsigned int buff_size = 0; +#ifdef HAVE_MP4AUDIOSPECIFICCONFIG + mp4AudioSpecificConfig mp4ASC; +#else + unsigned long dummy1_32; + unsigned char dummy2_8, dummy3_8, dummy4_8, dummy5_8, dummy6_8, + dummy7_8, dummy8_8; +#endif + + mp4ff_get_decoder_config(infile, i, &buff, &buff_size); + + if (buff) { +#ifdef HAVE_MP4AUDIOSPECIFICCONFIG + rc = AudioSpecificConfig(buff, buff_size, &mp4ASC); +#else + rc = AudioSpecificConfig(buff, &dummy1_32, &dummy2_8, + &dummy3_8, &dummy4_8, + &dummy5_8, &dummy6_8, + &dummy7_8, &dummy8_8); +#endif + free(buff); + if (rc < 0) + continue; + return i; + } + } + + /* can't decode this */ + return -1; +} + +static uint32_t mp4_inputStreamReadCallback(void *inStream, void *buffer, + uint32_t length) +{ + return readFromInputStream((InputStream *) inStream, buffer, 1, length); +} + +static uint32_t mp4_inputStreamSeekCallback(void *inStream, uint64_t position) +{ + return seekInputStream((InputStream *) inStream, position, SEEK_SET); +} + +static int mp4_decode(OutputBuffer * cb, DecoderControl * dc, char *path) +{ + mp4ff_t *mp4fh; + mp4ff_callback_t *mp4cb; + int32_t track; + float time; + int32_t scale; + faacDecHandle decoder; + faacDecFrameInfo frameInfo; + faacDecConfigurationPtr config; + unsigned char *mp4Buffer; + unsigned int mp4BufferSize; + unsigned long sampleRate; + unsigned char channels; + long sampleId; + long numSamples; + int eof = 0; + long dur; + unsigned int sampleCount; + char *sampleBuffer; + size_t sampleBufferLen; + unsigned int initial = 1; + float *seekTable; + long seekTableEnd = -1; + int seekPositionFound = 0; + long offset; + mpd_uint16 bitRate = 0; + InputStream inStream; + int seeking = 0; + + if (openInputStream(&inStream, path) < 0) { + ERROR("failed to open %s\n", path); + return -1; + } + + mp4cb = xmalloc(sizeof(mp4ff_callback_t)); + mp4cb->read = mp4_inputStreamReadCallback; + mp4cb->seek = mp4_inputStreamSeekCallback; + mp4cb->user_data = &inStream; + + mp4fh = mp4ff_open_read(mp4cb); + if (!mp4fh) { + ERROR("Input does not appear to be a mp4 stream.\n"); + free(mp4cb); + closeInputStream(&inStream); + return -1; + } + + track = mp4_getAACTrack(mp4fh); + if (track < 0) { + ERROR("No AAC track found in mp4 stream.\n"); + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(mp4cb); + return -1; + } + + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; +#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX + config->downMatrix = 1; +#endif +#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR + config->dontUpSampleImplicitSBR = 0; +#endif + faacDecSetConfiguration(decoder, config); + + dc->audioFormat.bits = 16; + + mp4Buffer = NULL; + mp4BufferSize = 0; + mp4ff_get_decoder_config(mp4fh, track, &mp4Buffer, &mp4BufferSize); + + if (faacDecInit2 + (decoder, mp4Buffer, mp4BufferSize, &sampleRate, &channels) < 0) { + ERROR("Error not a AAC stream.\n"); + faacDecClose(decoder); + mp4ff_close(mp4fh); + free(mp4cb); + closeInputStream(&inStream); + return -1; + } + + dc->audioFormat.sampleRate = sampleRate; + dc->audioFormat.channels = channels; + time = mp4ff_get_track_duration_use_offsets(mp4fh, track); + scale = mp4ff_time_scale(mp4fh, track); + + if (mp4Buffer) + free(mp4Buffer); + + if (scale < 0) { + ERROR("Error getting audio format of mp4 AAC track.\n"); + faacDecClose(decoder); + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(mp4cb); + return -1; + } + dc->totalTime = ((float)time) / scale; + + numSamples = mp4ff_num_samples(mp4fh, track); + + time = 0.0; + + seekTable = xmalloc(sizeof(float) * numSamples); + + for (sampleId = 0; sampleId < numSamples && !eof; sampleId++) { + if (dc->seek) + seeking = 1; + + if (seeking && seekTableEnd > 1 && + seekTable[seekTableEnd] >= dc->seekWhere) { + int i = 2; + while (seekTable[i] < dc->seekWhere) + i++; + sampleId = i - 1; + time = seekTable[sampleId]; + } + + dur = mp4ff_get_sample_duration(mp4fh, track, sampleId); + offset = mp4ff_get_sample_offset(mp4fh, track, sampleId); + + if (sampleId > seekTableEnd) { + seekTable[sampleId] = time; + seekTableEnd = sampleId; + } + + if (sampleId == 0) + dur = 0; + if (offset > dur) + dur = 0; + else + dur -= offset; + time += ((float)dur) / scale; + + if (seeking && time > dc->seekWhere) + seekPositionFound = 1; + + if (seeking && seekPositionFound) { + seekPositionFound = 0; + clearOutputBuffer(cb); + seeking = 0; + dc->seek = 0; + } + + if (seeking) + continue; + + if (mp4ff_read_sample(mp4fh, track, sampleId, &mp4Buffer, + &mp4BufferSize) == 0) { + eof = 1; + continue; + } +#ifdef HAVE_FAAD_BUFLEN_FUNCS + sampleBuffer = faacDecDecode(decoder, &frameInfo, mp4Buffer, + mp4BufferSize); +#else + sampleBuffer = faacDecDecode(decoder, &frameInfo, mp4Buffer); +#endif + + if (mp4Buffer) + free(mp4Buffer); + if (frameInfo.error > 0) { + ERROR("error decoding MP4 file: %s\n", path); + ERROR("faad2 error: %s\n", + faacDecGetErrorMessage(frameInfo.error)); + eof = 1; + break; + } + + if (dc->state != DECODE_STATE_DECODE) { + channels = frameInfo.channels; +#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE + scale = frameInfo.samplerate; +#endif + dc->audioFormat.sampleRate = scale; + dc->audioFormat.channels = frameInfo.channels; + getOutputAudioFormat(&(dc->audioFormat), + &(cb->audioFormat)); + dc->state = DECODE_STATE_DECODE; + } + + if (channels * (dur + offset) > frameInfo.samples) { + dur = frameInfo.samples / channels; + offset = 0; + } + + sampleCount = (unsigned long)(dur * channels); + + if (sampleCount > 0) { + initial = 0; + bitRate = frameInfo.bytesconsumed * 8.0 * + frameInfo.channels * scale / + frameInfo.samples / 1000 + 0.5; + } + + sampleBufferLen = sampleCount * 2; + + sampleBuffer += offset * channels * 2; + + sendDataToOutputBuffer(cb, NULL, dc, 1, sampleBuffer, + sampleBufferLen, time, bitRate, NULL); + if (dc->stop) { + eof = 1; + break; + } + } + + free(seekTable); + faacDecClose(decoder); + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(mp4cb); + + if (dc->state != DECODE_STATE_DECODE) + return -1; + + if (dc->seek && seeking) { + clearOutputBuffer(cb); + dc->seek = 0; + } + flushOutputBuffer(cb); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *mp4DataDup(char *file, int *mp4MetadataFound) +{ + MpdTag *ret = NULL; + InputStream inStream; + mp4ff_t *mp4fh; + mp4ff_callback_t *cb; + int32_t track; + int32_t time; + int32_t scale; + int i; + + *mp4MetadataFound = 0; + + if (openInputStream(&inStream, file) < 0) { + DEBUG("mp4DataDup: Failed to open file: %s\n", file); + return NULL; + } + + cb = xmalloc(sizeof(mp4ff_callback_t)); + cb->read = mp4_inputStreamReadCallback; + cb->seek = mp4_inputStreamSeekCallback; + cb->user_data = &inStream; + + mp4fh = mp4ff_open_read(cb); + if (!mp4fh) { + free(cb); + closeInputStream(&inStream); + return NULL; + } + + track = mp4_getAACTrack(mp4fh); + if (track < 0) { + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(cb); + return NULL; + } + + ret = newMpdTag(); + time = mp4ff_get_track_duration_use_offsets(mp4fh, track); + scale = mp4ff_time_scale(mp4fh, track); + if (scale < 0) { + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(cb); + freeMpdTag(ret); + return NULL; + } + ret->time = ((float)time) / scale + 0.5; + + for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) { + char *item; + char *value; + + mp4ff_meta_get_by_index(mp4fh, i, &item, &value); + + if (0 == strcasecmp("artist", item)) { + addItemToMpdTag(ret, TAG_ITEM_ARTIST, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("title", item)) { + addItemToMpdTag(ret, TAG_ITEM_TITLE, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("album", item)) { + addItemToMpdTag(ret, TAG_ITEM_ALBUM, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("track", item)) { + addItemToMpdTag(ret, TAG_ITEM_TRACK, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("disc", item)) { /* Is that the correct id? */ + addItemToMpdTag(ret, TAG_ITEM_DISC, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("genre", item)) { + addItemToMpdTag(ret, TAG_ITEM_GENRE, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("date", item)) { + addItemToMpdTag(ret, TAG_ITEM_DATE, value); + *mp4MetadataFound = 1; + } + + free(item); + free(value); + } + + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(cb); + + return ret; +} + +static MpdTag *mp4TagDup(char *file) +{ + MpdTag *ret = NULL; + int mp4MetadataFound = 0; + + ret = mp4DataDup(file, &mp4MetadataFound); + if (!ret) + return NULL; + if (!mp4MetadataFound) { + MpdTag *temp = id3Dup(file); + if (temp) { + temp->time = ret->time; + freeMpdTag(ret); + ret = temp; + } + } + + return ret; +} + +static char *mp4Suffixes[] = { "m4a", "mp4", NULL }; + +InputPlugin mp4Plugin = { + "mp4", + NULL, + NULL, + NULL, + NULL, + mp4_decode, + mp4TagDup, + INPUT_PLUGIN_STREAM_FILE, + mp4Suffixes, + NULL +}; + +#else + +InputPlugin mp4Plugin; + +#endif /* HAVE_FAAD */ diff --git a/trunk/src/inputPlugins/mpc_plugin.c b/trunk/src/inputPlugins/mpc_plugin.c new file mode 100644 index 000000000..885f6cfc9 --- /dev/null +++ b/trunk/src/inputPlugins/mpc_plugin.c @@ -0,0 +1,359 @@ +/* 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 "../inputPlugin.h" + +#ifdef HAVE_MPCDEC + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../replayGain.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <mpcdec/mpcdec.h> +#include <errno.h> +#include <math.h> + +typedef struct _MpcCallbackData { + InputStream *inStream; + DecoderControl *dc; +} MpcCallbackData; + +static mpc_int32_t mpc_read_cb(void *vdata, void *ptr, mpc_int32_t size) +{ + mpc_int32_t ret = 0; + MpcCallbackData *data = (MpcCallbackData *) vdata; + + while (1) { + ret = readFromInputStream(data->inStream, ptr, 1, size); + if (ret == 0 && !inputStreamAtEOF(data->inStream) && + (data->dc && !data->dc->stop)) { + my_usleep(10000); + } else + break; + } + + return ret; +} + +static mpc_bool_t mpc_seek_cb(void *vdata, mpc_int32_t offset) +{ + MpcCallbackData *data = (MpcCallbackData *) vdata; + + return seekInputStream(data->inStream, offset, SEEK_SET) < 0 ? 0 : 1; +} + +static mpc_int32_t mpc_tell_cb(void *vdata) +{ + MpcCallbackData *data = (MpcCallbackData *) vdata; + + return (long)(data->inStream->offset); +} + +static mpc_bool_t mpc_canseek_cb(void *vdata) +{ + MpcCallbackData *data = (MpcCallbackData *) vdata; + + return data->inStream->seekable; +} + +static mpc_int32_t mpc_getsize_cb(void *vdata) +{ + MpcCallbackData *data = (MpcCallbackData *) vdata; + + return data->inStream->size; +} + +/* this _looks_ performance-critical, don't de-inline -- eric */ +static inline mpd_sint16 convertSample(MPC_SAMPLE_FORMAT sample) +{ + /* only doing 16-bit audio for now */ + mpd_sint32 val; + + const int clip_min = -1 << (16 - 1); + const int clip_max = (1 << (16 - 1)) - 1; + +#ifdef MPC_FIXED_POINT + const int shift = 16 - MPC_FIXED_POINT_SCALE_SHIFT; + + if (sample > 0) { + sample <<= shift; + } else if (shift < 0) { + sample >>= -shift; + } + val = sample; +#else + const int float_scale = 1 << (16 - 1); + + val = sample * float_scale; +#endif + + if (val < clip_min) + val = clip_min; + else if (val > clip_max) + val = clip_max; + + return val; +} + +static int mpc_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + mpc_decoder decoder; + mpc_reader reader; + mpc_streaminfo info; + + MpcCallbackData data; + + MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; + + int eof = 0; + long ret; +#define MPC_CHUNK_SIZE 4096 + char chunk[MPC_CHUNK_SIZE]; + int chunkpos = 0; + long bitRate = 0; + mpd_sint16 *s16 = (mpd_sint16 *) chunk; + unsigned long samplePos = 0; + mpc_uint32_t vbrUpdateAcc; + mpc_uint32_t vbrUpdateBits; + float time; + int i; + ReplayGainInfo *replayGainInfo = NULL; + + data.inStream = inStream; + data.dc = dc; + + reader.read = mpc_read_cb; + reader.seek = mpc_seek_cb; + reader.tell = mpc_tell_cb; + reader.get_size = mpc_getsize_cb; + reader.canseek = mpc_canseek_cb; + reader.data = &data; + + mpc_streaminfo_init(&info); + + if ((ret = mpc_streaminfo_read(&info, &reader)) != ERROR_CODE_OK) { + closeInputStream(inStream); + if (!dc->stop) { + ERROR("Not a valid musepack stream"); + return -1; + } else { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } + return 0; + } + + mpc_decoder_setup(&decoder, &reader); + + if (!mpc_decoder_initialize(&decoder, &info)) { + closeInputStream(inStream); + if (!dc->stop) { + ERROR("Not a valid musepack stream"); + } else { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } + } + + dc->totalTime = mpc_streaminfo_get_length(&info); + + dc->audioFormat.bits = 16; + dc->audioFormat.channels = info.channels; + dc->audioFormat.sampleRate = info.sample_freq; + + getOutputAudioFormat(&(dc->audioFormat), &(cb->audioFormat)); + + replayGainInfo = newReplayGainInfo(); + replayGainInfo->albumGain = info.gain_album * 0.01; + replayGainInfo->albumPeak = info.peak_album / 32767.0; + 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 (mpc_decoder_seek_sample(&decoder, samplePos)) { + clearOutputBuffer(cb); + s16 = (mpd_sint16 *) chunk; + chunkpos = 0; + } else + dc->seekError = 1; + dc->seek = 0; + } + + vbrUpdateAcc = 0; + vbrUpdateBits = 0; + ret = mpc_decoder_decode(&decoder, sample_buffer, + &vbrUpdateAcc, &vbrUpdateBits); + + if (ret <= 0 || dc->stop) { + eof = 1; + break; + } + + samplePos += ret; + + /* ret is in samples, and we have stereo */ + ret *= 2; + + for (i = 0; i < ret; i++) { + /* 16 bit audio again */ + *s16 = convertSample(sample_buffer[i]); + chunkpos += 2; + s16++; + + if (chunkpos >= MPC_CHUNK_SIZE) { + time = ((float)samplePos) / + dc->audioFormat.sampleRate; + + bitRate = vbrUpdateBits * + dc->audioFormat.sampleRate / 1152 / 1000; + + sendDataToOutputBuffer(cb, inStream, dc, + inStream->seekable, + chunk, chunkpos, + time, + bitRate, replayGainInfo); + + chunkpos = 0; + s16 = (mpd_sint16 *) chunk; + if (dc->stop) { + eof = 1; + break; + } + } + } + } + + if (!dc->stop && chunkpos > 0) { + time = ((float)samplePos) / dc->audioFormat.sampleRate; + + bitRate = + vbrUpdateBits * dc->audioFormat.sampleRate / 1152 / 1000; + + sendDataToOutputBuffer(cb, NULL, dc, inStream->seekable, + chunk, chunkpos, time, bitRate, + replayGainInfo); + } + + closeInputStream(inStream); + + flushOutputBuffer(cb); + + freeReplayGainInfo(replayGainInfo); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else { + dc->state = DECODE_STATE_STOP; + } + + return 0; +} + +static float mpcGetTime(char *file) +{ + InputStream inStream; + float time = -1; + + mpc_reader reader; + mpc_streaminfo info; + MpcCallbackData data; + + data.inStream = &inStream; + data.dc = NULL; + + reader.read = mpc_read_cb; + reader.seek = mpc_seek_cb; + reader.tell = mpc_tell_cb; + reader.get_size = mpc_getsize_cb; + reader.canseek = mpc_canseek_cb; + reader.data = &data; + + mpc_streaminfo_init(&info); + + if (openInputStream(&inStream, file) < 0) { + DEBUG("mpcGetTime: Failed to open file: %s\n", file); + return -1; + } + + if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) { + closeInputStream(&inStream); + return -1; + } + + time = mpc_streaminfo_get_length(&info); + + closeInputStream(&inStream); + + return time; +} + +static MpdTag *mpcTagDup(char *file) +{ + MpdTag *ret = NULL; + float time = mpcGetTime(file); + + if (time < 0) { + DEBUG("mpcTagDup: Failed to get Songlength of file: %s\n", + file); + return NULL; + } + + ret = apeDup(file); + if (!ret) + ret = id3Dup(file); + if (!ret) + ret = newMpdTag(); + ret->time = time; + + return ret; +} + +static char *mpcSuffixes[] = { "mpc", NULL }; + +InputPlugin mpcPlugin = { + "mpc", + NULL, + NULL, + NULL, + mpc_decode, + NULL, + mpcTagDup, + INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + mpcSuffixes, + NULL +}; + +#else + +InputPlugin mpcPlugin; + +#endif /* HAVE_MPCDEC */ diff --git a/trunk/src/inputPlugins/oggflac_plugin.c b/trunk/src/inputPlugins/oggflac_plugin.c new file mode 100644 index 000000000..58eb0a5f7 --- /dev/null +++ b/trunk/src/inputPlugins/oggflac_plugin.c @@ -0,0 +1,423 @@ +/* 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 + * + * OggFLAC support (half-stolen from flac_plugin.c :)) + * (c) 2005 by Eric Wong <normalperson@yhbt.net> + * + * 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 "_flac_common.h" + +#ifdef HAVE_OGGFLAC + +#include "_ogg_common.h" + +#include "../utils.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../replayGain.h" +#include "../audio.h" + +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +static void oggflac_cleanup(InputStream * inStream, + FlacData * data, + OggFLAC__SeekableStreamDecoder * decoder) +{ + if (data->replayGainInfo) + freeReplayGainInfo(data->replayGainInfo); + if (decoder) + OggFLAC__seekable_stream_decoder_delete(decoder); + closeInputStream(inStream); +} + +static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__byte buf[], + unsigned *bytes, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + size_t r; + + while (1) { + r = readFromInputStream(data->inStream, (void *)buf, 1, *bytes); + if (r == 0 && !inputStreamAtEOF(data->inStream) && + !data->dc->stop) + my_usleep(10000); + else + break; + } + *bytes = r; + + if (r == 0 && !inputStreamAtEOF(data->inStream) && !data->dc->stop) + return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; + + return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; +} + +static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__uint64 offset, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + if (seekInputStream(data->inStream, offset, SEEK_SET) < 0) { + return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR; + } + + return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK; +} + +static OggFLAC__SeekableStreamDecoderTellStatus of_tell_cb(const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__uint64 * + offset, void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + *offset = (long)(data->inStream->offset); + + return OggFLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK; +} + +static OggFLAC__SeekableStreamDecoderLengthStatus of_length_cb(const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__uint64 * + length, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + *length = (size_t) (data->inStream->size); + + return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK; +} + +static FLAC__bool of_EOF_cb(const OggFLAC__SeekableStreamDecoder * decoder, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + if (inputStreamAtEOF(data->inStream) == 1) + return true; + return false; +} + +static void of_error_cb(const OggFLAC__SeekableStreamDecoder * decoder, + FLAC__StreamDecoderErrorStatus status, void *fdata) +{ + flac_error_common_cb("oggflac", status, (FlacData *) fdata); +} + +static void oggflacPrintErroredState(OggFLAC__SeekableStreamDecoderState state) +{ + switch (state) { + case OggFLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + ERROR("oggflac allocation error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: + ERROR("oggflac read error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: + ERROR("oggflac seek error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: + ERROR("oggflac seekable stream error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: + ERROR("oggflac decoder already initialized\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: + ERROR("invalid oggflac callback\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: + ERROR("oggflac decoder uninitialized\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_OK: + case OggFLAC__SEEKABLE_STREAM_DECODER_SEEKING: + case OggFLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: + break; + } +} + +static FLAC__StreamDecoderWriteStatus oggflacWrite(const + OggFLAC__SeekableStreamDecoder + * decoder, + const FLAC__Frame * frame, + const FLAC__int32 * + const buf[], void *vdata) +{ + FlacData *data = (FlacData *) vdata; + FLAC__uint32 samples = frame->header.blocksize; + FLAC__uint16 u16; + unsigned char *uc; + int c_samp, c_chan, d_samp; + int i; + float timeChange; + + timeChange = ((float)samples) / frame->header.sample_rate; + data->time += timeChange; + + /* ogg123 uses a complicated method of calculating bitrate + * with averaging which I'm not too fond of. + * (waste of memory/CPU cycles, especially given this is _lossless_) + * a get_decode_position() is not available in OggFLAC, either + * + * this does not give an accurate bitrate: + * (bytes_last_read was set in the read callback) + data->bitRate = ((8.0 * data->bytes_last_read * + frame->header.sample_rate) + /((float)samples * 1000)) + 0.5; + */ + + for (c_samp = d_samp = 0; c_samp < frame->header.blocksize; c_samp++) { + for (c_chan = 0; c_chan < frame->header.channels; + c_chan++, d_samp++) { + u16 = buf[c_chan][c_samp]; + uc = (unsigned char *)&u16; + for (i = 0; i < (data->dc->audioFormat.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 (data->dc->seek) { + return + FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + } + data->chunk[data->chunk_length++] = *(uc++); + } + } + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +/* used by TagDup */ +static void of_metadata_dup_cb(const OggFLAC__SeekableStreamDecoder * decoder, + const FLAC__StreamMetadata * block, void *vdata) +{ + FlacData *data = (FlacData *) vdata; + + switch (block->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + if (!data->tag) + data->tag = newMpdTag(); + data->tag->time = ((float)block->data.stream_info. + total_samples) / + block->data.stream_info.sample_rate + 0.5; + return; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + copyVorbisCommentBlockToMpdTag(block, data->tag); + default: + break; + } +} + +/* used by decode */ +static void of_metadata_decode_cb(const OggFLAC__SeekableStreamDecoder * dec, + const FLAC__StreamMetadata * block, + void *vdata) +{ + flac_metadata_common_cb(block, (FlacData *) vdata); +} + +static OggFLAC__SeekableStreamDecoder + * full_decoder_init_and_read_metadata(FlacData * data, + unsigned int metadata_only) +{ + OggFLAC__SeekableStreamDecoder *decoder = NULL; + unsigned int s = 1; + + if (!(decoder = OggFLAC__seekable_stream_decoder_new())) + return NULL; + + if (metadata_only) { + s &= OggFLAC__seekable_stream_decoder_set_metadata_callback + (decoder, of_metadata_dup_cb); + s &= OggFLAC__seekable_stream_decoder_set_metadata_respond + (decoder, FLAC__METADATA_TYPE_STREAMINFO); + } else { + s &= OggFLAC__seekable_stream_decoder_set_metadata_callback + (decoder, of_metadata_decode_cb); + } + + s &= OggFLAC__seekable_stream_decoder_set_read_callback(decoder, + of_read_cb); + s &= OggFLAC__seekable_stream_decoder_set_seek_callback(decoder, + of_seek_cb); + s &= OggFLAC__seekable_stream_decoder_set_tell_callback(decoder, + of_tell_cb); + s &= OggFLAC__seekable_stream_decoder_set_length_callback(decoder, + of_length_cb); + s &= OggFLAC__seekable_stream_decoder_set_eof_callback(decoder, + of_EOF_cb); + s &= OggFLAC__seekable_stream_decoder_set_write_callback(decoder, + oggflacWrite); + s &= OggFLAC__seekable_stream_decoder_set_metadata_respond(decoder, + FLAC__METADATA_TYPE_VORBIS_COMMENT); + s &= OggFLAC__seekable_stream_decoder_set_error_callback(decoder, + of_error_cb); + s &= OggFLAC__seekable_stream_decoder_set_client_data(decoder, + (void *)data); + + if (!s) { + ERROR("oggflac problem before init()\n"); + goto fail; + } + if (OggFLAC__seekable_stream_decoder_init(decoder) != + OggFLAC__SEEKABLE_STREAM_DECODER_OK) { + ERROR("oggflac problem doing init()\n"); + goto fail; + } + if (!OggFLAC__seekable_stream_decoder_process_until_end_of_metadata + (decoder)) { + ERROR("oggflac problem reading metadata\n"); + goto fail; + } + + return decoder; + +fail: + oggflacPrintErroredState(OggFLAC__seekable_stream_decoder_get_state + (decoder)); + OggFLAC__seekable_stream_decoder_delete(decoder); + return NULL; +} + +/* public functions: */ +static MpdTag *oggflac_TagDup(char *file) +{ + InputStream inStream; + OggFLAC__SeekableStreamDecoder *decoder; + FlacData data; + + if (openInputStream(&inStream, file) < 0) + return NULL; + if (ogg_stream_type_detect(&inStream) != FLAC) { + closeInputStream(&inStream); + return NULL; + } + + init_FlacData(&data, NULL, NULL, &inStream); + + /* errors here won't matter, + * data.tag will be set or unset, that's all we care about */ + decoder = full_decoder_init_and_read_metadata(&data, 1); + + oggflac_cleanup(&inStream, &data, decoder); + + return data.tag; +} + +static unsigned int oggflac_try_decode(InputStream * inStream) +{ + return (ogg_stream_type_detect(inStream) == FLAC) ? 1 : 0; +} + +static int oggflac_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + OggFLAC__SeekableStreamDecoder *decoder = NULL; + FlacData data; + int ret = 0; + + init_FlacData(&data, cb, dc, inStream); + + if (!(decoder = full_decoder_init_and_read_metadata(&data, 0))) { + ret = -1; + 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 (OggFLAC__seekable_stream_decoder_seek_absolute + (decoder, sampleToSeek)) { + clearOutputBuffer(cb); + data.time = ((float)sampleToSeek) / + dc->audioFormat.sampleRate; + data.position = 0; + } else + dc->seekError = 1; + dc->seek = 0; + } + } + + if (!dc->stop) { + 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) { + flacSendChunk(&data); + flushOutputBuffer(data.cb); + } + + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + +fail: + oggflac_cleanup(inStream, &data, decoder); + + return ret; +} + +static char *oggflac_Suffixes[] = { "ogg", NULL }; +static char *oggflac_mime_types[] = { "audio/x-flac+ogg", + "application/ogg", + "application/x-ogg", + NULL }; + +InputPlugin oggflacPlugin = { + "oggflac", + NULL, + NULL, + oggflac_try_decode, + oggflac_decode, + NULL, + oggflac_TagDup, + INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + oggflac_Suffixes, + oggflac_mime_types +}; + +#else /* !HAVE_FLAC */ + +InputPlugin oggflacPlugin; + +#endif /* HAVE_OGGFLAC */ diff --git a/trunk/src/inputPlugins/oggvorbis_plugin.c b/trunk/src/inputPlugins/oggvorbis_plugin.c new file mode 100644 index 000000000..4b4b87c8a --- /dev/null +++ b/trunk/src/inputPlugins/oggvorbis_plugin.c @@ -0,0 +1,434 @@ +/* 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 + */ + +/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */ + +#include "../inputPlugin.h" + +#ifdef HAVE_OGGVORBIS + +#include "_ogg_common.h" + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../replayGain.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#ifndef HAVE_TREMOR +#include <vorbis/vorbisfile.h> +#else +#include <tremor/ivorbisfile.h> +/* Macros to make Tremor's API look like libogg. Tremor always + returns host-byte-order 16-bit signed data, and uses integer + milliseconds where libogg uses double seconds. +*/ +#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ + ov_read(VF, BUFFER, LENGTH, BITSTREAM) +#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) +#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) +#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) +#endif /* HAVE_TREMOR */ + +#include <errno.h> + +#ifdef WORDS_BIGENDIAN +#define OGG_DECODE_USE_BIGENDIAN 1 +#else +#define OGG_DECODE_USE_BIGENDIAN 0 +#endif + +typedef struct _OggCallbackData { + InputStream *inStream; + DecoderControl *dc; +} OggCallbackData; + +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) +{ + size_t ret = 0; + OggCallbackData *data = (OggCallbackData *) vdata; + + while (1) { + ret = readFromInputStream(data->inStream, ptr, size, nmemb); + if (ret == 0 && !inputStreamAtEOF(data->inStream) && + !data->dc->stop) { + my_usleep(10000); + } else + break; + } + errno = 0; + /*if(ret<0) errno = ((InputStream *)inStream)->error; */ + + return ret; +} + +static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) +{ + OggCallbackData *data = (OggCallbackData *) vdata; + + return seekInputStream(data->inStream, offset, whence); +} + +static int ogg_close_cb(void *vdata) +{ + OggCallbackData *data = (OggCallbackData *) vdata; + + return closeInputStream(data->inStream); +} + +static long ogg_tell_cb(void *vdata) +{ + OggCallbackData *data = (OggCallbackData *) vdata; + + return (long)(data->inStream->offset); +} + +static char *ogg_parseComment(char *comment, char *needle) +{ + int len = strlen(needle); + + if (strncasecmp(comment, needle, len) == 0 && *(comment + len) == '=') { + return comment + len + 1; + } + + return NULL; +} + +static void ogg_getReplayGainInfo(char **comments, ReplayGainInfo ** infoPtr) +{ + char *temp; + int found = 0; + + if (*infoPtr) + freeReplayGainInfo(*infoPtr); + *infoPtr = newReplayGainInfo(); + + while (*comments) { + if ((temp = + ogg_parseComment(*comments, "replaygain_track_gain"))) { + (*infoPtr)->trackGain = atof(temp); + found = 1; + } else if ((temp = ogg_parseComment(*comments, + "replaygain_album_gain"))) { + (*infoPtr)->albumGain = atof(temp); + found = 1; + } else if ((temp = ogg_parseComment(*comments, + "replaygain_track_peak"))) { + (*infoPtr)->trackPeak = atof(temp); + found = 1; + } else if ((temp = ogg_parseComment(*comments, + "replaygain_album_peak"))) { + (*infoPtr)->albumPeak = atof(temp); + found = 1; + } + + comments++; + } + + if (!found) { + freeReplayGainInfo(*infoPtr); + *infoPtr = NULL; + } +} + +static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; +static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; + +static unsigned int ogg_parseCommentAddToTag(char *comment, + unsigned int itemType, + MpdTag ** tag) +{ + const char *needle; + unsigned int len; + switch (itemType) { + case TAG_ITEM_TRACK: + needle = VORBIS_COMMENT_TRACK_KEY; + break; + case TAG_ITEM_DISC: + needle = VORBIS_COMMENT_DISC_KEY; + break; + default: + needle = mpdTagItemKeys[itemType]; + } + len = strlen(needle); + + if (strncasecmp(comment, needle, len) == 0 && *(comment + len) == '=') { + if (!*tag) + *tag = newMpdTag(); + + addItemToMpdTag(*tag, itemType, comment + len + 1); + + return 1; + } + + return 0; +} + +static MpdTag *oggCommentsParse(char **comments) +{ + MpdTag *tag = NULL; + + while (*comments) { + int j; + for (j = TAG_NUM_OF_ITEM_TYPES; --j >= 0;) { + if (ogg_parseCommentAddToTag(*comments, j, &tag)) + break; + } + comments++; + } + + return tag; +} + +static void putOggCommentsIntoOutputBuffer(OutputBuffer * cb, char *streamName, + char **comments) +{ + MpdTag *tag; + + tag = oggCommentsParse(comments); + if (!tag && streamName) { + tag = newMpdTag(); + } + if (!tag) + return; + + /*if(tag->artist) printf("Artist: %s\n", tag->artist); + if(tag->album) printf("Album: %s\n", tag->album); + if(tag->track) printf("Track: %s\n", tag->track); + if(tag->title) printf("Title: %s\n", tag->title); */ + + if (streamName) { + clearItemsFromMpdTag(tag, TAG_ITEM_NAME); + addItemToMpdTag(tag, TAG_ITEM_NAME, streamName); + } + + copyMpdTagToOutputBuffer(cb, tag); + + freeMpdTag(tag); +} + +/* public */ +static int oggvorbis_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + OggVorbis_File vf; + ov_callbacks callbacks; + OggCallbackData data; + int current_section; + int prev_section = -1; + int eof = 0; + long ret; +#define OGG_CHUNK_SIZE 4096 + char chunk[OGG_CHUNK_SIZE]; + int chunkpos = 0; + long bitRate = 0; + long test; + ReplayGainInfo *replayGainInfo = NULL; + char **comments; + char *errorStr; + + data.inStream = inStream; + data.dc = dc; + + callbacks.read_func = ogg_read_cb; + callbacks.seek_func = ogg_seek_cb; + callbacks.close_func = ogg_close_cb; + callbacks.tell_func = ogg_tell_cb; + + if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) { + closeInputStream(inStream); + if (!dc->stop) { + switch (ret) { + case OV_EREAD: + errorStr = "read error"; + break; + case OV_ENOTVORBIS: + errorStr = "not vorbis stream"; + break; + case OV_EVERSION: + errorStr = "vorbis version mismatch"; + break; + case OV_EBADHEADER: + errorStr = "invalid vorbis header"; + break; + case OV_EFAULT: + errorStr = "internal logic error"; + break; + default: + errorStr = "unknown error"; + break; + } + ERROR("Error decoding Ogg Vorbis stream: %s\n", + errorStr); + return -1; + } else { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } + return 0; + } + + dc->totalTime = ov_time_total(&vf, -1); + if (dc->totalTime < 0) + dc->totalTime = 0; + + dc->audioFormat.bits = 16; + + while (!eof) { + if (dc->seek) { + if (0 == ov_time_seek_page(&vf, dc->seekWhere)) { + clearOutputBuffer(cb); + chunkpos = 0; + } else + dc->seekError = 1; + dc->seek = 0; + } + ret = ov_read(&vf, chunk + chunkpos, + OGG_CHUNK_SIZE - chunkpos, + OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); + + 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), + &(cb->audioFormat)); + dc->state = DECODE_STATE_DECODE; + } + comments = ov_comment(&vf, -1)->user_comments; + putOggCommentsIntoOutputBuffer(cb, inStream->metaName, + comments); + ogg_getReplayGainInfo(comments, &replayGainInfo); + } + + prev_section = current_section; + + if (ret <= 0 && ret != OV_HOLE) { + eof = 1; + break; + } + if (ret == OV_HOLE) + ret = 0; + + chunkpos += ret; + + if (chunkpos >= OGG_CHUNK_SIZE) { + if ((test = ov_bitrate_instant(&vf)) > 0) { + bitRate = test / 1000; + } + sendDataToOutputBuffer(cb, inStream, dc, + inStream->seekable, + chunk, chunkpos, + ov_pcm_tell(&vf) / + dc->audioFormat.sampleRate, + bitRate, replayGainInfo); + chunkpos = 0; + if (dc->stop) + break; + } + } + + if (!dc->stop && chunkpos > 0) { + sendDataToOutputBuffer(cb, NULL, dc, inStream->seekable, + chunk, chunkpos, + ov_time_tell(&vf), bitRate, + replayGainInfo); + } + + if (replayGainInfo) + freeReplayGainInfo(replayGainInfo); + + ov_clear(&vf); + + flushOutputBuffer(cb); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *oggvorbis_TagDup(char *file) +{ + MpdTag *ret = NULL; + FILE *fp; + OggVorbis_File vf; + + fp = fopen(file, "r"); + if (!fp) { + DEBUG("oggvorbis_TagDup: Failed to open file: '%s', %s\n", + file, strerror(errno)); + return NULL; + } + if (ov_open(fp, &vf, NULL, 0) < 0) { + fclose(fp); + return NULL; + } + + ret = oggCommentsParse(ov_comment(&vf, -1)->user_comments); + + if (!ret) + ret = newMpdTag(); + ret->time = (int)(ov_time_total(&vf, -1) + 0.5); + + ov_clear(&vf); + + return ret; +} + +static unsigned int oggvorbis_try_decode(InputStream * inStream) +{ + return (ogg_stream_type_detect(inStream) == VORBIS) ? 1 : 0; +} + +static char *oggvorbis_Suffixes[] = { "ogg", NULL }; +static char *oggvorbis_MimeTypes[] = { "application/ogg", + "audio/x-vorbis+ogg", + "application/x-ogg", + NULL }; + +InputPlugin oggvorbisPlugin = { + "oggvorbis", + NULL, + NULL, + oggvorbis_try_decode, + oggvorbis_decode, + NULL, + oggvorbis_TagDup, + INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + oggvorbis_Suffixes, + oggvorbis_MimeTypes +}; + +#else /* !HAVE_OGGVORBIS */ + +InputPlugin oggvorbisPlugin; + +#endif /* HAVE_OGGVORBIS */ diff --git a/trunk/src/inputStream.c b/trunk/src/inputStream.c new file mode 100644 index 000000000..013d75f17 --- /dev/null +++ b/trunk/src/inputStream.c @@ -0,0 +1,83 @@ +/* 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 "inputStream.h" + +#include "inputStream_file.h" +#include "inputStream_http.h" + +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> + +void initInputStream(void) +{ + inputStream_initFile(); + inputStream_initHttp(); +} + +int openInputStream(InputStream * inStream, char *url) +{ + inStream->offset = 0; + inStream->size = 0; + inStream->error = 0; + inStream->mime = NULL; + inStream->seekable = 0; + inStream->metaName = NULL; + inStream->metaTitle = NULL; + + if (inputStream_fileOpen(inStream, url) == 0) + return 0; + if (inputStream_httpOpen(inStream, url) == 0) + return 0; + + return -1; +} + +int seekInputStream(InputStream * inStream, long offset, int whence) +{ + return inStream->seekFunc(inStream, offset, whence); +} + +size_t readFromInputStream(InputStream * inStream, void *ptr, size_t size, + size_t nmemb) +{ + return inStream->readFunc(inStream, ptr, size, nmemb); +} + +int closeInputStream(InputStream * inStream) +{ + if (inStream->mime) + free(inStream->mime); + if (inStream->metaName) + free(inStream->metaName); + if (inStream->metaTitle) + free(inStream->metaTitle); + + return inStream->closeFunc(inStream); +} + +int inputStreamAtEOF(InputStream * inStream) +{ + return inStream->atEOFFunc(inStream); +} + +int bufferInputStream(InputStream * inStream) +{ + return inStream->bufferFunc(inStream); +} diff --git a/trunk/src/inputStream.h b/trunk/src/inputStream.h new file mode 100644 index 000000000..74397f07f --- /dev/null +++ b/trunk/src/inputStream.h @@ -0,0 +1,70 @@ +/* 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 INPUT_STREAM_H +#define INPUT_STREAM_H + +#include <stdlib.h> + +typedef struct _InputStream InputStream; + +typedef int (*InputStreamSeekFunc) (InputStream * inStream, long offset, + int whence); +typedef size_t(*InputStreamReadFunc) (InputStream * inStream, void *ptr, + size_t size, size_t nmemb); +typedef int (*InputStreamCloseFunc) (InputStream * inStream); +typedef int (*InputStreamAtEOFFunc) (InputStream * inStream); +typedef int (*InputStreamBufferFunc) (InputStream * inStream); + +struct _InputStream { + int error; + long offset; + size_t size; + char *mime; + int seekable; + + /* don't touc this stuff */ + InputStreamSeekFunc seekFunc; + InputStreamReadFunc readFunc; + InputStreamCloseFunc closeFunc; + InputStreamAtEOFFunc atEOFFunc; + InputStreamBufferFunc bufferFunc; + void *data; + char *metaName; + char *metaTitle; +}; + +void initInputStream(void); + +int isUrlSaneForInputStream(char *url); + +/* if an error occurs for these 3 functions, then -1 is returned and errno + for the input stream is set */ +int openInputStream(InputStream * inStream, char *url); +int seekInputStream(InputStream * inStream, long offset, int whence); +int closeInputStream(InputStream * inStream); +int inputStreamAtEOF(InputStream * inStream); + +/* return value: -1 is error, 1 inidicates stuff was buffered, 0 means nothing + was buffered */ +int bufferInputStream(InputStream * inStream); + +size_t readFromInputStream(InputStream * inStream, void *ptr, size_t size, + size_t nmemb); + +#endif diff --git a/trunk/src/inputStream_file.c b/trunk/src/inputStream_file.c new file mode 100644 index 000000000..389aaad01 --- /dev/null +++ b/trunk/src/inputStream_file.c @@ -0,0 +1,119 @@ +/* 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 "inputStream_file.h" + +#include "log.h" + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> +#define _XOPEN_SOURCE 600 +#include <fcntl.h> + +void inputStream_initFile(void) +{ +} + +int inputStream_fileOpen(InputStream * inStream, char *filename) +{ + FILE *fp; + + fp = fopen(filename, "r"); + if (!fp) { + inStream->error = errno; + return -1; + } + + inStream->seekable = 1; + + fseek(fp, 0, SEEK_END); + inStream->size = ftell(fp); + fseek(fp, 0, SEEK_SET); + +#ifdef POSIX_FADV_SEQUENTIAL + posix_fadvise(fileno(fp), (off_t)0, inStream->size, POSIX_FADV_SEQUENTIAL); +#endif + + inStream->data = fp; + inStream->seekFunc = inputStream_fileSeek; + inStream->closeFunc = inputStream_fileClose; + inStream->readFunc = inputStream_fileRead; + inStream->atEOFFunc = inputStream_fileAtEOF; + inStream->bufferFunc = inputStream_fileBuffer; + + return 0; +} + +int inputStream_fileSeek(InputStream * inStream, long offset, int whence) +{ + if (fseek((FILE *) inStream->data, offset, whence) == 0) { + inStream->offset = ftell((FILE *) inStream->data); + } else { + inStream->error = errno; + return -1; + } + + return 0; +} + +size_t inputStream_fileRead(InputStream * inStream, void *ptr, size_t size, + size_t nmemb) +{ + size_t readSize; + + readSize = fread(ptr, size, nmemb, (FILE *) inStream->data); + if (readSize <= 0 && ferror((FILE *) inStream->data)) { + inStream->error = errno; + DEBUG("inputStream_fileRead: error reading: %s\n", + strerror(inStream->error)); + } + + inStream->offset = ftell((FILE *) inStream->data); + + return readSize; +} + +int inputStream_fileClose(InputStream * inStream) +{ + if (fclose((FILE *) inStream->data) < 0) { + inStream->error = errno; + return -1; + } + + return 0; +} + +int inputStream_fileAtEOF(InputStream * inStream) +{ + if (feof((FILE *) inStream->data)) + return 1; + + if (ferror((FILE *) inStream->data) && inStream->error != EINTR) { + return 1; + } + + return 0; +} + +int inputStream_fileBuffer(InputStream * inStream) +{ + return 0; +} diff --git a/trunk/src/inputStream_file.h b/trunk/src/inputStream_file.h new file mode 100644 index 000000000..fad7ac26e --- /dev/null +++ b/trunk/src/inputStream_file.h @@ -0,0 +1,39 @@ +/* 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 INPUT_STREAM_FILE_H +#define INPUT_STREAM_FILE_H + +#include "inputStream.h" + +void inputStream_initFile(void); + +int inputStream_fileOpen(InputStream * inStream, char *filename); + +int inputStream_fileSeek(InputStream * inStream, long offset, int whence); + +size_t inputStream_fileRead(InputStream * inStream, void *ptr, size_t size, + size_t nmemb); + +int inputStream_fileClose(InputStream * inStream); + +int inputStream_fileAtEOF(InputStream * inStream); + +int inputStream_fileBuffer(InputStream * inStream); + +#endif diff --git a/trunk/src/inputStream_http.c b/trunk/src/inputStream_http.c new file mode 100644 index 000000000..3f18575dd --- /dev/null +++ b/trunk/src/inputStream_http.c @@ -0,0 +1,912 @@ +/* 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 "inputStream_http.h" + +#include "utils.h" +#include "log.h" +#include "conf.h" + +#include <stdio.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> + +#define HTTP_CONN_STATE_CLOSED 0 +#define HTTP_CONN_STATE_INIT 1 +#define HTTP_CONN_STATE_HELLO 2 +#define HTTP_CONN_STATE_OPEN 3 +#define HTTP_CONN_STATE_REOPEN 4 + +#define HTTP_BUFFER_SIZE_DEFAULT 131072 +#define HTTP_PREBUFFER_SIZE_DEFAULT (HTTP_BUFFER_SIZE_DEFAULT >> 2) + +#define HTTP_REDIRECT_MAX 10 + +static char *proxyHost; +static char *proxyPort; +static char *proxyUser; +static char *proxyPassword; +static int bufferSize = HTTP_BUFFER_SIZE_DEFAULT; +static int prebufferSize = HTTP_PREBUFFER_SIZE_DEFAULT; + +typedef struct _InputStreemHTTPData { + char *host; + char *path; + char *port; + int sock; + int connState; + char *buffer; + size_t buflen; + int timesRedirected; + int icyMetaint; + int prebuffer; + int icyOffset; + char *proxyAuth; + char *httpAuth; +} InputStreamHTTPData; + +void inputStream_initHttp(void) +{ + ConfigParam *param = getConfigParam(CONF_HTTP_PROXY_HOST); + char *test; + + if (param) { + proxyHost = param->value; + + param = getConfigParam(CONF_HTTP_PROXY_PORT); + + if (!param) { + FATAL("%s specified but not %s", CONF_HTTP_PROXY_HOST, + CONF_HTTP_PROXY_PORT); + } + proxyPort = param->value; + + param = getConfigParam(CONF_HTTP_PROXY_USER); + + if (param) { + proxyUser = param->value; + + param = getConfigParam(CONF_HTTP_PROXY_PASSWORD); + + if (!param) { + FATAL("%s specified but not %s\n", + CONF_HTTP_PROXY_USER, + CONF_HTTP_PROXY_PASSWORD); + } + + proxyPassword = param->value; + } else { + param = getConfigParam(CONF_HTTP_PROXY_PASSWORD); + + if (param) { + FATAL("%s specified but not %s\n", + CONF_HTTP_PROXY_PASSWORD, CONF_HTTP_PROXY_USER); + } + } + } else if ((param = getConfigParam(CONF_HTTP_PROXY_PORT))) { + FATAL("%s specified but not %s, line %i\n", + CONF_HTTP_PROXY_PORT, CONF_HTTP_PROXY_HOST, param->line); + } else if ((param = getConfigParam(CONF_HTTP_PROXY_USER))) { + FATAL("%s specified but not %s, line %i\n", + CONF_HTTP_PROXY_USER, CONF_HTTP_PROXY_HOST, param->line); + } else if ((param = getConfigParam(CONF_HTTP_PROXY_PASSWORD))) { + FATAL("%s specified but not %s, line %i\n", + CONF_HTTP_PROXY_PASSWORD, CONF_HTTP_PROXY_HOST, + param->line); + } + + param = getConfigParam(CONF_HTTP_BUFFER_SIZE); + + if (param) { + bufferSize = strtol(param->value, &test, 10); + + if (bufferSize <= 0 || *test != '\0') { + FATAL("\"%s\" specified for %s at line %i is not a " + "positive integer\n", + param->value, CONF_HTTP_BUFFER_SIZE, param->line); + } + + bufferSize *= 1024; + + if (prebufferSize > bufferSize) + prebufferSize = bufferSize; + } + + param = getConfigParam(CONF_HTTP_PREBUFFER_SIZE); + + if (param) { + prebufferSize = strtol(param->value, &test, 10); + + if (prebufferSize <= 0 || *test != '\0') { + FATAL("\"%s\" specified for %s at line %i is not a " + "positive integer\n", + param->value, CONF_HTTP_PREBUFFER_SIZE, + param->line); + } + + prebufferSize *= 1024; + } + + if (prebufferSize > bufferSize) + prebufferSize = bufferSize; +} + +/* base64 code taken from xmms */ + +#define BASE64_LENGTH(len) (4 * (((len) + 2) / 3)) + +static char *base64Dup(char *s) +{ + int i; + int len = strlen(s); + char *ret = xcalloc(BASE64_LENGTH(len) + 1, 1); + unsigned char *p = (unsigned char *)ret; + + char tbl[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /* Transform the 3x8 bits to 4x6 bits, as required by base64. */ + for (i = 0; i < len; i += 3) { + *p++ = tbl[s[0] >> 2]; + *p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)]; + *p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)]; + *p++ = tbl[s[2] & 0x3f]; + s += 3; + } + /* Pad the result if necessary... */ + if (i == len + 1) + *(p - 1) = '='; + else if (i == len + 2) + *(p - 1) = *(p - 2) = '='; + /* ...and zero-terminate it. */ + *p = '\0'; + + return ret; +} + +static char *authString(char *header, char *user, char *password) +{ + char *ret = NULL; + int templen; + char *temp; + char *temp64; + + if (!user || !password) + return NULL; + + templen = strlen(user) + strlen(password) + 2; + temp = xmalloc(templen); + strcpy(temp, user); + strcat(temp, ":"); + strcat(temp, password); + temp64 = base64Dup(temp); + free(temp); + + ret = xmalloc(strlen(temp64) + strlen(header) + 3); + strcpy(ret, header); + strcat(ret, temp64); + strcat(ret, "\r\n"); + free(temp64); + + return ret; +} + +#define PROXY_AUTH_HEADER "Proxy-Authorization: Basic " +#define HTTP_AUTH_HEADER "Authorization: Basic " + +#define proxyAuthString(x, y) authString(PROXY_AUTH_HEADER, x, y) +#define httpAuthString(x, y) authString(HTTP_AUTH_HEADER, x, y) + +static InputStreamHTTPData *newInputStreamHTTPData(void) +{ + InputStreamHTTPData *ret = xmalloc(sizeof(InputStreamHTTPData)); + + if (proxyHost) { + ret->proxyAuth = proxyAuthString(proxyUser, proxyPassword); + } else + ret->proxyAuth = NULL; + + ret->httpAuth = NULL; + ret->host = NULL; + ret->path = NULL; + ret->port = NULL; + ret->connState = HTTP_CONN_STATE_CLOSED; + ret->timesRedirected = 0; + ret->icyMetaint = 0; + ret->prebuffer = 0; + ret->icyOffset = 0; + ret->buffer = xmalloc(bufferSize); + + return ret; +} + +static void freeInputStreamHTTPData(InputStreamHTTPData * data) +{ + if (data->host) + free(data->host); + if (data->path) + free(data->path); + if (data->port) + free(data->port); + if (data->proxyAuth) + free(data->proxyAuth); + if (data->httpAuth) + free(data->httpAuth); + + free(data->buffer); + + free(data); +} + +static int parseUrl(InputStreamHTTPData * data, char *url) +{ + char *temp; + char *colon; + char *slash; + char *at; + int len; + + if (strncmp("http://", url, strlen("http://")) != 0) + return -1; + + temp = url + strlen("http://"); + + colon = strchr(temp, ':'); + at = strchr(temp, '@'); + + if (data->httpAuth) { + free(data->httpAuth); + data->httpAuth = NULL; + } + + if (at) { + char *user; + char *passwd; + + if (colon && colon < at) { + user = xmalloc(colon - temp + 1); + memcpy(user, temp, colon - temp); + user[colon - temp] = '\0'; + + passwd = xmalloc(at - colon); + memcpy(passwd, colon + 1, at - colon - 1); + passwd[at - colon - 1] = '\0'; + } else { + user = xmalloc(at - temp + 1); + memcpy(user, temp, at - temp); + user[at - temp] = '\0'; + + passwd = xstrdup(""); + } + + data->httpAuth = httpAuthString(user, passwd); + + free(user); + free(passwd); + + temp = at + 1; + colon = strchr(temp, ':'); + } + + slash = strchr(temp, '/'); + + if (slash && colon && slash <= colon) + return -1; + + /* fetch the host portion */ + if (colon) + len = colon - temp + 1; + else if (slash) + len = slash - temp + 1; + else + len = strlen(temp) + 1; + + if (len <= 1) + return -1; + + data->host = xmalloc(len); + memcpy(data->host, temp, len - 1); + data->host[len - 1] = '\0'; + /* fetch the port */ + if (colon && (!slash || slash != colon + 1)) { + len = strlen(colon) - 1; + if (slash) + len -= strlen(slash); + data->port = xmalloc(len + 1); + memcpy(data->port, colon + 1, len); + data->port[len] = '\0'; + DEBUG(__FILE__ ": Port: %s\n", data->port); + } else { + data->port = xstrdup("80"); + } + + /* fetch the path */ + if (proxyHost) + data->path = xstrdup(url); + else + data->path = xstrdup(slash ? slash : "/"); + + return 0; +} + +static int initHTTPConnection(InputStream * inStream) +{ + char *connHost; + char *connPort; + struct addrinfo *ans = NULL; + struct addrinfo *ap = NULL; + struct addrinfo hints; + int error, flags; + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + /** + * Setup hints + */ + hints.ai_flags = 0; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_addrlen = 0; + hints.ai_addr = NULL; + hints.ai_canonname = NULL; + hints.ai_next = NULL; + + if (proxyHost) { + connHost = proxyHost; + connPort = proxyPort; + } else { + connHost = data->host; + connPort = data->port; + } + + error = getaddrinfo(connHost, connPort, &hints, &ans); + if (error) { + DEBUG(__FILE__ ": Error getting address info: %s\n", + gai_strerror(error)); + return -1; + } + + /* loop through possible addresses */ + for (ap = ans; ap != NULL; ap = ap->ai_next) { + if ((data->sock = socket(ap->ai_family, ap->ai_socktype, + ap->ai_protocol)) < 0) { + DEBUG(__FILE__ ": unable to connect: %s\n", + strerror(errno)); + freeaddrinfo(ans); + return -1; + } + + flags = fcntl(data->sock, F_GETFL, 0); + fcntl(data->sock, F_SETFL, flags | O_NONBLOCK); + + if (connect(data->sock, ap->ai_addr, ap->ai_addrlen) >= 0 + || errno == EINPROGRESS) { + data->connState = HTTP_CONN_STATE_INIT; + data->buflen = 0; + freeaddrinfo(ans); + return 0; /* success */ + } + + /* failed, get the next one */ + + DEBUG(__FILE__ ": unable to connect: %s\n", strerror(errno)); + close(data->sock); + } + + freeaddrinfo(ans); + return -1; /* failed */ +} + +static int finishHTTPInit(InputStream * inStream) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + struct timeval tv; + fd_set writeSet; + fd_set errorSet; + int error; + socklen_t error_len = sizeof(int); + int ret; + int length; + char request[2048]; + + tv.tv_sec = 0; + tv.tv_usec = 0; + + FD_ZERO(&writeSet); + FD_ZERO(&errorSet); + FD_SET(data->sock, &writeSet); + FD_SET(data->sock, &errorSet); + + ret = select(data->sock + 1, NULL, &writeSet, &errorSet, &tv); + + if (ret == 0 || (ret < 0 && errno == EINTR)) + return 0; + + if (ret < 0) { + DEBUG(__FILE__ ": problem select'ing: %s\n", strerror(errno)); + goto close_err; + } + + getsockopt(data->sock, SOL_SOCKET, SO_ERROR, &error, &error_len); + if (error) + goto close_err; + + /* deal with ICY metadata later, for now its fucking up stuff! */ + length = snprintf(request, sizeof(request), + "GET %s HTTP/1.1\r\n" "Host: %s\r\n" + /*"Connection: close\r\n" */ + "User-Agent: %s/%s\r\n" + "Range: bytes=%ld-\r\n" + "%s" /* authorization */ + "Icy-Metadata:1\r\n" + "\r\n", + data->path, data->host, + PACKAGE_NAME, PACKAGE_VERSION, + inStream->offset, + data->proxyAuth ? data->proxyAuth : + (data->httpAuth ? data->httpAuth : "")); + + if (length >= sizeof(request)) + goto close_err; + ret = write(data->sock, request, length); + if (ret != length) + goto close_err; + + data->connState = HTTP_CONN_STATE_HELLO; + return 0; + +close_err: + close(data->sock); + data->connState = HTTP_CONN_STATE_CLOSED; + return -1; +} + +static int getHTTPHello(InputStream * inStream) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + fd_set readSet; + struct timeval tv; + int ret; + char *needle; + char *cur = data->buffer; + int rc; + long readed; + + FD_ZERO(&readSet); + FD_SET(data->sock, &readSet); + + tv.tv_sec = 0; + tv.tv_usec = 0; + + ret = select(data->sock + 1, &readSet, NULL, NULL, &tv); + + if (ret == 0 || (ret < 0 && errno == EINTR)) + return 0; + + if (ret < 0) { + data->connState = HTTP_CONN_STATE_CLOSED; + close(data->sock); + data->buflen = 0; + return -1; + } + + if (data->buflen >= bufferSize - 1) { + data->connState = HTTP_CONN_STATE_CLOSED; + close(data->sock); + return -1; + } + + readed = recv(data->sock, data->buffer + data->buflen, + bufferSize - 1 - data->buflen, 0); + + if (readed < 0 && (errno == EAGAIN || errno == EINTR)) + return 0; + + if (readed <= 0) { + data->connState = HTTP_CONN_STATE_CLOSED; + close(data->sock); + data->buflen = 0; + return -1; + } + + data->buffer[data->buflen + readed] = '\0'; + data->buflen += readed; + + needle = strstr(data->buffer, "\r\n\r\n"); + + if (!needle) + return 0; + + if (0 == strncmp(cur, "HTTP/1.0 ", 9)) { + inStream->seekable = 0; + rc = atoi(cur + 9); + } else if (0 == strncmp(cur, "HTTP/1.1 ", 9)) { + inStream->seekable = 1; + rc = atoi(cur + 9); + } else if (0 == strncmp(cur, "ICY 200 OK", 10)) { + inStream->seekable = 0; + rc = 200; + } else if (0 == strncmp(cur, "ICY 400 Server Full", 19)) + rc = 400; + else if (0 == strncmp(cur, "ICY 404", 7)) + rc = 404; + else { + close(data->sock); + data->connState = HTTP_CONN_STATE_CLOSED; + return -1; + } + + switch (rc) { + case 200: + case 206: + break; + case 301: + case 302: + cur = strstr(cur, "Location: "); + if (cur) { + char *url; + int curlen = 0; + cur += strlen("Location: "); + while (*(cur + curlen) != '\0' + && *(cur + curlen) != '\r') { + curlen++; + } + url = xmalloc(curlen + 1); + memcpy(url, cur, curlen); + url[curlen] = '\0'; + ret = parseUrl(data, url); + free(url); + if (ret == 0 && data->timesRedirected < + HTTP_REDIRECT_MAX) { + data->timesRedirected++; + close(data->sock); + data->connState = HTTP_CONN_STATE_REOPEN; + data->buflen = 0; + return 0; + } + } + case 400: + case 401: + case 403: + case 404: + default: + close(data->sock); + data->connState = HTTP_CONN_STATE_CLOSED; + data->buflen = 0; + return -1; + } + + cur = strstr(data->buffer, "\r\n"); + while (cur && cur != needle) { + if (0 == strncmp(cur, "\r\nContent-Length: ", 18)) { + if (!inStream->size) + inStream->size = atol(cur + 18); + } else if (0 == strncmp(cur, "\r\nicy-metaint:", 14)) { + data->icyMetaint = atoi(cur + 14); + } else if (0 == strncmp(cur, "\r\nicy-name:", 11) || + 0 == strncmp(cur, "\r\nice-name:", 11)) { + int incr = 11; + char *temp = strstr(cur + incr, "\r\n"); + if (!temp) + break; + *temp = '\0'; + if (inStream->metaName) + free(inStream->metaName); + while (*(incr + cur) == ' ') + incr++; + inStream->metaName = xstrdup(cur + incr); + *temp = '\r'; + DEBUG("inputStream_http: metaName: %s\n", + inStream->metaName); + } else if (0 == strncmp(cur, "\r\nx-audiocast-name:", 19)) { + int incr = 19; + char *temp = strstr(cur + incr, "\r\n"); + if (!temp) + break; + *temp = '\0'; + if (inStream->metaName) + free(inStream->metaName); + while (*(incr + cur) == ' ') + incr++; + inStream->metaName = xstrdup(cur + incr); + *temp = '\r'; + DEBUG("inputStream_http: metaName: %s\n", + inStream->metaName); + } else if (0 == strncmp(cur, "\r\nContent-Type:", 15)) { + int incr = 15; + char *temp = strstr(cur + incr, "\r\n"); + if (!temp) + break; + *temp = '\0'; + if (inStream->mime) + free(inStream->mime); + while (*(incr + cur) == ' ') + incr++; + inStream->mime = xstrdup(cur + incr); + *temp = '\r'; + } + + cur = strstr(cur + 2, "\r\n"); + } + + if (inStream->size <= 0) + inStream->seekable = 0; + + needle += 4; /* 4 == strlen("\r\n\r\n") */ + data->buflen -= (needle - data->buffer); + /*fwrite(data->buffer, 1, data->buflen, stdout); */ + memmove(data->buffer, needle, data->buflen); + + data->connState = HTTP_CONN_STATE_OPEN; + + data->prebuffer = 1; + + return 0; +} + +int inputStream_httpOpen(InputStream * inStream, char *url) +{ + InputStreamHTTPData *data = newInputStreamHTTPData(); + + inStream->data = data; + + if (parseUrl(data, url) < 0) { + freeInputStreamHTTPData(data); + return -1; + } + + if (initHTTPConnection(inStream) < 0) { + freeInputStreamHTTPData(data); + return -1; + } + + inStream->seekFunc = inputStream_httpSeek; + inStream->closeFunc = inputStream_httpClose; + inStream->readFunc = inputStream_httpRead; + inStream->atEOFFunc = inputStream_httpAtEOF; + inStream->bufferFunc = inputStream_httpBuffer; + + return 0; +} + +int inputStream_httpSeek(InputStream * inStream, long offset, int whence) +{ + InputStreamHTTPData *data; + + if (!inStream->seekable) + return -1; + + switch (whence) { + case SEEK_SET: + inStream->offset = offset; + break; + case SEEK_CUR: + inStream->offset += offset; + break; + case SEEK_END: + inStream->offset = inStream->size + offset; + break; + default: + return -1; + } + + data = (InputStreamHTTPData *)inStream->data; + close(data->sock); + data->connState = HTTP_CONN_STATE_REOPEN; + data->buflen = 0; + + inputStream_httpBuffer(inStream); + + return 0; +} + +static void parseIcyMetadata(InputStream * inStream, char *metadata, int size) +{ + char *r; + char *s; + char *temp = xmalloc(size + 1); + memcpy(temp, metadata, size); + temp[size] = '\0'; + s = strtok_r(temp, ";", &r); + while (s) { + if (0 == strncmp(s, "StreamTitle=", 12)) { + int cur = 12; + if (inStream->metaTitle) + free(inStream->metaTitle); + if (*(s + cur) == '\'') + cur++; + if (s[strlen(s) - 1] == '\'') { + s[strlen(s) - 1] = '\0'; + } + inStream->metaTitle = xstrdup(s + cur); + DEBUG("inputStream_http: metaTitle: %s\n", + inStream->metaTitle); + } + s = strtok_r(NULL, ";", &r); + } + free(temp); +} + +size_t inputStream_httpRead(InputStream * inStream, void *ptr, size_t size, + size_t nmemb) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + long tosend = 0; + long inlen = size * nmemb; + long maxToSend = data->buflen; + + inputStream_httpBuffer(inStream); + + switch (data->connState) { + case HTTP_CONN_STATE_OPEN: + if (data->prebuffer || data->buflen < data->icyMetaint) + return 0; + + break; + case HTTP_CONN_STATE_CLOSED: + if (data->buflen) + break; + default: + return 0; + } + + if (data->icyMetaint > 0) { + if (data->icyOffset >= data->icyMetaint) { + int metalen = *(data->buffer); + metalen <<= 4; + if (metalen < 0) + metalen = 0; + if (metalen + 1 > data->buflen) { + /* damn that's some fucking big metadata! */ + if (bufferSize < metalen + 1) { + data->connState = + HTTP_CONN_STATE_CLOSED; + close(data->sock); + data->buflen = 0; + } + return 0; + } + if (metalen > 0) { + parseIcyMetadata(inStream, data->buffer + 1, + metalen); + } + data->buflen -= metalen + 1; + memmove(data->buffer, data->buffer + metalen + 1, + data->buflen); + data->icyOffset = 0; + } + maxToSend = data->icyMetaint - data->icyOffset; + maxToSend = maxToSend > data->buflen ? data->buflen : maxToSend; + } + + if (data->buflen > 0) { + tosend = inlen > maxToSend ? maxToSend : inlen; + tosend = (tosend / size) * size; + + memcpy(ptr, data->buffer, tosend); + /*fwrite(ptr,1,readed,stdout); */ + data->buflen -= tosend; + data->icyOffset += tosend; + /*fwrite(data->buffer,1,readed,stdout); */ + memmove(data->buffer, data->buffer + tosend, data->buflen); + + inStream->offset += tosend; + } + + return tosend / size; +} + +int inputStream_httpClose(InputStream * inStream) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + + switch (data->connState) { + case HTTP_CONN_STATE_CLOSED: + break; + default: + close(data->sock); + } + + freeInputStreamHTTPData(data); + + return 0; +} + +int inputStream_httpAtEOF(InputStream * inStream) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + switch (data->connState) { + case HTTP_CONN_STATE_CLOSED: + if (data->buflen == 0) + return 1; + default: + return 0; + } +} + +int inputStream_httpBuffer(InputStream * inStream) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + ssize_t readed = 0; + + if (data->connState == HTTP_CONN_STATE_REOPEN) { + if (initHTTPConnection(inStream) < 0) + return -1; + } + + if (data->connState == HTTP_CONN_STATE_INIT) { + if (finishHTTPInit(inStream) < 0) + return -1; + } + + if (data->connState == HTTP_CONN_STATE_HELLO) { + if (getHTTPHello(inStream) < 0) + return -1; + } + + switch (data->connState) { + case HTTP_CONN_STATE_OPEN: + case HTTP_CONN_STATE_CLOSED: + break; + default: + return -1; + } + + if (data->buflen == 0 || data->buflen < data->icyMetaint) { + data->prebuffer = 1; + } else if (data->buflen > prebufferSize) + data->prebuffer = 0; + + if (data->connState == HTTP_CONN_STATE_OPEN && + data->buflen < bufferSize - 1) { + readed = read(data->sock, data->buffer + data->buflen, + (size_t) (bufferSize - 1 - data->buflen)); + + if (readed < 0 && (errno == EAGAIN || errno == EINTR)) { + readed = 0; + } else if (readed <= 0) { + close(data->sock); + data->connState = HTTP_CONN_STATE_CLOSED; + readed = 0; + } + /*fwrite(data->buffer+data->buflen,1,readed,stdout); */ + data->buflen += readed; + } + + if (data->buflen > prebufferSize) + data->prebuffer = 0; + + return (readed ? 1 : 0); +} diff --git a/trunk/src/inputStream_http.h b/trunk/src/inputStream_http.h new file mode 100644 index 000000000..7ab23a5de --- /dev/null +++ b/trunk/src/inputStream_http.h @@ -0,0 +1,39 @@ +/* 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 INPUT_STREAM_HTTP_H +#define INPUT_STREAM_HTTP_H + +#include "inputStream.h" + +void inputStream_initHttp(void); + +int inputStream_httpOpen(InputStream * inStream, char *filename); + +int inputStream_httpSeek(InputStream * inStream, long offset, int whence); + +size_t inputStream_httpRead(InputStream * inStream, void *ptr, size_t size, + size_t nmemb); + +int inputStream_httpClose(InputStream * inStream); + +int inputStream_httpAtEOF(InputStream * inStream); + +int inputStream_httpBuffer(InputStream * inStream); + +#endif diff --git a/trunk/src/interface.c b/trunk/src/interface.c new file mode 100644 index 000000000..22660432f --- /dev/null +++ b/trunk/src/interface.c @@ -0,0 +1,851 @@ +/* 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 "interface.h" +#include "command.h" +#include "conf.h" +#include "list.h" +#include "log.h" +#include "listen.h" +#include "playlist.h" +#include "permission.h" +#include "sllist.h" +#include "utils.h" +#include "ioops.h" + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <signal.h> + +#define GREETING "OK MPD " PROTOCOL_VERSION "\n" + +#define INTERFACE_MAX_BUFFER_LENGTH (40960) +#define INTERFACE_LIST_MODE_BEGIN "command_list_begin" +#define INTERFACE_LIST_OK_MODE_BEGIN "command_list_ok_begin" +#define INTERFACE_LIST_MODE_END "command_list_end" +#define INTERFACE_DEFAULT_OUT_BUFFER_SIZE (4096) +#define INTERFACE_TIMEOUT_DEFAULT (60) +#define INTERFACE_MAX_CONNECTIONS_DEFAULT (10) +#define INTERFACE_MAX_COMMAND_LIST_DEFAULT (2048*1024) +#define INTERFACE_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) + +/* set this to zero to indicate we have no possible interfaces */ +static int interface_max_connections; /*INTERFACE_MAX_CONNECTIONS_DEFAULT; */ +static int interface_timeout = INTERFACE_TIMEOUT_DEFAULT; +static size_t interface_max_command_list_size = + INTERFACE_MAX_COMMAND_LIST_DEFAULT; +static size_t interface_max_output_buffer_size = + INTERFACE_MAX_OUTPUT_BUFFER_SIZE_DEFAULT; + +/* List of registered external IO handlers */ +static struct ioOps *ioList; + +/* maybe make conf option for this, or... 32 might be good enough */ +static long int interface_list_cache_size = 32; + +/* shared globally between all interfaces: */ +static struct strnode *list_cache; +static struct strnode *list_cache_head; +static struct strnode *list_cache_tail; + +typedef struct _Interface { + char buffer[INTERFACE_MAX_BUFFER_LENGTH]; + int bufferLength; + int bufferPos; + int fd; /* file descriptor */ + int permission; + time_t lastTime; + struct strnode *cmd_list; /* for when in list mode */ + struct strnode *cmd_list_tail; /* for when in list mode */ + int cmd_list_OK; /* print OK after each command execution */ + int cmd_list_size; /* mem cmd_list consumes */ + int cmd_list_dup; /* has the cmd_list been copied to private space? */ + struct sllnode *deferred_send; /* for output if client is slow */ + int deferred_bytes; /* mem deferred_send consumes */ + int expired; /* set whether this interface should be closed on next + check of old interfaces */ + int num; /* interface number */ + + char *send_buf; + int send_buf_used; /* bytes used this instance */ + int send_buf_size; /* bytes usable this instance */ + int send_buf_alloc; /* bytes actually allocated */ +} Interface; + +static Interface *interfaces; + +static void flushInterfaceBuffer(Interface * interface); + +static void printInterfaceOutBuffer(Interface * interface); + +#ifdef SO_SNDBUF +static int get_default_snd_buf_size(Interface * interface) +{ + int new_size; + socklen_t sockOptLen = sizeof(int); + + if (getsockopt(interface->fd, SOL_SOCKET, SO_SNDBUF, + (char *)&new_size, &sockOptLen) < 0) { + DEBUG("problem getting sockets send buffer size\n"); + return INTERFACE_DEFAULT_OUT_BUFFER_SIZE; + } + if (new_size > 0) + return new_size; + DEBUG("sockets send buffer size is not positive\n"); + return INTERFACE_DEFAULT_OUT_BUFFER_SIZE; +} +#else /* !SO_SNDBUF */ +static int get_default_snd_buf_size(Interface * interface) +{ + return INTERFACE_DEFAULT_OUT_BUFFER_SIZE; +} +#endif /* !SO_SNDBUF */ + +static void set_send_buf_size(Interface * interface) +{ + int new_size = get_default_snd_buf_size(interface); + if (interface->send_buf_size != new_size) { + interface->send_buf_size = new_size; + /* don't resize to get smaller, only bigger */ + if (interface->send_buf_alloc < new_size) { + if (interface->send_buf) + free(interface->send_buf); + interface->send_buf = xmalloc(new_size); + interface->send_buf_alloc = new_size; + } + } +} + +static void openInterface(Interface * interface, int fd) +{ + int flags; + + assert(interface->fd < 0); + + interface->cmd_list_size = 0; + interface->cmd_list_dup = 0; + interface->cmd_list_OK = -1; + interface->bufferLength = 0; + interface->bufferPos = 0; + interface->fd = fd; + while ((flags = fcntl(fd, F_GETFL)) < 0 && errno == EINTR) ; + while (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0 && errno == EINTR) ; + interface->lastTime = time(NULL); + interface->cmd_list = NULL; + interface->cmd_list_tail = NULL; + interface->deferred_send = NULL; + interface->expired = 0; + interface->deferred_bytes = 0; + interface->send_buf_used = 0; + + interface->permission = getDefaultPermissions(); + set_send_buf_size(interface); + + xwrite(fd, GREETING, strlen(GREETING)); +} + +static void free_cmd_list(struct strnode *list) +{ + struct strnode *tmp = list; + + while (tmp) { + struct strnode *next = tmp->next; + if (tmp >= list_cache_head && tmp <= list_cache_tail) { + /* inside list_cache[] array */ + tmp->data = NULL; + tmp->next = NULL; + } else + free(tmp); + tmp = next; + } +} + +static void cmd_list_clone(Interface * interface) +{ + struct strnode *new = dup_strlist(interface->cmd_list); + free_cmd_list(interface->cmd_list); + interface->cmd_list = new; + interface->cmd_list_dup = 1; + + /* new tail */ + while (new && new->next) + new = new->next; + interface->cmd_list_tail = new; +} + +static void new_cmd_list_ptr(Interface * interface, char *s, const int size) +{ + int i; + struct strnode *new; + + if (!interface->cmd_list_dup) { + for (i = interface_list_cache_size - 1; i >= 0; --i) { + if (list_cache[i].data) + continue; + new = &(list_cache[i]); + new->data = s; + /* implied in free_cmd_list() and init: */ + /* last->next->next = NULL; */ + goto out; + } + } + + /* allocate from the heap */ + new = interface->cmd_list_dup ? new_strnode_dup(s, size) + : new_strnode(s); +out: + if (interface->cmd_list) { + interface->cmd_list_tail->next = new; + interface->cmd_list_tail = new; + } else + interface->cmd_list = interface->cmd_list_tail = new; +} + +static void closeInterface(Interface * interface) +{ + struct sllnode *buf; + if (interface->fd < 0) + return; + xclose(interface->fd); + interface->fd = -1; + + if (interface->cmd_list) { + free_cmd_list(interface->cmd_list); + interface->cmd_list = NULL; + } + + if ((buf = interface->deferred_send)) { + do { + struct sllnode *prev = buf; + buf = buf->next; + free(prev); + } while (buf); + interface->deferred_send = NULL; + } + + SECURE("interface %i: closed\n", interface->num); +} + +void openAInterface(int fd, struct sockaddr *addr) +{ + int i; + + for (i = 0; i < interface_max_connections + && interfaces[i].fd >= 0; i++) /* nothing */ ; + + if (i == interface_max_connections) { + ERROR("Max Connections Reached!\n"); + xclose(fd); + } else { + SECURE("interface %i: opened from ", i); + switch (addr->sa_family) { + case AF_INET: + { + char *host = inet_ntoa(((struct sockaddr_in *) + addr)->sin_addr); + if (host) { + SECURE("%s\n", host); + } else { + SECURE("error getting ipv4 address\n"); + } + } + break; +#ifdef HAVE_IPV6 + case AF_INET6: + { + char host[INET6_ADDRSTRLEN + 1]; + memset(host, 0, INET6_ADDRSTRLEN + 1); + if (inet_ntop(AF_INET6, (void *) + &(((struct sockaddr_in6 *)addr)-> + sin6_addr), host, + INET6_ADDRSTRLEN)) { + SECURE("%s\n", host); + } else { + SECURE("error getting ipv6 address\n"); + } + } + break; +#endif + case AF_UNIX: + SECURE("local connection\n"); + break; + default: + SECURE("unknown\n"); + } + openInterface(&(interfaces[i]), fd); + } +} + +static int processLineOfInput(Interface * interface) +{ + int ret = 1; + char *line = interface->buffer + interface->bufferPos; + + if (interface->cmd_list_OK >= 0) { + if (strcmp(line, INTERFACE_LIST_MODE_END) == 0) { + DEBUG("interface %i: process command " + "list\n", interface->num); + ret = processListOfCommands(interface->fd, + &(interface->permission), + &(interface->expired), + interface->cmd_list_OK, + interface->cmd_list); + DEBUG("interface %i: process command " + "list returned %i\n", interface->num, ret); + if (ret == 0) + commandSuccess(interface->fd); + else if (ret == COMMAND_RETURN_CLOSE + || interface->expired) + closeInterface(interface); + + printInterfaceOutBuffer(interface); + free_cmd_list(interface->cmd_list); + interface->cmd_list = NULL; + interface->cmd_list_OK = -1; + } else { + size_t len = strlen(line) + 1; + interface->cmd_list_size += len; + if (interface->cmd_list_size > + interface_max_command_list_size) { + ERROR("interface %i: command " + "list size (%i) is " + "larger than the max " + "(%li)\n", + interface->num, + interface->cmd_list_size, + (long)interface_max_command_list_size); + closeInterface(interface); + ret = COMMAND_RETURN_CLOSE; + } else + new_cmd_list_ptr(interface, line, len); + } + } else { + if (strcmp(line, INTERFACE_LIST_MODE_BEGIN) == 0) { + interface->cmd_list_OK = 0; + ret = 1; + } else if (strcmp(line, INTERFACE_LIST_OK_MODE_BEGIN) == 0) { + interface->cmd_list_OK = 1; + ret = 1; + } else { + DEBUG("interface %i: process command \"%s\"\n", + interface->num, line); + ret = processCommand(interface->fd, + &(interface->permission), line); + DEBUG("interface %i: command returned %i\n", + interface->num, ret); + if (ret == 0) + commandSuccess(interface->fd); + else if (ret == COMMAND_RETURN_CLOSE + || interface->expired) { + closeInterface(interface); + } + printInterfaceOutBuffer(interface); + } + } + + return ret; +} + +static int processBytesRead(Interface * interface, int bytesRead) +{ + int ret = 0; + char *buf_tail = &(interface->buffer[interface->bufferLength - 1]); + + while (bytesRead > 0) { + interface->bufferLength++; + bytesRead--; + buf_tail++; + if (*buf_tail == '\n') { + *buf_tail = '\0'; + if (interface->bufferLength - interface->bufferPos > 1) { + if (*(buf_tail - 1) == '\r') + *(buf_tail - 1) = '\0'; + } + ret = processLineOfInput(interface); + interface->bufferPos = interface->bufferLength; + } + if (interface->bufferLength == INTERFACE_MAX_BUFFER_LENGTH) { + if (interface->bufferPos == 0) { + ERROR("interface %i: buffer overflow\n", + interface->num); + closeInterface(interface); + return 1; + } + if (interface->cmd_list_OK >= 0 && + interface->cmd_list && + !interface->cmd_list_dup) + cmd_list_clone(interface); + interface->bufferLength -= interface->bufferPos; + memmove(interface->buffer, + interface->buffer + interface->bufferPos, + interface->bufferLength); + interface->bufferPos = 0; + } + if (ret == COMMAND_RETURN_KILL || ret == COMMAND_RETURN_CLOSE) { + return ret; + } + + } + + return ret; +} + +static int interfaceReadInput(Interface * interface) +{ + int bytesRead; + + bytesRead = read(interface->fd, + interface->buffer + interface->bufferLength, + INTERFACE_MAX_BUFFER_LENGTH - interface->bufferLength); + + if (bytesRead > 0) + return processBytesRead(interface, bytesRead); + else if (bytesRead == 0 || (bytesRead < 0 && errno != EINTR)) { + closeInterface(interface); + } else + return 0; + + return 1; +} + +static void addInterfacesReadyToReadAndListenSocketToFdSet(fd_set * fds, + int *fdmax) +{ + int i; + + FD_ZERO(fds); + addListenSocketsToFdSet(fds, fdmax); + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd >= 0 && !interfaces[i].expired + && !interfaces[i].deferred_send) { + FD_SET(interfaces[i].fd, fds); + if (*fdmax < interfaces[i].fd) + *fdmax = interfaces[i].fd; + } + } +} + +static void addInterfacesForBufferFlushToFdSet(fd_set * fds, int *fdmax) +{ + int i; + + FD_ZERO(fds); + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd >= 0 && !interfaces[i].expired + && interfaces[i].deferred_send) { + FD_SET(interfaces[i].fd, fds); + if (*fdmax < interfaces[i].fd) + *fdmax = interfaces[i].fd; + } + } +} + +static void closeNextErroredInterface(void) +{ + fd_set fds; + struct timeval tv; + int i; + + tv.tv_sec = 0; + tv.tv_usec = 0; + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd >= 0) { + FD_ZERO(&fds); + FD_SET(interfaces[i].fd, &fds); + if (select(FD_SETSIZE, &fds, NULL, NULL, &tv) < 0) { + closeInterface(&interfaces[i]); + return; + } + } + } +} + +int doIOForInterfaces(void) +{ + fd_set rfds; + fd_set wfds; + fd_set efds; + struct timeval tv; + int i; + int selret; + int fdmax; + + tv.tv_sec = 1; + tv.tv_usec = 0; + + while (1) { + fdmax = 0; + + FD_ZERO( &rfds ); + FD_ZERO( &wfds ); + FD_ZERO( &efds ); + addInterfacesReadyToReadAndListenSocketToFdSet(&rfds, &fdmax); + addInterfacesForBufferFlushToFdSet(&wfds, &fdmax); + + /* Add fds for all registered IO handlers */ + if( ioList ) { + struct ioOps *o = ioList; + while( o ) { + struct ioOps *current = o; + int fdnum; + assert( current->fdset ); + fdnum = current->fdset( &rfds, &wfds, &efds ); + if( fdmax < fdnum ) + fdmax = fdnum; + o = o->next; + } + } + + selret = select(fdmax + 1, &rfds, &wfds, &efds, &tv); + + if (selret < 0 && errno == EINTR) + break; + + /* Consume fds for all registered IO handlers */ + if( ioList ) { + struct ioOps *o = ioList; + while( o ) { + struct ioOps *current = o; + assert( current->consume ); + selret = current->consume( selret, &rfds, &wfds, &efds ); + o = o->next; + } + } + + if (selret == 0) + break; + + if (selret < 0) { + closeNextErroredInterface(); + continue; + } + + getConnections(&rfds); + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd >= 0 + && FD_ISSET(interfaces[i].fd, &rfds)) { + if (COMMAND_RETURN_KILL == + interfaceReadInput(&(interfaces[i]))) { + return COMMAND_RETURN_KILL; + } + interfaces[i].lastTime = time(NULL); + } + if (interfaces[i].fd >= 0 + && FD_ISSET(interfaces[i].fd, &wfds)) { + flushInterfaceBuffer(&interfaces[i]); + interfaces[i].lastTime = time(NULL); + } + } + + tv.tv_sec = 0; + tv.tv_usec = 0; + } + + return 1; +} + +void initInterfaces(void) +{ + int i; + char *test; + ConfigParam *param; + + param = getConfigParam(CONF_CONN_TIMEOUT); + + if (param) { + interface_timeout = strtol(param->value, &test, 10); + if (*test != '\0' || interface_timeout <= 0) { + FATAL("connection timeout \"%s\" is not a positive " + "integer, line %i\n", CONF_CONN_TIMEOUT, + param->line); + } + } + + param = getConfigParam(CONF_MAX_CONN); + + if (param) { + interface_max_connections = strtol(param->value, &test, 10); + if (*test != '\0' || interface_max_connections <= 0) { + FATAL("max connections \"%s\" is not a positive integer" + ", line %i\n", param->value, param->line); + } + } else + interface_max_connections = INTERFACE_MAX_CONNECTIONS_DEFAULT; + + param = getConfigParam(CONF_MAX_COMMAND_LIST_SIZE); + + if (param) { + interface_max_command_list_size = strtol(param->value, + &test, 10); + if (*test != '\0' || interface_max_command_list_size <= 0) { + FATAL("max command list size \"%s\" is not a positive " + "integer, line %i\n", param->value, param->line); + } + interface_max_command_list_size *= 1024; + } + + param = getConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE); + + if (param) { + interface_max_output_buffer_size = strtol(param->value, + &test, 10); + if (*test != '\0' || interface_max_output_buffer_size <= 0) { + FATAL("max output buffer size \"%s\" is not a positive " + "integer, line %i\n", param->value, param->line); + } + interface_max_output_buffer_size *= 1024; + } + + interfaces = xmalloc(sizeof(Interface) * interface_max_connections); + + list_cache = xcalloc(interface_list_cache_size, sizeof(struct strnode)); + list_cache_head = &(list_cache[0]); + list_cache_tail = &(list_cache[interface_list_cache_size - 1]); + + for (i = 0; i < interface_max_connections; i++) { + interfaces[i].fd = -1; + interfaces[i].send_buf = NULL; + interfaces[i].send_buf_size = 0; + interfaces[i].send_buf_alloc = 0; + interfaces[i].num = i; + } +} + +static void closeAllInterfaces(void) +{ + int i; + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd > 0) + closeInterface(&(interfaces[i])); + if (interfaces[i].send_buf) + free(interfaces[i].send_buf); + } + free(list_cache); +} + +void freeAllInterfaces(void) +{ + closeAllInterfaces(); + + free(interfaces); + + interface_max_connections = 0; +} + +void closeOldInterfaces(void) +{ + int i; + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd > 0) { + if (interfaces[i].expired) { + DEBUG("interface %i: expired\n", i); + closeInterface(&(interfaces[i])); + } else if (time(NULL) - interfaces[i].lastTime > + interface_timeout) { + DEBUG("interface %i: timeout\n", i); + closeInterface(&(interfaces[i])); + } + } + } +} + +static void flushInterfaceBuffer(Interface * interface) +{ + struct sllnode *buf; + int ret = 0; + + buf = interface->deferred_send; + while (buf) { + ret = write(interface->fd, buf->data, buf->size); + if (ret < 0) + break; + else if (ret < buf->size) { + interface->deferred_bytes -= ret; + buf->data = (char *)buf->data + ret; + buf->size -= ret; + } else { + struct sllnode *tmp = buf; + interface->deferred_bytes -= (buf->size + + sizeof(struct sllnode)); + buf = buf->next; + free(tmp); + interface->deferred_send = buf; + } + interface->lastTime = time(NULL); + } + + if (!interface->deferred_send) { + DEBUG("interface %i: buffer empty %i\n", interface->num, + interface->deferred_bytes); + assert(interface->deferred_bytes == 0); + } else if (ret < 0 && errno != EAGAIN && errno != EINTR) { + /* cause interface to close */ + DEBUG("interface %i: problems flushing buffer\n", + interface->num); + buf = interface->deferred_send; + do { + struct sllnode *prev = buf; + buf = buf->next; + free(prev); + } while (buf); + interface->deferred_send = NULL; + interface->deferred_bytes = 0; + interface->expired = 1; + } +} + +int interfacePrintWithFD(int fd, char *buffer, int buflen) +{ + static int i; + int copylen; + Interface *interface; + + assert(fd > 0); + + if (i >= interface_max_connections || + interfaces[i].fd < 0 || interfaces[i].fd != fd) { + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd == fd) + break; + } + if (i == interface_max_connections) + return -1; + } + + /* if fd isn't found or interfaces is going to be closed, do nothing */ + if (interfaces[i].expired) + return 0; + + interface = interfaces + i; + + while (buflen > 0 && !interface->expired) { + int left = interface->send_buf_size - interface->send_buf_used; + copylen = buflen > left ? left : buflen; + memcpy(interface->send_buf + interface->send_buf_used, buffer, + copylen); + buflen -= copylen; + interface->send_buf_used += copylen; + buffer += copylen; + if (interface->send_buf_used >= interface->send_buf_size) + printInterfaceOutBuffer(interface); + } + + return 0; +} + +static void printInterfaceOutBuffer(Interface * interface) +{ + int ret; + struct sllnode *buf; + + if (interface->fd < 0 || interface->expired || + !interface->send_buf_used) + return; + + if ((buf = interface->deferred_send)) { + interface->deferred_bytes += sizeof(struct sllnode) + + interface->send_buf_used; + if (interface->deferred_bytes > + interface_max_output_buffer_size) { + ERROR("interface %i: output buffer size (%li) is " + "larger than the max (%li)\n", + interface->num, + (long)interface->deferred_bytes, + (long)interface_max_output_buffer_size); + /* cause interface to close */ + interface->expired = 1; + do { + struct sllnode *prev = buf; + buf = buf->next; + free(prev); + } while (buf); + interface->deferred_send = NULL; + interface->deferred_bytes = 0; + } else { + while (buf->next) + buf = buf->next; + buf->next = new_sllnode(interface->send_buf, + interface->send_buf_used); + } + } else { + if ((ret = write(interface->fd, interface->send_buf, + interface->send_buf_used)) < 0) { + if (errno == EAGAIN || errno == EINTR) { + interface->deferred_send = + new_sllnode(interface->send_buf, + interface->send_buf_used); + } else { + DEBUG("interface %i: problems writing\n", + interface->num); + interface->expired = 1; + return; + } + } else if (ret < interface->send_buf_used) { + interface->deferred_send = + new_sllnode(interface->send_buf + ret, + interface->send_buf_used - ret); + } + if (interface->deferred_send) { + DEBUG("interface %i: buffer created\n", interface->num); + interface->deferred_bytes = + interface->deferred_send->size + + sizeof(struct sllnode); + } + } + + interface->send_buf_used = 0; +} + +/* From ioops.h: */ +void registerIO( struct ioOps *ops ) +{ + assert( ops != NULL ); + + ops->next = ioList; + ioList = ops; + ops->prev = NULL; + if( ops->next ) + ops->next->prev = ops; +} + +void deregisterIO( struct ioOps *ops ) +{ + assert( ops != NULL ); + + if( ioList == ops ) + ioList = ops->next; + else if( ops->prev != NULL ) + ops->prev->next = ops->next; +} diff --git a/trunk/src/interface.h b/trunk/src/interface.h new file mode 100644 index 000000000..6f3c2c070 --- /dev/null +++ b/trunk/src/interface.h @@ -0,0 +1,37 @@ +/* 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 INTERFACE_H +#define INTERFACE_H + +#include "../config.h" + +#include <stdio.h> +#include <time.h> +#include <sys/types.h> +#include <sys/socket.h> + +void initInterfaces(void); +void openAInterface(int fd, struct sockaddr *addr); +void freeAllInterfaces(void); +void closeOldInterfaces(void); +int interfacePrintWithFD(int fd, char *buffer, int len); + +int doIOForInterfaces(void); + +#endif diff --git a/trunk/src/ioops.h b/trunk/src/ioops.h new file mode 100644 index 000000000..e797a7153 --- /dev/null +++ b/trunk/src/ioops.h @@ -0,0 +1,51 @@ +/* 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 IOOPS_H +#define IOOPS_H + +#include <sys/select.h> + +struct ioOps { + struct ioOps *prev, *next; + + /* + * Called before each 'select' statement. + * To register for IO, call FD_SET for each required queue + * Return the highest fd number you registered + */ + int (*fdset) ( fd_set *rfds, fd_set *wfds, fd_set *efds ); + + /* + * Called after each 'select' statement. + * fdCount is the number of fds total in all sets. It may be 0. + * For each fd you registered for in (fdset), you should FD_CLR it from the + * appropriate queue(s). + * Return the total number of fds left in all sets (Ie, return fdCount + * minus the number of times you called FD_CLR). + */ + int (*consume) ( int fdCount, fd_set *rfds, fd_set *wfds, fd_set *efds ); +}; + +/* Call this to register your io operation handler struct */ +void registerIO( struct ioOps *ops ); + +/* Call this to deregister your io operation handler struct */ +void deregisterIO( struct ioOps *ops ); + +#endif diff --git a/trunk/src/list.c b/trunk/src/list.c new file mode 100644 index 000000000..71c30f7b6 --- /dev/null +++ b/trunk/src/list.c @@ -0,0 +1,519 @@ +/* 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 "list.h" +#include "utils.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <time.h> +#include <stdio.h> + +static void makeListNodesArray(List * list) +{ + ListNode *node = list->firstNode; + long i; + + if (!list->numberOfNodes) + return; + + list->nodesArray = xrealloc(list->nodesArray, + sizeof(ListNode *) * list->numberOfNodes); + + for (i = 0; i < list->numberOfNodes; i++) { + list->nodesArray[i] = node; + node = node->nextNode; + } +} + +static void freeListNodesArray(List * list) +{ + if (!list->nodesArray) + return; + free(list->nodesArray); + list->nodesArray = NULL; +} + +List *makeList(ListFreeDataFunc * freeDataFunc, int strdupKeys) +{ + List *list = xmalloc(sizeof(List)); + + assert(list != NULL); + + list->sorted = 0; + list->firstNode = NULL; + list->lastNode = NULL; + list->freeDataFunc = freeDataFunc; + list->numberOfNodes = 0; + list->nodesArray = NULL; + list->strdupKeys = strdupKeys; + + return list; +} + +ListNode *insertInListBeforeNode(List * list, ListNode * beforeNode, int pos, + char *key, void *data) +{ + ListNode *node; + + assert(list != NULL); + assert(key != NULL); + /*assert(data!=NULL); */ + + node = xmalloc(sizeof(ListNode)); + assert(node != NULL); + + node->nextNode = beforeNode; + if (beforeNode == list->firstNode) { + if (list->firstNode == NULL) { + assert(list->lastNode == NULL); + list->lastNode = node; + } else { + assert(list->lastNode != NULL); + assert(list->lastNode->nextNode == NULL); + list->firstNode->prevNode = node; + } + node->prevNode = NULL; + list->firstNode = node; + } else { + if (beforeNode) { + node->prevNode = beforeNode->prevNode; + beforeNode->prevNode = node; + } else { + node->prevNode = list->lastNode; + list->lastNode = node; + } + node->prevNode->nextNode = node; + } + + if (list->strdupKeys) + node->key = xstrdup(key); + else + node->key = key; + + node->data = data; + + list->numberOfNodes++; + + if (list->sorted) { + list->nodesArray = xrealloc(list->nodesArray, + list->numberOfNodes * + sizeof(ListNode *)); + if (node == list->lastNode) { + list->nodesArray[list->numberOfNodes - 1] = node; + } else if (pos < 0) + makeListNodesArray(list); + else { + memmove(list->nodesArray + pos + 1, + list->nodesArray + pos, + sizeof(ListNode *) * (list->numberOfNodes - + pos - 1)); + list->nodesArray[pos] = node; + } + } + + return node; +} + +ListNode *insertInList(List * list, char *key, void *data) +{ + ListNode *node; + + assert(list != NULL); + assert(key != NULL); + /*assert(data!=NULL); */ + + node = xmalloc(sizeof(ListNode)); + assert(node != NULL); + + if (list->nodesArray) + freeListNodesArray(list); + + if (list->firstNode == NULL) { + assert(list->lastNode == NULL); + list->firstNode = node; + } else { + assert(list->lastNode != NULL); + assert(list->lastNode->nextNode == NULL); + list->lastNode->nextNode = node; + } + + if (list->strdupKeys) + node->key = xstrdup(key); + else + node->key = key; + + node->data = data; + node->nextNode = NULL; + node->prevNode = list->lastNode; + + list->lastNode = node; + + list->numberOfNodes++; + + return node; +} + +int insertInListWithoutKey(List * list, void *data) +{ + ListNode *node; + + assert(list != NULL); + assert(data != NULL); + + node = xmalloc(sizeof(ListNode)); + assert(node != NULL); + + if (list->nodesArray) + freeListNodesArray(list); + + if (list->firstNode == NULL) { + assert(list->lastNode == NULL); + list->firstNode = node; + } else { + assert(list->lastNode != NULL); + assert(list->lastNode->nextNode == NULL); + list->lastNode->nextNode = node; + } + + node->key = NULL; + node->data = data; + node->nextNode = NULL; + node->prevNode = list->lastNode; + + list->lastNode = node; + + list->numberOfNodes++; + + return 1; +} + +/* if _key_ is not found, *_node_ is assigned to the node before which + the info would be found */ +int findNodeInList(List * list, char *key, ListNode ** node, int *pos) +{ + long high; + long low; + long cur; + ListNode *tmpNode; + int cmp; + + assert(list != NULL); + + if (list->sorted && list->nodesArray) { + high = list->numberOfNodes - 1; + low = 0; + cur = high; + + while (high > low) { + cur = (high + low) / 2; + tmpNode = list->nodesArray[cur]; + cmp = strcmp(tmpNode->key, key); + if (cmp == 0) { + *node = tmpNode; + *pos = cur; + return 1; + } else if (cmp > 0) + high = cur; + else { + if (low == cur) + break; + low = cur; + } + } + + cur = high; + if (cur >= 0) { + tmpNode = list->nodesArray[cur]; + *node = tmpNode; + *pos = high; + cmp = tmpNode ? strcmp(tmpNode->key, key) : -1; + if (0 == cmp) + return 1; + else if (cmp > 0) + return 0; + else { + *pos = -1; + *node = NULL; + return 0; + } + } else { + *pos = 0; + *node = list->firstNode; + return 0; + } + } else { + tmpNode = list->firstNode; + + while (tmpNode != NULL && strcmp(tmpNode->key, key) != 0) { + tmpNode = tmpNode->nextNode; + } + + *node = tmpNode; + if (tmpNode) + return 1; + } + + return 0; +} + +int findInList(List * list, char *key, void **data) +{ + ListNode *node; + int pos; + + if (findNodeInList(list, key, &node, &pos)) { + if (data) + *data = node->data; + return 1; + } + + return 0; +} + +int deleteFromList(List * list, char *key) +{ + ListNode *tmpNode; + + assert(list != NULL); + + tmpNode = list->firstNode; + + while (tmpNode != NULL && strcmp(tmpNode->key, key) != 0) { + tmpNode = tmpNode->nextNode; + } + + if (tmpNode != NULL) + deleteNodeFromList(list, tmpNode); + else + return 0; + + return 1; +} + +void deleteNodeFromList(List * list, ListNode * node) +{ + assert(list != NULL); + assert(node != NULL); + + if (node->prevNode == NULL) { + list->firstNode = node->nextNode; + } else { + node->prevNode->nextNode = node->nextNode; + } + if (node->nextNode == NULL) { + list->lastNode = node->prevNode; + } else { + node->nextNode->prevNode = node->prevNode; + } + if (list->freeDataFunc) { + list->freeDataFunc(node->data); + } + + if (list->strdupKeys) + free(node->key); + free(node); + list->numberOfNodes--; + + if (list->nodesArray) { + freeListNodesArray(list); + if (list->sorted) + makeListNodesArray(list); + } + +} + +void freeList(void *list) +{ + ListNode *tmpNode; + ListNode *tmpNode2; + + assert(list != NULL); + + tmpNode = ((List *) list)->firstNode; + + if (((List *) list)->nodesArray) + free(((List *) list)->nodesArray); + + while (tmpNode != NULL) { + tmpNode2 = tmpNode->nextNode; + if (((List *) list)->strdupKeys) + free(tmpNode->key); + if (((List *) list)->freeDataFunc) { + ((List *) list)->freeDataFunc(tmpNode->data); + } + free(tmpNode); + tmpNode = tmpNode2; + } + + free(list); +} + +static void swapNodes(ListNode * nodeA, ListNode * nodeB) +{ + char *key; + void *data; + + assert(nodeA != NULL); + assert(nodeB != NULL); + + key = nodeB->key; + data = nodeB->data; + + nodeB->key = nodeA->key; + nodeB->data = nodeA->data; + + nodeA->key = key; + nodeA->data = data; +} + +static void bubbleSort(ListNode ** nodesArray, long start, long end) +{ + long i; + long j; + ListNode *node; + + if (start >= end) + return; + + for (j = start; j < end; j++) { + for (i = end - 1; i >= start; i--) { + node = nodesArray[i]; + if (strcmp(node->key, node->nextNode->key) > 0) { + swapNodes(node, node->nextNode); + } + } + } +} + +static void quickSort(ListNode ** nodesArray, long start, long end) +{ + if (start >= end) + return; + else if (end - start < 5) + bubbleSort(nodesArray, start, end); + else { + long i; + ListNode *node; + long pivot; + ListNode *pivotNode; + char *pivotKey; + + List *startList = makeList(free, 0); + List *endList = makeList(free, 0); + long *startPtr = xmalloc(sizeof(long)); + long *endPtr = xmalloc(sizeof(long)); + *startPtr = start; + *endPtr = end; + insertInListWithoutKey(startList, (void *)startPtr); + insertInListWithoutKey(endList, (void *)endPtr); + + while (startList->numberOfNodes) { + start = *((long *)startList->lastNode->data); + end = *((long *)endList->lastNode->data); + + if (end - start < 5) { + bubbleSort(nodesArray, start, end); + deleteNodeFromList(startList, + startList->lastNode); + deleteNodeFromList(endList, endList->lastNode); + } else { + pivot = (start + end) / 2; + pivotNode = nodesArray[pivot]; + pivotKey = pivotNode->key; + + for (i = pivot - 1; i >= start; i--) { + node = nodesArray[i]; + if (strcmp(node->key, pivotKey) > 0) { + pivot--; + if (pivot > i) { + swapNodes(node, + nodesArray + [pivot]); + } + swapNodes(pivotNode, + nodesArray[pivot]); + pivotNode = nodesArray[pivot]; + } + } + for (i = pivot + 1; i <= end; i++) { + node = nodesArray[i]; + if (strcmp(pivotKey, node->key) > 0) { + pivot++; + if (pivot < i) { + swapNodes(node, + nodesArray + [pivot]); + } + swapNodes(pivotNode, + nodesArray[pivot]); + pivotNode = nodesArray[pivot]; + } + } + + deleteNodeFromList(startList, + startList->lastNode); + deleteNodeFromList(endList, endList->lastNode); + + if (pivot - 1 - start > 0) { + startPtr = xmalloc(sizeof(long)); + endPtr = xmalloc(sizeof(long)); + *startPtr = start; + *endPtr = pivot - 1; + insertInListWithoutKey(startList, + (void *) + startPtr); + insertInListWithoutKey(endList, + (void *)endPtr); + } + + if (end - pivot - 1 > 0) { + startPtr = xmalloc(sizeof(long)); + endPtr = xmalloc(sizeof(long)); + *startPtr = pivot + 1; + *endPtr = end; + insertInListWithoutKey(startList, + (void *) + startPtr); + insertInListWithoutKey(endList, + (void *)endPtr); + } + } + } + + freeList(startList); + freeList(endList); + } +} + +void sortList(List * list) +{ + assert(list != NULL); + + list->sorted = 1; + + if (list->numberOfNodes < 2) + return; + + if (list->nodesArray) + freeListNodesArray(list); + makeListNodesArray(list); + + quickSort(list->nodesArray, 0, list->numberOfNodes - 1); +} diff --git a/trunk/src/list.h b/trunk/src/list.h new file mode 100644 index 000000000..5938934ff --- /dev/null +++ b/trunk/src/list.h @@ -0,0 +1,110 @@ +/* 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 LIST_H +#define LIST_H + +#include "../config.h" + +#include <stdlib.h> + +/* used to make a list where free() will be used to free data in list */ +#define DEFAULT_FREE_DATA_FUNC free + +/* typedef for function to free data stored in the list nodes */ +typedef void ListFreeDataFunc(void *); + +typedef struct _ListNode { + /* used to identify node (ie. when using findInList) */ + char *key; + /* data store in node */ + void *data; + /* next node in list */ + struct _ListNode *nextNode; + /* previous node in list */ + struct _ListNode *prevNode; +} ListNode; + +typedef struct _List { + /* first node in list */ + ListNode *firstNode; + /* last node in list */ + ListNode *lastNode; + /* function used to free data stored in nodes of the list */ + ListFreeDataFunc *freeDataFunc; + /* number of nodes */ + long numberOfNodes; + /* array for searching when list is sorted */ + ListNode **nodesArray; + /* sorted */ + int sorted; + /* whether to strdup() key's on insertion */ + int strdupKeys; +} List; + +/* allocates memory for a new list and initializes it + * _freeDataFunc_ -> pointer to function used to free data, use + * DEFAULT_FREE_DATAFUNC to use free() + * returns pointer to new list if successful, NULL otherwise + */ +List *makeList(ListFreeDataFunc * freeDataFunc, int strdupKeys); + +/* inserts a node into _list_ with _key_ and _data_ + * _list_ -> list the data will be inserted in + * _key_ -> identifier for node/data to be inserted into list + * _data_ -> data to be inserted in list + * returns 1 if successful, 0 otherwise + */ +ListNode *insertInList(List * list, char *key, void *data); + +ListNode *insertInListBeforeNode(List * list, ListNode * beforeNode, + int pos, char *key, void *data); + +int insertInListWithoutKey(List * list, void *data); + +/* deletes the first node in the list with the key _key_ + * _list_ -> list the node will be deleted from + * _key_ -> key used to identify node to delete + * returns 1 if node is found and deleted, 0 otherwise + */ +int deleteFromList(List * list, char *key); + +void deleteNodeFromList(List * list, ListNode * node); + +/* finds data in a list based on key + * _list_ -> list to search for _key_ in + * _key_ -> which node is being searched for + * _data_ -> a pointer to where data will be placed, + * _data_ memory should not by allocated or freed + * _data_ can be NULL + * returns 1 if successful, 0 otherwise + */ +int findInList(List * list, char *key, void **data); + +/* if _key_ is not found, *_node_ is assigned to the node before which + the info would be found */ +int findNodeInList(List * list, char *key, ListNode ** node, int *pos); + +/* frees memory malloc'd for list and its nodes + * _list_ -> List to be free'd + */ +void freeList(void *list); + +void sortList(List * list); + +#endif diff --git a/trunk/src/listen.c b/trunk/src/listen.c new file mode 100644 index 000000000..323bf430f --- /dev/null +++ b/trunk/src/listen.c @@ -0,0 +1,258 @@ +/* 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 "listen.h" +#include "interface.h" +#include "conf.h" +#include "log.h" +#include "utils.h" + +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <errno.h> +#include <resolv.h> +#include <fcntl.h> + +#define MAXHOSTNAME 1024 + +#define ALLOW_REUSE 1 + +#define DEFAULT_PORT 6600 + +#define BINDERROR() do { \ + FATAL("unable to bind port %u: %s\n" \ + "maybe MPD is still running?\n", \ + port, strerror(errno)); \ +} while (0); + +static int *listenSockets; +static int numberOfListenSockets; +static int boundPort; + +static int establishListen(unsigned int port, + struct sockaddr *addrp, socklen_t addrlen) +{ + int pf = 0; + int sock; + int allowReuse = ALLOW_REUSE; + + switch (addrp->sa_family) { + case AF_INET: + pf = PF_INET; + break; +#ifdef HAVE_IPV6 + case AF_INET6: + pf = PF_INET6; + break; +#endif + case AF_UNIX: + pf = PF_UNIX; + break; + default: + FATAL("unknown address family: %i\n", addrp->sa_family); + } + + if ((sock = socket(pf, SOCK_STREAM, 0)) < 0) + FATAL("socket < 0\n"); + + if (fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK) < 0) { + FATAL("problems setting nonblocking on listen socket: %s\n", + strerror(errno)); + } + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&allowReuse, + sizeof(allowReuse)) < 0) { + FATAL("problems setsockopt'ing: %s\n", strerror(errno)); + } + + if (bind(sock, addrp, addrlen) < 0) { + close(sock); + return -1; + } + + if (listen(sock, 5) < 0) + FATAL("problems listen'ing: %s\n", strerror(errno)); + + numberOfListenSockets++; + listenSockets = + xrealloc(listenSockets, sizeof(int) * numberOfListenSockets); + + listenSockets[numberOfListenSockets - 1] = sock; + + return 0; +} + +static void parseListenConfigParam(unsigned int port, ConfigParam * param) +{ + struct sockaddr *addrp = NULL; + socklen_t addrlen = 0; + struct sockaddr_in sin; +#ifdef HAVE_IPV6 + struct sockaddr_in6 sin6; + int useIpv6 = ipv6Supported(); + + memset(&sin6, 0, sizeof(struct sockaddr_in6)); + sin6.sin6_port = htons(port); + sin6.sin6_family = AF_INET6; +#endif + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_port = htons(port); + sin.sin_family = AF_INET; + + if (!param || 0 == strcmp(param->value, "any")) { + DEBUG("binding to any address\n"); +#ifdef HAVE_IPV6 + if (useIpv6) { + sin6.sin6_addr = in6addr_any; + addrp = (struct sockaddr *)&sin6; + addrlen = sizeof(struct sockaddr_in6); + if (establishListen(port, addrp, addrlen) < 0) + BINDERROR(); + } +#endif + sin.sin_addr.s_addr = INADDR_ANY; + addrp = (struct sockaddr *)&sin; + addrlen = sizeof(struct sockaddr_in); +#ifdef HAVE_IPV6 + if ((establishListen(port, addrp, addrlen) < 0) && !useIpv6) { +#else + if (establishListen(port, addrp, addrlen) < 0) { +#endif + BINDERROR(); + } + } else { + struct hostent *he; + DEBUG("binding to address for %s\n", param->value); + if (!(he = gethostbyname(param->value))) { + FATAL("can't lookup host \"%s\" at line %i\n", + param->value, param->line); + } + switch (he->h_addrtype) { +#ifdef HAVE_IPV6 + case AF_INET6: + if (!useIpv6) { + FATAL("no IPv6 support, but a IPv6 address " + "found for \"%s\" at line %i\n", + param->value, param->line); + } + memcpy((char *)&sin6.sin6_addr.s6_addr, + (char *)he->h_addr, he->h_length); + addrp = (struct sockaddr *)&sin6; + addrlen = sizeof(struct sockaddr_in6); + break; +#endif + case AF_INET: + memcpy((char *)&sin.sin_addr.s_addr, + (char *)he->h_addr, he->h_length); + addrp = (struct sockaddr *)&sin; + addrlen = sizeof(struct sockaddr_in); + break; + default: + FATAL("address type for \"%s\" is not IPv4 or IPv6 " + "at line %i\n", param->value, param->line); + } + + if (establishListen(port, addrp, addrlen) < 0) + BINDERROR(); + } +} + +void listenOnPort(void) +{ + int port = DEFAULT_PORT; + ConfigParam *param = getNextConfigParam(CONF_BIND_TO_ADDRESS, NULL); + ConfigParam *portParam = getConfigParam(CONF_PORT); + + if (portParam) { + char *test; + port = strtol(portParam->value, &test, 10); + if (port <= 0 || *test != '\0') { + FATAL("%s \"%s\" specified at line %i is not a " + "positive integer", CONF_PORT, + portParam->value, portParam->line); + } + } + + boundPort = port; + + do { + parseListenConfigParam(port, param); + } while ((param = getNextConfigParam(CONF_BIND_TO_ADDRESS, param))); +} + +void addListenSocketsToFdSet(fd_set * fds, int *fdmax) +{ + int i; + + for (i = 0; i < numberOfListenSockets; i++) { + FD_SET(listenSockets[i], fds); + if (listenSockets[i] > *fdmax) + *fdmax = listenSockets[i]; + } +} + +void closeAllListenSockets(void) +{ + int i; + + DEBUG("closeAllListenSockets called\n"); + + for (i = 0; i < numberOfListenSockets; i++) { + DEBUG("closing listen socket %i\n", i); + while (close(listenSockets[i]) < 0 && errno == EINTR) ; + } + freeAllListenSockets(); +} + +void freeAllListenSockets(void) +{ + numberOfListenSockets = 0; + free(listenSockets); + listenSockets = NULL; +} + +void getConnections(fd_set * fds) +{ + int i; + int fd = 0; + struct sockaddr sockAddr; + socklen_t socklen = sizeof(sockAddr); + + for (i = 0; i < numberOfListenSockets; i++) { + if (FD_ISSET(listenSockets[i], fds)) { + if ((fd = accept(listenSockets[i], &sockAddr, &socklen)) + >= 0) { + openAInterface(fd, &sockAddr); + } else if (fd < 0 + && (errno != EAGAIN && errno != EINTR)) { + ERROR("Problems accept()'ing\n"); + } + } + } +} + +int getBoundPort(void) +{ + return boundPort; +} diff --git a/trunk/src/listen.h b/trunk/src/listen.h new file mode 100644 index 000000000..638214003 --- /dev/null +++ b/trunk/src/listen.h @@ -0,0 +1,41 @@ +/* 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 LISTEN_H +#define LISTEN_H + +#include "../config.h" + +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/select.h> + +void listenOnPort(void); + +void getConnections(fd_set * fds); + +void closeAllListenSockets(void); +void freeAllListenSockets(void); + +/* fdmax should be initialized to something */ +void addListenSocketsToFdSet(fd_set * fds, int *fdmax); + +int getBoundPort(void); + +#endif diff --git a/trunk/src/locate.c b/trunk/src/locate.c new file mode 100644 index 000000000..7c3bab899 --- /dev/null +++ b/trunk/src/locate.c @@ -0,0 +1,211 @@ +/* the Music Player Daemon (MPD) + * (c)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 "locate.h" + +#include "utils.h" + +#define LOCATE_TAG_FILE_KEY "file" +#define LOCATE_TAG_FILE_KEY_OLD "filename" +#define LOCATE_TAG_ANY_KEY "any" + +int getLocateTagItemType(char *str) +{ + int i; + + if (0 == strcasecmp(str, LOCATE_TAG_FILE_KEY) || + 0 == strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD)) + { + return LOCATE_TAG_FILE_TYPE; + } + + if (0 == strcasecmp(str, LOCATE_TAG_ANY_KEY)) + { + return LOCATE_TAG_ANY_TYPE; + } + + for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) + { + if (0 == strcasecmp(str, mpdTagItemKeys[i])) + return i; + } + + return -1; +} + +static int initLocateTagItem(LocateTagItem * item, char *typeStr, char *needle) +{ + item->tagType = getLocateTagItemType(typeStr); + + if (item->tagType < 0) + return -1; + + item->needle = xstrdup(needle); + + return 0; +} + +LocateTagItem *newLocateTagItem(char *typeStr, char *needle) +{ + LocateTagItem *ret = xmalloc(sizeof(LocateTagItem)); + + if (initLocateTagItem(ret, typeStr, needle) < 0) { + free(ret); + ret = NULL; + } + + return ret; +} + +void freeLocateTagItemArray(int count, LocateTagItem * array) +{ + int i; + + for (i = 0; i < count; i++) + free(array[i].needle); + + free(array); +} + +int newLocateTagItemArrayFromArgArray(char *argArray[], + int numArgs, LocateTagItem ** arrayRet) +{ + int i, j; + LocateTagItem *item; + + if (numArgs == 0) + return 0; + + if (numArgs % 2 != 0) + return -1; + + *arrayRet = xmalloc(sizeof(LocateTagItem) * numArgs / 2); + + for (i = 0, item = *arrayRet; i < numArgs / 2; i++, item++) { + if (initLocateTagItem + (item, argArray[i * 2], argArray[i * 2 + 1]) < 0) + goto fail; + } + + return numArgs / 2; + +fail: + for (j = 0; j < i; j++) { + free((*arrayRet)[j].needle); + } + + free(*arrayRet); + *arrayRet = NULL; + return -1; +} + +void freeLocateTagItem(LocateTagItem * item) +{ + free(item->needle); + free(item); +} + +static int strstrSearchTag(Song * song, int type, char *str) +{ + int i; + char *dup; + int ret = 0; + + if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) { + dup = strDupToUpper(getSongUrl(song)); + if (strstr(dup, str)) + ret = 1; + free(dup); + if (ret == 1 || type == LOCATE_TAG_FILE_TYPE) { + return ret; + } + } + + if (!song->tag) + return 0; + + for (i = 0; i < song->tag->numOfItems && !ret; i++) { + if (type != LOCATE_TAG_ANY_TYPE && + song->tag->items[i].type != type) { + continue; + } + + dup = strDupToUpper(song->tag->items[i].value); + if (strstr(dup, str)) + ret = 1; + free(dup); + } + + return ret; +} + +int strstrSearchTags(Song * song, int numItems, LocateTagItem * items) +{ + int i; + + for (i = 0; i < numItems; i++) { + if (!strstrSearchTag(song, items[i].tagType, + items[i].needle)) { + return 0; + } + } + + return 1; +} + +static int tagItemFoundAndMatches(Song * song, int type, char *str) +{ + int i; + + if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) { + if (0 == strcmp(str, getSongUrl(song))) + return 1; + if (type == LOCATE_TAG_FILE_TYPE) + return 0; + } + + if (!song->tag) + return 0; + + for (i = 0; i < song->tag->numOfItems; i++) { + if (type != LOCATE_TAG_ANY_TYPE && + song->tag->items[i].type != type) { + continue; + } + + if (0 == strcmp(str, song->tag->items[i].value)) + return 1; + } + + return 0; +} + + +int tagItemsFoundAndMatches(Song * song, int numItems, LocateTagItem * items) +{ + int i; + + for (i = 0; i < numItems; i++) { + if (!tagItemFoundAndMatches(song, items[i].tagType, + items[i].needle)) { + return 0; + } + } + + return 1; +} diff --git a/trunk/src/locate.h b/trunk/src/locate.h new file mode 100644 index 000000000..c165a310a --- /dev/null +++ b/trunk/src/locate.h @@ -0,0 +1,46 @@ +/* the Music Player Daemon (MPD) + * (c)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 "song.h" + +#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 +#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 + +/* struct used for search, find, list queries */ +typedef struct _LocateTagItem { + mpd_sint8 tagType; + /* what we are looking for */ + char *needle; +} LocateTagItem; + +int getLocateTagItemType(char *str); + +/* returns NULL if not a known type */ +LocateTagItem *newLocateTagItem(char *typeString, char *needle); + +/* return number of items or -1 on error */ +int newLocateTagItemArrayFromArgArray(char *argArray[], int numArgs, + LocateTagItem ** arrayRet); + +void freeLocateTagItemArray(int count, LocateTagItem * array); + +void freeLocateTagItem(LocateTagItem * item); + +int strstrSearchTags(Song * song, int numItems, LocateTagItem * items); + +int tagItemsFoundAndMatches(Song * song, int numItems, LocateTagItem * items); diff --git a/trunk/src/log.c b/trunk/src/log.c new file mode 100644 index 000000000..fa4ae64f0 --- /dev/null +++ b/trunk/src/log.c @@ -0,0 +1,262 @@ +/* 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 "log.h" + +#include "conf.h" +#include "myfprintf.h" +#include "utils.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> + +static unsigned int logLevel = LOG_LEVEL_LOW; +static int warningFlushed; +static int stdout_mode = 1; +static char *warningBuffer; +static int out_fd = -1; +static int err_fd = -1; +static const char *out_filename; +static const char *err_filename; + +/* redirect stdin to /dev/null to work around a libao bug */ +static void redirect_stdin(void) +{ + int fd; + if ((fd = open("/dev/null", O_RDONLY)) < 0) + FATAL("failed to open /dev/null %s\n", strerror(errno)); + if (dup2(fd, STDIN_FILENO) < 0) + FATAL("dup2 stdin: %s\n", strerror(errno)); +} + +static void redirect_logs(void) +{ + assert(out_fd > 0); + assert(err_fd > 0); + if (dup2(out_fd, STDOUT_FILENO) < 0) + FATAL("problems dup2 stdout : %s\n", strerror(errno)); + if (dup2(err_fd, STDERR_FILENO) < 0) + FATAL("problems dup2 stderr : %s\n", strerror(errno)); +} + +static const char *log_date(void) +{ + static char buf[16]; + time_t t = time(NULL); + strftime(buf, 16, "%b %d %H:%M : ", localtime(&t)); + return buf; +} + +#define BUFFER_LENGTH 4096 +static void buffer_warning(const char *fmt, va_list args) +{ + char buffer[BUFFER_LENGTH]; + char *tmp = buffer; + size_t len = BUFFER_LENGTH; + + if (!stdout_mode) { + memcpy(buffer, log_date(), 15); + tmp += 15; + len -= 15; + } + + vsnprintf(tmp, len, fmt, args); + warningBuffer = appendToString(warningBuffer, buffer); + + va_end(args); +} + +static void do_log(FILE *fp, const char *fmt, va_list args) +{ + if (!stdout_mode) + fwrite(log_date(), 15, 1, fp); + vfprintf(fp, fmt, args); +} + +void flushWarningLog(void) +{ + char *s = warningBuffer; + + DEBUG("flushing warning messages\n"); + + if (warningBuffer != NULL) + { + while (s != NULL) { + char *next = strchr(s, '\n'); + if (next == NULL) break; + *next = '\0'; + next++; + fprintf(stderr, "%s\n", s); + s = next; + } + + warningBuffer = NULL; + } + + warningFlushed = 1; + + DEBUG("done flushing warning messages\n"); +} + +void initLog(const int verbose) +{ + ConfigParam *param; + + /* unbuffer stdout, stderr is unbuffered by default, leave it */ + setvbuf(stdout, (char *)NULL, _IONBF, 0); + + if (verbose) { + logLevel = LOG_LEVEL_DEBUG; + return; + } + if (!(param = getConfigParam(CONF_LOG_LEVEL))) + return; + if (0 == strcmp(param->value, "default")) { + logLevel = LOG_LEVEL_LOW; + } else if (0 == strcmp(param->value, "secure")) { + logLevel = LOG_LEVEL_SECURE; + } else if (0 == strcmp(param->value, "verbose")) { + logLevel = LOG_LEVEL_DEBUG; + } else { + FATAL("unknown log level \"%s\" at line %i\n", + param->value, param->line); + } +} + +void open_log_files(const int use_stdout) +{ + mode_t prev; + ConfigParam *param; + + if (use_stdout) { + flushWarningLog(); + return; + } + + prev = umask(0066); + param = parseConfigFilePath(CONF_LOG_FILE, 1); + out_filename = param->value; + out_fd = open(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (out_fd < 0) + FATAL("problem opening log file \"%s\" (config line %i) for " + "writing\n", param->value, param->line); + + param = parseConfigFilePath(CONF_ERROR_FILE, 1); + err_filename = param->value; + err_fd = open(err_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (err_fd < 0) + FATAL("problem opening error file \"%s\" (config line %i) for " + "writing\n", param->value, param->line); + + umask(prev); +} + +void setup_log_output(const int use_stdout) +{ + fflush(NULL); + if (!use_stdout) { + redirect_logs(); + stdout_mode = 0; + } + redirect_stdin(); +} + +#define log_func(func,level,fp) \ +mpd_printf void func(const char *fmt, ...) \ +{ \ + if (logLevel >= level) { \ + va_list args; \ + va_start(args, fmt); \ + do_log(fp, fmt, args); \ + va_end(args); \ + } \ +} + +log_func(ERROR, 0, stderr) +log_func(LOG, 0, stdout) +log_func(SECURE, LOG_LEVEL_SECURE, stdout) +log_func(DEBUG, LOG_LEVEL_DEBUG, stdout) + +#undef log_func + +void WARNING(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + if (warningFlushed) { + do_log(stderr, fmt, args); + } else + buffer_warning(fmt, args); + va_end(args); +} + +mpd_printf mpd_noreturn void FATAL(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + do_log(stderr, fmt, args); + va_end(args); + exit(EXIT_FAILURE); +} + +int cycle_log_files(void) +{ + mode_t prev; + + if (stdout_mode) + return 0; + assert(out_filename); + assert(err_filename); + + DEBUG("Cycling log files...\n"); + close_log_files(); + + prev = umask(0066); + + out_fd = open(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (out_fd < 0) { + ERROR("error re-opening log file: %s\n", out_filename); + return -1; + } + + err_fd = open(err_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (err_fd < 0) { + ERROR("error re-opening error file: %s\n", err_filename); + return -1; + } + + umask(prev); + + redirect_logs(); + DEBUG("Done cycling log files\n"); + return 0; +} + +void close_log_files(void) +{ + if (stdout_mode) + return; + assert(out_fd > 0); + assert(err_fd > 0); + xclose(out_fd); + xclose(err_fd); +} + diff --git a/trunk/src/log.h b/trunk/src/log.h new file mode 100644 index 000000000..34f6ac00e --- /dev/null +++ b/trunk/src/log.h @@ -0,0 +1,50 @@ +/* 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 LOG_H +#define LOG_H + +#include "../config.h" +#include "gcc.h" + +#include <unistd.h> + +#define LOG_LEVEL_LOW 0 +#define LOG_LEVEL_SECURE 1 +#define LOG_LEVEL_DEBUG 2 + +mpd_printf void ERROR(const char *fmt, ...); +mpd_printf void LOG(const char *fmt, ...); +mpd_printf void SECURE(const char *fmt, ...); +mpd_printf void DEBUG(const char *fmt, ...); +mpd_printf void WARNING(const char *fmt, ...); +mpd_printf void FATAL(const char *fmt, ...); + +void initLog(const int verbose); + +void setup_log_output(const int use_stdout); + +void open_log_files(const int use_stdout); + +int cycle_log_files(void); + +void close_log_files(void); + +void flushWarningLog(void); + +#endif /* LOG_H */ diff --git a/trunk/src/ls.c b/trunk/src/ls.c new file mode 100644 index 000000000..0b3f7f354 --- /dev/null +++ b/trunk/src/ls.c @@ -0,0 +1,281 @@ +/* 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 "ls.h" +#include "playlist.h" +#include "path.h" +#include "myfprintf.h" +#include "log.h" +#include "utf8.h" +#include "utils.h" + +#include <dirent.h> +#include <stdio.h> +#include <errno.h> + +static char *remoteUrlPrefixes[] = { + "http://", + NULL +}; + +int printRemoteUrlHandlers(int fd) +{ + char **prefixes = remoteUrlPrefixes; + + while (*prefixes) { + fdprintf(fd, "handler: %s\n", *prefixes); + prefixes++; + } + + return 0; +} + +int isValidRemoteUtf8Url(char *utf8url) +{ + int ret = 0; + char *temp; + + switch (isRemoteUrl(utf8url)) { + case 1: + ret = 1; + temp = utf8url; + while (*temp) { + if ((*temp >= 'a' && *temp <= 'z') || + (*temp >= 'A' && *temp <= 'Z') || + (*temp >= '0' && *temp <= '9') || + *temp == '$' || + *temp == '-' || + *temp == '.' || + *temp == '+' || + *temp == '!' || + *temp == '*' || + *temp == '\'' || + *temp == '(' || + *temp == ')' || + *temp == ',' || + *temp == '%' || + *temp == '/' || + *temp == ':' || + *temp == '?' || + *temp == ';' || *temp == '&' || *temp == '=') { + } else { + ret = 1; + break; + } + temp++; + } + break; + } + + return ret; +} + +int isRemoteUrl(char *url) +{ + int count = 0; + char **urlPrefixes = remoteUrlPrefixes; + + while (*urlPrefixes) { + count++; + if (strncmp(*urlPrefixes, url, strlen(*urlPrefixes)) == 0) { + return count; + } + urlPrefixes++; + } + + return 0; +} + +int lsPlaylists(int fd, char *utf8path) +{ + DIR *dir; + struct stat st; + struct dirent *ent; + char *dup; + char *utf8; + char s[MAXPATHLEN + 1]; + List *list = NULL; + ListNode *node = NULL; + char *path = utf8ToFsCharset(utf8path); + char *actualPath = rpp2app(path); + int actlen = strlen(actualPath) + 1; + int maxlen = MAXPATHLEN - actlen; + int suflen = strlen(PLAYLIST_FILE_SUFFIX) + 1; + int suff; + + if (actlen > MAXPATHLEN - 1 || (dir = opendir(actualPath)) == NULL) { + return 0; + } + + s[MAXPATHLEN] = '\0'; + /* this is safe, notice actlen > MAXPATHLEN-1 above */ + strcpy(s, actualPath); + strcat(s, "/"); + + while ((ent = readdir(dir))) { + size_t len = strlen(ent->d_name) + 1; + dup = ent->d_name; + if (mpd_likely(len <= maxlen) && + dup[0] != '.' && + (suff = strlen(dup) - suflen) > 0 && + dup[suff] == '.' && + strcmp(dup + suff + 1, PLAYLIST_FILE_SUFFIX) == 0) { + memcpy(s + actlen, ent->d_name, len); + if (stat(s, &st) == 0) { + if (S_ISREG(st.st_mode)) { + if (list == NULL) + list = makeList(NULL, 1); + dup[suff] = '\0'; + if ((utf8 = fsCharsetToUtf8(dup))) { + insertInList(list, utf8, NULL); + } + } + } + } + } + + closedir(dir); + + if (list) { + int i; + sortList(list); + + dup = xmalloc(strlen(utf8path) + 2); + strcpy(dup, utf8path); + for (i = strlen(dup) - 1; i >= 0 && dup[i] == '/'; i--) { + dup[i] = '\0'; + } + if (strlen(dup)) + strcat(dup, "/"); + + node = list->firstNode; + while (node != NULL) { + if (!strchr(node->key, '\n')) { + fdprintf(fd, "playlist: %s%s\n", dup, + node->key); + } + node = node->nextNode; + } + + freeList(list); + free(dup); + } + + return 0; +} + +int myStat(char *utf8file, struct stat *st) +{ + char *file = utf8ToFsCharset(utf8file); + char *actualFile = file; + + if (actualFile[0] != '/') + actualFile = rmp2amp(file); + + return stat(actualFile, st); +} + +static int isFile(char *utf8file, time_t * mtime) +{ + struct stat st; + + if (myStat(utf8file, &st) == 0) { + if (S_ISREG(st.st_mode)) { + if (mtime) + *mtime = st.st_mtime; + return 1; + } else { + DEBUG("isFile: %s is not a regular file\n", utf8file); + return 0; + } + } else { + DEBUG("isFile: failed to stat: %s: %s\n", utf8file, + strerror(errno)); + } + + return 0; +} + +/* suffixes should be ascii only characters */ +char *getSuffix(char *utf8file) +{ + char *ret = NULL; + + while (*utf8file) { + if (*utf8file == '.') + ret = utf8file + 1; + utf8file++; + } + + return ret; +} + +static int hasSuffix(char *utf8file, char *suffix) +{ + char *s = getSuffix(utf8file); + if (s && 0 == strcmp(s, suffix)) + return 1; + return 0; +} + +int isPlaylist(char *utf8file) +{ + if (isFile(utf8file, NULL)) { + return hasSuffix(utf8file, PLAYLIST_FILE_SUFFIX); + } + return 0; +} + +int isDir(char *utf8name) +{ + struct stat st; + + if (myStat(utf8name, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + return 1; + } + } + + return 0; +} + +InputPlugin *hasMusicSuffix(char *utf8file, unsigned int next) +{ + InputPlugin *ret = NULL; + + char *s = getSuffix(utf8file); + if (s) { + ret = getInputPluginFromSuffix(s, next); + } else { + DEBUG("hasMusicSuffix: The file: %s has no valid suffix\n", + utf8file); + } + + return ret; +} + +InputPlugin *isMusic(char *utf8file, time_t * mtime, unsigned int next) +{ + if (isFile(utf8file, mtime)) { + InputPlugin *plugin = hasMusicSuffix(utf8file, next); + if (plugin != NULL) + return plugin; + } + DEBUG("isMusic: %s is not a valid file\n", utf8file); + return NULL; +} diff --git a/trunk/src/ls.h b/trunk/src/ls.h new file mode 100644 index 000000000..20f668bd9 --- /dev/null +++ b/trunk/src/ls.h @@ -0,0 +1,52 @@ +/* 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 LS_H +#define LS_H + +#include "../config.h" + +#include "inputPlugin.h" + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <time.h> + +int lsPlaylists(int fd, char *utf8path); + +char *getSuffix(char *utf8file); + +int isValidRemoteUtf8Url(char *utf8url); + +int isRemoteUrl(char *url); + +int myStat(char *utf8file, struct stat *st); + +int isDir(char *utf8name); + +int isPlaylist(char *utf8file); + +InputPlugin *hasMusicSuffix(char *utf8file, unsigned int next); + +InputPlugin *isMusic(char *utf8file, time_t * mtime, unsigned int next); + +int printRemoteUrlHandlers(int fd); + +#endif diff --git a/trunk/src/main.c b/trunk/src/main.c new file mode 100644 index 000000000..4c537eb1d --- /dev/null +++ b/trunk/src/main.c @@ -0,0 +1,482 @@ +/* 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 "interface.h" +#include "command.h" +#include "playlist.h" +#include "directory.h" +#include "player.h" +#include "listen.h" +#include "conf.h" +#include "path.h" +#include "playerData.h" +#include "stats.h" +#include "sig_handlers.h" +#include "audio.h" +#include "volume.h" +#include "log.h" +#include "permission.h" +#include "replayGain.h" +#include "inputPlugin.h" +#include "audioOutput.h" +#include "inputStream.h" +#include "state_file.h" +#include "tag.h" +#include "tagTracker.h" +#include "dbUtils.h" +#include "../config.h" +#include "utils.h" +#include "normalize.h" +#include "zeroconf.h" + +#include <stdio.h> +#include <sys/select.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> +#include <time.h> +#include <unistd.h> + +#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf" +#define USER_CONFIG_FILE_LOCATION "/.mpdconf" + +typedef struct _Options { + int kill; + int daemon; + int stdOutput; + int createDB; + int verbose; +} Options; + +/* + * from git-1.3.0, needed for solaris + */ +#ifndef HAVE_SETENV +static int setenv(const char *name, const char *value, int replace) +{ + int out; + size_t namelen, valuelen; + char *envstr; + + if (!name || !value) + return -1; + if (!replace) { + char *oldval = NULL; + oldval = getenv(name); + if (oldval) + return 0; + } + + namelen = strlen(name); + valuelen = strlen(value); + envstr = xmalloc((namelen + valuelen + 2)); + if (!envstr) + return -1; + + memcpy(envstr, name, namelen); + envstr[namelen] = '='; + memcpy(envstr + namelen + 1, value, valuelen); + envstr[namelen + valuelen + 1] = 0; + + out = putenv(envstr); + /* putenv(3) makes the argument string part of the environment, + * and changing that string modifies the environment --- which + * means we do not own that storage anymore. Do not free + * envstr. + */ + + return out; +} +#endif /* HAVE_SETENV */ + +static void usage(char *argv[]) +{ + ERROR("usage:\n"); + ERROR(" %s [options] <conf file>\n", argv[0]); + ERROR(" %s [options] (searches for ~%s then %s)\n", + argv[0], USER_CONFIG_FILE_LOCATION, SYSTEM_CONFIG_FILE_LOCATION); + ERROR("\n"); + ERROR("options:\n"); + ERROR(" --help this usage statement\n"); + ERROR(" --kill kill the currently running mpd session\n"); + ERROR + (" --create-db force (re)creation of database and exit\n"); + ERROR + (" --no-create-db don't create database, even if it doesn't exist\n"); + ERROR(" --no-daemon don't detach from console\n"); + ERROR(" --stdout print messages to stdout and stderr\n"); + ERROR(" --verbose verbose logging\n"); + ERROR(" --version prints version information\n"); +} + +static void version(void) +{ + LOG("mpd (MPD: Music Player Daemon) %s\n", VERSION); + LOG("\n"); + LOG("Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"); + LOG("This is free software; see the source for copying conditions. There is NO\n"); + LOG("warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"); + LOG("\n"); + LOG("Supported formats:\n"); + + initInputPlugins(); + printAllInputPluginSuffixes(stdout); + + LOG("\n"); + LOG("Supported outputs:\n"); + loadAudioDrivers(); + printAllOutputPluginTypes(stdout); +} + +static void parseOptions(int argc, char **argv, Options * options) +{ + int argcLeft = argc; + + options->verbose = 0; + options->daemon = 1; + options->stdOutput = 0; + options->createDB = 0; + options->kill = 0; + + if (argc > 1) { + int i = 1; + while (i < argc) { + if (strncmp(argv[i], "--", 2) == 0) { + if (strcmp(argv[i], "--help") == 0) { + usage(argv); + exit(EXIT_SUCCESS); + } else if (strcmp(argv[i], "--kill") == 0) { + options->kill++; + argcLeft--; + } else if (strcmp(argv[i], "--no-daemon") == 0) { + options->daemon = 0; + argcLeft--; + } else if (strcmp(argv[i], "--stdout") == 0) { + options->stdOutput = 1; + argcLeft--; + } else if (strcmp(argv[i], "--create-db") == 0) { + options->stdOutput = 1; + options->createDB = 1; + argcLeft--; + } else if (strcmp(argv[i], "--no-create-db") == + 0) { + options->createDB = -1; + argcLeft--; + } else if (strcmp(argv[i], "--verbose") == 0) { + options->verbose = 1; + argcLeft--; + } else if (strcmp(argv[i], "--version") == 0) { + version(); + exit(EXIT_SUCCESS); + } else { + fprintf(stderr, + "unknown command line option: %s\n", + argv[i]); + exit(EXIT_FAILURE); + } + } else + break; + i++; + } + } + + if (argcLeft <= 2) { + if (argcLeft == 2) { + readConf(argv[argc - 1]); + return; + } else if (argcLeft == 1) { + struct stat st; + char *homedir = getenv("HOME"); + char userfile[MAXPATHLEN + 1] = ""; + if (homedir && (strlen(homedir) + + strlen(USER_CONFIG_FILE_LOCATION)) < + MAXPATHLEN) { + strcpy(userfile, homedir); + strcat(userfile, USER_CONFIG_FILE_LOCATION); + } + if (strlen(userfile) && (0 == stat(userfile, &st))) { + readConf(userfile); + return; + } else if (0 == stat(SYSTEM_CONFIG_FILE_LOCATION, &st)) { + readConf(SYSTEM_CONFIG_FILE_LOCATION); + return; + } + } + } + + usage(argv); + exit(EXIT_FAILURE); +} + +static void closeAllFDs(void) +{ + int i; + int fds = getdtablesize(); + + for (i = 3; i < fds; i++) + close(i); +} + +static void changeToUser(void) +{ + ConfigParam *param = getConfigParam(CONF_USER); + + if (param && strlen(param->value)) { + /* get uid */ + struct passwd *userpwd; + if ((userpwd = getpwnam(param->value)) == NULL) { + FATAL("no such user \"%s\" at line %i\n", param->value, + param->line); + } + + if (setgid(userpwd->pw_gid) == -1) { + FATAL("cannot setgid for user \"%s\" at line %i: %s\n", + param->value, param->line, strerror(errno)); + } +#ifdef _BSD_SOURCE + /* init suplementary groups + * (must be done before we change our uid) + */ + if (initgroups(param->value, userpwd->pw_gid) == -1) { + WARNING("cannot init supplementary groups " + "of user \"%s\" at line %i: %s\n", + param->value, param->line, strerror(errno)); + } +#endif + + /* set uid */ + if (setuid(userpwd->pw_uid) == -1) { + FATAL("cannot change to uid of user " + "\"%s\" at line %i: %s\n", + param->value, param->line, strerror(errno)); + } + + /* this is needed by libs such as arts */ + if (userpwd->pw_dir) { + setenv("HOME", userpwd->pw_dir, 1); + } + } +} + +static void openDB(Options * options, char *argv0) +{ + if (options->createDB > 0 || readDirectoryDB() < 0) { + if (options->createDB < 0) { + FATAL("can't open db file and using " + "\"--no-create-db\" command line option\n" + "try running \"%s --create-db\"\n", argv0); + } + flushWarningLog(); + if (checkDirectoryDB() < 0) + exit(EXIT_FAILURE); + initMp3Directory(); + if (writeDirectoryDB() < 0) + exit(EXIT_FAILURE); + if (options->createDB) + exit(EXIT_SUCCESS); + } +} + +static void daemonize(Options * options) +{ + FILE *fp = NULL; + ConfigParam *pidFileParam = parseConfigFilePath(CONF_PID_FILE, 0); + + if (pidFileParam) { + /* do this before daemon'izing so we can fail gracefully if we can't + * write to the pid file */ + DEBUG("opening pid file\n"); + fp = fopen(pidFileParam->value, "w+"); + if (!fp) { + FATAL("could not open %s \"%s\" (at line %i) for writing: %s\n", + CONF_PID_FILE, pidFileParam->value, + pidFileParam->line, strerror(errno)); + } + } + + if (options->daemon) { + int pid; + + fflush(NULL); + pid = fork(); + if (pid > 0) + _exit(EXIT_SUCCESS); + else if (pid < 0) { + FATAL("problems fork'ing for daemon!\n"); + } + + if (chdir("/") < 0) { + FATAL("problems changing to root directory\n"); + } + + if (setsid() < 0) { + FATAL("problems setsid'ing\n"); + } + + fflush(NULL); + pid = fork(); + if (pid > 0) + _exit(EXIT_SUCCESS); + else if (pid < 0) { + FATAL("problems fork'ing for daemon!\n"); + } + + DEBUG("daemonized!\n"); + } + + if (pidFileParam) { + DEBUG("writing pid file\n"); + fprintf(fp, "%lu\n", (unsigned long)getpid()); + fclose(fp); + } +} + +static void cleanUpPidFile(void) +{ + ConfigParam *pidFileParam = parseConfigFilePath(CONF_PID_FILE, 0); + + if (!pidFileParam) + return; + + DEBUG("cleaning up pid file\n"); + + unlink(pidFileParam->value); +} + +static void killFromPidFile(char *cmd, int killOption) +{ + FILE *fp; + ConfigParam *pidFileParam = parseConfigFilePath(CONF_PID_FILE, 0); + int pid; + + if (!pidFileParam) { + FATAL("no pid_file specified in the config file\n"); + } + + fp = fopen(pidFileParam->value, "r"); + if (!fp) { + FATAL("unable to open %s \"%s\": %s\n", + CONF_PID_FILE, pidFileParam->value, strerror(errno)); + } + if (fscanf(fp, "%i", &pid) != 1) { + FATAL("unable to read the pid from file \"%s\"\n", + pidFileParam->value); + } + fclose(fp); + + if (kill(pid, SIGTERM)) { + FATAL("unable to kill proccess %i: %s\n", pid, strerror(errno)); + } + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + Options options; + clock_t start; + + closeAllFDs(); + + initConf(); + + parseOptions(argc, argv, &options); + + if (options.kill) + killFromPidFile(argv[0], options.kill); + + initStats(); + initTagConfig(); + initLog(options.verbose); + + if (options.createDB <= 0) + listenOnPort(); + + changeToUser(); + + open_log_files(options.stdOutput); + + initPaths(); + initPermissions(); + initPlaylist(); + initInputPlugins(); + + openDB(&options, argv[0]); + + initCommands(); + initPlayerData(); + initAudioConfig(); + initAudioDriver(); + initVolume(); + initInterfaces(); + initZeroconf(); + initReplayGainState(); + initNormalization(); + initInputStream(); + + daemonize(&options); + + setup_log_output(options.stdOutput); + + + + initSigHandlers(); + + openVolumeDevice(); + read_state_file(); + + while (COMMAND_RETURN_KILL != doIOForInterfaces()) { + if (COMMAND_RETURN_KILL == handlePendingSignals()) + break; + syncPlayerAndPlaylist(); + closeOldInterfaces(); + readDirectoryDBIfUpdateIsFinished(); + } + + write_state_file(); + playerKill(); + finishZeroconf(); + freeAllInterfaces(); + closeAllListenSockets(); + finishPlaylist(); + + start = clock(); + closeMp3Directory(); + DEBUG("closeMp3Directory took %f seconds\n", + ((float)(clock()-start))/CLOCKS_PER_SEC); + + finishNormalization(); + finishAudioDriver(); + finishAudioConfig(); + finishVolume(); + finishPaths(); + finishPermissions(); + finishCommands(); + finishInputPlugins(); + cleanUpPidFile(); + finishConf(); + freePlayerData(); + + close_log_files(); + return EXIT_SUCCESS; +} diff --git a/trunk/src/metadataChunk.c b/trunk/src/metadataChunk.c new file mode 100644 index 000000000..bc5118fd0 --- /dev/null +++ b/trunk/src/metadataChunk.c @@ -0,0 +1,94 @@ +/* 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 "metadataChunk.h" +#include "gcc.h" + +#include <string.h> + +static void initMetadataChunk(MetadataChunk * chunk) +{ + chunk->name = -1; + chunk->artist = -1; + chunk->album = -1; + chunk->title = -1; +} + +#define dupElementToTag(item, element) { \ + if(element >= 0 && element < METADATA_BUFFER_LENGTH) { \ + addItemToMpdTag(ret, item, chunk->buffer+element); \ + } \ +} + +MpdTag *metadataChunkToMpdTagDup(MetadataChunk * chunk) +{ + MpdTag *ret = newMpdTag(); + + chunk->buffer[METADATA_BUFFER_LENGTH - 1] = '\0'; + + dupElementToTag(TAG_ITEM_NAME, chunk->name); + dupElementToTag(TAG_ITEM_TITLE, chunk->title); + dupElementToTag(TAG_ITEM_ARTIST, chunk->artist); + dupElementToTag(TAG_ITEM_ALBUM, chunk->album); + + return ret; +} + +#define copyStringToChunk(string, element) { \ + if(element < 0 && string && (slen = strlen(string)) && \ + pos < METADATA_BUFFER_LENGTH-1) \ + { \ + size_t len = slen; \ + size_t max = METADATA_BUFFER_LENGTH - 1 - pos; \ + if (mpd_unlikely(len > max)) \ + len = max; \ + memcpy(chunk->buffer+pos, string, len); \ + *(chunk->buffer+pos+len) = '\0'; \ + element = pos; \ + pos += slen+1; \ + } \ +} + +void copyMpdTagToMetadataChunk(MpdTag * tag, MetadataChunk * chunk) +{ + int pos = 0; + int slen; + int i; + + initMetadataChunk(chunk); + + if (!tag) + return; + + for (i = 0; i < tag->numOfItems; i++) { + switch (tag->items[i].type) { + case TAG_ITEM_NAME: + copyStringToChunk(tag->items[i].value, chunk->name); + break; + case TAG_ITEM_TITLE: + copyStringToChunk(tag->items[i].value, chunk->title); + break; + case TAG_ITEM_ARTIST: + copyStringToChunk(tag->items[i].value, chunk->artist); + break; + case TAG_ITEM_ALBUM: + copyStringToChunk(tag->items[i].value, chunk->album); + break; + } + } +} diff --git a/trunk/src/metadataChunk.h b/trunk/src/metadataChunk.h new file mode 100644 index 000000000..c1da8b320 --- /dev/null +++ b/trunk/src/metadataChunk.h @@ -0,0 +1,38 @@ +/* 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 METADATA_CHUNK_H +#define METADATA_CHUNK_H + +#define METADATA_BUFFER_LENGTH 1024 + +#include "tag.h" + +typedef struct _MetadataChunk { + int name; + int title; + int artist; + int album; + char buffer[METADATA_BUFFER_LENGTH]; +} MetadataChunk; + +MpdTag *metadataChunkToMpdTagDup(MetadataChunk * chunk); + +void copyMpdTagToMetadataChunk(MpdTag * tag, MetadataChunk * chunk); + +#endif diff --git a/trunk/src/mp4ff/Makefile.am b/trunk/src/mp4ff/Makefile.am new file mode 100644 index 000000000..d1258e7b8 --- /dev/null +++ b/trunk/src/mp4ff/Makefile.am @@ -0,0 +1,9 @@ +noinst_LTLIBRARIES = libmp4ff.la + +noinst_HEADERS = mp4ff.h + +libmp4ff_la_SOURCES = mp4ff.c mp4atom.c mp4meta.c mp4sample.c mp4util.c \ + mp4tagupdate.c mp4ff.h mp4ffint.h mp4ff_int_types.h \ + drms.h drms.c drmstables.h + +AM_CFLAGS = -DUSE_TAGGING=1 diff --git a/trunk/src/mp4ff/drms.c b/trunk/src/mp4ff/drms.c new file mode 100644 index 000000000..368b88110 --- /dev/null +++ b/trunk/src/mp4ff/drms.c @@ -0,0 +1,1043 @@ +/***************************************************************************** + * drms.c : DRMS + ***************************************************************************** + * Copyright (C) 2004 VideoLAN + * $Id: drms.c,v 1.3 2004/01/11 15:52:18 menno Exp $ + * + * Author: Jon Lech Johansen <jon-vl@nanocrew.net> + * + * 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, USA. + *****************************************************************************/ + +#include <stdlib.h> /* malloc(), free() */ + +#include "mp4ffint.h" + +#ifdef ITUNES_DRM + +#ifdef _WIN32 +#include <tchar.h> +#include <shlobj.h> +#include <windows.h> +#endif + +#include "drms.h" +#include "drmstables.h" + +static __inline uint32_t U32_AT( void * _p ) +{ + uint8_t * p = (uint8_t *)_p; + return ( ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) + | ((uint32_t)p[2] << 8) | p[3] ); +} + +#define TAOS_INIT( tmp, i ) \ + memset( tmp, 0, sizeof(tmp) ); \ + tmp[ i + 0 ] = 0x67452301; \ + tmp[ i + 1 ] = 0xEFCDAB89; \ + tmp[ i + 2 ] = 0x98BADCFE; \ + tmp[ i + 3 ] = 0x10325476; + +#define ROR( x, n ) (((x) << (32-(n))) | ((x) >> (n))) + +static void init_ctx( uint32_t *p_ctx, uint32_t *p_input ) +{ + uint32_t i; + uint32_t p_tmp[ 6 ]; + + p_ctx[ 0 ] = sizeof(*p_input); + + memset( &p_ctx[ 1 + 4 ], 0, sizeof(*p_input) * 4 ); + memcpy( &p_ctx[ 1 + 0 ], p_input, sizeof(*p_input) * 4 ); + + p_tmp[ 0 ] = p_ctx[ 1 + 3 ]; + + for( i = 0; i < sizeof(p_drms_tab1)/sizeof(p_drms_tab1[ 0 ]); i++ ) + { + p_tmp[ 0 ] = ROR( p_tmp[ 0 ], 8 ); + + p_tmp[ 5 ] = p_drms_tab2[ (p_tmp[ 0 ] >> 24) & 0xFF ] + ^ ROR( p_drms_tab2[ (p_tmp[ 0 ] >> 16) & 0xFF ], 8 ) + ^ ROR( p_drms_tab2[ (p_tmp[ 0 ] >> 8) & 0xFF ], 16 ) + ^ ROR( p_drms_tab2[ p_tmp[ 0 ] & 0xFF ], 24 ) + ^ p_drms_tab1[ i ] + ^ p_ctx[ 1 + ((i + 1) * 4) - 4 ]; + + p_ctx[ 1 + ((i + 1) * 4) + 0 ] = p_tmp[ 5 ]; + p_tmp[ 5 ] ^= p_ctx[ 1 + ((i + 1) * 4) - 3 ]; + p_ctx[ 1 + ((i + 1) * 4) + 1 ] = p_tmp[ 5 ]; + p_tmp[ 5 ] ^= p_ctx[ 1 + ((i + 1) * 4) - 2 ]; + p_ctx[ 1 + ((i + 1) * 4) + 2 ] = p_tmp[ 5 ]; + p_tmp[ 5 ] ^= p_ctx[ 1 + ((i + 1) * 4) - 1 ]; + p_ctx[ 1 + ((i + 1) * 4) + 3 ] = p_tmp[ 5 ]; + + p_tmp[ 0 ] = p_tmp[ 5 ]; + } + + memcpy( &p_ctx[ 1 + 64 ], &p_ctx[ 1 ], sizeof(*p_ctx) * 4 ); + + for( i = 4; i < sizeof(p_drms_tab1); i++ ) + { + p_tmp[ 2 ] = p_ctx[ 1 + 4 + (i - 4) ]; + + p_tmp[ 0 ] = (((p_tmp[ 2 ] >> 7) & 0x01010101) * 27) + ^ ((p_tmp[ 2 ] & 0xFF7F7F7F) << 1); + p_tmp[ 1 ] = (((p_tmp[ 0 ] >> 7) & 0x01010101) * 27) + ^ ((p_tmp[ 0 ] & 0xFF7F7F7F) << 1); + p_tmp[ 4 ] = (((p_tmp[ 1 ] >> 7) & 0x01010101) * 27) + ^ ((p_tmp[ 1 ] & 0xFF7F7F7F) << 1); + + p_tmp[ 2 ] ^= p_tmp[ 4 ]; + + p_tmp[ 3 ] = ROR( p_tmp[ 1 ] ^ p_tmp[ 2 ], 16 ) + ^ ROR( p_tmp[ 0 ] ^ p_tmp[ 2 ], 8 ) + ^ ROR( p_tmp[ 2 ], 24 ); + + p_ctx[ 1 + 4 + 64 + (i - 4) ] = p_tmp[ 3 ] ^ p_tmp[ 4 ] + ^ p_tmp[ 1 ] ^ p_tmp[ 0 ]; + } +} + +static void ctx_xor( uint32_t *p_ctx, uint32_t *p_in, uint32_t *p_out, + uint32_t p_table1[ 256 ], uint32_t p_table2[ 256 ] ) +{ + uint32_t i, x, y; + uint32_t p_tmp1[ 4 ]; + uint32_t p_tmp2[ 4 ]; + + i = p_ctx[ 0 ] * 4; + + p_tmp1[ 0 ] = p_ctx[ 1 + i + 24 ] ^ p_in[ 0 ]; + p_tmp1[ 1 ] = p_ctx[ 1 + i + 25 ] ^ p_in[ 1 ]; + p_tmp1[ 2 ] = p_ctx[ 1 + i + 26 ] ^ p_in[ 2 ]; + p_tmp1[ 3 ] = p_ctx[ 1 + i + 27 ] ^ p_in[ 3 ]; + + i += 84; + +#define XOR_ROR( p_table, p_tmp, i_ctx ) \ + p_table[ (p_tmp[ y > 2 ? y - 3 : y + 1 ] >> 24) & 0xFF ] \ + ^ ROR( p_table[ (p_tmp[ y > 1 ? y - 2 : y + 2 ] >> 16) & 0xFF ], 8 ) \ + ^ ROR( p_table[ (p_tmp[ y > 0 ? y - 1 : y + 3 ] >> 8) & 0xFF ], 16 ) \ + ^ ROR( p_table[ p_tmp[ y ] & 0xFF ], 24 ) \ + ^ p_ctx[ i_ctx ] + + for( x = 0; x < 1; x++ ) + { + memcpy( p_tmp2, p_tmp1, sizeof(p_tmp1) ); + + for( y = 0; y < 4; y++ ) + { + p_tmp1[ y ] = XOR_ROR( p_table1, p_tmp2, 1 + i - x + y ); + } + } + + for( ; x < 9; x++ ) + { + memcpy( p_tmp2, p_tmp1, sizeof(p_tmp1) ); + + for( y = 0; y < 4; y++ ) + { + p_tmp1[ y ] = XOR_ROR( p_table1, p_tmp2, + 1 + i - x - ((x * 3) - y) ); + } + } + + for( y = 0; y < 4; y++ ) + { + p_out[ y ] = XOR_ROR( p_table2, p_tmp1, + 1 + i - x - ((x * 3) - y) ); + } + +#undef XOR_ROR +} + +static void taos( uint32_t *p_buffer, uint32_t *p_input ) +{ + uint32_t i; + uint32_t x = 0; + uint32_t p_tmp1[ 4 ]; + uint32_t p_tmp2[ 4 ]; + + memcpy( p_tmp1, p_buffer, sizeof(p_tmp1) ); + + p_tmp2[ 0 ] = ((~p_tmp1[ 1 ] & p_tmp1[ 3 ]) + | (p_tmp1[ 2 ] & p_tmp1[ 1 ])) + p_input[ x ]; + p_tmp1[ 0 ] = p_tmp2[ 0 ] + p_tmp1[ 0 ] + p_drms_tab_taos[ x++ ]; + + for( i = 0; i < 4; i++ ) + { + p_tmp2[ 0 ] = ((p_tmp1[ 0 ] >> 0x19) + | (p_tmp1[ 0 ] << 0x7)) + p_tmp1[ 1 ]; + p_tmp2[ 1 ] = ((~p_tmp2[ 0 ] & p_tmp1[ 2 ]) + | (p_tmp1[ 1 ] & p_tmp2[ 0 ])) + p_input[ x ]; + p_tmp2[ 1 ] += p_tmp1[ 3 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 3 ] = ((p_tmp2[ 1 ] >> 0x14) + | (p_tmp2[ 1 ] << 0xC)) + p_tmp2[ 0 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 3 ] & p_tmp1[ 1 ]) + | (p_tmp1[ 3 ] & p_tmp2[ 0 ])) + p_input[ x ]; + p_tmp2[ 1 ] += p_tmp1[ 2 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 2 ] = ((p_tmp2[ 1 ] >> 0xF) + | (p_tmp2[ 1 ] << 0x11)) + p_tmp1[ 3 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 2 ] & p_tmp2[ 0 ]) + | (p_tmp1[ 3 ] & p_tmp1[ 2 ])) + p_input[ x ]; + p_tmp2[ 2 ] = p_tmp2[ 1 ] + p_tmp1[ 1 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 1 ] = ((p_tmp2[ 2 ] << 0x16) + | (p_tmp2[ 2 ] >> 0xA)) + p_tmp1[ 2 ]; + if( i == 3 ) + { + p_tmp2[ 1 ] = ((~p_tmp1[ 3 ] & p_tmp1[ 2 ]) + | (p_tmp1[ 3 ] & p_tmp1[ 1 ])) + p_input[ 1 ]; + } + else + { + p_tmp2[ 1 ] = ((~p_tmp1[ 1 ] & p_tmp1[ 3 ]) + | (p_tmp1[ 2 ] & p_tmp1[ 1 ])) + p_input[ x ]; + } + p_tmp1[ 0 ] = p_tmp2[ 0 ] + p_tmp2[ 1 ] + p_drms_tab_taos[ x++ ]; + } + + for( i = 0; i < 4; i++ ) + { + uint8_t p_table[ 4 ][ 4 ] = + { + { 6, 11, 0, 5 }, + { 10, 15, 4, 9 }, + { 14, 3, 8, 13 }, + { 2, 7, 12, 5 } + }; + + p_tmp2[ 0 ] = ((p_tmp1[ 0 ] >> 0x1B) + | (p_tmp1[ 0 ] << 0x5)) + p_tmp1[ 1 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 2 ] & p_tmp1[ 1 ]) + | (p_tmp1[ 2 ] & p_tmp2[ 0 ])) + + p_input[ p_table[ i ][ 0 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 3 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 3 ] = ((p_tmp2[ 1 ] >> 0x17) + | (p_tmp2[ 1 ] << 0x9)) + p_tmp2[ 0 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 1 ] & p_tmp2[ 0 ]) + | (p_tmp1[ 3 ] & p_tmp1[ 1 ])) + + p_input[ p_table[ i ][ 1 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 2 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 2 ] = ((p_tmp2[ 1 ] >> 0x12) + | (p_tmp2[ 1 ] << 0xE)) + p_tmp1[ 3 ]; + p_tmp2[ 1 ] = ((~p_tmp2[ 0 ] & p_tmp1[ 3 ]) + | (p_tmp1[ 2 ] & p_tmp2[ 0 ])) + + p_input[ p_table[ i ][ 2 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 1 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 1 ] = ((p_tmp2[ 1 ] << 0x14) + | (p_tmp2[ 1 ] >> 0xC)) + p_tmp1[ 2 ]; + if( i == 3 ) + { + p_tmp2[ 1 ] = (p_tmp1[ 3 ] ^ p_tmp1[ 2 ] ^ p_tmp1[ 1 ]) + + p_input[ p_table[ i ][ 3 ] ]; + } + else + { + p_tmp2[ 1 ] = ((~p_tmp1[ 3 ] & p_tmp1[ 2 ]) + | (p_tmp1[ 3 ] & p_tmp1[ 1 ])) + + p_input[ p_table[ i ][ 3 ] ]; + } + p_tmp1[ 0 ] = p_tmp2[ 0 ] + p_tmp2[ 1 ] + p_drms_tab_taos[ x++ ]; + } + + for( i = 0; i < 4; i++ ) + { + uint8_t p_table[ 4 ][ 4 ] = + { + { 8, 11, 14, 1 }, + { 4, 7, 10, 13 }, + { 0, 3, 6, 9 }, + { 12, 15, 2, 0 } + }; + + p_tmp2[ 0 ] = ((p_tmp1[ 0 ] >> 0x1C) + | (p_tmp1[ 0 ] << 0x4)) + p_tmp1[ 1 ]; + p_tmp2[ 1 ] = (p_tmp1[ 2 ] ^ p_tmp1[ 1 ] ^ p_tmp2[ 0 ]) + + p_input[ p_table[ i ][ 0 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 3 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 3 ] = ((p_tmp2[ 1 ] >> 0x15) + | (p_tmp2[ 1 ] << 0xB)) + p_tmp2[ 0 ]; + p_tmp2[ 1 ] = (p_tmp1[ 3 ] ^ p_tmp1[ 1 ] ^ p_tmp2[ 0 ]) + + p_input[ p_table[ i ][ 1 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 2 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 2 ] = ((p_tmp2[ 1 ] >> 0x10) + | (p_tmp2[ 1 ] << 0x10)) + p_tmp1[ 3 ]; + p_tmp2[ 1 ] = (p_tmp1[ 3 ] ^ p_tmp1[ 2 ] ^ p_tmp2[ 0 ]) + + p_input[ p_table[ i ][ 2 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 1 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 1 ] = ((p_tmp2[ 1 ] << 0x17) + | (p_tmp2[ 1 ] >> 0x9)) + p_tmp1[ 2 ]; + if( i == 3 ) + { + p_tmp2[ 1 ] = ((~p_tmp1[ 3 ] | p_tmp1[ 1 ]) ^ p_tmp1[ 2 ]) + + p_input[ p_table[ i ][ 3 ] ]; + } + else + { + p_tmp2[ 1 ] = (p_tmp1[ 3 ] ^ p_tmp1[ 2 ] ^ p_tmp1[ 1 ]) + + p_input[ p_table[ i ][ 3 ] ]; + } + p_tmp1[ 0 ] = p_tmp2[ 0 ] + p_tmp2[ 1 ] + p_drms_tab_taos[ x++ ]; + } + + for( i = 0; i < 4; i++ ) + { + uint8_t p_table[ 4 ][ 4 ] = + { + { 7, 14, 5, 12 }, + { 3, 10, 1, 8 }, + { 15, 6, 13, 4 }, + { 11, 2, 9, 0 } + }; + + p_tmp2[ 0 ] = ((p_tmp1[ 0 ] >> 0x1A) + | (p_tmp1[ 0 ] << 0x6)) + p_tmp1[ 1 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 2 ] | p_tmp2[ 0 ]) ^ p_tmp1[ 1 ]) + + p_input[ p_table[ i ][ 0 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 3 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 3 ] = ((p_tmp2[ 1 ] >> 0x16) + | (p_tmp2[ 1 ] << 0xA)) + p_tmp2[ 0 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 1 ] | p_tmp1[ 3 ]) ^ p_tmp2[ 0 ]) + + p_input[ p_table[ i ][ 1 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 2 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 2 ] = ((p_tmp2[ 1 ] >> 0x11) + | (p_tmp2[ 1 ] << 0xF)) + p_tmp1[ 3 ]; + p_tmp2[ 1 ] = ((~p_tmp2[ 0 ] | p_tmp1[ 2 ]) ^ p_tmp1[ 3 ]) + + p_input[ p_table[ i ][ 2 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 1 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 1 ] = ((p_tmp2[ 1 ] << 0x15) + | (p_tmp2[ 1 ] >> 0xB)) + p_tmp1[ 2 ]; + + if( i < 3 ) + { + p_tmp2[ 1 ] = ((~p_tmp1[ 3 ] | p_tmp1[ 1 ]) ^ p_tmp1[ 2 ]) + + p_input[ p_table[ i ][ 3 ] ]; + p_tmp1[ 0 ] = p_tmp2[ 0 ] + p_tmp2[ 1 ] + p_drms_tab_taos[ x++ ]; + } + } + + p_buffer[ 0 ] += p_tmp2[ 0 ]; + p_buffer[ 1 ] += p_tmp1[ 1 ]; + p_buffer[ 2 ] += p_tmp1[ 2 ]; + p_buffer[ 3 ] += p_tmp1[ 3 ]; +} + +static void taos_add1( uint32_t *p_buffer, + uint8_t *p_in, uint32_t i_len ) +{ + uint32_t i; + uint32_t x, y; + uint32_t p_tmp[ 16 ]; + uint32_t i_offset = 0; + + x = p_buffer[ 6 ] & 63; + y = 64 - x; + + p_buffer[ 6 ] += i_len; + + if( i_len < y ) + { + memcpy( &((uint8_t *)p_buffer)[ 48 + x ], p_in, i_len ); + } + else + { + if( x ) + { + memcpy( &((uint8_t *)p_buffer)[ 48 + x ], p_in, y ); + taos( &p_buffer[ 8 ], &p_buffer[ 12 ] ); + i_offset = y; + i_len -= y; + } + + if( i_len >= 64 ) + { + for( i = 0; i < i_len / 64; i++ ) + { + memcpy( p_tmp, &p_in[ i_offset ], sizeof(p_tmp) ); + taos( &p_buffer[ 8 ], p_tmp ); + i_offset += 64; + i_len -= 64; + } + } + + if( i_len ) + { + memcpy( &p_buffer[ 12 ], &p_in[ i_offset ], i_len ); + } + } +} + +static void taos_end1( uint32_t *p_buffer, uint32_t *p_out ) +{ + uint32_t x, y; + + x = p_buffer[ 6 ] & 63; + y = 63 - x; + + ((uint8_t *)p_buffer)[ 48 + x++ ] = 128; + + if( y < 8 ) + { + memset( &((uint8_t *)p_buffer)[ 48 + x ], 0, y ); + taos( &p_buffer[ 8 ], &p_buffer[ 12 ] ); + y = 64; + x = 0; + } + + memset( &((uint8_t *)p_buffer)[ 48 + x ], 0, y ); + + p_buffer[ 26 ] = p_buffer[ 6 ] * 8; + p_buffer[ 27 ] = p_buffer[ 6 ] >> 29; + taos( &p_buffer[ 8 ], &p_buffer[ 12 ] ); + + memcpy( p_out, &p_buffer[ 8 ], sizeof(*p_out) * 4 ); +} + +static void taos_add2( uint32_t *p_buffer, uint8_t *p_in, uint32_t i_len ) +{ + uint32_t i, x; + uint32_t p_tmp[ 16 ]; + + x = (p_buffer[ 0 ] / 8) & 63; + i = p_buffer[ 0 ] + i_len * 8; + + if( i < p_buffer[ 0 ] ) + { + p_buffer[ 1 ] += 1; + } + + p_buffer[ 0 ] = i; + p_buffer[ 1 ] += i_len >> 29; + + for( i = 0; i < i_len; i++ ) + { + ((uint8_t *)p_buffer)[ 24 + x++ ] = p_in[ i ]; + + if( x != 64 ) + continue; + + memcpy( p_tmp, &p_buffer[ 6 ], sizeof(p_tmp) ); + taos( &p_buffer[ 2 ], p_tmp ); + } +} + +static void taos_add2e( uint32_t *p_buffer, uint32_t *p_in, uint32_t i_len ) +{ + uint32_t i, x, y; + uint32_t p_tmp[ 32 ]; + + if( i_len ) + { + for( x = i_len; x; x -= y ) + { + y = x > 32 ? 32 : x; + + for( i = 0; i < y; i++ ) + { + p_tmp[ i ] = U32_AT(&p_in[ i ]); + } + } + } + + taos_add2( p_buffer, (uint8_t *)p_tmp, i_len * sizeof(p_tmp[ 0 ]) ); +} + +static void taos_end2( uint32_t *p_buffer ) +{ + uint32_t x; + uint32_t p_tmp[ 16 ]; + + p_tmp[ 14 ] = p_buffer[ 0 ]; + p_tmp[ 15 ] = p_buffer[ 1 ]; + + x = (p_buffer[ 0 ] / 8) & 63; + + taos_add2( p_buffer, p_drms_tab_tend, 56 - x ); + memcpy( p_tmp, &p_buffer[ 6 ], 56 ); + taos( &p_buffer[ 2 ], p_tmp ); + memcpy( &p_buffer[ 22 ], &p_buffer[ 2 ], sizeof(*p_buffer) * 4 ); +} + +static void taos_add3( uint32_t *p_buffer, uint8_t *p_key, uint32_t i_len ) +{ + uint32_t x, y; + uint32_t i = 0; + + x = (p_buffer[ 4 ] / 8) & 63; + p_buffer[ 4 ] += i_len * 8; + + if( p_buffer[ 4 ] < i_len * 8 ) + p_buffer[ 5 ] += 1; + + p_buffer[ 5 ] += i_len >> 29; + + y = 64 - x; + + if( i_len >= y ) + { + memcpy( &((uint8_t *)p_buffer)[ 24 + x ], p_key, y ); + taos( p_buffer, &p_buffer[ 6 ] ); + + i = y; + y += 63; + + if( y < i_len ) + { + for( ; y < i_len; y += 64, i += 64 ) + { + taos( p_buffer, (uint32_t *)&p_key[y - 63] ); + } + } + else + { + x = 0; + } + } + + memcpy( &((uint8_t *)p_buffer)[ 24 + x ], &p_key[ i ], i_len - i ); +} + +static int taos_osi( uint32_t *p_buffer ) +{ + int i_ret = 0; + +#ifdef _WIN32 + HKEY i_key; + uint32_t i; + DWORD i_size; + DWORD i_serial; + LPBYTE p_reg_buf; + + static LPCTSTR p_reg_keys[ 3 ][ 2 ] = + { + { + _T("HARDWARE\\DESCRIPTION\\System"), + _T("SystemBiosVersion") + }, + + { + _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), + _T("ProcessorNameString") + }, + + { + _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion"), + _T("ProductId") + } + }; + + taos_add1( p_buffer, "cache-control", 13 ); + taos_add1( p_buffer, "Ethernet", 8 ); + + GetVolumeInformation( _T("C:\\"), NULL, 0, &i_serial, + NULL, NULL, NULL, 0 ); + taos_add1( p_buffer, (uint8_t *)&i_serial, 4 ); + + for( i = 0; i < sizeof(p_reg_keys)/sizeof(p_reg_keys[ 0 ]); i++ ) + { + if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, p_reg_keys[ i ][ 0 ], + 0, KEY_READ, &i_key ) == ERROR_SUCCESS ) + { + if( RegQueryValueEx( i_key, p_reg_keys[ i ][ 1 ], + NULL, NULL, NULL, + &i_size ) == ERROR_SUCCESS ) + { + p_reg_buf = malloc( i_size ); + + if( p_reg_buf != NULL ) + { + if( RegQueryValueEx( i_key, p_reg_keys[ i ][ 1 ], + NULL, NULL, p_reg_buf, + &i_size ) == ERROR_SUCCESS ) + { + taos_add1( p_buffer, (uint8_t *)p_reg_buf, + i_size ); + } + + free( p_reg_buf ); + } + } + + RegCloseKey( i_key ); + } + } + +#else + i_ret = -1; +#endif + + return( i_ret ); +} + +static int get_sci_data( uint32_t p_sci[ 11 ][ 4 ] ) +{ + int i_ret = -1; + +#ifdef _WIN32 + HANDLE i_file; + DWORD i_size, i_read; + TCHAR p_path[ MAX_PATH ]; + TCHAR *p_filename = _T("\\Apple Computer\\iTunes\\SC Info\\SC Info.sidb"); + + typedef HRESULT (WINAPI *SHGETFOLDERPATH)( HWND, int, HANDLE, DWORD, + LPTSTR ); + + HINSTANCE shfolder_dll = NULL; + SHGETFOLDERPATH dSHGetFolderPath = NULL; + + if( ( shfolder_dll = LoadLibrary( _T("SHFolder.dll") ) ) != NULL ) + { + dSHGetFolderPath = + (SHGETFOLDERPATH)GetProcAddress( shfolder_dll, +#ifdef _UNICODE + _T("SHGetFolderPathW") ); +#else + _T("SHGetFolderPathA") ); +#endif + } + + if( dSHGetFolderPath != NULL && + SUCCEEDED( dSHGetFolderPath( NULL, /*CSIDL_COMMON_APPDATA*/ 0x0023, + NULL, 0, p_path ) ) ) + { + _tcsncat( p_path, p_filename, min( _tcslen( p_filename ), + (MAX_PATH-1) - _tcslen( p_path ) ) ); + + i_file = CreateFile( p_path, GENERIC_READ, 0, NULL, + OPEN_EXISTING, 0, NULL ); + if( i_file != INVALID_HANDLE_VALUE ) + { + i_read = sizeof(p_sci[ 0 ]) * 11; + i_size = GetFileSize( i_file, NULL ); + if( i_size != INVALID_FILE_SIZE && i_size >= i_read ) + { + i_size = SetFilePointer( i_file, 4, NULL, FILE_BEGIN ); + if( i_size != /*INVALID_SET_FILE_POINTER*/ ((DWORD)-1)) + { + if( ReadFile( i_file, p_sci, i_read, &i_size, NULL ) && + i_size == i_read ) + { + i_ret = 0; + } + } + } + + CloseHandle( i_file ); + } + } +#endif + + return( i_ret ); +} + +static void acei_taxs( uint32_t *p_acei, uint32_t i_val ) +{ + uint32_t i, x; + + i = (i_val / 16) & 15; + x = (~(i_val & 15)) & 15; + + if( (i_val & 768) == 768 ) + { + x = (~i) & 15; + i = i_val & 15; + + p_acei[ 25 + i ] = p_acei[ 25 + ((16 - x) & 15) ] + + p_acei[ 25 + (15 - x) ]; + } + else if( (i_val & 512) == 512 ) + { + p_acei[ 25 + i ] ^= p_drms_tab_xor[ 15 - i ][ x ]; + } + else if( (i_val & 256) == 256 ) + { + p_acei[ 25 + i ] -= p_drms_tab_sub[ 15 - i ][ x ]; + } + else + { + p_acei[ 25 + i ] += p_drms_tab_add[ 15 - i ][ x ]; + } +} + +static void acei( uint32_t *p_acei, uint8_t *p_buffer, uint32_t i_len ) +{ + uint32_t i, x; + uint32_t p_tmp[ 26 ]; + + for( i = 5; i < 25; i++ ) + { + if( p_acei[ i ] ) + { + acei_taxs( p_acei, p_acei[ i ] ); + } + } + + TAOS_INIT( p_tmp, 2 ); + taos_add2e( p_tmp, &p_acei[ 25 ], sizeof(*p_acei) * 4 ); + taos_end2( p_tmp ); + + x = i_len < 16 ? i_len : 16; + + if( x > 0 ) + { + for( i = 0; i < x; i++ ) + { + p_buffer[ i ] ^= ((uint8_t *)&p_tmp)[ 88 + i ]; + } + } +} + +static uint32_t ttov_calc( uint32_t *p_acei ) +{ + int32_t i_val; + uint32_t p_tmp[ 26 ]; + + TAOS_INIT( p_tmp, 2 ); + taos_add2e( p_tmp, &p_acei[ 0 ], 4 ); + taos_add2e( p_tmp, &p_acei[ 4 ], 1 ); + taos_end2( p_tmp ); + + p_acei[ 4 ]++; + + i_val = ((int32_t)U32_AT(&p_tmp[ 22 ])) % 1024; + + return( i_val < 0 ? i_val * -1 : i_val ); +} + +static void acei_init( uint32_t *p_acei, uint32_t *p_sys_key ) +{ + uint32_t i; + + for( i = 0; i < 4; i++ ) + { + p_acei[ i ] = U32_AT(&p_sys_key[ i ]); + } + + p_acei[ 4 ] = 0x5476212A; + + for( i = 5; i < 25; i++ ) + { + p_acei[ i ] = ttov_calc( p_acei ); + } + + p_acei[ 25 + 0 ] = p_acei[ 0 ]; + p_acei[ 25 + 1 ] = 0x68723876; + p_acei[ 25 + 2 ] = 0x41617376; + p_acei[ 25 + 3 ] = 0x4D4B4F76; + + p_acei[ 25 + 4 ] = p_acei[ 1 ]; + p_acei[ 25 + 5 ] = 0x48556646; + p_acei[ 25 + 6 ] = 0x38393725; + p_acei[ 25 + 7 ] = 0x2E3B5B3D; + + p_acei[ 25 + 8 ] = p_acei[ 2 ]; + p_acei[ 25 + 9 ] = 0x37363866; + p_acei[ 25 + 10 ] = 0x30383637; + p_acei[ 25 + 11 ] = 0x34333661; + + p_acei[ 25 + 12 ] = p_acei[ 3 ]; + p_acei[ 25 + 13 ] = 0x37386162; + p_acei[ 25 + 14 ] = 0x494F6E66; + p_acei[ 25 + 15 ] = 0x2A282966; +} + +static __inline void block_xor( uint32_t *p_in, uint32_t *p_key, + uint32_t *p_out ) +{ + uint32_t i; + + for( i = 0; i < 4; i++ ) + { + p_out[ i ] = p_key[ i ] ^ p_in[ i ]; + } +} + +int drms_get_sys_key( uint32_t *p_sys_key ) +{ + uint32_t p_tmp[ 128 ]; + uint32_t p_tmp_key[ 4 ]; + + TAOS_INIT( p_tmp, 8 ); + if( taos_osi( p_tmp ) ) + { + return( -1 ); + } + taos_end1( p_tmp, p_tmp_key ); + + TAOS_INIT( p_tmp, 2 ); + taos_add2( p_tmp, "YuaFlafu", 8 ); + taos_add2( p_tmp, (uint8_t *)p_tmp_key, 6 ); + taos_add2( p_tmp, (uint8_t *)p_tmp_key, 6 ); + taos_add2( p_tmp, (uint8_t *)p_tmp_key, 6 ); + taos_add2( p_tmp, "zPif98ga", 8 ); + taos_end2( p_tmp ); + + memcpy( p_sys_key, &p_tmp[ 2 ], sizeof(*p_sys_key) * 4 ); + + return( 0 ); +} + +int drms_get_user_key( uint32_t *p_sys_key, uint32_t *p_user_key ) +{ + uint32_t i; + uint32_t p_tmp[ 4 ]; + uint32_t *p_cur_key; + uint32_t p_acei[ 41 ]; + uint32_t p_ctx[ 128 ]; + uint32_t p_sci[ 2 ][ 11 ][ 4 ]; + + uint32_t p_sci_key[ 4 ] = + { + 0x6E66556D, 0x6E676F70, 0x67666461, 0x33373866 + }; + + if( p_sys_key == NULL ) + { + if( drms_get_sys_key( p_tmp ) ) + { + return( -1 ); + } + + p_sys_key = p_tmp; + } + + if( get_sci_data( p_sci[ 0 ] ) ) + { + return( -1 ); + } + + init_ctx( p_ctx, p_sys_key ); + + for( i = 0, p_cur_key = p_sci_key; + i < sizeof(p_sci[ 0 ])/sizeof(p_sci[ 0 ][ 0 ]); i++ ) + { + ctx_xor( p_ctx, &p_sci[ 0 ][ i ][ 0 ], &p_sci[ 1 ][ i ][ 0 ], + p_drms_tab3, p_drms_tab4 ); + block_xor( &p_sci[ 1 ][ i ][ 0 ], p_cur_key, &p_sci[ 1 ][ i ][ 0 ] ); + + p_cur_key = &p_sci[ 0 ][ i ][ 0 ]; + } + + acei_init( p_acei, p_sys_key ); + + for( i = 0; i < sizeof(p_sci[ 1 ])/sizeof(p_sci[ 1 ][ 0 ]); i++ ) + { + acei( p_acei, (uint8_t *)&p_sci[ 1 ][ i ][ 0 ], + sizeof(p_sci[ 1 ][ i ]) ); + } + + memcpy( p_user_key, &p_sci[ 1 ][ 10 ][ 0 ], sizeof(p_sci[ 1 ][ i ]) ); + + return( 0 ); +} + +struct drms_s +{ + uint8_t *p_iviv; + uint32_t i_iviv_len; + uint8_t *p_name; + uint32_t i_name_len; + + uint32_t *p_tmp; + uint32_t i_tmp_len; + + uint32_t p_key[ 4 ]; + uint32_t p_ctx[ 128 ]; +}; + +#define P_DRMS ((struct drms_s *)p_drms) + +void *drms_alloc() +{ + struct drms_s *p_drms; + + p_drms = malloc( sizeof(struct drms_s) ); + + if( p_drms != NULL ) + { + memset( p_drms, 0, sizeof(struct drms_s) ); + + p_drms->i_tmp_len = 1024; + p_drms->p_tmp = malloc( p_drms->i_tmp_len ); + if( p_drms->p_tmp == NULL ) + { + free( (void *)p_drms ); + p_drms = NULL; + } + } + + return( (void *)p_drms ); +} + +void drms_free( void *p_drms ) +{ + if( P_DRMS->p_name != NULL ) + { + free( (void *)P_DRMS->p_name ); + } + + if( P_DRMS->p_iviv != NULL ) + { + free( (void *)P_DRMS->p_iviv ); + } + + if( P_DRMS->p_tmp != NULL ) + { + free( (void *)P_DRMS->p_tmp ); + } + + free( p_drms ); +} + +void drms_decrypt( void *p_drms, uint32_t *p_buffer, uint32_t i_len ) +{ + uint32_t i, x, y; + uint32_t *p_cur_key = P_DRMS->p_key; + + x = (i_len / sizeof(P_DRMS->p_key)) * sizeof(P_DRMS->p_key); + + if( P_DRMS->i_tmp_len < x ) + { + free( (void *)P_DRMS->p_tmp ); + + P_DRMS->i_tmp_len = x; + P_DRMS->p_tmp = malloc( P_DRMS->i_tmp_len ); + } + + if( P_DRMS->p_tmp != NULL ) + { + memcpy( P_DRMS->p_tmp, p_buffer, x ); + + for( i = 0, x /= sizeof(P_DRMS->p_key); i < x; i++ ) + { + y = i * sizeof(*p_buffer); + + ctx_xor( P_DRMS->p_ctx, P_DRMS->p_tmp + y, p_buffer + y, + p_drms_tab3, p_drms_tab4 ); + block_xor( p_buffer + y, p_cur_key, p_buffer + y ); + + p_cur_key = P_DRMS->p_tmp + y; + } + } +} + +int drms_init( void *p_drms, uint32_t i_type, + uint8_t *p_info, uint32_t i_len ) +{ + int i_ret = 0; + + switch( i_type ) + { + case DRMS_INIT_UKEY: + { + if( i_len != sizeof(P_DRMS->p_key) ) + { + i_ret = -1; + break; + } + + init_ctx( P_DRMS->p_ctx, (uint32_t *)p_info ); + } + break; + + case DRMS_INIT_IVIV: + { + if( i_len != sizeof(P_DRMS->p_key) ) + { + i_ret = -1; + break; + } + + P_DRMS->p_iviv = malloc( i_len ); + if( P_DRMS->p_iviv == NULL ) + { + i_ret = -1; + break; + } + + memcpy( P_DRMS->p_iviv, p_info, i_len ); + P_DRMS->i_iviv_len = i_len; + } + break; + + case DRMS_INIT_NAME: + { + P_DRMS->p_name = malloc( i_len ); + if( P_DRMS->p_name == NULL ) + { + i_ret = -1; + break; + } + + memcpy( P_DRMS->p_name, p_info, i_len ); + P_DRMS->i_name_len = i_len; + } + break; + + case DRMS_INIT_PRIV: + { + uint32_t i; + uint32_t p_priv[ 64 ]; + uint32_t p_tmp[ 128 ]; + + if( i_len < 64 ) + { + i_ret = -1; + break; + } + + TAOS_INIT( p_tmp, 0 ); + taos_add3( p_tmp, P_DRMS->p_name, P_DRMS->i_name_len ); + taos_add3( p_tmp, P_DRMS->p_iviv, P_DRMS->i_iviv_len ); + memcpy( p_priv, &p_tmp[ 4 ], sizeof(p_priv[ 0 ]) * 2 ); + i = (p_tmp[ 4 ] / 8) & 63; + i = i >= 56 ? 120 - i : 56 - i; + taos_add3( p_tmp, p_drms_tab_tend, i ); + taos_add3( p_tmp, (uint8_t *)p_priv, sizeof(p_priv[ 0 ]) * 2 ); + + memcpy( p_priv, p_info, 64 ); + memcpy( P_DRMS->p_key, p_tmp, sizeof(P_DRMS->p_key) ); + drms_decrypt( p_drms, p_priv, sizeof(p_priv) ); + + init_ctx( P_DRMS->p_ctx, &p_priv[ 6 ] ); + memcpy( P_DRMS->p_key, &p_priv[ 12 ], sizeof(P_DRMS->p_key) ); + + free( (void *)P_DRMS->p_name ); + P_DRMS->p_name = NULL; + free( (void *)P_DRMS->p_iviv ); + P_DRMS->p_iviv = NULL; + } + break; + } + + return( i_ret ); +} + +#undef P_DRMS + +#endif + diff --git a/trunk/src/mp4ff/drms.h b/trunk/src/mp4ff/drms.h new file mode 100644 index 000000000..42d957400 --- /dev/null +++ b/trunk/src/mp4ff/drms.h @@ -0,0 +1,40 @@ +/***************************************************************************** + * drms.h : DRMS + ***************************************************************************** + * Copyright (C) 2004 VideoLAN + * $Id: drms.h,v 1.3 2004/01/11 15:52:18 menno Exp $ + * + * Author: Jon Lech Johansen <jon-vl@nanocrew.net> + * + * 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, USA. + *****************************************************************************/ + + +#define DRMS_INIT_UKEY 0 +#define DRMS_INIT_IVIV 1 +#define DRMS_INIT_NAME 2 +#define DRMS_INIT_PRIV 3 + +extern int drms_get_sys_key( uint32_t *p_sys_key ); +extern int drms_get_user_key( uint32_t *p_sys_key, + uint32_t *p_user_key ); + +extern void *drms_alloc(); +extern void drms_free( void *p_drms ); +extern int drms_init( void *p_drms, uint32_t i_type, + uint8_t *p_info, uint32_t i_len ); +extern void drms_decrypt( void *p_drms, uint32_t *p_buffer, + uint32_t i_len ); + diff --git a/trunk/src/mp4ff/drmstables.h b/trunk/src/mp4ff/drmstables.h new file mode 100644 index 000000000..e38c1f762 --- /dev/null +++ b/trunk/src/mp4ff/drmstables.h @@ -0,0 +1,449 @@ +/***************************************************************************** + * drmstables.h : DRMS tables + ***************************************************************************** + * Copyright (C) 2004 VideoLAN + * $Id: drmstables.h,v 1.2 2004/01/11 15:52:18 menno Exp $ + * + * Author: Jon Lech Johansen <jon-vl@nanocrew.net> + * + * 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, USA. + *****************************************************************************/ + + +static uint32_t p_drms_tab1[ 10 ] = +{ + 0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, + 0x00000040, 0x00000080, 0x0000001B, 0x00000036 +}; + +static uint32_t p_drms_tab2[ 256 ] = +{ + 0x63000000, 0x7C000000, 0x77000000, 0x7B000000, 0xF2000000, 0x6B000000, + 0x6F000000, 0xC5000000, 0x30000000, 0x01000000, 0x67000000, 0x2B000000, + 0xFE000000, 0xD7000000, 0xAB000000, 0x76000000, 0xCA000000, 0x82000000, + 0xC9000000, 0x7D000000, 0xFA000000, 0x59000000, 0x47000000, 0xF0000000, + 0xAD000000, 0xD4000000, 0xA2000000, 0xAF000000, 0x9C000000, 0xA4000000, + 0x72000000, 0xC0000000, 0xB7000000, 0xFD000000, 0x93000000, 0x26000000, + 0x36000000, 0x3F000000, 0xF7000000, 0xCC000000, 0x34000000, 0xA5000000, + 0xE5000000, 0xF1000000, 0x71000000, 0xD8000000, 0x31000000, 0x15000000, + 0x04000000, 0xC7000000, 0x23000000, 0xC3000000, 0x18000000, 0x96000000, + 0x05000000, 0x9A000000, 0x07000000, 0x12000000, 0x80000000, 0xE2000000, + 0xEB000000, 0x27000000, 0xB2000000, 0x75000000, 0x09000000, 0x83000000, + 0x2C000000, 0x1A000000, 0x1B000000, 0x6E000000, 0x5A000000, 0xA0000000, + 0x52000000, 0x3B000000, 0xD6000000, 0xB3000000, 0x29000000, 0xE3000000, + 0x2F000000, 0x84000000, 0x53000000, 0xD1000000, 0x00000000, 0xED000000, + 0x20000000, 0xFC000000, 0xB1000000, 0x5B000000, 0x6A000000, 0xCB000000, + 0xBE000000, 0x39000000, 0x4A000000, 0x4C000000, 0x58000000, 0xCF000000, + 0xD0000000, 0xEF000000, 0xAA000000, 0xFB000000, 0x43000000, 0x4D000000, + 0x33000000, 0x85000000, 0x45000000, 0xF9000000, 0x02000000, 0x7F000000, + 0x50000000, 0x3C000000, 0x9F000000, 0xA8000000, 0x51000000, 0xA3000000, + 0x40000000, 0x8F000000, 0x92000000, 0x9D000000, 0x38000000, 0xF5000000, + 0xBC000000, 0xB6000000, 0xDA000000, 0x21000000, 0x10000000, 0xFF000000, + 0xF3000000, 0xD2000000, 0xCD000000, 0x0C000000, 0x13000000, 0xEC000000, + 0x5F000000, 0x97000000, 0x44000000, 0x17000000, 0xC4000000, 0xA7000000, + 0x7E000000, 0x3D000000, 0x64000000, 0x5D000000, 0x19000000, 0x73000000, + 0x60000000, 0x81000000, 0x4F000000, 0xDC000000, 0x22000000, 0x2A000000, + 0x90000000, 0x88000000, 0x46000000, 0xEE000000, 0xB8000000, 0x14000000, + 0xDE000000, 0x5E000000, 0x0B000000, 0xDB000000, 0xE0000000, 0x32000000, + 0x3A000000, 0x0A000000, 0x49000000, 0x06000000, 0x24000000, 0x5C000000, + 0xC2000000, 0xD3000000, 0xAC000000, 0x62000000, 0x91000000, 0x95000000, + 0xE4000000, 0x79000000, 0xE7000000, 0xC8000000, 0x37000000, 0x6D000000, + 0x8D000000, 0xD5000000, 0x4E000000, 0xA9000000, 0x6C000000, 0x56000000, + 0xF4000000, 0xEA000000, 0x65000000, 0x7A000000, 0xAE000000, 0x08000000, + 0xBA000000, 0x78000000, 0x25000000, 0x2E000000, 0x1C000000, 0xA6000000, + 0xB4000000, 0xC6000000, 0xE8000000, 0xDD000000, 0x74000000, 0x1F000000, + 0x4B000000, 0xBD000000, 0x8B000000, 0x8A000000, 0x70000000, 0x3E000000, + 0xB5000000, 0x66000000, 0x48000000, 0x03000000, 0xF6000000, 0x0E000000, + 0x61000000, 0x35000000, 0x57000000, 0xB9000000, 0x86000000, 0xC1000000, + 0x1D000000, 0x9E000000, 0xE1000000, 0xF8000000, 0x98000000, 0x11000000, + 0x69000000, 0xD9000000, 0x8E000000, 0x94000000, 0x9B000000, 0x1E000000, + 0x87000000, 0xE9000000, 0xCE000000, 0x55000000, 0x28000000, 0xDF000000, + 0x8C000000, 0xA1000000, 0x89000000, 0x0D000000, 0xBF000000, 0xE6000000, + 0x42000000, 0x68000000, 0x41000000, 0x99000000, 0x2D000000, 0x0F000000, + 0xB0000000, 0x54000000, 0xBB000000, 0x16000000 +}; + +static uint32_t p_drms_tab3[ 256 ] = +{ + 0x5150A7F4, 0x7E536541, 0x1AC3A417, 0x3A965E27, 0x3BCB6BAB, 0x1FF1459D, + 0xACAB58FA, 0x4B9303E3, 0x2055FA30, 0xADF66D76, 0x889176CC, 0xF5254C02, + 0x4FFCD7E5, 0xC5D7CB2A, 0x26804435, 0xB58FA362, 0xDE495AB1, 0x25671BBA, + 0x45980EEA, 0x5DE1C0FE, 0xC302752F, 0x8112F04C, 0x8DA39746, 0x6BC6F9D3, + 0x03E75F8F, 0x15959C92, 0xBFEB7A6D, 0x95DA5952, 0xD42D83BE, 0x58D32174, + 0x492969E0, 0x8E44C8C9, 0x756A89C2, 0xF478798E, 0x996B3E58, 0x27DD71B9, + 0xBEB64FE1, 0xF017AD88, 0xC966AC20, 0x7DB43ACE, 0x63184ADF, 0xE582311A, + 0x97603351, 0x62457F53, 0xB1E07764, 0xBB84AE6B, 0xFE1CA081, 0xF9942B08, + 0x70586848, 0x8F19FD45, 0x94876CDE, 0x52B7F87B, 0xAB23D373, 0x72E2024B, + 0xE3578F1F, 0x662AAB55, 0xB20728EB, 0x2F03C2B5, 0x869A7BC5, 0xD3A50837, + 0x30F28728, 0x23B2A5BF, 0x02BA6A03, 0xED5C8216, 0x8A2B1CCF, 0xA792B479, + 0xF3F0F207, 0x4EA1E269, 0x65CDF4DA, 0x06D5BE05, 0xD11F6234, 0xC48AFEA6, + 0x349D532E, 0xA2A055F3, 0x0532E18A, 0xA475EBF6, 0x0B39EC83, 0x40AAEF60, + 0x5E069F71, 0xBD51106E, 0x3EF98A21, 0x963D06DD, 0xDDAE053E, 0x4D46BDE6, + 0x91B58D54, 0x71055DC4, 0x046FD406, 0x60FF1550, 0x1924FB98, 0xD697E9BD, + 0x89CC4340, 0x67779ED9, 0xB0BD42E8, 0x07888B89, 0xE7385B19, 0x79DBEEC8, + 0xA1470A7C, 0x7CE90F42, 0xF8C91E84, 0x00000000, 0x09838680, 0x3248ED2B, + 0x1EAC7011, 0x6C4E725A, 0xFDFBFF0E, 0x0F563885, 0x3D1ED5AE, 0x3627392D, + 0x0A64D90F, 0x6821A65C, 0x9BD1545B, 0x243A2E36, 0x0CB1670A, 0x930FE757, + 0xB4D296EE, 0x1B9E919B, 0x804FC5C0, 0x61A220DC, 0x5A694B77, 0x1C161A12, + 0xE20ABA93, 0xC0E52AA0, 0x3C43E022, 0x121D171B, 0x0E0B0D09, 0xF2ADC78B, + 0x2DB9A8B6, 0x14C8A91E, 0x578519F1, 0xAF4C0775, 0xEEBBDD99, 0xA3FD607F, + 0xF79F2601, 0x5CBCF572, 0x44C53B66, 0x5B347EFB, 0x8B762943, 0xCBDCC623, + 0xB668FCED, 0xB863F1E4, 0xD7CADC31, 0x42108563, 0x13402297, 0x842011C6, + 0x857D244A, 0xD2F83DBB, 0xAE1132F9, 0xC76DA129, 0x1D4B2F9E, 0xDCF330B2, + 0x0DEC5286, 0x77D0E3C1, 0x2B6C16B3, 0xA999B970, 0x11FA4894, 0x472264E9, + 0xA8C48CFC, 0xA01A3FF0, 0x56D82C7D, 0x22EF9033, 0x87C74E49, 0xD9C1D138, + 0x8CFEA2CA, 0x98360BD4, 0xA6CF81F5, 0xA528DE7A, 0xDA268EB7, 0x3FA4BFAD, + 0x2CE49D3A, 0x500D9278, 0x6A9BCC5F, 0x5462467E, 0xF6C2138D, 0x90E8B8D8, + 0x2E5EF739, 0x82F5AFC3, 0x9FBE805D, 0x697C93D0, 0x6FA92DD5, 0xCFB31225, + 0xC83B99AC, 0x10A77D18, 0xE86E639C, 0xDB7BBB3B, 0xCD097826, 0x6EF41859, + 0xEC01B79A, 0x83A89A4F, 0xE6656E95, 0xAA7EE6FF, 0x2108CFBC, 0xEFE6E815, + 0xBAD99BE7, 0x4ACE366F, 0xEAD4099F, 0x29D67CB0, 0x31AFB2A4, 0x2A31233F, + 0xC63094A5, 0x35C066A2, 0x7437BC4E, 0xFCA6CA82, 0xE0B0D090, 0x3315D8A7, + 0xF14A9804, 0x41F7DAEC, 0x7F0E50CD, 0x172FF691, 0x768DD64D, 0x434DB0EF, + 0xCC544DAA, 0xE4DF0496, 0x9EE3B5D1, 0x4C1B886A, 0xC1B81F2C, 0x467F5165, + 0x9D04EA5E, 0x015D358C, 0xFA737487, 0xFB2E410B, 0xB35A1D67, 0x9252D2DB, + 0xE9335610, 0x6D1347D6, 0x9A8C61D7, 0x377A0CA1, 0x598E14F8, 0xEB893C13, + 0xCEEE27A9, 0xB735C961, 0xE1EDE51C, 0x7A3CB147, 0x9C59DFD2, 0x553F73F2, + 0x1879CE14, 0x73BF37C7, 0x53EACDF7, 0x5F5BAAFD, 0xDF146F3D, 0x7886DB44, + 0xCA81F3AF, 0xB93EC468, 0x382C3424, 0xC25F40A3, 0x1672C31D, 0xBC0C25E2, + 0x288B493C, 0xFF41950D, 0x397101A8, 0x08DEB30C, 0xD89CE4B4, 0x6490C156, + 0x7B6184CB, 0xD570B632, 0x48745C6C, 0xD04257B8 +}; + +static uint32_t p_drms_tab4[ 256 ] = +{ + 0x52000000, 0x09000000, 0x6A000000, 0xD5000000, 0x30000000, 0x36000000, + 0xA5000000, 0x38000000, 0xBF000000, 0x40000000, 0xA3000000, 0x9E000000, + 0x81000000, 0xF3000000, 0xD7000000, 0xFB000000, 0x7C000000, 0xE3000000, + 0x39000000, 0x82000000, 0x9B000000, 0x2F000000, 0xFF000000, 0x87000000, + 0x34000000, 0x8E000000, 0x43000000, 0x44000000, 0xC4000000, 0xDE000000, + 0xE9000000, 0xCB000000, 0x54000000, 0x7B000000, 0x94000000, 0x32000000, + 0xA6000000, 0xC2000000, 0x23000000, 0x3D000000, 0xEE000000, 0x4C000000, + 0x95000000, 0x0B000000, 0x42000000, 0xFA000000, 0xC3000000, 0x4E000000, + 0x08000000, 0x2E000000, 0xA1000000, 0x66000000, 0x28000000, 0xD9000000, + 0x24000000, 0xB2000000, 0x76000000, 0x5B000000, 0xA2000000, 0x49000000, + 0x6D000000, 0x8B000000, 0xD1000000, 0x25000000, 0x72000000, 0xF8000000, + 0xF6000000, 0x64000000, 0x86000000, 0x68000000, 0x98000000, 0x16000000, + 0xD4000000, 0xA4000000, 0x5C000000, 0xCC000000, 0x5D000000, 0x65000000, + 0xB6000000, 0x92000000, 0x6C000000, 0x70000000, 0x48000000, 0x50000000, + 0xFD000000, 0xED000000, 0xB9000000, 0xDA000000, 0x5E000000, 0x15000000, + 0x46000000, 0x57000000, 0xA7000000, 0x8D000000, 0x9D000000, 0x84000000, + 0x90000000, 0xD8000000, 0xAB000000, 0x00000000, 0x8C000000, 0xBC000000, + 0xD3000000, 0x0A000000, 0xF7000000, 0xE4000000, 0x58000000, 0x05000000, + 0xB8000000, 0xB3000000, 0x45000000, 0x06000000, 0xD0000000, 0x2C000000, + 0x1E000000, 0x8F000000, 0xCA000000, 0x3F000000, 0x0F000000, 0x02000000, + 0xC1000000, 0xAF000000, 0xBD000000, 0x03000000, 0x01000000, 0x13000000, + 0x8A000000, 0x6B000000, 0x3A000000, 0x91000000, 0x11000000, 0x41000000, + 0x4F000000, 0x67000000, 0xDC000000, 0xEA000000, 0x97000000, 0xF2000000, + 0xCF000000, 0xCE000000, 0xF0000000, 0xB4000000, 0xE6000000, 0x73000000, + 0x96000000, 0xAC000000, 0x74000000, 0x22000000, 0xE7000000, 0xAD000000, + 0x35000000, 0x85000000, 0xE2000000, 0xF9000000, 0x37000000, 0xE8000000, + 0x1C000000, 0x75000000, 0xDF000000, 0x6E000000, 0x47000000, 0xF1000000, + 0x1A000000, 0x71000000, 0x1D000000, 0x29000000, 0xC5000000, 0x89000000, + 0x6F000000, 0xB7000000, 0x62000000, 0x0E000000, 0xAA000000, 0x18000000, + 0xBE000000, 0x1B000000, 0xFC000000, 0x56000000, 0x3E000000, 0x4B000000, + 0xC6000000, 0xD2000000, 0x79000000, 0x20000000, 0x9A000000, 0xDB000000, + 0xC0000000, 0xFE000000, 0x78000000, 0xCD000000, 0x5A000000, 0xF4000000, + 0x1F000000, 0xDD000000, 0xA8000000, 0x33000000, 0x88000000, 0x07000000, + 0xC7000000, 0x31000000, 0xB1000000, 0x12000000, 0x10000000, 0x59000000, + 0x27000000, 0x80000000, 0xEC000000, 0x5F000000, 0x60000000, 0x51000000, + 0x7F000000, 0xA9000000, 0x19000000, 0xB5000000, 0x4A000000, 0x0D000000, + 0x2D000000, 0xE5000000, 0x7A000000, 0x9F000000, 0x93000000, 0xC9000000, + 0x9C000000, 0xEF000000, 0xA0000000, 0xE0000000, 0x3B000000, 0x4D000000, + 0xAE000000, 0x2A000000, 0xF5000000, 0xB0000000, 0xC8000000, 0xEB000000, + 0xBB000000, 0x3C000000, 0x83000000, 0x53000000, 0x99000000, 0x61000000, + 0x17000000, 0x2B000000, 0x04000000, 0x7E000000, 0xBA000000, 0x77000000, + 0xD6000000, 0x26000000, 0xE1000000, 0x69000000, 0x14000000, 0x63000000, + 0x55000000, 0x21000000, 0x0C000000, 0x7D000000 +}; + +static int32_t p_drms_tab_taos[ 64 ] = +{ + -0x28955B88, -0x173848AA, +0x242070DB, -0x3E423112, -0x0A83F051, + +0x4787C62A, -0x57CFB9ED, -0x02B96AFF, +0x698098D8, -0x74BB0851, + -0x0000A44F, -0x76A32842, +0x6B901122, -0x02678E6D, -0x5986BC72, + +0x49B40821, -0x09E1DA9E, -0x3FBF4CC0, +0x265E5A51, -0x16493856, + -0x29D0EFA3, +0x02441453, -0x275E197F, -0x182C0438, +0x21E1CDE6, + -0x3CC8F82A, -0x0B2AF279, +0x455A14ED, -0x561C16FB, -0x03105C08, + +0x676F02D9, -0x72D5B376, -0x0005C6BE, -0x788E097F, +0x6D9D6122, + -0x021AC7F4, -0x5B4115BC, +0x4BDECFA9, -0x0944B4A0, -0x41404390, + +0x289B7EC6, -0x155ED806, -0x2B10CF7B, +0x04881D05, -0x262B2FC7, + -0x1924661B, +0x1FA27CF8, -0x3B53A99B, -0x0BD6DDBC, +0x432AFF97, + -0x546BDC59, -0x036C5FC7, +0x655B59C3, -0x70F3336E, -0x00100B83, + -0x7A7BA22F, +0x6FA87E4F, -0x01D31920, -0x5CFEBCEC, +0x4E0811A1, + -0x08AC817E, -0x42C50DCB, +0x2AD7D2BB, -0x14792C6F +}; + +static uint8_t p_drms_tab_tend[ 64 ] = +{ + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static uint16_t p_drms_tab_xor[ 16 ][ 16 ] = +{ + { + 0x00D1, 0x0315, 0x1A32, 0x19EC, 0x1BBB, 0x1D6F, 0x14FE, 0x0E9E, + 0x029E, 0x1B8F, 0x0B70, 0x033A, 0x188E, 0x1D18, 0x0BD8, 0x0EDB + }, + + { + 0x0C64, 0x1C2B, 0x149C, 0x047B, 0x1064, 0x1C7C, 0x118D, 0x1355, + 0x0AE5, 0x0F18, 0x016F, 0x17D6, 0x1595, 0x0084, 0x0616, 0x1CCD + }, + + { + 0x1D94, 0x0618, 0x182C, 0x195B, 0x196D, 0x0394, 0x07DB, 0x0287, + 0x1636, 0x0B81, 0x1519, 0x0DF9, 0x1BA3, 0x1CC3, 0x0EE2, 0x1434 + }, + + { + 0x1457, 0x0CED, 0x0F7D, 0x0D7B, 0x0B9E, 0x0D13, 0x13D7, 0x18D0, + 0x1259, 0x1977, 0x0606, 0x1E80, 0x05F2, 0x06B8, 0x1F07, 0x1365 + }, + + { + 0x0334, 0x0E30, 0x195F, 0x15F1, 0x058E, 0x0AA8, 0x045A, 0x0465, + 0x0B3E, 0x071E, 0x0A36, 0x105C, 0x01AC, 0x1A1E, 0x04E4, 0x056B + }, + + { + 0x12BF, 0x0DA2, 0x0B41, 0x0EAF, 0x034F, 0x0181, 0x04E2, 0x002B, + 0x12E6, 0x01BE, 0x10E8, 0x128F, 0x0EB2, 0x1369, 0x05BE, 0x1A59 + }, + + { + 0x117E, 0x047C, 0x1E86, 0x056A, 0x0DA7, 0x0D61, 0x03FC, 0x1E6E, + 0x1D0C, 0x1E6D, 0x14BF, 0x0C50, 0x063A, 0x1B47, 0x17AE, 0x1321 + }, + + { + 0x041B, 0x0A24, 0x0D4D, 0x1F2B, 0x1CB6, 0x1BED, 0x1549, 0x03A7, + 0x0254, 0x006C, 0x0C9E, 0x0F73, 0x006C, 0x0008, 0x11F9, 0x0DD5 + }, + + { + 0x0BCF, 0x0AF9, 0x1DFE, 0x0341, 0x0E49, 0x0D38, 0x17CB, 0x1513, + 0x0E96, 0x00ED, 0x0556, 0x1B28, 0x100C, 0x19D8, 0x14FA, 0x028C + }, + + { + 0x1C60, 0x1232, 0x13D3, 0x0D00, 0x1534, 0x192C, 0x14B5, 0x1CF2, + 0x0504, 0x0B5B, 0x1ECF, 0x0423, 0x183B, 0x06B0, 0x169E, 0x1066 + }, + + { + 0x04CB, 0x08A2, 0x1B4A, 0x1254, 0x198D, 0x1044, 0x0236, 0x1BD8, + 0x18A1, 0x03FF, 0x1A0D, 0x0277, 0x0C2D, 0x17C9, 0x007C, 0x116E + }, + + { + 0x048A, 0x1EAF, 0x0922, 0x0C45, 0x0766, 0x1E5F, 0x1A28, 0x0120, + 0x1C15, 0x034C, 0x0508, 0x0E73, 0x0879, 0x0441, 0x09AE, 0x132F + }, + + { + 0x14FE, 0x0413, 0x0A9D, 0x1727, 0x01D7, 0x1A2B, 0x0474, 0x18F0, + 0x1F3B, 0x14F5, 0x1071, 0x0895, 0x1071, 0x18FF, 0x18E3, 0x0EB9 + }, + + { + 0x0BA9, 0x0961, 0x1599, 0x019E, 0x1D12, 0x1BAA, 0x1E94, 0x1921, + 0x14DC, 0x124E, 0x0A25, 0x03AB, 0x1CC0, 0x1EBB, 0x0B4B, 0x16E5 + }, + + { + 0x11EA, 0x0D78, 0x1BB3, 0x1BA7, 0x1510, 0x1B7B, 0x0C64, 0x1995, + 0x1A58, 0x1651, 0x1964, 0x147A, 0x15F2, 0x11BB, 0x1654, 0x166E + }, + + { + 0x0EA9, 0x1DE1, 0x1443, 0x13C5, 0x00E1, 0x0B2F, 0x0B6F, 0x0A37, + 0x18AC, 0x08E6, 0x06F0, 0x136E, 0x0853, 0x0B2E, 0x0813, 0x10D6 + } +}; + +static uint16_t p_drms_tab_sub[ 16 ][ 16 ] = +{ + { + 0x067A, 0x0C7D, 0x0B4F, 0x127D, 0x0BD6, 0x04AC, 0x16E0, 0x1730, + 0x0587, 0x0AFB, 0x1AC3, 0x0120, 0x14B5, 0x0F67, 0x11DE, 0x0961 + }, + + { + 0x1127, 0x1A68, 0x07F0, 0x17D0, 0x1A6F, 0x1F3B, 0x01EF, 0x0919, + 0x131E, 0x0F90, 0x19E9, 0x18A8, 0x0CB2, 0x1AD0, 0x0C66, 0x0378 + }, + + { + 0x03B0, 0x01BE, 0x1866, 0x1159, 0x197C, 0x1105, 0x010B, 0x0353, + 0x1ABB, 0x09A6, 0x028A, 0x1BAD, 0x1B20, 0x0455, 0x0F57, 0x0588 + }, + + { + 0x1491, 0x0A1D, 0x0F04, 0x0650, 0x191E, 0x1E0E, 0x174B, 0x016B, + 0x051F, 0x0532, 0x00DF, 0x1AEA, 0x0005, 0x0E1B, 0x0FF6, 0x08D8 + }, + + { + 0x14B4, 0x086A, 0x0C20, 0x0149, 0x1971, 0x0F26, 0x1852, 0x017D, + 0x1228, 0x0352, 0x0A44, 0x1330, 0x18DF, 0x1E38, 0x01BC, 0x0BAC + }, + + { + 0x1A48, 0x021F, 0x02F7, 0x0C31, 0x0BC4, 0x1E75, 0x105C, 0x13E3, + 0x0B20, 0x03A1, 0x1AF3, 0x1A36, 0x0E34, 0x181F, 0x09BD, 0x122B + }, + + { + 0x0EE0, 0x163B, 0x0BE7, 0x103D, 0x1075, 0x1E9D, 0x02AF, 0x0BA2, + 0x1DAA, 0x0CF1, 0x04B6, 0x0598, 0x06A1, 0x0D33, 0x1CFE, 0x04EE + }, + + { + 0x1BAD, 0x07C8, 0x1A48, 0x05E6, 0x031F, 0x0E0A, 0x0326, 0x1650, + 0x0526, 0x0B4E, 0x08FC, 0x0E4D, 0x0832, 0x06EA, 0x09BF, 0x0993 + }, + + { + 0x09EB, 0x0F31, 0x071B, 0x14D5, 0x11CA, 0x0722, 0x120D, 0x014C, + 0x1993, 0x0AE4, 0x1CCB, 0x04E9, 0x0AEE, 0x1708, 0x0C3D, 0x12F2 + }, + + { + 0x1A19, 0x07C1, 0x05A7, 0x0744, 0x1606, 0x1A9B, 0x042D, 0x1BFC, + 0x1841, 0x0C3C, 0x0FFE, 0x1AB1, 0x1416, 0x18A9, 0x0320, 0x1EC2 + }, + + { + 0x0AE7, 0x11C6, 0x124A, 0x11DF, 0x0F81, 0x06CF, 0x0ED9, 0x0253, + 0x1D2B, 0x0349, 0x0805, 0x08B3, 0x1052, 0x12CF, 0x0A44, 0x0EA6 + }, + + { + 0x03BF, 0x1D90, 0x0EF8, 0x0657, 0x156D, 0x0405, 0x10BE, 0x091F, + 0x1C82, 0x1725, 0x19EF, 0x0B8C, 0x04D9, 0x02C7, 0x025A, 0x1B89 + }, + + { + 0x0F5C, 0x013D, 0x02F7, 0x12E3, 0x0BC5, 0x1B56, 0x0848, 0x0239, + 0x0FCF, 0x03A4, 0x092D, 0x1354, 0x1D83, 0x01BD, 0x071A, 0x0AF1 + }, + + { + 0x0875, 0x0793, 0x1B41, 0x1782, 0x0DEF, 0x1D20, 0x13BE, 0x0095, + 0x1650, 0x19D4, 0x0DE3, 0x0980, 0x18F2, 0x0CA3, 0x0098, 0x149A + }, + + { + 0x0B81, 0x0AD2, 0x1BBA, 0x1A02, 0x027B, 0x1906, 0x07F5, 0x1CAE, + 0x0C3F, 0x02F6, 0x1298, 0x175E, 0x15B2, 0x13D8, 0x14CC, 0x161A + }, + + { + 0x0A42, 0x15F3, 0x0870, 0x1C1D, 0x1203, 0x18B1, 0x1738, 0x1954, + 0x1143, 0x1AE8, 0x1D9D, 0x155B, 0x11E8, 0x0ED9, 0x06F7, 0x04CA + } +}; + +static uint16_t p_drms_tab_add[ 16 ][ 16 ] = +{ + { + 0x0706, 0x175A, 0x0DEF, 0x1E72, 0x0297, 0x1B0E, 0x1D5A, 0x15B8, + 0x13E2, 0x1347, 0x10C6, 0x0B4F, 0x0629, 0x0A75, 0x0A9B, 0x0F55 + }, + + { + 0x1A69, 0x09BF, 0x0BA6, 0x1582, 0x1086, 0x1921, 0x01CB, 0x1C6A, + 0x0FF5, 0x00F7, 0x0A67, 0x0A1E, 0x1838, 0x0196, 0x10D6, 0x0C7A + }, + + { + 0x180E, 0x038D, 0x1ADD, 0x0684, 0x154A, 0x0AB0, 0x18A4, 0x0D73, + 0x1641, 0x0EC6, 0x09F1, 0x1A62, 0x0414, 0x162A, 0x194E, 0x1EC9 + }, + + { + 0x022F, 0x0296, 0x1104, 0x14FC, 0x096C, 0x1D02, 0x09BD, 0x027C, + 0x080E, 0x1324, 0x128C, 0x0DC1, 0x00B9, 0x17F2, 0x0CBC, 0x0F97 + }, + + { + 0x1B93, 0x1C3C, 0x0415, 0x0395, 0x0C7A, 0x06CC, 0x0D4B, 0x16E2, + 0x04A2, 0x0DAB, 0x1228, 0x012B, 0x0896, 0x0012, 0x1CD6, 0x1DAC + }, + + { + 0x080D, 0x0446, 0x047A, 0x00AD, 0x029E, 0x0686, 0x17C3, 0x1466, + 0x0D16, 0x1896, 0x076E, 0x00CD, 0x17DC, 0x1E9F, 0x1A7C, 0x02BB + }, + + { + 0x0D06, 0x112B, 0x14CB, 0x0A03, 0x1541, 0x1290, 0x0F6D, 0x1503, + 0x084B, 0x0382, 0x1A3F, 0x0371, 0x1977, 0x0B67, 0x0CAD, 0x1DF8 + }, + + { + 0x1CE3, 0x1306, 0x13F8, 0x1163, 0x1B0B, 0x00BD, 0x0BF0, 0x1A4F, + 0x16F7, 0x0B4F, 0x0CF8, 0x1254, 0x0541, 0x100D, 0x0296, 0x0410 + }, + + { + 0x1A2B, 0x1169, 0x17D9, 0x0819, 0x03D6, 0x0D03, 0x194D, 0x184A, + 0x07CA, 0x1989, 0x0FAD, 0x011C, 0x1C71, 0x0EF6, 0x0DC8, 0x0F2F + }, + + { + 0x0FA5, 0x11BE, 0x0F3B, 0x1D52, 0x0DE2, 0x016E, 0x1AD1, 0x0C4A, + 0x1BC2, 0x0AC9, 0x1485, 0x1BEE, 0x0949, 0x1A79, 0x1894, 0x12BB + }, + + { + 0x17B6, 0x14F5, 0x16B1, 0x142C, 0x1301, 0x03EF, 0x16FF, 0x0D37, + 0x0D78, 0x01FF, 0x00D6, 0x1053, 0x1A2A, 0x0F61, 0x1352, 0x0C7F + }, + + { + 0x137F, 0x09C4, 0x1D96, 0x021D, 0x1037, 0x1B19, 0x10EF, 0x14E4, + 0x02A0, 0x0236, 0x0A5D, 0x1519, 0x141C, 0x1399, 0x007E, 0x1E74 + }, + + { + 0x0941, 0x1B3C, 0x0062, 0x0371, 0x09AD, 0x08E8, 0x0A24, 0x0B97, + 0x1ED2, 0x0889, 0x136B, 0x0006, 0x1C4C, 0x0444, 0x06F8, 0x0DFB + }, + + { + 0x1D0F, 0x198D, 0x0700, 0x0AFC, 0x1781, 0x12F3, 0x10DA, 0x1F19, + 0x1055, 0x0DC9, 0x1860, 0x012B, 0x05BF, 0x082D, 0x0C17, 0x1941 + }, + + { + 0x0359, 0x1232, 0x104C, 0x0762, 0x0897, 0x1D6C, 0x030F, 0x1A36, + 0x16B0, 0x094D, 0x1782, 0x036F, 0x0EEA, 0x06E6, 0x0D00, 0x0187 + }, + + { + 0x17E2, 0x05E5, 0x19FA, 0x1950, 0x146A, 0x0B2A, 0x0512, 0x0EE0, + 0x1E27, 0x112D, 0x1DF0, 0x0B13, 0x0378, 0x1DD0, 0x00C1, 0x01E6 + } +}; + diff --git a/trunk/src/mp4ff/mp4atom.c b/trunk/src/mp4ff/mp4atom.c new file mode 100644 index 000000000..b75a3da7f --- /dev/null +++ b/trunk/src/mp4ff/mp4atom.c @@ -0,0 +1,781 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4atom.c,v 1.17 2004/01/11 15:52:18 menno Exp $ +**/ + +#include <stdlib.h> +#include "mp4ffint.h" + +#include "drms.h" + +/* parse atom header size */ +static int32_t mp4ff_atom_get_size(const int8_t *data) +{ + uint32_t result; + uint32_t a, b, c, d; + + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + c = (uint8_t)data[2]; + d = (uint8_t)data[3]; + + result = (a<<24) | (b<<16) | (c<<8) | d; + //if (result > 0 && result < 8) result = 8; + + return (int32_t)result; +} + +/* comnapre 2 atom names, returns 1 for equal, 0 for unequal */ +static int32_t mp4ff_atom_compare(const int8_t a1, const int8_t b1, const int8_t c1, const int8_t d1, + const int8_t a2, const int8_t b2, const int8_t c2, const int8_t d2) +{ + if (a1 == a2 && b1 == b2 && c1 == c2 && d1 == d2) + return 1; + else + return 0; +} + +static uint8_t mp4ff_atom_name_to_type(const int8_t a, const int8_t b, + const int8_t c, const int8_t d) +{ + if (a == 'm') + { + if (mp4ff_atom_compare(a,b,c,d, 'm','o','o','v')) + return ATOM_MOOV; + else if (mp4ff_atom_compare(a,b,c,d, 'm','i','n','f')) + return ATOM_MINF; + else if (mp4ff_atom_compare(a,b,c,d, 'm','d','i','a')) + return ATOM_MDIA; + else if (mp4ff_atom_compare(a,b,c,d, 'm','d','a','t')) + return ATOM_MDAT; + else if (mp4ff_atom_compare(a,b,c,d, 'm','d','h','d')) + return ATOM_MDHD; + else if (mp4ff_atom_compare(a,b,c,d, 'm','v','h','d')) + return ATOM_MVHD; + else if (mp4ff_atom_compare(a,b,c,d, 'm','p','4','a')) + return ATOM_MP4A; + else if (mp4ff_atom_compare(a,b,c,d, 'm','p','4','v')) + return ATOM_MP4V; + else if (mp4ff_atom_compare(a,b,c,d, 'm','p','4','s')) + return ATOM_MP4S; + else if (mp4ff_atom_compare(a,b,c,d, 'm','e','t','a')) + return ATOM_META; + } else if (a == 't') { + if (mp4ff_atom_compare(a,b,c,d, 't','r','a','k')) + return ATOM_TRAK; + else if (mp4ff_atom_compare(a,b,c,d, 't','k','h','d')) + return ATOM_TKHD; + else if (mp4ff_atom_compare(a,b,c,d, 't','r','e','f')) + return ATOM_TREF; + else if (mp4ff_atom_compare(a,b,c,d, 't','r','k','n')) + return ATOM_TRACK; + else if (mp4ff_atom_compare(a,b,c,d, 't','m','p','o')) + return ATOM_TEMPO; + } else if (a == 's') { + if (mp4ff_atom_compare(a,b,c,d, 's','t','b','l')) + return ATOM_STBL; + else if (mp4ff_atom_compare(a,b,c,d, 's','m','h','d')) + return ATOM_SMHD; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','s','d')) + return ATOM_STSD; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','t','s')) + return ATOM_STTS; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','c','o')) + return ATOM_STCO; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','s','c')) + return ATOM_STSC; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','s','z')) + return ATOM_STSZ; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','z','2')) + return ATOM_STZ2; + else if (mp4ff_atom_compare(a,b,c,d, 's','k','i','p')) + return ATOM_SKIP; + else if (mp4ff_atom_compare(a,b,c,d, 's','i','n','f')) + return ATOM_SINF; + else if (mp4ff_atom_compare(a,b,c,d, 's','c','h','i')) + return ATOM_SCHI; + } else if (a == '©') { + if (mp4ff_atom_compare(a,b,c,d, '©','n','a','m')) + return ATOM_TITLE; + else if (mp4ff_atom_compare(a,b,c,d, '©','A','R','T')) + return ATOM_ARTIST; + else if (mp4ff_atom_compare(a,b,c,d, '©','w','r','t')) + return ATOM_WRITER; + else if (mp4ff_atom_compare(a,b,c,d, '©','a','l','b')) + return ATOM_ALBUM; + else if (mp4ff_atom_compare(a,b,c,d, '©','d','a','y')) + return ATOM_DATE; + else if (mp4ff_atom_compare(a,b,c,d, '©','t','o','o')) + return ATOM_TOOL; + else if (mp4ff_atom_compare(a,b,c,d, '©','c','m','t')) + return ATOM_COMMENT; + else if (mp4ff_atom_compare(a,b,c,d, '©','g','e','n')) + return ATOM_GENRE1; + } + + if (mp4ff_atom_compare(a,b,c,d, 'e','d','t','s')) + return ATOM_EDTS; + else if (mp4ff_atom_compare(a,b,c,d, 'e','s','d','s')) + return ATOM_ESDS; + else if (mp4ff_atom_compare(a,b,c,d, 'f','t','y','p')) + return ATOM_FTYP; + else if (mp4ff_atom_compare(a,b,c,d, 'f','r','e','e')) + return ATOM_FREE; + else if (mp4ff_atom_compare(a,b,c,d, 'h','m','h','d')) + return ATOM_HMHD; + else if (mp4ff_atom_compare(a,b,c,d, 'v','m','h','d')) + return ATOM_VMHD; + else if (mp4ff_atom_compare(a,b,c,d, 'u','d','t','a')) + return ATOM_UDTA; + else if (mp4ff_atom_compare(a,b,c,d, 'i','l','s','t')) + return ATOM_ILST; + else if (mp4ff_atom_compare(a,b,c,d, 'n','a','m','e')) + return ATOM_NAME; + else if (mp4ff_atom_compare(a,b,c,d, 'd','a','t','a')) + return ATOM_DATA; + else if (mp4ff_atom_compare(a,b,c,d, 'd','i','s','k')) + return ATOM_DISC; + else if (mp4ff_atom_compare(a,b,c,d, 'g','n','r','e')) + return ATOM_GENRE2; + else if (mp4ff_atom_compare(a,b,c,d, 'c','o','v','r')) + return ATOM_COVER; + else if (mp4ff_atom_compare(a,b,c,d, 'c','p','i','l')) + return ATOM_COMPILATION; + else if (mp4ff_atom_compare(a,b,c,d, 'c','t','t','s')) + return ATOM_CTTS; + else if (mp4ff_atom_compare(a,b,c,d, 'd','r','m','s')) + return ATOM_DRMS; + else if (mp4ff_atom_compare(a,b,c,d, 'f','r','m','a')) + return ATOM_FRMA; + else if (mp4ff_atom_compare(a,b,c,d, 'p','r','i','v')) + return ATOM_PRIV; + else if (mp4ff_atom_compare(a,b,c,d, 'i','v','i','v')) + return ATOM_IVIV; + else + return ATOM_UNKNOWN; +} + +/* read atom header, return atom size, atom size is with header included */ +uint64_t mp4ff_atom_read_header(mp4ff_t *f, uint8_t *atom_type, uint8_t *header_size) +{ + uint64_t size; + int32_t ret; + int8_t atom_header[8]; + + ret = mp4ff_read_data(f, atom_header, 8); + if (ret != 8) + return 0; + + size = mp4ff_atom_get_size(atom_header); + *header_size = 8; + + /* check for 64 bit atom size */ + if (size == 1) + { + *header_size = 16; + size = mp4ff_read_int64(f); + } + + //printf("%c%c%c%c\n", atom_header[4], atom_header[5], atom_header[6], atom_header[7]); + + *atom_type = mp4ff_atom_name_to_type(atom_header[4], atom_header[5], atom_header[6], atom_header[7]); + + return size; +} + +static int32_t mp4ff_read_stsz(mp4ff_t *f) +{ + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + f->track[f->total_tracks - 1]->stsz_sample_size = mp4ff_read_int32(f); + f->track[f->total_tracks - 1]->stsz_sample_count = mp4ff_read_int32(f); + + if (f->track[f->total_tracks - 1]->stsz_sample_size == 0) + { + int32_t i; + f->track[f->total_tracks - 1]->stsz_table = + (int32_t*)malloc(f->track[f->total_tracks - 1]->stsz_sample_count*sizeof(int32_t)); + + for (i = 0; i < f->track[f->total_tracks - 1]->stsz_sample_count; i++) + { + f->track[f->total_tracks - 1]->stsz_table[i] = mp4ff_read_int32(f); + } + } + + return 0; +} + +static int32_t mp4ff_read_esds(mp4ff_t *f) +{ + uint8_t tag; + uint32_t temp; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + + /* get and verify ES_DescrTag */ + tag = mp4ff_read_char(f); + if (tag == 0x03) + { + /* read length */ + if (mp4ff_read_mp4_descr_length(f) < 5 + 15) + { + return 1; + } + /* skip 3 bytes */ + mp4ff_read_int24(f); + } else { + /* skip 2 bytes */ + mp4ff_read_int16(f); + } + + /* get and verify DecoderConfigDescrTab */ + if (mp4ff_read_char(f) != 0x04) + { + return 1; + } + + /* read length */ + temp = mp4ff_read_mp4_descr_length(f); + if (temp < 13) return 1; + + f->track[f->total_tracks - 1]->audioType = mp4ff_read_char(f); + mp4ff_read_int32(f);//0x15000414 ???? + f->track[f->total_tracks - 1]->maxBitrate = mp4ff_read_int32(f); + f->track[f->total_tracks - 1]->avgBitrate = mp4ff_read_int32(f); + + /* get and verify DecSpecificInfoTag */ + if (mp4ff_read_char(f) != 0x05) + { + return 1; + } + + /* read length */ + f->track[f->total_tracks - 1]->decoderConfigLen = mp4ff_read_mp4_descr_length(f); + + if (f->track[f->total_tracks - 1]->decoderConfig) + free(f->track[f->total_tracks - 1]->decoderConfig); + f->track[f->total_tracks - 1]->decoderConfig = malloc(f->track[f->total_tracks - 1]->decoderConfigLen); + if (f->track[f->total_tracks - 1]->decoderConfig) + { + mp4ff_read_data(f, f->track[f->total_tracks - 1]->decoderConfig, f->track[f->total_tracks - 1]->decoderConfigLen); + } else { + f->track[f->total_tracks - 1]->decoderConfigLen = 0; + } + + /* will skip the remainder of the atom */ + return 0; +} + +static int32_t mp4ff_read_mp4a(mp4ff_t *f) +{ + uint64_t size; + int32_t i; + uint8_t atom_type = 0; + uint8_t header_size = 0; + + for (i = 0; i < 6; i++) + { + mp4ff_read_char(f); /* reserved */ + } + /* data_reference_index */ mp4ff_read_int16(f); + + mp4ff_read_int32(f); /* reserved */ + mp4ff_read_int32(f); /* reserved */ + + f->track[f->total_tracks - 1]->channelCount = mp4ff_read_int16(f); + f->track[f->total_tracks - 1]->sampleSize = mp4ff_read_int16(f); + + mp4ff_read_int16(f); + mp4ff_read_int16(f); + + f->track[f->total_tracks - 1]->sampleRate = mp4ff_read_int16(f); + + mp4ff_read_int16(f); + + size = mp4ff_atom_read_header(f, &atom_type, &header_size); + if (atom_type == ATOM_ESDS) + { + mp4ff_read_esds(f); + } + + return 0; +} + +#ifdef ITUNES_DRM +static int32_t mp4ff_read_drms(mp4ff_t *f, uint64_t skip) +{ + uint64_t size; + int32_t i; + uint8_t atom_type = 0; + uint8_t header_size = 0; + uint32_t drms_user_key[4]; + + if (drms_get_user_key(NULL, drms_user_key) == 0) + { + f->track[f->total_tracks - 1]->p_drms = drms_alloc(); + + drms_init( f->track[f->total_tracks - 1]->p_drms, + DRMS_INIT_UKEY, (uint8_t *)drms_user_key, + sizeof(drms_user_key) ); + } + + for (i = 0; i < 6; i++) + { + mp4ff_read_char(f); /* reserved */ + } + /* data_reference_index */ mp4ff_read_int16(f); + + mp4ff_read_int32(f); /* reserved */ + mp4ff_read_int32(f); /* reserved */ + + f->track[f->total_tracks - 1]->channelCount = mp4ff_read_int16(f); + f->track[f->total_tracks - 1]->sampleSize = mp4ff_read_int16(f); + + mp4ff_read_int16(f); + mp4ff_read_int16(f); + + f->track[f->total_tracks - 1]->sampleRate = mp4ff_read_int16(f); + + mp4ff_read_int16(f); + + size = mp4ff_atom_read_header(f, &atom_type, &header_size); + if (atom_type == ATOM_ESDS) + { + mp4ff_read_esds(f); + } + mp4ff_set_position(f, skip+size+28); + + size = mp4ff_atom_read_header(f, &atom_type, &header_size); + if (atom_type == ATOM_SINF) + { + parse_sub_atoms(f, size-header_size); + } + + return 0; +} + +static int32_t mp4ff_read_frma(mp4ff_t *f) +{ + uint8_t atom_type; + int8_t type[4]; + + mp4ff_read_data(f, type, 4); + + atom_type = mp4ff_atom_name_to_type(type[0], type[1], type[2], type[3]); + + if (atom_type == ATOM_MP4A) + { + f->track[f->total_tracks - 1]->type = TRACK_AUDIO; + } else if (atom_type == ATOM_MP4V) { + f->track[f->total_tracks - 1]->type = TRACK_VIDEO; + } else if (atom_type == ATOM_MP4S) { + f->track[f->total_tracks - 1]->type = TRACK_SYSTEM; + } else { + f->track[f->total_tracks - 1]->type = TRACK_UNKNOWN; + } + + return 0; +} + +static int32_t mp4ff_read_name(mp4ff_t *f, uint64_t size) +{ + uint8_t *data = malloc(size); + mp4ff_read_data(f, data, size); + + if (f->track[f->total_tracks - 1]->p_drms != NULL) + { + drms_init(f->track[f->total_tracks - 1]->p_drms, + DRMS_INIT_NAME, data, strlen(data) ); + } + + if (data) + free(data); + + return 0; +} + +static int32_t mp4ff_read_priv(mp4ff_t *f, uint64_t size) +{ + uint8_t *data = malloc(size); + mp4ff_read_data(f, data, size); + + if (f->track[f->total_tracks - 1]->p_drms != 0) + { + drms_init(f->track[f->total_tracks - 1]->p_drms, + DRMS_INIT_PRIV, data, size ); + } + + if (data) + free(data); + + return 0; +} + +static int32_t mp4ff_read_iviv(mp4ff_t *f, uint64_t size) +{ + uint8_t *data = malloc(size); + mp4ff_read_data(f, data, size); + + if (f->track[f->total_tracks - 1]->p_drms != 0) + { + drms_init(f->track[f->total_tracks - 1]->p_drms, + DRMS_INIT_IVIV, data, sizeof(uint32_t) * 4 ); + } + + if (data) + free(data); + + return 0; +} +#endif + +static int32_t mp4ff_read_stsd(mp4ff_t *f) +{ + int32_t i; + uint8_t header_size = 0; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + + f->track[f->total_tracks - 1]->stsd_entry_count = mp4ff_read_int32(f); + + for (i = 0; i < f->track[f->total_tracks - 1]->stsd_entry_count; i++) + { + uint64_t skip = mp4ff_position(f); + uint64_t size; + uint8_t atom_type = 0; + size = mp4ff_atom_read_header(f, &atom_type, &header_size); + skip += size; + + if (atom_type == ATOM_MP4A) + { + f->track[f->total_tracks - 1]->type = TRACK_AUDIO; + mp4ff_read_mp4a(f); + } else if (atom_type == ATOM_MP4V) { + f->track[f->total_tracks - 1]->type = TRACK_VIDEO; + } else if (atom_type == ATOM_MP4S) { + f->track[f->total_tracks - 1]->type = TRACK_SYSTEM; +#ifdef ITUNES_DRM + } else if (atom_type == ATOM_DRMS) { + // track type is read from the "frma" atom + f->track[f->total_tracks - 1]->type = TRACK_UNKNOWN; + mp4ff_read_drms(f, skip-size+header_size); +#endif + } else { + f->track[f->total_tracks - 1]->type = TRACK_UNKNOWN; + } + + mp4ff_set_position(f, skip); + } + + return 0; +} + +static int32_t mp4ff_read_stsc(mp4ff_t *f) +{ + int32_t i; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + f->track[f->total_tracks - 1]->stsc_entry_count = mp4ff_read_int32(f); + + f->track[f->total_tracks - 1]->stsc_first_chunk = + (int32_t*)malloc(f->track[f->total_tracks - 1]->stsc_entry_count*sizeof(int32_t)); + f->track[f->total_tracks - 1]->stsc_samples_per_chunk = + (int32_t*)malloc(f->track[f->total_tracks - 1]->stsc_entry_count*sizeof(int32_t)); + f->track[f->total_tracks - 1]->stsc_sample_desc_index = + (int32_t*)malloc(f->track[f->total_tracks - 1]->stsc_entry_count*sizeof(int32_t)); + + for (i = 0; i < f->track[f->total_tracks - 1]->stsc_entry_count; i++) + { + f->track[f->total_tracks - 1]->stsc_first_chunk[i] = mp4ff_read_int32(f); + f->track[f->total_tracks - 1]->stsc_samples_per_chunk[i] = mp4ff_read_int32(f); + f->track[f->total_tracks - 1]->stsc_sample_desc_index[i] = mp4ff_read_int32(f); + } + + return 0; +} + +static int32_t mp4ff_read_stco(mp4ff_t *f) +{ + int32_t i; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + f->track[f->total_tracks - 1]->stco_entry_count = mp4ff_read_int32(f); + + f->track[f->total_tracks - 1]->stco_chunk_offset = + (int32_t*)malloc(f->track[f->total_tracks - 1]->stco_entry_count*sizeof(int32_t)); + + for (i = 0; i < f->track[f->total_tracks - 1]->stco_entry_count; i++) + { + f->track[f->total_tracks - 1]->stco_chunk_offset[i] = mp4ff_read_int32(f); + } + + return 0; +} + +static int32_t mp4ff_read_ctts(mp4ff_t *f) +{ + int32_t i; + mp4ff_track_t * p_track = f->track[f->total_tracks - 1]; + + if (p_track->ctts_entry_count) return 0; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + p_track->ctts_entry_count = mp4ff_read_int32(f); + + p_track->ctts_sample_count = (int32_t*)malloc(p_track->ctts_entry_count * sizeof(int32_t)); + p_track->ctts_sample_offset = (int32_t*)malloc(p_track->ctts_entry_count * sizeof(int32_t)); + + if (p_track->ctts_sample_count == 0 || p_track->ctts_sample_offset == 0) + { + if (p_track->ctts_sample_count) {free(p_track->ctts_sample_count);p_track->ctts_sample_count=0;} + if (p_track->ctts_sample_offset) {free(p_track->ctts_sample_offset);p_track->ctts_sample_offset=0;} + p_track->ctts_entry_count = 0; + return 0; + } + else + { + for (i = 0; i < f->track[f->total_tracks - 1]->ctts_entry_count; i++) + { + p_track->ctts_sample_count[i] = mp4ff_read_int32(f); + p_track->ctts_sample_offset[i] = mp4ff_read_int32(f); + } + return 1; + } +} + +static int32_t mp4ff_read_stts(mp4ff_t *f) +{ + int32_t i; + mp4ff_track_t * p_track = f->track[f->total_tracks - 1]; + + if (p_track->stts_entry_count) return 0; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + p_track->stts_entry_count = mp4ff_read_int32(f); + + p_track->stts_sample_count = (int32_t*)malloc(p_track->stts_entry_count * sizeof(int32_t)); + p_track->stts_sample_delta = (int32_t*)malloc(p_track->stts_entry_count * sizeof(int32_t)); + + if (p_track->stts_sample_count == 0 || p_track->stts_sample_delta == 0) + { + if (p_track->stts_sample_count) {free(p_track->stts_sample_count);p_track->stts_sample_count=0;} + if (p_track->stts_sample_delta) {free(p_track->stts_sample_delta);p_track->stts_sample_delta=0;} + p_track->stts_entry_count = 0; + return 0; + } + else + { + for (i = 0; i < f->track[f->total_tracks - 1]->stts_entry_count; i++) + { + p_track->stts_sample_count[i] = mp4ff_read_int32(f); + p_track->stts_sample_delta[i] = mp4ff_read_int32(f); + } + return 1; + } +} + +static int32_t mp4ff_read_mvhd(mp4ff_t *f) +{ + int32_t i; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + /* creation_time */ mp4ff_read_int32(f); + /* modification_time */ mp4ff_read_int32(f); + f->time_scale = mp4ff_read_int32(f); + f->duration = mp4ff_read_int32(f); + /* preferred_rate */ mp4ff_read_int32(f); /*mp4ff_read_fixed32(f);*/ + /* preferred_volume */ mp4ff_read_int16(f); /*mp4ff_read_fixed16(f);*/ + for (i = 0; i < 10; i++) + { + /* reserved */ mp4ff_read_char(f); + } + for (i = 0; i < 9; i++) + { + mp4ff_read_int32(f); /* matrix */ + } + /* preview_time */ mp4ff_read_int32(f); + /* preview_duration */ mp4ff_read_int32(f); + /* poster_time */ mp4ff_read_int32(f); + /* selection_time */ mp4ff_read_int32(f); + /* selection_duration */ mp4ff_read_int32(f); + /* current_time */ mp4ff_read_int32(f); + /* next_track_id */ mp4ff_read_int32(f); + + return 0; +} + +#if 0 +static int32_t mp4ff_read_tkhd(mp4ff_t *f) +{ + uint8_t version; + uint32_t flags; + version = mp4ff_read_char(f); /* version */ + flags = mp4ff_read_int24(f); /* flags */ + if (version==1) + { + mp4ff_read_int64(f);//creation-time + mp4ff_read_int64(f);//modification-time + mp4ff_read_int32(f);//track-id + mp4ff_read_int32(f);//reserved + f->track[f->total_tracks - 1]->duration = mp4ff_read_int64(f);//duration + } + else //version == 0 + { + mp4ff_read_int32(f);//creation-time + mp4ff_read_int32(f);//modification-time + mp4ff_read_int32(f);//track-id + mp4ff_read_int32(f);//reserved + f->track[f->total_tracks - 1]->duration = mp4ff_read_int32(f);//duration + if (f->track[f->total_tracks - 1]->duration == 0xFFFFFFFF) + f->track[f->total_tracks - 1]->duration = 0xFFFFFFFFFFFFFFFF; + + } + mp4ff_read_int32(f);//reserved + mp4ff_read_int32(f);//reserved + mp4ff_read_int16(f);//layer + mp4ff_read_int16(f);//pre-defined + mp4ff_read_int16(f);//volume + mp4ff_read_int16(f);//reserved + + //matrix + mp4ff_read_int32(f); mp4ff_read_int32(f); mp4ff_read_int32(f); + mp4ff_read_int32(f); mp4ff_read_int32(f); mp4ff_read_int32(f); + mp4ff_read_int32(f); mp4ff_read_int32(f); mp4ff_read_int32(f); + mp4ff_read_int32(f);//width + mp4ff_read_int32(f);//height + return 1; +} +#endif + +static int32_t mp4ff_read_mdhd(mp4ff_t *f) +{ + uint32_t version; + + version = mp4ff_read_int32(f); + if (version==1) + { + mp4ff_read_int64(f);//creation-time + mp4ff_read_int64(f);//modification-time + f->track[f->total_tracks - 1]->timeScale = mp4ff_read_int32(f);//timescale + f->track[f->total_tracks - 1]->duration = mp4ff_read_int64(f);//duration + } + else //version == 0 + { + uint32_t temp; + + mp4ff_read_int32(f);//creation-time + mp4ff_read_int32(f);//modification-time + f->track[f->total_tracks - 1]->timeScale = mp4ff_read_int32(f);//timescale + temp = mp4ff_read_int32(f); + f->track[f->total_tracks - 1]->duration = (temp == (uint32_t)(-1)) ? (uint64_t)(-1) : (uint64_t)(temp); + } + mp4ff_read_int16(f); + mp4ff_read_int16(f); + return 1; +} +#ifdef USE_TAGGING +static int32_t mp4ff_read_meta(mp4ff_t *f, const uint64_t size) +{ + uint64_t subsize, sumsize = 0; + uint8_t atom_type; + uint8_t header_size = 0; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + + while (sumsize < (size-12)) + { + subsize = mp4ff_atom_read_header(f, &atom_type, &header_size); + if (atom_type == ATOM_ILST) + { + mp4ff_parse_metadata(f, (uint32_t)(subsize-(header_size+4))); + } else { + mp4ff_set_position(f, mp4ff_position(f)+subsize-header_size); + } + sumsize += subsize; + } + + return 0; +} +#endif + +int32_t mp4ff_atom_read(mp4ff_t *f, const int32_t size, const uint8_t atom_type) +{ + uint64_t dest_position = mp4ff_position(f)+size-8; + if (atom_type == ATOM_STSZ) + { + /* sample size box */ + mp4ff_read_stsz(f); + } else if (atom_type == ATOM_STTS) { + /* time to sample box */ + mp4ff_read_stts(f); + } else if (atom_type == ATOM_CTTS) { + /* composition offset box */ + mp4ff_read_ctts(f); + } else if (atom_type == ATOM_STSC) { + /* sample to chunk box */ + mp4ff_read_stsc(f); + } else if (atom_type == ATOM_STCO) { + /* chunk offset box */ + mp4ff_read_stco(f); + } else if (atom_type == ATOM_STSD) { + /* sample description box */ + mp4ff_read_stsd(f); + } else if (atom_type == ATOM_MVHD) { + /* movie header box */ + mp4ff_read_mvhd(f); + } else if (atom_type == ATOM_MDHD) { + /* track header */ + mp4ff_read_mdhd(f); +#ifdef ITUNES_DRM + } else if (atom_type == ATOM_FRMA) { + /* DRM track format */ + mp4ff_read_frma(f); + } else if (atom_type == ATOM_IVIV) { + mp4ff_read_iviv(f, size-8); + } else if (atom_type == ATOM_NAME) { + mp4ff_read_name(f, size-8); + } else if (atom_type == ATOM_PRIV) { + mp4ff_read_priv(f, size-8); +#endif +#ifdef USE_TAGGING + } else if (atom_type == ATOM_META) { + /* iTunes Metadata box */ + mp4ff_read_meta(f, size); +#endif + } + + mp4ff_set_position(f, dest_position); + + + return 0; +} diff --git a/trunk/src/mp4ff/mp4ff.c b/trunk/src/mp4ff/mp4ff.c new file mode 100644 index 000000000..e0bb781e8 --- /dev/null +++ b/trunk/src/mp4ff/mp4ff.c @@ -0,0 +1,430 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4ff.c,v 1.15 2004/01/11 15:52:18 menno Exp $ +**/ + +#include <stdlib.h> +#include <string.h> +#include "mp4ffint.h" + +#include "drms.h" + +int64_t mp4ff_get_track_duration_use_offsets(const mp4ff_t *f, const int32_t track); + +mp4ff_t *mp4ff_open_read(mp4ff_callback_t *f) +{ + mp4ff_t *ff = malloc(sizeof(mp4ff_t)); + + memset(ff, 0, sizeof(mp4ff_t)); + + ff->stream = f; + + parse_atoms(ff); + + return ff; +} + +void mp4ff_close(mp4ff_t *ff) +{ + int32_t i; + + for (i = 0; i < ff->total_tracks; i++) + { + if (ff->track[i]) + { + if (ff->track[i]->stsz_table) + free(ff->track[i]->stsz_table); + if (ff->track[i]->stts_sample_count) + free(ff->track[i]->stts_sample_count); + if (ff->track[i]->stts_sample_delta) + free(ff->track[i]->stts_sample_delta); + if (ff->track[i]->stsc_first_chunk) + free(ff->track[i]->stsc_first_chunk); + if (ff->track[i]->stsc_samples_per_chunk) + free(ff->track[i]->stsc_samples_per_chunk); + if (ff->track[i]->stsc_sample_desc_index) + free(ff->track[i]->stsc_sample_desc_index); + if (ff->track[i]->stco_chunk_offset) + free(ff->track[i]->stco_chunk_offset); + if (ff->track[i]->decoderConfig) + free(ff->track[i]->decoderConfig); + if (ff->track[i]->ctts_sample_count) + free(ff->track[i]->ctts_sample_count); + if (ff->track[i]->ctts_sample_offset) + free(ff->track[i]->ctts_sample_offset); +#ifdef ITUNES_DRM + if (ff->track[i]->p_drms) + drms_free(ff->track[i]->p_drms); +#endif + free(ff->track[i]); + } + } + +#ifdef USE_TAGGING + mp4ff_tag_delete(&(ff->tags)); +#endif + + if (ff) free(ff); +} + +static void mp4ff_track_add(mp4ff_t *f) +{ + f->total_tracks++; + + f->track[f->total_tracks - 1] = malloc(sizeof(mp4ff_track_t)); + + memset(f->track[f->total_tracks - 1], 0, sizeof(mp4ff_track_t)); +} + +/* parse atoms that are sub atoms of other atoms */ +int32_t parse_sub_atoms(mp4ff_t *f, const uint64_t total_size) +{ + uint64_t size; + uint8_t atom_type = 0; + uint64_t counted_size = 0; + uint8_t header_size = 0; + + while (counted_size < total_size) + { + size = mp4ff_atom_read_header(f, &atom_type, &header_size); + counted_size += size; + + /* check for end of file */ + if (size == 0) + break; + + /* we're starting to read a new track, update index, + * so that all data and tables get written in the right place + */ + if (atom_type == ATOM_TRAK) + { + mp4ff_track_add(f); + } + + /* parse subatoms */ + if (atom_type < SUBATOMIC) + { + parse_sub_atoms(f, size-header_size); + } else { + mp4ff_atom_read(f, (uint32_t)size, atom_type); + } + } + + return 0; +} + +/* parse root atoms */ +int32_t parse_atoms(mp4ff_t *f) +{ + uint64_t size; + uint8_t atom_type = 0; + uint8_t header_size = 0; + + f->file_size = 0; + + while ((size = mp4ff_atom_read_header(f, &atom_type, &header_size)) != 0) + { + f->file_size += size; + f->last_atom = atom_type; + + if (atom_type == ATOM_MDAT && f->moov_read) + { + /* moov atom is before mdat, we can stop reading when mdat is encountered */ + /* file position will stay at beginning of mdat data */ +// break; + } + + if (atom_type == ATOM_MOOV && size > header_size) + { + f->moov_read = 1; + f->moov_offset = mp4ff_position(f)-header_size; + f->moov_size = size; + } + + /* parse subatoms */ + if (atom_type < SUBATOMIC) + { + parse_sub_atoms(f, size-header_size); + } else { + /* skip this atom */ + mp4ff_set_position(f, mp4ff_position(f)+size-header_size); + } + } + + return 0; +} + +int32_t mp4ff_get_decoder_config(const mp4ff_t *f, const int32_t track, + uint8_t** ppBuf, uint32_t* pBufSize) +{ + if (track >= f->total_tracks) + { + *ppBuf = NULL; + *pBufSize = 0; + return 1; + } + + if (f->track[track]->decoderConfig == NULL || f->track[track]->decoderConfigLen == 0) + { + *ppBuf = NULL; + *pBufSize = 0; + } else { + *ppBuf = malloc(f->track[track]->decoderConfigLen); + if (*ppBuf == NULL) + { + *pBufSize = 0; + return 1; + } + memcpy(*ppBuf, f->track[track]->decoderConfig, f->track[track]->decoderConfigLen); + *pBufSize = f->track[track]->decoderConfigLen; + } + + return 0; +} + +static int32_t mp4ff_get_track_type(const mp4ff_t *f, const int track) +{ + return f->track[track]->type; +} + +int32_t mp4ff_total_tracks(const mp4ff_t *f) +{ + return f->total_tracks; +} + +int32_t mp4ff_time_scale(const mp4ff_t *f, const int32_t track) +{ + return f->track[track]->timeScale; +} + +static uint32_t mp4ff_get_avg_bitrate(const mp4ff_t *f, const int32_t track) +{ + return f->track[track]->avgBitrate; +} + +static uint32_t mp4ff_get_max_bitrate(const mp4ff_t *f, const int32_t track) +{ + return f->track[track]->maxBitrate; +} + +static int64_t mp4ff_get_track_duration(const mp4ff_t *f, const int32_t track) +{ + return f->track[track]->duration; +} + +int64_t mp4ff_get_track_duration_use_offsets(const mp4ff_t *f, const int32_t track) +{ + int64_t duration = mp4ff_get_track_duration(f,track); + if (duration!=-1) + { + int64_t offset = mp4ff_get_sample_offset(f,track,0); + if (offset > duration) duration = 0; + else duration -= offset; + } + return duration; +} + + +int32_t mp4ff_num_samples(const mp4ff_t *f, const int32_t track) +{ + int32_t i; + int32_t total = 0; + + for (i = 0; i < f->track[track]->stts_entry_count; i++) + { + total += f->track[track]->stts_sample_count[i]; + } + return total; +} + + + + +static uint32_t mp4ff_get_sample_rate(const mp4ff_t *f, const int32_t track) +{ + return f->track[track]->sampleRate; +} + +static uint32_t mp4ff_get_channel_count(const mp4ff_t * f,const int32_t track) +{ + return f->track[track]->channelCount; +} + +static uint32_t mp4ff_get_audio_type(const mp4ff_t * f,const int32_t track) +{ + return f->track[track]->audioType; +} + +static int32_t mp4ff_get_sample_duration_use_offsets(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t d,o; + d = mp4ff_get_sample_duration(f,track,sample); + if (d!=-1) + { + o = mp4ff_get_sample_offset(f,track,sample); + if (o>d) d = 0; + else d -= o; + } + return d; +} + +int32_t mp4ff_get_sample_duration(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t i, co = 0; + + for (i = 0; i < f->track[track]->stts_entry_count; i++) + { + int32_t delta = f->track[track]->stts_sample_count[i]; + if (sample < co + delta) + return f->track[track]->stts_sample_delta[i]; + co += delta; + } + return (int32_t)(-1); +} + +int64_t mp4ff_get_sample_position(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t i, co = 0; + int64_t acc = 0; + + for (i = 0; i < f->track[track]->stts_entry_count; i++) + { + int32_t delta = f->track[track]->stts_sample_count[i]; + if (sample < co + delta) + { + acc += f->track[track]->stts_sample_delta[i] * (sample - co); + return acc; + } + else + { + acc += f->track[track]->stts_sample_delta[i] * delta; + } + co += delta; + } + return (int64_t)(-1); +} + +int32_t mp4ff_get_sample_offset(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t i, co = 0; + + for (i = 0; i < f->track[track]->ctts_entry_count; i++) + { + int32_t delta = f->track[track]->ctts_sample_count[i]; + if (sample < co + delta) + return f->track[track]->ctts_sample_offset[i]; + co += delta; + } + return 0; +} + +int32_t mp4ff_find_sample(const mp4ff_t *f, const int32_t track, const int64_t offset,int32_t * toskip) +{ + int32_t i, co = 0; + int64_t offset_total = 0; + mp4ff_track_t * p_track = f->track[track]; + + for (i = 0; i < p_track->stts_entry_count; i++) + { + int32_t sample_count = p_track->stts_sample_count[i]; + int32_t sample_delta = p_track->stts_sample_delta[i]; + int64_t offset_delta = (int64_t)sample_delta * (int64_t)sample_count; + if (offset < offset_total + offset_delta) + { + int64_t offset_fromstts = offset - offset_total; + if (toskip) *toskip = (int32_t)(offset_fromstts % sample_delta); + return co + (int32_t)(offset_fromstts / sample_delta); + } + else + { + offset_total += offset_delta; + } + co += sample_count; + } + return (int32_t)(-1); +} + +static int32_t mp4ff_find_sample_use_offsets(const mp4ff_t *f, const int32_t track, const int64_t offset,int32_t * toskip) +{ + return mp4ff_find_sample(f,track,offset + mp4ff_get_sample_offset(f,track,0),toskip); +} + +int32_t mp4ff_read_sample(mp4ff_t *f, const int32_t track, const int32_t sample, + uint8_t **audio_buffer, uint32_t *bytes) +{ + int32_t result = 0; + + *bytes = mp4ff_audio_frame_size(f, track, sample); + + if (*bytes==0) return 0; + + *audio_buffer = (uint8_t*)malloc(*bytes); + + mp4ff_set_sample_position(f, track, sample); + + result = mp4ff_read_data(f, *audio_buffer, *bytes); + + if (!result) + { + free(*audio_buffer); + *audio_buffer = 0; + return 0; + } + +#ifdef ITUNES_DRM + if (f->track[track]->p_drms != NULL) + { + drms_decrypt(f->track[track]->p_drms, (uint32_t*)*audio_buffer, *bytes); + } +#endif + + return *bytes; +} + + +static int32_t mp4ff_read_sample_v2(mp4ff_t *f, const int track, const int sample,unsigned char *buffer) +{ + int32_t result = 0; + int32_t size = mp4ff_audio_frame_size(f,track,sample); + if (size<=0) return 0; + mp4ff_set_sample_position(f, track, sample); + result = mp4ff_read_data(f,buffer,size); + +#ifdef ITUNES_DRM + if (f->track[track]->p_drms != NULL) + { + drms_decrypt(f->track[track]->p_drms, (uint32_t*)buffer, size); + } +#endif + + return result; +} + +static int32_t mp4ff_read_sample_getsize(mp4ff_t *f, const int track, const int sample) +{ + int32_t temp = mp4ff_audio_frame_size(f, track, sample); + if (temp<0) temp = 0; + return temp; +} diff --git a/trunk/src/mp4ff/mp4ff.dsp b/trunk/src/mp4ff/mp4ff.dsp new file mode 100644 index 000000000..e3a2d0899 --- /dev/null +++ b/trunk/src/mp4ff/mp4ff.dsp @@ -0,0 +1,144 @@ +# Microsoft Developer Studio Project File - Name="mp4ff" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=mp4ff - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mp4ff.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mp4ff.mak" CFG="mp4ff - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mp4ff - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "mp4ff - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=xicl6.exe +RSC=rc.exe + +!IF "$(CFG)" == "mp4ff - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +F90=df.exe +MTL=midl.exe +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "USE_TAGGING" /YX /FD /c +# ADD BASE RSC /l 0x413 /d "NDEBUG" +# ADD RSC /l 0x413 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=xilink6.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "mp4ff - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +F90=df.exe +MTL=midl.exe +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "USE_TAGGING" /YX /FD /GZ /c +# ADD BASE RSC /l 0x413 /d "_DEBUG" +# ADD RSC /l 0x413 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=xilink6.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "mp4ff - Win32 Release" +# Name "mp4ff - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\drms.c +# End Source File +# Begin Source File + +SOURCE=.\mp4atom.c +# End Source File +# Begin Source File + +SOURCE=.\mp4ff.c +# End Source File +# Begin Source File + +SOURCE=.\mp4meta.c +# End Source File +# Begin Source File + +SOURCE=.\mp4sample.c +# End Source File +# Begin Source File + +SOURCE=.\mp4tagupdate.c +# End Source File +# Begin Source File + +SOURCE=.\mp4util.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\drms.h +# End Source File +# Begin Source File + +SOURCE=.\drmstables.h +# End Source File +# Begin Source File + +SOURCE=.\mp4ff.h +# End Source File +# Begin Source File + +SOURCE=.\mp4ff_int_types.h +# End Source File +# Begin Source File + +SOURCE=.\mp4ffint.h +# End Source File +# End Group +# End Target +# End Project diff --git a/trunk/src/mp4ff/mp4ff.h b/trunk/src/mp4ff/mp4ff.h new file mode 100644 index 000000000..5fb485d77 --- /dev/null +++ b/trunk/src/mp4ff/mp4ff.h @@ -0,0 +1,128 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4ff.h,v 1.19 2004/01/11 15:52:18 menno Exp $ +**/ + +#ifndef MP4FF_H +#define MP4FF_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "mp4ff_int_types.h" + +/* file callback structure */ +typedef struct +{ + uint32_t (*read)(void *user_data, void *buffer, uint32_t length); + uint32_t (*write)(void *udata, void *buffer, uint32_t length); + uint32_t (*seek)(void *user_data, uint64_t position); + uint32_t (*truncate)(void *user_data); + void *user_data; +} mp4ff_callback_t; + +/* mp4 main file structure */ +typedef void* mp4ff_t; + + +/* API */ + +mp4ff_t *mp4ff_open_read(mp4ff_callback_t *f); +void mp4ff_close(mp4ff_t *f); +int32_t mp4ff_get_sample_duration(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_get_sample_duration_use_offsets(const mp4ff_t *f, const int32_t track, const int32_t sample); +int64_t mp4ff_get_sample_position(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_get_sample_offset(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_find_sample(const mp4ff_t *f, const int32_t track, const int64_t offset,int32_t * toskip); +int32_t mp4ff_find_sample_use_offsets(const mp4ff_t *f, const int32_t track, const int64_t offset,int32_t * toskip); + +int32_t mp4ff_read_sample(mp4ff_t *f, const int track, const int sample, + unsigned char **audio_buffer, unsigned int *bytes); + +int32_t mp4ff_read_sample_v2(mp4ff_t *f, const int track, const int sample,unsigned char *buffer);//returns 0 on error, number of bytes read on success, use mp4ff_read_sample_getsize() to check buffer size needed +int32_t mp4ff_read_sample_getsize(mp4ff_t *f, const int track, const int sample);//returns 0 on error, buffer size needed for mp4ff_read_sample_v2() on success + + + +int32_t mp4ff_get_decoder_config(const mp4ff_t *f, const int track, + unsigned char** ppBuf, unsigned int* pBufSize); +int32_t mp4ff_get_track_type(const mp4ff_t *f, const int track); +int32_t mp4ff_total_tracks(const mp4ff_t *f); +int32_t mp4ff_num_samples(const mp4ff_t *f, const int track); +int32_t mp4ff_time_scale(const mp4ff_t *f, const int track); + +uint32_t mp4ff_get_avg_bitrate(const mp4ff_t *f, const int32_t track); +uint32_t mp4ff_get_max_bitrate(const mp4ff_t *f, const int32_t track); +int64_t mp4ff_get_track_duration(const mp4ff_t *f, const int32_t track); //returns (-1) if unknown +int64_t mp4ff_get_track_duration_use_offsets(const mp4ff_t *f, const int32_t track); //returns (-1) if unknown +uint32_t mp4ff_get_sample_rate(const mp4ff_t *f, const int32_t track); +uint32_t mp4ff_get_channel_count(const mp4ff_t * f,const int32_t track); +uint32_t mp4ff_get_audio_type(const mp4ff_t * f,const int32_t track); + + +/* metadata */ +int mp4ff_meta_get_num_items(const mp4ff_t *f); +int mp4ff_meta_get_by_index(const mp4ff_t *f, unsigned int index, + char **item, char **value); +int mp4ff_meta_get_title(const mp4ff_t *f, char **value); +int mp4ff_meta_get_artist(const mp4ff_t *f, char **value); +int mp4ff_meta_get_writer(const mp4ff_t *f, char **value); +int mp4ff_meta_get_album(const mp4ff_t *f, char **value); +int mp4ff_meta_get_date(const mp4ff_t *f, char **value); +int mp4ff_meta_get_tool(const mp4ff_t *f, char **value); +int mp4ff_meta_get_comment(const mp4ff_t *f, char **value); +int mp4ff_meta_get_genre(const mp4ff_t *f, char **value); +int mp4ff_meta_get_track(const mp4ff_t *f, char **value); +int mp4ff_meta_get_disc(const mp4ff_t *f, char **value); +int mp4ff_meta_get_compilation(const mp4ff_t *f, char **value); +int mp4ff_meta_get_tempo(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_coverart(const mp4ff_t *f, char **value); +#ifdef USE_TAGGING + +/* metadata tag structure */ +typedef struct +{ + char *item; + char *value; +} mp4ff_tag_t; + +/* metadata list structure */ +typedef struct +{ + mp4ff_tag_t *tags; + uint32_t count; +} mp4ff_metadata_t; + +int32_t mp4ff_meta_update(mp4ff_callback_t *f,const mp4ff_metadata_t * data); + +#endif + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/trunk/src/mp4ff/mp4ff_int_types.h b/trunk/src/mp4ff/mp4ff_int_types.h new file mode 100644 index 000000000..2da8fee6e --- /dev/null +++ b/trunk/src/mp4ff/mp4ff_int_types.h @@ -0,0 +1,32 @@ +#ifndef _MP4FF_INT_TYPES_H_ +#define _MP4FF_INT_TYPES_H_ + +#ifdef _WIN32 + +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef long int32_t; +typedef unsigned long uint32_t; + +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#else + +#include "config.h" + +#if defined(HAVE_STDINT_H) +#include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +#include <inttypes.h> +#elif defined(HAVE_SYS_INTTYPES_H) +#include <sys/inttypes.h> +#elif defined(HAVE_SYS_TYPES_H) +#include <sys/types.h> +#endif + +#endif + +#endif diff --git a/trunk/src/mp4ff/mp4ffint.h b/trunk/src/mp4ff/mp4ffint.h new file mode 100644 index 000000000..fc13f469d --- /dev/null +++ b/trunk/src/mp4ff/mp4ffint.h @@ -0,0 +1,329 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4ffint.h,v 1.15 2004/01/14 20:50:22 menno Exp $ +**/ + +#ifndef MP4FF_INTERNAL_H +#define MP4FF_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "mp4ff_int_types.h" + + +#ifdef _WIN32 +#define ITUNES_DRM +#endif + + +#define MAX_TRACKS 1024 +#define TRACK_UNKNOWN 0 +#define TRACK_AUDIO 1 +#define TRACK_VIDEO 2 +#define TRACK_SYSTEM 3 + + +#define SUBATOMIC 128 + +/* atoms without subatoms */ +#define ATOM_FTYP 129 +#define ATOM_MDAT 130 +#define ATOM_MVHD 131 +#define ATOM_TKHD 132 +#define ATOM_TREF 133 +#define ATOM_MDHD 134 +#define ATOM_VMHD 135 +#define ATOM_SMHD 136 +#define ATOM_HMHD 137 +#define ATOM_STSD 138 +#define ATOM_STTS 139 +#define ATOM_STSZ 140 +#define ATOM_STZ2 141 +#define ATOM_STCO 142 +#define ATOM_STSC 143 +#define ATOM_MP4A 144 +#define ATOM_MP4V 145 +#define ATOM_MP4S 146 +#define ATOM_ESDS 147 +#define ATOM_META 148 /* iTunes Metadata box */ +#define ATOM_NAME 149 /* iTunes Metadata name box */ +#define ATOM_DATA 150 /* iTunes Metadata data box */ +#define ATOM_CTTS 151 +#define ATOM_FRMA 152 +#define ATOM_IVIV 153 +#define ATOM_PRIV 154 + +#define ATOM_UNKNOWN 255 +#define ATOM_FREE ATOM_UNKNOWN +#define ATOM_SKIP ATOM_UNKNOWN + +/* atoms with subatoms */ +#define ATOM_MOOV 1 +#define ATOM_TRAK 2 +#define ATOM_EDTS 3 +#define ATOM_MDIA 4 +#define ATOM_MINF 5 +#define ATOM_STBL 6 +#define ATOM_UDTA 7 +#define ATOM_ILST 8 /* iTunes Metadata list */ +#define ATOM_TITLE 9 +#define ATOM_ARTIST 10 +#define ATOM_WRITER 11 +#define ATOM_ALBUM 12 +#define ATOM_DATE 13 +#define ATOM_TOOL 14 +#define ATOM_COMMENT 15 +#define ATOM_GENRE1 16 +#define ATOM_TRACK 17 +#define ATOM_DISC 18 +#define ATOM_COMPILATION 19 +#define ATOM_GENRE2 20 +#define ATOM_TEMPO 21 +#define ATOM_COVER 22 +#define ATOM_DRMS 23 +#define ATOM_SINF 24 +#define ATOM_SCHI 25 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef _WIN32 +#define stricmp strcasecmp +#endif + +/* file callback structure */ +typedef struct +{ + uint32_t (*read)(void *user_data, void *buffer, uint32_t length); + uint32_t (*write)(void *udata, void *buffer, uint32_t length); + uint32_t (*seek)(void *user_data, uint64_t position); + uint32_t (*truncate)(void *user_data); + void *user_data; +} mp4ff_callback_t; + + +/* metadata tag structure */ +typedef struct +{ + char *item; + char *value; +} mp4ff_tag_t; + +/* metadata list structure */ +typedef struct +{ + mp4ff_tag_t *tags; + uint32_t count; +} mp4ff_metadata_t; + + +typedef struct +{ + int32_t type; + int32_t channelCount; + int32_t sampleSize; + uint16_t sampleRate; + int32_t audioType; + + /* stsd */ + int32_t stsd_entry_count; + + /* stsz */ + int32_t stsz_sample_size; + int32_t stsz_sample_count; + int32_t *stsz_table; + + /* stts */ + int32_t stts_entry_count; + int32_t *stts_sample_count; + int32_t *stts_sample_delta; + + /* stsc */ + int32_t stsc_entry_count; + int32_t *stsc_first_chunk; + int32_t *stsc_samples_per_chunk; + int32_t *stsc_sample_desc_index; + + /* stsc */ + int32_t stco_entry_count; + int32_t *stco_chunk_offset; + + /* ctts */ + int32_t ctts_entry_count; + int32_t *ctts_sample_count; + int32_t *ctts_sample_offset; + + /* esde */ + uint8_t *decoderConfig; + int32_t decoderConfigLen; + + uint32_t maxBitrate; + uint32_t avgBitrate; + + uint32_t timeScale; + uint64_t duration; + +#ifdef ITUNES_DRM + /* drms */ + void *p_drms; +#endif + +} mp4ff_track_t; + +/* mp4 main file structure */ +typedef struct +{ + /* stream to read from */ + mp4ff_callback_t *stream; + int64_t current_position; + + int32_t moov_read; + uint64_t moov_offset; + uint64_t moov_size; + uint8_t last_atom; + uint64_t file_size; + + /* mvhd */ + int32_t time_scale; + int32_t duration; + + /* incremental track index while reading the file */ + int32_t total_tracks; + + /* track data */ + mp4ff_track_t *track[MAX_TRACKS]; + + /* metadata */ + mp4ff_metadata_t tags; +} mp4ff_t; + + + + +/* mp4util.c */ +int32_t mp4ff_read_data(mp4ff_t *f, int8_t *data, uint32_t size); +int32_t mp4ff_write_data(mp4ff_t *f, int8_t *data, uint32_t size); +uint64_t mp4ff_read_int64(mp4ff_t *f); +uint32_t mp4ff_read_int32(mp4ff_t *f); +uint32_t mp4ff_read_int24(mp4ff_t *f); +uint16_t mp4ff_read_int16(mp4ff_t *f); +uint8_t mp4ff_read_char(mp4ff_t *f); +int32_t mp4ff_write_int32(mp4ff_t *f,const uint32_t data); +uint32_t mp4ff_read_mp4_descr_length(mp4ff_t *f); +int64_t mp4ff_position(const mp4ff_t *f); +int32_t mp4ff_set_position(mp4ff_t *f, const int64_t position); +int32_t mp4ff_truncate(mp4ff_t * f); +char * mp4ff_read_string(mp4ff_t * f,uint32_t length); + +/* mp4atom.c */ +static int32_t mp4ff_atom_get_size(const int8_t *data); +static int32_t mp4ff_atom_compare(const int8_t a1, const int8_t b1, const int8_t c1, const int8_t d1, + const int8_t a2, const int8_t b2, const int8_t c2, const int8_t d2); +static uint8_t mp4ff_atom_name_to_type(const int8_t a, const int8_t b, const int8_t c, const int8_t d); +uint64_t mp4ff_atom_read_header(mp4ff_t *f, uint8_t *atom_type, uint8_t *header_size); +static int32_t mp4ff_read_stsz(mp4ff_t *f); +static int32_t mp4ff_read_esds(mp4ff_t *f); +static int32_t mp4ff_read_mp4a(mp4ff_t *f); +static int32_t mp4ff_read_stsd(mp4ff_t *f); +static int32_t mp4ff_read_stsc(mp4ff_t *f); +static int32_t mp4ff_read_stco(mp4ff_t *f); +static int32_t mp4ff_read_stts(mp4ff_t *f); +#ifdef USE_TAGGING +static int32_t mp4ff_read_meta(mp4ff_t *f, const uint64_t size); +#endif +int32_t mp4ff_atom_read(mp4ff_t *f, const int32_t size, const uint8_t atom_type); + +/* mp4sample.c */ +static int32_t mp4ff_chunk_of_sample(const mp4ff_t *f, const int32_t track, const int32_t sample, + int32_t *chunk_sample, int32_t *chunk); +static int32_t mp4ff_chunk_to_offset(const mp4ff_t *f, const int32_t track, const int32_t chunk); +static int32_t mp4ff_sample_range_size(const mp4ff_t *f, const int32_t track, + const int32_t chunk_sample, const int32_t sample); +static int32_t mp4ff_sample_to_offset(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_audio_frame_size(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample); + +#ifdef USE_TAGGING +/* mp4meta.c */ +static int32_t mp4ff_tag_add_field(mp4ff_metadata_t *tags, const char *item, const char *value); +static int32_t mp4ff_tag_set_field(mp4ff_metadata_t *tags, const char *item, const char *value); +static int32_t mp4ff_set_metadata_name(mp4ff_t *f, const uint8_t atom_type, char **name); +static int32_t mp4ff_parse_tag(mp4ff_t *f, const uint8_t parent_atom_type, const int32_t size); +static int32_t mp4ff_meta_find_by_name(const mp4ff_t *f, const char *item, char **value); +int32_t mp4ff_parse_metadata(mp4ff_t *f, const int32_t size); +int32_t mp4ff_tag_delete(mp4ff_metadata_t *tags); +int32_t mp4ff_meta_get_num_items(const mp4ff_t *f); +int32_t mp4ff_meta_get_by_index(const mp4ff_t *f, uint32_t index, + char **item, char **value); +int32_t mp4ff_meta_get_title(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_artist(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_writer(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_album(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_date(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_tool(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_comment(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_genre(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_track(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_disc(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_compilation(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_tempo(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_coverart(const mp4ff_t *f, char **value); +#endif + +/* mp4ff.c */ +mp4ff_t *mp4ff_open_read(mp4ff_callback_t *f); +#ifdef USE_TAGGING +mp4ff_t *mp4ff_open_edit(mp4ff_callback_t *f); +#endif +void mp4ff_close(mp4ff_t *ff); +/*void mp4ff_track_add(mp4ff_t *f);*/ +int32_t parse_sub_atoms(mp4ff_t *f, const uint64_t total_size); +int32_t parse_atoms(mp4ff_t *f); + +int32_t mp4ff_get_sample_duration(const mp4ff_t *f, const int32_t track, const int32_t sample); +int64_t mp4ff_get_sample_position(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_get_sample_offset(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_find_sample(const mp4ff_t *f, const int32_t track, const int64_t offset,int32_t * toskip); + +int32_t mp4ff_read_sample(mp4ff_t *f, const int32_t track, const int32_t sample, + uint8_t **audio_buffer, uint32_t *bytes); +int32_t mp4ff_get_decoder_config(const mp4ff_t *f, const int32_t track, + uint8_t** ppBuf, uint32_t* pBufSize); +int32_t mp4ff_total_tracks(const mp4ff_t *f); +int32_t mp4ff_time_scale(const mp4ff_t *f, const int32_t track); +int32_t mp4ff_num_samples(const mp4ff_t *f, const int32_t track); + +uint32_t mp4ff_meta_genre_to_index(const char * genrestr);//returns 1-based index, 0 if not found +const char * mp4ff_meta_index_to_genre(uint32_t idx);//returns pointer to static string + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/trunk/src/mp4ff/mp4meta.c b/trunk/src/mp4ff/mp4meta.c new file mode 100644 index 000000000..762f5dee7 --- /dev/null +++ b/trunk/src/mp4ff/mp4meta.c @@ -0,0 +1,414 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4meta.c,v 1.13 2004/01/11 15:52:18 menno Exp $ +**/ + +#ifdef USE_TAGGING + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "mp4ffint.h" + +static int32_t mp4ff_tag_add_field(mp4ff_metadata_t *tags, const char *item, const char *value) +{ + void *backup = (void *)tags->tags; + + if (!item || (item && !*item) || !value) return 0; + + tags->tags = (mp4ff_tag_t*)realloc(tags->tags, (tags->count+1) * sizeof(mp4ff_tag_t)); + if (!tags->tags) + { + if (backup) free(backup); + return 0; + } else { + tags->tags[tags->count].item = strdup(item); + tags->tags[tags->count].value = strdup(value); + + if (!tags->tags[tags->count].item || !tags->tags[tags->count].value) + { + if (!tags->tags[tags->count].item) free (tags->tags[tags->count].item); + if (!tags->tags[tags->count].value) free (tags->tags[tags->count].value); + tags->tags[tags->count].item = NULL; + tags->tags[tags->count].value = NULL; + return 0; + } + + tags->count++; + return 1; + } +} + +static int32_t mp4ff_tag_set_field(mp4ff_metadata_t *tags, const char *item, const char *value) +{ + unsigned int i; + + if (!item || (item && !*item) || !value) return 0; + + for (i = 0; i < tags->count; i++) + { + if (!stricmp(tags->tags[i].item, item)) + { + free(tags->tags[i].value); + tags->tags[i].value = strdup(value); + return 1; + } + } + + return mp4ff_tag_add_field(tags, item, value); +} + +int32_t mp4ff_tag_delete(mp4ff_metadata_t *tags) +{ + uint32_t i; + + for (i = 0; i < tags->count; i++) + { + if (tags->tags[i].item) free(tags->tags[i].item); + if (tags->tags[i].value) free(tags->tags[i].value); + } + + if (tags->tags) free(tags->tags); + + tags->tags = NULL; + tags->count = 0; + + return 0; +} + +static const char* ID3v1GenreList[] = { + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", + "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", + "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", + "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", + "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", + "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", + "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", + "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", + "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", + "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", + "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", + "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", + "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", + "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", + "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", + "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", + "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", + "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", + "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", + "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", + "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall", + "Goa", "Drum & Bass", "Club House", "Hardcore", "Terror", + "Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat", + "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary C", + "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", + "SynthPop", +}; + +uint32_t mp4ff_meta_genre_to_index(const char * genrestr) +{ + unsigned n; + for(n=0;n<sizeof(ID3v1GenreList)/sizeof(ID3v1GenreList[0]);n++) + { + if (!stricmp(genrestr,ID3v1GenreList[n])) return n+1; + } + return 0; +} + +const char * mp4ff_meta_index_to_genre(uint32_t idx) +{ + if (idx>0 && idx<=sizeof(ID3v1GenreList)/sizeof(ID3v1GenreList[0])) + { + return ID3v1GenreList[idx-1]; + } + else + { + return 0; + } +} + + +static int32_t TrackToString(char** str, const uint16_t track, const uint16_t totalTracks) +{ + char temp[32]; + sprintf(temp, "%.5u of %.5u", track, totalTracks); + *str = strdup(temp); + return 0; +} + +static int32_t mp4ff_set_metadata_name(mp4ff_t *f, const uint8_t atom_type, char **name) +{ + static char *tag_names[] = { + "unknown", "title", "artist", "writer", "album", + "date", "tool", "comment", "genre", "track", + "disc", "compilation", "genre", "tempo", "cover" + }; + uint8_t tag_idx = 0; + + switch (atom_type) + { + case ATOM_TITLE: tag_idx = 1; break; + case ATOM_ARTIST: tag_idx = 2; break; + case ATOM_WRITER: tag_idx = 3; break; + case ATOM_ALBUM: tag_idx = 4; break; + case ATOM_DATE: tag_idx = 5; break; + case ATOM_TOOL: tag_idx = 6; break; + case ATOM_COMMENT: tag_idx = 7; break; + case ATOM_GENRE1: tag_idx = 8; break; + case ATOM_TRACK: tag_idx = 9; break; + case ATOM_DISC: tag_idx = 10; break; + case ATOM_COMPILATION: tag_idx = 11; break; + case ATOM_GENRE2: tag_idx = 12; break; + case ATOM_TEMPO: tag_idx = 13; break; + case ATOM_COVER: tag_idx = 14; break; + default: tag_idx = 0; break; + } + + *name = strdup(tag_names[tag_idx]); + + return 0; +} + +static int32_t mp4ff_parse_tag(mp4ff_t *f, const uint8_t parent_atom_type, const int32_t size) +{ + uint8_t atom_type; + uint8_t header_size = 0; + uint64_t subsize, sumsize = 0; + char * name = NULL; + char * data = NULL; + uint32_t done = 0; + + + while (sumsize < size) + { + uint64_t destpos; + subsize = mp4ff_atom_read_header(f, &atom_type, &header_size); + destpos = mp4ff_position(f)+subsize-header_size; + if (!done) + { + if (atom_type == ATOM_DATA) + { + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + mp4ff_read_int32(f); /* reserved */ + + /* some need special attention */ + if (parent_atom_type == ATOM_GENRE2 || parent_atom_type == ATOM_TEMPO) + { + if (subsize - header_size >= 8 + 2) + { + uint16_t val = mp4ff_read_int16(f); + + if (parent_atom_type == ATOM_TEMPO) + { + char temp[16]; + sprintf(temp, "%.5u BPM", val); + mp4ff_tag_add_field(&(f->tags), "tempo", temp); + } + else + { + const char * temp = mp4ff_meta_index_to_genre(val); + if (temp) + { + mp4ff_tag_add_field(&(f->tags), "genre", temp); + } + } + done = 1; + } + } else if (parent_atom_type == ATOM_TRACK || parent_atom_type == ATOM_DISC) { + if (!done && subsize - header_size >= 8 + 8) + { + uint16_t index,total; + char temp[32]; + mp4ff_read_int16(f); + index = mp4ff_read_int16(f); + total = mp4ff_read_int16(f); + mp4ff_read_int16(f); + + sprintf(temp,"%d",index); + mp4ff_tag_add_field(&(f->tags), parent_atom_type == ATOM_TRACK ? "track" : "disc", temp); + if (total>0) + { + sprintf(temp,"%d",total); + mp4ff_tag_add_field(&(f->tags), parent_atom_type == ATOM_TRACK ? "totaltracks" : "totaldiscs", temp); + } + done = 1; + } + } else + { + if (data) {free(data);data = NULL;} + data = mp4ff_read_string(f,(uint32_t)(subsize-(header_size+8))); + } + } else if (atom_type == ATOM_NAME) { + if (!done) + { + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + if (name) free(name); + name = mp4ff_read_string(f,(uint32_t)(subsize-(header_size+4))); + } + } + mp4ff_set_position(f, destpos); + sumsize += subsize; + } + } + + if (data) + { + if (!done) + { + if (name == NULL) mp4ff_set_metadata_name(f, parent_atom_type, &name); + if (name) mp4ff_tag_add_field(&(f->tags), name, data); + } + + free(data); + } + if (name) free(name); + return 1; +} + +int32_t mp4ff_parse_metadata(mp4ff_t *f, const int32_t size) +{ + uint64_t subsize, sumsize = 0; + uint8_t atom_type; + uint8_t header_size = 0; + + while (sumsize < size) + { + subsize = mp4ff_atom_read_header(f, &atom_type, &header_size); + mp4ff_parse_tag(f, atom_type, (uint32_t)(subsize-header_size)); + sumsize += subsize; + } + + return 0; +} + +/* find a metadata item by name */ +/* returns 0 if item found, 1 if no such item */ +static int32_t mp4ff_meta_find_by_name(const mp4ff_t *f, const char *item, char **value) +{ + uint32_t i; + + for (i = 0; i < f->tags.count; i++) + { + if (!stricmp(f->tags.tags[i].item, item)) + { + *value = strdup(f->tags.tags[i].value); + return 1; + } + } + + *value = NULL; + + /* not found */ + return 0; +} + +int32_t mp4ff_meta_get_num_items(const mp4ff_t *f) +{ + return f->tags.count; +} + +int32_t mp4ff_meta_get_by_index(const mp4ff_t *f, uint32_t index, + char **item, char **value) +{ + if (index >= f->tags.count) + { + *item = NULL; + *value = NULL; + return 0; + } else { + *item = strdup(f->tags.tags[index].item); + *value = strdup(f->tags.tags[index].value); + return 1; + } +} + +int32_t mp4ff_meta_get_title(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "title", value); +} + +int32_t mp4ff_meta_get_artist(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "artist", value); +} + +int32_t mp4ff_meta_get_writer(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "writer", value); +} + +int32_t mp4ff_meta_get_album(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "album", value); +} + +int32_t mp4ff_meta_get_date(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "date", value); +} + +int32_t mp4ff_meta_get_tool(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "tool", value); +} + +int32_t mp4ff_meta_get_comment(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "comment", value); +} + +int32_t mp4ff_meta_get_genre(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "genre", value); +} + +int32_t mp4ff_meta_get_track(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "track", value); +} + +int32_t mp4ff_meta_get_disc(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "disc", value); +} + +int32_t mp4ff_meta_get_compilation(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "compilation", value); +} + +int32_t mp4ff_meta_get_tempo(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "tempo", value); +} + +int32_t mp4ff_meta_get_coverart(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "cover", value); +} + +#endif diff --git a/trunk/src/mp4ff/mp4sample.c b/trunk/src/mp4ff/mp4sample.c new file mode 100644 index 000000000..5688a3a8f --- /dev/null +++ b/trunk/src/mp4ff/mp4sample.c @@ -0,0 +1,152 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4sample.c,v 1.15 2004/01/11 15:52:19 menno Exp $ +**/ + +#include <stdlib.h> +#include "mp4ffint.h" + + +static int32_t mp4ff_chunk_of_sample(const mp4ff_t *f, const int32_t track, const int32_t sample, + int32_t *chunk_sample, int32_t *chunk) +{ + int32_t total_entries = 0; + int32_t chunk2entry; + int32_t chunk1, chunk2, chunk1samples, range_samples, total = 0; + + if (f->track[track] == NULL) + { + return -1; + } + + total_entries = f->track[track]->stsc_entry_count; + + chunk1 = 1; + chunk1samples = 0; + chunk2entry = 0; + + do + { + chunk2 = f->track[track]->stsc_first_chunk[chunk2entry]; + *chunk = chunk2 - chunk1; + range_samples = *chunk * chunk1samples; + + if (sample < total + range_samples) break; + + chunk1samples = f->track[track]->stsc_samples_per_chunk[chunk2entry]; + chunk1 = chunk2; + + if(chunk2entry < total_entries) + { + chunk2entry++; + total += range_samples; + } + } while (chunk2entry < total_entries); + + if (chunk1samples) + *chunk = (sample - total) / chunk1samples + chunk1; + else + *chunk = 1; + + *chunk_sample = total + (*chunk - chunk1) * chunk1samples; + + return 0; +} + +static int32_t mp4ff_chunk_to_offset(const mp4ff_t *f, const int32_t track, const int32_t chunk) +{ + const mp4ff_track_t * p_track = f->track[track]; + + if (p_track->stco_entry_count && (chunk > p_track->stco_entry_count)) + { + return p_track->stco_chunk_offset[p_track->stco_entry_count - 1]; + } else if (p_track->stco_entry_count) { + return p_track->stco_chunk_offset[chunk - 1]; + } else { + return 8; + } + + return 0; +} + +static int32_t mp4ff_sample_range_size(const mp4ff_t *f, const int32_t track, + const int32_t chunk_sample, const int32_t sample) +{ + int32_t i, total; + const mp4ff_track_t * p_track = f->track[track]; + + if (p_track->stsz_sample_size) + { + return (sample - chunk_sample) * p_track->stsz_sample_size; + } + else + { + if (sample>=p_track->stsz_sample_count) return 0;//error + + for(i = chunk_sample, total = 0; i < sample; i++) + { + total += p_track->stsz_table[i]; + } + } + + return total; +} + +static int32_t mp4ff_sample_to_offset(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t chunk, chunk_sample, chunk_offset1, chunk_offset2; + + mp4ff_chunk_of_sample(f, track, sample, &chunk_sample, &chunk); + + chunk_offset1 = mp4ff_chunk_to_offset(f, track, chunk); + chunk_offset2 = chunk_offset1 + mp4ff_sample_range_size(f, track, chunk_sample, sample); + + return chunk_offset2; +} + +int32_t mp4ff_audio_frame_size(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t bytes; + const mp4ff_track_t * p_track = f->track[track]; + + if (p_track->stsz_sample_size) + { + bytes = p_track->stsz_sample_size; + } else { + bytes = p_track->stsz_table[sample]; + } + + return bytes; +} + +int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t offset; + + offset = mp4ff_sample_to_offset(f, track, sample); + mp4ff_set_position(f, offset); + + return 0; +} diff --git a/trunk/src/mp4ff/mp4tagupdate.c b/trunk/src/mp4ff/mp4tagupdate.c new file mode 100644 index 000000000..c999fa5ee --- /dev/null +++ b/trunk/src/mp4ff/mp4tagupdate.c @@ -0,0 +1,645 @@ +#include <stdlib.h> +#include <string.h> +#include "mp4ffint.h" + +#ifdef USE_TAGGING + +static uint32_t fix_byte_order_32(uint32_t src) +{ + uint32_t result; + uint32_t a, b, c, d; + int8_t data[4]; + + memcpy(data,&src,sizeof(src)); + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + c = (uint8_t)data[2]; + d = (uint8_t)data[3]; + + result = (a<<24) | (b<<16) | (c<<8) | d; + return (uint32_t)result; +} + +static uint16_t fix_byte_order_16(uint16_t src) +{ + uint16_t result; + uint16_t a, b; + int8_t data[2]; + + memcpy(data,&src,sizeof(src)); + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + + result = (a<<8) | b; + return (uint16_t)result; +} + + +typedef struct +{ + void * data; + unsigned written; + unsigned allocated; + unsigned error; +} membuffer; + +static unsigned membuffer_write(membuffer * buf,const void * ptr,unsigned bytes) +{ + unsigned dest_size = buf->written + bytes; + + if (buf->error) return 0; + if (dest_size > buf->allocated) + { + do + { + buf->allocated <<= 1; + } while(dest_size > buf->allocated); + + { + void * newptr = realloc(buf->data,buf->allocated); + if (newptr==0) + { + free(buf->data); + buf->data = 0; + buf->error = 1; + return 0; + } + buf->data = newptr; + } + } + + if (ptr) memcpy((char*)buf->data + buf->written,ptr,bytes); + buf->written += bytes; + return bytes; +} + +#define membuffer_write_data membuffer_write + +static unsigned membuffer_write_int32(membuffer * buf,uint32_t data) +{ + uint8_t temp[4] = {(uint8_t)(data>>24),(uint8_t)(data>>16),(uint8_t)(data>>8),(uint8_t)data}; + return membuffer_write_data(buf,temp,4); +} + +static unsigned membuffer_write_int24(membuffer * buf,uint32_t data) +{ + uint8_t temp[3] = {(uint8_t)(data>>16),(uint8_t)(data>>8),(uint8_t)data}; + return membuffer_write_data(buf,temp,3); +} + +static unsigned membuffer_write_int16(membuffer * buf,uint16_t data) +{ + uint8_t temp[2] = {(uint8_t)(data>>8),(uint8_t)data}; + return membuffer_write_data(buf,temp,2); +} + +static unsigned membuffer_write_atom_name(membuffer * buf,const char * data) +{ + return membuffer_write_data(buf,data,4)==4 ? 1 : 0; +} + +static void membuffer_write_atom(membuffer * buf,const char * name,unsigned size,const void * data) +{ + membuffer_write_int32(buf,size + 8); + membuffer_write_atom_name(buf,name); + membuffer_write_data(buf,data,size); +} + +static unsigned membuffer_write_string(membuffer * buf,const char * data) +{ + return membuffer_write_data(buf,data,strlen(data)); +} + +static unsigned membuffer_write_int8(membuffer * buf,uint8_t data) +{ + return membuffer_write_data(buf,&data,1); +} + +static void * membuffer_get_ptr(const membuffer * buf) +{ + return buf->data; +} + +static unsigned membuffer_get_size(const membuffer * buf) +{ + return buf->written; +} + +static unsigned membuffer_error(const membuffer * buf) +{ + return buf->error; +} + +static void membuffer_set_error(membuffer * buf) {buf->error = 1;} + +static unsigned membuffer_transfer_from_file(membuffer * buf,mp4ff_t * src,unsigned bytes) +{ + unsigned oldsize; + void * bufptr; + + oldsize = membuffer_get_size(buf); + if (membuffer_write_data(buf,0,bytes) != bytes) return 0; + + bufptr = membuffer_get_ptr(buf); + if (bufptr==0) return 0; + + if ((unsigned)mp4ff_read_data(src,(char*)bufptr + oldsize,bytes)!=bytes) + { + membuffer_set_error(buf); + return 0; + } + + return bytes; +} + + +static membuffer * membuffer_create() +{ + const unsigned initial_size = 256; + + membuffer * buf = (membuffer *) malloc(sizeof(membuffer)); + buf->data = malloc(initial_size); + buf->written = 0; + buf->allocated = initial_size; + buf->error = buf->data == 0 ? 1 : 0; + + return buf; +} + +static void membuffer_free(membuffer * buf) +{ + if (buf->data) free(buf->data); + free(buf); +} + +static void * membuffer_detach(membuffer * buf) +{ + void * ret; + + if (buf->error) return 0; + + ret = realloc(buf->data,buf->written); + + if (ret == 0) free(buf->data); + + buf->data = 0; + buf->error = 1; + + return ret; +} + +#if 0 +/* metadata tag structure */ +typedef struct +{ + char *item; + char *value; +} mp4ff_tag_t; + +/* metadata list structure */ +typedef struct +{ + mp4ff_tag_t *tags; + uint32_t count; +} mp4ff_metadata_t; +#endif + +typedef struct +{ + const char * atom; + const char * name; +} stdmeta_entry; + +static stdmeta_entry stdmetas[] = +{ + {"©nam","title"}, + {"©ART","artist"}, + {"©wrt","writer"}, + {"©alb","album"}, + {"©day","date"}, + {"©too","tool"}, + {"©cmt","comment"}, +// {"©gen","genre"}, + {"cpil","compilation"}, +// {"trkn","track"}, +// {"disk","disc"}, +// {"gnre","genre"}, + {"covr","cover"}, +}; + + +static const char* find_standard_meta(const char * name) //returns atom name if found, 0 if not +{ + unsigned n; + for(n=0;n<sizeof(stdmetas)/sizeof(stdmetas[0]);n++) + { + if (!stricmp(name,stdmetas[n].name)) return stdmetas[n].atom; + } + return 0; +} + +static void membuffer_write_track_tag(membuffer * buf,const char * name,uint32_t index,uint32_t total) +{ + membuffer_write_int32(buf,8 /*atom header*/ + 8 /*data atom header*/ + 8 /*flags + reserved*/ + 8 /*actual data*/ ); + membuffer_write_atom_name(buf,name); + membuffer_write_int32(buf,8 /*data atom header*/ + 8 /*flags + reserved*/ + 8 /*actual data*/ ); + membuffer_write_atom_name(buf,"data"); + membuffer_write_int32(buf,0);//flags + membuffer_write_int32(buf,0);//reserved + membuffer_write_int16(buf,0); + membuffer_write_int16(buf,(uint16_t)index);//track number + membuffer_write_int16(buf,(uint16_t)total);//total tracks + membuffer_write_int16(buf,0); +} + +static void membuffer_write_int16_tag(membuffer * buf,const char * name,uint16_t value) +{ + membuffer_write_int32(buf,8 /*atom header*/ + 8 /*data atom header*/ + 8 /*flags + reserved*/ + 2 /*actual data*/ ); + membuffer_write_atom_name(buf,name); + membuffer_write_int32(buf,8 /*data atom header*/ + 8 /*flags + reserved*/ + 2 /*actual data*/ ); + membuffer_write_atom_name(buf,"data"); + membuffer_write_int32(buf,0);//flags + membuffer_write_int32(buf,0);//reserved + membuffer_write_int16(buf,value);//value +} + +static void membuffer_write_std_tag(membuffer * buf,const char * name,const char * value) +{ + membuffer_write_int32(buf,8 /*atom header*/ + 8 /*data atom header*/ + 8 /*flags + reserved*/ + strlen(value) ); + membuffer_write_atom_name(buf,name); + membuffer_write_int32(buf,8 /*data atom header*/ + 8 /*flags + reserved*/ + strlen(value)); + membuffer_write_atom_name(buf,"data"); + membuffer_write_int32(buf,1);//flags + membuffer_write_int32(buf,0);//reserved + membuffer_write_data(buf,value,strlen(value)); +} + +static void membuffer_write_custom_tag(membuffer * buf,const char * name,const char * value) +{ + membuffer_write_int32(buf,8 /*atom header*/ + 0x1C /*weirdo itunes atom*/ + 12 /*name atom header*/ + strlen(name) + 16 /*data atom header + flags*/ + strlen(value) ); + membuffer_write_atom_name(buf,"----"); + membuffer_write_int32(buf,0x1C);//weirdo itunes atom + membuffer_write_atom_name(buf,"mean"); + membuffer_write_int32(buf,0); + membuffer_write_data(buf,"com.apple.iTunes",16); + membuffer_write_int32(buf,12 + strlen(name)); + membuffer_write_atom_name(buf,"name"); + membuffer_write_int32(buf,0); + membuffer_write_data(buf,name,strlen(name)); + membuffer_write_int32(buf,8 /*data atom header*/ + 8 /*flags + reserved*/ + strlen(value)); + membuffer_write_atom_name(buf,"data"); + membuffer_write_int32(buf,1);//flags + membuffer_write_int32(buf,0);//reserved + membuffer_write_data(buf,value,strlen(value)); + +} + +static uint32_t myatoi(const char * param) +{ + return param ? atoi(param) : 0; +} + +static uint32_t create_ilst(const mp4ff_metadata_t * data,void ** out_buffer,uint32_t * out_size) +{ + membuffer * buf = membuffer_create(); + unsigned metaptr; + char * mask = (char*)malloc(data->count); + memset(mask,0,data->count); + + { + const char * tracknumber_ptr = 0, * totaltracks_ptr = 0; + const char * discnumber_ptr = 0, * totaldiscs_ptr = 0; + const char * genre_ptr = 0, * tempo_ptr = 0; + for(metaptr = 0; metaptr < data->count; metaptr++) + { + mp4ff_tag_t * tag = &data->tags[metaptr]; + if (!stricmp(tag->item,"tracknumber") || !stricmp(tag->item,"track")) + { + if (tracknumber_ptr==0) tracknumber_ptr = tag->value; + mask[metaptr] = 1; + } + else if (!stricmp(tag->item,"totaltracks")) + { + if (totaltracks_ptr==0) totaltracks_ptr = tag->value; + mask[metaptr] = 1; + } + else if (!stricmp(tag->item,"discnumber") || !stricmp(tag->item,"disc")) + { + if (discnumber_ptr==0) discnumber_ptr = tag->value; + mask[metaptr] = 1; + } + else if (!stricmp(tag->item,"totaldiscs")) + { + if (totaldiscs_ptr==0) totaldiscs_ptr = tag->value; + mask[metaptr] = 1; + } + else if (!stricmp(tag->item,"genre")) + { + if (genre_ptr==0) genre_ptr = tag->value; + mask[metaptr] = 1; + } + else if (!stricmp(tag->item,"tempo")) + { + if (tempo_ptr==0) tempo_ptr = tag->value; + mask[metaptr] = 1; + } + + } + + if (tracknumber_ptr) membuffer_write_track_tag(buf,"trkn",myatoi(tracknumber_ptr),myatoi(totaltracks_ptr)); + if (discnumber_ptr) membuffer_write_track_tag(buf,"disk",myatoi(discnumber_ptr),myatoi(totaldiscs_ptr)); + if (tempo_ptr) membuffer_write_int16_tag(buf,"tmpo",(uint16_t)myatoi(tempo_ptr)); + + if (genre_ptr) + { + uint32_t index = mp4ff_meta_genre_to_index(genre_ptr); + if (index==0) + membuffer_write_std_tag(buf,"©gen",genre_ptr); + else + membuffer_write_int16_tag(buf,"gnre",(uint16_t)index); + } + } + + for(metaptr = 0; metaptr < data->count; metaptr++) + { + if (!mask[metaptr]) + { + mp4ff_tag_t * tag = &data->tags[metaptr]; + const char * std_meta_atom = find_standard_meta(tag->item); + if (std_meta_atom) + { + membuffer_write_std_tag(buf,std_meta_atom,tag->value); + } + else + { + membuffer_write_custom_tag(buf,tag->item,tag->value); + } + } + } + + free(mask); + + if (membuffer_error(buf)) + { + membuffer_free(buf); + return 0; + } + + *out_size = membuffer_get_size(buf); + *out_buffer = membuffer_detach(buf); + membuffer_free(buf); + + return 1; +} + +static uint32_t find_atom(mp4ff_t * f,uint64_t base,uint32_t size,const char * name) +{ + uint32_t remaining = size; + uint64_t atom_offset = base; + for(;;) + { + char atom_name[4]; + uint32_t atom_size; + + mp4ff_set_position(f,atom_offset); + + if (remaining < 8) break; + atom_size = mp4ff_read_int32(f); + if (atom_size > remaining || atom_size < 8) break; + mp4ff_read_data(f,atom_name,4); + + if (!memcmp(atom_name,name,4)) + { + mp4ff_set_position(f,atom_offset); + return 1; + } + + remaining -= atom_size; + atom_offset += atom_size; + } + return 0; +} + +static uint32_t find_atom_v2(mp4ff_t * f,uint64_t base,uint32_t size,const char * name,uint32_t extraheaders,const char * name_inside) +{ + uint64_t first_base = (uint64_t)(-1); + while(find_atom(f,base,size,name))//try to find atom <name> with atom <name_inside> in it + { + uint64_t mybase = mp4ff_position(f); + uint32_t mysize = mp4ff_read_int32(f); + + if (first_base == (uint64_t)(-1)) first_base = mybase; + + if (mysize < 8 + extraheaders) break; + + if (find_atom(f,mybase+(8+extraheaders),mysize-(8+extraheaders),name_inside)) + { + mp4ff_set_position(f,mybase); + return 2; + } + base += mysize; + if (size<=mysize) {size=0;break;} + size -= mysize; + } + + if (first_base != (uint64_t)(-1))//wanted atom inside not found + { + mp4ff_set_position(f,first_base); + return 1; + } + else return 0; +} + +static uint32_t create_meta(const mp4ff_metadata_t * data,void ** out_buffer,uint32_t * out_size) +{ + membuffer * buf; + uint32_t ilst_size; + void * ilst_buffer; + + if (!create_ilst(data,&ilst_buffer,&ilst_size)) return 0; + + buf = membuffer_create(); + + membuffer_write_int32(buf,0); + membuffer_write_atom(buf,"ilst",ilst_size,ilst_buffer); + free(ilst_buffer); + + *out_size = membuffer_get_size(buf); + *out_buffer = membuffer_detach(buf); + membuffer_free(buf); + return 1; +} + +static uint32_t create_udta(const mp4ff_metadata_t * data,void ** out_buffer,uint32_t * out_size) +{ + membuffer * buf; + uint32_t meta_size; + void * meta_buffer; + + if (!create_meta(data,&meta_buffer,&meta_size)) return 0; + + buf = membuffer_create(); + + membuffer_write_atom(buf,"meta",meta_size,meta_buffer); + + free(meta_buffer); + + *out_size = membuffer_get_size(buf); + *out_buffer = membuffer_detach(buf); + membuffer_free(buf); + return 1; +} + +static uint32_t modify_moov(mp4ff_t * f,const mp4ff_metadata_t * data,void ** out_buffer,uint32_t * out_size) +{ + uint64_t total_base = f->moov_offset + 8; + uint32_t total_size = (uint32_t)(f->moov_size - 8); + + uint64_t udta_offset,meta_offset,ilst_offset; + uint32_t udta_size, meta_size, ilst_size; + + uint32_t new_ilst_size; + void * new_ilst_buffer; + + uint8_t * p_out; + int32_t size_delta; + + + if (!find_atom_v2(f,total_base,total_size,"udta",0,"meta")) + { + membuffer * buf; + void * new_udta_buffer; + uint32_t new_udta_size; + if (!create_udta(data,&new_udta_buffer,&new_udta_size)) return 0; + + buf = membuffer_create(); + mp4ff_set_position(f,total_base); + membuffer_transfer_from_file(buf,f,total_size); + + membuffer_write_atom(buf,"udta",new_udta_size,new_udta_buffer); + + free(new_udta_buffer); + + *out_size = membuffer_get_size(buf); + *out_buffer = membuffer_detach(buf); + membuffer_free(buf); + return 1; + } + else + { + udta_offset = mp4ff_position(f); + udta_size = mp4ff_read_int32(f); + if (find_atom_v2(f,udta_offset+8,udta_size-8,"meta",4,"ilst")<2) + { + membuffer * buf; + void * new_meta_buffer; + uint32_t new_meta_size; + if (!create_meta(data,&new_meta_buffer,&new_meta_size)) return 0; + + buf = membuffer_create(); + mp4ff_set_position(f,total_base); + membuffer_transfer_from_file(buf,f,(uint32_t)(udta_offset - total_base)); + + membuffer_write_int32(buf,udta_size + 8 + new_meta_size); + membuffer_write_atom_name(buf,"udta"); + membuffer_transfer_from_file(buf,f,udta_size); + + membuffer_write_atom(buf,"meta",new_meta_size,new_meta_buffer); + free(new_meta_buffer); + + *out_size = membuffer_get_size(buf); + *out_buffer = membuffer_detach(buf); + membuffer_free(buf); + return 1; + } + meta_offset = mp4ff_position(f); + meta_size = mp4ff_read_int32(f); + if (!find_atom(f,meta_offset+12,meta_size-12,"ilst")) return 0;//shouldn't happen, find_atom_v2 above takes care of it + ilst_offset = mp4ff_position(f); + ilst_size = mp4ff_read_int32(f); + + if (!create_ilst(data,&new_ilst_buffer,&new_ilst_size)) return 0; + + size_delta = new_ilst_size - (ilst_size - 8); + + *out_size = total_size + size_delta; + *out_buffer = malloc(*out_size); + if (*out_buffer == 0) + { + free(new_ilst_buffer); + return 0; + } + + p_out = (uint8_t*)*out_buffer; + + mp4ff_set_position(f,total_base); + mp4ff_read_data(f,p_out,(uint32_t)(udta_offset - total_base )); p_out += (uint32_t)(udta_offset - total_base ); + *(uint32_t*)p_out = fix_byte_order_32(mp4ff_read_int32(f) + size_delta); p_out += 4; + mp4ff_read_data(f,p_out,4); p_out += 4; + mp4ff_read_data(f,p_out,(uint32_t)(meta_offset - udta_offset - 8)); p_out += (uint32_t)(meta_offset - udta_offset - 8); + *(uint32_t*)p_out = fix_byte_order_32(mp4ff_read_int32(f) + size_delta); p_out += 4; + mp4ff_read_data(f,p_out,4); p_out += 4; + mp4ff_read_data(f,p_out,(uint32_t)(ilst_offset - meta_offset - 8)); p_out += (uint32_t)(ilst_offset - meta_offset - 8); + *(uint32_t*)p_out = fix_byte_order_32(mp4ff_read_int32(f) + size_delta); p_out += 4; + mp4ff_read_data(f,p_out,4); p_out += 4; + + memcpy(p_out,new_ilst_buffer,new_ilst_size); + p_out += new_ilst_size; + + mp4ff_set_position(f,ilst_offset + ilst_size); + mp4ff_read_data(f,p_out,(uint32_t)(total_size - (ilst_offset - total_base) - ilst_size)); + + free(new_ilst_buffer); + } + return 1; + +} + +static int32_t mp4ff_meta_update(mp4ff_callback_t *f,const mp4ff_metadata_t * data) +{ + void * new_moov_data; + uint32_t new_moov_size; + + mp4ff_t *ff = malloc(sizeof(mp4ff_t)); + + memset(ff, 0, sizeof(mp4ff_t)); + ff->stream = f; + mp4ff_set_position(ff,0); + + parse_atoms(ff); + + + if (!modify_moov(ff,data,&new_moov_data,&new_moov_size)) + { + mp4ff_close(ff); + return 0; + } + + /* copy moov atom to end of the file */ + if (ff->last_atom != ATOM_MOOV) + { + char *free_data = "free"; + + /* rename old moov to free */ + mp4ff_set_position(ff, ff->moov_offset + 4); + mp4ff_write_data(ff, free_data, 4); + + mp4ff_set_position(ff, ff->file_size); + mp4ff_write_int32(ff,new_moov_size + 8); + mp4ff_write_data(ff,"moov",4); + mp4ff_write_data(ff, new_moov_data, new_moov_size); + } + else + { + mp4ff_set_position(ff, ff->moov_offset); + mp4ff_write_int32(ff,new_moov_size + 8); + mp4ff_write_data(ff,"moov",4); + mp4ff_write_data(ff, new_moov_data, new_moov_size); + } + + mp4ff_truncate(ff); + + mp4ff_close(ff); + return 1; +} +#endif diff --git a/trunk/src/mp4ff/mp4util.c b/trunk/src/mp4ff/mp4util.c new file mode 100644 index 000000000..1a77c97ae --- /dev/null +++ b/trunk/src/mp4ff/mp4util.c @@ -0,0 +1,188 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4util.c,v 1.15 2004/01/11 15:52:19 menno Exp $ +**/ + +#include "mp4ffint.h" +#include <stdlib.h> + +int32_t mp4ff_read_data(mp4ff_t *f, int8_t *data, uint32_t size) +{ + int32_t result = 1; + + result = f->stream->read(f->stream->user_data, data, size); + + f->current_position += size; + + return result; +} + +int32_t mp4ff_truncate(mp4ff_t * f) +{ + return f->stream->truncate(f->stream->user_data); +} + +int32_t mp4ff_write_data(mp4ff_t *f, int8_t *data, uint32_t size) +{ + int32_t result = 1; + + result = f->stream->write(f->stream->user_data, data, size); + + f->current_position += size; + + return result; +} + +int32_t mp4ff_write_int32(mp4ff_t *f,const uint32_t data) +{ + uint32_t result; + uint32_t a, b, c, d; + int8_t temp[4]; + + *(uint32_t*)temp = data; + a = (uint8_t)temp[0]; + b = (uint8_t)temp[1]; + c = (uint8_t)temp[2]; + d = (uint8_t)temp[3]; + + result = (a<<24) | (b<<16) | (c<<8) | d; + + return mp4ff_write_data(f,(uint8_t*)&result,sizeof(result)); +} + +int32_t mp4ff_set_position(mp4ff_t *f, const int64_t position) +{ + f->stream->seek(f->stream->user_data, position); + f->current_position = position; + + return 0; +} + +int64_t mp4ff_position(const mp4ff_t *f) +{ + return f->current_position; +} + +uint64_t mp4ff_read_int64(mp4ff_t *f) +{ + uint8_t data[8]; + uint64_t result = 0; + int8_t i; + + mp4ff_read_data(f, data, 8); + + for (i = 0; i < 8; i++) + { + result |= ((uint64_t)data[i]) << ((7 - i) * 8); + } + + return result; +} + +uint32_t mp4ff_read_int32(mp4ff_t *f) +{ + uint32_t result; + uint32_t a, b, c, d; + int8_t data[4]; + + mp4ff_read_data(f, data, 4); + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + c = (uint8_t)data[2]; + d = (uint8_t)data[3]; + + result = (a<<24) | (b<<16) | (c<<8) | d; + return (uint32_t)result; +} + +uint32_t mp4ff_read_int24(mp4ff_t *f) +{ + uint32_t result; + uint32_t a, b, c; + int8_t data[4]; + + mp4ff_read_data(f, data, 3); + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + c = (uint8_t)data[2]; + + result = (a<<16) | (b<<8) | c; + return (uint32_t)result; +} + +uint16_t mp4ff_read_int16(mp4ff_t *f) +{ + uint32_t result; + uint32_t a, b; + int8_t data[2]; + + mp4ff_read_data(f, data, 2); + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + + result = (a<<8) | b; + return (uint16_t)result; +} + +char * mp4ff_read_string(mp4ff_t * f,uint32_t length) +{ + char * str = (char*)malloc(length + 1); + if (str!=0) + { + if ((uint32_t)mp4ff_read_data(f,str,length)!=length) + { + free(str); + str = 0; + } + else + { + str[length] = 0; + } + } + return str; +} + +uint8_t mp4ff_read_char(mp4ff_t *f) +{ + uint8_t output; + mp4ff_read_data(f, &output, 1); + return output; +} + +uint32_t mp4ff_read_mp4_descr_length(mp4ff_t *f) +{ + uint8_t b; + uint8_t numBytes = 0; + uint32_t length = 0; + + do + { + b = mp4ff_read_char(f); + numBytes++; + length = (length << 7) | (b & 0x7F); + } while ((b & 0x80) && numBytes < 4); + + return length; +} diff --git a/trunk/src/mpd_types.h b/trunk/src/mpd_types.h new file mode 100644 index 000000000..dbdfc6865 --- /dev/null +++ b/trunk/src/mpd_types.h @@ -0,0 +1,43 @@ +/* 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 MPD_TYPES_H +#define MPD_TYPES_H + +#include "../config.h" + +typedef unsigned char mpd_uint8; +typedef signed char mpd_sint8; + +#if SIZEOF_SHORT == 2 +typedef unsigned short mpd_uint16; +typedef signed short mpd_sint16; +#elif SIZEOF_INT == 2 +typedef unsigned int mpd_uint16; +typedef signed int mpd_sint16; +#endif + +#if SIZEOF_INT == 4 +typedef unsigned int mpd_uint32; +typedef signed int mpd_sint32; +#elif SIZEOF_LONG == 4 +typedef unsigned long mpd_uint32; +typedef signed long mpd_sint32; +#endif + +#endif diff --git a/trunk/src/myfprintf.c b/trunk/src/myfprintf.c new file mode 100644 index 000000000..a09ae4324 --- /dev/null +++ b/trunk/src/myfprintf.c @@ -0,0 +1,72 @@ +/* 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 "myfprintf.h" +#include "interface.h" +#include "path.h" +#include "log.h" +#include "conf.h" +#include "utils.h" + +#include <stdarg.h> +#include <sys/param.h> +#include <unistd.h> +#include <sys/select.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> + +#define BUFFER_LENGTH MAXPATHLEN+1024 + +static void blockingWrite(const int fd, const char *string, size_t len) +{ + while (len) { + ssize_t ret = xwrite(fd, string, len); + if (ret == len) + return; + if (ret >= 0) { + len -= ret; + string += ret; + continue; + } + return; /* error */ + } +} + +void vfdprintf(const int fd, const char *fmt, va_list args) +{ + static char buffer[BUFFER_LENGTH]; + char *buf = buffer; + size_t len; + + vsnprintf(buf, BUFFER_LENGTH, fmt, args); + len = strlen(buf); + if (fd == STDERR_FILENO || + fd == STDOUT_FILENO || + interfacePrintWithFD(fd, buf, len) < 0) + blockingWrite(fd, buf, len); +} + +mpd_fprintf void fdprintf(const int fd, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfdprintf(fd, fmt, args); + va_end(args); +} + diff --git a/trunk/src/myfprintf.h b/trunk/src/myfprintf.h new file mode 100644 index 000000000..287902f9b --- /dev/null +++ b/trunk/src/myfprintf.h @@ -0,0 +1,31 @@ +/* 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 MYFPRINTF_H +#define MYFPRINTF_H + +#include "../config.h" +#include "gcc.h" + +#include <stdarg.h> +#include <stdio.h> + +mpd_fprintf void fdprintf(const int fd, const char *fmt, ...); +void vfdprintf(const int fd, const char *fmt, va_list arglist); + +#endif diff --git a/trunk/src/normalize.c b/trunk/src/normalize.c new file mode 100644 index 000000000..fb62e7a4e --- /dev/null +++ b/trunk/src/normalize.c @@ -0,0 +1,47 @@ +/* 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 "compress.h" +#include "conf.h" +#include "normalize.h" + +#include <stdlib.h> + +int normalizationEnabled; + +void initNormalization(void) +{ + normalizationEnabled = getBoolConfigParam(CONF_VOLUME_NORMALIZATION); + if (normalizationEnabled == -1) normalizationEnabled = 0; + else if (normalizationEnabled < 0) exit(EXIT_FAILURE); + + if (normalizationEnabled) + CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS); +} + +void finishNormalization(void) +{ + if (normalizationEnabled) CompressFree(); +} + +void normalizeData(char *buffer, int bufferSize, AudioFormat *format) +{ + if ((format->bits != 16) || (format->channels != 2)) return; + + CompressDo(buffer, bufferSize); +} diff --git a/trunk/src/normalize.h b/trunk/src/normalize.h new file mode 100644 index 000000000..ddbefab08 --- /dev/null +++ b/trunk/src/normalize.h @@ -0,0 +1,32 @@ +/* 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 NORMALIZE_H +#define NORMALIZE_H + +#include "audio.h" + +extern int normalizationEnabled; + +void initNormalization(void); + +void finishNormalization(void); + +void normalizeData(char *buffer, int bufferSize, AudioFormat *format); + +#endif /* !NORMALIZE_H */ diff --git a/trunk/src/outputBuffer.c b/trunk/src/outputBuffer.c new file mode 100644 index 000000000..c7ff8b479 --- /dev/null +++ b/trunk/src/outputBuffer.c @@ -0,0 +1,198 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "outputBuffer.h" + +#include "pcm_utils.h" +#include "playerData.h" +#include "utils.h" +#include "log.h" +#include "normalize.h" +#include "conf.h" + +#include <string.h> + +static mpd_sint16 currentChunk = -1; + +static mpd_sint8 currentMetaChunk = -1; +static mpd_sint8 sendMetaChunk; + +void clearAllMetaChunkSets(OutputBuffer * cb) +{ + memset(cb->metaChunkSet, 0, BUFFERED_METACHUNKS); +} + +void clearOutputBuffer(OutputBuffer * cb) +{ + int currentSet = 1; + + cb->end = cb->begin; + + /* be sure to reset metaChunkSets cause we are skipping over audio + * audio chunks, and thus skipping over metadata */ + if (currentChunk >= 0 && sendMetaChunk == 0 && currentMetaChunk >= 0) { + currentSet = cb->metaChunkSet[currentChunk]; + } + clearAllMetaChunkSets(cb); + if (currentChunk >= 0 && sendMetaChunk == 0 && currentMetaChunk >= 0) { + cb->metaChunkSet[currentChunk] = currentSet; + } + currentChunk = -1; +} + +void flushOutputBuffer(OutputBuffer * cb) +{ + if (currentChunk == cb->end) { + if ((cb->end + 1) >= buffered_chunks) { + cb->end = 0; + } + else cb->end++; + currentChunk = -1; + } +} + +int sendDataToOutputBuffer(OutputBuffer * cb, InputStream * inStream, + DecoderControl * dc, int seekable, void *dataIn, + long dataInLen, float time, mpd_uint16 bitRate, + ReplayGainInfo * replayGainInfo) +{ + mpd_uint16 dataToSend; + mpd_uint16 chunkLeft; + char *data; + size_t datalen; + static char *convBuffer; + static long convBufferLen; + + if (cmpAudioFormat(&(cb->audioFormat), &(dc->audioFormat)) == 0) { + data = dataIn; + datalen = dataInLen; + } else { + datalen = pcm_sizeOfConvBuffer(&(dc->audioFormat), dataInLen, + &(cb->audioFormat)); + if (datalen > convBufferLen) { + convBuffer = xrealloc(convBuffer, datalen); + convBufferLen = datalen; + } + data = convBuffer; + datalen = pcm_convertAudioFormat(&(dc->audioFormat), dataIn, + dataInLen, &(cb->audioFormat), + data, &(cb->convState)); + } + + if (replayGainInfo && (replayGainState != REPLAYGAIN_OFF)) + doReplayGain(replayGainInfo, data, datalen, &cb->audioFormat); + else if (normalizationEnabled) + normalizeData(data, datalen, &cb->audioFormat); + + while (datalen) { + if (currentChunk != cb->end) { + int next = cb->end + 1; + if (next >= buffered_chunks) { + next = 0; + } + while (cb->begin == next && !dc->stop) { + if (dc->seek) { + if (seekable) { + return OUTPUT_BUFFER_DC_SEEK; + } else { + dc->seekError = 1; + dc->seek = 0; + } + } + if (!inStream || + bufferInputStream(inStream) <= 0) { + my_usleep(10000); + } + } + if (dc->stop) + return OUTPUT_BUFFER_DC_STOP; + + currentChunk = cb->end; + cb->chunkSize[currentChunk] = 0; + + if (sendMetaChunk) { + cb->metaChunk[currentChunk] = currentMetaChunk; + } else + cb->metaChunk[currentChunk] = -1; + cb->bitRate[currentChunk] = bitRate; + cb->times[currentChunk] = time; + } + + chunkLeft = CHUNK_SIZE - cb->chunkSize[currentChunk]; + dataToSend = datalen > chunkLeft ? chunkLeft : datalen; + + memcpy(cb->chunks + currentChunk * CHUNK_SIZE + + cb->chunkSize[currentChunk], data, dataToSend); + cb->chunkSize[currentChunk] += dataToSend; + datalen -= dataToSend; + data += dataToSend; + + if (cb->chunkSize[currentChunk] == CHUNK_SIZE) { + flushOutputBuffer(cb); + } + } + + return 0; +} + +int copyMpdTagToOutputBuffer(OutputBuffer * cb, MpdTag * tag) +{ + int nextChunk; + static MpdTag *last; + + if (!cb->acceptMetadata || !tag) { + sendMetaChunk = 0; + if (last) + freeMpdTag(last); + last = NULL; + DEBUG("copyMpdTagToOB: !acceptMetadata || !tag\n"); + return 0; + } + + if (last && mpdTagsAreEqual(last, tag)) { + DEBUG("copyMpdTagToOB: same as last\n"); + return 0; + } + + if (last) + freeMpdTag(last); + last = NULL; + + nextChunk = currentMetaChunk + 1; + if (nextChunk >= BUFFERED_METACHUNKS) + nextChunk = 0; + + if (cb->metaChunkSet[nextChunk]) { + sendMetaChunk = 0; + DEBUG("copyMpdTagToOB: metachunk in use!\n"); + return -1; + } + + sendMetaChunk = 1; + currentMetaChunk = nextChunk; + + last = mpdTagDup(tag); + + copyMpdTagToMetadataChunk(tag, &(cb->metadataChunks[currentMetaChunk])); + + cb->metaChunkSet[nextChunk] = 1; + + DEBUG("copyMpdTagToOB: copiedTag\n"); + + return 0; +} diff --git a/trunk/src/outputBuffer.h b/trunk/src/outputBuffer.h new file mode 100644 index 000000000..f690941d4 --- /dev/null +++ b/trunk/src/outputBuffer.h @@ -0,0 +1,69 @@ +/* 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_H +#define OUTPUT_BUFFER_H + +#include "pcm_utils.h" +#include "mpd_types.h" +#include "decode.h" +#include "audio.h" +#include "inputStream.h" +#include "metadataChunk.h" +#include "replayGain.h" + +#define OUTPUT_BUFFER_DC_STOP -1 +#define OUTPUT_BUFFER_DC_SEEK -2 + +#define BUFFERED_METACHUNKS 25 + +typedef struct _OutputBuffer { + char *volatile chunks; + mpd_uint16 *volatile chunkSize; + mpd_uint16 *volatile bitRate; + float *volatile times; + mpd_sint16 volatile begin; + mpd_sint16 volatile end; + AudioFormat audioFormat; + ConvState convState; + MetadataChunk metadataChunks[BUFFERED_METACHUNKS]; + mpd_sint8 metaChunkSet[BUFFERED_METACHUNKS]; + mpd_sint8 *volatile metaChunk; + volatile mpd_sint8 acceptMetadata; +} OutputBuffer; + +void clearOutputBuffer(OutputBuffer * cb); + +void flushOutputBuffer(OutputBuffer * cb); + +/* we send inStream for buffering the inputStream while waiting to + send the next chunk */ +int sendDataToOutputBuffer(OutputBuffer * cb, + InputStream * inStream, + DecoderControl * dc, + int seekable, + void *data, + long datalen, + float time, + mpd_uint16 bitRate, ReplayGainInfo * replayGainInfo); + +int copyMpdTagToOutputBuffer(OutputBuffer * cb, MpdTag * tag); + +void clearAllMetaChunkSets(OutputBuffer * cb); + +#endif diff --git a/trunk/src/path.c b/trunk/src/path.c new file mode 100644 index 000000000..f30eb0793 --- /dev/null +++ b/trunk/src/path.c @@ -0,0 +1,310 @@ +/* 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 "path.h" +#include "log.h" +#include "charConv.h" +#include "conf.h" +#include "utf8.h" +#include "utils.h" + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <dirent.h> + +#ifdef HAVE_LOCALE +#ifdef HAVE_LANGINFO_CODESET +#include <locale.h> +#include <langinfo.h> +#endif +#endif + +const char *musicDir; +static const char *playlistDir; +static char *fsCharset; + +static char *pathConvCharset(char *to, char *from, char *str, char *ret) +{ + if (ret) + free(ret); + return setCharSetConversion(to, from) ? NULL : convStrDup(str); +} + +char *fsCharsetToUtf8(char *str) +{ + static char *ret; + + ret = pathConvCharset("UTF-8", fsCharset, str, ret); + + if (ret && !validUtf8String(ret)) { + free(ret); + ret = NULL; + } + + return ret; +} + +char *utf8ToFsCharset(char *str) +{ + static char *ret; + + ret = pathConvCharset(fsCharset, "UTF-8", str, ret); + + if (!ret) + ret = xstrdup(str); + + return ret; +} + +void setFsCharset(char *charset) +{ + int error = 0; + + if (fsCharset) + free(fsCharset); + + fsCharset = xstrdup(charset); + + DEBUG("setFsCharset: fs charset is: %s\n", fsCharset); + + if (setCharSetConversion("UTF-8", fsCharset) != 0) { + WARNING("fs charset conversion problem: " + "not able to convert from \"%s\" to \"%s\"\n", + fsCharset, "UTF-8"); + error = 1; + } + if (setCharSetConversion(fsCharset, "UTF-8") != 0) { + WARNING("fs charset conversion problem: " + "not able to convert from \"%s\" to \"%s\"\n", + "UTF-8", fsCharset); + error = 1; + } + + if (error) { + free(fsCharset); + WARNING("setting fs charset to ISO-8859-1!\n"); + fsCharset = xstrdup("ISO-8859-1"); + } +} + +char *getFsCharset(void) +{ + return fsCharset; +} + +static char *appendSlash(char **path) +{ + char *temp = *path; + int len = strlen(temp); + + if (temp[len - 1] != '/') { + temp = xmalloc(len + 2); + memset(temp, 0, len + 2); + memcpy(temp, *path, len); + temp[len] = '/'; + free(*path); + *path = temp; + } + + return temp; +} + +void initPaths(void) +{ + ConfigParam *musicParam = parseConfigFilePath(CONF_MUSIC_DIR, 1); + ConfigParam *playlistParam = parseConfigFilePath(CONF_PLAYLIST_DIR, 1); + ConfigParam *fsCharsetParam = getConfigParam(CONF_FS_CHARSET); + + char *charset = NULL; + char *originalLocale; + DIR *dir; + + musicDir = appendSlash(&(musicParam->value)); + playlistDir = appendSlash(&(playlistParam->value)); + + if ((dir = opendir(playlistDir)) == NULL) { + FATAL("cannot open %s \"%s\" (config line %i): %s\n", + CONF_PLAYLIST_DIR, playlistParam->value, + playlistParam->line, strerror(errno)); + } + closedir(dir); + + if ((dir = opendir(musicDir)) == NULL) { + FATAL("cannot open %s \"%s\" (config line %i): %s\n", + CONF_MUSIC_DIR, musicParam->value, + musicParam->line, strerror(errno)); + } + closedir(dir); + + if (fsCharsetParam) { + charset = xstrdup(fsCharsetParam->value); + } +#ifdef HAVE_LOCALE +#ifdef HAVE_LANGINFO_CODESET + else if ((originalLocale = setlocale(LC_CTYPE, NULL))) { + char *temp; + char *currentLocale; + originalLocale = xstrdup(originalLocale); + + if (!(currentLocale = setlocale(LC_CTYPE, ""))) { + WARNING("problems setting current locale with " + "setlocale()\n"); + } else { + if (strcmp(currentLocale, "C") == 0 || + strcmp(currentLocale, "POSIX") == 0) { + WARNING("current locale is \"%s\"\n", + currentLocale); + } else if ((temp = nl_langinfo(CODESET))) { + charset = xstrdup(temp); + } else + WARNING + ("problems getting charset for locale\n"); + if (!setlocale(LC_CTYPE, originalLocale)) { + WARNING + ("problems resetting locale with setlocale()\n"); + } + } + + free(originalLocale); + } else + WARNING("problems getting locale with setlocale()\n"); +#endif +#endif + + if (charset) { + setFsCharset(charset); + free(charset); + } else { + WARNING("setting filesystem charset to ISO-8859-1\n"); + setFsCharset("ISO-8859-1"); + } +} + +void finishPaths(void) +{ + free(fsCharset); + fsCharset = NULL; +} + +static char *pfx_path(const char *path, const char *pfx, const size_t pfx_len) +{ + static char ret[MAXPATHLEN+1]; + size_t rp_len = strlen(path); + + /* check for the likely condition first: */ + if (mpd_likely((pfx_len + rp_len) < MAXPATHLEN)) { + memcpy(ret, pfx, pfx_len); + memcpy(ret + pfx_len, path, rp_len + 1); + return ret; + } + + /* unlikely, return an empty string because truncating would + * also be wrong... break early and break loudly (the system + * headers are likely screwed, not mpd) */ + ERROR("Cannot prefix '%s' to '%s', max: %d", pfx, path, MAXPATHLEN); + ret[0] = '\0'; + return ret; +} + +char *rmp2amp(char *relativePath) +{ + size_t pfx_len = strlen(musicDir); + return pfx_path(relativePath, musicDir, pfx_len); +} + +char *rpp2app(char *relativePath) +{ + size_t pfx_len = strlen(playlistDir); + return pfx_path(relativePath, playlistDir, pfx_len); +} + +/* this is actually like strlcpy (OpenBSD), but we don't actually want to + * blindly use it everywhere, only for paths that are OK to truncate (for + * error reporting and such */ +void pathcpy_trunc(char *dest, const char *src) +{ + size_t len = strlen(src); + + if (mpd_unlikely(len > MAXPATHLEN)) + len = MAXPATHLEN; + memcpy(dest, src, len); + dest[len] = '\0'; +} + +char *parentPath(char *path) +{ + static char parentPath[MAXPATHLEN+1]; + char *c; + + pathcpy_trunc(parentPath, path); + c = strrchr(parentPath,'/'); + + if (c == NULL) + parentPath[0] = '\0'; + else { + while ((parentPath <= c) && *(--c) == '/') /* nothing */ + ; + c[1] = '\0'; + } + + return parentPath; +} + +char *sanitizePathDup(char *path) +{ + int len = strlen(path) + 1; + char *ret = xmalloc(len); + char *cp = ret; + + memset(ret, 0, len); + + len = 0; + + /* eliminate more than one '/' in a row, like "///" */ + while (*path) { + while (*path == '/') + path++; + if (*path == '.') { + /* we don't want to have hidden directories, or '.' or + ".." in our path */ + free(ret); + return NULL; + } + while (*path && *path != '/') { + *(cp++) = *(path++); + len++; + } + if (*path == '/') { + *(cp++) = *(path++); + len++; + } + } + + if (len && ret[len - 1] == '/') { + len--; + ret[len] = '\0'; + } + + DEBUG("sanitized: %s\n", ret); + + return xrealloc(ret, len + 1); +} diff --git a/trunk/src/path.h b/trunk/src/path.h new file mode 100644 index 000000000..2357aa25d --- /dev/null +++ b/trunk/src/path.h @@ -0,0 +1,69 @@ +/* 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 PATH_H +#define PATH_H + +#include "../config.h" + +#include <sys/param.h> + +extern const char *musicDir; + +void initPaths(void); + +void finishPaths(void); + +/* utf8ToFsCharset() and fsCharsetToUtf8() + * Each returns a static pointer to a dynamically allocated buffer + * which means: + * - Do not manually free the return value of these functions, it'll be + * automatically freed the next time it is called. + * - They are not reentrant, xstrdup the return value immediately if + * you expect to call one of these functions again, but still need the + * previous result. + * - The static pointer is unique to each function. + */ +char *utf8ToFsCharset(char *str); +char *fsCharsetToUtf8(char *str); + +void setFsCharset(char *charset); + +char *getFsCharset(void); + +/* relative music path to absolute music path + * char * passed is a static variable, so don't free it + */ +char *rmp2amp(char *file); + +/* static char * returned */ +char *rpp2app(char *file); + +/* static char * returned */ +char *parentPath(char *path); + +/* strips extra "///" and leading "/" and trailing "/" */ +char *sanitizePathDup(char *path); + +/* this is actually like strlcpy (OpenBSD), but we don't actually want to + * blindly use it everywhere, only for paths that are OK to truncate (for + * error reporting and such. + * dest must be MAXPATHLEN+1 bytes large (which is standard in mpd) */ +void pathcpy_trunc(char *dest, const char *src); + +#endif diff --git a/trunk/src/pcm_utils.c b/trunk/src/pcm_utils.c new file mode 100644 index 000000000..534095620 --- /dev/null +++ b/trunk/src/pcm_utils.c @@ -0,0 +1,470 @@ +/* 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 "pcm_utils.h" + +#include "mpd_types.h" +#include "log.h" +#include "utils.h" +#include "conf.h" + +#include <string.h> +#include <math.h> +#include <assert.h> + +void pcm_volumeChange(char *buffer, int bufferSize, AudioFormat * format, + int volume) +{ + mpd_sint32 temp32; + mpd_sint8 *buffer8 = (mpd_sint8 *) buffer; + mpd_sint16 *buffer16 = (mpd_sint16 *) buffer; + + if (volume >= 1000) + return; + + if (volume <= 0) { + memset(buffer, 0, bufferSize); + return; + } + + switch (format->bits) { + case 16: + while (bufferSize > 0) { + temp32 = *buffer16; + temp32 *= volume; + temp32 += rand() & 511; + temp32 -= rand() & 511; + temp32 += 500; + temp32 /= 1000; + *buffer16 = temp32 > 32767 ? 32767 : + (temp32 < -32768 ? -32768 : temp32); + buffer16++; + bufferSize -= 2; + } + break; + case 8: + while (bufferSize > 0) { + temp32 = *buffer8; + temp32 *= volume; + temp32 += rand() & 511; + temp32 -= rand() & 511; + temp32 += 500; + temp32 /= 1000; + *buffer8 = temp32 > 127 ? 127 : + (temp32 < -128 ? -128 : temp32); + buffer8++; + bufferSize--; + } + break; + default: + FATAL("%i bits not supported by pcm_volumeChange!\n", + format->bits); + } +} + +static void pcm_add(char *buffer1, char *buffer2, size_t bufferSize1, + size_t bufferSize2, int vol1, int vol2, + AudioFormat * format) +{ + mpd_sint32 temp32; + mpd_sint8 *buffer8_1 = (mpd_sint8 *) buffer1; + mpd_sint8 *buffer8_2 = (mpd_sint8 *) buffer2; + mpd_sint16 *buffer16_1 = (mpd_sint16 *) buffer1; + mpd_sint16 *buffer16_2 = (mpd_sint16 *) buffer2; + + switch (format->bits) { + case 16: + while (bufferSize1 > 0 && bufferSize2 > 0) { + temp32 = + (vol1 * (*buffer16_1) + + vol2 * (*buffer16_2)); + temp32 += rand() & 511; + temp32 -= rand() & 511; + temp32 += 500; + temp32 /= 1000; + *buffer16_1 = + temp32 > 32767 ? 32767 : (temp32 < + -32768 ? -32768 : temp32); + buffer16_1++; + buffer16_2++; + bufferSize1 -= 2; + bufferSize2 -= 2; + } + if (bufferSize2 > 0) + memcpy(buffer16_1, buffer16_2, bufferSize2); + break; + case 8: + while (bufferSize1 > 0 && bufferSize2 > 0) { + temp32 = + (vol1 * (*buffer8_1) + vol2 * (*buffer8_2)); + temp32 += rand() & 511; + temp32 -= rand() & 511; + temp32 += 500; + temp32 /= 1000; + *buffer8_1 = + temp32 > 127 ? 127 : (temp32 < + -128 ? -128 : temp32); + buffer8_1++; + buffer8_2++; + bufferSize1--; + bufferSize2--; + } + if (bufferSize2 > 0) + memcpy(buffer8_1, buffer8_2, bufferSize2); + break; + default: + FATAL("%i bits not supported by pcm_add!\n", format->bits); + } +} + +void pcm_mix(char *buffer1, char *buffer2, size_t bufferSize1, + size_t bufferSize2, AudioFormat * format, float portion1) +{ + int vol1; + float s = sin(M_PI_2 * portion1); + s *= s; + + vol1 = s * 1000 + 0.5; + vol1 = vol1 > 1000 ? 1000 : (vol1 < 0 ? 0 : vol1); + + pcm_add(buffer1, buffer2, bufferSize1, bufferSize2, vol1, 1000 - vol1, + format); +} + +#ifdef HAVE_LIBSAMPLERATE +static int pcm_getSampleRateConverter(void) +{ + const char *conf = getConfigParamValue(CONF_SAMPLERATE_CONVERTER); + long convalgo; + char *test; + size_t len; + + if (!conf) { + convalgo = SRC_SINC_FASTEST; + goto out; + } + + convalgo = strtol(conf, &test, 10); + if (*test == '\0' && src_get_name(convalgo)) + goto out; + + len = strlen(conf); + for (convalgo = 0 ; ; convalgo++) { + test = (char *)src_get_name(convalgo); + if (!test) { + convalgo = SRC_SINC_FASTEST; + break; + } + if (strncasecmp(test, conf, len) == 0) + goto out; + } + + ERROR("unknown samplerate converter \"%s\"\n", conf); +out: + DEBUG("selecting samplerate converter \"%s\"\n", + src_get_name(convalgo)); + + return convalgo; +} +#endif + +#ifdef HAVE_LIBSAMPLERATE +static size_t pcm_convertSampleRate(mpd_sint8 channels, mpd_uint32 inSampleRate, + char *inBuffer, size_t inSize, + mpd_uint32 outSampleRate, char *outBuffer, + size_t outSize, ConvState *convState) +{ + static int convalgo = -1; + SRC_DATA *data = &convState->data; + size_t dataInSize; + size_t dataOutSize; + int error; + + if (convalgo < 0) + convalgo = pcm_getSampleRateConverter(); + + /* (re)set the state/ratio if the in or out format changed */ + if ((channels != convState->lastChannels) || + (inSampleRate != convState->lastInSampleRate) || + (outSampleRate != convState->lastOutSampleRate)) { + convState->error = 0; + convState->lastChannels = channels; + convState->lastInSampleRate = inSampleRate; + convState->lastOutSampleRate = outSampleRate; + + if (convState->state) + convState->state = src_delete(convState->state); + + convState->state = src_new(convalgo, channels, &error); + if (!convState->state) { + ERROR("cannot create new libsamplerate state: %s\n", + src_strerror(error)); + convState->error = 1; + return 0; + } + + data->src_ratio = (double)outSampleRate / (double)inSampleRate; + DEBUG("setting samplerate conversion ratio to %.2lf\n", + data->src_ratio); + src_set_ratio(convState->state, data->src_ratio); + } + + /* there was an error previously, and nothing has changed */ + if (convState->error) + return 0; + + data->input_frames = inSize / 2 / channels; + dataInSize = data->input_frames * sizeof(float) * channels; + if (dataInSize > convState->dataInSize) { + convState->dataInSize = dataInSize; + data->data_in = xrealloc(data->data_in, dataInSize); + } + + data->output_frames = outSize / 2 / channels; + dataOutSize = data->output_frames * sizeof(float) * channels; + if (dataOutSize > convState->dataOutSize) { + convState->dataOutSize = dataOutSize; + data->data_out = xrealloc(data->data_out, dataOutSize); + } + + src_short_to_float_array((short *)inBuffer, data->data_in, + data->input_frames * channels); + + error = src_process(convState->state, data); + if (error) { + ERROR("error processing samples with libsamplerate: %s\n", + src_strerror(error)); + convState->error = 1; + return 0; + } + + src_float_to_short_array(data->data_out, (short *)outBuffer, + data->output_frames_gen * channels); + + return data->output_frames_gen * 2 * channels; +} +#else /* !HAVE_LIBSAMPLERATE */ +/* resampling code blatantly ripped from ESD */ +static size_t pcm_convertSampleRate(mpd_sint8 channels, mpd_uint32 inSampleRate, + char *inBuffer, size_t inSize, + mpd_uint32 outSampleRate, char *outBuffer, + size_t outSize, ConvState *convState) +{ + mpd_uint32 rd_dat = 0; + mpd_uint32 wr_dat = 0; + mpd_sint16 *in = (mpd_sint16 *)inBuffer; + mpd_sint16 *out = (mpd_sint16 *)outBuffer; + mpd_uint32 nlen = outSize / 2; + mpd_sint16 lsample, rsample; + + switch (channels) { + case 1: + while (wr_dat < nlen) { + rd_dat = wr_dat * inSampleRate / outSampleRate; + + lsample = in[rd_dat++]; + + out[wr_dat++] = lsample; + } + break; + case 2: + while (wr_dat < nlen) { + rd_dat = wr_dat * inSampleRate / outSampleRate; + rd_dat &= ~1; + + lsample = in[rd_dat++]; + rsample = in[rd_dat++]; + + out[wr_dat++] = lsample; + out[wr_dat++] = rsample; + } + break; + } + + return outSize; +} +#endif /* !HAVE_LIBSAMPLERATE */ + +static char *pcm_convertChannels(mpd_sint8 channels, char *inBuffer, + size_t inSize, size_t *outSize) +{ + static char *buf; + static size_t len; + char *outBuffer = NULL;; + mpd_sint16 *in; + mpd_sint16 *out; + int inSamples, i; + + switch (channels) { + /* convert from 1 -> 2 channels */ + case 1: + *outSize = (inSize >> 1) << 2; + if (*outSize > len) { + len = *outSize; + buf = xrealloc(buf, len); + } + outBuffer = buf; + + inSamples = inSize >> 1; + in = (mpd_sint16 *)inBuffer; + out = (mpd_sint16 *)outBuffer; + for (i = 0; i < inSamples; i++) { + *out++ = *in; + *out++ = *in++; + } + + break; + /* convert from 2 -> 1 channels */ + case 2: + *outSize = inSize >> 1; + if (*outSize > len) { + len = *outSize; + buf = xrealloc(buf, len); + } + outBuffer = buf; + + inSamples = inSize >> 2; + in = (mpd_sint16 *)inBuffer; + out = (mpd_sint16 *)outBuffer; + for (i = 0; i < inSamples; i++) { + *out = (*in++) / 2; + *out++ += (*in++) / 2; + } + + break; + default: + ERROR("only 1 or 2 channels are supported for conversion!\n"); + } + + return outBuffer; +} + +static char *pcm_convertTo16bit(mpd_sint8 bits, char *inBuffer, size_t inSize, + size_t *outSize) +{ + static char *buf; + static size_t len; + char *outBuffer = NULL; + mpd_sint8 *in; + mpd_sint16 *out; + int i; + + switch (bits) { + case 8: + *outSize = inSize << 1; + if (*outSize > len) { + len = *outSize; + buf = xrealloc(buf, len); + } + outBuffer = buf; + + in = (mpd_sint8 *)inBuffer; + out = (mpd_sint16 *)outBuffer; + for (i = 0; i < inSize; i++) + *out++ = (*in++) << 8; + + break; + case 16: + *outSize = inSize; + outBuffer = inBuffer; + break; + case 24: + /* put dithering code from mp3_decode here */ + default: + ERROR("only 8 or 16 bits are supported for conversion!\n"); + } + + return outBuffer; +} + +/* outFormat bits must be 16 and channels must be 1 or 2! */ +size_t pcm_convertAudioFormat(AudioFormat * inFormat, char *inBuffer, + size_t inSize, AudioFormat * outFormat, + char *outBuffer, ConvState *convState) +{ + char *buf; + size_t len; + size_t outSize = pcm_sizeOfConvBuffer(inFormat, inSize, outFormat); + + assert(outFormat->bits == 16); + assert(outFormat->channels == 2 || outFormat->channels == 1); + + /* everything else supports 16 bit only, so convert to that first */ + buf = pcm_convertTo16bit(inFormat->bits, inBuffer, inSize, &len); + if (!buf) + exit(EXIT_FAILURE); + + if (inFormat->channels != outFormat->channels) { + buf = pcm_convertChannels(inFormat->channels, buf, len, &len); + if (!buf) + exit(EXIT_FAILURE); + } + + if (inFormat->sampleRate == outFormat->sampleRate) { + assert(outSize >= len); + memcpy(outBuffer, buf, len); + } else { + len = pcm_convertSampleRate(outFormat->channels, + inFormat->sampleRate, buf, len, + outFormat->sampleRate, outBuffer, + outSize, convState); + if (len == 0) + exit(EXIT_FAILURE); + } + + return len; +} + +size_t pcm_sizeOfConvBuffer(AudioFormat * inFormat, size_t inSize, + AudioFormat * outFormat) +{ + const double ratio = (double)outFormat->sampleRate / + (double)inFormat->sampleRate; + const int shift = 2 * outFormat->channels; + size_t outSize = inSize; + + switch (inFormat->bits) { + case 8: + outSize <<= 1; + break; + case 16: + break; + default: + FATAL("only 8 or 16 bits are supported for conversion!\n"); + } + + if (inFormat->channels != outFormat->channels) { + switch (inFormat->channels) { + case 1: + outSize = (outSize >> 1) << 2; + break; + case 2: + outSize >>= 1; + break; + default: + FATAL("only 1 or 2 channels are supported " + "for conversion!\n"); + } + } + + outSize /= shift; + outSize = floor(0.5 + (double)outSize * ratio); + outSize *= shift; + + return outSize; +} diff --git a/trunk/src/pcm_utils.h b/trunk/src/pcm_utils.h new file mode 100644 index 000000000..2c6610a75 --- /dev/null +++ b/trunk/src/pcm_utils.h @@ -0,0 +1,57 @@ +/* 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 PCM_UTILS_H +#define PCM_UTILS_H + +#include "../config.h" + +#include "audio.h" + +#include <stdlib.h> + +#ifdef HAVE_LIBSAMPLERATE +#include <samplerate.h> +#endif + +typedef struct _ConvState { +#ifdef HAVE_LIBSAMPLERATE + SRC_STATE *state; + SRC_DATA data; + size_t dataInSize; + size_t dataOutSize; + mpd_sint8 lastChannels; + mpd_sint32 lastInSampleRate; + mpd_sint32 lastOutSampleRate; + int error; +#endif +} ConvState; + +void pcm_volumeChange(char *buffer, int bufferSize, AudioFormat * format, + int volume); + +void pcm_mix(char *buffer1, char *buffer2, size_t bufferSize1, + size_t bufferSize2, AudioFormat * format, float portion1); + +size_t pcm_convertAudioFormat(AudioFormat * inFormat, char *inBuffer, + size_t inSize, AudioFormat * outFormat, + char *outBuffer, ConvState *convState); + +size_t pcm_sizeOfConvBuffer(AudioFormat * inFormat, size_t inSize, + AudioFormat * outFormat); +#endif diff --git a/trunk/src/permission.c b/trunk/src/permission.c new file mode 100644 index 000000000..3d597052c --- /dev/null +++ b/trunk/src/permission.c @@ -0,0 +1,140 @@ +/* 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 "permission.h" + +#include "conf.h" +#include "list.h" +#include "log.h" +#include "utils.h" + +#include <string.h> + +#define PERMISSION_PASSWORD_CHAR "@" +#define PERMISSION_SEPERATOR "," + +#define PERMISSION_READ_STRING "read" +#define PERMISSION_ADD_STRING "add" +#define PERMISSION_CONTROL_STRING "control" +#define PERMISSION_ADMIN_STRING "admin" + +static List *permission_passwords; + +static int permission_default; + +static int parsePermissions(char *string) +{ + int permission = 0; + char *temp; + char *tok; + + if (!string) + return 0; + + temp = strtok_r(string, PERMISSION_SEPERATOR, &tok); + while (temp) { + if (strcmp(temp, PERMISSION_READ_STRING) == 0) { + permission |= PERMISSION_READ; + } else if (strcmp(temp, PERMISSION_ADD_STRING) == 0) { + permission |= PERMISSION_ADD; + } else if (strcmp(temp, PERMISSION_CONTROL_STRING) == 0) { + permission |= PERMISSION_CONTROL; + } else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) { + permission |= PERMISSION_ADMIN; + } else { + FATAL("unknown permission \"%s\"\n", temp); + } + + temp = strtok_r(NULL, PERMISSION_SEPERATOR, &tok); + } + + return permission; +} + +void initPermissions(void) +{ + char *temp; + char *cp2; + char *password; + int *permission; + ConfigParam *param; + + permission_passwords = makeList(free, 1); + + permission_default = PERMISSION_READ | PERMISSION_ADD | + PERMISSION_CONTROL | PERMISSION_ADMIN; + + param = getNextConfigParam(CONF_PASSWORD, NULL); + + if (param) { + permission_default = 0; + + do { + if (!strstr(param->value, PERMISSION_PASSWORD_CHAR)) { + FATAL("\"%s\" not found in password string " + "\"%s\", line %i\n", + PERMISSION_PASSWORD_CHAR, + param->value, param->line); + } + + if (!(temp = strtok_r(param->value, + PERMISSION_PASSWORD_CHAR, + &cp2))) { + FATAL("something weird just happened in permission.c\n"); + } + + password = temp; + + permission = xmalloc(sizeof(int)); + *permission = + parsePermissions(strtok_r(NULL, "", &cp2)); + + insertInList(permission_passwords, password, + permission); + } while ((param = getNextConfigParam(CONF_PASSWORD, param))); + } + + param = getConfigParam(CONF_DEFAULT_PERMS); + + if (param) + permission_default = parsePermissions(param->value); + + sortList(permission_passwords); +} + +int getPermissionFromPassword(char *password, int *permission) +{ + void *foundPermission; + + if (findInList(permission_passwords, password, &foundPermission)) { + *permission = *((int *)foundPermission); + return 0; + } + + return -1; +} + +void finishPermissions(void) +{ + freeList(permission_passwords); +} + +int getDefaultPermissions(void) +{ + return permission_default; +} diff --git a/trunk/src/permission.h b/trunk/src/permission.h new file mode 100644 index 000000000..bd4257080 --- /dev/null +++ b/trunk/src/permission.h @@ -0,0 +1,39 @@ +/* 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 PERMISSION_H +#define PERMISSION_H + +#include "../config.h" + +#define PERMISSION_NONE 0 +#define PERMISSION_READ 1 +#define PERMISSION_ADD 2 +#define PERMISSION_CONTROL 4 +#define PERMISSION_ADMIN 8 + + +int getPermissionFromPassword(char *password, int *permission); + +void finishPermissions(void); + +int getDefaultPermissions(void); + +void initPermissions(void); + +#endif diff --git a/trunk/src/player.c b/trunk/src/player.c new file mode 100644 index 000000000..7c92d088e --- /dev/null +++ b/trunk/src/player.c @@ -0,0 +1,530 @@ +/* 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 "sig_handlers.h" + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +volatile int player_pid = 0; + +void clearPlayerPid(void) +{ + player_pid = 0; +} + +static void resetPlayerMetadata(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (pc->metadataState == PLAYER_METADATA_STATE_READ) { + pc->metadataState = PLAYER_METADATA_STATE_WRITE; + } +} + +static void resetPlayer(void) +{ + int pid; + + clearPlayerPid(); + getPlayerData()->playerControl.stop = 0; + getPlayerData()->playerControl.play = 0; + getPlayerData()->playerControl.pause = 0; + getPlayerData()->playerControl.lockQueue = 0; + getPlayerData()->playerControl.unlockQueue = 0; + getPlayerData()->playerControl.state = PLAYER_STATE_STOP; + getPlayerData()->playerControl.queueState = PLAYER_QUEUE_UNLOCKED; + getPlayerData()->playerControl.seek = 0; + getPlayerData()->playerControl.metadataState = + PLAYER_METADATA_STATE_WRITE; + pid = getPlayerData()->playerControl.decode_pid; + if (pid > 0) + kill(pid, SIGTERM); + getPlayerData()->playerControl.decode_pid = 0; +} + +void player_sigChldHandler(int pid, int status) +{ + if (player_pid == pid) + { + DEBUG("SIGCHLD caused by player process\n"); + if (WIFSIGNALED(status) && + WTERMSIG(status) != SIGTERM && + WTERMSIG(status) != SIGINT) + { + ERROR("player process died from signal: %i\n", + WTERMSIG(status)); + } + resetPlayer(); + } + else if (pid == getPlayerData()->playerControl.decode_pid && + player_pid <= 0) + { + if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) + { + ERROR("(caught by master parent) " + "decode process died from a " + "non-TERM signal: %i\n", WTERMSIG(status)); + } + getPlayerData()->playerControl.decode_pid = 0; + } +} + +int playerInit(void) +{ + blockSignals(); + player_pid = fork(); + if (player_pid==0) + { + clock_t start = clock(); + + PlayerControl *pc = &(getPlayerData()->playerControl); + + unblockSignals(); + + setSigHandlersForDecoder(); + + closeAllListenSockets(); + freeAllInterfaces(); + finishPlaylist(); + closeMp3Directory(); + finishPermissions(); + finishCommands(); + finishVolume(); + + DEBUG("took %f to init player\n", + (float)(clock()-start)/CLOCKS_PER_SEC); + + while (1) { + if (pc->play) + decode(); + 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; + kill(getppid(), SIGUSR1); + } 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 if (pc->cycleLogFiles) { + cycle_log_files(); + pc->cycleLogFiles = 0; + } else + my_usleep(10000); + } + + exit(EXIT_SUCCESS); + } + else if (player_pid < 0) + { + unblockSignals(); + ERROR("player Problems fork()'ing\n"); + player_pid = 0; + return -1; + } + + unblockSignals(); + + return 0; +} + +int playerPlay(int fd, Song * song) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (playerStop(fd) < 0) + return -1; + + if (song->tag) + pc->fileTime = song->tag->time; + else + pc->fileTime = 0; + + copyMpdTagToMetadataChunk(song->tag, &(pc->fileMetadataChunk)); + + pathcpy_trunc(pc->utf8url, getSongUrl(song)); + + pc->play = 1; + if (player_pid == 0 && playerInit() < 0) { + pc->play = 0; + return -1; + } + + resetPlayerMetadata(); + while (player_pid > 0 && pc->play) + my_usleep(1000); + + return 0; +} + +int playerStop(int fd) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid > 0 && pc->state != PLAYER_STATE_STOP) { + pc->stop = 1; + while (player_pid > 0 && pc->stop) + my_usleep(1000); + } + + pc->queueState = PLAYER_QUEUE_BLANK; + playerQueueUnlock(); + + return 0; +} + +void playerKill(void) +{ + int pid; + /*PlayerControl * pc = &(getPlayerData()->playerControl); + + playerStop(stderr); + playerCloseAudio(stderr); + if(player_pid>0 && pc->closeAudio) sleep(1); */ + + pid = player_pid; + if (pid > 0) + kill(pid, SIGTERM); +} + +int playerPause(int fd) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid > 0 && pc->state != PLAYER_STATE_STOP) { + pc->pause = 1; + while (player_pid > 0 && pc->pause) + my_usleep(1000); + } + + return 0; +} + +int playerSetPause(int fd, int pause) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid <= 0) + return 0; + + switch (pc->state) { + case PLAYER_STATE_PLAY: + if (pause) + playerPause(fd); + break; + case PLAYER_STATE_PAUSE: + if (!pause) + playerPause(fd); + break; + } + + return 0; +} + +int getPlayerElapsedTime(void) +{ + return (int)(getPlayerData()->playerControl.elapsedTime + 0.5); +} + +unsigned long getPlayerBitRate(void) +{ + return getPlayerData()->playerControl.bitRate; +} + +int getPlayerTotalTime(void) +{ + return (int)(getPlayerData()->playerControl.totalTime + 0.5); +} + +int getPlayerState(void) +{ + return getPlayerData()->playerControl.state; +} + +void clearPlayerError(void) +{ + getPlayerData()->playerControl.error = 0; +} + +int getPlayerError(void) +{ + return getPlayerData()->playerControl.error; +} + +char *getPlayerErrorStr(void) +{ + static char *error; + int errorlen = MAXPATHLEN + 1024; + PlayerControl *pc = &(getPlayerData()->playerControl); + + error = xrealloc(error, errorlen); + error[0] = '\0'; + + switch (pc->error) { + case PLAYER_ERROR_FILENOTFOUND: + snprintf(error, errorlen, + "file \"%s\" does not exist or is inaccessible", + pc->erroredUrl); + break; + case PLAYER_ERROR_FILE: + snprintf(error, errorlen, "problems decoding \"%s\"", + pc->erroredUrl); + break; + case PLAYER_ERROR_AUDIO: + snprintf(error, errorlen, "problems opening audio device"); + break; + case PLAYER_ERROR_SYSTEM: + snprintf(error, errorlen, "system error occured"); + break; + case PLAYER_ERROR_UNKTYPE: + snprintf(error, errorlen, "file type of \"%s\" is unknown", + pc->erroredUrl); + default: + break; + } + + errorlen = strlen(error); + error = xrealloc(error, errorlen + 1); + + if (errorlen) + return error; + + return NULL; +} + +void playerCloseAudio(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid > 0) { + if (playerStop(STDERR_FILENO) < 0) + return; + pc->closeAudio = 1; + } +} + +int queueSong(Song * song) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (pc->queueState == PLAYER_QUEUE_BLANK) { + pathcpy_trunc(pc->utf8url, getSongUrl(song)); + + if (song->tag) + pc->fileTime = song->tag->time; + else + pc->fileTime = 0; + + copyMpdTagToMetadataChunk(song->tag, &(pc->fileMetadataChunk)); + + pc->queueState = PLAYER_QUEUE_FULL; + return 0; + } + + return -1; +} + +int getPlayerQueueState(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->queueState; +} + +void setQueueState(int queueState) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + pc->queueState = queueState; +} + +void playerQueueLock(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid > 0 && pc->queueLockState == PLAYER_QUEUE_UNLOCKED) { + pc->lockQueue = 1; + while (player_pid > 0 && pc->lockQueue) + my_usleep(1000); + } +} + +void playerQueueUnlock(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid > 0 && pc->queueLockState == PLAYER_QUEUE_LOCKED) { + pc->unlockQueue = 1; + while (player_pid > 0 && pc->unlockQueue) + my_usleep(1000); + } +} + +int playerSeek(int fd, Song * song, float time) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (pc->state == PLAYER_STATE_STOP) { + commandError(fd, ACK_ERROR_PLAYER_SYNC, + "player not currently playing"); + return -1; + } + + if (strcmp(pc->utf8url, getSongUrl(song)) != 0) { + if (song->tag) + pc->fileTime = song->tag->time; + else + pc->fileTime = 0; + + copyMpdTagToMetadataChunk(song->tag, &(pc->fileMetadataChunk)); + + pathcpy_trunc(pc->utf8url, getSongUrl(song)); + } + + if (pc->error == PLAYER_ERROR_NOERROR) { + resetPlayerMetadata(); + pc->seekWhere = time; + pc->seek = 1; + while (player_pid > 0 && pc->seek) + my_usleep(1000); + } + + return 0; +} + +float getPlayerCrossFade(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->crossFade; +} + +void setPlayerCrossFade(float crossFadeInSeconds) +{ + PlayerControl *pc; + if (crossFadeInSeconds < 0) + crossFadeInSeconds = 0; + + pc = &(getPlayerData()->playerControl); + + pc->crossFade = crossFadeInSeconds; +} + +void setPlayerSoftwareVolume(int volume) +{ + PlayerControl *pc; + volume = (volume > 1000) ? 1000 : (volume < 0 ? 0 : volume); + + pc = &(getPlayerData()->playerControl); + + pc->softwareVolume = volume; +} + +double getPlayerTotalPlayTime(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->totalPlayTime; +} + +unsigned int getPlayerSampleRate(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->sampleRate; +} + +int getPlayerBits(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->bits; +} + +int getPlayerChannels(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->channels; +} + +void playerCycleLogFiles(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + DecoderControl *dc = &(getPlayerData()->decoderControl); + + pc->cycleLogFiles = 1; + dc->cycleLogFiles = 1; +} + +/* this actually creates a dupe of the current metadata */ +Song *playerCurrentDecodeSong(void) +{ + static Song *song; + static MetadataChunk *prev; + Song *ret = NULL; + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (pc->metadataState == PLAYER_METADATA_STATE_READ) { + DEBUG("playerCurrentDecodeSong: caught new metadata!\n"); + if (prev) + free(prev); + prev = xmalloc(sizeof(MetadataChunk)); + memcpy(prev, &(pc->metadataChunk), sizeof(MetadataChunk)); + if (song) + freeJustSong(song); + song = newNullSong(); + song->url = xstrdup(pc->currentUrl); + song->tag = metadataChunkToMpdTagDup(prev); + ret = song; + resetPlayerMetadata(); + } + + return ret; +} diff --git a/trunk/src/player.h b/trunk/src/player.h new file mode 100644 index 000000000..b62fab2ea --- /dev/null +++ b/trunk/src/player.h @@ -0,0 +1,154 @@ +/* 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 "../config.h" + +#include "decode.h" +#include "mpd_types.h" +#include "song.h" +#include "metadataChunk.h" + +#include <stdio.h> +#include <sys/param.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 + +#define PLAYER_METADATA_STATE_READ 1 +#define PLAYER_METADATA_STATE_WRITE 2 + +typedef struct _PlayerControl { + 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; + char utf8url[MAXPATHLEN + 1]; + char currentUrl[MAXPATHLEN + 1]; + char erroredUrl[MAXPATHLEN + 1]; + 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; + volatile int decode_pid; + volatile mpd_sint8 cycleLogFiles; + volatile mpd_sint8 metadataState; + MetadataChunk metadataChunk; + MetadataChunk fileMetadataChunk; +} PlayerControl; + +void clearPlayerPid(void); + +void player_sigChldHandler(int pid, int status); + +int playerPlay(int fd, Song * song); + +int playerSetPause(int fd, int pause); + +int playerPause(int fd); + +int playerStop(int fd); + +void playerCloseAudio(void); + +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 playerInit(void); + +int queueSong(Song * song); + +int getPlayerQueueState(void); + +void setQueueState(int queueState); + +void playerQueueLock(void); + +void playerQueueUnlock(void); + +int playerSeek(int fd, Song * song, float 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); + +void playerCycleLogFiles(void); + +Song *playerCurrentDecodeSong(void); + +#endif diff --git a/trunk/src/playerData.c b/trunk/src/playerData.c new file mode 100644 index 000000000..30ff6d6d6 --- /dev/null +++ b/trunk/src/playerData.c @@ -0,0 +1,162 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "playerData.h" +#include "conf.h" +#include "log.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int buffered_before_play; +int buffered_chunks; + +#define DEFAULT_BUFFER_SIZE 2048 +#define DEFAULT_BUFFER_BEFORE_PLAY 10 + +static PlayerData *playerData_pd; + +void initPlayerData(void) +{ + float perc = DEFAULT_BUFFER_BEFORE_PLAY; + char *test; + int shmid; + int crossfade = 0; + size_t bufferSize = DEFAULT_BUFFER_SIZE; + size_t allocationSize; + OutputBuffer *buffer; + ConfigParam *param; + size_t device_array_size = audio_device_count() * sizeof(mpd_sint8); + + param = getConfigParam(CONF_AUDIO_BUFFER_SIZE); + + if (param) { + bufferSize = strtol(param->value, &test, 10); + if (*test != '\0' || bufferSize <= 0) { + FATAL("buffer size \"%s\" is not a positive integer, " + "line %i\n", param->value, param->line); + } + } + + bufferSize *= 1024; + + buffered_chunks = bufferSize / CHUNK_SIZE; + + if (buffered_chunks >= 1 << 15) { + FATAL("buffer size \"%li\" is too big\n", (long)bufferSize); + } + + param = getConfigParam(CONF_BUFFER_BEFORE_PLAY); + + if (param) { + perc = strtod(param->value, &test); + if (*test != '%' || perc < 0 || perc > 100) { + FATAL("buffered before play \"%s\" is not a positive " + "percentage and less than 100 percent, line %i" + "\n", param->value, param->line); + } + } + + buffered_before_play = (perc / 100) * buffered_chunks; + if (buffered_before_play > buffered_chunks) { + buffered_before_play = buffered_chunks; + } else if (buffered_before_play < 0) + buffered_before_play = 0; + + allocationSize = buffered_chunks * CHUNK_SIZE; /*actual buffer */ + allocationSize += buffered_chunks * sizeof(float); /*for times */ + allocationSize += buffered_chunks * sizeof(mpd_sint16); /*for chunkSize */ + allocationSize += buffered_chunks * sizeof(mpd_sint16); /*for bitRate */ + allocationSize += buffered_chunks * sizeof(mpd_sint8); /*for metaChunk */ + allocationSize += sizeof(PlayerData); /*for playerData struct */ + + /* for audioDeviceStates[] */ + allocationSize += device_array_size; + + if ((shmid = shmget(IPC_PRIVATE, allocationSize, IPC_CREAT | 0600)) < 0) + FATAL("problems shmget'ing\n"); + if (!(playerData_pd = shmat(shmid, NULL, 0))) + FATAL("problems shmat'ing\n"); + if (shmctl(shmid, IPC_RMID, NULL) < 0) + FATAL("problems shmctl'ing\n"); + + playerData_pd->audioDeviceStates = (mpd_uint8 *)playerData_pd + + allocationSize - device_array_size; + buffer = &(playerData_pd->buffer); + + memset(&buffer->convState, 0, sizeof(ConvState)); + buffer->chunks = ((char *)playerData_pd) + sizeof(PlayerData); + buffer->chunkSize = (mpd_uint16 *) (((char *)buffer->chunks) + + buffered_chunks * CHUNK_SIZE); + buffer->bitRate = (mpd_uint16 *) (((char *)buffer->chunkSize) + + buffered_chunks * sizeof(mpd_sint16)); + buffer->metaChunk = (mpd_sint8 *) (((char *)buffer->bitRate) + + buffered_chunks * + sizeof(mpd_sint16)); + buffer->times = + (float *)(((char *)buffer->metaChunk) + + buffered_chunks * sizeof(mpd_sint8)); + buffer->acceptMetadata = 0; + + playerData_pd->playerControl.stop = 0; + playerData_pd->playerControl.pause = 0; + playerData_pd->playerControl.play = 0; + playerData_pd->playerControl.error = PLAYER_ERROR_NOERROR; + playerData_pd->playerControl.lockQueue = 0; + playerData_pd->playerControl.unlockQueue = 0; + playerData_pd->playerControl.state = PLAYER_STATE_STOP; + playerData_pd->playerControl.queueState = PLAYER_QUEUE_BLANK; + playerData_pd->playerControl.queueLockState = PLAYER_QUEUE_UNLOCKED; + playerData_pd->playerControl.seek = 0; + playerData_pd->playerControl.closeAudio = 0; + memset(playerData_pd->playerControl.utf8url, 0, MAXPATHLEN + 1); + memset(playerData_pd->playerControl.erroredUrl, 0, MAXPATHLEN + 1); + memset(playerData_pd->playerControl.currentUrl, 0, MAXPATHLEN + 1); + playerData_pd->playerControl.crossFade = crossfade; + playerData_pd->playerControl.softwareVolume = 1000; + playerData_pd->playerControl.totalPlayTime = 0; + playerData_pd->playerControl.decode_pid = 0; + playerData_pd->playerControl.metadataState = + PLAYER_METADATA_STATE_WRITE; + + playerData_pd->decoderControl.stop = 0; + playerData_pd->decoderControl.start = 0; + playerData_pd->decoderControl.state = DECODE_STATE_STOP; + playerData_pd->decoderControl.seek = 0; + playerData_pd->decoderControl.error = DECODE_ERROR_NOERROR; + memset(playerData_pd->decoderControl.utf8url, 0, MAXPATHLEN + 1); +} + +PlayerData *getPlayerData(void) +{ + return playerData_pd; +} + +void freePlayerData(void) +{ + /* We don't want to release this memory until we know our player and + * decoder have exited. Otherwise, their signal handlers will want to + * access playerData_pd and we need to keep it available for them */ + waitpid(-1, NULL, 0); + shmdt(playerData_pd); +} diff --git a/trunk/src/playerData.h b/trunk/src/playerData.h new file mode 100644 index 000000000..00e4040be --- /dev/null +++ b/trunk/src/playerData.h @@ -0,0 +1,49 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef PLAYER_DATA_H +#define PLAYER_DATA_H + +#include "../config.h" + +#include "audio.h" +#include "player.h" +#include "decode.h" +#include "mpd_types.h" +#include "outputBuffer.h" + +/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ +#define CHUNK_SIZE 1020 + +extern int buffered_before_play; +extern int buffered_chunks; + +typedef struct _PlayerData { + OutputBuffer buffer; + PlayerControl playerControl; + DecoderControl decoderControl; + mpd_uint8 *audioDeviceStates; +} PlayerData; + +void initPlayerData(void); + +PlayerData *getPlayerData(void); + +void freePlayerData(void); + +#endif diff --git a/trunk/src/playlist.c b/trunk/src/playlist.c new file mode 100644 index 000000000..d8f2c6b65 --- /dev/null +++ b/trunk/src/playlist.c @@ -0,0 +1,1499 @@ +/* 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 "playlist.h" +#include "player.h" +#include "command.h" +#include "ls.h" +#include "tag.h" +#include "conf.h" +#include "directory.h" +#include "log.h" +#include "path.h" +#include "utils.h" +#include "sig_handlers.h" +#include "state_file.h" +#include "storedPlaylist.h" + +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> + +#define PLAYLIST_STATE_STOP 0 +#define PLAYLIST_STATE_PLAY 1 + +#define PLAYLIST_PREV_UNLESS_ELAPSED 10 + +#define PLAYLIST_STATE_FILE_STATE "state: " +#define PLAYLIST_STATE_FILE_RANDOM "random: " +#define PLAYLIST_STATE_FILE_REPEAT "repeat: " +#define PLAYLIST_STATE_FILE_CURRENT "current: " +#define PLAYLIST_STATE_FILE_TIME "time: " +#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " +#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" +#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" + +#define PLAYLIST_STATE_FILE_STATE_PLAY "play" +#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause" +#define PLAYLIST_STATE_FILE_STATE_STOP "stop" + +#define PLAYLIST_BUFFER_SIZE 2*MAXPATHLEN + +#define PLAYLIST_HASH_MULT 4 + +#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 int playlist_max_length = DEFAULT_PLAYLIST_MAX_LENGTH; +static int playlist_stopOnError; +static int playlist_errorCount; +static int playlist_queueError; +static int playlist_noGoToNext; + +int playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; + +static void swapOrder(int a, int b); +static int playPlaylistOrderNumber(int fd, int orderNum); +static void randomizeOrder(int start, int end); + +static void incrPlaylistVersion(void) +{ + static unsigned long max = ((mpd_uint32) 1 << 31) - 1; + playlist.version++; + if (playlist.version >= max) { + int i; + + for (i = 0; i < playlist.length; i++) { + playlist.songMod[i] = 0; + } + + playlist.version = 1; + } +} + +void playlistVersionChange(void) +{ + int i = 0; + + for (i = 0; i < playlist.length; i++) { + playlist.songMod[i] = playlist.version; + } + + incrPlaylistVersion(); +} + +static void incrPlaylistCurrent(void) +{ + if (playlist.current < 0) + return; + + if (playlist.current >= playlist.length - 1) { + if (playlist.repeat) + playlist.current = 0; + else + playlist.current = -1; + } else + playlist.current++; +} + +void initPlaylist(void) +{ + char *test; + int i; + ConfigParam *param; + + playlist.length = 0; + playlist.repeat = 0; + playlist.version = 1; + playlist.random = 0; + playlist.queued = -1; + playlist.current = -1; + + param = getConfigParam(CONF_MAX_PLAYLIST_LENGTH); + + if (param) { + playlist_max_length = strtol(param->value, &test, 10); + if (*test != '\0') { + FATAL("max playlist length \"%s\" is not an integer, " + "line %i\n", param->value, param->line); + } + } + + playlist_saveAbsolutePaths = getBoolConfigParam(CONF_SAVE_ABSOLUTE_PATHS); + if (playlist_saveAbsolutePaths == -1) playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; + else if (playlist_saveAbsolutePaths < 0) exit(EXIT_FAILURE); + + playlist.songs = xmalloc(sizeof(Song *) * playlist_max_length); + playlist.songMod = xmalloc(sizeof(mpd_uint32) * playlist_max_length); + playlist.order = xmalloc(sizeof(int) * playlist_max_length); + playlist.idToPosition = xmalloc(sizeof(int) * playlist_max_length * + PLAYLIST_HASH_MULT); + playlist.positionToId = xmalloc(sizeof(int) * playlist_max_length); + + memset(playlist.songs, 0, sizeof(char *) * playlist_max_length); + + srandom(time(NULL)); + + for (i = 0; i < playlist_max_length * PLAYLIST_HASH_MULT; i++) { + playlist.idToPosition[i] = -1; + } +} + +static int getNextId(void) +{ + static int cur = -1; + + do { + cur++; + if (cur >= playlist_max_length * PLAYLIST_HASH_MULT) { + cur = 0; + } + } while (playlist.idToPosition[cur] != -1); + + return cur; +} + +void finishPlaylist(void) +{ + int i; + for (i = 0; i < playlist.length; i++) { + if (playlist.songs[i]->type == SONG_TYPE_URL) { + freeJustSong(playlist.songs[i]); + } + } + + playlist.length = 0; + + free(playlist.songs); + playlist.songs = NULL; + free(playlist.songMod); + playlist.songMod = NULL; + free(playlist.order); + playlist.order = NULL; + free(playlist.idToPosition); + playlist.idToPosition = NULL; + free(playlist.positionToId); + playlist.positionToId = NULL; +} + +int clearPlaylist(int fd) +{ + int i; + + if (stopPlaylist(fd) < 0) + return -1; + + for (i = 0; i < playlist.length; i++) { + if (playlist.songs[i]->type == SONG_TYPE_URL) { + freeJustSong(playlist.songs[i]); + } + playlist.idToPosition[playlist.positionToId[i]] = -1; + playlist.songs[i] = NULL; + } + playlist.length = 0; + playlist.current = -1; + + incrPlaylistVersion(); + + return 0; +} + +int clearStoredPlaylist(int fd, char *utf8file) +{ + removeAllFromStoredPlaylistByPath(fd, utf8file); + return 0; +} + +int showPlaylist(int fd) +{ + int i; + + for (i = 0; i < playlist.length; i++) { + fdprintf(fd, "%i:%s\n", i, getSongUrl(playlist.songs[i])); + } + + return 0; +} + +void savePlaylistState(FILE *fp) +{ + fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE); + switch (playlist_state) { + case PLAYLIST_STATE_PLAY: + switch (getPlayerState()) { + case PLAYER_STATE_PAUSE: + fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE); + break; + default: + fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY); + } + fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT, + playlist.order[playlist.current]); + fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME, + getPlayerElapsedTime()); + break; + default: + fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP); + break; + } + 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())); + fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN); + fflush(fp); + showPlaylist(fileno(fp)); + fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END); +} + +static void loadPlaylistFromStateFile(FILE *fp, char *buffer, + int state, int current, int time) +{ + char *temp; + int song; + + if (!myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) + state_file_fatal(); + while (strcmp(buffer, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + song = atoi(strtok(buffer, ":")); + if (!(temp = strtok(NULL, ""))) + state_file_fatal(); + if (!addToPlaylist(STDERR_FILENO, temp, 0) && 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) { + seekSongInPlaylist(STDERR_FILENO, + playlist.length - 1, time); + } + } + if (!myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) + state_file_fatal(); + } +} + +void readPlaylistState(FILE *fp) +{ + int current = -1; + int time = 0; + int state = PLAYER_STATE_STOP; + char buffer[PLAYLIST_BUFFER_SIZE]; + + while (myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) { + if (strncmp(buffer, PLAYLIST_STATE_FILE_STATE, + strlen(PLAYLIST_STATE_FILE_STATE)) == 0) { + if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]), + PLAYLIST_STATE_FILE_STATE_PLAY) == 0) { + state = PLAYER_STATE_PLAY; + } else + if (strcmp + (&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]), + PLAYLIST_STATE_FILE_STATE_PAUSE) + == 0) { + state = PLAYER_STATE_PAUSE; + } + } else if (strncmp(buffer, PLAYLIST_STATE_FILE_TIME, + strlen(PLAYLIST_STATE_FILE_TIME)) == 0) { + time = + atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)])); + } else + if (strncmp + (buffer, PLAYLIST_STATE_FILE_REPEAT, + strlen(PLAYLIST_STATE_FILE_REPEAT)) == 0) { + if (strcmp + (&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + "1") == 0) { + setPlaylistRepeatStatus(STDERR_FILENO, 1); + } else + setPlaylistRepeatStatus(STDERR_FILENO, 0); + } else + if (strncmp + (buffer, PLAYLIST_STATE_FILE_CROSSFADE, + strlen(PLAYLIST_STATE_FILE_CROSSFADE)) == 0) { + setPlayerCrossFade(atoi + (& + (buffer + [strlen + (PLAYLIST_STATE_FILE_CROSSFADE)]))); + } else + if (strncmp + (buffer, PLAYLIST_STATE_FILE_RANDOM, + strlen(PLAYLIST_STATE_FILE_RANDOM)) == 0) { + if (strcmp + (& + (buffer + [strlen(PLAYLIST_STATE_FILE_RANDOM)]), + "1") == 0) { + setPlaylistRandomStatus(STDERR_FILENO, 1); + } else + setPlaylistRandomStatus(STDERR_FILENO, 0); + } else if (strncmp(buffer, PLAYLIST_STATE_FILE_CURRENT, + strlen(PLAYLIST_STATE_FILE_CURRENT)) + == 0) { + if (strlen(buffer) == + strlen(PLAYLIST_STATE_FILE_CURRENT)) + state_file_fatal(); + current = atoi(&(buffer + [strlen + (PLAYLIST_STATE_FILE_CURRENT)])); + } else + if (strncmp + (buffer, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN, + strlen(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN) + ) == 0) { + if (state == PLAYER_STATE_STOP) + current = -1; + loadPlaylistFromStateFile(fp, buffer, state, + current, time); + } + } +} + +static void printPlaylistSongInfo(int fd, int song) +{ + printSongInfo(fd, playlist.songs[song]); + fdprintf(fd, "Pos: %i\nId: %i\n", song, playlist.positionToId[song]); +} + +int playlistChanges(int fd, mpd_uint32 version) +{ + int i; + + for (i = 0; i < playlist.length; i++) { + if (version > playlist.version || + playlist.songMod[i] >= version || + playlist.songMod[i] == 0) { + printPlaylistSongInfo(fd, i); + } + } + + return 0; +} + +int playlistChangesPosId(int fd, mpd_uint32 version) +{ + int i; + + for (i = 0; i < playlist.length; i++) { + if (version > playlist.version || + playlist.songMod[i] >= version || + playlist.songMod[i] == 0) { + fdprintf(fd, "cpos: %i\nId: %i\n", + i, playlist.positionToId[i]); + } + } + + return 0; +} + +int playlistInfo(int fd, int song) +{ + int i; + int begin = 0; + int end = playlist.length; + + if (song >= 0) { + begin = song; + end = song + 1; + } + if (song >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song); + return -1; + } + + for (i = begin; i < end; i++) + printPlaylistSongInfo(fd, i); + + return 0; +} + +# define checkSongId(id) { \ + if(id < 0 || id >= PLAYLIST_HASH_MULT*playlist_max_length || \ + playlist.idToPosition[id] == -1 ) \ + { \ + commandError(fd, ACK_ERROR_NO_EXIST, \ + "song id doesn't exist: \"%i\"", id); \ + return -1; \ + } \ +} + +int playlistId(int fd, int id) +{ + int i; + int begin = 0; + int end = playlist.length; + + if (id >= 0) { + checkSongId(id); + begin = playlist.idToPosition[id]; + end = begin + 1; + } + + for (i = begin; i < end; i++) + printPlaylistSongInfo(fd, i); + + return 0; +} + +static void swapSongs(int song1, int song2) +{ + Song *sTemp; + int iTemp; + + sTemp = playlist.songs[song1]; + playlist.songs[song1] = playlist.songs[song2]; + playlist.songs[song2] = sTemp; + + playlist.songMod[song1] = playlist.version; + playlist.songMod[song2] = playlist.version; + + playlist.idToPosition[playlist.positionToId[song1]] = song2; + playlist.idToPosition[playlist.positionToId[song2]] = song1; + + iTemp = playlist.positionToId[song1]; + playlist.positionToId[song1] = playlist.positionToId[song2]; + playlist.positionToId[song2] = iTemp; +} + +static void queueNextSongInPlaylist(void) +{ + if (playlist.current < playlist.length - 1) { + playlist.queued = playlist.current + 1; + DEBUG("playlist: queue song %i:\"%s\"\n", + playlist.queued, + getSongUrl(playlist. + songs[playlist.order[playlist.queued]])); + if (queueSong(playlist.songs[playlist.order[playlist.queued]]) < + 0) { + playlist.queued = -1; + playlist_queueError = 1; + } + } else if (playlist.length && playlist.repeat) { + if (playlist.length > 1 && playlist.random) { + randomizeOrder(0, playlist.length - 1); + } + playlist.queued = 0; + DEBUG("playlist: queue song %i:\"%s\"\n", + playlist.queued, + getSongUrl(playlist. + songs[playlist.order[playlist.queued]])); + if (queueSong(playlist.songs[playlist.order[playlist.queued]]) < + 0) { + playlist.queued = -1; + playlist_queueError = 1; + } + } +} + +static void syncPlaylistWithQueue(int queue) +{ + 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(); + } +} + +static void lockPlaylistInteraction(void) +{ + if (getPlayerQueueState() == PLAYER_QUEUE_PLAY || + getPlayerQueueState() == PLAYER_QUEUE_FULL) { + playerQueueLock(); + syncPlaylistWithQueue(0); + } +} + +static void unlockPlaylistInteraction(void) +{ + playerQueueUnlock(); +} + +static void clearPlayerQueue(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; + } +} + +int addToPlaylist(int fd, char *url, int printId) +{ + Song *song; + + DEBUG("add to playlist: %s\n", url); + + if ((song = getSongFromDB(url))) { + } else if (!(isValidRemoteUtf8Url(url) && + (song = newSong(url, SONG_TYPE_URL, NULL)))) { + commandError(fd, ACK_ERROR_NO_EXIST, + "\"%s\" is not in the music db or is " + "not a valid url", url); + return -1; + } + + return addSongToPlaylist(fd, song, printId); +} + +int addToStoredPlaylist(int fd, char *url, char *utf8file) +{ + Song *song; + + DEBUG("add to stored playlist: %s\n", url); + + song = getSongFromDB(url); + if (song) { + appendSongToStoredPlaylistByPath(fd, utf8file, song); + return 0; + } + + if (!isValidRemoteUtf8Url(url)) + goto fail; + + song = newSong(url, SONG_TYPE_URL, NULL); + if (song) { + appendSongToStoredPlaylistByPath(fd, utf8file, song); + freeJustSong(song); + return 0; + } + +fail: + commandError(fd, ACK_ERROR_NO_EXIST, "\"%s\" is not in the music db" + "or is not a valid url", url); + return -1; +} + +int addSongToPlaylist(int fd, Song * song, int printId) +{ + int id; + + if (playlist.length == playlist_max_length) { + commandError(fd, ACK_ERROR_PLAYLIST_MAX, + "playlist is at the max size"); + return -1; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0 + && playlist.current == playlist.length - 1) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + id = getNextId(); + + playlist.songs[playlist.length] = song; + playlist.songMod[playlist.length] = playlist.version; + playlist.order[playlist.length] = playlist.length; + playlist.positionToId[playlist.length] = id; + playlist.idToPosition[playlist.positionToId[playlist.length]] = + playlist.length; + playlist.length++; + + if (playlist.random) { + int swap; + int start; + /*if(playlist_state==PLAYLIST_STATE_STOP) start = 0; + else */ if (playlist.queued >= 0) + start = playlist.queued + 1; + else + start = playlist.current + 1; + if (start < playlist.length) { + swap = random() % (playlist.length - start); + swap += start; + swapOrder(playlist.length - 1, swap); + } + } + + incrPlaylistVersion(); + + if (printId) + fdprintf(fd, "Id: %i\n", id); + + return 0; +} + +int swapSongsInPlaylist(int fd, int song1, int song2) +{ + int queuedSong = -1; + int currentSong = -1; + + if (song1 < 0 || song1 >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song1); + return -1; + } + if (song2 < 0 || song2 >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song2); + return -1; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0) { + queuedSong = playlist.order[playlist.queued]; + } + currentSong = playlist.order[playlist.current]; + + if (queuedSong == song1 || queuedSong == song2 + || currentSong == song1 || currentSong == song2) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + swapSongs(song1, song2); + if (playlist.random) { + int i; + int k; + int j = -1; + for (i = 0; playlist.order[i] != song1; i++) { + if (playlist.order[i] == song2) + j = i; + } + k = i; + for (; j == -1; i++) + if (playlist.order[i] == song2) + j = i; + swapOrder(k, j); + } else { + if (playlist.current == song1) + playlist.current = song2; + else if (playlist.current == song2) + playlist.current = song1; + } + + incrPlaylistVersion(); + + return 0; +} + +int swapSongsInPlaylistById(int fd, int id1, int id2) +{ + checkSongId(id1); + checkSongId(id2); + + return swapSongsInPlaylist(fd, playlist.idToPosition[id1], + playlist.idToPosition[id2]); +} + +#define moveSongFromTo(from, to) { \ + playlist.idToPosition[playlist.positionToId[from]] = to; \ + playlist.positionToId[to] = playlist.positionToId[from]; \ + playlist.songs[to] = playlist.songs[from]; \ + playlist.songMod[to] = playlist.version; \ +} + +int deleteFromPlaylist(int fd, int song) +{ + int i; + int songOrder; + + if (song < 0 || song >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song); + return -1; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0 + && (playlist.order[playlist.queued] == song + || playlist.order[playlist.current] == song)) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + if (playlist.songs[song]->type == SONG_TYPE_URL) { + freeJustSong(playlist.songs[song]); + } + + playlist.idToPosition[playlist.positionToId[song]] = -1; + + /* delete song from songs array */ + for (i = song; i < playlist.length - 1; i++) { + moveSongFromTo(i + 1, i); + } + /* now find it in the order array */ + for (i = 0; i < playlist.length - 1; i++) { + if (playlist.order[i] == song) + break; + } + songOrder = i; + /* delete the entry from the order array */ + for (; i < playlist.length - 1; i++) + playlist.order[i] = playlist.order[i + 1]; + /* readjust values in the order array */ + for (i = 0; i < playlist.length - 1; i++) { + if (playlist.order[i] > song) + playlist.order[i]--; + } + /* now take care of other misc stuff */ + playlist.songs[playlist.length - 1] = NULL; + playlist.length--; + + incrPlaylistVersion(); + + if (playlist_state != PLAYLIST_STATE_STOP + && playlist.current == songOrder) { + /*if(playlist.current>=playlist.length) return playerStop(fd); + else return playPlaylistOrderNumber(fd,playlist.current); */ + playerStop(STDERR_FILENO); + playlist_noGoToNext = 1; + } + + if (playlist.current > songOrder) { + playlist.current--; + } else if (playlist.current >= playlist.length) { + incrPlaylistCurrent(); + } + + if (playlist.queued > songOrder) { + playlist.queued--; + } + + return 0; +} + +int deleteFromPlaylistById(int fd, int id) +{ + checkSongId(id); + + return deleteFromPlaylist(fd, playlist.idToPosition[id]); +} + +void deleteASongFromPlaylist(Song * song) +{ + int i; + + if (NULL == playlist.songs) + return; + + for (i = 0; i < playlist.length; i++) { + if (song == playlist.songs[i]) { + deleteFromPlaylist(STDERR_FILENO, i); + } + } +} + +int stopPlaylist(int fd) +{ + DEBUG("playlist: stop\n"); + if (playerStop(fd) < 0) + return -1; + playerCloseAudio(); + playlist.queued = -1; + playlist_state = PLAYLIST_STATE_STOP; + playlist_noGoToNext = 0; + if (playlist.random) + randomizeOrder(0, playlist.length - 1); + return 0; +} + +static int playPlaylistOrderNumber(int fd, int orderNum) +{ + + if (playerStop(fd) < 0) + return -1; + + playlist_state = PLAYLIST_STATE_PLAY; + playlist_noGoToNext = 0; + playlist.queued = -1; + playlist_queueError = 0; + + DEBUG("playlist: play %i:\"%s\"\n", orderNum, + getSongUrl(playlist.songs[playlist.order[orderNum]])); + + if (playerPlay(fd, (playlist.songs[playlist.order[orderNum]])) < 0) { + stopPlaylist(fd); + return -1; + } + + playlist.current = orderNum; + + return 0; +} + +int playPlaylist(int fd, int song, int stopOnError) +{ + int i = song; + + clearPlayerError(); + + if (song == -1) { + if (playlist.length == 0) + return 0; + + if (playlist_state == PLAYLIST_STATE_PLAY) { + return playerSetPause(fd, 0); + } + if (playlist.current >= 0 && playlist.current < playlist.length) { + i = playlist.current; + } else { + i = 0; + } + } else if (song < 0 || song >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song); + return -1; + } + + if (playlist.random) { + if (song == -1 && playlist_state == PLAYLIST_STATE_PLAY) { + 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; + } + swapOrder(i, playlist.current); + i = playlist.current; + } + } + + playlist_stopOnError = stopOnError; + playlist_errorCount = 0; + + return playPlaylistOrderNumber(fd, i); +} + +int playPlaylistById(int fd, int id, int stopOnError) +{ + if (id == -1) { + return playPlaylist(fd, id, stopOnError); + } + + checkSongId(id); + + return playPlaylist(fd, playlist.idToPosition[id], stopOnError); +} + +static void syncCurrentPlayerDecodeMetadata(void) +{ + Song *songPlayer = playerCurrentDecodeSong(); + Song *song; + int songNum; + + if (!songPlayer) + return; + + if (playlist_state != PLAYLIST_STATE_PLAY) + return; + + songNum = playlist.order[playlist.current]; + song = playlist.songs[songNum]; + + if (song->type == SONG_TYPE_URL && + 0 == strcmp(getSongUrl(song), songPlayer->url) && + !mpdTagsAreEqual(song->tag, songPlayer->tag)) { + if (song->tag) + freeMpdTag(song->tag); + song->tag = mpdTagDup(songPlayer->tag); + playlist.songMod[songNum] = playlist.version; + incrPlaylistVersion(); + } +} + +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); + + return 0; +} + +int nextSongInPlaylist(int fd) +{ + 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 { + incrPlaylistCurrent(); + return stopPlaylist(fd); + } + + return 0; +} + +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); + } +} + +int getPlaylistRepeatStatus(void) +{ + return playlist.repeat; +} + +int getPlaylistRandomStatus(void) +{ + return playlist.random; +} + +int setPlaylistRepeatStatus(int fd, int status) +{ + if (status != 0 && status != 1) { + commandError(fd, ACK_ERROR_ARG, "\"%i\" is not 0 or 1", status); + return -1; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.repeat && !status && playlist.queued == 0) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + playlist.repeat = status; + + return 0; +} + +int moveSongInPlaylist(int fd, int from, int to) +{ + int i; + Song *tmpSong; + int tmpId; + int queuedSong = -1; + int currentSong = -1; + + if (from < 0 || from >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", from); + return -1; + } + + if (to < 0 || to >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", to); + return -1; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0) { + queuedSong = playlist.order[playlist.queued]; + } + currentSong = playlist.order[playlist.current]; + if (queuedSong == from || queuedSong == to + || currentSong == from || currentSong == to) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + tmpSong = playlist.songs[from]; + tmpId = playlist.positionToId[from]; + /* move songs to one less in from->to */ + for (i = from; i < to; i++) { + moveSongFromTo(i + 1, i); + } + /* move songs to one more in to->from */ + for (i = from; i > to; i--) { + moveSongFromTo(i - 1, i); + } + /* put song at _to_ */ + playlist.idToPosition[tmpId] = to; + playlist.positionToId[to] = tmpId; + playlist.songs[to] = tmpSong; + playlist.songMod[to] = playlist.version; + /* now deal with order */ + if (playlist.random) { + for (i = 0; i < playlist.length; i++) { + if (playlist.order[i] > from && playlist.order[i] <= to) { + playlist.order[i]--; + } else if (playlist.order[i] < from && + playlist.order[i] >= to) { + playlist.order[i]++; + } else if (from == playlist.order[i]) { + playlist.order[i] = 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++; + } + } + + incrPlaylistVersion(); + + return 0; +} + +int moveSongInPlaylistById(int fd, int id1, int to) +{ + checkSongId(id1); + + return moveSongInPlaylist(fd, playlist.idToPosition[id1], to); +} + +static void orderPlaylist(void) +{ + int i; + + if (playlist.current >= 0 && playlist.current < playlist.length) { + playlist.current = playlist.order[playlist.current]; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + for (i = 0; i < playlist.length; i++) { + playlist.order[i] = i; + } + +} + +static void swapOrder(int a, int b) +{ + int bak = playlist.order[a]; + playlist.order[a] = playlist.order[b]; + playlist.order[b] = bak; +} + +static void randomizeOrder(int start, int end) +{ + int i; + int ri; + + DEBUG("playlist: randomize from %i to %i\n", start, end); + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= start && playlist.queued <= end) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + for (i = start; i <= end; i++) { + ri = random() % (end - start + 1) + start; + if (ri == playlist.current) + playlist.current = i; + else if (i == playlist.current) + playlist.current = ri; + swapOrder(i, ri); + } + +} + +int setPlaylistRandomStatus(int fd, int status) +{ + int statusWas = playlist.random; + + if (status != 0 && status != 1) { + commandError(fd, ACK_ERROR_ARG, "\"%i\" is not 0 or 1", status); + return -1; + } + + 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 + orderPlaylist(); + } + + return 0; +} + +int previousSongInPlaylist(int fd) +{ + static time_t lastTime; + time_t diff = time(NULL) - lastTime; + + lastTime += diff; + + if (playlist_state != PLAYLIST_STATE_PLAY) + return 0; + + syncPlaylistWithQueue(0); + + if (diff && getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) { + return playPlaylistOrderNumber(fd, 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); + } + } + + return 0; +} + +int shufflePlaylist(int fd) +{ + int i; + int ri; + + if (playlist.length > 1) { + if (playlist_state == PLAYLIST_STATE_PLAY) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + /* put current playing song first */ + swapSongs(0, playlist.order[playlist.current]); + if (playlist.random) { + int j; + for (j = 0; 0 != playlist.order[j]; j++) ; + playlist.current = j; + } else + playlist.current = 0; + i = 1; + } else { + i = 0; + playlist.current = -1; + } + /* shuffle the rest of the list */ + for (; i < playlist.length; i++) { + ri = random() % (playlist.length - 1) + 1; + swapSongs(i, ri); + } + + incrPlaylistVersion(); + } + + return 0; +} + +int deletePlaylist(int fd, char *utf8file) +{ + char *file = utf8ToFsCharset(utf8file); + char *rfile = xmalloc(strlen(file) + strlen(".") + + strlen(PLAYLIST_FILE_SUFFIX) + 1); + char *actualFile; + + strcpy(rfile, file); + strcat(rfile, "."); + strcat(rfile, PLAYLIST_FILE_SUFFIX); + + if ((actualFile = rpp2app(rfile)) && isPlaylist(actualFile)) + free(rfile); + else { + free(rfile); + commandError(fd, ACK_ERROR_NO_EXIST, + "playlist \"%s\" not found", utf8file); + return -1; + } + + if (unlink(actualFile) < 0) { + commandError(fd, ACK_ERROR_SYSTEM, + "problems deleting file"); + return -1; + } + + return 0; +} + +int savePlaylist(int fd, char *utf8file) +{ + StoredPlaylist *sp = newStoredPlaylist(utf8file, fd, 0); + if (!sp) + return -1; + + appendPlaylistToStoredPlaylist(sp, &playlist); + if (writeStoredPlaylist(sp) != 0) { + freeStoredPlaylist(sp); + return -1; + } + + freeStoredPlaylist(sp); + return 0; +} + +int getPlaylistCurrentSong(void) +{ + if (playlist.current >= 0 && playlist.current < playlist.length) { + return playlist.order[playlist.current]; + } + + return -1; +} + +unsigned long getPlaylistVersion(void) +{ + return playlist.version; +} + +int getPlaylistLength(void) +{ + return playlist.length; +} + +int seekSongInPlaylist(int fd, int song, float time) +{ + int i = song; + + if (song < 0 || song >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song); + return -1; + } + + if (playlist.random) + for (i = 0; song != playlist.order[i]; i++) ; + + clearPlayerError(); + playlist_stopOnError = 1; + playlist_errorCount = 0; + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } else if (playPlaylistOrderNumber(fd, i) < 0) + return -1; + + if (playlist.current != i) { + if (playPlaylistOrderNumber(fd, i) < 0) + return -1; + } + + return playerSeek(fd, playlist.songs[playlist.order[i]], time); +} + +int seekSongInPlaylistById(int fd, int id, float time) +{ + checkSongId(id); + + return seekSongInPlaylist(fd, playlist.idToPosition[id], time); +} + +int getPlaylistSongId(int song) +{ + return playlist.positionToId[song]; +} + +int PlaylistInfo(int fd, char *utf8file, int detail) +{ + ListNode *node; + StoredPlaylist *sp = loadStoredPlaylist(utf8file, fd); + if (sp == NULL) + return -1; + + node = sp->list->firstNode; + while (node != NULL) { + char *temp = node->data; + int wrote = 0; + + if (detail) { + Song *song = getSongFromDB(temp); + if (song) { + printSongInfo(fd, song); + wrote = 1; + } + } + + if (!wrote) { + fdprintf(fd, SONG_FILE "%s\n", temp); + } + + node = node->nextNode; + } + + freeStoredPlaylist(sp); + return 0; +} + +int loadPlaylist(int fd, char *utf8file) +{ + ListNode *node; + StoredPlaylist *sp = loadStoredPlaylist(utf8file, fd); + if (sp == NULL) + return -1; + + node = sp->list->firstNode; + while (node != NULL) { + char *temp = node->data; + if ((addToPlaylist(STDERR_FILENO, temp, 0)) < 0) { + /* for windows compatibility, convert slashes */ + char *temp2 = xstrdup(temp); + char *p = temp2; + while (*p) { + if (*p == '\\') + *p = '/'; + p++; + } + if ((addToPlaylist(STDERR_FILENO, temp2, 0)) < 0) { + commandError(fd, ACK_ERROR_PLAYLIST_LOAD, + "can't add file \"%s\"", temp2); + } + free(temp2); + } + + node = node->nextNode; + } + + freeStoredPlaylist(sp); + return 0; +} + +void searchForSongsInPlaylist(int fd, int numItems, LocateTagItem * items) +{ + int i; + char **originalNeedles = xmalloc(numItems * sizeof(char *)); + + for (i = 0; i < numItems; i++) { + originalNeedles[i] = items[i].needle; + items[i].needle = strDupToUpper(originalNeedles[i]); + } + + for (i = 0; i < playlist.length; i++) { + if (strstrSearchTags(playlist.songs[i], numItems, items)) + printPlaylistSongInfo(fd, i); + } + + for (i = 0; i < numItems; i++) { + free(items[i].needle); + items[i].needle = originalNeedles[i]; + } + + free(originalNeedles); +} + +void findSongsInPlaylist(int fd, int numItems, LocateTagItem * items) +{ + int i; + + for (i = 0; i < playlist.length; i++) { + if (tagItemsFoundAndMatches(playlist.songs[i], numItems, items)) + printPlaylistSongInfo(fd, i); + } +} diff --git a/trunk/src/playlist.h b/trunk/src/playlist.h new file mode 100644 index 000000000..0ae3a677f --- /dev/null +++ b/trunk/src/playlist.h @@ -0,0 +1,144 @@ +/* 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 PLAYLIST_H +#define PLAYLIST_H + +#include "../config.h" + +#include "dbUtils.h" + +#include <stdio.h> +#include <sys/param.h> +#include <time.h> + +#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; + +void initPlaylist(void); + +void finishPlaylist(void); + +void readPlaylistState(FILE *); + +void savePlaylistState(FILE *); + +int clearPlaylist(int fd); + +int clearStoredPlaylist(int fd, char *utf8file); + +int addToPlaylist(int fd, char *file, int printId); + +int addToStoredPlaylist(int fd, char *file, char *utf8file); + +int addSongToPlaylist(int fd, Song * song, int printId); + +int showPlaylist(int fd); + +int deleteFromPlaylist(int fd, int song); + +int deleteFromPlaylistById(int fd, int song); + +int playlistInfo(int fd, int song); + +int playlistId(int fd, int song); + +int stopPlaylist(int fd); + +int playPlaylist(int fd, int song, int stopOnError); + +int playPlaylistById(int fd, int song, int stopOnError); + +int nextSongInPlaylist(int fd); + +void syncPlayerAndPlaylist(void); + +int previousSongInPlaylist(int fd); + +int shufflePlaylist(int fd); + +int savePlaylist(int fd, char *utf8file); + +int deletePlaylist(int fd, char *utf8file); + +int deletePlaylistById(int fd, char *utf8file); + +void deleteASongFromPlaylist(Song * song); + +int moveSongInPlaylist(int fd, int from, int to); + +int moveSongInPlaylistById(int fd, int id, int to); + +int swapSongsInPlaylist(int fd, int song1, int song2); + +int swapSongsInPlaylistById(int fd, int id1, int id2); + +int loadPlaylist(int fd, char *utf8file); + +int getPlaylistRepeatStatus(void); + +int setPlaylistRepeatStatus(int fd, int status); + +int getPlaylistRandomStatus(void); + +int setPlaylistRandomStatus(int fd, int status); + +int getPlaylistCurrentSong(void); + +int getPlaylistSongId(int song); + +int getPlaylistLength(void); + +unsigned long getPlaylistVersion(void); + +void playPlaylistIfPlayerStopped(void); + +int seekSongInPlaylist(int fd, int song, float time); + +int seekSongInPlaylistById(int fd, int id, float time); + +void playlistVersionChange(void); + +int playlistChanges(int fd, mpd_uint32 version); + +int playlistChangesPosId(int fd, mpd_uint32 version); + +int PlaylistInfo(int fd, char *utf8file, int detail); + +void searchForSongsInPlaylist(int fd, int numItems, LocateTagItem * items); + +void findSongsInPlaylist(int fd, int numItems, LocateTagItem * items); + +#endif diff --git a/trunk/src/replayGain.c b/trunk/src/replayGain.c new file mode 100644 index 000000000..7c20919b8 --- /dev/null +++ b/trunk/src/replayGain.c @@ -0,0 +1,165 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * (c)2004 replayGain code by AliasMrJones + * 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 "replayGain.h" +#include "utils.h" + +#include "log.h" +#include "conf.h" + +#include <string.h> +#include <math.h> +#include <stdlib.h> + +/* Added 4/14/2004 by AliasMrJones */ +int replayGainState = REPLAYGAIN_OFF; + +static float replayGainPreamp = 1.0; + +void initReplayGainState(void) +{ + ConfigParam *param = getConfigParam(CONF_REPLAYGAIN); + + if (!param) + return; + + if (strcmp(param->value, "track") == 0) { + replayGainState = REPLAYGAIN_TRACK; + } else if (strcmp(param->value, "album") == 0) { + replayGainState = REPLAYGAIN_ALBUM; + } else { + FATAL("replaygain value \"%s\" at line %i is invalid\n", + param->value, param->line); + } + + param = getConfigParam(CONF_REPLAYGAIN_PREAMP); + + if (param) { + char *test; + float f = strtod(param->value, &test); + + if (*test != '\0') { + FATAL("Replaygain preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + FATAL("Replaygain preamp \"%s\" is not between -15 and" + "15 at line %i\n", param->value, param->line); + } + + replayGainPreamp = pow(10, f / 20.0); + } +} + +static float computeReplayGainScale(float gain, float peak) +{ + float scale; + + if (gain == 0.0) + return (1); + scale = pow(10.0, gain / 20.0); + scale *= replayGainPreamp; + if (scale > 15.0) + scale = 15.0; + + if (scale * peak > 1.0) { + scale = 1.0 / peak; + } + return (scale); +} + +ReplayGainInfo *newReplayGainInfo(void) +{ + ReplayGainInfo *ret = xmalloc(sizeof(ReplayGainInfo)); + + ret->albumGain = 0.0; + ret->albumPeak = 0.0; + + ret->trackGain = 0.0; + ret->trackPeak = 0.0; + + /* set to -1 so that we know in doReplayGain to compute the scale */ + ret->scale = -1.0; + + return ret; +} + +void freeReplayGainInfo(ReplayGainInfo * info) +{ + free(info); +} + +void doReplayGain(ReplayGainInfo * info, char *buffer, int bufferSize, + AudioFormat * format) +{ + mpd_sint16 *buffer16; + mpd_sint8 *buffer8; + mpd_sint32 temp32; + float scale; + + if (replayGainState == REPLAYGAIN_OFF || !info) + return; + + if (info->scale < 0) { + switch (replayGainState) { + case REPLAYGAIN_TRACK: + info->scale = computeReplayGainScale(info->trackGain, + info->trackPeak); + break; + default: + info->scale = computeReplayGainScale(info->albumGain, + info->albumPeak); + break; + } + } + + if (info->scale <= 1.01 && info->scale >= 0.99) + return; + + buffer16 = (mpd_sint16 *) buffer; + buffer8 = (mpd_sint8 *) buffer; + + scale = info->scale; + + switch (format->bits) { + case 16: + while (bufferSize > 0) { + temp32 = *buffer16; + temp32 *= scale; + *buffer16 = temp32 > 32767 ? 32767 : + (temp32 < -32768 ? -32768 : temp32); + buffer16++; + bufferSize -= 2; + } + break; + case 8: + while (bufferSize > 0) { + temp32 = *buffer8; + temp32 *= scale; + *buffer8 = temp32 > 127 ? 127 : + (temp32 < -128 ? -128 : temp32); + buffer8++; + bufferSize--; + } + break; + default: + ERROR("%i bits not supported by doReplaygain!\n", format->bits); + } +} diff --git a/trunk/src/replayGain.h b/trunk/src/replayGain.h new file mode 100644 index 000000000..c2d471464 --- /dev/null +++ b/trunk/src/replayGain.h @@ -0,0 +1,50 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * (c)2004 replayGain code by AliasMrJones + * 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 REPLAYGAIN_H +#define REPLAYGAIN_H + +#include "audio.h" + +#define REPLAYGAIN_OFF 0 +#define REPLAYGAIN_TRACK 1 +#define REPLAYGAIN_ALBUM 2 + +extern int replayGainState; + +typedef struct _ReplayGainInfo { + float albumGain; + float albumPeak; + float trackGain; + float trackPeak; + + /* used internally by mpd, to mess with it */ + float scale; +} ReplayGainInfo; + +ReplayGainInfo *newReplayGainInfo(void); + +void freeReplayGainInfo(ReplayGainInfo * info); + +void initReplayGainState(void); + +void doReplayGain(ReplayGainInfo * info, char *buffer, int bufferSize, + AudioFormat * format); + +#endif diff --git a/trunk/src/sig_handlers.c b/trunk/src/sig_handlers.c new file mode 100644 index 000000000..fc29d2522 --- /dev/null +++ b/trunk/src/sig_handlers.c @@ -0,0 +1,159 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * (c) 2004 Nick Welch (mack@incise.org) + * 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 "sig_handlers.h" +#include "player.h" +#include "playerData.h" +#include "playlist.h" +#include "directory.h" +#include "command.h" +#include "signal_check.h" +#include "log.h" +#include "player.h" +#include "decode.h" + +#include <signal.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/wait.h> +#include <errno.h> +#include <unistd.h> + +int handlePendingSignals(void) +{ + if (signal_is_pending(SIGINT) || signal_is_pending(SIGTERM)) { + DEBUG("main process got SIGINT or SIGTERM, exiting\n"); + return COMMAND_RETURN_KILL; + } + + if (signal_is_pending(SIGHUP)) { + DEBUG("got SIGHUP, rereading DB\n"); + signal_clear(SIGHUP); + if (!isUpdatingDB()) { + readDirectoryDB(); + playlistVersionChange(); + } + if (cycle_log_files() < 0) + return COMMAND_RETURN_KILL; + playerCycleLogFiles(); + } + + return 0; +} + +static void chldSigHandler(int signal) +{ + int status; + int pid; + DEBUG("main process got SIGCHLD\n"); + while (0 != (pid = wait3(&status, WNOHANG, NULL))) { + if (pid < 0) { + if (errno == EINTR) + continue; + else + break; + } + player_sigChldHandler(pid, status); + directory_sigChldHandler(pid, status); + } +} + +void initSigHandlers(void) +{ + struct sigaction sa; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + while (sigaction(SIGPIPE, &sa, NULL) < 0 && errno == EINTR) ; + sa.sa_handler = chldSigHandler; + while (sigaction(SIGCHLD, &sa, NULL) < 0 && errno == EINTR) ; + signal_handle(SIGUSR1); + signal_handle(SIGINT); + signal_handle(SIGTERM); + signal_handle(SIGHUP); +} + +void finishSigHandlers(void) +{ + signal_unhandle(SIGINT); + signal_unhandle(SIGUSR1); + signal_unhandle(SIGTERM); + signal_unhandle(SIGHUP); +} + +void setSigHandlersForDecoder(void) +{ + struct sigaction sa; + + finishSigHandlers(); + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + while (sigaction(SIGHUP, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGINT, &sa, NULL) < 0 && errno == EINTR) ; + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = decodeSigHandler; + while (sigaction(SIGCHLD, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGTERM, &sa, NULL) < 0 && errno == EINTR) ; +} + +void ignoreSignals(void) +{ + struct sigaction sa; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + sa.sa_sigaction = NULL; + while (sigaction(SIGPIPE, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGCHLD, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGUSR1, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGINT, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGTERM, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGHUP, &sa, NULL) < 0 && errno == EINTR) ; +} + +void blockSignals(void) +{ + sigset_t sset; + + sigemptyset(&sset); + sigaddset(&sset, SIGCHLD); + sigaddset(&sset, SIGUSR1); + sigaddset(&sset, SIGHUP); + sigaddset(&sset, SIGINT); + sigaddset(&sset, SIGTERM); + while (sigprocmask(SIG_BLOCK, &sset, NULL) < 0 && errno == EINTR) ; +} + +void unblockSignals(void) +{ + sigset_t sset; + + sigemptyset(&sset); + sigaddset(&sset, SIGCHLD); + sigaddset(&sset, SIGUSR1); + sigaddset(&sset, SIGHUP); + sigaddset(&sset, SIGINT); + sigaddset(&sset, SIGTERM); + while (sigprocmask(SIG_UNBLOCK, &sset, NULL) < 0 && errno == EINTR) ; +} diff --git a/trunk/src/sig_handlers.h b/trunk/src/sig_handlers.h new file mode 100644 index 000000000..15fa181ee --- /dev/null +++ b/trunk/src/sig_handlers.h @@ -0,0 +1,42 @@ +/* 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 SIG_HANDLERS_H +#define SIG_HANDLERS_H + +#include "../config.h" + +int handlePendingSignals(void); + +void initSigHandlers(void); + +void finishSigHandlers(void); + +void setSigHandlersForDecoder(void); + +void ignoreSignals(void); + +void blockSignals(void); + +void unblockSignals(void); + +void blockTermSignal(void); + +void unblockTermSignal(void); + +#endif diff --git a/trunk/src/signal_check.c b/trunk/src/signal_check.c new file mode 100644 index 000000000..77a2b1251 --- /dev/null +++ b/trunk/src/signal_check.c @@ -0,0 +1,60 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * (c)2004 by mackstann + * 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 "signal_check.h" + +#include <errno.h> +#include <stddef.h> + +static volatile sig_atomic_t __caught_signals[NSIG]; + +static void __signal_handler(int sig) +{ + __caught_signals[sig] = 1; +} + +static void __set_signal_handler(int sig, void (*handler) (int)) +{ + struct sigaction act; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + act.sa_handler = handler; + while (sigaction(sig, &act, NULL) && errno == EINTR) ; +} + +void signal_handle(int sig) +{ + __set_signal_handler(sig, __signal_handler); +} + +void signal_unhandle(int sig) +{ + signal_clear(sig); + __set_signal_handler(sig, SIG_DFL); +} + +int signal_is_pending(int sig) +{ + return __caught_signals[sig]; +} + +void signal_clear(int sig) +{ + __caught_signals[sig] = 0; +} diff --git a/trunk/src/signal_check.h b/trunk/src/signal_check.h new file mode 100644 index 000000000..58c9f3c3e --- /dev/null +++ b/trunk/src/signal_check.h @@ -0,0 +1,30 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * (c)2004 by mackstann + * 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 SIGNAL_CHECK_H +#define SIGNAL_CHECK_H + +#include <signal.h> + +void signal_handle(int sig); +void signal_unhandle(int sig); +int signal_is_pending(int sig); +void signal_clear(int sig); + +#endif /* SIGNAL_CHECK_H */ diff --git a/trunk/src/sllist.c b/trunk/src/sllist.c new file mode 100644 index 000000000..00408a3cd --- /dev/null +++ b/trunk/src/sllist.c @@ -0,0 +1,72 @@ +/* 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 + */ + +/* a very simple singly-linked-list structure for queues/buffers */ + +#include <string.h> +#include "sllist.h" +#include "utils.h" + +static void init_strnode(struct strnode *x, char *s) +{ + x->data = s; + x->next = NULL; +} + +struct strnode *new_strnode(char *s) +{ + struct strnode *x = xmalloc(sizeof(struct strnode)); + init_strnode(x, s); + return x; +} + +struct strnode *new_strnode_dup(char *s, const size_t size) +{ + struct strnode *x = xmalloc(sizeof(struct strnode) + size); + x->next = NULL; + x->data = ((char *)x + sizeof(struct strnode)); + memcpy((void *)x->data, (void*)s, size); + return x; +} + +struct sllnode *new_sllnode(void *s, const size_t size) +{ + struct sllnode *x = xmalloc(sizeof(struct sllnode) + size); + x->next = NULL; + x->size = size; + x->data = ((char *)x + sizeof(struct sllnode)); + memcpy(x->data, (void *)s, size); + return x; +} + +struct strnode *dup_strlist(struct strnode *old) +{ + struct strnode *tmp, *new, *cur; + + tmp = old; + cur = new = new_strnode_dup(tmp->data, strlen(tmp->data) + 1); + tmp = tmp->next; + while (tmp) { + cur->next = new_strnode_dup(tmp->data, strlen(tmp->data) + 1); + cur = cur->next; + tmp = tmp->next; + } + return new; +} + + diff --git a/trunk/src/sllist.h b/trunk/src/sllist.h new file mode 100644 index 000000000..1e81abef3 --- /dev/null +++ b/trunk/src/sllist.h @@ -0,0 +1,52 @@ +/* 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 + */ + +/* a very simple singly-linked-list structure for queues/buffers */ + +#ifndef SLLIST_H +#define SLLIST_H + +#include <stddef.h> + +/* just free the entire structure if it's free-able, the 'data' member + * should _NEVER_ be explicitly freed + * + * there's no free command, iterate through them yourself and just + * call free() on it iff you xmalloc'd them */ + +struct strnode { + struct strnode *next; + char *data; +}; + +struct sllnode { + struct sllnode *next; + void *data; + size_t size; +}; + +struct strnode *new_strnode(char *s); + +struct strnode *new_strnode_dup(char *s, const size_t size); + +struct strnode *dup_strlist(struct strnode *old); + +struct sllnode *new_sllnode(void *s, const size_t size); + + +#endif /* SLLIST_H */ diff --git a/trunk/src/song.c b/trunk/src/song.c new file mode 100644 index 000000000..9bcb1a0b4 --- /dev/null +++ b/trunk/src/song.c @@ -0,0 +1,353 @@ +/* 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 "song.h" +#include "ls.h" +#include "directory.h" +#include "utils.h" +#include "tag.h" +#include "log.h" +#include "path.h" +#include "playlist.h" +#include "inputPlugin.h" +#include "myfprintf.h" + +#define SONG_KEY "key: " +#define SONG_MTIME "mtime: " + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +Song *newNullSong(void) +{ + Song *song = xmalloc(sizeof(Song)); + + song->tag = NULL; + song->url = NULL; + song->type = SONG_TYPE_FILE; + song->parentDir = NULL; + + return song; +} + +Song *newSong(char *url, int type, Directory * parentDir) +{ + Song *song = NULL; + + if (strchr(url, '\n')) { + DEBUG("newSong: '%s' is not a valid uri\n", url); + return NULL; + } + + song = newNullSong(); + + song->url = xstrdup(url); + song->type = type; + song->parentDir = parentDir; + + assert(type == SONG_TYPE_URL || parentDir); + + if (song->type == SONG_TYPE_FILE) { + InputPlugin *plugin; + unsigned int next = 0; + char *song_url = getSongUrl(song); + char *abs_path = rmp2amp(utf8ToFsCharset(song_url)); + while (!song->tag && (plugin = isMusic(song_url, + &(song->mtime), + next++))) { + song->tag = plugin->tagDupFunc(abs_path); + } + if (!song->tag || song->tag->time < 0) { + freeSong(song); + song = NULL; + } + } + + return song; +} + +void freeSong(Song * song) +{ + deleteASongFromPlaylist(song); + freeJustSong(song); +} + +void freeJustSong(Song * song) +{ + free(song->url); + if (song->tag) + freeMpdTag(song->tag); + free(song); + getSongUrl(NULL); +} + +SongList *newSongList(void) +{ + return makeList((ListFreeDataFunc *) freeSong, 0); +} + +Song *addSongToList(SongList * list, char *url, char *utf8path, + int songType, Directory * parentDirectory) +{ + Song *song = NULL; + + switch (songType) { + case SONG_TYPE_FILE: + if (isMusic(utf8path, NULL, 0)) { + song = newSong(url, songType, parentDirectory); + } + break; + case SONG_TYPE_URL: + song = newSong(url, songType, parentDirectory); + break; + default: + DEBUG("addSongToList: Trying to add an invalid song type\n"); + } + + if (song == NULL) + return NULL; + + insertInList(list, song->url, (void *)song); + + return song; +} + +void freeSongList(SongList * list) +{ + freeList(list); +} + +void printSongUrl(int fd, Song * song) +{ + if (song->parentDir && song->parentDir->path) { + fdprintf(fd, "%s%s/%s\n", SONG_FILE, + getDirectoryPath(song->parentDir), song->url); + } else { + fdprintf(fd, "%s%s\n", SONG_FILE, song->url); + } +} + +int printSongInfo(int fd, Song * song) +{ + printSongUrl(fd, song); + + if (song->tag) + printMpdTag(fd, song->tag); + + return 0; +} + +int printSongInfoFromList(int fd, SongList * list) +{ + ListNode *tempNode = list->firstNode; + + while (tempNode != NULL) { + printSongInfo(fd, (Song *) tempNode->data); + tempNode = tempNode->nextNode; + } + + return 0; +} + +void writeSongInfoFromList(FILE * fp, SongList * list) +{ + ListNode *tempNode = list->firstNode; + + fprintf(fp, "%s\n", SONG_BEGIN); + + while (tempNode != NULL) { + fprintf(fp, "%s%s\n", SONG_KEY, tempNode->key); + fflush(fp); + printSongInfo(fileno(fp), (Song *) tempNode->data); + fprintf(fp, "%s%li\n", SONG_MTIME, + (long)((Song *) tempNode->data)->mtime); + tempNode = tempNode->nextNode; + } + + fprintf(fp, "%s\n", SONG_END); +} + +static void insertSongIntoList(SongList * list, ListNode ** nextSongNode, + char *key, Song * song) +{ + ListNode *nodeTemp; + int cmpRet = 0; + + while (*nextSongNode + && (cmpRet = strcmp(key, (*nextSongNode)->key)) > 0) { + nodeTemp = (*nextSongNode)->nextNode; + deleteNodeFromList(list, *nextSongNode); + *nextSongNode = nodeTemp; + } + + if (!(*nextSongNode)) { + insertInList(list, song->url, (void *)song); + } else if (cmpRet == 0) { + Song *tempSong = (Song *) ((*nextSongNode)->data); + if (tempSong->mtime != song->mtime) { + freeMpdTag(tempSong->tag); + tempSong->tag = song->tag; + tempSong->mtime = song->mtime; + song->tag = NULL; + } + freeJustSong(song); + *nextSongNode = (*nextSongNode)->nextNode; + } else { + insertInListBeforeNode(list, *nextSongNode, -1, song->url, + (void *)song); + } +} + +static int matchesAnMpdTagItemKey(char *buffer, int *itemType) +{ + int i; + + for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { + if (0 == strncmp(mpdTagItemKeys[i], buffer, + strlen(mpdTagItemKeys[i]))) { + *itemType = i; + return 1; + } + } + + return 0; +} + +void readSongInfoIntoList(FILE * fp, SongList * list, Directory * parentDir) +{ + char buffer[MAXPATHLEN + 1024]; + int bufferSize = MAXPATHLEN + 1024; + Song *song = NULL; + ListNode *nextSongNode = list->firstNode; + ListNode *nodeTemp; + int itemType; + + while (myFgets(buffer, bufferSize, fp) && 0 != strcmp(SONG_END, buffer)) { + if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) { + if (song) { + insertSongIntoList(list, &nextSongNode, + song->url, song); + song = NULL; + } + + song = newNullSong(); + song->url = xstrdup(buffer + strlen(SONG_KEY)); + song->type = SONG_TYPE_FILE; + song->parentDir = parentDir; + } else if (0 == strncmp(SONG_FILE, buffer, strlen(SONG_FILE))) { + if (!song) + FATAL("Problems reading song info\n"); + /* we don't need this info anymore + song->url = xstrdup(&(buffer[strlen(SONG_FILE)])); + */ + } else if (matchesAnMpdTagItemKey(buffer, &itemType)) { + if (!song->tag) + song->tag = newMpdTag(); + addItemToMpdTag(song->tag, itemType, + &(buffer + [strlen(mpdTagItemKeys[itemType]) + + 2])); + } else if (0 == strncmp(SONG_TIME, buffer, strlen(SONG_TIME))) { + if (!song->tag) + song->tag = newMpdTag(); + song->tag->time = atoi(&(buffer[strlen(SONG_TIME)])); + } else if (0 == strncmp(SONG_MTIME, buffer, strlen(SONG_MTIME))) { + song->mtime = atoi(&(buffer[strlen(SONG_MTIME)])); + } + /* ignore empty lines (starting with '\0') */ + else if (*buffer) + FATAL("songinfo: unknown line in db: %s\n", buffer); + } + + if (song) { + insertSongIntoList(list, &nextSongNode, song->url, song); + song = NULL; + } + + while (nextSongNode) { + nodeTemp = nextSongNode->nextNode; + deleteNodeFromList(list, nextSongNode); + nextSongNode = nodeTemp; + } +} + +int updateSongInfo(Song * song) +{ + if (song->type == SONG_TYPE_FILE) { + InputPlugin *plugin; + unsigned int next = 0; + char *song_url = getSongUrl(song); + char *abs_path = rmp2amp(song_url); + + if (song->tag) + freeMpdTag(song->tag); + + song->tag = NULL; + + while (!song->tag && (plugin = isMusic(song_url, + &(song->mtime), + next++))) { + song->tag = plugin->tagDupFunc(abs_path); + } + if (!song->tag || song->tag->time < 0) + return -1; + } + + return 0; +} + +/* pass song = NULL to reset, we do this freeJustSong(), so that if + * we free and recreate this memory we make sure to print it correctly*/ +char *getSongUrl(Song * song) +{ + static char *buffer; + static int bufferSize; + static Song *lastSong; + int slen; + int dlen; + int size; + + if (!song) { + lastSong = song; + return NULL; + } + + if (!song->parentDir || !song->parentDir->path) + return song->url; + + /* be careful with this! */ + if (song == lastSong) + return buffer; + + slen = strlen(song->url); + dlen = strlen(getDirectoryPath(song->parentDir)); + + size = slen + dlen + 2; + + if (size > bufferSize) { + buffer = xrealloc(buffer, size); + bufferSize = size; + } + + strcpy(buffer, getDirectoryPath(song->parentDir)); + buffer[dlen] = '/'; + strcpy(buffer + dlen + 1, song->url); + + return buffer; +} diff --git a/trunk/src/song.h b/trunk/src/song.h new file mode 100644 index 000000000..c4100d2a2 --- /dev/null +++ b/trunk/src/song.h @@ -0,0 +1,79 @@ +/* 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 SONG_H +#define SONG_H + +#include "../config.h" + +#include <sys/param.h> +#include <time.h> + +#include "tag.h" +#include "list.h" + +#define SONG_BEGIN "songList begin" +#define SONG_END "songList end" + +#define SONG_TYPE_FILE 1 +#define SONG_TYPE_URL 2 + +#define SONG_FILE "file: " +#define SONG_TIME "Time: " + +typedef struct _Song { + char *url; + mpd_sint8 type; + MpdTag *tag; + struct _Directory *parentDir; + time_t mtime; +} Song; + +typedef List SongList; + +Song *newNullSong(void); + +Song *newSong(char *url, int songType, struct _Directory *parentDir); + +void freeSong(Song *); + +void freeJustSong(Song *); + +SongList *newSongList(void); + +void freeSongList(SongList * list); + +Song *addSongToList(SongList * list, char *url, char *utf8path, + int songType, struct _Directory *parentDir); + +int printSongInfo(int fd, Song * song); + +int printSongInfoFromList(int fd, SongList * list); + +void writeSongInfoFromList(FILE * fp, SongList * list); + +void readSongInfoIntoList(FILE * fp, SongList * list, + struct _Directory *parent); + +int updateSongInfo(Song * song); + +void printSongUrl(int fd, Song * song); + +char *getSongUrl(Song * song); + +#endif diff --git a/trunk/src/state_file.c b/trunk/src/state_file.c new file mode 100644 index 000000000..ac2fcde7a --- /dev/null +++ b/trunk/src/state_file.c @@ -0,0 +1,111 @@ +/* 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 "../config.h" +#include "state_file.h" +#include "conf.h" +#include "gcc.h" +#include "log.h" +#include "audio.h" +#include "playlist.h" +#include "utils.h" +#include "volume.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> + +static struct _sf_cb { + void (*reader)(FILE *); + void (*writer)(FILE *); +} sf_callbacks [] = { + { read_sw_volume_state, save_sw_volume_state }, + { readAudioDevicesState, saveAudioDevicesState }, + { readPlaylistState, savePlaylistState }, +}; + +static const char *sfpath; + +static void get_state_file_path(void) +{ + ConfigParam *param; + if (sfpath) + return; + param = parseConfigFilePath(CONF_STATE_FILE, 0); + if (param) + sfpath = (const char *)param->value; +} + +void write_state_file(void) +{ + int i; + FILE *fp; + + if (!sfpath) + return; + while (!(fp = fopen(sfpath, "w")) && errno == EINTR); + + if (mpd_unlikely(!fp)) { + ERROR("problems opening state file \"%s\" for writing: %s\n", + sfpath, strerror(errno)); + return; + } + + for (i = 0; i < ARRAY_SIZE(sf_callbacks); i++) + sf_callbacks[i].writer(fp); + + while(fclose(fp) && errno == EINTR) /* nothing */; +} + +void read_state_file(void) +{ + struct stat st; + int i; + FILE *fp; + + get_state_file_path(); + if (!sfpath) + return; + if (stat(sfpath, &st) < 0) { + DEBUG("failed to stat state file: %s\n", sfpath); + return; + } + if (!S_ISREG(st.st_mode)) + FATAL("state file \"%s\" is not a regular file\n", sfpath); + + while (!(fp = fopen(sfpath, "r")) && errno == EINTR); + if (mpd_unlikely(!fp)) { + FATAL("problems opening state file \"%s\" for reading: %s\n", + sfpath, strerror(errno)); + } + for (i = 0; i < ARRAY_SIZE(sf_callbacks); i++) { + sf_callbacks[i].reader(fp); + rewind(fp); + } + + while(fclose(fp) && errno == EINTR) /* nothing */; +} + +void mpd_noreturn state_file_fatal(void) +{ + FATAL("error parsing state file \"%s\"\n", sfpath); + exit(EXIT_FAILURE); +} + diff --git a/trunk/src/state_file.h b/trunk/src/state_file.h new file mode 100644 index 000000000..4a7d012ec --- /dev/null +++ b/trunk/src/state_file.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 STATE_FILE_H +#define STATE_FILE_H + +#include "gcc.h" + +#include <stdio.h> + +void write_state_file(void); +void read_state_file(void); +void mpd_noreturn state_file_fatal(void); + +#endif /* STATE_FILE_H */ diff --git a/trunk/src/stats.c b/trunk/src/stats.c new file mode 100644 index 000000000..5045077c0 --- /dev/null +++ b/trunk/src/stats.c @@ -0,0 +1,48 @@ +/* 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 "stats.h" + +#include "directory.h" +#include "myfprintf.h" +#include "player.h" +#include "tag.h" +#include "tagTracker.h" + +#include <time.h> + +Stats stats; + +void initStats(void) +{ + stats.daemonStart = time(NULL); + stats.numberOfSongs = 0; +} + +int printStats(int fd) +{ + fdprintf(fd, "artists: %i\n", getNumberOfTagItems(TAG_ITEM_ARTIST)); + fdprintf(fd, "albums: %i\n", getNumberOfTagItems(TAG_ITEM_ALBUM)); + 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)); + fdprintf(fd, "db_playtime: %li\n", stats.dbPlayTime); + fdprintf(fd, "db_update: %li\n", getDbModTime()); + return 0; +} diff --git a/trunk/src/stats.h b/trunk/src/stats.h new file mode 100644 index 000000000..cd7d0122c --- /dev/null +++ b/trunk/src/stats.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 STATS_H +#define STATS_H + +#include "../config.h" + +#include <stdio.h> + +typedef struct _Stats { + unsigned long daemonStart; + int numberOfSongs; + unsigned long dbPlayTime; + /*unsigned long playTime; + unsigned long songsPlayed; */ +} Stats; + +extern Stats stats; + +void initStats(void); + +int printStats(int fd); + +#endif diff --git a/trunk/src/storedPlaylist.c b/trunk/src/storedPlaylist.c new file mode 100644 index 000000000..322cb1b5b --- /dev/null +++ b/trunk/src/storedPlaylist.c @@ -0,0 +1,501 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 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 "storedPlaylist.h" +#include "log.h" +#include "path.h" +#include "utils.h" +#include "playlist.h" +#include "ack.h" +#include "command.h" +#include "ls.h" +#include "directory.h" + +#include <string.h> +#include <errno.h> + +static char *utf8pathToFsPathInStoredPlaylist(const char *utf8path, int fd) +{ + char *file; + char *rfile; + char *actualFile; + + if (strstr(utf8path, "/")) { + commandError(fd, ACK_ERROR_ARG, "playlist name \"%s\" is " + "invalid: playlist names may not contain slashes", + utf8path); + return NULL; + } + + file = utf8ToFsCharset((char *)utf8path); + + rfile = xmalloc(strlen(file) + strlen(".") + + strlen(PLAYLIST_FILE_SUFFIX) + 1); + + strcpy(rfile, file); + strcat(rfile, "."); + strcat(rfile, PLAYLIST_FILE_SUFFIX); + + actualFile = rpp2app(rfile); + + free(rfile); + + return actualFile; +} + +static unsigned int lengthOfStoredPlaylist(StoredPlaylist *sp) +{ + return sp->list->numberOfNodes; +} + +static ListNode *nodeOfStoredPlaylist(StoredPlaylist *sp, int index) +{ + int forward; + ListNode *node; + int i; + + if (index >= lengthOfStoredPlaylist(sp) || index < 0) + return NULL; + + if (index > lengthOfStoredPlaylist(sp)/2) { + forward = 0; + node = sp->list->lastNode; + i = lengthOfStoredPlaylist(sp) - 1; + } else { + forward = 1; + node = sp->list->firstNode; + i = 0; + } + + while (node != NULL) { + if (i == index) + return node; + + if (forward) { + i++; + node = node->nextNode; + } else { + i--; + node = node->prevNode; + } + } + + return NULL; +} + +static void appendSongToStoredPlaylist(StoredPlaylist *sp, Song *song) +{ + insertInListWithoutKey(sp->list, xstrdup(getSongUrl(song))); +} + +StoredPlaylist *newStoredPlaylist(const char *utf8name, int fd, int ignoreExisting) +{ + struct stat buf; + char *filename = NULL; + StoredPlaylist *sp = calloc(1, sizeof(*sp)); + if (!sp) + return NULL; + + if (utf8name) { + filename = utf8pathToFsPathInStoredPlaylist(utf8name, fd); + + if (filename && stat(filename, &buf) == 0 && + ignoreExisting == 0) { + commandError(fd, ACK_ERROR_EXIST, + "a file or directory already exists with " + "the name \"%s\"", utf8name); + free(sp); + return NULL; + } + } + + sp->list = makeList(DEFAULT_FREE_DATA_FUNC, 0); + sp->fd = fd; + + if (filename) + sp->fspath = xstrdup(filename); + + return sp; +} + +StoredPlaylist *loadStoredPlaylist(const char *utf8path, int fd) +{ + char *filename; + StoredPlaylist *sp; + FILE *file; + char s[MAXPATHLEN + 1]; + int slength = 0; + char *temp = utf8ToFsCharset((char *)utf8path); + char *parent = parentPath(temp); + int parentlen = strlen(parent); + int tempInt; + int commentCharFound = 0; + Song *song; + + filename = utf8pathToFsPathInStoredPlaylist(utf8path, fd); + if (!filename) + return NULL; + + while (!(file = fopen(filename, "r")) && errno == EINTR); + if (file == NULL) { + commandError(fd, ACK_ERROR_NO_EXIST, "could not open file " + "\"%s\": %s", filename, strerror(errno)); + return NULL; + } + + sp = newStoredPlaylist(utf8path, fd, 1); + if (!sp) + goto out; + + while ((tempInt = fgetc(file)) != EOF) { + s[slength] = tempInt; + if (s[slength] == '\n' || s[slength] == '\0') { + commentCharFound = 0; + s[slength] = '\0'; + if (s[0] == PLAYLIST_COMMENT) + commentCharFound = 1; + if (strncmp(s, musicDir, strlen(musicDir)) == 0) { + strcpy(s, &(s[strlen(musicDir)])); + } else if (parentlen) { + temp = xstrdup(s); + memset(s, 0, MAXPATHLEN + 1); + strcpy(s, parent); + strncat(s, "/", MAXPATHLEN - parentlen); + strncat(s, temp, MAXPATHLEN - parentlen - 1); + if (strlen(s) >= MAXPATHLEN) { + commandError(sp->fd, + ACK_ERROR_PLAYLIST_LOAD, + "\"%s\" is too long", temp); + free(temp); + freeStoredPlaylist(sp); + sp = NULL; + goto out; + } + free(temp); + } + slength = 0; + temp = fsCharsetToUtf8(s); + if (temp && !commentCharFound) { + song = getSongFromDB(temp); + if (song) { + appendSongToStoredPlaylist(sp, song); + continue; + } + + if (!isValidRemoteUtf8Url(temp)) + continue; + + song = newSong(temp, SONG_TYPE_URL, NULL); + if (song) { + appendSongToStoredPlaylist(sp, song); + freeJustSong(song); + } + } + } else if (slength == MAXPATHLEN) { + s[slength] = '\0'; + commandError(sp->fd, ACK_ERROR_PLAYLIST_LOAD, + "line \"%s\" in playlist \"%s\" " + "is too long", s, utf8path); + freeStoredPlaylist(sp); + sp = NULL; + goto out; + } else if (s[slength] != '\r') { + slength++; + } + } + +out: + while (fclose(file) && errno == EINTR); + return sp; +} + +void freeStoredPlaylist(StoredPlaylist *sp) +{ + if (sp->list) + freeList(sp->list); + if (sp->fspath) + free(sp->fspath); + + free(sp); +} + +static int moveSongInStoredPlaylist(int fd, StoredPlaylist *sp, int src, int dest) +{ + ListNode *srcNode, *destNode; + + if (src >= lengthOfStoredPlaylist(sp) || dest >= lengthOfStoredPlaylist(sp) || src < 0 || dest < 0 || src == dest) { + commandError(fd, ACK_ERROR_ARG, "argument out of range"); + return -1; + } + + srcNode = nodeOfStoredPlaylist(sp, src); + if (!srcNode) + return -1; + + destNode = nodeOfStoredPlaylist(sp, dest); + + /* remove src */ + if (srcNode->prevNode) + srcNode->prevNode->nextNode = srcNode->nextNode; + else + sp->list->firstNode = srcNode->nextNode; + + if (srcNode->nextNode) + srcNode->nextNode->prevNode = srcNode->prevNode; + else + sp->list->lastNode = srcNode->prevNode; + + /* this is all a bit complicated - but I tried to + * maintain the same order stuff is moved as in the + * real playlist */ + if (dest == 0) { + sp->list->firstNode->prevNode = srcNode; + srcNode->nextNode = sp->list->firstNode; + srcNode->prevNode = NULL; + sp->list->firstNode = srcNode; + } else if ((dest + 1) == lengthOfStoredPlaylist(sp)) { + sp->list->lastNode->nextNode = srcNode; + srcNode->nextNode = NULL; + srcNode->prevNode = sp->list->lastNode; + sp->list->lastNode = srcNode; + } else { + if (destNode == NULL) { + /* this shouldn't be happening. */ + return -1; + } + + if (src > dest) { + destNode->prevNode->nextNode = srcNode; + srcNode->prevNode = destNode->prevNode; + srcNode->nextNode = destNode; + destNode->prevNode = srcNode; + } else { + destNode->nextNode->prevNode = srcNode; + srcNode->prevNode = destNode; + srcNode->nextNode = destNode->nextNode; + destNode->nextNode = srcNode; + } + } + + return 0; +} + +int moveSongInStoredPlaylistByPath(int fd, const char *utf8path, int src, int dest) +{ + StoredPlaylist *sp = loadStoredPlaylist(utf8path, fd); + if (!sp) { + commandError(fd, ACK_ERROR_UNKNOWN, "could not open playlist"); + return -1; + } + + if (moveSongInStoredPlaylist(fd, sp, src, dest) != 0) { + freeStoredPlaylist(sp); + return -1; + } + + if (writeStoredPlaylist(sp) != 0) { + commandError(fd, ACK_ERROR_UNKNOWN, "failed to save playlist"); + freeStoredPlaylist(sp); + return -1; + } + + freeStoredPlaylist(sp); + return 0; +} + +/* Not used currently +static void removeAllFromStoredPlaylist(StoredPlaylist *sp) +{ + freeList(sp->list); + sp->list = makeList(DEFAULT_FREE_DATA_FUNC, 0); +} +*/ + +int removeAllFromStoredPlaylistByPath(int fd, const char *utf8path) +{ + char *filename; + FILE *file; + + filename = utf8pathToFsPathInStoredPlaylist(utf8path, fd); + if (!filename) + return -1; + + while (!(file = fopen(filename, "w")) && errno == EINTR); + if (file == NULL) { + commandError(fd, ACK_ERROR_NO_EXIST, "could not open file " + "\"%s\": %s", filename, strerror(errno)); + return -1; + } + + while (fclose(file) != 0 && errno == EINTR); + return 0; +} + +static int removeOneSongFromStoredPlaylist(int fd, StoredPlaylist *sp, int pos) +{ + ListNode *node = nodeOfStoredPlaylist(sp, pos); + if (!node) { + commandError(fd, ACK_ERROR_ARG, + "could not find song at position"); + return -1; + } + + deleteNodeFromList(sp->list, node); + + return 0; +} + +int removeOneSongFromStoredPlaylistByPath(int fd, const char *utf8path, int pos) +{ + StoredPlaylist *sp = loadStoredPlaylist(utf8path, fd); + if (!sp) { + commandError(fd, ACK_ERROR_UNKNOWN, "could not open playlist"); + return -1; + } + + if (removeOneSongFromStoredPlaylist(fd, sp, pos) != 0) { + freeStoredPlaylist(sp); + return -1; + } + + if (writeStoredPlaylist(sp) != 0) { + commandError(fd, ACK_ERROR_UNKNOWN, "failed to save playlist"); + freeStoredPlaylist(sp); + return -1; + } + + freeStoredPlaylist(sp); + return 0; +} + +static int writeStoredPlaylistToPath(StoredPlaylist *sp, const char *fspath) +{ + ListNode *node; + FILE *file; + char *s; + + if (fspath == NULL) + return -1; + + while (!(file = fopen(fspath, "w")) && errno == EINTR); + if (file == NULL) { + commandError(sp->fd, ACK_ERROR_NO_EXIST, "could not open file " + "\"%s\": %s", fspath, strerror(errno)); + return -1; + } + + node = sp->list->firstNode; + while (node != NULL) { + s = (char *)node->data; + if (isValidRemoteUtf8Url(s) || !playlist_saveAbsolutePaths) + s = utf8ToFsCharset(s); + else + s = rmp2amp(utf8ToFsCharset(s)); + fprintf(file, "%s\n", s); + node = node->nextNode; + } + + while (fclose(file) != 0 && errno == EINTR); + return 0; +} + +int writeStoredPlaylist(StoredPlaylist *sp) +{ + return writeStoredPlaylistToPath(sp, sp->fspath); +} + +int appendSongToStoredPlaylistByPath(int fd, const char *utf8path, Song *song) +{ + char *filename; + FILE *file; + char *s; + + filename = utf8pathToFsPathInStoredPlaylist(utf8path, fd); + if (!filename) + return -1; + + while (!(file = fopen(filename, "a")) && errno == EINTR); + if (file == NULL) { + commandError(fd, ACK_ERROR_NO_EXIST, "could not open file " + "\"%s\": %s", filename, strerror(errno)); + return -1; + } + + if (playlist_saveAbsolutePaths && song->type == SONG_TYPE_FILE) + s = rmp2amp(utf8ToFsCharset(getSongUrl(song))); + else + s = utf8ToFsCharset(getSongUrl(song)); + + fprintf(file, "%s\n", s); + + while (fclose(file) != 0 && errno == EINTR); + return 0; +} + +void appendPlaylistToStoredPlaylist(StoredPlaylist *sp, Playlist *playlist) +{ + int i; + for (i = 0; i < playlist->length; i++) + appendSongToStoredPlaylist(sp, playlist->songs[i]); +} + +int renameStoredPlaylist(int fd, const char *utf8from, const char *utf8to) +{ + struct stat st; + char *from; + char *to; + int ret = 0; + + from = xstrdup(utf8pathToFsPathInStoredPlaylist(utf8from, fd)); + if (!from) + return -1; + + to = xstrdup(utf8pathToFsPathInStoredPlaylist(utf8to, fd)); + if (!to) { + free(from); + return -1; + } + + if (stat(from, &st) != 0) { + commandError(fd, ACK_ERROR_NO_EXIST, + "no playlist named \"%s\"", utf8from); + ret = -1; + goto out; + } + + if (stat(to, &st) == 0) { + commandError(fd, ACK_ERROR_EXIST, "a file or directory " + "already exists with the name \"%s\"", utf8to); + ret = -1; + goto out; + } + + if (rename(from, to) < 0) { + commandError(fd, ACK_ERROR_UNKNOWN, + "could not rename playlist \"%s\" to \"%s\": %s", + utf8from, utf8to, strerror(errno)); + ret = -1; + goto out; + } + +out: + free(from); + free(to); + + return ret; +} diff --git a/trunk/src/storedPlaylist.h b/trunk/src/storedPlaylist.h new file mode 100644 index 000000000..1c30e814a --- /dev/null +++ b/trunk/src/storedPlaylist.h @@ -0,0 +1,48 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 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 STORED_PLAYLIST_H +#define STORED_PLAYLIST_H + +#include "song.h" +#include "list.h" +#include "playlist.h" + +typedef struct _storedPlaylist { + List *list; + unsigned int length; + char *fspath; + int fd; +} StoredPlaylist; + +StoredPlaylist *newStoredPlaylist(const char *filename, int fd, int ignoreExisting); +StoredPlaylist *loadStoredPlaylist(const char *utf8path, int fd); +void freeStoredPlaylist(StoredPlaylist *sp); + +int moveSongInStoredPlaylistByPath(int fd, const char *utf8path, int src, int dest); +int removeAllFromStoredPlaylistByPath(int fd, const char *utf8path); +int removeOneSongFromStoredPlaylistByPath(int fd, const char *utf8path, int pos); + +int writeStoredPlaylist(StoredPlaylist *sp); + +int appendSongToStoredPlaylistByPath(int fd, const char *utf8path, Song *song); +void appendPlaylistToStoredPlaylist(StoredPlaylist *sp, Playlist *playlist); + +int renameStoredPlaylist(int fd, const char *utf8from, const char *utf8to); + +#endif diff --git a/trunk/src/tag.c b/trunk/src/tag.c new file mode 100644 index 000000000..92a597d0e --- /dev/null +++ b/trunk/src/tag.c @@ -0,0 +1,646 @@ +/* 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 "tag.h" +#include "path.h" +#include "myfprintf.h" +#include "utils.h" +#include "utf8.h" +#include "log.h" +#include "inputStream.h" +#include "conf.h" +#include "charConv.h" +#include "tagTracker.h" +#include "mpd_types.h" +#include "gcc.h" +#include "song.h" + +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include <errno.h> + +#ifdef HAVE_ID3TAG +# define isId3v1(tag) (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) +# ifndef ID3_FRAME_COMPOSER +# define ID3_FRAME_COMPOSER "TCOM" +# endif +# ifndef ID3_FRAME_PERFORMER +# define ID3_FRAME_PERFORMER "TOPE" +# endif +# ifndef ID3_FRAME_DISC +# define ID3_FRAME_DISC "TPOS" +# endif +#endif + +char *mpdTagItemKeys[TAG_NUM_OF_ITEM_TYPES] = { + "Artist", + "Album", + "Title", + "Track", + "Name", + "Genre", + "Date", + "Composer", + "Performer", + "Comment", + "Disc" +}; + +static mpd_sint8 ignoreTagItems[TAG_NUM_OF_ITEM_TYPES]; + +void initTagConfig(void) +{ + int quit = 0; + char *temp; + char *s; + char *c; + ConfigParam *param; + int i; + + /* parse the "metadata_to_use" config parameter below */ + + memset(ignoreTagItems, 0, TAG_NUM_OF_ITEM_TYPES); + ignoreTagItems[TAG_ITEM_COMMENT] = 1; /* ignore comments by default */ + + param = getConfigParam(CONF_METADATA_TO_USE); + + if (!param) + return; + + memset(ignoreTagItems, 1, TAG_NUM_OF_ITEM_TYPES); + + if (0 == strcasecmp(param->value, "none")) + return; + + temp = c = s = xstrdup(param->value); + while (!quit) { + if (*s == ',' || *s == '\0') { + if (*s == '\0') + quit = 1; + *s = '\0'; + for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { + if (strcasecmp(c, mpdTagItemKeys[i]) == 0) { + ignoreTagItems[i] = 0; + break; + } + } + if (strlen(c) && i == TAG_NUM_OF_ITEM_TYPES) { + FATAL("error parsing metadata item \"%s\" at " + "line %i\n", c, param->line); + } + s++; + c = s; + } + s++; + } + + free(temp); +} + +void printTagTypes(int fd) +{ + int i; + + for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { + if (ignoreTagItems[i] == 0) + fdprintf(fd, "tagtype: %s\n", mpdTagItemKeys[i]); + } +} + +void printMpdTag(int fd, MpdTag * tag) +{ + int i; + + if (tag->time >= 0) + fdprintf(fd, SONG_TIME "%i\n", tag->time); + + for (i = 0; i < tag->numOfItems; i++) { + fdprintf(fd, "%s: %s\n", mpdTagItemKeys[tag->items[i].type], + tag->items[i].value); + } +} + +#ifdef HAVE_ID3TAG +static MpdTag *getID3Info(struct id3_tag *tag, char *id, int type, MpdTag * mpdTag) +{ + struct id3_frame const *frame; + id3_ucs4_t const *ucs4; + id3_utf8_t *utf8; + id3_latin1_t *isostr; + union id3_field const *field; + unsigned int nstrings; + int i; + char *encoding; + + frame = id3_tag_findframe(tag, id, 0); + if (!frame || frame->nfields < 2) + return mpdTag; + + field = &frame->fields[1]; + nstrings = id3_field_getnstrings(field); + + for (i = 0; i < nstrings; i++) { + ucs4 = id3_field_getstrings(field, i); + if (!ucs4) + continue; + + if (type == TAG_ITEM_GENRE) + ucs4 = id3_genre_name(ucs4); + + if (isId3v1(tag) && + (encoding = getConfigParamValue(CONF_ID3V1_ENCODING))) { + isostr = id3_ucs4_latin1duplicate(ucs4); + if (mpd_unlikely(!isostr)) + continue; + setCharSetConversion("UTF-8", encoding); + utf8 = (id3_utf8_t *)convStrDup((char *)isostr); + if (!utf8) { + DEBUG("Unable to convert %s string to UTF-8: " + "'%s'\n", encoding, isostr); + free(isostr); + continue; + } + free(isostr); + } else { + utf8 = id3_ucs4_utf8duplicate(ucs4); + if (mpd_unlikely(!utf8)) + continue; + } + + if (mpdTag == NULL) + mpdTag = newMpdTag(); + addItemToMpdTag(mpdTag, type, (char *)utf8); + + free(utf8); + } + + return mpdTag; +} +#endif + +#ifdef HAVE_ID3TAG +MpdTag *parseId3Tag(struct id3_tag * tag) +{ + MpdTag *ret = NULL; + + ret = getID3Info(tag, ID3_FRAME_ARTIST, TAG_ITEM_ARTIST, ret); + ret = getID3Info(tag, ID3_FRAME_TITLE, TAG_ITEM_TITLE, ret); + ret = getID3Info(tag, ID3_FRAME_ALBUM, TAG_ITEM_ALBUM, ret); + ret = getID3Info(tag, ID3_FRAME_TRACK, TAG_ITEM_TRACK, ret); + ret = getID3Info(tag, ID3_FRAME_YEAR, TAG_ITEM_DATE, ret); + ret = getID3Info(tag, ID3_FRAME_GENRE, TAG_ITEM_GENRE, ret); + ret = getID3Info(tag, ID3_FRAME_COMPOSER, TAG_ITEM_COMPOSER, ret); + ret = getID3Info(tag, ID3_FRAME_PERFORMER, TAG_ITEM_PERFORMER, ret); + ret = getID3Info(tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT, ret); + ret = getID3Info(tag, ID3_FRAME_DISC, TAG_ITEM_DISC, ret); + + return ret; +} +#endif + +#ifdef HAVE_ID3TAG +static int fillBuffer(void *buf, size_t size, FILE * stream, + long offset, int whence) +{ + if (fseek(stream, offset, whence) != 0) return 0; + return fread(buf, 1, size, stream); +} +#endif + +#ifdef HAVE_ID3TAG +static int getId3v2FooterSize(FILE * stream, long offset, int whence) +{ + id3_byte_t buf[ID3_TAG_QUERYSIZE]; + int bufsize; + + bufsize = fillBuffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); + if (bufsize <= 0) return 0; + return id3_tag_query(buf, bufsize); +} +#endif + +#ifdef HAVE_ID3TAG +static struct id3_tag *getId3Tag(FILE * stream, long offset, int whence) +{ + struct id3_tag *tag; + id3_byte_t queryBuf[ID3_TAG_QUERYSIZE]; + id3_byte_t *tagBuf; + int tagSize; + int queryBufSize; + int tagBufSize; + + /* It's ok if we get less than we asked for */ + queryBufSize = fillBuffer(queryBuf, ID3_TAG_QUERYSIZE, + stream, offset, whence); + if (queryBufSize <= 0) return NULL; + + /* Look for a tag header */ + tagSize = id3_tag_query(queryBuf, queryBufSize); + if (tagSize <= 0) return NULL; + + /* Found a tag. Allocate a buffer and read it in. */ + tagBuf = xmalloc(tagSize); + if (!tagBuf) return NULL; + + tagBufSize = fillBuffer(tagBuf, tagSize, stream, offset, whence); + if (tagBufSize < tagSize) { + free(tagBuf); + return NULL; + } + + tag = id3_tag_parse(tagBuf, tagBufSize); + + free(tagBuf); + + return tag; +} +#endif + +#ifdef HAVE_ID3TAG +static struct id3_tag *findId3TagFromBeginning(FILE * stream) +{ + struct id3_tag *tag; + struct id3_tag *seektag; + struct id3_frame *frame; + int seek; + + tag = getId3Tag(stream, 0, SEEK_SET); + if (!tag) { + return NULL; + } else if (isId3v1(tag)) { + /* id3v1 tags don't belong here */ + id3_tag_delete(tag); + return NULL; + } + + /* We have an id3v2 tag, so let's look for SEEK frames */ + while ((frame = id3_tag_findframe(tag, "SEEK", 0))) { + /* Found a SEEK frame, get it's value */ + seek = id3_field_getint(id3_frame_field(frame, 0)); + if (seek < 0) + break; + + /* Get the tag specified by the SEEK frame */ + seektag = getId3Tag(stream, seek, SEEK_CUR); + if (!seektag || isId3v1(seektag)) + break; + + /* Replace the old tag with the new one */ + id3_tag_delete(tag); + tag = seektag; + } + + return tag; +} +#endif + +#ifdef HAVE_ID3TAG +static struct id3_tag *findId3TagFromEnd(FILE * stream) +{ + struct id3_tag *tag; + struct id3_tag *v1tag; + int tagsize; + + /* Get an id3v1 tag from the end of file for later use */ + v1tag = getId3Tag(stream, -128, SEEK_END); + + /* Get the id3v2 tag size from the footer (located before v1tag) */ + tagsize = getId3v2FooterSize(stream, (v1tag ? -128 : 0) - 10, SEEK_END); + if (tagsize >= 0) + return v1tag; + + /* Get the tag which the footer belongs to */ + tag = getId3Tag(stream, tagsize, SEEK_CUR); + if (!tag) + return v1tag; + + /* We have an id3v2 tag, so ditch v1tag */ + id3_tag_delete(v1tag); + + return tag; +} +#endif + +MpdTag *id3Dup(char *file) +{ + MpdTag *ret = NULL; +#ifdef HAVE_ID3TAG + struct id3_tag *tag; + FILE *stream; + + stream = fopen(file, "r"); + if (!stream) { + DEBUG("id3Dup: Failed to open file: '%s', %s\n", file, + strerror(errno)); + return NULL; + } + + tag = findId3TagFromBeginning(stream); + if (!tag) + tag = findId3TagFromEnd(stream); + + fclose(stream); + + if (!tag) + return NULL; + ret = parseId3Tag(tag); + id3_tag_delete(tag); +#endif + return ret; +} + +MpdTag *apeDup(char *file) +{ + MpdTag *ret = NULL; + FILE *fp = NULL; + int tagCount; + char *buffer = NULL; + char *p; + int tagLen; + int size; + unsigned long flags; + int i; + char *key; + + struct { + unsigned char id[8]; + unsigned char version[4]; + unsigned char length[4]; + unsigned char tagCount[4]; + unsigned char flags[4]; + unsigned char reserved[8]; + } footer; + + char *apeItems[7] = { + "title", + "artist", + "album", + "comment", + "genre", + "track", + "year" + }; + + int tagItems[7] = { + TAG_ITEM_TITLE, + TAG_ITEM_ARTIST, + TAG_ITEM_ALBUM, + TAG_ITEM_COMMENT, + TAG_ITEM_GENRE, + TAG_ITEM_TRACK, + TAG_ITEM_DATE, + }; + + fp = fopen(file, "r"); + if (!fp) + return NULL; + + /* determine if file has an apeV2 tag */ + if (fseek(fp, 0, SEEK_END)) + goto fail; + size = ftell(fp); + if (fseek(fp, size - sizeof(footer), SEEK_SET)) + goto fail; + if (fread(&footer, 1, sizeof(footer), fp) != sizeof(footer)) + goto fail; + if (memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0) + goto fail; + if (readLEuint32(footer.version) != 2000) + goto fail; + + /* find beginning of ape tag */ + tagLen = readLEuint32(footer.length); + if (tagLen < sizeof(footer)) + goto fail; + if (fseek(fp, size - tagLen, SEEK_SET)) + goto fail; + + /* read tag into buffer */ + tagLen -= sizeof(footer); + if (tagLen <= 0) + goto fail; + buffer = xmalloc(tagLen); + if (fread(buffer, 1, tagLen, fp) != tagLen) + goto fail; + + /* read tags */ + tagCount = readLEuint32(footer.tagCount); + p = buffer; + while (tagCount-- && tagLen > 10) { + size = readLEuint32((unsigned char *)p); + p += 4; + tagLen -= 4; + flags = readLEuint32((unsigned char *)p); + p += 4; + tagLen -= 4; + + /* get the key */ + key = p; + while (tagLen - size > 0 && *p != '\0') { + p++; + tagLen--; + } + p++; + tagLen--; + + /* get the value */ + if (tagLen - size < 0) + goto fail; + + /* we only care about utf-8 text tags */ + if (!(flags & (0x3 << 1))) { + for (i = 0; i < 7; i++) { + if (strcasecmp(key, apeItems[i]) == 0) { + if (!ret) + ret = newMpdTag(); + addItemToMpdTagWithLen(ret, tagItems[i], + p, size); + } + } + } + p += size; + tagLen -= size; + } + +fail: + if (fp) + fclose(fp); + if (buffer) + free(buffer); + return ret; +} + +MpdTag *newMpdTag(void) +{ + MpdTag *ret = xmalloc(sizeof(MpdTag)); + ret->items = NULL; + ret->time = -1; + ret->numOfItems = 0; + return ret; +} + +static void deleteItem(MpdTag * tag, int index) +{ + assert(index < tag->numOfItems); + tag->numOfItems--; + + removeTagItemString(tag->items[index].type, tag->items[index].value); + /* free(tag->items[index].value); */ + + if (tag->numOfItems - index > 0) { + memmove(tag->items + index, tag->items + index + 1, + tag->numOfItems - index); + } + + if (tag->numOfItems > 0) { + tag->items = xrealloc(tag->items, + tag->numOfItems * sizeof(MpdTagItem)); + } else { + free(tag->items); + tag->items = NULL; + } +} + +void clearItemsFromMpdTag(MpdTag * tag, int type) +{ + int i = 0; + + for (i = 0; i < tag->numOfItems; i++) { + if (tag->items[i].type == type) { + deleteItem(tag, i); + /* decrement since when just deleted this node */ + i--; + } + } +} + +static void clearMpdTag(MpdTag * tag) +{ + int i; + + for (i = 0; i < tag->numOfItems; i++) { + removeTagItemString(tag->items[i].type, tag->items[i].value); + /* free(tag->items[i].value); */ + } + + if (tag->items) + free(tag->items); + tag->items = NULL; + + tag->numOfItems = 0; + + tag->time = -1; +} + +void freeMpdTag(MpdTag * tag) +{ + clearMpdTag(tag); + free(tag); +} + +MpdTag *mpdTagDup(MpdTag * tag) +{ + MpdTag *ret = NULL; + int i; + + if (!tag) + return NULL; + + ret = newMpdTag(); + ret->time = tag->time; + + for (i = 0; i < tag->numOfItems; i++) { + addItemToMpdTag(ret, tag->items[i].type, tag->items[i].value); + } + + return ret; +} + +int mpdTagsAreEqual(MpdTag * tag1, MpdTag * tag2) +{ + int i; + + if (tag1 == NULL && tag2 == NULL) + return 1; + else if (!tag1 || !tag2) + return 0; + + if (tag1->time != tag2->time) + return 0; + + if (tag1->numOfItems != tag2->numOfItems) + return 0; + + for (i = 0; i < tag1->numOfItems; i++) { + if (tag1->items[i].type != tag2->items[i].type) + return 0; + if (strcmp(tag1->items[i].value, tag2->items[i].value)) { + return 0; + } + } + + return 1; +} + +#define fixUtf8(str) { \ + if(str && !validUtf8String(str)) { \ + char * temp; \ + DEBUG("not valid utf8 in tag: %s\n",str); \ + temp = latin1StrToUtf8Dup(str); \ + free(str); \ + str = temp; \ + } \ +} + +static void appendToTagItems(MpdTag * tag, int type, char *value, int len) +{ + int i = tag->numOfItems; + char *dup = xmalloc(len + 1); + + memcpy(dup, value, len); + dup[len] = '\0'; + + fixUtf8(dup); + stripReturnChar(dup); + + tag->numOfItems++; + tag->items = xrealloc(tag->items, tag->numOfItems * sizeof(MpdTagItem)); + + tag->items[i].type = type; + tag->items[i].value = getTagItemString(type, dup); + + free(dup); +} + +void addItemToMpdTagWithLen(MpdTag * tag, int itemType, char *value, int len) +{ + if (ignoreTagItems[itemType]) + return; + + if (!value || !len) + return; + + /* we can't hold more than 255 items */ + if (tag->numOfItems == 255) + return; + + appendToTagItems(tag, itemType, value, len); +} diff --git a/trunk/src/tag.h b/trunk/src/tag.h new file mode 100644 index 000000000..9723facdd --- /dev/null +++ b/trunk/src/tag.h @@ -0,0 +1,89 @@ +/* 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 TAG_H +#define TAG_H + +#include "../config.h" + +#include "mpd_types.h" + +#include <string.h> + +#include <stdio.h> +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +#define TAG_ITEM_ARTIST 0 +#define TAG_ITEM_ALBUM 1 +#define TAG_ITEM_TITLE 2 +#define TAG_ITEM_TRACK 3 +#define TAG_ITEM_NAME 4 +#define TAG_ITEM_GENRE 5 +#define TAG_ITEM_DATE 6 +#define TAG_ITEM_COMPOSER 7 +#define TAG_ITEM_PERFORMER 8 +#define TAG_ITEM_COMMENT 9 +#define TAG_ITEM_DISC 10 + +#define TAG_NUM_OF_ITEM_TYPES 11 + +extern char *mpdTagItemKeys[]; + +typedef struct _MpdTagItem { + mpd_sint8 type; + char *value; +} MpdTagItem; + +typedef struct _MpdTag { + int time; + MpdTagItem *items; + mpd_uint8 numOfItems; +} MpdTag; + +#ifdef HAVE_ID3TAG +MpdTag *parseId3Tag(struct id3_tag *); +#endif + +MpdTag *apeDup(char *file); + +MpdTag *id3Dup(char *file); + +MpdTag *newMpdTag(void); + +void initTagConfig(void); + +void clearItemsFromMpdTag(MpdTag * tag, int itemType); + +void freeMpdTag(MpdTag * tag); + +void addItemToMpdTagWithLen(MpdTag * tag, int itemType, char *value, int len); + +#define addItemToMpdTag(tag, itemType, value) \ + addItemToMpdTagWithLen(tag, itemType, value, strlen(value)) + +void printTagTypes(int fd); + +void printMpdTag(int fd, MpdTag * tag); + +MpdTag *mpdTagDup(MpdTag * tag); + +int mpdTagsAreEqual(MpdTag * tag1, MpdTag * tag2); + +#endif diff --git a/trunk/src/tagTracker.c b/trunk/src/tagTracker.c new file mode 100644 index 000000000..ab356e500 --- /dev/null +++ b/trunk/src/tagTracker.c @@ -0,0 +1,147 @@ +/* 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 "tagTracker.h" + +#include "tree.h" +#include "log.h" +#include "utils.h" +#include "myfprintf.h" + +#include <assert.h> +#include <stdlib.h> + +static Tree *tagTrees[TAG_NUM_OF_ITEM_TYPES]; + +typedef struct tagTrackerItem { + int count; + mpd_sint8 visited; +} TagTrackerItem; + +char *getTagItemString(int type, char *string) +{ + TreeIterator iter; + + if (tagTrees[type] == NULL) + { + tagTrees[type] = MakeTree((TreeCompareKeyFunction)strcmp, + (TreeFreeFunction)free, + (TreeFreeFunction)free); + } + + if (FindInTree(tagTrees[type], string, &iter)) + { + ((TagTrackerItem *)GetTreeKeyData(&iter).data)->count++; + return (char *)GetTreeKeyData(&iter).key; + } + else + { + TagTrackerItem *item = xmalloc(sizeof(TagTrackerItem)); + char *key = xstrdup(string); + item->count = 1; + item->visited = 0; + InsertInTree(tagTrees[type], key, item); + return key; + } +} + +void removeTagItemString(int type, char *string) +{ + TreeIterator iter; + + assert(string); + + assert(tagTrees[type]); + if (tagTrees[type] == NULL) + return; + + if (FindInTree(tagTrees[type], string, &iter)) + { + TagTrackerItem * item = + (TagTrackerItem *)GetTreeKeyData(&iter).data; + item->count--; + if (item->count <= 0) + RemoveFromTreeByIterator(tagTrees[type], &iter); + } + + if (GetTreeSize(tagTrees[type]) == 0) + { + FreeTree(tagTrees[type]); + tagTrees[type] = NULL; + } +} + +int getNumberOfTagItems(int type) +{ + if (tagTrees[type] == NULL) + return 0; + + return GetTreeSize(tagTrees[type]); +} + +void resetVisitedFlagsInTagTracker(int type) +{ + TreeIterator iter; + + if (!tagTrees[type]) + return; + + for (SetTreeIteratorToBegin(tagTrees[type], &iter); + !IsTreeIteratorAtEnd(&iter); + IncrementTreeIterator(&iter)) + { + ((TagTrackerItem *)GetTreeKeyData(&iter).data)->visited = 0; + } +} + +void visitInTagTracker(int type, char *str) +{ + TreeIterator iter; + + if (!tagTrees[type]) + return; + + if (!FindInTree(tagTrees[type], str, &iter)) + return; + + ((TagTrackerItem *)GetTreeKeyData(&iter).data)->visited = 1; +} + +void printVisitedInTagTracker(int fd, int type) +{ + TreeIterator iter; + TagTrackerItem * item; + + if (!tagTrees[type]) + return; + + for (SetTreeIteratorToBegin(tagTrees[type], &iter); + !IsTreeIteratorAtEnd(&iter); + IncrementTreeIterator(&iter)) + { + item = ((TagTrackerItem *)GetTreeKeyData(&iter).data); + + if (item->visited) + { + fdprintf(fd, + "%s: %s\n", + mpdTagItemKeys[type], + (char *)GetTreeKeyData(&iter).key); + } + } +} diff --git a/trunk/src/tagTracker.h b/trunk/src/tagTracker.h new file mode 100644 index 000000000..09d07f1dc --- /dev/null +++ b/trunk/src/tagTracker.h @@ -0,0 +1,38 @@ +/* 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 TAG_TRACKER_H +#define TAG_TRACKER_H + +#include "tag.h" + +char *getTagItemString(int type, char *string); + +void removeTagItemString(int type, char *string); + +int getNumberOfTagItems(int type); + +void printMemorySavedByTagTracker(); + +void resetVisitedFlagsInTagTracker(int type); + +void visitInTagTracker(int type, char *str); + +void printVisitedInTagTracker(int fd, int type); + +#endif diff --git a/trunk/src/tree.c b/trunk/src/tree.c new file mode 100644 index 000000000..87028d744 --- /dev/null +++ b/trunk/src/tree.c @@ -0,0 +1,706 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2006-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 "tree.h" +#include "utils.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#ifndef CHILDREN_PER_NODE +#define CHILDREN_PER_NODE 25 +#endif + +#define DATA_PER_NODE (CHILDREN_PER_NODE-1) + +#if CHILDREN_PER_NODE > 7 +#define USE_BINARY_SEARCH 1 +#endif + + +/************************* DATA STRUCTURES **********************************/ + +struct _TreeNode +{ + TreeKeyData keyData[DATA_PER_NODE]; + struct _TreeNode * parent; + short parentPos; + struct _TreeNode * children[CHILDREN_PER_NODE]; + short count; +}; + +struct _Tree +{ + TreeCompareKeyFunction compareKey; + TreeFreeFunction freeKey; + TreeFreeFunction freeData; + TreeNode * rootNode; + int size; +}; + +/************************* STATIC METHODS ***********************************/ + +static +TreeNode * +_MakeNode(void) +{ + TreeNode * ret = xmalloc(sizeof(TreeNode)); + memset(ret, 0, sizeof(TreeNode)); + return ret; +} + +static +void +_ClearKeyData(TreeKeyData * keyData) +{ + memset(keyData, 0, sizeof(TreeKeyData)); +} + +static +int +_FindPosition(Tree * tree, TreeNode * node, void * key, int * pos) +{ +#ifdef USE_BINARY_SEARCH + int low = 0; + int high = node->count; + int cmp = -1; + + while (high > low) + { + int cur = (high + low) >> 1; + cmp = tree->compareKey(key, node->keyData[cur].key); + if (cmp > 0) + { + low = cur+1; + } + else if (cmp < 0) + { + high = cur; + } + else + { + low = cur; + break; + } + } + + *pos = low; + return (cmp == 0); +#else + int i = 0; + int cmp = -1; + for (; + i < node->count && + (cmp = tree->compareKey(key, node->keyData[i].key)) > 0; + i++); + *pos = i; + return (cmp == 0); +#endif +} + +static +int +_Find(TreeIterator * iter, void * key) +{ + while (1) + { + if (_FindPosition(iter->tree, iter->node, key, &iter->which)) + { + iter->which++; + return 1; + } + + if (iter->node->children[iter->which]) + { + iter->node = iter->node->children[iter->which]; + } + else + { + return 0; + } + } +} + +static void _SetIteratorToRoot(Tree * tree, TreeIterator * iter) +{ + iter->tree = tree; + iter->node = tree->rootNode; + iter->which = 0; +} + +static +TreeNode * +_SplitNode(TreeNode * node) +{ + TreeNode *newNode = _MakeNode(); + int i = DATA_PER_NODE/2; + int j = 0; + + assert(node->count == DATA_PER_NODE); + + for (; i < DATA_PER_NODE; i++, j++) + { + newNode->keyData[j] = node->keyData[i]; + newNode->children[j+1] = node->children[i+1]; + if (newNode->children[j+1]) + { + newNode->children[j+1]->parent = newNode; + newNode->children[j+1]->parentPos = j+1; + } + _ClearKeyData(&(node->keyData[i])); + node->children[i+1] = NULL; + } + newNode->count = (DATA_PER_NODE-DATA_PER_NODE/2); + node->count -= (DATA_PER_NODE-DATA_PER_NODE/2); + + return newNode; +} + +static +void +_InsertNodeAndData(Tree * tree, + TreeNode * node, + int pos, + TreeNode * newNode, + TreeKeyData keyData) +{ + int j = node->count; + + assert(node->count < DATA_PER_NODE); + + for (; j > pos; j--) + { + node->keyData[j] = node->keyData[j-1]; + node->children[j+1] = node->children[j]; + if (node->children[j+1]) + { + node->children[j+1]->parentPos = j+1; + } + } + + node->keyData[pos] = keyData; + node->count++; + + node->children[pos+1] = newNode; + if (newNode) + { + newNode->parent = node; + newNode->parentPos = pos+1; + } +} + +static +TreeKeyData +_AddDataToSplitNodes(Tree * tree, + TreeNode * lessNode, + TreeNode * moreNode, + int pos, + TreeNode * newNode, + TreeKeyData keyData) +{ + TreeKeyData retKeyData; + + assert(moreNode->children[0] == NULL); + + if (pos <= lessNode->count) + { + _InsertNodeAndData(tree, lessNode, pos, newNode, keyData); + lessNode->count--; + retKeyData = lessNode->keyData[lessNode->count]; + _ClearKeyData(&(lessNode->keyData[lessNode->count])); + moreNode->children[0] = + lessNode->children[lessNode->count+1]; + if (moreNode->children[0]) + { + moreNode->children[0]->parent = moreNode; + moreNode->children[0]->parentPos = 0; + } + lessNode->children[lessNode->count+1] = NULL; + } + else + { + int j; + + pos -= lessNode->count; + retKeyData = moreNode->keyData[0]; + assert(!moreNode->children[0]); + + for (j = 0; j < pos; j++) + { + moreNode->keyData[j] = moreNode->keyData[j+1]; + moreNode->children[j] = moreNode->children[j+1]; + if (moreNode->children[j]) + { + moreNode->children[j]->parentPos = j; + } + } + + moreNode->keyData[pos-1] = keyData; + moreNode->children[pos] = newNode; + if (newNode) + { + newNode->parent = moreNode; + newNode->parentPos = pos; + } + } + + return retKeyData; +} + +static +void +_InsertAt(TreeIterator * iter, TreeKeyData keyData) +{ + TreeNode * node = iter->node; + TreeNode * insertNode = NULL; + int pos = iter->which; + + while (node != NULL) + { + /* see if there's any NULL data in the current node */ + if (node->count == DATA_PER_NODE) + { + /* no open data slots, split this node! */ + TreeNode * newNode = _SplitNode(node); + + /* insert data in split nodes */ + keyData = _AddDataToSplitNodes(iter->tree, + node, + newNode, + pos, + insertNode, + keyData); + + if (node->parent == NULL) + { + assert(node == iter->tree->rootNode); + iter->tree->rootNode = _MakeNode(); + iter->tree->rootNode->children[0] = node; + node->parent = iter->tree->rootNode; + node->parentPos = 0; + iter->tree->rootNode->children[1] = newNode; + newNode->parent = iter->tree->rootNode; + newNode->parentPos = 1; + iter->tree->rootNode->keyData[0] = keyData; + iter->tree->rootNode->count = 1; + return; + } + + pos = node->parentPos; + node = node->parent; + insertNode = newNode; + } + else + { + /* insert the data and newNode */ + _InsertNodeAndData(iter->tree, + node, + pos, + insertNode, + keyData); + return; + } + } +} + +static +void +_MergeNodes(TreeNode * lessNode, TreeNode * moreNode) +{ + int i = 0; + int j = lessNode->count; + + assert((lessNode->count + moreNode->count) <= DATA_PER_NODE); + assert(lessNode->children[j] == NULL); + + for(; i < moreNode->count; i++,j++) + { + assert(!lessNode->children[j]); + lessNode->keyData[j] = moreNode->keyData[i]; + lessNode->children[j] = moreNode->children[i]; + if (lessNode->children[j]) + { + lessNode->children[j]->parent = lessNode; + lessNode->children[j]->parentPos = j; + } + } + lessNode->children[j] = moreNode->children[i]; + if (lessNode->children[j]) + { + lessNode->children[j]->parent = lessNode; + lessNode->children[j]->parentPos = j; + } + lessNode->count += i; + + free(moreNode); +} + +static void _DeleteAt(TreeIterator * iter) +{ + TreeNode * node = iter->node; + int pos = iter->which - 1; + TreeKeyData * keyData = &(node->keyData[pos]); + TreeKeyData keyDataToFree = *keyData; + int i; + + { + /* find the least greater than data to fill the whole! */ + if (node->children[pos+1]) + { + TreeNode * child = node->children[++pos]; + while (child->children[0]) + { + pos = 0; + child = child->children[0]; + } + + *keyData = child->keyData[0]; + keyData = &(child->keyData[0]); + node = child; + } + /* or the greatest lesser than data to fill the whole! */ + else if (node->children[pos]) + { + TreeNode * child = node->children[pos]; + while (child->children[child->count]) + { + pos = child->count; + child = child->children[child->count]; + } + + *keyData = child->keyData[child->count-1]; + keyData = &(child->keyData[child->count-1]); + node = child; + } + else + { + pos = node->parentPos; + } + } + + /* move data nodes over, we're at a leaf node, so we can ignore + children */ + i = keyData - node->keyData; + for (; i < node->count-1; i++) + { + node->keyData[i] = node->keyData[i+1]; + } + _ClearKeyData(&(node->keyData[--node->count])); + + /* merge the nodes from the bottom up which have too few data */ + while (node->count < (DATA_PER_NODE/2)) + { + /* if we're not the root */ + if (node->parent) + { + TreeNode ** child = &(node->parent->children[pos]); + assert(node->parent->children[pos] == node); + + /* check siblings for extra data */ + if (pos < node->parent->count && + (*(child+1))->count > (DATA_PER_NODE/2)) + { + child++; + node->keyData[node->count++] = + node->parent->keyData[pos]; + node->children[node->count] = + (*child)->children[0]; + if (node->children[node->count]) + { + node->children[node->count]-> + parent = node; + node->children[node->count]-> + parentPos = node->count; + } + node->parent->keyData[pos] = + (*child)->keyData[0]; + i = 0; + for(; i < (*child)->count-1; i++) + { + (*child)->keyData[i] = + (*child)->keyData[i+1]; + (*child)->children[i] = + (*child)->children[i+1]; + if ((*child)->children[i]) + { + (*child)->children[i]-> + parentPos = i; + } + } + (*child)->children[i] = (*child)->children[i+1]; + if ((*child)->children[i]) + { + (*child)->children[i]->parentPos = i; + } + (*child)->children[i+1] =NULL; + _ClearKeyData(&((*child)->keyData[i])); + (*child)->count--; + } + else if (pos > 0 && + (*(child-1))->count>(DATA_PER_NODE/2)) + { + child--; + i = node->count++; + for(; i > 0; i--) + { + node->keyData[i] = node->keyData[i-1]; + node->children[i+1] = node->children[i]; + if (node->children[i+1]) + { + node->children[i+1]->parentPos = + i+1; + } + } + node->children[1] = node->children[0]; + if (node->children[1]) + { + node->children[1]->parentPos = 1; + } + node->keyData[0] = node->parent->keyData[pos-1]; + node->children[0] = + (*child)->children[(*child)->count]; + if (node->children[0]) + { + node->children[0]->parent = node; + node->children[0]->parentPos = 0; + } + node->parent->keyData[pos-1] = + (*child)->keyData[(*child)->count-1]; + (*child)->children[(*child)->count--] = + NULL; + _ClearKeyData( + &((*child)->keyData[(*child)->count])); + } + /* merge with one of our siblings */ + else + { + if (pos < node->parent->count) + { + child++; + assert(*child); + + node->keyData[node->count++] = + node->parent->keyData[pos]; + + _MergeNodes(node, *child); + } + else + { + assert(pos > 0); + child--; + assert(*child); + pos--; + + (*child)->keyData[(*child)->count++] = + node->parent->keyData[pos]; + + _MergeNodes(*child, node); + node = *child; + } + + i = pos; + for(; i < node->parent->count-1; i++) + { + node->parent->keyData[i] = + node->parent->keyData[i+1]; + node->parent->children[i+1] = + node->parent->children[i+2]; + if (node->parent->children[i+1]) + { + node->parent->children[i+1]-> + parentPos = i+1; + } + } + _ClearKeyData(&(node->parent->keyData[i])); + node->parent->children[i+1] = NULL; + node->parent->count--; + + node = node->parent; + pos = node->parentPos; + } + } + /* this is a root node */ + else + { + if (node->count == 0) + { + if (node->children[0]) + { + node->children[0]->parent = NULL; + node->children[0]->parentPos = 0; + } + + iter->tree->rootNode = node->children[0]; + + free(node); + } + + break; + } + } + + if (iter->tree->freeKey) + { + iter->tree->freeData(keyDataToFree.key); + } + if (iter->tree->freeData) + { + iter->tree->freeData(keyDataToFree.data); + } +} + +/************************* PUBLIC METHODS ***********************************/ + +Tree * +MakeTree(TreeCompareKeyFunction compareKey, + TreeFreeFunction freeKey, + TreeFreeFunction freeData) +{ + Tree * ret = xmalloc(sizeof(Tree)); + ret->compareKey = compareKey; + ret->freeKey = freeKey; + ret->freeData = freeData; + ret->rootNode = _MakeNode(); + ret->size = 0; + return ret; +} + +void +FreeTree(Tree * tree) +{ + assert(tree->rootNode == NULL); + free(tree); +} + +int +GetTreeSize(Tree * tree) +{ + return tree->size; +} + +void SetTreeIteratorToBegin(Tree * tree, TreeIterator * iter) +{ + _SetIteratorToRoot(tree, iter); + IncrementTreeIterator(iter); +} + +int IsTreeIteratorAtEnd(const TreeIterator * iter) +{ + return (iter->node == NULL); +} + +void IncrementTreeIterator(TreeIterator * iter) +{ + while(iter->node) + { + if (iter->node->children[iter->which]) + { + iter->node = iter->node->children[iter->which]; + iter->which = 0; + } + else + { + iter->which++; + } + + while (iter->node && iter->which > iter->node->count) + { + iter->which = iter->node->parentPos + 1; + iter->node = iter->node->parent; + } + + if (iter->node && + iter->which > 0 && iter->which <= iter->node->count) + { + return; + } + } +} + +TreeKeyData +GetTreeKeyData(TreeIterator * iter) +{ + assert(iter->node && + iter->which > 0 && + iter->which <= iter->node->count); + return iter->node->keyData[iter->which-1]; +} + +int +InsertInTree(Tree * tree, void * key, void * data) +{ + TreeKeyData keyData; + TreeIterator iter; + + _SetIteratorToRoot(tree, &iter); + + if (_Find(&iter, key)) + { + return 0; + } + + keyData.key = key; + keyData.data = data; + _InsertAt(&iter, keyData); + tree->size++; + + return 1; +} + +int +RemoveFromTreeByKey(Tree * tree, void * key) +{ + TreeIterator iter; + _SetIteratorToRoot(tree, &iter); + + if (_Find(&iter, key)) + { + _DeleteAt(&iter); + tree->size--; + return 1; + } + + return 0; +} + +void +RemoveFromTreeByIterator(Tree * tree, TreeIterator * iter) +{ + _DeleteAt(iter); + tree->size--; +} + +int +FindInTree(Tree * tree, void * key, TreeIterator * iter) +{ + TreeIterator i; + + if (iter == NULL) + { + iter = &i; + } + + _SetIteratorToRoot(tree, iter); + if (_Find(iter, key)) + { + return 1; + } + + return 0; +} diff --git a/trunk/src/tree.h b/trunk/src/tree.h new file mode 100644 index 000000000..76a980cd2 --- /dev/null +++ b/trunk/src/tree.h @@ -0,0 +1,62 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2006-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 TREE_H +#define TREE_H + +typedef struct _Tree Tree; +typedef struct _TreeNode TreeNode; +typedef struct _TreeIterator TreeIterator; +typedef struct _TreeKeyData TreeKeyData; + +struct _TreeIterator +{ + Tree * tree; + TreeNode * node; + int which; +}; + +struct _TreeKeyData +{ + void * key; + void * data; +}; + +typedef int (*TreeCompareKeyFunction)(const void * key1, const void * key2); +typedef void (*TreeFreeFunction)(void * data); + +Tree * MakeTree(TreeCompareKeyFunction compareFunc, + TreeFreeFunction freeKey, + TreeFreeFunction freeData); +void FreeTree(Tree * tree); + +int GetTreeSize(Tree * tree); + +void SetTreeIteratorToBegin(Tree * tree, TreeIterator * iter); +int IsTreeIteratorAtEnd(const TreeIterator * iter); +void IncrementTreeIterator(TreeIterator * iter); + +TreeKeyData GetTreeKeyData(TreeIterator * iter); + +int InsertInTree(Tree * tree, void * key, void * data); +int RemoveFromTreeByKey(Tree * tree, void * key); +void RemoveFromTreeByIterator(Tree * tree, TreeIterator * iter); + +int FindInTree(Tree * tree, void * key, TreeIterator * iter /* can be NULL */); + +#endif diff --git a/trunk/src/utf8.c b/trunk/src/utf8.c new file mode 100644 index 000000000..2061a78de --- /dev/null +++ b/trunk/src/utf8.c @@ -0,0 +1,148 @@ +/* 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 "utf8.h" +#include "utils.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +static char *latin1ToUtf8(char c) +{ + static unsigned char utf8[3]; + unsigned char uc = c; + + memset(utf8, 0, 3); + + if (uc < 128) + utf8[0] = uc; + else if (uc < 192) { + utf8[0] = 194; + utf8[1] = uc; + } else { + utf8[0] = 195; + utf8[1] = uc - 64; + } + + return (char *)utf8; +} + +char *latin1StrToUtf8Dup(char *latin1) +{ + /* utf8 should have at most two char's per latin1 char */ + int len = strlen(latin1) * 2 + 1; + char *ret = xmalloc(len); + char *cp = ret; + char *utf8; + + memset(ret, 0, len); + + len = 0; + + while (*latin1) { + utf8 = latin1ToUtf8(*latin1); + while (*utf8) { + *(cp++) = *(utf8++); + len++; + } + latin1++; + } + + return xrealloc(ret, len + 1); +} + +static char utf8ToLatin1(char *inUtf8) +{ + unsigned char c = 0; + unsigned char *utf8 = (unsigned char *)inUtf8; + + if (utf8[0] < 128) + return utf8[0]; + else if (utf8[0] == 195) + c += 64; + else if (utf8[0] != 194) + return '?'; + return (char)(c + utf8[1]); +} + +static int validateUtf8Char(char *inUtf8Char) +{ + unsigned char *utf8Char = (unsigned char *)inUtf8Char; + + if (utf8Char[0] < 0x80) + return 1; + + if (utf8Char[0] >= 0xC0 && utf8Char[0] <= 0xFD) { + int count = 1; + char t = 1 << 5; + int i; + while (count < 6 && (t & utf8Char[0])) { + t = (t >> 1); + count++; + } + if (count > 5) + return 0; + for (i = 1; i <= count; i++) { + if (utf8Char[i] < 0x80 || utf8Char[i] > 0xBF) + return 0; + } + return count + 1; + } else + return 0; +} + +int validUtf8String(char *string) +{ + int ret; + + while (*string) { + ret = validateUtf8Char(string); + if (0 == ret) + return 0; + string += ret; + } + + return 1; +} + +char *utf8StrToLatin1Dup(char *utf8) +{ + /* utf8 should have at most two char's per latin1 char */ + int len = strlen(utf8) + 1; + char *ret = xmalloc(len); + char *cp = ret; + int count; + + memset(ret, 0, len); + + len = 0; + + while (*utf8) { + count = validateUtf8Char(utf8); + if (!count) { + free(ret); + return NULL; + } + *(cp++) = utf8ToLatin1(utf8); + utf8 += count; + len++; + } + + return xrealloc(ret, len + 1); +} diff --git a/trunk/src/utf8.h b/trunk/src/utf8.h new file mode 100644 index 000000000..0eb60d82c --- /dev/null +++ b/trunk/src/utf8.h @@ -0,0 +1,28 @@ +/* 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 UTF_8_H +#define UTF_8_H + +char *latin1StrToUtf8Dup(char *latin1); + +char *utf8StrToLatin1Dup(char *utf8); + +int validUtf8String(char *string); + +#endif diff --git a/trunk/src/utils.c b/trunk/src/utils.c new file mode 100644 index 000000000..a6dd9d8ae --- /dev/null +++ b/trunk/src/utils.c @@ -0,0 +1,161 @@ +/* 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 "utils.h" +#include "log.h" + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/time.h> +#include <unistd.h> +#include <assert.h> + +char *myFgets(char *buffer, int bufferSize, FILE * fp) +{ + char *ret = fgets(buffer, bufferSize, fp); + if (ret && strlen(buffer) > 0 && buffer[strlen(buffer) - 1] == '\n') { + buffer[strlen(buffer) - 1] = '\0'; + } + if (ret && strlen(buffer) > 0 && buffer[strlen(buffer) - 1] == '\r') { + buffer[strlen(buffer) - 1] = '\0'; + } + return ret; +} + +char *strDupToUpper(char *str) +{ + char *ret = xstrdup(str); + int i; + + for (i = 0; i < strlen(str); i++) + ret[i] = toupper((int)ret[i]); + + return ret; +} + +void stripReturnChar(char *string) +{ + while (string && (string = strchr(string, '\n'))) { + *string = ' '; + } +} + +void my_usleep(long usec) +{ + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = usec; + + select(0, NULL, NULL, NULL, &tv); +} + +int ipv6Supported(void) +{ +#ifdef HAVE_IPV6 + int s; + s = socket(AF_INET6, SOCK_STREAM, 0); + if (s == -1) + return 0; + close(s); + return 1; +#endif + return 0; +} + +char *appendToString(char *dest, const char *src) +{ + int destlen; + int srclen = strlen(src); + + if (dest == NULL) { + dest = xmalloc(srclen + 1); + memset(dest, 0, srclen + 1); + destlen = 0; + } else { + destlen = strlen(dest); + dest = xrealloc(dest, destlen + srclen + 1); + } + + memcpy(dest + destlen, src, srclen); + dest[destlen + srclen] = '\0'; + + return dest; +} + +unsigned long readLEuint32(const unsigned char *p) +{ + return ((unsigned long)p[0] << 0) | + ((unsigned long)p[1] << 8) | + ((unsigned long)p[2] << 16) | ((unsigned long)p[3] << 24); +} + +mpd_malloc char *xstrdup(const char *s) +{ + char *ret = strdup(s); + if (mpd_unlikely(!ret)) + FATAL("OOM: strdup\n"); + return ret; +} + +/* borrowed from git :) */ + +mpd_malloc void *xmalloc(size_t size) +{ + void *ret; + + assert(mpd_likely(size)); + + ret = malloc(size); + if (mpd_unlikely(!ret)) + FATAL("OOM: malloc\n"); + return ret; +} + +mpd_malloc void *xrealloc(void *ptr, size_t size) +{ + void *ret = realloc(ptr, size); + + /* some C libraries return NULL when size == 0, + * make sure we get a free()-able pointer (free(NULL) + * doesn't work with all C libraries, either) */ + if (mpd_unlikely(!ret && !size)) + ret = realloc(ptr, 1); + + if (mpd_unlikely(!ret)) + FATAL("OOM: realloc\n"); + return ret; +} + +mpd_malloc void *xcalloc(size_t nmemb, size_t size) +{ + void *ret; + + assert(mpd_likely(nmemb && size)); + + ret = calloc(nmemb, size); + if (mpd_unlikely(!ret)) + FATAL("OOM: calloc\n"); + return ret; +} + + diff --git a/trunk/src/utils.h b/trunk/src/utils.h new file mode 100644 index 000000000..2f911499b --- /dev/null +++ b/trunk/src/utils.h @@ -0,0 +1,85 @@ +/* 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 UTILS_H +#define UTILS_H + +#include "../config.h" +#include "gcc.h" + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +char *myFgets(char *buffer, int bufferSize, FILE * fp); + +char *strDupToUpper(char *str); + +void stripReturnChar(char *string); + +void my_usleep(long usec); + +int ipv6Supported(void); + +char *appendToString(char *dest, const char *src); + +unsigned long readLEuint32(const unsigned char *p); + +/* trivial functions, keep them inlined */ +static inline void xclose(int fd) +{ + while (close(fd) && errno == EINTR); +} + +static inline ssize_t xread(int fd, void *buf, size_t len) +{ + ssize_t nr; + while (1) { + nr = read(fd, buf, len); + if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) + continue; + return nr; + } +} + +static inline ssize_t xwrite(int fd, const void *buf, size_t len) +{ + ssize_t nr; + while (1) { + nr = write(fd, buf, len); + if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) + continue; + return nr; + } +} + +mpd_malloc char *xstrdup(const char *s); + +mpd_malloc void *xmalloc(size_t size); + +mpd_malloc void *xrealloc(void *ptr, size_t size); + +mpd_malloc void *xcalloc(size_t nmemb, size_t size); + +#endif diff --git a/trunk/src/volume.c b/trunk/src/volume.c new file mode 100644 index 000000000..59e8b550c --- /dev/null +++ b/trunk/src/volume.c @@ -0,0 +1,552 @@ +/* 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 "volume.h" + +#include "command.h" +#include "conf.h" +#include "log.h" +#include "player.h" +#include "state_file.h" +#include "gcc.h" +#include "utils.h" + +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#ifdef HAVE_OSS +#include <sys/soundcard.h> +#endif +#ifdef HAVE_ALSA +#include <alsa/asoundlib.h> +#endif + +#define VOLUME_MIXER_TYPE_SOFTWARE 0 +#define VOLUME_MIXER_TYPE_OSS 1 +#define VOLUME_MIXER_TYPE_ALSA 2 + +#define VOLUME_MIXER_SOFTWARE_DEFAULT "" +#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" +#define VOLUME_MIXER_ALSA_DEFAULT "default" +#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" +#define SW_VOLUME_STATE "sw_volume: " + +#ifdef HAVE_OSS +#define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_OSS +#define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_OSS_DEFAULT +#else +#ifdef HAVE_ALSA +#define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_ALSA +#define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_ALSA_DEFAULT +#else +#define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_SOFTWARE +#define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_SOFTWARE_DEFAULT +#endif +#endif + +static int volume_mixerType = VOLUME_MIXER_TYPE_DEFAULT; +static char *volume_mixerDevice = VOLUME_MIXER_DEVICE_DEFAULT; + +static int volume_softwareSet = 100; + +#ifdef HAVE_OSS +static int volume_ossFd = -1; +static int volume_ossControl = SOUND_MIXER_PCM; +#endif + +#ifdef HAVE_ALSA +static snd_mixer_t *volume_alsaMixerHandle; +static snd_mixer_elem_t *volume_alsaElem; +static long volume_alsaMin; +static long volume_alsaMax; +static int volume_alsaSet = -1; +#endif + +#ifdef HAVE_OSS + +static void closeOssMixer(void) +{ + while (close(volume_ossFd) && errno == EINTR) ; + volume_ossFd = -1; +} + +static int prepOssMixer(char *device) +{ + ConfigParam *param; + + if ((volume_ossFd = open(device, O_RDONLY)) < 0) { + WARNING("unable to open oss mixer \"%s\"\n", device); + return -1; + } + + param = getConfigParam(CONF_MIXER_CONTROL); + + if (param) { + char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; + char *dup; + int i, j; + int devmask = 0; + + if (ioctl(volume_ossFd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + WARNING("errors getting read_devmask for oss mixer\n"); + closeOssMixer(); + return -1; + } + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + dup = xstrdup(labels[i]); + /* eliminate spaces at the end */ + j = strlen(dup) - 1; + while (j >= 0 && dup[j] == ' ') + dup[j--] = '\0'; + if (strcasecmp(dup, param->value) == 0) { + free(dup); + break; + } + free(dup); + } + + if (i >= SOUND_MIXER_NRDEVICES) { + WARNING("mixer control \"%s\" not found at line %i\n", + param->value, param->line); + closeOssMixer(); + return -1; + } else if (!((1 << i) & devmask)) { + WARNING("mixer control \"%s\" not usable at line %i\n", + param->value, param->line); + closeOssMixer(); + return -1; + } + + volume_ossControl = i; + } + + return 0; +} + +static int ensure_oss_open(void) +{ + if ((volume_ossFd < 0 && prepOssMixer(volume_mixerDevice) < 0)) + return -1; + return 0; +} + +static int getOssVolumeLevel(void) +{ + int left, right, level; + + if (ensure_oss_open() < 0) + return -1; + + if (ioctl(volume_ossFd, MIXER_READ(volume_ossControl), &level) < 0) { + closeOssMixer(); + WARNING("unable to read volume\n"); + return -1; + } + + left = level & 0xff; + right = (level & 0xff00) >> 8; + + if (left != right) { + WARNING("volume for left and right is not the same, \"%i\" and " + "\"%i\"\n", left, right); + } + + return left; +} + +static int changeOssVolumeLevel(int fd, int change, int rel) +{ + int current; + int new; + int level; + + if (rel) { + if ((current = getOssVolumeLevel()) < 0) { + commandError(fd, ACK_ERROR_SYSTEM, + "problem getting current volume"); + return -1; + } + + new = current + change; + } else { + if (ensure_oss_open() < 0) + return -1; + new = change; + } + + if (new < 0) + new = 0; + else if (new > 100) + new = 100; + + level = (new << 8) + new; + + if (ioctl(volume_ossFd, MIXER_WRITE(volume_ossControl), &level) < 0) { + closeOssMixer(); + commandError(fd, ACK_ERROR_SYSTEM, "problems setting volume"); + return -1; + } + + return 0; +} +#endif + +#ifdef HAVE_ALSA +static void closeAlsaMixer(void) +{ + snd_mixer_close(volume_alsaMixerHandle); + volume_alsaMixerHandle = NULL; +} + +static int prepAlsaMixer(char *card) +{ + int err; + snd_mixer_elem_t *elem; + char *controlName = VOLUME_MIXER_ALSA_CONTROL_DEFAULT; + ConfigParam *param; + + err = snd_mixer_open(&volume_alsaMixerHandle, 0); + snd_config_update_free_global(); + if (err < 0) { + WARNING("problems opening alsa mixer: %s\n", snd_strerror(err)); + return -1; + } + + if ((err = snd_mixer_attach(volume_alsaMixerHandle, card)) < 0) { + closeAlsaMixer(); + WARNING("problems attaching alsa mixer: %s\n", + snd_strerror(err)); + return -1; + } + + if ((err = + snd_mixer_selem_register(volume_alsaMixerHandle, NULL, + NULL)) < 0) { + closeAlsaMixer(); + WARNING("problems snd_mixer_selem_register'ing: %s\n", + snd_strerror(err)); + return -1; + } + + if ((err = snd_mixer_load(volume_alsaMixerHandle)) < 0) { + closeAlsaMixer(); + WARNING("problems snd_mixer_selem_register'ing: %s\n", + snd_strerror(err)); + return -1; + } + + elem = snd_mixer_first_elem(volume_alsaMixerHandle); + + param = getConfigParam(CONF_MIXER_CONTROL); + + if (param) { + controlName = param->value; + } + + while (elem) { + if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) { + if (strcasecmp(controlName, + snd_mixer_selem_get_name(elem)) == 0) { + break; + } + } + elem = snd_mixer_elem_next(elem); + } + + if (elem) { + volume_alsaElem = elem; + snd_mixer_selem_get_playback_volume_range(volume_alsaElem, + &volume_alsaMin, + &volume_alsaMax); + return 0; + } + + WARNING("can't find alsa mixer_control \"%s\"\n", controlName); + + closeAlsaMixer(); + return -1; +} + +static int prep_alsa_get_level(long *level) +{ + const char *cmd; + int err; + + if (!volume_alsaMixerHandle && prepAlsaMixer(volume_mixerDevice) < 0) + return -1; + + if ((err = snd_mixer_handle_events(volume_alsaMixerHandle)) < 0) { + cmd = "handle_events"; + goto error; + } + if ((err = snd_mixer_selem_get_playback_volume(volume_alsaElem, + SND_MIXER_SCHN_FRONT_LEFT, + level)) < 0) { + cmd = "selem_get_playback_volume"; + goto error; + } + return 0; + +error: + WARNING("problems getting alsa volume: %s (snd_mixer_%s)\n", + snd_strerror(err), cmd); + closeAlsaMixer(); + return -1; +} + +static int getAlsaVolumeLevel(void) +{ + int ret; + long level; + long max = volume_alsaMax; + long min = volume_alsaMin; + + if (prep_alsa_get_level(&level) < 0) + return -1; + + ret = ((volume_alsaSet / 100.0) * (max - min) + min) + 0.5; + if (volume_alsaSet > 0 && ret == level) { + ret = volume_alsaSet; + } else + ret = (int)(100 * (((float)(level - min)) / (max - min)) + 0.5); + + return ret; +} + +static int changeAlsaVolumeLevel(int fd, int change, int rel) +{ + float vol; + long level; + long test; + long max = volume_alsaMax; + long min = volume_alsaMin; + int err; + + if (prep_alsa_get_level(&level) < 0) + return -1; + + if (rel) { + test = ((volume_alsaSet / 100.0) * (max - min) + min) + 0.5; + if (volume_alsaSet >= 0 && level == test) { + vol = volume_alsaSet; + } else + vol = 100.0 * (((float)(level - min)) / (max - min)); + vol += change; + } else + vol = change; + + volume_alsaSet = vol + 0.5; + volume_alsaSet = volume_alsaSet > 100 ? 100 : + (volume_alsaSet < 0 ? 0 : volume_alsaSet); + + level = (long)(((vol / 100.0) * (max - min) + min) + 0.5); + level = level > max ? max : level; + level = level < min ? min : level; + + if ((err = + snd_mixer_selem_set_playback_volume_all(volume_alsaElem, + level)) < 0) { + commandError(fd, ACK_ERROR_SYSTEM, "problems setting volume"); + WARNING("problems setting alsa volume: %s\n", + snd_strerror(err)); + closeAlsaMixer(); + return -1; + } + + return 0; +} +#endif + +static int prepMixer(char *device) +{ + switch (volume_mixerType) { +#ifdef HAVE_ALSA + case VOLUME_MIXER_TYPE_ALSA: + return prepAlsaMixer(device); +#endif +#ifdef HAVE_OSS + case VOLUME_MIXER_TYPE_OSS: + return prepOssMixer(device); +#endif + } + + return 0; +} + +void finishVolume(void) +{ + switch (volume_mixerType) { +#ifdef HAVE_ALSA + case VOLUME_MIXER_TYPE_ALSA: + closeAlsaMixer(); + break; +#endif +#ifdef HAVE_OSS + case VOLUME_MIXER_TYPE_OSS: + closeOssMixer(); + break; +#endif + } +} + +void initVolume(void) +{ + ConfigParam *param = getConfigParam(CONF_MIXER_TYPE); + + if (param) { + if (0) ; +#ifdef HAVE_ALSA + else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) { + volume_mixerType = VOLUME_MIXER_TYPE_ALSA; + volume_mixerDevice = VOLUME_MIXER_ALSA_DEFAULT; + } +#endif +#ifdef HAVE_OSS + else if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) { + volume_mixerType = VOLUME_MIXER_TYPE_OSS; + volume_mixerDevice = VOLUME_MIXER_OSS_DEFAULT; + } +#endif + else if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) { + volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE; + volume_mixerDevice = VOLUME_MIXER_SOFTWARE_DEFAULT; + } else { + FATAL("unknown mixer type %s at line %i\n", + param->value, param->line); + } + } + + param = getConfigParam(CONF_MIXER_DEVICE); + + if (param) { + volume_mixerDevice = param->value; + } +} + +void openVolumeDevice(void) +{ + if (prepMixer(volume_mixerDevice) < 0) { + WARNING("using software volume\n"); + volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE; + } +} + +static int getSoftwareVolume(void) +{ + return volume_softwareSet; +} + +int getVolumeLevel(void) +{ + switch (volume_mixerType) { +#ifdef HAVE_ALSA + case VOLUME_MIXER_TYPE_ALSA: + return getAlsaVolumeLevel(); +#endif +#ifdef HAVE_OSS + case VOLUME_MIXER_TYPE_OSS: + return getOssVolumeLevel(); +#endif + case VOLUME_MIXER_TYPE_SOFTWARE: + return getSoftwareVolume(); + default: + return -1; + } +} + +static int changeSoftwareVolume(int fd, int change, int rel) +{ + int new = change; + + if (rel) + new += volume_softwareSet; + + if (new > 100) + new = 100; + else if (new < 0) + new = 0; + + volume_softwareSet = new; + + /*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5; */ + if (new >= 100) + new = 1000; + else if (new <= 0) + new = 0; + else + new = + 1000.0 * (exp(new / 25.0) - 1) / (54.5981500331F - 1) + 0.5; + + setPlayerSoftwareVolume(new); + + return 0; +} + +int changeVolumeLevel(int fd, int change, int rel) +{ + switch (volume_mixerType) { +#ifdef HAVE_ALSA + case VOLUME_MIXER_TYPE_ALSA: + return changeAlsaVolumeLevel(fd, change, rel); +#endif +#ifdef HAVE_OSS + case VOLUME_MIXER_TYPE_OSS: + return changeOssVolumeLevel(fd, change, rel); +#endif + case VOLUME_MIXER_TYPE_SOFTWARE: + return changeSoftwareVolume(fd, change, rel); + default: + return 0; + break; + } +} + +void read_sw_volume_state(FILE *fp) +{ + /* strlen(SW_VOLUME_STATE) + strlen('100') + '\0' */ + #define bufsize 16 + char buf[bufsize]; + const size_t len = strlen(SW_VOLUME_STATE); + char *end = NULL; + long int sv; + + if (volume_mixerType != VOLUME_MIXER_TYPE_SOFTWARE) + return; + while (myFgets(buf, bufsize, fp)) { + if (strncmp(buf, SW_VOLUME_STATE, len)) + continue; + sv = strtol(buf + len, &end, 10); + if (mpd_likely(!*end)) + changeSoftwareVolume(STDERR_FILENO, sv, 0); + else + ERROR("Can't parse software volume: %s\n", buf); + return; + } + #undef bufsize +} + +void save_sw_volume_state(FILE *fp) +{ + if (volume_mixerType == VOLUME_MIXER_TYPE_SOFTWARE) + fprintf(fp, SW_VOLUME_STATE "%d\n", volume_softwareSet); +} + diff --git a/trunk/src/volume.h b/trunk/src/volume.h new file mode 100644 index 000000000..fcaefc64d --- /dev/null +++ b/trunk/src/volume.h @@ -0,0 +1,44 @@ +/* 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 VOLUME_H +#define VOLUME_H + +#include "../config.h" + +#include <stdio.h> + +#define VOLUME_MIXER_OSS "oss" +#define VOLUME_MIXER_ALSA "alsa" +#define VOLUME_MIXER_SOFTWARE "software" + +void initVolume(void); + +void openVolumeDevice(void); + +void finishVolume(void); + +int getVolumeLevel(void); + +int changeVolumeLevel(int fd, int change, int rel); + +void read_sw_volume_state(FILE *fp); + +void save_sw_volume_state(FILE *fp); + +#endif diff --git a/trunk/src/zeroconf.c b/trunk/src/zeroconf.c new file mode 100644 index 000000000..eeec794cd --- /dev/null +++ b/trunk/src/zeroconf.c @@ -0,0 +1,498 @@ +/* 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 <stdlib.h> +#include <assert.h> +#include <string.h> + +#include "zeroconf.h" +#include "conf.h" +#include "log.h" +#include "listen.h" +#include "ioops.h" +#include "utils.h" + +/* The dns-sd service type qualifier to publish */ +#define SERVICE_TYPE "_mpd._tcp" + +/* The default service name to publish + * (overridden by 'zeroconf_name' config parameter) + */ +#define SERVICE_NAME "Music Player" + +/* Here is the implementation for Avahi (http://avahi.org) Zeroconf support */ +#ifdef HAVE_AVAHI + +#include <avahi-client/client.h> +#include <avahi-client/publish.h> + +#include <avahi-common/alternative.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +/* Static avahi data */ +static AvahiEntryGroup *avahiGroup; +static char *avahiName; +static AvahiClient* avahiClient; +static AvahiPoll avahiPoll; +static int avahiRunning; + +static int avahiFdset( fd_set* rfds, fd_set* wfds, fd_set* efds ); +static int avahiFdconsume( int fdCount, fd_set* rfds, fd_set* wfds, fd_set* efds ); +static struct ioOps avahiIo = { + .fdset = avahiFdset, + .consume = avahiFdconsume, +}; + +/* Forward Declaration */ +static void avahiRegisterService(AvahiClient *c); + +struct AvahiWatch { + struct AvahiWatch* prev; + struct AvahiWatch* next; + int fd; + AvahiWatchEvent requestedEvent; + AvahiWatchEvent observedEvent; + AvahiWatchCallback callback; + void* userdata; +}; + +struct AvahiTimeout { + struct AvahiTimeout* prev; + struct AvahiTimeout* next; + struct timeval expiry; + int enabled; + AvahiTimeoutCallback callback; + void* userdata; +}; + +static AvahiWatch* avahiWatchList; +static AvahiTimeout* avahiTimeoutList; + +static AvahiWatch* avahiWatchNew( const AvahiPoll *api, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void *userdata ) +{ + struct AvahiWatch* newWatch = xmalloc( sizeof(struct AvahiWatch) ); + + newWatch->fd = fd; + newWatch->requestedEvent = event; + newWatch->observedEvent = 0; + newWatch->callback = callback; + newWatch->userdata = userdata; + + /* Insert at front of list */ + newWatch->next = avahiWatchList; + avahiWatchList = newWatch; + newWatch->prev = NULL; + if( newWatch->next ) + newWatch->next->prev = newWatch; + + return newWatch; +} + +static void avahiWatchUpdate( AvahiWatch *w, AvahiWatchEvent event ) +{ + assert( w != NULL ); + w->requestedEvent = event; +} + +static AvahiWatchEvent avahiWatchGetEvents( AvahiWatch *w ) +{ + assert( w != NULL ); + return w->observedEvent; +} + +static void avahiWatchFree( AvahiWatch *w ) +{ + assert( w != NULL ); + + if( avahiWatchList == w ) + avahiWatchList = w->next; + else if( w->prev != NULL ) + w->prev->next = w->next; + + free( w ); +} + +static void avahiCheckExpiry( AvahiTimeout *t ) +{ + assert( t != NULL ); + if( t->enabled ) { + struct timeval now; + gettimeofday( &now, NULL ); + if( timercmp( &now, &(t->expiry), > ) ) { + t->enabled = 0; + t->callback( t, t->userdata ); + } + } +} + +static void avahiTimeoutUpdate( AvahiTimeout *t, const struct timeval *tv ) +{ + assert( t != NULL ); + if( tv ) { + t->enabled = 1; + t->expiry.tv_sec = tv->tv_sec; + t->expiry.tv_usec = tv->tv_usec; + } else { + t->enabled = 0; + } +} + +static void avahiTimeoutFree( AvahiTimeout *t ) +{ + assert( t != NULL ); + + if( avahiTimeoutList == t ) + avahiTimeoutList = t->next; + else if( t->prev != NULL ) + t->prev->next = t->next; + + free( t ); +} + +static AvahiTimeout* avahiTimeoutNew( const AvahiPoll *api, const struct timeval *tv, AvahiTimeoutCallback callback, void *userdata ) +{ + struct AvahiTimeout* newTimeout = xmalloc( sizeof(struct AvahiTimeout) ); + + newTimeout->callback = callback; + newTimeout->userdata = userdata; + + avahiTimeoutUpdate( newTimeout, tv ); + + /* Insert at front of list */ + newTimeout->next = avahiTimeoutList; + avahiTimeoutList = newTimeout; + newTimeout->prev = NULL; + if( newTimeout->next ) + newTimeout->next->prev = newTimeout; + + return newTimeout; +} + +/* Callback when the EntryGroup changes state */ +static void avahiGroupCallback( + AvahiEntryGroup *g, + AvahiEntryGroupState state, + void *userdata) +{ + assert(g); + + DEBUG( "Avahi: Service group changed to state %d\n", state ); + + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED : + /* The entry group has been established successfully */ + LOG( "Avahi: Service '%s' successfully established.\n", avahiName ); + break; + + case AVAHI_ENTRY_GROUP_COLLISION : { + char *n; + + /* A service name collision happened. Let's pick a new name */ + n = avahi_alternative_service_name(avahiName); + avahi_free(avahiName); + avahiName = n; + + LOG( "Avahi: Service name collision, renaming service to '%s'\n", avahiName ); + + /* And recreate the services */ + avahiRegisterService(avahi_entry_group_get_client(g)); + break; + } + + case AVAHI_ENTRY_GROUP_FAILURE : + ERROR( "Avahi: Entry group failure: %s\n", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) ); + /* Some kind of failure happened while we were registering our services */ + avahiRunning = 0; + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + DEBUG( "Avahi: Service group is UNCOMMITED\n" ); + break; + case AVAHI_ENTRY_GROUP_REGISTERING: + DEBUG( "Avahi: Service group is REGISTERING\n" ); + ; + } +} + +/* Registers a new service with avahi */ +static void avahiRegisterService(AvahiClient *c) +{ + int ret; + assert(c); + DEBUG( "Avahi: Registering service %s/%s\n", SERVICE_TYPE, avahiName ); + + /* If this is the first time we're called, let's create a new entry group */ + if (!avahiGroup) { + avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, NULL); + if( !avahiGroup ) { + ERROR( "Avahi: Failed to create avahi EntryGroup: %s\n", avahi_strerror(avahi_client_errno(c)) ); + goto fail; + } + } + + /* Add the service */ + /* TODO: This currently binds to ALL interfaces. + * We could maybe add a service per actual bound interface, if that's better. */ + ret = avahi_entry_group_add_service(avahiGroup, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, + avahiName, SERVICE_TYPE, + NULL, NULL, + getBoundPort(), + NULL); + if( ret < 0 ) { + ERROR( "Avahi: Failed to add service %s: %s\n", SERVICE_TYPE, avahi_strerror(ret) ); + goto fail; + } + + /* Tell the server to register the service group */ + ret = avahi_entry_group_commit(avahiGroup); + if( ret < 0 ) { + ERROR( "Avahi: Failed to commit service group: %s\n", avahi_strerror(ret) ); + goto fail; + } + return; + +fail: + avahiRunning = 0; +} + +/* Callback when avahi changes state */ +static void avahiClientCallback(AvahiClient *c, AvahiClientState state, void *userdata) +{ + assert(c); + + /* Called whenever the client or server state changes */ + DEBUG( "Avahi: Client changed to state %d\n", state ); + + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + DEBUG( "Avahi: Client is RUNNING\n" ); + + /* The server has startup successfully and registered its host + * name on the network, so it's time to create our services */ + if (!avahiGroup) + avahiRegisterService(c); + break; + + case AVAHI_CLIENT_FAILURE: + { + int reason = avahi_client_errno(c); + if( reason == AVAHI_ERR_DISCONNECTED ) { + LOG( "Avahi: Client Disconnected, will reconnect shortly\n"); + avahi_entry_group_free( avahiGroup ); + avahiGroup = NULL; + avahi_client_free( avahiClient ); + avahiClient = NULL; + avahiClient = avahi_client_new( &avahiPoll, AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, NULL, &reason ); + if( !avahiClient ) { + ERROR( "Avahi: Could not reconnect: %s\n", avahi_strerror(reason) ); + avahiRunning = 0; + } + } else { + ERROR( "Avahi: Client failure: %s (terminal)\n", avahi_strerror(reason)); + avahiRunning = 0; + } + } + break; + + case AVAHI_CLIENT_S_COLLISION: + DEBUG( "Avahi: Client is COLLISION\n" ); + /* Let's drop our registered services. When the server is back + * in AVAHI_SERVER_RUNNING state we will register them + * again with the new host name. */ + if (avahiGroup) { + DEBUG( "Avahi: Resetting group\n" ); + avahi_entry_group_reset(avahiGroup); + } + + case AVAHI_CLIENT_S_REGISTERING: + DEBUG( "Avahi: Client is REGISTERING\n" ); + /* The server records are now being established. This + * might be caused by a host name change. We need to wait + * for our own records to register until the host name is + * properly esatblished. */ + + if (avahiGroup) { + DEBUG( "Avahi: Resetting group\n" ); + avahi_entry_group_reset(avahiGroup); + } + + break; + + case AVAHI_CLIENT_CONNECTING: + DEBUG( "Avahi: Client is CONNECTING\n" ); + ; + } +} + +static int avahiFdset( fd_set* rfds, fd_set* wfds, fd_set* efds ) +{ + AvahiWatch* w; + int maxfd = -1; + if( !avahiRunning ) + return maxfd; + for( w = avahiWatchList; w != NULL; w = w->next ) { + if( w->requestedEvent & AVAHI_WATCH_IN ) { + FD_SET( w->fd, rfds ); + } + if( w->requestedEvent & AVAHI_WATCH_OUT ) { + FD_SET( w->fd, wfds ); + } + if( w->requestedEvent & AVAHI_WATCH_ERR ) { + FD_SET( w->fd, efds ); + } + if( w->requestedEvent & AVAHI_WATCH_HUP ) { + ERROR( "Avahi: No support for HUP events! (ignoring)\n" ); + } + + if( w->fd > maxfd ) + maxfd = w->fd; + } + return maxfd; +} + +static int avahiFdconsume( int fdCount, fd_set* rfds, fd_set* wfds, fd_set* efds ) +{ + int retval = fdCount; + AvahiTimeout* t; + AvahiWatch* w = avahiWatchList; + + while( w != NULL && retval > 0 ) { + AvahiWatch* current = w; + current->observedEvent = 0; + if( FD_ISSET( current->fd, rfds ) ) { + current->observedEvent |= AVAHI_WATCH_IN; + FD_CLR( current->fd, rfds ); + retval--; + } + if( FD_ISSET( current->fd, wfds ) ) { + current->observedEvent |= AVAHI_WATCH_OUT; + FD_CLR( current->fd, wfds ); + retval--; + } + if( FD_ISSET( current->fd, efds ) ) { + current->observedEvent |= AVAHI_WATCH_ERR; + FD_CLR( current->fd, efds ); + retval--; + } + + /* Advance to the next one right now, in case the callback + * removes itself + */ + w = w->next; + + if( current->observedEvent && avahiRunning ) { + current->callback( current, current->fd, + current->observedEvent, current->userdata ); + } + } + + t = avahiTimeoutList; + while( t != NULL && avahiRunning ) { + AvahiTimeout* current = t; + + /* Advance to the next one right now, in case the callback + * removes itself + */ + t = t->next; + avahiCheckExpiry( current ); + } + + return retval; +} + +static void init_avahi(const char *serviceName) +{ + int error; + DEBUG( "Avahi: Initializing interface\n" ); + + if( avahi_is_valid_service_name( serviceName ) ) { + avahiName = avahi_strdup( serviceName ); + } else { + ERROR( "Invalid zeroconf_name \"%s\", defaulting to \"%s\" instead.\n", serviceName, SERVICE_NAME ); + avahiName = avahi_strdup( SERVICE_NAME ); + } + + avahiRunning = 1; + + avahiPoll.userdata = NULL; + avahiPoll.watch_new = avahiWatchNew; + avahiPoll.watch_update = avahiWatchUpdate; + avahiPoll.watch_get_events = avahiWatchGetEvents; + avahiPoll.watch_free = avahiWatchFree; + avahiPoll.timeout_new = avahiTimeoutNew; + avahiPoll.timeout_update = avahiTimeoutUpdate; + avahiPoll.timeout_free = avahiTimeoutFree; + + avahiClient = avahi_client_new( &avahiPoll, AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, NULL, &error ); + + if( !avahiClient ) { + ERROR( "Avahi: Failed to create client: %s\n", avahi_strerror(error) ); + goto fail; + } + + avahiIo.fdset = avahiFdset; + avahiIo.consume = avahiFdconsume; + registerIO( &avahiIo ); + + return; + +fail: + finishZeroconf(); +} +#else /* !HAVE_AVAHI */ +static void init_avahi(const char *serviceName) { } +#endif /* HAVE_AVAHI */ + +void initZeroconf(void) +{ + const char* serviceName = SERVICE_NAME; + ConfigParam *param; + + param = getConfigParam(CONF_ZEROCONF_NAME); + + if (param && strlen(param->value) > 0) + serviceName = param->value; + init_avahi(serviceName); +} + +void finishZeroconf(void) +{ +#ifdef HAVE_AVAHI + DEBUG( "Avahi: Shutting down interface\n" ); + deregisterIO( &avahiIo ); + + if( avahiGroup ) { + avahi_entry_group_free( avahiGroup ); + avahiGroup = NULL; + } + + if( avahiClient ) { + avahi_client_free( avahiClient ); + avahiClient = NULL; + } + + avahi_free( avahiName ); + avahiName = NULL; +#endif /* HAVE_AVAHI */ +} diff --git a/trunk/src/zeroconf.h b/trunk/src/zeroconf.h new file mode 100644 index 000000000..ef7167d53 --- /dev/null +++ b/trunk/src/zeroconf.h @@ -0,0 +1,27 @@ +/* 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 ZEROCONF_H +#define ZEROCONF_H + +#include "../config.h" + +void initZeroconf(void); +void finishZeroconf(void); + +#endif |