/* 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 <kodest@gmail.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,
ReplayGainInfo *replayGainInfo)
{
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, replayGainInfo);
}
} while (samplesgot == samplesreq);
flushOutputBuffer(cb);
dc->state = DECODE_STATE_STOP;
dc->stop = 0;
}
/*
* These functions aren't currently used, which just results in warnings.
*/
#if 0
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)
{
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;
}
#endif
/*
* 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), NULL);
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;
ReplayGainInfo *replayGainInfo;
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;
}
/*
* ReplayGain support is currently disabled, because WavpackGetTagItem
* can't seem to find the replaygain_* fields in APEv2 tags.
*/
/* replayGainInfo = wavpack_replaygain(wpc); */
replayGainInfo = NULL;
wavpack_decode(cb, dc, wpc, 1, replayGainInfo);
if (replayGainInfo)
freeReplayGainInfo(replayGainInfo);
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 */