forked from len0rd/rockbox
Build librbcodec with DSP and metadata.
All associated files are moved to /lib/rbcodec. Change-Id: I572ddd2b8a996aae1e98c081d06b1ed356dce222
This commit is contained in:
parent
24bd9d5393
commit
b5716df4cb
80 changed files with 97 additions and 112 deletions
54
apps/SOURCES
54
apps/SOURCES
|
|
@ -26,7 +26,6 @@ menus/audiohw_eq_menu.c
|
|||
menus/eq_menu.c
|
||||
buffering.c
|
||||
voice_thread.c
|
||||
replaygain.c
|
||||
#else /* !SWCODEC */
|
||||
mpeg.c
|
||||
#endif
|
||||
|
|
@ -42,7 +41,6 @@ menus/sound_menu.c
|
|||
menus/time_menu.c
|
||||
#endif
|
||||
misc.c
|
||||
mp3data.c
|
||||
onplay.c
|
||||
playlist.c
|
||||
playlist_catalog.c
|
||||
|
|
@ -168,29 +166,13 @@ pcmbuf.c
|
|||
codec_thread.c
|
||||
playback.c
|
||||
codecs.c
|
||||
dsp.c
|
||||
compressor.c
|
||||
#ifndef HAVE_HARDWARE_BEEP
|
||||
beep.c
|
||||
#endif
|
||||
#ifdef HAVE_PITCHSCREEN
|
||||
tdspeed.c
|
||||
#endif
|
||||
#ifdef HAVE_RECORDING
|
||||
enc_config.c
|
||||
recorder/pcm_record.c
|
||||
#endif
|
||||
eq.c
|
||||
#if defined(CPU_COLDFIRE)
|
||||
dsp_cf.S
|
||||
eq_cf.S
|
||||
#elif defined(CPU_ARM)
|
||||
dsp_arm.S
|
||||
#if ARM_ARCH >= 6
|
||||
dsp_arm_v6.S
|
||||
#endif
|
||||
eq_arm.S
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USB_ENABLE_HID
|
||||
usb_keymaps.c
|
||||
|
|
@ -198,42 +180,6 @@ usb_keymaps.c
|
|||
#ifndef USB_NONE
|
||||
gui/usb_screen.c
|
||||
#endif
|
||||
metadata.c
|
||||
metadata/id3tags.c
|
||||
metadata/mp3.c
|
||||
#if CONFIG_CODEC == SWCODEC
|
||||
metadata/metadata_common.c
|
||||
metadata/aiff.c
|
||||
metadata/ape.c
|
||||
metadata/asf.c
|
||||
metadata/adx.c
|
||||
metadata/flac.c
|
||||
metadata/monkeys.c
|
||||
metadata/mp4.c
|
||||
metadata/mpc.c
|
||||
metadata/ogg.c
|
||||
metadata/sid.c
|
||||
metadata/mod.c
|
||||
metadata/spc.c
|
||||
metadata/vorbis.c
|
||||
metadata/wave.c
|
||||
metadata/wavpack.c
|
||||
metadata/a52.c
|
||||
metadata/asap.c
|
||||
metadata/rm.c
|
||||
metadata/nsf.c
|
||||
metadata/oma.c
|
||||
metadata/smaf.c
|
||||
metadata/au.c
|
||||
metadata/vox.c
|
||||
metadata/tta.c
|
||||
metadata/ay.c
|
||||
metadata/gbs.c
|
||||
metadata/hes.c
|
||||
metadata/sgc.c
|
||||
metadata/vgm.c
|
||||
metadata/kss.c
|
||||
#endif
|
||||
#ifdef HAVE_TAGCACHE
|
||||
tagcache.c
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,363 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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;
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 */
|
||||
1573
apps/dsp.c
1573
apps/dsp.c
File diff suppressed because it is too large
Load diff
125
apps/dsp.h
125
apps/dsp.h
|
|
@ -1,125 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
561
apps/dsp_arm.S
561
apps/dsp_arm.S
|
|
@ -1,561 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <config.h>
|
||||
|
||||
#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 */
|
||||
611
apps/dsp_cf.S
611
apps/dsp_cf.S
|
|
@ -1,611 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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
|
||||
268
apps/eq.c
268
apps/eq.c
|
|
@ -1,268 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <inttypes.h>
|
||||
#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
|
||||
|
||||
50
apps/eq.h
50
apps/eq.h
|
|
@ -1,50 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* 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
|
||||
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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
|
||||
|
||||
91
apps/eq_cf.S
91
apps/eq_cf.S
|
|
@ -1,91 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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
|
||||
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
#include <stdint.h>
|
||||
#include "gcc_extensions.h"
|
||||
|
||||
/** FRACTIONAL MULTIPLICATION - TAKEN FROM apps/dsp.h
|
||||
/** FRACTIONAL MULTIPLICATION
|
||||
* Multiply two fixed point numbers with 31 fractional bits:
|
||||
* FRACMUL(x, y)
|
||||
*
|
||||
|
|
|
|||
641
apps/metadata.c
641
apps/metadata.c
|
|
@ -1,641 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include "string-extra.h"
|
||||
|
||||
#include "debug.h"
|
||||
#include "logf.h"
|
||||
#include "settings.h"
|
||||
#include "cuesheet.h"
|
||||
#include "metadata.h"
|
||||
|
||||
#include "metadata/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__ */
|
||||
353
apps/metadata.h
353
apps/metadata.h
|
|
@ -1,353 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdbool.h>
|
||||
#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<<FP_BITS) */
|
||||
long album_level;
|
||||
long track_gain; /* s19.12 signed fixed point. 0 for no gain. */
|
||||
long album_gain;
|
||||
long track_peak; /* s19.12 signed fixed point. 0 for no peak. */
|
||||
long album_peak;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ALBUMART
|
||||
bool has_embedded_albumart;
|
||||
struct mp3_albumart albumart;
|
||||
#endif
|
||||
|
||||
/* Cuesheet support */
|
||||
bool has_embedded_cuesheet;
|
||||
struct embedded_cuesheet embedded_cuesheet;
|
||||
struct cuesheet *cuesheet;
|
||||
|
||||
/* Musicbrainz Track ID */
|
||||
char* mb_track_id;
|
||||
};
|
||||
|
||||
unsigned int probe_file_format(const char *filename);
|
||||
bool get_metadata(struct mp3entry* id3, int fd, const char* trackname);
|
||||
bool mp3info(struct mp3entry *entry, const char *filename);
|
||||
void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig);
|
||||
void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig);
|
||||
void wipe_mp3entry(struct mp3entry *id3);
|
||||
|
||||
#if CONFIG_CODEC == SWCODEC
|
||||
void fill_metadata_from_path(struct mp3entry *id3, const char *trackname);
|
||||
int get_audio_base_codec_type(int type);
|
||||
void strip_tags(int handle_id);
|
||||
enum data_type get_audio_base_data_type(int afmt);
|
||||
bool format_buffers_with_offset(int afmt);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TAGCACHE
|
||||
bool autoresumable(struct mp3entry *id3);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,591 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "metadata.h"
|
||||
#include "replaygain.h"
|
||||
#include "debug.h"
|
||||
#include "rbunicode.h"
|
||||
#include "metadata_common.h"
|
||||
#include "metadata_parsers.h"
|
||||
#include "system.h"
|
||||
#include <codecs/libasf/asf.h>
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,53 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
@ -1,374 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include "string-extra.h"
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <inttypes.h>
|
||||
#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);
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 */
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "system.h"
|
||||
#include "metadata.h"
|
||||
#include <string-extra.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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<EFBFBD>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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,842 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <string.h>
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#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<<FRAC_BITS)/256, FRAC_BITS) << (FP_BITS-FRAC_BITS)
|
||||
**/
|
||||
peak = (fp_factor((peak-SV8_TO_SV7_CONVERT_PEAK)*16, 12) << 12);
|
||||
|
||||
/* 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, id3);
|
||||
}
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
static int sv8_get_size(uint8_t *buffer, int index, uint64_t *p_size)
|
||||
{
|
||||
unsigned char tmp;
|
||||
uint64_t size = 0;
|
||||
|
||||
do {
|
||||
tmp = buffer[index++];
|
||||
size = (size << 7) | (tmp & 0x7F);
|
||||
} while((tmp & 0x80));
|
||||
|
||||
*p_size = size;
|
||||
return index;
|
||||
}
|
||||
|
||||
bool get_musepack_metadata(int fd, struct mp3entry *id3)
|
||||
{
|
||||
static const int32_t sfreqs[4] = { 44100, 48000, 37800, 32000 };
|
||||
uint32_t header[8];
|
||||
uint64_t samples = 0;
|
||||
int i;
|
||||
|
||||
if (!skip_id3v2(fd, id3))
|
||||
return false;
|
||||
if (read(fd, header, 4*8) != 4*8) return false;
|
||||
/* Musepack files are little endian, might need swapping */
|
||||
for (i = 1; i < 8; i++)
|
||||
header[i] = letoh32(header[i]);
|
||||
if (!memcmp(header, "MP+", 3)) { /* Compare to sig "MP+" */
|
||||
unsigned int streamversion;
|
||||
header[0] = letoh32(header[0]);
|
||||
streamversion = (header[0] >> 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;
|
||||
}
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,464 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <codecs/librm/rm.h>
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,470 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,381 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -1,432 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
849
apps/mp3data.c
849
apps/mp3data.c
|
|
@ -1,849 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#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
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <string.h> /* 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
|
||||
|
|
@ -1113,7 +1113,6 @@ static void load_lrc_file(void)
|
|||
/*******************************
|
||||
* read lyrics from id3
|
||||
*******************************/
|
||||
/* taken from apps/metadata/mp3.c */
|
||||
static unsigned long unsync(unsigned long b0, unsigned long b1,
|
||||
unsigned long b2, unsigned long b3)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,222 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <ctype.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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
|
||||
450
apps/tdspeed.c
450
apps/tdspeed.c
|
|
@ -1,450 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2006 by Nicolas Pitre <nico@cam.org>
|
||||
* Copyright (C) 2006-2007 by Stéphane Doyon <s.doyon@videotron.ca>
|
||||
*
|
||||
* 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 <inttypes.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2006 by Nicolas Pitre <nico@cam.org>
|
||||
* Copyright (C) 2006-2007 by Stéphane Doyon <s.doyon@videotron.ca>
|
||||
*
|
||||
* 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue