Initial revision 6759
[qball-mpd.git] / src / inputPlugins / .svn / text-base / wavpack_plugin.c.svn-base
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.
11  *
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
19  */
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.
65  */
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);
81                         }
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);
89                         }
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);
97                         }
98                         break;
99         }
103  * This function converts floating point sample data to 16 bit integer.
104  */
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++);
112         }
116  * This does the main decoding thing.
117  * Requires an already opened WavpackContext.
118  */
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;
181                         }
183                         dc->seek = 0;
184                 }
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);
203                 }
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);
224         }
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;
242         }
244         value = wavpack_tag(wpc, "replaygain_album_gain");
245         if (value) {
246                 replayGainInfo->albumGain = atof(value);
247                 free(value);
248                 found = 1;
249         }
251         value = wavpack_tag(wpc, "replaygain_track_peak");
252         if (value) {
253                 replayGainInfo->trackPeak = atof(value);
254                 free(value);
255                 found = 1;
256         }
258         value = wavpack_tag(wpc, "replaygain_album_peak");
259         if (value) {
260                 replayGainInfo->albumPeak = atof(value);
261                 free(value);
262                 found = 1;
263         }
266         if (found)
267                 return replayGainInfo;
269         freeReplayGainInfo(replayGainInfo);
271         return NULL;
275  * Reads metainfo from the specified file.
276  */
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;
290         }
292         tag = newMpdTag();
293         if (tag == NULL) {
294                 ERROR("failed to newMpdTag()\n");
295                 return NULL;
296         }
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;
318                         }
320                         WavpackGetTagItem(wpc, tagtypes[i].name, s, j);
321                         addItemToMpdTag(tag, tagtypes[i].type, s);
322                 }
323         }
325         if (s != NULL)
326                 free(s);
328         WavpackCloseFile(wpc);
330         return tag;
334  * mpd InputStream <=> WavpackStreamReader wrapper callbacks
335  */
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;
355         }
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.
410  */
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.
433  */
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;
451                 /*
452                  * As we use dc->utf8url, this function will be bad for
453                  * single files. utf8url is not absolute file path :\
454                  */
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 */
473                 /* 
474                  * And we try to buffer in order to get know
475                  * about a possible 404.
476                  */
477                 for (;;) {
478                         if (inputStreamAtEOF(&is_wvc))
479                                 /*
480                                  * EOF is reached even without
481                                  * reading a single byte...
482                                  * So, this is not good :\
483                                  */
484                                 break;
486                         if (bufferInputStream(&is_wvc) >= 0) {
487                                 err = 0;
488                                 break;
489                         }
491                         /* Can this happen?: */
492                         if (dc->stop)
493                                 break;
495                         /* Save some CPU */
496                         my_usleep(1000);
498                 }
499                 if (err) {
500                         closeInputStream(&is_wvc);
501                         break;
502                 }
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;
518                 }
519         }
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;
530         }
532         wavpack_decode(cb, dc, wpc, canseek, NULL);
534         WavpackCloseFile(wpc);
535         if (wvc_url != NULL) {
536                 closeInputStream(&is_wvc);
537                 free(wvc_url);
538         }
539         closeInputStream(is); /* calling side doesn't do this in mpd 0.13.0 */
541         return 0;
545  * Decodes a file.
546  */
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;
559         }
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 */