summaryrefslogtreecommitdiff
path: root/lib
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
parent693af46f6de65012828a621ab07573096857ab64 (diff)
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')
-rw-r--r--lib/rbcodec/test/Makefile74
-rw-r--r--lib/rbcodec/test/autoconf.h17
-rw-r--r--lib/rbcodec/test/warble.c837
3 files changed, 928 insertions, 0 deletions
diff --git a/lib/rbcodec/test/Makefile b/lib/rbcodec/test/Makefile
new file mode 100644
index 0000000000..cbda9acbdb
--- /dev/null
+++ b/lib/rbcodec/test/Makefile
@@ -0,0 +1,74 @@
+default: all
+
+.PHONY: default all clean dep
+
+ROOTDIR = $(shell readlink -e ../../..)
+BUILDDIR = $(shell pwd)/build
+APPSDIR = $(ROOTDIR)/apps
+TOOLSDIR = $(ROOTDIR)/tools
+DEPFILE = $(BUILDDIR)/make.dep
+APP_TYPE = sdl-sim
+
+INCLUDES = -I$(shell pwd)
+INCLUDES += -I$(APPSDIR) -I$(APPSDIR)/codecs -I$(APPSDIR)/codecs/lib \
+ -I$(APPSDIR)/gui -I$(APPSDIR)/metadata
+INCLUDES += -I$(ROOTDIR)/firmware/export -I$(ROOTDIR)/firmware/include \
+ -I$(ROOTDIR)/firmware/target/hosted \
+ -I$(ROOTDIR)/firmware/target/hosted/sdl
+
+CFLAGS = $(INCLUDES) -DROCKBOX -DSIMULATOR=1
+CFLAGS += -O0 -ggdb -DDEBUG -DLOGF_ENABLE -Wall -Wno-pointer-sign
+CFLAGS += -Wstrict-prototypes -pipe -std=gnu99
+PPCFLAGS = $(CFLAGS)
+
+SHARED_CFLAGS = -fPIC -fvisibility=hidden
+SHARED_LDFLAG = -shared
+
+WARBLE_OBJS = $(BUILDDIR)/warble.o
+WARBLE_CFLAGS = '-DCODECDIR="$(CODECDIR)"' $(shell sdl-config --cflags)
+WARBLE_LDFLAGS = -lm -ldl $(shell sdl-config --libs)
+
+include $(ROOTDIR)/tools/functions.make
+include $(APPSDIR)/codecs/codecs.make
+
+SRC = $(ROOTDIR)/apps/metadata.c $(ROOTDIR)/apps/replaygain.c \
+ $(ROOTDIR)/firmware/buflib.c \
+ $(ROOTDIR)/firmware/core_alloc.c \
+ $(ROOTDIR)/firmware/common/strlcpy.c \
+ $(ROOTDIR)/firmware/common/unicode.c \
+ $(ROOTDIR)/firmware/common/structec.c $(ROOTDIR)/apps/mp3data.c \
+ $(ROOTDIR)/apps/fixedpoint.c $(ROOTDIR)/uisimulator/common/io.c
+SRC += $(APPSDIR)/compressor.c $(APPSDIR)/dsp.c $(APPSDIR)/eq.c $(APPSDIR)/tdspeed.c
+SRC += $(wildcard $(ROOTDIR)/apps/metadata/*.c)
+
+OBJ := $(SRC:.c=.o)
+OBJ := $(OBJ:.S=.o)
+OBJ := $(subst $(ROOTDIR),$(BUILDDIR),$(OBJ))
+
+all: warble $(CODECS)
+
+dep $(DEPFILE):
+ $(SILENT)mkdir -p $(dir $(DEPFILE))
+ $(call PRINTS,Generating dependencies)
+ @rm -f $(DEPFILE)_
+ $(call mkdepfile,$(DEPFILE)_,$(SRC))
+ $(call mkdepfile,$(DEPFILE)_,$(OTHER_SRC))
+ $(call mkdepfile,$(DEPFILE)_,$(ASMDEFS_SRC))
+ @mv $(DEPFILE)_ $(DEPFILE)
+
+-include $(DEPFILE)
+
+warble: $(WARBLE_OBJS) $(OBJ)
+ $(call PRINTS,LD $@)$(CC) $(LDFLAGS) $^ -o $@ $(WARBLE_LDFLAGS)
+
+$(BUILDDIR)/%.o: %.c
+ $(SILENT)mkdir -p $(dir $@)
+ $(call PRINTS,CC $<)$(CC) $(CFLAGS) -c $< -o $@ $(WARBLE_CFLAGS)
+
+$(BUILDDIR)/%.o: $(ROOTDIR)/%.c
+ $(SILENT)mkdir -p $(dir $@)
+ $(call PRINTS,CC $<)$(CC) $(CFLAGS) -c $< -o $@
+
+clean:
+ $(SILENT)echo Cleaning build directory
+ $(SILENT)rm -rf warble $(BUILDDIR)/*
diff --git a/lib/rbcodec/test/autoconf.h b/lib/rbcodec/test/autoconf.h
new file mode 100644
index 0000000000..0908ade420
--- /dev/null
+++ b/lib/rbcodec/test/autoconf.h
@@ -0,0 +1,17 @@
+#ifndef __BUILD_AUTOCONF_H
+#define __BUILD_AUTOCONF_H
+
+#define __PCTOOL__
+#define CONFIG_CODEC SWCODEC
+#define TARGET_ID 73 /* sdlapp */
+#define MEMORYSIZE 64
+#define ROCKBOX_LITTLE_ENDIAN 1
+#define HAVE_PITCHSCREEN
+#define HAVE_SW_TONE_CONTROLS
+#define HAVE_SW_VOLUME_CONTROL
+#define VOLUME_MIN -100
+#define VOLUME_MAX 100
+#define SW_VOLUME_MIN -100
+#define SW_VOLUME_MAX 100
+
+#endif /* __BUILD_AUTOCONF_H */
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 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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;
+}