Initial revision 6759
[qball-mpd.git] / src / inputPlugins / wavpack_plugin.c
blob1d056dcd77ff29cf1e2eb6ef86bdf8ac6b5c5f99
1 /* the Music Player Daemon (MPD)
2 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
3 * This project's homepage is: http://www.musicpd.org
4 *
5 * WavPack support added by Laszlo Ashin <kodest@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 #include "../inputPlugin.h"
23 #ifdef HAVE_WAVPACK
25 #include "../utils.h"
26 #include "../audio.h"
27 #include "../log.h"
28 #include "../pcm_utils.h"
29 #include "../playerData.h"
30 #include "../outputBuffer.h"
32 #include <stdio.h>
33 #include <unistd.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <wavpack/wavpack.h>
39 #include <math.h>
41 #define ERRORLEN 80
43 static struct {
44 const char *name;
45 int type;
46 } tagtypes[] = {
47 { "artist", TAG_ITEM_ARTIST },
48 { "album", TAG_ITEM_ALBUM },
49 { "title", TAG_ITEM_TITLE },
50 { "track", TAG_ITEM_TRACK },
51 { "name", TAG_ITEM_NAME },
52 { "genre", TAG_ITEM_GENRE },
53 { "date", TAG_ITEM_DATE },
54 { "composer", TAG_ITEM_COMPOSER },
55 { "performer", TAG_ITEM_PERFORMER },
56 { "comment", TAG_ITEM_COMMENT },
57 { "disc", TAG_ITEM_DISC },
58 { NULL, 0 }
62 * This function has been borrowed from the tiny player found on
63 * wavpack.com. Modifications were required because mpd only handles
64 * max 16 bit samples.
66 static void format_samples_int(int Bps, void *buffer, uint32_t samcnt)
68 int32_t temp;
69 uchar *dst = (uchar *)buffer;
70 int32_t *src = (int32_t *)buffer;
72 switch (Bps) {
73 case 1:
74 while (samcnt--)
75 *dst++ = *src++;
76 break;
77 case 2:
78 while (samcnt--) {
79 *dst++ = (uchar)(temp = *src++);
80 *dst++ = (uchar)(temp >> 8);
82 break;
83 case 3:
84 /* downscale to 16 bits */
85 while (samcnt--) {
86 temp = *src++;
87 *dst++ = (uchar)(temp >> 8);
88 *dst++ = (uchar)(temp >> 16);
90 break;
91 case 4:
92 /* downscale to 16 bits */
93 while (samcnt--) {
94 temp = *src++;
95 *dst++ = (uchar)(temp >> 16);
96 *dst++ = (uchar)(temp >> 24);
98 break;
103 * This function converts floating point sample data to 16 bit integer.
105 static void format_samples_float(int Bps, void *buffer, uint32_t samcnt)
107 int16_t *dst = (int16_t *)buffer;
108 float *src = (float *)buffer;
110 while (samcnt--) {
111 *dst++ = (int16_t)(*src++);
116 * This does the main decoding thing.
117 * Requires an already opened WavpackContext.
119 static void wavpack_decode(OutputBuffer *cb, DecoderControl *dc,
120 WavpackContext *wpc, int canseek,
121 ReplayGainInfo *replayGainInfo)
123 void (*format_samples)(int Bps, void *buffer, uint32_t samcnt);
124 char chunk[CHUNK_SIZE];
125 float time;
126 int samplesreq, samplesgot;
127 int allsamples;
128 int position, outsamplesize;
129 int Bps;
131 dc->audioFormat.sampleRate = WavpackGetSampleRate(wpc);
132 dc->audioFormat.channels = WavpackGetReducedChannels(wpc);
133 dc->audioFormat.bits = WavpackGetBitsPerSample(wpc);
135 if (dc->audioFormat.bits > 16)
136 dc->audioFormat.bits = 16;
138 if ((WavpackGetMode(wpc) & MODE_FLOAT) == MODE_FLOAT)
139 format_samples = format_samples_float;
140 else
141 format_samples = format_samples_int;
143 if ((WavpackGetMode(wpc) & MODE_WVC) == MODE_WVC)
144 ERROR("decoding WITH wvc!!!\n");
145 else
146 ERROR("decoding without wvc\n");
148 allsamples = WavpackGetNumSamples(wpc);
149 Bps = WavpackGetBytesPerSample(wpc);
151 outsamplesize = Bps;
152 if (outsamplesize > 2)
153 outsamplesize = 2;
154 outsamplesize *= dc->audioFormat.channels;
156 samplesreq = sizeof(chunk) / (4 * dc->audioFormat.channels);
158 getOutputAudioFormat(&(dc->audioFormat), &(cb->audioFormat));
160 dc->totalTime = (float)allsamples / dc->audioFormat.sampleRate;
161 dc->state = DECODE_STATE_DECODE;
162 dc->seekable = canseek;
164 position = 0;
166 do {
167 if (dc->seek) {
168 if (canseek) {
169 int where;
171 clearOutputBuffer(cb);
173 where = dc->seekWhere *
174 dc->audioFormat.sampleRate;
175 if (WavpackSeekSample(wpc, where))
176 position = where;
177 else
178 dc->seekError = 1;
179 } else {
180 dc->seekError = 1;
183 dc->seek = 0;
186 if (dc->stop)
187 break;
189 samplesgot = WavpackUnpackSamples(wpc,
190 (int32_t *)chunk, samplesreq);
191 if (samplesgot > 0) {
192 int bitrate = (int)(WavpackGetInstantBitrate(wpc) /
193 1000 + 0.5);
194 position += samplesgot;
195 time = (float)position / dc->audioFormat.sampleRate;
197 format_samples(Bps, chunk,
198 samplesgot * dc->audioFormat.channels);
200 sendDataToOutputBuffer(cb, NULL, dc, 0, chunk,
201 samplesgot * outsamplesize,
202 time, bitrate, replayGainInfo);
204 } while (samplesgot == samplesreq);
206 flushOutputBuffer(cb);
208 dc->state = DECODE_STATE_STOP;
209 dc->stop = 0;
212 static char *wavpack_tag(WavpackContext *wpc, char *key)
214 char *value = NULL;
215 int size;
217 size = WavpackGetTagItem(wpc, key, NULL, 0);
218 if (size > 0) {
219 size++;
220 value = xmalloc(size);
221 if (!value)
222 return NULL;
223 WavpackGetTagItem(wpc, key, value, size);
226 return value;
229 static ReplayGainInfo *wavpack_replaygain(WavpackContext *wpc)
231 ReplayGainInfo *replayGainInfo;
232 int found = 0;
233 char *value;
235 replayGainInfo = newReplayGainInfo();
237 value = wavpack_tag(wpc, "replaygain_track_gain");
238 if (value) {
239 replayGainInfo->trackGain = atof(value);
240 free(value);
241 found = 1;
244 value = wavpack_tag(wpc, "replaygain_album_gain");
245 if (value) {
246 replayGainInfo->albumGain = atof(value);
247 free(value);
248 found = 1;
251 value = wavpack_tag(wpc, "replaygain_track_peak");
252 if (value) {
253 replayGainInfo->trackPeak = atof(value);
254 free(value);
255 found = 1;
258 value = wavpack_tag(wpc, "replaygain_album_peak");
259 if (value) {
260 replayGainInfo->albumPeak = atof(value);
261 free(value);
262 found = 1;
266 if (found)
267 return replayGainInfo;
269 freeReplayGainInfo(replayGainInfo);
271 return NULL;
275 * Reads metainfo from the specified file.
277 static MpdTag *wavpack_tagdup(char *fname)
279 WavpackContext *wpc;
280 MpdTag *tag;
281 char error[ERRORLEN];
282 char *s;
283 int ssize;
284 int i, j;
286 wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0);
287 if (wpc == NULL) {
288 ERROR("failed to open WavPack file \"%s\": %s\n", fname, error);
289 return NULL;
292 tag = newMpdTag();
293 if (tag == NULL) {
294 ERROR("failed to newMpdTag()\n");
295 return NULL;
298 tag->time =
299 (float)WavpackGetNumSamples(wpc) / WavpackGetSampleRate(wpc);
301 ssize = 0;
302 s = NULL;
304 for (i = 0; tagtypes[i].name != NULL; ++i) {
305 j = WavpackGetTagItem(wpc, tagtypes[i].name, NULL, 0);
306 if (j > 0) {
307 ++j;
309 if (s == NULL) {
310 s = xmalloc(j);
311 if (s == NULL) break;
312 ssize = j;
313 } else if (j > ssize) {
314 char *t = (char *)xrealloc(s, j);
315 if (t == NULL) break;
316 ssize = j;
317 s = t;
320 WavpackGetTagItem(wpc, tagtypes[i].name, s, j);
321 addItemToMpdTag(tag, tagtypes[i].type, s);
325 if (s != NULL)
326 free(s);
328 WavpackCloseFile(wpc);
330 return tag;
334 * mpd InputStream <=> WavpackStreamReader wrapper callbacks
337 /* This struct is needed for per-stream last_byte storage. */
338 typedef struct {
339 InputStream *is;
340 /* Needed for push_back_byte() */
341 int last_byte;
342 } InputStreamPlus;
344 static int32_t read_bytes(void *id, void *data, int32_t bcount)
346 InputStreamPlus *isp = (InputStreamPlus *)id;
347 uint8_t *buf = (uint8_t *)data;
348 int32_t i = 0;
350 if (isp->last_byte != EOF) {
351 *buf++ = isp->last_byte;
352 isp->last_byte = EOF;
353 --bcount;
354 ++i;
356 return i + readFromInputStream(isp->is, buf, 1, bcount);
359 static uint32_t get_pos(void *id)
361 return ((InputStreamPlus *)id)->is->offset;
364 static int set_pos_abs(void *id, uint32_t pos)
366 return seekInputStream(((InputStreamPlus *)id)->is, pos, SEEK_SET);
369 static int set_pos_rel(void *id, int32_t delta, int mode)
371 return seekInputStream(((InputStreamPlus *)id)->is, delta, mode);
374 static int push_back_byte(void *id, int c)
376 ((InputStreamPlus *)id)->last_byte = c;
377 return 1;
380 static uint32_t get_length(void *id)
382 return ((InputStreamPlus *)id)->is->size;
385 static int can_seek(void *id)
387 return ((InputStreamPlus *)id)->is->seekable;
390 static WavpackStreamReader mpd_is_reader = {
391 .read_bytes = read_bytes,
392 .get_pos = get_pos,
393 .set_pos_abs = set_pos_abs,
394 .set_pos_rel = set_pos_rel,
395 .push_back_byte = push_back_byte,
396 .get_length = get_length,
397 .can_seek = can_seek,
398 .write_bytes = NULL /* no need to write edited tags */
401 static void
402 initInputStreamPlus(InputStreamPlus *isp, InputStream *is)
404 isp->is = is;
405 isp->last_byte = EOF;
409 * Tries to decode the specified stream, and gives true if managed to do it.
411 static unsigned int wavpack_trydecode(InputStream *is)
413 char error[ERRORLEN];
414 WavpackContext *wpc;
415 InputStreamPlus isp;
417 initInputStreamPlus(&isp, is);
418 wpc = WavpackOpenFileInputEx(&mpd_is_reader, &isp, NULL, error,
419 OPEN_STREAMING, 0);
420 if (wpc == NULL)
421 return 0;
423 WavpackCloseFile(wpc);
424 /* Seek it back in order to play from the first byte. */
425 if (is->seekable)
426 seekInputStream(is, 0, SEEK_SET);
428 return 1;
432 * Decodes a stream.
434 static int wavpack_streamdecode(OutputBuffer *cb, DecoderControl *dc,
435 InputStream *is)
437 char error[ERRORLEN];
438 WavpackContext *wpc;
439 InputStream is_wvc;
440 int open_flags = OPEN_2CH_MAX | OPEN_NORMALIZE;
441 char *wvc_url = NULL;
442 int err;
443 InputStreamPlus isp, isp_wvc;
444 int canseek;
446 /* Try to find wvc */
447 do {
448 size_t len;
449 err = 1;
452 * As we use dc->utf8url, this function will be bad for
453 * single files. utf8url is not absolute file path :\
455 if (dc->utf8url == NULL)
456 break;
458 len = strlen(dc->utf8url);
459 if (!len)
460 break;
462 wvc_url = (char *)xmalloc(len + 2);
463 if (wvc_url == NULL)
464 break;
466 strncpy(wvc_url, dc->utf8url, len);
467 wvc_url[len] = 'c';
468 wvc_url[len + 1] = '\0';
470 if (openInputStream(&is_wvc, wvc_url))
471 break;
472 #if 0 /* this code is moved to http stuff */
474 * And we try to buffer in order to get know
475 * about a possible 404.
477 for (;;) {
478 if (inputStreamAtEOF(&is_wvc))
480 * EOF is reached even without
481 * reading a single byte...
482 * So, this is not good :\
484 break;
486 if (bufferInputStream(&is_wvc) >= 0) {
487 err = 0;
488 break;
491 /* Can this happen?: */
492 if (dc->stop)
493 break;
495 /* Save some CPU */
496 my_usleep(1000);
499 if (err) {
500 closeInputStream(&is_wvc);
501 break;
503 #endif
504 err = 0;
506 } while (0);
508 canseek = can_seek(&isp);
509 if (wvc_url != NULL) {
510 if (err) {
511 free(wvc_url);
512 wvc_url = NULL;
513 } else {
514 initInputStreamPlus(&isp_wvc, &is_wvc);
515 open_flags |= OPEN_WVC;
516 if (!can_seek(&isp_wvc))
517 canseek = 0;
521 if (!canseek)
522 open_flags |= OPEN_STREAMING;
524 initInputStreamPlus(&isp, is);
525 wpc = WavpackOpenFileInputEx(&mpd_is_reader, &isp, &isp_wvc, error,
526 open_flags, 15);
527 if (wpc == NULL) {
528 ERROR("failed to open WavPack stream: %s\n", error);
529 return -1;
532 wavpack_decode(cb, dc, wpc, canseek, NULL);
534 WavpackCloseFile(wpc);
535 if (wvc_url != NULL) {
536 closeInputStream(&is_wvc);
537 free(wvc_url);
539 closeInputStream(is); /* calling side doesn't do this in mpd 0.13.0 */
541 return 0;
545 * Decodes a file.
547 static int wavpack_filedecode(OutputBuffer *cb, DecoderControl *dc, char *fname)
549 char error[ERRORLEN];
550 WavpackContext *wpc;
551 ReplayGainInfo *replayGainInfo;
553 wpc = WavpackOpenFileInput(fname, error,
554 OPEN_TAGS | OPEN_WVC |
555 OPEN_2CH_MAX | OPEN_NORMALIZE, 15);
556 if (wpc == NULL) {
557 ERROR("failed to open WavPack file \"%s\": %s\n", fname, error);
558 return -1;
561 replayGainInfo = wavpack_replaygain(wpc);
563 wavpack_decode(cb, dc, wpc, 1, replayGainInfo);
565 if (replayGainInfo)
566 freeReplayGainInfo(replayGainInfo);
568 WavpackCloseFile(wpc);
570 return 0;
573 static char *wavpackSuffixes[] = { "wv", NULL };
574 static char *wavpackMimeTypes[] = { "audio/x-wavpack", NULL };
576 InputPlugin wavpackPlugin = {
577 "wavpack",
578 NULL,
579 NULL,
580 /* Disabled till the http seek bug is fixed. */
581 wavpack_trydecode,
582 wavpack_streamdecode,
583 wavpack_filedecode,
584 wavpack_tagdup,
585 INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL,
586 wavpackSuffixes,
587 wavpackMimeTypes
590 #else /* !HAVE_WAVPACK */
592 InputPlugin wavpackPlugin;
594 #endif /* !HAVE_WAVPACK */