rockbox/lib/rbcodec/test/warble.c

907 lines
26 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 "kernel.h"
#include "codecs.h"
#include "dsp_core.h"
#include "metadata.h"
#include "settings.h"
#include "sound.h"
#include "tdspeed.h"
#include "platform.h"
/***************** EXPORTED *****************/
struct user_settings global_settings;
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);
}
int find_first_set_bit(uint32_t value)
{
if (value == 0)
return 32;
return __builtin_ctz(value);
}
/***************** INTERNAL *****************/
static enum { MODE_PLAY, MODE_WRITE } mode;
static bool use_dsp = true;
static bool enable_loop = false;
static const char *config = "";
/* Volume control */
#define VOL_FRACBITS 31
#define VOL_FACTOR_UNITY (1u << VOL_FRACBITS)
static uint32_t playback_vol_factor = VOL_FACTOR_UNITY;
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 = dsp_get_output_frequency(ci.dsp);
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_copy_audio_buffer_S16SYS(
void *dst, const void *src, int len)
{
int64_t factor = playback_vol_factor;
if (factor == VOL_FACTOR_UNITY) {
memcpy(dst, src, len);
} else {
const int16_t *s = src;
int16_t *d = dst;
while (len) {
*d++ = factor * *s++ >> VOL_FRACBITS;
*d++ = factor * *s++ >> VOL_FRACBITS;
len -= sizeof (int16_t) * 2;
}
}
}
static void playback_set_volume(int volume)
{
if (volume > 0)
volume = 0;
playback_vol_factor = pow(10, (double)volume / 20.0) * VOL_FACTOR_UNITY;
}
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);
playback_copy_audio_buffer_S16SYS(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 = dsp_get_output_frequency(ci.dsp);
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)) {
dsp_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)) {
playback_set_volume(atoi(val));
} 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) {
struct dsp_buffer src;
src.remcount = count;
src.pin[0] = ch1;
src.pin[1] = ch2;
src.proc_mask = 0;
while (1) {
int out_count = MAX(count, 512);
int16_t buf[2 * out_count];
struct dsp_buffer dst;
dst.remcount = 0;
dst.p16out = buf;
dst.bufcount = out_count;
dsp_process(ci.dsp, &src, &dst);
if (dst.remcount > 0) {
if (mode == MODE_WRITE)
write_pcm(buf, dst.remcount);
else if (mode == MODE_PLAY)
playback_pcm(buf, dst.remcount);
} else if (src.remcount <= 0) {
break;
}
}
} 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 (!rbcodec_format_is_atomic(ci.id3->codectype))
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_SET_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_debugf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
#ifdef ROCKBOX_HAS_LOGF
static void ci_logf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
putc('\n', stderr);
va_end(ap);
}
#endif
static void ci_yield(void)
{
}
static void commit_dcache(void) {}
static void commit_discard_dcache(void) {}
static void commit_discard_idcache(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,
ci_yield,
#if NUM_CORES > 1
ci_create_thread,
ci_thread_thaw,
ci_thread_wait,
ci_semaphore_init,
ci_semaphore_wait,
ci_semaphore_release,
#endif
commit_dcache,
commit_discard_dcache,
commit_discard_idcache,
/* strings and memory */
strcpy,
strlen,
strcmp,
strcat,
memset,
memcpy,
memmove,
memcmp,
memchr,
#if defined(DEBUG) || defined(SIMULATOR)
ci_debugf,
#endif
#ifdef ROCKBOX_HAS_LOGF
ci_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)
{
/* Initialize DSP before any sort of interaction */
dsp_init();
/* Set up global settings */
memset(&global_settings, 0, sizeof(global_settings));
global_settings.timestretch_enabled = true;
dsp_timestretch_enable(true);
/* 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 = dsp_get_config(CODEC_IDX_AUDIO);
dsp_configure(ci.dsp, DSP_SET_OUT_FREQUENCY, DSP_OUT_DEFAULT_HZ);
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 attenuation 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);
}
}
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;
}