diff options
Diffstat (limited to '')
-rw-r--r-- | trunk/src/inputPlugins/_flac_common.c | 211 | ||||
-rw-r--r-- | trunk/src/inputPlugins/_flac_common.h | 187 | ||||
-rw-r--r-- | trunk/src/inputPlugins/_ogg_common.c | 73 | ||||
-rw-r--r-- | trunk/src/inputPlugins/_ogg_common.h | 35 | ||||
-rw-r--r-- | trunk/src/inputPlugins/aac_plugin.c | 475 | ||||
-rw-r--r-- | trunk/src/inputPlugins/audiofile_plugin.c | 188 | ||||
-rw-r--r-- | trunk/src/inputPlugins/flac_plugin.c | 530 | ||||
-rw-r--r-- | trunk/src/inputPlugins/mod_plugin.c | 299 | ||||
-rw-r--r-- | trunk/src/inputPlugins/mp3_plugin.c | 1092 | ||||
-rw-r--r-- | trunk/src/inputPlugins/mp4_plugin.c | 455 | ||||
-rw-r--r-- | trunk/src/inputPlugins/mpc_plugin.c | 359 | ||||
-rw-r--r-- | trunk/src/inputPlugins/oggflac_plugin.c | 423 | ||||
-rw-r--r-- | trunk/src/inputPlugins/oggvorbis_plugin.c | 434 |
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, ¤t_section); + + if (current_section != prev_section) { + /*printf("new song!\n"); */ + vorbis_info *vi = ov_info(&vf, -1); + dc->audioFormat.channels = vi->channels; + dc->audioFormat.sampleRate = vi->rate; + if (dc->state == DECODE_STATE_START) { + getOutputAudioFormat(&(dc->audioFormat), + &(cb->audioFormat)); + dc->state = DECODE_STATE_DECODE; + } + comments = ov_comment(&vf, -1)->user_comments; + putOggCommentsIntoOutputBuffer(cb, inStream->metaName, + comments); + ogg_getReplayGainInfo(comments, &replayGainInfo); + } + + prev_section = current_section; + + if (ret <= 0 && ret != OV_HOLE) { + eof = 1; + break; + } + if (ret == OV_HOLE) + ret = 0; + + chunkpos += ret; + + if (chunkpos >= OGG_CHUNK_SIZE) { + if ((test = ov_bitrate_instant(&vf)) > 0) { + bitRate = test / 1000; + } + sendDataToOutputBuffer(cb, inStream, dc, + inStream->seekable, + chunk, chunkpos, + ov_pcm_tell(&vf) / + dc->audioFormat.sampleRate, + bitRate, replayGainInfo); + chunkpos = 0; + if (dc->stop) + break; + } + } + + if (!dc->stop && chunkpos > 0) { + sendDataToOutputBuffer(cb, NULL, dc, inStream->seekable, + chunk, chunkpos, + ov_time_tell(&vf), bitRate, + replayGainInfo); + } + + if (replayGainInfo) + freeReplayGainInfo(replayGainInfo); + + ov_clear(&vf); + + flushOutputBuffer(cb); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *oggvorbis_TagDup(char *file) +{ + MpdTag *ret = NULL; + FILE *fp; + OggVorbis_File vf; + + fp = fopen(file, "r"); + if (!fp) { + DEBUG("oggvorbis_TagDup: Failed to open file: '%s', %s\n", + file, strerror(errno)); + return NULL; + } + if (ov_open(fp, &vf, NULL, 0) < 0) { + fclose(fp); + return NULL; + } + + ret = oggCommentsParse(ov_comment(&vf, -1)->user_comments); + + if (!ret) + ret = newMpdTag(); + ret->time = (int)(ov_time_total(&vf, -1) + 0.5); + + ov_clear(&vf); + + return ret; +} + +static unsigned int oggvorbis_try_decode(InputStream * inStream) +{ + return (ogg_stream_type_detect(inStream) == VORBIS) ? 1 : 0; +} + +static char *oggvorbis_Suffixes[] = { "ogg", NULL }; +static char *oggvorbis_MimeTypes[] = { "application/ogg", + "audio/x-vorbis+ogg", + "application/x-ogg", + NULL }; + +InputPlugin oggvorbisPlugin = { + "oggvorbis", + NULL, + NULL, + oggvorbis_try_decode, + oggvorbis_decode, + NULL, + oggvorbis_TagDup, + INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + oggvorbis_Suffixes, + oggvorbis_MimeTypes +}; + +#else /* !HAVE_OGGVORBIS */ + +InputPlugin oggvorbisPlugin; + +#endif /* HAVE_OGGVORBIS */ |