1 /*
2     libsndfile plugin for DeaDBeeF Player
3     Copyright (C) 2009-2014 Alexey Yakovenko <waker@users.sourceforge.net>
4 
5     This program is free software; you can redistribute it and/or
6     modify it under the terms of the GNU General Public License
7     as published by the Free Software Foundation; either version 2
8     of the License, or (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 */
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22 #ifndef __linux__
23 #define _LARGEFILE64_SOURCE
24 #endif
25 #include <string.h>
26 #include <sndfile.h>
27 #include <math.h>
28 #include <stdlib.h>
29 #include "../../deadbeef.h"
30 
31 #define min(x,y) ((x)<(y)?(x):(y))
32 #define max(x,y) ((x)>(y)?(x):(y))
33 
34 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
35 #define trace(fmt,...)
36 
37 static DB_decoder_t plugin;
38 static DB_functions_t *deadbeef;
39 
40 typedef struct {
41     DB_fileinfo_t info;
42     SNDFILE *ctx;
43     DB_FILE *file;
44     int startsample;
45     int endsample;
46     int currentsample;
47     int bitrate;
48     int sf_format;
49     int read_as_short;
50     int sf_need_endswap;
51 } sndfile_info_t;
52 
53 // vfs wrapper for sf
54 static sf_count_t
sf_vfs_get_filelen(void * user_data)55 sf_vfs_get_filelen (void *user_data) {
56     sndfile_info_t *ctx = user_data;
57     return deadbeef->fgetlength (ctx->file);
58 }
59 
60 static sf_count_t
sf_vfs_read(void * ptr,sf_count_t count,void * user_data)61 sf_vfs_read (void *ptr, sf_count_t count, void *user_data) {
62     sndfile_info_t *ctx = user_data;
63     return deadbeef->fread (ptr, 1, count, ctx->file);
64 }
65 
66 static sf_count_t
sf_vfs_write(const void * ptr,sf_count_t count,void * user_data)67 sf_vfs_write (const void *ptr, sf_count_t count, void *user_data) {
68     return -1;
69 }
70 
71 static sf_count_t
sf_vfs_seek(sf_count_t offset,int whence,void * user_data)72 sf_vfs_seek (sf_count_t offset, int whence, void *user_data) {
73     sndfile_info_t *ctx = user_data;
74     int ret = deadbeef->fseek (ctx->file, offset, whence);
75     if (!ret) {
76         return offset;
77     }
78     return -1;
79 }
80 
81 static sf_count_t
sf_vfs_tell(void * user_data)82 sf_vfs_tell (void *user_data) {
83     sndfile_info_t *ctx = user_data;
84     return deadbeef->ftell (ctx->file);
85 }
86 
87 static SF_VIRTUAL_IO vfs = {
88     .get_filelen = sf_vfs_get_filelen,
89     .seek = sf_vfs_seek,
90     .read = sf_vfs_read,
91     .write = sf_vfs_write,
92     .tell = sf_vfs_tell
93 };
94 
95 static DB_fileinfo_t *
sndfile_open(uint32_t hints)96 sndfile_open (uint32_t hints) {
97     DB_fileinfo_t *_info = malloc (sizeof (sndfile_info_t));
98     memset (_info, 0, sizeof (sndfile_info_t));
99     return _info;
100 }
101 
102 
103 // taken from libsndfile
104 #define     ARRAY_LEN(x)    ((int) (sizeof (x) / sizeof ((x) [0])))
105 /* This stores which bit in dwChannelMask maps to which channel */
106 static const struct chanmap_s
107 {	int id ;
108 	const char * name ;
109 } channel_mask_bits [] =
110 {	/* WAVEFORMATEXTENSIBLE doesn't distuingish FRONT_LEFT from LEFT */
111 	{	SF_CHANNEL_MAP_LEFT, "L" },
112 	{	SF_CHANNEL_MAP_RIGHT, "R" },
113 	{	SF_CHANNEL_MAP_CENTER, "C" },
114 	{	SF_CHANNEL_MAP_LFE, "LFE" },
115 	{	SF_CHANNEL_MAP_REAR_LEFT, "Ls" },
116 	{	SF_CHANNEL_MAP_REAR_RIGHT, "Rs" },
117 	{	SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER, "Lc" },
118 	{	SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER, "Rc" },
119 	{	SF_CHANNEL_MAP_REAR_CENTER, "Cs" },
120 	{	SF_CHANNEL_MAP_SIDE_LEFT, "Sl" },
121 	{	SF_CHANNEL_MAP_SIDE_RIGHT, "Sr" },
122 	{	SF_CHANNEL_MAP_TOP_CENTER, "Tc" },
123 	{	SF_CHANNEL_MAP_TOP_FRONT_LEFT, "Tfl" },
124 	{	SF_CHANNEL_MAP_TOP_FRONT_CENTER, "Tfc" },
125 	{	SF_CHANNEL_MAP_TOP_FRONT_RIGHT, "Tfr" },
126 	{	SF_CHANNEL_MAP_TOP_REAR_LEFT, "Trl" },
127 	{	SF_CHANNEL_MAP_TOP_REAR_CENTER, "Trc" },
128 	{	SF_CHANNEL_MAP_TOP_REAR_RIGHT, "Trr" },
129 } ;
130 
131 
132 static int
wavex_gen_channel_mask(const int * chan_map,int channels)133 wavex_gen_channel_mask (const int *chan_map, int channels)
134 {   int chan, mask = 0, bit = -1, last_bit = -1 ;
135 
136     if (chan_map == NULL)
137         return 0 ;
138 
139     for (chan = 0 ; chan < channels ; chan ++)
140     {   int k ;
141 
142         for (k = bit + 1 ; k < ARRAY_LEN (channel_mask_bits) ; k++)
143             if (chan_map [chan] == channel_mask_bits [k].id)
144             {   bit = k ;
145                 break ;
146                 } ;
147 
148         /* Check for bad sequence. */
149         if (bit <= last_bit)
150             return 0 ;
151 
152         mask += 1 << bit ;
153         last_bit = bit ;
154         } ;
155 
156     return mask ;
157 } /* wavex_gen_channel_mask */
158 
159 
160 static int
sndfile_init(DB_fileinfo_t * _info,DB_playItem_t * it)161 sndfile_init (DB_fileinfo_t *_info, DB_playItem_t *it) {
162     sndfile_info_t *info = (sndfile_info_t*)_info;
163 
164     SF_INFO inf;
165     deadbeef->pl_lock ();
166     DB_FILE *fp = deadbeef->fopen (deadbeef->pl_find_meta (it, ":URI"));
167     deadbeef->pl_unlock ();
168     if (!fp) {
169         trace ("sndfile: failed to open %s\n", deadbeef->pl_find_meta (it, ":URI"));
170         return -1;
171     }
172     int fsize = deadbeef->fgetlength (fp);
173 
174     info->file = fp;
175     info->ctx = sf_open_virtual (&vfs, SFM_READ, &inf, info);
176     if (!info->ctx) {
177         trace ("sndfile: %s: unsupported file format\n");
178         return -1;
179     }
180     _info->plugin = &plugin;
181     info->sf_format = inf.format&SF_FORMAT_SUBMASK;
182     info->sf_need_endswap = sf_command (info->ctx, SFC_RAW_DATA_NEEDS_ENDSWAP, NULL, 0);
183 
184     switch (inf.format&SF_FORMAT_SUBMASK) {
185     case SF_FORMAT_PCM_S8:
186     case SF_FORMAT_PCM_U8:
187         _info->fmt.bps = 8;
188         break;
189     case SF_FORMAT_PCM_16:
190         _info->fmt.bps = 16;
191         break;
192     case SF_FORMAT_PCM_24:
193         _info->fmt.bps = 24;
194         break;
195     case SF_FORMAT_FLOAT:
196         _info->fmt.is_float = 1;
197     case SF_FORMAT_PCM_32:
198         _info->fmt.bps = 32;
199         break;
200     default:
201         info->read_as_short = 1;
202         _info->fmt.bps = 16;
203         trace ("[sndfile] unidentified input format: 0x%X\n", inf.format&SF_FORMAT_SUBMASK);
204         break;
205     }
206 
207     _info->fmt.channels = inf.channels;
208     _info->fmt.samplerate = inf.samplerate;
209 
210 // FIXME: streamer and maybe output plugins need to be fixed to support
211 // arbitrary channelmask
212 //
213 //    int channel_map [inf.channels];
214 //    int cmdres = sf_command (info->ctx, SFC_GET_CHANNEL_MAP_INFO, channel_map, sizeof (channel_map)) ;
215 //    if (cmdres != SF_FALSE) {
216 //        // channel map found, convert to channel mask
217 //        _info->fmt.channelmask = wavex_gen_channel_mask (channel_map, inf.channels);
218 //    }
219 //    else
220     {
221         // channel map not found, generate from channel number
222         for (int i = 0; i < inf.channels; i++) {
223             _info->fmt.channelmask |= 1 << i;
224         }
225     }
226 
227     _info->readpos = 0;
228     if (it->endsample > 0) {
229         info->startsample = it->startsample;
230         info->endsample = it->endsample;
231         if (plugin.seek_sample (_info, 0) < 0) {
232             return -1;
233         }
234     }
235     else {
236         info->startsample = 0;
237         info->endsample = inf.frames-1;
238     }
239     // hack bitrate
240 
241     int totalsamples = inf.frames;
242     float sec = (float)totalsamples / inf.samplerate;
243     if (sec > 0) {
244         info->bitrate = fsize / sec * 8 / 1000;
245     }
246     else {
247         info->bitrate = -1;
248     }
249 
250     return 0;
251 }
252 
253 static void
sndfile_free(DB_fileinfo_t * _info)254 sndfile_free (DB_fileinfo_t *_info) {
255     sndfile_info_t *info = (sndfile_info_t*)_info;
256     if (info->ctx) {
257         sf_close (info->ctx);
258     }
259     if (info->file) {
260         deadbeef->fclose (info->file);
261     }
262     memset (&info, 0, sizeof (info));
263 }
264 
265 static int
sndfile_read(DB_fileinfo_t * _info,char * bytes,int size)266 sndfile_read (DB_fileinfo_t *_info, char *bytes, int size) {
267     sndfile_info_t *info = (sndfile_info_t*)_info;
268     int samplesize = _info->fmt.channels * _info->fmt.bps / 8;
269     if (size / samplesize + info->currentsample > info->endsample) {
270         size = (info->endsample - info->currentsample + 1) * samplesize;
271         trace ("sndfile: size truncated to %d bytes, cursample=%d, endsample=%d\n", size, info->currentsample, info->endsample);
272         if (size <= 0) {
273             return 0;
274         }
275     }
276 
277     int n = 0;
278     if (info->read_as_short) {
279         n = sf_readf_short(info->ctx, (short *)bytes, size/samplesize);
280     }
281     else {
282         n = sf_read_raw (info->ctx, (short *)bytes, size);
283 
284         if (info->sf_format == SF_FORMAT_PCM_U8) {
285             for (int i = 0; i < n; i++) {
286                 int sample = ((uint8_t *)bytes)[i];
287                 ((int8_t *)bytes)[i] = sample-0x80;
288             }
289         }
290         else if (info->sf_need_endswap) {
291             switch (info->info.fmt.bps) {
292             case 16:
293                 {
294                     uint16_t *data = (uint16_t *)bytes;
295                     for (int i = 0; i < n/2; i++, data++) {
296                         *data = ((*data & 0xff) << 8) | ((*data & 0xff00) >> 8);
297                     }
298                 }
299                 break;
300             case 24:
301                 {
302                     uint8_t *data = bytes;
303                     for (int i = 0; i < n/3; i++, data += 3) {
304                         uint8_t temp = data[0];
305                         data[0] = data[2];
306                         data[2] = temp;
307                     }
308                 }
309                 break;
310             case 32:
311                 {
312                     uint32_t *data = (uint32_t *)bytes;
313                     for (int i = 0; i < n/4; i++, data++) {
314                         *data = ((*data & 0xff) << 24) | ((*data & 0xff00) << 8) | ((*data & 0xff0000) >> 8) | ((*data & 0xff0000) >> 24);
315                     }
316                 }
317                 break;
318             }
319         }
320         n /= samplesize;
321     }
322 
323     info->currentsample += n;
324 
325     size = n * samplesize;
326     _info->readpos = (float)(info->currentsample-info->startsample)/_info->fmt.samplerate;
327     if (info->bitrate > 0) {
328         deadbeef->streamer_set_bitrate (info->bitrate);
329     }
330     return size;
331 }
332 
333 static int
sndfile_seek_sample(DB_fileinfo_t * _info,int sample)334 sndfile_seek_sample (DB_fileinfo_t *_info, int sample) {
335     sndfile_info_t *info = (sndfile_info_t*)_info;
336     int ret = sf_seek (info->ctx, sample + info->startsample, SEEK_SET);
337     if (ret < 0) {
338         return -1;
339     }
340     info->currentsample = ret;
341     _info->readpos = (float)(info->currentsample - info->startsample) / _info->fmt.samplerate;
342     return 0;
343 }
344 
345 static int
sndfile_seek(DB_fileinfo_t * _info,float sec)346 sndfile_seek (DB_fileinfo_t *_info, float sec) {
347     return sndfile_seek_sample (_info, sec * _info->fmt.samplerate);
348 }
349 
350 static DB_playItem_t *
sndfile_insert(ddb_playlist_t * plt,DB_playItem_t * after,const char * fname)351 sndfile_insert (ddb_playlist_t *plt, DB_playItem_t *after, const char *fname) {
352     trace ("adding file %s\n", fname);
353     SF_INFO inf;
354     sndfile_info_t info;
355     memset (&info, 0, sizeof (info));
356     info.file = deadbeef->fopen (fname);
357     if (!info.file) {
358         trace ("sndfile: failed to open %s\n", fname);
359         return NULL;
360     }
361     int64_t fsize = deadbeef->fgetlength (info.file);
362     trace ("file: %p, size: %lld\n", info.file, deadbeef->fgetlength (info.file));
363     trace ("calling sf_open_virtual\n");
364     info.ctx = sf_open_virtual (&vfs, SFM_READ, &inf, &info);
365     if (!info.ctx) {
366         trace ("sndfile: sf_open failed\n");
367         deadbeef->fclose (info.file);
368         return NULL;
369     }
370     trace ("calling sf_open_virtual ok\n");
371     int totalsamples = inf.frames;
372     int samplerate = inf.samplerate;
373     sf_close (info.ctx);
374     deadbeef->fclose (info.file);
375 
376     float duration = (float)totalsamples / samplerate;
377     DB_playItem_t *it = deadbeef->pl_item_alloc_init (fname, plugin.plugin.id);
378     deadbeef->pl_add_meta (it, ":FILETYPE", "wav");
379     deadbeef->plt_set_item_duration (plt, it, duration);
380 
381     trace ("sndfile: totalsamples=%d, samplerate=%d, duration=%f\n", totalsamples, samplerate, duration);
382 
383     char s[100];
384     snprintf (s, sizeof (s), "%lld", fsize);
385     deadbeef->pl_add_meta (it, ":FILE_SIZE", s);
386 
387     int bps = -1;
388     switch (inf.format&SF_FORMAT_SUBMASK) {
389     case SF_FORMAT_IMA_ADPCM:
390     case SF_FORMAT_MS_ADPCM:
391         bps = 4;
392         break;
393     case SF_FORMAT_ALAW:
394     case SF_FORMAT_ULAW:
395     case SF_FORMAT_PCM_S8:
396     case SF_FORMAT_PCM_U8:
397         bps = 8;
398         break;
399     case SF_FORMAT_PCM_16:
400         bps = 16;
401         break;
402     case SF_FORMAT_PCM_24:
403         bps = 24;
404         break;
405     case SF_FORMAT_FLOAT:
406     case SF_FORMAT_PCM_32:
407         bps = 32;
408         break;
409     }
410 
411     if (bps == -1) {
412         snprintf (s, sizeof (s), "unknown");
413     }
414     else {
415         snprintf (s, sizeof (s), "%d", bps);
416     }
417     deadbeef->pl_add_meta (it, ":BPS", s);
418     snprintf (s, sizeof (s), "%d", inf.channels);
419     deadbeef->pl_add_meta (it, ":CHANNELS", s);
420     snprintf (s, sizeof (s), "%d", samplerate);
421     deadbeef->pl_add_meta (it, ":SAMPLERATE", s);
422     int br = (int)roundf(fsize / duration * 8 / 1000);
423     snprintf (s, sizeof (s), "%d", br);
424     deadbeef->pl_add_meta (it, ":BITRATE", s);
425 
426     // sndfile subformats
427     const char *subformats[] = {
428         "",
429         "PCM_S8",
430         "PCM_16",
431         "PCM_24",
432         "PCM_32",
433         "PCM_U8",
434         "FLOAT",
435         "DOUBLE",
436         "",
437         "",
438         "ULAW",
439         "ALAW",
440         "IMA_ADPCM",
441         "MS_ADPCM",
442         "",
443         "",
444         "",
445         "",
446         "",
447         "",
448         "",
449         "GSM610",
450         "VOX_ADPCM",
451         "",
452         "",
453         "",
454         "",
455         "",
456         "",
457         "",
458         "",
459         "G721_32",
460         "G723_24",
461         "G723_40",
462         "",
463         "",
464         "",
465         "",
466         "",
467         "",
468         "",
469         "DWVW_12",
470         "DWVW_16",
471         "DWVW_24",
472         "DWVW_N",
473         "",
474         "",
475         "",
476         "",
477         "",
478         "",
479         "DPCM_8",
480         "DPCM_16",
481         "",
482         "",
483         "",
484         "",
485         "",
486         "",
487         "",
488         "",
489         "VORBIS",
490     };
491 
492     if (inf.format&SF_FORMAT_SUBMASK <= SF_FORMAT_VORBIS) {
493         deadbeef->pl_add_meta (it, ":SF_FORMAT", subformats[inf.format&SF_FORMAT_SUBMASK]);
494     }
495 
496     DB_playItem_t *cue_after = deadbeef->plt_insert_cue (plt, after, it, totalsamples, samplerate);
497     if (cue_after) {
498         deadbeef->pl_item_unref (it);
499         deadbeef->pl_item_unref (cue_after);
500         return cue_after;
501     }
502 
503     deadbeef->pl_add_meta (it, "title", NULL);
504     after = deadbeef->plt_insert_item (plt, after, it);
505     deadbeef->pl_item_unref (it);
506 
507     return after;
508 }
509 
510 #define DEFAULT_EXTS "wav;aif;aiff;snd;au;paf;svx;nist;voc;ircam;w64;mat4;mat5;pvf;xi;htk;sds;avr;wavex;sd2;caf;wve"
511 
512 #define EXT_MAX 100
513 
514 static char *exts[EXT_MAX] = {NULL};
515 
516 static void
sndfile_init_exts(void)517 sndfile_init_exts (void) {
518     for (int i = 0; exts[i]; i++) {
519         free (exts[i]);
520     }
521     exts[0] = NULL;
522 
523     int n = 0;
524     deadbeef->conf_lock ();
525     const char *new_exts = deadbeef->conf_get_str_fast ("sndfile.extensions", DEFAULT_EXTS);
526     while (*new_exts) {
527         if (n >= EXT_MAX) {
528             fprintf (stderr, "sndfile: too many extensions, max is %d\n", EXT_MAX);
529             break;
530         }
531         const char *e = new_exts;
532         while (*e && *e != ';') {
533             e++;
534         }
535         if (e != new_exts) {
536             char *ext = malloc (e-new_exts+1);
537             memcpy (ext, new_exts, e-new_exts);
538             ext[e-new_exts] = 0;
539             exts[n++] = ext;
540         }
541         if (*e == 0) {
542             break;
543         }
544         new_exts = e+1;
545     }
546     deadbeef->conf_unlock ();
547     exts[n] = NULL;
548 }
549 
550 static int
sndfile_message(uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)551 sndfile_message (uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
552     switch (id) {
553     case DB_EV_CONFIGCHANGED:
554         sndfile_init_exts ();
555         break;
556     }
557     return 0;
558 }
559 
560 static int
sndfile_start(void)561 sndfile_start (void) {
562     sndfile_init_exts ();
563     return 0;
564 }
565 
566 static int
sndfile_stop(void)567 sndfile_stop (void) {
568     for (int i = 0; exts[i]; i++) {
569         free (exts[i]);
570     }
571     exts[0] = NULL;
572     return 0;
573 }
574 
575 static const char settings_dlg[] =
576     "property \"File Extensions (separate with ';')\" entry sndfile.extensions \"" DEFAULT_EXTS "\";\n"
577 ;
578 
579 
580 // define plugin interface
581 static DB_decoder_t plugin = {
582     .plugin.api_vmajor = 1,
583     .plugin.api_vminor = 0,
584     .plugin.version_major = 1,
585     .plugin.version_minor = 0,
586     .plugin.type = DB_PLUGIN_DECODER,
587     .plugin.id = "sndfile",
588     .plugin.name = "WAV/PCM player",
589     .plugin.descr = "wav/aiff player using libsndfile",
590     .plugin.copyright =
591         "libsndfile plugin for DeaDBeeF Player\n"
592         "Copyright (C) 2009-2014 Alexey Yakovenko <waker@users.sourceforge.net>\n"
593         "\n"
594         "This program is free software; you can redistribute it and/or\n"
595         "modify it under the terms of the GNU General Public License\n"
596         "as published by the Free Software Foundation; either version 2\n"
597         "of the License, or (at your option) any later version.\n"
598         "\n"
599         "This program is distributed in the hope that it will be useful,\n"
600         "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
601         "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
602         "GNU General Public License for more details.\n"
603         "\n"
604         "You should have received a copy of the GNU General Public License\n"
605         "along with this program; if not, write to the Free Software\n"
606         "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n"
607     ,
608     .plugin.website = "http://deadbeef.sf.net",
609     .open = sndfile_open,
610     .init = sndfile_init,
611     .free = sndfile_free,
612     .read = sndfile_read,
613     .seek = sndfile_seek,
614     .seek_sample = sndfile_seek_sample,
615     .insert = sndfile_insert,
616     .exts = (const char **)exts,
617     .plugin.start = sndfile_start,
618     .plugin.stop = sndfile_stop,
619     .plugin.configdialog = settings_dlg,
620     .plugin.message = sndfile_message,
621 };
622 
623 DB_plugin_t *
sndfile_load(DB_functions_t * api)624 sndfile_load (DB_functions_t *api) {
625     deadbeef = api;
626     return DB_PLUGIN (&plugin);
627 }
628