aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/src
diff options
context:
space:
mode:
authorJ. Alexander Treuman <jat@spatialrift.net>2007-05-28 13:09:41 +0000
committerJ. Alexander Treuman <jat@spatialrift.net>2007-05-28 13:09:41 +0000
commit6e5c90e098005b66f86a9fd99a26956cbaa0c392 (patch)
treed5699855fe945b0b02e511c87def301d119ae922 /trunk/src
parent28c7a91d2462128a7df9a417cbbd59cad89ba19b (diff)
downloadmpd-6e5c90e098005b66f86a9fd99a26956cbaa0c392.tar.gz
mpd-6e5c90e098005b66f86a9fd99a26956cbaa0c392.tar.xz
mpd-6e5c90e098005b66f86a9fd99a26956cbaa0c392.zip
Re-tagging 0.13.0 release to fix a couple of bugs with the tarball.
git-svn-id: https://svn.musicpd.org/mpd/tags/release-0.13.0@6325 09075e82-0dd4-0310-85a5-a0d7c8717e4f
Diffstat (limited to 'trunk/src')
-rw-r--r--trunk/src/Makefile.am148
-rw-r--r--trunk/src/ack.h37
-rw-r--r--trunk/src/audio.c518
-rw-r--r--trunk/src/audio.h83
-rw-r--r--trunk/src/audioOutput.c269
-rw-r--r--trunk/src/audioOutput.h117
-rw-r--r--trunk/src/audioOutputs/audioOutput_alsa.c427
-rw-r--r--trunk/src/audioOutputs/audioOutput_ao.c246
-rw-r--r--trunk/src/audioOutputs/audioOutput_jack.c440
-rw-r--r--trunk/src/audioOutputs/audioOutput_mvp.c284
-rw-r--r--trunk/src/audioOutputs/audioOutput_oss.c575
-rw-r--r--trunk/src/audioOutputs/audioOutput_osx.c374
-rw-r--r--trunk/src/audioOutputs/audioOutput_pulse.c221
-rw-r--r--trunk/src/audioOutputs/audioOutput_shout.c636
-rw-r--r--trunk/src/buffer2array.c132
-rw-r--r--trunk/src/buffer2array.h32
-rw-r--r--trunk/src/charConv.c168
-rw-r--r--trunk/src/charConv.h28
-rw-r--r--trunk/src/command.c1299
-rw-r--r--trunk/src/command.h50
-rw-r--r--trunk/src/compress.c411
-rw-r--r--trunk/src/compress.h47
-rw-r--r--trunk/src/conf.c450
-rw-r--r--trunk/src/conf.h98
-rw-r--r--trunk/src/dbUtils.c341
-rw-r--r--trunk/src/dbUtils.h51
-rw-r--r--trunk/src/decode.c706
-rw-r--r--trunk/src/decode.h69
-rw-r--r--trunk/src/directory.c1362
-rw-r--r--trunk/src/directory.h76
-rw-r--r--trunk/src/gcc.h67
-rw-r--r--trunk/src/inputPlugin.c158
-rw-r--r--trunk/src/inputPlugin.h109
-rw-r--r--trunk/src/inputPlugins/_flac_common.c211
-rw-r--r--trunk/src/inputPlugins/_flac_common.h187
-rw-r--r--trunk/src/inputPlugins/_ogg_common.c73
-rw-r--r--trunk/src/inputPlugins/_ogg_common.h35
-rw-r--r--trunk/src/inputPlugins/aac_plugin.c475
-rw-r--r--trunk/src/inputPlugins/audiofile_plugin.c188
-rw-r--r--trunk/src/inputPlugins/flac_plugin.c530
-rw-r--r--trunk/src/inputPlugins/mod_plugin.c299
-rw-r--r--trunk/src/inputPlugins/mp3_plugin.c1092
-rw-r--r--trunk/src/inputPlugins/mp4_plugin.c455
-rw-r--r--trunk/src/inputPlugins/mpc_plugin.c359
-rw-r--r--trunk/src/inputPlugins/oggflac_plugin.c423
-rw-r--r--trunk/src/inputPlugins/oggvorbis_plugin.c434
-rw-r--r--trunk/src/inputStream.c83
-rw-r--r--trunk/src/inputStream.h70
-rw-r--r--trunk/src/inputStream_file.c119
-rw-r--r--trunk/src/inputStream_file.h39
-rw-r--r--trunk/src/inputStream_http.c912
-rw-r--r--trunk/src/inputStream_http.h39
-rw-r--r--trunk/src/interface.c851
-rw-r--r--trunk/src/interface.h37
-rw-r--r--trunk/src/ioops.h51
-rw-r--r--trunk/src/list.c519
-rw-r--r--trunk/src/list.h110
-rw-r--r--trunk/src/listen.c258
-rw-r--r--trunk/src/listen.h41
-rw-r--r--trunk/src/locate.c211
-rw-r--r--trunk/src/locate.h46
-rw-r--r--trunk/src/log.c262
-rw-r--r--trunk/src/log.h50
-rw-r--r--trunk/src/ls.c281
-rw-r--r--trunk/src/ls.h52
-rw-r--r--trunk/src/main.c482
-rw-r--r--trunk/src/metadataChunk.c94
-rw-r--r--trunk/src/metadataChunk.h38
-rw-r--r--trunk/src/mp4ff/Makefile.am9
-rw-r--r--trunk/src/mp4ff/drms.c1043
-rw-r--r--trunk/src/mp4ff/drms.h40
-rw-r--r--trunk/src/mp4ff/drmstables.h449
-rw-r--r--trunk/src/mp4ff/mp4atom.c781
-rw-r--r--trunk/src/mp4ff/mp4ff.c430
-rw-r--r--trunk/src/mp4ff/mp4ff.dsp144
-rw-r--r--trunk/src/mp4ff/mp4ff.h128
-rw-r--r--trunk/src/mp4ff/mp4ff_int_types.h32
-rw-r--r--trunk/src/mp4ff/mp4ffint.h329
-rw-r--r--trunk/src/mp4ff/mp4meta.c414
-rw-r--r--trunk/src/mp4ff/mp4sample.c152
-rw-r--r--trunk/src/mp4ff/mp4tagupdate.c645
-rw-r--r--trunk/src/mp4ff/mp4util.c188
-rw-r--r--trunk/src/mpd_types.h43
-rw-r--r--trunk/src/myfprintf.c72
-rw-r--r--trunk/src/myfprintf.h31
-rw-r--r--trunk/src/normalize.c47
-rw-r--r--trunk/src/normalize.h32
-rw-r--r--trunk/src/outputBuffer.c198
-rw-r--r--trunk/src/outputBuffer.h69
-rw-r--r--trunk/src/path.c310
-rw-r--r--trunk/src/path.h69
-rw-r--r--trunk/src/pcm_utils.c470
-rw-r--r--trunk/src/pcm_utils.h57
-rw-r--r--trunk/src/permission.c140
-rw-r--r--trunk/src/permission.h39
-rw-r--r--trunk/src/player.c530
-rw-r--r--trunk/src/player.h154
-rw-r--r--trunk/src/playerData.c162
-rw-r--r--trunk/src/playerData.h49
-rw-r--r--trunk/src/playlist.c1499
-rw-r--r--trunk/src/playlist.h144
-rw-r--r--trunk/src/replayGain.c165
-rw-r--r--trunk/src/replayGain.h50
-rw-r--r--trunk/src/sig_handlers.c159
-rw-r--r--trunk/src/sig_handlers.h42
-rw-r--r--trunk/src/signal_check.c60
-rw-r--r--trunk/src/signal_check.h30
-rw-r--r--trunk/src/sllist.c72
-rw-r--r--trunk/src/sllist.h52
-rw-r--r--trunk/src/song.c353
-rw-r--r--trunk/src/song.h79
-rw-r--r--trunk/src/state_file.c111
-rw-r--r--trunk/src/state_file.h30
-rw-r--r--trunk/src/stats.c48
-rw-r--r--trunk/src/stats.h40
-rw-r--r--trunk/src/storedPlaylist.c501
-rw-r--r--trunk/src/storedPlaylist.h48
-rw-r--r--trunk/src/tag.c646
-rw-r--r--trunk/src/tag.h89
-rw-r--r--trunk/src/tagTracker.c147
-rw-r--r--trunk/src/tagTracker.h38
-rw-r--r--trunk/src/tree.c706
-rw-r--r--trunk/src/tree.h62
-rw-r--r--trunk/src/utf8.c148
-rw-r--r--trunk/src/utf8.h28
-rw-r--r--trunk/src/utils.c161
-rw-r--r--trunk/src/utils.h85
-rw-r--r--trunk/src/volume.c552
-rw-r--r--trunk/src/volume.h44
-rw-r--r--trunk/src/zeroconf.c498
-rw-r--r--trunk/src/zeroconf.h27
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, &copy)) {
+ 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,
+ &currentChunkSent, &currentMetadataChunk);
+ 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,
+ &currentChunkSent,
+ &currentMetadataChunk,
+ 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, &current_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