109
|
1 #include <assert.h>
|
|
2 #include <string.h>
|
|
3 #include <stdio.h>
|
|
4 #include <stdlib.h>
|
|
5 #include <unistd.h>
|
|
6 #include <wavpack/wputils.h>
|
|
7 extern "C" {
|
|
8 #include <audacious/plugin.h>
|
111
|
9 #include <audacious/output.h>
|
109
|
10 #include <audacious/configdb.h>
|
|
11 #include <audacious/titlestring.h>
|
|
12 #include <audacious/util.h>
|
|
13 }
|
|
14 #include <glib.h>
|
|
15 #include <gtk/gtk.h>
|
|
16 #include <iconv.h>
|
|
17 #include <math.h>
|
|
18 #include "tags.h"
|
|
19 #ifndef M_LN10
|
|
20 #define M_LN10 2.3025850929940456840179914546843642
|
|
21 #endif
|
|
22
|
112
|
23 #ifdef DEBUG
|
|
24 # define DBG(format, args...) fprintf(stderr, format, ## args)
|
|
25 #else
|
|
26 # define DBG(format, args...)
|
|
27 #endif
|
|
28
|
109
|
29 #define BUFFER_SIZE 256 // read buffer size, in samples
|
|
30
|
|
31 extern "C" InputPlugin * get_iplugin_info(void);
|
|
32 static void wv_load_config();
|
|
33 static int wv_is_our_file(char *);
|
|
34 static void wv_play(char *);
|
|
35 static void wv_stop(void);
|
|
36 static void wv_pause(short);
|
|
37 static void wv_seek(int);
|
|
38 static int wv_get_time(void);
|
|
39 static void wv_get_song_info(char *, char **, int *);
|
|
40 static char *generate_title(const char *, WavpackContext *ctx);
|
|
41 static double isSeek;
|
|
42 static short paused;
|
|
43 static bool killDecodeThread;
|
|
44 static bool AudioError;
|
|
45 static GThread *thread_handle;
|
114
|
46 static TitleInput *wv_get_song_tuple(char *);
|
109
|
47
|
|
48 // in ui.cpp
|
|
49 void wv_configure();
|
|
50 void wv_about_box(void);
|
|
51 void wv_file_info_box(char *);
|
|
52 extern gboolean clipPreventionEnabled;
|
|
53 extern gboolean dynBitrateEnabled;
|
|
54 extern gboolean replaygainEnabled;
|
|
55 extern gboolean albumReplaygainEnabled;
|
|
56 extern gboolean openedAudio;
|
|
57
|
|
58 InputPlugin mod = {
|
|
59 NULL, //handle
|
|
60 NULL, //filename
|
|
61 NULL,
|
|
62 wv_load_config,
|
|
63 wv_about_box,
|
|
64 wv_configure,
|
|
65 wv_is_our_file,
|
|
66 NULL, //no use
|
|
67 wv_play,
|
|
68 wv_stop,
|
|
69 wv_pause,
|
|
70 wv_seek,
|
|
71 NULL, //set eq
|
|
72 wv_get_time,
|
|
73 NULL, //get volume
|
|
74 NULL, //set volume
|
|
75 NULL, //cleanup
|
|
76 NULL, //obsolete
|
|
77 NULL, //add_vis
|
|
78 NULL,
|
|
79 NULL,
|
|
80 wv_get_song_info,
|
|
81 wv_file_info_box, //info box
|
|
82 NULL, //output
|
114
|
83 wv_get_song_tuple,
|
109
|
84 };
|
|
85
|
|
86 class WavpackDecoder
|
|
87 {
|
|
88 public:
|
|
89 InputPlugin *mod;
|
|
90 int32_t *input;
|
|
91 int16_t *output;
|
|
92 int sample_rate;
|
|
93 int num_channels;
|
|
94 WavpackContext *ctx;
|
|
95 char error_buff[4096]; // TODO: fixme!
|
|
96
|
|
97 WavpackDecoder(InputPlugin *mod) : mod(mod)
|
|
98 {
|
|
99 ctx = NULL;
|
|
100 input = NULL;
|
|
101 output = NULL;
|
|
102 }
|
|
103
|
|
104 ~WavpackDecoder()
|
|
105 {
|
|
106 if (input != NULL) {
|
|
107 free(input);
|
|
108 input = NULL;
|
|
109 }
|
|
110 if (output != NULL) {
|
|
111 free(output);
|
|
112 output = NULL;
|
|
113 }
|
|
114 if (ctx != NULL) {
|
|
115 WavpackCloseFile(ctx);
|
|
116 ctx = NULL;
|
|
117 }
|
|
118 }
|
|
119
|
|
120 bool attach(const char *filename)
|
|
121 {
|
|
122 ctx = WavpackOpenFileInput(filename, error_buff, OPEN_TAGS | OPEN_WVC, 0);
|
|
123
|
|
124 if (ctx == NULL) {
|
|
125 return false;
|
|
126 }
|
|
127
|
|
128 sample_rate = WavpackGetSampleRate(ctx);
|
|
129 num_channels = WavpackGetNumChannels(ctx);
|
|
130 input = (int32_t *)calloc(BUFFER_SIZE, num_channels * sizeof(int32_t));
|
|
131 output = (int16_t *)calloc(BUFFER_SIZE, num_channels * sizeof(int16_t));
|
|
132 mod->set_info(generate_title(filename, ctx),
|
|
133 (int) (WavpackGetNumSamples(ctx) / sample_rate) * 1000,
|
|
134 (int) WavpackGetAverageBitrate(ctx, num_channels),
|
|
135 (int) sample_rate, num_channels);
|
|
136 return true;
|
|
137 }
|
|
138
|
|
139 bool open_audio()
|
|
140 {
|
112
|
141 return mod->output->open_audio(FMT_S16_NE, sample_rate, num_channels);
|
109
|
142 }
|
|
143
|
|
144 void process_buffer(size_t num_samples)
|
|
145 {
|
|
146 for (int i = 0; i < num_samples * num_channels; i++) {
|
|
147 output[i] = input[i];
|
|
148 }
|
112
|
149 produce_audio(mod->output->output_time(), FMT_S16_NE,
|
111
|
150 num_channels,
|
|
151 num_samples * num_channels * sizeof(int16_t),
|
|
152 output,
|
|
153 NULL);
|
109
|
154 }
|
|
155 };
|
|
156
|
|
157 extern "C" InputPlugin *
|
|
158 get_iplugin_info(void)
|
|
159 {
|
|
160 mod.description =
|
|
161 g_strdup_printf(("Wavpack Decoder Plugin %s"), VERSION);
|
|
162 return &mod;
|
|
163 }
|
|
164
|
|
165 static int
|
|
166 wv_is_our_file(char *filename)
|
|
167 {
|
|
168 char *ext;
|
|
169
|
|
170 ext = strrchr(filename, '.');
|
|
171 if (ext) {
|
|
172 if (!strcasecmp(ext, ".wv")) {
|
|
173 return TRUE;
|
|
174 }
|
|
175 }
|
|
176 return FALSE;
|
|
177 }
|
|
178
|
|
179 void
|
|
180 load_tag(ape_tag *tag, WavpackContext *ctx)
|
|
181 {
|
|
182 memset(tag, 0, sizeof(ape_tag));
|
|
183 WavpackGetTagItem(ctx, "Album", tag->album, sizeof(tag->album));
|
|
184 WavpackGetTagItem(ctx, "Artist", tag->artist, sizeof(tag->artist));
|
|
185 WavpackGetTagItem(ctx, "Comment", tag->comment, sizeof(tag->comment));
|
|
186 WavpackGetTagItem(ctx, "Genre", tag->genre, sizeof(tag->genre));
|
|
187 WavpackGetTagItem(ctx, "Title", tag->title, sizeof(tag->title));
|
|
188 WavpackGetTagItem(ctx, "Track", tag->track, sizeof(tag->track));
|
|
189 WavpackGetTagItem(ctx, "Year", tag->year, sizeof(tag->year));
|
|
190 }
|
|
191
|
|
192 static char *
|
|
193 convertUTF8toLocale(char *utf8)
|
|
194 {
|
|
195 // note - opens a new iconv descriptor for each call
|
|
196 // will have to find a way to reuse the descriptor if this turns
|
|
197 // out to be too slow
|
|
198 iconv_t idesc = iconv_open("", "UTF-8");
|
|
199 if (idesc == (iconv_t) -1) {
|
|
200 perror("iconv_open failed");
|
|
201 return g_strdup(utf8);
|
|
202 }
|
|
203
|
|
204 size_t in_left = strlen(utf8);
|
|
205 size_t out_left = 2 * in_left + 1;
|
|
206 char *buf = (char *)g_malloc(out_left);
|
|
207 char *in = utf8;
|
|
208 char *out = buf;
|
|
209
|
|
210 memset(buf, 0, out_left);
|
|
211 size_t err = iconv(idesc, &in, &in_left, &out, &out_left);
|
|
212 iconv_close(idesc);
|
|
213 return buf;
|
|
214 }
|
|
215
|
|
216 static void *
|
|
217 end_thread()
|
|
218 {
|
|
219 return 0;
|
|
220 }
|
|
221
|
|
222 static void *
|
|
223 DecodeThread(void *a)
|
|
224 {
|
|
225 ape_tag tag;
|
|
226 char *filename = (char *) a;
|
|
227 int bps_updateCounter = 0;
|
|
228 int bps;
|
|
229 int i;
|
|
230 WavpackDecoder d(&mod);
|
|
231
|
|
232 if (!d.attach(filename)) {
|
|
233 printf("wavpack: Error opening file: \"%s\"\n", filename);
|
|
234 killDecodeThread = true;
|
|
235 return end_thread();
|
|
236 }
|
|
237 bps = WavpackGetBytesPerSample(d.ctx) * d.num_channels;
|
|
238 DBG("reading %s at %d rate with %d channels\n", filename, d.sample_rate, d.num_channels);
|
|
239
|
|
240 if (!d.open_audio()) {
|
|
241 DBG("error opening xmms audio channel\n");
|
|
242 killDecodeThread = true;
|
|
243 AudioError = true;
|
|
244 openedAudio = false;
|
|
245 }
|
|
246 else {
|
|
247 DBG("opened xmms audio channel\n");
|
|
248 openedAudio = true;
|
|
249 }
|
|
250 unsigned status;
|
|
251 char *display = generate_title(filename, d.ctx);
|
|
252 int length = (int) (1000 * WavpackGetNumSamples(d.ctx));
|
|
253
|
|
254 while (!killDecodeThread) {
|
|
255 if (isSeek != -1) {
|
|
256 DBG("seeking to position %d\n", isSeek);
|
|
257 WavpackSeekSample(d.ctx, isSeek * d.sample_rate);
|
|
258 isSeek = -1;
|
|
259 }
|
|
260 if (paused == 0
|
|
261 && (mod.output->buffer_free() >=
|
|
262 (1152 * 2 *
|
|
263 (16 / 8)) << (mod.output->buffer_playing()? 1 : 0))) {
|
|
264 status =
|
|
265 WavpackUnpackSamples(d.ctx, d.input, BUFFER_SIZE);
|
|
266 if (status == (unsigned) (-1)) {
|
|
267 printf("wavpack: Error decoding file.\n");
|
|
268 break;
|
|
269 }
|
|
270 else if (status == 0) {
|
|
271 killDecodeThread = true;
|
|
272 break;
|
|
273 }
|
|
274 else {
|
|
275 d.process_buffer(status);
|
|
276 }
|
|
277 }
|
|
278 else {
|
|
279 xmms_usleep(10000);
|
|
280 }
|
|
281 }
|
|
282 return end_thread();
|
|
283 }
|
|
284
|
|
285 static void
|
|
286 wv_play(char *filename)
|
|
287 {
|
|
288 paused = 0;
|
|
289 isSeek = -1;
|
|
290 killDecodeThread = false;
|
|
291 AudioError = false;
|
|
292 thread_handle = g_thread_create(DecodeThread, (void *) filename, TRUE, NULL);
|
|
293 return;
|
|
294 }
|
|
295
|
114
|
296 static TitleInput *
|
|
297 tuple_from_WavpackContext(const char *fn, WavpackContext *ctx)
|
|
298 {
|
|
299 ape_tag tag;
|
|
300 TitleInput *ti;
|
|
301 int sample_rate = WavpackGetSampleRate(ctx);
|
|
302
|
|
303 ti = bmp_title_input_new();
|
|
304
|
|
305 ti->file_name = g_strdup(g_basename(fn));
|
|
306 ti->file_ext = "wv";
|
|
307
|
|
308 load_tag(&tag, ctx);
|
|
309
|
|
310 ti->track_name = tag.title;
|
|
311 ti->performer = tag.artist;
|
|
312 ti->album_name = tag.album;
|
|
313 ti->date = tag.year;
|
|
314 ti->track_number = atoi(tag.track);
|
|
315 if (ti->track_number < 0)
|
|
316 ti->track_number = 0;
|
|
317 ti->year = atoi(tag.year);
|
|
318 if (ti->year < 0)
|
|
319 ti->year = 0;
|
|
320 ti->genre = tag.genre;
|
|
321 ti->comment = tag.comment;
|
|
322 ti->length = (int)(WavpackGetNumSamples(ctx) / sample_rate) * 1000;
|
|
323
|
|
324 return ti;
|
|
325 }
|
|
326
|
109
|
327 static char *
|
|
328 generate_title(const char *fn, WavpackContext *ctx)
|
|
329 {
|
|
330 static char *displaytitle = NULL;
|
|
331 ape_tag tag;
|
|
332 TitleInput *ti;
|
|
333
|
114
|
334 ti = tuple_from_WavpackContext(fn, ctx);
|
109
|
335
|
|
336 displaytitle = xmms_get_titlestring(xmms_get_gentitle_format(), ti);
|
|
337 if (!displaytitle || *displaytitle == '\0'
|
|
338 || (strlen(tag.title) == 0 && strlen(tag.artist) == 0))
|
|
339 displaytitle = ti->file_name;
|
114
|
340
|
|
341 bmp_title_input_free(ti);
|
109
|
342
|
|
343 return displaytitle;
|
|
344 }
|
|
345
|
114
|
346 static TitleInput *
|
|
347 wv_get_song_tuple(char *filename)
|
|
348 {
|
|
349 TitleInput *ti;
|
|
350 char error_buff[4096]; // TODO: fixme!
|
|
351 WavpackContext *ctx = WavpackOpenFileInput(filename, error_buff, OPEN_TAGS | OPEN_WVC, 0);
|
|
352
|
|
353 if (ctx == NULL) {
|
|
354 printf("wavpack: Error opening file: \"%s: %s\"\n", filename, error_buff);
|
|
355 return NULL;
|
|
356 }
|
|
357
|
|
358 ti = tuple_from_WavpackContext(filename, ctx);
|
|
359
|
|
360 WavpackCloseFile(ctx);
|
|
361
|
|
362 return ti;
|
|
363 }
|
|
364
|
109
|
365 static void
|
|
366 wv_get_song_info(char *filename, char **title, int *length)
|
|
367 {
|
|
368 assert(filename != NULL);
|
|
369 char error_buff[4096]; // TODO: fixme!
|
|
370 WavpackContext *ctx = WavpackOpenFileInput(filename, error_buff, OPEN_TAGS | OPEN_WVC, 0);
|
|
371 if (ctx == NULL) {
|
|
372 printf("wavpack: Error opening file: \"%s: %s\"\n", filename, error_buff);
|
|
373 return;
|
|
374 }
|
|
375 int sample_rate = WavpackGetSampleRate(ctx);
|
|
376 int num_channels = WavpackGetNumChannels(ctx);
|
|
377 DBG("reading %s at %d rate with %d channels\n", filename, sample_rate, num_channels);
|
|
378
|
|
379 *length = (int)(WavpackGetNumSamples(ctx) / sample_rate) * 1000,
|
|
380 *title = generate_title(filename, ctx);
|
|
381 DBG("title for %s = %s\n", filename, *title);
|
|
382 WavpackCloseFile(ctx);
|
|
383 }
|
|
384
|
|
385 static int
|
|
386 wv_get_time(void)
|
|
387 {
|
|
388 if (!mod.output)
|
|
389 return -1;
|
|
390 if (AudioError)
|
|
391 return -2;
|
|
392 if (killDecodeThread && !mod.output->buffer_playing())
|
|
393 return -1;
|
|
394 return mod.output->output_time();
|
|
395 }
|
|
396
|
|
397
|
|
398 static void
|
|
399 wv_seek(int sec)
|
|
400 {
|
|
401 isSeek = sec;
|
|
402 mod.output->flush((int) (1000 * isSeek));
|
|
403 }
|
|
404
|
|
405 static void
|
|
406 wv_pause(short pause)
|
|
407 {
|
|
408 mod.output->pause(paused = pause);
|
|
409 }
|
|
410
|
|
411 static void
|
|
412 wv_stop(void)
|
|
413 {
|
|
414 killDecodeThread = true;
|
|
415 if (thread_handle != 0) {
|
|
416 g_thread_join(thread_handle);
|
|
417 if (openedAudio) {
|
|
418 mod.output->buffer_free();
|
|
419 mod.output->close_audio();
|
|
420 }
|
|
421 openedAudio = false;
|
|
422 if (AudioError)
|
|
423 printf("Could not open Audio\n");
|
|
424 }
|
|
425
|
|
426 }
|
|
427
|
|
428 static void
|
|
429 wv_load_config()
|
|
430 {
|
|
431 ConfigDb *cfg;
|
|
432
|
|
433 cfg = bmp_cfg_db_open();
|
|
434
|
|
435 bmp_cfg_db_get_bool(cfg, "wavpack", "clip_prevention",
|
|
436 &clipPreventionEnabled);
|
|
437 bmp_cfg_db_get_bool(cfg, "wavpack", "album_replaygain",
|
|
438 &albumReplaygainEnabled);
|
|
439 bmp_cfg_db_get_bool(cfg, "wavpack", "dyn_bitrate", &dynBitrateEnabled);
|
|
440 bmp_cfg_db_get_bool(cfg, "wavpack", "replaygain", &replaygainEnabled);
|
|
441 bmp_cfg_db_close(cfg);
|
|
442
|
|
443 openedAudio = false;
|
|
444 }
|