61
|
1 /* BMP - Cross-platform multimedia player
|
|
2 * Copyright (C) 2003-2004 BMP development team.
|
|
3 *
|
|
4 * Based on XMMS:
|
|
5 * Copyright (C) 1998-2003 XMMS development team.
|
|
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 *
|
|
17 * You should have received a copy of the GNU General Public License
|
|
18 * along with this program; if not, write to the Free Software
|
|
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
20 */
|
|
21
|
|
22 #include "wav.h"
|
|
23
|
|
24 #include <glib.h>
|
|
25 #include <glib/gi18n.h>
|
|
26 #include <string.h>
|
|
27
|
|
28 #include <libaudacious/util.h>
|
|
29 #include <libaudacious/titlestring.h>
|
|
30 #include "audacious/output.h"
|
|
31
|
|
32
|
|
33 InputPlugin wav_ip = {
|
|
34 NULL,
|
|
35 NULL,
|
|
36 NULL, /* Description */
|
|
37 wav_init,
|
|
38 NULL,
|
|
39 NULL,
|
|
40 is_our_file,
|
|
41 NULL,
|
|
42 play_file,
|
|
43 stop,
|
|
44 wav_pause,
|
|
45 seek,
|
|
46 NULL,
|
|
47 get_time,
|
|
48 NULL,
|
|
49 NULL,
|
|
50 NULL,
|
|
51 NULL,
|
|
52 NULL,
|
|
53 NULL,
|
|
54 NULL,
|
|
55 get_song_info,
|
|
56 NULL, /* file_info_box */
|
|
57 NULL
|
|
58 };
|
|
59
|
|
60 WaveFile *wav_file = NULL;
|
|
61 static GThread *decode_thread;
|
|
62 static gboolean audio_error = FALSE;
|
|
63
|
|
64 InputPlugin *
|
|
65 get_iplugin_info(void)
|
|
66 {
|
|
67 wav_ip.description = g_strdup_printf(_("WAV Audio Plugin"));
|
|
68 return &wav_ip;
|
|
69 }
|
|
70
|
|
71 static void
|
|
72 wav_init(void)
|
|
73 {
|
|
74 /* empty */
|
|
75 }
|
|
76
|
|
77 /* needed for is_our_file() */
|
|
78 static gint
|
|
79 read_n_bytes(VFSFile * file, guint8 * buf, gint n)
|
|
80 {
|
|
81 if (vfs_fread(buf, 1, n, file) != n) {
|
|
82 return FALSE;
|
|
83 }
|
|
84 return TRUE;
|
|
85 }
|
|
86
|
|
87 static guint32
|
|
88 convert_to_header(guint8 * buf)
|
|
89 {
|
|
90
|
|
91 return (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3];
|
|
92 }
|
|
93
|
|
94 static guint32
|
|
95 convert_to_long(guint8 * buf)
|
|
96 {
|
|
97
|
|
98 return (buf[3] << 24) + (buf[2] << 16) + (buf[1] << 8) + buf[0];
|
|
99 }
|
|
100
|
|
101 static guint16
|
|
102 read_wav_id(gchar * filename)
|
|
103 {
|
|
104 VFSFile *file;
|
|
105 guint16 wavid;
|
|
106 guint8 buf[4];
|
|
107 guint32 head;
|
|
108 glong seek;
|
|
109
|
|
110 if (!(file = vfs_fopen(filename, "rb"))) { /* Could not open file */
|
|
111 return 0;
|
|
112 }
|
|
113 if (!(read_n_bytes(file, buf, 4))) {
|
|
114 vfs_fclose(file);
|
|
115 return 0;
|
|
116 }
|
|
117 head = convert_to_header(buf);
|
|
118 if (head == ('R' << 24) + ('I' << 16) + ('F' << 8) + 'F') { /* Found a riff -- maybe WAVE */
|
|
119 if (vfs_fseek(file, 4, SEEK_CUR) != 0) { /* some error occured */
|
|
120 vfs_fclose(file);
|
|
121 return 0;
|
|
122 }
|
|
123 if (!(read_n_bytes(file, buf, 4))) {
|
|
124 vfs_fclose(file);
|
|
125 return 0;
|
|
126 }
|
|
127 head = convert_to_header(buf);
|
|
128 if (head == ('W' << 24) + ('A' << 16) + ('V' << 8) + 'E') { /* Found a WAVE */
|
|
129 seek = 0;
|
|
130 do { /* we'll be looking for the fmt-chunk which comes before the data-chunk */
|
|
131 /* A chunk consists of an header identifier (4 bytes), the length of the chunk
|
|
132 (4 bytes), and the chunkdata itself, padded to be an even number of bytes.
|
|
133 We'll skip all chunks until we find the "data"-one which could contain
|
|
134 mpeg-data */
|
|
135 if (seek != 0) {
|
|
136 if (vfs_fseek(file, seek, SEEK_CUR) != 0) { /* some error occured */
|
|
137 vfs_fclose(file);
|
|
138 return 0;
|
|
139 }
|
|
140 }
|
|
141 if (!(read_n_bytes(file, buf, 4))) {
|
|
142 vfs_fclose(file);
|
|
143 return 0;
|
|
144 }
|
|
145 head = convert_to_header(buf);
|
|
146 if (!(read_n_bytes(file, buf, 4))) {
|
|
147 vfs_fclose(file);
|
|
148 return 0;
|
|
149 }
|
|
150 seek = convert_to_long(buf);
|
|
151 seek = seek + (seek % 2); /* Has to be even (padding) */
|
|
152 if (seek >= 2
|
|
153 && head == ('f' << 24) + ('m' << 16) + ('t' << 8) + ' ') {
|
|
154 if (!(read_n_bytes(file, buf, 2))) {
|
|
155 vfs_fclose(file);
|
|
156 return 0;
|
|
157 }
|
|
158 wavid = buf[0] + 256 * buf[1];
|
|
159 seek -= 2;
|
|
160 /* we could go on looking for other things, but all we wanted was the wavid */
|
|
161 vfs_fclose(file);
|
|
162 return wavid;
|
|
163 }
|
|
164 }
|
|
165 while (head != ('d' << 24) + ('a' << 16) + ('t' << 8) + 'a');
|
|
166 /* it's RIFF WAVE */
|
|
167 }
|
|
168 /* it's RIFF */
|
|
169 }
|
|
170 /* it's not even RIFF */
|
|
171 vfs_fclose(file);
|
|
172 return 0;
|
|
173 }
|
|
174
|
|
175 static const gchar *
|
|
176 get_extension(const gchar * filename)
|
|
177 {
|
|
178 const gchar *ext = strrchr(filename, '.');
|
|
179 return ext ? ext + 1 : NULL;
|
|
180 }
|
|
181
|
|
182 static gboolean
|
|
183 is_our_file(gchar * filename)
|
|
184 {
|
|
185 gchar *ext;
|
|
186
|
|
187 ext = strrchr(filename, '.');
|
|
188 if (ext)
|
|
189 if (!strcasecmp(ext, ".wav"))
|
|
190 if (read_wav_id(filename) == WAVE_FORMAT_PCM)
|
|
191 return TRUE;
|
|
192 return FALSE;
|
|
193 }
|
|
194
|
|
195
|
|
196 static gchar *
|
|
197 get_title(const gchar * filename)
|
|
198 {
|
|
199 TitleInput *input;
|
|
200 gchar *title;
|
|
201
|
|
202 input = bmp_title_input_new();
|
|
203
|
|
204 input->file_name = g_path_get_basename(filename);
|
|
205 input->file_ext = get_extension(filename);
|
|
206 input->file_path = g_path_get_dirname(filename);
|
|
207
|
|
208 if (!(title = xmms_get_titlestring(xmms_get_gentitle_format(), input)))
|
|
209 title = g_strdup(input->file_name);
|
|
210
|
|
211 g_free(input->file_path);
|
|
212 g_free(input->file_name);
|
|
213 g_free(input);
|
|
214
|
|
215 return title;
|
|
216 }
|
|
217
|
|
218 static gint
|
|
219 read_le_long(VFSFile * file, glong * ret)
|
|
220 {
|
|
221 guchar buf[4];
|
|
222
|
|
223 if (vfs_fread(buf, 1, 4, file) != 4)
|
|
224 return 0;
|
|
225
|
|
226 *ret = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
|
|
227 return TRUE;
|
|
228 }
|
|
229
|
|
230 #define read_le_ulong(file,ret) read_le_long(file,(long*)ret)
|
|
231
|
|
232 static int
|
|
233 read_le_short(VFSFile * file, gshort * ret)
|
|
234 {
|
|
235 guchar buf[2];
|
|
236
|
|
237 if (vfs_fread(buf, 1, 2, file) != 2)
|
|
238 return 0;
|
|
239
|
|
240 *ret = (buf[1] << 8) | buf[0];
|
|
241 return TRUE;
|
|
242 }
|
|
243
|
|
244 static gpointer
|
|
245 play_loop(gpointer arg)
|
|
246 {
|
|
247 gchar data[2048 * 2];
|
|
248 gint bytes, blk_size, rate;
|
|
249 gint actual_read;
|
|
250
|
|
251 blk_size = 512 * (wav_file->bits_per_sample / 8) * wav_file->channels;
|
|
252 rate =
|
|
253 wav_file->samples_per_sec * wav_file->channels *
|
|
254 (wav_file->bits_per_sample / 8);
|
|
255 while (wav_file->going) {
|
|
256 if (!wav_file->eof) {
|
|
257 bytes = blk_size;
|
|
258 if (wav_file->length - wav_file->position < bytes)
|
|
259 bytes = wav_file->length - wav_file->position;
|
|
260 if (bytes > 0) {
|
|
261 actual_read = vfs_fread(data, 1, bytes, wav_file->file);
|
|
262
|
|
263 if (actual_read == 0) {
|
|
264 wav_file->eof = 1;
|
|
265 wav_ip.output->buffer_free();
|
|
266 wav_ip.output->buffer_free();
|
|
267 }
|
|
268 else {
|
|
269 if (wav_file->seek_to == -1)
|
|
270 produce_audio(wav_ip.output->written_time(),
|
|
271 (wav_file->bits_per_sample ==
|
|
272 16) ? FMT_S16_LE : FMT_U8,
|
|
273 wav_file->channels, bytes, data,
|
|
274 &wav_file->going);
|
|
275 wav_file->position += actual_read;
|
|
276 }
|
|
277 }
|
|
278 else {
|
|
279 wav_file->eof = TRUE;
|
|
280 wav_ip.output->buffer_free();
|
|
281 wav_ip.output->buffer_free();
|
|
282 xmms_usleep(10000);
|
|
283 }
|
|
284 }
|
|
285 else
|
|
286 xmms_usleep(10000);
|
|
287 if (wav_file->seek_to != -1) {
|
|
288 wav_file->position = wav_file->seek_to * rate;
|
|
289 vfs_fseek(wav_file->file,
|
|
290 wav_file->position + wav_file->data_offset, SEEK_SET);
|
|
291 wav_ip.output->flush(wav_file->seek_to * 1000);
|
|
292 wav_file->seek_to = -1;
|
|
293 }
|
|
294
|
|
295 }
|
|
296 vfs_fclose(wav_file->file);
|
|
297
|
|
298 g_thread_exit(NULL);
|
|
299 return NULL;
|
|
300 }
|
|
301
|
|
302 static void
|
|
303 play_file(gchar * filename)
|
|
304 {
|
|
305 gchar magic[4], *name;
|
|
306 gulong len;
|
|
307 gint rate;
|
|
308
|
|
309 audio_error = FALSE;
|
|
310
|
|
311 wav_file = g_new0(WaveFile, 1);
|
|
312 if ((wav_file->file = vfs_fopen(filename, "rb"))) {
|
|
313 vfs_fread(magic, 1, 4, wav_file->file);
|
|
314 if (strncmp(magic, "RIFF", 4)) {
|
|
315 vfs_fclose(wav_file->file);
|
|
316 g_free(wav_file);
|
|
317 wav_file = NULL;
|
|
318 return;
|
|
319 }
|
|
320 read_le_ulong(wav_file->file, &len);
|
|
321 vfs_fread(magic, 1, 4, wav_file->file);
|
|
322 if (strncmp(magic, "WAVE", 4)) {
|
|
323 vfs_fclose(wav_file->file);
|
|
324 g_free(wav_file);
|
|
325 wav_file = NULL;
|
|
326 return;
|
|
327 }
|
|
328 for (;;) {
|
|
329 vfs_fread(magic, 1, 4, wav_file->file);
|
|
330 if (!read_le_ulong(wav_file->file, &len)) {
|
|
331 vfs_fclose(wav_file->file);
|
|
332 g_free(wav_file);
|
|
333 wav_file = NULL;
|
|
334 return;
|
|
335 }
|
|
336 if (!strncmp("fmt ", magic, 4))
|
|
337 break;
|
|
338 vfs_fseek(wav_file->file, len, SEEK_CUR);
|
|
339 }
|
|
340 if (len < 16) {
|
|
341 vfs_fclose(wav_file->file);
|
|
342 g_free(wav_file);
|
|
343 wav_file = NULL;
|
|
344 return;
|
|
345 }
|
|
346 read_le_short(wav_file->file, &wav_file->format_tag);
|
|
347 switch (wav_file->format_tag) {
|
|
348 case WAVE_FORMAT_UNKNOWN:
|
|
349 case WAVE_FORMAT_ALAW:
|
|
350 case WAVE_FORMAT_MULAW:
|
|
351 case WAVE_FORMAT_ADPCM:
|
|
352 case WAVE_FORMAT_OKI_ADPCM:
|
|
353 case WAVE_FORMAT_DIGISTD:
|
|
354 case WAVE_FORMAT_DIGIFIX:
|
|
355 case IBM_FORMAT_MULAW:
|
|
356 case IBM_FORMAT_ALAW:
|
|
357 case IBM_FORMAT_ADPCM:
|
|
358 vfs_fclose(wav_file->file);
|
|
359 g_free(wav_file);
|
|
360 wav_file = NULL;
|
|
361 return;
|
|
362 }
|
|
363 read_le_short(wav_file->file, &wav_file->channels);
|
|
364 read_le_long(wav_file->file, &wav_file->samples_per_sec);
|
|
365 read_le_long(wav_file->file, &wav_file->avg_bytes_per_sec);
|
|
366 read_le_short(wav_file->file, &wav_file->block_align);
|
|
367 read_le_short(wav_file->file, &wav_file->bits_per_sample);
|
|
368 if (wav_file->bits_per_sample != 8 && wav_file->bits_per_sample != 16) {
|
|
369 vfs_fclose(wav_file->file);
|
|
370 g_free(wav_file);
|
|
371 wav_file = NULL;
|
|
372 return;
|
|
373 }
|
|
374 len -= 16;
|
|
375 if (len)
|
|
376 vfs_fseek(wav_file->file, len, SEEK_CUR);
|
|
377
|
|
378 for (;;) {
|
|
379 vfs_fread(magic, 4, 1, wav_file->file);
|
|
380
|
|
381 if (!read_le_ulong(wav_file->file, &len)) {
|
|
382 vfs_fclose(wav_file->file);
|
|
383 g_free(wav_file);
|
|
384 wav_file = NULL;
|
|
385 return;
|
|
386 }
|
|
387 if (!strncmp("data", magic, 4))
|
|
388 break;
|
|
389 vfs_fseek(wav_file->file, len, SEEK_CUR);
|
|
390 }
|
|
391 wav_file->data_offset = vfs_ftell(wav_file->file);
|
|
392 wav_file->length = len;
|
|
393
|
|
394 wav_file->position = 0;
|
|
395 wav_file->going = 1;
|
|
396
|
|
397 if (wav_ip.output->
|
|
398 open_audio((wav_file->bits_per_sample ==
|
|
399 16) ? FMT_S16_LE : FMT_U8,
|
|
400 wav_file->samples_per_sec, wav_file->channels) == 0) {
|
|
401 audio_error = TRUE;
|
|
402 vfs_fclose(wav_file->file);
|
|
403 g_free(wav_file);
|
|
404 wav_file = NULL;
|
|
405 return;
|
|
406 }
|
|
407 name = get_title(filename);
|
|
408 rate =
|
|
409 wav_file->samples_per_sec * wav_file->channels *
|
|
410 (wav_file->bits_per_sample / 8);
|
|
411 wav_ip.set_info(name, 1000 * (wav_file->length / rate), 8 * rate,
|
|
412 wav_file->samples_per_sec, wav_file->channels);
|
|
413 g_free(name);
|
|
414 wav_file->seek_to = -1;
|
|
415 decode_thread = g_thread_create(play_loop, NULL, TRUE, NULL);
|
|
416 }
|
|
417 }
|
|
418
|
|
419 static void
|
|
420 stop(void)
|
|
421 {
|
|
422 if (wav_file && wav_file->going) {
|
|
423 wav_file->going = 0;
|
|
424 g_thread_join(decode_thread);
|
|
425 wav_ip.output->close_audio();
|
|
426 g_free(wav_file);
|
|
427 wav_file = NULL;
|
|
428 }
|
|
429 }
|
|
430
|
|
431 static void
|
|
432 wav_pause(gshort p)
|
|
433 {
|
|
434 wav_ip.output->pause(p);
|
|
435 }
|
|
436
|
|
437 static void
|
|
438 seek(gint time)
|
|
439 {
|
|
440 wav_file->seek_to = time;
|
|
441
|
|
442 wav_file->eof = FALSE;
|
|
443
|
|
444 while (wav_file->seek_to != -1)
|
|
445 xmms_usleep(10000);
|
|
446 }
|
|
447
|
|
448 static int
|
|
449 get_time(void)
|
|
450 {
|
|
451 if (audio_error)
|
|
452 return -2;
|
|
453 if (!wav_file)
|
|
454 return -1;
|
|
455 if (!wav_file->going
|
|
456 || (wav_file->eof && !wav_ip.output->buffer_playing()))
|
|
457 return -1;
|
|
458 else {
|
|
459 return wav_ip.output->output_time();
|
|
460 }
|
|
461 }
|
|
462
|
|
463 static void
|
|
464 get_song_info(gchar * filename, gchar ** title, gint * length)
|
|
465 {
|
|
466 gchar magic[4];
|
|
467 gulong len;
|
|
468 gint rate;
|
|
469 WaveFile *wav_file;
|
|
470
|
|
471 wav_file = g_malloc(sizeof(WaveFile));
|
|
472 memset(wav_file, 0, sizeof(WaveFile));
|
|
473 if (!(wav_file->file = vfs_fopen(filename, "rb")))
|
|
474 return;
|
|
475
|
|
476 vfs_fread(magic, 1, 4, wav_file->file);
|
|
477 if (strncmp(magic, "RIFF", 4)) {
|
|
478 vfs_fclose(wav_file->file);
|
|
479 g_free(wav_file);
|
|
480 wav_file = NULL;
|
|
481 return;
|
|
482 }
|
|
483 read_le_ulong(wav_file->file, &len);
|
|
484 vfs_fread(magic, 1, 4, wav_file->file);
|
|
485 if (strncmp(magic, "WAVE", 4)) {
|
|
486 vfs_fclose(wav_file->file);
|
|
487 g_free(wav_file);
|
|
488 wav_file = NULL;
|
|
489 return;
|
|
490 }
|
|
491 for (;;) {
|
|
492 vfs_fread(magic, 1, 4, wav_file->file);
|
|
493 if (!read_le_ulong(wav_file->file, &len)) {
|
|
494 vfs_fclose(wav_file->file);
|
|
495 g_free(wav_file);
|
|
496 wav_file = NULL;
|
|
497 return;
|
|
498 }
|
|
499 if (!strncmp("fmt ", magic, 4))
|
|
500 break;
|
|
501 vfs_fseek(wav_file->file, len, SEEK_CUR);
|
|
502 }
|
|
503 if (len < 16) {
|
|
504 vfs_fclose(wav_file->file);
|
|
505 g_free(wav_file);
|
|
506 wav_file = NULL;
|
|
507 return;
|
|
508 }
|
|
509 read_le_short(wav_file->file, &wav_file->format_tag);
|
|
510 switch (wav_file->format_tag) {
|
|
511 case WAVE_FORMAT_UNKNOWN:
|
|
512 case WAVE_FORMAT_ALAW:
|
|
513 case WAVE_FORMAT_MULAW:
|
|
514 case WAVE_FORMAT_ADPCM:
|
|
515 case WAVE_FORMAT_OKI_ADPCM:
|
|
516 case WAVE_FORMAT_DIGISTD:
|
|
517 case WAVE_FORMAT_DIGIFIX:
|
|
518 case IBM_FORMAT_MULAW:
|
|
519 case IBM_FORMAT_ALAW:
|
|
520 case IBM_FORMAT_ADPCM:
|
|
521 vfs_fclose(wav_file->file);
|
|
522 g_free(wav_file);
|
|
523 wav_file = NULL;
|
|
524 return;
|
|
525 }
|
|
526 read_le_short(wav_file->file, &wav_file->channels);
|
|
527 read_le_long(wav_file->file, &wav_file->samples_per_sec);
|
|
528 read_le_long(wav_file->file, &wav_file->avg_bytes_per_sec);
|
|
529 read_le_short(wav_file->file, &wav_file->block_align);
|
|
530 read_le_short(wav_file->file, &wav_file->bits_per_sample);
|
|
531 if (wav_file->bits_per_sample != 8 && wav_file->bits_per_sample != 16) {
|
|
532 vfs_fclose(wav_file->file);
|
|
533 g_free(wav_file);
|
|
534 wav_file = NULL;
|
|
535 return;
|
|
536 }
|
|
537 len -= 16;
|
|
538 if (len)
|
|
539 vfs_fseek(wav_file->file, len, SEEK_CUR);
|
|
540
|
|
541 for (;;) {
|
|
542 vfs_fread(magic, 4, 1, wav_file->file);
|
|
543
|
|
544 if (!read_le_ulong(wav_file->file, &len)) {
|
|
545 vfs_fclose(wav_file->file);
|
|
546 g_free(wav_file);
|
|
547 wav_file = NULL;
|
|
548 return;
|
|
549 }
|
|
550 if (!strncmp("data", magic, 4))
|
|
551 break;
|
|
552 vfs_fseek(wav_file->file, len, SEEK_CUR);
|
|
553 }
|
|
554 rate =
|
|
555 wav_file->samples_per_sec * wav_file->channels *
|
|
556 (wav_file->bits_per_sample / 8);
|
|
557 (*length) = 1000 * (len / rate);
|
|
558 (*title) = get_title(filename);
|
|
559
|
|
560 vfs_fclose(wav_file->file);
|
|
561 g_free(wav_file);
|
|
562 wav_file = NULL;
|
|
563 }
|