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