diff options
Diffstat (limited to 'src/inputPlugins')
-rw-r--r-- | src/inputPlugins/wavpack_plugin.c | 422 |
1 files changed, 422 insertions, 0 deletions
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 <kodest at gmail dot com> + * + * 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 <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <wavpack/wavpack.h> +#include <math.h> + +#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 */ |