diff --git a/AUTHORS b/AUTHORS index 3a801f150..00df1082f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,6 +24,9 @@ Qball Cow Patrik Weiskircher Stored playlist commands +Kodest + WavPack support + Former Developers ----------------- tw-nym diff --git a/ChangeLog b/ChangeLog index b70bd4ef7..2e629cf01 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,7 @@ ver 0.14.0 (????/??/??) * Make the shout output block while trying to connect instead of failing * New timeout parameter for shout outputs to define a connection timeout * New FIFO audio output +* Support for WavPack files ver 0.13.0 (2007/5/28) * New JACK audio output diff --git a/configure.ac b/configure.ac index 204943c6f..297247735 100644 --- a/configure.ac +++ b/configure.ac @@ -79,6 +79,7 @@ AC_ARG_ENABLE(aac,[ --disable-aac disable AAC support (default: enabl AC_ARG_ENABLE(audiofile,[ --disable-audiofile disable audiofile support, disables wave support (default: enable)],[enable_audiofile=$enableval],[enable_audiofile=yes]) AC_ARG_ENABLE(mod,[ --enable-mod enable MOD support (default: disable],[enable_mod=$enableval],[enable_mod=yes]) AC_ARG_ENABLE(mpc,[ --disable-mpc disable musepack (MPC) support (default: enable)],[enable_mpc=$enableval],[enable_mpc=yes]) +AC_ARG_ENABLE(wavpack,[ --disable-wavpack disable wavpack support (default: enable)],[enable_wavpack=$enableval],[enable_wavpack=yes]) AC_ARG_ENABLE(id3,[ --disable-id3 disable id3 support (default: enable)],[enable_id3=$enableval],[enable_id3=yes]) AC_ARG_ENABLE(lsr,[ --disable-lsr disable libsamplerate support (default: enable)],[enable_lsr=$enableval],[enable_lsr=yes]) @@ -182,7 +183,8 @@ if test x$enable_oss = xyes; then fi if test x$enable_pulse = xyes || test x$enable_jack = xyes || - test x$enable_lsr = xyes || test x$with_zeroconf != xno; then + test x$enable_lsr = xyes || test x$with_zeroconf != xno || + test x$enable_wavpack = xyes; then PKG_PROG_PKG_CONFIG fi @@ -368,6 +370,12 @@ if test x$enable_mpc = xyes; then CPPFLAGS=$oldcppflags fi +if test x$enable_wavpack = xyes; then + PKG_CHECK_MODULES([WAVPACK], [wavpack], + [enable_wavpack=yes;AC_DEFINE([HAVE_WAVPACK], 1, [Define to enable WavPack support])] MPD_LIBS="$MPD_LIBS $WAVPACK_LIBS" MPD_CFLAGS="$MPD_CFLAGS $WAVPACK_CFLAGS", + [enable_wavpack=no;AC_MSG_WARN([WavPack not found -- disabling])]) +fi + MP4FF_SUBDIR="" if test x$enable_aac = xyes; then @@ -764,6 +772,12 @@ else echo " Musepack (MPC) support ........disabled" fi +if test x$enable_wavpack = xyes; then + echo " WavPack support ...............enabled" +else + echo " WavPack support ...............disabled" +fi + if test x$enable_mod = xyes; then echo " MOD support ...................enabled" else @@ -778,6 +792,7 @@ if test x$enable_audiofile = xno && test x$enable_aac = xno && test x$enable_mpc = xno && + test x$enable_wavpack = xno && test x$enable_mod = xno; then AC_MSG_ERROR([No input plugins supported!]) fi diff --git a/src/Makefile.am b/src/Makefile.am index 88733be26..cf56ba1fb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,7 +24,8 @@ mpd_inputPlugins = \ inputPlugins/mod_plugin.c \ inputPlugins/mp3_plugin.c \ inputPlugins/mp4_plugin.c \ - inputPlugins/mpc_plugin.c + inputPlugins/mpc_plugin.c \ + inputPlugins/wavpack_plugin.c mpd_headers = \ diff --git a/src/inputPlugin.c b/src/inputPlugin.c index 3ecb4f175..ec5624bf2 100644 --- a/src/inputPlugin.c +++ b/src/inputPlugin.c @@ -150,6 +150,7 @@ void initInputPlugins(void) loadInputPlugin(&mp4Plugin); loadInputPlugin(&aacPlugin); loadInputPlugin(&mpcPlugin); + loadInputPlugin(&wavpackPlugin); loadInputPlugin(&modPlugin); } diff --git a/src/inputPlugin.h b/src/inputPlugin.h index acb1bd0f9..6e9b21106 100644 --- a/src/inputPlugin.h +++ b/src/inputPlugin.h @@ -104,6 +104,7 @@ extern InputPlugin audiofilePlugin; extern InputPlugin mp4Plugin; extern InputPlugin aacPlugin; extern InputPlugin mpcPlugin; +extern InputPlugin wavpackPlugin; extern InputPlugin modPlugin; #endif diff --git a/src/inputPlugins/wavpack_plugin.c b/src/inputPlugins/wavpack_plugin.c new file mode 100644 index 000000000..e27fbde4f --- /dev/null +++ b/src/inputPlugins/wavpack_plugin.c @@ -0,0 +1,422 @@ +/* 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 Kodest + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "../inputPlugin.h" + +#ifdef HAVE_WAVPACK + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../playerData.h" +#include "../outputBuffer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 } +}; + +/* workaround for at least the last push_back_byte */ +static int last_byte = EOF; + +/* + * 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--) { + *dst++ = (uchar)(temp = *src++); + *dst++ = (uchar)(temp >> 8); + } + break; + case 3: + /* downscale to 16 bits */ + while (samcnt--) { + temp = *src++; + *dst++ = (uchar)(temp >> 8); + *dst++ = (uchar)(temp >> 16); + } + break; + case 4: + /* downscale to 16 bits */ + while (samcnt--) { + temp = *src++; + *dst++ = (uchar)(temp >> 16); + *dst++ = (uchar)(temp >> 24); + } + break; + } +} + +/* + * This function converts floating point sample data to 16 bit integer. + */ +static void format_samples_float(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(OutputBuffer *cb, DecoderControl *dc, + WavpackContext *wpc, int canseek) +{ + void (*format_samples)(int Bps, void *buffer, uint32_t samcnt); + char chunk[CHUNK_SIZE]; + float time; + int samplesreq, samplesgot; + int allsamples; + int position, outsamplesize; + int Bps; + + dc->audioFormat.sampleRate = WavpackGetSampleRate(wpc); + dc->audioFormat.channels = WavpackGetReducedChannels(wpc); + dc->audioFormat.bits = WavpackGetBitsPerSample(wpc); + + if (dc->audioFormat.bits > 16) + dc->audioFormat.bits = 16; + + if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT) + format_samples = format_samples_float; + else + format_samples = format_samples_int; + + allsamples = WavpackGetNumSamples(wpc); + Bps = WavpackGetBytesPerSample(wpc); + + outsamplesize = Bps; + if (outsamplesize > 2) + outsamplesize = 2; + outsamplesize *= dc->audioFormat.channels; + + samplesreq = sizeof(chunk) / (4 * dc->audioFormat.channels); + + getOutputAudioFormat(&(dc->audioFormat), &(cb->audioFormat)); + + dc->totalTime = (float)allsamples / dc->audioFormat.sampleRate; + dc->state = DECODE_STATE_DECODE; + + position = 0; + + do { + if (dc->seek) { + if (canseek) { + int where; + + clearOutputBuffer(cb); + + where = dc->seekWhere * + dc->audioFormat.sampleRate; + if (WavpackSeekSample(wpc, where)) + position = where; + else + dc->seekError = 1; + } else { + dc->seekError = 1; + } + + dc->seek = 0; + } + + if (dc->stop) + break; + + samplesgot = WavpackUnpackSamples(wpc, + (int32_t *)chunk, samplesreq); + if (samplesgot > 0) { + int bitrate = (int)(WavpackGetInstantBitrate(wpc) / + 1000 + 0.5); + position += samplesgot; + time = (float)position / dc->audioFormat.sampleRate; + + format_samples(Bps, chunk, + samplesgot * dc->audioFormat.channels); + + sendDataToOutputBuffer(cb, NULL, dc, 0, chunk, + samplesgot * outsamplesize, + time, bitrate, NULL); + } + } while (samplesgot == samplesreq); + + flushOutputBuffer(cb); + + dc->state = DECODE_STATE_STOP; + dc->stop = 0; +} + +/* + * Reads metainfo from the specified file. + */ +static MpdTag *wavpack_tagdup(char *fname) +{ + WavpackContext *wpc; + MpdTag *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 = newMpdTag(); + if (tag == NULL) { + ERROR("failed to newMpdTag()\n"); + return NULL; + } + + 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); + addItemToMpdTag(tag, tagtypes[i].type, s); + } + } + + if (s != NULL) + free(s); + + WavpackCloseFile(wpc); + + return tag; +} + +/* + * mpd InputStream <=> WavpackStreamReader wrapper callbacks + */ + +static int32_t read_bytes(void *id, void *data, int32_t bcount) +{ + uint8_t *buf = (uint8_t *)data; + int32_t i = 0; + + if (last_byte != EOF) { + *buf++ = last_byte; + last_byte = EOF; + --bcount; + ++i; + } + return i + readFromInputStream((InputStream *)id, buf, 1, bcount); +} + +static uint32_t get_pos(void *id) +{ + return ((InputStream *)id)->offset; +} + +static int set_pos_abs(void *id, uint32_t pos) +{ + return seekInputStream((InputStream *)id, pos, SEEK_SET); +} + +static int set_pos_rel(void *id, int32_t delta, int mode) +{ + return seekInputStream((InputStream *)id, delta, mode); +} + +static int push_back_byte(void *id, int c) +{ + last_byte = c; + return 1; +} + +static uint32_t get_length(void *id) +{ + return ((InputStream *)id)->size; +} + +static int can_seek(void *id) +{ + return (seekInputStream((InputStream *)id, 0, SEEK_SET) != -1); +} + +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 */ +}; + +/* + * Tries to decode the specified stream, and gives true if managed to do it. + */ +static unsigned int wavpack_trydecode(InputStream *is) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + + wpc = WavpackOpenFileInputEx(&mpd_is_reader, (void *)is, NULL, error, + OPEN_STREAMING, 0); + if (wpc == NULL) + return 0; + + WavpackCloseFile(wpc); + /* Seek it back in order to play from the first byte. */ + seekInputStream(is, 0, SEEK_SET); + + return 1; +} + +/* + * Decodes a stream. + * We cannot handle wvc files this way, use the wavpack_filedecode for that. + */ +static int wavpack_streamdecode(OutputBuffer *cb, DecoderControl *dc, + InputStream *is) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + + /* + * wavpack_streamdecode is unable to use wvc :-( + * If we know the original stream url, we would find out the wvc url... + * This would require InputStream to store that. + */ + + wpc = WavpackOpenFileInputEx(&mpd_is_reader, (void *)is, NULL, error, + OPEN_2CH_MAX | OPEN_NORMALIZE, 15); + if (wpc == NULL) { + ERROR("failed to open WavPack stream: %s\n", error); + return -1; + } + + wavpack_decode(cb, dc, wpc, can_seek(is)); + + WavpackCloseFile(wpc); + closeInputStream(is); /* calling side doesn't do this in mpd 0.13.0 */ + + return 0; +} + +/* + * Decodes a file. This has the goods on wavpack_streamdecode that this + * can handle wvc files. + */ +static int wavpack_filedecode(OutputBuffer *cb, DecoderControl *dc, char *fname) +{ + char error[ERRORLEN]; + WavpackContext *wpc; + + wpc = WavpackOpenFileInput(fname, error, + OPEN_WVC | OPEN_2CH_MAX | OPEN_NORMALIZE, + 15); + if (wpc == NULL) { + ERROR("failed to open WavPack file \"%s\": %s\n", fname, error); + return -1; + } + + wavpack_decode(cb, dc, wpc, 1); + + WavpackCloseFile(wpc); + + return 0; +} + +static char *wavpackSuffixes[] = { "wv", NULL }; +static char *wavpackMimeTypes[] = { "audio/x-wavpack", NULL }; + +InputPlugin wavpackPlugin = { + "wavpack", + NULL, + NULL, + wavpack_trydecode, + wavpack_streamdecode, + wavpack_filedecode, /* provides more functionality! (wvc) */ + wavpack_tagdup, + INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, + wavpackSuffixes, + wavpackMimeTypes +}; + +#else /* !HAVE_WAVPACK */ + +InputPlugin wavpackPlugin; + +#endif /* !HAVE_WAVPACK */