d37bf24d90
Replaces the NATIVE_FREQUENCY constant with a configurable frequency. The user may select 48000Hz if the hardware supports it. The default is still 44100Hz and the minimum is 44100Hz. The setting is located in the playback settings, under "Frequency". "Frequency" was duplicated in english.lang for now to avoid having to fix every .lang file for the moment and throwing everything out of sync because of the new play_frequency feature in features.txt. The next cleanup should combine it with the one included for recording and generalize the ID label. If the hardware doesn't support 48000Hz, no setting will be available. On particular hardware where very high rates are practical and desireable, the upper bound can be extended by patching. The PCM mixer can be configured to play at the full hardware frequency range. The DSP core can configure to the hardware minimum up to the maximum playback setting (some buffers must be reserved according to the maximum rate). If only 44100Hz is supported or possible on a given target for playback, using the DSP and mixer at other samperates is possible if the hardware offers them. Change-Id: I6023cf0c0baa8bc6292b6919b4dd3618a6a25622 Reviewed-on: http://gerrit.rockbox.org/479 Reviewed-by: Michael Sevakis <jethead71@rockbox.org> Tested-by: Michael Sevakis <jethead71@rockbox.org>
906 lines
26 KiB
C
906 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 "codecs.h"
|
|
#include "dsp_core.h"
|
|
#include "metadata.h"
|
|
#include "settings.h"
|
|
#include "sound.h"
|
|
#include "tdspeed.h"
|
|
#include "kernel.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;
|
|
}
|