summaryrefslogtreecommitdiff
path: root/lib/rbcodec/test/warble.c
diff options
context:
space:
mode:
authorSean Bartell <wingedtachikoma@gmail.com>2011-06-17 00:49:54 -0400
committerNils Wallménius <nils@rockbox.org>2012-03-03 16:41:49 +0100
commit26fc31ae935c431ed0d2eb7786615901b4ea090d (patch)
treec8fe5a98f44ef6676d0c27bccf097ffbd3d2486c /lib/rbcodec/test/warble.c
parent693af46f6de65012828a621ab07573096857ab64 (diff)
downloadrockbox-26fc31ae935c431ed0d2eb7786615901b4ea090d.tar.gz
rockbox-26fc31ae935c431ed0d2eb7786615901b4ea090d.zip
Add the warble test program.
Warble uses Rockbox's codecs to play files with SDL or convert them to WAV or raw formats. It also prints metadata and supports some of the DSP effects. In the future, warble could be used to implement an automated test suite for codecs, metadata, and DSP. Change-Id: Ife1a63d2354496016277bfcbae4a9c23423ebd86 Reviewed-on: http://gerrit.rockbox.org/135 Reviewed-by: Nils Wallménius <nils@rockbox.org> Tested-by: Nils Wallménius <nils@rockbox.org>
Diffstat (limited to 'lib/rbcodec/test/warble.c')
-rw-r--r--lib/rbcodec/test/warble.c837
1 files changed, 837 insertions, 0 deletions
diff --git a/lib/rbcodec/test/warble.c b/lib/rbcodec/test/warble.c
new file mode 100644
index 0000000000..2cba6c0d59
--- /dev/null
+++ b/lib/rbcodec/test/warble.c
@@ -0,0 +1,837 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2011 Sean Bartell
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20
21#define _BSD_SOURCE /* htole64 from endian.h */
22#include <sys/types.h>
23#include <SDL.h>
24#include <dlfcn.h>
25#include <endian.h>
26#include <fcntl.h>
27#include <math.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <sys/stat.h>
33#include <unistd.h>
34#include "buffering.h" /* TYPE_PACKET_AUDIO */
35#include "codecs.h"
36#include "core_alloc.h" /* core_allocator_init */
37#include "debug.h"
38#include "dsp.h"
39#include "metadata.h"
40#include "settings.h"
41#include "sound.h"
42#include "tdspeed.h"
43
44/***************** EXPORTED *****************/
45
46struct user_settings global_settings;
47volatile long current_tick = 0;
48
49void yield(void)
50{
51}
52
53int set_irq_level(int level)
54{
55 return 0;
56}
57
58void mutex_init(struct mutex *m)
59{
60}
61
62void mutex_lock(struct mutex *m)
63{
64}
65
66void mutex_unlock(struct mutex *m)
67{
68}
69
70void debugf(const char *fmt, ...)
71{
72 va_list ap;
73 va_start(ap, fmt);
74 vfprintf(stderr, fmt, ap);
75 va_end(ap);
76}
77
78/***************** INTERNAL *****************/
79
80static enum { MODE_PLAY, MODE_WRITE } mode;
81static bool use_dsp = true;
82static bool enable_loop = false;
83static const char *config = "";
84
85static int input_fd;
86static enum codec_command_action codec_action;
87static intptr_t codec_action_param = 0;
88static unsigned long num_output_samples = 0;
89static struct codec_api ci;
90
91static struct {
92 intptr_t freq;
93 intptr_t stereo_mode;
94 intptr_t depth;
95 int channels;
96} format;
97
98/***** MODE_WRITE *****/
99
100#define WAVE_HEADER_SIZE 0x2e
101#define WAVE_FORMAT_PCM 1
102#define WAVE_FORMAT_IEEE_FLOAT 3
103static int output_fd;
104static bool write_raw = false;
105static bool write_header_written = false;
106
107static void write_init(const char *output_fn)
108{
109 mode = MODE_WRITE;
110 if (!strcmp(output_fn, "-")) {
111 output_fd = STDOUT_FILENO;
112 } else {
113 output_fd = creat(output_fn, 0666);
114 if (output_fd == -1) {
115 perror(output_fn);
116 exit(1);
117 }
118 }
119}
120
121static void set_le16(char *buf, uint16_t val)
122{
123 buf[0] = val;
124 buf[1] = val >> 8;
125}
126
127static void set_le32(char *buf, uint32_t val)
128{
129 buf[0] = val;
130 buf[1] = val >> 8;
131 buf[2] = val >> 16;
132 buf[3] = val >> 24;
133}
134
135static void write_wav_header(void)
136{
137 int channels, sample_size, freq, type;
138 if (use_dsp) {
139 channels = 2;
140 sample_size = 16;
141 freq = NATIVE_FREQUENCY;
142 type = WAVE_FORMAT_PCM;
143 } else {
144 channels = format.channels;
145 sample_size = 64;
146 freq = format.freq;
147 type = WAVE_FORMAT_IEEE_FLOAT;
148 }
149
150 /* The size fields are normally overwritten by write_quit(). If that fails,
151 * this fake size ensures the file can still be played. */
152 off_t total_size = 0x7fffff00 + WAVE_HEADER_SIZE;
153 char header[WAVE_HEADER_SIZE] = {"RIFF____WAVEfmt \x12\0\0\0"
154 "________________\0\0data____"};
155 set_le32(header + 0x04, total_size - 8);
156 set_le16(header + 0x14, type);
157 set_le16(header + 0x16, channels);
158 set_le32(header + 0x18, freq);
159 set_le32(header + 0x1c, freq * channels * sample_size / 8);
160 set_le16(header + 0x20, channels * sample_size / 8);
161 set_le16(header + 0x22, sample_size);
162 set_le32(header + 0x2a, total_size - WAVE_HEADER_SIZE);
163 write(output_fd, header, sizeof(header));
164 write_header_written = true;
165}
166
167static void write_quit(void)
168{
169 if (!write_raw) {
170 /* Write the correct size fields in the header. If lseek fails (e.g.
171 * for a pipe) nothing is written. */
172 off_t total_size = lseek(output_fd, 0, SEEK_CUR);
173 if (total_size != (off_t)-1) {
174 char buf[4];
175 set_le32(buf, total_size - 8);
176 lseek(output_fd, 4, SEEK_SET);
177 write(output_fd, buf, 4);
178 set_le32(buf, total_size - WAVE_HEADER_SIZE);
179 lseek(output_fd, 0x2a, SEEK_SET);
180 write(output_fd, buf, 4);
181 }
182 }
183 if (output_fd != STDOUT_FILENO)
184 close(output_fd);
185}
186
187static uint64_t make_float64(int32_t sample, int shift)
188{
189 /* TODO: be more portable */
190 double val = ldexp(sample, -shift);
191 return *(uint64_t*)&val;
192}
193
194static void write_pcm(int16_t *pcm, int count)
195{
196 if (!write_header_written)
197 write_wav_header();
198 int i;
199 for (i = 0; i < 2 * count; i++)
200 pcm[i] = htole16(pcm[i]);
201 write(output_fd, pcm, 4 * count);
202}
203
204static void write_pcm_raw(int32_t *pcm, int count)
205{
206 if (write_raw) {
207 write(output_fd, pcm, count * sizeof(*pcm));
208 } else {
209 if (!write_header_written)
210 write_wav_header();
211 int i;
212 uint64_t buf[count];
213
214 for (i = 0; i < count; i++)
215 buf[i] = htole64(make_float64(pcm[i], format.depth));
216 write(output_fd, buf, count * sizeof(*buf));
217 }
218}
219
220/***** MODE_PLAY *****/
221
222/* MODE_PLAY uses a double buffer: one half is read by the playback thread and
223 * the other half is written to by the main thread. When a thread is done with
224 * its current half, it waits for the other thread and then switches. The main
225 * advantage of this method is its simplicity; the main disadvantage is that it
226 * has long latency. ALSA buffer underruns still occur sometimes, but this is
227 * SDL's fault. */
228
229#define PLAYBACK_BUFFER_SIZE 0x10000
230static bool playback_running = false;
231static char playback_buffer[2][PLAYBACK_BUFFER_SIZE];
232static int playback_play_ind, playback_decode_ind;
233static int playback_play_pos, playback_decode_pos;
234static SDL_sem *playback_play_sema, *playback_decode_sema;
235
236static void playback_init(void)
237{
238 mode = MODE_PLAY;
239 if (SDL_Init(SDL_INIT_AUDIO)) {
240 fprintf(stderr, "error: Can't initialize SDL: %s\n", SDL_GetError());
241 exit(1);
242 }
243 playback_play_ind = 1;
244 playback_play_pos = PLAYBACK_BUFFER_SIZE;
245 playback_decode_ind = 0;
246 playback_decode_pos = 0;
247 playback_play_sema = SDL_CreateSemaphore(0);
248 playback_decode_sema = SDL_CreateSemaphore(0);
249}
250
251static void playback_callback(void *userdata, Uint8 *stream, int len)
252{
253 while (len > 0) {
254 if (!playback_running && playback_play_ind == playback_decode_ind
255 && playback_play_pos >= playback_decode_pos) {
256 /* end of data */
257 memset(stream, 0, len);
258 SDL_SemPost(playback_play_sema);
259 return;
260 }
261 if (playback_play_pos >= PLAYBACK_BUFFER_SIZE) {
262 SDL_SemPost(playback_play_sema);
263 SDL_SemWait(playback_decode_sema);
264 playback_play_ind = !playback_play_ind;
265 playback_play_pos = 0;
266 }
267 char *play_buffer = playback_buffer[playback_play_ind];
268 int copy_len = MIN(len, PLAYBACK_BUFFER_SIZE - playback_play_pos);
269 memcpy(stream, play_buffer + playback_play_pos, copy_len);
270 len -= copy_len;
271 stream += copy_len;
272 playback_play_pos += copy_len;
273 }
274}
275
276static void playback_start(void)
277{
278 playback_running = true;
279 SDL_AudioSpec spec = {0};
280 spec.freq = NATIVE_FREQUENCY;
281 spec.format = AUDIO_S16SYS;
282 spec.channels = 2;
283 spec.samples = 0x400;
284 spec.callback = playback_callback;
285 spec.userdata = NULL;
286 if (SDL_OpenAudio(&spec, NULL)) {
287 fprintf(stderr, "error: Can't open SDL audio: %s\n", SDL_GetError());
288 exit(1);
289 }
290 SDL_PauseAudio(0);
291}
292
293static void playback_quit(void)
294{
295 if (!playback_running)
296 playback_start();
297 memset(playback_buffer[playback_decode_ind] + playback_decode_pos, 0,
298 PLAYBACK_BUFFER_SIZE - playback_decode_pos);
299 playback_running = false;
300 SDL_SemPost(playback_decode_sema);
301 SDL_SemWait(playback_play_sema);
302 SDL_SemWait(playback_play_sema);
303 SDL_Quit();
304}
305
306static void playback_pcm(int16_t *pcm, int count)
307{
308 const char *stream = (const char *)pcm;
309 count *= 4;
310
311 while (count > 0) {
312 if (playback_decode_pos >= PLAYBACK_BUFFER_SIZE) {
313 if (!playback_running)
314 playback_start();
315 SDL_SemPost(playback_decode_sema);
316 SDL_SemWait(playback_play_sema);
317 playback_decode_ind = !playback_decode_ind;
318 playback_decode_pos = 0;
319 }
320 char *decode_buffer = playback_buffer[playback_decode_ind];
321 int copy_len = MIN(count, PLAYBACK_BUFFER_SIZE - playback_decode_pos);
322 memcpy(decode_buffer + playback_decode_pos, stream, copy_len);
323 stream += copy_len;
324 count -= copy_len;
325 playback_decode_pos += copy_len;
326 }
327}
328
329/***** ALL MODES *****/
330
331static void perform_config(void)
332{
333 /* TODO: equalizer, etc. */
334 while (config) {
335 const char *name = config;
336 const char *eq = strchr(config, '=');
337 if (!eq)
338 break;
339 const char *val = eq + 1;
340 const char *end = val + strcspn(val, ": \t\n");
341
342 if (!strncmp(name, "wait=", 5)) {
343 if (atoi(val) > num_output_samples)
344 return;
345 } else if (!strncmp(name, "dither=", 7)) {
346 dsp_dither_enable(atoi(val) ? true : false);
347 } else if (!strncmp(name, "halt=", 5)) {
348 if (atoi(val))
349 codec_action = CODEC_ACTION_HALT;
350 } else if (!strncmp(name, "loop=", 5)) {
351 enable_loop = atoi(val) != 0;
352 } else if (!strncmp(name, "offset=", 7)) {
353 ci.id3->offset = atoi(val);
354 } else if (!strncmp(name, "rate=", 5)) {
355 sound_set_pitch(atof(val) * PITCH_SPEED_100);
356 } else if (!strncmp(name, "seek=", 5)) {
357 codec_action = CODEC_ACTION_SEEK_TIME;
358 codec_action_param = atoi(val);
359 } else if (!strncmp(name, "tempo=", 6)) {
360 dsp_set_timestretch(atof(val) * PITCH_SPEED_100);
361 } else if (!strncmp(name, "vol=", 4)) {
362 global_settings.volume = atoi(val);
363 dsp_callback(DSP_CALLBACK_SET_SW_VOLUME, 0);
364 } else {
365 fprintf(stderr, "error: unrecognized config \"%.*s\"\n",
366 (int)(eq - name), name);
367 exit(1);
368 }
369
370 if (*end)
371 config = end + 1;
372 else
373 config = NULL;
374 }
375}
376
377static void *ci_codec_get_buffer(size_t *size)
378{
379 static char buffer[64 * 1024 * 1024];
380 char *ptr = buffer;
381 *size = sizeof(buffer);
382 if ((intptr_t)ptr & (CACHEALIGN_SIZE - 1))
383 ptr += CACHEALIGN_SIZE - ((intptr_t)ptr & (CACHEALIGN_SIZE - 1));
384 return ptr;
385}
386
387static void ci_pcmbuf_insert(const void *ch1, const void *ch2, int count)
388{
389 num_output_samples += count;
390
391 if (use_dsp) {
392 const char *src[2] = {ch1, ch2};
393 while (count > 0) {
394 int out_count = dsp_output_count(ci.dsp, count);
395 int in_count = MIN(dsp_input_count(ci.dsp, out_count), count);
396 int16_t buf[2 * out_count];
397 out_count = dsp_process(ci.dsp, (char *)buf, src, in_count);
398 if (mode == MODE_WRITE)
399 write_pcm(buf, out_count);
400 else if (mode == MODE_PLAY)
401 playback_pcm(buf, out_count);
402 count -= in_count;
403 }
404 } else {
405 /* Convert to 32-bit interleaved. */
406 count *= format.channels;
407 int i;
408 int32_t buf[count];
409 if (format.depth > 16) {
410 if (format.stereo_mode == STEREO_NONINTERLEAVED) {
411 for (i = 0; i < count; i += 2) {
412 buf[i+0] = ((int32_t*)ch1)[i/2];
413 buf[i+1] = ((int32_t*)ch2)[i/2];
414 }
415 } else {
416 memcpy(buf, ch1, sizeof(buf));
417 }
418 } else {
419 if (format.stereo_mode == STEREO_NONINTERLEAVED) {
420 for (i = 0; i < count; i += 2) {
421 buf[i+0] = ((int16_t*)ch1)[i/2];
422 buf[i+1] = ((int16_t*)ch2)[i/2];
423 }
424 } else {
425 for (i = 0; i < count; i++) {
426 buf[i] = ((int16_t*)ch1)[i];
427 }
428 }
429 }
430
431 if (mode == MODE_WRITE)
432 write_pcm_raw(buf, count);
433 }
434
435 perform_config();
436}
437
438static void ci_set_elapsed(unsigned long value)
439{
440 //debugf("Time elapsed: %lu\n", value);
441}
442
443static char *input_buffer = 0;
444
445/*
446 * Read part of the input file into a provided buffer.
447 *
448 * The entire size requested will be provided except at the end of the file.
449 * The current file position will be moved, just like with advance_buffer, but
450 * the offset is not updated. This invalidates buffers returned by
451 * request_buffer.
452 */
453static size_t ci_read_filebuf(void *ptr, size_t size)
454{
455 free(input_buffer);
456 input_buffer = NULL;
457
458 ssize_t actual = read(input_fd, ptr, size);
459 if (actual < 0)
460 actual = 0;
461 ci.curpos += actual;
462 return actual;
463}
464
465/*
466 * Request a buffer containing part of the input file.
467 *
468 * The size provided will be the requested size, or the remaining size of the
469 * file, whichever is smaller. Packet audio has an additional maximum of 32
470 * KiB. The returned buffer remains valid until the next time read_filebuf,
471 * request_buffer, advance_buffer, or seek_buffer is called.
472 */
473static void *ci_request_buffer(size_t *realsize, size_t reqsize)
474{
475 free(input_buffer);
476 if (get_audio_base_data_type(ci.id3->codectype) == TYPE_PACKET_AUDIO)
477 reqsize = MIN(reqsize, 32 * 1024);
478 input_buffer = malloc(reqsize);
479 *realsize = read(input_fd, input_buffer, reqsize);
480 if (*realsize < 0)
481 *realsize = 0;
482 lseek(input_fd, -*realsize, SEEK_CUR);
483 return input_buffer;
484}
485
486/*
487 * Advance the current position in the input file.
488 *
489 * This automatically updates the current offset. This invalidates buffers
490 * returned by request_buffer.
491 */
492static void ci_advance_buffer(size_t amount)
493{
494 free(input_buffer);
495 input_buffer = NULL;
496
497 lseek(input_fd, amount, SEEK_CUR);
498 ci.curpos += amount;
499 ci.id3->offset = ci.curpos;
500}
501
502/*
503 * Seek to a position in the input file.
504 *
505 * This invalidates buffers returned by request_buffer.
506 */
507static bool ci_seek_buffer(size_t newpos)
508{
509 free(input_buffer);
510 input_buffer = NULL;
511
512 off_t actual = lseek(input_fd, newpos, SEEK_SET);
513 if (actual >= 0)
514 ci.curpos = actual;
515 return actual != -1;
516}
517
518static void ci_seek_complete(void)
519{
520}
521
522static void ci_set_offset(size_t value)
523{
524 ci.id3->offset = value;
525}
526
527static void ci_configure(int setting, intptr_t value)
528{
529 if (use_dsp) {
530 dsp_configure(ci.dsp, setting, value);
531 } else {
532 if (setting == DSP_SET_FREQUENCY
533 || setting == DSP_SWITCH_FREQUENCY)
534 format.freq = value;
535 else if (setting == DSP_SET_SAMPLE_DEPTH)
536 format.depth = value;
537 else if (setting == DSP_SET_STEREO_MODE) {
538 format.stereo_mode = value;
539 format.channels = (value == STEREO_MONO) ? 1 : 2;
540 }
541 }
542}
543
544static enum codec_command_action ci_get_command(intptr_t *param)
545{
546 enum codec_command_action ret = codec_action;
547 *param = codec_action_param;
548 codec_action = CODEC_ACTION_NULL;
549 return ret;
550}
551
552static bool ci_should_loop(void)
553{
554 return enable_loop;
555}
556
557static unsigned ci_sleep(unsigned ticks)
558{
559 return 0;
560}
561
562static void ci_cpucache_flush(void)
563{
564}
565
566static void ci_cpucache_invalidate(void)
567{
568}
569
570static struct codec_api ci = {
571
572 0, /* filesize */
573 0, /* curpos */
574 NULL, /* id3 */
575 -1, /* audio_hid */
576 NULL, /* struct dsp_config *dsp */
577 ci_codec_get_buffer,
578 ci_pcmbuf_insert,
579 ci_set_elapsed,
580 ci_read_filebuf,
581 ci_request_buffer,
582 ci_advance_buffer,
583 ci_seek_buffer,
584 ci_seek_complete,
585 ci_set_offset,
586 ci_configure,
587 ci_get_command,
588 ci_should_loop,
589
590 ci_sleep,
591 yield,
592
593#if NUM_CORES > 1
594 ci_create_thread,
595 ci_thread_thaw,
596 ci_thread_wait,
597 ci_semaphore_init,
598 ci_semaphore_wait,
599 ci_semaphore_release,
600#endif
601
602 ci_cpucache_flush,
603 ci_cpucache_invalidate,
604
605 /* strings and memory */
606 strcpy,
607 strlen,
608 strcmp,
609 strcat,
610 memset,
611 memcpy,
612 memmove,
613 memcmp,
614 memchr,
615#if defined(DEBUG) || defined(SIMULATOR)
616 debugf,
617#endif
618#ifdef ROCKBOX_HAS_LOGF
619 debugf, /* logf */
620#endif
621
622 qsort,
623
624#ifdef HAVE_RECORDING
625 ci_enc_get_inputs,
626 ci_enc_set_parameters,
627 ci_enc_get_chunk,
628 ci_enc_finish_chunk,
629 ci_enc_get_pcm_data,
630 ci_enc_unget_pcm_data,
631
632 /* file */
633 open,
634 close,
635 read,
636 lseek,
637 write,
638 ci_round_value_to_list32,
639
640#endif /* HAVE_RECORDING */
641};
642
643static void print_mp3entry(const struct mp3entry *id3, FILE *f)
644{
645 fprintf(f, "Path: %s\n", id3->path);
646 if (id3->title) fprintf(f, "Title: %s\n", id3->title);
647 if (id3->artist) fprintf(f, "Artist: %s\n", id3->artist);
648 if (id3->album) fprintf(f, "Album: %s\n", id3->album);
649 if (id3->genre_string) fprintf(f, "Genre: %s\n", id3->genre_string);
650 if (id3->disc_string || id3->discnum) fprintf(f, "Disc: %s (%d)\n", id3->disc_string, id3->discnum);
651 if (id3->track_string || id3->tracknum) fprintf(f, "Track: %s (%d)\n", id3->track_string, id3->tracknum);
652 if (id3->year_string || id3->year) fprintf(f, "Year: %s (%d)\n", id3->year_string, id3->year);
653 if (id3->composer) fprintf(f, "Composer: %s\n", id3->composer);
654 if (id3->comment) fprintf(f, "Comment: %s\n", id3->comment);
655 if (id3->albumartist) fprintf(f, "Album artist: %s\n", id3->albumartist);
656 if (id3->grouping) fprintf(f, "Grouping: %s\n", id3->grouping);
657 if (id3->layer) fprintf(f, "Layer: %d\n", id3->layer);
658 if (id3->id3version) fprintf(f, "ID3 version: %u\n", (int)id3->id3version);
659 fprintf(f, "Codec: %s\n", audio_formats[id3->codectype].label);
660 fprintf(f, "Bitrate: %d kb/s\n", id3->bitrate);
661 fprintf(f, "Frequency: %lu Hz\n", id3->frequency);
662 if (id3->id3v2len) fprintf(f, "ID3v2 length: %lu\n", id3->id3v2len);
663 if (id3->id3v1len) fprintf(f, "ID3v1 length: %lu\n", id3->id3v1len);
664 if (id3->first_frame_offset) fprintf(f, "First frame offset: %lu\n", id3->first_frame_offset);
665 fprintf(f, "File size without headers: %lu\n", id3->filesize);
666 fprintf(f, "Song length: %lu ms\n", id3->length);
667 if (id3->lead_trim > 0 || id3->tail_trim > 0) fprintf(f, "Trim: %d/%d\n", id3->lead_trim, id3->tail_trim);
668 if (id3->samples) fprintf(f, "Number of samples: %lu\n", id3->samples);
669 if (id3->frame_count) fprintf(f, "Number of frames: %lu\n", id3->frame_count);
670 if (id3->bytesperframe) fprintf(f, "Bytes per frame: %lu\n", id3->bytesperframe);
671 if (id3->vbr) fprintf(f, "VBR: true\n");
672 if (id3->has_toc) fprintf(f, "Has TOC: true\n");
673 if (id3->channels) fprintf(f, "Number of channels: %u\n", id3->channels);
674 if (id3->extradata_size) fprintf(f, "Size of extra data: %u\n", id3->extradata_size);
675 if (id3->needs_upsampling_correction) fprintf(f, "Needs upsampling correction: true\n");
676 /* TODO: replaygain; albumart; cuesheet */
677 if (id3->mb_track_id) fprintf(f, "Musicbrainz track ID: %s\n", id3->mb_track_id);
678}
679
680static void decode_file(const char *input_fn)
681{
682 /* Set up global settings */
683 memset(&global_settings, 0, sizeof(global_settings));
684 global_settings.timestretch_enabled = true;
685 dsp_timestretch_enable(true);
686 tdspeed_init();
687
688 /* Open file */
689 if (!strcmp(input_fn, "-")) {
690 input_fd = STDIN_FILENO;
691 } else {
692 input_fd = open(input_fn, O_RDONLY);
693 if (input_fd == -1) {
694 perror(input_fn);
695 exit(1);
696 }
697 }
698
699 /* Set up ci */
700 struct mp3entry id3;
701 if (!get_metadata(&id3, input_fd, input_fn)) {
702 fprintf(stderr, "error: metadata parsing failed\n");
703 exit(1);
704 }
705 print_mp3entry(&id3, stderr);
706 ci.filesize = filesize(input_fd);
707 ci.id3 = &id3;
708 if (use_dsp) {
709 ci.dsp = (struct dsp_config *)dsp_configure(NULL, DSP_MYDSP, CODEC_IDX_AUDIO);
710 dsp_configure(ci.dsp, DSP_RESET, 0);
711 dsp_dither_enable(false);
712 }
713 perform_config();
714
715 /* Load codec */
716 char str[MAX_PATH];
717 snprintf(str, sizeof(str), CODECDIR"/%s.codec", audio_formats[id3.codectype].codec_root_fn);
718 debugf("Loading %s\n", str);
719 void *dlcodec = dlopen(str, RTLD_NOW);
720 if (!dlcodec) {
721 fprintf(stderr, "error: dlopen failed: %s\n", dlerror());
722 exit(1);
723 }
724 struct codec_header *c_hdr = NULL;
725 c_hdr = dlsym(dlcodec, "__header");
726 if (c_hdr->lc_hdr.magic != CODEC_MAGIC) {
727 fprintf(stderr, "error: %s invalid: incorrect magic\n", str);
728 exit(1);
729 }
730 if (c_hdr->lc_hdr.target_id != TARGET_ID) {
731 fprintf(stderr, "error: %s invalid: incorrect target id\n", str);
732 exit(1);
733 }
734 if (c_hdr->lc_hdr.api_version != CODEC_API_VERSION) {
735 fprintf(stderr, "error: %s invalid: incorrect API version\n", str);
736 exit(1);
737 }
738
739 /* Run the codec */
740 *c_hdr->api = &ci;
741 if (c_hdr->entry_point(CODEC_LOAD) != CODEC_OK) {
742 fprintf(stderr, "error: codec returned error from codec_main\n");
743 exit(1);
744 }
745 if (c_hdr->run_proc() != CODEC_OK) {
746 fprintf(stderr, "error: codec error\n");
747 }
748 c_hdr->entry_point(CODEC_UNLOAD);
749
750 /* Close */
751 dlclose(dlcodec);
752 if (input_fd != STDIN_FILENO)
753 close(input_fd);
754}
755
756static void print_help(const char *progname)
757{
758 fprintf(stderr, "Usage:\n"
759 " Play: %s [options] INPUTFILE\n"
760 "Write to WAV: %s [options] INPUTFILE OUTPUTFILE\n"
761 "\n"
762 "general options:\n"
763 " -c a=1:b=2 Configuration (see below)\n"
764 " -h Show this help\n"
765 "\n"
766 "write to WAV options:\n"
767 " -f Write raw codec output converted to 64-bit float\n"
768 " -r Write raw 32-bit codec output without WAV header\n"
769 "\n"
770 "configuration:\n"
771 " dither=<0|1> Enable/disable dithering [0]\n"
772 " halt=<0|1> Stop decoding if 1 [0]\n"
773 " loop=<0|1> Enable/disable looping [0]\n"
774 " offset=<n> Start at byte offset within the file [0]\n"
775 " rate=<n> Multiply rate by <n> [1.0]\n"
776 " seek=<n> Seek <n> ms into the file\n"
777 " tempo=<n> Timestretch by <n> [1.0]\n"
778 " vol=<n> Set volume to <n> dB [0]\n"
779 " wait=<n> Don't apply remaining configuration until\n"
780 " <n> total samples have output\n"
781 "\n"
782 "examples:\n"
783 " # Play while looping; stop after 44100 output samples\n"
784 " %s in.adx -c loop=1:wait=44100:halt=1\n"
785 " # Lower pitch 1 octave and write to out.wav\n"
786 " %s in.ogg -c rate=0.5:tempo=2 out.wav\n"
787 , progname, progname, progname, progname);
788}
789
790int main(int argc, char **argv)
791{
792 int opt;
793 while ((opt = getopt(argc, argv, "c:fhr")) != -1) {
794 switch (opt) {
795 case 'c':
796 config = optarg;
797 break;
798 case 'f':
799 use_dsp = false;
800 break;
801 case 'r':
802 use_dsp = false;
803 write_raw = true;
804 break;
805 case 'h': /* fallthrough */
806 default:
807 print_help(argv[0]);
808 exit(1);
809 }
810 }
811
812 core_allocator_init();
813 if (argc == optind + 2) {
814 write_init(argv[optind + 1]);
815 } else if (argc == optind + 1) {
816 if (!use_dsp) {
817 fprintf(stderr, "error: -r can't be used for playback\n");
818 print_help(argv[0]);
819 exit(1);
820 }
821 playback_init();
822 } else {
823 if (argc > 1)
824 fprintf(stderr, "error: wrong number of arguments\n");
825 print_help(argv[0]);
826 exit(1);
827 }
828
829 decode_file(argv[optind]);
830
831 if (mode == MODE_WRITE)
832 write_quit();
833 else if (mode == MODE_PLAY)
834 playback_quit();
835
836 return 0;
837}