From 69635022133488e6b19569fb59b16c4658a244eb Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 16 Mar 2006 06:52:46 +0000 Subject: merge with mpd/trunk up to r3925 git-svn-id: https://svn.musicpd.org/mpd/trunk@3926 09075e82-0dd4-0310-85a5-a0d7c8717e4f --- src/Makefile.am | 9 +- src/decode.c | 81 +++++-- src/directory.c | 10 +- src/inputPlugin.c | 30 ++- src/inputPlugin.h | 29 ++- src/inputPlugins/_flac_common.c | 195 +++++++++++++++++ src/inputPlugins/_flac_common.h | 77 +++++++ src/inputPlugins/_ogg_common.c | 65 ++++++ src/inputPlugins/_ogg_common.h | 35 +++ src/inputPlugins/aac_plugin.c | 2 + src/inputPlugins/audiofile_plugin.c | 2 + src/inputPlugins/flac_plugin.c | 258 +++------------------- src/inputPlugins/mod_plugin.c | 2 + src/inputPlugins/mp3_plugin.c | 2 + src/inputPlugins/mp4_plugin.c | 2 + src/inputPlugins/mpc_plugin.c | 2 + src/inputPlugins/ogg_plugin.c | 420 ----------------------------------- src/inputPlugins/oggflac_plugin.c | 426 ++++++++++++++++++++++++++++++++++++ src/inputPlugins/oggvorbis_plugin.c | 426 ++++++++++++++++++++++++++++++++++++ src/inputStream_http.c | 14 ++ src/ls.c | 10 +- src/ls.h | 4 +- src/song.c | 20 +- src/tag.c | 2 +- 24 files changed, 1423 insertions(+), 700 deletions(-) create mode 100644 src/inputPlugins/_flac_common.c create mode 100644 src/inputPlugins/_flac_common.h create mode 100644 src/inputPlugins/_ogg_common.c create mode 100644 src/inputPlugins/_ogg_common.h delete mode 100644 src/inputPlugins/ogg_plugin.c create mode 100644 src/inputPlugins/oggflac_plugin.c create mode 100644 src/inputPlugins/oggvorbis_plugin.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index b861f1b65..9197c3d69 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -10,14 +10,17 @@ mpd_audioOutputs = \ audioOutputs/audioOutput_shout.c mpd_inputPlugins = \ + inputPlugins/_flac_common.c \ + inputPlugins/_ogg_common.c \ + inputPlugins/oggflac_plugin.c \ + inputPlugins/oggvorbis_plugin.c \ inputPlugins/aac_plugin.c \ inputPlugins/audiofile_plugin.c \ inputPlugins/flac_plugin.c \ inputPlugins/mod_plugin.c \ inputPlugins/mp3_plugin.c \ inputPlugins/mp4_plugin.c \ - inputPlugins/mpc_plugin.c \ - inputPlugins/ogg_plugin.c + inputPlugins/mpc_plugin.c mpd_headers = \ @@ -32,6 +35,8 @@ mpd_headers = \ decode.h \ directory.h \ inputPlugin.h \ + inputPlugins/_flac_common.h \ + inputPlugins/_ogg_common.h \ inputStream.h \ inputStream_file.h \ inputStream_http.h \ diff --git a/src/decode.c b/src/decode.c index 25b34ded8..146f5a974 100644 --- a/src/decode.c +++ b/src/decode.c @@ -322,37 +322,76 @@ void decodeStart(PlayerControl * pc, OutputBuffer * cb, DecoderControl * dc) { ret = DECODE_ERROR_UNKTYPE; if(isRemoteUrl(dc->utf8url)) { + unsigned int next = 0; cb->acceptMetadata = 1; - plugin = getInputPluginFromMimeType(inStream.mime); + + /* first we try mime types: */ + while(ret && (plugin = getInputPluginFromMimeType( + inStream.mime, next++))) { + if (!plugin->streamDecodeFunc) + continue; + if (!(plugin->streamTypes & INPUT_PLUGIN_STREAM_URL)) + continue; + if(plugin->tryDecodeFunc && !plugin->tryDecodeFunc( + &inStream)) + continue; + ret = plugin->streamDecodeFunc(cb, dc, &inStream); + break; + } + + /* if that fails, try suffix matching the URL: */ if(plugin == NULL) { - plugin = getInputPluginFromSuffix( - getSuffix(dc->utf8url)); + char * s = getSuffix(dc->utf8url); + next = 0; + while(ret && (plugin = getInputPluginFromSuffix( + s, next++))) { + if (!plugin->streamDecodeFunc) + continue; + if(!(plugin->streamTypes & + INPUT_PLUGIN_STREAM_URL)) + continue; + if(plugin->tryDecodeFunc && + !plugin->tryDecodeFunc( + &inStream)) + continue; + ret = plugin->streamDecodeFunc( + cb, dc, &inStream); + break; + } } + /* fallback to mp3: */ /* this is needed for bastard streams that don't have a suffix or set the mimeType */ if(plugin == NULL) { - plugin = getInputPluginFromName("mp3"); - } - if(plugin && (plugin->streamTypes & INPUT_PLUGIN_STREAM_URL) && - plugin->streamDecodeFunc) - { - ret = plugin->streamDecodeFunc(cb, dc, &inStream); + /* we already know our mp3Plugin supports streams, no + * need to check for stream{Types,DecodeFunc} */ + if ((plugin = getInputPluginFromName("mp3"))) + ret = plugin->streamDecodeFunc(cb, dc, + &inStream); } } else { + unsigned int next = 0; + char * s = getSuffix(dc->utf8url); cb->acceptMetadata = 0; - plugin = getInputPluginFromSuffix(getSuffix(dc->utf8url)); - if(plugin && (plugin->streamTypes & INPUT_PLUGIN_STREAM_FILE)) - { - if(plugin->streamDecodeFunc) { - ret = plugin->streamDecodeFunc(cb, dc, - &inStream); - } - else if(plugin->fileDecodeFunc) { - closeInputStream(&inStream); - ret = plugin->fileDecodeFunc(cb, dc, path); - } - } + while (ret && (plugin = getInputPluginFromSuffix(s, next++))) { + if(!plugin->streamTypes & INPUT_PLUGIN_STREAM_FILE) + continue; + if(plugin->tryDecodeFunc && !plugin->tryDecodeFunc( + &inStream)) + continue; + + if(plugin->streamDecodeFunc) { + ret = plugin->streamDecodeFunc( + cb, dc, &inStream); + break; + } + else if(plugin->fileDecodeFunc) { + closeInputStream(&inStream); + ret = plugin->fileDecodeFunc( + cb, dc, path); + } + } } if(ret<0 || ret == DECODE_ERROR_UNKTYPE) { diff --git a/src/directory.c b/src/directory.c index d6c5ca659..891e7c351 100644 --- a/src/directory.c +++ b/src/directory.c @@ -323,7 +323,7 @@ int updateInDirectory(Directory * directory, char * shortname, char * name) { if(myStat(name, &st)) return -1; - if(S_ISREG(st.st_mode) && hasMusicSuffix(name)) { + if(S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) { if(0==findInList(directory->songs,shortname,&song)) { addToDirectory(directory, shortname, name); return DIRECTORY_RETURN_UPDATE; @@ -414,7 +414,7 @@ int removeDeletedFromDirectory(Directory * directory, DIR * dir) { while(node) { tmpNode = node->nextNode; if(findInList(entList,node->key,(void **)&name)) { - if(!isMusic(name,NULL)) { + if(!isMusic(name,NULL,0)) { removeSongFromDirectory(directory,node->key); ret = 1; } @@ -550,7 +550,7 @@ int updatePath(char * utf8path) { parentDirectory->parent, parentDirectory->stat->inode, parentDirectory->stat->device) && - song && isMusic(getSongUrl(song), &mtime)) + song && isMusic(getSongUrl(song), &mtime, 0)) { free(path); if(song->mtime==mtime) return 0; @@ -573,7 +573,7 @@ int updatePath(char * utf8path) { * Also, if by chance a directory was replaced by a file of the same * name or vice versa, we need to add it to the db */ - if(isDir(path) || isMusic(path,NULL)) { + if(isDir(path) || isMusic(path,NULL,0)) { parentDirectory = addParentPathToDB(path,&shortname); if(!parentDirectory || ( !parentDirectory->stat && @@ -765,7 +765,7 @@ int addToDirectory(Directory * directory, char * shortname, char * name) { return -1; } - if(S_ISREG(st.st_mode) && hasMusicSuffix(name)) { + if(S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) { Song * song; song = addSongToList(directory->songs, shortname, name, SONG_TYPE_FILE, directory); diff --git a/src/inputPlugin.c b/src/inputPlugin.c index 2fe20f5fc..5c08b265f 100644 --- a/src/inputPlugin.c +++ b/src/inputPlugin.c @@ -49,15 +49,23 @@ static int stringFoundInStringArray(char ** array, char * suffix) { return 0; } -InputPlugin * getInputPluginFromSuffix(char * suffix) { - ListNode * node = inputPlugin_list->firstNode; - InputPlugin * plugin = NULL; +InputPlugin * getInputPluginFromSuffix(char * suffix, unsigned int next) { + static ListNode * pos = NULL; + ListNode * node; + InputPlugin * plugin; if(suffix == NULL) return NULL; + + if (next) { + if (pos) node = pos; + else return NULL; + } else + node = inputPlugin_list->firstNode; while(node != NULL) { plugin = node->data; if(stringFoundInStringArray(plugin->suffixes, suffix)) { + pos = node->nextNode; return plugin; } node = node->nextNode; @@ -66,15 +74,19 @@ InputPlugin * getInputPluginFromSuffix(char * suffix) { return NULL; } -InputPlugin * getInputPluginFromMimeType(char * mimeType) { - ListNode * node = inputPlugin_list->firstNode; - InputPlugin * plugin = NULL; +InputPlugin * getInputPluginFromMimeType(char * mimeType, unsigned int next) { + static ListNode * pos = NULL; + ListNode * node; + InputPlugin * plugin; if(mimeType == NULL) return NULL; + + node = (next && pos) ? pos : inputPlugin_list->firstNode; while(node != NULL) { plugin = node->data; if(stringFoundInStringArray(plugin->mimeTypes, mimeType)) { + pos = node->nextNode; return plugin; } node = node->nextNode; @@ -109,8 +121,9 @@ void printAllInputPluginSuffixes(FILE * fp) { } extern InputPlugin mp3Plugin; -extern InputPlugin oggPlugin; +extern InputPlugin oggvorbisPlugin; extern InputPlugin flacPlugin; +extern InputPlugin oggflacPlugin; extern InputPlugin audiofilePlugin; extern InputPlugin mp4Plugin; extern InputPlugin mpcPlugin; @@ -122,7 +135,8 @@ void initInputPlugins() { /* load plugins here */ loadInputPlugin(&mp3Plugin); - loadInputPlugin(&oggPlugin); + loadInputPlugin(&oggvorbisPlugin); + loadInputPlugin(&oggflacPlugin); loadInputPlugin(&flacPlugin); loadInputPlugin(&audiofilePlugin); loadInputPlugin(&mp4Plugin); diff --git a/src/inputPlugin.h b/src/inputPlugin.h index dc2e2219e..78268a7f3 100644 --- a/src/inputPlugin.h +++ b/src/inputPlugin.h @@ -25,30 +25,53 @@ #include "outputBuffer.h" #include "tag.h" +/* valid values for streamTypes in the InputPlugin struct: */ #define INPUT_PLUGIN_STREAM_FILE 0x01 #define INPUT_PLUGIN_STREAM_URL 0x02 +/* optional, set this to NULL if the InputPlugin doesn't have/need one + * this must return < 0 if there is an error and >= 0 otherwise */ typedef int (* InputPlugin_initFunc) (); +/* optional, set this to NULL if the InputPlugin doesn't have/need one */ typedef void (* InputPlugin_finishFunc) (); +/* boolean return value, returns 1 if the InputStream is decodable by + * the InputPlugin, 0 if not */ +typedef unsigned int (* InputPlugin_tryDecodeFunc) (InputStream *); + +/* this will be used to decode InputStreams, and is recommended for files + * and networked (HTTP) connections. + * + * returns -1 on error, 0 on success */ typedef int (* InputPlugin_streamDecodeFunc) (OutputBuffer *, DecoderControl *, InputStream *); +/* use this if and only if your InputPlugin can only be passed a filename or + * handle as input, and will not allow callbacks to be set (like Ogg-Vorbis + * and FLAC libraries allow) + * + * returns -1 on error, 0 on success */ typedef int (* InputPlugin_fileDecodeFunc) (OutputBuffer *, DecoderControl *, char * path); -/* file should be the full path! */ +/* file should be the full path! Returns NULL if a tag cannot be found + * or read */ typedef MpdTag * (* InputPlugin_tagDupFunc) (char * file); typedef struct _InputPlugin { char * name; InputPlugin_initFunc initFunc; InputPlugin_finishFunc finishFunc; + InputPlugin_tryDecodeFunc tryDecodeFunc; InputPlugin_streamDecodeFunc streamDecodeFunc; InputPlugin_fileDecodeFunc fileDecodeFunc; InputPlugin_tagDupFunc tagDupFunc; + + /* one or more of the INPUT_PLUGIN_STREAM_* values OR'd together */ unsigned char streamTypes; + + /* last element in these arrays must always be a NULL: */ char ** suffixes; char ** mimeTypes; } InputPlugin; @@ -59,9 +82,9 @@ void unloadInputPlugin(InputPlugin * inputPlugin); /* interface for using plugins */ -InputPlugin * getInputPluginFromSuffix(char * suffix); +InputPlugin * getInputPluginFromSuffix(char * suffix, unsigned int next); -InputPlugin * getInputPluginFromMimeType(char * mimeType); +InputPlugin * getInputPluginFromMimeType(char * mimeType, unsigned int next); InputPlugin * getInputPluginFromName(char * name); diff --git a/src/inputPlugins/_flac_common.c b/src/inputPlugins/_flac_common.c new file mode 100644 index 000000000..f6a470ed2 --- /dev/null +++ b/src/inputPlugins/_flac_common.c @@ -0,0 +1,195 @@ +/* the Music Player Daemon (MPD) + * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) + * This project's homepage is: http://www.musicpd.org + * + * Common data structures and functions used by FLAC and OggFLAC + * (c) 2005 by Eric Wong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public 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 +#include +#include +#include +#include + +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, + 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) { + unsigned 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 unsigned int commentMatchesAddToTag( + const FLAC__StreamMetadata_VorbisComment_Entry * entry, + unsigned int itemType, + MpdTag ** tag) +{ + const char * str = (itemType == TAG_ITEM_TRACK) ? + VORBIS_COMMENT_TRACK_KEY : mpdTagItemKeys[itemType]; + size_t slen = strlen(str); + int 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/src/inputPlugins/_flac_common.h b/src/inputPlugins/_flac_common.h new file mode 100644 index 000000000..830d3bfc0 --- /dev/null +++ b/src/inputPlugins/_flac_common.h @@ -0,0 +1,77 @@ +/* the Music Player Daemon (MPD) + * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) + * This project's homepage is: http://www.musicpd.org + * + * Common data structures and functions used by FLAC and OggFLAC + * (c) 2005 by Eric Wong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public 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 +#include + +#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); + +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/src/inputPlugins/_ogg_common.c b/src/inputPlugins/_ogg_common.c new file mode 100644 index 000000000..fb0bb19e0 --- /dev/null +++ b/src/inputPlugins/_ogg_common.c @@ -0,0 +1,65 @@ +/* the Music Player Daemon (MPD) + * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) + * 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public 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 + +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 (r < 0) + 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/src/inputPlugins/_ogg_common.h b/src/inputPlugins/_ogg_common.h new file mode 100644 index 000000000..4cd7fda41 --- /dev/null +++ b/src/inputPlugins/_ogg_common.h @@ -0,0 +1,35 @@ +/* the Music Player Daemon (MPD) + * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) + * 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public 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/src/inputPlugins/aac_plugin.c b/src/inputPlugins/aac_plugin.c index e368f3089..3fb5f4742 100644 --- a/src/inputPlugins/aac_plugin.c +++ b/src/inputPlugins/aac_plugin.c @@ -418,6 +418,7 @@ InputPlugin aacPlugin = { "aac", NULL, + NULL, NULL, NULL, aac_decode, @@ -435,6 +436,7 @@ InputPlugin aacPlugin = NULL, NULL, NULL, + NULL, NULL, NULL, 0, diff --git a/src/inputPlugins/audiofile_plugin.c b/src/inputPlugins/audiofile_plugin.c index adc279b60..d212eaf47 100644 --- a/src/inputPlugins/audiofile_plugin.c +++ b/src/inputPlugins/audiofile_plugin.c @@ -163,6 +163,7 @@ InputPlugin audiofilePlugin = NULL, NULL, NULL, + NULL, audiofile_decode, audiofileTagDup, INPUT_PLUGIN_STREAM_FILE, @@ -176,6 +177,7 @@ InputPlugin audiofilePlugin = { NULL, NULL, + NULL, NULL, NULL, NULL, diff --git a/src/inputPlugins/flac_plugin.c b/src/inputPlugins/flac_plugin.c index 455394888..242dc6f22 100644 --- a/src/inputPlugins/flac_plugin.c +++ b/src/inputPlugins/flac_plugin.c @@ -20,6 +20,8 @@ #ifdef HAVE_FLAC +#include "_flac_common.h" + #include "../utils.h" #include "../log.h" #include "../pcm_utils.h" @@ -34,22 +36,8 @@ #include #include -typedef struct { -#define FLAC_CHUNK_SIZE 4080 - unsigned char chunk[FLAC_CHUNK_SIZE]; - int chunk_length; - float time; - int bitRate; - FLAC__uint64 position; - OutputBuffer * cb; - DecoderControl * dc; - InputStream * inStream; - ReplayGainInfo * replayGainInfo; -} FlacData; - /* this code is based on flac123, from flac-tools */ -int flacSendChunk(FlacData * data); void flacError(const FLAC__SeekableStreamDecoder *, FLAC__StreamDecoderErrorStatus, void *); void flacPrintErroredState(FLAC__SeekableStreamDecoderState state); @@ -75,14 +63,7 @@ int flac_decode(OutputBuffer * cb, DecoderControl * dc, InputStream * inStream) int status = 1; int ret =0; - 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; + init_FlacData(&data, cb, dc, inStream); if(!(flacDec = FLAC__seekable_stream_decoder_new())) { ret = -1; @@ -176,19 +157,16 @@ int flac_decode(OutputBuffer * cb, DecoderControl * dc, InputStream * inStream) dc->seek = 0; } */ - if(dc->stop) { - dc->state = DECODE_STATE_STOP; - dc->stop = 0; - } - else dc->state = DECODE_STATE_STOP; + dc->state = DECODE_STATE_STOP; + dc->stop = 0; fail: if(data.replayGainInfo) freeReplayGainInfo(data.replayGainInfo); - closeInputStream(inStream); - if(flacDec) FLAC__seekable_stream_decoder_delete(flacDec); + closeInputStream(inStream); + return ret; } @@ -250,35 +228,17 @@ FLAC__SeekableStreamDecoderLengthStatus flacLength( } FLAC__bool flacEOF(const FLAC__SeekableStreamDecoder * flacDec, void * fdata) { - FlacData * data = (FlacData *) fdata; - - switch(inputStreamAtEOF(data->inStream)) { - case 1: + FlacData * data = (FlacData *) fdata; + + if (inputStreamAtEOF(data->inStream) == 1) return true; - default: - return false; - } + return false; } void flacError(const FLAC__SeekableStreamDecoder *dec, FLAC__StreamDecoderErrorStatus status, void *fdata) { - FlacData * data = (FlacData *) fdata; - if(data->dc->stop) return; - - switch(status) { - case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: - ERROR("flac lost sync\n"); - break; - case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: - ERROR("bad header\n"); - break; - case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: - ERROR("crc mismatch\n"); - break; - default: - ERROR("unknown flac error\n"); - } + flac_error_common_cb("flac",status,(FlacData *) fdata); } void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) @@ -312,93 +272,10 @@ void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) } } -int flacFindVorbisCommentFloat(const FLAC__StreamMetadata * block, char * cmnt, - float * fl) -{ - int offset = FLAC__metadata_object_vorbiscomment_find_entry_from( - block,0,cmnt); - - if(offset >= 0) { - int pos = strlen(cmnt)+1; /* 1 is for '=' */ - int len = block->data.vorbis_comment.comments[offset].length - -pos; - if(len > 0) { - char * dup = malloc(len+1); - memcpy(dup,&(block->data.vorbis_comment.comments[offset].entry[pos]),len); - dup[len] = '\0'; - *fl = atof(dup); - free(dup); - return 1; - } - } - - return 0; -} - -/* replaygain stuff by AliasMrJones */ -void flacParseReplayGain(const FLAC__StreamMetadata *block, FlacData * data) { - int found = 0; - - if(NULL != data->replayGainInfo) { - freeReplayGainInfo(data->replayGainInfo); - data->replayGainInfo = NULL; - } - - 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; - } -} - void flacMetadata(const FLAC__SeekableStreamDecoder *dec, const FLAC__StreamMetadata *block, void *vdata) { - FlacData * data = (FlacData *)vdata; - - switch(block->type) { - case FLAC__METADATA_TYPE_STREAMINFO: - data->dc->audioFormat.bits = - block->data.stream_info.bits_per_sample; - data->dc->audioFormat.sampleRate = - block->data.stream_info.sample_rate; - data->dc->audioFormat.channels = - block->data.stream_info.channels; - data->dc->totalTime = - ((float)block->data.stream_info.total_samples)/ - data->dc->audioFormat.sampleRate; - getOutputAudioFormat(&(data->dc->audioFormat), - &(data->cb->audioFormat)); - break; - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flacParseReplayGain(block,data); - default: - break; - } -} - -int flacSendChunk(FlacData * data) { - switch(sendDataToOutputBuffer(data->cb, NULL, data->dc, 1, data->chunk, - data->chunk_length, data->time, data->bitRate, - data->replayGainInfo)) - { - case OUTPUT_BUFFER_DC_STOP: - return -1; - default: - return 0; - } - - return 0; + flac_metadata_common_cb(block, (FlacData *)vdata); } FLAC__StreamDecoderWriteStatus flacWrite(const FLAC__SeekableStreamDecoder *dec, @@ -447,79 +324,6 @@ FLAC__StreamDecoderWriteStatus flacWrite(const FLAC__SeekableStreamDecoder *dec, return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } -static int commentMatchesAddToTag( - char * str, - FLAC__StreamMetadata_VorbisComment_Entry * entry, - int itemType, - MpdTag ** tag) -{ - int slen = strlen(str); - int vlen = entry->length - slen; - - if( vlen <= 0 ) return 0; - - if( 0 == strncasecmp(str, entry->entry, slen) ) { - if(*tag == NULL) *tag = newMpdTag(); - addItemToMpdTagWithLen(*tag, itemType, - entry->entry+slen, vlen); - return 1; - } - - return 0; -} - - -static MpdTag * copyVorbisCommentBlockToMpdTag(FLAC__StreamMetadata * block, - MpdTag * tag) -{ - int i; - - for(i = 0; i < block->data.vorbis_comment.num_comments; i++) { - if(commentMatchesAddToTag( - "artist=", - block->data.vorbis_comment.comments+i, - TAG_ITEM_ARTIST, - &tag)); - else if(commentMatchesAddToTag( - "title=", - block->data.vorbis_comment.comments+i, - TAG_ITEM_TITLE, - &tag)); - else if(commentMatchesAddToTag( - "album=", - block->data.vorbis_comment.comments+i, - TAG_ITEM_ALBUM, - &tag)); - else if(commentMatchesAddToTag( - "tracknumber=", - block->data.vorbis_comment.comments+i, - TAG_ITEM_TRACK, - &tag)); - else if(commentMatchesAddToTag( - "genre=", - block->data.vorbis_comment.comments+i, - TAG_ITEM_GENRE, - &tag)); - else if(commentMatchesAddToTag( - "date=", - block->data.vorbis_comment.comments+i, - TAG_ITEM_DATE, - &tag)); - else if(commentMatchesAddToTag( - "composer=", - block->data.vorbis_comment.comments+i, - TAG_ITEM_COMPOSER, - &tag)); - else if(commentMatchesAddToTag( - "performer=", - block->data.vorbis_comment.comments+i, - TAG_ITEM_PERFORMER, - &tag)); - } - - return tag; -} - MpdTag * flacMetadataDup(char * file, int * vorbisCommentFound) { MpdTag * ret = NULL; FLAC__Metadata_SimpleIterator * it; @@ -594,30 +398,32 @@ char * flac_mime_types[] = {"application/x-flac", NULL}; InputPlugin flacPlugin = { - "flac", - NULL, + "flac", + NULL, NULL, - flac_decode, NULL, - flacTagDup, - INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, - flacSuffixes, - flac_mime_types + flac_decode, + NULL, + flacTagDup, + INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + flacSuffixes, + flac_mime_types }; -#else +#else /* !HAVE_FLAC */ InputPlugin flacPlugin = -{ - NULL, - NULL, - NULL, - NULL, +{ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 0, NULL, NULL, - 0, - NULL, - NULL, }; -#endif +#endif /* HAVE_FLAC */ diff --git a/src/inputPlugins/mod_plugin.c b/src/inputPlugins/mod_plugin.c index 547f4af4a..8b5a245a0 100644 --- a/src/inputPlugins/mod_plugin.c +++ b/src/inputPlugins/mod_plugin.c @@ -259,6 +259,7 @@ InputPlugin modPlugin = NULL, mod_finishMikMod, NULL, + NULL, mod_decode, modTagDup, INPUT_PLUGIN_STREAM_FILE, @@ -273,6 +274,7 @@ InputPlugin modPlugin = NULL, NULL, NULL, + NULL, NULL, NULL, NULL, diff --git a/src/inputPlugins/mp3_plugin.c b/src/inputPlugins/mp3_plugin.c index 0657f25b1..8a73ed85d 100644 --- a/src/inputPlugins/mp3_plugin.c +++ b/src/inputPlugins/mp3_plugin.c @@ -820,6 +820,7 @@ InputPlugin mp3Plugin = "mp3", NULL, NULL, + NULL, mp3_decode, NULL, mp3_tagDup, @@ -837,6 +838,7 @@ InputPlugin mp3Plugin = NULL, NULL, NULL, + NULL, 0, NULL, NULL diff --git a/src/inputPlugins/mp4_plugin.c b/src/inputPlugins/mp4_plugin.c index a1af848c5..e41c054c0 100644 --- a/src/inputPlugins/mp4_plugin.c +++ b/src/inputPlugins/mp4_plugin.c @@ -431,6 +431,7 @@ InputPlugin mp4Plugin = NULL, NULL, NULL, + NULL, mp4_decode, mp4TagDup, INPUT_PLUGIN_STREAM_FILE, @@ -447,6 +448,7 @@ InputPlugin mp4Plugin = NULL, NULL, NULL, + NULL, NULL, 0, NULL, diff --git a/src/inputPlugins/mpc_plugin.c b/src/inputPlugins/mpc_plugin.c index 335c5c368..01a165c44 100644 --- a/src/inputPlugins/mpc_plugin.c +++ b/src/inputPlugins/mpc_plugin.c @@ -336,6 +336,7 @@ InputPlugin mpcPlugin = "mpc", NULL, NULL, + NULL, mpc_decode, NULL, mpcTagDup, @@ -351,6 +352,7 @@ InputPlugin mpcPlugin = NULL, NULL, NULL, + NULL, NULL, NULL, NULL, diff --git a/src/inputPlugins/ogg_plugin.c b/src/inputPlugins/ogg_plugin.c deleted file mode 100644 index abadca388..000000000 --- a/src/inputPlugins/ogg_plugin.c +++ /dev/null @@ -1,420 +0,0 @@ -/* the Music Player Daemon (MPD) - * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) - * 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_OGG - -#include "../utils.h" -#include "../audio.h" -#include "../log.h" -#include "../pcm_utils.h" -#include "../inputStream.h" -#include "../outputBuffer.h" -#include "../replayGain.h" - -#include -#include -#include -#include - -#ifndef HAVE_TREMOR -#include -#else -#include -/* 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 - -#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; - -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; -} - -int ogg_seek_cb(void * vdata, ogg_int64_t offset, int whence) { - OggCallbackData * data = (OggCallbackData *)vdata; - - return seekInputStream(data->inStream,offset,whence); -} - -int ogg_close_cb(void * vdata) { - OggCallbackData * data = (OggCallbackData *)vdata; - - return closeInputStream(data->inStream); -} - -long ogg_tell_cb(void * vdata) { - OggCallbackData * data = (OggCallbackData *)vdata; - - return (long)(data->inStream->offset); -} - -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; -} - -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; - } -} - -MpdTag * oggCommentsParse(char ** comments) { - MpdTag * ret = NULL; - char * temp; - - while(*comments) { - if((temp = ogg_parseComment(*comments,"artist"))) { - if(!ret) ret = newMpdTag(); - addItemToMpdTag(ret, TAG_ITEM_ARTIST, temp); - } - else if((temp = ogg_parseComment(*comments,"title"))) { - if(!ret) ret = newMpdTag(); - addItemToMpdTag(ret, TAG_ITEM_TITLE, temp); - } - else if((temp = ogg_parseComment(*comments,"album"))) { - if(!ret) ret = newMpdTag(); - addItemToMpdTag(ret, TAG_ITEM_ALBUM, temp); - } - else if((temp = ogg_parseComment(*comments,"tracknumber"))) { - if(!ret) ret = newMpdTag(); - addItemToMpdTag(ret, TAG_ITEM_TRACK, temp); - } - else if((temp = ogg_parseComment(*comments,"genre"))) { - if(!ret) ret = newMpdTag(); - addItemToMpdTag(ret, TAG_ITEM_GENRE, temp); - } - else if((temp = ogg_parseComment(*comments,"date"))) { - if(!ret) ret = newMpdTag(); - addItemToMpdTag(ret, TAG_ITEM_DATE, temp); - } - else if((temp = ogg_parseComment(*comments,"composer"))) { - if(!ret) ret = newMpdTag(); - addItemToMpdTag(ret, TAG_ITEM_COMPOSER, temp); - } - else if((temp = ogg_parseComment(*comments,"performer"))) { - if(!ret) ret = newMpdTag(); - addItemToMpdTag(ret, TAG_ITEM_PERFORMER, temp); - } - - comments++; - } - - return ret; -} - -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); -} - -int ogg_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; - - 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) { - ERROR("Error decoding Ogg Vorbis stream: "); - switch(ret) { - case OV_EREAD: - ERROR("read error\n"); - break; - case OV_ENOTVORBIS: - ERROR("not vorbis stream\n"); - break; - case OV_EVERSION: - ERROR("vorbis version mismatch\n"); - break; - case OV_EBADHEADER: - ERROR("invalid vorbis header\n"); - break; - case OV_EFAULT: - ERROR("internal logic error\n"); - break; - default: - ERROR("unknown error\n"); - break; - } - 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; -} - -MpdTag * oggTagDup(char * file) { - MpdTag * ret = NULL; - FILE * fp; - OggVorbis_File vf; - - fp = fopen(file,"r"); - if(!fp) - { - DEBUG("oggTagDup: 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; -} - -char * oggSuffixes[] = {"ogg", NULL}; -char * oggMimeTypes[] = {"application/ogg", NULL}; - -InputPlugin oggPlugin = -{ - "ogg", - NULL, - NULL, - ogg_decode, - NULL, - oggTagDup, - INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, - oggSuffixes, - oggMimeTypes -}; - -#else - -InputPlugin oggPlugin = -{ - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - 0, - NULL, - NULL -}; - -#endif diff --git a/src/inputPlugins/oggflac_plugin.c b/src/inputPlugins/oggflac_plugin.c new file mode 100644 index 000000000..25f9a3cdb --- /dev/null +++ b/src/inputPlugins/oggflac_plugin.c @@ -0,0 +1,426 @@ +/* the Music Player Daemon (MPD) + * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) + * This project's homepage is: http://www.musicpd.org + * + * OggFLAC support (half-stolen from flac_plugin.c :)) + * (c) 2005 by Eric Wong + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public 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_OGGFLAC + +#include "_flac_common.h" +#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 +#include +#include +#include +#include + +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; + const int bytesPerSample = data->dc->audioFormat.bits/8; + + 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[] = {"application/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 = +{ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 0, + NULL, + NULL, +}; + +#endif /* HAVE_OGGFLAC */ + diff --git a/src/inputPlugins/oggvorbis_plugin.c b/src/inputPlugins/oggvorbis_plugin.c new file mode 100644 index 000000000..63bac411e --- /dev/null +++ b/src/inputPlugins/oggvorbis_plugin.c @@ -0,0 +1,426 @@ +/* the Music Player Daemon (MPD) + * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) + * 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 +#include +#include +#include + +#ifndef HAVE_TREMOR +#include +#else +#include +/* 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 + +#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; + +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; +} + +int ogg_seek_cb(void * vdata, ogg_int64_t offset, int whence) { + OggCallbackData * data = (OggCallbackData *)vdata; + + return seekInputStream(data->inStream,offset,whence); +} + +int ogg_close_cb(void * vdata) { + OggCallbackData * data = (OggCallbackData *)vdata; + + return closeInputStream(data->inStream); +} + +long ogg_tell_cb(void * vdata) { + OggCallbackData * data = (OggCallbackData *)vdata; + + return (long)(data->inStream->offset); +} + +static inline 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; +} + +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 inline unsigned int ogg_parseCommentAddToTag(char * comment, + unsigned int itemType, MpdTag ** tag) +{ + const char * needle = (itemType == TAG_ITEM_TRACK) ? + VORBIS_COMMENT_TRACK_KEY : mpdTagItemKeys[itemType]; + unsigned int 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) { + unsigned int j; + for (j = TAG_NUM_OF_ITEM_TYPES; j--; ) { + if (ogg_parseCommentAddToTag(*comments, j, &tag)) + break; + } + comments++; + } + + return tag; +} + +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 */ +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; + + 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) { + ERROR("Error decoding Ogg Vorbis stream: "); + switch(ret) { + case OV_EREAD: + ERROR("read error\n"); + break; + case OV_ENOTVORBIS: + ERROR("not vorbis stream\n"); + break; + case OV_EVERSION: + ERROR("vorbis version mismatch\n"); + break; + case OV_EBADHEADER: + ERROR("invalid vorbis header\n"); + break; + case OV_EFAULT: + ERROR("internal logic error\n"); + break; + default: + ERROR("unknown error\n"); + break; + } + 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; +} + +MpdTag * oggvorbis_TagDup(char * file) { + MpdTag * ret = NULL; + FILE * fp; + OggVorbis_File vf; + + fp = fopen(file,"r"); + if(!fp) + { + DEBUG("oggTagDup: 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", 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 = +{ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + 0, + NULL, + NULL, +}; + +#endif /* HAVE_OGGVORBIS */ diff --git a/src/inputStream_http.c b/src/inputStream_http.c index 34303dfab..9781b1b7a 100644 --- a/src/inputStream_http.c +++ b/src/inputStream_http.c @@ -708,6 +708,20 @@ int inputStream_httpOpen(InputStream * inStream, char * url) { } int inputStream_httpSeek(InputStream * inStream, long offset, int whence) { + /* hack to reopen an HTTP stream if we're trying to seek to + * the beginning */ + if ((whence == SEEK_SET) && (offset == 0)) { + InputStreamHTTPData * data; + + data = (InputStreamHTTPData*)inStream->data; + close(data->sock); + data->connState = HTTP_CONN_STATE_REOPEN; + data->buflen = 0; + inStream->offset = 0; + return 0; + } + + /* otherwise, we don't know how to seek in HTTP yet */ return -1; } diff --git a/src/ls.c b/src/ls.c index 762ddd672..f913ff2d3 100644 --- a/src/ls.c +++ b/src/ls.c @@ -262,12 +262,12 @@ int isDir(char * utf8name) { return 0; } -InputPlugin * hasMusicSuffix(char * utf8file) { +InputPlugin * hasMusicSuffix(char * utf8file, unsigned int next) { InputPlugin * ret = NULL; char * s = getSuffix(utf8file); - if(s) { - ret = getInputPluginFromSuffix(s); + if(s) { + ret = getInputPluginFromSuffix(s, next); } else { DEBUG("hasMusicSuffix: The file: %s has no valid suffix\n",utf8file); @@ -276,9 +276,9 @@ InputPlugin * hasMusicSuffix(char * utf8file) { return ret; } -InputPlugin * isMusic(char * utf8file, time_t * mtime) { +InputPlugin * isMusic(char * utf8file, time_t * mtime, unsigned int next) { if(isFile(utf8file,mtime)) { - InputPlugin * plugin = hasMusicSuffix(utf8file); + InputPlugin * plugin = hasMusicSuffix(utf8file, next); if (plugin != NULL) return plugin; } diff --git a/src/ls.h b/src/ls.h index dd8dddd9c..97406c062 100644 --- a/src/ls.h +++ b/src/ls.h @@ -45,9 +45,9 @@ int isDir(char * utf8name); int isPlaylist(char * utf8file); -InputPlugin * hasMusicSuffix(char * utf8file); +InputPlugin * hasMusicSuffix(char * utf8file, unsigned int next); -InputPlugin * isMusic(char * utf8file, time_t * mtime); +InputPlugin * isMusic(char * utf8file, time_t * mtime, unsigned int next); char * dupAndStripPlaylistSuffix(char * file); diff --git a/src/song.c b/src/song.c index 9a2c8d67d..07a7e04fb 100644 --- a/src/song.c +++ b/src/song.c @@ -64,9 +64,12 @@ Song * newSong(char * url, int type, Directory * parentDir) { if(song->type == SONG_TYPE_FILE) { InputPlugin * plugin; - if((plugin = isMusic(getSongUrl(song), &(song->mtime)))) { - song->tag = plugin->tagDupFunc( - rmp2amp(utf8ToFsCharset(getSongUrl(song)))); + unsigned int next = 0; + char * song_url = getSongUrl(song); + char * abs_path = rmp2amp(utf8ToFsCharset(song_url)); + while(!song->tag && (plugin = isMusic(song_url, + &(song->mtime), next++))) { + song->tag = plugin->tagDupFunc(abs_path); } if(!song->tag || song->tag->time<0) { freeSong(song); @@ -100,7 +103,7 @@ Song * addSongToList(SongList * list, char * url, char * utf8path, switch(songType) { case SONG_TYPE_FILE: - if(isMusic(utf8path, NULL)) { + if(isMusic(utf8path, NULL, 0)) { song = newSong(url, songType, parentDirectory); } break; @@ -278,14 +281,17 @@ void readSongInfoIntoList(FILE * fp, SongList * list, Directory * parentDir) { int updateSongInfo(Song * song) { if(song->type == SONG_TYPE_FILE) { InputPlugin * plugin; + unsigned int next = 0; + char * song_url = getSongUrl(song); + char * abs_path = rmp2amp(song_url); if(song->tag) freeMpdTag(song->tag); song->tag = NULL; - if((plugin = isMusic(getSongUrl(song),&(song->mtime)))) { - song->tag = plugin->tagDupFunc( - rmp2amp(getSongUrl(song))); + while(!song->tag && (plugin = isMusic(song_url, + &(song->mtime), next++))) { + song->tag = plugin->tagDupFunc(abs_path); } if(!song->tag || song->tag->time<0) return -1; } diff --git a/src/tag.c b/src/tag.c index b1239f56d..0fa232345 100644 --- a/src/tag.c +++ b/src/tag.c @@ -34,7 +34,7 @@ #include #include #include -#ifdef HAVE_OGG +#ifdef HAVE_OGGVORBIS #include #endif #ifdef HAVE_FLAC -- cgit v1.2.3