aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/src/inputPlugins
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/inputPlugins')
-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
13 files changed, 4761 insertions, 0 deletions
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 */