From b5716df4cb2837bbbc42195cf1aefcf03e21d6a6 Mon Sep 17 00:00:00 2001 From: Sean Bartell Date: Fri, 24 Jun 2011 01:25:21 -0400 Subject: Build librbcodec with DSP and metadata. All associated files are moved to /lib/rbcodec. Change-Id: I572ddd2b8a996aae1e98c081d06b1ed356dce222 --- lib/rbcodec/SOURCES | 54 ++ lib/rbcodec/dsp/compressor.c | 363 +++++++ lib/rbcodec/dsp/compressor.h | 29 + lib/rbcodec/dsp/dsp.c | 1573 +++++++++++++++++++++++++++++++ lib/rbcodec/dsp/dsp.h | 125 +++ lib/rbcodec/dsp/dsp_arm.S | 561 +++++++++++ lib/rbcodec/dsp/dsp_arm_v6.S | 127 +++ lib/rbcodec/dsp/dsp_asm.h | 86 ++ lib/rbcodec/dsp/dsp_cf.S | 611 ++++++++++++ lib/rbcodec/dsp/eq.c | 268 ++++++ lib/rbcodec/dsp/eq.h | 50 + lib/rbcodec/dsp/eq_arm.S | 89 ++ lib/rbcodec/dsp/eq_cf.S | 91 ++ lib/rbcodec/dsp/eqs/Acoustic.cfg | 17 + lib/rbcodec/dsp/eqs/Bass.cfg | 17 + lib/rbcodec/dsp/eqs/Classical.cfg | 17 + lib/rbcodec/dsp/eqs/Default.cfg | 17 + lib/rbcodec/dsp/eqs/Disco.cfg | 17 + lib/rbcodec/dsp/eqs/Electronic.cfg | 17 + lib/rbcodec/dsp/eqs/Hip-Hop.cfg | 17 + lib/rbcodec/dsp/eqs/Jazz.cfg | 17 + lib/rbcodec/dsp/eqs/Lounge.cfg | 17 + lib/rbcodec/dsp/eqs/Pop.cfg | 17 + lib/rbcodec/dsp/eqs/R&B.cfg | 17 + lib/rbcodec/dsp/eqs/Rock.cfg | 17 + lib/rbcodec/dsp/eqs/Vocal.cfg | 17 + lib/rbcodec/dsp/tdspeed.c | 450 +++++++++ lib/rbcodec/dsp/tdspeed.h | 49 + lib/rbcodec/metadata/a52.c | 103 ++ lib/rbcodec/metadata/adx.c | 124 +++ lib/rbcodec/metadata/aiff.c | 108 +++ lib/rbcodec/metadata/ape.c | 182 ++++ lib/rbcodec/metadata/asap.c | 254 +++++ lib/rbcodec/metadata/asf.c | 591 ++++++++++++ lib/rbcodec/metadata/au.c | 105 +++ lib/rbcodec/metadata/ay.c | 148 +++ lib/rbcodec/metadata/flac.c | 127 +++ lib/rbcodec/metadata/gbs.c | 65 ++ lib/rbcodec/metadata/hes.c | 39 + lib/rbcodec/metadata/id3tags.c | 1199 +++++++++++++++++++++++ lib/rbcodec/metadata/kss.c | 53 ++ lib/rbcodec/metadata/metadata.c | 641 +++++++++++++ lib/rbcodec/metadata/metadata.h | 353 +++++++ lib/rbcodec/metadata/metadata_common.c | 374 ++++++++ lib/rbcodec/metadata/metadata_common.h | 69 ++ lib/rbcodec/metadata/metadata_parsers.h | 59 ++ lib/rbcodec/metadata/mod.c | 103 ++ lib/rbcodec/metadata/monkeys.c | 97 ++ lib/rbcodec/metadata/mp3.c | 193 ++++ lib/rbcodec/metadata/mp3data.c | 849 +++++++++++++++++ lib/rbcodec/metadata/mp3data.h | 89 ++ lib/rbcodec/metadata/mp4.c | 842 +++++++++++++++++ lib/rbcodec/metadata/mpc.c | 220 +++++ lib/rbcodec/metadata/nsf.c | 278 ++++++ lib/rbcodec/metadata/ogg.c | 215 +++++ lib/rbcodec/metadata/oma.c | 189 ++++ lib/rbcodec/metadata/replaygain.c | 222 +++++ lib/rbcodec/metadata/replaygain.h | 34 + lib/rbcodec/metadata/rm.c | 464 +++++++++ lib/rbcodec/metadata/sgc.c | 67 ++ lib/rbcodec/metadata/sid.c | 89 ++ lib/rbcodec/metadata/smaf.c | 470 +++++++++ lib/rbcodec/metadata/spc.c | 130 +++ lib/rbcodec/metadata/tta.c | 123 +++ lib/rbcodec/metadata/vgm.c | 195 ++++ lib/rbcodec/metadata/vorbis.c | 381 ++++++++ lib/rbcodec/metadata/vox.c | 49 + lib/rbcodec/metadata/wave.c | 432 +++++++++ lib/rbcodec/metadata/wavpack.c | 160 ++++ lib/rbcodec/rbcodec.make | 19 + lib/rbcodec/test/SOURCES | 41 - lib/rbcodec/test/warble.make | 8 +- 72 files changed, 15257 insertions(+), 43 deletions(-) create mode 100644 lib/rbcodec/SOURCES create mode 100644 lib/rbcodec/dsp/compressor.c create mode 100644 lib/rbcodec/dsp/compressor.h create mode 100644 lib/rbcodec/dsp/dsp.c create mode 100644 lib/rbcodec/dsp/dsp.h create mode 100644 lib/rbcodec/dsp/dsp_arm.S create mode 100644 lib/rbcodec/dsp/dsp_arm_v6.S create mode 100644 lib/rbcodec/dsp/dsp_asm.h create mode 100644 lib/rbcodec/dsp/dsp_cf.S create mode 100644 lib/rbcodec/dsp/eq.c create mode 100644 lib/rbcodec/dsp/eq.h create mode 100644 lib/rbcodec/dsp/eq_arm.S create mode 100644 lib/rbcodec/dsp/eq_cf.S create mode 100644 lib/rbcodec/dsp/eqs/Acoustic.cfg create mode 100644 lib/rbcodec/dsp/eqs/Bass.cfg create mode 100644 lib/rbcodec/dsp/eqs/Classical.cfg create mode 100644 lib/rbcodec/dsp/eqs/Default.cfg create mode 100644 lib/rbcodec/dsp/eqs/Disco.cfg create mode 100644 lib/rbcodec/dsp/eqs/Electronic.cfg create mode 100644 lib/rbcodec/dsp/eqs/Hip-Hop.cfg create mode 100644 lib/rbcodec/dsp/eqs/Jazz.cfg create mode 100644 lib/rbcodec/dsp/eqs/Lounge.cfg create mode 100644 lib/rbcodec/dsp/eqs/Pop.cfg create mode 100644 lib/rbcodec/dsp/eqs/R&B.cfg create mode 100644 lib/rbcodec/dsp/eqs/Rock.cfg create mode 100644 lib/rbcodec/dsp/eqs/Vocal.cfg create mode 100644 lib/rbcodec/dsp/tdspeed.c create mode 100644 lib/rbcodec/dsp/tdspeed.h create mode 100644 lib/rbcodec/metadata/a52.c create mode 100644 lib/rbcodec/metadata/adx.c create mode 100644 lib/rbcodec/metadata/aiff.c create mode 100644 lib/rbcodec/metadata/ape.c create mode 100644 lib/rbcodec/metadata/asap.c create mode 100644 lib/rbcodec/metadata/asf.c create mode 100644 lib/rbcodec/metadata/au.c create mode 100644 lib/rbcodec/metadata/ay.c create mode 100644 lib/rbcodec/metadata/flac.c create mode 100644 lib/rbcodec/metadata/gbs.c create mode 100644 lib/rbcodec/metadata/hes.c create mode 100644 lib/rbcodec/metadata/id3tags.c create mode 100644 lib/rbcodec/metadata/kss.c create mode 100644 lib/rbcodec/metadata/metadata.c create mode 100644 lib/rbcodec/metadata/metadata.h create mode 100644 lib/rbcodec/metadata/metadata_common.c create mode 100644 lib/rbcodec/metadata/metadata_common.h create mode 100644 lib/rbcodec/metadata/metadata_parsers.h create mode 100644 lib/rbcodec/metadata/mod.c create mode 100644 lib/rbcodec/metadata/monkeys.c create mode 100644 lib/rbcodec/metadata/mp3.c create mode 100644 lib/rbcodec/metadata/mp3data.c create mode 100644 lib/rbcodec/metadata/mp3data.h create mode 100644 lib/rbcodec/metadata/mp4.c create mode 100644 lib/rbcodec/metadata/mpc.c create mode 100644 lib/rbcodec/metadata/nsf.c create mode 100644 lib/rbcodec/metadata/ogg.c create mode 100644 lib/rbcodec/metadata/oma.c create mode 100644 lib/rbcodec/metadata/replaygain.c create mode 100644 lib/rbcodec/metadata/replaygain.h create mode 100644 lib/rbcodec/metadata/rm.c create mode 100644 lib/rbcodec/metadata/sgc.c create mode 100644 lib/rbcodec/metadata/sid.c create mode 100644 lib/rbcodec/metadata/smaf.c create mode 100644 lib/rbcodec/metadata/spc.c create mode 100644 lib/rbcodec/metadata/tta.c create mode 100644 lib/rbcodec/metadata/vgm.c create mode 100644 lib/rbcodec/metadata/vorbis.c create mode 100644 lib/rbcodec/metadata/vox.c create mode 100644 lib/rbcodec/metadata/wave.c create mode 100644 lib/rbcodec/metadata/wavpack.c create mode 100644 lib/rbcodec/rbcodec.make (limited to 'lib') diff --git a/lib/rbcodec/SOURCES b/lib/rbcodec/SOURCES new file mode 100644 index 0000000000..3ac2660a38 --- /dev/null +++ b/lib/rbcodec/SOURCES @@ -0,0 +1,54 @@ +metadata/metadata.c +metadata/id3tags.c +metadata/mp3.c +metadata/mp3data.c +#if CONFIG_CODEC == SWCODEC +dsp/compressor.c +dsp/dsp.c +dsp/eq.c +# if defined(CPU_COLDFIRE) +dsp/dsp_cf.S +dsp/eq_cf.S +# elif defined(CPU_ARM) +dsp/dsp_arm.S +dsp/eq_arm.S +# if ARM_ARCH >= 6 +dsp/dsp_arm_v6.S +# endif +# endif +# ifdef HAVE_PITCHSCREEN +dsp/tdspeed.c +# endif +metadata/replaygain.c +metadata/metadata_common.c +metadata/a52.c +metadata/adx.c +metadata/aiff.c +metadata/ape.c +metadata/asap.c +metadata/asf.c +metadata/au.c +metadata/ay.c +metadata/flac.c +metadata/gbs.c +metadata/hes.c +metadata/kss.c +metadata/mod.c +metadata/monkeys.c +metadata/mp4.c +metadata/mpc.c +metadata/nsf.c +metadata/ogg.c +metadata/oma.c +metadata/rm.c +metadata/sgc.c +metadata/sid.c +metadata/smaf.c +metadata/spc.c +metadata/tta.c +metadata/vgm.c +metadata/vorbis.c +metadata/vox.c +metadata/wave.c +metadata/wavpack.c +#endif diff --git a/lib/rbcodec/dsp/compressor.c b/lib/rbcodec/dsp/compressor.c new file mode 100644 index 0000000000..3a8d52e4da --- /dev/null +++ b/lib/rbcodec/dsp/compressor.c @@ -0,0 +1,363 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2009 Jeffrey Goode + * + * 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. + * + ****************************************************************************/ +#include "config.h" +#include "fixedpoint.h" +#include "fracmul.h" +#include "settings.h" +#include "dsp.h" +#include "compressor.h" + +/* Define LOGF_ENABLE to enable logf output in this file */ +/*#define LOGF_ENABLE*/ +#include "logf.h" + +static int32_t comp_rel_slope IBSS_ATTR; /* S7.24 format */ +static int32_t comp_makeup_gain IBSS_ATTR; /* S7.24 format */ +static int32_t comp_curve[66] IBSS_ATTR; /* S7.24 format */ +static int32_t release_gain IBSS_ATTR; /* S7.24 format */ + +#define UNITY (1L << 24) /* unity gain in S7.24 format */ + +/** COMPRESSOR UPDATE + * Called via the menu system to configure the compressor process */ +bool compressor_update(void) +{ + static int curr_set[5]; + int new_set[5] = { + global_settings.compressor_threshold, + global_settings.compressor_makeup_gain, + global_settings.compressor_ratio, + global_settings.compressor_knee, + global_settings.compressor_release_time}; + + /* make menu values useful */ + int threshold = new_set[0]; + bool auto_gain = (new_set[1] == 1); + const int comp_ratios[] = {2, 4, 6, 10, 0}; + int ratio = comp_ratios[new_set[2]]; + bool soft_knee = (new_set[3] == 1); + int release = new_set[4] * NATIVE_FREQUENCY / 1000; + + bool changed = false; + bool active = (threshold < 0); + + for (int i = 0; i < 5; i++) + { + if (curr_set[i] != new_set[i]) + { + changed = true; + curr_set[i] = new_set[i]; + +#if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE) + switch (i) + { + case 0: + logf(" Compressor Threshold: %d dB\tEnabled: %s", + threshold, active ? "Yes" : "No"); + break; + case 1: + logf(" Compressor Makeup Gain: %s", + auto_gain ? "Auto" : "Off"); + break; + case 2: + if (ratio) + { logf(" Compressor Ratio: %d:1", ratio); } + else + { logf(" Compressor Ratio: Limit"); } + break; + case 3: + logf(" Compressor Knee: %s", soft_knee?"Soft":"Hard"); + break; + case 4: + logf(" Compressor Release: %d", release); + break; + } +#endif + } + } + + if (changed && active) + { + /* configure variables for compressor operation */ + static const int32_t db[] = { + /* positive db equivalents in S15.16 format */ + 0x000000, 0x241FA4, 0x1E1A5E, 0x1A94C8, + 0x181518, 0x1624EA, 0x148F82, 0x1338BD, + 0x120FD2, 0x1109EB, 0x101FA4, 0x0F4BB6, + 0x0E8A3C, 0x0DD840, 0x0D3377, 0x0C9A0E, + 0x0C0A8C, 0x0B83BE, 0x0B04A5, 0x0A8C6C, + 0x0A1A5E, 0x09ADE1, 0x094670, 0x08E398, + 0x0884F6, 0x082A30, 0x07D2FA, 0x077F0F, + 0x072E31, 0x06E02A, 0x0694C8, 0x064BDF, + 0x060546, 0x05C0DA, 0x057E78, 0x053E03, + 0x04FF5F, 0x04C273, 0x048726, 0x044D64, + 0x041518, 0x03DE30, 0x03A89B, 0x037448, + 0x03412A, 0x030F32, 0x02DE52, 0x02AE80, + 0x027FB0, 0x0251D6, 0x0224EA, 0x01F8E2, + 0x01CDB4, 0x01A359, 0x0179C9, 0x0150FC, + 0x0128EB, 0x010190, 0x00DAE4, 0x00B4E1, + 0x008F82, 0x006AC1, 0x004699, 0x002305}; + + struct curve_point + { + int32_t db; /* S15.16 format */ + int32_t offset; /* S15.16 format */ + } db_curve[5]; + + /** Set up the shape of the compression curve first as decibel + values */ + /* db_curve[0] = bottom of knee + [1] = threshold + [2] = top of knee + [3] = 0 db input + [4] = ~+12db input (2 bits clipping overhead) */ + + db_curve[1].db = threshold << 16; + if (soft_knee) + { + /* bottom of knee is 3dB below the threshold for soft knee*/ + db_curve[0].db = db_curve[1].db - (3 << 16); + /* top of knee is 3dB above the threshold for soft knee */ + db_curve[2].db = db_curve[1].db + (3 << 16); + if (ratio) + /* offset = -3db * (ratio - 1) / ratio */ + db_curve[2].offset = (int32_t)((long long)(-3 << 16) + * (ratio - 1) / ratio); + else + /* offset = -3db for hard limit */ + db_curve[2].offset = (-3 << 16); + } + else + { + /* bottom of knee is at the threshold for hard knee */ + db_curve[0].db = threshold << 16; + /* top of knee is at the threshold for hard knee */ + db_curve[2].db = threshold << 16; + db_curve[2].offset = 0; + } + + /* Calculate 0db and ~+12db offsets */ + db_curve[4].db = 0xC0A8C; /* db of 2 bits clipping */ + if (ratio) + { + /* offset = threshold * (ratio - 1) / ratio */ + db_curve[3].offset = (int32_t)((long long)(threshold << 16) + * (ratio - 1) / ratio); + db_curve[4].offset = (int32_t)((long long)-db_curve[4].db + * (ratio - 1) / ratio) + db_curve[3].offset; + } + else + { + /* offset = threshold for hard limit */ + db_curve[3].offset = (threshold << 16); + db_curve[4].offset = -db_curve[4].db + db_curve[3].offset; + } + + /** Now set up the comp_curve table with compression offsets in the + form of gain factors in S7.24 format */ + /* comp_curve[0] is 0 (-infinity db) input */ + comp_curve[0] = UNITY; + /* comp_curve[1 to 63] are intermediate compression values + corresponding to the 6 MSB of the input values of a non-clipped + signal */ + for (int i = 1; i < 64; i++) + { + /* db constants are stored as positive numbers; + make them negative here */ + int32_t this_db = -db[i]; + + /* no compression below the knee */ + if (this_db <= db_curve[0].db) + comp_curve[i] = UNITY; + + /* if soft knee and below top of knee, + interpolate along soft knee slope */ + else if (soft_knee && (this_db <= db_curve[2].db)) + comp_curve[i] = fp_factor(fp_mul( + ((this_db - db_curve[0].db) / 6), + db_curve[2].offset, 16), 16) << 8; + + /* interpolate along ratio slope above the knee */ + else + comp_curve[i] = fp_factor(fp_mul( + fp_div((db_curve[1].db - this_db), db_curve[1].db, 16), + db_curve[3].offset, 16), 16) << 8; + } + /* comp_curve[64] is the compression level of a maximum level, + non-clipped signal */ + comp_curve[64] = fp_factor(db_curve[3].offset, 16) << 8; + + /* comp_curve[65] is the compression level of a maximum level, + clipped signal */ + comp_curve[65] = fp_factor(db_curve[4].offset, 16) << 8; + +#if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE) + logf("\n *** Compression Offsets ***"); + /* some settings for display only, not used in calculations */ + db_curve[0].offset = 0; + db_curve[1].offset = 0; + db_curve[3].db = 0; + + for (int i = 0; i <= 4; i++) + { + logf("Curve[%d]: db: % 6.2f\toffset: % 6.2f", i, + (float)db_curve[i].db / (1 << 16), + (float)db_curve[i].offset / (1 << 16)); + } + + logf("\nGain factors:"); + for (int i = 1; i <= 65; i++) + { + debugf("%02d: %.6f ", i, (float)comp_curve[i] / UNITY); + if (i % 4 == 0) debugf("\n"); + } + debugf("\n"); +#endif + + /* if using auto peak, then makeup gain is max offset - + .1dB headroom */ + comp_makeup_gain = auto_gain ? + fp_factor(-(db_curve[3].offset) - 0x199A, 16) << 8 : UNITY; + logf("Makeup gain:\t%.6f", (float)comp_makeup_gain / UNITY); + + /* calculate per-sample gain change a rate of 10db over release time + */ + comp_rel_slope = 0xAF0BB2 / release; + logf("Release slope:\t%.6f", (float)comp_rel_slope / UNITY); + + release_gain = UNITY; + } + + return active; +} + +/** GET COMPRESSION GAIN + * Returns the required gain factor in S7.24 format in order to compress the + * sample in accordance with the compression curve. Always 1 or less. + */ +static inline int32_t get_compression_gain(struct dsp_data *data, + int32_t sample) +{ + const int frac_bits_offset = data->frac_bits - 15; + + /* sample must be positive */ + if (sample < 0) + sample = -(sample + 1); + + /* shift sample into 15 frac bit range */ + if (frac_bits_offset > 0) + sample >>= frac_bits_offset; + if (frac_bits_offset < 0) + sample <<= -frac_bits_offset; + + /* normal case: sample isn't clipped */ + if (sample < (1 << 15)) + { + /* index is 6 MSB, rem is 9 LSB */ + int index = sample >> 9; + int32_t rem = (sample & 0x1FF) << 22; + + /* interpolate from the compression curve: + higher gain - ((rem / (1 << 31)) * (higher gain - lower gain)) */ + return comp_curve[index] - (FRACMUL(rem, + (comp_curve[index] - comp_curve[index + 1]))); + } + /* sample is somewhat clipped, up to 2 bits of overhead */ + if (sample < (1 << 17)) + { + /* straight interpolation: + higher gain - ((clipped portion of sample * 4/3 + / (1 << 31)) * (higher gain - lower gain)) */ + return comp_curve[64] - (FRACMUL(((sample - (1 << 15)) / 3) << 16, + (comp_curve[64] - comp_curve[65]))); + } + + /* sample is too clipped, return invalid value */ + return -1; +} + +/** COMPRESSOR PROCESS + * Changes the gain of the samples according to the compressor curve + */ +void compressor_process(int count, struct dsp_data *data, int32_t *buf[]) +{ + const int num_chan = data->num_channels; + int32_t *in_buf[2] = {buf[0], buf[1]}; + + while (count-- > 0) + { + int ch; + /* use lowest (most compressed) gain factor of the output buffer + sample pair for both samples (mono is also handled correctly here) + */ + int32_t sample_gain = UNITY; + for (ch = 0; ch < num_chan; ch++) + { + int32_t this_gain = get_compression_gain(data, *in_buf[ch]); + if (this_gain < sample_gain) + sample_gain = this_gain; + } + + /* perform release slope; skip if no compression and no release slope + */ + if ((sample_gain != UNITY) || (release_gain != UNITY)) + { + /* if larger offset than previous slope, start new release slope + */ + if ((sample_gain <= release_gain) && (sample_gain > 0)) + { + release_gain = sample_gain; + } + else + /* keep sloping towards unity gain (and ignore invalid value) */ + { + release_gain += comp_rel_slope; + if (release_gain > UNITY) + { + release_gain = UNITY; + } + } + } + + /* total gain factor is the product of release gain and makeup gain, + but avoid computation if possible */ + int32_t total_gain = ((release_gain == UNITY) ? comp_makeup_gain : + (comp_makeup_gain == UNITY) ? release_gain : + FRACMUL_SHL(release_gain, comp_makeup_gain, 7)); + + /* Implement the compressor: apply total gain factor (if any) to the + output buffer sample pair/mono sample */ + if (total_gain != UNITY) + { + for (ch = 0; ch < num_chan; ch++) + { + *in_buf[ch] = FRACMUL_SHL(total_gain, *in_buf[ch], 7); + } + } + in_buf[0]++; + in_buf[1]++; + } +} + +void compressor_reset(void) +{ + release_gain = UNITY; +} diff --git a/lib/rbcodec/dsp/compressor.h b/lib/rbcodec/dsp/compressor.h new file mode 100644 index 0000000000..6154372e05 --- /dev/null +++ b/lib/rbcodec/dsp/compressor.h @@ -0,0 +1,29 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2009 Jeffrey Goode + * + * 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. + * + ****************************************************************************/ + +#ifndef COMPRESSOR_H +#define COMPRESSOR_H + +void compressor_process(int count, struct dsp_data *data, int32_t *buf[]); +bool compressor_update(void); +void compressor_reset(void); + +#endif /* COMPRESSOR_H */ diff --git a/lib/rbcodec/dsp/dsp.c b/lib/rbcodec/dsp/dsp.c new file mode 100644 index 0000000000..4da555747b --- /dev/null +++ b/lib/rbcodec/dsp/dsp.c @@ -0,0 +1,1573 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Miika Pekkarinen + * + * 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. + * + ****************************************************************************/ +#include "config.h" +#include "system.h" +#include +#include "dsp.h" +#include "dsp-util.h" +#include "eq.h" +#include "compressor.h" +#include "kernel.h" +#include "settings.h" +#include "replaygain.h" +#include "tdspeed.h" +#include "core_alloc.h" +#include "fixedpoint.h" +#include "fracmul.h" + +/* Define LOGF_ENABLE to enable logf output in this file */ +/*#define LOGF_ENABLE*/ +#include "logf.h" + +/* 16-bit samples are scaled based on these constants. The shift should be + * no more than 15. + */ +#define WORD_SHIFT 12 +#define WORD_FRACBITS 27 + +#define NATIVE_DEPTH 16 +#define SMALL_SAMPLE_BUF_COUNT 128 /* Per channel */ +#define DEFAULT_GAIN 0x01000000 + +/* enums to index conversion properly with stereo mode and other settings */ +enum +{ + SAMPLE_INPUT_LE_NATIVE_I_STEREO = STEREO_INTERLEAVED, + SAMPLE_INPUT_LE_NATIVE_NI_STEREO = STEREO_NONINTERLEAVED, + SAMPLE_INPUT_LE_NATIVE_MONO = STEREO_MONO, + SAMPLE_INPUT_GT_NATIVE_I_STEREO = STEREO_INTERLEAVED + STEREO_NUM_MODES, + SAMPLE_INPUT_GT_NATIVE_NI_STEREO = STEREO_NONINTERLEAVED + STEREO_NUM_MODES, + SAMPLE_INPUT_GT_NATIVE_MONO = STEREO_MONO + STEREO_NUM_MODES, + SAMPLE_INPUT_GT_NATIVE_1ST_INDEX = STEREO_NUM_MODES +}; + +enum +{ + SAMPLE_OUTPUT_MONO = 0, + SAMPLE_OUTPUT_STEREO, + SAMPLE_OUTPUT_DITHERED_MONO, + SAMPLE_OUTPUT_DITHERED_STEREO +}; + +/* No asm...yet */ +struct dither_data +{ + long error[3]; /* 00h */ + long random; /* 0ch */ + /* 10h */ +}; + +struct crossfeed_data +{ + int32_t gain; /* 00h - Direct path gain */ + int32_t coefs[3]; /* 04h - Coefficients for the shelving filter */ + int32_t history[4]; /* 10h - Format is x[n - 1], y[n - 1] for both channels */ + int32_t delay[13][2]; /* 20h */ + int32_t *index; /* 88h - Current pointer into the delay line */ + /* 8ch */ +}; + +/* Current setup is one lowshelf filters three peaking filters and one + * highshelf filter. Varying the number of shelving filters make no sense, + * but adding peaking filters is possible. + */ +struct eq_state +{ + char enabled[5]; /* 00h - Flags for active filters */ + struct eqfilter filters[5]; /* 08h - packing is 4? */ + /* 10ch */ +}; + +/* Include header with defines which functions are implemented in assembly + code for the target */ +#include + +/* Typedefs keep things much neater in this case */ +typedef void (*sample_input_fn_type)(int count, const char *src[], + int32_t *dst[]); +typedef int (*resample_fn_type)(int count, struct dsp_data *data, + const int32_t *src[], int32_t *dst[]); +typedef void (*sample_output_fn_type)(int count, struct dsp_data *data, + const int32_t *src[], int16_t *dst); + +/* Single-DSP channel processing in place */ +typedef void (*channels_process_fn_type)(int count, int32_t *buf[]); +/* DSP local channel processing in place */ +typedef void (*channels_process_dsp_fn_type)(int count, struct dsp_data *data, + int32_t *buf[]); + +/* + ***************************************************************************/ + +struct dsp_config +{ + struct dsp_data data; /* Config members for use in external routines */ + long codec_frequency; /* Sample rate of data coming from the codec */ + long frequency; /* Effective sample rate after pitch shift (if any) */ + int sample_depth; + int sample_bytes; + int stereo_mode; + int32_t tdspeed_percent; /* Speed% * PITCH_SPEED_PRECISION */ +#ifdef HAVE_PITCHSCREEN + bool tdspeed_active; /* Timestretch is in use */ +#endif +#ifdef HAVE_SW_TONE_CONTROLS + /* Filter struct for software bass/treble controls */ + struct eqfilter tone_filter; +#endif + /* Functions that change depending upon settings - NULL if stage is + disabled */ + sample_input_fn_type input_samples; + resample_fn_type resample; + sample_output_fn_type output_samples; + /* These will be NULL for the voice codec and is more economical that + way */ + channels_process_dsp_fn_type apply_gain; + channels_process_fn_type apply_crossfeed; + channels_process_fn_type eq_process; + channels_process_fn_type channels_process; + channels_process_dsp_fn_type compressor_process; +}; + +/* General DSP config */ +static struct dsp_config dsp_conf[2] IBSS_ATTR; /* 0=A, 1=V */ +/* Dithering */ +static struct dither_data dither_data[2] IBSS_ATTR; /* 0=left, 1=right */ +static long dither_mask IBSS_ATTR; +static long dither_bias IBSS_ATTR; +/* Crossfeed */ +struct crossfeed_data crossfeed_data IDATA_ATTR = /* A */ +{ + .index = (int32_t *)crossfeed_data.delay +}; + +/* Equalizer */ +static struct eq_state eq_data; /* A */ + +/* Software tone controls */ +#ifdef HAVE_SW_TONE_CONTROLS +static int prescale; /* A/V */ +static int bass; /* A/V */ +static int treble; /* A/V */ +#endif + +/* Settings applicable to audio codec only */ +#ifdef HAVE_PITCHSCREEN +static int32_t pitch_ratio = PITCH_SPEED_100; +static int big_sample_locks; +#endif +static int channels_mode; + long dsp_sw_gain; + long dsp_sw_cross; +static bool dither_enabled; +static long eq_precut; +static long track_gain; +static bool new_gain; +static long album_gain; +static long track_peak; +static long album_peak; +static long replaygain; +static bool crossfeed_enabled; + +#define AUDIO_DSP (dsp_conf[CODEC_IDX_AUDIO]) +#define VOICE_DSP (dsp_conf[CODEC_IDX_VOICE]) + +/* The internal format is 32-bit samples, non-interleaved, stereo. This + * format is similar to the raw output from several codecs, so the amount + * of copying needed is minimized for that case. + */ + +#define RESAMPLE_RATIO 4 /* Enough for 11,025 Hz -> 44,100 Hz */ +#define SMALL_RESAMPLE_BUF_COUNT (SMALL_SAMPLE_BUF_COUNT * RESAMPLE_RATIO) +#define BIG_SAMPLE_BUF_COUNT SMALL_RESAMPLE_BUF_COUNT +#define BIG_RESAMPLE_BUF_COUNT (BIG_SAMPLE_BUF_COUNT * RESAMPLE_RATIO) + +static int32_t small_sample_buf[2][SMALL_SAMPLE_BUF_COUNT] IBSS_ATTR; +static int32_t small_resample_buf[2][SMALL_RESAMPLE_BUF_COUNT] IBSS_ATTR; + +#ifdef HAVE_PITCHSCREEN +static int32_t (* big_sample_buf)[BIG_SAMPLE_BUF_COUNT] = NULL; +static int32_t (* big_resample_buf)[BIG_RESAMPLE_BUF_COUNT] = NULL; +#endif + +static int sample_buf_count = SMALL_SAMPLE_BUF_COUNT; +static int32_t *sample_buf[2] = { small_sample_buf[0], small_sample_buf[1] }; +static int resample_buf_count = SMALL_RESAMPLE_BUF_COUNT; +static int32_t *resample_buf[2] = { small_resample_buf[0], small_resample_buf[1] }; + +#ifdef HAVE_PITCHSCREEN +int32_t sound_get_pitch(void) +{ + return pitch_ratio; +} + +void sound_set_pitch(int32_t percent) +{ + pitch_ratio = percent; + dsp_configure(&AUDIO_DSP, DSP_SWITCH_FREQUENCY, + AUDIO_DSP.codec_frequency); +} + +static void tdspeed_set_pointers( bool time_stretch_active ) +{ + if( time_stretch_active ) + { + sample_buf_count = BIG_SAMPLE_BUF_COUNT; + resample_buf_count = BIG_RESAMPLE_BUF_COUNT; + sample_buf[0] = big_sample_buf[0]; + sample_buf[1] = big_sample_buf[1]; + resample_buf[0] = big_resample_buf[0]; + resample_buf[1] = big_resample_buf[1]; + } + else + { + sample_buf_count = SMALL_SAMPLE_BUF_COUNT; + resample_buf_count = SMALL_RESAMPLE_BUF_COUNT; + sample_buf[0] = small_sample_buf[0]; + sample_buf[1] = small_sample_buf[1]; + resample_buf[0] = small_resample_buf[0]; + resample_buf[1] = small_resample_buf[1]; + } +} + +static void tdspeed_setup(struct dsp_config *dspc) +{ + /* Assume timestretch will not be used */ + dspc->tdspeed_active = false; + + tdspeed_set_pointers( false ); + + if (!dsp_timestretch_available()) + return; /* Timestretch not enabled or buffer not allocated */ + + if (dspc->tdspeed_percent == 0) + dspc->tdspeed_percent = PITCH_SPEED_100; + + if (!tdspeed_config( + dspc->codec_frequency == 0 ? NATIVE_FREQUENCY : dspc->codec_frequency, + dspc->stereo_mode != STEREO_MONO, + dspc->tdspeed_percent)) + return; /* Timestretch not possible or needed with these parameters */ + + /* Timestretch is to be used */ + dspc->tdspeed_active = true; + + tdspeed_set_pointers( true ); +} + + +static int move_callback(int handle, void* current, void* new) +{ + (void)handle;(void)current; + + if ( big_sample_locks > 0 ) + return BUFLIB_CB_CANNOT_MOVE; + + big_sample_buf = new; + + /* no allocation without timestretch enabled */ + tdspeed_set_pointers( true ); + return BUFLIB_CB_OK; +} + +static void lock_sample_buf( bool lock ) +{ + if ( lock ) + big_sample_locks++; + else + big_sample_locks--; +} + +static struct buflib_callbacks ops = { + .move_callback = move_callback, + .shrink_callback = NULL, +}; + + +void dsp_timestretch_enable(bool enabled) +{ + /* Hook to set up timestretch buffer on first call to settings_apply() */ + static int handle = -1; + if (enabled) + { + if (big_sample_buf) + return; /* already allocated and enabled */ + + /* Set up timestretch buffers */ + big_sample_buf = &small_resample_buf[0]; + handle = core_alloc_ex("resample buf", + 2 * BIG_RESAMPLE_BUF_COUNT * sizeof(int32_t), + &ops); + big_sample_locks = 0; + enabled = handle >= 0; + + if (enabled) + { + /* success, now setup tdspeed */ + big_resample_buf = core_get_data(handle); + + tdspeed_init(); + tdspeed_setup(&AUDIO_DSP); + } + } + + if (!enabled) + { + dsp_set_timestretch(PITCH_SPEED_100); + tdspeed_finish(); + + if (handle >= 0) + core_free(handle); + + handle = -1; + big_sample_buf = NULL; + } +} + +void dsp_set_timestretch(int32_t percent) +{ + AUDIO_DSP.tdspeed_percent = percent; + tdspeed_setup(&AUDIO_DSP); +} + +int32_t dsp_get_timestretch() +{ + return AUDIO_DSP.tdspeed_percent; +} + +bool dsp_timestretch_available() +{ + return (global_settings.timestretch_enabled && big_sample_buf); +} +#endif /* HAVE_PITCHSCREEN */ + +/* Convert count samples to the internal format, if needed. Updates src + * to point past the samples "consumed" and dst is set to point to the + * samples to consume. Note that for mono, dst[0] equals dst[1], as there + * is no point in processing the same data twice. + */ + +/* convert count 16-bit mono to 32-bit mono */ +static void sample_input_lte_native_mono( + int count, const char *src[], int32_t *dst[]) +{ + const int16_t *s = (int16_t *) src[0]; + const int16_t * const send = s + count; + int32_t *d = dst[0] = dst[1] = sample_buf[0]; + int scale = WORD_SHIFT; + + while (s < send) + { + *d++ = *s++ << scale; + } + + src[0] = (char *)s; +} + +/* convert count 16-bit interleaved stereo to 32-bit noninterleaved */ +static void sample_input_lte_native_i_stereo( + int count, const char *src[], int32_t *dst[]) +{ + const int32_t *s = (int32_t *) src[0]; + const int32_t * const send = s + count; + int32_t *dl = dst[0] = sample_buf[0]; + int32_t *dr = dst[1] = sample_buf[1]; + int scale = WORD_SHIFT; + + while (s < send) + { + int32_t slr = *s++; +#ifdef ROCKBOX_LITTLE_ENDIAN + *dl++ = (slr >> 16) << scale; + *dr++ = (int32_t)(int16_t)slr << scale; +#else /* ROCKBOX_BIG_ENDIAN */ + *dl++ = (int32_t)(int16_t)slr << scale; + *dr++ = (slr >> 16) << scale; +#endif + } + + src[0] = (char *)s; +} + +/* convert count 16-bit noninterleaved stereo to 32-bit noninterleaved */ +static void sample_input_lte_native_ni_stereo( + int count, const char *src[], int32_t *dst[]) +{ + const int16_t *sl = (int16_t *) src[0]; + const int16_t *sr = (int16_t *) src[1]; + const int16_t * const slend = sl + count; + int32_t *dl = dst[0] = sample_buf[0]; + int32_t *dr = dst[1] = sample_buf[1]; + int scale = WORD_SHIFT; + + while (sl < slend) + { + *dl++ = *sl++ << scale; + *dr++ = *sr++ << scale; + } + + src[0] = (char *)sl; + src[1] = (char *)sr; +} + +/* convert count 32-bit mono to 32-bit mono */ +static void sample_input_gt_native_mono( + int count, const char *src[], int32_t *dst[]) +{ + dst[0] = dst[1] = (int32_t *)src[0]; + src[0] = (char *)(dst[0] + count); +} + +/* convert count 32-bit interleaved stereo to 32-bit noninterleaved stereo */ +static void sample_input_gt_native_i_stereo( + int count, const char *src[], int32_t *dst[]) +{ + const int32_t *s = (int32_t *)src[0]; + const int32_t * const send = s + 2*count; + int32_t *dl = dst[0] = sample_buf[0]; + int32_t *dr = dst[1] = sample_buf[1]; + + while (s < send) + { + *dl++ = *s++; + *dr++ = *s++; + } + + src[0] = (char *)send; +} + +/* convert 32 bit-noninterleaved stereo to 32-bit noninterleaved stereo */ +static void sample_input_gt_native_ni_stereo( + int count, const char *src[], int32_t *dst[]) +{ + dst[0] = (int32_t *)src[0]; + dst[1] = (int32_t *)src[1]; + src[0] = (char *)(dst[0] + count); + src[1] = (char *)(dst[1] + count); +} + +/** + * sample_input_new_format() + * + * set the to-native sample conversion function based on dsp sample parameters + * + * !DSPPARAMSYNC + * needs syncing with changes to the following dsp parameters: + * * dsp->stereo_mode (A/V) + * * dsp->sample_depth (A/V) + */ +static void sample_input_new_format(struct dsp_config *dsp) +{ + static const sample_input_fn_type sample_input_functions[] = + { + [SAMPLE_INPUT_LE_NATIVE_I_STEREO] = sample_input_lte_native_i_stereo, + [SAMPLE_INPUT_LE_NATIVE_NI_STEREO] = sample_input_lte_native_ni_stereo, + [SAMPLE_INPUT_LE_NATIVE_MONO] = sample_input_lte_native_mono, + [SAMPLE_INPUT_GT_NATIVE_I_STEREO] = sample_input_gt_native_i_stereo, + [SAMPLE_INPUT_GT_NATIVE_NI_STEREO] = sample_input_gt_native_ni_stereo, + [SAMPLE_INPUT_GT_NATIVE_MONO] = sample_input_gt_native_mono, + }; + + int convert = dsp->stereo_mode; + + if (dsp->sample_depth > NATIVE_DEPTH) + convert += SAMPLE_INPUT_GT_NATIVE_1ST_INDEX; + + dsp->input_samples = sample_input_functions[convert]; +} + + +#ifndef DSP_HAVE_ASM_SAMPLE_OUTPUT_MONO +/* write mono internal format to output format */ +static void sample_output_mono(int count, struct dsp_data *data, + const int32_t *src[], int16_t *dst) +{ + const int32_t *s0 = src[0]; + const int scale = data->output_scale; + const int dc_bias = 1 << (scale - 1); + + while (count-- > 0) + { + int32_t lr = clip_sample_16((*s0++ + dc_bias) >> scale); + *dst++ = lr; + *dst++ = lr; + } +} +#endif /* DSP_HAVE_ASM_SAMPLE_OUTPUT_MONO */ + +/* write stereo internal format to output format */ +#ifndef DSP_HAVE_ASM_SAMPLE_OUTPUT_STEREO +static void sample_output_stereo(int count, struct dsp_data *data, + const int32_t *src[], int16_t *dst) +{ + const int32_t *s0 = src[0]; + const int32_t *s1 = src[1]; + const int scale = data->output_scale; + const int dc_bias = 1 << (scale - 1); + + while (count-- > 0) + { + *dst++ = clip_sample_16((*s0++ + dc_bias) >> scale); + *dst++ = clip_sample_16((*s1++ + dc_bias) >> scale); + } +} +#endif /* DSP_HAVE_ASM_SAMPLE_OUTPUT_STEREO */ + +/** + * The "dither" code to convert the 24-bit samples produced by libmad was + * taken from the coolplayer project - coolplayer.sourceforge.net + * + * This function handles mono and stereo outputs. + */ +static void sample_output_dithered(int count, struct dsp_data *data, + const int32_t *src[], int16_t *dst) +{ + const int32_t mask = dither_mask; + const int32_t bias = dither_bias; + const int scale = data->output_scale; + const int32_t min = data->clip_min; + const int32_t max = data->clip_max; + const int32_t range = max - min; + int ch; + int16_t *d; + + for (ch = 0; ch < data->num_channels; ch++) + { + struct dither_data * const dither = &dither_data[ch]; + const int32_t *s = src[ch]; + int i; + + for (i = 0, d = &dst[ch]; i < count; i++, s++, d += 2) + { + int32_t output, sample; + int32_t random; + + /* Noise shape and bias (for correct rounding later) */ + sample = *s; + sample += dither->error[0] - dither->error[1] + dither->error[2]; + dither->error[2] = dither->error[1]; + dither->error[1] = dither->error[0]/2; + + output = sample + bias; + + /* Dither, highpass triangle PDF */ + random = dither->random*0x0019660dL + 0x3c6ef35fL; + output += (random & mask) - (dither->random & mask); + dither->random = random; + + /* Round sample to output range */ + output &= ~mask; + + /* Error feedback */ + dither->error[0] = sample - output; + + /* Clip */ + if ((uint32_t)(output - min) > (uint32_t)range) + { + int32_t c = min; + if (output > min) + c += range; + output = c; + } + + /* Quantize and store */ + *d = output >> scale; + } + } + + if (data->num_channels == 2) + return; + + /* Have to duplicate left samples into the right channel since + pcm buffer and hardware is interleaved stereo */ + d = &dst[0]; + + while (count-- > 0) + { + int16_t s = *d++; + *d++ = s; + } +} + +/** + * sample_output_new_format() + * + * set the from-native to ouput sample conversion routine + * + * !DSPPARAMSYNC + * needs syncing with changes to the following dsp parameters: + * * dsp->stereo_mode (A/V) + * * dither_enabled (A) + */ +static void sample_output_new_format(struct dsp_config *dsp) +{ + static const sample_output_fn_type sample_output_functions[] = + { + sample_output_mono, + sample_output_stereo, + sample_output_dithered, + sample_output_dithered + }; + + int out = dsp->data.num_channels - 1; + + if (dsp == &AUDIO_DSP && dither_enabled) + out += 2; + + dsp->output_samples = sample_output_functions[out]; +} + +/** + * Linear interpolation resampling that introduces a one sample delay because + * of our inability to look into the future at the end of a frame. + */ +#ifndef DSP_HAVE_ASM_RESAMPLING +static int dsp_downsample(int count, struct dsp_data *data, + const int32_t *src[], int32_t *dst[]) +{ + int ch = data->num_channels - 1; + uint32_t delta = data->resample_data.delta; + uint32_t phase, pos; + int32_t *d; + + /* Rolled channel loop actually showed slightly faster. */ + do + { + /* Just initialize things and not worry too much about the relatively + * uncommon case of not being able to spit out a sample for the frame. + */ + const int32_t *s = src[ch]; + int32_t last = data->resample_data.last_sample[ch]; + + data->resample_data.last_sample[ch] = s[count - 1]; + d = dst[ch]; + phase = data->resample_data.phase; + pos = phase >> 16; + + /* Do we need last sample of previous frame for interpolation? */ + if (pos > 0) + last = s[pos - 1]; + + while (pos < (uint32_t)count) + { + *d++ = last + FRACMUL((phase & 0xffff) << 15, s[pos] - last); + phase += delta; + pos = phase >> 16; + last = s[pos - 1]; + } + } + while (--ch >= 0); + + /* Wrap phase accumulator back to start of next frame. */ + data->resample_data.phase = phase - (count << 16); + return d - dst[0]; +} + +static int dsp_upsample(int count, struct dsp_data *data, + const int32_t *src[], int32_t *dst[]) +{ + int ch = data->num_channels - 1; + uint32_t delta = data->resample_data.delta; + uint32_t phase, pos; + int32_t *d; + + /* Rolled channel loop actually showed slightly faster. */ + do + { + /* Should always be able to output a sample for a ratio up to RESAMPLE_RATIO */ + const int32_t *s = src[ch]; + int32_t last = data->resample_data.last_sample[ch]; + + data->resample_data.last_sample[ch] = s[count - 1]; + d = dst[ch]; + phase = data->resample_data.phase; + pos = phase >> 16; + + while (pos == 0) + { + *d++ = last + FRACMUL((phase & 0xffff) << 15, s[0] - last); + phase += delta; + pos = phase >> 16; + } + + while (pos < (uint32_t)count) + { + last = s[pos - 1]; + *d++ = last + FRACMUL((phase & 0xffff) << 15, s[pos] - last); + phase += delta; + pos = phase >> 16; + } + } + while (--ch >= 0); + + /* Wrap phase accumulator back to start of next frame. */ + data->resample_data.phase = phase & 0xffff; + return d - dst[0]; +} +#endif /* DSP_HAVE_ASM_RESAMPLING */ + +static void resampler_new_delta(struct dsp_config *dsp) +{ + dsp->data.resample_data.delta = (unsigned long) + dsp->frequency * 65536LL / NATIVE_FREQUENCY; + + if (dsp->frequency == NATIVE_FREQUENCY) + { + /* NOTE: If fully glitch-free transistions from no resampling to + resampling are desired, last_sample history should be maintained + even when not resampling. */ + dsp->resample = NULL; + dsp->data.resample_data.phase = 0; + dsp->data.resample_data.last_sample[0] = 0; + dsp->data.resample_data.last_sample[1] = 0; + } + else if (dsp->frequency < NATIVE_FREQUENCY) + dsp->resample = dsp_upsample; + else + dsp->resample = dsp_downsample; +} + +/* Resample count stereo samples. Updates the src array, if resampling is + * done, to refer to the resampled data. Returns number of stereo samples + * for further processing. + */ +static inline int resample(struct dsp_config *dsp, int count, int32_t *src[]) +{ + int32_t *dst[2] = + { + resample_buf[0], + resample_buf[1] + }; + lock_sample_buf( true ); + count = dsp->resample(count, &dsp->data, (const int32_t **)src, dst); + + src[0] = dst[0]; + src[1] = dst[dsp->data.num_channels - 1]; + lock_sample_buf( false ); + return count; +} + +static void dither_init(struct dsp_config *dsp) +{ + memset(dither_data, 0, sizeof (dither_data)); + dither_bias = (1L << (dsp->data.frac_bits - NATIVE_DEPTH)); + dither_mask = (1L << (dsp->data.frac_bits + 1 - NATIVE_DEPTH)) - 1; +} + +void dsp_dither_enable(bool enable) +{ + struct dsp_config *dsp = &AUDIO_DSP; + dither_enabled = enable; + sample_output_new_format(dsp); +} + +/* Applies crossfeed to the stereo signal in src. + * Crossfeed is a process where listening over speakers is simulated. This + * is good for old hard panned stereo records, which might be quite fatiguing + * to listen to on headphones with no crossfeed. + */ +#ifndef DSP_HAVE_ASM_CROSSFEED +static void apply_crossfeed(int count, int32_t *buf[]) +{ + int32_t *hist_l = &crossfeed_data.history[0]; + int32_t *hist_r = &crossfeed_data.history[2]; + int32_t *delay = &crossfeed_data.delay[0][0]; + int32_t *coefs = &crossfeed_data.coefs[0]; + int32_t gain = crossfeed_data.gain; + int32_t *di = crossfeed_data.index; + + int32_t acc; + int32_t left, right; + int i; + + for (i = 0; i < count; i++) + { + left = buf[0][i]; + right = buf[1][i]; + + /* Filter delayed sample from left speaker */ + acc = FRACMUL(*di, coefs[0]); + acc += FRACMUL(hist_l[0], coefs[1]); + acc += FRACMUL(hist_l[1], coefs[2]); + /* Save filter history for left speaker */ + hist_l[1] = acc; + hist_l[0] = *di; + *di++ = left; + /* Filter delayed sample from right speaker */ + acc = FRACMUL(*di, coefs[0]); + acc += FRACMUL(hist_r[0], coefs[1]); + acc += FRACMUL(hist_r[1], coefs[2]); + /* Save filter history for right speaker */ + hist_r[1] = acc; + hist_r[0] = *di; + *di++ = right; + /* Now add the attenuated direct sound and write to outputs */ + buf[0][i] = FRACMUL(left, gain) + hist_r[1]; + buf[1][i] = FRACMUL(right, gain) + hist_l[1]; + + /* Wrap delay line index if bigger than delay line size */ + if (di >= delay + 13*2) + di = delay; + } + /* Write back local copies of data we've modified */ + crossfeed_data.index = di; +} +#endif /* DSP_HAVE_ASM_CROSSFEED */ + +/** + * dsp_set_crossfeed(bool enable) + * + * !DSPPARAMSYNC + * needs syncing with changes to the following dsp parameters: + * * dsp->stereo_mode (A) + */ +void dsp_set_crossfeed(bool enable) +{ + crossfeed_enabled = enable; + AUDIO_DSP.apply_crossfeed = (enable && AUDIO_DSP.data.num_channels > 1) + ? apply_crossfeed : NULL; +} + +void dsp_set_crossfeed_direct_gain(int gain) +{ + crossfeed_data.gain = get_replaygain_int(gain * 10) << 7; + /* If gain is negative, the calculation overflowed and we need to clamp */ + if (crossfeed_data.gain < 0) + crossfeed_data.gain = 0x7fffffff; +} + +/* Both gains should be below 0 dB */ +void dsp_set_crossfeed_cross_params(long lf_gain, long hf_gain, long cutoff) +{ + int32_t *c = crossfeed_data.coefs; + long scaler = get_replaygain_int(lf_gain * 10) << 7; + + cutoff = 0xffffffff/NATIVE_FREQUENCY*cutoff; + hf_gain -= lf_gain; + /* Divide cutoff by sqrt(10^(hf_gain/20)) to place cutoff at the -3 dB + * point instead of shelf midpoint. This is for compatibility with the old + * crossfeed shelf filter and should be removed if crossfeed settings are + * ever made incompatible for any other good reason. + */ + cutoff = fp_div(cutoff, get_replaygain_int(hf_gain*5), 24); + filter_shelf_coefs(cutoff, hf_gain, false, c); + /* Scale coefs by LF gain and shift them to s0.31 format. We have no gains + * over 1 and can do this safely + */ + c[0] = FRACMUL_SHL(c[0], scaler, 4); + c[1] = FRACMUL_SHL(c[1], scaler, 4); + c[2] <<= 4; +} + +/* Apply a constant gain to the samples (e.g., for ReplayGain). + * Note that this must be called before the resampler. + */ +#ifndef DSP_HAVE_ASM_APPLY_GAIN +static void dsp_apply_gain(int count, struct dsp_data *data, int32_t *buf[]) +{ + const int32_t gain = data->gain; + int ch; + + for (ch = 0; ch < data->num_channels; ch++) + { + int32_t *d = buf[ch]; + int i; + + for (i = 0; i < count; i++) + d[i] = FRACMUL_SHL(d[i], gain, 8); + } +} +#endif /* DSP_HAVE_ASM_APPLY_GAIN */ + +/* Combine all gains to a global gain. */ +static void set_gain(struct dsp_config *dsp) +{ + /* gains are in S7.24 format */ + dsp->data.gain = DEFAULT_GAIN; + + /* Replay gain not relevant to voice */ + if (dsp == &AUDIO_DSP && replaygain) + { + dsp->data.gain = replaygain; + } + + if (dsp->eq_process && eq_precut) + { + dsp->data.gain = fp_mul(dsp->data.gain, eq_precut, 24); + } + +#ifdef HAVE_SW_VOLUME_CONTROL + if (global_settings.volume < SW_VOLUME_MAX || + global_settings.volume > SW_VOLUME_MIN) + { + int vol_gain = get_replaygain_int(global_settings.volume * 100); + dsp->data.gain = (long) (((int64_t) dsp->data.gain * vol_gain) >> 24); + } +#endif + + if (dsp->data.gain == DEFAULT_GAIN) + { + dsp->data.gain = 0; + } + else + { + dsp->data.gain >>= 1; /* convert gain to S8.23 format */ + } + + dsp->apply_gain = dsp->data.gain != 0 ? dsp_apply_gain : NULL; +} + +/** + * Update the amount to cut the audio before applying the equalizer. + * + * @param precut to apply in decibels (multiplied by 10) + */ +void dsp_set_eq_precut(int precut) +{ + eq_precut = get_replaygain_int(precut * -10); + set_gain(&AUDIO_DSP); +} + +/** + * Synchronize the equalizer filter coefficients with the global settings. + * + * @param band the equalizer band to synchronize + */ +void dsp_set_eq_coefs(int band) +{ + /* Adjust setting pointer to the band we actually want to change */ + struct eq_band_setting *setting = &global_settings.eq_band_settings[band]; + + /* Convert user settings to format required by coef generator functions */ + unsigned long cutoff = 0xffffffff / NATIVE_FREQUENCY * setting->cutoff; + unsigned long q = setting->q; + int gain = setting->gain; + + if (q == 0) + q = 1; + + /* NOTE: The coef functions assume the EMAC unit is in fractional mode, + which it should be, since we're executed from the main thread. */ + + /* Assume a band is disabled if the gain is zero */ + if (gain == 0) + { + eq_data.enabled[band] = 0; + } + else + { + if (band == 0) + eq_ls_coefs(cutoff, q, gain, eq_data.filters[band].coefs); + else if (band == 4) + eq_hs_coefs(cutoff, q, gain, eq_data.filters[band].coefs); + else + eq_pk_coefs(cutoff, q, gain, eq_data.filters[band].coefs); + + eq_data.enabled[band] = 1; + } +} + +/* Apply EQ filters to those bands that have got it switched on. */ +static void eq_process(int count, int32_t *buf[]) +{ + static const int shifts[] = + { + EQ_SHELF_SHIFT, /* low shelf */ + EQ_PEAK_SHIFT, /* peaking */ + EQ_PEAK_SHIFT, /* peaking */ + EQ_PEAK_SHIFT, /* peaking */ + EQ_SHELF_SHIFT, /* high shelf */ + }; + unsigned int channels = AUDIO_DSP.data.num_channels; + int i; + + /* filter configuration currently is 1 low shelf filter, 3 band peaking + filters and 1 high shelf filter, in that order. we need to know this + so we can choose the correct shift factor. + */ + for (i = 0; i < 5; i++) + { + if (!eq_data.enabled[i]) + continue; + eq_filter(buf, &eq_data.filters[i], count, channels, shifts[i]); + } +} + +/** + * Use to enable the equalizer. + * + * @param enable true to enable the equalizer + */ +void dsp_set_eq(bool enable) +{ + AUDIO_DSP.eq_process = enable ? eq_process : NULL; + set_gain(&AUDIO_DSP); +} + +static void dsp_set_stereo_width(int value) +{ + long width, straight, cross; + + width = value * 0x7fffff / 100; + + if (value <= 100) + { + straight = (0x7fffff + width) / 2; + cross = straight - width; + } + else + { + /* straight = (1 + width) / (2 * width) */ + straight = ((int64_t)(0x7fffff + width) << 22) / width; + cross = straight - 0x7fffff; + } + + dsp_sw_gain = straight << 8; + dsp_sw_cross = cross << 8; +} + +/** + * Implements the different channel configurations and stereo width. + */ + +/* SOUND_CHAN_STEREO mode is a noop so has no function - just outline one for + * completeness. */ +#if 0 +static void channels_process_sound_chan_stereo(int count, int32_t *buf[]) +{ + /* The channels are each just themselves */ + (void)count; (void)buf; +} +#endif + +#ifndef DSP_HAVE_ASM_SOUND_CHAN_MONO +static void channels_process_sound_chan_mono(int count, int32_t *buf[]) +{ + int32_t *sl = buf[0], *sr = buf[1]; + + while (count-- > 0) + { + int32_t lr = *sl/2 + *sr/2; + *sl++ = lr; + *sr++ = lr; + } +} +#endif /* DSP_HAVE_ASM_SOUND_CHAN_MONO */ + +#ifndef DSP_HAVE_ASM_SOUND_CHAN_CUSTOM +static void channels_process_sound_chan_custom(int count, int32_t *buf[]) +{ + const int32_t gain = dsp_sw_gain; + const int32_t cross = dsp_sw_cross; + int32_t *sl = buf[0], *sr = buf[1]; + + while (count-- > 0) + { + int32_t l = *sl; + int32_t r = *sr; + *sl++ = FRACMUL(l, gain) + FRACMUL(r, cross); + *sr++ = FRACMUL(r, gain) + FRACMUL(l, cross); + } +} +#endif /* DSP_HAVE_ASM_SOUND_CHAN_CUSTOM */ + +static void channels_process_sound_chan_mono_left(int count, int32_t *buf[]) +{ + /* Just copy over the other channel */ + memcpy(buf[1], buf[0], count * sizeof (*buf)); +} + +static void channels_process_sound_chan_mono_right(int count, int32_t *buf[]) +{ + /* Just copy over the other channel */ + memcpy(buf[0], buf[1], count * sizeof (*buf)); +} + +#ifndef DSP_HAVE_ASM_SOUND_CHAN_KARAOKE +static void channels_process_sound_chan_karaoke(int count, int32_t *buf[]) +{ + int32_t *sl = buf[0], *sr = buf[1]; + + while (count-- > 0) + { + int32_t ch = *sl/2 - *sr/2; + *sl++ = ch; + *sr++ = -ch; + } +} +#endif /* DSP_HAVE_ASM_SOUND_CHAN_KARAOKE */ + +static void dsp_set_channel_config(int value) +{ + static const channels_process_fn_type channels_process_functions[] = + { + /* SOUND_CHAN_STEREO = All-purpose index for no channel processing */ + [SOUND_CHAN_STEREO] = NULL, + [SOUND_CHAN_MONO] = channels_process_sound_chan_mono, + [SOUND_CHAN_CUSTOM] = channels_process_sound_chan_custom, + [SOUND_CHAN_MONO_LEFT] = channels_process_sound_chan_mono_left, + [SOUND_CHAN_MONO_RIGHT] = channels_process_sound_chan_mono_right, + [SOUND_CHAN_KARAOKE] = channels_process_sound_chan_karaoke, + }; + + if ((unsigned)value >= ARRAYLEN(channels_process_functions) || + AUDIO_DSP.stereo_mode == STEREO_MONO) + { + value = SOUND_CHAN_STEREO; + } + + /* This doesn't apply to voice */ + channels_mode = value; + AUDIO_DSP.channels_process = channels_process_functions[value]; +} + +#if CONFIG_CODEC == SWCODEC + +#ifdef HAVE_SW_TONE_CONTROLS +static void set_tone_controls(void) +{ + filter_bishelf_coefs(0xffffffff/NATIVE_FREQUENCY*200, + 0xffffffff/NATIVE_FREQUENCY*3500, + bass, treble, -prescale, + AUDIO_DSP.tone_filter.coefs); + /* Sync the voice dsp coefficients */ + memcpy(&VOICE_DSP.tone_filter.coefs, AUDIO_DSP.tone_filter.coefs, + sizeof (VOICE_DSP.tone_filter.coefs)); +} +#endif + +/* Hook back from firmware/ part of audio, which can't/shouldn't call apps/ + * code directly. + */ +int dsp_callback(int msg, intptr_t param) +{ + switch (msg) + { +#ifdef HAVE_SW_TONE_CONTROLS + case DSP_CALLBACK_SET_PRESCALE: + prescale = param; + set_tone_controls(); + break; + /* prescaler is always set after calling any of these, so we wait with + * calculating coefs until the above case is hit. + */ + case DSP_CALLBACK_SET_BASS: + bass = param; + break; + case DSP_CALLBACK_SET_TREBLE: + treble = param; + break; +#ifdef HAVE_SW_VOLUME_CONTROL + case DSP_CALLBACK_SET_SW_VOLUME: + set_gain(&AUDIO_DSP); + break; +#endif +#endif + case DSP_CALLBACK_SET_CHANNEL_CONFIG: + dsp_set_channel_config(param); + break; + case DSP_CALLBACK_SET_STEREO_WIDTH: + dsp_set_stereo_width(param); + break; + default: + break; + } + return 0; +} +#endif + +/* Process and convert src audio to dst based on the DSP configuration, + * reading count number of audio samples. dst is assumed to be large + * enough; use dsp_output_count() to get the required number. src is an + * array of pointers; for mono and interleaved stereo, it contains one + * pointer to the start of the audio data and the other is ignored; for + * non-interleaved stereo, it contains two pointers, one for each audio + * channel. Returns number of bytes written to dst. + */ +int dsp_process(struct dsp_config *dsp, char *dst, const char *src[], int count) +{ + static int32_t *tmp[2]; /* tdspeed_doit() needs it static */ + static long last_yield; + long tick; + int written = 0; + +#if defined(CPU_COLDFIRE) + /* set emac unit for dsp processing, and save old macsr, we're running in + codec thread context at this point, so can't clobber it */ + unsigned long old_macsr = coldfire_get_macsr(); + coldfire_set_macsr(EMAC_FRACTIONAL | EMAC_SATURATE); +#endif + + if (new_gain) + dsp_set_replaygain(); /* Gain has changed */ + + /* Perform at least one yield before starting */ + last_yield = current_tick; + yield(); + + /* Testing function pointers for NULL is preferred since the pointer + will be preloaded to be used for the call if not. */ + while (count > 0) + { + int samples = MIN(sample_buf_count, count); + count -= samples; + + dsp->input_samples(samples, src, tmp); + +#ifdef HAVE_PITCHSCREEN + if (dsp->tdspeed_active) + samples = tdspeed_doit(tmp, samples); +#endif + + int chunk_offset = 0; + while (samples > 0) + { + int32_t *t2[2]; + t2[0] = tmp[0]+chunk_offset; + t2[1] = tmp[1]+chunk_offset; + + int chunk = MIN(sample_buf_count, samples); + chunk_offset += chunk; + samples -= chunk; + + if (dsp->apply_gain) + dsp->apply_gain(chunk, &dsp->data, t2); + + if (dsp->resample && (chunk = resample(dsp, chunk, t2)) <= 0) + break; /* I'm pretty sure we're downsampling here */ + + if (dsp->apply_crossfeed) + dsp->apply_crossfeed(chunk, t2); + + if (dsp->eq_process) + dsp->eq_process(chunk, t2); + +#ifdef HAVE_SW_TONE_CONTROLS + if ((bass | treble) != 0) + eq_filter(t2, &dsp->tone_filter, chunk, + dsp->data.num_channels, FILTER_BISHELF_SHIFT); +#endif + + if (dsp->channels_process) + dsp->channels_process(chunk, t2); + + if (dsp->compressor_process) + dsp->compressor_process(chunk, &dsp->data, t2); + + dsp->output_samples(chunk, &dsp->data, (const int32_t **)t2, (int16_t *)dst); + + written += chunk; + dst += chunk * sizeof (int16_t) * 2; + + /* yield at least once each tick */ + tick = current_tick; + if (TIME_AFTER(tick, last_yield)) + { + last_yield = tick; + yield(); + } + } + } + +#if defined(CPU_COLDFIRE) + /* set old macsr again */ + coldfire_set_macsr(old_macsr); +#endif + return written; +} + +/* Given count number of input samples, calculate the maximum number of + * samples of output data that would be generated (the calculation is not + * entirely exact and rounds upwards to be on the safe side; during + * resampling, the number of samples generated depends on the current state + * of the resampler). + */ +/* dsp_input_size MUST be called afterwards */ +int dsp_output_count(struct dsp_config *dsp, int count) +{ +#ifdef HAVE_PITCHSCREEN + if (dsp->tdspeed_active) + count = tdspeed_est_output_size(); +#endif + if (dsp->resample) + { + count = (int)(((unsigned long)count * NATIVE_FREQUENCY + + (dsp->frequency - 1)) / dsp->frequency); + } + + /* Now we have the resampled sample count which must not exceed + * resample_buf_count to avoid resample buffer overflow. One + * must call dsp_input_count() to get the correct input sample + * count. + */ + if (count > resample_buf_count) + count = resample_buf_count; + + return count; +} + +/* Given count output samples, calculate number of input samples + * that would be consumed in order to fill the output buffer. + */ +int dsp_input_count(struct dsp_config *dsp, int count) +{ + /* count is now the number of resampled input samples. Convert to + original input samples. */ + if (dsp->resample) + { + /* Use the real resampling delta = + * dsp->frequency * 65536 / NATIVE_FREQUENCY, and + * round towards zero to avoid buffer overflows. */ + count = (int)(((unsigned long)count * + dsp->data.resample_data.delta) >> 16); + } + +#ifdef HAVE_PITCHSCREEN + if (dsp->tdspeed_active) + count = tdspeed_est_input_size(count); +#endif + + return count; +} + +static void dsp_set_gain_var(long *var, long value) +{ + *var = value; + new_gain = true; +} + +static void dsp_update_functions(struct dsp_config *dsp) +{ + sample_input_new_format(dsp); + sample_output_new_format(dsp); + if (dsp == &AUDIO_DSP) + dsp_set_crossfeed(crossfeed_enabled); +} + +intptr_t dsp_configure(struct dsp_config *dsp, int setting, intptr_t value) +{ + switch (setting) + { + case DSP_MYDSP: + switch (value) + { + case CODEC_IDX_AUDIO: + return (intptr_t)&AUDIO_DSP; + case CODEC_IDX_VOICE: + return (intptr_t)&VOICE_DSP; + default: + return (intptr_t)NULL; + } + + case DSP_SET_FREQUENCY: + memset(&dsp->data.resample_data, 0, sizeof (dsp->data.resample_data)); + /* Fall through!!! */ + case DSP_SWITCH_FREQUENCY: + dsp->codec_frequency = (value == 0) ? NATIVE_FREQUENCY : value; + /* Account for playback speed adjustment when setting dsp->frequency + if we're called from the main audio thread. Voice UI thread should + not need this feature. + */ +#ifdef HAVE_PITCHSCREEN + if (dsp == &AUDIO_DSP) + dsp->frequency = pitch_ratio * dsp->codec_frequency / PITCH_SPEED_100; + else +#endif + dsp->frequency = dsp->codec_frequency; + + resampler_new_delta(dsp); +#ifdef HAVE_PITCHSCREEN + tdspeed_setup(dsp); +#endif + break; + + case DSP_SET_SAMPLE_DEPTH: + dsp->sample_depth = value; + + if (dsp->sample_depth <= NATIVE_DEPTH) + { + dsp->data.frac_bits = WORD_FRACBITS; + dsp->sample_bytes = sizeof (int16_t); /* samples are 16 bits */ + dsp->data.clip_max = ((1 << WORD_FRACBITS) - 1); + dsp->data.clip_min = -((1 << WORD_FRACBITS)); + } + else + { + dsp->data.frac_bits = value; + dsp->sample_bytes = sizeof (int32_t); /* samples are 32 bits */ + dsp->data.clip_max = (1 << value) - 1; + dsp->data.clip_min = -(1 << value); + } + + dsp->data.output_scale = dsp->data.frac_bits + 1 - NATIVE_DEPTH; + sample_input_new_format(dsp); + dither_init(dsp); + break; + + case DSP_SET_STEREO_MODE: + dsp->stereo_mode = value; + dsp->data.num_channels = value == STEREO_MONO ? 1 : 2; + dsp_update_functions(dsp); +#ifdef HAVE_PITCHSCREEN + tdspeed_setup(dsp); +#endif + break; + + case DSP_RESET: + dsp->stereo_mode = STEREO_NONINTERLEAVED; + dsp->data.num_channels = 2; + dsp->sample_depth = NATIVE_DEPTH; + dsp->data.frac_bits = WORD_FRACBITS; + dsp->sample_bytes = sizeof (int16_t); + dsp->data.output_scale = dsp->data.frac_bits + 1 - NATIVE_DEPTH; + dsp->data.clip_max = ((1 << WORD_FRACBITS) - 1); + dsp->data.clip_min = -((1 << WORD_FRACBITS)); + dsp->codec_frequency = dsp->frequency = NATIVE_FREQUENCY; + + if (dsp == &AUDIO_DSP) + { + track_gain = 0; + album_gain = 0; + track_peak = 0; + album_peak = 0; + new_gain = true; + } + + dsp_update_functions(dsp); + resampler_new_delta(dsp); +#ifdef HAVE_PITCHSCREEN + tdspeed_setup(dsp); +#endif + if (dsp == &AUDIO_DSP) + compressor_reset(); + break; + + case DSP_FLUSH: + memset(&dsp->data.resample_data, 0, + sizeof (dsp->data.resample_data)); + resampler_new_delta(dsp); + dither_init(dsp); +#ifdef HAVE_PITCHSCREEN + tdspeed_setup(dsp); +#endif + if (dsp == &AUDIO_DSP) + compressor_reset(); + break; + + case DSP_SET_TRACK_GAIN: + if (dsp == &AUDIO_DSP) + dsp_set_gain_var(&track_gain, value); + break; + + case DSP_SET_ALBUM_GAIN: + if (dsp == &AUDIO_DSP) + dsp_set_gain_var(&album_gain, value); + break; + + case DSP_SET_TRACK_PEAK: + if (dsp == &AUDIO_DSP) + dsp_set_gain_var(&track_peak, value); + break; + + case DSP_SET_ALBUM_PEAK: + if (dsp == &AUDIO_DSP) + dsp_set_gain_var(&album_peak, value); + break; + + default: + return 0; + } + + return 1; +} + +int get_replaygain_mode(bool have_track_gain, bool have_album_gain) +{ + int type; + + bool track = ((global_settings.replaygain_type == REPLAYGAIN_TRACK) + || ((global_settings.replaygain_type == REPLAYGAIN_SHUFFLE) + && global_settings.playlist_shuffle)); + + type = (!track && have_album_gain) ? REPLAYGAIN_ALBUM + : have_track_gain ? REPLAYGAIN_TRACK : -1; + + return type; +} + +void dsp_set_replaygain(void) +{ + long gain = 0; + + new_gain = false; + + if ((global_settings.replaygain_type != REPLAYGAIN_OFF) || + global_settings.replaygain_noclip) + { + bool track_mode = get_replaygain_mode(track_gain != 0, + album_gain != 0) == REPLAYGAIN_TRACK; + long peak = (track_mode || !album_peak) ? track_peak : album_peak; + + if (global_settings.replaygain_type != REPLAYGAIN_OFF) + { + gain = (track_mode || !album_gain) ? track_gain : album_gain; + + if (global_settings.replaygain_preamp) + { + long preamp = get_replaygain_int( + global_settings.replaygain_preamp * 10); + + gain = (long) (((int64_t) gain * preamp) >> 24); + } + } + + if (gain == 0) + { + /* So that noclip can work even with no gain information. */ + gain = DEFAULT_GAIN; + } + + if (global_settings.replaygain_noclip && (peak != 0) + && ((((int64_t) gain * peak) >> 24) >= DEFAULT_GAIN)) + { + gain = (((int64_t) DEFAULT_GAIN << 24) / peak); + } + + if (gain == DEFAULT_GAIN) + { + /* Nothing to do, disable processing. */ + gain = 0; + } + } + + /* Store in S7.24 format to simplify calculations. */ + replaygain = gain; + set_gain(&AUDIO_DSP); +} + +/** SET COMPRESSOR + * Called by the menu system to configure the compressor process */ +void dsp_set_compressor(void) +{ + /* enable/disable the compressor */ + AUDIO_DSP.compressor_process = compressor_update() ? + compressor_process : NULL; +} diff --git a/lib/rbcodec/dsp/dsp.h b/lib/rbcodec/dsp/dsp.h new file mode 100644 index 0000000000..2a00f649f8 --- /dev/null +++ b/lib/rbcodec/dsp/dsp.h @@ -0,0 +1,125 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Miika Pekkarinen + * + * 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. + * + ****************************************************************************/ + +#ifndef _DSP_H +#define _DSP_H + +#include +#include + +#define NATIVE_FREQUENCY 44100 + +enum +{ + STEREO_INTERLEAVED = 0, + STEREO_NONINTERLEAVED, + STEREO_MONO, + STEREO_NUM_MODES, +}; + +enum +{ + CODEC_IDX_AUDIO = 0, + CODEC_IDX_VOICE, +}; + +enum +{ + DSP_MYDSP = 1, + DSP_SET_FREQUENCY, + DSP_SWITCH_FREQUENCY, + DSP_SET_SAMPLE_DEPTH, + DSP_SET_STEREO_MODE, + DSP_RESET, + DSP_FLUSH, + DSP_SET_TRACK_GAIN, + DSP_SET_ALBUM_GAIN, + DSP_SET_TRACK_PEAK, + DSP_SET_ALBUM_PEAK, + DSP_CROSSFEED +}; + + +/**************************************************************************** + * NOTE: Any assembly routines that use these structures must be updated + * if current data members are moved or changed. + */ +struct resample_data +{ + uint32_t delta; /* 00h */ + uint32_t phase; /* 04h */ + int32_t last_sample[2]; /* 08h */ + /* 10h */ +}; + +/* This is for passing needed data to external dsp routines. If another + * dsp parameter needs to be passed, add to the end of the structure + * and remove from dsp_config. + * If another function type becomes assembly/external and requires dsp + * config info, add a pointer paramter of type "struct dsp_data *". + * If removing something from other than the end, reserve the spot or + * else update every implementation for every target. + * Be sure to add the offset of the new member for easy viewing as well. :) + * It is the first member of dsp_config and all members can be accessesed + * through the main aggregate but this is intended to make a safe haven + * for these items whereas the c part can be rearranged at will. dsp_data + * could even moved within dsp_config without disurbing the order. + */ +struct dsp_data +{ + int output_scale; /* 00h */ + int num_channels; /* 04h */ + struct resample_data resample_data; /* 08h */ + int32_t clip_min; /* 18h */ + int32_t clip_max; /* 1ch */ + int32_t gain; /* 20h - Note that this is in S8.23 format. */ + int frac_bits; /* 24h */ + /* 28h */ +}; + +struct dsp_config; + +int dsp_process(struct dsp_config *dsp, char *dest, + const char *src[], int count); +int dsp_input_count(struct dsp_config *dsp, int count); +int dsp_output_count(struct dsp_config *dsp, int count); +intptr_t dsp_configure(struct dsp_config *dsp, int setting, + intptr_t value); +int get_replaygain_mode(bool have_track_gain, bool have_album_gain); +void dsp_set_replaygain(void); +void dsp_set_crossfeed(bool enable); +void dsp_set_crossfeed_direct_gain(int gain); +void dsp_set_crossfeed_cross_params(long lf_gain, long hf_gain, + long cutoff); +void dsp_set_eq(bool enable); +void dsp_set_eq_precut(int precut); +void dsp_set_eq_coefs(int band); +void dsp_dither_enable(bool enable); +void dsp_timestretch_enable(bool enable); +bool dsp_timestretch_available(void); +void sound_set_pitch(int32_t r); +int32_t sound_get_pitch(void); +void dsp_set_timestretch(int32_t percent); +int32_t dsp_get_timestretch(void); +int dsp_callback(int msg, intptr_t param); +void dsp_set_compressor(void); + +#endif diff --git a/lib/rbcodec/dsp/dsp_arm.S b/lib/rbcodec/dsp/dsp_arm.S new file mode 100644 index 0000000000..7e360749a3 --- /dev/null +++ b/lib/rbcodec/dsp/dsp_arm.S @@ -0,0 +1,561 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006-2007 Thom Johansen + * + * 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. + * + ****************************************************************************/ + #include "config.h" + +/**************************************************************************** + * void channels_process_sound_chan_mono(int count, int32_t *buf[]) + */ + +#include "config.h" + + .section .icode, "ax", %progbits + .align 2 + .global channels_process_sound_chan_mono + .type channels_process_sound_chan_mono, %function +channels_process_sound_chan_mono: + @ input: r0 = count, r1 = buf + stmfd sp!, { r4, lr } @ + @ + ldmia r1, { r1, r2 } @ r1 = buf[0], r2 = buf[1] + subs r0, r0, #1 @ odd: end at 0; even: end at -1 + beq .mono_singlesample @ Zero? Only one sample! + @ +.monoloop: @ + ldmia r1, { r3, r4 } @ r3, r4 = Li0, Li1 + ldmia r2, { r12, r14 } @ r12, r14 = Ri0, Ri1 + mov r3, r3, asr #1 @ Mo0 = Li0 / 2 + Ri0 / 2 + mov r4, r4, asr #1 @ Mo1 = Li1 / 2 + Ri1 / 2 + add r12, r3, r12, asr #1 @ + add r14, r4, r14, asr #1 @ + subs r0, r0, #2 @ + stmia r1!, { r12, r14 } @ store Mo0, Mo1 + stmia r2!, { r12, r14 } @ store Mo0, Mo1 + bgt .monoloop @ + @ + ldmpc cond=lt, regs=r4 @ if count was even, we're done + @ +.mono_singlesample: @ + ldr r3, [r1] @ r3 = Ls + ldr r12, [r2] @ r12 = Rs + mov r3, r3, asr #1 @ Mo = Ls / 2 + Rs / 2 + add r12, r3, r12, asr #1 @ + str r12, [r1] @ store Mo + str r12, [r2] @ store Mo + @ + ldmpc regs=r4 @ + .size channels_process_sound_chan_mono, \ + .-channels_process_sound_chan_mono + +/**************************************************************************** + * void channels_process_sound_chan_custom(int count, int32_t *buf[]) + */ + .section .icode, "ax", %progbits + .align 2 + .global channels_process_sound_chan_custom + .type channels_process_sound_chan_custom, %function +channels_process_sound_chan_custom: + stmfd sp!, { r4-r10, lr } + + ldr r3, =dsp_sw_gain + ldr r4, =dsp_sw_cross + + ldmia r1, { r1, r2 } @ r1 = buf[0], r2 = buf[1] + ldr r3, [r3] @ r3 = dsp_sw_gain + ldr r4, [r4] @ r4 = dsp_sw_cross + + subs r0, r0, #1 + beq .custom_single_sample @ Zero? Only one sample! + +.custom_loop: + ldmia r1, { r5, r6 } @ r5 = Li0, r6 = Li1 + ldmia r2, { r7, r8 } @ r7 = Ri0, r8 = Ri1 + + subs r0, r0, #2 + + smull r9, r10, r5, r3 @ Lc0 = Li0*gain + smull r12, r14, r7, r3 @ Rc0 = Ri0*gain + smlal r9, r10, r7, r4 @ Lc0 += Ri0*cross + smlal r12, r14, r5, r4 @ Rc0 += Li0*cross + + mov r9, r9, lsr #31 @ Convert to s0.31 + mov r12, r12, lsr #31 + orr r5, r9, r10, asl #1 + orr r7, r12, r14, asl #1 + + smull r9, r10, r6, r3 @ Lc1 = Li1*gain + smull r12, r14, r8, r3 @ Rc1 = Ri1*gain + smlal r9, r10, r8, r4 @ Lc1 += Ri1*cross + smlal r12, r14, r6, r4 @ Rc1 += Li1*cross + + mov r9, r9, lsr #31 @ Convert to s0.31 + mov r12, r12, lsr #31 + orr r6, r9, r10, asl #1 + orr r8, r12, r14, asl #1 + + stmia r1!, { r5, r6 } @ Store Lc0, Lc1 + stmia r2!, { r7, r8 } @ Store Rc0, Rc1 + + bgt .custom_loop + + ldmpc cond=lt, regs=r4-r10 @ < 0? even count + +.custom_single_sample: + ldr r5, [r1] @ handle odd sample + ldr r7, [r2] + + smull r9, r10, r5, r3 @ Lc0 = Li0*gain + smull r12, r14, r7, r3 @ Rc0 = Ri0*gain + smlal r9, r10, r7, r4 @ Lc0 += Ri0*cross + smlal r12, r14, r5, r4 @ Rc0 += Li0*cross + + mov r9, r9, lsr #31 @ Convert to s0.31 + mov r12, r12, lsr #31 + orr r5, r9, r10, asl #1 + orr r7, r12, r14, asl #1 + + str r5, [r1] @ Store Lc0 + str r7, [r2] @ Store Rc0 + + ldmpc regs=r4-r10 + .size channels_process_sound_chan_custom, \ + .-channels_process_sound_chan_custom + +/**************************************************************************** + * void channels_process_sound_chan_karaoke(int count, int32_t *buf[]) + */ + .section .icode, "ax", %progbits + .align 2 + .global channels_process_sound_chan_karaoke + .type channels_process_sound_chan_karaoke, %function +channels_process_sound_chan_karaoke: + @ input: r0 = count, r1 = buf + stmfd sp!, { r4, lr } @ + @ + ldmia r1, { r1, r2 } @ r1 = buf[0], r2 = buf[1] + subs r0, r0, #1 @ odd: end at 0; even: end at -1 + beq .karaoke_singlesample @ Zero? Only one sample! + @ +.karaokeloop: @ + ldmia r1, { r3, r4 } @ r3, r4 = Li0, Li1 + ldmia r2, { r12, r14 } @ r12, r14 = Ri0, Ri1 + mov r3, r3, asr #1 @ Lo0 = Li0 / 2 - Ri0 / 2 + mov r4, r4, asr #1 @ Lo1 = Li1 / 2 - Ri1 / 2 + sub r3, r3, r12, asr #1 @ + sub r4, r4, r14, asr #1 @ + rsb r12, r3, #0 @ Ro0 = -Lk0 = Rs0 / 2 - Ls0 / 2 + rsb r14, r4, #0 @ Ro1 = -Lk1 = Ri1 / 2 - Li1 / 2 + subs r0, r0, #2 @ + stmia r1!, { r3, r4 } @ store Lo0, Lo1 + stmia r2!, { r12, r14 } @ store Ro0, Ro1 + bgt .karaokeloop @ + @ + ldmpc cond=lt, regs=r4 @ if count was even, we're done + @ +.karaoke_singlesample: @ + ldr r3, [r1] @ r3 = Li + ldr r12, [r2] @ r12 = Ri + mov r3, r3, asr #1 @ Lk = Li / 2 - Ri /2 + sub r3, r3, r12, asr #1 @ + rsb r12, r3, #0 @ Rk = -Lo = Ri / 2 - Li / 2 + str r3, [r1] @ store Lo + str r12, [r2] @ store Ro + @ + ldmpc regs=r4 @ + .size channels_process_sound_chan_karaoke, \ + .-channels_process_sound_chan_karaoke + +#if ARM_ARCH < 6 +/**************************************************************************** + * void sample_output_mono(int count, struct dsp_data *data, + * const int32_t *src[], int16_t *dst) + */ + .section .icode, "ax", %progbits + .align 2 + .global sample_output_mono + .type sample_output_mono, %function +sample_output_mono: + @ input: r0 = count, r1 = data, r2 = src, r3 = dst + stmfd sp!, { r4-r6, lr } + + ldr r1, [r1] @ lr = data->output_scale + ldr r2, [r2] @ r2 = src[0] + + mov r4, #1 + mov r4, r4, lsl r1 @ r4 = 1 << (scale-1) + mov r4, r4, lsr #1 + mvn r14, #0x8000 @ r14 = 0xffff7fff, needed for + @ clipping and masking + subs r0, r0, #1 @ + beq .som_singlesample @ Zero? Only one sample! + +.somloop: + ldmia r2!, { r5, r6 } + add r5, r5, r4 @ r6 = (r6 + 1<<(scale-1)) >> scale + mov r5, r5, asr r1 + mov r12, r5, asr #15 + teq r12, r12, asr #31 + eorne r5, r14, r5, asr #31 @ Clip (-32768...+32767) + add r6, r6, r4 + mov r6, r6, asr r1 @ r7 = (r7 + 1<<(scale-1)) >> scale + mov r12, r6, asr #15 + teq r12, r12, asr #31 + eorne r6, r14, r6, asr #31 @ Clip (-32768...+32767) + + and r5, r5, r14, lsr #16 + and r6, r6, r14, lsr #16 + orr r5, r5, r5, lsl #16 @ pack first 2 halfwords into 1 word + orr r6, r6, r6, lsl #16 @ pack last 2 halfwords into 1 word + stmia r3!, { r5, r6 } + + subs r0, r0, #2 + bgt .somloop + + ldmpc cond=lt, regs=r4-r6 @ even 'count'? return + +.som_singlesample: + ldr r5, [r2] @ do odd sample + add r5, r5, r4 + mov r5, r5, asr r1 + mov r12, r5, asr #15 + teq r12, r12, asr #31 + eorne r5, r14, r5, asr #31 + + and r5, r5, r14, lsr #16 @ pack 2 halfwords into 1 word + orr r5, r5, r5, lsl #16 + str r5, [r3] + + ldmpc regs=r4-r6 + .size sample_output_mono, .-sample_output_mono + +/**************************************************************************** + * void sample_output_stereo(int count, struct dsp_data *data, + * const int32_t *src[], int16_t *dst) + */ + .section .icode, "ax", %progbits + .align 2 + .global sample_output_stereo + .type sample_output_stereo, %function +sample_output_stereo: + @ input: r0 = count, r1 = data, r2 = src, r3 = dst + stmfd sp!, { r4-r9, lr } + + ldr r1, [r1] @ r1 = data->output_scale + ldmia r2, { r2, r5 } @ r2 = src[0], r5 = src[1] + + mov r4, #1 + mov r4, r4, lsl r1 @ r4 = 1 << (scale-1) + mov r4, r4, lsr #1 @ + + mvn r14, #0x8000 @ r14 = 0xffff7fff, needed for + @ clipping and masking + subs r0, r0, #1 @ + beq .sos_singlesample @ Zero? Only one sample! + +.sosloop: + ldmia r2!, { r6, r7 } @ 2 left + ldmia r5!, { r8, r9 } @ 2 right + + add r6, r6, r4 @ r6 = (r6 + 1<<(scale-1)) >> scale + mov r6, r6, asr r1 + mov r12, r6, asr #15 + teq r12, r12, asr #31 + eorne r6, r14, r6, asr #31 @ Clip (-32768...+32767) + add r7, r7, r4 + mov r7, r7, asr r1 @ r7 = (r7 + 1<<(scale-1)) >> scale + mov r12, r7, asr #15 + teq r12, r12, asr #31 + eorne r7, r14, r7, asr #31 @ Clip (-32768...+32767) + + add r8, r8, r4 @ r8 = (r8 + 1<<(scale-1)) >> scale + mov r8, r8, asr r1 + mov r12, r8, asr #15 + teq r12, r12, asr #31 + eorne r8, r14, r8, asr #31 @ Clip (-32768...+32767) + add r9, r9, r4 @ r9 = (r9 + 1<<(scale-1)) >> scale + mov r9, r9, asr r1 + mov r12, r9, asr #15 + teq r12, r12, asr #31 + eorne r9, r14, r9, asr #31 @ Clip (-32768...+32767) + + and r6, r6, r14, lsr #16 @ pack first 2 halfwords into 1 word + orr r8, r6, r8, asl #16 + and r7, r7, r14, lsr #16 @ pack last 2 halfwords into 1 word + orr r9, r7, r9, asl #16 + + stmia r3!, { r8, r9 } + + subs r0, r0, #2 + bgt .sosloop + + ldmpc cond=lt, regs=r4-r9 @ even 'count'? return + +.sos_singlesample: + ldr r6, [r2] @ left odd sample + ldr r8, [r5] @ right odd sample + + add r6, r6, r4 @ r6 = (r7 + 1<<(scale-1)) >> scale + mov r6, r6, asr r1 + mov r12, r6, asr #15 + teq r12, r12, asr #31 + eorne r6, r14, r6, asr #31 @ Clip (-32768...+32767) + add r8, r8, r4 @ r8 = (r8 + 1<<(scale-1)) >> scale + mov r8, r8, asr r1 + mov r12, r8, asr #15 + teq r12, r12, asr #31 + eorne r8, r14, r8, asr #31 @ Clip (-32768...+32767) + + and r6, r6, r14, lsr #16 @ pack 2 halfwords into 1 word + orr r8, r6, r8, asl #16 + + str r8, [r3] + + ldmpc regs=r4-r9 + .size sample_output_stereo, .-sample_output_stereo +#endif /* ARM_ARCH < 6 */ + +/**************************************************************************** + * void apply_crossfeed(int count, int32_t* src[]) + */ + .section .text + .global apply_crossfeed +apply_crossfeed: + @ unfortunately, we ended up in a bit of a register squeeze here, and need + @ to keep the count on the stack :/ + stmdb sp!, { r4-r11, lr } @ stack modified regs + ldmia r1, { r2-r3 } @ r2 = src[0], r3 = src[1] + + ldr r1, =crossfeed_data + ldmia r1!, { r4-r11 } @ load direct gain and filter data + mov r12, r0 @ better to ldm delay + count later + add r0, r1, #13*4*2 @ calculate end of delay + stmdb sp!, { r0, r12 } @ stack end of delay adr and count + ldr r0, [r1, #13*4*2] @ fetch current delay line address + + /* Register usage in loop: + * r0 = &delay[index][0], r1 = accumulator high, r2 = src[0], r3 = src[1], + * r4 = direct gain, r5-r7 = b0, b1, a1 (filter coefs), + * r8-r11 = filter history, r12 = temp, r14 = accumulator low + */ +.cfloop: + smull r14, r1, r6, r8 @ acc = b1*dr[n - 1] + smlal r14, r1, r7, r9 @ acc += a1*y_l[n - 1] + ldr r8, [r0, #4] @ r8 = dr[n] + smlal r14, r1, r5, r8 @ acc += b0*dr[n] + mov r9, r1, lsl #1 @ fix format for filter history + ldr r12, [r2] @ load left input + smlal r14, r1, r4, r12 @ acc += gain*x_l[n] + mov r1, r1, lsl #1 @ fix format + str r1, [r2], #4 @ save result + + smull r14, r1, r6, r10 @ acc = b1*dl[n - 1] + smlal r14, r1, r7, r11 @ acc += a1*y_r[n - 1] + ldr r10, [r0] @ r10 = dl[n] + str r12, [r0], #4 @ save left input to delay line + smlal r14, r1, r5, r10 @ acc += b0*dl[n] + mov r11, r1, lsl #1 @ fix format for filter history + ldr r12, [r3] @ load right input + smlal r14, r1, r4, r12 @ acc += gain*x_r[n] + str r12, [r0], #4 @ save right input to delay line + mov r1, r1, lsl #1 @ fix format + ldmia sp, { r12, r14 } @ fetch delay line end addr and count from stack + str r1, [r3], #4 @ save result + + cmp r0, r12 @ need to wrap to start of delay? + subeq r0, r0, #13*4*2 @ wrap back delay line ptr to start + + subs r14, r14, #1 @ are we finished? + strne r14, [sp, #4] @ nope, save count back to stack + bne .cfloop + + @ save data back to struct + ldr r12, =crossfeed_data + 4*4 + stmia r12, { r8-r11 } @ save filter history + str r0, [r12, #30*4] @ save delay line index + add sp, sp, #8 @ remove temp variables from stack + ldmpc regs=r4-r11 + .size apply_crossfeed, .-apply_crossfeed + +/**************************************************************************** + * int dsp_downsample(int count, struct dsp_data *data, + * in32_t *src[], int32_t *dst[]) + */ + .section .text + .global dsp_downsample +dsp_downsample: + stmdb sp!, { r4-r11, lr } @ stack modified regs + ldmib r1, { r5-r6 } @ r5 = num_channels,r6 = resample_data.delta + sub r5, r5, #1 @ pre-decrement num_channels for use + add r4, r1, #12 @ r4 = &resample_data.phase + mov r12, #0xff + orr r12, r12, #0xff00 @ r12 = 0xffff +.dschannel_loop: + ldr r1, [r4] @ r1 = resample_data.phase + ldr r7, [r2, r5, lsl #2] @ r7 = s = src[ch - 1] + ldr r8, [r3, r5, lsl #2] @ r8 = d = dst[ch - 1] + add r9, r4, #4 @ r9 = &last_sample[0] + ldr r10, [r9, r5, lsl #2] @ r10 = last_sample[ch - 1] + sub r11, r0, #1 + ldr r14, [r7, r11, lsl #2] @ load last sample in s[] ... + str r14, [r9, r5, lsl #2] @ and write as next frame's last_sample + movs r9, r1, lsr #16 @ r9 = pos = phase >> 16 + ldreq r11, [r7] @ if pos = 0, load src[0] and jump into loop + beq .dsuse_last_start + cmp r9, r0 @ if pos >= count, we're already done + bge .dsloop_skip + + @ Register usage in loop: + @ r0 = count, r1 = phase, r4 = &resample_data.phase, r5 = cur_channel, + @ r6 = delta, r7 = s, r8 = d, r9 = pos, r10 = s[pos - 1], r11 = s[pos] +.dsloop: + add r9, r7, r9, lsl #2 @ r9 = &s[pos] + ldmda r9, { r10, r11 } @ r10 = s[pos - 1], r11 = s[pos] +.dsuse_last_start: + sub r11, r11, r10 @ r11 = diff = s[pos] - s[pos - 1] + @ keep frac in lower bits to take advantage of multiplier early termination + and r9, r1, r12 @ frac = phase & 0xffff + smull r9, r14, r11, r9 + add r1, r1, r6 @ phase += delta + add r10, r10, r9, lsr #16 @ r10 = out = s[pos - 1] + frac*diff + add r10, r10, r14, lsl #16 + str r10, [r8], #4 @ *d++ = out + mov r9, r1, lsr #16 @ pos = phase >> 16 + cmp r9, r0 @ pos < count? + blt .dsloop @ yup, do more samples +.dsloop_skip: + subs r5, r5, #1 + bpl .dschannel_loop @ if (--ch) >= 0, do another channel + sub r1, r1, r0, lsl #16 @ wrap phase back to start + str r1, [r4] @ store back + ldr r1, [r3] @ r1 = &dst[0] + sub r8, r8, r1 @ dst - &dst[0] + mov r0, r8, lsr #2 @ convert bytes->samples + ldmpc regs=r4-r11 @ ... and we're out + .size dsp_downsample, .-dsp_downsample + +/**************************************************************************** + * int dsp_upsample(int count, struct dsp_data *dsp, + * in32_t *src[], int32_t *dst[]) + */ + .section .text + .global dsp_upsample +dsp_upsample: + stmfd sp!, { r4-r11, lr } @ stack modified regs + ldmib r1, { r5-r6 } @ r5 = num_channels,r6 = resample_data.delta + sub r5, r5, #1 @ pre-decrement num_channels for use + add r4, r1, #12 @ r4 = &resample_data.phase + mov r6, r6, lsl #16 @ we'll use carry to detect pos increments + stmfd sp!, { r0, r4 } @ stack count and &resample_data.phase +.uschannel_loop: + ldr r12, [r4] @ r12 = resample_data.phase + ldr r7, [r2, r5, lsl #2] @ r7 = s = src[ch - 1] + ldr r8, [r3, r5, lsl #2] @ r8 = d = dst[ch - 1] + add r9, r4, #4 @ r9 = &last_sample[0] + mov r1, r12, lsl #16 @ we'll use carry to detect pos increments + sub r11, r0, #1 + ldr r14, [r7, r11, lsl #2] @ load last sample in s[] ... + ldr r10, [r9, r5, lsl #2] @ r10 = last_sample[ch - 1] + str r14, [r9, r5, lsl #2] @ and write as next frame's last_sample + movs r14, r12, lsr #16 @ pos = resample_data.phase >> 16 + beq .usstart_0 @ pos = 0 + cmp r14, r0 @ if pos >= count, we're already done + bge .usloop_skip + add r7, r7, r14, lsl #2 @ r7 = &s[pos] + ldr r10, [r7, #-4] @ r11 = s[pos - 1] + b .usstart_0 + + @ Register usage in loop: + @ r0 = count, r1 = phase, r4 = &resample_data.phase, r5 = cur_channel, + @ r6 = delta, r7 = s, r8 = d, r9 = diff, r10 = s[pos - 1], r11 = s[pos] +.usloop_1: + mov r10, r11 @ r10 = previous sample +.usstart_0: + ldr r11, [r7], #4 @ r11 = next sample + mov r4, r1, lsr #16 @ r4 = frac = phase >> 16 + sub r9, r11, r10 @ r9 = diff = s[pos] - s[pos - 1] +.usloop_0: + smull r12, r14, r4, r9 + adds r1, r1, r6 @ phase += delta << 16 + mov r4, r1, lsr #16 @ r4 = frac = phase >> 16 + add r14, r10, r14, lsl #16 + add r14, r14, r12, lsr #16 @ r14 = out = s[pos - 1] + frac*diff + str r14, [r8], #4 @ *d++ = out + bcc .usloop_0 @ if carry is set, pos is incremented + subs r0, r0, #1 @ if count > 0, do another sample + bgt .usloop_1 +.usloop_skip: + subs r5, r5, #1 + ldmfd sp, { r0, r4 } @ reload count and &resample_data.phase + bpl .uschannel_loop @ if (--ch) >= 0, do another channel + mov r1, r1, lsr #16 @ wrap phase back to start of next frame + ldr r2, [r3] @ r1 = &dst[0] + str r1, [r4] @ store phase + sub r8, r8, r2 @ dst - &dst[0] + mov r0, r8, lsr #2 @ convert bytes->samples + add sp, sp, #8 @ adjust stack for temp variables + ldmpc regs=r4-r11 @ ... and we're out + .size dsp_upsample, .-dsp_upsample + +/**************************************************************************** + * void dsp_apply_gain(int count, struct dsp_data *data, int32_t *buf[]) + */ + .section .icode, "ax", %progbits + .align 2 + .global dsp_apply_gain + .type dsp_apply_gain, %function +dsp_apply_gain: + @ input: r0 = count, r1 = data, r2 = buf[] + stmfd sp!, { r4-r8, lr } + + ldr r3, [r1, #4] @ r3 = data->num_channels + ldr r4, [r1, #32] @ r5 = data->gain + +.dag_outerloop: + ldr r1, [r2], #4 @ r1 = buf[0] and increment index of buf[] + subs r12, r0, #1 @ r12 = r0 = count - 1 + beq .dag_singlesample @ Zero? Only one sample! + +.dag_innerloop: + ldmia r1, { r5, r6 } @ load r5, r6 from r1 + smull r7, r8, r5, r4 @ r7 = FRACMUL_SHL(r5, r4, 8) + smull r14, r5, r6, r4 @ r14 = FRACMUL_SHL(r6, r4, 8) + subs r12, r12, #2 + mov r7, r7, lsr #23 + mov r14, r14, lsr #23 + orr r7, r7, r8, asl #9 + orr r14, r14, r5, asl #9 + stmia r1!, { r7, r14 } @ save r7, r14 to [r1] and increment r1 + bgt .dag_innerloop @ end of inner loop + + blt .dag_evencount @ < 0? even count + +.dag_singlesample: + ldr r5, [r1] @ handle odd sample + smull r7, r8, r5, r4 @ r7 = FRACMUL_SHL(r5, r4, 8) + mov r7, r7, lsr #23 + orr r7, r7, r8, asl #9 + str r7, [r1] + +.dag_evencount: + subs r3, r3, #1 + bgt .dag_outerloop @ end of outer loop + + ldmpc regs=r4-r8 + .size dsp_apply_gain, .-dsp_apply_gain diff --git a/lib/rbcodec/dsp/dsp_arm_v6.S b/lib/rbcodec/dsp/dsp_arm_v6.S new file mode 100644 index 0000000000..39949498ea --- /dev/null +++ b/lib/rbcodec/dsp/dsp_arm_v6.S @@ -0,0 +1,127 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2010 Michael Sevakis + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * void sample_output_mono(int count, struct dsp_data *data, + * const int32_t *src[], int16_t *dst) + */ + .section .text, "ax", %progbits + .align 2 + .global sample_output_mono + .type sample_output_mono, %function +sample_output_mono: + @ input: r0 = count, r1 = data, r2 = src, r3 = dst + stmfd sp!, { r4, lr } @ + @ + ldr r1, [r1] @ r1 = data->output_scale + ldr r2, [r2] @ r2 = src[0] + @ + mov r4, #1 @ r4 = 1 << (scale - 1) + mov r4, r4, lsl r1 @ + subs r0, r0, #1 @ odd: end at 0; even: end at -1 + mov r4, r4, lsr #1 @ + beq 2f @ Zero? Only one sample! + @ +1: @ + ldmia r2!, { r12, r14 } @ load Mi0, Mi1 + qadd r12, r12, r4 @ round, scale, saturate and + qadd r14, r14, r4 @ pack Mi0 to So0, Mi1 to So1 + mov r12, r12, asr r1 @ + mov r14, r14, asr r1 @ + ssat r12, #16, r12 @ + ssat r14, #16, r14 @ + pkhbt r12, r12, r12, asl #16 @ + pkhbt r14, r14, r14, asl #16 @ + subs r0, r0, #2 @ + stmia r3!, { r12, r14 } @ store So0, So1 + bgt 1b @ + @ + ldmltfd sp!, { r4, pc } @ if count was even, we're done + @ +2: @ + ldr r12, [r2] @ round, scale, saturate + qadd r12, r12, r4 @ and pack Mi to So + mov r12, r12, asr r1 @ + ssat r12, #16, r12 @ + pkhbt r12, r12, r12, asl #16 @ + str r12, [r3] @ store So + @ + ldmfd sp!, { r4, pc } @ + .size sample_output_mono, .-sample_output_mono + +/**************************************************************************** + * void sample_output_stereo(int count, struct dsp_data *data, + * const int32_t *src[], int16_t *dst) + */ + .section .text, "ax", %progbits + .align 2 + .global sample_output_stereo + .type sample_output_stereo, %function +sample_output_stereo: + @ input: r0 = count, r1 = data, r2 = src, r3 = dst + stmfd sp!, { r4-r7, lr } @ + @ + ldr r1, [r1] @ r1 = data->output_scale + ldmia r2, { r2, r4 } @ r2 = src[0], r4 = src[1] + @ + mov r5, #1 @ r5 = 1 << (scale - 1) + mov r5, r5, lsl r1 @ + subs r0, r0, #1 @ odd: end at 0; even: end at -1 + mov r5, r5, lsr #1 @ + beq 2f @ Zero? Only one sample! + @ +1: @ + ldmia r2!, { r6, r7 } @ r6, r7 = Li0, Li1 + ldmia r4!, { r12, r14 } @ r12, r14 = Ri0, Ri1 + qadd r6, r6, r5 @ round, scale, saturate and pack + qadd r7, r7, r5 @ Li0+Ri0 to So0, Li1+Ri1 to So1 + qadd r12, r12, r5 @ + qadd r14, r14, r5 @ + mov r6, r6, asr r1 @ + mov r7, r7, asr r1 @ + mov r12, r12, asr r1 @ + mov r14, r14, asr r1 @ + ssat r6, #16, r6 @ + ssat r12, #16, r12 @ + ssat r7, #16, r7 @ + ssat r14, #16, r14 @ + pkhbt r6, r6, r12, asl #16 @ + pkhbt r7, r7, r14, asl #16 @ + subs r0, r0, #2 @ + stmia r3!, { r6, r7 } @ store So0, So1 + bgt 1b @ + @ + ldmltfd sp!, { r4-r7, pc } @ if count was even, we're done + @ +2: @ + ldr r6, [r2] @ r6 = Li + ldr r12, [r4] @ r12 = Ri + qadd r6, r6, r5 @ round, scale, saturate + qadd r12, r12, r5 @ and pack Li+Ri to So + mov r6, r6, asr r1 @ + mov r12, r12, asr r1 @ + ssat r6, #16, r6 @ + ssat r12, #16, r12 @ + pkhbt r6, r6, r12, asl #16 @ + str r6, [r3] @ store So + @ + ldmfd sp!, { r4-r7, pc } @ + .size sample_output_stereo, .-sample_output_stereo diff --git a/lib/rbcodec/dsp/dsp_asm.h b/lib/rbcodec/dsp/dsp_asm.h new file mode 100644 index 0000000000..7bf18370a3 --- /dev/null +++ b/lib/rbcodec/dsp/dsp_asm.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 Thom Johansen + * + * 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. + * + ****************************************************************************/ + +#include + +#ifndef _DSP_ASM_H +#define _DSP_ASM_H + +/* Set the appropriate #defines based on CPU or whatever matters */ +#if defined(CPU_ARM) +#define DSP_HAVE_ASM_APPLY_GAIN +#define DSP_HAVE_ASM_RESAMPLING +#define DSP_HAVE_ASM_CROSSFEED +#define DSP_HAVE_ASM_SOUND_CHAN_MONO +#define DSP_HAVE_ASM_SOUND_CHAN_CUSTOM +#define DSP_HAVE_ASM_SOUND_CHAN_KARAOKE +#define DSP_HAVE_ASM_SAMPLE_OUTPUT_MONO +#define DSP_HAVE_ASM_SAMPLE_OUTPUT_STEREO +#elif defined (CPU_COLDFIRE) +#define DSP_HAVE_ASM_APPLY_GAIN +#define DSP_HAVE_ASM_RESAMPLING +#define DSP_HAVE_ASM_CROSSFEED +#define DSP_HAVE_ASM_SOUND_CHAN_MONO +#define DSP_HAVE_ASM_SOUND_CHAN_CUSTOM +#define DSP_HAVE_ASM_SOUND_CHAN_KARAOKE +#define DSP_HAVE_ASM_SAMPLE_OUTPUT_MONO +#define DSP_HAVE_ASM_SAMPLE_OUTPUT_STEREO +#endif /* CPU_COLDFIRE */ + +/* Declare prototypes based upon what's #defined above */ +#ifdef DSP_HAVE_ASM_CROSSFEED +void apply_crossfeed(int count, int32_t *buf[]); +#endif + +#ifdef DSP_HAVE_ASM_APPLY_GAIN +void dsp_apply_gain(int count, struct dsp_data *data, int32_t *buf[]); +#endif /* DSP_HAVE_ASM_APPLY_GAIN* */ + +#ifdef DSP_HAVE_ASM_RESAMPLING +int dsp_upsample(int count, struct dsp_data *data, + const int32_t *src[], int32_t *dst[]); +int dsp_downsample(int count, struct dsp_data *data, + const int32_t *src[], int32_t *dst[]); +#endif /* DSP_HAVE_ASM_RESAMPLING */ + +#ifdef DSP_HAVE_ASM_SOUND_CHAN_MONO +void channels_process_sound_chan_mono(int count, int32_t *buf[]); +#endif + +#ifdef DSP_HAVE_ASM_SOUND_CHAN_CUSTOM +void channels_process_sound_chan_custom(int count, int32_t *buf[]); +#endif + +#ifdef DSP_HAVE_ASM_SOUND_CHAN_KARAOKE +void channels_process_sound_chan_karaoke(int count, int32_t *buf[]); +#endif + +#ifdef DSP_HAVE_ASM_SAMPLE_OUTPUT_STEREO +void sample_output_stereo(int count, struct dsp_data *data, + const int32_t *src[], int16_t *dst); +#endif + +#ifdef DSP_HAVE_ASM_SAMPLE_OUTPUT_MONO +void sample_output_mono(int count, struct dsp_data *data, + const int32_t *src[], int16_t *dst); +#endif + +#endif /* _DSP_ASM_H */ diff --git a/lib/rbcodec/dsp/dsp_cf.S b/lib/rbcodec/dsp/dsp_cf.S new file mode 100644 index 0000000000..cda811a7d5 --- /dev/null +++ b/lib/rbcodec/dsp/dsp_cf.S @@ -0,0 +1,611 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 Thom Johansen + * Portions Copyright (C) 2007 Michael Sevakis + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * void dsp_apply_gain(int count, struct dsp_data *data, int32_t *buf[]) + */ + .section .text + .align 2 + .global dsp_apply_gain +dsp_apply_gain: + lea.l -20(%sp), %sp | save registers + movem.l %d2-%d4/%a2-%a3, (%sp) | + movem.l 28(%sp), %a0-%a1 | %a0 = data, + | %a1 = buf + move.l 4(%a0), %d1 | %d1 = data->num_channels + move.l 32(%a0), %a0 | %a0 = data->gain (in s8.23) +10: | channel loop | + move.l 24(%sp), %d0 | %d0 = count + move.l -4(%a1, %d1.l*4), %a2 | %a2 = s = buf[ch-1] + move.l %a2, %a3 | %a3 = d = s + move.l (%a2)+, %d2 | %d2 = *s++, + mac.l %a0, %d2, (%a2)+, %d2, %acc0 | %acc0 = S(n)*gain, load S(n+1) + subq.l #1, %d0 | --count > 0 ? : effectively n++ + ble.b 30f | loop done | no? finish up +20: | loop | + move.l %accext01, %d4 | fetch S(n-1)[7:0] + movclr.l %acc0, %d3 | fetch S(n-1)[40:8] in %d5[31:0] + asl.l #8, %d3 | *s++ = (S(n-1)[40:8] << 8) | S(n-1)[7:0] + mac.l %a0, %d2, (%a2)+, %d2, %acc0 | %acc0 = S(n)*gain, load S(n+1) + move.b %d4, %d3 | + move.l %d3, (%a3)+ | + subq.l #1, %d0 | --count > 0 ? : effectively n++ + bgt.b 20b | loop | yes? do more samples +30: | loop done | + move.l %accext01, %d4 | fetch S(n-1)[7:0] + movclr.l %acc0, %d3 | fetch S(n-1)[40:8] in %d5[31:0] + asl.l #8, %d3 | *s = (S(n-1)[40:8] << 8) | S(n-1)[7:0] + move.b %d4, %d3 | + move.l %d3, (%a3) | + subq.l #1, %d1 | next channel + bgt.b 10b | channel loop | + movem.l (%sp), %d2-%d4/%a2-%a3 | restore registers + lea.l 20(%sp), %sp | cleanup stack + rts | + .size dsp_apply_gain,.-dsp_apply_gain + +/**************************************************************************** + * void apply_crossfeed(int count, int32_t *buf[]) + */ + .section .text + .align 2 + .global apply_crossfeed +apply_crossfeed: + lea.l -44(%sp), %sp | + movem.l %d2-%d7/%a2-%a6, (%sp) | save all regs + movem.l 48(%sp), %d7/%a4 | %d7 = count, %a4 = src + movem.l (%a4), %a4-%a5 | %a4 = src[0], %a5 = src[1] + lea.l crossfeed_data, %a1 | %a1 = &crossfeed_data + move.l (%a1)+, %d6 | %d6 = direct gain + movem.l 12(%a1), %d0-%d3 | fetch filter history samples + move.l 132(%a1), %a0 | fetch delay line address + movem.l (%a1), %a1-%a3 | load filter coefs + lea.l crossfeed_data+136, %a6 | %a6 = delay line wrap limit + bra.b 20f | loop start | go to loop start point + /* Register usage in loop: + * %a0 = delay_p, %a1..%a3 = b0, b1, a1 (filter coefs), + * %a4 = buf[0], %a5 = buf[1], + * %a6 = delay line pointer wrap limit, + * %d0..%d3 = history + * %d4..%d5 = temp. + * %d6 = direct gain, + * %d7 = count + */ +10: | loop | + movclr.l %acc0, %d4 | write outputs + move.l %d4, (%a4)+ | . + movclr.l %acc1, %d5 | . + move.l %d5, (%a5)+ | . +20: | loop start | + mac.l %a2, %d0, (%a0)+, %d0, %acc0 | %acc0 = b1*dl[n - 1], %d0 = dl[n] + mac.l %a1, %d0 , %acc0 | %acc0 += b0*dl[n] + mac.l %a3, %d1, (%a5), %d5, %acc0 | %acc0 += a1*y_r[n - 1], load R + mac.l %a2, %d2, (%a0)+, %d2, %acc1 | %acc1 = b1*dr[n - 1], %d2 = dr[n] + mac.l %a1, %d2 , %acc1 | %acc1 += b0*dr[n] + mac.l %a3, %d3, (%a4), %d4, %acc1 | %acc1 += a1*y_l[n - 1], load L + movem.l %d4-%d5, -8(%a0) | save left & right inputs to delay line + move.l %acc0, %d3 | get filtered delayed left sample (y_l[n]) + move.l %acc1, %d1 | get filtered delayed right sample (y_r[n]) + mac.l %d6, %d4, %acc0 | %acc0 += gain*x_l[n] + mac.l %d6, %d5, %acc1 | %acc1 += gain*x_r[n] + cmp.l %a6, %a0 | wrap %a0 if passed end + bhs.b 30f | wrap buffer | + .word 0x51fb | tpf.l | trap the buffer wrap +30: | wrap buffer | ...fwd taken branches more costly + lea.l -104(%a0), %a0 | wrap it up + subq.l #1, %d7 | --count > 0 ? + bgt.b 10b | loop | yes? do more + movclr.l %acc0, %d4 | write last outputs + move.l %d4, (%a4) | . + movclr.l %acc1, %d5 | . + move.l %d5, (%a5) | . + lea.l crossfeed_data+16, %a1 | save data back to struct + movem.l %d0-%d3, (%a1) | ...history + move.l %a0, 120(%a1) | ...delay_p + movem.l (%sp), %d2-%d7/%a2-%a6 | restore all regs + lea.l 44(%sp), %sp | + rts | + .size apply_crossfeed,.-apply_crossfeed + +/**************************************************************************** + * int dsp_downsample(int count, struct dsp_data *data, + * in32_t *src[], int32_t *dst[]) + */ + .section .text + .align 2 + .global dsp_downsample +dsp_downsample: + lea.l -40(%sp), %sp | save non-clobberables + movem.l %d2-%d7/%a2-%a5, (%sp) | + movem.l 44(%sp), %d2/%a0-%a2 | %d2 = count + | %a0 = data + | %a1 = src + | %a2 = dst + movem.l 4(%a0), %d3-%d4 | %d3 = ch = data->num_channels + | %d4 = delta = data->resample_data.delta + moveq.l #16, %d7 | %d7 = shift +10: | channel loop | + move.l 12(%a0), %d5 | %d5 = phase = data->resample_data.phase + move.l -4(%a1, %d3.l*4), %a3 | %a3 = s = src[ch-1] + move.l -4(%a2, %d3.l*4), %a4 | %a4 = d = dst[ch-1] + lea.l 12(%a0, %d3.l*4), %a5 | %a5 = &data->resample_data.ast_sample[ch-1] + move.l (%a5), %d0 | %d0 = last = data->resample_data.last_sample[ch-1] + move.l -4(%a3, %d2.l*4), (%a5) | data->resample_data.last_sample[ch-1] = s[count-1] + move.l %d5, %d6 | %d6 = pos = phase >> 16 + lsr.l %d7, %d6 | + cmp.l %d2, %d6 | past end of samples? + bge.b 40f | skip resample loop| yes? skip loop + tst.l %d6 | need last sample of prev. frame? + bne.b 20f | resample loop | no? start main loop + move.l (%a3, %d6.l*4), %d1 | %d1 = s[pos] + bra.b 30f | resample start last | start with last (last in %d0) +20: | resample loop | + lea.l -4(%a3, %d6.l*4), %a5 | load s[pos-1] and s[pos] + movem.l (%a5), %d0-%d1 | +30: | resample start last | + sub.l %d0, %d1 | %d1 = diff = s[pos] - s[pos-1] + move.l %d0, %acc0 | %acc0 = previous sample + move.l %d5, %d0 | frac = (phase << 16) >> 1 + lsl.l %d7, %d0 | + lsr.l #1, %d0 | + mac.l %d0, %d1, %acc0 | %acc0 += frac * diff + add.l %d4, %d5 | phase += delta + move.l %d5, %d6 | pos = phase >> 16 + lsr.l %d7, %d6 | + movclr.l %acc0, %d0 | + move.l %d0, (%a4)+ | *d++ = %d0 + cmp.l %d2, %d6 | pos < count? + blt.b 20b | resample loop | yes? continue resampling +40: | skip resample loop | + subq.l #1, %d3 | ch > 0? + bgt.b 10b | channel loop | yes? process next channel + lsl.l %d7, %d2 | wrap phase to start of next frame + sub.l %d2, %d5 | data->resample_data.phase = + move.l %d5, 12(%a0) | ... phase - (count << 16) + move.l %a4, %d0 | return d - d[0] + sub.l (%a2), %d0 | + asr.l #2, %d0 | convert bytes->samples + movem.l (%sp), %d2-%d7/%a2-%a5 | restore non-clobberables + lea.l 40(%sp), %sp | cleanup stack + rts | buh-bye + .size dsp_downsample,.-dsp_downsample + +/**************************************************************************** + * int dsp_upsample(int count, struct dsp_data *dsp, + * const int32_t *src[], int32_t *dst[]) + */ + .section .text + .align 2 + .global dsp_upsample +dsp_upsample: + lea.l -40(%sp), %sp | save non-clobberables + movem.l %d2-%d7/%a2-%a5, (%sp) | + movem.l 44(%sp), %d2/%a0-%a2 | %d2 = count + | %a0 = data + | %a1 = src + | %a2 = dst + movem.l 4(%a0), %d3-%d4 | %d3 = ch = channels + | %d4 = delta = data->resample_data.delta + swap %d4 | swap delta to high word to use... + | ...carries to increment position +10: | channel loop | + move.l 12(%a0), %d5 | %d5 = phase = data->resample_data.phase + move.l -4(%a1, %d3.l*4), %a3 | %a3 = s = src[ch-1] + lea.l 12(%a0, %d3.l*4), %a4 | %a4 = &data->resample_data.last_sample[ch-1] + lea.l -4(%a3, %d2.l*4), %a5 | %a5 = src_end = &src[count-1] + move.l (%a4), %d0 | %d0 = last = data->resample_data.last_sample[ch-1] + move.l (%a5), (%a4) | data->resample_data.last_sample[ch-1] = s[count-1] + move.l -4(%a2, %d3.l*4), %a4 | %a4 = d = dst[ch-1] + move.l (%a3)+, %d1 | fetch first sample - might throw this... + | ...away later but we'll be preincremented + move.l %d1, %d6 | save sample value + sub.l %d0, %d1 | %d1 = diff = s[0] - last + swap %d5 | swap phase to high word to use + | carries to increment position + move.l %d5, %d7 | %d7 = pos = phase >> 16 + clr.w %d5 | + eor.l %d5, %d7 | pos == 0? + beq.b 40f | loop start | yes? start loop + cmp.l %d2, %d7 | past end of samples? + bge.b 50f | skip resample loop| yes? go to next channel and collect info + lea.l (%a3, %d7.l*4), %a3 | %a3 = s = &s[pos+1] + movem.l -8(%a3), %d0-%d1 | %d0 = s[pos-1], %d1 = s[pos] + move.l %d1, %d6 | save sample value + sub.l %d0, %d1 | %d1 = diff = s[pos] - s[pos-1] + bra.b 40f | loop start | +20: | next sample loop | + move.l %d6, %d0 | move previous sample to %d0 + move.l (%a3)+, %d1 | fetch next sample + move.l %d1, %d6 | save sample value + sub.l %d0, %d1 | %d1 = diff = s[pos] - s[pos-1] +30: | same sample loop | + movclr.l %acc0, %d7 | %d7 = result + move.l %d7, (%a4)+ | *d++ = %d7 +40: | loop start | + lsr.l #1, %d5 | make phase into frac + move.l %d0, %acc0 | %acc0 = s[pos-1] + mac.l %d1, %d5, %acc0 | %acc0 = diff * frac + lsl.l #1, %d5 | restore frac to phase + add.l %d4, %d5 | phase += delta + bcc.b 30b | same sample loop | load next values? + cmp.l %a5, %a3 | src <= src_end? + bls.b 20b | next sample loop | yes? continue resampling + movclr.l %acc0, %d7 | %d7 = result + move.l %d7, (%a4)+ | *d++ = %d7 +50: | skip resample loop | + subq.l #1, %d3 | ch > 0? + bgt.b 10b | channel loop | yes? process next channel + swap %d5 | wrap phase to start of next frame + move.l %d5, 12(%a0) | ...and save in data->resample_data.phase + move.l %a4, %d0 | return d - d[0] + sub.l (%a2), %d0 | + movem.l (%sp), %d2-%d7/%a2-%a5 | restore non-clobberables + asr.l #2, %d0 | convert bytes->samples + lea.l 40(%sp), %sp | cleanup stack + rts | buh-bye + .size dsp_upsample,.-dsp_upsample + +/**************************************************************************** + * void channels_process_sound_chan_mono(int count, int32_t *buf[]) + * + * Mix left and right channels 50/50 into a center channel. + */ + .section .text + .align 2 + .global channels_process_sound_chan_mono +channels_process_sound_chan_mono: + movem.l 4(%sp), %d0/%a0 | %d0 = count, %a0 = buf + lea.l -20(%sp), %sp | save registers + movem.l %d2-%d4/%a2-%a3, (%sp) | + movem.l (%a0), %a0-%a1 | get channel pointers + move.l %a0, %a2 | use separate dst pointers since read + move.l %a1, %a3 | pointers run one ahead of write + move.l #0x40000000, %d3 | %d3 = 0.5 + move.l (%a0)+, %d1 | prime the input registers + move.l (%a1)+, %d2 | + mac.l %d1, %d3, (%a0)+, %d1, %acc0 | + mac.l %d2, %d3, (%a1)+, %d2, %acc0 | + subq.l #1, %d0 | + ble.s 20f | loop done | +10: | loop | + movclr.l %acc0, %d4 | L = R = l/2 + r/2 + mac.l %d1, %d3, (%a0)+, %d1, %acc0 | + mac.l %d2, %d3, (%a1)+, %d2, %acc0 | + move.l %d4, (%a2)+ | output to original buffer + move.l %d4, (%a3)+ | + subq.l #1, %d0 | + bgt.s 10b | loop | +20: | loop done | + movclr.l %acc0, %d4 | output last sample + move.l %d4, (%a2) | + move.l %d4, (%a3) | + movem.l (%sp), %d2-%d4/%a2-%a3 | restore registers + lea.l 20(%sp), %sp | cleanup + rts | + .size channels_process_sound_chan_mono, \ + .-channels_process_sound_chan_mono + +/**************************************************************************** + * void channels_process_sound_chan_custom(int count, int32_t *buf[]) + * + * Apply stereo width (narrowing/expanding) effect. + */ + .section .text + .align 2 + .global channels_process_sound_chan_custom +channels_process_sound_chan_custom: + movem.l 4(%sp), %d0/%a0 | %d0 = count, %a0 = buf + lea.l -28(%sp), %sp | save registers + movem.l %d2-%d6/%a2-%a3, (%sp) | + movem.l (%a0), %a0-%a1 | get channel pointers + move.l %a0, %a2 | use separate dst pointers since read + move.l %a1, %a3 | pointers run one ahead of write + move.l dsp_sw_gain, %d3 | load straight (mid) gain + move.l dsp_sw_cross, %d4 | load cross (side) gain + move.l (%a0)+, %d1 | prime the input registers + move.l (%a1)+, %d2 | + mac.l %d1, %d3 , %acc0 | L = l*gain + r*cross + mac.l %d1, %d4, (%a0)+, %d1, %acc1 | R = r*gain + l*cross + mac.l %d2, %d4 , %acc0 | + mac.l %d2, %d3, (%a1)+, %d2, %acc1 | + subq.l #1, %d0 | + ble.b 20f | loop done | +10: | loop | + movclr.l %acc0, %d5 | + movclr.l %acc1, %d6 | + mac.l %d1, %d3 , %acc0 | L = l*gain + r*cross + mac.l %d1, %d4, (%a0)+, %d1, %acc1 | R = r*gain + l*cross + mac.l %d2, %d4 , %acc0 | + mac.l %d2, %d3, (%a1)+, %d2, %acc1 | + move.l %d5, (%a2)+ | + move.l %d6, (%a3)+ | + subq.l #1, %d0 | + bgt.s 10b | loop | +20: | loop done | + movclr.l %acc0, %d5 | output last sample + movclr.l %acc1, %d6 | + move.l %d5, (%a2) | + move.l %d6, (%a3) | + movem.l (%sp), %d2-%d6/%a2-%a3 | restore registers + lea.l 28(%sp), %sp | cleanup + rts | + .size channels_process_sound_chan_custom, \ + .-channels_process_sound_chan_custom + +/**************************************************************************** + * void channels_process_sound_chan_karaoke(int count, int32_t *buf[]) + * + * Separate channels into side channels. + */ + .section .text + .align 2 + .global channels_process_sound_chan_karaoke +channels_process_sound_chan_karaoke: + movem.l 4(%sp), %d0/%a0 | %d0 = count, %a0 = buf + lea.l -20(%sp), %sp | save registers + movem.l %d2-%d4/%a2-%a3, (%sp) | + movem.l (%a0), %a0-%a1 | get channel src pointers + move.l %a0, %a2 | use separate dst pointers since read + move.l %a1, %a3 | pointers run one ahead of write + move.l #0x40000000, %d3 | %d3 = 0.5 + move.l (%a0)+, %d1 | prime the input registers + move.l (%a1)+, %d2 | + mac.l %d1, %d3, (%a0)+, %d1, %acc0 | L = l/2 - r/2 + msac.l %d2, %d3, (%a1)+, %d2, %acc0 | + subq.l #1, %d0 | + ble.b 20f | loop done | +10: | loop | + movclr.l %acc0, %d4 | + mac.l %d1, %d3, (%a0)+, %d1, %acc0 | L = l/2 - r/2 + msac.l %d2, %d3, (%a1)+, %d2, %acc0 | + move.l %d4, (%a2)+ | + neg.l %d4 | R = -L = -(l/2 - r/2) = r/2 - l/2 + move.l %d4, (%a3)+ | + subq.l #1, %d0 | + bgt.s 10b | loop | +20: | loop done | + movclr.l %acc0, %d4 | output last sample + move.l %d4, (%a2) | + neg.l %d4 | R = -L = -(l/2 - r/2) = r/2 - l/2 + move.l %d4, (%a3) | + movem.l (%sp), %d2-%d4/%a2-%a3 | restore registers + lea.l 20(%sp), %sp | cleanup + rts | + .size channels_process_sound_chan_karaoke, \ + .-channels_process_sound_chan_karaoke + +/**************************************************************************** + * void sample_output_stereo(int count, struct dsp_data *data, + * const int32_t *src[], int16_t *dst) + * + * Framework based on the ubiquitous Rockbox line transfer logic for + * Coldfire CPUs. + * + * Does emac clamping and scaling (which proved faster than the usual + * checks and branches - even single test clamping) and writes using + * line burst transfers. Also better than writing a single L-R pair per + * loop but a good deal more code. + * + * Attemping bursting during reads is rather futile since the source and + * destination alignments rarely agree and too much complication will + * slow us up. The parallel loads seem to do a bit better at least until + * a pcm buffer can always give line aligned chunk and then aligning the + * dest can then imply the source is aligned if the source buffers are. + * For now longword alignment is assumed of both the source and dest. + * + */ + .section .text + .align 2 + .global sample_output_stereo +sample_output_stereo: + lea.l -48(%sp), %sp | save registers + move.l %macsr, %d1 | do it now as at many lines will + movem.l %d1-%d7/%a2-%a6, (%sp) | be the far more common condition + move.l #0x80, %macsr | put emac unit in signed int mode + movem.l 52(%sp), %a0-%a2/%a4 | + lea.l (%a4, %a0.l*4), %a0 | %a0 = end address + move.l (%a1), %d1 | %a1 = multiplier: (1 << (16 - scale)) + sub.l #16, %d1 | + neg.l %d1 | + moveq.l #1, %d0 | + asl.l %d1, %d0 | + move.l %d0, %a1 | + move.l #0x8000, %a6 | %a6 = rounding term + movem.l (%a2), %a2-%a3 | get L/R channel pointers + moveq.l #28, %d0 | %d0 = second line bound + add.l %a4, %d0 | + and.l #0xfffffff0, %d0 | + cmp.l %a0, %d0 | at least a full line? + bhi.w 40f | long loop 1 start | no? do as trailing longwords + sub.l #16, %d0 | %d1 = first line bound + cmp.l %a4, %d0 | any leading longwords? + bls.b 20f | line loop start | no? start line loop +10: | long loop 0 | + move.l (%a2)+, %d1 | read longword from L and R + move.l %a6, %acc0 | + move.l %acc0, %acc1 | + mac.l %d1, %a1, (%a3)+, %d2, %acc0 | shift L to high word + mac.l %d2, %a1, %acc1 | shift R to high word + movclr.l %acc0, %d1 | get possibly saturated results + movclr.l %acc1, %d2 | + swap %d2 | move R to low word + move.w %d2, %d1 | interleave MS 16 bits of each + move.l %d1, (%a4)+ | ...and write both + cmp.l %a4, %d0 | + bhi.b 10b | long loop 0 | +20: | line loop start | + lea.l -12(%a0), %a5 | %a5 = at or just before last line bound +30: | line loop | + move.l (%a3)+, %d4 | get next 4 R samples and scale + move.l %a6, %acc0 | + move.l %acc0, %acc1 | + move.l %acc1, %acc2 | + move.l %acc2, %acc3 | + mac.l %d4, %a1, (%a3)+, %d5, %acc0 | with saturation + mac.l %d5, %a1, (%a3)+, %d6, %acc1 | + mac.l %d6, %a1, (%a3)+, %d7, %acc2 | + mac.l %d7, %a1, (%a2)+, %d0, %acc3 | + lea.l 16(%a4), %a4 | increment dest here, mitigate stalls + movclr.l %acc0, %d4 | obtain R results + movclr.l %acc1, %d5 | + movclr.l %acc2, %d6 | + movclr.l %acc3, %d7 | + move.l %a6, %acc0 | + move.l %acc0, %acc1 | + move.l %acc1, %acc2 | + move.l %acc2, %acc3 | + mac.l %d0, %a1, (%a2)+, %d1, %acc0 | get next 4 L samples and scale + mac.l %d1, %a1, (%a2)+, %d2, %acc1 | with saturation + mac.l %d2, %a1, (%a2)+, %d3, %acc2 | + mac.l %d3, %a1 , %acc3 | + swap %d4 | a) interleave most significant... + swap %d5 | + swap %d6 | + swap %d7 | + movclr.l %acc0, %d0 | obtain L results + movclr.l %acc1, %d1 | + movclr.l %acc2, %d2 | + movclr.l %acc3, %d3 | + move.w %d4, %d0 | a) ... 16 bits of L and R + move.w %d5, %d1 | + move.w %d6, %d2 | + move.w %d7, %d3 | + movem.l %d0-%d3, -16(%a4) | write four stereo samples + cmp.l %a4, %a5 | + bhi.b 30b | line loop | +40: | long loop 1 start | + cmp.l %a4, %a0 | any longwords left? + bls.b 60f | output end | no? stop +50: | long loop 1 | + move.l (%a2)+, %d1 | handle trailing longwords + move.l %a6, %acc0 | + move.l %acc0, %acc1 | + mac.l %d1, %a1, (%a3)+, %d2, %acc0 | the same way as leading ones + mac.l %d2, %a1, %acc1 | + movclr.l %acc0, %d1 | + movclr.l %acc1, %d2 | + swap %d2 | + move.w %d2, %d1 | + move.l %d1, (%a4)+ | + cmp.l %a4, %a0 | + bhi.b 50b | long loop 1 +60: | output end | + movem.l (%sp), %d1-%d7/%a2-%a6 | restore registers + move.l %d1, %macsr | + lea.l 48(%sp), %sp | cleanup + rts | + .size sample_output_stereo, .-sample_output_stereo + +/**************************************************************************** + * void sample_output_mono(int count, struct dsp_data *data, + * const int32_t *src[], int16_t *dst) + * + * Same treatment as sample_output_stereo but for one channel. + */ + .section .text + .align 2 + .global sample_output_mono +sample_output_mono: + lea.l -32(%sp), %sp | save registers + move.l %macsr, %d1 | do it now as at many lines will + movem.l %d1-%d5/%a2-%a4, (%sp) | be the far more common condition + move.l #0x80, %macsr | put emac unit in signed int mode + movem.l 36(%sp), %a0-%a3 | + lea.l (%a3, %a0.l*4), %a0 | %a0 = end address + move.l (%a1), %d1 | %d5 = multiplier: (1 << (16 - scale)) + sub.l #16, %d1 | + neg.l %d1 | + moveq.l #1, %d5 | + asl.l %d1, %d5 | + move.l #0x8000, %a4 | %a4 = rounding term + movem.l (%a2), %a2 | get source channel pointer + moveq.l #28, %d0 | %d0 = second line bound + add.l %a3, %d0 | + and.l #0xfffffff0, %d0 | + cmp.l %a0, %d0 | at least a full line? + bhi.w 40f | long loop 1 start | no? do as trailing longwords + sub.l #16, %d0 | %d1 = first line bound + cmp.l %a3, %d0 | any leading longwords? + bls.b 20f | line loop start | no? start line loop +10: | long loop 0 | + move.l (%a2)+, %d1 | read longword from L and R + move.l %a4, %acc0 | + mac.l %d1, %d5, %acc0 | shift L to high word + movclr.l %acc0, %d1 | get possibly saturated results + move.l %d1, %d2 | + swap %d2 | move R to low word + move.w %d2, %d1 | duplicate single channel into + move.l %d1, (%a3)+ | L and R + cmp.l %a3, %d0 | + bhi.b 10b | long loop 0 | +20: | line loop start | + lea.l -12(%a0), %a1 | %a1 = at or just before last line bound +30: | line loop | + move.l (%a2)+, %d0 | get next 4 L samples and scale + move.l %a4, %acc0 | + move.l %acc0, %acc1 | + move.l %acc1, %acc2 | + move.l %acc2, %acc3 | + mac.l %d0, %d5, (%a2)+, %d1, %acc0 | with saturation + mac.l %d1, %d5, (%a2)+, %d2, %acc1 | + mac.l %d2, %d5, (%a2)+, %d3, %acc2 | + mac.l %d3, %d5 , %acc3 | + lea.l 16(%a3), %a3 | increment dest here, mitigate stalls + movclr.l %acc0, %d0 | obtain results + movclr.l %acc1, %d1 | + movclr.l %acc2, %d2 | + movclr.l %acc3, %d3 | + move.l %d0, %d4 | duplicate single channel + swap %d4 | into L and R + move.w %d4, %d0 | + move.l %d1, %d4 | + swap %d4 | + move.w %d4, %d1 | + move.l %d2, %d4 | + swap %d4 | + move.w %d4, %d2 | + move.l %d3, %d4 | + swap %d4 | + move.w %d4, %d3 | + movem.l %d0-%d3, -16(%a3) | write four stereo samples + cmp.l %a3, %a1 | + bhi.b 30b | line loop | +40: | long loop 1 start | + cmp.l %a3, %a0 | any longwords left? + bls.b 60f | output end | no? stop +50: | loop loop 1 | + move.l (%a2)+, %d1 | handle trailing longwords + move.l %a4, %acc0 | + mac.l %d1, %d5, %acc0 | the same way as leading ones + movclr.l %acc0, %d1 | + move.l %d1, %d2 | + swap %d2 | + move.w %d2, %d1 | + move.l %d1, (%a3)+ | + cmp.l %a3, %a0 | + bhi.b 50b | long loop 1 | +60: | output end | + movem.l (%sp), %d1-%d5/%a2-%a4 | restore registers + move.l %d1, %macsr | + lea.l 32(%sp), %sp | cleanup + rts | + .size sample_output_mono, .-sample_output_mono diff --git a/lib/rbcodec/dsp/eq.c b/lib/rbcodec/dsp/eq.c new file mode 100644 index 0000000000..122a46a4c5 --- /dev/null +++ b/lib/rbcodec/dsp/eq.c @@ -0,0 +1,268 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006-2007 Thom Johansen + * + * 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. + * + ****************************************************************************/ + +#include +#include "config.h" +#include "fixedpoint.h" +#include "fracmul.h" +#include "eq.h" +#include "replaygain.h" + +/** + * Calculate first order shelving filter. Filter is not directly usable by the + * eq_filter() function. + * @param cutoff shelf midpoint frequency. See eq_pk_coefs for format. + * @param A decibel value multiplied by ten, describing gain/attenuation of + * shelf. Max value is 24 dB. + * @param low true for low-shelf filter, false for high-shelf filter. + * @param c pointer to coefficient storage. Coefficients are s4.27 format. + */ +void filter_shelf_coefs(unsigned long cutoff, long A, bool low, int32_t *c) +{ + long sin, cos; + int32_t b0, b1, a0, a1; /* s3.28 */ + const long g = get_replaygain_int(A*5) << 4; /* 10^(db/40), s3.28 */ + + sin = fp_sincos(cutoff/2, &cos); + if (low) { + const int32_t sin_div_g = fp_div(sin, g, 25); + const int32_t sin_g = FRACMUL(sin, g); + cos >>= 3; + b0 = sin_g + cos; /* 0.25 .. 4.10 */ + b1 = sin_g - cos; /* -1 .. 3.98 */ + a0 = sin_div_g + cos; /* 0.25 .. 4.10 */ + a1 = sin_div_g - cos; /* -1 .. 3.98 */ + } else { + const int32_t cos_div_g = fp_div(cos, g, 25); + const int32_t cos_g = FRACMUL(cos, g); + sin >>= 3; + b0 = sin + cos_g; /* 0.25 .. 4.10 */ + b1 = sin - cos_g; /* -3.98 .. 1 */ + a0 = sin + cos_div_g; /* 0.25 .. 4.10 */ + a1 = sin - cos_div_g; /* -3.98 .. 1 */ + } + + const int32_t rcp_a0 = fp_div(1, a0, 57); /* 0.24 .. 3.98, s2.29 */ + *c++ = FRACMUL_SHL(b0, rcp_a0, 1); /* 0.063 .. 15.85 */ + *c++ = FRACMUL_SHL(b1, rcp_a0, 1); /* -15.85 .. 15.85 */ + *c++ = -FRACMUL_SHL(a1, rcp_a0, 1); /* -1 .. 1 */ +} + +#ifdef HAVE_SW_TONE_CONTROLS +/** + * Calculate second order section filter consisting of one low-shelf and one + * high-shelf section. + * @param cutoff_low low-shelf midpoint frequency. See eq_pk_coefs for format. + * @param cutoff_high high-shelf midpoint frequency. + * @param A_low decibel value multiplied by ten, describing gain/attenuation of + * low-shelf part. Max value is 24 dB. + * @param A_high decibel value multiplied by ten, describing gain/attenuation of + * high-shelf part. Max value is 24 dB. + * @param A decibel value multiplied by ten, describing additional overall gain. + * @param c pointer to coefficient storage. Coefficients are s4.27 format. + */ +void filter_bishelf_coefs(unsigned long cutoff_low, unsigned long cutoff_high, + long A_low, long A_high, long A, int32_t *c) +{ + const long g = get_replaygain_int(A*10) << 7; /* 10^(db/20), s0.31 */ + int32_t c_ls[3], c_hs[3]; + + filter_shelf_coefs(cutoff_low, A_low, true, c_ls); + filter_shelf_coefs(cutoff_high, A_high, false, c_hs); + c_ls[0] = FRACMUL(g, c_ls[0]); + c_ls[1] = FRACMUL(g, c_ls[1]); + + /* now we cascade the two first order filters to one second order filter + * which can be used by eq_filter(). these resulting coefficients have a + * really wide numerical range, so we use a fixed point format which will + * work for the selected cutoff frequencies (in dsp.c) only. + */ + const int32_t b0 = c_ls[0], b1 = c_ls[1], b2 = c_hs[0], b3 = c_hs[1]; + const int32_t a0 = c_ls[2], a1 = c_hs[2]; + *c++ = FRACMUL_SHL(b0, b2, 4); + *c++ = FRACMUL_SHL(b0, b3, 4) + FRACMUL_SHL(b1, b2, 4); + *c++ = FRACMUL_SHL(b1, b3, 4); + *c++ = a0 + a1; + *c++ = -FRACMUL_SHL(a0, a1, 4); +} +#endif + +/* Coef calculation taken from Audio-EQ-Cookbook.txt by Robert Bristow-Johnson. + * Slightly faster calculation can be done by deriving forms which use tan() + * instead of cos() and sin(), but the latter are far easier to use when doing + * fixed point math, and performance is not a big point in the calculation part. + * All the 'a' filter coefficients are negated so we can use only additions + * in the filtering equation. + */ + +/** + * Calculate second order section peaking filter coefficients. + * @param cutoff a value from 0 to 0x80000000, where 0 represents 0 Hz and + * 0x80000000 represents the Nyquist frequency (samplerate/2). + * @param Q Q factor value multiplied by ten. Lower bound is artificially set + * at 0.5. + * @param db decibel value multiplied by ten, describing gain/attenuation at + * peak freq. Max value is 24 dB. + * @param c pointer to coefficient storage. Coefficients are s3.28 format. + */ +void eq_pk_coefs(unsigned long cutoff, unsigned long Q, long db, int32_t *c) +{ + long cs; + const long one = 1 << 28; /* s3.28 */ + const long A = get_replaygain_int(db*5) << 5; /* 10^(db/40), s2.29 */ + const long alpha = fp_sincos(cutoff, &cs)/(2*Q)*10 >> 1; /* s1.30 */ + int32_t a0, a1, a2; /* these are all s3.28 format */ + int32_t b0, b1, b2; + const long alphadivA = fp_div(alpha, A, 27); + const long alphaA = FRACMUL(alpha, A); + + /* possible numerical ranges are in comments by each coef */ + b0 = one + alphaA; /* [1 .. 5] */ + b1 = a1 = -2*(cs >> 3); /* [-2 .. 2] */ + b2 = one - alphaA; /* [-3 .. 1] */ + a0 = one + alphadivA; /* [1 .. 5] */ + a2 = one - alphadivA; /* [-3 .. 1] */ + + /* range of this is roughly [0.2 .. 1], but we'll never hit 1 completely */ + const long rcp_a0 = fp_div(1, a0, 59); /* s0.31 */ + *c++ = FRACMUL(b0, rcp_a0); /* [0.25 .. 4] */ + *c++ = FRACMUL(b1, rcp_a0); /* [-2 .. 2] */ + *c++ = FRACMUL(b2, rcp_a0); /* [-2.4 .. 1] */ + *c++ = FRACMUL(-a1, rcp_a0); /* [-2 .. 2] */ + *c++ = FRACMUL(-a2, rcp_a0); /* [-0.6 .. 1] */ +} + +/** + * Calculate coefficients for lowshelf filter. Parameters are as for + * eq_pk_coefs, but the coefficient format is s5.26 fixed point. + */ +void eq_ls_coefs(unsigned long cutoff, unsigned long Q, long db, int32_t *c) +{ + long cs; + const long one = 1 << 25; /* s6.25 */ + const long sqrtA = get_replaygain_int(db*5/2) << 2; /* 10^(db/80), s5.26 */ + const long A = FRACMUL_SHL(sqrtA, sqrtA, 8); /* s2.29 */ + const long alpha = fp_sincos(cutoff, &cs)/(2*Q)*10 >> 1; /* s1.30 */ + const long ap1 = (A >> 4) + one; + const long am1 = (A >> 4) - one; + const long ap1_cs = FRACMUL(ap1, cs); + const long am1_cs = FRACMUL(am1, cs); + const long twosqrtalpha = 2*FRACMUL(sqrtA, alpha); + int32_t a0, a1, a2; /* these are all s6.25 format */ + int32_t b0, b1, b2; + + /* [0.1 .. 40] */ + b0 = FRACMUL_SHL(A, ap1 - am1_cs + twosqrtalpha, 2); + /* [-16 .. 63.4] */ + b1 = FRACMUL_SHL(A, am1 - ap1_cs, 3); + /* [0 .. 31.7] */ + b2 = FRACMUL_SHL(A, ap1 - am1_cs - twosqrtalpha, 2); + /* [0.5 .. 10] */ + a0 = ap1 + am1_cs + twosqrtalpha; + /* [-16 .. 4] */ + a1 = -2*(am1 + ap1_cs); + /* [0 .. 8] */ + a2 = ap1 + am1_cs - twosqrtalpha; + + /* [0.1 .. 1.99] */ + const long rcp_a0 = fp_div(1, a0, 55); /* s1.30 */ + *c++ = FRACMUL_SHL(b0, rcp_a0, 2); /* [0.06 .. 15.9] */ + *c++ = FRACMUL_SHL(b1, rcp_a0, 2); /* [-2 .. 31.7] */ + *c++ = FRACMUL_SHL(b2, rcp_a0, 2); /* [0 .. 15.9] */ + *c++ = FRACMUL_SHL(-a1, rcp_a0, 2); /* [-2 .. 2] */ + *c++ = FRACMUL_SHL(-a2, rcp_a0, 2); /* [0 .. 1] */ +} + +/** + * Calculate coefficients for highshelf filter. Parameters are as for + * eq_pk_coefs, but the coefficient format is s5.26 fixed point. + */ +void eq_hs_coefs(unsigned long cutoff, unsigned long Q, long db, int32_t *c) +{ + long cs; + const long one = 1 << 25; /* s6.25 */ + const long sqrtA = get_replaygain_int(db*5/2) << 2; /* 10^(db/80), s5.26 */ + const long A = FRACMUL_SHL(sqrtA, sqrtA, 8); /* s2.29 */ + const long alpha = fp_sincos(cutoff, &cs)/(2*Q)*10 >> 1; /* s1.30 */ + const long ap1 = (A >> 4) + one; + const long am1 = (A >> 4) - one; + const long ap1_cs = FRACMUL(ap1, cs); + const long am1_cs = FRACMUL(am1, cs); + const long twosqrtalpha = 2*FRACMUL(sqrtA, alpha); + int32_t a0, a1, a2; /* these are all s6.25 format */ + int32_t b0, b1, b2; + + /* [0.1 .. 40] */ + b0 = FRACMUL_SHL(A, ap1 + am1_cs + twosqrtalpha, 2); + /* [-63.5 .. 16] */ + b1 = -FRACMUL_SHL(A, am1 + ap1_cs, 3); + /* [0 .. 32] */ + b2 = FRACMUL_SHL(A, ap1 + am1_cs - twosqrtalpha, 2); + /* [0.5 .. 10] */ + a0 = ap1 - am1_cs + twosqrtalpha; + /* [-4 .. 16] */ + a1 = 2*(am1 - ap1_cs); + /* [0 .. 8] */ + a2 = ap1 - am1_cs - twosqrtalpha; + + /* [0.1 .. 1.99] */ + const long rcp_a0 = fp_div(1, a0, 55); /* s1.30 */ + *c++ = FRACMUL_SHL(b0, rcp_a0, 2); /* [0 .. 16] */ + *c++ = FRACMUL_SHL(b1, rcp_a0, 2); /* [-31.7 .. 2] */ + *c++ = FRACMUL_SHL(b2, rcp_a0, 2); /* [0 .. 16] */ + *c++ = FRACMUL_SHL(-a1, rcp_a0, 2); /* [-2 .. 2] */ + *c++ = FRACMUL_SHL(-a2, rcp_a0, 2); /* [0 .. 1] */ +} + +/* We realise the filters as a second order direct form 1 structure. Direct + * form 1 was chosen because of better numerical properties for fixed point + * implementations. + */ + +#if (!defined(CPU_COLDFIRE) && !defined(CPU_ARM)) +void eq_filter(int32_t **x, struct eqfilter *f, unsigned num, + unsigned channels, unsigned shift) +{ + unsigned c, i; + long long acc; + + /* Direct form 1 filtering code. + y[n] = b0*x[i] + b1*x[i - 1] + b2*x[i - 2] + a1*y[i - 1] + a2*y[i - 2], + where y[] is output and x[] is input. + */ + + for (c = 0; c < channels; c++) { + for (i = 0; i < num; i++) { + acc = (long long) x[c][i] * f->coefs[0]; + acc += (long long) f->history[c][0] * f->coefs[1]; + acc += (long long) f->history[c][1] * f->coefs[2]; + acc += (long long) f->history[c][2] * f->coefs[3]; + acc += (long long) f->history[c][3] * f->coefs[4]; + f->history[c][1] = f->history[c][0]; + f->history[c][0] = x[c][i]; + f->history[c][3] = f->history[c][2]; + x[c][i] = (acc << shift) >> 32; + f->history[c][2] = x[c][i]; + } + } +} +#endif + diff --git a/lib/rbcodec/dsp/eq.h b/lib/rbcodec/dsp/eq.h new file mode 100644 index 0000000000..a44e9153ac --- /dev/null +++ b/lib/rbcodec/dsp/eq.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006-2007 Thom Johansen + * + * 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. + * + ****************************************************************************/ + +#ifndef _EQ_H +#define _EQ_H + +#include +#include + +/* These depend on the fixed point formats used by the different filter types + and need to be changed when they change. + */ +#define FILTER_BISHELF_SHIFT 5 +#define EQ_PEAK_SHIFT 4 +#define EQ_SHELF_SHIFT 6 + +struct eqfilter { + int32_t coefs[5]; /* Order is b0, b1, b2, a1, a2 */ + int32_t history[2][4]; +}; + +void filter_shelf_coefs(unsigned long cutoff, long A, bool low, int32_t *c); +void filter_bishelf_coefs(unsigned long cutoff_low, unsigned long cutoff_high, + long A_low, long A_high, long A, int32_t *c); +void eq_pk_coefs(unsigned long cutoff, unsigned long Q, long db, int32_t *c); +void eq_ls_coefs(unsigned long cutoff, unsigned long Q, long db, int32_t *c); +void eq_hs_coefs(unsigned long cutoff, unsigned long Q, long db, int32_t *c); +void eq_filter(int32_t **x, struct eqfilter *f, unsigned num, + unsigned channels, unsigned shift); + +#endif + diff --git a/lib/rbcodec/dsp/eq_arm.S b/lib/rbcodec/dsp/eq_arm.S new file mode 100644 index 0000000000..b0e1771e89 --- /dev/null +++ b/lib/rbcodec/dsp/eq_arm.S @@ -0,0 +1,89 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006-2007 Thom Johansen + * + * 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. + * + ****************************************************************************/ + +#include "config.h" + +/* uncomment this to make filtering calculate lower bits after shifting. + * without this, "shift" of the lower bits will be lost here. + */ +/* #define HIGH_PRECISION */ + +/* + * void eq_filter(int32_t **x, struct eqfilter *f, unsigned num, + * unsigned channels, unsigned shift) + */ +#if CONFIG_CPU == PP5002 + .section .icode,"ax",%progbits +#else + .text +#endif + .global eq_filter +eq_filter: + ldr r12, [sp] @ get shift parameter + stmdb sp!, { r0-r11, lr } @ save all params and clobbered regs + ldmia r1!, { r4-r8 } @ load coefs + mov r10, r1 @ loop prelude expects filter struct addr in r10 + +.filterloop: + ldr r9, [sp] @ get pointer to this channels data + add r0, r9, #4 + str r0, [sp] @ save back pointer to next channels data + ldr r9, [r9] @ r9 = x[] + ldr r14, [sp, #8] @ r14 = numsamples + ldmia r10, { r0-r3 } @ load history, r10 should be filter struct addr + str r10, [sp, #4] @ save it for loop end + + /* r0-r3 = history, r4-r8 = coefs, r9 = x[], r10..r11 = accumulator, + * r12 = shift amount, r14 = number of samples. + */ +.loop: + /* Direct form 1 filtering code. + * y[n] = b0*x[i] + b1*x[i - 1] + b2*x[i - 2] + a1*y[i - 1] + a2*y[i - 2], + * where y[] is output and x[] is input. This is performed out of order to + * reuse registers, we're pretty short on regs. + */ + smull r10, r11, r6, r1 @ acc = b2*x[i - 2] + mov r1, r0 @ fix input history + smlal r10, r11, r5, r0 @ acc += b1*x[i - 1] + ldr r0, [r9] @ load input and fix history in same operation + smlal r10, r11, r7, r2 @ acc += a1*y[i - 1] + smlal r10, r11, r8, r3 @ acc += a2*y[i - 2] + smlal r10, r11, r4, r0 @ acc += b0*x[i] /* avoid stall on arm9*/ + mov r3, r2 @ fix output history + mov r2, r11, asl r12 @ get upper part of result and shift left +#ifdef HIGH_PRECISION + rsb r11, r12, #32 @ get shift amount for lower part + orr r2, r2, r10, lsr r11 @ then mix in correctly shifted lower part +#endif + str r2, [r9], #4 @ save result + subs r14, r14, #1 @ are we done with this channel? + bne .loop + + ldr r10, [sp, #4] @ load filter struct pointer + stmia r10!, { r0-r3 } @ save back history + ldr r11, [sp, #12] @ load number of channels + subs r11, r11, #1 @ all channels processed? + strne r11, [sp, #12] + bne .filterloop + + add sp, sp, #16 @ compensate for temp storage + ldmpc regs=r4-r11 + diff --git a/lib/rbcodec/dsp/eq_cf.S b/lib/rbcodec/dsp/eq_cf.S new file mode 100644 index 0000000000..30a28b9d99 --- /dev/null +++ b/lib/rbcodec/dsp/eq_cf.S @@ -0,0 +1,91 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006-2007 Thom Johansen + * + * 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. + * + ****************************************************************************/ + +/* uncomment this to make filtering calculate lower bits after shifting. + * without this, "shift" - 1 of the lower bits will be lost here. + */ +/* #define HIGH_PRECISION */ + +/* + * void eq_filter(int32_t **x, struct eqfilter *f, unsigned num, + * unsigned channels, unsigned shift) + */ + .text + .global eq_filter +eq_filter: + lea.l (-11*4, %sp), %sp + movem.l %d2-%d7/%a2-%a6, (%sp) | save clobbered regs + move.l (11*4+8, %sp), %a5 | fetch filter structure address + move.l (11*4+20, %sp), %d7 | load shift count + subq.l #1, %d7 | EMAC gives us one free shift +#ifdef HIGH_PRECISION + moveq.l #8, %d6 + sub.l %d7, %d6 | shift for lower part of accumulator +#endif + movem.l (%a5), %a0-%a4 | load coefs + lea.l (5*4, %a5), %a5 | point to filter history + +.filterloop: + move.l (11*4+4, %sp), %a6 | load input channel pointer + addq.l #4, (11*4+4, %sp) | point x to next channel + move.l (%a6), %a6 + move.l (11*4+12, %sp), %d5 | number of samples + movem.l (%a5), %d0-%d3 | load filter history + + /* d0-d3 = history, d4 = temp, d5 = sample count, d6 = lower shift amount, + * d7 = upper shift amount, a0-a4 = coefs, a5 = history pointer, a6 = x[] + */ +.loop: + /* Direct form 1 filtering code. We assume DSP has put EMAC in frac mode. + * y[n] = b0*x[i] + b1*x[i - 1] + b2*x[i - 2] + a1*y[i - 1] + a2*y[i - 2], + * where y[] is output and x[] is input. This is performed out of order + * to do parallel load of input value. + */ + mac.l %a2, %d1, %acc0 | acc = b2*x[i - 2] + move.l %d0, %d1 | fix input history + mac.l %a1, %d0, (%a6), %d0, %acc0 | acc += b1*x[i - 1], x[i] -> d0 + mac.l %a0, %d0, %acc0 | acc += b0*x[i] + mac.l %a3, %d2, %acc0 | acc += a1*y[i - 1] + mac.l %a4, %d3, %acc0 | acc += a2*y[i - 2] + move.l %d2, %d3 | fix output history +#ifdef HIGH_PRECISION + move.l %accext01, %d2 | fetch lower part of accumulator + move.b %d2, %d4 | clear upper three bytes + lsr.l %d6, %d4 | shift lower bits +#endif + movclr.l %acc0, %d2 | fetch upper part of result + asl.l %d7, %d2 | restore fixed point format +#ifdef HIGH_PRECISION + or.l %d2, %d4 | combine lower and upper parts +#endif + move.l %d2, (%a6)+ | save result + subq.l #1, %d5 | are we done with this channel? + jne .loop + + movem.l %d0-%d3, (%a5) | save history back to struct + lea.l (4*4, %a5), %a5 | point to next channel's history + subq.l #1, (11*4+16, %sp) | have we processed both channels? + jne .filterloop + + movem.l (%sp), %d2-%d7/%a2-%a6 + lea.l (11*4, %sp), %sp + rts + diff --git a/lib/rbcodec/dsp/eqs/Acoustic.cfg b/lib/rbcodec/dsp/eqs/Acoustic.cfg new file mode 100644 index 0000000000..34b5ed8a2b --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Acoustic.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 45 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: 45 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 10 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: 15 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 30 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 20 diff --git a/lib/rbcodec/dsp/eqs/Bass.cfg b/lib/rbcodec/dsp/eqs/Bass.cfg new file mode 100644 index 0000000000..2742459081 --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Bass.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 50 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: 50 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 35 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: 15 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 5 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: -5 diff --git a/lib/rbcodec/dsp/eqs/Classical.cfg b/lib/rbcodec/dsp/eqs/Classical.cfg new file mode 100644 index 0000000000..bf2f9f9566 --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Classical.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 50 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: 50 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 40 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: -20 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 10 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 20 diff --git a/lib/rbcodec/dsp/eqs/Default.cfg b/lib/rbcodec/dsp/eqs/Default.cfg new file mode 100644 index 0000000000..d6f345fa9e --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Default.cfg @@ -0,0 +1,17 @@ +eq enabled: off +eq precut: 0 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: 0 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 0 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: 0 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 0 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 0 diff --git a/lib/rbcodec/dsp/eqs/Disco.cfg b/lib/rbcodec/dsp/eqs/Disco.cfg new file mode 100644 index 0000000000..f894f26da1 --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Disco.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 45 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: 30 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 10 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: 45 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 25 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 10 diff --git a/lib/rbcodec/dsp/eqs/Electronic.cfg b/lib/rbcodec/dsp/eqs/Electronic.cfg new file mode 100644 index 0000000000..e70c911272 --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Electronic.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 55 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: 45 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 5 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: 25 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 15 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 55 diff --git a/lib/rbcodec/dsp/eqs/Hip-Hop.cfg b/lib/rbcodec/dsp/eqs/Hip-Hop.cfg new file mode 100644 index 0000000000..2d38425dc4 --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Hip-Hop.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 65 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: 65 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 25 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: -10 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 15 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 35 diff --git a/lib/rbcodec/dsp/eqs/Jazz.cfg b/lib/rbcodec/dsp/eqs/Jazz.cfg new file mode 100644 index 0000000000..f576f9fcc1 --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Jazz.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 60 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: 40 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 15 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: -25 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 5 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 60 diff --git a/lib/rbcodec/dsp/eqs/Lounge.cfg b/lib/rbcodec/dsp/eqs/Lounge.cfg new file mode 100644 index 0000000000..39ae23a7e7 --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Lounge.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 20 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: -25 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 5 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: 20 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: -15 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 15 diff --git a/lib/rbcodec/dsp/eqs/Pop.cfg b/lib/rbcodec/dsp/eqs/Pop.cfg new file mode 100644 index 0000000000..1d8cefe173 --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Pop.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 50 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: -10 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 5 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: 50 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 15 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: -10 diff --git a/lib/rbcodec/dsp/eqs/R&B.cfg b/lib/rbcodec/dsp/eqs/R&B.cfg new file mode 100644 index 0000000000..a460b587f5 --- /dev/null +++ b/lib/rbcodec/dsp/eqs/R&B.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 45 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: 35 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 45 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: 5 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 25 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 30 diff --git a/lib/rbcodec/dsp/eqs/Rock.cfg b/lib/rbcodec/dsp/eqs/Rock.cfg new file mode 100644 index 0000000000..ec4f0356a8 --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Rock.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 45 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: 25 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 10 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: 0 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 20 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 45 diff --git a/lib/rbcodec/dsp/eqs/Vocal.cfg b/lib/rbcodec/dsp/eqs/Vocal.cfg new file mode 100644 index 0000000000..1de754f07c --- /dev/null +++ b/lib/rbcodec/dsp/eqs/Vocal.cfg @@ -0,0 +1,17 @@ +eq enabled: on +eq precut: 45 +eq band 0 cutoff: 60 +eq band 0 q: 7 +eq band 0 gain: -45 +eq band 1 cutoff: 200 +eq band 1 q: 10 +eq band 1 gain: 5 +eq band 2 cutoff: 800 +eq band 2 q: 10 +eq band 2 gain: 45 +eq band 3 cutoff: 4000 +eq band 3 q: 10 +eq band 3 gain: 20 +eq band 4 cutoff: 12000 +eq band 4 q: 7 +eq band 4 gain: 0 diff --git a/lib/rbcodec/dsp/tdspeed.c b/lib/rbcodec/dsp/tdspeed.c new file mode 100644 index 0000000000..731be12621 --- /dev/null +++ b/lib/rbcodec/dsp/tdspeed.c @@ -0,0 +1,450 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 by Nicolas Pitre + * Copyright (C) 2006-2007 by Stéphane Doyon + * + * 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. + * + ****************************************************************************/ + +#include +#include +#include +#include +#include "sound.h" +#include "core_alloc.h" +#include "system.h" +#include "tdspeed.h" +#include "settings.h" + +#define assert(cond) + +#define MIN_RATE 8000 +#define MAX_RATE 48000 /* double buffer for double rate */ +#define MINFREQ 100 + +#define FIXED_BUFSIZE 3072 /* 48KHz factor 3.0 */ + +static int32_t** dsp_src; +static int handles[4]; +static int32_t *overlap_buffer[2] = { NULL, NULL }; +static int32_t *outbuf[2] = { NULL, NULL }; + +static int move_callback(int handle, void* current, void* new) +{ + /* TODO */ + (void)handle; + if (dsp_src) + { + int ch = (current == outbuf[0]) ? 0 : 1; + dsp_src[ch] = outbuf[ch] = new; + } + return BUFLIB_CB_OK; +} + +static struct buflib_callbacks ops = { + .move_callback = move_callback, + .shrink_callback = NULL, +}; + +static int ovl_move_callback(int handle, void* current, void* new) +{ + /* TODO */ + (void)handle; + if (dsp_src) + { + int ch = (current == overlap_buffer[0]) ? 0 : 1; + overlap_buffer[ch] = new; + } + return BUFLIB_CB_OK; +} + +static struct buflib_callbacks ovl_ops = { + .move_callback = ovl_move_callback, + .shrink_callback = NULL, +}; + + +static struct tdspeed_state_s +{ + bool stereo; + int32_t shift_max; /* maximum displacement on a frame */ + int32_t src_step; /* source window pace */ + int32_t dst_step; /* destination window pace */ + int32_t dst_order; /* power of two for dst_step */ + int32_t ovl_shift; /* overlap buffer frame shift */ + int32_t ovl_size; /* overlap buffer used size */ + int32_t ovl_space; /* overlap buffer size */ + int32_t *ovl_buff[2]; /* overlap buffer */ +} tdspeed_state; + +void tdspeed_init(void) +{ + if (!global_settings.timestretch_enabled) + return; + + /* Allocate buffers */ + if (overlap_buffer[0] == NULL) + { + handles[0] = core_alloc_ex("tdspeed ovl left", FIXED_BUFSIZE * sizeof(int32_t), &ovl_ops); + overlap_buffer[0] = core_get_data(handles[0]); + } + if (overlap_buffer[1] == NULL) + { + handles[1] = core_alloc_ex("tdspeed ovl right", FIXED_BUFSIZE * sizeof(int32_t), &ovl_ops); + overlap_buffer[1] = core_get_data(handles[1]); + } + if (outbuf[0] == NULL) + { + handles[2] = core_alloc_ex("tdspeed left", TDSPEED_OUTBUFSIZE * sizeof(int32_t), &ops); + outbuf[0] = core_get_data(handles[2]); + } + if (outbuf[1] == NULL) + { + handles[3] = core_alloc_ex("tdspeed right", TDSPEED_OUTBUFSIZE * sizeof(int32_t), &ops); + outbuf[1] = core_get_data(handles[3]); + } +} + +void tdspeed_finish(void) +{ + for(unsigned i = 0; i < ARRAYLEN(handles); i++) + { + if (handles[i] > 0) + { + core_free(handles[i]); + handles[i] = 0; + } + } + overlap_buffer[0] = overlap_buffer[1] = NULL; + outbuf[0] = outbuf[1] = NULL; +} + +bool tdspeed_config(int samplerate, bool stereo, int32_t factor) +{ + struct tdspeed_state_s *st = &tdspeed_state; + int src_frame_sz; + + /* Check buffers were allocated ok */ + if (overlap_buffer[0] == NULL || overlap_buffer[1] == NULL) + return false; + + if (outbuf[0] == NULL || outbuf[1] == NULL) + return false; + + /* Check parameters */ + if (factor == PITCH_SPEED_100) + return false; + + if (samplerate < MIN_RATE || samplerate > MAX_RATE) + return false; + + if (factor < STRETCH_MIN || factor > STRETCH_MAX) + return false; + + st->stereo = stereo; + st->dst_step = samplerate / MINFREQ; + + if (factor > PITCH_SPEED_100) + st->dst_step = st->dst_step * PITCH_SPEED_100 / factor; + + st->dst_order = 1; + + while (st->dst_step >>= 1) + st->dst_order++; + + st->dst_step = (1 << st->dst_order); + st->src_step = st->dst_step * factor / PITCH_SPEED_100; + st->shift_max = (st->dst_step > st->src_step) ? st->dst_step : st->src_step; + + src_frame_sz = st->shift_max + st->dst_step; + + if (st->dst_step > st->src_step) + src_frame_sz += st->dst_step - st->src_step; + + st->ovl_space = ((src_frame_sz - 2) / st->src_step) * st->src_step + + src_frame_sz; + + if (st->src_step > st->dst_step) + st->ovl_space += 2*st->src_step - st->dst_step; + + if (st->ovl_space > FIXED_BUFSIZE) + st->ovl_space = FIXED_BUFSIZE; + + st->ovl_size = 0; + st->ovl_shift = 0; + + st->ovl_buff[0] = overlap_buffer[0]; + + if (stereo) + st->ovl_buff[1] = overlap_buffer[1]; + else + st->ovl_buff[1] = st->ovl_buff[0]; + + return true; +} + +static int tdspeed_apply(int32_t *buf_out[2], int32_t *buf_in[2], + int data_len, int last, int out_size) +/* data_len in samples */ +{ + struct tdspeed_state_s *st = &tdspeed_state; + int32_t *dest[2]; + int32_t next_frame, prev_frame, src_frame_sz; + bool stereo = buf_in[0] != buf_in[1]; + + assert(stereo == st->stereo); + + src_frame_sz = st->shift_max + st->dst_step; + + if (st->dst_step > st->src_step) + src_frame_sz += st->dst_step - st->src_step; + + /* deal with overlap data first, if any */ + if (st->ovl_size) + { + int32_t have, copy, steps; + have = st->ovl_size; + + if (st->ovl_shift > 0) + have -= st->ovl_shift; + + /* append just enough data to have all of the overlap buffer consumed */ + steps = (have - 1) / st->src_step; + copy = steps * st->src_step + src_frame_sz - have; + + if (copy < src_frame_sz - st->dst_step) + copy += st->src_step; /* one more step to allow for pregap data */ + + if (copy > data_len) + copy = data_len; + + assert(st->ovl_size + copy <= FIXED_BUFSIZE); + memcpy(st->ovl_buff[0] + st->ovl_size, buf_in[0], + copy * sizeof(int32_t)); + + if (stereo) + memcpy(st->ovl_buff[1] + st->ovl_size, buf_in[1], + copy * sizeof(int32_t)); + + if (!last && have + copy < src_frame_sz) + { + /* still not enough to process at least one frame */ + st->ovl_size += copy; + return 0; + } + + /* recursively call ourselves to process the overlap buffer */ + have = st->ovl_size; + st->ovl_size = 0; + + if (copy == data_len) + { + assert(have + copy <= FIXED_BUFSIZE); + return tdspeed_apply(buf_out, st->ovl_buff, have+copy, last, + out_size); + } + + assert(have + copy <= FIXED_BUFSIZE); + int i = tdspeed_apply(buf_out, st->ovl_buff, have+copy, -1, out_size); + + dest[0] = buf_out[0] + i; + dest[1] = buf_out[1] + i; + + /* readjust pointers to account for data already consumed */ + next_frame = copy - src_frame_sz + st->src_step; + prev_frame = next_frame - st->ovl_shift; + } + else + { + dest[0] = buf_out[0]; + dest[1] = buf_out[1]; + + next_frame = prev_frame = 0; + + if (st->ovl_shift > 0) + next_frame += st->ovl_shift; + else + prev_frame += -st->ovl_shift; + } + + st->ovl_shift = 0; + + /* process all complete frames */ + while (data_len - next_frame >= src_frame_sz) + { + /* find frame overlap by autocorelation */ + int const INC1 = 8; + int const INC2 = 32; + + int64_t min_delta = INT64_MAX; /* most positive */ + int shift = 0; + + /* Power of 2 of a 28bit number requires 56bits, can accumulate + 256times in a 64bit variable. */ + assert(st->dst_step / INC2 <= 256); + assert(next_frame + st->shift_max - 1 + st->dst_step - 1 < data_len); + assert(prev_frame + st->dst_step - 1 < data_len); + + for (int i = 0; i < st->shift_max; i += INC1) + { + int64_t delta = 0; + + int32_t *curr = buf_in[0] + next_frame + i; + int32_t *prev = buf_in[0] + prev_frame; + + for (int j = 0; j < st->dst_step; j += INC2, curr += INC2, prev += INC2) + { + int32_t diff = *curr - *prev; + delta += abs(diff); + + if (delta >= min_delta) + goto skip; + } + + if (stereo) + { + curr = buf_in[1] + next_frame + i; + prev = buf_in[1] + prev_frame; + + for (int j = 0; j < st->dst_step; j += INC2, curr += INC2, prev += INC2) + { + int32_t diff = *curr - *prev; + delta += abs(diff); + + if (delta >= min_delta) + goto skip; + } + } + + min_delta = delta; + shift = i; +skip:; + } + + /* overlap fading-out previous frame with fading-in current frame */ + int32_t *curr = buf_in[0] + next_frame + shift; + int32_t *prev = buf_in[0] + prev_frame; + + int32_t *d = dest[0]; + + assert(next_frame + shift + st->dst_step - 1 < data_len); + assert(prev_frame + st->dst_step - 1 < data_len); + assert(dest[0] - buf_out[0] + st->dst_step - 1 < out_size); + + for (int i = 0, j = st->dst_step; j; i++, j--) + { + *d++ = (*curr++ * (int64_t)i + + *prev++ * (int64_t)j) >> st->dst_order; + } + + dest[0] = d; + + if (stereo) + { + curr = buf_in[1] + next_frame + shift; + prev = buf_in[1] + prev_frame; + + d = dest[1]; + + for (int i = 0, j = st->dst_step; j; i++, j--) + { + assert(d < buf_out[1] + out_size); + + *d++ = (*curr++ * (int64_t)i + + *prev++ * (int64_t)j) >> st->dst_order; + } + + dest[1] = d; + } + + /* adjust pointers for next frame */ + prev_frame = next_frame + shift + st->dst_step; + next_frame += st->src_step; + + /* here next_frame - prev_frame = src_step - dst_step - shift */ + assert(next_frame - prev_frame == st->src_step - st->dst_step - shift); + } + + /* now deal with remaining partial frames */ + if (last == -1) + { + /* special overlap buffer processing: remember frame shift only */ + st->ovl_shift = next_frame - prev_frame; + } + else if (last != 0) + { + /* last call: purge all remaining data to output buffer */ + int i = data_len - prev_frame; + + assert(dest[0] + i <= buf_out[0] + out_size); + memcpy(dest[0], buf_in[0] + prev_frame, i * sizeof(int32_t)); + + dest[0] += i; + + if (stereo) + { + assert(dest[1] + i <= buf_out[1] + out_size); + memcpy(dest[1], buf_in[1] + prev_frame, i * sizeof(int32_t)); + dest[1] += i; + } + } + else + { + /* preserve remaining data + needed overlap data for next call */ + st->ovl_shift = next_frame - prev_frame; + int i = (st->ovl_shift < 0) ? next_frame : prev_frame; + st->ovl_size = data_len - i; + + assert(st->ovl_size <= FIXED_BUFSIZE); + memcpy(st->ovl_buff[0], buf_in[0] + i, st->ovl_size * sizeof(int32_t)); + + if (stereo) + memcpy(st->ovl_buff[1], buf_in[1] + i, st->ovl_size * sizeof(int32_t)); + } + + return dest[0] - buf_out[0]; +} + +long tdspeed_est_output_size() +{ + return TDSPEED_OUTBUFSIZE; +} + +long tdspeed_est_input_size(long size) +{ + struct tdspeed_state_s *st = &tdspeed_state; + + size = (size - st->ovl_size) * st->src_step / st->dst_step; + + if (size < 0) + size = 0; + + return size; +} + +int tdspeed_doit(int32_t *src[], int count) +{ + dsp_src = src; + count = tdspeed_apply( (int32_t *[2]) { outbuf[0], outbuf[1] }, + src, count, 0, TDSPEED_OUTBUFSIZE); + + src[0] = outbuf[0]; + src[1] = outbuf[1]; + + return count; +} + diff --git a/lib/rbcodec/dsp/tdspeed.h b/lib/rbcodec/dsp/tdspeed.h new file mode 100644 index 0000000000..e91eeb1701 --- /dev/null +++ b/lib/rbcodec/dsp/tdspeed.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 by Nicolas Pitre + * Copyright (C) 2006-2007 by Stéphane Doyon + * + * 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. + * + ****************************************************************************/ + +#ifndef _TDSPEED_H +#define _TDSPEED_H + +#include "dsp.h" + +#define TDSPEED_OUTBUFSIZE 4096 + +/* some #define functions to get the pitch, stretch and speed values based on */ +/* two known values. Remember that params are alphabetical. */ +#define GET_SPEED(pitch, stretch) \ + ((pitch * stretch + PITCH_SPEED_100 / 2L) / PITCH_SPEED_100) +#define GET_PITCH(speed, stretch) \ + ((speed * PITCH_SPEED_100 + stretch / 2L) / stretch) +#define GET_STRETCH(pitch, speed) \ + ((speed * PITCH_SPEED_100 + pitch / 2L) / pitch) + +void tdspeed_init(void); +void tdspeed_finish(void); +bool tdspeed_config(int samplerate, bool stereo, int32_t factor); +long tdspeed_est_output_size(void); +long tdspeed_est_input_size(long size); +int tdspeed_doit(int32_t *src[], int count); + +#define STRETCH_MAX (250L * PITCH_SPEED_PRECISION) /* 250% */ +#define STRETCH_MIN (35L * PITCH_SPEED_PRECISION) /* 35% */ + +#endif diff --git a/lib/rbcodec/metadata/a52.c b/lib/rbcodec/metadata/a52.c new file mode 100644 index 0000000000..a8aad3fa4f --- /dev/null +++ b/lib/rbcodec/metadata/a52.c @@ -0,0 +1,103 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ + +#include +#include "metadata.h" +#include "logf.h" + +#include "metadata_parsers.h" + +static const unsigned short a52_bitrates[] = +{ + 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, + 192, 224, 256, 320, 384, 448, 512, 576, 640 +}; + +/* Only store frame sizes for 44.1KHz - others are simply multiples + of the bitrate */ +static const unsigned short a52_441framesizes[] = +{ + 69 * 2, 70 * 2, 87 * 2, 88 * 2, 104 * 2, 105 * 2, 121 * 2, + 122 * 2, 139 * 2, 140 * 2, 174 * 2, 175 * 2, 208 * 2, 209 * 2, + 243 * 2, 244 * 2, 278 * 2, 279 * 2, 348 * 2, 349 * 2, 417 * 2, + 418 * 2, 487 * 2, 488 * 2, 557 * 2, 558 * 2, 696 * 2, 697 * 2, + 835 * 2, 836 * 2, 975 * 2, 976 * 2, 1114 * 2, 1115 * 2, 1253 * 2, + 1254 * 2, 1393 * 2, 1394 * 2 +}; + +bool get_a52_metadata(int fd, struct mp3entry *id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + unsigned long totalsamples; + int i; + + if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 5) < 5)) + { + return false; + } + + if ((buf[0] != 0x0b) || (buf[1] != 0x77)) + { + logf("not an A52/AC3 file\n"); + return false; + } + + i = buf[4] & 0x3e; + + if (i > 36) + { + logf("A52: Invalid frmsizecod: %d\n",i); + return false; + } + + id3->bitrate = a52_bitrates[i >> 1]; + id3->vbr = false; + id3->filesize = filesize(fd); + + switch (buf[4] & 0xc0) + { + case 0x00: + id3->frequency = 48000; + id3->bytesperframe=id3->bitrate * 2 * 2; + break; + + case 0x40: + id3->frequency = 44100; + id3->bytesperframe = a52_441framesizes[i]; + break; + + case 0x80: + id3->frequency = 32000; + id3->bytesperframe = id3->bitrate * 3 * 2; + break; + + default: + logf("A52: Invalid samplerate code: 0x%02x\n", buf[4] & 0xc0); + return false; + break; + } + + /* One A52 frame contains 6 blocks, each containing 256 samples */ + totalsamples = id3->filesize / id3->bytesperframe * 6 * 256; + id3->length = totalsamples / id3->frequency * 1000; + return true; +} diff --git a/lib/rbcodec/metadata/adx.c b/lib/rbcodec/metadata/adx.c new file mode 100644 index 0000000000..7c341b4835 --- /dev/null +++ b/lib/rbcodec/metadata/adx.c @@ -0,0 +1,124 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "debug.h" + +bool get_adx_metadata(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char * buf = (unsigned char *)id3->path; + int chanstart, channels; + int looping = 0, start_adr = 0, end_adr = 0; + + /* try to get the basic header */ + if ((lseek(fd, 0, SEEK_SET) < 0) + || (read(fd, buf, 0x38) < 0x38)) + { + DEBUGF("lseek or read failed\n"); + return false; + } + + /* ADX starts with 0x80 */ + if (buf[0] != 0x80) { + DEBUGF("get_adx_metadata: wrong first byte %c\n",buf[0]); + return false; + } + + /* check for a reasonable offset */ + chanstart = ((buf[2] << 8) | buf[3]) + 4; + if (chanstart > 4096) { + DEBUGF("get_adx_metadata: bad chanstart %i\n", chanstart); + return false; + } + + /* check for a workable number of channels */ + channels = buf[7]; + if (channels != 1 && channels != 2) { + DEBUGF("get_adx_metadata: bad channel count %i\n",channels); + return false; + } + + id3->frequency = get_long_be(&buf[8]); + /* 32 samples per 18 bytes */ + id3->bitrate = id3->frequency * channels * 18 * 8 / 32 / 1000; + id3->length = get_long_be(&buf[12]) / id3->frequency * 1000; + id3->vbr = false; + id3->filesize = filesize(fd); + + /* get loop info */ + if (!memcmp(buf+0x10,"\x01\xF4\x03",3)) { + /* Soul Calibur 2 style (type 03) */ + DEBUGF("get_adx_metadata: type 03 found\n"); + /* check if header is too small for loop data */ + if (chanstart-6 < 0x2c) looping=0; + else { + looping = get_long_be(&buf[0x18]); + end_adr = get_long_be(&buf[0x28]); + start_adr = get_long_be(&buf[0x1c])/32*channels*18+chanstart; + } + } else if (!memcmp(buf+0x10,"\x01\xF4\x04",3)) { + /* Standard (type 04) */ + DEBUGF("get_adx_metadata: type 04 found\n"); + /* check if header is too small for loop data */ + if (chanstart-6 < 0x38) looping=0; + else { + looping = get_long_be(&buf[0x24]); + end_adr = get_long_be(&buf[0x34]); + start_adr = get_long_be(&buf[0x28])/32*channels*18+chanstart; + } + } else { + DEBUGF("get_adx_metadata: error, couldn't determine ADX type\n"); + return false; + } + + /* is file using encryption */ + if (buf[0x13]==0x08) { + DEBUGF("get_adx_metadata: error, encrypted ADX not supported\n"); + return false; + } + + if (looping) { + /* 2 loops, 10 second fade */ + id3->length = (start_adr-chanstart + 2*(end_adr-start_adr)) + *8 / id3->bitrate + 10000; + } + + /* try to get the channel header */ + if ((lseek(fd, chanstart-6, SEEK_SET) < 0) + || (read(fd, buf, 6) < 6)) + { + return false; + } + + /* check channel header */ + if (memcmp(buf, "(c)CRI", 6) != 0) return false; + + return true; +} diff --git a/lib/rbcodec/metadata/aiff.c b/lib/rbcodec/metadata/aiff.c new file mode 100644 index 0000000000..654f37cf98 --- /dev/null +++ b/lib/rbcodec/metadata/aiff.c @@ -0,0 +1,108 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" + +#include "debug.h" + +/* compressionType: AIFC QuickTime IMA ADPCM */ +#define AIFC_FORMAT_QT_IMA_ADPCM "ima4" + +bool get_aiff_metadata(int fd, struct mp3entry* id3) +{ + unsigned char buf[512]; + unsigned long numChannels = 0; + unsigned long numSampleFrames = 0; + unsigned long numbytes = 0; + bool is_aifc = false; + + if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, &buf[0], 12) < 12) || + (memcmp(&buf[0], "FORM", 4) != 0) || (memcmp(&buf[8], "AIF", 3) != 0) || + (!(is_aifc = (buf[11] == 'C')) && buf[11] != 'F')) + { + return false; + } + + while (read(fd, &buf[0], 8) == 8) + { + size_t size = get_long_be(&buf[4]); /* chunkSize */ + + if (memcmp(&buf[0], "SSND", 4) == 0) + { + numbytes = size - 8; + break; /* assume COMM was already read */ + } + + /* odd chunk sizes must be padded */ + size += size & 1; + + if (size > sizeof(buf)) + { + DEBUGF("AIFF \"%4.4s\" chunk too large (%zd > %zd)", + (char*) &buf[0], size, sizeof(buf)); + } + + if (memcmp(&buf[0], "COMM", 4) == 0) + { + if (size > sizeof(buf) || read(fd, &buf[0], size) != (ssize_t)size) + return false; + + numChannels = ((buf[0]<<8)|buf[1]); + + numSampleFrames = get_long_be(&buf[2]); + + /* sampleRate */ + id3->frequency = get_long_be(&buf[10]); + id3->frequency >>= (16+14-buf[9]); + + /* save format infos */ + id3->bitrate = ((buf[6]<<8)|buf[7]) * numChannels * id3->frequency; + id3->bitrate /= 1000; + + if (!is_aifc || memcmp(&buf[18], AIFC_FORMAT_QT_IMA_ADPCM, 4) != 0) + id3->length = ((int64_t) numSampleFrames * 1000) / id3->frequency; + else + { + /* QuickTime IMA ADPCM is 1block = 64 data for each channel */ + id3->length = ((int64_t) numSampleFrames * 64000LL) / id3->frequency; + } + + id3->vbr = false; /* AIFF files are CBR */ + id3->filesize = filesize(fd); + } + else + { + /* skip chunk */ + if (lseek(fd, size, SEEK_CUR) < 0) + return false; + } + } + + return numbytes && numChannels; +} diff --git a/lib/rbcodec/metadata/ape.c b/lib/rbcodec/metadata/ape.c new file mode 100644 index 0000000000..0bd2477431 --- /dev/null +++ b/lib/rbcodec/metadata/ape.c @@ -0,0 +1,182 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "structec.h" + +#define APETAG_HEADER_LENGTH 32 +#define APETAG_HEADER_FORMAT "8llll8" +#define APETAG_ITEM_HEADER_FORMAT "ll" +#define APETAG_ITEM_TYPE_MASK 3 + +#ifdef HAVE_ALBUMART +/* The AA header consists of the pseudo filename "Album Cover (Front).ext" + * whereas ".ext" is the file extension. For now ".jpg" and ".png" are + * supported by this APE metadata parser. Therefore the length is 22. */ +#define APETAG_AA_HEADER_LENGTH 22 +#endif + +struct apetag_header +{ + char id[8]; + long version; + long length; + long item_count; + long flags; + char reserved[8]; +}; + +struct apetag_item_header +{ + long length; + long flags; +}; + +/* Read the items in an APEV2 tag. Only looks for a tag at the end of a + * file. Returns true if a tag was found and fully read, false otherwise. + */ +bool read_ape_tags(int fd, struct mp3entry* id3) +{ + struct apetag_header header; + + if ((lseek(fd, -APETAG_HEADER_LENGTH, SEEK_END) < 0) + || (ecread(fd, &header, 1, APETAG_HEADER_FORMAT, IS_BIG_ENDIAN) + != APETAG_HEADER_LENGTH) + || (memcmp(header.id, "APETAGEX", sizeof(header.id)))) + { + return false; + } + + if ((header.version == 2000) && (header.item_count > 0) + && (header.length > APETAG_HEADER_LENGTH)) + { + char *buf = id3->id3v2buf; + unsigned int buf_remaining = sizeof(id3->id3v2buf) + + sizeof(id3->id3v1buf); + unsigned int tag_remaining = header.length - APETAG_HEADER_LENGTH; + int i; + + if (lseek(fd, -header.length, SEEK_END) < 0) + { + return false; + } + + for (i = 0; i < header.item_count; i++) + { + struct apetag_item_header item; + char name[TAG_NAME_LENGTH]; + char value[TAG_VALUE_LENGTH]; + long r; + + if (tag_remaining < sizeof(item)) + { + break; + } + + if (ecread(fd, &item, 1, APETAG_ITEM_HEADER_FORMAT, IS_BIG_ENDIAN) + < (long) sizeof(item)) + { + return false; + } + + tag_remaining -= sizeof(item); + r = read_string(fd, name, sizeof(name), 0, tag_remaining); + + if (r == -1) + { + return false; + } + + tag_remaining -= r + item.length; + + if ((item.flags & APETAG_ITEM_TYPE_MASK) == 0) + { + long len; + + if (read_string(fd, value, sizeof(value), -1, item.length) + != item.length) + { + return false; + } + + len = parse_tag(name, value, id3, buf, buf_remaining, + TAGTYPE_APE); + buf += len; + buf_remaining -= len; + } + else + { +#ifdef HAVE_ALBUMART + if (strcasecmp(name, "cover art (front)") == 0) + { + /* Allow to read at least APETAG_AA_HEADER_LENGTH bytes. */ + r = read_string(fd, name, sizeof(name), 0, APETAG_AA_HEADER_LENGTH); + if (r == -1) + { + return false; + } + + /* Gather the album art format from the pseudo file name's ending. */ + strcpy(name, name + strlen(name) - 4); + id3->albumart.type = AA_TYPE_UNKNOWN; + if (strcasecmp(name, ".jpg") == 0) + { + id3->albumart.type = AA_TYPE_JPG; + } + else if (strcasecmp(name, ".png") == 0) + { + id3->albumart.type = AA_TYPE_PNG; + } + + /* Set the album art size and position. */ + if (id3->albumart.type != AA_TYPE_UNKNOWN) + { + id3->albumart.pos = lseek(fd, 0, SEEK_CUR); + id3->albumart.size = item.length - r; + id3->has_embedded_albumart = true; + } + + /* Seek back to this APE items begin. */ + if (lseek(fd, -r, SEEK_CUR) < 0) + { + return false; + } + } +#endif + /* Seek to the next APE item. */ + if (lseek(fd, item.length, SEEK_CUR) < 0) + { + return false; + } + } + } + } + + return true; +} diff --git a/lib/rbcodec/metadata/asap.c b/lib/rbcodec/metadata/asap.c new file mode 100644 index 0000000000..9e7f227031 --- /dev/null +++ b/lib/rbcodec/metadata/asap.c @@ -0,0 +1,254 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Dominik Wenger + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" +#include "debug.h" + +#define MAX_SONGS 32 + +static bool parse_dec(int *retval, const char *p, int minval, int maxval) +{ + int r = 0; + do { + char c = *p; + if (c >= '0' && c <= '9') + r = 10 * r + c - '0'; + else + return false; + if (r > maxval) + return false; + } while (*++p != '\0'); + if (r < minval) + return false; + *retval = r; + return true; +} + +static bool parse_text(char *retval, const char *p) +{ + int i; + if (*p != '"') + return false; + p++; + if (p[0] == '<' && p[1] == '?' && p[2] == '>' && p[3] == '"') + return true; + i = 0; + while (*p != '"') { + if (i >= 127) + return false; + if (*p == '\0') + return false; + retval[i++] = *p++; + } + retval[i] = '\0'; + return true; +} + +static int ASAP_ParseDuration(const char *s) +{ + int r; + if (*s < '0' || *s > '9') + return -1; + r = *s++ - '0'; + if (*s >= '0' && *s <= '9') + r = 10 * r + *s++ - '0'; + if (*s == ':') { + s++; + if (*s < '0' || *s > '5') + return -1; + r = 60 * r + (*s++ - '0') * 10; + if (*s < '0' || *s > '9') + return -1; + r += *s++ - '0'; + } + r *= 1000; + if (*s != '.') + return r; + s++; + if (*s < '0' || *s > '9') + return r; + r += 100 * (*s++ - '0'); + if (*s < '0' || *s > '9') + return r; + r += 10 * (*s++ - '0'); + if (*s < '0' || *s > '9') + return r; + r += *s - '0'; + return r; +} + +static bool read_asap_string(char* source, char** buf, char** buffer_end, char** dest) +{ + if(parse_text(*buf,source) == false) + return false; + + /* set dest pointer */ + *dest = *buf; + + /* move buf ptr */ + *buf += strlen(*buf)+1; + + /* check size */ + if(*buf >= *buffer_end) + { + DEBUGF("Buffer full\n"); + return false; + } + return true; +} + +static bool parse_sap_header(int fd, struct mp3entry* id3, int file_len) +{ + int module_index = 0; + int sap_signature = -1; + int duration_index = 0; + unsigned char cur_char = 0; + int i; + + /* set defaults */ + int numSongs = 1; + int defSong = 0; + int durations[MAX_SONGS]; + for (i = 0; i < MAX_SONGS; i++) + durations[i] = -1; + + /* use id3v2 buffer for our strings */ + char* buffer = id3->id3v2buf; + char* buffer_end = id3->id3v2buf + ID3V2_BUF_SIZE; + + /* parse file */ + while (1) + { + char line[256]; + char *p; + + if (module_index + 8 >= file_len) + return false; + /* read a char */ + read(fd,&cur_char,1); + /* end of header */ + if (cur_char == 0xff) + break; + + i = 0; + while (cur_char != 0x0d) + { + line[i++] = cur_char; + module_index++; + if (module_index >= file_len || (unsigned)i >= sizeof(line) - 1) + return false; + /* read a char */ + read(fd,&cur_char,1); + } + if (++module_index >= file_len ) + return false; + /* read a char */ + read(fd,&cur_char,1); + if ( cur_char != 0x0a) + return false; + + line[i] = '\0'; + for (p = line; *p != '\0'; p++) { + if (*p == ' ') { + *p++ = '\0'; + break; + } + } + + /* parse tags */ + if(strcmp(line, "SAP") == 0) + sap_signature = 1; + if (sap_signature == -1) + return false; + if (strcmp(line, "AUTHOR") == 0) + { + if(read_asap_string(p, &buffer, &buffer_end, &id3->artist) == false) + return false; + } + else if(strcmp(line, "NAME") == 0) + { + if(read_asap_string(p, &buffer, &buffer_end, &id3->title) == false) + return false; + } + else if(strcmp(line, "DATE") == 0) + { + if(read_asap_string(p, &buffer, &buffer_end, &id3->year_string) == false) + return false; + } + else if (strcmp(line, "SONGS") == 0) + { + if (parse_dec(&numSongs, p, 1, MAX_SONGS) == false ) + return false; + } + else if (strcmp(line, "DEFSONG") == 0) + { + if (parse_dec(&defSong, p, 0, MAX_SONGS) == false) + return false; + } + else if (strcmp(line, "TIME") == 0) + { + int durationTemp = ASAP_ParseDuration(p); + if (durationTemp < 0 || duration_index >= MAX_SONGS) + return false; + durations[duration_index++] = durationTemp; + } + } + + /* set length: */ + int length = durations[defSong]; + if (length < 0) + length = 180 * 1000; + id3->length = length; + + lseek(fd, 0, SEEK_SET); + return true; +} + + +bool get_asap_metadata(int fd, struct mp3entry* id3) +{ + + int filelength = filesize(fd); + + if(parse_sap_header(fd, id3, filelength) == false) + { + DEBUGF("parse sap header failed.\n"); + return false; + } + + id3->bitrate = 706; + id3->frequency = 44100; + + id3->vbr = false; + id3->filesize = filelength; + id3->genre_string = id3_get_num_genre(36); + + return true; +} diff --git a/lib/rbcodec/metadata/asf.c b/lib/rbcodec/metadata/asf.c new file mode 100644 index 0000000000..b815c09769 --- /dev/null +++ b/lib/rbcodec/metadata/asf.c @@ -0,0 +1,591 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * $Id$ + * + * Copyright (C) 2007 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "metadata.h" +#include "replaygain.h" +#include "debug.h" +#include "rbunicode.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "system.h" +#include + +/* TODO: Just read the GUIDs into a 16-byte array, and use memcmp to compare */ +struct guid_s { + uint32_t v1; + uint16_t v2; + uint16_t v3; + uint8_t v4[8]; +}; +typedef struct guid_s guid_t; + +struct asf_object_s { + guid_t guid; + uint64_t size; + uint64_t datalen; +}; +typedef struct asf_object_s asf_object_t; + +static const guid_t asf_guid_null = +{0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +/* top level object guids */ + +static const guid_t asf_guid_header = +{0x75B22630, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}}; + +static const guid_t asf_guid_data = +{0x75B22636, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}}; + +static const guid_t asf_guid_index = +{0x33000890, 0xE5B1, 0x11CF, {0x89, 0xF4, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xCB}}; + +/* header level object guids */ + +static const guid_t asf_guid_file_properties = +{0x8cabdca1, 0xa947, 0x11cf, {0x8E, 0xe4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}}; + +static const guid_t asf_guid_stream_properties = +{0xB7DC0791, 0xA9B7, 0x11CF, {0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}}; + +static const guid_t asf_guid_content_description = +{0x75B22633, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}}; + +static const guid_t asf_guid_extended_content_description = +{0xD2D0A440, 0xE307, 0x11D2, {0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50}}; + +static const guid_t asf_guid_content_encryption = +{0x2211b3fb, 0xbd23, 0x11d2, {0xb4, 0xb7, 0x00, 0xa0, 0xc9, 0x55, 0xfc, 0x6e}}; + +static const guid_t asf_guid_extended_content_encryption = +{0x298ae614, 0x2622, 0x4c17, {0xb9, 0x35, 0xda, 0xe0, 0x7e, 0xe9, 0x28, 0x9c}}; + +/* stream type guids */ + +static const guid_t asf_guid_stream_type_audio = +{0xF8699E40, 0x5B4D, 0x11CF, {0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B}}; + +static int asf_guid_match(const guid_t *guid1, const guid_t *guid2) +{ + if((guid1->v1 != guid2->v1) || + (guid1->v2 != guid2->v2) || + (guid1->v3 != guid2->v3) || + (memcmp(guid1->v4, guid2->v4, 8))) { + return 0; + } + + return 1; +} + +/* Read the 16 byte GUID from a file */ +static void asf_readGUID(int fd, guid_t* guid) +{ + read_uint32le(fd, &guid->v1); + read_uint16le(fd, &guid->v2); + read_uint16le(fd, &guid->v3); + read(fd, guid->v4, 8); +} + +static void asf_read_object_header(asf_object_t *obj, int fd) +{ + asf_readGUID(fd, &obj->guid); + read_uint64le(fd, &obj->size); + obj->datalen = 0; +} + +/* Parse an integer from the extended content object - we always + convert to an int, regardless of native format. +*/ +static int asf_intdecode(int fd, int type, int length) +{ + uint16_t tmp16; + uint32_t tmp32; + uint64_t tmp64; + + if (type == 3) { + read_uint32le(fd, &tmp32); + lseek(fd,length - 4,SEEK_CUR); + return (int)tmp32; + } else if (type == 4) { + read_uint64le(fd, &tmp64); + lseek(fd,length - 8,SEEK_CUR); + return (int)tmp64; + } else if (type == 5) { + read_uint16le(fd, &tmp16); + lseek(fd,length - 2,SEEK_CUR); + return (int)tmp16; + } + + return 0; +} + +/* Decode a LE utf16 string from a disk buffer into a fixed-sized + utf8 buffer. +*/ + +static void asf_utf16LEdecode(int fd, + uint16_t utf16bytes, + unsigned char **utf8, + int* utf8bytes + ) +{ + unsigned long ucs; + int n; + unsigned char utf16buf[256]; + unsigned char* utf16 = utf16buf; + unsigned char* newutf8; + + n = read(fd, utf16buf, MIN(sizeof(utf16buf), utf16bytes)); + utf16bytes -= n; + + while (n > 0) { + /* Check for a surrogate pair */ + if (utf16[1] >= 0xD8 && utf16[1] < 0xE0) { + if (n < 4) { + /* Run out of utf16 bytes, read some more */ + utf16buf[0] = utf16[0]; + utf16buf[1] = utf16[1]; + + n = read(fd, utf16buf + 2, MIN(sizeof(utf16buf)-2, utf16bytes)); + utf16 = utf16buf; + utf16bytes -= n; + n += 2; + } + + if (n < 4) { + /* Truncated utf16 string, abort */ + break; + } + ucs = 0x10000 + ((utf16[0] << 10) | ((utf16[1] - 0xD8) << 18) + | utf16[2] | ((utf16[3] - 0xDC) << 8)); + utf16 += 4; + n -= 4; + } else { + ucs = (utf16[0] | (utf16[1] << 8)); + utf16 += 2; + n -= 2; + } + + if (*utf8bytes > 6) { + newutf8 = utf8encode(ucs, *utf8); + *utf8bytes -= (newutf8 - *utf8); + *utf8 += (newutf8 - *utf8); + } + + /* We have run out of utf16 bytes, read more if available */ + if ((n == 0) && (utf16bytes > 0)) { + n = read(fd, utf16buf, MIN(sizeof(utf16buf), utf16bytes)); + utf16 = utf16buf; + utf16bytes -= n; + } + } + + *utf8[0] = 0; + --*utf8bytes; + + if (utf16bytes > 0) { + /* Skip any remaining bytes */ + lseek(fd, utf16bytes, SEEK_CUR); + } + return; +} + +static int asf_parse_header(int fd, struct mp3entry* id3, + asf_waveformatex_t* wfx) +{ + asf_object_t current; + asf_object_t header; + uint64_t datalen; + int i; + int fileprop = 0; + uint64_t play_duration; + uint16_t flags; + uint32_t subobjects; + uint8_t utf8buf[512]; + int id3buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf); + unsigned char* id3buf = (unsigned char*)id3->id3v2buf; + + asf_read_object_header((asf_object_t *) &header, fd); + + //DEBUGF("header.size=%d\n",(int)header.size); + if (header.size < 30) { + /* invalid size for header object */ + return ASF_ERROR_OBJECT_SIZE; + } + + read_uint32le(fd, &subobjects); + + /* Two reserved bytes - do we need to read them? */ + lseek(fd, 2, SEEK_CUR); + + //DEBUGF("Read header - size=%d, subobjects=%d\n",(int)header.size, (int)subobjects); + + if (subobjects > 0) { + header.datalen = header.size - 30; + + /* TODO: Check that we have datalen bytes left in the file */ + datalen = header.datalen; + + for (i=0; i<(int)subobjects; i++) { + //DEBUGF("Parsing header object %d - datalen=%d\n",i,(int)datalen); + if (datalen < 24) { + //DEBUGF("not enough data for reading object\n"); + break; + } + + asf_read_object_header(¤t, fd); + + if (current.size > datalen || current.size < 24) { + //DEBUGF("invalid object size - current.size=%d, datalen=%d\n",(int)current.size,(int)datalen); + break; + } + + if (asf_guid_match(¤t.guid, &asf_guid_file_properties)) { + if (current.size < 104) + return ASF_ERROR_OBJECT_SIZE; + + if (fileprop) { + /* multiple file properties objects not allowed */ + return ASF_ERROR_INVALID_OBJECT; + } + + fileprop = 1; + + /* Get the number of logical packets - uint16_t at offset 31 + * (Big endian byte order) */ + lseek(fd, 31, SEEK_CUR); + read_uint16be(fd, &wfx->numpackets); + + /* Now get the play duration - uint64_t at offset 40 */ + lseek(fd, 7, SEEK_CUR); + read_uint64le(fd, &play_duration); + id3->length = play_duration / 10000; + + //DEBUGF("****** length = %lums\n", id3->length); + + /* Read the packet size - uint32_t at offset 68 */ + lseek(fd, 20, SEEK_CUR); + read_uint32le(fd, &wfx->packet_size); + + /* Skip bytes remaining in object */ + lseek(fd, current.size - 24 - 72, SEEK_CUR); + } else if (asf_guid_match(¤t.guid, &asf_guid_stream_properties)) { + guid_t guid; + uint32_t propdatalen; + + if (current.size < 78) + return ASF_ERROR_OBJECT_SIZE; + +#if 0 + asf_byteio_getGUID(&guid, current->data); + datalen = asf_byteio_getDWLE(current->data + 40); + flags = asf_byteio_getWLE(current->data + 48); +#endif + + asf_readGUID(fd, &guid); + + lseek(fd, 24, SEEK_CUR); + read_uint32le(fd, &propdatalen); + lseek(fd, 4, SEEK_CUR); + read_uint16le(fd, &flags); + + if (!asf_guid_match(&guid, &asf_guid_stream_type_audio)) { + //DEBUGF("Found stream properties for non audio stream, skipping\n"); + lseek(fd,current.size - 24 - 50,SEEK_CUR); + } else if (wfx->audiostream == -1) { + lseek(fd, 4, SEEK_CUR); + //DEBUGF("Found stream properties for audio stream %d\n",flags&0x7f); + + if (propdatalen < 18) { + return ASF_ERROR_INVALID_LENGTH; + } + +#if 0 + if (asf_byteio_getWLE(data + 16) > datalen - 16) { + return ASF_ERROR_INVALID_LENGTH; + } +#endif + read_uint16le(fd, &wfx->codec_id); + read_uint16le(fd, &wfx->channels); + read_uint32le(fd, &wfx->rate); + read_uint32le(fd, &wfx->bitrate); + wfx->bitrate *= 8; + read_uint16le(fd, &wfx->blockalign); + read_uint16le(fd, &wfx->bitspersample); + read_uint16le(fd, &wfx->datalen); + + /* Round bitrate to the nearest kbit */ + id3->bitrate = (wfx->bitrate + 500) / 1000; + id3->frequency = wfx->rate; + + if (wfx->codec_id == ASF_CODEC_ID_WMAV1) { + read(fd, wfx->data, 4); + lseek(fd,current.size - 24 - 72 - 4,SEEK_CUR); + wfx->audiostream = flags&0x7f; + } else if (wfx->codec_id == ASF_CODEC_ID_WMAV2) { + read(fd, wfx->data, 6); + lseek(fd,current.size - 24 - 72 - 6,SEEK_CUR); + wfx->audiostream = flags&0x7f; + } else if (wfx->codec_id == ASF_CODEC_ID_WMAPRO) { + /* wma pro decoder needs the extra-data */ + read(fd, wfx->data, wfx->datalen); + lseek(fd,current.size - 24 - 72 - wfx->datalen,SEEK_CUR); + wfx->audiostream = flags&0x7f; + /* Correct codectype to redirect playback to the proper .codec */ + id3->codectype = AFMT_WMAPRO; + } else if (wfx->codec_id == ASF_CODEC_ID_WMAVOICE) { + read(fd, wfx->data, wfx->datalen); + lseek(fd,current.size - 24 - 72 - wfx->datalen,SEEK_CUR); + wfx->audiostream = flags&0x7f; + id3->codectype = AFMT_WMAVOICE; + } else { + DEBUGF("Unsupported WMA codec (Lossless, Voice, etc)\n"); + lseek(fd,current.size - 24 - 72,SEEK_CUR); + } + + } + } else if (asf_guid_match(¤t.guid, &asf_guid_content_description)) { + /* Object contains five 16-bit string lengths, followed by the five strings: + title, artist, copyright, description, rating + */ + uint16_t strlength[5]; + int i; + + //DEBUGF("Found GUID_CONTENT_DESCRIPTION - size=%d\n",(int)(current.size - 24)); + + /* Read the 5 string lengths - number of bytes included trailing zero */ + for (i=0; i<5; i++) { + read_uint16le(fd, &strlength[i]); + //DEBUGF("strlength = %u\n",strlength[i]); + } + + if (strlength[0] > 0) { /* 0 - Title */ + id3->title = id3buf; + asf_utf16LEdecode(fd, strlength[0], &id3buf, &id3buf_remaining); + } + + if (strlength[1] > 0) { /* 1 - Artist */ + id3->artist = id3buf; + asf_utf16LEdecode(fd, strlength[1], &id3buf, &id3buf_remaining); + } + + lseek(fd, strlength[2], SEEK_CUR); /* 2 - copyright */ + + if (strlength[3] > 0) { /* 3 - description */ + id3->comment = id3buf; + asf_utf16LEdecode(fd, strlength[3], &id3buf, &id3buf_remaining); + } + + lseek(fd, strlength[4], SEEK_CUR); /* 4 - rating */ + } else if (asf_guid_match(¤t.guid, &asf_guid_extended_content_description)) { + uint16_t count; + int i; + int bytesleft = current.size - 24; + //DEBUGF("Found GUID_EXTENDED_CONTENT_DESCRIPTION\n"); + + read_uint16le(fd, &count); + bytesleft -= 2; + //DEBUGF("extended metadata count = %u\n",count); + + for (i=0; i < count; i++) { + uint16_t length, type; + unsigned char* utf8 = utf8buf; + int utf8length = 512; + + read_uint16le(fd, &length); + asf_utf16LEdecode(fd, length, &utf8, &utf8length); + bytesleft -= 2 + length; + + read_uint16le(fd, &type); + read_uint16le(fd, &length); + + if (!strcmp("WM/TrackNumber",utf8buf)) { + if (type == 0) { + id3->track_string = id3buf; + asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining); + id3->tracknum = atoi(id3->track_string); + } else if ((type >=2) && (type <= 5)) { + id3->tracknum = asf_intdecode(fd, type, length); + } else { + lseek(fd, length, SEEK_CUR); + } + } else if ((!strcmp("WM/Genre", utf8buf)) && (type == 0)) { + id3->genre_string = id3buf; + asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining); + } else if ((!strcmp("WM/AlbumTitle", utf8buf)) && (type == 0)) { + id3->album = id3buf; + asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining); + } else if ((!strcmp("WM/AlbumArtist", utf8buf)) && (type == 0)) { + id3->albumartist = id3buf; + asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining); + } else if ((!strcmp("WM/Composer", utf8buf)) && (type == 0)) { + id3->composer = id3buf; + asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining); + } else if (!strcmp("WM/Year", utf8buf)) { + if (type == 0) { + id3->year_string = id3buf; + asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining); + id3->year = atoi(id3->year_string); + } else if ((type >=2) && (type <= 5)) { + id3->year = asf_intdecode(fd, type, length); + } else { + lseek(fd, length, SEEK_CUR); + } + } else if (!strncmp("replaygain_", utf8buf, 11)) { + char *value = id3buf; + asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining); + parse_replaygain(utf8buf, value, id3); + } else if (!strcmp("MusicBrainz/Track Id", utf8buf)) { + id3->mb_track_id = id3buf; + asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining); +#ifdef HAVE_ALBUMART + } else if (!strcmp("WM/Picture", utf8buf)) { + uint32_t datalength, strlength; + /* Expected is either "01 00 xx xx 03 yy yy yy yy" or + * "03 yy yy yy yy". xx is the size of the WM/Picture + * container in bytes. yy equals the raw data length of + * the embedded image. */ + lseek(fd, -4, SEEK_CUR); + read(fd, &type, 1); + if (type == 1) { + lseek(fd, 3, SEEK_CUR); + read(fd, &type, 1); + /* In case the parsing will fail in the next step we + * might at least be able to skip the whole section. */ + datalength = length - 1; + } + if (type == 3) { + /* Read the raw data length of the embedded image. */ + read_uint32le(fd, &datalength); + + /* Reset utf8 buffer */ + utf8 = utf8buf; + utf8length = 512; + + /* Gather the album art format, this string has a + * double zero-termination. */ + asf_utf16LEdecode(fd, 32, &utf8, &utf8length); + strlength = (strlen(utf8buf) + 2) * 2; + lseek(fd, strlength-32, SEEK_CUR); + if (!strcmp("image/jpeg", utf8buf)) { + id3->albumart.type = AA_TYPE_JPG; + } else if (!strcmp("image/png", utf8buf)) { + id3->albumart.type = AA_TYPE_PNG; + } else { + id3->albumart.type = AA_TYPE_UNKNOWN; + } + + /* Set the album art size and position. */ + if (id3->albumart.type != AA_TYPE_UNKNOWN) { + id3->albumart.pos = lseek(fd, 0, SEEK_CUR); + id3->albumart.size = datalength; + id3->has_embedded_albumart = true; + } + } + + lseek(fd, datalength, SEEK_CUR); +#endif + } else { + lseek(fd, length, SEEK_CUR); + } + bytesleft -= 4 + length; + } + + lseek(fd, bytesleft, SEEK_CUR); + } else if (asf_guid_match(¤t.guid, &asf_guid_content_encryption) + || asf_guid_match(¤t.guid, &asf_guid_extended_content_encryption)) { + //DEBUGF("File is encrypted\n"); + return ASF_ERROR_ENCRYPTED; + } else { + //DEBUGF("Skipping %d bytes of object\n",(int)(current.size - 24)); + lseek(fd,current.size - 24,SEEK_CUR); + } + + //DEBUGF("Parsed object - size = %d\n",(int)current.size); + datalen -= current.size; + } + + if (i != (int)subobjects || datalen != 0) { + //DEBUGF("header data doesn't match given subobject count\n"); + return ASF_ERROR_INVALID_VALUE; + } + + //DEBUGF("%d subobjects read successfully\n", i); + } + +#if 0 + tmp = asf_parse_header_validate(file, &header); + if (tmp < 0) { + /* header read ok but doesn't validate correctly */ + return tmp; + } +#endif + + //DEBUGF("header validated correctly\n"); + + return 0; +} + +bool get_asf_metadata(int fd, struct mp3entry* id3) +{ + int res; + asf_object_t obj; + asf_waveformatex_t wfx; + + wfx.audiostream = -1; + + res = asf_parse_header(fd, id3, &wfx); + + if (res < 0) { + DEBUGF("ASF: parsing error - %d\n",res); + return false; + } + + if (wfx.audiostream == -1) { + DEBUGF("ASF: No WMA streams found\n"); + return false; + } + + asf_read_object_header(&obj, fd); + + if (!asf_guid_match(&obj.guid, &asf_guid_data)) { + DEBUGF("ASF: No data object found\n"); + return false; + } + + /* Store the current file position - no need to parse the header + again in the codec. The +26 skips the rest of the data object + header. + */ + id3->first_frame_offset = lseek(fd, 0, SEEK_CUR) + 26; + id3->filesize = filesize(fd); + /* We copy the wfx struct to the MP3 TOC field in the id3 struct so + the codec doesn't need to parse the header object again */ + memcpy(id3->toc, &wfx, sizeof(wfx)); + + return true; +} diff --git a/lib/rbcodec/metadata/au.c b/lib/rbcodec/metadata/au.c new file mode 100644 index 0000000000..94e7453644 --- /dev/null +++ b/lib/rbcodec/metadata/au.c @@ -0,0 +1,105 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2010 Yoshihisa Uchida + * + * 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. + * + ****************************************************************************/ +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "logf.h" + +static const unsigned char bitspersamples[9] = { + 0, /* encoding */ + 8, /* 1: G.711 MULAW */ + 8, /* 2: Linear PCM 8bit */ + 16, /* 3: Linear PCM 16bit */ + 24, /* 4: Linear PCM 24bit */ + 32, /* 5: Linear PCM 32bit */ + 32, /* 6: IEEE float 32bit */ + 64, /* 7: IEEE float 64bit */ + /* encoding 8 - 26 unsupported. */ + 8, /* 27: G.711 ALAW */ +}; + +static inline unsigned char get_au_bitspersample(unsigned int encoding) +{ + if (encoding < 8) + return bitspersamples[encoding]; + else if (encoding == 27) + return bitspersamples[8]; + + return 0; +} + +bool get_au_metadata(int fd, struct mp3entry* id3) +{ + /* temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + unsigned long numbytes = 0; + int offset; + + id3->vbr = false; /* All Sun audio files are CBR */ + id3->filesize = filesize(fd); + id3->length = 0; + + lseek(fd, 0, SEEK_SET); + if ((read(fd, buf, 24) < 24) || (memcmp(buf, ".snd", 4) != 0)) + { + /* + * no header + * + * frequency: 8000 Hz + * bits per sample: 8 bit + * channel: mono + */ + numbytes = id3->filesize; + id3->frequency = 8000; + id3->bitrate = 8; + } + else + { + /* parse header */ + + /* data offset */ + offset = get_long_be(buf + 4); + if (offset < 24) + { + DEBUGF("CODEC_ERROR: sun audio offset size is small: %d\n", offset); + return false; + } + /* data size */ + numbytes = get_long_be(buf + 8); + if (numbytes == (uint32_t)0xffffffff) + numbytes = id3->filesize - offset; + + id3->frequency = get_long_be(buf + 16); + id3->bitrate = get_au_bitspersample(get_long_be(buf + 12)) * get_long_be(buf + 20) + * id3->frequency / 1000; + } + + /* Calculate track length [ms] */ + if (id3->bitrate) + id3->length = (numbytes << 3) / id3->bitrate; + + return true; +} diff --git a/lib/rbcodec/metadata/ay.c b/lib/rbcodec/metadata/ay.c new file mode 100644 index 0000000000..5d00264b3d --- /dev/null +++ b/lib/rbcodec/metadata/ay.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +/* Taken from blargg's Game_Music_Emu library */ + +typedef unsigned char byte; + +/* AY file header */ +enum { header_size = 0x14 }; +struct header_t +{ + byte tag[8]; + byte vers; + byte player; + byte unused[2]; + byte author[2]; + byte comment[2]; + byte max_track; + byte first_track; + byte track_info[2]; +}; + +struct file_t { + struct header_t const* header; + byte const* tracks; + byte const* end; /* end of file data */ +}; + +static int get_be16( const void *a ) +{ + return get_short_be( (void*) a ); +} + +/* Given pointer to 2-byte offset of data, returns pointer to data, or NULL if + * offset is 0 or there is less than min_size bytes of data available. */ +static byte const* get_data( struct file_t const* file, byte const ptr [], int min_size ) +{ + int offset = (int16_t) get_be16( ptr ); + int pos = ptr - (byte const*) file->header; + int size = file->end - (byte const*) file->header; + int limit = size - min_size; + if ( limit < 0 || !offset || (unsigned) (pos + offset) > (unsigned) limit ) + return NULL; + return ptr + offset; +} + +static const char *parse_header( byte const in [], int size, struct file_t* out ) +{ + if ( size < header_size ) + return "wrong file type"; + + out->header = (struct header_t const*) in; + out->end = in + size; + struct header_t const* h = (struct header_t const*) in; + if ( memcmp( h->tag, "ZXAYEMUL", 8 ) ) + return "wrong file type"; + + out->tracks = get_data( out, h->track_info, (h->max_track + 1) * 4 ); + if ( !out->tracks ) + return "missing track data"; + + return 0; +} + +static void copy_ay_fields( struct file_t const* file, struct mp3entry* id3, int track ) +{ + int track_count = file->header->max_track + 1; + + /* calculate track length based on number of subtracks */ + if (track_count > 1) { + id3->length = file->header->max_track * 1000; + } else { + byte const* track_info = get_data( file, file->tracks + track * 4 + 2, 6 ); + if (track_info) + id3->length = get_be16( track_info + 4 ) * (1000 / 50); /* frames to msec */ + else id3->length = 120 * 1000; + } + + if ( id3->length <= 0 ) + id3->length = 120 * 1000; /* 2 minutes */ + + /* If meta info was found in the m3u skip next step */ + if (id3->title && id3->title[0]) return; + + /* If file has more than one track will + use file name as title */ + char * tmp; + if (track_count <= 1) { + tmp = (char *) get_data( file, file->tracks + track * 4, 1 ); + if ( tmp ) id3->title = tmp; + } + + /* Author */ + tmp = (char *) get_data( file, file->header->author, 1 ); + if (tmp) id3->artist = tmp; + + /* Comment */ + tmp = (char *) get_data( file, file->header->comment, 1 ); + if (tmp) id3->comment = tmp; +} + +static bool parse_ay_header(int fd, struct mp3entry *id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->id3v2buf; + struct file_t file; + int read_bytes; + + lseek(fd, 0, SEEK_SET); + if ((read_bytes = read(fd, buf, ID3V2_BUF_SIZE)) < header_size) + return false; + + buf [ID3V2_BUF_SIZE] = '\0'; + if ( parse_header( buf, read_bytes, &file ) ) + return false; + + copy_ay_fields( &file, id3, 0 ); + return true; +} + +bool get_ay_metadata(int fd, struct mp3entry* id3) +{ + char ay_type[8]; + if ((lseek(fd, 0, SEEK_SET) < 0) || + read(fd, ay_type, 8) < 8) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + + id3->bitrate = 706; + id3->frequency = 44100; + + /* Make sure this is a ZX Ay file */ + if (memcmp( ay_type, "ZXAYEMUL", 8 ) != 0) + return false; + + return parse_ay_header(fd, id3); +} diff --git a/lib/rbcodec/metadata/flac.c b/lib/rbcodec/metadata/flac.c new file mode 100644 index 0000000000..29937173fd --- /dev/null +++ b/lib/rbcodec/metadata/flac.c @@ -0,0 +1,127 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "logf.h" + +bool get_flac_metadata(int fd, struct mp3entry* id3) +{ + /* A simple parser to read vital metadata from a FLAC file - length, + * frequency, bitrate etc. This code should either be moved to a + * seperate file, or discarded in favour of the libFLAC code. + * The FLAC stream specification can be found at + * http://flac.sourceforge.net/format.html#stream + */ + + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + bool last_metadata = false; + bool rc = false; + + if (!skip_id3v2(fd, id3) || (read(fd, buf, 4) < 4)) + { + return rc; + } + + if (memcmp(buf, "fLaC", 4) != 0) + { + return rc; + } + + while (!last_metadata) + { + unsigned long i; + int type; + + if (read(fd, buf, 4) < 0) + { + return rc; + } + + last_metadata = buf[0] & 0x80; + type = buf[0] & 0x7f; + /* The length of the block */ + i = (buf[1] << 16) | (buf[2] << 8) | buf[3]; + + if (type == 0) /* 0 is the STREAMINFO block */ + { + unsigned long totalsamples; + + if (i >= sizeof(id3->path) || read(fd, buf, i) < 0) + { + return rc; + } + + id3->vbr = true; /* All FLAC files are VBR */ + id3->filesize = filesize(fd); + id3->frequency = (buf[10] << 12) | (buf[11] << 4) + | ((buf[12] & 0xf0) >> 4); + rc = true; /* Got vital metadata */ + + /* totalsamples is a 36-bit field, but we assume <= 32 bits are used */ + totalsamples = get_long_be(&buf[14]); + + if(totalsamples > 0) + { + /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ + id3->length = ((int64_t) totalsamples * 1000) / id3->frequency; + id3->bitrate = (id3->filesize * 8) / id3->length; + } + else if (totalsamples == 0) + { + id3->length = 0; + id3->bitrate = 0; + } + else + { + logf("flac length invalid!"); + return false; + } + + } + else if (type == 4) /* 4 is the VORBIS_COMMENT block */ + { + /* The next i bytes of the file contain the VORBIS COMMENTS. */ + if (read_vorbis_tags(fd, id3, i) == 0) + { + return rc; + } + } + else if (!last_metadata) + { + /* Skip to next metadata block */ + if (lseek(fd, i, SEEK_CUR) < 0) + { + return rc; + } + } + } + + return true; +} diff --git a/lib/rbcodec/metadata/gbs.c b/lib/rbcodec/metadata/gbs.c new file mode 100644 index 0000000000..68f2b2a393 --- /dev/null +++ b/lib/rbcodec/metadata/gbs.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +static bool parse_gbs_header(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + lseek(fd, 0, SEEK_SET); + if (read(fd, buf, 112) < 112) + return false; + + /* Calculate track length with number of subtracks */ + id3->length = buf[4] * 1000; + + /* If meta info was found in the m3u skip next step */ + if (id3->title && id3->title[0]) return true; + + char *p = id3->id3v2buf; + + /* Some metadata entries have 32 bytes length */ + /* Game */ + memcpy(p, &buf[16], 32); *(p + 33) = '\0'; + id3->title = p; + p += strlen(p)+1; + + /* Artist */ + memcpy(p, &buf[48], 32); *(p + 33) = '\0'; + id3->artist = p; + p += strlen(p)+1; + + /* Copyright */ + memcpy(p, &buf[80], 32); *(p + 33) = '\0'; + id3->album = p; + + return true; +} + +bool get_gbs_metadata(int fd, struct mp3entry* id3) +{ + char gbs_type[3]; + if ((lseek(fd, 0, SEEK_SET) < 0) || + (read(fd, gbs_type, 3) < 3)) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + /* we only render 16 bits, 44.1KHz, Stereo */ + id3->bitrate = 706; + id3->frequency = 44100; + + /* Check for GBS magic */ + if (memcmp( gbs_type, "GBS", 3 ) != 0) + return false; + + return parse_gbs_header(fd, id3); +} diff --git a/lib/rbcodec/metadata/hes.c b/lib/rbcodec/metadata/hes.c new file mode 100644 index 0000000000..6d99d523cb --- /dev/null +++ b/lib/rbcodec/metadata/hes.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" +#include "plugin.h" + +bool get_hes_metadata(int fd, struct mp3entry* id3) +{ + /* Use the id3v2 buffer part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->id3v2buf; + int read_bytes; + + if ((lseek(fd, 0, SEEK_SET) < 0) + || ((read_bytes = read(fd, buf, 4)) < 4)) + return false; + + /* Verify this is a HES file */ + if (memcmp(buf,"HESM",4) != 0) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + /* we only render 16 bits, 44.1KHz, Stereo */ + id3->bitrate = 706; + id3->frequency = 44100; + + /* Set default track count (length)*/ + id3->length = 255 * 1000; + + return true; +} + diff --git a/lib/rbcodec/metadata/id3tags.c b/lib/rbcodec/metadata/id3tags.c new file mode 100644 index 0000000000..2dd1c662ed --- /dev/null +++ b/lib/rbcodec/metadata/id3tags.c @@ -0,0 +1,1199 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Daniel Stenberg + * + * 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. + * + ****************************************************************************/ +/* + * Parts of this code has been stolen from the Ample project and was written + * by David H�deman. It has since been extended and enhanced pretty much by + * all sorts of friendly Rockbox people. + * + */ + + /* tagResolver and associated code copyright 2003 Thomas Paul Diffenbach + */ + +#include +#include +#include +#include +#include +#include +#include +#include "string-extra.h" +#include "config.h" +#include "file.h" +#include "logf.h" +#include "system.h" +#include "replaygain.h" +#include "rbunicode.h" + +#include "metadata.h" +#include "mp3data.h" +#if CONFIG_CODEC == SWCODEC +#include "metadata_common.h" +#endif +#include "metadata_parsers.h" +#include "misc.h" + +static unsigned long unsync(unsigned long b0, + unsigned long b1, + unsigned long b2, + unsigned long b3) +{ + return (((long)(b0 & 0x7F) << (3*7)) | + ((long)(b1 & 0x7F) << (2*7)) | + ((long)(b2 & 0x7F) << (1*7)) | + ((long)(b3 & 0x7F) << (0*7))); +} + +static const char* const genres[] = { + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", + "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", + "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", + "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", + "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", + "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", + "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", + "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", + "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", + "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", + "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", + "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", + "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", + + /* winamp extensions */ + "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", + "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", + "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", + "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", + "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", + "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", + "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", + "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall", + "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", + "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap", + "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", + "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "Jpop", + "Synthpop" +}; + +#if CONFIG_CODEC != SWCODEC +static +#endif +char* id3_get_num_genre(unsigned int genre_num) +{ + if (genre_num < ARRAYLEN(genres)) + return (char*)genres[genre_num]; + return NULL; +} + +/* + HOW TO ADD ADDITIONAL ID3 VERSION 2 TAGS + Code and comments by Thomas Paul Diffenbach + + To add another ID3v2 Tag, do the following: + 1. add a char* named for the tag to struct mp3entry in id3.h, + (I (tpd) prefer to use char* rather than ints, even for what seems like + numerical values, for cases where a number won't do, e.g., + YEAR: "circa 1765", "1790/1977" (composed/performed), "28 Feb 1969" + TRACK: "1/12", "1 of 12", GENRE: "Freeform genre name" + Text is more flexible, and as the main use of id3 data is to + display it, converting it to an int just means reconverting to + display it, at a runtime cost.) + + 2. If any special processing beyond copying the tag value from the Id3 + block to the struct mp3entry is rrequired (such as converting to an + int), write a function to perform this special processing. + + This function's prototype must match that of + typedef tagPostProcessFunc, that is it must be: + int func( struct mp3entry*, char* tag, int bufferpos ) + the first argument is a pointer to the current mp3entry structure the + second argument is a pointer to the null terminated string value of the + tag found the third argument is the offset of the next free byte in the + mp3entry's buffer your function should return the corrected offset; if + you don't lengthen or shorten the tag string, you can return the third + argument unchanged. + + Unless you have a good reason no to, make the function static. + TO JUST COPY THE TAG NO SPECIAL PROCESSING FUNCTION IS NEEDED. + + 3. add one or more entries to the tagList array, using the format: + char* ID3 Tag symbolic name -- see the ID3 specification for these, + sizeof() that name minus 1, + offsetof( struct mp3entry, variable_name_in_struct_mp3entry ), + pointer to your special processing function or NULL + if you need no special processing + flag indicating if this tag is binary or textual + Many ID3 symbolic names come in more than one form. You can add both + forms, each referencing the same variable in struct mp3entry. + If both forms are present, the last found will be used. + Note that the offset can be zero, in which case no entry will be set + in the mp3entry struct; the frame is still read into the buffer and + the special processing function is called (several times, if there + are several frames with the same name). + + 4. Alternately, use the TAG_LIST_ENTRY macro with + ID3 tag symbolic name, + variable in struct mp3entry, + special processing function address + + 5. Add code to wps-display.c function get_tag to assign a printf-like + format specifier for the tag */ + +/* Structure for ID3 Tag extraction information */ +struct tag_resolver { + const char* tag; + int tag_length; + size_t offset; + int (*ppFunc)(struct mp3entry*, char* tag, int bufferpos); + bool binary; +}; + +static bool global_ff_found; + +static int unsynchronize(char* tag, int len, bool *ff_found) +{ + int i; + unsigned char c; + unsigned char *rp, *wp; + + wp = rp = (unsigned char *)tag; + + rp = (unsigned char *)tag; + for(i = 0;i < len;i++) { + /* Read the next byte and write it back, but don't increment the + write pointer */ + c = *rp++; + *wp = c; + if(*ff_found) { + /* Increment the write pointer if it isn't an unsynch pattern */ + if(c != 0) + wp++; + *ff_found = false; + } else { + if(c == 0xff) + *ff_found = true; + wp++; + } + } + return (long)wp - (long)tag; +} + +static int unsynchronize_frame(char* tag, int len) +{ + bool ff_found = false; + + return unsynchronize(tag, len, &ff_found); +} + +static int read_unsynched(int fd, void *buf, int len) +{ + int i; + int rc; + int remaining = len; + char *wp; + char *rp; + + wp = buf; + + while(remaining) { + rp = wp; + rc = read(fd, rp, remaining); + if(rc <= 0) + return rc; + + i = unsynchronize(wp, remaining, &global_ff_found); + remaining -= i; + wp += i; + } + + return len; +} + +static int skip_unsynched(int fd, int len) +{ + int rc; + int remaining = len; + int rlen; + char buf[32]; + + while(remaining) { + rlen = MIN(sizeof(buf), (unsigned int)remaining); + rc = read(fd, buf, rlen); + if(rc <= 0) + return rc; + + remaining -= unsynchronize(buf, rlen, &global_ff_found); + } + + return len; +} + +/* parse numeric value from string */ +static int parsetracknum( struct mp3entry* entry, char* tag, int bufferpos ) +{ + entry->tracknum = atoi( tag ); + return bufferpos; +} + +/* parse numeric value from string */ +static int parsediscnum( struct mp3entry* entry, char* tag, int bufferpos ) +{ + entry->discnum = atoi( tag ); + return bufferpos; +} + +/* parse numeric value from string */ +static int parseyearnum( struct mp3entry* entry, char* tag, int bufferpos ) +{ + entry->year = atoi( tag ); + return bufferpos; +} + +/* parse numeric genre from string, version 2.2 and 2.3 */ +static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos ) +{ + /* Use bufferpos to hold current position in entry->id3v2buf. */ + bufferpos = tag - entry->id3v2buf; + + if(entry->id3version >= ID3_VER_2_4) { + /* In version 2.4 and up, there are no parentheses, and the genre frame + is a list of strings, either numbers or text. */ + + /* Is it a number? */ + if(isdigit(tag[0])) { + entry->genre_string = id3_get_num_genre(atoi( tag )); + return bufferpos; + } else { + entry->genre_string = tag; + return bufferpos + strlen(tag) + 1; + } + } else { + if( tag[0] == '(' && tag[1] != '(' ) { + entry->genre_string = id3_get_num_genre(atoi( tag + 1 )); + return bufferpos; + } + else { + entry->genre_string = tag; + return bufferpos + strlen(tag) + 1; + } + } +} + +#ifdef HAVE_ALBUMART +/* parse embed albumart */ +static int parsealbumart( struct mp3entry* entry, char* tag, int bufferpos ) +{ + entry->has_embedded_albumart = false; + + /* we currently don't support unsynchronizing albumart */ + if (entry->albumart.type == AA_TYPE_UNSYNC) + return bufferpos; + + entry->albumart.type = AA_TYPE_UNKNOWN; + + char *start = tag; + /* skip text encoding */ + tag += 1; + + if (memcmp(tag, "image/", 6) == 0) + { + /* ID3 v2.3+ */ + tag += 6; + if (strcmp(tag, "jpeg") == 0) + { + entry->albumart.type = AA_TYPE_JPG; + tag += 5; + } + else if (strcmp(tag, "png") == 0) + { + entry->albumart.type = AA_TYPE_PNG; + tag += 4; + } + } + else + { + /* ID3 v2.2 */ + if (memcmp(tag, "JPG", 3) == 0) + entry->albumart.type = AA_TYPE_JPG; + else if (memcmp(tag, "PNG", 3) == 0) + entry->albumart.type = AA_TYPE_PNG; + tag += 3; + } + + if (entry->albumart.type != AA_TYPE_UNKNOWN) + { + /* skip picture type */ + tag += 1; + /* skip description */ + tag = strchr(tag, '\0') + 1; + /* fixup offset&size for image data */ + entry->albumart.pos += tag - start; + entry->albumart.size -= tag - start; + entry->has_embedded_albumart = true; + } + /* return bufferpos as we didn't store anything in id3v2buf */ + return bufferpos; +} +#endif + +/* parse user defined text, looking for album artist and replaygain + * information. + */ +static int parseuser( struct mp3entry* entry, char* tag, int bufferpos ) +{ + char* value = NULL; + int desc_len = strlen(tag); + int length = 0; + + if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos) { + /* At least part of the value was read, so we can safely try to + * parse it */ + value = tag + desc_len + 1; + + if (!strcasecmp(tag, "ALBUM ARTIST")) { + length = strlen(value) + 1; + strlcpy(tag, value, length); + entry->albumartist = tag; +#if CONFIG_CODEC == SWCODEC + } else { + /* Call parse_replaygain(). */ + parse_replaygain(tag, value, entry); +#endif + } + } + + return tag - entry->id3v2buf + length; +} + +#if CONFIG_CODEC == SWCODEC +/* parse RVA2 binary data and convert to replaygain information. */ +static int parserva2( struct mp3entry* entry, char* tag, int bufferpos) +{ + int desc_len = strlen(tag); + int start_pos = tag - entry->id3v2buf; + int end_pos = start_pos + desc_len + 5; + unsigned char* value = tag + desc_len + 1; + + /* Only parse RVA2 replaygain tags if tag version == 2.4 and channel + * type is master volume. + */ + if (entry->id3version == ID3_VER_2_4 && end_pos < bufferpos + && *value++ == 1) { + long gain = 0; + long peak = 0; + long peakbits; + long peakbytes; + bool album = false; + + /* The RVA2 specification is unclear on some things (id string and + * peak volume), but this matches how Quod Libet use them. + */ + + gain = (int16_t) ((value[0] << 8) | value[1]); + value += 2; + peakbits = *value++; + peakbytes = (peakbits + 7) / 8; + + /* Only use the topmost 24 bits for peak volume */ + if (peakbytes > 3) { + peakbytes = 3; + } + + /* Make sure the peak bits were read */ + if (end_pos + peakbytes < bufferpos) { + long shift = ((8 - (peakbits & 7)) & 7) + (3 - peakbytes) * 8; + + for ( ; peakbytes; peakbytes--) { + peak <<= 8; + peak += *value++; + } + + peak <<= shift; + + if (peakbits > 24) { + peak += *value >> (8 - shift); + } + } + + if (strcasecmp(tag, "album") == 0) { + album = true; + } else if (strcasecmp(tag, "track") != 0) { + /* Only accept non-track values if we don't have any previous + * value. + */ + if (entry->track_gain != 0) { + return start_pos; + } + } + + parse_replaygain_int(album, gain, peak * 2, entry); + } + + return start_pos; +} +#endif + +static int parsembtid( struct mp3entry* entry, char* tag, int bufferpos ) +{ + char* value = NULL; + int desc_len = strlen(tag); + /*DEBUGF("MBID len: %d\n", desc_len);*/ + /* Musicbrainz track IDs are always 36 chars long */ + const size_t mbtid_len = 36; + + if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos) + { + value = tag + desc_len + 1; + + if (strcasecmp(tag, "http://musicbrainz.org") == 0) + { + if (mbtid_len == strlen(value)) + { + entry->mb_track_id = value; + return bufferpos + mbtid_len + 1; + } + } + } + + return bufferpos; +} + +static const struct tag_resolver taglist[] = { + { "TPE1", 4, offsetof(struct mp3entry, artist), NULL, false }, + { "TP1", 3, offsetof(struct mp3entry, artist), NULL, false }, + { "TIT2", 4, offsetof(struct mp3entry, title), NULL, false }, + { "TT2", 3, offsetof(struct mp3entry, title), NULL, false }, + { "TALB", 4, offsetof(struct mp3entry, album), NULL, false }, + { "TAL", 3, offsetof(struct mp3entry, album), NULL, false }, + { "TRK", 3, offsetof(struct mp3entry, track_string), &parsetracknum, false }, + { "TPOS", 4, offsetof(struct mp3entry, disc_string), &parsediscnum, false }, + { "TPA", 3, offsetof(struct mp3entry, disc_string), &parsediscnum, false }, + { "TRCK", 4, offsetof(struct mp3entry, track_string), &parsetracknum, false }, + { "TDRC", 4, offsetof(struct mp3entry, year_string), &parseyearnum, false }, + { "TYER", 4, offsetof(struct mp3entry, year_string), &parseyearnum, false }, + { "TYE", 3, offsetof(struct mp3entry, year_string), &parseyearnum, false }, + { "TCOM", 4, offsetof(struct mp3entry, composer), NULL, false }, + { "TCM", 3, offsetof(struct mp3entry, composer), NULL, false }, + { "TPE2", 4, offsetof(struct mp3entry, albumartist), NULL, false }, + { "TP2", 3, offsetof(struct mp3entry, albumartist), NULL, false }, + { "TIT1", 4, offsetof(struct mp3entry, grouping), NULL, false }, + { "TT1", 3, offsetof(struct mp3entry, grouping), NULL, false }, + { "COMM", 4, offsetof(struct mp3entry, comment), NULL, false }, + { "COM", 3, offsetof(struct mp3entry, comment), NULL, false }, + { "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre, false }, + { "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre, false }, +#ifdef HAVE_ALBUMART + { "APIC", 4, 0, &parsealbumart, true }, + { "PIC", 3, 0, &parsealbumart, true }, +#endif + { "TXXX", 4, 0, &parseuser, false }, +#if CONFIG_CODEC == SWCODEC + { "RVA2", 4, 0, &parserva2, true }, +#endif + { "UFID", 4, 0, &parsembtid, false }, +}; + +#define TAGLIST_SIZE ((int)ARRAYLEN(taglist)) + +/* Get the length of an ID3 string in the given encoding. Returns the length + * in bytes, including end nil, or -1 if the encoding is unknown. + */ +static int unicode_len(char encoding, const void* string) +{ + int len = 0; + + if (encoding == 0x01 || encoding == 0x02) { + char first; + const char *s = string; + /* string might be unaligned, so using short* can crash on ARM and SH1 */ + do { + first = *s++; + } while ((first | *s++) != 0); + + len = s - (const char*) string; + } else { + len = strlen((char*) string) + 1; + } + + return len; +} + +/* Checks to see if the passed in string is a 16-bit wide Unicode v2 + string. If it is, we convert it to a UTF-8 string. If it's not unicode, + we convert from the default codepage */ +static int unicode_munge(char* string, char* utf8buf, int *len) { + long tmp; + bool le = false; + int i = 0; + unsigned char *str = (unsigned char *)string; + int templen = 0; + unsigned char* utf8 = (unsigned char *)utf8buf; + + switch (str[0]) { + case 0x00: /* Type 0x00 is ordinary ISO 8859-1 */ + str++; + (*len)--; + utf8 = iso_decode(str, utf8, -1, *len); + *utf8 = 0; + *len = (unsigned long)utf8 - (unsigned long)utf8buf; + break; + + case 0x01: /* Unicode with or without BOM */ + case 0x02: + (*len)--; + str++; + + /* Handle frames with more than one string + (needed for TXXX frames).*/ + do { + tmp = bytes2int(0, 0, str[0], str[1]); + + /* Now check if there is a BOM + (zero-width non-breaking space, 0xfeff) + and if it is in little or big endian format */ + if(tmp == 0xfffe) { /* Little endian? */ + le = true; + str += 2; + (*len)-=2; + } else if(tmp == 0xfeff) { /* Big endian? */ + str += 2; + (*len)-=2; + } else + /* If there is no BOM (which is a specification violation), + let's try to guess it. If one of the bytes is 0x00, it is + probably the most significant one. */ + if(str[1] == 0) + le = true; + + while ((i < *len) && (str[0] || str[1])) { + if(le) + utf8 = utf16LEdecode(str, utf8, 1); + else + utf8 = utf16BEdecode(str, utf8, 1); + + str+=2; + i += 2; + } + + *utf8++ = 0; /* Terminate the string */ + templen += (strlen(&utf8buf[templen]) + 1); + str += 2; + i+=2; + } while(i < *len); + *len = templen - 1; + break; + + case 0x03: /* UTF-8 encoded string */ + for(i=0; i < *len; i++) + utf8[i] = str[i+1]; + (*len)--; + break; + + default: /* Plain old string */ + utf8 = iso_decode(str, utf8, -1, *len); + *utf8 = 0; + *len = (unsigned long)utf8 - (unsigned long)utf8buf; + break; + } + return 0; +} + +/* + * Sets the title of an MP3 entry based on its ID3v1 tag. + * + * Arguments: file - the MP3 file to scen for a ID3v1 tag + * entry - the entry to set the title in + * + * Returns: true if a title was found and created, else false + */ +bool setid3v1title(int fd, struct mp3entry *entry) +{ + unsigned char buffer[128]; + static const char offsets[] = {3, 33, 63, 97, 93, 125, 127}; + int i, j; + unsigned char* utf8; + + if (-1 == lseek(fd, -128, SEEK_END)) + return false; + + if (read(fd, buffer, sizeof buffer) != sizeof buffer) + return false; + + if (strncmp((char *)buffer, "TAG", 3)) + return false; + + entry->id3v1len = 128; + entry->id3version = ID3_VER_1_0; + + for (i=0; i < (int)sizeof offsets; i++) { + unsigned char* ptr = (unsigned char *)buffer + offsets[i]; + + switch(i) { + case 0: + case 1: + case 2: + /* kill trailing space in strings */ + for (j=29; j && (ptr[j]==0 || ptr[j]==' '); j--) + ptr[j] = 0; + /* convert string to utf8 */ + utf8 = (unsigned char *)entry->id3v1buf[i]; + utf8 = iso_decode(ptr, utf8, -1, 30); + /* make sure string is terminated */ + *utf8 = 0; + break; + + case 3: + /* kill trailing space in strings */ + for (j=27; j && (ptr[j]==0 || ptr[j]==' '); j--) + ptr[j] = 0; + /* convert string to utf8 */ + utf8 = (unsigned char *)entry->id3v1buf[3]; + utf8 = iso_decode(ptr, utf8, -1, 28); + /* make sure string is terminated */ + *utf8 = 0; + break; + + case 4: + ptr[4] = 0; + entry->year = atoi((char *)ptr); + break; + + case 5: + /* id3v1.1 uses last two bytes of comment field for track + number: first must be 0 and second is track num */ + if (!ptr[0] && ptr[1]) { + entry->tracknum = ptr[1]; + entry->id3version = ID3_VER_1_1; + } + break; + + case 6: + /* genre */ + entry->genre_string = id3_get_num_genre(ptr[0]); + break; + } + } + + entry->title = entry->id3v1buf[0]; + entry->artist = entry->id3v1buf[1]; + entry->album = entry->id3v1buf[2]; + entry->comment = entry->id3v1buf[3]; + + return true; +} + + +/* + * Sets the title of an MP3 entry based on its ID3v2 tag. + * + * Arguments: file - the MP3 file to scan for a ID3v2 tag + * entry - the entry to set the title in + * + * Returns: true if a title was found and created, else false + */ +void setid3v2title(int fd, struct mp3entry *entry) +{ + int minframesize; + int size; + long bufferpos = 0, totframelen, framelen; + char header[10]; + char tmp[4]; + unsigned char version; + char *buffer = entry->id3v2buf; + int bytesread = 0; + int buffersize = sizeof(entry->id3v2buf); + unsigned char global_flags; + int flags; + bool global_unsynch = false; + bool unsynch = false; + int i, j; + int rc; +#if CONFIG_CODEC == SWCODEC + bool itunes_gapless = false; +#endif + + global_ff_found = false; + + /* Bail out if the tag is shorter than 10 bytes */ + if(entry->id3v2len < 10) + return; + + /* Read the ID3 tag version from the header */ + lseek(fd, 0, SEEK_SET); + if(10 != read(fd, header, 10)) + return; + + /* Get the total ID3 tag size */ + size = entry->id3v2len - 10; + + version = header[3]; + switch ( version ) { + case 2: + version = ID3_VER_2_2; + minframesize = 8; + break; + + case 3: + version = ID3_VER_2_3; + minframesize = 12; + break; + + case 4: + version = ID3_VER_2_4; + minframesize = 12; + break; + + default: + /* unsupported id3 version */ + return; + } + entry->id3version = version; + entry->tracknum = entry->year = entry->discnum = 0; + entry->title = entry->artist = entry->album = NULL; /* FIXME incomplete */ + + global_flags = header[5]; + + /* Skip the extended header if it is present */ + if(global_flags & 0x40) { + if(version == ID3_VER_2_3) { + if(10 != read(fd, header, 10)) + return; + /* The 2.3 extended header size doesn't include the header size + field itself. Also, it is not unsynched. */ + framelen = + bytes2int(header[0], header[1], header[2], header[3]) + 4; + + /* Skip the rest of the header */ + lseek(fd, framelen - 10, SEEK_CUR); + } + + if(version >= ID3_VER_2_4) { + if(4 != read(fd, header, 4)) + return; + + /* The 2.4 extended header size does include the entire header, + so here we can just skip it. This header is unsynched. */ + framelen = unsync(header[0], header[1], + header[2], header[3]); + + lseek(fd, framelen - 4, SEEK_CUR); + } + } + + /* Is unsynchronization applied? */ + if(global_flags & 0x80) { + global_unsynch = true; + } + + /* + * We must have at least minframesize bytes left for the + * remaining frames to be interesting + */ + while (size >= minframesize && bufferpos < buffersize - 1) { + flags = 0; + + /* Read frame header and check length */ + if(version >= ID3_VER_2_3) { + if(global_unsynch && version <= ID3_VER_2_3) + rc = read_unsynched(fd, header, 10); + else + rc = read(fd, header, 10); + if(rc != 10) + return; + /* Adjust for the 10 bytes we read */ + size -= 10; + + flags = bytes2int(0, 0, header[8], header[9]); + + if (version >= ID3_VER_2_4) { + framelen = unsync(header[4], header[5], + header[6], header[7]); + } else { + /* version .3 files don't use synchsafe ints for + * size */ + framelen = bytes2int(header[4], header[5], + header[6], header[7]); + } + } else { + if(6 != read(fd, header, 6)) + return; + /* Adjust for the 6 bytes we read */ + size -= 6; + + framelen = bytes2int(0, header[3], header[4], header[5]); + } + + logf("framelen = %ld, flags = 0x%04x", framelen, flags); + if(framelen == 0){ + if (header[0] == 0 && header[1] == 0 && header[2] == 0) + return; + else + continue; + } + + unsynch = false; + + if(flags) + { + if (version >= ID3_VER_2_4) { + if(flags & 0x0040) { /* Grouping identity */ + lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */ + framelen--; + } + } else { + if(flags & 0x0020) { /* Grouping identity */ + lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */ + framelen--; + } + } + + if(flags & 0x000c) /* Compression or encryption */ + { + /* Skip it */ + size -= framelen; + lseek(fd, framelen, SEEK_CUR); + continue; + } + + if(flags & 0x0002) /* Unsynchronization */ + unsynch = true; + + if (version >= ID3_VER_2_4) { + if(flags & 0x0001) { /* Data length indicator */ + if(4 != read(fd, tmp, 4)) + return; + + /* We don't need the data length */ + framelen -= 4; + } + } + } + + if (framelen == 0) + continue; + + if (framelen < 0) + return; + + /* Keep track of the remaining frame size */ + totframelen = framelen; + + /* If the frame is larger than the remaining buffer space we try + to read as much as would fit in the buffer */ + if(framelen >= buffersize - bufferpos) + framelen = buffersize - bufferpos - 1; + + /* Limit the maximum length of an id3 data item to ID3V2_MAX_ITEM_SIZE + bytes. This reduces the chance that the available buffer is filled + by single metadata items like large comments. */ + if (ID3V2_MAX_ITEM_SIZE < framelen) + framelen = ID3V2_MAX_ITEM_SIZE; + + logf("id3v2 frame: %.4s", header); + + /* Check for certain frame headers + + 'size' is the amount of frame bytes remaining. We decrement it by + the amount of bytes we read. If we fail to read as many bytes as + we expect, we assume that we can't read from this file, and bail + out. + + For each frame. we will iterate over the list of supported tags, + and read the tag into entry's buffer. All tags will be kept as + strings, for cases where a number won't do, e.g., YEAR: "circa + 1765", "1790/1977" (composed/performed), "28 Feb 1969" TRACK: + "1/12", "1 of 12", GENRE: "Freeform genre name" Text is more + flexible, and as the main use of id3 data is to display it, + converting it to an int just means reconverting to display it, at a + runtime cost. + + For tags that the current code does convert to ints, a post + processing function will be called via a pointer to function. */ + + for (i=0; ioffset ? (char**) (((char*)entry) + tr->offset) + : NULL; + char* tag; + + /* Only ID3_VER_2_2 uses frames with three-character names. */ + if (((version == ID3_VER_2_2) && (tr->tag_length != 3)) + || ((version > ID3_VER_2_2) && (tr->tag_length != 4))) { + continue; + } + + if( !memcmp( header, tr->tag, tr->tag_length ) ) { + + /* found a tag matching one in tagList, and not yet filled */ + tag = buffer + bufferpos; + + if(global_unsynch && version <= ID3_VER_2_3) + bytesread = read_unsynched(fd, tag, framelen); + else + bytesread = read(fd, tag, framelen); + + if( bytesread != framelen ) + return; + + size -= bytesread; + + if(unsynch || (global_unsynch && version >= ID3_VER_2_4)) + bytesread = unsynchronize_frame(tag, bytesread); + + /* the COMM frame has a 3 char field to hold an ISO-639-1 + * language string and an optional short description; + * remove them so unicode_munge can work correctly + */ + + if((tr->tag_length == 4 && !memcmp( header, "COMM", 4)) || + (tr->tag_length == 3 && !memcmp( header, "COM", 3))) { + int offset; + if(bytesread >= 8 && !strncmp(tag+4, "iTun", 4)) { +#if CONFIG_CODEC == SWCODEC + /* check for iTunes gapless information */ + if(bytesread >= 12 && !strncmp(tag+4, "iTunSMPB", 8)) + itunes_gapless = true; + else +#endif + /* ignore other with iTunes tags */ + break; + } + + offset = 3 + unicode_len(*tag, tag + 4); + if(bytesread > offset) { + bytesread -= offset; + memmove(tag + 1, tag + 1 + offset, bytesread - 1); + } + } + + /* Attempt to parse Unicode string only if the tag contents + aren't binary */ + if(!tr->binary) { + /* UTF-8 could potentially be 3 times larger */ + /* so we need to create a new buffer */ + char utf8buf[(3 * bytesread) + 1]; + + unicode_munge( tag, utf8buf, &bytesread ); + + if(bytesread >= buffersize - bufferpos) + bytesread = buffersize - bufferpos - 1; + + if ( /* Is it an embedded cuesheet? */ + (tr->tag_length == 4 && !memcmp(header, "TXXX", 4)) && + (bytesread >= 14 && !strncmp(utf8buf, "CUESHEET", 8)) + ) { + unsigned char char_enc = 0; + /* [enc type]+"CUESHEET\0" = 10 */ + unsigned char cuesheet_offset = 10; + switch (tag[0]) { + case 0x00: + char_enc = CHAR_ENC_ISO_8859_1; + break; + case 0x01: + tag++; + if (!memcmp(tag, + BOM_UTF_16_BE, BOM_UTF_16_SIZE)) { + char_enc = CHAR_ENC_UTF_16_BE; + } else if (!memcmp(tag, + BOM_UTF_16_LE, BOM_UTF_16_SIZE)) { + char_enc = CHAR_ENC_UTF_16_LE; + } + /* \1 + BOM(2) + C0U0E0S0H0E0E0T000 = 21 */ + cuesheet_offset = 21; + break; + case 0x02: + char_enc = CHAR_ENC_UTF_16_BE; + /* \2 + 0C0U0E0S0H0E0E0T00 = 19 */ + cuesheet_offset = 19; + break; + case 0x03: + char_enc = CHAR_ENC_UTF_8; + break; + } + if (char_enc > 0) { + entry->has_embedded_cuesheet = true; + entry->embedded_cuesheet.pos = lseek(fd, 0, SEEK_CUR) + - framelen + cuesheet_offset; + entry->embedded_cuesheet.size = totframelen + - cuesheet_offset; + entry->embedded_cuesheet.encoding = char_enc; + } + break; + } + + for (j = 0; j < bytesread; j++) + tag[j] = utf8buf[j]; + + /* remove trailing spaces */ + while ( bytesread > 0 && isspace(tag[bytesread-1])) + bytesread--; + } + + if(bytesread == 0) + /* Skip empty frames */ + break; + + tag[bytesread] = 0; + bufferpos += bytesread + 1; + +#if CONFIG_CODEC == SWCODEC + /* parse the tag if it contains iTunes gapless info */ + if (itunes_gapless) + { + itunes_gapless = false; + entry->lead_trim = get_itunes_int32(tag, 1); + entry->tail_trim = get_itunes_int32(tag, 2); + } +#endif + + /* Note that parser functions sometimes set *ptag to NULL, so + * the "!*ptag" check here doesn't always have the desired + * effect. Should the parser functions (parsegenre in + * particular) be updated to handle the case of being called + * multiple times, or should the "*ptag" check be removed? + */ + if (ptag && !*ptag) + *ptag = tag; + +#ifdef HAVE_ALBUMART + /* albumart */ + if ((!entry->has_embedded_albumart) && + ((tr->tag_length == 4 && !memcmp( header, "APIC", 4)) || + (tr->tag_length == 3 && !memcmp( header, "PIC" , 3)))) + { + if (unsynch || (global_unsynch && version <= ID3_VER_2_3)) + entry->albumart.type = AA_TYPE_UNSYNC; + else + { + entry->albumart.pos = lseek(fd, 0, SEEK_CUR) - framelen; + entry->albumart.size = totframelen; + entry->albumart.type = AA_TYPE_UNKNOWN; + } + } +#endif + if( tr->ppFunc ) + bufferpos = tr->ppFunc(entry, tag, bufferpos); + break; + } + } + + if( i == TAGLIST_SIZE ) { + /* no tag in tagList was found, or it was a repeat. + skip it using the total size */ + + if(global_unsynch && version <= ID3_VER_2_3) { + size -= skip_unsynched(fd, totframelen); + } else { + size -= totframelen; + if( lseek(fd, totframelen, SEEK_CUR) == -1 ) + return; + } + } else { + /* Seek to the next frame */ + if(framelen < totframelen) + lseek(fd, totframelen - framelen, SEEK_CUR); + } + } +} + +/* + * Calculates the size of the ID3v2 tag. + * + * Arguments: file - the file to search for a tag. + * + * Returns: the size of the tag or 0 if none was found + */ +int getid3v2len(int fd) +{ + char buf[6]; + int offset; + + /* Make sure file has a ID3 tag */ + if((-1 == lseek(fd, 0, SEEK_SET)) || + (read(fd, buf, 6) != 6) || + (strncmp(buf, "ID3", strlen("ID3")) != 0)) + offset = 0; + + /* Now check what the ID3v2 size field says */ + else + if(read(fd, buf, 4) != 4) + offset = 0; + else + offset = unsync(buf[0], buf[1], buf[2], buf[3]) + 10; + + logf("ID3V2 Length: 0x%x", offset); + return offset; +} + +#ifdef DEBUG_STANDALONE + +char *secs2str(int ms) +{ + static char buffer[32]; + int secs = ms/1000; + ms %= 1000; + snprintf(buffer, sizeof(buffer), "%d:%02d.%d", secs/60, secs%60, ms/100); + return buffer; +} + +int main(int argc, char **argv) +{ + int i; + for(i=1; i", + mp3.artist?mp3.artist:"", + mp3.album?mp3.album:"", + mp3.genre_string?mp3.genre_string:"", + mp3.genre, + mp3.composer?mp3.composer:"", + mp3.year_string?mp3.year_string:"", + mp3.year, + mp3.track_string?mp3.track_string:"", + mp3.tracknum, + secs2str(mp3.length), + mp3.length/1000, + mp3.bitrate, + mp3.frequency); + } + + return 0; +} + +#endif diff --git a/lib/rbcodec/metadata/kss.c b/lib/rbcodec/metadata/kss.c new file mode 100644 index 0000000000..2ae0cf50b0 --- /dev/null +++ b/lib/rbcodec/metadata/kss.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +static bool parse_kss_header(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + + lseek(fd, 0, SEEK_SET); + if (read(fd, buf, 0x20) < 0x20) + return false; + + /* calculate track length with number of tracks */ + id3->length = 0; + if (buf[14] == 0x10) { + id3->length = (get_short_le((void *)(buf + 26)) + 1) * 1000; + } + + if (id3->length <= 0) + id3->length = 255 * 1000; /* 255 tracks */ + + return true; +} + + +bool get_kss_metadata(int fd, struct mp3entry* id3) +{ + uint32_t kss_type; + if ((lseek(fd, 0, SEEK_SET) < 0) || + read_uint32be(fd, &kss_type) != (int)sizeof(kss_type)) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + /* we only render 16 bits, 44.1KHz, Stereo */ + id3->bitrate = 706; + id3->frequency = 44100; + + /* Make sure this is an SGC file */ + if (kss_type != FOURCC('K','S','C','C') && kss_type != FOURCC('K','S','S','X')) + return false; + + return parse_kss_header(fd, id3); +} diff --git a/lib/rbcodec/metadata/metadata.c b/lib/rbcodec/metadata/metadata.c new file mode 100644 index 0000000000..b91e00cc4e --- /dev/null +++ b/lib/rbcodec/metadata/metadata.c @@ -0,0 +1,641 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include "string-extra.h" + +#include "debug.h" +#include "logf.h" +#include "settings.h" +#include "cuesheet.h" +#include "metadata.h" + +#include "metadata_parsers.h" + +#if CONFIG_CODEC == SWCODEC + +/* For trailing tag stripping and base audio data types */ +#include "buffering.h" + +#include "metadata/metadata_common.h" + +static bool get_shn_metadata(int fd, struct mp3entry *id3) +{ + /* TODO: read the id3v2 header if it exists */ + id3->vbr = true; + id3->filesize = filesize(fd); + return skip_id3v2(fd, id3); +} + +static bool get_other_asap_metadata(int fd, struct mp3entry *id3) +{ + id3->bitrate = 706; + id3->frequency = 44100; + id3->vbr = false; + id3->filesize = filesize(fd); + id3->genre_string = id3_get_num_genre(36); + return true; +} +#endif /* CONFIG_CODEC == SWCODEC */ +bool write_metadata_log = false; + +const struct afmt_entry audio_formats[AFMT_NUM_CODECS] = +{ + /* Unknown file format */ + [0 ... AFMT_NUM_CODECS-1] = + AFMT_ENTRY("???", NULL, NULL, NULL, "\0" ), + + /* MPEG Audio layer 2 */ + [AFMT_MPA_L2] = + AFMT_ENTRY("MP2", "mpa", NULL, get_mp3_metadata, "mpa\0mp2\0"), + +#if CONFIG_CODEC != SWCODEC + /* MPEG Audio layer 3 on HWCODEC: .talk clips, no encoder */ + [AFMT_MPA_L3] = + AFMT_ENTRY("MP3", "mpa", NULL, get_mp3_metadata, "mp3\0talk\0"), + +#else /* CONFIG_CODEC == SWCODEC */ + /* MPEG Audio layer 3 on SWCODEC */ + [AFMT_MPA_L3] = + AFMT_ENTRY("MP3", "mpa", "mp3_enc", get_mp3_metadata, "mp3\0"), + + /* MPEG Audio layer 1 */ + [AFMT_MPA_L1] = + AFMT_ENTRY("MP1", "mpa", NULL, get_mp3_metadata, "mp1\0"), + /* Audio Interchange File Format */ + [AFMT_AIFF] = + AFMT_ENTRY("AIFF", "aiff", "aiff_enc", get_aiff_metadata, "aiff\0aif\0"), + /* Uncompressed PCM in a WAV file OR ATRAC3 stream in WAV file (.at3) */ + [AFMT_PCM_WAV] = + AFMT_ENTRY("WAV", "wav", "wav_enc", get_wave_metadata, "wav\0at3\0"), + /* Ogg Vorbis */ + [AFMT_OGG_VORBIS] = + AFMT_ENTRY("Ogg", "vorbis", NULL, get_ogg_metadata, "ogg\0oga\0"), + /* FLAC */ + [AFMT_FLAC] = + AFMT_ENTRY("FLAC", "flac", NULL, get_flac_metadata, "flac\0"), + /* Musepack SV7 */ + [AFMT_MPC_SV7] = + AFMT_ENTRY("MPCv7", "mpc", NULL, get_musepack_metadata,"mpc\0"), + /* A/52 (aka AC3) audio */ + [AFMT_A52] = + AFMT_ENTRY("AC3", "a52", NULL, get_a52_metadata, "a52\0ac3\0"), + /* WavPack */ + [AFMT_WAVPACK] = + AFMT_ENTRY("WV","wavpack","wavpack_enc",get_wavpack_metadata,"wv\0"), + /* Apple Lossless Audio Codec */ + [AFMT_MP4_ALAC] = + AFMT_ENTRY("ALAC", "alac", NULL, get_mp4_metadata, "m4a\0m4b\0"), + /* Advanced Audio Coding in M4A container */ + [AFMT_MP4_AAC] = + AFMT_ENTRY("AAC", "aac", NULL, get_mp4_metadata, "mp4\0"), + /* Shorten */ + [AFMT_SHN] = + AFMT_ENTRY("SHN","shorten", NULL, get_shn_metadata, "shn\0"), + /* SID File Format */ + [AFMT_SID] = + AFMT_ENTRY("SID", "sid", NULL, get_sid_metadata, "sid\0"), + /* ADX File Format */ + [AFMT_ADX] = + AFMT_ENTRY("ADX", "adx", NULL, get_adx_metadata, "adx\0"), + /* NESM (NES Sound Format) */ + [AFMT_NSF] = + AFMT_ENTRY("NSF", "nsf", NULL, get_nsf_metadata, "nsf\0nsfe\0"), + /* Speex File Format */ + [AFMT_SPEEX] = + AFMT_ENTRY("Speex", "speex",NULL, get_ogg_metadata, "spx\0"), + /* SPC700 Save State */ + [AFMT_SPC] = + AFMT_ENTRY("SPC", "spc", NULL, get_spc_metadata, "spc\0"), + /* APE (Monkey's Audio) */ + [AFMT_APE] = + AFMT_ENTRY("APE", "ape", NULL, get_monkeys_metadata,"ape\0mac\0"), + /* WMA (WMAV1/V2 in ASF) */ + [AFMT_WMA] = + AFMT_ENTRY("WMA", "wma", NULL, get_asf_metadata,"wma\0wmv\0asf\0"), + /* WMA Professional in ASF */ + [AFMT_WMAPRO] = + AFMT_ENTRY("WMAPro","wmapro",NULL, NULL, "wma\0wmv\0asf\0"), + /* Amiga MOD File */ + [AFMT_MOD] = + AFMT_ENTRY("MOD", "mod", NULL, get_mod_metadata, "mod\0"), + /* Atari SAP File */ + [AFMT_SAP] = + AFMT_ENTRY("SAP", "asap", NULL, get_asap_metadata, "sap\0"), + /* Cook in RM/RA */ + [AFMT_RM_COOK] = + AFMT_ENTRY("Cook", "cook", NULL, get_rm_metadata,"rm\0ra\0rmvb\0"), + /* AAC in RM/RA */ + [AFMT_RM_AAC] = + AFMT_ENTRY("RAAC", "raac", NULL, NULL, "rm\0ra\0rmvb\0"), + /* AC3 in RM/RA */ + [AFMT_RM_AC3] = + AFMT_ENTRY("AC3", "a52_rm", NULL, NULL, "rm\0ra\0rmvb\0"), + /* ATRAC3 in RM/RA */ + [AFMT_RM_ATRAC3] = + AFMT_ENTRY("ATRAC3","atrac3_rm",NULL, NULL, "rm\0ra\0rmvb\0"), + /* Atari CMC File */ + [AFMT_CMC] = + AFMT_ENTRY("CMC", "asap", NULL, get_other_asap_metadata,"cmc\0"), + /* Atari CM3 File */ + [AFMT_CM3] = + AFMT_ENTRY("CM3", "asap", NULL, get_other_asap_metadata,"cm3\0"), + /* Atari CMR File */ + [AFMT_CMR] = + AFMT_ENTRY("CMR", "asap", NULL, get_other_asap_metadata,"cmr\0"), + /* Atari CMS File */ + [AFMT_CMS] = + AFMT_ENTRY("CMS", "asap", NULL, get_other_asap_metadata,"cms\0"), + /* Atari DMC File */ + [AFMT_DMC] = + AFMT_ENTRY("DMC", "asap", NULL, get_other_asap_metadata,"dmc\0"), + /* Atari DLT File */ + [AFMT_DLT] = + AFMT_ENTRY("DLT", "asap", NULL, get_other_asap_metadata,"dlt\0"), + /* Atari MPT File */ + [AFMT_MPT] = + AFMT_ENTRY("MPT", "asap", NULL, get_other_asap_metadata,"mpt\0"), + /* Atari MPD File */ + [AFMT_MPD] = + AFMT_ENTRY("MPD", "asap", NULL, get_other_asap_metadata,"mpd\0"), + /* Atari RMT File */ + [AFMT_RMT] = + AFMT_ENTRY("RMT", "asap", NULL, get_other_asap_metadata,"rmt\0"), + /* Atari TMC File */ + [AFMT_TMC] = + AFMT_ENTRY("TMC", "asap", NULL, get_other_asap_metadata,"tmc\0"), + /* Atari TM8 File */ + [AFMT_TM8] = + AFMT_ENTRY("TM8", "asap", NULL, get_other_asap_metadata,"tm8\0"), + /* Atari TM2 File */ + [AFMT_TM2] = + AFMT_ENTRY("TM2", "asap", NULL, get_other_asap_metadata,"tm2\0"), + /* Atrac3 in Sony OMA Container */ + [AFMT_OMA_ATRAC3] = + AFMT_ENTRY("ATRAC3","atrac3_oma",NULL, get_oma_metadata, "oma\0aa3\0"), + /* SMAF (Synthetic music Mobile Application Format) */ + [AFMT_SMAF] = + AFMT_ENTRY("SMAF", "smaf", NULL, get_smaf_metadata, "mmf\0"), + /* Sun Audio file */ + [AFMT_AU] = + AFMT_ENTRY("AU", "au", NULL, get_au_metadata, "au\0snd\0"), + /* VOX (Dialogic telephony file formats) */ + [AFMT_VOX] = + AFMT_ENTRY("VOX", "vox", NULL, get_vox_metadata, "vox\0"), + /* Wave64 */ + [AFMT_WAVE64] = + AFMT_ENTRY("WAVE64","wav64",NULL, get_wave64_metadata,"w64\0"), + /* True Audio */ + [AFMT_TTA] = + AFMT_ENTRY("TTA", "tta", NULL, get_tta_metadata, "tta\0"), + /* WMA Voice in ASF */ + [AFMT_WMAVOICE] = + AFMT_ENTRY("WMAVoice","wmavoice",NULL, NULL, "wma\0wmv\0"), + /* Musepack SV8 */ + [AFMT_MPC_SV8] = + AFMT_ENTRY("MPCv8", "mpc", NULL, get_musepack_metadata,"mpc\0"), + /* Advanced Audio Coding High Efficiency in M4A container */ + [AFMT_MP4_AAC_HE] = + AFMT_ENTRY("AAC-HE","aac", NULL, get_mp4_metadata, "mp4\0"), + /* AY (ZX Spectrum, Amstrad CPC Sound Format) */ + [AFMT_AY] = + AFMT_ENTRY("AY", "ay", NULL, get_ay_metadata, "ay\0"), + /* GBS (Game Boy Sound Format) */ + [AFMT_GBS] = + AFMT_ENTRY("GBS", "gbs", NULL, get_gbs_metadata, "gbs\0"), + /* HES (Hudson Entertainment System Sound Format) */ + [AFMT_HES] = + AFMT_ENTRY("HES", "hes", NULL, get_hes_metadata, "hes\0"), + /* SGC (Sega Master System, Game Gear, Coleco Vision Sound Format) */ + [AFMT_SGC] = + AFMT_ENTRY("SGC", "sgc", NULL, get_sgc_metadata, "sgc\0"), + /* VGM (Video Game Music Format) */ + [AFMT_VGM] = + AFMT_ENTRY("VGM", "vgm", NULL, get_vgm_metadata, "vgm\0vgz\0"), + /* KSS (MSX computer KSS Music File) */ + [AFMT_KSS] = + AFMT_ENTRY("KSS", "kss", NULL, get_kss_metadata, "kss\0"), +#endif +}; + +#if CONFIG_CODEC == SWCODEC && defined (HAVE_RECORDING) +/* get REC_FORMAT_* corresponding AFMT_* */ +const int rec_format_afmt[REC_NUM_FORMATS] = +{ + /* give AFMT_UNKNOWN by default */ + [0 ... REC_NUM_FORMATS-1] = AFMT_UNKNOWN, + /* add new entries below this line */ + [REC_FORMAT_AIFF] = AFMT_AIFF, + [REC_FORMAT_MPA_L3] = AFMT_MPA_L3, + [REC_FORMAT_WAVPACK] = AFMT_WAVPACK, + [REC_FORMAT_PCM_WAV] = AFMT_PCM_WAV, +}; + +#if 0 /* Currently unused, left for reference and future use */ +/* get AFMT_* corresponding REC_FORMAT_* */ +const int afmt_rec_format[AFMT_NUM_CODECS] = +{ + /* give -1 by default */ + [0 ... AFMT_NUM_CODECS-1] = -1, + /* add new entries below this line */ + [AFMT_AIFF] = REC_FORMAT_AIFF, + [AFMT_MPA_L3] = REC_FORMAT_MPA_L3, + [AFMT_WAVPACK] = REC_FORMAT_WAVPACK, + [AFMT_PCM_WAV] = REC_FORMAT_PCM_WAV, +}; +#endif +#endif /* CONFIG_CODEC == SWCODEC && defined (HAVE_RECORDING) */ + +#if CONFIG_CODEC == SWCODEC +/* Get the canonical AFMT type */ +int get_audio_base_codec_type(int type) +{ + int base_type = type; + switch (type) { + case AFMT_MPA_L1: + case AFMT_MPA_L2: + case AFMT_MPA_L3: + base_type = AFMT_MPA_L3; + break; + case AFMT_MPC_SV7: + case AFMT_MPC_SV8: + base_type = AFMT_MPC_SV7; + break; + case AFMT_MP4_AAC: + case AFMT_MP4_AAC_HE: + base_type = AFMT_MP4_AAC; + break; + case AFMT_SAP: + case AFMT_CMC: + case AFMT_CM3: + case AFMT_CMR: + case AFMT_CMS: + case AFMT_DMC: + case AFMT_DLT: + case AFMT_MPT: + case AFMT_MPD: + case AFMT_RMT: + case AFMT_TMC: + case AFMT_TM8: + case AFMT_TM2: + base_type = AFMT_SAP; + break; + default: + break; + } + + return base_type; +} + +/* Get the basic audio type */ +enum data_type get_audio_base_data_type(int afmt) +{ + if ((unsigned)afmt >= AFMT_NUM_CODECS) + return TYPE_UNKNOWN; + + switch (get_audio_base_codec_type(afmt)) + { + case AFMT_NSF: + case AFMT_SPC: + case AFMT_SID: + case AFMT_MOD: + case AFMT_SAP: + case AFMT_AY: + case AFMT_GBS: + case AFMT_HES: + case AFMT_SGC: + case AFMT_VGM: + case AFMT_KSS: + /* Type must be allocated and loaded in its entirety onto + the buffer */ + return TYPE_ATOMIC_AUDIO; + + default: + /* Assume type may be loaded and discarded incrementally */ + return TYPE_PACKET_AUDIO; + + case AFMT_UNKNOWN: + /* Have no idea at all */ + return TYPE_UNKNOWN; + } +} + +/* Is the format allowed to buffer starting at some offset other than 0 + or first frame only for resume purposes? */ +bool format_buffers_with_offset(int afmt) +{ + switch (afmt) + { + case AFMT_MPA_L1: + case AFMT_MPA_L2: + case AFMT_MPA_L3: + case AFMT_WAVPACK: + /* Format may be loaded at the first needed frame */ + return true; + default: + /* Format must be loaded from the beginning of the file + (does not imply 'atomic', while 'atomic' implies 'no offset') */ + return false; + } +} +#endif /* CONFIG_CODEC == SWCODEC */ + + +/* Simple file type probing by looking at the filename extension. */ +unsigned int probe_file_format(const char *filename) +{ + char *suffix; + unsigned int i; + + suffix = strrchr(filename, '.'); + + if (suffix == NULL) + { + return AFMT_UNKNOWN; + } + + /* skip '.' */ + suffix++; + + for (i = 1; i < AFMT_NUM_CODECS; i++) + { + /* search extension list for type */ + const char *ext = audio_formats[i].ext_list; + + do + { + if (strcasecmp(suffix, ext) == 0) + { + return i; + } + + ext += strlen(ext) + 1; + } + while (*ext != '\0'); + } + + return AFMT_UNKNOWN; +} + +/* Note, that this returns false for successful, true for error! */ +bool mp3info(struct mp3entry *entry, const char *filename) +{ + int fd; + bool result; + + fd = open(filename, O_RDONLY); + if (fd < 0) + return true; + + result = !get_metadata(entry, fd, filename); + + close(fd); + + return result; +} + +/* Get metadata for track - return false if parsing showed problems with the + * file that would prevent playback. + */ +bool get_metadata(struct mp3entry* id3, int fd, const char* trackname) +{ + const struct afmt_entry *entry; + int logfd = 0; + DEBUGF("Read metadata for %s\n", trackname); + if (write_metadata_log) + { + logfd = open("/metadata.log", O_WRONLY | O_APPEND | O_CREAT, 0666); + if (logfd >= 0) + { + write(logfd, trackname, strlen(trackname)); + write(logfd, "\n", 1); + close(logfd); + } + } + + /* Clear the mp3entry to avoid having bogus pointers appear */ + wipe_mp3entry(id3); + + /* Take our best guess at the codec type based on file extension */ + id3->codectype = probe_file_format(trackname); + + /* default values for embedded cuesheets */ + id3->has_embedded_cuesheet = false; + id3->embedded_cuesheet.pos = 0; + + entry = &audio_formats[id3->codectype]; + + /* Load codec specific track tag information and confirm the codec type. */ + if (!entry->parse_func) + { + DEBUGF("nothing to parse for %s (format %s)", trackname, entry->label); + return false; + } + + if (!entry->parse_func(fd, id3)) + { + DEBUGF("parsing %s failed (format: %s)", trackname, entry->label); + return false; + } + + lseek(fd, 0, SEEK_SET); + strlcpy(id3->path, trackname, sizeof(id3->path)); + /* We have successfully read the metadata from the file */ + return true; +} + +#ifndef __PCTOOL__ +#if CONFIG_CODEC == SWCODEC +void strip_tags(int handle_id) +{ + static const unsigned char tag[] = "TAG"; + static const unsigned char apetag[] = "APETAGEX"; + size_t len, version; + void *tail; + + if (bufgettail(handle_id, 128, &tail) != 128) + return; + + if (memcmp(tail, tag, 3) == 0) + { + /* Skip id3v1 tag */ + logf("Cutting off ID3v1 tag"); + bufcuttail(handle_id, 128); + } + + /* Get a new tail, as the old one may have been cut */ + if (bufgettail(handle_id, 32, &tail) != 32) + return; + + /* Check for APE tag (look for the APE tag footer) */ + if (memcmp(tail, apetag, 8) != 0) + return; + + /* Read the version and length from the footer */ + version = get_long_le(&((unsigned char *)tail)[8]); + len = get_long_le(&((unsigned char *)tail)[12]); + if (version == 2000) + len += 32; /* APEv2 has a 32 byte header */ + + /* Skip APE tag */ + logf("Cutting off APE tag (%ldB)", len); + bufcuttail(handle_id, len); +} +#endif /* CONFIG_CODEC == SWCODEC */ +#endif /* ! __PCTOOL__ */ + +#define MOVE_ENTRY(x) if (x) x += offset; + +void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig) +{ + long offset; + if (orig > dest) + offset = -((size_t)orig - (size_t)dest); + else + offset = ((size_t)dest - (size_t)orig); + + MOVE_ENTRY(entry->title) + MOVE_ENTRY(entry->artist) + MOVE_ENTRY(entry->album) + + if (entry->genre_string > (char*)orig && + entry->genre_string < (char*)orig + sizeof(struct mp3entry)) + /* Don't adjust that if it points to an entry of the "genres" array */ + entry->genre_string += offset; + + MOVE_ENTRY(entry->track_string) + MOVE_ENTRY(entry->disc_string) + MOVE_ENTRY(entry->year_string) + MOVE_ENTRY(entry->composer) + MOVE_ENTRY(entry->comment) + MOVE_ENTRY(entry->albumartist) + MOVE_ENTRY(entry->grouping) + MOVE_ENTRY(entry->mb_track_id) +} + +void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig) +{ + memcpy(dest, orig, sizeof(struct mp3entry)); + adjust_mp3entry(dest, dest, orig); +} + +/* A shortcut to simplify the common task of clearing the struct */ +void wipe_mp3entry(struct mp3entry *id3) +{ + memset(id3, 0, sizeof (struct mp3entry)); +} + +#if CONFIG_CODEC == SWCODEC +/* Glean what is possible from the filename alone - does not parse metadata */ +void fill_metadata_from_path(struct mp3entry *id3, const char *trackname) +{ + char *p; + + /* Clear the mp3entry to avoid having bogus pointers appear */ + wipe_mp3entry(id3); + + /* Find the filename portion of the path */ + p = strrchr(trackname, '/'); + strlcpy(id3->id3v2buf, p ? ++p : id3->path, ID3V2_BUF_SIZE); + + /* Get the format from the extension and trim it off */ + p = strrchr(id3->id3v2buf, '.'); + if (p) + { + /* Might be wrong for container formats - should we bother? */ + id3->codectype = probe_file_format(p); + + if (id3->codectype != AFMT_UNKNOWN) + *p = '\0'; + } + + /* Set the filename as the title */ + id3->title = id3->id3v2buf; + + /* Copy the path info */ + strlcpy(id3->path, trackname, sizeof (id3->path)); +} +#endif /* CONFIG_CODEC == SWCODEC */ + +#ifndef __PCTOOL__ +#ifdef HAVE_TAGCACHE +#if CONFIG_CODEC == SWCODEC + +enum { AUTORESUMABLE_UNKNOWN = 0, AUTORESUMABLE_TRUE, AUTORESUMABLE_FALSE }; + +bool autoresumable(struct mp3entry *id3) +{ + char *endp, *path; + size_t len; + bool is_resumable; + + if (id3->autoresumable) /* result cached? */ + return id3->autoresumable == AUTORESUMABLE_TRUE; + + is_resumable = false; + + if (id3->path) + { + for (path = global_settings.autoresume_paths; + *path; /* search terms left? */ + path++) + { + if (*path == ':') /* Skip empty search patterns */ + continue; + + /* FIXME: As soon as strcspn or strchrnul are made available in + the core, the following can be made more efficient. */ + endp = strchr(path, ':'); + if (endp) + len = endp - path; + else + len = strlen(path); + + /* Note: At this point, len is always > 0 */ + + if (strncasecmp(id3->path, path, len) == 0) + { + /* Full directory-name matches only. Trailing '/' in + search path OK. */ + if (id3->path[len] == '/' || id3->path[len - 1] == '/') + { + is_resumable = true; + break; + } + } + path += len - 1; + } + } + + /* cache result */ + id3->autoresumable = + is_resumable ? AUTORESUMABLE_TRUE : AUTORESUMABLE_FALSE; + + logf("autoresumable: %s is%s resumable", + id3->path, is_resumable ? "" : " not"); + + return is_resumable; +} + +#endif /* SWCODEC */ +#endif /* HAVE_TAGCACHE */ +#endif /* __PCTOOL__ */ diff --git a/lib/rbcodec/metadata/metadata.h b/lib/rbcodec/metadata/metadata.h new file mode 100644 index 0000000000..55e4d76f25 --- /dev/null +++ b/lib/rbcodec/metadata/metadata.h @@ -0,0 +1,353 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ + +#ifndef _METADATA_H +#define _METADATA_H + +#include +#include "config.h" +#include "file.h" + + +/* Audio file types. */ +/* NOTE: The values of the AFMT_* items are used for the %fc tag in the WPS + - so new entries MUST be added to the end to maintain compatibility. + */ +enum +{ + AFMT_UNKNOWN = 0, /* Unknown file format */ + + /* start formats */ + AFMT_MPA_L1, /* MPEG Audio layer 1 */ + AFMT_MPA_L2, /* MPEG Audio layer 2 */ + AFMT_MPA_L3, /* MPEG Audio layer 3 */ + +#if CONFIG_CODEC == SWCODEC + AFMT_AIFF, /* Audio Interchange File Format */ + AFMT_PCM_WAV, /* Uncompressed PCM in a WAV file */ + AFMT_OGG_VORBIS, /* Ogg Vorbis */ + AFMT_FLAC, /* FLAC */ + AFMT_MPC_SV7, /* Musepack SV7 */ + AFMT_A52, /* A/52 (aka AC3) audio */ + AFMT_WAVPACK, /* WavPack */ + AFMT_MP4_ALAC, /* Apple Lossless Audio Codec */ + AFMT_MP4_AAC, /* Advanced Audio Coding (AAC) in M4A container */ + AFMT_SHN, /* Shorten */ + AFMT_SID, /* SID File Format */ + AFMT_ADX, /* ADX File Format */ + AFMT_NSF, /* NESM (NES Sound Format) */ + AFMT_SPEEX, /* Ogg Speex speech */ + AFMT_SPC, /* SPC700 save state */ + AFMT_APE, /* Monkey's Audio (APE) */ + AFMT_WMA, /* WMAV1/V2 in ASF */ + AFMT_WMAPRO, /* WMA Professional in ASF */ + AFMT_MOD, /* Amiga MOD File Format */ + AFMT_SAP, /* Atari 8Bit SAP Format */ + AFMT_RM_COOK, /* Cook in RM/RA */ + AFMT_RM_AAC, /* AAC in RM/RA */ + AFMT_RM_AC3, /* AC3 in RM/RA */ + AFMT_RM_ATRAC3, /* ATRAC3 in RM/RA */ + AFMT_CMC, /* Atari 8bit cmc format */ + AFMT_CM3, /* Atari 8bit cm3 format */ + AFMT_CMR, /* Atari 8bit cmr format */ + AFMT_CMS, /* Atari 8bit cms format */ + AFMT_DMC, /* Atari 8bit dmc format */ + AFMT_DLT, /* Atari 8bit dlt format */ + AFMT_MPT, /* Atari 8bit mpt format */ + AFMT_MPD, /* Atari 8bit mpd format */ + AFMT_RMT, /* Atari 8bit rmt format */ + AFMT_TMC, /* Atari 8bit tmc format */ + AFMT_TM8, /* Atari 8bit tm8 format */ + AFMT_TM2, /* Atari 8bit tm2 format */ + AFMT_OMA_ATRAC3, /* Atrac3 in Sony OMA container */ + AFMT_SMAF, /* SMAF */ + AFMT_AU, /* Sun Audio file */ + AFMT_VOX, /* VOX */ + AFMT_WAVE64, /* Wave64 */ + AFMT_TTA, /* True Audio */ + AFMT_WMAVOICE, /* WMA Voice in ASF */ + AFMT_MPC_SV8, /* Musepack SV8 */ + AFMT_MP4_AAC_HE, /* Advanced Audio Coding (AAC-HE) in M4A container */ + AFMT_AY, /* AY (ZX Spectrum, Amstrad CPC Sound Format) */ + AFMT_GBS, /* GBS (Game Boy Sound Format) */ + AFMT_HES, /* HES (Hudson Entertainment System Sound Format) */ + AFMT_SGC, /* SGC (Sega Master System, Game Gear, Coleco Vision Sound Format) */ + AFMT_VGM, /* VGM (Video Game Music Format) */ + AFMT_KSS, /* KSS (MSX computer KSS Music File) */ +#endif + + /* add new formats at any index above this line to have a sensible order - + specified array index inits are used */ + /* format arrays defined in id3.c */ + + AFMT_NUM_CODECS, + +#if CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) + /* masks to decompose parts */ + CODEC_AFMT_MASK = 0x0fff, + CODEC_TYPE_MASK = 0x7000, + + /* switch for specifying codec type when requesting a filename */ + CODEC_TYPE_DECODER = (0 << 12), /* default */ + CODEC_TYPE_ENCODER = (1 << 12), +#endif /* CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) */ +}; + +#if CONFIG_CODEC == SWCODEC +#if (CONFIG_PLATFORM & PLATFORM_ANDROID) +#define CODEC_EXTENSION "so" +#define CODEC_PREFIX "lib" +#else +#define CODEC_EXTENSION "codec" +#define CODEC_PREFIX "" +#endif + +#ifdef HAVE_RECORDING +enum rec_format_indexes +{ + __REC_FORMAT_START_INDEX = -1, + + /* start formats */ + + REC_FORMAT_PCM_WAV, + REC_FORMAT_AIFF, + REC_FORMAT_WAVPACK, + REC_FORMAT_MPA_L3, + + /* add new formats at any index above this line to have a sensible order - + specified array index inits are used + REC_FORMAT_CFG_NUM_BITS should allocate enough bits to hold the range + REC_FORMAT_CFG_VALUE_LIST should be in same order as indexes + */ + + REC_NUM_FORMATS, + + REC_FORMAT_DEFAULT = REC_FORMAT_PCM_WAV, + REC_FORMAT_CFG_NUM_BITS = 2 +}; + +#define REC_FORMAT_CFG_VAL_LIST "wave,aiff,wvpk,mpa3" + +/* get REC_FORMAT_* corresponding AFMT_* */ +extern const int rec_format_afmt[REC_NUM_FORMATS]; +/* get AFMT_* corresponding REC_FORMAT_* */ +/* unused: extern const int afmt_rec_format[AFMT_NUM_CODECS]; */ + +#define AFMT_ENTRY(label, root_fname, enc_root_fname, func, ext_list) \ + { label, root_fname, enc_root_fname, func, ext_list } +#else /* !HAVE_RECORDING */ +#define AFMT_ENTRY(label, root_fname, enc_root_fname, func, ext_list) \ + { label, root_fname, func, ext_list } +#endif /* HAVE_RECORDING */ + +#else /* !SWCODEC */ + +#define AFMT_ENTRY(label, root_fname, enc_root_fname, func, ext_list) \ + { label, func, ext_list } +#endif /* CONFIG_CODEC == SWCODEC */ + +/** Database of audio formats **/ +/* record describing the audio format */ +struct mp3entry; +struct afmt_entry +{ + const char *label; /* format label */ +#if CONFIG_CODEC == SWCODEC + const char *codec_root_fn; /* root codec filename (sans _enc and .codec) */ +#ifdef HAVE_RECORDING + const char *codec_enc_root_fn; /* filename of encoder codec */ +#endif +#endif + bool (*parse_func)(int fd, struct mp3entry *id3); /* return true on success */ + const char *ext_list; /* NULL terminated extension + list for type with the first as + the default for recording */ +}; + +/* database of labels and codecs. add formats per above enum */ +extern const struct afmt_entry audio_formats[AFMT_NUM_CODECS]; + +#if MEMORYSIZE > 2 +#define ID3V2_BUF_SIZE 900 +#define ID3V2_MAX_ITEM_SIZE 240 +#else +#define ID3V2_BUF_SIZE 300 +#define ID3V2_MAX_ITEM_SIZE 90 +#endif + +enum { + ID3_VER_1_0 = 1, + ID3_VER_1_1, + ID3_VER_2_2, + ID3_VER_2_3, + ID3_VER_2_4 +}; + +#ifdef HAVE_ALBUMART +enum mp3_aa_type { + AA_TYPE_UNSYNC = -1, + AA_TYPE_UNKNOWN, + AA_TYPE_BMP, + AA_TYPE_PNG, + AA_TYPE_JPG, +}; + +struct mp3_albumart { + enum mp3_aa_type type; + int size; + off_t pos; +}; +#endif + +enum character_encoding { + CHAR_ENC_ISO_8859_1 = 1, + CHAR_ENC_UTF_8, + CHAR_ENC_UTF_16_LE, + CHAR_ENC_UTF_16_BE, +}; + +/* cache embedded cuesheet details */ +struct embedded_cuesheet { + int size; + off_t pos; + enum character_encoding encoding; +}; + +struct mp3entry { + char path[MAX_PATH]; + char* title; + char* artist; + char* album; + char* genre_string; + char* disc_string; + char* track_string; + char* year_string; + char* composer; + char* comment; + char* albumartist; + char* grouping; + int discnum; + int tracknum; + int layer; + int year; + unsigned char id3version; + unsigned int codectype; + unsigned int bitrate; + unsigned long frequency; + unsigned long id3v2len; + unsigned long id3v1len; + unsigned long first_frame_offset; /* Byte offset to first real MP3 frame. + Used for skipping leading garbage to + avoid gaps between tracks. */ + unsigned long filesize; /* without headers; in bytes */ + unsigned long length; /* song length in ms */ + unsigned long elapsed; /* ms played */ + + int lead_trim; /* Number of samples to skip at the beginning */ + int tail_trim; /* Number of samples to remove from the end */ + + /* Added for Vorbis, used by mp4 parser as well. */ + unsigned long samples; /* number of samples in track */ + + /* MP3 stream specific info */ + unsigned long frame_count; /* number of frames in the file (if VBR) */ + + /* Used for A52/AC3 */ + unsigned long bytesperframe; /* number of bytes per frame (if CBR) */ + + /* Xing VBR fields */ + bool vbr; + bool has_toc; /* True if there is a VBR header in the file */ + unsigned char toc[100]; /* table of contents */ + + /* Added for ATRAC3 */ + unsigned int channels; /* Number of channels in the stream */ + unsigned int extradata_size; /* Size (in bytes) of the codec's extradata from the container */ + + /* Added for AAC HE SBR */ + bool needs_upsampling_correction; /* flag used by aac codec */ + + /* these following two fields are used for local buffering */ + char id3v2buf[ID3V2_BUF_SIZE]; + char id3v1buf[4][92]; + + /* resume related */ + unsigned long offset; /* bytes played */ + int index; /* playlist index */ + +#ifdef HAVE_TAGCACHE + unsigned char autoresumable; /* caches result of autoresumable() */ + + /* runtime database fields */ + long tagcache_idx; /* 0=invalid, otherwise idx+1 */ + int rating; + int score; + long playcount; + long lastplayed; + long playtime; +#endif + + /* replaygain support */ +#if CONFIG_CODEC == SWCODEC + long track_level; /* holds the level in dB * (1< ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include "string-extra.h" +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "replaygain.h" + +/* Read a string from the file. Read up to size bytes, or, if eos != -1, + * until the eos character is found (eos is not stored in buf, unless it is + * nil). Writes up to buf_size chars to buf, always terminating with a nil. + * Returns number of chars read or -1 on read error. + */ +long read_string(int fd, char* buf, long buf_size, int eos, long size) +{ + long read_bytes = 0; + char c; + + while (size != 0) + { + if (read(fd, &c, 1) != 1) + { + read_bytes = -1; + break; + } + + read_bytes++; + size--; + + if ((eos != -1) && (eos == (unsigned char) c)) + { + break; + } + + if (buf_size > 1) + { + *buf++ = c; + buf_size--; + } + } + + *buf = 0; + return read_bytes; +} +/* Read an unsigned 8-bit integer from a file. */ +int read_uint8(int fd, uint8_t* buf) +{ + size_t n; + + n = read(fd, (char*) buf, 1); + return n; +} + +#ifdef ROCKBOX_LITTLE_ENDIAN +/* Read an unsigned 16-bit integer from a big-endian file. */ +int read_uint16be(int fd, uint16_t* buf) +{ + size_t n; + + n = read(fd, (char*) buf, 2); + *buf = betoh16(*buf); + return n; +} +/* Read an unsigned 32-bit integer from a big-endian file. */ +int read_uint32be(int fd, uint32_t* buf) +{ + size_t n; + + n = read(fd, (char*) buf, 4); + *buf = betoh32(*buf); + return n; +} +/* Read an unsigned 64-bit integer from a big-endian file. */ +int read_uint64be(int fd, uint64_t* buf) +{ + size_t n; + uint8_t data[8]; + int i; + + n = read(fd, data, 8); + + for (i=0, *buf=0; i<=7; i++) { + *buf <<= 8; + *buf |= data[i]; + } + return n; +} +#else +/* Read unsigned integers from a little-endian file. */ +int read_uint16le(int fd, uint16_t* buf) +{ + size_t n; + + n = read(fd, (char*) buf, 2); + *buf = letoh16(*buf); + return n; +} +int read_uint32le(int fd, uint32_t* buf) +{ + size_t n; + + n = read(fd, (char*) buf, 4); + *buf = letoh32(*buf); + return n; +} +int read_uint64le(int fd, uint64_t* buf) +{ + size_t n; + uint8_t data[8]; + int i; + + n = read(fd, data, 8); + + for (i=7, *buf=0; i>=0; i--) { + *buf <<= 8; + *buf |= data[i]; + } + + return n; +} +#endif + +/* Read an unaligned 64-bit little endian unsigned integer from buffer. */ +uint64_t get_uint64_le(void* buf) +{ + unsigned char* p = (unsigned char*) buf; + + return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24) | ((uint64_t)p[4] << 32) | + ((uint64_t)p[5] << 40) | ((uint64_t)p[6] << 48) | ((uint64_t)p[7] << 56); +} + +/* Read an unaligned 32-bit little endian long from buffer. */ +uint32_t get_long_le(void* buf) +{ + unsigned char* p = (unsigned char*) buf; + + return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); +} + +/* Read an unaligned 16-bit little endian short from buffer. */ +uint16_t get_short_le(void* buf) +{ + unsigned char* p = (unsigned char*) buf; + + return p[0] | (p[1] << 8); +} + +/* Read an unaligned 32-bit big endian long from buffer. */ +uint32_t get_long_be(void* buf) +{ + unsigned char* p = (unsigned char*) buf; + + return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; +} + +/* Read an unaligned 16-bit little endian short from buffer. */ +uint16_t get_short_be(void* buf) +{ + unsigned char* p = (unsigned char*) buf; + + return (p[0] << 8) | p[1]; +} + +/* Read an unaligned 32-bit little endian long from buffer. */ +int32_t get_slong(void* buf) +{ + unsigned char* p = (unsigned char*) buf; + + return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); +} + +uint32_t get_itunes_int32(char* value, int count) +{ + static const char hexdigits[] = "0123456789ABCDEF"; + const char* c; + int r = 0; + + while (count-- > 0) + { + while (isspace(*value)) + { + value++; + } + + while (*value && !isspace(*value)) + { + value++; + } + } + + while (isspace(*value)) + { + value++; + } + + while (*value && ((c = strchr(hexdigits, toupper(*value))) != NULL)) + { + r = (r << 4) | (c - hexdigits); + value++; + } + + return r; +} + +/* Skip an ID3v2 tag if it can be found. We assume the tag is located at the + * start of the file, which should be true in all cases where we need to skip it. + * Returns true if successfully skipped or not skipped, and false if + * something went wrong while skipping. + */ +bool skip_id3v2(int fd, struct mp3entry *id3) +{ + char buf[4]; + + read(fd, buf, 4); + if (memcmp(buf, "ID3", 3) == 0) + { + /* We have found an ID3v2 tag at the start of the file - find its + length and then skip it. */ + if ((id3->first_frame_offset = getid3v2len(fd)) == 0) + return false; + + if ((lseek(fd, id3->first_frame_offset, SEEK_SET) < 0)) + return false; + + return true; + } else { + lseek(fd, 0, SEEK_SET); + id3->first_frame_offset = 0; + return true; + } +} + +/* Parse the tag (the name-value pair) and fill id3 and buffer accordingly. + * String values to keep are written to buf. Returns number of bytes written + * to buf (including end nil). + */ +long parse_tag(const char* name, char* value, struct mp3entry* id3, + char* buf, long buf_remaining, enum tagtype type) +{ + long len = 0; + char** p; + + if ((((strcasecmp(name, "track") == 0) && (type == TAGTYPE_APE))) + || ((strcasecmp(name, "tracknumber") == 0) && (type == TAGTYPE_VORBIS))) + { + id3->tracknum = atoi(value); + p = &(id3->track_string); + } + else if (strcasecmp(name, "discnumber") == 0 || strcasecmp(name, "disc") == 0) + { + id3->discnum = atoi(value); + p = &(id3->disc_string); + } + else if (((strcasecmp(name, "year") == 0) && (type == TAGTYPE_APE)) + || ((strcasecmp(name, "date") == 0) && (type == TAGTYPE_VORBIS))) + { + /* Date's can be in any format in Vorbis. However most of them + * are in ISO8601 format so if we try and parse the first part + * of the tag as a number, we should get the year. If we get crap, + * then act like we never parsed it. + */ + id3->year = atoi(value); + if (id3->year < 1900) + { /* yeah, not likely */ + id3->year = 0; + } + p = &(id3->year_string); + } + else if (strcasecmp(name, "title") == 0) + { + p = &(id3->title); + } + else if (strcasecmp(name, "artist") == 0) + { + p = &(id3->artist); + } + else if (strcasecmp(name, "album") == 0) + { + p = &(id3->album); + } + else if (strcasecmp(name, "genre") == 0) + { + p = &(id3->genre_string); + } + else if (strcasecmp(name, "composer") == 0) + { + p = &(id3->composer); + } + else if (strcasecmp(name, "comment") == 0) + { + p = &(id3->comment); + } + else if (strcasecmp(name, "albumartist") == 0) + { + p = &(id3->albumartist); + } + else if (strcasecmp(name, "album artist") == 0) + { + p = &(id3->albumartist); + } + else if (strcasecmp(name, "ensemble") == 0) + { + p = &(id3->albumartist); + } + else if (strcasecmp(name, "grouping") == 0) + { + p = &(id3->grouping); + } + else if (strcasecmp(name, "content group") == 0) + { + p = &(id3->grouping); + } + else if (strcasecmp(name, "contentgroup") == 0) + { + p = &(id3->grouping); + } + else if (strcasecmp(name, "musicbrainz_trackid") == 0 + || strcasecmp(name, "http://musicbrainz.org") == 0 ) + { + p = &(id3->mb_track_id); + } + else + { + parse_replaygain(name, value, id3); + p = NULL; + } + + /* Do not overwrite already available metadata. Especially when reading + * tags with e.g. multiple genres / artists. This way only the first + * of multiple entries is used, all following are dropped. */ + if (p!=NULL && *p==NULL) + { + len = strlen(value); + len = MIN(len, buf_remaining - 1); + len = MIN(len, ID3V2_MAX_ITEM_SIZE); /* Limit max. item size. */ + + if (len > 0) + { + len++; + strlcpy(buf, value, len); + *p = buf; + } + else + { + len = 0; + } + } + + return len; +} diff --git a/lib/rbcodec/metadata/metadata_common.h b/lib/rbcodec/metadata/metadata_common.h new file mode 100644 index 0000000000..db91729de4 --- /dev/null +++ b/lib/rbcodec/metadata/metadata_common.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include "metadata.h" + +#ifdef ROCKBOX_BIG_ENDIAN +#define IS_BIG_ENDIAN 1 +#else +#define IS_BIG_ENDIAN 0 +#endif + +#define TAG_NAME_LENGTH 32 +#define TAG_VALUE_LENGTH 128 + +#define FOURCC(a,b,c,d) (((a)<<24) | ((b) << 16) | ((c) << 8) | (d)) + +enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS }; + +bool read_ape_tags(int fd, struct mp3entry* id3); +long read_vorbis_tags(int fd, struct mp3entry *id3, + long tag_remaining); + +bool skip_id3v2(int fd, struct mp3entry *id3); +long read_string(int fd, char* buf, long buf_size, int eos, long size); + +int read_uint8(int fd, uint8_t* buf); +#ifdef ROCKBOX_BIG_ENDIAN +#define read_uint16be(fd,buf) read((fd), (buf), 2) +#define read_uint32be(fd,buf) read((fd), (buf), 4) +#define read_uint64be(fd,buf) read((fd), (buf), 8) +int read_uint16le(int fd, uint16_t* buf); +int read_uint32le(int fd, uint32_t* buf); +int read_uint64le(int fd, uint64_t* buf); +#else +int read_uint16be(int fd, uint16_t* buf); +int read_uint32be(int fd, uint32_t* buf); +int read_uint64be(int fd, uint64_t* buf); +#define read_uint16le(fd,buf) read((fd), (buf), 2) +#define read_uint32le(fd,buf) read((fd), (buf), 4) +#define read_uint64le(fd,buf) read((fd), (buf), 8) +#endif + +uint64_t get_uint64_le(void* buf); +uint32_t get_long_le(void* buf); +uint16_t get_short_le(void* buf); +uint32_t get_long_be(void* buf); +uint16_t get_short_be(void* buf); +int32_t get_slong(void* buf); +uint32_t get_itunes_int32(char* value, int count); +long parse_tag(const char* name, char* value, struct mp3entry* id3, + char* buf, long buf_remaining, enum tagtype type); diff --git a/lib/rbcodec/metadata/metadata_parsers.h b/lib/rbcodec/metadata/metadata_parsers.h new file mode 100644 index 0000000000..304e393538 --- /dev/null +++ b/lib/rbcodec/metadata/metadata_parsers.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ + +#if CONFIG_CODEC == SWCODEC +char* id3_get_num_genre(unsigned int genre_num); +#endif +int getid3v2len(int fd); +bool setid3v1title(int fd, struct mp3entry *entry); +void setid3v2title(int fd, struct mp3entry *entry); +bool get_mp3_metadata(int fd, struct mp3entry* id3); +#if CONFIG_CODEC == SWCODEC +bool get_adx_metadata(int fd, struct mp3entry* id3); +bool get_aiff_metadata(int fd, struct mp3entry* id3); +bool get_flac_metadata(int fd, struct mp3entry* id3); +bool get_mp4_metadata(int fd, struct mp3entry* id3); +bool get_monkeys_metadata(int fd, struct mp3entry* id3); +bool get_musepack_metadata(int fd, struct mp3entry *id3); +bool get_sid_metadata(int fd, struct mp3entry* id3); +bool get_mod_metadata(int fd, struct mp3entry* id3); +bool get_spc_metadata(int fd, struct mp3entry* id3); +bool get_ogg_metadata(int fd, struct mp3entry* id3); +bool get_wave_metadata(int fd, struct mp3entry* id3); +bool get_wavpack_metadata(int fd, struct mp3entry* id3); +bool get_a52_metadata(int fd, struct mp3entry* id3); +bool get_asf_metadata(int fd, struct mp3entry* id3); +bool get_asap_metadata(int fd, struct mp3entry* id3); +bool get_rm_metadata(int fd, struct mp3entry* id3); +bool get_nsf_metadata(int fd, struct mp3entry* id3); +bool get_oma_metadata(int fd, struct mp3entry* id3); +bool get_smaf_metadata(int fd, struct mp3entry* id3); +bool get_au_metadata(int fd, struct mp3entry* id3); +bool get_vox_metadata(int fd, struct mp3entry* id3); +bool get_wave64_metadata(int fd, struct mp3entry* id3); +bool get_tta_metadata(int fd, struct mp3entry* id3); +bool get_ay_metadata(int fd, struct mp3entry* id3); +bool get_gbs_metadata(int fd, struct mp3entry* id3); +bool get_hes_metadata(int fd, struct mp3entry* id3); +bool get_sgc_metadata(int fd, struct mp3entry* id3); +bool get_vgm_metadata(int fd, struct mp3entry* id3); +bool get_kss_metadata(int fd, struct mp3entry* id3); +#endif /* CONFIG_CODEC == SWCODEC */ diff --git a/lib/rbcodec/metadata/mod.c b/lib/rbcodec/metadata/mod.c new file mode 100644 index 0000000000..de76823e91 --- /dev/null +++ b/lib/rbcodec/metadata/mod.c @@ -0,0 +1,103 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +#define MODULEHEADERSIZE 0x438 + +bool get_mod_metadata(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char *buf = id3->id3v2buf; + unsigned char id[4]; + bool is_mod_file = false; + + /* Seek to file begin */ + if (lseek(fd, 0, SEEK_SET) < 0) + return false; + /* Use id3v2buf as buffer for the track name */ + if (read(fd, buf, sizeof(id3->id3v2buf)) < (ssize_t)sizeof(id3->id3v2buf)) + return false; + /* Seek to MOD ID position */ + if (lseek(fd, MODULEHEADERSIZE, SEEK_SET) < 0) + return false; + /* Read MOD ID */ + if (read(fd, id, sizeof(id)) < (ssize_t)sizeof(id)) + return false; + + /* Mod type checking based on MikMod */ + /* Protracker and variants */ + if ((!memcmp(id, "M.K.", 4)) || (!memcmp(id, "M!K!", 4))) { + is_mod_file = true; + } + + /* Star Tracker */ + if (((!memcmp(id, "FLT", 3)) || (!memcmp(id, "EXO", 3))) && + (isdigit(id[3]))) { + char numchn = id[3] - '0'; + if (numchn == 4 || numchn == 8) + is_mod_file = true; + } + + /* Oktalyzer (Amiga) */ + if (!memcmp(id, "OKTA", 4)) { + is_mod_file = true; + } + + /* Oktalyser (Atari) */ + if (!memcmp(id, "CD81", 4)) { + is_mod_file = true; + } + + /* Fasttracker */ + if ((!memcmp(id + 1, "CHN", 3)) && (isdigit(id[0]))) { + is_mod_file = true; + } + /* Fasttracker or Taketracker */ + if (((!memcmp(id + 2, "CH", 2)) || (!memcmp(id + 2, "CN", 2))) + && (isdigit(id[0])) && (isdigit(id[1]))) { + is_mod_file = true; + } + + /* Don't try to play if we can't find a known mod type + * (there are mod files which have nothing to do with music) */ + if (!is_mod_file) + return false; + + id3->title = id3->id3v2buf; /* Point title to previous read ID3 buffer. */ + id3->bitrate = filesize(fd)/1024; /* size in kb */ + id3->frequency = 44100; + id3->length = 120*1000; + id3->vbr = false; + id3->filesize = filesize(fd); + + return true; +} + diff --git a/lib/rbcodec/metadata/monkeys.c b/lib/rbcodec/metadata/monkeys.c new file mode 100644 index 0000000000..4aff1412aa --- /dev/null +++ b/lib/rbcodec/metadata/monkeys.c @@ -0,0 +1,97 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" + +bool get_monkeys_metadata(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + unsigned char* header; + bool rc = false; + uint32_t descriptorlength; + uint32_t totalsamples; + uint32_t blocksperframe, finalframeblocks, totalframes; + int fileversion; + + lseek(fd, 0, SEEK_SET); + + if (read(fd, buf, 4) < 4) + { + return rc; + } + + if (memcmp(buf, "MAC ", 4) != 0) + { + return rc; + } + + read(fd, buf + 4, MAX_PATH - 4); + + fileversion = get_short_le(buf+4); + if (fileversion < 3970) + { + /* Not supported */ + return false; + } + + if (fileversion >= 3980) + { + descriptorlength = get_long_le(buf+8); + + header = buf + descriptorlength; + + blocksperframe = get_long_le(header+4); + finalframeblocks = get_long_le(header+8); + totalframes = get_long_le(header+12); + id3->frequency = get_long_le(header+20); + } + else + { + /* v3.95 and later files all have a fixed framesize */ + blocksperframe = 73728 * 4; + + finalframeblocks = get_long_le(buf+28); + totalframes = get_long_le(buf+24); + id3->frequency = get_long_le(buf+12); + } + + id3->vbr = true; /* All APE files are VBR */ + id3->filesize = filesize(fd); + + totalsamples = finalframeblocks; + if (totalframes > 1) + totalsamples += blocksperframe * (totalframes-1); + + id3->length = ((int64_t) totalsamples * 1000) / id3->frequency; + id3->bitrate = (id3->filesize * 8) / id3->length; + + read_ape_tags(fd, id3); + return true; +} diff --git a/lib/rbcodec/metadata/mp3.c b/lib/rbcodec/metadata/mp3.c new file mode 100644 index 0000000000..feb1a52f77 --- /dev/null +++ b/lib/rbcodec/metadata/mp3.c @@ -0,0 +1,193 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Daniel Stenberg + * + * 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. + * + ****************************************************************************/ +/* + * Parts of this code has been stolen from the Ample project and was written + * by David H�deman. It has since been extended and enhanced pretty much by + * all sorts of friendly Rockbox people. + * + */ + + /* tagResolver and associated code copyright 2003 Thomas Paul Diffenbach + */ + +#include +#include +#include +#include +#include "string-extra.h" +#include "config.h" +#include "file.h" +#include "logf.h" + +#include "system.h" +#include "metadata.h" +#include "mp3data.h" +#include "metadata_common.h" +#include "metadata_parsers.h" + +/* + * Calculates the length (in milliseconds) of an MP3 file. + * + * Modified to only use integers. + * + * Arguments: file - the file to calculate the length upon + * entry - the entry to update with the length + * + * Returns: the song length in milliseconds, + * 0 means that it couldn't be calculated + */ +static int getsonglength(int fd, struct mp3entry *entry) +{ + unsigned long filetime = 0; + struct mp3info info; + long bytecount; + + /* Start searching after ID3v2 header */ + if(-1 == lseek(fd, entry->id3v2len, SEEK_SET)) + return 0; + + bytecount = get_mp3file_info(fd, &info); + + logf("Space between ID3V2 tag and first audio frame: 0x%lx bytes", + bytecount); + + if(bytecount < 0) + return -1; + + bytecount += entry->id3v2len; + + /* Validate byte count, in case the file has been edited without + * updating the header. + */ + if (info.byte_count) + { + const unsigned long expected = entry->filesize - entry->id3v1len + - entry->id3v2len; + const unsigned long diff = MAX(10240, info.byte_count / 20); + + if ((info.byte_count > expected + diff) + || (info.byte_count < expected - diff)) + { + logf("Note: info.byte_count differs from expected value by " + "%ld bytes", labs((long) (expected - info.byte_count))); + info.byte_count = 0; + info.frame_count = 0; + info.file_time = 0; + info.enc_padding = 0; + + /* Even if the bitrate was based on "known bad" values, it + * should still be better for VBR files than using the bitrate + * of the first audio frame. + */ + } + } + + entry->bitrate = info.bitrate; + entry->frequency = info.frequency; + entry->layer = info.layer; + switch(entry->layer) { +#if CONFIG_CODEC==SWCODEC + case 0: + entry->codectype=AFMT_MPA_L1; + break; +#endif + case 1: + entry->codectype=AFMT_MPA_L2; + break; + case 2: + entry->codectype=AFMT_MPA_L3; + break; + } + + /* If the file time hasn't been established, this may be a fixed + rate MP3, so just use the default formula */ + + filetime = info.file_time; + + if(filetime == 0) + { + /* Prevent a division by zero */ + if (info.bitrate < 8) + filetime = 0; + else + filetime = (entry->filesize - bytecount) / (info.bitrate / 8); + /* bitrate is in kbps so this delivers milliseconds. Doing bitrate / 8 + * instead of filesize * 8 is exact, because mpeg audio bitrates are + * always multiples of 8, and it avoids overflows. */ + } + + entry->frame_count = info.frame_count; + + entry->vbr = info.is_vbr; + entry->has_toc = info.has_toc; + +#if CONFIG_CODEC==SWCODEC + if (!entry->lead_trim) + entry->lead_trim = info.enc_delay; + if (!entry->tail_trim) + entry->tail_trim = info.enc_padding; +#endif + + memcpy(entry->toc, info.toc, sizeof(info.toc)); + + /* Update the seek point for the first playable frame */ + entry->first_frame_offset = bytecount; + logf("First frame is at %lx", entry->first_frame_offset); + + return filetime; +} + +/* + * Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc) + * about an MP3 file and updates it's entry accordingly. + * + Note, that this returns true for successful, false for error! */ +bool get_mp3_metadata(int fd, struct mp3entry *entry) +{ + entry->title = NULL; + entry->filesize = filesize(fd); + entry->id3v2len = getid3v2len(fd); + entry->tracknum = 0; + entry->discnum = 0; + + if (entry->id3v2len) + setid3v2title(fd, entry); + int len = getsonglength(fd, entry); + if (len < 0) + return false; + entry->length = len; + + /* Subtract the meta information from the file size to get + the true size of the MP3 stream */ + entry->filesize -= entry->first_frame_offset; + + /* only seek to end of file if no id3v2 tags were found */ + if (!entry->id3v2len) { + setid3v1title(fd, entry); + } + + if(!entry->length || (entry->filesize < 8 )) + /* no song length or less than 8 bytes is hereby considered to be an + invalid mp3 and won't be played by us! */ + return false; + + return true; +} diff --git a/lib/rbcodec/metadata/mp3data.c b/lib/rbcodec/metadata/mp3data.c new file mode 100644 index 0000000000..13ff0a87a7 --- /dev/null +++ b/lib/rbcodec/metadata/mp3data.c @@ -0,0 +1,849 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Daniel Stenberg + * + * 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. + * + ****************************************************************************/ + +/* + * Parts of this code has been stolen from the Ample project and was written + * by David Härdeman. It has since been extended and enhanced pretty much by + * all sorts of friendly Rockbox people. + * + * A nice reference for MPEG header info: + * http://rockbox.haxx.se/docs/mpeghdr.html + * + */ + +#include +#include +#include +#include +#include +#include "debug.h" +#include "logf.h" +#include "mp3data.h" +#include "file.h" +#include "system.h" + +//#define DEBUG_VERBOSE + +#ifdef DEBUG_VERBOSE +#define VDEBUGF DEBUGF +#else +#define VDEBUGF(...) do { } while(0) +#endif + +#define SYNC_MASK (0x7ffL << 21) +#define VERSION_MASK (3L << 19) +#define LAYER_MASK (3L << 17) +#define PROTECTION_MASK (1L << 16) +#define BITRATE_MASK (0xfL << 12) +#define SAMPLERATE_MASK (3L << 10) +#define PADDING_MASK (1L << 9) +#define PRIVATE_MASK (1L << 8) +#define CHANNELMODE_MASK (3L << 6) +#define MODE_EXT_MASK (3L << 4) +#define COPYRIGHT_MASK (1L << 3) +#define ORIGINAL_MASK (1L << 2) +#define EMPHASIS_MASK (3L) + +/* Maximum number of bytes needed by Xing/Info/VBRI parser. */ +#define VBR_HEADER_MAX_SIZE (180) + +/* MPEG Version table, sorted by version index */ +static const signed char version_table[4] = { + MPEG_VERSION2_5, -1, MPEG_VERSION2, MPEG_VERSION1 +}; + +/* Bitrate table for mpeg audio, indexed by row index and birate index */ +static const short bitrates[5][16] = { + {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, /* V1 L1 */ + {0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,0}, /* V1 L2 */ + {0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,0}, /* V1 L3 */ + {0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256,0}, /* V2 L1 */ + {0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0} /* V2 L2+L3 */ +}; + +/* Bitrate pointer table, indexed by version and layer */ +static const short *bitrate_table[3][3] = +{ + {bitrates[0], bitrates[1], bitrates[2]}, + {bitrates[3], bitrates[4], bitrates[4]}, + {bitrates[3], bitrates[4], bitrates[4]} +}; + +/* Sampling frequency table, indexed by version and frequency index */ +static const unsigned short freq_table[3][3] = +{ + {44100, 48000, 32000}, /* MPEG Version 1 */ + {22050, 24000, 16000}, /* MPEG version 2 */ + {11025, 12000, 8000}, /* MPEG version 2.5 */ +}; + +unsigned long bytes2int(unsigned long b0, unsigned long b1, + unsigned long b2, unsigned long b3) +{ + return (b0 & 0xFF) << (3*8) | + (b1 & 0xFF) << (2*8) | + (b2 & 0xFF) << (1*8) | + (b3 & 0xFF) << (0*8); +} + +/* check if 'head' is a valid mp3 frame header */ +static bool is_mp3frameheader(unsigned long head) +{ + if ((head & SYNC_MASK) != (unsigned long)SYNC_MASK) /* bad sync? */ + return false; + if ((head & VERSION_MASK) == (1L << 19)) /* bad version? */ + return false; + if (!(head & LAYER_MASK)) /* no layer? */ + return false; +#if CONFIG_CODEC != SWCODEC + /* The MAS can't decode layer 1, so treat layer 1 data as invalid */ + if ((head & LAYER_MASK) == LAYER_MASK) + return false; +#endif + if ((head & BITRATE_MASK) == BITRATE_MASK) /* bad bitrate? */ + return false; + if (!(head & BITRATE_MASK)) /* no bitrate? */ + return false; + if ((head & SAMPLERATE_MASK) == SAMPLERATE_MASK) /* bad sample rate? */ + return false; + + return true; +} + +static bool mp3headerinfo(struct mp3info *info, unsigned long header) +{ + int bitindex, freqindex; + + /* MPEG Audio Version */ + if ((header & VERSION_MASK) >> 19 >= sizeof(version_table)) + return false; + + info->version = version_table[(header & VERSION_MASK) >> 19]; + if (info->version < 0) + return false; + + /* Layer */ + info->layer = 3 - ((header & LAYER_MASK) >> 17); + if (info->layer == 3) + return false; + +/* Rockbox: not used + info->protection = (header & PROTECTION_MASK) ? true : false; +*/ + + /* Bitrate */ + bitindex = (header & BITRATE_MASK) >> 12; + info->bitrate = bitrate_table[info->version][info->layer][bitindex]; + if(info->bitrate == 0) + return false; + + /* Sampling frequency */ + freqindex = (header & SAMPLERATE_MASK) >> 10; + if (freqindex == 3) + return false; + info->frequency = freq_table[info->version][freqindex]; + + info->padding = (header & PADDING_MASK) ? 1 : 0; + + /* Calculate number of bytes, calculation depends on layer */ + if (info->layer == 0) { + info->frame_samples = 384; + info->frame_size = (12000 * info->bitrate / info->frequency + + info->padding) * 4; + } + else { + if ((info->version > MPEG_VERSION1) && (info->layer == 2)) + info->frame_samples = 576; + else + info->frame_samples = 1152; + info->frame_size = (1000/8) * info->frame_samples * info->bitrate + / info->frequency + info->padding; + } + + /* Frametime fraction denominator */ + if (freqindex != 0) { /* 48/32/24/16/12/8 kHz */ + info->ft_den = 1; /* integer number of milliseconds */ + } + else { /* 44.1/22.05/11.025 kHz */ + if (info->layer == 0) /* layer 1 */ + info->ft_den = 147; + else /* layer 2+3 */ + info->ft_den = 49; + } + /* Frametime fraction numerator */ + info->ft_num = 1000 * info->ft_den * info->frame_samples / info->frequency; + + info->channel_mode = (header & CHANNELMODE_MASK) >> 6; +/* Rockbox: not used + info->mode_extension = (header & MODE_EXT_MASK) >> 4; + info->emphasis = header & EMPHASIS_MASK; +*/ + VDEBUGF( "Header: %08lx, Ver %d, lay %d, bitr %d, freq %ld, " + "chmode %d, bytes: %d time: %d/%d\n", + header, info->version, info->layer+1, info->bitrate, + info->frequency, info->channel_mode, + info->frame_size, info->ft_num, info->ft_den); + return true; +} + +static bool headers_have_same_type(unsigned long header1, + unsigned long header2) +{ + /* Compare MPEG version, layer and sampling frequency. If header1 is zero + * it is assumed both frame headers are of same type. */ + unsigned int mask = SYNC_MASK | VERSION_MASK | LAYER_MASK | SAMPLERATE_MASK; + header1 &= mask; + header2 &= mask; + return header1 ? (header1 == header2) : true; +} + +/* Helper function to read 4-byte in big endian format. */ +static void read_uint32be_mp3data(int fd, unsigned long *data) +{ +#ifdef ROCKBOX_BIG_ENDIAN + (void)read(fd, (char*)data, 4); +#else + (void)read(fd, (char*)data, 4); + *data = betoh32(*data); +#endif +} + +static unsigned long __find_next_frame(int fd, long *offset, long max_offset, + unsigned long reference_header, + int(*getfunc)(int fd, unsigned char *c), + bool single_header) +{ + unsigned long header=0; + unsigned char tmp; + long pos = 0; + + /* We will search until we find two consecutive MPEG frame headers with + * the same MPEG version, layer and sampling frequency. The first header + * of this pair is assumed to be the first valid MPEG frame header of the + * whole stream. */ + do { + /* Read 1 new byte. */ + header <<= 8; + if (!getfunc(fd, &tmp)) + return 0; + header |= tmp; + pos++; + + /* Abort if max_offset is reached. Stop parsing. */ + if (max_offset > 0 && pos > max_offset) + return 0; + + if (is_mp3frameheader(header)) { + if (single_header) { + /* We search for one _single_ valid header that has the same + * type as the reference_header (if reference_header != 0). + * In this case we are finished. */ + if (headers_have_same_type(reference_header, header)) + break; + } else { + /* The current header is valid. Now gather the frame size, + * seek to this byte position and check if there is another + * valid MPEG frame header of the same type. */ + struct mp3info info; + + /* Gather frame size from given header and seek to next + * frame header. */ + mp3headerinfo(&info, header); + lseek(fd, info.frame_size-4, SEEK_CUR); + + /* Read possible next frame header and seek back to last frame + * headers byte position. */ + reference_header = 0; + read_uint32be_mp3data(fd, &reference_header); + // + lseek(fd, -info.frame_size, SEEK_CUR); + + /* If the current header is of the same type as the previous + * header we are finished. */ + if (headers_have_same_type(header, reference_header)) + break; + } + } + + } while (true); + + *offset = pos - 4; + + if(*offset) + VDEBUGF("Warning: skipping %ld bytes of garbage\n", *offset); + + return header; +} + +static int fileread(int fd, unsigned char *c) +{ + return read(fd, c, 1); +} + +unsigned long find_next_frame(int fd, + long *offset, + long max_offset, + unsigned long reference_header) +{ + return __find_next_frame(fd, offset, max_offset, reference_header, + fileread, true); +} + +#ifndef __PCTOOL__ +static int fnf_read_index; +static int fnf_buf_len; +static unsigned char *fnf_buf; + +static int buf_getbyte(int fd, unsigned char *c) +{ + if(fnf_read_index < fnf_buf_len) + { + *c = fnf_buf[fnf_read_index++]; + return 1; + } + else + { + fnf_buf_len = read(fd, fnf_buf, fnf_buf_len); + if(fnf_buf_len < 0) + return -1; + + fnf_read_index = 0; + + if(fnf_buf_len > 0) + { + *c = fnf_buf[fnf_read_index++]; + return 1; + } + else + return 0; + } + return 0; +} + +static int buf_seek(int fd, int len) +{ + fnf_read_index += len; + if(fnf_read_index > fnf_buf_len) + { + len = fnf_read_index - fnf_buf_len; + + fnf_buf_len = read(fd, fnf_buf, fnf_buf_len); + if(fnf_buf_len < 0) + return -1; + + fnf_read_index = 0; + fnf_read_index += len; + } + + if(fnf_read_index > fnf_buf_len) + { + return -1; + } + else + return 0; +} + +static void buf_init(unsigned char* buf, size_t buflen) +{ + fnf_buf = buf; + fnf_buf_len = buflen; + fnf_read_index = 0; +} + +static unsigned long buf_find_next_frame(int fd, long *offset, long max_offset) +{ + return __find_next_frame(fd, offset, max_offset, 0, buf_getbyte, true); +} + +static size_t mem_buflen; +static unsigned char* mem_buf; +static size_t mem_pos; +static int mem_cnt; +static int mem_maxlen; + +static int mem_getbyte(int dummy, unsigned char *c) +{ + (void)dummy; + + *c = mem_buf[mem_pos++]; + if(mem_pos >= mem_buflen) + mem_pos = 0; + + if(mem_cnt++ >= mem_maxlen) + return 0; + else + return 1; +} + +unsigned long mem_find_next_frame(int startpos, + long *offset, + long max_offset, + unsigned long reference_header, + unsigned char* buf, size_t buflen) +{ + mem_buf = buf; + mem_buflen = buflen; + mem_pos = startpos; + mem_cnt = 0; + mem_maxlen = max_offset; + + return __find_next_frame(0, offset, max_offset, reference_header, + mem_getbyte, true); +} +#endif + +/* Extract information from a 'Xing' or 'Info' header. */ +static void get_xing_info(struct mp3info *info, unsigned char *buf) +{ + int i = 8; + + /* Is it a VBR file? */ + info->is_vbr = !memcmp(buf, "Xing", 4); + + if (buf[7] & VBR_FRAMES_FLAG) /* Is the frame count there? */ + { + info->frame_count = bytes2int(buf[i], buf[i+1], buf[i+2], buf[i+3]); + if (info->frame_count <= ULONG_MAX / info->ft_num) + info->file_time = info->frame_count * info->ft_num / info->ft_den; + else + info->file_time = info->frame_count / info->ft_den * info->ft_num; + i += 4; + } + + if (buf[7] & VBR_BYTES_FLAG) /* Is byte count there? */ + { + info->byte_count = bytes2int(buf[i], buf[i+1], buf[i+2], buf[i+3]); + i += 4; + } + + if (info->file_time && info->byte_count) + { + if (info->byte_count <= (ULONG_MAX/8)) + info->bitrate = info->byte_count * 8 / info->file_time; + else + info->bitrate = info->byte_count / (info->file_time >> 3); + } + + if (buf[7] & VBR_TOC_FLAG) /* Is table-of-contents there? */ + { + info->has_toc = true; + memcpy( info->toc, buf+i, 100 ); + i += 100; + } + if (buf[7] & VBR_QUALITY_FLAG) + { + /* We don't care about this, but need to skip it */ + i += 4; + } +#if CONFIG_CODEC==SWCODEC + i += 21; + info->enc_delay = ((int)buf[i ] << 4) | (buf[i+1] >> 4); + info->enc_padding = ((int)(buf[i+1]&0xF) << 8) | buf[i+2]; + /* TODO: This sanity checking is rather silly, seeing as how the LAME + header contains a CRC field that can be used to verify integrity. */ + if (!(info->enc_delay >= 0 && info->enc_delay <= 2880 && + info->enc_padding >= 0 && info->enc_padding <= 2*1152)) + { + /* Invalid data */ + info->enc_delay = -1; + info->enc_padding = -1; + } +#endif +} + +/* Extract information from a 'VBRI' header. */ +static void get_vbri_info(struct mp3info *info, unsigned char *buf) +{ + /* We don't parse the TOC, since we don't yet know how to (FIXME) */ + /* + int i, num_offsets, offset = 0; + */ + + info->is_vbr = true; /* Yes, it is a FhG VBR file */ + info->has_toc = false; /* We don't parse the TOC (yet) */ + + info->byte_count = bytes2int(buf[10], buf[11], buf[12], buf[13]); + info->frame_count = bytes2int(buf[14], buf[15], buf[16], buf[17]); + if (info->frame_count <= ULONG_MAX / info->ft_num) + info->file_time = info->frame_count * info->ft_num / info->ft_den; + else + info->file_time = info->frame_count / info->ft_den * info->ft_num; + + if (info->byte_count <= (ULONG_MAX/8)) + info->bitrate = info->byte_count * 8 / info->file_time; + else + info->bitrate = info->byte_count / (info->file_time >> 3); + + VDEBUGF("Frame size (%dkpbs): %d bytes (0x%x)\n", + info->bitrate, info->frame_size, info->frame_size); + VDEBUGF("Frame count: %lx\n", info->frame_count); + VDEBUGF("Byte count: %lx\n", info->byte_count); + + /* We don't parse the TOC, since we don't yet know how to (FIXME) */ + /* + num_offsets = bytes2int(0, 0, buf[18], buf[19]); + VDEBUGF("Offsets: %d\n", num_offsets); + VDEBUGF("Frames/entry: %ld\n", bytes2int(0, 0, buf[24], buf[25])); + + for(i = 0; i < num_offsets; i++) + { + offset += bytes2int(0, 0, buf[26+i*2], buf[27+i*2]);; + VDEBUGF("%03d: %lx\n", i, offset - bytecount,); + } + */ +} + +/* Seek to next mpeg header and extract relevant information. */ +static int get_next_header_info(int fd, long *bytecount, struct mp3info *info, + bool single_header) +{ + long tmp; + unsigned long header = 0; + + header = __find_next_frame(fd, &tmp, 0x20000, 0, fileread, single_header); + if(header == 0) + return -1; + + if(!mp3headerinfo(info, header)) + return -2; + + /* Next frame header is tmp bytes away. */ + *bytecount += tmp; + + return 0; +} + +int get_mp3file_info(int fd, struct mp3info *info) +{ + unsigned char frame[VBR_HEADER_MAX_SIZE], *vbrheader; + long bytecount = 0; + int result, buf_size; + + /* Initialize info and frame */ + memset(info, 0, sizeof(struct mp3info)); + memset(frame, 0, sizeof(frame)); + +#if CONFIG_CODEC==SWCODEC + /* These two are needed for proper LAME gapless MP3 playback */ + info->enc_delay = -1; + info->enc_padding = -1; +#endif + + /* Get the very first single MPEG frame. */ + result = get_next_header_info(fd, &bytecount, info, true); + if(result) + return result; + + /* Read the amount of frame data to the buffer that is required for the + * vbr tag parsing. Skip the rest. */ + buf_size = MIN(info->frame_size-4, (int)sizeof(frame)); + if(read(fd, frame, buf_size) < 0) + return -3; + lseek(fd, info->frame_size - 4 - buf_size, SEEK_CUR); + + /* Calculate position of a possible VBR header */ + if (info->version == MPEG_VERSION1) { + if (info->channel_mode == 3) /* mono */ + vbrheader = frame + 17; + else + vbrheader = frame + 32; + } else { + if (info->channel_mode == 3) /* mono */ + vbrheader = frame + 9; + else + vbrheader = frame + 17; + } + + if (!memcmp(vbrheader, "Xing", 4) || !memcmp(vbrheader, "Info", 4)) + { + VDEBUGF("-- XING header --\n"); + + /* We want to skip the Xing frame when playing the stream */ + bytecount += info->frame_size; + + /* Now get the next frame to read the real info about the mp3 stream */ + result = get_next_header_info(fd, &bytecount, info, false); + if(result) + return result; + + get_xing_info(info, vbrheader); + } + else if (!memcmp(vbrheader, "VBRI", 4)) + { + VDEBUGF("-- VBRI header --\n"); + + /* We want to skip the VBRI frame when playing the stream */ + bytecount += info->frame_size; + + /* Now get the next frame to read the real info about the mp3 stream */ + result = get_next_header_info(fd, &bytecount, info, false); + if(result) + return result; + + get_vbri_info(info, vbrheader); + } + else + { + VDEBUGF("-- No VBR header --\n"); + + /* There was no VBR header found. So, we seek back to beginning and + * search for the first MPEG frame header of the mp3 stream. */ + lseek(fd, -info->frame_size, SEEK_CUR); + result = get_next_header_info(fd, &bytecount, info, false); + if(result) + return result; + } + + return bytecount; +} + +#ifndef __PCTOOL__ +static void long2bytes(unsigned char *buf, long val) +{ + buf[0] = (val >> 24) & 0xff; + buf[1] = (val >> 16) & 0xff; + buf[2] = (val >> 8) & 0xff; + buf[3] = val & 0xff; +} + +int count_mp3_frames(int fd, int startpos, int filesize, + void (*progressfunc)(int), + unsigned char* buf, size_t buflen) +{ + unsigned long header = 0; + struct mp3info info; + int num_frames; + long bytes; + int cnt; + long progress_chunk = filesize / 50; /* Max is 50%, in 1% increments */ + int progress_cnt = 0; + bool is_vbr = false; + int last_bitrate = 0; + int header_template = 0; + + if(lseek(fd, startpos, SEEK_SET) < 0) + return -1; + + buf_init(buf, buflen); + + /* Find out the total number of frames */ + num_frames = 0; + cnt = 0; + + while((header = buf_find_next_frame(fd, &bytes, header_template))) { + mp3headerinfo(&info, header); + + if(!header_template) + header_template = header; + + /* See if this really is a VBR file */ + if(last_bitrate && info.bitrate != last_bitrate) + { + is_vbr = true; + } + last_bitrate = info.bitrate; + + buf_seek(fd, info.frame_size-4); + num_frames++; + if(progressfunc) + { + cnt += bytes + info.frame_size; + if(cnt > progress_chunk) + { + progress_cnt++; + progressfunc(progress_cnt); + cnt = 0; + } + } + } + VDEBUGF("Total number of frames: %d\n", num_frames); + + if(is_vbr) + return num_frames; + else + { + DEBUGF("Not a VBR file\n"); + return 0; + } +} + +static const char cooltext[] = "Rockbox - rocks your box"; + +/* buf needs to be the audio buffer with TOC generation enabled, + and at least MAX_XING_HEADER_SIZE bytes otherwise */ +int create_xing_header(int fd, long startpos, long filesize, + unsigned char *buf, unsigned long num_frames, + unsigned long rec_time, unsigned long header_template, + void (*progressfunc)(int), bool generate_toc, + unsigned char *tempbuf, size_t tempbuflen ) +{ + struct mp3info info; + unsigned char toc[100]; + unsigned long header = 0; + unsigned long xing_header_template = header_template; + unsigned long filepos; + long pos, last_pos; + long j; + long bytes; + int i; + int index; + + DEBUGF("create_xing_header()\n"); + + if(generate_toc) + { + lseek(fd, startpos, SEEK_SET); + buf_init(tempbuf, tempbuflen); + + /* Generate filepos table */ + last_pos = 0; + filepos = 0; + header = 0; + for(i = 0;i < 100;i++) { + /* Calculate the absolute frame number for this seek point */ + pos = i * num_frames / 100; + + /* Advance from the last seek point to this one */ + for(j = 0;j < pos - last_pos;j++) + { + header = buf_find_next_frame(fd, &bytes, header_template); + filepos += bytes; + mp3headerinfo(&info, header); + buf_seek(fd, info.frame_size-4); + filepos += info.frame_size; + + if(!header_template) + header_template = header; + } + + /* Save a header for later use if header_template is empty. + We only save one header, and we want to save one in the + middle of the stream, just in case the first and the last + headers are corrupt. */ + if(!xing_header_template && i == 1) + xing_header_template = header; + + if(progressfunc) + { + progressfunc(50 + i/2); + } + + /* Fill in the TOC entry */ + /* each toc is a single byte indicating how many 256ths of the + * way through the file, is that percent of the way through the + * song. the easy method, filepos*256/filesize, chokes when + * the upper 8 bits of the file position are nonzero + * (i.e. files over 16mb in size). + */ + if (filepos > (ULONG_MAX/256)) + { + /* instead of multiplying filepos by 256, we divide + * filesize by 256. + */ + toc[i] = filepos / (filesize >> 8); + } + else + { + toc[i] = filepos * 256 / filesize; + } + + VDEBUGF("Pos %d: %ld relpos: %ld filepos: %lx tocentry: %x\n", + i, pos, pos-last_pos, filepos, toc[i]); + + last_pos = pos; + } + } + + /* Use the template header and create a new one. + We ignore the Protection bit even if the rest of the stream is + protected. */ + header = xing_header_template & ~(BITRATE_MASK|PROTECTION_MASK|PADDING_MASK); + header |= 8 << 12; /* This gives us plenty of space, 192..576 bytes */ + + if (!mp3headerinfo(&info, header)) + return 0; /* invalid header */ + + if (num_frames == 0 && rec_time) { + /* estimate the number of frames based on the recording time */ + if (rec_time <= ULONG_MAX / info.ft_den) + num_frames = rec_time * info.ft_den / info.ft_num; + else + num_frames = rec_time / info.ft_num * info.ft_den; + } + + /* Clear the frame */ + memset(buf, 0, MAX_XING_HEADER_SIZE); + + /* Write the header to the buffer */ + long2bytes(buf, header); + + /* Calculate position of VBR header */ + if (info.version == MPEG_VERSION1) { + if (info.channel_mode == 3) /* mono */ + index = 21; + else + index = 36; + } + else { + if (info.channel_mode == 3) /* mono */ + index = 13; + else + index = 21; + } + + /* Create the Xing data */ + memcpy(&buf[index], "Xing", 4); + long2bytes(&buf[index+4], (num_frames ? VBR_FRAMES_FLAG : 0) + | (filesize ? VBR_BYTES_FLAG : 0) + | (generate_toc ? VBR_TOC_FLAG : 0)); + index += 8; + if(num_frames) + { + long2bytes(&buf[index], num_frames); + index += 4; + } + + if(filesize) + { + long2bytes(&buf[index], filesize - startpos); + index += 4; + } + + /* Copy the TOC */ + memcpy(buf + index, toc, 100); + + /* And some extra cool info */ + memcpy(buf + index + 100, cooltext, sizeof(cooltext)); + +#ifdef DEBUG + for(i = 0;i < info.frame_size;i++) + { + if(i && !(i % 16)) + DEBUGF("\n"); + + DEBUGF("%02x ", buf[i]); + } +#endif + + return info.frame_size; +} + +#endif diff --git a/lib/rbcodec/metadata/mp3data.h b/lib/rbcodec/metadata/mp3data.h new file mode 100644 index 0000000000..762c2f4583 --- /dev/null +++ b/lib/rbcodec/metadata/mp3data.h @@ -0,0 +1,89 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Linus Nielsen Feltzing + * + * 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. + * + ****************************************************************************/ + +#ifndef _MP3DATA_H_ +#define _MP3DATA_H_ + +#define MPEG_VERSION1 0 +#define MPEG_VERSION2 1 +#define MPEG_VERSION2_5 2 + +#include /* size_t */ + +struct mp3info { + /* Standard MP3 frame header fields */ + int version; + int layer; + int bitrate; + long frequency; + int padding; + int channel_mode; + int frame_size; /* Frame size in bytes */ + int frame_samples;/* Samples per frame */ + int ft_num; /* Numerator of frametime in milliseconds */ + int ft_den; /* Denominator of frametime in milliseconds */ + + bool is_vbr; /* True if the file is VBR */ + bool has_toc; /* True if there is a VBR header in the file */ + unsigned char toc[100]; + unsigned long frame_count; /* Number of frames in the file (if VBR) */ + unsigned long byte_count; /* File size in bytes */ + unsigned long file_time; /* Length of the whole file in milliseconds */ + int enc_delay; /* Encoder delay, fetched from LAME header */ + int enc_padding; /* Padded samples added to last frame. LAME header */ +}; + +/* Xing header information */ +#define VBR_FRAMES_FLAG 0x01 +#define VBR_BYTES_FLAG 0x02 +#define VBR_TOC_FLAG 0x04 +#define VBR_QUALITY_FLAG 0x08 + +#define MAX_XING_HEADER_SIZE 576 + +unsigned long find_next_frame(int fd, + long *offset, + long max_offset, + unsigned long reference_header); +unsigned long mem_find_next_frame(int startpos, + long *offset, + long max_offset, + unsigned long reference_header, + unsigned char* buf, size_t buflen); +int get_mp3file_info(int fd, + struct mp3info *info); + +int count_mp3_frames(int fd, int startpos, int filesize, + void (*progressfunc)(int), + unsigned char* buf, size_t buflen); + +int create_xing_header(int fd, long startpos, long filesize, + unsigned char *buf, unsigned long num_frames, + unsigned long rec_time, unsigned long header_template, + void (*progressfunc)(int), bool generate_toc, + unsigned char *tempbuf, size_t tempbuflen ); + +extern unsigned long bytes2int(unsigned long b0, + unsigned long b1, + unsigned long b2, + unsigned long b3); + +#endif diff --git a/lib/rbcodec/metadata/mp4.c b/lib/rbcodec/metadata/mp4.c new file mode 100644 index 0000000000..df164436f5 --- /dev/null +++ b/lib/rbcodec/metadata/mp4.c @@ -0,0 +1,842 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Magnus Holmgren + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "errno.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "logf.h" +#include "debug.h" +#include "replaygain.h" + +#ifdef DEBUGF +#undef DEBUGF +#define DEBUGF(...) +#endif + +#define MP4_3gp6 FOURCC('3', 'g', 'p', '6') +#define MP4_aART FOURCC('a', 'A', 'R', 'T') +#define MP4_alac FOURCC('a', 'l', 'a', 'c') +#define MP4_calb FOURCC(0xa9, 'a', 'l', 'b') +#define MP4_cART FOURCC(0xa9, 'A', 'R', 'T') +#define MP4_cgrp FOURCC(0xa9, 'g', 'r', 'p') +#define MP4_cgen FOURCC(0xa9, 'g', 'e', 'n') +#define MP4_chpl FOURCC('c', 'h', 'p', 'l') +#define MP4_cnam FOURCC(0xa9, 'n', 'a', 'm') +#define MP4_cwrt FOURCC(0xa9, 'w', 'r', 't') +#define MP4_ccmt FOURCC(0xa9, 'c', 'm', 't') +#define MP4_cday FOURCC(0xa9, 'd', 'a', 'y') +#define MP4_covr FOURCC('c', 'o', 'v', 'r') +#define MP4_disk FOURCC('d', 'i', 's', 'k') +#define MP4_esds FOURCC('e', 's', 'd', 's') +#define MP4_ftyp FOURCC('f', 't', 'y', 'p') +#define MP4_gnre FOURCC('g', 'n', 'r', 'e') +#define MP4_hdlr FOURCC('h', 'd', 'l', 'r') +#define MP4_ilst FOURCC('i', 'l', 's', 't') +#define MP4_isom FOURCC('i', 's', 'o', 'm') +#define MP4_M4A FOURCC('M', '4', 'A', ' ') +#define MP4_m4a FOURCC('m', '4', 'a', ' ') /*technically its "M4A "*/ +#define MP4_M4B FOURCC('M', '4', 'B', ' ') /*but files exist with lower case*/ +#define MP4_mdat FOURCC('m', 'd', 'a', 't') +#define MP4_mdia FOURCC('m', 'd', 'i', 'a') +#define MP4_mdir FOURCC('m', 'd', 'i', 'r') +#define MP4_meta FOURCC('m', 'e', 't', 'a') +#define MP4_minf FOURCC('m', 'i', 'n', 'f') +#define MP4_moov FOURCC('m', 'o', 'o', 'v') +#define MP4_mp4a FOURCC('m', 'p', '4', 'a') +#define MP4_mp42 FOURCC('m', 'p', '4', '2') +#define MP4_qt FOURCC('q', 't', ' ', ' ') +#define MP4_soun FOURCC('s', 'o', 'u', 'n') +#define MP4_stbl FOURCC('s', 't', 'b', 'l') +#define MP4_stsd FOURCC('s', 't', 's', 'd') +#define MP4_stts FOURCC('s', 't', 't', 's') +#define MP4_trak FOURCC('t', 'r', 'a', 'k') +#define MP4_trkn FOURCC('t', 'r', 'k', 'n') +#define MP4_udta FOURCC('u', 'd', 't', 'a') +#define MP4_extra FOURCC('-', '-', '-', '-') + +/* Read the tag data from an MP4 file, storing up to buffer_size bytes in + * buffer. + */ +static unsigned long read_mp4_tag(int fd, unsigned int size_left, char* buffer, + unsigned int buffer_left) +{ + unsigned int bytes_read = 0; + + if (buffer_left == 0) + { + lseek(fd, size_left, SEEK_CUR); /* Skip everything */ + } + else + { + /* Skip the data tag header - maybe we should parse it properly? */ + lseek(fd, 16, SEEK_CUR); + size_left -= 16; + + if (size_left > buffer_left) + { + read(fd, buffer, buffer_left); + lseek(fd, size_left - buffer_left, SEEK_CUR); + bytes_read = buffer_left; + } + else + { + read(fd, buffer, size_left); + bytes_read = size_left; + } + } + + return bytes_read; +} + +/* Read a string tag from an MP4 file */ +static unsigned int read_mp4_tag_string(int fd, int size_left, char** buffer, + unsigned int* buffer_left, char** dest) +{ + unsigned int bytes_read = read_mp4_tag(fd, size_left, *buffer, + *buffer_left > 0 ? *buffer_left - 1 : 0); + unsigned int length = 0; + + if (bytes_read) + { + /* Do not overwrite already available metadata. Especially when reading + * tags with e.g. multiple genres / artists. This way only the first + * of multiple entries is used, all following are dropped. */ + if (*dest == NULL) + { + (*buffer)[bytes_read] = 0; /* zero-terminate for correct strlen().*/ + length = strlen(*buffer) + 1; + length = MIN(length, ID3V2_MAX_ITEM_SIZE); /* Limit item size. */ + + *dest = *buffer; + (*buffer)[length-1] = 0; /* zero-terminate buffer. */ + *buffer_left -= length; + *buffer += length; + } + } + else + { + *dest = NULL; + } + + return length; +} + +static unsigned int read_mp4_atom(int fd, uint32_t* size, + uint32_t* type, uint32_t size_left) +{ + read_uint32be(fd, size); + read_uint32be(fd, type); + + if (*size == 1) + { + /* FAT32 doesn't support files this big, so something seems to + * be wrong. (64-bit sizes should only be used when required.) + */ + errno = EFBIG; + *type = 0; + return 0; + } + + if (*size > 0) + { + if (*size > size_left) + { + size_left = 0; + } + else + { + size_left -= *size; + } + + *size -= 8; + } + else + { + *size = size_left; + size_left = 0; + } + + return size_left; +} + +static unsigned int read_mp4_length(int fd, uint32_t* size) +{ + unsigned int length = 0; + int bytes = 0; + unsigned char c; + + do + { + read(fd, &c, 1); + bytes++; + (*size)--; + length = (length << 7) | (c & 0x7F); + } + while ((c & 0x80) && (bytes < 4) && (*size > 0)); + + return length; +} + +static bool read_mp4_esds(int fd, struct mp3entry* id3, uint32_t* size) +{ + unsigned char buf[8]; + bool sbr = false; + + lseek(fd, 4, SEEK_CUR); /* Version and flags. */ + read(fd, buf, 1); /* Verify ES_DescrTag. */ + *size -= 5; + + if (*buf == 3) + { + /* read length */ + if (read_mp4_length(fd, size) < 20) + { + return sbr; + } + + lseek(fd, 3, SEEK_CUR); + *size -= 3; + } + else + { + lseek(fd, 2, SEEK_CUR); + *size -= 2; + } + + read(fd, buf, 1); /* Verify DecoderConfigDescrTab. */ + *size -= 1; + + if (*buf != 4) + { + return sbr; + } + + if (read_mp4_length(fd, size) < 13) + { + return sbr; + } + + lseek(fd, 13, SEEK_CUR); /* Skip audio type, bit rates, etc. */ + read(fd, buf, 1); + *size -= 14; + + if (*buf != 5) /* Verify DecSpecificInfoTag. */ + { + return sbr; + } + + { + static const int sample_rates[] = + { + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000 + }; + unsigned long bits; + unsigned int length; + unsigned int index; + unsigned int type; + + /* Read the (leading part of the) decoder config. */ + length = read_mp4_length(fd, size); + length = MIN(length, *size); + length = MIN(length, sizeof(buf)); + memset(buf, 0, sizeof(buf)); + read(fd, buf, length); + *size -= length; + + /* Maybe time to write a simple read_bits function... */ + + /* Decoder config format: + * Object type - 5 bits + * Frequency index - 4 bits + * Channel configuration - 4 bits + */ + bits = get_long_be(buf); + type = bits >> 27; /* Object type - 5 bits */ + index = (bits >> 23) & 0xf; /* Frequency index - 4 bits */ + + if (index < (sizeof(sample_rates) / sizeof(*sample_rates))) + { + id3->frequency = sample_rates[index]; + } + + if (type == 5) + { + unsigned int old_index = index; + + sbr = true; + index = (bits >> 15) & 0xf; /* Frequency index - 4 bits */ + + if (index == 15) + { + /* 17 bits read so far... */ + bits = get_long_be(&buf[2]); + id3->frequency = (bits >> 7) & 0x00ffffff; + } + else if (index < (sizeof(sample_rates) / sizeof(*sample_rates))) + { + id3->frequency = sample_rates[index]; + } + + if (old_index == index) + { + /* Downsampled SBR */ + id3->frequency *= 2; + } + } + /* Skip 13 bits from above, plus 3 bits, then read 11 bits */ + else if ((length >= 4) && (((bits >> 5) & 0x7ff) == 0x2b7)) + { + /* We found an extensionAudioObjectType */ + type = bits & 0x1f; /* Object type - 5 bits*/ + bits = get_long_be(&buf[4]); + + if (type == 5) + { + sbr = bits >> 31; + + if (sbr) + { + unsigned int old_index = index; + + /* 1 bit read so far */ + index = (bits >> 27) & 0xf; /* Frequency index - 4 bits */ + + if (index == 15) + { + /* 5 bits read so far */ + id3->frequency = (bits >> 3) & 0x00ffffff; + } + else if (index < (sizeof(sample_rates) / sizeof(*sample_rates))) + { + id3->frequency = sample_rates[index]; + } + + if (old_index == index) + { + /* Downsampled SBR */ + id3->frequency *= 2; + } + } + } + } + + if (!sbr && (id3->frequency <= 24000) && (length <= 2)) + { + /* Double the frequency for low-frequency files without a "long" + * DecSpecificConfig header. The file may or may not contain SBR, + * but here we guess it does if the header is short. This can + * fail on some files, but it's the best we can do, short of + * decoding (parts of) the file. + */ + id3->frequency *= 2; + sbr = true; + } + } + + return sbr; +} + +static bool read_mp4_tags(int fd, struct mp3entry* id3, + uint32_t size_left) +{ + uint32_t size; + uint32_t type; + unsigned int buffer_left = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf); + char* buffer = id3->id3v2buf; + bool cwrt = false; + + do + { + size_left = read_mp4_atom(fd, &size, &type, size_left); + + /* DEBUGF("Tag atom: '%c%c%c%c' (%d bytes left)\n", type >> 24 & 0xff, + type >> 16 & 0xff, type >> 8 & 0xff, type & 0xff, size); */ + + switch (type) + { + case MP4_cnam: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->title); + break; + + case MP4_cART: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->artist); + break; + + case MP4_aART: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->albumartist); + break; + + case MP4_cgrp: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->grouping); + break; + + case MP4_calb: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->album); + break; + + case MP4_cwrt: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->composer); + cwrt = false; + break; + + case MP4_ccmt: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->comment); + break; + + case MP4_cday: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->year_string); + + /* Try to parse it as a year, for the benefit of the database. + */ + if(id3->year_string) + { + id3->year = atoi(id3->year_string); + if (id3->year < 1900) + { + id3->year = 0; + } + } + else + id3->year = 0; + + break; + + case MP4_gnre: + { + unsigned short genre; + + read_mp4_tag(fd, size, (char*) &genre, sizeof(genre)); + id3->genre_string = id3_get_num_genre(betoh16(genre) - 1); + } + break; + + case MP4_cgen: + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->genre_string); + break; + + case MP4_disk: + { + unsigned short n[2]; + + read_mp4_tag(fd, size, (char*) &n, sizeof(n)); + id3->discnum = betoh16(n[1]); + } + break; + + case MP4_trkn: + { + unsigned short n[2]; + + read_mp4_tag(fd, size, (char*) &n, sizeof(n)); + id3->tracknum = betoh16(n[1]); + } + break; + +#ifdef HAVE_ALBUMART + case MP4_covr: + { + int pos = lseek(fd, 0, SEEK_CUR) + 16; + + read_mp4_tag(fd, size, buffer, 8); + id3->albumart.type = AA_TYPE_UNKNOWN; + if (memcmp(buffer, "\xff\xd8\xff\xe0", 4) == 0) + { + id3->albumart.type = AA_TYPE_JPG; + } + else if (memcmp(buffer, "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0) + { + id3->albumart.type = AA_TYPE_PNG; + } + + if (id3->albumart.type != AA_TYPE_UNKNOWN) + { + id3->albumart.pos = pos; + id3->albumart.size = size - 16; + id3->has_embedded_albumart = true; + } + } + break; +#endif + + case MP4_extra: + { + char tag_name[TAG_NAME_LENGTH]; + uint32_t sub_size; + + /* "mean" atom */ + read_uint32be(fd, &sub_size); + size -= sub_size; + lseek(fd, sub_size - 4, SEEK_CUR); + /* "name" atom */ + read_uint32be(fd, &sub_size); + size -= sub_size; + lseek(fd, 8, SEEK_CUR); + sub_size -= 12; + + if (sub_size > sizeof(tag_name) - 1) + { + read(fd, tag_name, sizeof(tag_name) - 1); + lseek(fd, sub_size - (sizeof(tag_name) - 1), SEEK_CUR); + tag_name[sizeof(tag_name) - 1] = 0; + } + else + { + read(fd, tag_name, sub_size); + tag_name[sub_size] = 0; + } + + if ((strcasecmp(tag_name, "composer") == 0) && !cwrt) + { + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->composer); + } + else if (strcasecmp(tag_name, "iTunSMPB") == 0) + { + char value[TAG_VALUE_LENGTH]; + char* value_p = value; + char* any; + unsigned int length = sizeof(value); + + read_mp4_tag_string(fd, size, &value_p, &length, &any); + id3->lead_trim = get_itunes_int32(value, 1); + id3->tail_trim = get_itunes_int32(value, 2); + DEBUGF("AAC: lead_trim %d, tail_trim %d\n", + id3->lead_trim, id3->tail_trim); + } + else if (strcasecmp(tag_name, "musicbrainz track id") == 0) + { + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->mb_track_id); + } + else if ((strcasecmp(tag_name, "album artist") == 0)) + { + read_mp4_tag_string(fd, size, &buffer, &buffer_left, + &id3->albumartist); + } + else + { + char* any = NULL; + unsigned int length = read_mp4_tag_string(fd, size, + &buffer, &buffer_left, &any); + + if (length > 0) + { + /* Re-use the read buffer as the dest buffer... */ + buffer -= length; + buffer_left += length; + + parse_replaygain(tag_name, buffer, id3); + } + } + } + break; + + default: + lseek(fd, size, SEEK_CUR); + break; + } + } + while ((size_left > 0) && (errno == 0)); + + return true; +} + +static bool read_mp4_container(int fd, struct mp3entry* id3, + uint32_t size_left) +{ + uint32_t size = 0; + uint32_t type = 0; + uint32_t handler = 0; + bool rc = true; + bool done = false; + + do + { + size_left = read_mp4_atom(fd, &size, &type, size_left); + + /* DEBUGF("Atom: '%c%c%c%c' (0x%08lx, %lu bytes left)\n", + (int) ((type >> 24) & 0xff), (int) ((type >> 16) & 0xff), + (int) ((type >> 8) & 0xff), (int) (type & 0xff), + type, size); */ + + switch (type) + { + case MP4_ftyp: + { + uint32_t id; + + read_uint32be(fd, &id); + size -= 4; + + if ((id != MP4_M4A) && (id != MP4_M4B) && (id != MP4_mp42) + && (id != MP4_qt) && (id != MP4_3gp6) && (id != MP4_m4a) + && (id != MP4_isom)) + { + DEBUGF("Unknown MP4 file type: '%c%c%c%c'\n", + (int)(id >> 24 & 0xff), (int)(id >> 16 & 0xff), + (int)(id >> 8 & 0xff), (int)(id & 0xff)); + return false; + } + } + break; + + case MP4_meta: + lseek(fd, 4, SEEK_CUR); /* Skip version */ + size -= 4; + /* Fall through */ + + case MP4_moov: + case MP4_udta: + case MP4_mdia: + case MP4_stbl: + case MP4_trak: + rc = read_mp4_container(fd, id3, size); + size = 0; + break; + + case MP4_ilst: + /* We need at least a size of 8 to read the next atom. */ + if (handler == MP4_mdir && size>8) + { + rc = read_mp4_tags(fd, id3, size); + size = 0; + } + break; + + case MP4_minf: + if (handler == MP4_soun) + { + rc = read_mp4_container(fd, id3, size); + size = 0; + } + break; + + case MP4_stsd: + lseek(fd, 8, SEEK_CUR); + size -= 8; + rc = read_mp4_container(fd, id3, size); + size = 0; + break; + + case MP4_hdlr: + lseek(fd, 8, SEEK_CUR); + read_uint32be(fd, &handler); + size -= 12; + /* DEBUGF(" Handler '%c%c%c%c'\n", handler >> 24 & 0xff, + handler >> 16 & 0xff, handler >> 8 & 0xff,handler & 0xff); */ + break; + + case MP4_stts: + { + uint32_t entries; + unsigned int i; + + /* Reset to false. */ + id3->needs_upsampling_correction = false; + + lseek(fd, 4, SEEK_CUR); + read_uint32be(fd, &entries); + id3->samples = 0; + + for (i = 0; i < entries; i++) + { + uint32_t n; + uint32_t l; + + read_uint32be(fd, &n); + read_uint32be(fd, &l); + + /* Some AAC file use HE profile. In this case the number + * of output samples is doubled to a maximum of 2048 + * samples per frame. This means that files which already + * report a frame size of 2048 in their header will not + * need any further special handling. */ + if (id3->codectype==AFMT_MP4_AAC_HE && l<=1024) + { + id3->samples += n * l * 2; + id3->needs_upsampling_correction = true; + } + else + { + id3->samples += n * l; + } + } + + size = 0; + } + break; + + case MP4_mp4a: + { + uint32_t subsize; + uint32_t subtype; + + /* Move to the next expected mp4 atom. */ + lseek(fd, 28, SEEK_CUR); + read_mp4_atom(fd, &subsize, &subtype, size); + size -= 36; + + if (subtype == MP4_esds) + { + /* Read esds metadata and return if AAC-HE/SBR is used. */ + if (read_mp4_esds(fd, id3, &size)) + id3->codectype = AFMT_MP4_AAC_HE; + else + id3->codectype = AFMT_MP4_AAC; + } + } + break; + + case MP4_alac: + { + uint32_t frequency; + uint32_t subsize; + uint32_t subtype; + + /* Move to the next expected mp4 atom. */ + lseek(fd, 28, SEEK_CUR); + read_mp4_atom(fd, &subsize, &subtype, size); + size -= 36; +#if 0 + /* We might need to parse for the alac metadata atom. */ + while (!((subsize==28) && (subtype==MP4_alac)) && (size>0)) + { + lseek(fd, -7, SEEK_CUR); + read_mp4_atom(fd, &subsize, &subtype, size); + size -= 1; + errno = 0; /* will most likely be set while parsing */ + } +#endif + if (subtype == MP4_alac) + { + lseek(fd, 24, SEEK_CUR); + read_uint32be(fd, &frequency); + size -= 28; + id3->frequency = frequency; + id3->codectype = AFMT_MP4_ALAC; + } + } + break; + + case MP4_mdat: + /* Some AAC files appear to contain additional empty mdat chunks. + Ignore them. */ + if(size == 0) + break; + id3->filesize = size; + if(id3->samples > 0) { + /* We've already seen the moov chunk. */ + done = true; + } + break; + + case MP4_chpl: + { + /* ADDME: add support for real chapters. Right now it's only + * used for Nero's gapless hack */ + uint8_t chapters; + uint64_t timestamp; + + lseek(fd, 8, SEEK_CUR); + read_uint8(fd, &chapters); + size -= 9; + + /* the first chapter will be used as the lead_trim */ + if (chapters > 0) { + read_uint64be(fd, ×tamp); + id3->lead_trim = (timestamp * id3->frequency) / 10000000; + size -= 8; + } + } + break; + + default: + break; + } + + /* Skip final seek. */ + if (!done) + { + lseek(fd, size, SEEK_CUR); + } + } while (rc && (size_left > 0) && (errno == 0) && !done); + + return rc; +} + +bool get_mp4_metadata(int fd, struct mp3entry* id3) +{ + id3->codectype = AFMT_UNKNOWN; + id3->filesize = 0; + errno = 0; + + if (read_mp4_container(fd, id3, filesize(fd)) && (errno == 0) + && (id3->samples > 0) && (id3->frequency > 0) + && (id3->filesize > 0)) + { + if (id3->codectype == AFMT_UNKNOWN) + { + logf("Not an ALAC or AAC file"); + return false; + } + + id3->length = ((int64_t) id3->samples * 1000) / id3->frequency; + + id3->vbr = true; /* ALAC is native VBR, AAC very unlikely is CBR. */ + + if (id3->length <= 0) + { + logf("mp4 length invalid!"); + return false; + } + + id3->bitrate = ((int64_t) id3->filesize * 8) / id3->length; + DEBUGF("MP4 bitrate %d, frequency %ld Hz, length %ld ms\n", + id3->bitrate, id3->frequency, id3->length); + } + else + { + logf("MP4 metadata error"); + DEBUGF("MP4 metadata error. errno %d, samples %ld, frequency %ld, " + "filesize %ld\n", errno, id3->samples, id3->frequency, + id3->filesize); + return false; + } + + return true; +} diff --git a/lib/rbcodec/metadata/mpc.c b/lib/rbcodec/metadata/mpc.c new file mode 100644 index 0000000000..0b75ed04dd --- /dev/null +++ b/lib/rbcodec/metadata/mpc.c @@ -0,0 +1,220 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Thom Johansen + * Copyright (C) 2010 Andree Buschmann + * + * 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. + * + ****************************************************************************/ + +#include +#include +#include +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "logf.h" +#include "replaygain.h" +#include "fixedpoint.h" + +/* Needed for replay gain and clipping prevention of SV8 files. */ +#define SV8_TO_SV7_CONVERT_GAIN (6482) /* 64.82 * 100, MPC_OLD_GAIN_REF */ +#define SV8_TO_SV7_CONVERT_PEAK (23119) /* 256 * 20 * log10(32768) */ + +static int set_replaygain_sv7(struct mp3entry* id3, + bool album, + long value, + long used) +{ + long gain = (int16_t) ((value >> 16) & 0xffff); + long peak = (uint16_t) (value & 0xffff); + + /* We use a peak value of 0 to indicate a given gain type isn't used. */ + if (peak != 0) { + /* Save the ReplayGain data to id3-structure for further processing. */ + parse_replaygain_int(album, gain * 512 / 100, peak << 9, id3); + } + + return used; +} + +static int set_replaygain_sv8(struct mp3entry* id3, + bool album, + long gain, + long peak, + long used) +{ + gain = (long)(SV8_TO_SV7_CONVERT_GAIN - ((gain*100)/256)); + + /* Transform SV8's logarithmic peak representation to the desired linear + * representation: linear = pow(10, peak/256/20). + * + * FP_BITS = 24 bits = desired fp representation for dsp routines + * FRAC_BITS = 12 bits = resolution used for fp_bits + * fp_factor(peak*(1<> 24) & 15; + if (streamversion == 7) { + unsigned int gapless = (header[5] >> 31) & 0x0001; + unsigned int last_frame_samples = (header[5] >> 20) & 0x07ff; + unsigned int bufused = 0; + + id3->frequency = sfreqs[(header[2] >> 16) & 0x0003]; + samples = (uint64_t)header[1]*1152; /* 1152 is mpc frame size */ + if (gapless) + samples -= 1152 - last_frame_samples; + else + samples -= 481; /* Musepack subband synth filter delay */ + + bufused = set_replaygain_sv7(id3, false, header[3], bufused); + bufused = set_replaygain_sv7(id3, true , header[4], bufused); + + id3->codectype = AFMT_MPC_SV7; + } else { + return false; /* only SV7 is allowed within a "MP+" signature */ + } + } else if (!memcmp(header, "MPCK", 4)) { /* Compare to sig "MPCK" */ + uint8_t sv8_header[32]; + /* 4 bytes 'MPCK' */ + lseek(fd, 4, SEEK_SET); + if (read(fd, sv8_header, 2) != 2) return false; /* read frame ID */ + if (!memcmp(sv8_header, "SH", 2)) { /* Stream Header ID */ + int32_t k = 0; + uint32_t streamversion; + uint64_t size = 0; /* tag size */ + uint64_t dummy = 0; /* used to dummy read data from header */ + + /* 4 bytes 'MPCK' + 2 'SH' */ + lseek(fd, 6, SEEK_SET); + if (read(fd, sv8_header, 32) != 32) return false; + + /* Read the size of 'SH'-tag */ + k = sv8_get_size(sv8_header, k, &size); + + /* Skip crc32 */ + k += 4; + + /* Read stream version */ + streamversion = sv8_header[k++]; + if (streamversion != 8) return false; /* Only SV8 is allowed. */ + + /* Number of samples */ + k = sv8_get_size(sv8_header, k, &samples); + + /* Number of leading zero-samples */ + k = sv8_get_size(sv8_header, k, &dummy); + + /* Sampling frequency */ + id3->frequency = sfreqs[(sv8_header[k++] >> 5) & 0x0003]; + + /* Number of channels */ + id3->channels = (sv8_header[k++] >> 4) + 1; + + /* Skip to next tag: k = size -2 */ + k = size - 2; + + if (!memcmp(sv8_header+k, "RG", 2)) { /* Replay Gain ID */ + long peak, gain; + int bufused = 0; + + k += 2; /* 2 bytes 'RG' */ + + /* sv8_get_size must be called to skip the right amount of + * bits within the header data. */ + k = sv8_get_size(sv8_header, k, &size); + + /* Read and set replay gain */ + if (sv8_header[k++] == 1) { + /* Title's peak and gain */ + gain = (int16_t) ((sv8_header[k]<<8) + sv8_header[k+1]); k += 2; + peak = (uint16_t)((sv8_header[k]<<8) + sv8_header[k+1]); k += 2; + bufused += set_replaygain_sv8(id3, false, gain, peak, bufused); + + /* Album's peak and gain */ + gain = (int16_t) ((sv8_header[k]<<8) + sv8_header[k+1]); k += 2; + peak = (uint16_t)((sv8_header[k]<<8) + sv8_header[k+1]); k += 2; + bufused += set_replaygain_sv8(id3, true , gain, peak, bufused); + } + } + + id3->codectype = AFMT_MPC_SV8; + } else { + /* No sv8 stream header found */ + return false; + } + } else { + return false; /* SV4-6 is not supported anymore */ + } + + id3->vbr = true; + /* Estimate bitrate, we should probably subtract the various header sizes + here for super-accurate results */ + id3->length = ((int64_t) samples * 1000) / id3->frequency; + + if (id3->length <= 0) + { + logf("mpc length invalid!"); + return false; + } + + id3->filesize = filesize(fd); + id3->bitrate = id3->filesize * 8 / id3->length; + + read_ape_tags(fd, id3); + return true; +} diff --git a/lib/rbcodec/metadata/nsf.c b/lib/rbcodec/metadata/nsf.c new file mode 100644 index 0000000000..2fa6f36b12 --- /dev/null +++ b/lib/rbcodec/metadata/nsf.c @@ -0,0 +1,278 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" +#include "string-extra.h" + +/* NOTE: This file was modified to work properly with the new nsf codec based + on Game_Music_Emu */ + +struct NESM_HEADER +{ + uint32_t nHeader; + uint8_t nHeaderExtra; + uint8_t nVersion; + uint8_t nTrackCount; + uint8_t nInitialTrack; + uint16_t nLoadAddress; + uint16_t nInitAddress; + uint16_t nPlayAddress; + uint8_t szGameTitle[32]; + uint8_t szArtist[32]; + uint8_t szCopyright[32]; + uint16_t nSpeedNTSC; + uint8_t nBankSwitch[8]; + uint16_t nSpeedPAL; + uint8_t nNTSC_PAL; + uint8_t nExtraChip; + uint8_t nExpansion[4]; +} __attribute__((packed)); + +struct NSFE_INFOCHUNK +{ + uint16_t nLoadAddress; + uint16_t nInitAddress; + uint16_t nPlayAddress; + uint8_t nIsPal; + uint8_t nExt; + uint8_t nTrackCount; + uint8_t nStartingTrack; +} __attribute__((packed)); + + +#define CHAR4_CONST(a, b, c, d) FOURCC(a, b, c, d) +#define CHUNK_INFO 0x0001 +#define CHUNK_DATA 0x0002 +#define CHUNK_NEND 0x0004 +#define CHUNK_plst 0x0008 +#define CHUNK_time 0x0010 +#define CHUNK_fade 0x0020 +#define CHUNK_tlbl 0x0040 +#define CHUNK_auth 0x0080 +#define CHUNK_BANK 0x0100 + +static bool parse_nsfe(int fd, struct mp3entry *id3) +{ + unsigned int chunks_found = 0; + long track_count = 0; + long playlist_count = 0; + + struct NSFE_INFOCHUNK info; + memset(&info, 0, sizeof(struct NSFE_INFOCHUNK)); + + /* default values */ + info.nTrackCount = 1; + id3->length = 150 * 1000; + + /* begin reading chunks */ + while (!(chunks_found & CHUNK_NEND)) + { + uint32_t chunk_size, chunk_type; + + if (read_uint32le(fd, &chunk_size) != (int)sizeof(uint32_t)) + return false; + + if (read_uint32be(fd, &chunk_type) != (int)sizeof(uint32_t)) + return false; + + switch (chunk_type) + { + /* first three types are mandatory (but don't worry about NEND + anyway) */ + case CHAR4_CONST('I', 'N', 'F', 'O'): + { + if (chunks_found & CHUNK_INFO) + return false; /* only one info chunk permitted */ + + chunks_found |= CHUNK_INFO; + + /* minimum size */ + if (chunk_size < 8) + return false; + + ssize_t size = MIN(sizeof(struct NSFE_INFOCHUNK), chunk_size); + + if (read(fd, &info, size) != size) + return false; + + if (size >= 9) + track_count = info.nTrackCount; + + chunk_size -= size; + break; + } + + case CHAR4_CONST('D', 'A', 'T', 'A'): + { + if (!(chunks_found & CHUNK_INFO)) + return false; + + if (chunks_found & CHUNK_DATA) + return false; /* only one may exist */ + + if (chunk_size < 1) + return false; + + chunks_found |= CHUNK_DATA; + break; + } + + case CHAR4_CONST('N', 'E', 'N', 'D'): + { + /* just end parsing regardless of whether or not this really is the + last chunk/data (but it _should_ be) */ + chunks_found |= CHUNK_NEND; + continue; + } + + /* remaining types are optional */ + + case CHAR4_CONST('a', 'u', 't', 'h'): + { + if (chunks_found & CHUNK_auth) + return false; /* only one may exist */ + + chunks_found |= CHUNK_auth; + + /* szGameTitle, szArtist, szCopyright */ + char ** const ar[] = { &id3->title, &id3->artist, &id3->album }; + + char *p = id3->id3v2buf; + long buf_rem = sizeof (id3->id3v2buf); + unsigned int i; + + for (i = 0; i < ARRAYLEN(ar) && chunk_size && buf_rem; i++) + { + long len = read_string(fd, p, buf_rem, '\0', chunk_size); + + if (len < 0) + return false; + + *ar[i] = p; + p += len; + buf_rem -= len; + + if (chunk_size >= (uint32_t)len) + chunk_size -= len; + else + chunk_size = 0; + } + + break; + } + + case CHAR4_CONST('p', 'l', 's', 't'): + { + if (chunks_found & CHUNK_plst) + return false; /* only one may exist */ + + chunks_found |= CHUNK_plst; + + /* each byte is the index of one track */ + playlist_count = chunk_size; + break; + } + + case CHAR4_CONST('t', 'i', 'm', 'e'): + case CHAR4_CONST('f', 'a', 'd', 'e'): + case CHAR4_CONST('t', 'l', 'b', 'l'): /* we unfortunately can't use these anyway */ + { + /* don't care how many of these there are even though there should + be only one */ + if (!(chunks_found & CHUNK_INFO)) + return false; + + case CHAR4_CONST('B', 'A', 'N', 'K'): + break; + } + + default: /* unknown chunk */ + { + /* check the first byte */ + chunk_type = (uint8_t)chunk_type; + + /* chunk is vital... don't continue */ + if(chunk_type >= 'A' && chunk_type <= 'Z') + return false; + + /* otherwise, just skip it */ + break; + } + } /* end switch */ + + lseek(fd, chunk_size, SEEK_CUR); + } /* end while */ + + if (track_count | playlist_count) + id3->length = MAX(track_count, playlist_count)*1000; + + /* Single subtrack files will be treated differently + by gme's nsf codec */ + if (id3->length <= 1000) id3->length = 150 * 1000; + + /* + * if we exited the while loop without a 'return', we must have hit an NEND + * chunk if this is the case, the file was layed out as it was expected. + * now.. make sure we found both an info chunk, AND a data chunk... since + * these are minimum requirements for a valid NSFE file + */ + return (chunks_found & (CHUNK_INFO | CHUNK_DATA)) == + (CHUNK_INFO | CHUNK_DATA); +} + +static bool parse_nesm(int fd, struct mp3entry *id3) +{ + struct NESM_HEADER hdr; + char *p = id3->id3v2buf; + + lseek(fd, 0, SEEK_SET); + if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) + return false; + + /* Length */ + id3->length = (hdr.nTrackCount > 1 ? hdr.nTrackCount : 150) * 1000; + + /* Title */ + id3->title = p; + p += strlcpy(p, hdr.szGameTitle, 32) + 1; + + /* Artist */ + id3->artist = p; + p += strlcpy(p, hdr.szArtist, 32) + 1; + + /* Copyright (per codec) */ + id3->album = p; + strlcpy(p, hdr.szCopyright, 32); + + return true; +} + +bool get_nsf_metadata(int fd, struct mp3entry* id3) +{ + uint32_t nsf_type; + if (lseek(fd, 0, SEEK_SET) < 0 || + read_uint32be(fd, &nsf_type) != (int)sizeof(nsf_type)) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + /* we only render 16 bits, 44.1KHz, Mono */ + id3->bitrate = 706; + id3->frequency = 44100; + + if (nsf_type == CHAR4_CONST('N', 'S', 'F', 'E')) + return parse_nsfe(fd, id3); + else if (nsf_type == CHAR4_CONST('N', 'E', 'S', 'M')) + return parse_nesm(fd, id3); + + /* not a valid format*/ + return false; +} + diff --git a/lib/rbcodec/metadata/ogg.c b/lib/rbcodec/metadata/ogg.c new file mode 100644 index 0000000000..3a3cb29998 --- /dev/null +++ b/lib/rbcodec/metadata/ogg.c @@ -0,0 +1,215 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "logf.h" + +/* A simple parser to read vital metadata from an Ogg Vorbis file. + * Can also handle parsing Ogg Speex files for metadata. Returns + * false if metadata needed by the codec couldn't be read. + */ +bool get_ogg_metadata(int fd, struct mp3entry* id3) +{ + /* An Ogg File is split into pages, each starting with the string + * "OggS". Each page has a timestamp (in PCM samples) referred to as + * the "granule position". + * + * An Ogg Vorbis has the following structure: + * 1) Identification header (containing samplerate, numchannels, etc) + * 2) Comment header - containing the Vorbis Comments + * 3) Setup header - containing codec setup information + * 4) Many audio packets... + * + * An Ogg Speex has the following structure: + * 1) Identification header (containing samplerate, numchannels, etc) + * Described in this page: (http://www.speex.org/manual2/node7.html) + * 2) Comment header - containing the Vorbis Comments + * 3) Many audio packets. + */ + + /* Use the path name of the id3 structure as a temporary buffer. */ + unsigned char* buf = (unsigned char *)id3->path; + long comment_size; + long remaining = 0; + long last_serial = 0; + long serial, r; + int segments, header_size; + int i; + bool eof = false; + + /* 92 bytes is enough for both Vorbis and Speex headers */ + if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 92) < 92)) + { + return false; + } + + /* All Ogg streams start with OggS */ + if (memcmp(buf, "OggS", 4) != 0) + { + return false; + } + + /* Check for format magic and then get metadata */ + if (memcmp(&buf[29], "vorbis", 6) == 0) + { + id3->codectype = AFMT_OGG_VORBIS; + id3->frequency = get_long_le(&buf[40]); + id3->vbr = true; + + /* Comments are in second Ogg page (byte 58 onwards for Vorbis) */ + if (lseek(fd, 58, SEEK_SET) < 0) + { + return false; + } + } + else if (memcmp(&buf[28], "Speex ", 8) == 0) + { + id3->codectype = AFMT_SPEEX; + id3->frequency = get_slong(&buf[64]); + id3->vbr = get_long_le(&buf[88]); + + header_size = get_long_le(&buf[60]); + + /* Comments are in second Ogg page (byte 108 onwards for Speex) */ + if (lseek(fd, 28 + header_size, SEEK_SET) < 0) + { + return false; + } + } + else + { + /* Unsupported format, try to print the marker, catches Ogg/FLAC at least */ + DEBUGF("Usupported format in Ogg stream: %16s\n", &buf[28]); + return false; + } + + id3->filesize = filesize(fd); + + /* We need to ensure the serial number from this page is the same as the + * one from the last page (since we only support a single bitstream). + */ + serial = get_long_le(&buf[14]); + comment_size = read_vorbis_tags(fd, id3, remaining); + + /* We now need to search for the last page in the file - identified by + * by ('O','g','g','S',0) and retrieve totalsamples. + */ + + /* A page is always < 64 kB */ + if (lseek(fd, -(MIN(64 * 1024, id3->filesize)), SEEK_END) < 0) + { + return false; + } + + remaining = 0; + + while (!eof) + { + r = read(fd, &buf[remaining], MAX_PATH - remaining); + + if (r <= 0) + { + eof = true; + } + else + { + remaining += r; + } + + /* Inefficient (but simple) search */ + i = 0; + + while (i < (remaining - 3)) + { + if ((buf[i] == 'O') && (memcmp(&buf[i], "OggS", 4) == 0)) + { + if (i < (remaining - 17)) + { + /* Note that this only reads the low 32 bits of a + * 64 bit value. + */ + id3->samples = get_long_le(&buf[i + 6]); + last_serial = get_long_le(&buf[i + 14]); + + /* If this page is very small the beginning of the next + * header could be in buffer. Jump near end of this header + * and continue */ + i += 27; + } + else + { + break; + } + } + else + { + i++; + } + } + + if (i < remaining) + { + /* Move the remaining bytes to start of buffer. + * Reuse var 'segments' as it is no longer needed */ + segments = 0; + while (i < remaining) + { + buf[segments++] = buf[i++]; + } + remaining = segments; + } + else + { + /* Discard the rest of the buffer */ + remaining = 0; + } + } + + /* This file has mutiple vorbis bitstreams (or is corrupt). */ + /* FIXME we should display an error here. */ + if (serial != last_serial) + { + logf("serialno mismatch"); + logf("%ld", serial); + logf("%ld", last_serial); + return false; + } + + id3->length = ((int64_t) id3->samples * 1000) / id3->frequency; + if (id3->length <= 0) + { + logf("ogg length invalid!"); + return false; + } + + id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length; + + return true; +} + diff --git a/lib/rbcodec/metadata/oma.c b/lib/rbcodec/metadata/oma.c new file mode 100644 index 0000000000..b82c0a4f73 --- /dev/null +++ b/lib/rbcodec/metadata/oma.c @@ -0,0 +1,189 @@ +/* + * Sony OpenMG (OMA) demuxer + * + * Copyright (c) 2008 Maxim Poliakovski + * 2008 Benjamin Larsson + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file oma.c + * This is a demuxer for Sony OpenMG Music files + * + * Known file extensions: ".oma", "aa3" + * The format of such files consists of three parts: + * - "ea3" header carrying overall info and metadata. + * - "EA3" header is a Sony-specific header containing information about + * the OpenMG file: codec type (usually ATRAC, can also be MP3 or WMA), + * codec specific info (packet size, sample rate, channels and so on) + * and DRM related info (file encryption, content id). + * - Sound data organized in packets follow the EA3 header + * (can be encrypted using the Sony DRM!). + * + * LIMITATIONS: This version supports only plain (unencrypted) OMA files. + * If any DRM-protected (encrypted) file is encountered you will get the + * corresponding error message. Try to remove the encryption using any + * Sony software (for example SonicStage). + * CODEC SUPPORT: Only ATRAC3 codec is currently supported! + */ + +#include +#include +#include +#include +#include "metadata.h" +#include "metadata_parsers.h" + +#define EA3_HEADER_SIZE 96 + +#if 0 +#define DEBUGF printf +#else +#define DEBUGF(...) +#endif + +/* Various helper macros taken from ffmpeg for reading * + * and writing buffers with a specified endianess. */ +# define AV_RB16(x) \ + ((((const uint8_t*)(x))[0] << 8) | \ + ((const uint8_t*)(x))[1]) +# define AV_RB24(x) \ + ((((const uint8_t*)(x))[0] << 16) | \ + (((const uint8_t*)(x))[1] << 8) | \ + ((const uint8_t*)(x))[2]) +# define AV_RB32(x) \ + ((((const uint8_t*)(x))[0] << 24) | \ + (((const uint8_t*)(x))[1] << 16) | \ + (((const uint8_t*)(x))[2] << 8) | \ + ((const uint8_t*)(x))[3]) +# define AV_WL32(p, d) do { \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[2] = (d)>>16; \ + ((uint8_t*)(p))[3] = (d)>>24; \ + } while(0) +# define AV_WL16(p, d) do { \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + } while(0) + +/* Different codecs that could be present in a Sony OMA * + * container file. */ +enum { + OMA_CODECID_ATRAC3 = 0, + OMA_CODECID_ATRAC3P = 1, + OMA_CODECID_MP3 = 3, + OMA_CODECID_LPCM = 4, + OMA_CODECID_WMA = 5, +}; + +/* FIXME: This functions currently read different file * + * parameters required for decoding. It still * + * does not read the metadata - which should be * + * present in the ea3 (first) header. The * + * metadata in ea3 is stored as a variation of * + * the ID3v2 metadata format. */ +static int oma_read_header(int fd, struct mp3entry* id3) +{ + static const uint16_t srate_tab[6] = {320,441,480,882,960,0}; + int ret, ea3_taglen, EA3_pos, jsflag; + uint32_t codec_params; + int16_t eid; + uint8_t buf[EA3_HEADER_SIZE]; + + ret = read(fd, buf, 10); + if (ret != 10) + return -1; + + ea3_taglen = ((buf[6] & 0x7f) << 21) | ((buf[7] & 0x7f) << 14) | ((buf[8] & 0x7f) << 7) | (buf[9] & 0x7f); + + EA3_pos = ea3_taglen + 10; + if (buf[5] & 0x10) + EA3_pos += 10; + + lseek(fd, EA3_pos, SEEK_SET); + ret = read(fd, buf, EA3_HEADER_SIZE); + if (ret != EA3_HEADER_SIZE) + return -1; + + if (memcmp(buf, ((const uint8_t[]){'E', 'A', '3'}),3) || buf[4] != 0 || buf[5] != EA3_HEADER_SIZE) { + DEBUGF("Couldn't find the EA3 header !\n"); + return -1; + } + + eid = AV_RB16(&buf[6]); + if (eid != -1 && eid != -128) { + DEBUGF("Encrypted file! Eid: %d\n", eid); + return -1; + } + + codec_params = AV_RB24(&buf[33]); + + switch (buf[32]) { + case OMA_CODECID_ATRAC3: + id3->frequency = srate_tab[(codec_params >> 13) & 7]*100; + if (id3->frequency != 44100) { + DEBUGF("Unsupported sample rate, send sample file to developers: %d\n", id3->frequency); + return -1; + } + + id3->bytesperframe = (codec_params & 0x3FF) * 8; + id3->codectype = AFMT_OMA_ATRAC3; + jsflag = (codec_params >> 17) & 1; /* get stereo coding mode, 1 for joint-stereo */ + + id3->bitrate = id3->frequency * id3->bytesperframe * 8 / (1024 * 1000); + + /* fake the atrac3 extradata (wav format, makes stream copy to wav work) */ + /* ATRAC3 expects and extra-data size of 14 bytes for wav format, and * + * looks for that in the id3v2buf. */ + id3->extradata_size = 14; + AV_WL16(&id3->id3v2buf[0], 1); // always 1 + AV_WL32(&id3->id3v2buf[2], id3->frequency); // samples rate + AV_WL16(&id3->id3v2buf[6], jsflag); // coding mode + AV_WL16(&id3->id3v2buf[8], jsflag); // coding mode + AV_WL16(&id3->id3v2buf[10], 1); // always 1 + AV_WL16(&id3->id3v2buf[12], 0); // always 0 + + id3->channels = 2; + DEBUGF("sample_rate = %d\n", id3->frequency); + DEBUGF("frame_size = %d\n", id3->bytesperframe); + DEBUGF("stereo_coding_mode = %d\n", jsflag); + break; + default: + DEBUGF("Unsupported codec %d!\n",buf[32]); + return -1; + break; + } + + /* Store the the offset of the first audio frame, to be able to seek to it * + * directly in atrac3_oma.codec. */ + id3->first_frame_offset = EA3_pos + EA3_HEADER_SIZE; + return 0; +} + +bool get_oma_metadata(int fd, struct mp3entry* id3) +{ + if(oma_read_header(fd, id3) < 0) + return false; + + /* Currently, there's no means of knowing the duration * + * directly from the the file so we calculate it. */ + id3->filesize = filesize(fd); + id3->length = ((id3->filesize - id3->first_frame_offset) * 8) / id3->bitrate; + return true; +} diff --git a/lib/rbcodec/metadata/replaygain.c b/lib/rbcodec/metadata/replaygain.c new file mode 100644 index 0000000000..a178321385 --- /dev/null +++ b/lib/rbcodec/metadata/replaygain.c @@ -0,0 +1,222 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Magnus Holmgren + * + * 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. + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "strlcpy.h" +#include "strcasecmp.h" +#include "system.h" +#include "metadata.h" +#include "debug.h" +#include "replaygain.h" +#include "fixedpoint.h" + +#define FP_BITS (12) +#define FP_ONE (1 << FP_BITS) +#define FP_MIN (-48 * FP_ONE) +#define FP_MAX ( 17 * FP_ONE) + +void replaygain_itoa(char* buffer, int length, long int_gain) +{ + /* int_gain uses Q19.12 format. */ + int one = abs(int_gain) >> FP_BITS; + int cent = ((abs(int_gain) & 0x0fff) * 100 + (FP_ONE/2)) >> FP_BITS; + snprintf(buffer, length, "%s%d.%02d dB", (int_gain<0) ? "-":"", one, cent); +} + +static long fp_atof(const char* s, int precision) +{ + long int_part = 0; + long int_one = BIT_N(precision); + long frac_part = 0; + long frac_count = 0; + long frac_max = ((precision * 4) + 12) / 13; + long frac_max_int = 1; + long sign = 1; + bool point = false; + + while ((*s != '\0') && isspace(*s)) + { + s++; + } + + if (*s == '-') + { + sign = -1; + s++; + } + else if (*s == '+') + { + s++; + } + + while (*s != '\0') + { + if (*s == '.') + { + if (point) + { + break; + } + + point = true; + } + else if (isdigit(*s)) + { + if (point) + { + if (frac_count < frac_max) + { + frac_part = frac_part * 10 + (*s - '0'); + frac_count++; + frac_max_int *= 10; + } + } + else + { + int_part = int_part * 10 + (*s - '0'); + } + } + else + { + break; + } + + s++; + } + + while (frac_count < frac_max) + { + frac_part *= 10; + frac_count++; + frac_max_int *= 10; + } + + return sign * ((int_part * int_one) + + (((int64_t) frac_part * int_one) / frac_max_int)); +} + +static long convert_gain(long gain) +{ + /* Don't allow unreasonably low or high gain changes. + * Our math code can't handle it properly anyway. :) */ + gain = MAX(gain, FP_MIN); + gain = MIN(gain, FP_MAX); + + return fp_factor(gain, FP_BITS) << (24 - FP_BITS); +} + +/* Get the sample scale factor in Q19.12 format from a gain value. Returns 0 + * for no gain. + * + * str Gain in dB as a string. E.g., "-3.45 dB"; the "dB" part is ignored. + */ +static long get_replaygain(const char* str) +{ + return fp_atof(str, FP_BITS); +} + +/* Get the peak volume in Q7.24 format. + * + * str Peak volume. Full scale is specified as "1.0". Returns 0 for no peak. + */ +static long get_replaypeak(const char* str) +{ + return fp_atof(str, 24); +} + +/* Get a sample scale factor in Q7.24 format from a gain value. + * + * int_gain Gain in dB, multiplied by 100. + */ +long get_replaygain_int(long int_gain) +{ + return convert_gain(int_gain * FP_ONE / 100); +} + +/* Parse a ReplayGain tag conforming to the "VorbisGain standard". If a + * valid tag is found, update mp3entry struct accordingly. Existing values + * are not overwritten. + * + * key Name of the tag. + * value Value of the tag. + * entry mp3entry struct to update. + */ +void parse_replaygain(const char* key, const char* value, + struct mp3entry* entry) +{ + if (((strcasecmp(key, "replaygain_track_gain") == 0) || + (strcasecmp(key, "rg_radio") == 0)) && + !entry->track_gain) + { + entry->track_level = get_replaygain(value); + entry->track_gain = convert_gain(entry->track_level); + } + else if (((strcasecmp(key, "replaygain_album_gain") == 0) || + (strcasecmp(key, "rg_audiophile") == 0)) && + !entry->album_gain) + { + entry->album_level = get_replaygain(value); + entry->album_gain = convert_gain(entry->album_level); + } + else if (((strcasecmp(key, "replaygain_track_peak") == 0) || + (strcasecmp(key, "rg_peak") == 0)) && + !entry->track_peak) + { + entry->track_peak = get_replaypeak(value); + } + else if ((strcasecmp(key, "replaygain_album_peak") == 0) && + !entry->album_peak) + { + entry->album_peak = get_replaypeak(value); + } +} + +/* Set ReplayGain values from integers. Existing values are not overwritten. + * + * album If true, set album values, otherwise set track values. + * gain Gain value in dB, multiplied by 512. 0 for no gain. + * peak Peak volume in Q7.24 format, where 1.0 is full scale. 0 for no + * peak volume. + * entry mp3entry struct to update. + */ +void parse_replaygain_int(bool album, long gain, long peak, + struct mp3entry* entry) +{ + gain = gain * FP_ONE / 512; + + if (album) + { + entry->album_level = gain; + entry->album_gain = convert_gain(gain); + entry->album_peak = peak; + } + else + { + entry->track_level = gain; + entry->track_gain = convert_gain(gain); + entry->track_peak = peak; + } +} diff --git a/lib/rbcodec/metadata/replaygain.h b/lib/rbcodec/metadata/replaygain.h new file mode 100644 index 0000000000..215464dfdf --- /dev/null +++ b/lib/rbcodec/metadata/replaygain.h @@ -0,0 +1,34 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Magnus Holmgren + * + * 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. + * + ****************************************************************************/ + +#ifndef _REPLAYGAIN_H +#define _REPLAYGAIN_H + +#include "metadata.h" + +long get_replaygain_int(long int_gain); +void parse_replaygain(const char* key, const char* value, + struct mp3entry* entry); +void parse_replaygain_int(bool album, long gain, long peak, + struct mp3entry* entry); +void replaygain_itoa(char* buffer, int length, long int_gain); + +#endif diff --git a/lib/rbcodec/metadata/rm.c b/lib/rbcodec/metadata/rm.c new file mode 100644 index 0000000000..27f541cb25 --- /dev/null +++ b/lib/rbcodec/metadata/rm.c @@ -0,0 +1,464 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2009 Mohamed Tarek + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "logf.h" + +/* Uncomment the following line for debugging */ +//#define DEBUG_RM +#ifndef DEBUG_RM +#undef DEBUGF +#define DEBUGF(...) +#endif + +#define ID3V1_OFFSET -128 +#define METADATA_FOOTER_OFFSET -140 + +static inline void print_cook_extradata(RMContext *rmctx) { + + DEBUGF(" cook_version = 0x%08lx\n", rm_get_uint32be(rmctx->codec_extradata)); + DEBUGF(" samples_per_frame_per_channel = %d\n", rm_get_uint16be(&rmctx->codec_extradata[4])); + DEBUGF(" number_of_subbands_in_freq_domain = %d\n", rm_get_uint16be(&rmctx->codec_extradata[6])); + if(rmctx->extradata_size == 16) { + DEBUGF(" joint_stereo_subband_start = %d\n",rm_get_uint16be(&rmctx->codec_extradata[12])); + DEBUGF(" joint_stereo_vlc_bits = %d\n", rm_get_uint16be(&rmctx->codec_extradata[14])); + } +} + + +struct real_object_t +{ + uint32_t fourcc; + uint32_t size; + uint16_t version; +}; + +static int real_read_object_header(int fd, struct real_object_t* obj) +{ + int n; + + if ((n = read_uint32be(fd, &obj->fourcc)) <= 0) + return n; + if ((n = read_uint32be(fd, &obj->size)) <= 0) + return n; + if ((n = read_uint16be(fd, &obj->version)) <= 0) + return n; + + return 1; +} + +#if (defined(SIMULATOR) && defined(DEBUG_RM)) +static char* fourcc2str(uint32_t f) +{ + static char res[5]; + + res[0] = (f & 0xff000000) >> 24; + res[1] = (f & 0xff0000) >> 16; + res[2] = (f & 0xff00) >> 8; + res[3] = (f & 0xff); + res[4] = 0; + + return res; +} +#endif + +static inline int real_read_audio_stream_info(int fd, RMContext *rmctx) +{ + int skipped = 0; + uint32_t version; + struct real_object_t obj; +#ifdef SIMULATOR + uint32_t header_size; + uint16_t flavor; + uint32_t coded_framesize; + uint8_t interleaver_id_length; + uint8_t fourcc_length; +#endif + uint32_t interleaver_id; + uint32_t fourcc = 0; + + memset(&obj,0,sizeof(obj)); + read_uint32be(fd, &version); + skipped += 4; + + DEBUGF(" version=0x%04lx\n",((version >> 16) & 0xff)); + if (((version >> 16) & 0xff) == 3) { + /* Very old version */ + } else { +#ifdef SIMULATOR + real_read_object_header(fd, &obj); + read_uint32be(fd, &header_size); + /* obj.size will be filled with an unknown value, replaced with header_size */ + DEBUGF(" Object: %s, size: %ld bytes, version: 0x%04x\n",fourcc2str(obj.fourcc),header_size,obj.version); + + read_uint16be(fd, &flavor); + read_uint32be(fd, &coded_framesize); +#else + lseek(fd, 20, SEEK_CUR); +#endif + lseek(fd, 12, SEEK_CUR); /* unknown */ + read_uint16be(fd, &rmctx->sub_packet_h); + read_uint16be(fd, &rmctx->block_align); + read_uint16be(fd, &rmctx->sub_packet_size); + lseek(fd, 2, SEEK_CUR); /* unknown */ + skipped += 40; + if (((version >> 16) & 0xff) == 5) + { + lseek(fd, 6, SEEK_CUR); /* unknown */ + skipped += 6; + } + read_uint16be(fd, &rmctx->sample_rate); + lseek(fd, 4, SEEK_CUR); /* unknown */ + read_uint16be(fd, &rmctx->nb_channels); + skipped += 8; + if (((version >> 16) & 0xff) == 4) + { +#ifdef SIMULATOR + read_uint8(fd, &interleaver_id_length); + read_uint32be(fd, &interleaver_id); + read_uint8(fd, &fourcc_length); +#else + lseek(fd, 6, SEEK_CUR); +#endif + read_uint32be(fd, &fourcc); + skipped += 10; + } + if (((version >> 16) & 0xff) == 5) + { + read_uint32be(fd, &interleaver_id); + read_uint32be(fd, &fourcc); + skipped += 8; + } + lseek(fd, 3, SEEK_CUR); /* unknown */ + skipped += 3; + if (((version >> 16) & 0xff) == 5) + { + lseek(fd, 1, SEEK_CUR); /* unknown */ + skipped += 1; + } + + switch(fourcc) { + case FOURCC('c','o','o','k'): + rmctx->codec_type = CODEC_COOK; + read_uint32be(fd, &rmctx->extradata_size); + skipped += 4; + read(fd, rmctx->codec_extradata, rmctx->extradata_size); + skipped += rmctx->extradata_size; + break; + + case FOURCC('r','a','a','c'): + case FOURCC('r','a','c','p'): + rmctx->codec_type = CODEC_AAC; + read_uint32be(fd, &rmctx->extradata_size); + skipped += 4; + read(fd, rmctx->codec_extradata, rmctx->extradata_size); + skipped += rmctx->extradata_size; + break; + + case FOURCC('d','n','e','t'): + rmctx->codec_type = CODEC_AC3; + break; + + case FOURCC('a','t','r','c'): + rmctx->codec_type = CODEC_ATRAC; + read_uint32be(fd, &rmctx->extradata_size); + skipped += 4; + read(fd, rmctx->codec_extradata, rmctx->extradata_size); + skipped += rmctx->extradata_size; + break; + + default: /* Not a supported codec */ + return -1; + } + + DEBUGF(" flavor = %d\n",flavor); + DEBUGF(" coded_frame_size = %ld\n",coded_framesize); + DEBUGF(" sub_packet_h = %d\n",rmctx->sub_packet_h); + DEBUGF(" frame_size = %d\n",rmctx->block_align); + DEBUGF(" sub_packet_size = %d\n",rmctx->sub_packet_size); + DEBUGF(" sample_rate= %d\n",rmctx->sample_rate); + DEBUGF(" channels= %d\n",rmctx->nb_channels); + DEBUGF(" fourcc = %s\n",fourcc2str(fourcc)); + DEBUGF(" codec_extra_data_length = %ld\n",rmctx->extradata_size); + DEBUGF(" codec_extradata :\n"); + if(rmctx->codec_type == CODEC_COOK) { + DEBUGF(" cook_extradata :\n"); + print_cook_extradata(rmctx); + } + + } + + return skipped; +} + +static int rm_parse_header(int fd, RMContext *rmctx, struct mp3entry *id3) +{ + struct real_object_t obj; + int res; + int skipped; + off_t curpos __attribute__((unused)); + uint8_t len; /* Holds a string_length, which is then passed to read_string() */ + +#ifdef SIMULATOR + uint32_t avg_bitrate = 0; + uint32_t max_packet_size; + uint32_t avg_packet_size; + uint32_t packet_count; + uint32_t duration; + uint32_t preroll; + uint32_t index_offset; + uint16_t stream_id; + uint32_t start_time; + uint32_t codec_data_size; +#endif + uint32_t v; + uint32_t max_bitrate; + uint16_t num_streams; + uint32_t next_data_off; + uint8_t header_end; + + memset(&obj,0,sizeof(obj)); + curpos = lseek(fd, 0, SEEK_SET); + res = real_read_object_header(fd, &obj); + + if (obj.fourcc == FOURCC('.','r','a',0xfd)) + { + /* Very old .ra format - not yet supported */ + return -1; + } + else if (obj.fourcc != FOURCC('.','R','M','F')) + { + return -1; + } + + lseek(fd, 8, SEEK_CUR); /* unknown */ + + DEBUGF("Object: %s, size: %d bytes, version: 0x%04x, pos: %d\n",fourcc2str(obj.fourcc),(int)obj.size,obj.version,(int)curpos); + + res = real_read_object_header(fd, &obj); + header_end = 0; + while(res) + { + DEBUGF("Object: %s, size: %d bytes, version: 0x%04x, pos: %d\n",fourcc2str(obj.fourcc),(int)obj.size,obj.version,(int)curpos); + skipped = 10; + if(obj.fourcc == FOURCC('I','N','D','X')) + break; + switch (obj.fourcc) + { + case FOURCC('P','R','O','P'): /* File properties */ + read_uint32be(fd, &max_bitrate); + read_uint32be(fd, &rmctx->bit_rate); /*avg bitrate*/ +#ifdef SIMULATOR + read_uint32be(fd, &max_packet_size); + read_uint32be(fd, &avg_packet_size); + read_uint32be(fd, &packet_count); +#else + lseek(fd, 3*sizeof(uint32_t), SEEK_CUR); +#endif + read_uint32be(fd, &rmctx->duration); +#ifdef SIMULATOR + read_uint32be(fd, &preroll); + read_uint32be(fd, &index_offset); +#else + lseek(fd, 2*sizeof(uint32_t), SEEK_CUR); +#endif + read_uint32be(fd, &rmctx->data_offset); + read_uint16be(fd, &num_streams); + read_uint16be(fd, &rmctx->flags); + skipped += 40; + + DEBUGF(" max_bitrate = %ld\n",max_bitrate); + DEBUGF(" avg_bitrate = %ld\n",rmctx->bit_rate); + DEBUGF(" max_packet_size = %ld\n",max_packet_size); + DEBUGF(" avg_packet_size = %ld\n",avg_packet_size); + DEBUGF(" packet_count = %ld\n",packet_count); + DEBUGF(" duration = %ld\n",rmctx->duration); + DEBUGF(" preroll = %ld\n",preroll); + DEBUGF(" index_offset = %ld\n",index_offset); + DEBUGF(" data_offset = %ld\n",rmctx->data_offset); + DEBUGF(" num_streams = %d\n",num_streams); + DEBUGF(" flags=0x%04x\n",rmctx->flags); + break; + + case FOURCC('C','O','N','T'): + /* Four strings - Title, Author, Copyright, Comment */ + read_uint8(fd,&len); + skipped += (int)read_string(fd, id3->id3v1buf[0], sizeof(id3->id3v1buf[0]), '\0', len); + read_uint8(fd,&len); + skipped += (int)read_string(fd, id3->id3v1buf[1], sizeof(id3->id3v1buf[1]), '\0', len); + read_uint8(fd,&len); + skipped += (int)read_string(fd, id3->id3v1buf[2], sizeof(id3->id3v1buf[2]), '\0', len); + read_uint8(fd,&len); + skipped += (int)read_string(fd, id3->id3v1buf[3], sizeof(id3->id3v1buf[3]), '\0', len); + skipped += 4; + + DEBUGF(" title=\"%s\"\n",id3->id3v1buf[0]); + DEBUGF(" author=\"%s\"\n",id3->id3v1buf[1]); + DEBUGF(" copyright=\"%s\"\n",id3->id3v1buf[2]); + DEBUGF(" comment=\"%s\"\n",id3->id3v1buf[3]); + break; + + case FOURCC('M','D','P','R'): /* Media properties */ +#ifdef SIMULATOR + read_uint16be(fd,&stream_id); + read_uint32be(fd,&max_bitrate); + read_uint32be(fd,&avg_bitrate); + read_uint32be(fd,&max_packet_size); + read_uint32be(fd,&avg_packet_size); + read_uint32be(fd,&start_time); + read_uint32be(fd,&preroll); + read_uint32be(fd,&duration); +#else + lseek(fd, 30, SEEK_CUR); +#endif + skipped += 30; + read_uint8(fd,&len); + skipped += 1; + lseek(fd, len, SEEK_CUR); /* desc */ + skipped += len; + read_uint8(fd,&len); + skipped += 1; +#ifdef SIMULATOR + lseek(fd, len, SEEK_CUR); /* mimetype */ + read_uint32be(fd,&codec_data_size); +#else + lseek(fd, len + 4, SEEK_CUR); +#endif + skipped += len + 4; + read_uint32be(fd,&v); + skipped += 4; + + DEBUGF(" stream_id = 0x%04x\n",stream_id); + DEBUGF(" max_bitrate = %ld\n",max_bitrate); + DEBUGF(" avg_bitrate = %ld\n",avg_bitrate); + DEBUGF(" max_packet_size = %ld\n",max_packet_size); + DEBUGF(" avg_packet_size = %ld\n",avg_packet_size); + DEBUGF(" start_time = %ld\n",start_time); + DEBUGF(" preroll = %ld\n",preroll); + DEBUGF(" duration = %ld\n",duration); + DEBUGF(" codec_data_size = %ld\n",codec_data_size); + DEBUGF(" v=\"%s\"\n", fourcc2str(v)); + + if (v == FOURCC('.','r','a',0xfd)) + { + int temp; + temp= real_read_audio_stream_info(fd, rmctx); + if(temp < 0) + return -1; + else + skipped += temp; + } + else if (v == FOURCC('L','S','D',':')) + { + DEBUGF("Real audio lossless is not supported."); + return -1; + } + else + { + /* We shall not abort with -1 here. *.rm file often seem + * to have a second media properties header that contains + * other metadata. */ + DEBUGF("Unknown header signature :\"%s\"\n", fourcc2str(v)); + } + + + break; + + case FOURCC('D','A','T','A'): + read_uint32be(fd,&rmctx->nb_packets); + skipped += 4; + read_uint32be(fd,&next_data_off); + skipped += 4; + + /*** + * nb_packets correction : + * in some samples, number of packets may not exactly form + * an integer number of scrambling units. This is corrected + * by constructing a partially filled unit out of the few + * remaining samples at the end of decoding. + ***/ + if(rmctx->nb_packets % rmctx->sub_packet_h) + rmctx->nb_packets += rmctx->sub_packet_h - (rmctx->nb_packets % rmctx->sub_packet_h); + + DEBUGF(" data_nb_packets = %ld\n",rmctx->nb_packets); + DEBUGF(" next DATA offset = %ld\n",next_data_off); + header_end = 1; + break; + } + if(header_end) break; + curpos = lseek(fd, obj.size - skipped, SEEK_CUR); + res = real_read_object_header(fd, &obj); + } + + + return 0; +} + + +bool get_rm_metadata(int fd, struct mp3entry* id3) +{ + RMContext *rmctx = (RMContext*) (( (intptr_t)id3->id3v2buf + 3 ) &~ 3); + memset(rmctx,0,sizeof(RMContext)); + if(rm_parse_header(fd, rmctx, id3) < 0) + return false; + + if (!setid3v1title(fd, id3)) { + /* file has no id3v1 tags, use the tags from CONT chunk */ + id3->title = id3->id3v1buf[0]; + id3->artist = id3->id3v1buf[1]; + id3->comment= id3->id3v1buf[3]; + } + + switch(rmctx->codec_type) + { + case CODEC_COOK: + /* Already set, do nothing */ + break; + case CODEC_AAC: + id3->codectype = AFMT_RM_AAC; + break; + + case CODEC_AC3: + id3->codectype = AFMT_RM_AC3; + break; + + case CODEC_ATRAC: + id3->codectype = AFMT_RM_ATRAC3; + break; + } + + id3->channels = rmctx->nb_channels; + id3->extradata_size = rmctx->extradata_size; + id3->bitrate = rmctx->bit_rate / 1000; + id3->frequency = rmctx->sample_rate; + id3->length = rmctx->duration; + id3->filesize = filesize(fd); + return true; +} diff --git a/lib/rbcodec/metadata/sgc.c b/lib/rbcodec/metadata/sgc.c new file mode 100644 index 0000000000..78cacb9b1b --- /dev/null +++ b/lib/rbcodec/metadata/sgc.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +static bool parse_sgc_header(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + + lseek(fd, 0, SEEK_SET); + if (read(fd, buf, 0xA0) < 0xA0) + return false; + + /* calculate track length with number of tracks */ + id3->length = buf[37] * 1000; + + /* If meta info was found in the m3u skip next step */ + if (id3->title && id3->title[0]) return true; + + char *p = id3->id3v2buf; + + /* Some metadata entries have 32 bytes length */ + /* Game */ + memcpy(p, &buf[64], 32); *(p + 33) = '\0'; + id3->title = p; + p += strlen(p)+1; + + /* Artist */ + memcpy(p, &buf[96], 32); *(p + 33) = '\0'; + id3->artist = p; + p += strlen(p)+1; + + /* Copyright */ + memcpy(p, &buf[128], 32); *(p + 33) = '\0'; + id3->album = p; + p += strlen(p)+1; + return true; +} + + +bool get_sgc_metadata(int fd, struct mp3entry* id3) +{ + uint32_t sgc_type; + if ((lseek(fd, 0, SEEK_SET) < 0) || + read_uint32be(fd, &sgc_type) != (int)sizeof(sgc_type)) + return false; + + id3->vbr = false; + id3->filesize = filesize(fd); + /* we only render 16 bits, 44.1KHz, Stereo */ + id3->bitrate = 706; + id3->frequency = 44100; + + /* Make sure this is an SGC file */ + if (sgc_type != FOURCC('S','G','C',0x1A)) + return false; + + return parse_sgc_header(fd, id3); +} diff --git a/lib/rbcodec/metadata/sid.c b/lib/rbcodec/metadata/sid.c new file mode 100644 index 0000000000..50b879b56d --- /dev/null +++ b/lib/rbcodec/metadata/sid.c @@ -0,0 +1,89 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +/* PSID metadata info is available here: + http://www.unusedino.de/ec64/technical/formats/sidplay.html */ +bool get_sid_metadata(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + char *p; + + + if ((lseek(fd, 0, SEEK_SET) < 0) + || (read(fd, buf, 0x80) < 0x80)) + { + return false; + } + + if ((memcmp(buf, "PSID", 4) != 0)) + { + return false; + } + + p = id3->id3v2buf; + + /* Copy Title (assumed max 0x1f letters + 1 zero byte) */ + id3->title = p; + buf[0x16+0x1f] = 0; + p = iso_decode(&buf[0x16], p, 0, strlen(&buf[0x16])+1); + + /* Copy Artist (assumed max 0x1f letters + 1 zero byte) */ + id3->artist = p; + buf[0x36+0x1f] = 0; + p = iso_decode(&buf[0x36], p, 0, strlen(&buf[0x36])+1); + + /* Copy Year (assumed max 4 letters + 1 zero byte) */ + buf[0x56+0x4] = 0; + id3->year = atoi(&buf[0x56]); + + /* Copy Album (assumed max 0x1f-0x05 letters + 1 zero byte) */ + id3->album = p; + buf[0x56+0x1f] = 0; + iso_decode(&buf[0x5b], p, 0, strlen(&buf[0x5b])+1); + + id3->bitrate = 706; + id3->frequency = 44100; + /* New idea as posted by Marco Alanen (ravon): + * Set the songlength in seconds to the number of subsongs + * so every second represents a subsong. + * Users can then skip the current subsong by seeking + * + * Note: the number of songs is a 16bit value at 0xE, so this code only + * uses the lower 8 bits of the counter. + */ + id3->length = (buf[0xf]-1)*1000; + id3->vbr = false; + id3->filesize = filesize(fd); + + return true; +} diff --git a/lib/rbcodec/metadata/smaf.c b/lib/rbcodec/metadata/smaf.c new file mode 100644 index 0000000000..1b745d3fa1 --- /dev/null +++ b/lib/rbcodec/metadata/smaf.c @@ -0,0 +1,470 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2010 Yoshihisa Uchida + * + * 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. + * + ****************************************************************************/ +#include +#include + +#include "string-extra.h" +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" +#include "logf.h" + +static const int basebits[4] = { 4, 8, 12, 16 }; + +static const int frequency[5] = { 4000, 8000, 11025, 22050, 44100 }; + +static const int support_codepages[5] = { +#ifdef HAVE_LCD_BITMAP + SJIS, ISO_8859_1, -1, GB_2312, BIG_5, +#else + -1, ISO_8859_1, -1, -1, -1, +#endif +}; + +/* extra codepage */ +#define UCS2 (NUM_CODEPAGES + 1) + +/* support id3 tag */ +#define TAG_TITLE (('S'<<8)|'T') +#define TAG_ARTIST (('A'<<8)|'N') +#define TAG_COMPOSER (('S'<<8)|'W') + +/* convert functions */ +#define CONVERT_SMAF_CHANNELS(c) (((c) >> 7) + 1) + + +static inline int convert_smaf_audio_basebit(unsigned int basebit) +{ + if (basebit > 3) + return 0; + return basebits[basebit]; +} + +static inline int convert_smaf_audio_frequency(unsigned int freq) +{ + if (freq > 4) + return 0; + return frequency[freq]; +} + +static int convert_smaf_codetype(unsigned int codetype) +{ + if (codetype < 5) + return support_codepages[codetype]; + else if (codetype == 0x20 || codetype == 0x24) /* In Rockbox, UCS2 and UTF-16 are same. */ + return UCS2; + else if (codetype == 0x23) + return UTF_8; + else if (codetype == 0xff) + return ISO_8859_1; + return -1; +} + +static void set_length(struct mp3entry *id3, unsigned int ch, unsigned int basebit, + unsigned int numbytes) +{ + int bitspersample = convert_smaf_audio_basebit(basebit); + + if (bitspersample != 0 && id3->frequency != 0) + { + /* Calculate track length [ms] and bitrate [kbit/s] */ + id3->length = (uint64_t)numbytes * 8000LL + / (bitspersample * CONVERT_SMAF_CHANNELS(ch) * id3->frequency); + id3->bitrate = bitspersample * id3->frequency / 1000; + } + + /* output contents/wave data/id3 info (for debug) */ + DEBUGF("contents info ----\n"); + DEBUGF(" TITLE: %s\n", (id3->title)? id3->title : "(NULL)"); + DEBUGF(" ARTIST: %s\n", (id3->artist)? id3->artist : "(NULL)"); + DEBUGF(" COMPOSER: %s\n", (id3->composer)? id3->composer : "(NULL)"); + DEBUGF("wave data info ----\n"); + DEBUGF(" channels: %u\n", CONVERT_SMAF_CHANNELS(ch)); + DEBUGF(" bitspersample: %d\n", bitspersample); + DEBUGF(" numbytes; %u\n", numbytes); + DEBUGF("id3 info ----\n"); + DEBUGF(" frquency: %u\n", (unsigned int)id3->frequency); + DEBUGF(" bitrate: %d\n", id3->bitrate); + DEBUGF(" length: %u\n", (unsigned int)id3->length); +} + +/* contents parse functions */ + +/* Note: + * 1) When the codepage is UTF-8 or UCS2, contents data do not start BOM. + * 2) The byte order of contents data is big endian. + */ + +static void decode2utf8(const unsigned char *src, unsigned char **dst, + int srcsize, int *dstsize, int codepage) +{ + unsigned char tmpbuf[srcsize * 3 + 1]; + unsigned char *p; + int utf8size; + + if (codepage < NUM_CODEPAGES) + p = iso_decode(src, tmpbuf, codepage, srcsize); + else /* codepage == UCS2 */ + p = utf16BEdecode(src, tmpbuf, srcsize); + + *p = '\0'; + + strlcpy(*dst, tmpbuf, *dstsize); + utf8size = (p - tmpbuf) + 1; + if (utf8size > *dstsize) + { + DEBUGF("metadata warning: data length: %d > contents store buffer size: %d\n", + utf8size, *dstsize); + utf8size = *dstsize; + } + *dst += utf8size; + *dstsize -= utf8size; +} + +static int read_audio_track_contets(int fd, int codepage, unsigned char **dst, + int *dstsize) +{ + /* value length <= 256 bytes */ + unsigned char buf[256]; + unsigned char *p = buf; + unsigned char *q = buf; + int datasize; + + read(fd, buf, 256); + + while (p - buf < 256 && *p != ',') + { + /* skip yen mark */ + if (codepage != UCS2) + { + if (*p == '\\') + p++; + } + else if (*p == '\0' && *(p+1) == '\\') + p += 2; + + if (*p > 0x7f) + { + if (codepage == UTF_8) + { + while ((*p & MASK) != COMP) + *q++ = *p++; + } +#ifdef HAVE_LCD_BITMAP + else if (codepage == SJIS) + { + if (*p <= 0xa0 || *p >= 0xe0) + *q++ = *p++; + } +#endif + } + + *q++ = *p++; + if (codepage == UCS2) + *q++ = *p++; + } + datasize = p - buf + 1; + lseek(fd, datasize - 256, SEEK_CUR); + + if (dst != NULL) + decode2utf8(buf, dst, q - buf, dstsize, codepage); + + return datasize; +} + +static void read_score_track_contets(int fd, int codepage, int datasize, + unsigned char **dst, int *dstsize) +{ + unsigned char buf[datasize]; + + read(fd, buf, datasize); + decode2utf8(buf, dst, datasize, dstsize, codepage); +} + +/* traverse chunk functions */ + +static unsigned int search_chunk(int fd, const unsigned char *name, int nlen) +{ + unsigned char buf[8]; + unsigned int chunksize; + + while (read(fd, buf, 8) > 0) + { + chunksize = get_long_be(buf + 4); + if (memcmp(buf, name, nlen) == 0) + return chunksize; + + lseek(fd, chunksize, SEEK_CUR); + } + DEBUGF("metadata error: missing '%s' chunk\n", name); + return 0; +} + +static bool parse_smaf_audio_track(int fd, struct mp3entry *id3, unsigned int datasize) +{ + /* temporary buffer */ + unsigned char *tmp = (unsigned char*)id3->path; + /* contents stored buffer */ + unsigned char *buf = id3->id3v2buf; + int bufsize = sizeof(id3->id3v2buf); + + unsigned int chunksize = datasize; + int valsize; + + int codepage; + + /* parse contents info */ + read(fd, tmp, 5); + codepage = convert_smaf_codetype(tmp[2]); + if (codepage < 0) + { + DEBUGF("metadata error: smaf unsupport codetype: %d\n", tmp[2]); + return false; + } + + datasize -= 5; + while ((id3->title == NULL || id3->artist == NULL || id3->composer == NULL) + && (datasize > 0 && bufsize > 0)) + { + if (read(fd, tmp, 3) <= 0) + return false; + + if (tmp[2] != ':') + { + DEBUGF("metadata error: illegal tag: %c%c%c\n", tmp[0], tmp[1], tmp[2]); + return false; + } + switch ((tmp[0]<<8)|tmp[1]) + { + case TAG_TITLE: + id3->title = buf; + valsize = read_audio_track_contets(fd, codepage, &buf, &bufsize); + break; + case TAG_ARTIST: + id3->artist = buf; + valsize = read_audio_track_contets(fd, codepage, &buf, &bufsize); + break; + case TAG_COMPOSER: + id3->composer = buf; + valsize = read_audio_track_contets(fd, codepage, &buf, &bufsize); + break; + default: + valsize = read_audio_track_contets(fd, codepage, NULL, &bufsize); + break; + } + datasize -= (valsize + 3); + } + + /* search PCM Audio Track Chunk */ + lseek(fd, 16 + chunksize, SEEK_SET); + + chunksize = search_chunk(fd, "ATR", 3); + if (chunksize == 0) + { + DEBUGF("metadata error: missing PCM Audio Track Chunk\n"); + return false; + } + + /* + * get format + * tmp + * +0: Format Type + * +1: Sequence Type + * +2: bit 7 0:mono/1:stereo, bit 4-6 format, bit 0-3: frequency + * +3: bit 4-7: base bit + * +4: TimeBase_D + * +5: TimeBase_G + * + * Note: If PCM Audio Track does not include Sequence Data Chunk, + * tmp+6 is the start position of Wave Data Chunk. + */ + read(fd, tmp, 6); + + /* search Wave Data Chunk */ + chunksize = search_chunk(fd, "Awa", 3); + if (chunksize == 0) + { + DEBUGF("metadata error: missing Wave Data Chunk\n"); + return false; + } + + /* set track length and bitrate */ + id3->frequency = convert_smaf_audio_frequency(tmp[2] & 0x0f); + set_length(id3, tmp[2], tmp[3] >> 4, chunksize); + return true; +} + +static bool parse_smaf_score_track(int fd, struct mp3entry *id3) +{ + /* temporary buffer */ + unsigned char *tmp = (unsigned char*)id3->path; + unsigned char *p = tmp; + /* contents stored buffer */ + unsigned char *buf = id3->id3v2buf; + int bufsize = sizeof(id3->id3v2buf); + + unsigned int chunksize; + unsigned int datasize; + int valsize; + + int codepage; + + /* parse Optional Data Chunk */ + read(fd, tmp, 21); + if (memcmp(tmp + 5, "OPDA", 4) != 0) + { + DEBUGF("metadata error: missing Optional Data Chunk\n"); + return false; + } + + /* Optional Data Chunk size */ + chunksize = get_long_be(tmp + 9); + + /* parse Data Chunk */ + if (memcmp(tmp + 13, "Dch", 3) != 0) + { + DEBUGF("metadata error: missing Data Chunk\n"); + return false; + } + + codepage = convert_smaf_codetype(tmp[16]); + if (codepage < 0) + { + DEBUGF("metadata error: smaf unsupport codetype: %d\n", tmp[16]); + return false; + } + + /* Data Chunk size */ + datasize = get_long_be(tmp + 17); + while ((id3->title == NULL || id3->artist == NULL || id3->composer == NULL) + && (datasize > 0 && bufsize > 0)) + { + if (read(fd, tmp, 4) <= 0) + return false; + + valsize = (tmp[2] << 8) | tmp[3]; + datasize -= (valsize + 4); + switch ((tmp[0]<<8)|tmp[1]) + { + case TAG_TITLE: + id3->title = buf; + read_score_track_contets(fd, codepage, valsize, &buf, &bufsize); + break; + case TAG_ARTIST: + id3->artist = buf; + read_score_track_contets(fd, codepage, valsize, &buf, &bufsize); + break; + case TAG_COMPOSER: + id3->composer = buf; + read_score_track_contets(fd, codepage, valsize, &buf, &bufsize); + break; + default: + lseek(fd, valsize, SEEK_CUR); + break; + } + } + + /* search Score Track Chunk */ + lseek(fd, 29 + chunksize, SEEK_SET); + + if (search_chunk(fd, "MTR", 3) == 0) + { + DEBUGF("metadata error: missing Score Track Chunk\n"); + return false; + } + + /* + * search next chunk + * usually, next chunk ('M***') found within 40 bytes. + */ + chunksize = 40; + read(fd, tmp, chunksize); + + tmp[chunksize] = 'M'; /* stopper */ + while (*p != 'M') + p++; + + chunksize -= (p - tmp); + if (chunksize == 0) + { + DEBUGF("metadata error: missing Score Track Stream PCM Data Chunk"); + return false; + } + + /* search Score Track Stream PCM Data Chunk */ + lseek(fd, -chunksize, SEEK_CUR); + if (search_chunk(fd, "Mtsp", 4) == 0) + { + DEBUGF("metadata error: missing Score Track Stream PCM Data Chunk\n"); + return false; + } + + /* + * parse Score Track Stream Wave Data Chunk + * tmp + * +4-7: chunk size (WaveType(3bytes) + wave data count) + * +8: bit 7 0:mono/1:stereo, bit 4-6 format, bit 0-3: base bit + * +9: frequency (MSB) + * +10: frequency (LSB) + */ + read(fd, tmp, 11); + if (memcmp(tmp, "Mwa", 3) != 0) + { + DEBUGF("metadata error: missing Score Track Stream Wave Data Chunk\n"); + return false; + } + + /* set track length and bitrate */ + id3->frequency = (tmp[9] << 8) | tmp[10]; + set_length(id3, tmp[8], tmp[8] & 0x0f, get_long_be(tmp + 4) - 3); + return true; +} + +bool get_smaf_metadata(int fd, struct mp3entry* id3) +{ + /* temporary buffer */ + unsigned char *tmp = (unsigned char *)id3->path; + unsigned int chunksize; + + id3->title = NULL; + id3->artist = NULL; + id3->composer = NULL; + + id3->vbr = false; /* All SMAF files are CBR */ + id3->filesize = filesize(fd); + + /* check File Chunk and Contents Info Chunk */ + lseek(fd, 0, SEEK_SET); + read(fd, tmp, 16); + if ((memcmp(tmp, "MMMD", 4) != 0) || (memcmp(tmp + 8, "CNTI", 4) != 0)) + { + DEBUGF("metadata error: does not smaf format\n"); + return false; + } + + chunksize = get_long_be(tmp + 12); + if (chunksize > 5) + return parse_smaf_audio_track(fd, id3, chunksize); + + return parse_smaf_score_track(fd, id3); +} diff --git a/lib/rbcodec/metadata/spc.c b/lib/rbcodec/metadata/spc.c new file mode 100644 index 0000000000..1c0206205d --- /dev/null +++ b/lib/rbcodec/metadata/spc.c @@ -0,0 +1,130 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "debug.h" +#include "rbunicode.h" + +bool get_spc_metadata(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char * buf = (unsigned char *)id3->path; + char * p; + + unsigned long length; + unsigned long fade; + bool isbinary = true; + int i; + + /* try to get the ID666 tag */ + if ((lseek(fd, 0x2e, SEEK_SET) < 0) + || (read(fd, buf, 0xD2) < 0xD2)) + { + DEBUGF("lseek or read failed\n"); + return false; + } + + p = id3->id3v2buf; + + id3->title = p; + buf[31] = 0; + p = iso_decode(buf, p, 0, 32); + buf += 32; + + id3->album = p; + buf[31] = 0; + p = iso_decode(buf, p, 0, 32); + buf += 48; + + id3->comment = p; + buf[31] = 0; + p = iso_decode(buf, p, 0, 32); + buf += 32; + + /* Date check */ + if(buf[2] == '/' && buf[5] == '/') + isbinary = false; + + /* Reserved bytes check */ + if(buf[0xD2 - 0x2E - 112] >= '0' && + buf[0xD2 - 0x2E - 112] <= '9' && + buf[0xD3 - 0x2E - 112] == 0x00) + isbinary = false; + + /* is length & fade only digits? */ + for (i=0;i<8 && ( + (buf[0xA9 - 0x2E - 112+i]>='0'&&buf[0xA9 - 0x2E - 112+i]<='9') || + buf[0xA9 - 0x2E - 112+i]=='\0'); + i++); + if (i==8) isbinary = false; + + if(isbinary) { + id3->year = buf[0] | (buf[1]<<8); + buf += 11; + + length = (buf[0] | (buf[1]<<8) | (buf[2]<<16)) * 1000; + buf += 3; + + fade = (buf[0] | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24)); + buf += 4; + } else { + char tbuf[6]; + + buf += 6; + buf[4] = 0; + id3->year = atoi(buf); + buf += 5; + + memcpy(tbuf, buf, 3); + tbuf[3] = 0; + length = atoi(tbuf) * 1000; + buf += 3; + + memcpy(tbuf, buf, 5); + tbuf[5] = 0; + fade = atoi(tbuf); + buf += 5; + } + + id3->artist = p; + buf[31] = 0; + iso_decode(buf, p, 0, 32); + + if (length==0) { + length=3*60*1000; /* 3 minutes */ + fade=5*1000; /* 5 seconds */ + } + + id3->length = length+fade; + + id3->filesize = filesize(fd); + id3->genre_string = id3_get_num_genre(36); + + return true; +} diff --git a/lib/rbcodec/metadata/tta.c b/lib/rbcodec/metadata/tta.c new file mode 100644 index 0000000000..1d3d95f118 --- /dev/null +++ b/lib/rbcodec/metadata/tta.c @@ -0,0 +1,123 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2010 Yoshihisa Uchida + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "logf.h" + +#define TTA1_SIGN 0x31415454 + +#define TTA_HEADER_ID 0 +#define TTA_HEADER_AUDIO_FORMAT (TTA_HEADER_ID + sizeof(unsigned int)) +#define TTA_HEADER_NUM_CHANNELS (TTA_HEADER_AUDIO_FORMAT + sizeof(unsigned short)) +#define TTA_HEADER_BITS_PER_SAMPLE (TTA_HEADER_NUM_CHANNELS + sizeof(unsigned short)) +#define TTA_HEADER_SAMPLE_RATE (TTA_HEADER_BITS_PER_SAMPLE + sizeof(unsigned short)) +#define TTA_HEADER_DATA_LENGTH (TTA_HEADER_SAMPLE_RATE + sizeof(unsigned int)) +#define TTA_HEADER_CRC32 (TTA_HEADER_DATA_LENGTH + sizeof(unsigned int)) +#define TTA_HEADER_SIZE (TTA_HEADER_CRC32 + sizeof(unsigned int)) + +#define TTA_HEADER_GETTER_ID(x) get_long_le(x) +#define TTA_HEADER_GETTER_AUDIO_FORMAT(x) get_short_le(x) +#define TTA_HEADER_GETTER_NUM_CHANNELS(x) get_short_le(x) +#define TTA_HEADER_GETTER_BITS_PER_SAMPLE(x) get_short_le(x) +#define TTA_HEADER_GETTER_SAMPLE_RATE(x) get_long_le(x) +#define TTA_HEADER_GETTER_DATA_LENGTH(x) get_long_le(x) +#define TTA_HEADER_GETTER_CRC32(x) get_long_le(x) + +#define GET_HEADER(x, tag) TTA_HEADER_GETTER_ ## tag((x) + TTA_HEADER_ ## tag) + +static void read_id3_tags(int fd, struct mp3entry* id3) +{ + id3->title = NULL; + id3->filesize = filesize(fd); + id3->id3v2len = getid3v2len(fd); + id3->tracknum = 0; + id3->discnum = 0; + id3->vbr = false; /* All TTA files are CBR */ + + /* first get id3v2 tags. if no id3v2 tags ware found, get id3v1 tags */ + if (id3->id3v2len) + { + setid3v2title(fd, id3); + id3->first_frame_offset = id3->id3v2len; + return; + } + setid3v1title(fd, id3); +} + +bool get_tta_metadata(int fd, struct mp3entry* id3) +{ + unsigned char ttahdr[TTA_HEADER_SIZE]; + unsigned int datasize; + unsigned int origsize; + int bps; + + lseek(fd, 0, SEEK_SET); + + /* read id3 tags */ + read_id3_tags(fd, id3); + lseek(fd, id3->id3v2len, SEEK_SET); + + /* read TTA header */ + if (read(fd, ttahdr, TTA_HEADER_SIZE) < 0) + return false; + + /* check for TTA3 signature */ + if ((GET_HEADER(ttahdr, ID)) != TTA1_SIGN) + return false; + + /* skip check CRC */ + + id3->channels = (GET_HEADER(ttahdr, NUM_CHANNELS)); + id3->frequency = (GET_HEADER(ttahdr, SAMPLE_RATE)); + id3->length = ((GET_HEADER(ttahdr, DATA_LENGTH)) / id3->frequency) * 1000LL; + bps = (GET_HEADER(ttahdr, BITS_PER_SAMPLE)); + + datasize = id3->filesize - id3->first_frame_offset; + origsize = (GET_HEADER(ttahdr, DATA_LENGTH)) * ((bps + 7) / 8) * id3->channels; + + id3->bitrate = (int) ((uint64_t) datasize * id3->frequency * id3->channels * bps + / (origsize * 1000LL)); + + /* output header info (for debug) */ + DEBUGF("TTA header info ----\n"); + DEBUGF("id: %x\n", (unsigned int)(GET_HEADER(ttahdr, ID))); + DEBUGF("channels: %d\n", id3->channels); + DEBUGF("frequency: %ld\n", id3->frequency); + DEBUGF("length: %ld\n", id3->length); + DEBUGF("bitrate: %d\n", id3->bitrate); + DEBUGF("bits per sample: %d\n", bps); + DEBUGF("compressed size: %d\n", datasize); + DEBUGF("original size: %d\n", origsize); + DEBUGF("id3----\n"); + DEBUGF("artist: %s\n", id3->artist); + DEBUGF("title: %s\n", id3->title); + DEBUGF("genre: %s\n", id3->genre_string); + + return true; +} diff --git a/lib/rbcodec/metadata/vgm.c b/lib/rbcodec/metadata/vgm.c new file mode 100644 index 0000000000..9ea95b3939 --- /dev/null +++ b/lib/rbcodec/metadata/vgm.c @@ -0,0 +1,195 @@ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" + +/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ + +typedef unsigned char byte; + +enum { header_size = 0x40 }; +enum { max_field = 64 }; + +struct header_t +{ + char tag [4]; + byte data_size [4]; + byte version [4]; + byte psg_rate [4]; + byte ym2413_rate [4]; + byte gd3_offset [4]; + byte track_duration [4]; + byte loop_offset [4]; + byte loop_duration [4]; + byte frame_rate [4]; + byte noise_feedback [2]; + byte noise_width; + byte unused1; + byte ym2612_rate [4]; + byte ym2151_rate [4]; + byte data_offset [4]; + byte unused2 [8]; +}; + +static byte const* skip_gd3_str( byte const* in, byte const* end ) +{ + while ( end - in >= 2 ) + { + in += 2; + if ( !(in [-2] | in [-1]) ) + break; + } + return in; +} + +static byte const* get_gd3_str( byte const* in, byte const* end, char* field ) +{ + byte const* mid = skip_gd3_str( in, end ); + int len = (mid - in) / 2 - 1; + if ( field && len > 0 ) + { + len = len < (int) max_field ? len : (int) max_field; + + field [len] = 0; + /* Conver to utf8 */ + utf16LEdecode( in, field, len ); + + /* Copy string back to id3v2buf */ + strcpy( (char*) in, field ); + } + return mid; +} + +static byte const* get_gd3_pair( byte const* in, byte const* end, char* field ) +{ + return skip_gd3_str( get_gd3_str( in, end, field ), end ); +} + +static void parse_gd3( byte const* in, byte const* end, struct mp3entry* id3 ) +{ + char* p = id3->path; + id3->title = (char *) in; + in = get_gd3_pair( in, end, p ); /* Song */ + + id3->album = (char *) in; + in = get_gd3_pair( in, end, p ); /* Game */ + + in = get_gd3_pair( in, end, NULL ); /* System */ + + id3->artist = (char *) in; + in = get_gd3_pair( in, end, p ); /* Author */ + +#if MEMORYSIZE > 2 + in = get_gd3_str ( in, end, NULL ); /* Copyright */ + in = get_gd3_pair( in, end, NULL ); /* Dumper */ + + id3->comment = (char *) in; + in = get_gd3_str ( in, end, p ); /* Comment */ +#endif +} + +int const gd3_header_size = 12; + +static long check_gd3_header( byte* h, long remain ) +{ + if ( remain < gd3_header_size ) return 0; + if ( memcmp( h, "Gd3 ", 4 ) ) return 0; + if ( get_long_le( h + 4 ) >= 0x200 ) return 0; + + long gd3_size = get_long_le( h + 8 ); + if ( gd3_size > remain - gd3_header_size ) + gd3_size = remain - gd3_header_size; + + return gd3_size; +} + +static void get_vgm_length( struct header_t* h, struct mp3entry* id3 ) +{ + long length = get_long_le( h->track_duration ) * 10 / 441; + if ( length > 0 ) + { + long loop_length = 0, intro_length = 0; + long loop = get_long_le( h->loop_duration ); + if ( loop > 0 && get_long_le( h->loop_offset ) ) + { + loop_length = loop * 10 / 441; + intro_length = length - loop_length; + } + else + { + intro_length = length; /* make it clear that track is no longer than length */ + loop_length = 0; + } + + id3->length = intro_length + 2 * loop_length; /* intro + 2 loops */ + return; + } + + id3->length = 150 * 1000; /* 2.5 minutes */ +} + +bool get_vgm_metadata(int fd, struct mp3entry* id3) +{ + /* Use the id3v2 part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->id3v2buf; + int read_bytes; + + memset(buf, 0, ID3V2_BUF_SIZE); + if ((lseek(fd, 0, SEEK_SET) < 0) + || ((read_bytes = read(fd, buf, header_size)) < header_size)) + { + return false; + } + + id3->vbr = false; + id3->filesize = filesize(fd); + + id3->bitrate = 706; + id3->frequency = 44100; + + /* If file is gzipped, will get metadata later */ + if (memcmp(buf, "Vgm ", 4)) + { + /* We must set a default song length here because + the codec can't do it anymore */ + id3->length = 150 * 1000; /* 2.5 minutes */ + return true; + } + + /* Get song length from header */ + struct header_t* header = (struct header_t*) buf; + get_vgm_length( header, id3 ); + + long gd3_offset = get_long_le( header->gd3_offset ) - 0x2C; + + /* No gd3 tag found */ + if ( gd3_offset < 0 ) + return true; + + /* Seek to gd3 offset and read as + many bytes posible */ + gd3_offset = id3->filesize - (header_size + gd3_offset); + if ((lseek(fd, -gd3_offset, SEEK_END) < 0) + || ((read_bytes = read(fd, buf, ID3V2_BUF_SIZE)) <= 0)) + return true; + + byte* gd3 = buf; + long gd3_size = check_gd3_header( gd3, read_bytes ); + + /* GD3 tag is zero */ + if ( gd3_size == 0 ) + return true; + + /* Finally, parse gd3 tag */ + if ( gd3 ) + parse_gd3( gd3 + gd3_header_size, gd3 + read_bytes, id3 ); + + return true; +} diff --git a/lib/rbcodec/metadata/vorbis.c b/lib/rbcodec/metadata/vorbis.c new file mode 100644 index 0000000000..58bd781873 --- /dev/null +++ b/lib/rbcodec/metadata/vorbis.c @@ -0,0 +1,381 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "structec.h" + +/* Define LOGF_ENABLE to enable logf output in this file */ +/*#define LOGF_ENABLE*/ +#include "logf.h" + +struct file +{ + int fd; + bool packet_ended; + long packet_remaining; +}; + + +/* Read an Ogg page header. file->packet_remaining is set to the size of the + * first packet on the page; file->packet_ended is set to true if the packet + * ended on the current page. Returns true if the page header was + * successfully read. + */ +static bool file_read_page_header(struct file* file) +{ + unsigned char buffer[64]; + ssize_t table_left; + + /* Size of page header without segment table */ + if (read(file->fd, buffer, 27) != 27) + { + return false; + } + + if (memcmp("OggS", buffer, 4)) + { + return false; + } + + /* Skip pattern (4), version (1), flags (1), granule position (8), + * serial (4), pageno (4), checksum (4) + */ + table_left = buffer[26]; + file->packet_remaining = 0; + + /* Read segment table for the first packet */ + do + { + ssize_t count = MIN(sizeof(buffer), (size_t) table_left); + int i; + + if (read(file->fd, buffer, count) < count) + { + return false; + } + + table_left -= count; + + for (i = 0; i < count; i++) + { + file->packet_remaining += buffer[i]; + + if (buffer[i] < 255) + { + file->packet_ended = true; + + /* Skip remainder of the table */ + if (lseek(file->fd, table_left, SEEK_CUR) < 0) + { + return false; + } + + table_left = 0; + break; + } + } + } + while (table_left > 0); + + return true; +} + + +/* Read (up to) buffer_size of data from the file. If buffer is NULL, just + * skip ahead buffer_size bytes (like lseek). Returns number of bytes read, + * 0 if there is no more data to read (in the packet or the file), < 0 if a + * read error occurred. + */ +static ssize_t file_read(struct file* file, void* buffer, size_t buffer_size) +{ + ssize_t done = 0; + ssize_t count = -1; + + do + { + if (file->packet_remaining <= 0) + { + if (file->packet_ended) + { + break; + } + + if (!file_read_page_header(file)) + { + count = -1; + break; + } + } + + count = MIN(buffer_size, (size_t) file->packet_remaining); + + if (buffer) + { + count = read(file->fd, buffer, count); + } + else + { + if (lseek(file->fd, count, SEEK_CUR) < 0) + { + count = -1; + } + } + + if (count <= 0) + { + break; + } + + if (buffer) + { + buffer += count; + } + + buffer_size -= count; + done += count; + file->packet_remaining -= count; + } + while (buffer_size > 0); + + return (count < 0 ? count : done); +} + + +/* Read an int32 from file. Returns false if a read error occurred. + */ +static bool file_read_int32(struct file* file, int32_t* value) +{ + char buf[sizeof(int32_t)]; + + if (file_read(file, buf, sizeof(buf)) < (ssize_t) sizeof(buf)) + { + return false; + } + + *value = get_long_le(buf); + return true; +} + + +/* Read a string from the file. Read up to buffer_size bytes, or, if eos + * != -1, until the eos character is found (eos is not stored in buf, + * unless it is nil). Writes up to buffer_size chars to buf, always + * terminating with a nil. Returns number of chars read or < 0 if a read + * error occurred. + * + * Unfortunately this is a slightly modified copy of read_string() in + * metadata_common.c... + */ +static long file_read_string(struct file* file, char* buffer, + long buffer_size, int eos, long size) +{ + long read_bytes = 0; + + while (size > 0) + { + char c; + + if (file_read(file, &c, 1) != 1) + { + read_bytes = -1; + break; + } + + read_bytes++; + size--; + + if ((eos != -1) && (eos == (unsigned char) c)) + { + break; + } + + if (buffer_size > 1) + { + *buffer++ = c; + buffer_size--; + } + else if (eos == -1) + { + /* No point in reading any more, skip remaining data */ + if (file_read(file, NULL, size) < 0) + { + read_bytes = -1; + } + else + { + read_bytes += size; + } + + break; + } + } + + *buffer = 0; + return read_bytes; +} + + +/* Init struct file for reading from fd. type is the AFMT_* codec type of + * the file, and determines if Ogg pages are to be read. remaining is the + * max amount to read if codec type is FLAC; it is ignored otherwise. + * Returns true if the file was successfully initialized. + */ +static bool file_init(struct file* file, int fd, int type, int remaining) +{ + memset(file, 0, sizeof(*file)); + file->fd = fd; + + if (type == AFMT_OGG_VORBIS || type == AFMT_SPEEX) + { + if (!file_read_page_header(file)) + { + return false; + } + } + + if (type == AFMT_OGG_VORBIS) + { + char buffer[7]; + + /* Read packet header (type and id string) */ + if (file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer)) + { + return false; + } + + /* The first byte of a packet is the packet type; comment packets + * are type 3. + */ + if (buffer[0] != 3) + { + return false; + } + } + else if (type == AFMT_FLAC) + { + file->packet_remaining = remaining; + file->packet_ended = true; + } + + return true; +} + + +/* Read the items in a Vorbis comment packet. For Ogg files, the file must + * be located on a page start, for other files, the beginning of the comment + * data (i.e., the vendor string length). Returns total size of the + * comments, or 0 if there was a read error. + */ +long read_vorbis_tags(int fd, struct mp3entry *id3, + long tag_remaining) +{ + struct file file; + char *buf = id3->id3v2buf; + int32_t comment_count; + int32_t len; + long comment_size = 0; + int buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf); + int i; + + if (!file_init(&file, fd, id3->codectype, tag_remaining)) + { + return 0; + } + + /* Skip vendor string */ + + if (!file_read_int32(&file, &len) || (file_read(&file, NULL, len) < 0)) + { + return 0; + } + + if (!file_read_int32(&file, &comment_count)) + { + return 0; + } + + comment_size += 4 + len + 4; + + for (i = 0; i < comment_count && file.packet_remaining > 0; i++) + { + char name[TAG_NAME_LENGTH]; + int32_t read_len; + + if (!file_read_int32(&file, &len)) + { + return 0; + } + + comment_size += 4 + len; + read_len = file_read_string(&file, name, sizeof(name), '=', len); + + if (read_len < 0) + { + return 0; + } + + len -= read_len; + read_len = file_read_string(&file, id3->path, sizeof(id3->path), -1, len); + + if (read_len < 0) + { + return 0; + } + + logf("Vorbis comment %d: %s=%s", i, name, id3->path); + + /* Is it an embedded cuesheet? */ + if (!strcasecmp(name, "CUESHEET")) + { + id3->has_embedded_cuesheet = true; + id3->embedded_cuesheet.pos = lseek(file.fd, 0, SEEK_CUR) - read_len; + id3->embedded_cuesheet.size = len; + id3->embedded_cuesheet.encoding = CHAR_ENC_UTF_8; + } + else + { + len = parse_tag(name, id3->path, id3, buf, buf_remaining, + TAGTYPE_VORBIS); + } + + buf += len; + buf_remaining -= len; + } + + /* Skip to the end of the block (needed by FLAC) */ + if (file.packet_remaining) + { + if (file_read(&file, NULL, file.packet_remaining) < 0) + { + return 0; + } + } + + return comment_size; +} diff --git a/lib/rbcodec/metadata/vox.c b/lib/rbcodec/metadata/vox.c new file mode 100644 index 0000000000..f6bc849a88 --- /dev/null +++ b/lib/rbcodec/metadata/vox.c @@ -0,0 +1,49 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2010 Yoshihisa Uchida + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "logf.h" + +bool get_vox_metadata(int fd, struct mp3entry* id3) +{ + /* + * vox is headerless format + * + * frequency: 8000 Hz + * channels: mono + * bitspersample: 4 + */ + id3->frequency = 8000; + id3->bitrate = 8000 * 4 / 1000; + id3->vbr = false; /* All VOX files are CBR */ + id3->filesize = filesize(fd); + id3->length = id3->filesize >> 2; + + return true; +} diff --git a/lib/rbcodec/metadata/wave.c b/lib/rbcodec/metadata/wave.c new file mode 100644 index 0000000000..45acea1fa1 --- /dev/null +++ b/lib/rbcodec/metadata/wave.c @@ -0,0 +1,432 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * Copyright (C) 2010 Yoshihisa Uchida + * + * 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. + * + ****************************************************************************/ +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "rbunicode.h" +#include "logf.h" + +#ifdef DEBUGF +#undef DEBUGF +#define DEBUGF(...) +#endif + +/* Wave(RIFF)/Wave64 format */ + + +# define AV_WL32(p, d) do { \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + ((uint8_t*)(p))[2] = (d)>>16; \ + ((uint8_t*)(p))[3] = (d)>>24; \ + } while(0) +# define AV_WL16(p, d) do { \ + ((uint8_t*)(p))[0] = (d); \ + ((uint8_t*)(p))[1] = (d)>>8; \ + } while(0) + +enum { + RIFF_CHUNK = 0, + WAVE_CHUNK, + FMT_CHUNK, + FACT_CHUNK, + DATA_CHUNK, + LIST_CHUNK, +}; + +/* Wave chunk names */ +#define WAVE_CHUNKNAME_LENGTH 4 +#define WAVE_CHUNKSIZE_LENGTH 4 + +static const unsigned char * const wave_chunklist + = "RIFF" + "WAVE" + "fmt " + "fact" + "data" + "LIST"; + +/* Wave64 GUIDs */ +#define WAVE64_CHUNKNAME_LENGTH 16 +#define WAVE64_CHUNKSIZE_LENGTH 8 + +static const unsigned char * const wave64_chunklist + = "riff\x2e\x91\xcf\x11\xa5\xd6\x28\xdb\x04\xc1\x00\x00" + "wave\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" + "fmt \xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" + "fact\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" + "data\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a" + "\xbc\x94\x5f\x92\x5a\x52\xd2\x11\x86\xdc\x00\xc0\x4f\x8e\xdb\x8a"; + +/* list/info chunk */ + +struct info_chunk { + const unsigned char* tag; + size_t offset; +}; + +/* info chunk names are common wave/wave64 */ +static const struct info_chunk info_chunks[] = { + { "INAM", offsetof(struct mp3entry, title), }, /* title */ + { "IART", offsetof(struct mp3entry, artist), }, /* artist */ + { "ISBJ", offsetof(struct mp3entry, albumartist), }, /* albumartist */ + { "IPRD", offsetof(struct mp3entry, album), }, /* album */ + { "IWRI", offsetof(struct mp3entry, composer), }, /* composer */ + { "ICMT", offsetof(struct mp3entry, comment), }, /* comment */ + { "ISRF", offsetof(struct mp3entry, grouping), }, /* grouping */ + { "IGNR", offsetof(struct mp3entry, genre_string), }, /* genre */ + { "ICRD", offsetof(struct mp3entry, year_string), }, /* date */ + { "IPRT", offsetof(struct mp3entry, track_string), }, /* track/trackcount */ + { "IFRM", offsetof(struct mp3entry, disc_string), }, /* disc/disccount */ +}; + +#define INFO_CHUNK_COUNT ((int)ARRAYLEN(info_chunks)) + +/* support formats */ +enum +{ + WAVE_FORMAT_PCM = 0x0001, /* Microsoft PCM Format */ + WAVE_FORMAT_ADPCM = 0x0002, /* Microsoft ADPCM Format */ + WAVE_FORMAT_IEEE_FLOAT = 0x0003, /* IEEE Float */ + WAVE_FORMAT_ALAW = 0x0006, /* Microsoft ALAW */ + WAVE_FORMAT_MULAW = 0x0007, /* Microsoft MULAW */ + WAVE_FORMAT_DVI_ADPCM = 0x0011, /* Intel's DVI ADPCM */ + WAVE_FORMAT_DIALOGIC_OKI_ADPCM = 0x0017, /* Dialogic OKI ADPCM */ + WAVE_FORMAT_YAMAHA_ADPCM = 0x0020, /* Yamaha ADPCM */ + WAVE_FORMAT_XBOX_ADPCM = 0x0069, /* XBOX ADPCM */ + IBM_FORMAT_MULAW = 0x0101, /* same as WAVE_FORMAT_MULAW */ + IBM_FORMAT_ALAW = 0x0102, /* same as WAVE_FORMAT_ALAW */ + WAVE_FORMAT_ATRAC3 = 0x0270, /* Atrac3 stream */ + WAVE_FORMAT_SWF_ADPCM = 0x5346, /* Adobe SWF ADPCM */ + WAVE_FORMAT_EXTENSIBLE = 0xFFFE, +}; + +struct wave_fmt { + unsigned int formattag; + unsigned int channels; + unsigned int blockalign; + unsigned int bitspersample; + unsigned int samplesperblock; + uint32_t totalsamples; + uint64_t numbytes; +}; + +static unsigned char *convert_utf8(const unsigned char *src, unsigned char *dst, + int size, bool is_64) +{ + if (is_64) + { + /* Note: wave64: metadata codepage is UTF-16 only */ + return utf16LEdecode(src, dst, size); + } + return iso_decode(src, dst, -1, size); +} + +static void set_totalsamples(struct wave_fmt *fmt, struct mp3entry* id3) +{ + switch (fmt->formattag) + { + case WAVE_FORMAT_PCM: + case WAVE_FORMAT_IEEE_FLOAT: + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + case IBM_FORMAT_ALAW: + case IBM_FORMAT_MULAW: + fmt->blockalign = fmt->bitspersample * fmt->channels >> 3; + fmt->samplesperblock = 1; + break; + case WAVE_FORMAT_YAMAHA_ADPCM: + if (id3->channels != 0) + { + fmt->samplesperblock = + (fmt->blockalign == ((id3->frequency / 60) + 4) * fmt->channels)? + id3->frequency / 30 : (fmt->blockalign << 1) / fmt->channels; + } + break; + case WAVE_FORMAT_DIALOGIC_OKI_ADPCM: + fmt->blockalign = 1; + fmt->samplesperblock = 2; + break; + case WAVE_FORMAT_SWF_ADPCM: + if (fmt->bitspersample != 0 && id3->channels != 0) + { + fmt->samplesperblock + = (((fmt->blockalign << 3) - 2) / fmt->channels - 22) + / fmt->bitspersample + 1; + } + break; + default: + break; + } + + if (fmt->blockalign != 0) + fmt->totalsamples = (fmt->numbytes / fmt->blockalign) * fmt->samplesperblock; +} + +static void parse_riff_format(unsigned char* buf, int fmtsize, struct wave_fmt *fmt, + struct mp3entry* id3) +{ + /* wFormatTag */ + fmt->formattag = buf[0] | (buf[1] << 8); + /* wChannels */ + fmt->channels = buf[2] | (buf[3] << 8); + /* dwSamplesPerSec */ + id3->frequency = get_long_le(&buf[4]); + /* dwAvgBytesPerSec */ + id3->bitrate = (get_long_le(&buf[8]) * 8) / 1000; + /* wBlockAlign */ + fmt->blockalign = buf[12] | (buf[13] << 8); + /* wBitsPerSample */ + fmt->bitspersample = buf[14] | (buf[15] << 8); + + if (fmt->formattag != WAVE_FORMAT_EXTENSIBLE) + { + if (fmtsize > 19) + { + /* wSamplesPerBlock */ + fmt->samplesperblock = buf[18] | (buf[19] << 8); + } + } + else if (fmtsize > 25) + { + /* wValidBitsPerSample */ + fmt->bitspersample = buf[18] | (buf[19] << 8); + /* SubFormat */ + fmt->formattag = buf[24] | (buf[25] << 8); + } + + /* Check for ATRAC3 stream */ + if (fmt->formattag == WAVE_FORMAT_ATRAC3) + { + int jsflag = 0; + if(id3->bitrate == 66 || id3->bitrate == 94) + jsflag = 1; + + id3->extradata_size = 14; + id3->channels = 2; + id3->codectype = AFMT_OMA_ATRAC3; + id3->bytesperframe = fmt->blockalign; + + /* Store the extradata for the codec */ + AV_WL16(&id3->id3v2buf[0], 1); // always 1 + AV_WL32(&id3->id3v2buf[2], id3->frequency);// samples rate + AV_WL16(&id3->id3v2buf[6], jsflag); // coding mode + AV_WL16(&id3->id3v2buf[8], jsflag); // coding mode + AV_WL16(&id3->id3v2buf[10], 1); // always 1 + AV_WL16(&id3->id3v2buf[12], 0); // always 0 + } +} + +static void parse_list_chunk(int fd, struct mp3entry* id3, int chunksize, bool is_64) +{ + unsigned char tmpbuf[ID3V2_BUF_SIZE]; + unsigned char *bp = tmpbuf; + unsigned char *endp; + unsigned char *data_pos; + unsigned char *tag_pos = id3->id3v2buf; + int datasize; + int infosize; + int remain; + int i; + + if (is_64) + lseek(fd, 4, SEEK_CUR); + else if (read(fd, bp, 4) < 4 || memcmp(bp, "INFO", 4)) + return; + + /* decrease skip bytes */ + chunksize -= 4; + + infosize = read(fd, bp, (ID3V2_BUF_SIZE > chunksize)? chunksize : ID3V2_BUF_SIZE); + if (infosize <= 8) + return; + + endp = bp + infosize; + while (bp < endp) + { + datasize = get_long_le(bp + 4); + data_pos = bp + 8; + remain = ID3V2_BUF_SIZE - (tag_pos - (unsigned char*)id3->id3v2buf); + if (remain < 1) + break; + + for (i = 0; i < INFO_CHUNK_COUNT; i++) + { + if (memcmp(bp, info_chunks[i].tag, 4) == 0) + { + *((char **)(((char*)id3) + info_chunks[i].offset)) = tag_pos; + tag_pos = convert_utf8(data_pos, tag_pos, + (datasize + 1 >= remain )? remain - 1 : datasize, + is_64); + *tag_pos++ = 0; + break; + } + } + bp = data_pos + datasize + (datasize & 1); + }; +} + +static bool read_header(int fd, struct mp3entry* id3, const unsigned char *chunknames, + bool is_64) +{ + /* Use the temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + + struct wave_fmt fmt; + + const unsigned int namelen = (is_64)? WAVE64_CHUNKNAME_LENGTH : WAVE_CHUNKNAME_LENGTH; + const unsigned int sizelen = (is_64)? WAVE64_CHUNKSIZE_LENGTH : WAVE_CHUNKSIZE_LENGTH; + const unsigned int len = namelen + sizelen; + uint64_t chunksize; + uint64_t offset = len + namelen; + int read_data; + + memset(&fmt, 0, sizeof(struct wave_fmt)); + + id3->vbr = false; /* All Wave/Wave64 files are CBR */ + id3->filesize = filesize(fd); + + /* get RIFF chunk header */ + lseek(fd, 0, SEEK_SET); + read(fd, buf, offset); + + if ((memcmp(buf, chunknames + RIFF_CHUNK * namelen, namelen) != 0) || + (memcmp(buf + len, chunknames + WAVE_CHUNK * namelen, namelen) != 0)) + { + DEBUGF("metadata error: missing riff header.\n"); + return false; + } + + /* iterate over WAVE chunks until 'data' chunk */ + while (read(fd, buf, len) > 0) + { + offset += len; + + /* get chunk size (when the header is wave64, chunksize includes GUID and data length) */ + chunksize = (is_64) ? get_uint64_le(buf + namelen) - len : + get_long_le(buf + namelen); + + read_data = 0; + if (memcmp(buf, chunknames + FMT_CHUNK * namelen, namelen) == 0) + { + DEBUGF("find 'fmt ' chunk\n"); + + if (chunksize < 16) + { + DEBUGF("metadata error: 'fmt ' chunk is too small: %d\n", (int)chunksize); + return false; + } + + /* get and parse format */ + read_data = (chunksize > 25)? 26 : chunksize; + + read(fd, buf, read_data); + parse_riff_format(buf, read_data, &fmt, id3); + } + else if (memcmp(buf, chunknames + FACT_CHUNK * namelen, namelen) == 0) + { + DEBUGF("find 'fact' chunk\n"); + + /* dwSampleLength */ + if (chunksize >= sizelen) + { + /* get totalsamples */ + read_data = sizelen; + read(fd, buf, read_data); + fmt.totalsamples = (is_64)? get_uint64_le(buf) : get_long_le(buf); + } + } + else if (memcmp(buf, chunknames + DATA_CHUNK * namelen, namelen) == 0) + { + DEBUGF("find 'data' chunk\n"); + fmt.numbytes = chunksize; + if (fmt.formattag == WAVE_FORMAT_ATRAC3) + id3->first_frame_offset = offset; + } + else if (memcmp(buf, chunknames + LIST_CHUNK * namelen, namelen) == 0) + { + DEBUGF("find 'LIST' chunk\n"); + parse_list_chunk(fd, id3, chunksize, is_64); + lseek(fd, offset, SEEK_SET); + } + + /* padded to next chunk */ + chunksize += ((is_64)? ((1 + ~chunksize) & 0x07) : (chunksize & 1)); + + offset += chunksize; + if (offset >= id3->filesize) + break; + + lseek(fd, chunksize - read_data, SEEK_CUR); + } + + if (fmt.numbytes == 0) + { + DEBUGF("metadata error: read error or missing 'data' chunk.\n"); + return false; + } + + if (fmt.totalsamples == 0) + set_totalsamples(&fmt, id3); + + if (id3->frequency == 0 || id3->bitrate == 0) + { + DEBUGF("metadata error: frequency or bitrate is 0\n"); + return false; + } + + /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ + id3->length = (fmt.formattag != WAVE_FORMAT_ATRAC3)? + (uint64_t)fmt.totalsamples * 1000 / id3->frequency : + ((id3->filesize - id3->first_frame_offset) * 8) / id3->bitrate; + + /* output header/id3 info (for debug) */ + DEBUGF("%s header info ----\n", (is_64)? "wave64" : "wave"); + DEBUGF(" format: %04x\n", (int)fmt.formattag); + DEBUGF(" channels: %u\n", fmt.channels); + DEBUGF(" blockalign: %u\n", fmt.blockalign); + DEBUGF(" bitspersample: %u\n", fmt.bitspersample); + DEBUGF(" samplesperblock: %u\n", fmt.samplesperblock); + DEBUGF(" totalsamples: %u\n", (unsigned int)fmt.totalsamples); + DEBUGF(" numbytes: %u\n", (unsigned int)fmt.numbytes); + DEBUGF("id3 info ----\n"); + DEBUGF(" frequency: %u\n", (unsigned int)id3->frequency); + DEBUGF(" bitrate: %d\n", id3->bitrate); + DEBUGF(" length: %u\n", (unsigned int)id3->length); + + return true; +} + +bool get_wave_metadata(int fd, struct mp3entry* id3) +{ + return read_header(fd, id3, wave_chunklist, false); +} + +bool get_wave64_metadata(int fd, struct mp3entry* id3) +{ + return read_header(fd, id3, wave64_chunklist, true); +} diff --git a/lib/rbcodec/metadata/wavpack.c b/lib/rbcodec/metadata/wavpack.c new file mode 100644 index 0000000000..f2811df8f3 --- /dev/null +++ b/lib/rbcodec/metadata/wavpack.c @@ -0,0 +1,160 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 David Bryant + * + * 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. + * + ****************************************************************************/ +#include +#include +#include +#include +#include + +#include "system.h" +#include "metadata.h" +#include "metadata_common.h" +#include "metadata_parsers.h" +#include "logf.h" + +#define ID_UNIQUE 0x3f +#define ID_LARGE 0x80 +#define ID_SAMPLE_RATE 0x27 + +#define MONO_FLAG 4 +#define HYBRID_FLAG 8 + +static const long wavpack_sample_rates [] = +{ + 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000, 192000 +}; + +/* A simple parser to read basic information from a WavPack file. This + * now works with self-extrating WavPack files and also will scan the + * metadata for non-standard sampling rates. This no longer fails on + * WavPack files containing floating-point audio data because these are + * now converted to standard Rockbox format in the decoder, and also + * handles the case where up to 15 non-audio blocks might occur at the + * beginning of the file. + */ + +bool get_wavpack_metadata(int fd, struct mp3entry* id3) +{ + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = (unsigned char *)id3->path; + uint32_t totalsamples = (uint32_t) -1; + int i; + + for (i = 0; i < 256; ++i) { + + /* at every 256 bytes into file, try to read a WavPack header */ + + if ((lseek(fd, i * 256, SEEK_SET) < 0) || (read(fd, buf, 32) < 32)) + return false; + + /* if valid WavPack 4 header version, break */ + + if (memcmp (buf, "wvpk", 4) == 0 && buf [9] == 4 && + (buf [8] >= 2 && buf [8] <= 0x10)) + break; + } + + if (i == 256) { + logf ("Not a WavPack file"); + return false; + } + + id3->vbr = true; /* All WavPack files are VBR */ + id3->filesize = filesize (fd); + + /* check up to 16 headers before we give up finding one with audio */ + + for (i = 0; i < 16; ++i) { + uint32_t meta_bytes = get_long_le(&buf [4]) - 24; + uint32_t trial_totalsamples = get_long_le(&buf[12]); + uint32_t blockindex = get_long_le(&buf[16]); + uint32_t blocksamples = get_long_le(&buf[20]); + uint32_t flags = get_long_le(&buf[24]); + + if (totalsamples == (uint32_t) -1 && blockindex == 0) + totalsamples = trial_totalsamples; + + if (blocksamples) { + int srindx = ((buf [26] >> 7) & 1) + ((buf [27] << 1) & 14); + + if (srindx == 15) { + uint32_t meta_size; + + id3->frequency = 44100; + + while (meta_bytes >= 6) { + if (read(fd, buf, 2) < 2) + break; + + if (buf [0] & ID_LARGE) { + if (read(fd, buf + 2, 2) < 2) + break; + + meta_size = (buf [1] << 1) + (buf [2] << 9) + (buf [3] << 17); + meta_bytes -= meta_size + 4; + } + else { + meta_size = buf [1] << 1; + meta_bytes -= meta_size + 2; + + if ((buf [0] & ID_UNIQUE) == ID_SAMPLE_RATE) { + if (meta_size == 4 && read(fd, buf + 2, 4) == 4) + id3->frequency = buf [2] + (buf [3] << 8) + (buf [4] << 16); + + break; + } + } + + if (meta_size > 0 && lseek(fd, meta_size, SEEK_CUR) < 0) + break; + } + } + else + id3->frequency = wavpack_sample_rates[srindx]; + + /* if the total number of samples is still unknown, make a guess on the high side (for now) */ + + if (totalsamples == (uint32_t) -1) { + totalsamples = id3->filesize * 3; + + if (!(flags & HYBRID_FLAG)) + totalsamples /= 2; + + if (!(flags & MONO_FLAG)) + totalsamples /= 2; + } + + id3->length = ((int64_t) totalsamples * 1000) / id3->frequency; + id3->bitrate = id3->filesize / (id3->length / 8); + + read_ape_tags(fd, id3); + return true; + } + else { /* block did not contain audio, so seek to the end and see if there's another */ + if ((meta_bytes > 0 && lseek(fd, meta_bytes, SEEK_CUR) < 0) || + read(fd, buf, 32) < 32 || memcmp (buf, "wvpk", 4) != 0) + break; + } + } + + return false; +} diff --git a/lib/rbcodec/rbcodec.make b/lib/rbcodec/rbcodec.make new file mode 100644 index 0000000000..afa289ba28 --- /dev/null +++ b/lib/rbcodec/rbcodec.make @@ -0,0 +1,19 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ + +RBCODEC_LIB = $(RBCODEC_BLD)/librbcodec.a +RBCODEC_SRC := $(call preprocess, $(RBCODEC_DIR)/SOURCES) +RBCODEC_OBJ := $(call c2obj, $(RBCODEC_SRC)) +INCLUDES += -I$(RBCODEC_DIR) -I$(RBCODEC_DIR)/dsp -I$(RBCODEC_DIR)/metadata +OTHER_SRC += $(RBCODEC_SRC) + +$(RBCODEC_BLD)/%.o: $(RBCODEC_DIR)/%.c + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $<)$(CC) $(CFLAGS) $(RBCODEC_CFLAGS) -c $< -o $@ + +$(RBCODEC_LIB): $(RBCODEC_OBJ) + $(call PRINTS,AR $(@F))$(AR) rcs $@ $^ >/dev/null diff --git a/lib/rbcodec/test/SOURCES b/lib/rbcodec/test/SOURCES index 467115e0d6..4c0d906dc5 100644 --- a/lib/rbcodec/test/SOURCES +++ b/lib/rbcodec/test/SOURCES @@ -1,49 +1,8 @@ warble.c -../../../apps/metadata.c -../../../apps/replaygain.c ../../../firmware/buflib.c ../../../firmware/core_alloc.c ../../../firmware/common/strlcpy.c ../../../firmware/common/unicode.c ../../../firmware/common/structec.c -../../../apps/mp3data.c ../../../apps/fixedpoint.c ../../../uisimulator/common/io.c -../../../apps/compressor.c -../../../apps/dsp.c -../../../apps/eq.c -../../../apps/tdspeed.c -../../../apps/metadata/a52.c -../../../apps/metadata/adx.c -../../../apps/metadata/aiff.c -../../../apps/metadata/ape.c -../../../apps/metadata/asap.c -../../../apps/metadata/asf.c -../../../apps/metadata/au.c -../../../apps/metadata/ay.c -../../../apps/metadata/flac.c -../../../apps/metadata/gbs.c -../../../apps/metadata/hes.c -../../../apps/metadata/id3tags.c -../../../apps/metadata/kss.c -../../../apps/metadata/metadata_common.c -../../../apps/metadata/mod.c -../../../apps/metadata/monkeys.c -../../../apps/metadata/mp3.c -../../../apps/metadata/mp4.c -../../../apps/metadata/mpc.c -../../../apps/metadata/nsf.c -../../../apps/metadata/ogg.c -../../../apps/metadata/oma.c -../../../apps/metadata/rm.c -../../../apps/metadata/sgc.c -../../../apps/metadata/sid.c -../../../apps/metadata/smaf.c -../../../apps/metadata/spc.c -../../../apps/metadata/tta.c -../../../apps/metadata/vgm.c -../../../apps/metadata/vorbis.c -../../../apps/metadata/vox.c -../../../apps/metadata/wave.c -../../../apps/metadata/wavpack.c - diff --git a/lib/rbcodec/test/warble.make b/lib/rbcodec/test/warble.make index 0b70e9a932..11f7ab03ec 100644 --- a/lib/rbcodec/test/warble.make +++ b/lib/rbcodec/test/warble.make @@ -9,12 +9,15 @@ +RBCODEC_DIR = $(ROOTDIR)/lib/rbcodec +RBCODEC_BLD = $(BUILDDIR)/lib/rbcodec + FLAGS=-g -D__PCTOOL__ $(TARGET) -Wall SRC= $(call preprocess, $(ROOTDIR)/lib/rbcodec/test/SOURCES) INCLUDES += -I$(ROOTDIR)/apps -I$(ROOTDIR)/apps/codecs -I$(ROOTDIR)/apps/codecs/lib \ - -I$(ROOTDIR)/apps/gui -I$(ROOTDIR)/apps/metadata + -I$(ROOTDIR)/apps/gui INCLUDES += -I$(ROOTDIR)/firmware/export -I$(ROOTDIR)/firmware/include \ -I$(ROOTDIR)/firmware/target/hosted \ -I$(ROOTDIR)/firmware/target/hosted/sdl @@ -30,9 +33,10 @@ endif include $(ROOTDIR)/tools/functions.make include $(ROOTDIR)/apps/codecs/codecs.make +include $(ROOTDIR)/lib/rbcodec/rbcodec.make $(BUILDDIR)/$(BINARY): $(CODECS) -$(BUILDDIR)/$(BINARY): $$(OBJ) +$(BUILDDIR)/$(BINARY): $$(OBJ) $(RBCODEC_LIB) @echo LD $(BINARY) $(SILENT)$(HOSTCC) $(SIMFLAGS) $(LIBS) -o $@ $+ -- cgit v1.2.3