rockbox/lib/rbcodec/test/warble.c
Sean Bartell 26fc31ae93 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>
2012-03-03 16:41:49 +01:00

837 lines
24 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2011 Sean Bartell
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#define _BSD_SOURCE /* htole64 from endian.h */
#include <sys/types.h>
#include <SDL.h>
#include <dlfcn.h>
#include <endian.h>
#include <fcntl.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "buffering.h" /* TYPE_PACKET_AUDIO */
#include "codecs.h"
#include "core_alloc.h" /* core_allocator_init */
#include "debug.h"
#include "dsp.h"
#include "metadata.h"
#include "settings.h"
#include "sound.h"
#include "tdspeed.h"
/***************** EXPORTED *****************/
struct user_settings global_settings;
volatile long current_tick = 0;
void yield(void)
{
}
int set_irq_level(int level)
{
return 0;
}
void mutex_init(struct mutex *m)
{
}
void mutex_lock(struct mutex *m)
{
}
void mutex_unlock(struct mutex *m)
{
}
void debugf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
/***************** INTERNAL *****************/
static enum { MODE_PLAY, MODE_WRITE } mode;
static bool use_dsp = true;
static bool enable_loop = false;
static const char *config = "";
static int input_fd;
static enum codec_command_action codec_action;
static intptr_t codec_action_param = 0;
static unsigned long num_output_samples = 0;
static struct codec_api ci;
static struct {
intptr_t freq;
intptr_t stereo_mode;
intptr_t depth;
int channels;
} format;
/***** MODE_WRITE *****/
#define WAVE_HEADER_SIZE 0x2e
#define WAVE_FORMAT_PCM 1
#define WAVE_FORMAT_IEEE_FLOAT 3
static int output_fd;
static bool write_raw = false;
static bool write_header_written = false;
static void write_init(const char *output_fn)
{
mode = MODE_WRITE;
if (!strcmp(output_fn, "-")) {
output_fd = STDOUT_FILENO;
} else {
output_fd = creat(output_fn, 0666);
if (output_fd == -1) {
perror(output_fn);
exit(1);
}
}
}
static void set_le16(char *buf, uint16_t val)
{
buf[0] = val;
buf[1] = val >> 8;
}
static void set_le32(char *buf, uint32_t val)
{
buf[0] = val;
buf[1] = val >> 8;
buf[2] = val >> 16;
buf[3] = val >> 24;
}
static void write_wav_header(void)
{
int channels, sample_size, freq, type;
if (use_dsp) {
channels = 2;
sample_size = 16;
freq = NATIVE_FREQUENCY;
type = WAVE_FORMAT_PCM;
} else {
channels = format.channels;
sample_size = 64;
freq = format.freq;
type = WAVE_FORMAT_IEEE_FLOAT;
}
/* The size fields are normally overwritten by write_quit(). If that fails,
* this fake size ensures the file can still be played. */
off_t total_size = 0x7fffff00 + WAVE_HEADER_SIZE;
char header[WAVE_HEADER_SIZE] = {"RIFF____WAVEfmt \x12\0\0\0"
"________________\0\0data____"};
set_le32(header + 0x04, total_size - 8);
set_le16(header + 0x14, type);
set_le16(header + 0x16, channels);
set_le32(header + 0x18, freq);
set_le32(header + 0x1c, freq * channels * sample_size / 8);
set_le16(header + 0x20, channels * sample_size / 8);
set_le16(header + 0x22, sample_size);
set_le32(header + 0x2a, total_size - WAVE_HEADER_SIZE);
write(output_fd, header, sizeof(header));
write_header_written = true;
}
static void write_quit(void)
{
if (!write_raw) {
/* Write the correct size fields in the header. If lseek fails (e.g.
* for a pipe) nothing is written. */
off_t total_size = lseek(output_fd, 0, SEEK_CUR);
if (total_size != (off_t)-1) {
char buf[4];
set_le32(buf, total_size - 8);
lseek(output_fd, 4, SEEK_SET);
write(output_fd, buf, 4);
set_le32(buf, total_size - WAVE_HEADER_SIZE);
lseek(output_fd, 0x2a, SEEK_SET);
write(output_fd, buf, 4);
}
}
if (output_fd != STDOUT_FILENO)
close(output_fd);
}
static uint64_t make_float64(int32_t sample, int shift)
{
/* TODO: be more portable */
double val = ldexp(sample, -shift);
return *(uint64_t*)&val;
}
static void write_pcm(int16_t *pcm, int count)
{
if (!write_header_written)
write_wav_header();
int i;
for (i = 0; i < 2 * count; i++)
pcm[i] = htole16(pcm[i]);
write(output_fd, pcm, 4 * count);
}
static void write_pcm_raw(int32_t *pcm, int count)
{
if (write_raw) {
write(output_fd, pcm, count * sizeof(*pcm));
} else {
if (!write_header_written)
write_wav_header();
int i;
uint64_t buf[count];
for (i = 0; i < count; i++)
buf[i] = htole64(make_float64(pcm[i], format.depth));
write(output_fd, buf, count * sizeof(*buf));
}
}
/***** MODE_PLAY *****/
/* MODE_PLAY uses a double buffer: one half is read by the playback thread and
* the other half is written to by the main thread. When a thread is done with
* its current half, it waits for the other thread and then switches. The main
* advantage of this method is its simplicity; the main disadvantage is that it
* has long latency. ALSA buffer underruns still occur sometimes, but this is
* SDL's fault. */
#define PLAYBACK_BUFFER_SIZE 0x10000
static bool playback_running = false;
static char playback_buffer[2][PLAYBACK_BUFFER_SIZE];
static int playback_play_ind, playback_decode_ind;
static int playback_play_pos, playback_decode_pos;
static SDL_sem *playback_play_sema, *playback_decode_sema;
static void playback_init(void)
{
mode = MODE_PLAY;
if (SDL_Init(SDL_INIT_AUDIO)) {
fprintf(stderr, "error: Can't initialize SDL: %s\n", SDL_GetError());
exit(1);
}
playback_play_ind = 1;
playback_play_pos = PLAYBACK_BUFFER_SIZE;
playback_decode_ind = 0;
playback_decode_pos = 0;
playback_play_sema = SDL_CreateSemaphore(0);
playback_decode_sema = SDL_CreateSemaphore(0);
}
static void playback_callback(void *userdata, Uint8 *stream, int len)
{
while (len > 0) {
if (!playback_running && playback_play_ind == playback_decode_ind
&& playback_play_pos >= playback_decode_pos) {
/* end of data */
memset(stream, 0, len);
SDL_SemPost(playback_play_sema);
return;
}
if (playback_play_pos >= PLAYBACK_BUFFER_SIZE) {
SDL_SemPost(playback_play_sema);
SDL_SemWait(playback_decode_sema);
playback_play_ind = !playback_play_ind;
playback_play_pos = 0;
}
char *play_buffer = playback_buffer[playback_play_ind];
int copy_len = MIN(len, PLAYBACK_BUFFER_SIZE - playback_play_pos);
memcpy(stream, play_buffer + playback_play_pos, copy_len);
len -= copy_len;
stream += copy_len;
playback_play_pos += copy_len;
}
}
static void playback_start(void)
{
playback_running = true;
SDL_AudioSpec spec = {0};
spec.freq = NATIVE_FREQUENCY;
spec.format = AUDIO_S16SYS;
spec.channels = 2;
spec.samples = 0x400;
spec.callback = playback_callback;
spec.userdata = NULL;
if (SDL_OpenAudio(&spec, NULL)) {
fprintf(stderr, "error: Can't open SDL audio: %s\n", SDL_GetError());
exit(1);
}
SDL_PauseAudio(0);
}
static void playback_quit(void)
{
if (!playback_running)
playback_start();
memset(playback_buffer[playback_decode_ind] + playback_decode_pos, 0,
PLAYBACK_BUFFER_SIZE - playback_decode_pos);
playback_running = false;
SDL_SemPost(playback_decode_sema);
SDL_SemWait(playback_play_sema);
SDL_SemWait(playback_play_sema);
SDL_Quit();
}
static void playback_pcm(int16_t *pcm, int count)
{
const char *stream = (const char *)pcm;
count *= 4;
while (count > 0) {
if (playback_decode_pos >= PLAYBACK_BUFFER_SIZE) {
if (!playback_running)
playback_start();
SDL_SemPost(playback_decode_sema);
SDL_SemWait(playback_play_sema);
playback_decode_ind = !playback_decode_ind;
playback_decode_pos = 0;
}
char *decode_buffer = playback_buffer[playback_decode_ind];
int copy_len = MIN(count, PLAYBACK_BUFFER_SIZE - playback_decode_pos);
memcpy(decode_buffer + playback_decode_pos, stream, copy_len);
stream += copy_len;
count -= copy_len;
playback_decode_pos += copy_len;
}
}
/***** ALL MODES *****/
static void perform_config(void)
{
/* TODO: equalizer, etc. */
while (config) {
const char *name = config;
const char *eq = strchr(config, '=');
if (!eq)
break;
const char *val = eq + 1;
const char *end = val + strcspn(val, ": \t\n");
if (!strncmp(name, "wait=", 5)) {
if (atoi(val) > num_output_samples)
return;
} else if (!strncmp(name, "dither=", 7)) {
dsp_dither_enable(atoi(val) ? true : false);
} else if (!strncmp(name, "halt=", 5)) {
if (atoi(val))
codec_action = CODEC_ACTION_HALT;
} else if (!strncmp(name, "loop=", 5)) {
enable_loop = atoi(val) != 0;
} else if (!strncmp(name, "offset=", 7)) {
ci.id3->offset = atoi(val);
} else if (!strncmp(name, "rate=", 5)) {
sound_set_pitch(atof(val) * PITCH_SPEED_100);
} else if (!strncmp(name, "seek=", 5)) {
codec_action = CODEC_ACTION_SEEK_TIME;
codec_action_param = atoi(val);
} else if (!strncmp(name, "tempo=", 6)) {
dsp_set_timestretch(atof(val) * PITCH_SPEED_100);
} else if (!strncmp(name, "vol=", 4)) {
global_settings.volume = atoi(val);
dsp_callback(DSP_CALLBACK_SET_SW_VOLUME, 0);
} else {
fprintf(stderr, "error: unrecognized config \"%.*s\"\n",
(int)(eq - name), name);
exit(1);
}
if (*end)
config = end + 1;
else
config = NULL;
}
}
static void *ci_codec_get_buffer(size_t *size)
{
static char buffer[64 * 1024 * 1024];
char *ptr = buffer;
*size = sizeof(buffer);
if ((intptr_t)ptr & (CACHEALIGN_SIZE - 1))
ptr += CACHEALIGN_SIZE - ((intptr_t)ptr & (CACHEALIGN_SIZE - 1));
return ptr;
}
static void ci_pcmbuf_insert(const void *ch1, const void *ch2, int count)
{
num_output_samples += count;
if (use_dsp) {
const char *src[2] = {ch1, ch2};
while (count > 0) {
int out_count = dsp_output_count(ci.dsp, count);
int in_count = MIN(dsp_input_count(ci.dsp, out_count), count);
int16_t buf[2 * out_count];
out_count = dsp_process(ci.dsp, (char *)buf, src, in_count);
if (mode == MODE_WRITE)
write_pcm(buf, out_count);
else if (mode == MODE_PLAY)
playback_pcm(buf, out_count);
count -= in_count;
}
} else {
/* Convert to 32-bit interleaved. */
count *= format.channels;
int i;
int32_t buf[count];
if (format.depth > 16) {
if (format.stereo_mode == STEREO_NONINTERLEAVED) {
for (i = 0; i < count; i += 2) {
buf[i+0] = ((int32_t*)ch1)[i/2];
buf[i+1] = ((int32_t*)ch2)[i/2];
}
} else {
memcpy(buf, ch1, sizeof(buf));
}
} else {
if (format.stereo_mode == STEREO_NONINTERLEAVED) {
for (i = 0; i < count; i += 2) {
buf[i+0] = ((int16_t*)ch1)[i/2];
buf[i+1] = ((int16_t*)ch2)[i/2];
}
} else {
for (i = 0; i < count; i++) {
buf[i] = ((int16_t*)ch1)[i];
}
}
}
if (mode == MODE_WRITE)
write_pcm_raw(buf, count);
}
perform_config();
}
static void ci_set_elapsed(unsigned long value)
{
//debugf("Time elapsed: %lu\n", value);
}
static char *input_buffer = 0;
/*
* Read part of the input file into a provided buffer.
*
* The entire size requested will be provided except at the end of the file.
* The current file position will be moved, just like with advance_buffer, but
* the offset is not updated. This invalidates buffers returned by
* request_buffer.
*/
static size_t ci_read_filebuf(void *ptr, size_t size)
{
free(input_buffer);
input_buffer = NULL;
ssize_t actual = read(input_fd, ptr, size);
if (actual < 0)
actual = 0;
ci.curpos += actual;
return actual;
}
/*
* Request a buffer containing part of the input file.
*
* The size provided will be the requested size, or the remaining size of the
* file, whichever is smaller. Packet audio has an additional maximum of 32
* KiB. The returned buffer remains valid until the next time read_filebuf,
* request_buffer, advance_buffer, or seek_buffer is called.
*/
static void *ci_request_buffer(size_t *realsize, size_t reqsize)
{
free(input_buffer);
if (get_audio_base_data_type(ci.id3->codectype) == TYPE_PACKET_AUDIO)
reqsize = MIN(reqsize, 32 * 1024);
input_buffer = malloc(reqsize);
*realsize = read(input_fd, input_buffer, reqsize);
if (*realsize < 0)
*realsize = 0;
lseek(input_fd, -*realsize, SEEK_CUR);
return input_buffer;
}
/*
* Advance the current position in the input file.
*
* This automatically updates the current offset. This invalidates buffers
* returned by request_buffer.
*/
static void ci_advance_buffer(size_t amount)
{
free(input_buffer);
input_buffer = NULL;
lseek(input_fd, amount, SEEK_CUR);
ci.curpos += amount;
ci.id3->offset = ci.curpos;
}
/*
* Seek to a position in the input file.
*
* This invalidates buffers returned by request_buffer.
*/
static bool ci_seek_buffer(size_t newpos)
{
free(input_buffer);
input_buffer = NULL;
off_t actual = lseek(input_fd, newpos, SEEK_SET);
if (actual >= 0)
ci.curpos = actual;
return actual != -1;
}
static void ci_seek_complete(void)
{
}
static void ci_set_offset(size_t value)
{
ci.id3->offset = value;
}
static void ci_configure(int setting, intptr_t value)
{
if (use_dsp) {
dsp_configure(ci.dsp, setting, value);
} else {
if (setting == DSP_SET_FREQUENCY
|| setting == DSP_SWITCH_FREQUENCY)
format.freq = value;
else if (setting == DSP_SET_SAMPLE_DEPTH)
format.depth = value;
else if (setting == DSP_SET_STEREO_MODE) {
format.stereo_mode = value;
format.channels = (value == STEREO_MONO) ? 1 : 2;
}
}
}
static enum codec_command_action ci_get_command(intptr_t *param)
{
enum codec_command_action ret = codec_action;
*param = codec_action_param;
codec_action = CODEC_ACTION_NULL;
return ret;
}
static bool ci_should_loop(void)
{
return enable_loop;
}
static unsigned ci_sleep(unsigned ticks)
{
return 0;
}
static void ci_cpucache_flush(void)
{
}
static void ci_cpucache_invalidate(void)
{
}
static struct codec_api ci = {
0, /* filesize */
0, /* curpos */
NULL, /* id3 */
-1, /* audio_hid */
NULL, /* struct dsp_config *dsp */
ci_codec_get_buffer,
ci_pcmbuf_insert,
ci_set_elapsed,
ci_read_filebuf,
ci_request_buffer,
ci_advance_buffer,
ci_seek_buffer,
ci_seek_complete,
ci_set_offset,
ci_configure,
ci_get_command,
ci_should_loop,
ci_sleep,
yield,
#if NUM_CORES > 1
ci_create_thread,
ci_thread_thaw,
ci_thread_wait,
ci_semaphore_init,
ci_semaphore_wait,
ci_semaphore_release,
#endif
ci_cpucache_flush,
ci_cpucache_invalidate,
/* strings and memory */
strcpy,
strlen,
strcmp,
strcat,
memset,
memcpy,
memmove,
memcmp,
memchr,
#if defined(DEBUG) || defined(SIMULATOR)
debugf,
#endif
#ifdef ROCKBOX_HAS_LOGF
debugf, /* logf */
#endif
qsort,
#ifdef HAVE_RECORDING
ci_enc_get_inputs,
ci_enc_set_parameters,
ci_enc_get_chunk,
ci_enc_finish_chunk,
ci_enc_get_pcm_data,
ci_enc_unget_pcm_data,
/* file */
open,
close,
read,
lseek,
write,
ci_round_value_to_list32,
#endif /* HAVE_RECORDING */
};
static void print_mp3entry(const struct mp3entry *id3, FILE *f)
{
fprintf(f, "Path: %s\n", id3->path);
if (id3->title) fprintf(f, "Title: %s\n", id3->title);
if (id3->artist) fprintf(f, "Artist: %s\n", id3->artist);
if (id3->album) fprintf(f, "Album: %s\n", id3->album);
if (id3->genre_string) fprintf(f, "Genre: %s\n", id3->genre_string);
if (id3->disc_string || id3->discnum) fprintf(f, "Disc: %s (%d)\n", id3->disc_string, id3->discnum);
if (id3->track_string || id3->tracknum) fprintf(f, "Track: %s (%d)\n", id3->track_string, id3->tracknum);
if (id3->year_string || id3->year) fprintf(f, "Year: %s (%d)\n", id3->year_string, id3->year);
if (id3->composer) fprintf(f, "Composer: %s\n", id3->composer);
if (id3->comment) fprintf(f, "Comment: %s\n", id3->comment);
if (id3->albumartist) fprintf(f, "Album artist: %s\n", id3->albumartist);
if (id3->grouping) fprintf(f, "Grouping: %s\n", id3->grouping);
if (id3->layer) fprintf(f, "Layer: %d\n", id3->layer);
if (id3->id3version) fprintf(f, "ID3 version: %u\n", (int)id3->id3version);
fprintf(f, "Codec: %s\n", audio_formats[id3->codectype].label);
fprintf(f, "Bitrate: %d kb/s\n", id3->bitrate);
fprintf(f, "Frequency: %lu Hz\n", id3->frequency);
if (id3->id3v2len) fprintf(f, "ID3v2 length: %lu\n", id3->id3v2len);
if (id3->id3v1len) fprintf(f, "ID3v1 length: %lu\n", id3->id3v1len);
if (id3->first_frame_offset) fprintf(f, "First frame offset: %lu\n", id3->first_frame_offset);
fprintf(f, "File size without headers: %lu\n", id3->filesize);
fprintf(f, "Song length: %lu ms\n", id3->length);
if (id3->lead_trim > 0 || id3->tail_trim > 0) fprintf(f, "Trim: %d/%d\n", id3->lead_trim, id3->tail_trim);
if (id3->samples) fprintf(f, "Number of samples: %lu\n", id3->samples);
if (id3->frame_count) fprintf(f, "Number of frames: %lu\n", id3->frame_count);
if (id3->bytesperframe) fprintf(f, "Bytes per frame: %lu\n", id3->bytesperframe);
if (id3->vbr) fprintf(f, "VBR: true\n");
if (id3->has_toc) fprintf(f, "Has TOC: true\n");
if (id3->channels) fprintf(f, "Number of channels: %u\n", id3->channels);
if (id3->extradata_size) fprintf(f, "Size of extra data: %u\n", id3->extradata_size);
if (id3->needs_upsampling_correction) fprintf(f, "Needs upsampling correction: true\n");
/* TODO: replaygain; albumart; cuesheet */
if (id3->mb_track_id) fprintf(f, "Musicbrainz track ID: %s\n", id3->mb_track_id);
}
static void decode_file(const char *input_fn)
{
/* Set up global settings */
memset(&global_settings, 0, sizeof(global_settings));
global_settings.timestretch_enabled = true;
dsp_timestretch_enable(true);
tdspeed_init();
/* Open file */
if (!strcmp(input_fn, "-")) {
input_fd = STDIN_FILENO;
} else {
input_fd = open(input_fn, O_RDONLY);
if (input_fd == -1) {
perror(input_fn);
exit(1);
}
}
/* Set up ci */
struct mp3entry id3;
if (!get_metadata(&id3, input_fd, input_fn)) {
fprintf(stderr, "error: metadata parsing failed\n");
exit(1);
}
print_mp3entry(&id3, stderr);
ci.filesize = filesize(input_fd);
ci.id3 = &id3;
if (use_dsp) {
ci.dsp = (struct dsp_config *)dsp_configure(NULL, DSP_MYDSP, CODEC_IDX_AUDIO);
dsp_configure(ci.dsp, DSP_RESET, 0);
dsp_dither_enable(false);
}
perform_config();
/* Load codec */
char str[MAX_PATH];
snprintf(str, sizeof(str), CODECDIR"/%s.codec", audio_formats[id3.codectype].codec_root_fn);
debugf("Loading %s\n", str);
void *dlcodec = dlopen(str, RTLD_NOW);
if (!dlcodec) {
fprintf(stderr, "error: dlopen failed: %s\n", dlerror());
exit(1);
}
struct codec_header *c_hdr = NULL;
c_hdr = dlsym(dlcodec, "__header");
if (c_hdr->lc_hdr.magic != CODEC_MAGIC) {
fprintf(stderr, "error: %s invalid: incorrect magic\n", str);
exit(1);
}
if (c_hdr->lc_hdr.target_id != TARGET_ID) {
fprintf(stderr, "error: %s invalid: incorrect target id\n", str);
exit(1);
}
if (c_hdr->lc_hdr.api_version != CODEC_API_VERSION) {
fprintf(stderr, "error: %s invalid: incorrect API version\n", str);
exit(1);
}
/* Run the codec */
*c_hdr->api = &ci;
if (c_hdr->entry_point(CODEC_LOAD) != CODEC_OK) {
fprintf(stderr, "error: codec returned error from codec_main\n");
exit(1);
}
if (c_hdr->run_proc() != CODEC_OK) {
fprintf(stderr, "error: codec error\n");
}
c_hdr->entry_point(CODEC_UNLOAD);
/* Close */
dlclose(dlcodec);
if (input_fd != STDIN_FILENO)
close(input_fd);
}
static void print_help(const char *progname)
{
fprintf(stderr, "Usage:\n"
" Play: %s [options] INPUTFILE\n"
"Write to WAV: %s [options] INPUTFILE OUTPUTFILE\n"
"\n"
"general options:\n"
" -c a=1:b=2 Configuration (see below)\n"
" -h Show this help\n"
"\n"
"write to WAV options:\n"
" -f Write raw codec output converted to 64-bit float\n"
" -r Write raw 32-bit codec output without WAV header\n"
"\n"
"configuration:\n"
" dither=<0|1> Enable/disable dithering [0]\n"
" halt=<0|1> Stop decoding if 1 [0]\n"
" loop=<0|1> Enable/disable looping [0]\n"
" offset=<n> Start at byte offset within the file [0]\n"
" rate=<n> Multiply rate by <n> [1.0]\n"
" seek=<n> Seek <n> ms into the file\n"
" tempo=<n> Timestretch by <n> [1.0]\n"
" vol=<n> Set volume to <n> dB [0]\n"
" wait=<n> Don't apply remaining configuration until\n"
" <n> total samples have output\n"
"\n"
"examples:\n"
" # Play while looping; stop after 44100 output samples\n"
" %s in.adx -c loop=1:wait=44100:halt=1\n"
" # Lower pitch 1 octave and write to out.wav\n"
" %s in.ogg -c rate=0.5:tempo=2 out.wav\n"
, progname, progname, progname, progname);
}
int main(int argc, char **argv)
{
int opt;
while ((opt = getopt(argc, argv, "c:fhr")) != -1) {
switch (opt) {
case 'c':
config = optarg;
break;
case 'f':
use_dsp = false;
break;
case 'r':
use_dsp = false;
write_raw = true;
break;
case 'h': /* fallthrough */
default:
print_help(argv[0]);
exit(1);
}
}
core_allocator_init();
if (argc == optind + 2) {
write_init(argv[optind + 1]);
} else if (argc == optind + 1) {
if (!use_dsp) {
fprintf(stderr, "error: -r can't be used for playback\n");
print_help(argv[0]);
exit(1);
}
playback_init();
} else {
if (argc > 1)
fprintf(stderr, "error: wrong number of arguments\n");
print_help(argv[0]);
exit(1);
}
decode_file(argv[optind]);
if (mode == MODE_WRITE)
write_quit();
else if (mode == MODE_PLAY)
playback_quit();
return 0;
}