From e11355f47d545fe523b019481415b1347aecd4bd Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 26 Oct 2008 11:29:25 +0100 Subject: renamed src/inputPlugins/ to src/decoder/ These plugins are not input plugins, they are decoder plugins. No CamelCase in the directory name. --- src/Makefile.am | 28 +- src/decoder/_flac_common.c | 323 +++++++++++ src/decoder/_flac_common.h | 168 ++++++ src/decoder/_ogg_common.c | 49 ++ src/decoder/_ogg_common.h | 31 + src/decoder/aac_plugin.c | 602 +++++++++++++++++++ src/decoder/audiofile_plugin.c | 147 +++++ src/decoder/ffmpeg_plugin.c | 419 ++++++++++++++ src/decoder/flac_plugin.c | 459 +++++++++++++++ src/decoder/mod_plugin.c | 278 +++++++++ src/decoder/mp3_plugin.c | 1086 +++++++++++++++++++++++++++++++++++ src/decoder/mp4_plugin.c | 423 ++++++++++++++ src/decoder/mpc_plugin.c | 308 ++++++++++ src/decoder/oggflac_plugin.c | 355 ++++++++++++ src/decoder/oggvorbis_plugin.c | 387 +++++++++++++ src/decoder/wavpack_plugin.c | 574 ++++++++++++++++++ src/inputPlugins/_flac_common.c | 323 ----------- src/inputPlugins/_flac_common.h | 168 ------ src/inputPlugins/_ogg_common.c | 49 -- src/inputPlugins/_ogg_common.h | 31 - src/inputPlugins/aac_plugin.c | 602 ------------------- src/inputPlugins/audiofile_plugin.c | 147 ----- src/inputPlugins/ffmpeg_plugin.c | 419 -------------- src/inputPlugins/flac_plugin.c | 459 --------------- src/inputPlugins/mod_plugin.c | 278 --------- src/inputPlugins/mp3_plugin.c | 1086 ----------------------------------- src/inputPlugins/mp4_plugin.c | 423 -------------- src/inputPlugins/mpc_plugin.c | 308 ---------- src/inputPlugins/oggflac_plugin.c | 355 ------------ src/inputPlugins/oggvorbis_plugin.c | 387 ------------- src/inputPlugins/wavpack_plugin.c | 574 ------------------ 31 files changed, 5623 insertions(+), 5623 deletions(-) create mode 100644 src/decoder/_flac_common.c create mode 100644 src/decoder/_flac_common.h create mode 100644 src/decoder/_ogg_common.c create mode 100644 src/decoder/_ogg_common.h create mode 100644 src/decoder/aac_plugin.c create mode 100644 src/decoder/audiofile_plugin.c create mode 100644 src/decoder/ffmpeg_plugin.c create mode 100644 src/decoder/flac_plugin.c create mode 100644 src/decoder/mod_plugin.c create mode 100644 src/decoder/mp3_plugin.c create mode 100644 src/decoder/mp4_plugin.c create mode 100644 src/decoder/mpc_plugin.c create mode 100644 src/decoder/oggflac_plugin.c create mode 100644 src/decoder/oggvorbis_plugin.c create mode 100644 src/decoder/wavpack_plugin.c delete mode 100644 src/inputPlugins/_flac_common.c delete mode 100644 src/inputPlugins/_flac_common.h delete mode 100644 src/inputPlugins/_ogg_common.c delete mode 100644 src/inputPlugins/_ogg_common.h delete mode 100644 src/inputPlugins/aac_plugin.c delete mode 100644 src/inputPlugins/audiofile_plugin.c delete mode 100644 src/inputPlugins/ffmpeg_plugin.c delete mode 100644 src/inputPlugins/flac_plugin.c delete mode 100644 src/inputPlugins/mod_plugin.c delete mode 100644 src/inputPlugins/mp3_plugin.c delete mode 100644 src/inputPlugins/mp4_plugin.c delete mode 100644 src/inputPlugins/mpc_plugin.c delete mode 100644 src/inputPlugins/oggflac_plugin.c delete mode 100644 src/inputPlugins/oggvorbis_plugin.c delete mode 100644 src/inputPlugins/wavpack_plugin.c diff --git a/src/Makefile.am b/src/Makefile.am index 967dfb861..5438c0abf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -45,8 +45,8 @@ mpd_headers = \ dirvec.h \ gcc.h \ decoder_list.h \ - inputPlugins/_flac_common.h \ - inputPlugins/_ogg_common.h \ + decoder/_flac_common.h \ + decoder/_ogg_common.h \ inputStream.h \ inputStream_file.h \ inputStream_http.h \ @@ -193,51 +193,51 @@ endif # decoder plugins if HAVE_MAD -mpd_SOURCES += inputPlugins/mp3_plugin.c +mpd_SOURCES += decoder/mp3_plugin.c endif if HAVE_MPCDEC -mpd_SOURCES += inputPlugins/mpc_plugin.c +mpd_SOURCES += decoder/mpc_plugin.c endif if HAVE_WAVPACK -mpd_SOURCES += inputPlugins/wavpack_plugin.c +mpd_SOURCES += decoder/wavpack_plugin.c endif if HAVE_FAAD -mpd_SOURCES += inputPlugins/aac_plugin.c inputPlugins/mp4_plugin.c +mpd_SOURCES += decoder/aac_plugin.c decoder/mp4_plugin.c endif if HAVE_OGG_COMMON -mpd_SOURCES += inputPlugins/_ogg_common.c +mpd_SOURCES += decoder/_ogg_common.c endif if HAVE_FLAC_COMMON -mpd_SOURCES += inputPlugins/_flac_common.c +mpd_SOURCES += decoder/_flac_common.c endif if HAVE_OGGVORBIS -mpd_SOURCES += inputPlugins/oggvorbis_plugin.c +mpd_SOURCES += decoder/oggvorbis_plugin.c endif if HAVE_FLAC -mpd_SOURCES += inputPlugins/flac_plugin.c +mpd_SOURCES += decoder/flac_plugin.c endif if HAVE_OGGFLAC -mpd_SOURCES += inputPlugins/oggflac_plugin.c +mpd_SOURCES += decoder/oggflac_plugin.c endif if HAVE_AUDIOFILE -mpd_SOURCES += inputPlugins/audiofile_plugin.c +mpd_SOURCES += decoder/audiofile_plugin.c endif if HAVE_MIKMOD -mpd_SOURCES += inputPlugins/mod_plugin.c +mpd_SOURCES += decoder/mod_plugin.c endif if HAVE_FFMPEG -mpd_SOURCES += inputPlugins/ffmpeg_plugin.c +mpd_SOURCES += decoder/ffmpeg_plugin.c endif diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c new file mode 100644 index 000000000..db43e0003 --- /dev/null +++ b/src/decoder/_flac_common.c @@ -0,0 +1,323 @@ +/* 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public 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" +#include "../log.h" + +#include +#include + +void init_FlacData(FlacData * data, struct decoder * decoder, + InputStream * inStream) +{ + data->time = 0; + data->position = 0; + data->bitRate = 0; + data->decoder = decoder; + 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 *p = &(block->data.vorbis_comment. + comments[offset].entry[pos]); + tmp = p[len]; + p[len] = '\0'; + *fl = (float)atof((char *)p); + p[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, + struct tag ** 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 = tag_new(); + + tag_add_item_n(*tag, itemType, + (char *)(entry->entry + slen + 1), vlen); + + return 1; + } + + return 0; +} + +struct tag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, + struct tag * 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) +{ + const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info); + + switch (block->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + data->audio_format.bits = (int8_t)si->bits_per_sample; + data->audio_format.sample_rate = si->sample_rate; + data->audio_format.channels = (int8_t)si->channels; + data->total_time = ((float)si->total_samples) / (si->sample_rate); + break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + flacParseReplayGain(block, data); + default: + break; + } +} + +void flac_error_common_cb(const char *plugin, + const FLAC__StreamDecoderErrorStatus status, + mpd_unused FlacData * data) +{ + if (decoder_get_command(data->decoder) == DECODE_COMMAND_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); + } +} + +static void flac_convert_stereo16(int16_t *dest, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + for (; position < end; ++position) { + *dest++ = buf[0][position]; + *dest++ = buf[1][position]; + } +} + +static void +flac_convert_16(int16_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +/** + * Note: this function also handles 24 bit files! + */ +static void +flac_convert_32(int32_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +static void +flac_convert_8(int8_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +static void flac_convert(unsigned char *dest, + unsigned int num_channels, + unsigned int bytes_per_sample, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + switch (bytes_per_sample) { + case 2: + if (num_channels == 2) + flac_convert_stereo16((int16_t*)dest, buf, + position, end); + else + flac_convert_16((int16_t*)dest, num_channels, buf, + position, end); + break; + + case 4: + flac_convert_32((int32_t*)dest, num_channels, buf, + position, end); + break; + + case 1: + flac_convert_8((int8_t*)dest, num_channels, buf, + position, end); + break; + } +} + +FLAC__StreamDecoderWriteStatus +flac_common_write(FlacData *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[]) +{ + unsigned int c_samp; + const unsigned int num_channels = frame->header.channels; + const unsigned int bytes_per_sample = + audio_format_sample_size(&data->audio_format); + const unsigned int bytes_per_channel = + bytes_per_sample * frame->header.channels; + const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel; + unsigned int num_samples; + enum decoder_command cmd; + + if (bytes_per_sample != 1 && bytes_per_sample != 2 && + bytes_per_sample != 4) + /* exotic unsupported bit rate */ + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + for (c_samp = 0; c_samp < frame->header.blocksize; + c_samp += num_samples) { + num_samples = frame->header.blocksize - c_samp; + if (num_samples > max_samples) + num_samples = max_samples; + + flac_convert(data->chunk, + num_channels, bytes_per_sample, buf, + c_samp, c_samp + num_samples); + + cmd = decoder_data(data->decoder, data->inStream, + 1, data->chunk, + num_samples * bytes_per_channel, + data->time, data->bitRate, + data->replayGainInfo); + switch (cmd) { + case DECODE_COMMAND_NONE: + case DECODE_COMMAND_START: + break; + + case DECODE_COMMAND_STOP: + return + FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + + case DECODE_COMMAND_SEEK: + return + FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h new file mode 100644 index 000000000..45714b4bd --- /dev/null +++ b/src/decoder/_flac_common.h @@ -0,0 +1,168 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 "../decoder_api.h" + +#include +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +# include +# 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 +# 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 + */ +# include "_ogg_common.h" + +# include +# 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 + +#define FLAC_CHUNK_SIZE 4080 + +typedef struct { + unsigned char chunk[FLAC_CHUNK_SIZE]; + float time; + unsigned int bitRate; + struct audio_format audio_format; + float total_time; + FLAC__uint64 position; + struct decoder *decoder; + InputStream *inStream; + ReplayGainInfo *replayGainInfo; + struct tag *tag; +} FlacData; + +/* initializes a given FlacData struct */ +void init_FlacData(FlacData * data, struct decoder * decoder, + 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); + +struct tag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, + struct tag *tag); + +FLAC__StreamDecoderWriteStatus +flac_common_write(FlacData *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[]); + +#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c new file mode 100644 index 000000000..841b2ad3f --- /dev/null +++ b/src/decoder/_ogg_common.c @@ -0,0 +1,49 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 "_ogg_common.h" +#include "_flac_common.h" +#include "../utils.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; + + seekInputStream(inStream, 0, SEEK_SET); + + r = decoder_read(NULL, inStream, buf, sizeof(buf)); + + if (r > 0) + 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; +} diff --git a/src/decoder/_ogg_common.h b/src/decoder/_ogg_common.h new file mode 100644 index 000000000..7c9e7b630 --- /dev/null +++ b/src/decoder/_ogg_common.h @@ -0,0 +1,31 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 "../decoder_api.h" + +typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; + +ogg_stream_type ogg_stream_type_detect(InputStream * inStream); + +#endif /* _OGG_COMMON_H */ diff --git a/src/decoder/aac_plugin.c b/src/decoder/aac_plugin.c new file mode 100644 index 000000000..7842bcc22 --- /dev/null +++ b/src/decoder/aac_plugin.c @@ -0,0 +1,602 @@ +/* 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 "../decoder_api.h" + +#define AAC_MAX_CHANNELS 6 + +#include "../utils.h" +#include "../log.h" + +#include +#include + +/* all code here is either based on or copied from FAAD2's frontend code */ +typedef struct { + struct decoder *decoder; + InputStream *inStream; + size_t bytesIntoBuffer; + size_t bytesConsumed; + off_t fileOffset; + unsigned char *buffer; + int atEof; +} AacBuffer; + +static void aac_buffer_shift(AacBuffer * b, size_t length) +{ + assert(length >= b->bytesConsumed); + assert(length <= b->bytesConsumed + b->bytesIntoBuffer); + + memmove(b->buffer, b->buffer + length, + b->bytesConsumed + b->bytesIntoBuffer - length); + + length -= b->bytesConsumed; + b->bytesConsumed = 0; + b->bytesIntoBuffer -= length; +} + +static void fillAacBuffer(AacBuffer * b) +{ + size_t bread; + + if (b->bytesIntoBuffer >= FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS) + /* buffer already full */ + return; + + aac_buffer_shift(b, b->bytesConsumed); + + if (!b->atEof) { + size_t rest = FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS - + b->bytesIntoBuffer; + + bread = decoder_read(b->decoder, b->inStream, + (void *)(b->buffer + b->bytesIntoBuffer), + rest); + if (bread == 0 && inputStreamAtEOF(b->inStream)) + b->atEof = 1; + b->bytesIntoBuffer += bread; + } + + if ((b->bytesIntoBuffer > 3 && memcmp(b->buffer, "TAG", 3) == 0) || + (b->bytesIntoBuffer > 11 && + memcmp(b->buffer, "LYRICSBEGIN", 11) == 0) || + (b->bytesIntoBuffer > 8 && memcmp(b->buffer, "APETAGEX", 8) == 0)) + b->bytesIntoBuffer = 0; +} + +static void advanceAacBuffer(AacBuffer * b, size_t 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 +}; + +/** + * Check whether the buffer head is an AAC frame, and return the frame + * length. Returns 0 if it is not a frame. + */ +static size_t adts_check_frame(AacBuffer * b) +{ + if (b->bytesIntoBuffer <= 7) + return 0; + + /* check syncword */ + if (!((b->buffer[0] == 0xFF) && ((b->buffer[1] & 0xF6) == 0xF0))) + return 0; + + return (((unsigned int)b->buffer[3] & 0x3) << 11) | + (((unsigned int)b->buffer[4]) << 3) | + (b->buffer[5] >> 5); +} + +/** + * Find the next AAC frame in the buffer. Returns 0 if no frame is + * found or if not enough data is available. + */ +static size_t adts_find_frame(AacBuffer * b) +{ + const unsigned char *p; + size_t frame_length; + + while ((p = memchr(b->buffer, 0xff, b->bytesIntoBuffer)) != NULL) { + /* discard data before 0xff */ + if (p > b->buffer) + aac_buffer_shift(b, p - b->buffer); + + if (b->bytesIntoBuffer <= 7) + /* not enough data yet */ + return 0; + + /* is it a frame? */ + frame_length = adts_check_frame(b); + if (frame_length > 0) + /* yes, it is */ + return frame_length; + + /* it's just some random 0xff byte; discard and and + continue searching */ + aac_buffer_shift(b, 1); + } + + /* nothing at all; discard the whole buffer */ + aac_buffer_shift(b, b->bytesIntoBuffer); + return 0; +} + +static void adtsParse(AacBuffer * b, float *length) +{ + unsigned int frames, frameLength; + int sample_rate = 0; + float framesPerSec; + + /* Read all frames to ensure correct time and bitrate */ + for (frames = 0;; frames++) { + fillAacBuffer(b); + + frameLength = adts_find_frame(b); + if (frameLength > 0) { + if (frames == 0) { + sample_rate = adtsSampleRates[(b-> + buffer[2] & 0x3c) + >> 2]; + } + + if (frameLength > b->bytesIntoBuffer) + break; + + advanceAacBuffer(b, frameLength); + } else + break; + } + + framesPerSec = (float)sample_rate / 1024.0; + if (framesPerSec != 0) + *length = (float)frames / framesPerSec; +} + +static void initAacBuffer(AacBuffer * b, + struct decoder *decoder, InputStream * inStream) +{ + memset(b, 0, sizeof(AacBuffer)); + + b->decoder = decoder; + b->inStream = inStream; + + b->buffer = xmalloc(FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + memset(b->buffer, 0, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); +} + +static void aac_parse_header(AacBuffer * b, float *length) +{ + size_t fileread; + size_t tagsize; + + if (length) + *length = -1; + + fileread = b->inStream->size; + + fillAacBuffer(b); + + tagsize = 0; + if (b->bytesIntoBuffer >= 10 && !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 (length == NULL) + return; + + if (b->bytesIntoBuffer >= 2 && + (b->buffer[0] == 0xFF) && ((b->buffer[1] & 0xF6) == 0xF0)) { + adtsParse(b, length); + seekInputStream(b->inStream, tagsize, SEEK_SET); + + b->bytesIntoBuffer = 0; + b->bytesConsumed = 0; + b->fileOffset = tagsize; + + fillAacBuffer(b); + } 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; + faacDecHandle decoder; + faacDecConfigurationPtr config; + uint32_t sample_rate; + unsigned char channels; + InputStream inStream; + long bread; + + if (openInputStream(&inStream, file) < 0) + return -1; + + initAacBuffer(&b, NULL, &inStream); + aac_parse_header(&b, &length); + + 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, + &sample_rate, &channels); +#else + bread = faacDecInit(decoder, b.buffer, &sample_rate, &channels); +#endif + if (bread >= 0 && sample_rate > 0 && channels > 0) + length = 0; + + faacDecClose(decoder); + } + + if (b.buffer) + free(b.buffer); + closeInputStream(&inStream); + + return length; +} + +static int getAacTotalTime(char *file) +{ + int file_time = -1; + float length; + + if ((length = getAacFloatTotalTime(file)) >= 0) + file_time = length + 0.5; + + return file_time; +} + +static int aac_stream_decode(struct decoder * mpd_decoder, + InputStream *inStream) +{ + float file_time; + float totalTime = 0; + faacDecHandle decoder; + faacDecFrameInfo frameInfo; + faacDecConfigurationPtr config; + long bread; + struct audio_format audio_format; + uint32_t sample_rate; + unsigned char channels; + unsigned int sampleCount; + char *sampleBuffer; + size_t sampleBufferLen; + uint16_t bitRate = 0; + AacBuffer b; + int initialized = 0; + + initAacBuffer(&b, mpd_decoder, inStream); + + 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); + + while (b.bytesIntoBuffer < FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS && + !b.atEof && + decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { + fillAacBuffer(&b); + adts_find_frame(&b); + fillAacBuffer(&b); + my_usleep(10000); + } + +#ifdef HAVE_FAAD_BUFLEN_FUNCS + bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer, + &sample_rate, &channels); +#else + bread = faacDecInit(decoder, b.buffer, &sample_rate, &channels); +#endif + if (bread < 0) { + ERROR("Error not a AAC stream.\n"); + faacDecClose(decoder); + if (b.buffer) + free(b.buffer); + return -1; + } + + audio_format.bits = 16; + + file_time = 0.0; + + advanceAacBuffer(&b, bread); + + while (1) { + fillAacBuffer(&b); + adts_find_frame(&b); + fillAacBuffer(&b); + + if (b.bytesIntoBuffer == 0) + 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 stream\n"); + ERROR("faad2 error: %s\n", + faacDecGetErrorMessage(frameInfo.error)); + break; + } +#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE + sample_rate = frameInfo.samplerate; +#endif + + if (!initialized) { + audio_format.channels = frameInfo.channels; + audio_format.sample_rate = sample_rate; + decoder_initialized(mpd_decoder, &audio_format, totalTime); + initialized = 1; + } + + advanceAacBuffer(&b, frameInfo.bytesconsumed); + + sampleCount = (unsigned long)(frameInfo.samples); + + if (sampleCount > 0) { + bitRate = frameInfo.bytesconsumed * 8.0 * + frameInfo.channels * sample_rate / + frameInfo.samples / 1000 + 0.5; + file_time += + (float)(frameInfo.samples) / frameInfo.channels / + sample_rate; + } + + sampleBufferLen = sampleCount * 2; + + decoder_data(mpd_decoder, NULL, 0, sampleBuffer, + sampleBufferLen, file_time, + bitRate, NULL); + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { + decoder_seek_error(mpd_decoder); + } else if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP) + break; + } + + decoder_flush(mpd_decoder); + + faacDecClose(decoder); + if (b.buffer) + free(b.buffer); + + if (!initialized) + return -1; + + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { + decoder_seek_error(mpd_decoder); + } + + return 0; +} + + +static int aac_decode(struct decoder * mpd_decoder, char *path) +{ + float file_time; + float totalTime; + faacDecHandle decoder; + faacDecFrameInfo frameInfo; + faacDecConfigurationPtr config; + long bread; + struct audio_format audio_format; + uint32_t sample_rate; + unsigned char channels; + unsigned int sampleCount; + char *sampleBuffer; + size_t sampleBufferLen; + /*float * seekTable; + long seekTableEnd = -1; + int seekPositionFound = 0; */ + uint16_t bitRate = 0; + AacBuffer b; + InputStream inStream; + int initialized = 0; + + if ((totalTime = getAacFloatTotalTime(path)) < 0) + return -1; + + if (openInputStream(&inStream, path) < 0) + return -1; + + initAacBuffer(&b, mpd_decoder, &inStream); + aac_parse_header(&b, 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, + &sample_rate, &channels); +#else + bread = faacDecInit(decoder, b.buffer, &sample_rate, &channels); +#endif + if (bread < 0) { + ERROR("Error not a AAC stream.\n"); + faacDecClose(decoder); + if (b.buffer) + free(b.buffer); + return -1; + } + + audio_format.bits = 16; + + file_time = 0.0; + + advanceAacBuffer(&b, bread); + + while (1) { + fillAacBuffer(&b); + + if (b.bytesIntoBuffer == 0) + 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)); + break; + } +#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE + sample_rate = frameInfo.samplerate; +#endif + + if (!initialized) { + audio_format.channels = frameInfo.channels; + audio_format.sample_rate = sample_rate; + decoder_initialized(mpd_decoder, &audio_format, + totalTime); + initialized = 1; + } + + advanceAacBuffer(&b, frameInfo.bytesconsumed); + + sampleCount = (unsigned long)(frameInfo.samples); + + if (sampleCount > 0) { + bitRate = frameInfo.bytesconsumed * 8.0 * + frameInfo.channels * sample_rate / + frameInfo.samples / 1000 + 0.5; + file_time += + (float)(frameInfo.samples) / frameInfo.channels / + sample_rate; + } + + sampleBufferLen = sampleCount * 2; + + decoder_data(mpd_decoder, NULL, 0, sampleBuffer, + sampleBufferLen, file_time, + bitRate, NULL); + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { + decoder_seek_error(mpd_decoder); + } else if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP) + break; + } + + decoder_flush(mpd_decoder); + + faacDecClose(decoder); + if (b.buffer) + free(b.buffer); + + if (!initialized) + return -1; + + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { + decoder_seek_error(mpd_decoder); + } + + return 0; +} + +static struct tag *aacTagDup(char *file) +{ + struct tag *ret = NULL; + int file_time = getAacTotalTime(file); + + if (file_time >= 0) { + if ((ret = tag_id3_load(file)) == NULL) + ret = tag_new(); + ret->time = file_time; + } else { + DEBUG("aacTagDup: Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static const char *aac_suffixes[] = { "aac", NULL }; +static const char *aac_mimeTypes[] = { "audio/aac", "audio/aacp", NULL }; + +struct decoder_plugin aacPlugin = { + .name = "aac", + .stream_decode = aac_stream_decode, + .file_decode = aac_decode, + .tag_dup = aacTagDup, + .stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, + .suffixes = aac_suffixes, + .mime_types = aac_mimeTypes +}; diff --git a/src/decoder/audiofile_plugin.c b/src/decoder/audiofile_plugin.c new file mode 100644 index 000000000..99846e853 --- /dev/null +++ b/src/decoder/audiofile_plugin.c @@ -0,0 +1,147 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * libaudiofile (wave) support added 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 "../decoder_api.h" +#include "../log.h" + +#include +#include + +/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ +#define CHUNK_SIZE 1020 + +static int getAudiofileTotalTime(char *file) +{ + int total_time; + AFfilehandle af_fp = afOpenFile(file, "r", NULL); + if (af_fp == AF_NULL_FILEHANDLE) { + return -1; + } + total_time = (int) + ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK) + / afGetRate(af_fp, AF_DEFAULT_TRACK)); + afCloseFile(af_fp); + return total_time; +} + +static int audiofile_decode(struct decoder * decoder, char *path) +{ + int fs, frame_count; + AFfilehandle af_fp; + int bits; + struct audio_format audio_format; + float total_time; + uint16_t bitRate; + struct stat st; + int ret, current = 0; + char chunk[CHUNK_SIZE]; + + 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); + audio_format.bits = (uint8_t)bits; + audio_format.sample_rate = + (unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK); + audio_format.channels = + (uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK); + + frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); + + total_time = ((float)frame_count / (float)audio_format.sample_rate); + + bitRate = (uint16_t)(st.st_size * 8.0 / total_time / 1000.0 + 0.5); + + if (audio_format.bits != 8 && audio_format.bits != 16) { + ERROR("Only 8 and 16-bit files are supported. %s is %i-bit\n", + path, audio_format.bits); + afCloseFile(af_fp); + return -1; + } + + fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); + + decoder_initialized(decoder, &audio_format, total_time); + + do { + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { + decoder_clear(decoder); + current = decoder_seek_where(decoder) * + audio_format.sample_rate; + afSeekFrame(af_fp, AF_DEFAULT_TRACK, current); + decoder_command_finished(decoder); + } + + ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, + CHUNK_SIZE / fs); + if (ret <= 0) + break; + + current += ret; + decoder_data(decoder, NULL, 1, + chunk, ret * fs, + (float)current / (float)audio_format.sample_rate, + bitRate, NULL); + } while (decoder_get_command(decoder) != DECODE_COMMAND_STOP); + + decoder_flush(decoder); + + afCloseFile(af_fp); + + return 0; +} + +static struct tag *audiofileTagDup(char *file) +{ + struct tag *ret = NULL; + int total_time = getAudiofileTotalTime(file); + + if (total_time >= 0) { + if (!ret) + ret = tag_new(); + ret->time = total_time; + } else { + DEBUG + ("audiofileTagDup: Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static const char *audiofileSuffixes[] = { "wav", "au", "aiff", "aif", NULL }; + +struct decoder_plugin audiofilePlugin = { + .name = "audiofile", + .file_decode = audiofile_decode, + .tag_dup = audiofileTagDup, + .stream_types = INPUT_PLUGIN_STREAM_FILE, + .suffixes = audiofileSuffixes, +}; diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c new file mode 100644 index 000000000..6455cd1ce --- /dev/null +++ b/src/decoder/ffmpeg_plugin.c @@ -0,0 +1,419 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2008 Viliam Mateicka + * 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 "../decoder_api.h" +#include "../log.h" +#include "../utils.h" +#include "../log.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef OLD_FFMPEG_INCLUDES +#include +#include +#include +#else +#include +#include +#include +#endif + +typedef struct { + int audioStream; + AVFormatContext *pFormatCtx; + AVCodecContext *aCodecCtx; + AVCodec *aCodec; + struct decoder *decoder; + InputStream *input; + struct tag *tag; +} BasePtrs; + +typedef struct { + /** hack - see url_to_base() */ + char url[8]; + + struct decoder *decoder; + InputStream *input; +} FopsHelper; + +/** + * Convert a faked mpd:// URL to a FopsHelper structure. This is a + * hack because ffmpeg does not provide a nice API for passing a + * user-defined pointer to mpdurl_open(). + */ +static FopsHelper *url_to_base(const char *url) +{ + union { + const char *in; + FopsHelper *out; + } u = { .in = url }; + return u.out; +} + +static int mpdurl_open(URLContext *h, const char *filename, + mpd_unused int flags) +{ + FopsHelper *base = url_to_base(filename); + h->priv_data = base; + h->is_streamed = (base->input->seekable ? 0 : 1); + return 0; +} + +static int mpdurl_read(URLContext *h, unsigned char *buf, int size) +{ + int ret; + FopsHelper *base = (FopsHelper *) h->priv_data; + while (1) { + ret = readFromInputStream(base->input, (void *)buf, size); + if (ret == 0) { + DEBUG("ret 0\n"); + if (inputStreamAtEOF(base->input) || + (base->decoder && + decoder_get_command(base->decoder) != DECODE_COMMAND_NONE)) { + DEBUG("eof stream\n"); + return ret; + } else { + my_usleep(10000); + } + } else { + break; + } + } + return ret; +} + +static int64_t mpdurl_seek(URLContext *h, int64_t pos, int whence) +{ + FopsHelper *base = (FopsHelper *) h->priv_data; + if (whence != AVSEEK_SIZE) { //only ftell + (void) seekInputStream(base->input, pos, whence); + } + return base->input->offset; +} + +static int mpdurl_close(URLContext *h) +{ + FopsHelper *base = (FopsHelper *) h->priv_data; + if (base && base->input->seekable) { + (void) seekInputStream(base->input, 0, SEEK_SET); + } + h->priv_data = 0; + return 0; +} + +static URLProtocol mpdurl_fileops = { + .name = "mpd", + .url_open = mpdurl_open, + .url_read = mpdurl_read, + .url_seek = mpdurl_seek, + .url_close = mpdurl_close, +}; + +static int ffmpeg_init(void) +{ + av_register_all(); + register_protocol(&mpdurl_fileops); + return 0; +} + +static int ffmpeg_helper(InputStream *input, int (*callback)(BasePtrs *ptrs), + BasePtrs *ptrs) +{ + AVFormatContext *pFormatCtx; + AVCodecContext *aCodecCtx; + AVCodec *aCodec; + int ret, audioStream; + unsigned i; + FopsHelper fopshelp = { + .url = "mpd://X", /* only the mpd:// prefix matters */ + }; + + fopshelp.input = input; + if (ptrs && ptrs->decoder) { + fopshelp.decoder = ptrs->decoder; //are we in decoding loop ? + } else { + fopshelp.decoder = NULL; + } + + //ffmpeg works with ours "fileops" helper + if (av_open_input_file(&pFormatCtx, fopshelp.url, NULL, 0, NULL)!=0) { + ERROR("Open failed!\n"); + return -1; + } + + if (av_find_stream_info(pFormatCtx)<0) { + ERROR("Couldn't find stream info!\n"); + return -1; + } + + audioStream = -1; + for(i=0; inb_streams; i++) { + if (pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO && + audioStream < 0) { + audioStream=i; + } + } + + if(audioStream==-1) { + ERROR("No audio stream inside!\n"); + return -1; + } + + aCodecCtx = pFormatCtx->streams[audioStream]->codec; + aCodec = avcodec_find_decoder(aCodecCtx->codec_id); + + if (!aCodec) { + ERROR("Unsupported audio codec!\n"); + return -1; + } + + if (avcodec_open(aCodecCtx, aCodec)<0) { + ERROR("Could not open codec!\n"); + return -1; + } + + if (callback) { + ptrs->audioStream = audioStream; + ptrs->pFormatCtx = pFormatCtx; + ptrs->aCodecCtx = aCodecCtx; + ptrs->aCodec = aCodec; + + ret = (*callback)( ptrs ); + } else { + ret = 0; + DEBUG("playable\n"); + } + + avcodec_close(aCodecCtx); + av_close_input_file(pFormatCtx); + + return ret; +} + +static bool ffmpeg_try_decode(InputStream *input) +{ + int ret; + if (input->seekable) { + ret = ffmpeg_helper(input, NULL, NULL); + } else { + ret = 0; + } + return (ret == -1 ? 0 : 1); +} + +static int ffmpeg_decode_internal(BasePtrs *base) +{ + struct decoder *decoder = base->decoder; + AVCodecContext *aCodecCtx = base->aCodecCtx; + AVFormatContext *pFormatCtx = base->pFormatCtx; + AVPacket packet; + int len, audio_size; + int position; + struct audio_format audio_format; + int current, total_time; + uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2]; + + total_time = 0; + + DEBUG("decoder_start\n"); + + if (aCodecCtx->channels > 2) { + aCodecCtx->channels = 2; + } + + audio_format.bits = (uint8_t)16; + audio_format.sample_rate = (unsigned int)aCodecCtx->sample_rate; + audio_format.channels = aCodecCtx->channels; + + // frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); + // total_time = ((float)frame_count / (float)audio_format.sample_rate); + + //there is some problem with this on some demux (mp3 at least) + if (pFormatCtx->duration != (int)AV_NOPTS_VALUE) { + total_time = pFormatCtx->duration / AV_TIME_BASE; + } + + DEBUG("ffmpeg sample rate: %dHz %d channels\n", + aCodecCtx->sample_rate, aCodecCtx->channels); + + decoder_initialized(decoder, &audio_format, total_time); + + position = 0; + + DEBUG("duration:%d (%d secs)\n", (int) pFormatCtx->duration, + (int) total_time); + + do { + + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { + + DEBUG("seek\n"); + decoder_clear(decoder); + current = decoder_seek_where(decoder) * AV_TIME_BASE; + + if (av_seek_frame(pFormatCtx, -1, current , 0) < 0) { + WARNING("seek to %d failed\n", current); + } + + decoder_command_finished(decoder); + } + + if (av_read_frame(pFormatCtx, &packet) >= 0) { + if(packet.stream_index == base->audioStream) { + + position = av_rescale_q(packet.pts, pFormatCtx->streams[base->audioStream]->time_base, + (AVRational){1, 1}); + + audio_size = sizeof(audio_buf); + len = avcodec_decode_audio2(aCodecCtx, + (int16_t *)audio_buf, + &audio_size, + packet.data, + packet.size); + + if(len >= 0) { + if(audio_size >= 0) { + // DEBUG("sending data %d/%d\n", audio_size, len); + + decoder_data(decoder, NULL, 1, + audio_buf, audio_size, + position, //(float)current / (float)audio_format.sample_rate, + aCodecCtx->bit_rate / 1000, NULL); + + } + } else { + WARNING("skiping frame!\n"); + } + } + av_free_packet(&packet); + } else { + //end of file + break; + } + } while (decoder_get_command(decoder) != DECODE_COMMAND_STOP); + + decoder_flush(decoder); + + DEBUG("decoder finish\n"); + + return 0; +} + +static int ffmpeg_decode(struct decoder *decoder, InputStream *input) +{ + BasePtrs base; + int ret; + + DEBUG("decode start\n"); + + base.input = input; + base.decoder = decoder; + + ret = ffmpeg_helper(input, ffmpeg_decode_internal, &base); + + DEBUG("decode finish\n"); + + return ret; +} + +static int ffmpeg_tag_internal(BasePtrs *base) +{ + struct tag *tag = (struct tag *) base->tag; + + if (base->pFormatCtx->duration != (int)AV_NOPTS_VALUE) { + tag->time = base->pFormatCtx->duration / AV_TIME_BASE; + } else { + tag->time = 0; + } + return 0; +} + +//no tag reading in ffmpeg, check if playable +static struct tag *ffmpeg_tag(char *file) +{ + InputStream input; + BasePtrs base; + int ret; + struct tag *tag = NULL; + + if (openInputStream(&input, file) < 0) { + ERROR("failed to open %s\n", file); + return NULL; + } + + tag = tag_new(); + + base.tag = tag; + ret = ffmpeg_helper(&input, ffmpeg_tag_internal, &base); + + if (ret != 0) { + free(tag); + tag = NULL; + } + + closeInputStream(&input); + + return tag; +} + +/** + * ffmpeg can decode almost everything from open codecs + * and also some of propietary codecs + * its hard to tell what can ffmpeg decode + * we can later put this into configure script + * to be sure ffmpeg is used to handle + * only that files + */ + +static const char *ffmpeg_Suffixes[] = { + "wma", "asf", "wmv", "mpeg", "mpg", "avi", "vob", "mov", "qt", "swf", "rm", "swf", + "mp1", "mp2", "mp3", "mp4", "m4a", "flac", "ogg", "wav", "au", "aiff", "aif", "ac3", "aac", "mpc", + NULL +}; + +//not sure if this is correct... +static const char *ffmpeg_Mimetypes[] = { + "video/x-ms-asf", + "audio/x-ms-wma", + "audio/x-ms-wax", + "video/x-ms-wmv", + "video/x-ms-wvx", + "video/x-ms-wm", + "video/x-ms-wmx", + "application/x-ms-wmz", + "application/x-ms-wmd", + "audio/mpeg", + NULL +}; + +struct decoder_plugin ffmpegPlugin = { + .name = "ffmpeg", + .init = ffmpeg_init, + .try_decode = ffmpeg_try_decode, + .stream_decode = ffmpeg_decode, + .tag_dup = ffmpeg_tag, + .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + .suffixes = ffmpeg_Suffixes, + .mime_types = ffmpeg_Mimetypes +}; diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c new file mode 100644 index 000000000..7b9fce27d --- /dev/null +++ b/src/decoder/flac_plugin.c @@ -0,0 +1,459 @@ +/* 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" +#include "../utils.h" +#include "../log.h" + +#include + +/* this code was based on flac123, from flac-tools */ + +static flac_read_status flacRead(mpd_unused const flac_decoder * flacDec, + FLAC__byte buf[], + flac_read_status_size_t *bytes, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + size_t r; + + r = decoder_read(data->decoder, data->inStream, (void *)buf, *bytes); + *bytes = r; + + if (r == 0) { + if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || + inputStreamAtEOF(data->inStream)) + return flac_read_status_eof; + else + return flac_read_status_abort; + } + + return flac_read_status_continue; +} + +static flac_seek_status flacSeek(mpd_unused 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(mpd_unused 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(mpd_unused 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(mpd_unused const flac_decoder * flacDec, void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && + decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || + inputStreamAtEOF(data->inStream); +} + +static void flacError(mpd_unused 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(mpd_unused 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) +{ + FLAC__uint32 samples = frame->header.blocksize; + FlacData *data = (FlacData *) vdata; + 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 && newPosition >= data->position) { + assert(timeChange >= 0); + + data->bitRate = + ((newPosition - data->position) * 8.0 / timeChange) + / 1000 + 0.5; + } + data->position = newPosition; + + return flac_common_write(data, frame, buf); +} + +static struct tag *flacMetadataDup(char *file, int *vorbisCommentFound) +{ + struct tag *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)) { + const char *err; + FLAC_API FLAC__Metadata_SimpleIteratorStatus s; + + s = FLAC__metadata_simple_iterator_status(it); + + switch (s) { /* slightly more human-friendly messages: */ + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: + err = "illegal input"; + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: + err = "error opening file"; + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: + err = "not a FLAC file"; + break; + default: + err = FLAC__Metadata_SimpleIteratorStatusString[s]; + } + DEBUG("flacMetadataDup: Reading '%s' " + "metadata gave the following error: %s\n", + file, err); + 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 = tag_new(); + 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 struct tag *flacTagDup(char *file) +{ + struct tag *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) { + struct tag *temp = tag_id3_load(file); + if (temp) { + temp->time = ret->time; + tag_free(ret); + ret = temp; + } + } + + return ret; +} + +static int flac_decode_internal(struct decoder * decoder, + InputStream * inStream, int is_ogg) +{ + flac_decoder *flacDec; + FlacData data; + const char *err = NULL; + + if (!(flacDec = flac_new())) + return -1; + init_FlacData(&data, decoder, inStream); + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + if(!FLAC__stream_decoder_set_metadata_respond(flacDec, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + { + DEBUG(__FILE__": Failed to set metadata respond\n"); + } +#endif + + + 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; + } + } + + decoder_initialized(decoder, &data.audio_format, data.total_time); + + while (1) { + if (!flac_process_single(flacDec)) + break; + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { + FLAC__uint64 sampleToSeek = decoder_seek_where(decoder) * + data.audio_format.sample_rate + 0.5; + if (flac_seek_absolute(flacDec, sampleToSeek)) { + decoder_clear(decoder); + data.time = ((float)sampleToSeek) / + data.audio_format.sample_rate; + data.position = 0; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } else if (flac_get_state(flacDec) == flac_decoder_eof) + break; + } + if (decoder_get_command(decoder) != DECODE_COMMAND_STOP) { + flacPrintErroredState(flac_get_state(flacDec)); + flac_finish(flacDec); + } + +fail: + if (data.replayGainInfo) + freeReplayGainInfo(data.replayGainInfo); + + if (flacDec) + flac_delete(flacDec); + + if (err) { + ERROR("flac %s\n", err); + return -1; + } + return 0; +} + +static int flac_decode(struct decoder * decoder, InputStream * inStream) +{ + return flac_decode_internal(decoder, inStream, 0); +} + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 && \ + !defined(HAVE_OGGFLAC) +static struct tag *oggflac_tag_dup(char *file) +{ + struct tag *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 = tag_new(); + 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(struct decoder *decoder, InputStream * inStream) +{ + return flac_decode_internal(decoder, inStream, 1); +} + +static bool oggflac_try_decode(InputStream * inStream) +{ + return FLAC_API_SUPPORTS_OGG_FLAC && + ogg_stream_type_detect(inStream) == FLAC; +} + +static const char *oggflac_suffixes[] = { "ogg", "oga", NULL }; +static const char *oggflac_mime_types[] = { "audio/x-flac+ogg", + "application/ogg", + "application/x-ogg", + NULL }; + +struct decoder_plugin oggflacPlugin = { + .name = "oggflac", + .try_decode = oggflac_try_decode, + .stream_decode = oggflac_decode, + .tag_dup = oggflac_tag_dup, + .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + .suffixes = oggflac_suffixes, + .mime_types = oggflac_mime_types +}; + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +static const char *flacSuffixes[] = { "flac", NULL }; +static const char *flac_mime_types[] = { "audio/x-flac", + "application/x-flac", + NULL }; + +struct decoder_plugin flacPlugin = { + .name = "flac", + .stream_decode = flac_decode, + .tag_dup = flacTagDup, + .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + .suffixes = flacSuffixes, + .mime_types = flac_mime_types +}; diff --git a/src/decoder/mod_plugin.c b/src/decoder/mod_plugin.c new file mode 100644 index 000000000..5916a24ab --- /dev/null +++ b/src/decoder/mod_plugin.c @@ -0,0 +1,278 @@ +/* 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 "../decoder_api.h" +#include "../utils.h" +#include "../log.h" + +#include + +/* 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 char drv_name[] = "MPD"; +static char drv_version[] = "MPD Output Driver v0.1"; + +#if (LIBMIKMOD_VERSION > 0x030106) +static char drv_alias[] = "mpd"; +#endif + +static MDRIVER drv_mpd = { + NULL, + drv_name, + drv_version, + 0, + 255, +#if (LIBMIKMOD_VERSION > 0x030106) + drv_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) +{ + static char params[] = ""; + + 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(params)) { + 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(struct decoder * decoder, char *path) +{ + mod_Data *data; + struct audio_format audio_format; + float total_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; + } + + audio_format.bits = 16; + audio_format.sample_rate = 44100; + audio_format.channels = 2; + + secPerByte = + 1.0 / ((audio_format.bits * audio_format.channels / 8.0) * + (float)audio_format.sample_rate); + + decoder_initialized(decoder, &audio_format, 0); + + while (1) { + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { + decoder_seek_error(decoder); + } + + if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) + break; + + if (!Player_Active()) + break; + + ret = VC_WriteBytes(data->audio_buffer, MIKMOD_FRAME_SIZE); + total_time += ret * secPerByte; + decoder_data(decoder, NULL, 0, + (char *)data->audio_buffer, ret, + total_time, 0, NULL); + } + + decoder_flush(decoder); + + mod_close(data); + + MikMod_Exit(); + + return 0; +} + +static struct tag *modTagDup(char *file) +{ + struct tag *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 = tag_new(); + + ret->time = 0; + title = xstrdup(Player_LoadTitle(file)); + if (title) + tag_add_item(ret, TAG_ITEM_TITLE, title); + + MikMod_Exit(); + + return ret; +} + +static const char *modSuffixes[] = { "amf", + "dsm", + "far", + "gdm", + "imf", + "it", + "med", + "mod", + "mtm", + "s3m", + "stm", + "stx", + "ult", + "uni", + "xm", + NULL +}; + +struct decoder_plugin modPlugin = { + .name = "mod", + .finish = mod_finishMikMod, + .file_decode = mod_decode, + .tag_dup = modTagDup, + .stream_types = INPUT_PLUGIN_STREAM_FILE, + .suffixes = modSuffixes, +}; diff --git a/src/decoder/mp3_plugin.c b/src/decoder/mp3_plugin.c new file mode 100644 index 000000000..a0de30ba7 --- /dev/null +++ b/src/decoder/mp3_plugin.c @@ -0,0 +1,1086 @@ +/* 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 "../decoder_api.h" +#include "../log.h" +#include "../utils.h" +#include "../conf.h" + +#include + +#ifdef HAVE_ID3TAG +#include +#endif + +#define FRAMES_CUSHION 2000 + +#define READ_BUFFER_SIZE 40960 + +enum mp3_action { + DECODE_SKIP = -3, + DECODE_BREAK = -2, + DECODE_CONT = -1, + DECODE_OK = 0 +}; + +enum muteframe { + MUTEFRAME_NONE, + MUTEFRAME_SKIP, + MUTEFRAME_SEEK +}; + +/* the number of samples of silence the decoder inserts at start */ +#define DECODERDELAY 529 + +#define DEFAULT_GAPLESS_MP3_PLAYBACK 1 + +static int gaplessPlaybackEnabled; + +static inline int32_t +mad_fixed_to_24_sample(mad_fixed_t sample) +{ + enum { + bits = 24, + MIN = -MAD_F_ONE, + MAX = MAD_F_ONE - 1 + }; + + /* round */ + sample = sample + (1L << (MAD_F_FRACBITS - bits)); + + /* clip */ + if (sample > MAX) + sample = MAX; + else if (sample < MIN) + sample = MIN; + + /* quantize */ + return sample >> (MAD_F_FRACBITS + 1 - bits); +} + +static void +mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth, + unsigned int start, unsigned int end, + unsigned int num_channels) +{ + unsigned int i, c; + + for (i = start; i < end; ++i) { + for (c = 0; c < num_channels; ++c) + *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); + } +} + +/* end of stolen stuff from mpg321 */ + +static int mp3_plugin_init(void) +{ + gaplessPlaybackEnabled = getBoolConfigParam(CONF_GAPLESS_MP3_PLAYBACK, + 1); + if (gaplessPlaybackEnabled == CONF_BOOL_UNSET) + gaplessPlaybackEnabled = DEFAULT_GAPLESS_MP3_PLAYBACK; + return 1; +} + +/* decoder stuff is based on madlld */ + +#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048 + +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]; + int32_t outputBuffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; + float totalTime; + float elapsedTime; + enum muteframe muteFrame; + long *frameOffset; + mad_timer_t *times; + unsigned long highestFrame; + unsigned long maxFrames; + unsigned long currentFrame; + unsigned int dropFramesAtStart; + unsigned int dropFramesAtEnd; + unsigned int dropSamplesAtStart; + unsigned int dropSamplesAtEnd; + int foundXing; + int foundFirstFrame; + int decodedFirstFrame; + unsigned long bitRate; + struct decoder *decoder; + InputStream *inStream; + enum mad_layer layer; +} mp3DecodeData; + +static void initMp3DecodeData(mp3DecodeData * data, struct decoder *decoder, + InputStream * inStream) +{ + data->muteFrame = MUTEFRAME_NONE; + 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->decoder = decoder; + data->inStream = inStream; + data->layer = 0; + + 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 = decoder_read(data->decoder, data->inStream, + readStart, readSize); + if (readed == 0) + return -1; + + 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, size_t tagsize, + struct tag ** mpdTag, ReplayGainInfo ** replayGainInfo) +{ + struct id3_tag *id3Tag = NULL; + id3_length_t count; + id3_byte_t const *id3_data; + id3_byte_t *allocated = NULL; + struct tag *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) { + size_t len; + + len = decoder_read(data->decoder, data->inStream, + allocated + count, tagsize - count); + if (len == 0) + break; + 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 = tag_id3_import(id3Tag); + if (tmpMpdTag) { + if (*mpdTag) + tag_free(*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 enum mp3_action +decodeNextFrameHeader(mp3DecodeData * data, struct tag ** 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, (size_t)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)); + 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 enum mp3_action +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)); + 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 version { + unsigned major; + unsigned minor; +}; + +struct lame { + char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ + struct version version; /* struct containing just the version */ + float peak; /* replaygain peak */ + float trackGain; /* replaygain track gain */ + float albumGain; /* replaygain album gain */ + int encoderDelay; /* # of added samples at start of mp3 */ + int encoderPadding; /* # of added samples at end of mp3 */ + int crc; /* CRC of the first 190 bytes of this frame */ +}; + +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 adj = 0; + int name; + int orig; + int sign; + int gain; + 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'; + + *bitlen -= 72; + + /* 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 (prefixcmp(lame->encoder, "LAME")) + return 0; + + if (sscanf(lame->encoder+4, "%u.%u", + &lame->version.major, &lame->version.minor) != 2) + return 0; + + DEBUG("detected LAME version %i.%i (\"%s\")\n", + lame->version.major, lame->version.minor, lame->encoder); + + /* The reference volume was changed from the 83dB used in the + * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older + * versions, since everyone else uses 89dB instead of 83dB. + * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so + * it's impossible to make the proper adjustment for 3.95. + * Fortunately, 3.95 was only out for about a day before 3.95.1 was + * released. -- tmz */ + if (lame->version.major < 3 || + (lame->version.major == 3 && lame->version.minor < 95)) + adj = 6; + + mad_bit_read(ptr, 16); + + lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */ + DEBUG("LAME peak found: %f\n", lame->peak); + + lame->trackGain = 0; + name = mad_bit_read(ptr, 3); /* gain name */ + orig = mad_bit_read(ptr, 3); /* gain originator */ + sign = mad_bit_read(ptr, 1); /* sign bit */ + gain = mad_bit_read(ptr, 9); /* gain*10 */ + if (gain && name == 1 && orig != 0) { + lame->trackGain = ((sign ? -gain : gain) / 10.0) + adj; + DEBUG("LAME track gain found: %f\n", lame->trackGain); + } + + /* tmz reports that this isn't currently written by any version of lame + * (as of 3.97). Since we have no way of testing it, don't use it. + * Wouldn't want to go blowing someone's ears just because we read it + * wrong. :P -- jat */ + lame->albumGain = 0; +#if 0 + name = mad_bit_read(ptr, 3); /* gain name */ + orig = mad_bit_read(ptr, 3); /* gain originator */ + sign = mad_bit_read(ptr, 1); /* sign bit */ + gain = mad_bit_read(ptr, 9); /* gain*10 */ + if (gain && name == 2 && orig != 0) { + lame->albumGain = ((sign ? -gain : gain) / 10.0) + adj; + DEBUG("LAME album gain found: %f\n", lame->trackGain); + } +#else + mad_bit_read(ptr, 16); +#endif + + mad_bit_read(ptr, 16); + + lame->encoderDelay = mad_bit_read(ptr, 12); + lame->encoderPadding = mad_bit_read(ptr, 12); + + DEBUG("encoder delay is %i, encoder padding is %i\n", + lame->encoderDelay, lame->encoderPadding); + + mad_bit_read(ptr, 80); + + lame->crc = mad_bit_read(ptr, 16); + + *bitlen -= 216; + + return 1; +} + +static int decodeFirstFrame(mp3DecodeData * data, + struct tag ** tag, ReplayGainInfo ** replayGainInfo) +{ + struct decoder *decoder = data->decoder; + 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 && + (!decoder || decoder_get_command(decoder) == DECODE_COMMAND_NONE)); + if (ret == DECODE_BREAK || + (decoder && decoder_get_command(decoder) != DECODE_COMMAND_NONE)) + return -1; + if (ret == DECODE_SKIP) continue; + + while ((ret = decodeNextFrame(data)) == DECODE_CONT && + (!decoder || decoder_get_command(decoder) == DECODE_COMMAND_NONE)); + if (ret == DECODE_BREAK || + (decoder && decoder_get_command(decoder) != DECODE_COMMAND_NONE)) + 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 ((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 (parse_lame(&lame, &ptr, &bitlen)) { + if (gaplessPlaybackEnabled && + data->inStream->seekable) { + data->dropSamplesAtStart = lame.encoderDelay + + DECODERDELAY; + data->dropSamplesAtEnd = lame.encoderPadding; + } + + /* Album gain isn't currently used. See comment in + * parse_lame() for details. -- jat */ + if (replayGainInfo && !*replayGainInfo && + lame.trackGain) { + *replayGainInfo = newReplayGainInfo(); + (*replayGainInfo)->trackGain = lame.trackGain; + (*replayGainInfo)->trackPeak = lame.peak; + } + } + } + + if (!data->maxFrames) return -1; + + if (data->maxFrames > 8 * 1024 * 1024) { + ERROR("mp3 file header indicates too many frames: %lu", + 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, NULL, &inStream); + if (decodeFirstFrame(&data, NULL, NULL) < 0) + ret = -1; + else + ret = data.totalTime + 0.5; + mp3DecodeDataFinalize(&data); + closeInputStream(&inStream); + + return ret; +} + +static int openMp3FromInputStream(InputStream * inStream, mp3DecodeData * data, + struct decoder * decoder, struct tag ** tag, + ReplayGainInfo ** replayGainInfo) +{ + initMp3DecodeData(data, decoder, inStream); + *tag = NULL; + if (decodeFirstFrame(data, tag, replayGainInfo) < 0) { + mp3DecodeDataFinalize(data); + if (tag && *tag) + tag_free(*tag); + return -1; + } + + return 0; +} + +static enum mp3_action +mp3Read(mp3DecodeData * data, ReplayGainInfo ** replayGainInfo) +{ + struct decoder *decoder = data->decoder; + unsigned int pcm_length, max_samples; + unsigned 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 = MUTEFRAME_NONE; + break; + case MUTEFRAME_SEEK: + if (decoder_seek_where(decoder) <= data->elapsedTime) { + decoder_clear(decoder); + data->muteFrame = MUTEFRAME_NONE; + decoder_command_finished(decoder); + } + break; + case MUTEFRAME_NONE: + mad_synth_frame(&data->synth, &data->frame); + + if (!data->foundFirstFrame) { + unsigned int 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) { + struct tag *tag = tag_new(); + if (data->inStream->metaName) { + tag_add_item(tag, TAG_ITEM_NAME, + data->inStream->metaName); + } + tag_add_item(tag, TAG_ITEM_TITLE, + data->inStream->metaTitle); + free(data->inStream->metaTitle); + data->inStream->metaTitle = NULL; + tag_free(tag); + } + + if (!data->decodedFirstFrame) { + i = data->dropSamplesAtStart; + data->decodedFirstFrame = 1; + } else + i = 0; + + pcm_length = data->synth.pcm.length; + if (data->dropSamplesAtEnd && + (data->currentFrame == data->maxFrames - data->dropFramesAtEnd)) { + if (data->dropSamplesAtEnd >= pcm_length) + pcm_length = 0; + else + pcm_length -= data->dropSamplesAtEnd; + } + + max_samples = sizeof(data->outputBuffer) / + sizeof(data->outputBuffer[0]) / + MAD_NCHANNELS(&(data->frame).header); + + while (i < pcm_length) { + enum decoder_command cmd; + unsigned int num_samples = pcm_length - i; + if (num_samples > max_samples) + num_samples = max_samples; + + i += num_samples; + + mad_fixed_to_24_buffer(data->outputBuffer, + &data->synth, + i - num_samples, i, + MAD_NCHANNELS(&(data->frame).header)); + num_samples *= MAD_NCHANNELS(&(data->frame).header); + + cmd = decoder_data(decoder, data->inStream, + data->inStream->seekable, + data->outputBuffer, + sizeof(data->outputBuffer[0]) * num_samples, + data->elapsedTime, + data->bitRate / 1000, + (replayGainInfo != NULL) ? *replayGainInfo : NULL); + if (cmd == DECODE_COMMAND_STOP) + return DECODE_BREAK; + } + + if (data->dropSamplesAtEnd && + (data->currentFrame == data->maxFrames - data->dropFramesAtEnd)) + /* stop decoding, effectively dropping + * all remaining samples */ + return DECODE_BREAK; + + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK && + data->inStream->seekable) { + unsigned long j = 0; + data->muteFrame = MUTEFRAME_SEEK; + while (j < data->highestFrame && + decoder_seek_where(decoder) > + ((float)mad_timer_count(data->times[j], + MAD_UNITS_MILLISECONDS)) + / 1000) { + j++; + } + if (j < data->highestFrame) { + if (seekMp3InputBuffer(data, + data->frameOffset[j]) == + 0) { + decoder_clear(decoder); + data->currentFrame = j; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + data->muteFrame = MUTEFRAME_NONE; + } + } else if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK && + !data->inStream->seekable) { + decoder_seek_error(decoder); + } + } + + while (1) { + skip = 0; + while ((ret = + decodeNextFrameHeader(data, NULL, + replayGainInfo)) == DECODE_CONT + && decoder_get_command(decoder) == DECODE_COMMAND_NONE) ; + if (ret == DECODE_BREAK || decoder_get_command(decoder) != DECODE_COMMAND_NONE) + break; + else if (ret == DECODE_SKIP) + skip = 1; + if (data->muteFrame == MUTEFRAME_NONE) { + while ((ret = decodeNextFrame(data)) == DECODE_CONT && + decoder_get_command(decoder) == DECODE_COMMAND_NONE) ; + if (ret == DECODE_BREAK || + decoder_get_command(decoder) != DECODE_COMMAND_NONE) + break; + } + if (!skip && ret == DECODE_OK) + break; + } + + switch (decoder_get_command(decoder)) { + case DECODE_COMMAND_NONE: + case DECODE_COMMAND_START: + break; + + case DECODE_COMMAND_STOP: + return DECODE_BREAK; + + case DECODE_COMMAND_SEEK: + return DECODE_CONT; + } + + return ret; +} + +static void initAudioFormatFromMp3DecodeData(mp3DecodeData * data, + struct audio_format * af) +{ + af->bits = 24; + af->sample_rate = (data->frame).header.samplerate; + af->channels = MAD_NCHANNELS(&(data->frame).header); +} + +static int mp3_decode(struct decoder * decoder, InputStream * inStream) +{ + mp3DecodeData data; + struct tag *tag = NULL; + ReplayGainInfo *replayGainInfo = NULL; + struct audio_format audio_format; + + if (openMp3FromInputStream(inStream, &data, decoder, + &tag, &replayGainInfo) < 0) { + if (decoder_get_command(decoder) == DECODE_COMMAND_NONE) { + ERROR + ("Input does not appear to be a mp3 bit stream.\n"); + return -1; + } + return 0; + } + + initAudioFormatFromMp3DecodeData(&data, &audio_format); + + if (inStream->metaTitle) { + if (tag) + tag_free(tag); + tag = tag_new(); + tag_add_item(tag, TAG_ITEM_TITLE, inStream->metaTitle); + free(inStream->metaTitle); + inStream->metaTitle = NULL; + if (inStream->metaName) { + tag_add_item(tag, TAG_ITEM_NAME, inStream->metaName); + } + tag_free(tag); + } else if (tag) { + if (inStream->metaName) { + tag_clear_items_by_type(tag, TAG_ITEM_NAME); + tag_add_item(tag, TAG_ITEM_NAME, inStream->metaName); + } + tag_free(tag); + } else if (inStream->metaName) { + tag = tag_new(); + if (inStream->metaName) { + tag_add_item(tag, TAG_ITEM_NAME, inStream->metaName); + } + tag_free(tag); + } + + decoder_initialized(decoder, &audio_format, data.totalTime); + + while (mp3Read(&data, &replayGainInfo) != DECODE_BREAK) ; + + if (replayGainInfo) + freeReplayGainInfo(replayGainInfo); + + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK && + data.muteFrame == MUTEFRAME_SEEK) { + decoder_clear(decoder); + decoder_command_finished(decoder); + } + + decoder_flush(decoder); + mp3DecodeDataFinalize(&data); + + return 0; +} + +static struct tag *mp3_tagDup(char *file) +{ + struct tag *ret = NULL; + int total_time; + + ret = tag_id3_load(file); + + total_time = getMp3TotalTime(file); + + if (total_time >= 0) { + if (!ret) + ret = tag_new(); + ret->time = total_time; + } else { + DEBUG("mp3_tagDup: Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static const char *mp3_suffixes[] = { "mp3", "mp2", NULL }; +static const char *mp3_mimeTypes[] = { "audio/mpeg", NULL }; + +struct decoder_plugin mp3Plugin = { + .name = "mp3", + .init = mp3_plugin_init, + .stream_decode = mp3_decode, + .tag_dup = mp3_tagDup, + .stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, + .suffixes = mp3_suffixes, + .mime_types = mp3_mimeTypes +}; diff --git a/src/decoder/mp4_plugin.c b/src/decoder/mp4_plugin.c new file mode 100644 index 000000000..4a613744e --- /dev/null +++ b/src/decoder/mp4_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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public 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 "../decoder_api.h" +#include "../utils.h" +#include "../log.h" + +#include "mp4ff.h" + +#include +#include +/* 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, length); +} + +static uint32_t mp4_inputStreamSeekCallback(void *inStream, uint64_t position) +{ + return seekInputStream((InputStream *) inStream, position, SEEK_SET); +} + +static int mp4_decode(struct decoder * mpd_decoder, InputStream * inStream) +{ + mp4ff_t *mp4fh; + mp4ff_callback_t *mp4cb; + int32_t track; + float file_time, total_time; + int32_t scale; + faacDecHandle decoder; + faacDecFrameInfo frameInfo; + faacDecConfigurationPtr config; + struct audio_format audio_format; + unsigned char *mp4Buffer; + unsigned int mp4BufferSize; + uint32_t sample_rate; + unsigned char channels; + long sampleId; + long numSamples; + long dur; + unsigned int sampleCount; + char *sampleBuffer; + size_t sampleBufferLen; + unsigned int initial = 1; + float *seekTable; + long seekTableEnd = -1; + bool seekPositionFound = false; + long offset; + uint16_t bitRate = 0; + bool seeking = false; + double seek_where = 0; + bool initialized = false; + + 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); + return -1; + } + + track = mp4_getAACTrack(mp4fh); + if (track < 0) { + ERROR("No AAC track found in mp4 stream.\n"); + mp4ff_close(mp4fh); + 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); + + audio_format.bits = 16; + + mp4Buffer = NULL; + mp4BufferSize = 0; + mp4ff_get_decoder_config(mp4fh, track, &mp4Buffer, &mp4BufferSize); + + if (faacDecInit2 + (decoder, mp4Buffer, mp4BufferSize, &sample_rate, &channels) < 0) { + ERROR("Error not a AAC stream.\n"); + faacDecClose(decoder); + mp4ff_close(mp4fh); + free(mp4cb); + return -1; + } + + audio_format.sample_rate = sample_rate; + audio_format.channels = channels; + file_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); + free(mp4cb); + return -1; + } + total_time = ((float)file_time) / scale; + + numSamples = mp4ff_num_samples(mp4fh, track); + if (numSamples > (long)(INT_MAX / sizeof(float))) { + ERROR("Integer overflow.\n"); + faacDecClose(decoder); + mp4ff_close(mp4fh); + free(mp4cb); + return -1; + } + + file_time = 0.0; + + seekTable = xmalloc(sizeof(float) * numSamples); + + for (sampleId = 0; sampleId < numSamples; sampleId++) { + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { + seeking = true; + seek_where = decoder_seek_where(mpd_decoder); + } + + if (seeking && seekTableEnd > 1 && + seekTable[seekTableEnd] >= seek_where) { + int i = 2; + while (seekTable[i] < seek_where) + i++; + sampleId = i - 1; + file_time = seekTable[sampleId]; + } + + dur = mp4ff_get_sample_duration(mp4fh, track, sampleId); + offset = mp4ff_get_sample_offset(mp4fh, track, sampleId); + + if (sampleId > seekTableEnd) { + seekTable[sampleId] = file_time; + seekTableEnd = sampleId; + } + + if (sampleId == 0) + dur = 0; + if (offset > dur) + dur = 0; + else + dur -= offset; + file_time += ((float)dur) / scale; + + if (seeking && file_time > seek_where) + seekPositionFound = true; + + if (seeking && seekPositionFound) { + seekPositionFound = false; + decoder_clear(mpd_decoder); + seeking = 0; + decoder_command_finished(mpd_decoder); + } + + if (seeking) + continue; + + if (mp4ff_read_sample(mp4fh, track, sampleId, &mp4Buffer, + &mp4BufferSize) == 0) + break; + +#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("faad2 error: %s\n", + faacDecGetErrorMessage(frameInfo.error)); + break; + } + + if (!initialized) { + channels = frameInfo.channels; +#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE + scale = frameInfo.samplerate; +#endif + audio_format.sample_rate = scale; + audio_format.channels = frameInfo.channels; + decoder_initialized(mpd_decoder, &audio_format, + total_time); + initialized = true; + } + + if (channels * (unsigned long)(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; + + decoder_data(mpd_decoder, inStream, 1, sampleBuffer, + sampleBufferLen, file_time, + bitRate, NULL); + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP) + break; + } + + free(seekTable); + faacDecClose(decoder); + mp4ff_close(mp4fh); + free(mp4cb); + + if (!initialized) + return -1; + + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK && seeking) { + decoder_clear(mpd_decoder); + decoder_command_finished(mpd_decoder); + } + decoder_flush(mpd_decoder); + + return 0; +} + +static struct tag *mp4DataDup(char *file, int *mp4MetadataFound) +{ + struct tag *ret = NULL; + InputStream inStream; + mp4ff_t *mp4fh; + mp4ff_callback_t *callback; + int32_t track; + int32_t file_time; + int32_t scale; + int i; + + *mp4MetadataFound = 0; + + if (openInputStream(&inStream, file) < 0) { + DEBUG("mp4DataDup: Failed to open file: %s\n", file); + return NULL; + } + + callback = xmalloc(sizeof(mp4ff_callback_t)); + callback->read = mp4_inputStreamReadCallback; + callback->seek = mp4_inputStreamSeekCallback; + callback->user_data = &inStream; + + mp4fh = mp4ff_open_read(callback); + if (!mp4fh) { + free(callback); + closeInputStream(&inStream); + return NULL; + } + + track = mp4_getAACTrack(mp4fh); + if (track < 0) { + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(callback); + return NULL; + } + + ret = tag_new(); + file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); + scale = mp4ff_time_scale(mp4fh, track); + if (scale < 0) { + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(callback); + tag_free(ret); + return NULL; + } + ret->time = ((float)file_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)) { + tag_add_item(ret, TAG_ITEM_ARTIST, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("title", item)) { + tag_add_item(ret, TAG_ITEM_TITLE, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("album", item)) { + tag_add_item(ret, TAG_ITEM_ALBUM, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("track", item)) { + tag_add_item(ret, TAG_ITEM_TRACK, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("disc", item)) { /* Is that the correct id? */ + tag_add_item(ret, TAG_ITEM_DISC, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("genre", item)) { + tag_add_item(ret, TAG_ITEM_GENRE, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("date", item)) { + tag_add_item(ret, TAG_ITEM_DATE, value); + *mp4MetadataFound = 1; + } + + free(item); + free(value); + } + + mp4ff_close(mp4fh); + closeInputStream(&inStream); + + return ret; +} + +static struct tag *mp4TagDup(char *file) +{ + struct tag *ret = NULL; + int mp4MetadataFound = 0; + + ret = mp4DataDup(file, &mp4MetadataFound); + if (!ret) + return NULL; + if (!mp4MetadataFound) { + struct tag *temp = tag_id3_load(file); + if (temp) { + temp->time = ret->time; + tag_free(ret); + ret = temp; + } + } + + return ret; +} + +static const char *mp4_suffixes[] = { "m4a", "mp4", NULL }; +static const char *mp4_mimeTypes[] = { "audio/mp4", "audio/m4a", NULL }; + +struct decoder_plugin mp4Plugin = { + .name = "mp4", + .stream_decode = mp4_decode, + .tag_dup = mp4TagDup, + .stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, + .suffixes = mp4_suffixes, + .mime_types = mp4_mimeTypes, +}; diff --git a/src/decoder/mpc_plugin.c b/src/decoder/mpc_plugin.c new file mode 100644 index 000000000..fb1b0b56c --- /dev/null +++ b/src/decoder/mpc_plugin.c @@ -0,0 +1,308 @@ +/* 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 "../decoder_api.h" +#include "../utils.h" +#include "../log.h" + +#include + +typedef struct _MpcCallbackData { + InputStream *inStream; + struct decoder *decoder; +} MpcCallbackData; + +static mpc_int32_t mpc_read_cb(void *vdata, void *ptr, mpc_int32_t size) +{ + MpcCallbackData *data = (MpcCallbackData *) vdata; + + return decoder_read(data->decoder, data->inStream, ptr, size); +} + +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 int16_t convertSample(MPC_SAMPLE_FORMAT sample) +{ + /* only doing 16-bit audio for now */ + int32_t 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(struct decoder * mpd_decoder, InputStream * inStream) +{ + mpc_decoder decoder; + mpc_reader reader; + mpc_streaminfo info; + struct audio_format audio_format; + + 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; + int16_t *s16 = (int16_t *) chunk; + unsigned long samplePos = 0; + mpc_uint32_t vbrUpdateAcc; + mpc_uint32_t vbrUpdateBits; + float total_time; + int i; + ReplayGainInfo *replayGainInfo = NULL; + + data.inStream = inStream; + data.decoder = mpd_decoder; + + 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) { + if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) { + ERROR("Not a valid musepack stream\n"); + return -1; + } + return 0; + } + + mpc_decoder_setup(&decoder, &reader); + + if (!mpc_decoder_initialize(&decoder, &info)) { + if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) { + ERROR("Not a valid musepack stream\n"); + return -1; + } + return 0; + } + + audio_format.bits = 16; + audio_format.channels = info.channels; + audio_format.sample_rate = info.sample_freq; + + 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; + + decoder_initialized(mpd_decoder, &audio_format, + mpc_streaminfo_get_length(&info)); + + while (!eof) { + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { + samplePos = decoder_seek_where(mpd_decoder) * + audio_format.sample_rate; + if (mpc_decoder_seek_sample(&decoder, samplePos)) { + decoder_clear(mpd_decoder); + s16 = (int16_t *) chunk; + chunkpos = 0; + decoder_command_finished(mpd_decoder); + } else + decoder_seek_error(mpd_decoder); + } + + vbrUpdateAcc = 0; + vbrUpdateBits = 0; + ret = mpc_decoder_decode(&decoder, sample_buffer, + &vbrUpdateAcc, &vbrUpdateBits); + + if (ret <= 0 || decoder_get_command(mpd_decoder) == DECODE_COMMAND_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) { + total_time = ((float)samplePos) / + audio_format.sample_rate; + + bitRate = vbrUpdateBits * + audio_format.sample_rate / 1152 / 1000; + + decoder_data(mpd_decoder, inStream, + inStream->seekable, + chunk, chunkpos, + total_time, + bitRate, replayGainInfo); + + chunkpos = 0; + s16 = (int16_t *) chunk; + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP) { + eof = 1; + break; + } + } + } + } + + if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP && + chunkpos > 0) { + total_time = ((float)samplePos) / audio_format.sample_rate; + + bitRate = + vbrUpdateBits * audio_format.sample_rate / 1152 / 1000; + + decoder_data(mpd_decoder, NULL, inStream->seekable, + chunk, chunkpos, total_time, bitRate, + replayGainInfo); + } + + decoder_flush(mpd_decoder); + + freeReplayGainInfo(replayGainInfo); + + return 0; +} + +static float mpcGetTime(char *file) +{ + InputStream inStream; + float total_time = -1; + + mpc_reader reader; + mpc_streaminfo info; + MpcCallbackData data; + + data.inStream = &inStream; + data.decoder = 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; + } + + total_time = mpc_streaminfo_get_length(&info); + + closeInputStream(&inStream); + + return total_time; +} + +static struct tag *mpcTagDup(char *file) +{ + struct tag *ret = NULL; + float total_time = mpcGetTime(file); + + if (total_time < 0) { + DEBUG("mpcTagDup: Failed to get Songlength of file: %s\n", + file); + return NULL; + } + + ret = tag_ape_load(file); + if (!ret) + ret = tag_id3_load(file); + if (!ret) + ret = tag_new(); + ret->time = total_time; + + return ret; +} + +static const char *mpcSuffixes[] = { "mpc", NULL }; + +struct decoder_plugin mpcPlugin = { + .name = "mpc", + .stream_decode = mpc_decode, + .tag_dup = mpcTagDup, + .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + .suffixes = mpcSuffixes, +}; diff --git a/src/decoder/oggflac_plugin.c b/src/decoder/oggflac_plugin.c new file mode 100644 index 000000000..091b00988 --- /dev/null +++ b/src/decoder/oggflac_plugin.c @@ -0,0 +1,355 @@ +/* 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public 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" +#include "_ogg_common.h" + +#include "../utils.h" +#include "../log.h" + +#include + +static void oggflac_cleanup(FlacData * data, + OggFLAC__SeekableStreamDecoder * decoder) +{ + if (data->replayGainInfo) + freeReplayGainInfo(data->replayGainInfo); + if (decoder) + OggFLAC__seekable_stream_decoder_delete(decoder); +} + +static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(mpd_unused const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__byte buf[], + unsigned *bytes, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + size_t r; + + r = decoder_read(data->decoder, data->inStream, (void *)buf, *bytes); + *bytes = r; + + if (r == 0 && !inputStreamAtEOF(data->inStream) && + decoder_get_command(data->decoder) == DECODE_COMMAND_NONE) + return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; + + return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; +} + +static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(mpd_unused 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(mpd_unused 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(mpd_unused 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(mpd_unused const OggFLAC__SeekableStreamDecoder * decoder, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && + decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || + inputStreamAtEOF(data->inStream); +} + +static void of_error_cb(mpd_unused 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(mpd_unused 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; + float timeChange; + + timeChange = ((float)samples) / frame->header.sample_rate; + data->time += timeChange; + + return flac_common_write(data, frame, buf); +} + +/* used by TagDup */ +static void of_metadata_dup_cb(mpd_unused 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 = tag_new(); + 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(mpd_unused 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 struct tag *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, &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(&data, decoder); + closeInputStream(&inStream); + + return data.tag; +} + +static bool oggflac_try_decode(InputStream * inStream) +{ + if (!inStream->seekable) + /* we cannot seek after the detection, so don't bother + checking */ + return true; + + return ogg_stream_type_detect(inStream) == FLAC; +} + +static int oggflac_decode(struct decoder * mpd_decoder, InputStream * inStream) +{ + OggFLAC__SeekableStreamDecoder *decoder = NULL; + FlacData data; + int ret = 0; + + init_FlacData(&data, mpd_decoder, inStream); + + if (!(decoder = full_decoder_init_and_read_metadata(&data, 0))) { + ret = -1; + goto fail; + } + + decoder_initialized(mpd_decoder, &data.audio_format, data.total_time); + + while (1) { + OggFLAC__seekable_stream_decoder_process_single(decoder); + if (OggFLAC__seekable_stream_decoder_get_state(decoder) != + OggFLAC__SEEKABLE_STREAM_DECODER_OK) { + break; + } + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { + FLAC__uint64 sampleToSeek = decoder_seek_where(mpd_decoder) * + data.audio_format.sample_rate + 0.5; + if (OggFLAC__seekable_stream_decoder_seek_absolute + (decoder, sampleToSeek)) { + decoder_clear(mpd_decoder); + data.time = ((float)sampleToSeek) / + data.audio_format.sample_rate; + data.position = 0; + decoder_command_finished(mpd_decoder); + } else + decoder_seek_error(mpd_decoder); + } + } + + if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { + oggflacPrintErroredState + (OggFLAC__seekable_stream_decoder_get_state(decoder)); + OggFLAC__seekable_stream_decoder_finish(decoder); + } + +fail: + oggflac_cleanup(&data, decoder); + + return ret; +} + +static const char *oggflac_Suffixes[] = { "ogg", "oga",NULL }; +static const char *oggflac_mime_types[] = { "audio/x-flac+ogg", + "application/ogg", + "application/x-ogg", + NULL }; + +struct decoder_plugin oggflacPlugin = { + .name = "oggflac", + .try_decode = oggflac_try_decode, + .stream_decode = oggflac_decode, + .tag_dup = oggflac_TagDup, + .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + .suffixes = oggflac_Suffixes, + .mime_types = oggflac_mime_types +}; diff --git a/src/decoder/oggvorbis_plugin.c b/src/decoder/oggvorbis_plugin.c new file mode 100644 index 000000000..0eecb783f --- /dev/null +++ b/src/decoder/oggvorbis_plugin.c @@ -0,0 +1,387 @@ +/* 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 "_ogg_common.h" +#include "../utils.h" +#include "../log.h" + +#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 */ + +#ifdef WORDS_BIGENDIAN +#define OGG_DECODE_USE_BIGENDIAN 1 +#else +#define OGG_DECODE_USE_BIGENDIAN 0 +#endif + +typedef struct _OggCallbackData { + InputStream *inStream; + struct decoder *decoder; +} OggCallbackData; + +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) +{ + size_t ret; + OggCallbackData *data = (OggCallbackData *) vdata; + + ret = decoder_read(data->decoder, data->inStream, ptr, size * nmemb); + + errno = 0; + /*if(ret<0) errno = ((InputStream *)inStream)->error; */ + + return ret / size; +} + +static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) +{ + const OggCallbackData *data = (const OggCallbackData *) vdata; + if(decoder_get_command(data->decoder) == DECODE_COMMAND_STOP) + return -1; + return seekInputStream(data->inStream, offset, whence); +} + +/* TODO: check Ogg libraries API and see if we can just not have this func */ +static int ogg_close_cb(mpd_unused void *vdata) +{ + return 0; +} + +static long ogg_tell_cb(void *vdata) +{ + const OggCallbackData *data = (const OggCallbackData *) vdata; + + return (long)(data->inStream->offset); +} + +static const char *ogg_parseComment(const char *comment, const 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) +{ + const 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, + struct tag ** 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 = tag_new(); + + tag_add_item(*tag, itemType, comment + len + 1); + + return 1; + } + + return 0; +} + +static struct tag *oggCommentsParse(char **comments) +{ + struct tag *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(char *streamName, + char **comments) +{ + struct tag *tag; + + tag = oggCommentsParse(comments); + if (!tag && streamName) { + tag = tag_new(); + } + if (!tag) + return; + + if (streamName) { + tag_clear_items_by_type(tag, TAG_ITEM_NAME); + tag_add_item(tag, TAG_ITEM_NAME, streamName); + } + + tag_free(tag); +} + +/* public */ +static int oggvorbis_decode(struct decoder * decoder, InputStream * inStream) +{ + OggVorbis_File vf; + ov_callbacks callbacks; + OggCallbackData data; + struct audio_format audio_format; + int current_section; + int prev_section = -1; + 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; + const char *errorStr; + int initialized = 0; + + data.inStream = inStream; + data.decoder = decoder; + + 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) { + if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) + return 0; + + 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; + } + audio_format.bits = 16; + + while (1) { + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { + double seek_where = decoder_seek_where(decoder); + if (0 == ov_time_seek_page(&vf, seek_where)) { + decoder_clear(decoder); + chunkpos = 0; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } + 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); + audio_format.channels = vi->channels; + audio_format.sample_rate = vi->rate; + if (!initialized) { + float total_time = ov_time_total(&vf, -1); + if (total_time < 0) + total_time = 0; + decoder_initialized(decoder, &audio_format, + total_time); + initialized = 1; + } + comments = ov_comment(&vf, -1)->user_comments; + putOggCommentsIntoOutputBuffer(inStream->metaName, + comments); + ogg_getReplayGainInfo(comments, &replayGainInfo); + } + + prev_section = current_section; + + if (ret <= 0) { + if (ret == OV_HOLE) /* bad packet */ + ret = 0; + else /* break on EOF or other error */ + break; + } + + chunkpos += ret; + + if (chunkpos >= OGG_CHUNK_SIZE) { + if ((test = ov_bitrate_instant(&vf)) > 0) { + bitRate = test / 1000; + } + decoder_data(decoder, inStream, + inStream->seekable, + chunk, chunkpos, + ov_pcm_tell(&vf) / audio_format.sample_rate, + bitRate, replayGainInfo); + chunkpos = 0; + if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) + break; + } + } + + if (decoder_get_command(decoder) == DECODE_COMMAND_NONE && + chunkpos > 0) { + decoder_data(decoder, NULL, inStream->seekable, + chunk, chunkpos, + ov_time_tell(&vf), bitRate, + replayGainInfo); + } + + if (replayGainInfo) + freeReplayGainInfo(replayGainInfo); + + ov_clear(&vf); + + decoder_flush(decoder); + + return 0; +} + +static struct tag *oggvorbis_TagDup(char *file) +{ + struct tag *ret; + 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 = tag_new(); + ret->time = (int)(ov_time_total(&vf, -1) + 0.5); + + ov_clear(&vf); + + return ret; +} + +static bool oggvorbis_try_decode(InputStream * inStream) +{ + if (!inStream->seekable) + /* we cannot seek after the detection, so don't bother + checking */ + return true; + + return ogg_stream_type_detect(inStream) == VORBIS; +} + +static const char *oggvorbis_Suffixes[] = { "ogg","oga", NULL }; +static const char *oggvorbis_MimeTypes[] = { "application/ogg", + "audio/x-vorbis+ogg", + "application/x-ogg", + NULL }; + +struct decoder_plugin oggvorbisPlugin = { + .name = "oggvorbis", + .try_decode = oggvorbis_try_decode, + .stream_decode = oggvorbis_decode, + .tag_dup = oggvorbis_TagDup, + .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + .suffixes = oggvorbis_Suffixes, + .mime_types = oggvorbis_MimeTypes +}; diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_plugin.c new file mode 100644 index 000000000..14b7e5f69 --- /dev/null +++ b/src/decoder/wavpack_plugin.c @@ -0,0 +1,574 @@ +/* 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 + * + * WavPack support added by Laszlo Ashin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public 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 "../decoder_api.h" +#include "../utils.h" +#include "../log.h" +#include "../path.h" + +#include + +/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ +#define CHUNK_SIZE 1020 + +#define ERRORLEN 80 + +static struct { + const char *name; + int type; +} tagtypes[] = { + { "artist", TAG_ITEM_ARTIST }, + { "album", TAG_ITEM_ALBUM }, + { "title", TAG_ITEM_TITLE }, + { "track", TAG_ITEM_TRACK }, + { "name", TAG_ITEM_NAME }, + { "genre", TAG_ITEM_GENRE }, + { "date", TAG_ITEM_DATE }, + { "composer", TAG_ITEM_COMPOSER }, + { "performer", TAG_ITEM_PERFORMER }, + { "comment", TAG_ITEM_COMMENT }, + { "disc", TAG_ITEM_DISC }, + { NULL, 0 } +}; + +/* + * This function has been borrowed from the tiny player found on + * wavpack.com. Modifications were required because mpd only handles + * max 16 bit samples. + */ +static void format_samples_int(int Bps, void *buffer, uint32_t samcnt) +{ + int32_t temp; + uchar *dst = (uchar *)buffer; + int32_t *src = (int32_t *)buffer; + + switch (Bps) { + case 1: + while (samcnt--) + *dst++ = *src++; + break; + case 2: + while (samcnt--) { + temp = *src++; +#ifdef WORDS_BIGENDIAN + *dst++ = (uchar)(temp >> 8); + *dst++ = (uchar)(temp); +#else + *dst++ = (uchar)(temp); + *dst++ = (uchar)(temp >> 8); +#endif + } + break; + case 3: + /* downscale to 16 bits */ + while (samcnt--) { + temp = *src++; +#ifdef WORDS_BIGENDIAN + *dst++ = (uchar)(temp >> 16); + *dst++ = (uchar)(temp >> 8); +#else + *dst++ = (uchar)(temp >> 8); + *dst++ = (uchar)(temp >> 16); +#endif + } + break; + case 4: + /* downscale to 16 bits */ + while (samcnt--) { + temp = *src++; +#ifdef WORDS_BIGENDIAN + *dst++ = (uchar)(temp >> 24); + *dst++ = (uchar)(temp >> 16); + +#else + *dst++ = (uchar)(temp >> 16); + *dst++ = (uchar)(temp >> 24); +#endif + } + break; + } +} + +/* + * This function converts floating point sample data to 16 bit integer. + */ +static void format_samples_float(mpd_unused int Bps, void *buffer, + uint32_t samcnt) +{ + int16_t *dst = (int16_t *)buffer; + float *src = (float *)buffer; + + while (samcnt--) { + *dst++ = (int16_t)(*src++); + } +} + +/* + * This does the main decoding thing. + * Requires an already opened WavpackContext. + */ +static void wavpack_decode(struct decoder * decoder, + WavpackContext *wpc, int canseek, + ReplayGainInfo *replayGainInfo) +{ + struct audio_format audio_format; + void (*format_samples)(int Bps, void *buffer, uint32_t samcnt); + char chunk[CHUNK_SIZE]; + float file_time; + int samplesreq, samplesgot; + int allsamples; + int position, outsamplesize; + int Bps; + + audio_format.sample_rate = WavpackGetSampleRate(wpc); + audio_format.channels = WavpackGetReducedChannels(wpc); + audio_format.bits = WavpackGetBitsPerSample(wpc); + + if (audio_format.bits > 16) + audio_format.bits = 16; + + if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT) + format_samples = format_samples_float; + else + format_samples = format_samples_int; +/* + if ((WavpackGetMode(wpc) & MODE_WVC) == MODE_WVC) + ERROR("decoding WITH wvc!!!\n"); + else + ERROR("decoding without wvc\n"); +*/ + allsamples = WavpackGetNumSamples(wpc); + Bps = WavpackGetBytesPerSample(wpc); + + outsamplesize = Bps; + if (outsamplesize > 2) + outsamplesize = 2; + outsamplesize *= audio_format.channels; + + samplesreq = sizeof(chunk) / (4 * audio_format.channels); + + decoder_initialized(decoder, &audio_format, + (float)allsamples / audio_format.sample_rate); + + position = 0; + + do { + if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { + if (canseek) { + int where; + + decoder_clear(decoder); + + where = decoder_seek_where(decoder) * + audio_format.sample_rate; + if (WavpackSeekSample(wpc, where)) { + position = where; + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } else { + decoder_seek_error(decoder); + } + } + + if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) + break; + + samplesgot = WavpackUnpackSamples(wpc, + (int32_t *)chunk, samplesreq); + if (samplesgot > 0) { + int bitrate = (int)(WavpackGetInstantBitrate(wpc) / + 1000 + 0.5); + position += samplesgot; + file_time = (float)position / audio_format.sample_rate; + + format_samples(Bps, chunk, + samplesgot * audio_format.channels); + + decoder_data(decoder, NULL, 0, chunk, + samplesgot * outsamplesize, + file_time, bitrate, + replayGainInfo); + } + } while (samplesgot == samplesreq); + + decoder_flush(decoder); +} + +static char *wavpack_tag(WavpackContext *wpc, char *key) +{ + char *value = NULL; + int size; + + size = WavpackGetTagItem(wpc, key, NULL, 0); + if (size > 0) { + size++; + value = xmalloc(size); + if (!value) + return NULL; + WavpackGetTagItem(wpc, key, value, size); + } + + return value; +} + +static ReplayGainInfo *wavpack_replaygain(WavpackContext *wpc) +{ + static char replaygain_track_gain[] = "replaygain_track_gain"; + static char replaygain_album_gain[] = "replaygain_album_gain"; + static char replaygain_track_peak[] = "replaygain_track_peak"; + static char replaygain_album_peak[] = "replaygain_album_peak"; + ReplayGainInfo *replayGainInfo; + int found = 0; + char *value; + + replayGainInfo = newReplayGainInfo(); + + value = wavpack_tag(wpc, replaygain_track_gain); + if (value) { + replayGainInfo->trackGain = atof(value); + free(value); + found = 1; + } + + value = wavpack_tag(wpc, replaygain_album_gain); + if (value) { + replayGainInfo->albumGain = atof(value); + free(value); + found = 1; + } + + value = wavpack_tag(wpc, replaygain_track_peak); + if (value) { + replayGainInfo->trackPeak = atof(value); + free(value); + found = 1; + } + + value = wavpack_tag(wpc, replaygain_album_peak); + if (value) { + replayGainInfo->albumPeak = atof(value); + free(value); + found = 1; + } + + + if (found) + return replayGainInfo; + + freeReplayGainInfo(replayGainInfo); + + return NULL; +} + +/* + * Reads metainfo from the specified file. + */ +static struct tag *wavpack_tagdup(char *fname) +{ + WavpackContext *wpc; + struct tag *tag; + char error[ERRORLEN]; + char *s; + int ssize; + int i, j; + + wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); + if (wpc == NULL) { + ERROR("failed to open WavPack file \"%s\": %s\n", fname, error); + return NULL; + } + + tag = tag_new(); + tag->time = + (float)WavpackGetNumSamples(wpc) / WavpackGetSampleRate(wpc); + + ssize = 0; + s = NULL; + + for (i = 0; tagtypes[i].name != NULL; ++i) { + j = WavpackGetTagItem(wpc, tagtypes[i].name, NULL, 0); + if (j > 0) { + ++j; + + if (s == NULL) { + s = xmalloc(j); + if (s == NULL) break; + ssize = j; + } else if (j > ssize) { + char *t = (char *)xrealloc(s, j); + if (t == NULL) break; + ssize = j; + s = t; + } + + WavpackGetTagItem(wpc, tagtypes[i].name, s, j); + tag_add_item(tag, tagtypes[i].type, s); + } + } + + if (s != NULL) + free(s); + + WavpackCloseFile(wpc); + + return tag; +} + +/* + * mpd InputStream <=> WavpackStreamReader wrapper callbacks + */ + +/* This struct is needed for per-stream last_byte storage. */ +typedef struct { + struct decoder *decoder; + InputStream *is; + /* Needed for push_back_byte() */ + int last_byte; +} InputStreamPlus; + +static int32_t read_bytes(void *id, void *data, int32_t bcount) +{ + InputStreamPlus *isp = (InputStreamPlus *)id; + uint8_t *buf = (uint8_t *)data; + int32_t i = 0; + + if (isp->last_byte != EOF) { + *buf++ = isp->last_byte; + isp->last_byte = EOF; + --bcount; + ++i; + } + return i + decoder_read(isp->decoder, isp->is, buf, bcount); +} + +static uint32_t get_pos(void *id) +{ + return ((InputStreamPlus *)id)->is->offset; +} + +static int set_pos_abs(void *id, uint32_t pos) +{ + return seekInputStream(((InputStreamPlus *)id)->is, pos, SEEK_SET); +} + +static int set_pos_rel(void *id, int32_t delta, int mode) +{ + return seekInputStream(((InputStreamPlus *)id)->is, delta, mode); +} + +static int push_back_byte(void *id, int c) +{ + ((InputStreamPlus *)id)->last_byte = c; + return 1; +} + +static uint32_t get_length(void *id) +{ + return ((InputStreamPlus *)id)->is->size; +} + +static int can_seek(void *id) +{ + return ((InputStreamPlus *)id)->is->seekable; +} + +static WavpackStreamReader mpd_is_reader = { + .read_bytes = read_bytes, + .get_pos = get_pos, + .set_pos_abs = set_pos_abs, + .set_pos_rel = set_pos_rel, + .push_back_byte = push_back_byte, + .get_length = get_length, + .can_seek = can_seek, + .write_bytes = NULL /* no need to write edited tags */ +}; + +static void +initInputStreamPlus(InputStreamPlus *isp, struct decoder *decoder, + InputStream *is) +{ + isp->decoder = decoder; + isp->is = is; + isp->last_byte = EOF; +} + +/* + * Tries to decode the specified stream, and gives true if managed to do it. + */ +static bool wavpack_trydecode(InputStream *is) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + InputStreamPlus isp; + + initInputStreamPlus(&isp, NULL, is); + wpc = WavpackOpenFileInputEx(&mpd_is_reader, &isp, NULL, error, + OPEN_STREAMING, 0); + if (wpc == NULL) + return false; + + WavpackCloseFile(wpc); + /* Seek it back in order to play from the first byte. */ + seekInputStream(is, 0, SEEK_SET); + + return true; +} + +static int wavpack_open_wvc(struct decoder *decoder, + InputStream *is_wvc) +{ + char tmp[MPD_PATH_MAX]; + const char *utf8url; + size_t len; + char *wvc_url = NULL; + int ret; + + /* + * As we use dc->utf8url, this function will be bad for + * single files. utf8url is not absolute file path :/ + */ + utf8url = decoder_get_url(decoder, tmp); + if (utf8url == NULL) + return 0; + + len = strlen(utf8url); + if (!len) + return 0; + + wvc_url = (char *)xmalloc(len + 2); /* +2: 'c' and EOS */ + if (wvc_url == NULL) + return 0; + + memcpy(wvc_url, utf8url, len); + wvc_url[len] = 'c'; + wvc_url[len + 1] = '\0'; + + ret = openInputStream(is_wvc, wvc_url); + free(wvc_url); + + if (ret) + return 0; + + /* + * And we try to buffer in order to get know + * about a possible 404 error. + */ + for (;;) { + if (inputStreamAtEOF(is_wvc)) { + /* + * EOF is reached even without + * a single byte is read... + * So, this is not good :/ + */ + closeInputStream(is_wvc); + return 0; + } + + if (bufferInputStream(is_wvc) >= 0) + return 1; + + if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) { + closeInputStream(is_wvc); + return 0; + } + + /* Save some CPU */ + my_usleep(1000); + } +} + +/* + * Decodes a stream. + */ +static int wavpack_streamdecode(struct decoder * decoder, InputStream *is) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + InputStream is_wvc; + int open_flags = OPEN_2CH_MAX | OPEN_NORMALIZE /*| OPEN_STREAMING*/; + InputStreamPlus isp, isp_wvc; + + if (wavpack_open_wvc(decoder, &is_wvc)) { + initInputStreamPlus(&isp_wvc, decoder, &is_wvc); + open_flags |= OPEN_WVC; + } + + initInputStreamPlus(&isp, decoder, is); + wpc = WavpackOpenFileInputEx(&mpd_is_reader, &isp, &isp_wvc, error, + open_flags, 15); + + if (wpc == NULL) { + ERROR("failed to open WavPack stream: %s\n", error); + return -1; + } + + wavpack_decode(decoder, wpc, can_seek(&isp), NULL); + + WavpackCloseFile(wpc); + if (open_flags & OPEN_WVC) + closeInputStream(&is_wvc); + closeInputStream(is); + + return 0; +} + +/* + * Decodes a file. + */ +static int wavpack_filedecode(struct decoder * decoder, char *fname) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + ReplayGainInfo *replayGainInfo; + + wpc = WavpackOpenFileInput(fname, error, + OPEN_TAGS | OPEN_WVC | + OPEN_2CH_MAX | OPEN_NORMALIZE, 15); + if (wpc == NULL) { + ERROR("failed to open WavPack file \"%s\": %s\n", fname, error); + return -1; + } + + replayGainInfo = wavpack_replaygain(wpc); + + wavpack_decode(decoder, wpc, 1, replayGainInfo); + + if (replayGainInfo) + freeReplayGainInfo(replayGainInfo); + + WavpackCloseFile(wpc); + + return 0; +} + +static char const *wavpackSuffixes[] = { "wv", NULL }; +static char const *wavpackMimeTypes[] = { "audio/x-wavpack", NULL }; + +struct decoder_plugin wavpackPlugin = { + .name = "wavpack", + .try_decode = wavpack_trydecode, + .stream_decode = wavpack_streamdecode, + .file_decode = wavpack_filedecode, + .tag_dup = wavpack_tagdup, + .stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, + .suffixes = wavpackSuffixes, + .mime_types = wavpackMimeTypes +}; diff --git a/src/inputPlugins/_flac_common.c b/src/inputPlugins/_flac_common.c deleted file mode 100644 index db43e0003..000000000 --- a/src/inputPlugins/_flac_common.c +++ /dev/null @@ -1,323 +0,0 @@ -/* 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 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public 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" -#include "../log.h" - -#include -#include - -void init_FlacData(FlacData * data, struct decoder * decoder, - InputStream * inStream) -{ - data->time = 0; - data->position = 0; - data->bitRate = 0; - data->decoder = decoder; - 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 *p = &(block->data.vorbis_comment. - comments[offset].entry[pos]); - tmp = p[len]; - p[len] = '\0'; - *fl = (float)atof((char *)p); - p[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, - struct tag ** 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 = tag_new(); - - tag_add_item_n(*tag, itemType, - (char *)(entry->entry + slen + 1), vlen); - - return 1; - } - - return 0; -} - -struct tag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, - struct tag * 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) -{ - const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info); - - switch (block->type) { - case FLAC__METADATA_TYPE_STREAMINFO: - data->audio_format.bits = (int8_t)si->bits_per_sample; - data->audio_format.sample_rate = si->sample_rate; - data->audio_format.channels = (int8_t)si->channels; - data->total_time = ((float)si->total_samples) / (si->sample_rate); - break; - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flacParseReplayGain(block, data); - default: - break; - } -} - -void flac_error_common_cb(const char *plugin, - const FLAC__StreamDecoderErrorStatus status, - mpd_unused FlacData * data) -{ - if (decoder_get_command(data->decoder) == DECODE_COMMAND_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); - } -} - -static void flac_convert_stereo16(int16_t *dest, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - for (; position < end; ++position) { - *dest++ = buf[0][position]; - *dest++ = buf[1][position]; - } -} - -static void -flac_convert_16(int16_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -/** - * Note: this function also handles 24 bit files! - */ -static void -flac_convert_32(int32_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -static void -flac_convert_8(int8_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -static void flac_convert(unsigned char *dest, - unsigned int num_channels, - unsigned int bytes_per_sample, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - switch (bytes_per_sample) { - case 2: - if (num_channels == 2) - flac_convert_stereo16((int16_t*)dest, buf, - position, end); - else - flac_convert_16((int16_t*)dest, num_channels, buf, - position, end); - break; - - case 4: - flac_convert_32((int32_t*)dest, num_channels, buf, - position, end); - break; - - case 1: - flac_convert_8((int8_t*)dest, num_channels, buf, - position, end); - break; - } -} - -FLAC__StreamDecoderWriteStatus -flac_common_write(FlacData *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[]) -{ - unsigned int c_samp; - const unsigned int num_channels = frame->header.channels; - const unsigned int bytes_per_sample = - audio_format_sample_size(&data->audio_format); - const unsigned int bytes_per_channel = - bytes_per_sample * frame->header.channels; - const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel; - unsigned int num_samples; - enum decoder_command cmd; - - if (bytes_per_sample != 1 && bytes_per_sample != 2 && - bytes_per_sample != 4) - /* exotic unsupported bit rate */ - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - for (c_samp = 0; c_samp < frame->header.blocksize; - c_samp += num_samples) { - num_samples = frame->header.blocksize - c_samp; - if (num_samples > max_samples) - num_samples = max_samples; - - flac_convert(data->chunk, - num_channels, bytes_per_sample, buf, - c_samp, c_samp + num_samples); - - cmd = decoder_data(data->decoder, data->inStream, - 1, data->chunk, - num_samples * bytes_per_channel, - data->time, data->bitRate, - data->replayGainInfo); - switch (cmd) { - case DECODE_COMMAND_NONE: - case DECODE_COMMAND_START: - break; - - case DECODE_COMMAND_STOP: - return - FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - case DECODE_COMMAND_SEEK: - return - FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; - } - } - - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; -} diff --git a/src/inputPlugins/_flac_common.h b/src/inputPlugins/_flac_common.h deleted file mode 100644 index 45714b4bd..000000000 --- a/src/inputPlugins/_flac_common.h +++ /dev/null @@ -1,168 +0,0 @@ -/* 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 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public 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 "../decoder_api.h" - -#include -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -# include -# 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 -# 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 - */ -# include "_ogg_common.h" - -# include -# 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 - -#define FLAC_CHUNK_SIZE 4080 - -typedef struct { - unsigned char chunk[FLAC_CHUNK_SIZE]; - float time; - unsigned int bitRate; - struct audio_format audio_format; - float total_time; - FLAC__uint64 position; - struct decoder *decoder; - InputStream *inStream; - ReplayGainInfo *replayGainInfo; - struct tag *tag; -} FlacData; - -/* initializes a given FlacData struct */ -void init_FlacData(FlacData * data, struct decoder * decoder, - 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); - -struct tag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, - struct tag *tag); - -FLAC__StreamDecoderWriteStatus -flac_common_write(FlacData *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[]); - -#endif /* _FLAC_COMMON_H */ diff --git a/src/inputPlugins/_ogg_common.c b/src/inputPlugins/_ogg_common.c deleted file mode 100644 index 841b2ad3f..000000000 --- a/src/inputPlugins/_ogg_common.c +++ /dev/null @@ -1,49 +0,0 @@ -/* 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 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public 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 "_ogg_common.h" -#include "_flac_common.h" -#include "../utils.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; - - seekInputStream(inStream, 0, SEEK_SET); - - r = decoder_read(NULL, inStream, buf, sizeof(buf)); - - if (r > 0) - 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; -} diff --git a/src/inputPlugins/_ogg_common.h b/src/inputPlugins/_ogg_common.h deleted file mode 100644 index 7c9e7b630..000000000 --- a/src/inputPlugins/_ogg_common.h +++ /dev/null @@ -1,31 +0,0 @@ -/* 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 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public 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 "../decoder_api.h" - -typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; - -ogg_stream_type ogg_stream_type_detect(InputStream * inStream); - -#endif /* _OGG_COMMON_H */ diff --git a/src/inputPlugins/aac_plugin.c b/src/inputPlugins/aac_plugin.c deleted file mode 100644 index 7842bcc22..000000000 --- a/src/inputPlugins/aac_plugin.c +++ /dev/null @@ -1,602 +0,0 @@ -/* 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 "../decoder_api.h" - -#define AAC_MAX_CHANNELS 6 - -#include "../utils.h" -#include "../log.h" - -#include -#include - -/* all code here is either based on or copied from FAAD2's frontend code */ -typedef struct { - struct decoder *decoder; - InputStream *inStream; - size_t bytesIntoBuffer; - size_t bytesConsumed; - off_t fileOffset; - unsigned char *buffer; - int atEof; -} AacBuffer; - -static void aac_buffer_shift(AacBuffer * b, size_t length) -{ - assert(length >= b->bytesConsumed); - assert(length <= b->bytesConsumed + b->bytesIntoBuffer); - - memmove(b->buffer, b->buffer + length, - b->bytesConsumed + b->bytesIntoBuffer - length); - - length -= b->bytesConsumed; - b->bytesConsumed = 0; - b->bytesIntoBuffer -= length; -} - -static void fillAacBuffer(AacBuffer * b) -{ - size_t bread; - - if (b->bytesIntoBuffer >= FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS) - /* buffer already full */ - return; - - aac_buffer_shift(b, b->bytesConsumed); - - if (!b->atEof) { - size_t rest = FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS - - b->bytesIntoBuffer; - - bread = decoder_read(b->decoder, b->inStream, - (void *)(b->buffer + b->bytesIntoBuffer), - rest); - if (bread == 0 && inputStreamAtEOF(b->inStream)) - b->atEof = 1; - b->bytesIntoBuffer += bread; - } - - if ((b->bytesIntoBuffer > 3 && memcmp(b->buffer, "TAG", 3) == 0) || - (b->bytesIntoBuffer > 11 && - memcmp(b->buffer, "LYRICSBEGIN", 11) == 0) || - (b->bytesIntoBuffer > 8 && memcmp(b->buffer, "APETAGEX", 8) == 0)) - b->bytesIntoBuffer = 0; -} - -static void advanceAacBuffer(AacBuffer * b, size_t 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 -}; - -/** - * Check whether the buffer head is an AAC frame, and return the frame - * length. Returns 0 if it is not a frame. - */ -static size_t adts_check_frame(AacBuffer * b) -{ - if (b->bytesIntoBuffer <= 7) - return 0; - - /* check syncword */ - if (!((b->buffer[0] == 0xFF) && ((b->buffer[1] & 0xF6) == 0xF0))) - return 0; - - return (((unsigned int)b->buffer[3] & 0x3) << 11) | - (((unsigned int)b->buffer[4]) << 3) | - (b->buffer[5] >> 5); -} - -/** - * Find the next AAC frame in the buffer. Returns 0 if no frame is - * found or if not enough data is available. - */ -static size_t adts_find_frame(AacBuffer * b) -{ - const unsigned char *p; - size_t frame_length; - - while ((p = memchr(b->buffer, 0xff, b->bytesIntoBuffer)) != NULL) { - /* discard data before 0xff */ - if (p > b->buffer) - aac_buffer_shift(b, p - b->buffer); - - if (b->bytesIntoBuffer <= 7) - /* not enough data yet */ - return 0; - - /* is it a frame? */ - frame_length = adts_check_frame(b); - if (frame_length > 0) - /* yes, it is */ - return frame_length; - - /* it's just some random 0xff byte; discard and and - continue searching */ - aac_buffer_shift(b, 1); - } - - /* nothing at all; discard the whole buffer */ - aac_buffer_shift(b, b->bytesIntoBuffer); - return 0; -} - -static void adtsParse(AacBuffer * b, float *length) -{ - unsigned int frames, frameLength; - int sample_rate = 0; - float framesPerSec; - - /* Read all frames to ensure correct time and bitrate */ - for (frames = 0;; frames++) { - fillAacBuffer(b); - - frameLength = adts_find_frame(b); - if (frameLength > 0) { - if (frames == 0) { - sample_rate = adtsSampleRates[(b-> - buffer[2] & 0x3c) - >> 2]; - } - - if (frameLength > b->bytesIntoBuffer) - break; - - advanceAacBuffer(b, frameLength); - } else - break; - } - - framesPerSec = (float)sample_rate / 1024.0; - if (framesPerSec != 0) - *length = (float)frames / framesPerSec; -} - -static void initAacBuffer(AacBuffer * b, - struct decoder *decoder, InputStream * inStream) -{ - memset(b, 0, sizeof(AacBuffer)); - - b->decoder = decoder; - b->inStream = inStream; - - b->buffer = xmalloc(FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); - memset(b->buffer, 0, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); -} - -static void aac_parse_header(AacBuffer * b, float *length) -{ - size_t fileread; - size_t tagsize; - - if (length) - *length = -1; - - fileread = b->inStream->size; - - fillAacBuffer(b); - - tagsize = 0; - if (b->bytesIntoBuffer >= 10 && !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 (length == NULL) - return; - - if (b->bytesIntoBuffer >= 2 && - (b->buffer[0] == 0xFF) && ((b->buffer[1] & 0xF6) == 0xF0)) { - adtsParse(b, length); - seekInputStream(b->inStream, tagsize, SEEK_SET); - - b->bytesIntoBuffer = 0; - b->bytesConsumed = 0; - b->fileOffset = tagsize; - - fillAacBuffer(b); - } 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; - faacDecHandle decoder; - faacDecConfigurationPtr config; - uint32_t sample_rate; - unsigned char channels; - InputStream inStream; - long bread; - - if (openInputStream(&inStream, file) < 0) - return -1; - - initAacBuffer(&b, NULL, &inStream); - aac_parse_header(&b, &length); - - 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, - &sample_rate, &channels); -#else - bread = faacDecInit(decoder, b.buffer, &sample_rate, &channels); -#endif - if (bread >= 0 && sample_rate > 0 && channels > 0) - length = 0; - - faacDecClose(decoder); - } - - if (b.buffer) - free(b.buffer); - closeInputStream(&inStream); - - return length; -} - -static int getAacTotalTime(char *file) -{ - int file_time = -1; - float length; - - if ((length = getAacFloatTotalTime(file)) >= 0) - file_time = length + 0.5; - - return file_time; -} - -static int aac_stream_decode(struct decoder * mpd_decoder, - InputStream *inStream) -{ - float file_time; - float totalTime = 0; - faacDecHandle decoder; - faacDecFrameInfo frameInfo; - faacDecConfigurationPtr config; - long bread; - struct audio_format audio_format; - uint32_t sample_rate; - unsigned char channels; - unsigned int sampleCount; - char *sampleBuffer; - size_t sampleBufferLen; - uint16_t bitRate = 0; - AacBuffer b; - int initialized = 0; - - initAacBuffer(&b, mpd_decoder, inStream); - - 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); - - while (b.bytesIntoBuffer < FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS && - !b.atEof && - decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { - fillAacBuffer(&b); - adts_find_frame(&b); - fillAacBuffer(&b); - my_usleep(10000); - } - -#ifdef HAVE_FAAD_BUFLEN_FUNCS - bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer, - &sample_rate, &channels); -#else - bread = faacDecInit(decoder, b.buffer, &sample_rate, &channels); -#endif - if (bread < 0) { - ERROR("Error not a AAC stream.\n"); - faacDecClose(decoder); - if (b.buffer) - free(b.buffer); - return -1; - } - - audio_format.bits = 16; - - file_time = 0.0; - - advanceAacBuffer(&b, bread); - - while (1) { - fillAacBuffer(&b); - adts_find_frame(&b); - fillAacBuffer(&b); - - if (b.bytesIntoBuffer == 0) - 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 stream\n"); - ERROR("faad2 error: %s\n", - faacDecGetErrorMessage(frameInfo.error)); - break; - } -#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE - sample_rate = frameInfo.samplerate; -#endif - - if (!initialized) { - audio_format.channels = frameInfo.channels; - audio_format.sample_rate = sample_rate; - decoder_initialized(mpd_decoder, &audio_format, totalTime); - initialized = 1; - } - - advanceAacBuffer(&b, frameInfo.bytesconsumed); - - sampleCount = (unsigned long)(frameInfo.samples); - - if (sampleCount > 0) { - bitRate = frameInfo.bytesconsumed * 8.0 * - frameInfo.channels * sample_rate / - frameInfo.samples / 1000 + 0.5; - file_time += - (float)(frameInfo.samples) / frameInfo.channels / - sample_rate; - } - - sampleBufferLen = sampleCount * 2; - - decoder_data(mpd_decoder, NULL, 0, sampleBuffer, - sampleBufferLen, file_time, - bitRate, NULL); - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { - decoder_seek_error(mpd_decoder); - } else if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP) - break; - } - - decoder_flush(mpd_decoder); - - faacDecClose(decoder); - if (b.buffer) - free(b.buffer); - - if (!initialized) - return -1; - - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { - decoder_seek_error(mpd_decoder); - } - - return 0; -} - - -static int aac_decode(struct decoder * mpd_decoder, char *path) -{ - float file_time; - float totalTime; - faacDecHandle decoder; - faacDecFrameInfo frameInfo; - faacDecConfigurationPtr config; - long bread; - struct audio_format audio_format; - uint32_t sample_rate; - unsigned char channels; - unsigned int sampleCount; - char *sampleBuffer; - size_t sampleBufferLen; - /*float * seekTable; - long seekTableEnd = -1; - int seekPositionFound = 0; */ - uint16_t bitRate = 0; - AacBuffer b; - InputStream inStream; - int initialized = 0; - - if ((totalTime = getAacFloatTotalTime(path)) < 0) - return -1; - - if (openInputStream(&inStream, path) < 0) - return -1; - - initAacBuffer(&b, mpd_decoder, &inStream); - aac_parse_header(&b, 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, - &sample_rate, &channels); -#else - bread = faacDecInit(decoder, b.buffer, &sample_rate, &channels); -#endif - if (bread < 0) { - ERROR("Error not a AAC stream.\n"); - faacDecClose(decoder); - if (b.buffer) - free(b.buffer); - return -1; - } - - audio_format.bits = 16; - - file_time = 0.0; - - advanceAacBuffer(&b, bread); - - while (1) { - fillAacBuffer(&b); - - if (b.bytesIntoBuffer == 0) - 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)); - break; - } -#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE - sample_rate = frameInfo.samplerate; -#endif - - if (!initialized) { - audio_format.channels = frameInfo.channels; - audio_format.sample_rate = sample_rate; - decoder_initialized(mpd_decoder, &audio_format, - totalTime); - initialized = 1; - } - - advanceAacBuffer(&b, frameInfo.bytesconsumed); - - sampleCount = (unsigned long)(frameInfo.samples); - - if (sampleCount > 0) { - bitRate = frameInfo.bytesconsumed * 8.0 * - frameInfo.channels * sample_rate / - frameInfo.samples / 1000 + 0.5; - file_time += - (float)(frameInfo.samples) / frameInfo.channels / - sample_rate; - } - - sampleBufferLen = sampleCount * 2; - - decoder_data(mpd_decoder, NULL, 0, sampleBuffer, - sampleBufferLen, file_time, - bitRate, NULL); - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { - decoder_seek_error(mpd_decoder); - } else if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP) - break; - } - - decoder_flush(mpd_decoder); - - faacDecClose(decoder); - if (b.buffer) - free(b.buffer); - - if (!initialized) - return -1; - - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { - decoder_seek_error(mpd_decoder); - } - - return 0; -} - -static struct tag *aacTagDup(char *file) -{ - struct tag *ret = NULL; - int file_time = getAacTotalTime(file); - - if (file_time >= 0) { - if ((ret = tag_id3_load(file)) == NULL) - ret = tag_new(); - ret->time = file_time; - } else { - DEBUG("aacTagDup: Failed to get total song time from: %s\n", - file); - } - - return ret; -} - -static const char *aac_suffixes[] = { "aac", NULL }; -static const char *aac_mimeTypes[] = { "audio/aac", "audio/aacp", NULL }; - -struct decoder_plugin aacPlugin = { - .name = "aac", - .stream_decode = aac_stream_decode, - .file_decode = aac_decode, - .tag_dup = aacTagDup, - .stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, - .suffixes = aac_suffixes, - .mime_types = aac_mimeTypes -}; diff --git a/src/inputPlugins/audiofile_plugin.c b/src/inputPlugins/audiofile_plugin.c deleted file mode 100644 index 99846e853..000000000 --- a/src/inputPlugins/audiofile_plugin.c +++ /dev/null @@ -1,147 +0,0 @@ -/* 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 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public 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 "../decoder_api.h" -#include "../log.h" - -#include -#include - -/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ -#define CHUNK_SIZE 1020 - -static int getAudiofileTotalTime(char *file) -{ - int total_time; - AFfilehandle af_fp = afOpenFile(file, "r", NULL); - if (af_fp == AF_NULL_FILEHANDLE) { - return -1; - } - total_time = (int) - ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK) - / afGetRate(af_fp, AF_DEFAULT_TRACK)); - afCloseFile(af_fp); - return total_time; -} - -static int audiofile_decode(struct decoder * decoder, char *path) -{ - int fs, frame_count; - AFfilehandle af_fp; - int bits; - struct audio_format audio_format; - float total_time; - uint16_t bitRate; - struct stat st; - int ret, current = 0; - char chunk[CHUNK_SIZE]; - - 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); - audio_format.bits = (uint8_t)bits; - audio_format.sample_rate = - (unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK); - audio_format.channels = - (uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK); - - frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); - - total_time = ((float)frame_count / (float)audio_format.sample_rate); - - bitRate = (uint16_t)(st.st_size * 8.0 / total_time / 1000.0 + 0.5); - - if (audio_format.bits != 8 && audio_format.bits != 16) { - ERROR("Only 8 and 16-bit files are supported. %s is %i-bit\n", - path, audio_format.bits); - afCloseFile(af_fp); - return -1; - } - - fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); - - decoder_initialized(decoder, &audio_format, total_time); - - do { - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { - decoder_clear(decoder); - current = decoder_seek_where(decoder) * - audio_format.sample_rate; - afSeekFrame(af_fp, AF_DEFAULT_TRACK, current); - decoder_command_finished(decoder); - } - - ret = afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, - CHUNK_SIZE / fs); - if (ret <= 0) - break; - - current += ret; - decoder_data(decoder, NULL, 1, - chunk, ret * fs, - (float)current / (float)audio_format.sample_rate, - bitRate, NULL); - } while (decoder_get_command(decoder) != DECODE_COMMAND_STOP); - - decoder_flush(decoder); - - afCloseFile(af_fp); - - return 0; -} - -static struct tag *audiofileTagDup(char *file) -{ - struct tag *ret = NULL; - int total_time = getAudiofileTotalTime(file); - - if (total_time >= 0) { - if (!ret) - ret = tag_new(); - ret->time = total_time; - } else { - DEBUG - ("audiofileTagDup: Failed to get total song time from: %s\n", - file); - } - - return ret; -} - -static const char *audiofileSuffixes[] = { "wav", "au", "aiff", "aif", NULL }; - -struct decoder_plugin audiofilePlugin = { - .name = "audiofile", - .file_decode = audiofile_decode, - .tag_dup = audiofileTagDup, - .stream_types = INPUT_PLUGIN_STREAM_FILE, - .suffixes = audiofileSuffixes, -}; diff --git a/src/inputPlugins/ffmpeg_plugin.c b/src/inputPlugins/ffmpeg_plugin.c deleted file mode 100644 index 6455cd1ce..000000000 --- a/src/inputPlugins/ffmpeg_plugin.c +++ /dev/null @@ -1,419 +0,0 @@ -/* the Music Player Daemon (MPD) - * Copyright (C) 2008 Viliam Mateicka - * 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 "../decoder_api.h" -#include "../log.h" -#include "../utils.h" -#include "../log.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef OLD_FFMPEG_INCLUDES -#include -#include -#include -#else -#include -#include -#include -#endif - -typedef struct { - int audioStream; - AVFormatContext *pFormatCtx; - AVCodecContext *aCodecCtx; - AVCodec *aCodec; - struct decoder *decoder; - InputStream *input; - struct tag *tag; -} BasePtrs; - -typedef struct { - /** hack - see url_to_base() */ - char url[8]; - - struct decoder *decoder; - InputStream *input; -} FopsHelper; - -/** - * Convert a faked mpd:// URL to a FopsHelper structure. This is a - * hack because ffmpeg does not provide a nice API for passing a - * user-defined pointer to mpdurl_open(). - */ -static FopsHelper *url_to_base(const char *url) -{ - union { - const char *in; - FopsHelper *out; - } u = { .in = url }; - return u.out; -} - -static int mpdurl_open(URLContext *h, const char *filename, - mpd_unused int flags) -{ - FopsHelper *base = url_to_base(filename); - h->priv_data = base; - h->is_streamed = (base->input->seekable ? 0 : 1); - return 0; -} - -static int mpdurl_read(URLContext *h, unsigned char *buf, int size) -{ - int ret; - FopsHelper *base = (FopsHelper *) h->priv_data; - while (1) { - ret = readFromInputStream(base->input, (void *)buf, size); - if (ret == 0) { - DEBUG("ret 0\n"); - if (inputStreamAtEOF(base->input) || - (base->decoder && - decoder_get_command(base->decoder) != DECODE_COMMAND_NONE)) { - DEBUG("eof stream\n"); - return ret; - } else { - my_usleep(10000); - } - } else { - break; - } - } - return ret; -} - -static int64_t mpdurl_seek(URLContext *h, int64_t pos, int whence) -{ - FopsHelper *base = (FopsHelper *) h->priv_data; - if (whence != AVSEEK_SIZE) { //only ftell - (void) seekInputStream(base->input, pos, whence); - } - return base->input->offset; -} - -static int mpdurl_close(URLContext *h) -{ - FopsHelper *base = (FopsHelper *) h->priv_data; - if (base && base->input->seekable) { - (void) seekInputStream(base->input, 0, SEEK_SET); - } - h->priv_data = 0; - return 0; -} - -static URLProtocol mpdurl_fileops = { - .name = "mpd", - .url_open = mpdurl_open, - .url_read = mpdurl_read, - .url_seek = mpdurl_seek, - .url_close = mpdurl_close, -}; - -static int ffmpeg_init(void) -{ - av_register_all(); - register_protocol(&mpdurl_fileops); - return 0; -} - -static int ffmpeg_helper(InputStream *input, int (*callback)(BasePtrs *ptrs), - BasePtrs *ptrs) -{ - AVFormatContext *pFormatCtx; - AVCodecContext *aCodecCtx; - AVCodec *aCodec; - int ret, audioStream; - unsigned i; - FopsHelper fopshelp = { - .url = "mpd://X", /* only the mpd:// prefix matters */ - }; - - fopshelp.input = input; - if (ptrs && ptrs->decoder) { - fopshelp.decoder = ptrs->decoder; //are we in decoding loop ? - } else { - fopshelp.decoder = NULL; - } - - //ffmpeg works with ours "fileops" helper - if (av_open_input_file(&pFormatCtx, fopshelp.url, NULL, 0, NULL)!=0) { - ERROR("Open failed!\n"); - return -1; - } - - if (av_find_stream_info(pFormatCtx)<0) { - ERROR("Couldn't find stream info!\n"); - return -1; - } - - audioStream = -1; - for(i=0; inb_streams; i++) { - if (pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO && - audioStream < 0) { - audioStream=i; - } - } - - if(audioStream==-1) { - ERROR("No audio stream inside!\n"); - return -1; - } - - aCodecCtx = pFormatCtx->streams[audioStream]->codec; - aCodec = avcodec_find_decoder(aCodecCtx->codec_id); - - if (!aCodec) { - ERROR("Unsupported audio codec!\n"); - return -1; - } - - if (avcodec_open(aCodecCtx, aCodec)<0) { - ERROR("Could not open codec!\n"); - return -1; - } - - if (callback) { - ptrs->audioStream = audioStream; - ptrs->pFormatCtx = pFormatCtx; - ptrs->aCodecCtx = aCodecCtx; - ptrs->aCodec = aCodec; - - ret = (*callback)( ptrs ); - } else { - ret = 0; - DEBUG("playable\n"); - } - - avcodec_close(aCodecCtx); - av_close_input_file(pFormatCtx); - - return ret; -} - -static bool ffmpeg_try_decode(InputStream *input) -{ - int ret; - if (input->seekable) { - ret = ffmpeg_helper(input, NULL, NULL); - } else { - ret = 0; - } - return (ret == -1 ? 0 : 1); -} - -static int ffmpeg_decode_internal(BasePtrs *base) -{ - struct decoder *decoder = base->decoder; - AVCodecContext *aCodecCtx = base->aCodecCtx; - AVFormatContext *pFormatCtx = base->pFormatCtx; - AVPacket packet; - int len, audio_size; - int position; - struct audio_format audio_format; - int current, total_time; - uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2]; - - total_time = 0; - - DEBUG("decoder_start\n"); - - if (aCodecCtx->channels > 2) { - aCodecCtx->channels = 2; - } - - audio_format.bits = (uint8_t)16; - audio_format.sample_rate = (unsigned int)aCodecCtx->sample_rate; - audio_format.channels = aCodecCtx->channels; - - // frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); - // total_time = ((float)frame_count / (float)audio_format.sample_rate); - - //there is some problem with this on some demux (mp3 at least) - if (pFormatCtx->duration != (int)AV_NOPTS_VALUE) { - total_time = pFormatCtx->duration / AV_TIME_BASE; - } - - DEBUG("ffmpeg sample rate: %dHz %d channels\n", - aCodecCtx->sample_rate, aCodecCtx->channels); - - decoder_initialized(decoder, &audio_format, total_time); - - position = 0; - - DEBUG("duration:%d (%d secs)\n", (int) pFormatCtx->duration, - (int) total_time); - - do { - - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { - - DEBUG("seek\n"); - decoder_clear(decoder); - current = decoder_seek_where(decoder) * AV_TIME_BASE; - - if (av_seek_frame(pFormatCtx, -1, current , 0) < 0) { - WARNING("seek to %d failed\n", current); - } - - decoder_command_finished(decoder); - } - - if (av_read_frame(pFormatCtx, &packet) >= 0) { - if(packet.stream_index == base->audioStream) { - - position = av_rescale_q(packet.pts, pFormatCtx->streams[base->audioStream]->time_base, - (AVRational){1, 1}); - - audio_size = sizeof(audio_buf); - len = avcodec_decode_audio2(aCodecCtx, - (int16_t *)audio_buf, - &audio_size, - packet.data, - packet.size); - - if(len >= 0) { - if(audio_size >= 0) { - // DEBUG("sending data %d/%d\n", audio_size, len); - - decoder_data(decoder, NULL, 1, - audio_buf, audio_size, - position, //(float)current / (float)audio_format.sample_rate, - aCodecCtx->bit_rate / 1000, NULL); - - } - } else { - WARNING("skiping frame!\n"); - } - } - av_free_packet(&packet); - } else { - //end of file - break; - } - } while (decoder_get_command(decoder) != DECODE_COMMAND_STOP); - - decoder_flush(decoder); - - DEBUG("decoder finish\n"); - - return 0; -} - -static int ffmpeg_decode(struct decoder *decoder, InputStream *input) -{ - BasePtrs base; - int ret; - - DEBUG("decode start\n"); - - base.input = input; - base.decoder = decoder; - - ret = ffmpeg_helper(input, ffmpeg_decode_internal, &base); - - DEBUG("decode finish\n"); - - return ret; -} - -static int ffmpeg_tag_internal(BasePtrs *base) -{ - struct tag *tag = (struct tag *) base->tag; - - if (base->pFormatCtx->duration != (int)AV_NOPTS_VALUE) { - tag->time = base->pFormatCtx->duration / AV_TIME_BASE; - } else { - tag->time = 0; - } - return 0; -} - -//no tag reading in ffmpeg, check if playable -static struct tag *ffmpeg_tag(char *file) -{ - InputStream input; - BasePtrs base; - int ret; - struct tag *tag = NULL; - - if (openInputStream(&input, file) < 0) { - ERROR("failed to open %s\n", file); - return NULL; - } - - tag = tag_new(); - - base.tag = tag; - ret = ffmpeg_helper(&input, ffmpeg_tag_internal, &base); - - if (ret != 0) { - free(tag); - tag = NULL; - } - - closeInputStream(&input); - - return tag; -} - -/** - * ffmpeg can decode almost everything from open codecs - * and also some of propietary codecs - * its hard to tell what can ffmpeg decode - * we can later put this into configure script - * to be sure ffmpeg is used to handle - * only that files - */ - -static const char *ffmpeg_Suffixes[] = { - "wma", "asf", "wmv", "mpeg", "mpg", "avi", "vob", "mov", "qt", "swf", "rm", "swf", - "mp1", "mp2", "mp3", "mp4", "m4a", "flac", "ogg", "wav", "au", "aiff", "aif", "ac3", "aac", "mpc", - NULL -}; - -//not sure if this is correct... -static const char *ffmpeg_Mimetypes[] = { - "video/x-ms-asf", - "audio/x-ms-wma", - "audio/x-ms-wax", - "video/x-ms-wmv", - "video/x-ms-wvx", - "video/x-ms-wm", - "video/x-ms-wmx", - "application/x-ms-wmz", - "application/x-ms-wmd", - "audio/mpeg", - NULL -}; - -struct decoder_plugin ffmpegPlugin = { - .name = "ffmpeg", - .init = ffmpeg_init, - .try_decode = ffmpeg_try_decode, - .stream_decode = ffmpeg_decode, - .tag_dup = ffmpeg_tag, - .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, - .suffixes = ffmpeg_Suffixes, - .mime_types = ffmpeg_Mimetypes -}; diff --git a/src/inputPlugins/flac_plugin.c b/src/inputPlugins/flac_plugin.c deleted file mode 100644 index 7b9fce27d..000000000 --- a/src/inputPlugins/flac_plugin.c +++ /dev/null @@ -1,459 +0,0 @@ -/* 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" -#include "../utils.h" -#include "../log.h" - -#include - -/* this code was based on flac123, from flac-tools */ - -static flac_read_status flacRead(mpd_unused const flac_decoder * flacDec, - FLAC__byte buf[], - flac_read_status_size_t *bytes, - void *fdata) -{ - FlacData *data = (FlacData *) fdata; - size_t r; - - r = decoder_read(data->decoder, data->inStream, (void *)buf, *bytes); - *bytes = r; - - if (r == 0) { - if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || - inputStreamAtEOF(data->inStream)) - return flac_read_status_eof; - else - return flac_read_status_abort; - } - - return flac_read_status_continue; -} - -static flac_seek_status flacSeek(mpd_unused 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(mpd_unused 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(mpd_unused 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(mpd_unused const flac_decoder * flacDec, void *fdata) -{ - FlacData *data = (FlacData *) fdata; - - return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && - decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - inputStreamAtEOF(data->inStream); -} - -static void flacError(mpd_unused 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(mpd_unused 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) -{ - FLAC__uint32 samples = frame->header.blocksize; - FlacData *data = (FlacData *) vdata; - 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 && newPosition >= data->position) { - assert(timeChange >= 0); - - data->bitRate = - ((newPosition - data->position) * 8.0 / timeChange) - / 1000 + 0.5; - } - data->position = newPosition; - - return flac_common_write(data, frame, buf); -} - -static struct tag *flacMetadataDup(char *file, int *vorbisCommentFound) -{ - struct tag *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)) { - const char *err; - FLAC_API FLAC__Metadata_SimpleIteratorStatus s; - - s = FLAC__metadata_simple_iterator_status(it); - - switch (s) { /* slightly more human-friendly messages: */ - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: - err = "illegal input"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: - err = "error opening file"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: - err = "not a FLAC file"; - break; - default: - err = FLAC__Metadata_SimpleIteratorStatusString[s]; - } - DEBUG("flacMetadataDup: Reading '%s' " - "metadata gave the following error: %s\n", - file, err); - 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 = tag_new(); - 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 struct tag *flacTagDup(char *file) -{ - struct tag *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) { - struct tag *temp = tag_id3_load(file); - if (temp) { - temp->time = ret->time; - tag_free(ret); - ret = temp; - } - } - - return ret; -} - -static int flac_decode_internal(struct decoder * decoder, - InputStream * inStream, int is_ogg) -{ - flac_decoder *flacDec; - FlacData data; - const char *err = NULL; - - if (!(flacDec = flac_new())) - return -1; - init_FlacData(&data, decoder, inStream); - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - if(!FLAC__stream_decoder_set_metadata_respond(flacDec, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - { - DEBUG(__FILE__": Failed to set metadata respond\n"); - } -#endif - - - 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; - } - } - - decoder_initialized(decoder, &data.audio_format, data.total_time); - - while (1) { - if (!flac_process_single(flacDec)) - break; - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { - FLAC__uint64 sampleToSeek = decoder_seek_where(decoder) * - data.audio_format.sample_rate + 0.5; - if (flac_seek_absolute(flacDec, sampleToSeek)) { - decoder_clear(decoder); - data.time = ((float)sampleToSeek) / - data.audio_format.sample_rate; - data.position = 0; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } else if (flac_get_state(flacDec) == flac_decoder_eof) - break; - } - if (decoder_get_command(decoder) != DECODE_COMMAND_STOP) { - flacPrintErroredState(flac_get_state(flacDec)); - flac_finish(flacDec); - } - -fail: - if (data.replayGainInfo) - freeReplayGainInfo(data.replayGainInfo); - - if (flacDec) - flac_delete(flacDec); - - if (err) { - ERROR("flac %s\n", err); - return -1; - } - return 0; -} - -static int flac_decode(struct decoder * decoder, InputStream * inStream) -{ - return flac_decode_internal(decoder, inStream, 0); -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 && \ - !defined(HAVE_OGGFLAC) -static struct tag *oggflac_tag_dup(char *file) -{ - struct tag *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 = tag_new(); - 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(struct decoder *decoder, InputStream * inStream) -{ - return flac_decode_internal(decoder, inStream, 1); -} - -static bool oggflac_try_decode(InputStream * inStream) -{ - return FLAC_API_SUPPORTS_OGG_FLAC && - ogg_stream_type_detect(inStream) == FLAC; -} - -static const char *oggflac_suffixes[] = { "ogg", "oga", NULL }; -static const char *oggflac_mime_types[] = { "audio/x-flac+ogg", - "application/ogg", - "application/x-ogg", - NULL }; - -struct decoder_plugin oggflacPlugin = { - .name = "oggflac", - .try_decode = oggflac_try_decode, - .stream_decode = oggflac_decode, - .tag_dup = oggflac_tag_dup, - .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -}; - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -static const char *flacSuffixes[] = { "flac", NULL }; -static const char *flac_mime_types[] = { "audio/x-flac", - "application/x-flac", - NULL }; - -struct decoder_plugin flacPlugin = { - .name = "flac", - .stream_decode = flac_decode, - .tag_dup = flacTagDup, - .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, - .suffixes = flacSuffixes, - .mime_types = flac_mime_types -}; diff --git a/src/inputPlugins/mod_plugin.c b/src/inputPlugins/mod_plugin.c deleted file mode 100644 index 5916a24ab..000000000 --- a/src/inputPlugins/mod_plugin.c +++ /dev/null @@ -1,278 +0,0 @@ -/* 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 "../decoder_api.h" -#include "../utils.h" -#include "../log.h" - -#include - -/* 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 char drv_name[] = "MPD"; -static char drv_version[] = "MPD Output Driver v0.1"; - -#if (LIBMIKMOD_VERSION > 0x030106) -static char drv_alias[] = "mpd"; -#endif - -static MDRIVER drv_mpd = { - NULL, - drv_name, - drv_version, - 0, - 255, -#if (LIBMIKMOD_VERSION > 0x030106) - drv_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) -{ - static char params[] = ""; - - 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(params)) { - 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(struct decoder * decoder, char *path) -{ - mod_Data *data; - struct audio_format audio_format; - float total_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; - } - - audio_format.bits = 16; - audio_format.sample_rate = 44100; - audio_format.channels = 2; - - secPerByte = - 1.0 / ((audio_format.bits * audio_format.channels / 8.0) * - (float)audio_format.sample_rate); - - decoder_initialized(decoder, &audio_format, 0); - - while (1) { - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { - decoder_seek_error(decoder); - } - - if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) - break; - - if (!Player_Active()) - break; - - ret = VC_WriteBytes(data->audio_buffer, MIKMOD_FRAME_SIZE); - total_time += ret * secPerByte; - decoder_data(decoder, NULL, 0, - (char *)data->audio_buffer, ret, - total_time, 0, NULL); - } - - decoder_flush(decoder); - - mod_close(data); - - MikMod_Exit(); - - return 0; -} - -static struct tag *modTagDup(char *file) -{ - struct tag *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 = tag_new(); - - ret->time = 0; - title = xstrdup(Player_LoadTitle(file)); - if (title) - tag_add_item(ret, TAG_ITEM_TITLE, title); - - MikMod_Exit(); - - return ret; -} - -static const char *modSuffixes[] = { "amf", - "dsm", - "far", - "gdm", - "imf", - "it", - "med", - "mod", - "mtm", - "s3m", - "stm", - "stx", - "ult", - "uni", - "xm", - NULL -}; - -struct decoder_plugin modPlugin = { - .name = "mod", - .finish = mod_finishMikMod, - .file_decode = mod_decode, - .tag_dup = modTagDup, - .stream_types = INPUT_PLUGIN_STREAM_FILE, - .suffixes = modSuffixes, -}; diff --git a/src/inputPlugins/mp3_plugin.c b/src/inputPlugins/mp3_plugin.c deleted file mode 100644 index a0de30ba7..000000000 --- a/src/inputPlugins/mp3_plugin.c +++ /dev/null @@ -1,1086 +0,0 @@ -/* 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 "../decoder_api.h" -#include "../log.h" -#include "../utils.h" -#include "../conf.h" - -#include - -#ifdef HAVE_ID3TAG -#include -#endif - -#define FRAMES_CUSHION 2000 - -#define READ_BUFFER_SIZE 40960 - -enum mp3_action { - DECODE_SKIP = -3, - DECODE_BREAK = -2, - DECODE_CONT = -1, - DECODE_OK = 0 -}; - -enum muteframe { - MUTEFRAME_NONE, - MUTEFRAME_SKIP, - MUTEFRAME_SEEK -}; - -/* the number of samples of silence the decoder inserts at start */ -#define DECODERDELAY 529 - -#define DEFAULT_GAPLESS_MP3_PLAYBACK 1 - -static int gaplessPlaybackEnabled; - -static inline int32_t -mad_fixed_to_24_sample(mad_fixed_t sample) -{ - enum { - bits = 24, - MIN = -MAD_F_ONE, - MAX = MAD_F_ONE - 1 - }; - - /* round */ - sample = sample + (1L << (MAD_F_FRACBITS - bits)); - - /* clip */ - if (sample > MAX) - sample = MAX; - else if (sample < MIN) - sample = MIN; - - /* quantize */ - return sample >> (MAD_F_FRACBITS + 1 - bits); -} - -static void -mad_fixed_to_24_buffer(int32_t *dest, const struct mad_synth *synth, - unsigned int start, unsigned int end, - unsigned int num_channels) -{ - unsigned int i, c; - - for (i = start; i < end; ++i) { - for (c = 0; c < num_channels; ++c) - *dest++ = mad_fixed_to_24_sample(synth->pcm.samples[c][i]); - } -} - -/* end of stolen stuff from mpg321 */ - -static int mp3_plugin_init(void) -{ - gaplessPlaybackEnabled = getBoolConfigParam(CONF_GAPLESS_MP3_PLAYBACK, - 1); - if (gaplessPlaybackEnabled == CONF_BOOL_UNSET) - gaplessPlaybackEnabled = DEFAULT_GAPLESS_MP3_PLAYBACK; - return 1; -} - -/* decoder stuff is based on madlld */ - -#define MP3_DATA_OUTPUT_BUFFER_SIZE 2048 - -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]; - int32_t outputBuffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; - float totalTime; - float elapsedTime; - enum muteframe muteFrame; - long *frameOffset; - mad_timer_t *times; - unsigned long highestFrame; - unsigned long maxFrames; - unsigned long currentFrame; - unsigned int dropFramesAtStart; - unsigned int dropFramesAtEnd; - unsigned int dropSamplesAtStart; - unsigned int dropSamplesAtEnd; - int foundXing; - int foundFirstFrame; - int decodedFirstFrame; - unsigned long bitRate; - struct decoder *decoder; - InputStream *inStream; - enum mad_layer layer; -} mp3DecodeData; - -static void initMp3DecodeData(mp3DecodeData * data, struct decoder *decoder, - InputStream * inStream) -{ - data->muteFrame = MUTEFRAME_NONE; - 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->decoder = decoder; - data->inStream = inStream; - data->layer = 0; - - 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 = decoder_read(data->decoder, data->inStream, - readStart, readSize); - if (readed == 0) - return -1; - - 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, size_t tagsize, - struct tag ** mpdTag, ReplayGainInfo ** replayGainInfo) -{ - struct id3_tag *id3Tag = NULL; - id3_length_t count; - id3_byte_t const *id3_data; - id3_byte_t *allocated = NULL; - struct tag *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) { - size_t len; - - len = decoder_read(data->decoder, data->inStream, - allocated + count, tagsize - count); - if (len == 0) - break; - 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 = tag_id3_import(id3Tag); - if (tmpMpdTag) { - if (*mpdTag) - tag_free(*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 enum mp3_action -decodeNextFrameHeader(mp3DecodeData * data, struct tag ** 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, (size_t)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)); - 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 enum mp3_action -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)); - 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 version { - unsigned major; - unsigned minor; -}; - -struct lame { - char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ - struct version version; /* struct containing just the version */ - float peak; /* replaygain peak */ - float trackGain; /* replaygain track gain */ - float albumGain; /* replaygain album gain */ - int encoderDelay; /* # of added samples at start of mp3 */ - int encoderPadding; /* # of added samples at end of mp3 */ - int crc; /* CRC of the first 190 bytes of this frame */ -}; - -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 adj = 0; - int name; - int orig; - int sign; - int gain; - 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'; - - *bitlen -= 72; - - /* 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 (prefixcmp(lame->encoder, "LAME")) - return 0; - - if (sscanf(lame->encoder+4, "%u.%u", - &lame->version.major, &lame->version.minor) != 2) - return 0; - - DEBUG("detected LAME version %i.%i (\"%s\")\n", - lame->version.major, lame->version.minor, lame->encoder); - - /* The reference volume was changed from the 83dB used in the - * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older - * versions, since everyone else uses 89dB instead of 83dB. - * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so - * it's impossible to make the proper adjustment for 3.95. - * Fortunately, 3.95 was only out for about a day before 3.95.1 was - * released. -- tmz */ - if (lame->version.major < 3 || - (lame->version.major == 3 && lame->version.minor < 95)) - adj = 6; - - mad_bit_read(ptr, 16); - - lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */ - DEBUG("LAME peak found: %f\n", lame->peak); - - lame->trackGain = 0; - name = mad_bit_read(ptr, 3); /* gain name */ - orig = mad_bit_read(ptr, 3); /* gain originator */ - sign = mad_bit_read(ptr, 1); /* sign bit */ - gain = mad_bit_read(ptr, 9); /* gain*10 */ - if (gain && name == 1 && orig != 0) { - lame->trackGain = ((sign ? -gain : gain) / 10.0) + adj; - DEBUG("LAME track gain found: %f\n", lame->trackGain); - } - - /* tmz reports that this isn't currently written by any version of lame - * (as of 3.97). Since we have no way of testing it, don't use it. - * Wouldn't want to go blowing someone's ears just because we read it - * wrong. :P -- jat */ - lame->albumGain = 0; -#if 0 - name = mad_bit_read(ptr, 3); /* gain name */ - orig = mad_bit_read(ptr, 3); /* gain originator */ - sign = mad_bit_read(ptr, 1); /* sign bit */ - gain = mad_bit_read(ptr, 9); /* gain*10 */ - if (gain && name == 2 && orig != 0) { - lame->albumGain = ((sign ? -gain : gain) / 10.0) + adj; - DEBUG("LAME album gain found: %f\n", lame->trackGain); - } -#else - mad_bit_read(ptr, 16); -#endif - - mad_bit_read(ptr, 16); - - lame->encoderDelay = mad_bit_read(ptr, 12); - lame->encoderPadding = mad_bit_read(ptr, 12); - - DEBUG("encoder delay is %i, encoder padding is %i\n", - lame->encoderDelay, lame->encoderPadding); - - mad_bit_read(ptr, 80); - - lame->crc = mad_bit_read(ptr, 16); - - *bitlen -= 216; - - return 1; -} - -static int decodeFirstFrame(mp3DecodeData * data, - struct tag ** tag, ReplayGainInfo ** replayGainInfo) -{ - struct decoder *decoder = data->decoder; - 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 && - (!decoder || decoder_get_command(decoder) == DECODE_COMMAND_NONE)); - if (ret == DECODE_BREAK || - (decoder && decoder_get_command(decoder) != DECODE_COMMAND_NONE)) - return -1; - if (ret == DECODE_SKIP) continue; - - while ((ret = decodeNextFrame(data)) == DECODE_CONT && - (!decoder || decoder_get_command(decoder) == DECODE_COMMAND_NONE)); - if (ret == DECODE_BREAK || - (decoder && decoder_get_command(decoder) != DECODE_COMMAND_NONE)) - 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 ((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 (parse_lame(&lame, &ptr, &bitlen)) { - if (gaplessPlaybackEnabled && - data->inStream->seekable) { - data->dropSamplesAtStart = lame.encoderDelay + - DECODERDELAY; - data->dropSamplesAtEnd = lame.encoderPadding; - } - - /* Album gain isn't currently used. See comment in - * parse_lame() for details. -- jat */ - if (replayGainInfo && !*replayGainInfo && - lame.trackGain) { - *replayGainInfo = newReplayGainInfo(); - (*replayGainInfo)->trackGain = lame.trackGain; - (*replayGainInfo)->trackPeak = lame.peak; - } - } - } - - if (!data->maxFrames) return -1; - - if (data->maxFrames > 8 * 1024 * 1024) { - ERROR("mp3 file header indicates too many frames: %lu", - 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, NULL, &inStream); - if (decodeFirstFrame(&data, NULL, NULL) < 0) - ret = -1; - else - ret = data.totalTime + 0.5; - mp3DecodeDataFinalize(&data); - closeInputStream(&inStream); - - return ret; -} - -static int openMp3FromInputStream(InputStream * inStream, mp3DecodeData * data, - struct decoder * decoder, struct tag ** tag, - ReplayGainInfo ** replayGainInfo) -{ - initMp3DecodeData(data, decoder, inStream); - *tag = NULL; - if (decodeFirstFrame(data, tag, replayGainInfo) < 0) { - mp3DecodeDataFinalize(data); - if (tag && *tag) - tag_free(*tag); - return -1; - } - - return 0; -} - -static enum mp3_action -mp3Read(mp3DecodeData * data, ReplayGainInfo ** replayGainInfo) -{ - struct decoder *decoder = data->decoder; - unsigned int pcm_length, max_samples; - unsigned 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 = MUTEFRAME_NONE; - break; - case MUTEFRAME_SEEK: - if (decoder_seek_where(decoder) <= data->elapsedTime) { - decoder_clear(decoder); - data->muteFrame = MUTEFRAME_NONE; - decoder_command_finished(decoder); - } - break; - case MUTEFRAME_NONE: - mad_synth_frame(&data->synth, &data->frame); - - if (!data->foundFirstFrame) { - unsigned int 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) { - struct tag *tag = tag_new(); - if (data->inStream->metaName) { - tag_add_item(tag, TAG_ITEM_NAME, - data->inStream->metaName); - } - tag_add_item(tag, TAG_ITEM_TITLE, - data->inStream->metaTitle); - free(data->inStream->metaTitle); - data->inStream->metaTitle = NULL; - tag_free(tag); - } - - if (!data->decodedFirstFrame) { - i = data->dropSamplesAtStart; - data->decodedFirstFrame = 1; - } else - i = 0; - - pcm_length = data->synth.pcm.length; - if (data->dropSamplesAtEnd && - (data->currentFrame == data->maxFrames - data->dropFramesAtEnd)) { - if (data->dropSamplesAtEnd >= pcm_length) - pcm_length = 0; - else - pcm_length -= data->dropSamplesAtEnd; - } - - max_samples = sizeof(data->outputBuffer) / - sizeof(data->outputBuffer[0]) / - MAD_NCHANNELS(&(data->frame).header); - - while (i < pcm_length) { - enum decoder_command cmd; - unsigned int num_samples = pcm_length - i; - if (num_samples > max_samples) - num_samples = max_samples; - - i += num_samples; - - mad_fixed_to_24_buffer(data->outputBuffer, - &data->synth, - i - num_samples, i, - MAD_NCHANNELS(&(data->frame).header)); - num_samples *= MAD_NCHANNELS(&(data->frame).header); - - cmd = decoder_data(decoder, data->inStream, - data->inStream->seekable, - data->outputBuffer, - sizeof(data->outputBuffer[0]) * num_samples, - data->elapsedTime, - data->bitRate / 1000, - (replayGainInfo != NULL) ? *replayGainInfo : NULL); - if (cmd == DECODE_COMMAND_STOP) - return DECODE_BREAK; - } - - if (data->dropSamplesAtEnd && - (data->currentFrame == data->maxFrames - data->dropFramesAtEnd)) - /* stop decoding, effectively dropping - * all remaining samples */ - return DECODE_BREAK; - - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK && - data->inStream->seekable) { - unsigned long j = 0; - data->muteFrame = MUTEFRAME_SEEK; - while (j < data->highestFrame && - decoder_seek_where(decoder) > - ((float)mad_timer_count(data->times[j], - MAD_UNITS_MILLISECONDS)) - / 1000) { - j++; - } - if (j < data->highestFrame) { - if (seekMp3InputBuffer(data, - data->frameOffset[j]) == - 0) { - decoder_clear(decoder); - data->currentFrame = j; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - data->muteFrame = MUTEFRAME_NONE; - } - } else if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK && - !data->inStream->seekable) { - decoder_seek_error(decoder); - } - } - - while (1) { - skip = 0; - while ((ret = - decodeNextFrameHeader(data, NULL, - replayGainInfo)) == DECODE_CONT - && decoder_get_command(decoder) == DECODE_COMMAND_NONE) ; - if (ret == DECODE_BREAK || decoder_get_command(decoder) != DECODE_COMMAND_NONE) - break; - else if (ret == DECODE_SKIP) - skip = 1; - if (data->muteFrame == MUTEFRAME_NONE) { - while ((ret = decodeNextFrame(data)) == DECODE_CONT && - decoder_get_command(decoder) == DECODE_COMMAND_NONE) ; - if (ret == DECODE_BREAK || - decoder_get_command(decoder) != DECODE_COMMAND_NONE) - break; - } - if (!skip && ret == DECODE_OK) - break; - } - - switch (decoder_get_command(decoder)) { - case DECODE_COMMAND_NONE: - case DECODE_COMMAND_START: - break; - - case DECODE_COMMAND_STOP: - return DECODE_BREAK; - - case DECODE_COMMAND_SEEK: - return DECODE_CONT; - } - - return ret; -} - -static void initAudioFormatFromMp3DecodeData(mp3DecodeData * data, - struct audio_format * af) -{ - af->bits = 24; - af->sample_rate = (data->frame).header.samplerate; - af->channels = MAD_NCHANNELS(&(data->frame).header); -} - -static int mp3_decode(struct decoder * decoder, InputStream * inStream) -{ - mp3DecodeData data; - struct tag *tag = NULL; - ReplayGainInfo *replayGainInfo = NULL; - struct audio_format audio_format; - - if (openMp3FromInputStream(inStream, &data, decoder, - &tag, &replayGainInfo) < 0) { - if (decoder_get_command(decoder) == DECODE_COMMAND_NONE) { - ERROR - ("Input does not appear to be a mp3 bit stream.\n"); - return -1; - } - return 0; - } - - initAudioFormatFromMp3DecodeData(&data, &audio_format); - - if (inStream->metaTitle) { - if (tag) - tag_free(tag); - tag = tag_new(); - tag_add_item(tag, TAG_ITEM_TITLE, inStream->metaTitle); - free(inStream->metaTitle); - inStream->metaTitle = NULL; - if (inStream->metaName) { - tag_add_item(tag, TAG_ITEM_NAME, inStream->metaName); - } - tag_free(tag); - } else if (tag) { - if (inStream->metaName) { - tag_clear_items_by_type(tag, TAG_ITEM_NAME); - tag_add_item(tag, TAG_ITEM_NAME, inStream->metaName); - } - tag_free(tag); - } else if (inStream->metaName) { - tag = tag_new(); - if (inStream->metaName) { - tag_add_item(tag, TAG_ITEM_NAME, inStream->metaName); - } - tag_free(tag); - } - - decoder_initialized(decoder, &audio_format, data.totalTime); - - while (mp3Read(&data, &replayGainInfo) != DECODE_BREAK) ; - - if (replayGainInfo) - freeReplayGainInfo(replayGainInfo); - - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK && - data.muteFrame == MUTEFRAME_SEEK) { - decoder_clear(decoder); - decoder_command_finished(decoder); - } - - decoder_flush(decoder); - mp3DecodeDataFinalize(&data); - - return 0; -} - -static struct tag *mp3_tagDup(char *file) -{ - struct tag *ret = NULL; - int total_time; - - ret = tag_id3_load(file); - - total_time = getMp3TotalTime(file); - - if (total_time >= 0) { - if (!ret) - ret = tag_new(); - ret->time = total_time; - } else { - DEBUG("mp3_tagDup: Failed to get total song time from: %s\n", - file); - } - - return ret; -} - -static const char *mp3_suffixes[] = { "mp3", "mp2", NULL }; -static const char *mp3_mimeTypes[] = { "audio/mpeg", NULL }; - -struct decoder_plugin mp3Plugin = { - .name = "mp3", - .init = mp3_plugin_init, - .stream_decode = mp3_decode, - .tag_dup = mp3_tagDup, - .stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, - .suffixes = mp3_suffixes, - .mime_types = mp3_mimeTypes -}; diff --git a/src/inputPlugins/mp4_plugin.c b/src/inputPlugins/mp4_plugin.c deleted file mode 100644 index 4a613744e..000000000 --- a/src/inputPlugins/mp4_plugin.c +++ /dev/null @@ -1,423 +0,0 @@ -/* 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 "../decoder_api.h" -#include "../utils.h" -#include "../log.h" - -#include "mp4ff.h" - -#include -#include -/* 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, length); -} - -static uint32_t mp4_inputStreamSeekCallback(void *inStream, uint64_t position) -{ - return seekInputStream((InputStream *) inStream, position, SEEK_SET); -} - -static int mp4_decode(struct decoder * mpd_decoder, InputStream * inStream) -{ - mp4ff_t *mp4fh; - mp4ff_callback_t *mp4cb; - int32_t track; - float file_time, total_time; - int32_t scale; - faacDecHandle decoder; - faacDecFrameInfo frameInfo; - faacDecConfigurationPtr config; - struct audio_format audio_format; - unsigned char *mp4Buffer; - unsigned int mp4BufferSize; - uint32_t sample_rate; - unsigned char channels; - long sampleId; - long numSamples; - long dur; - unsigned int sampleCount; - char *sampleBuffer; - size_t sampleBufferLen; - unsigned int initial = 1; - float *seekTable; - long seekTableEnd = -1; - bool seekPositionFound = false; - long offset; - uint16_t bitRate = 0; - bool seeking = false; - double seek_where = 0; - bool initialized = false; - - 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); - return -1; - } - - track = mp4_getAACTrack(mp4fh); - if (track < 0) { - ERROR("No AAC track found in mp4 stream.\n"); - mp4ff_close(mp4fh); - 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); - - audio_format.bits = 16; - - mp4Buffer = NULL; - mp4BufferSize = 0; - mp4ff_get_decoder_config(mp4fh, track, &mp4Buffer, &mp4BufferSize); - - if (faacDecInit2 - (decoder, mp4Buffer, mp4BufferSize, &sample_rate, &channels) < 0) { - ERROR("Error not a AAC stream.\n"); - faacDecClose(decoder); - mp4ff_close(mp4fh); - free(mp4cb); - return -1; - } - - audio_format.sample_rate = sample_rate; - audio_format.channels = channels; - file_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); - free(mp4cb); - return -1; - } - total_time = ((float)file_time) / scale; - - numSamples = mp4ff_num_samples(mp4fh, track); - if (numSamples > (long)(INT_MAX / sizeof(float))) { - ERROR("Integer overflow.\n"); - faacDecClose(decoder); - mp4ff_close(mp4fh); - free(mp4cb); - return -1; - } - - file_time = 0.0; - - seekTable = xmalloc(sizeof(float) * numSamples); - - for (sampleId = 0; sampleId < numSamples; sampleId++) { - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { - seeking = true; - seek_where = decoder_seek_where(mpd_decoder); - } - - if (seeking && seekTableEnd > 1 && - seekTable[seekTableEnd] >= seek_where) { - int i = 2; - while (seekTable[i] < seek_where) - i++; - sampleId = i - 1; - file_time = seekTable[sampleId]; - } - - dur = mp4ff_get_sample_duration(mp4fh, track, sampleId); - offset = mp4ff_get_sample_offset(mp4fh, track, sampleId); - - if (sampleId > seekTableEnd) { - seekTable[sampleId] = file_time; - seekTableEnd = sampleId; - } - - if (sampleId == 0) - dur = 0; - if (offset > dur) - dur = 0; - else - dur -= offset; - file_time += ((float)dur) / scale; - - if (seeking && file_time > seek_where) - seekPositionFound = true; - - if (seeking && seekPositionFound) { - seekPositionFound = false; - decoder_clear(mpd_decoder); - seeking = 0; - decoder_command_finished(mpd_decoder); - } - - if (seeking) - continue; - - if (mp4ff_read_sample(mp4fh, track, sampleId, &mp4Buffer, - &mp4BufferSize) == 0) - break; - -#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("faad2 error: %s\n", - faacDecGetErrorMessage(frameInfo.error)); - break; - } - - if (!initialized) { - channels = frameInfo.channels; -#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE - scale = frameInfo.samplerate; -#endif - audio_format.sample_rate = scale; - audio_format.channels = frameInfo.channels; - decoder_initialized(mpd_decoder, &audio_format, - total_time); - initialized = true; - } - - if (channels * (unsigned long)(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; - - decoder_data(mpd_decoder, inStream, 1, sampleBuffer, - sampleBufferLen, file_time, - bitRate, NULL); - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP) - break; - } - - free(seekTable); - faacDecClose(decoder); - mp4ff_close(mp4fh); - free(mp4cb); - - if (!initialized) - return -1; - - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK && seeking) { - decoder_clear(mpd_decoder); - decoder_command_finished(mpd_decoder); - } - decoder_flush(mpd_decoder); - - return 0; -} - -static struct tag *mp4DataDup(char *file, int *mp4MetadataFound) -{ - struct tag *ret = NULL; - InputStream inStream; - mp4ff_t *mp4fh; - mp4ff_callback_t *callback; - int32_t track; - int32_t file_time; - int32_t scale; - int i; - - *mp4MetadataFound = 0; - - if (openInputStream(&inStream, file) < 0) { - DEBUG("mp4DataDup: Failed to open file: %s\n", file); - return NULL; - } - - callback = xmalloc(sizeof(mp4ff_callback_t)); - callback->read = mp4_inputStreamReadCallback; - callback->seek = mp4_inputStreamSeekCallback; - callback->user_data = &inStream; - - mp4fh = mp4ff_open_read(callback); - if (!mp4fh) { - free(callback); - closeInputStream(&inStream); - return NULL; - } - - track = mp4_getAACTrack(mp4fh); - if (track < 0) { - mp4ff_close(mp4fh); - closeInputStream(&inStream); - free(callback); - return NULL; - } - - ret = tag_new(); - file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); - scale = mp4ff_time_scale(mp4fh, track); - if (scale < 0) { - mp4ff_close(mp4fh); - closeInputStream(&inStream); - free(callback); - tag_free(ret); - return NULL; - } - ret->time = ((float)file_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)) { - tag_add_item(ret, TAG_ITEM_ARTIST, value); - *mp4MetadataFound = 1; - } else if (0 == strcasecmp("title", item)) { - tag_add_item(ret, TAG_ITEM_TITLE, value); - *mp4MetadataFound = 1; - } else if (0 == strcasecmp("album", item)) { - tag_add_item(ret, TAG_ITEM_ALBUM, value); - *mp4MetadataFound = 1; - } else if (0 == strcasecmp("track", item)) { - tag_add_item(ret, TAG_ITEM_TRACK, value); - *mp4MetadataFound = 1; - } else if (0 == strcasecmp("disc", item)) { /* Is that the correct id? */ - tag_add_item(ret, TAG_ITEM_DISC, value); - *mp4MetadataFound = 1; - } else if (0 == strcasecmp("genre", item)) { - tag_add_item(ret, TAG_ITEM_GENRE, value); - *mp4MetadataFound = 1; - } else if (0 == strcasecmp("date", item)) { - tag_add_item(ret, TAG_ITEM_DATE, value); - *mp4MetadataFound = 1; - } - - free(item); - free(value); - } - - mp4ff_close(mp4fh); - closeInputStream(&inStream); - - return ret; -} - -static struct tag *mp4TagDup(char *file) -{ - struct tag *ret = NULL; - int mp4MetadataFound = 0; - - ret = mp4DataDup(file, &mp4MetadataFound); - if (!ret) - return NULL; - if (!mp4MetadataFound) { - struct tag *temp = tag_id3_load(file); - if (temp) { - temp->time = ret->time; - tag_free(ret); - ret = temp; - } - } - - return ret; -} - -static const char *mp4_suffixes[] = { "m4a", "mp4", NULL }; -static const char *mp4_mimeTypes[] = { "audio/mp4", "audio/m4a", NULL }; - -struct decoder_plugin mp4Plugin = { - .name = "mp4", - .stream_decode = mp4_decode, - .tag_dup = mp4TagDup, - .stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, - .suffixes = mp4_suffixes, - .mime_types = mp4_mimeTypes, -}; diff --git a/src/inputPlugins/mpc_plugin.c b/src/inputPlugins/mpc_plugin.c deleted file mode 100644 index fb1b0b56c..000000000 --- a/src/inputPlugins/mpc_plugin.c +++ /dev/null @@ -1,308 +0,0 @@ -/* 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 "../decoder_api.h" -#include "../utils.h" -#include "../log.h" - -#include - -typedef struct _MpcCallbackData { - InputStream *inStream; - struct decoder *decoder; -} MpcCallbackData; - -static mpc_int32_t mpc_read_cb(void *vdata, void *ptr, mpc_int32_t size) -{ - MpcCallbackData *data = (MpcCallbackData *) vdata; - - return decoder_read(data->decoder, data->inStream, ptr, size); -} - -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 int16_t convertSample(MPC_SAMPLE_FORMAT sample) -{ - /* only doing 16-bit audio for now */ - int32_t 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(struct decoder * mpd_decoder, InputStream * inStream) -{ - mpc_decoder decoder; - mpc_reader reader; - mpc_streaminfo info; - struct audio_format audio_format; - - 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; - int16_t *s16 = (int16_t *) chunk; - unsigned long samplePos = 0; - mpc_uint32_t vbrUpdateAcc; - mpc_uint32_t vbrUpdateBits; - float total_time; - int i; - ReplayGainInfo *replayGainInfo = NULL; - - data.inStream = inStream; - data.decoder = mpd_decoder; - - 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) { - if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) { - ERROR("Not a valid musepack stream\n"); - return -1; - } - return 0; - } - - mpc_decoder_setup(&decoder, &reader); - - if (!mpc_decoder_initialize(&decoder, &info)) { - if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP) { - ERROR("Not a valid musepack stream\n"); - return -1; - } - return 0; - } - - audio_format.bits = 16; - audio_format.channels = info.channels; - audio_format.sample_rate = info.sample_freq; - - 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; - - decoder_initialized(mpd_decoder, &audio_format, - mpc_streaminfo_get_length(&info)); - - while (!eof) { - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { - samplePos = decoder_seek_where(mpd_decoder) * - audio_format.sample_rate; - if (mpc_decoder_seek_sample(&decoder, samplePos)) { - decoder_clear(mpd_decoder); - s16 = (int16_t *) chunk; - chunkpos = 0; - decoder_command_finished(mpd_decoder); - } else - decoder_seek_error(mpd_decoder); - } - - vbrUpdateAcc = 0; - vbrUpdateBits = 0; - ret = mpc_decoder_decode(&decoder, sample_buffer, - &vbrUpdateAcc, &vbrUpdateBits); - - if (ret <= 0 || decoder_get_command(mpd_decoder) == DECODE_COMMAND_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) { - total_time = ((float)samplePos) / - audio_format.sample_rate; - - bitRate = vbrUpdateBits * - audio_format.sample_rate / 1152 / 1000; - - decoder_data(mpd_decoder, inStream, - inStream->seekable, - chunk, chunkpos, - total_time, - bitRate, replayGainInfo); - - chunkpos = 0; - s16 = (int16_t *) chunk; - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_STOP) { - eof = 1; - break; - } - } - } - } - - if (decoder_get_command(mpd_decoder) != DECODE_COMMAND_STOP && - chunkpos > 0) { - total_time = ((float)samplePos) / audio_format.sample_rate; - - bitRate = - vbrUpdateBits * audio_format.sample_rate / 1152 / 1000; - - decoder_data(mpd_decoder, NULL, inStream->seekable, - chunk, chunkpos, total_time, bitRate, - replayGainInfo); - } - - decoder_flush(mpd_decoder); - - freeReplayGainInfo(replayGainInfo); - - return 0; -} - -static float mpcGetTime(char *file) -{ - InputStream inStream; - float total_time = -1; - - mpc_reader reader; - mpc_streaminfo info; - MpcCallbackData data; - - data.inStream = &inStream; - data.decoder = 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; - } - - total_time = mpc_streaminfo_get_length(&info); - - closeInputStream(&inStream); - - return total_time; -} - -static struct tag *mpcTagDup(char *file) -{ - struct tag *ret = NULL; - float total_time = mpcGetTime(file); - - if (total_time < 0) { - DEBUG("mpcTagDup: Failed to get Songlength of file: %s\n", - file); - return NULL; - } - - ret = tag_ape_load(file); - if (!ret) - ret = tag_id3_load(file); - if (!ret) - ret = tag_new(); - ret->time = total_time; - - return ret; -} - -static const char *mpcSuffixes[] = { "mpc", NULL }; - -struct decoder_plugin mpcPlugin = { - .name = "mpc", - .stream_decode = mpc_decode, - .tag_dup = mpcTagDup, - .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, - .suffixes = mpcSuffixes, -}; diff --git a/src/inputPlugins/oggflac_plugin.c b/src/inputPlugins/oggflac_plugin.c deleted file mode 100644 index 091b00988..000000000 --- a/src/inputPlugins/oggflac_plugin.c +++ /dev/null @@ -1,355 +0,0 @@ -/* 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 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public 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" -#include "_ogg_common.h" - -#include "../utils.h" -#include "../log.h" - -#include - -static void oggflac_cleanup(FlacData * data, - OggFLAC__SeekableStreamDecoder * decoder) -{ - if (data->replayGainInfo) - freeReplayGainInfo(data->replayGainInfo); - if (decoder) - OggFLAC__seekable_stream_decoder_delete(decoder); -} - -static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(mpd_unused const - OggFLAC__SeekableStreamDecoder - * decoder, - FLAC__byte buf[], - unsigned *bytes, - void *fdata) -{ - FlacData *data = (FlacData *) fdata; - size_t r; - - r = decoder_read(data->decoder, data->inStream, (void *)buf, *bytes); - *bytes = r; - - if (r == 0 && !inputStreamAtEOF(data->inStream) && - decoder_get_command(data->decoder) == DECODE_COMMAND_NONE) - return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; - - return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; -} - -static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(mpd_unused 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(mpd_unused 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(mpd_unused 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(mpd_unused const OggFLAC__SeekableStreamDecoder * decoder, - void *fdata) -{ - FlacData *data = (FlacData *) fdata; - - return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && - decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - inputStreamAtEOF(data->inStream); -} - -static void of_error_cb(mpd_unused 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(mpd_unused 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; - float timeChange; - - timeChange = ((float)samples) / frame->header.sample_rate; - data->time += timeChange; - - return flac_common_write(data, frame, buf); -} - -/* used by TagDup */ -static void of_metadata_dup_cb(mpd_unused 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 = tag_new(); - 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(mpd_unused 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 struct tag *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, &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(&data, decoder); - closeInputStream(&inStream); - - return data.tag; -} - -static bool oggflac_try_decode(InputStream * inStream) -{ - if (!inStream->seekable) - /* we cannot seek after the detection, so don't bother - checking */ - return true; - - return ogg_stream_type_detect(inStream) == FLAC; -} - -static int oggflac_decode(struct decoder * mpd_decoder, InputStream * inStream) -{ - OggFLAC__SeekableStreamDecoder *decoder = NULL; - FlacData data; - int ret = 0; - - init_FlacData(&data, mpd_decoder, inStream); - - if (!(decoder = full_decoder_init_and_read_metadata(&data, 0))) { - ret = -1; - goto fail; - } - - decoder_initialized(mpd_decoder, &data.audio_format, data.total_time); - - while (1) { - OggFLAC__seekable_stream_decoder_process_single(decoder); - if (OggFLAC__seekable_stream_decoder_get_state(decoder) != - OggFLAC__SEEKABLE_STREAM_DECODER_OK) { - break; - } - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { - FLAC__uint64 sampleToSeek = decoder_seek_where(mpd_decoder) * - data.audio_format.sample_rate + 0.5; - if (OggFLAC__seekable_stream_decoder_seek_absolute - (decoder, sampleToSeek)) { - decoder_clear(mpd_decoder); - data.time = ((float)sampleToSeek) / - data.audio_format.sample_rate; - data.position = 0; - decoder_command_finished(mpd_decoder); - } else - decoder_seek_error(mpd_decoder); - } - } - - if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) { - oggflacPrintErroredState - (OggFLAC__seekable_stream_decoder_get_state(decoder)); - OggFLAC__seekable_stream_decoder_finish(decoder); - } - -fail: - oggflac_cleanup(&data, decoder); - - return ret; -} - -static const char *oggflac_Suffixes[] = { "ogg", "oga",NULL }; -static const char *oggflac_mime_types[] = { "audio/x-flac+ogg", - "application/ogg", - "application/x-ogg", - NULL }; - -struct decoder_plugin oggflacPlugin = { - .name = "oggflac", - .try_decode = oggflac_try_decode, - .stream_decode = oggflac_decode, - .tag_dup = oggflac_TagDup, - .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, - .suffixes = oggflac_Suffixes, - .mime_types = oggflac_mime_types -}; diff --git a/src/inputPlugins/oggvorbis_plugin.c b/src/inputPlugins/oggvorbis_plugin.c deleted file mode 100644 index 0eecb783f..000000000 --- a/src/inputPlugins/oggvorbis_plugin.c +++ /dev/null @@ -1,387 +0,0 @@ -/* 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 "_ogg_common.h" -#include "../utils.h" -#include "../log.h" - -#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 */ - -#ifdef WORDS_BIGENDIAN -#define OGG_DECODE_USE_BIGENDIAN 1 -#else -#define OGG_DECODE_USE_BIGENDIAN 0 -#endif - -typedef struct _OggCallbackData { - InputStream *inStream; - struct decoder *decoder; -} OggCallbackData; - -static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) -{ - size_t ret; - OggCallbackData *data = (OggCallbackData *) vdata; - - ret = decoder_read(data->decoder, data->inStream, ptr, size * nmemb); - - errno = 0; - /*if(ret<0) errno = ((InputStream *)inStream)->error; */ - - return ret / size; -} - -static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) -{ - const OggCallbackData *data = (const OggCallbackData *) vdata; - if(decoder_get_command(data->decoder) == DECODE_COMMAND_STOP) - return -1; - return seekInputStream(data->inStream, offset, whence); -} - -/* TODO: check Ogg libraries API and see if we can just not have this func */ -static int ogg_close_cb(mpd_unused void *vdata) -{ - return 0; -} - -static long ogg_tell_cb(void *vdata) -{ - const OggCallbackData *data = (const OggCallbackData *) vdata; - - return (long)(data->inStream->offset); -} - -static const char *ogg_parseComment(const char *comment, const 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) -{ - const 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, - struct tag ** 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 = tag_new(); - - tag_add_item(*tag, itemType, comment + len + 1); - - return 1; - } - - return 0; -} - -static struct tag *oggCommentsParse(char **comments) -{ - struct tag *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(char *streamName, - char **comments) -{ - struct tag *tag; - - tag = oggCommentsParse(comments); - if (!tag && streamName) { - tag = tag_new(); - } - if (!tag) - return; - - if (streamName) { - tag_clear_items_by_type(tag, TAG_ITEM_NAME); - tag_add_item(tag, TAG_ITEM_NAME, streamName); - } - - tag_free(tag); -} - -/* public */ -static int oggvorbis_decode(struct decoder * decoder, InputStream * inStream) -{ - OggVorbis_File vf; - ov_callbacks callbacks; - OggCallbackData data; - struct audio_format audio_format; - int current_section; - int prev_section = -1; - 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; - const char *errorStr; - int initialized = 0; - - data.inStream = inStream; - data.decoder = decoder; - - 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) { - if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) - return 0; - - 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; - } - audio_format.bits = 16; - - while (1) { - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { - double seek_where = decoder_seek_where(decoder); - if (0 == ov_time_seek_page(&vf, seek_where)) { - decoder_clear(decoder); - chunkpos = 0; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } - 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); - audio_format.channels = vi->channels; - audio_format.sample_rate = vi->rate; - if (!initialized) { - float total_time = ov_time_total(&vf, -1); - if (total_time < 0) - total_time = 0; - decoder_initialized(decoder, &audio_format, - total_time); - initialized = 1; - } - comments = ov_comment(&vf, -1)->user_comments; - putOggCommentsIntoOutputBuffer(inStream->metaName, - comments); - ogg_getReplayGainInfo(comments, &replayGainInfo); - } - - prev_section = current_section; - - if (ret <= 0) { - if (ret == OV_HOLE) /* bad packet */ - ret = 0; - else /* break on EOF or other error */ - break; - } - - chunkpos += ret; - - if (chunkpos >= OGG_CHUNK_SIZE) { - if ((test = ov_bitrate_instant(&vf)) > 0) { - bitRate = test / 1000; - } - decoder_data(decoder, inStream, - inStream->seekable, - chunk, chunkpos, - ov_pcm_tell(&vf) / audio_format.sample_rate, - bitRate, replayGainInfo); - chunkpos = 0; - if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) - break; - } - } - - if (decoder_get_command(decoder) == DECODE_COMMAND_NONE && - chunkpos > 0) { - decoder_data(decoder, NULL, inStream->seekable, - chunk, chunkpos, - ov_time_tell(&vf), bitRate, - replayGainInfo); - } - - if (replayGainInfo) - freeReplayGainInfo(replayGainInfo); - - ov_clear(&vf); - - decoder_flush(decoder); - - return 0; -} - -static struct tag *oggvorbis_TagDup(char *file) -{ - struct tag *ret; - 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 = tag_new(); - ret->time = (int)(ov_time_total(&vf, -1) + 0.5); - - ov_clear(&vf); - - return ret; -} - -static bool oggvorbis_try_decode(InputStream * inStream) -{ - if (!inStream->seekable) - /* we cannot seek after the detection, so don't bother - checking */ - return true; - - return ogg_stream_type_detect(inStream) == VORBIS; -} - -static const char *oggvorbis_Suffixes[] = { "ogg","oga", NULL }; -static const char *oggvorbis_MimeTypes[] = { "application/ogg", - "audio/x-vorbis+ogg", - "application/x-ogg", - NULL }; - -struct decoder_plugin oggvorbisPlugin = { - .name = "oggvorbis", - .try_decode = oggvorbis_try_decode, - .stream_decode = oggvorbis_decode, - .tag_dup = oggvorbis_TagDup, - .stream_types = INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, - .suffixes = oggvorbis_Suffixes, - .mime_types = oggvorbis_MimeTypes -}; diff --git a/src/inputPlugins/wavpack_plugin.c b/src/inputPlugins/wavpack_plugin.c deleted file mode 100644 index 14b7e5f69..000000000 --- a/src/inputPlugins/wavpack_plugin.c +++ /dev/null @@ -1,574 +0,0 @@ -/* 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 - * - * WavPack support added by Laszlo Ashin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * You should have received a copy of the GNU General Public 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 "../decoder_api.h" -#include "../utils.h" -#include "../log.h" -#include "../path.h" - -#include - -/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ -#define CHUNK_SIZE 1020 - -#define ERRORLEN 80 - -static struct { - const char *name; - int type; -} tagtypes[] = { - { "artist", TAG_ITEM_ARTIST }, - { "album", TAG_ITEM_ALBUM }, - { "title", TAG_ITEM_TITLE }, - { "track", TAG_ITEM_TRACK }, - { "name", TAG_ITEM_NAME }, - { "genre", TAG_ITEM_GENRE }, - { "date", TAG_ITEM_DATE }, - { "composer", TAG_ITEM_COMPOSER }, - { "performer", TAG_ITEM_PERFORMER }, - { "comment", TAG_ITEM_COMMENT }, - { "disc", TAG_ITEM_DISC }, - { NULL, 0 } -}; - -/* - * This function has been borrowed from the tiny player found on - * wavpack.com. Modifications were required because mpd only handles - * max 16 bit samples. - */ -static void format_samples_int(int Bps, void *buffer, uint32_t samcnt) -{ - int32_t temp; - uchar *dst = (uchar *)buffer; - int32_t *src = (int32_t *)buffer; - - switch (Bps) { - case 1: - while (samcnt--) - *dst++ = *src++; - break; - case 2: - while (samcnt--) { - temp = *src++; -#ifdef WORDS_BIGENDIAN - *dst++ = (uchar)(temp >> 8); - *dst++ = (uchar)(temp); -#else - *dst++ = (uchar)(temp); - *dst++ = (uchar)(temp >> 8); -#endif - } - break; - case 3: - /* downscale to 16 bits */ - while (samcnt--) { - temp = *src++; -#ifdef WORDS_BIGENDIAN - *dst++ = (uchar)(temp >> 16); - *dst++ = (uchar)(temp >> 8); -#else - *dst++ = (uchar)(temp >> 8); - *dst++ = (uchar)(temp >> 16); -#endif - } - break; - case 4: - /* downscale to 16 bits */ - while (samcnt--) { - temp = *src++; -#ifdef WORDS_BIGENDIAN - *dst++ = (uchar)(temp >> 24); - *dst++ = (uchar)(temp >> 16); - -#else - *dst++ = (uchar)(temp >> 16); - *dst++ = (uchar)(temp >> 24); -#endif - } - break; - } -} - -/* - * This function converts floating point sample data to 16 bit integer. - */ -static void format_samples_float(mpd_unused int Bps, void *buffer, - uint32_t samcnt) -{ - int16_t *dst = (int16_t *)buffer; - float *src = (float *)buffer; - - while (samcnt--) { - *dst++ = (int16_t)(*src++); - } -} - -/* - * This does the main decoding thing. - * Requires an already opened WavpackContext. - */ -static void wavpack_decode(struct decoder * decoder, - WavpackContext *wpc, int canseek, - ReplayGainInfo *replayGainInfo) -{ - struct audio_format audio_format; - void (*format_samples)(int Bps, void *buffer, uint32_t samcnt); - char chunk[CHUNK_SIZE]; - float file_time; - int samplesreq, samplesgot; - int allsamples; - int position, outsamplesize; - int Bps; - - audio_format.sample_rate = WavpackGetSampleRate(wpc); - audio_format.channels = WavpackGetReducedChannels(wpc); - audio_format.bits = WavpackGetBitsPerSample(wpc); - - if (audio_format.bits > 16) - audio_format.bits = 16; - - if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT) - format_samples = format_samples_float; - else - format_samples = format_samples_int; -/* - if ((WavpackGetMode(wpc) & MODE_WVC) == MODE_WVC) - ERROR("decoding WITH wvc!!!\n"); - else - ERROR("decoding without wvc\n"); -*/ - allsamples = WavpackGetNumSamples(wpc); - Bps = WavpackGetBytesPerSample(wpc); - - outsamplesize = Bps; - if (outsamplesize > 2) - outsamplesize = 2; - outsamplesize *= audio_format.channels; - - samplesreq = sizeof(chunk) / (4 * audio_format.channels); - - decoder_initialized(decoder, &audio_format, - (float)allsamples / audio_format.sample_rate); - - position = 0; - - do { - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { - if (canseek) { - int where; - - decoder_clear(decoder); - - where = decoder_seek_where(decoder) * - audio_format.sample_rate; - if (WavpackSeekSample(wpc, where)) { - position = where; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } else { - decoder_seek_error(decoder); - } - } - - if (decoder_get_command(decoder) == DECODE_COMMAND_STOP) - break; - - samplesgot = WavpackUnpackSamples(wpc, - (int32_t *)chunk, samplesreq); - if (samplesgot > 0) { - int bitrate = (int)(WavpackGetInstantBitrate(wpc) / - 1000 + 0.5); - position += samplesgot; - file_time = (float)position / audio_format.sample_rate; - - format_samples(Bps, chunk, - samplesgot * audio_format.channels); - - decoder_data(decoder, NULL, 0, chunk, - samplesgot * outsamplesize, - file_time, bitrate, - replayGainInfo); - } - } while (samplesgot == samplesreq); - - decoder_flush(decoder); -} - -static char *wavpack_tag(WavpackContext *wpc, char *key) -{ - char *value = NULL; - int size; - - size = WavpackGetTagItem(wpc, key, NULL, 0); - if (size > 0) { - size++; - value = xmalloc(size); - if (!value) - return NULL; - WavpackGetTagItem(wpc, key, value, size); - } - - return value; -} - -static ReplayGainInfo *wavpack_replaygain(WavpackContext *wpc) -{ - static char replaygain_track_gain[] = "replaygain_track_gain"; - static char replaygain_album_gain[] = "replaygain_album_gain"; - static char replaygain_track_peak[] = "replaygain_track_peak"; - static char replaygain_album_peak[] = "replaygain_album_peak"; - ReplayGainInfo *replayGainInfo; - int found = 0; - char *value; - - replayGainInfo = newReplayGainInfo(); - - value = wavpack_tag(wpc, replaygain_track_gain); - if (value) { - replayGainInfo->trackGain = atof(value); - free(value); - found = 1; - } - - value = wavpack_tag(wpc, replaygain_album_gain); - if (value) { - replayGainInfo->albumGain = atof(value); - free(value); - found = 1; - } - - value = wavpack_tag(wpc, replaygain_track_peak); - if (value) { - replayGainInfo->trackPeak = atof(value); - free(value); - found = 1; - } - - value = wavpack_tag(wpc, replaygain_album_peak); - if (value) { - replayGainInfo->albumPeak = atof(value); - free(value); - found = 1; - } - - - if (found) - return replayGainInfo; - - freeReplayGainInfo(replayGainInfo); - - return NULL; -} - -/* - * Reads metainfo from the specified file. - */ -static struct tag *wavpack_tagdup(char *fname) -{ - WavpackContext *wpc; - struct tag *tag; - char error[ERRORLEN]; - char *s; - int ssize; - int i, j; - - wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0); - if (wpc == NULL) { - ERROR("failed to open WavPack file \"%s\": %s\n", fname, error); - return NULL; - } - - tag = tag_new(); - tag->time = - (float)WavpackGetNumSamples(wpc) / WavpackGetSampleRate(wpc); - - ssize = 0; - s = NULL; - - for (i = 0; tagtypes[i].name != NULL; ++i) { - j = WavpackGetTagItem(wpc, tagtypes[i].name, NULL, 0); - if (j > 0) { - ++j; - - if (s == NULL) { - s = xmalloc(j); - if (s == NULL) break; - ssize = j; - } else if (j > ssize) { - char *t = (char *)xrealloc(s, j); - if (t == NULL) break; - ssize = j; - s = t; - } - - WavpackGetTagItem(wpc, tagtypes[i].name, s, j); - tag_add_item(tag, tagtypes[i].type, s); - } - } - - if (s != NULL) - free(s); - - WavpackCloseFile(wpc); - - return tag; -} - -/* - * mpd InputStream <=> WavpackStreamReader wrapper callbacks - */ - -/* This struct is needed for per-stream last_byte storage. */ -typedef struct { - struct decoder *decoder; - InputStream *is; - /* Needed for push_back_byte() */ - int last_byte; -} InputStreamPlus; - -static int32_t read_bytes(void *id, void *data, int32_t bcount) -{ - InputStreamPlus *isp = (InputStreamPlus *)id; - uint8_t *buf = (uint8_t *)data; - int32_t i = 0; - - if (isp->last_byte != EOF) { - *buf++ = isp->last_byte; - isp->last_byte = EOF; - --bcount; - ++i; - } - return i + decoder_read(isp->decoder, isp->is, buf, bcount); -} - -static uint32_t get_pos(void *id) -{ - return ((InputStreamPlus *)id)->is->offset; -} - -static int set_pos_abs(void *id, uint32_t pos) -{ - return seekInputStream(((InputStreamPlus *)id)->is, pos, SEEK_SET); -} - -static int set_pos_rel(void *id, int32_t delta, int mode) -{ - return seekInputStream(((InputStreamPlus *)id)->is, delta, mode); -} - -static int push_back_byte(void *id, int c) -{ - ((InputStreamPlus *)id)->last_byte = c; - return 1; -} - -static uint32_t get_length(void *id) -{ - return ((InputStreamPlus *)id)->is->size; -} - -static int can_seek(void *id) -{ - return ((InputStreamPlus *)id)->is->seekable; -} - -static WavpackStreamReader mpd_is_reader = { - .read_bytes = read_bytes, - .get_pos = get_pos, - .set_pos_abs = set_pos_abs, - .set_pos_rel = set_pos_rel, - .push_back_byte = push_back_byte, - .get_length = get_length, - .can_seek = can_seek, - .write_bytes = NULL /* no need to write edited tags */ -}; - -static void -initInputStreamPlus(InputStreamPlus *isp, struct decoder *decoder, - InputStream *is) -{ - isp->decoder = decoder; - isp->is = is; - isp->last_byte = EOF; -} - -/* - * Tries to decode the specified stream, and gives true if managed to do it. - */ -static bool wavpack_trydecode(InputStream *is) -{ - char error[ERRORLEN]; - WavpackContext *wpc; - InputStreamPlus isp; - - initInputStreamPlus(&isp, NULL, is); - wpc = WavpackOpenFileInputEx(&mpd_is_reader, &isp, NULL, error, - OPEN_STREAMING, 0); - if (wpc == NULL) - return false; - - WavpackCloseFile(wpc); - /* Seek it back in order to play from the first byte. */ - seekInputStream(is, 0, SEEK_SET); - - return true; -} - -static int wavpack_open_wvc(struct decoder *decoder, - InputStream *is_wvc) -{ - char tmp[MPD_PATH_MAX]; - const char *utf8url; - size_t len; - char *wvc_url = NULL; - int ret; - - /* - * As we use dc->utf8url, this function will be bad for - * single files. utf8url is not absolute file path :/ - */ - utf8url = decoder_get_url(decoder, tmp); - if (utf8url == NULL) - return 0; - - len = strlen(utf8url); - if (!len) - return 0; - - wvc_url = (char *)xmalloc(len + 2); /* +2: 'c' and EOS */ - if (wvc_url == NULL) - return 0; - - memcpy(wvc_url, utf8url, len); - wvc_url[len] = 'c'; - wvc_url[len + 1] = '\0'; - - ret = openInputStream(is_wvc, wvc_url); - free(wvc_url); - - if (ret) - return 0; - - /* - * And we try to buffer in order to get know - * about a possible 404 error. - */ - for (;;) { - if (inputStreamAtEOF(is_wvc)) { - /* - * EOF is reached even without - * a single byte is read... - * So, this is not good :/ - */ - closeInputStream(is_wvc); - return 0; - } - - if (bufferInputStream(is_wvc) >= 0) - return 1; - - if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) { - closeInputStream(is_wvc); - return 0; - } - - /* Save some CPU */ - my_usleep(1000); - } -} - -/* - * Decodes a stream. - */ -static int wavpack_streamdecode(struct decoder * decoder, InputStream *is) -{ - char error[ERRORLEN]; - WavpackContext *wpc; - InputStream is_wvc; - int open_flags = OPEN_2CH_MAX | OPEN_NORMALIZE /*| OPEN_STREAMING*/; - InputStreamPlus isp, isp_wvc; - - if (wavpack_open_wvc(decoder, &is_wvc)) { - initInputStreamPlus(&isp_wvc, decoder, &is_wvc); - open_flags |= OPEN_WVC; - } - - initInputStreamPlus(&isp, decoder, is); - wpc = WavpackOpenFileInputEx(&mpd_is_reader, &isp, &isp_wvc, error, - open_flags, 15); - - if (wpc == NULL) { - ERROR("failed to open WavPack stream: %s\n", error); - return -1; - } - - wavpack_decode(decoder, wpc, can_seek(&isp), NULL); - - WavpackCloseFile(wpc); - if (open_flags & OPEN_WVC) - closeInputStream(&is_wvc); - closeInputStream(is); - - return 0; -} - -/* - * Decodes a file. - */ -static int wavpack_filedecode(struct decoder * decoder, char *fname) -{ - char error[ERRORLEN]; - WavpackContext *wpc; - ReplayGainInfo *replayGainInfo; - - wpc = WavpackOpenFileInput(fname, error, - OPEN_TAGS | OPEN_WVC | - OPEN_2CH_MAX | OPEN_NORMALIZE, 15); - if (wpc == NULL) { - ERROR("failed to open WavPack file \"%s\": %s\n", fname, error); - return -1; - } - - replayGainInfo = wavpack_replaygain(wpc); - - wavpack_decode(decoder, wpc, 1, replayGainInfo); - - if (replayGainInfo) - freeReplayGainInfo(replayGainInfo); - - WavpackCloseFile(wpc); - - return 0; -} - -static char const *wavpackSuffixes[] = { "wv", NULL }; -static char const *wavpackMimeTypes[] = { "audio/x-wavpack", NULL }; - -struct decoder_plugin wavpackPlugin = { - .name = "wavpack", - .try_decode = wavpack_trydecode, - .stream_decode = wavpack_streamdecode, - .file_decode = wavpack_filedecode, - .tag_dup = wavpack_tagdup, - .stream_types = INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, - .suffixes = wavpackSuffixes, - .mime_types = wavpackMimeTypes -}; -- cgit v1.2.3