/* 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 "../inputPlugin.h" #ifdef HAVE_WAVPACK #include "../utils.h" #include "../log.h" #include "../path.h" #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 } }; /* * 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(WavpackContext *wpc, int canseek, ReplayGainInfo *replayGainInfo) { 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; dc.audio_format.sampleRate = WavpackGetSampleRate(wpc); dc.audio_format.channels = WavpackGetReducedChannels(wpc); dc.audio_format.bits = WavpackGetBitsPerSample(wpc); if (dc.audio_format.bits > 16) dc.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 *= dc.audio_format.channels; samplesreq = sizeof(chunk) / (4 * dc.audio_format.channels); dc.total_time = (float)allsamples / dc.audio_format.sampleRate; position = 0; do { if (dc_seek()) { dc_action_begin(); assert(dc.action == DC_ACTION_SEEK); if (canseek) { int where; where = dc.seek_where * dc.audio_format.sampleRate; if (WavpackSeekSample(wpc, where)) position = where; else dc.seek_where = DC_SEEK_ERROR; } else { dc.seek_where = DC_SEEK_ERROR; } dc_action_end(); } if (dc_intr()) 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 / dc.audio_format.sampleRate; format_samples(Bps, chunk, samplesgot * dc.audio_format.channels); ob_send(chunk, samplesgot * outsamplesize, file_time, bitrate, replayGainInfo); } } while (samplesgot == samplesreq); } 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 mpd_tag *wavpack_tagdup(char *fname) { WavpackContext *wpc; struct mpd_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 { 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 + readFromInputStream(isp->is, buf, 1, 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; static int wavpack_init(void) { mpd_is_reader.read_bytes = read_bytes; mpd_is_reader.get_pos = get_pos; mpd_is_reader.set_pos_abs = set_pos_abs; mpd_is_reader.set_pos_rel = set_pos_rel; mpd_is_reader.push_back_byte = push_back_byte; mpd_is_reader.get_length = get_length; mpd_is_reader.can_seek = can_seek; /* mpd_is_reader.write_bytes = NULL; no need to write edited tags */ return 0; } static void initInputStreamPlus(InputStreamPlus *isp, InputStream *is) { isp->is = is; isp->last_byte = EOF; } /* * 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; InputStreamPlus isp; initInputStreamPlus(&isp, is); wpc = WavpackOpenFileInputEx(&mpd_is_reader, &isp, 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; } /* wvc being the "correction" file to supplement the original .wv */ static int wavpack_open_wvc(InputStream *is_wvc) { char wvc_url[MPD_PATH_MAX]; size_t len; /* This is the only reader of dc.utf8url (in inputPlugins) */ pathcpy_trunc(wvc_url, dc.utf8url); len = strlen(wvc_url); if ((len + 2) >= MPD_PATH_MAX) return 0; /* convert the original ".wv" path to a ".wvc" path */ assert(wvc_url[len - 3] == '.'); assert(wvc_url[len - 2] == 'w' || wvc_url[len - 2] == 'w'); assert(wvc_url[len - 1] == 'v' || wvc_url[len - 1] == 'V'); assert(wvc_url[len] == '\0'); wvc_url[len] = 'c'; wvc_url[len + 1] = '\0'; if (openInputStream(is_wvc, wvc_url) < 0) { /* lowercase 'c' didn't work, maybe uppercase... */ wvc_url[len] = 'C'; if (openInputStream(is_wvc, wvc_url) < 0) 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 :/ */ break; /* FIXME: replace with future "peek" function */ if (bufferInputStream(is_wvc) >= 0) { DEBUG("wavpack: got wvc file: %s\n", wvc_url); return 1; /* success */ } if (dc_intr()) break; /* Save some CPU */ my_usleep(1000); /* FIXME: remove */ } closeInputStream(is_wvc); return 0; } /* * Decodes a stream. */ static int wavpack_streamdecode(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(&is_wvc)) { initInputStreamPlus(&isp_wvc, &is_wvc); open_flags |= OPEN_WVC; } initInputStreamPlus(&isp, 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); if (open_flags & OPEN_WVC) closeInputStream(&is_wvc); return -1; } wavpack_decode(wpc, can_seek(&isp), NULL); WavpackCloseFile(wpc); if (open_flags & OPEN_WVC) closeInputStream(&is_wvc); return 0; } /* * Decodes a file. */ static int wavpack_filedecode(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(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 }; InputPlugin wavpackPlugin = { "wavpack", wavpack_init, NULL, wavpack_trydecode, wavpack_streamdecode, wavpack_filedecode, wavpack_tagdup, INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, wavpackSuffixes, wavpackMimeTypes }; #else /* !HAVE_WAVPACK */ InputPlugin wavpackPlugin; #endif /* !HAVE_WAVPACK */