summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
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;
+}