ReplayGain support for Ogg Vorbis files (also called VorbisGain) added.
Note that there is a small delay from leaving a setting until the change can be heard (due to audio data buffering). git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7234 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
6bd8e5db08
commit
4a53787992
17 changed files with 404 additions and 38 deletions
|
@ -121,6 +121,8 @@ extern vorbis_comment *ov_comment(OggVorbis_File *vf,int link);
|
||||||
|
|
||||||
extern long ov_read(OggVorbis_File *vf,char *buffer,int length,
|
extern long ov_read(OggVorbis_File *vf,char *buffer,int length,
|
||||||
int *bitstream);
|
int *bitstream);
|
||||||
|
extern long ov_read_fixed(OggVorbis_File *vf,ogg_int32_t ***pcm_channels,
|
||||||
|
int length,int *bitstream);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -1604,3 +1604,47 @@ long ov_read(OggVorbis_File *vf,char *buffer,int bytes_req,int *bitstream){
|
||||||
return(samples);
|
return(samples);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* input values: pcm_channels) a float vector per channel of output
|
||||||
|
length) the sample length being read by the app
|
||||||
|
|
||||||
|
return values: <0) error/hole in data (OV_HOLE), partial open (OV_EINVAL)
|
||||||
|
0) EOF
|
||||||
|
n) number of samples of PCM actually returned. The
|
||||||
|
below works on a packet-by-packet basis, so the
|
||||||
|
return length is not related to the 'length' passed
|
||||||
|
in, just guaranteed to fit.
|
||||||
|
|
||||||
|
*section) set to the logical bitstream number */
|
||||||
|
|
||||||
|
long ov_read_fixed(OggVorbis_File *vf,ogg_int32_t ***pcm_channels,int length,
|
||||||
|
int *bitstream){
|
||||||
|
if(vf->ready_state<OPENED)return(OV_EINVAL);
|
||||||
|
|
||||||
|
#if CONFIG_CPU == MCF5249
|
||||||
|
mcf5249_init_mac();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while(1){
|
||||||
|
if(vf->ready_state==INITSET){
|
||||||
|
ogg_int32_t **pcm;
|
||||||
|
long samples=vorbis_synthesis_pcmout(&vf->vd,&pcm);
|
||||||
|
if(samples){
|
||||||
|
if(pcm_channels)*pcm_channels=pcm;
|
||||||
|
if(samples>length)samples=length;
|
||||||
|
vorbis_synthesis_read(&vf->vd,samples);
|
||||||
|
vf->pcm_offset+=samples;
|
||||||
|
if(bitstream)*bitstream=vf->current_link;
|
||||||
|
return samples;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* suck in another packet */
|
||||||
|
{
|
||||||
|
int ret=_fetch_and_process_packet(vf,1,1);
|
||||||
|
if(ret==OV_EOF)return(0);
|
||||||
|
if(ret<=0)return(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "playback.h"
|
#include "playback.h"
|
||||||
#include "codeclib.h"
|
#include "codeclib.h"
|
||||||
#include "xxx2wav.h"
|
#include "xxx2wav.h"
|
||||||
|
#include "id3.h"
|
||||||
|
|
||||||
struct codec_api *local_rb;
|
struct codec_api *local_rb;
|
||||||
|
|
||||||
|
@ -34,3 +35,11 @@ int codec_init(struct codec_api* rb)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void codec_set_replaygain(struct mp3entry* id3)
|
||||||
|
{
|
||||||
|
local_rb->configure(DSP_SET_TRACK_GAIN, (long *) id3->track_gain);
|
||||||
|
local_rb->configure(DSP_SET_ALBUM_GAIN, (long *) id3->album_gain);
|
||||||
|
local_rb->configure(DSP_SET_TRACK_PEAK, (long *) id3->track_peak);
|
||||||
|
local_rb->configure(DSP_SET_ALBUM_PEAK, (long *) id3->album_peak);
|
||||||
|
}
|
||||||
|
|
|
@ -37,4 +37,4 @@ int memcmp(const void *s1, const void *s2, size_t n);
|
||||||
void* memmove(const void *s1, const void *s2, size_t n);
|
void* memmove(const void *s1, const void *s2, size_t n);
|
||||||
|
|
||||||
int codec_init(struct codec_api* rb);
|
int codec_init(struct codec_api* rb);
|
||||||
|
void codec_set_replaygain(struct mp3entry* id3);
|
||||||
|
|
|
@ -105,16 +105,11 @@ bool vorbis_set_codec_parameters(OggVorbis_File *vf)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rb->id3->frequency != NATIVE_FREQUENCY) {
|
|
||||||
rb->configure(CODEC_DSP_ENABLE, (bool *)true);
|
|
||||||
} else {
|
|
||||||
rb->configure(CODEC_DSP_ENABLE, (bool *)false);
|
|
||||||
}
|
|
||||||
|
|
||||||
rb->configure(DSP_SET_FREQUENCY, (int *)rb->id3->frequency);
|
rb->configure(DSP_SET_FREQUENCY, (int *)rb->id3->frequency);
|
||||||
|
codec_set_replaygain(rb->id3);
|
||||||
|
|
||||||
if (vi->channels == 2) {
|
if (vi->channels == 2) {
|
||||||
rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_INTERLEAVED);
|
rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_NONINTERLEAVED);
|
||||||
} else if (vi->channels == 1) {
|
} else if (vi->channels == 1) {
|
||||||
rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_MONO);
|
rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_MONO);
|
||||||
}
|
}
|
||||||
|
@ -129,14 +124,12 @@ extern char iramend[];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/* reserve the PCM buffer in the IRAM area */
|
|
||||||
static char pcmbuf[4096] IDATA_ATTR;
|
|
||||||
|
|
||||||
/* this is the codec entry point */
|
/* this is the codec entry point */
|
||||||
enum codec_status codec_start(struct codec_api* api)
|
enum codec_status codec_start(struct codec_api* api)
|
||||||
{
|
{
|
||||||
ov_callbacks callbacks;
|
ov_callbacks callbacks;
|
||||||
OggVorbis_File vf;
|
OggVorbis_File vf;
|
||||||
|
ogg_int32_t** pcm;
|
||||||
|
|
||||||
int error;
|
int error;
|
||||||
long n;
|
long n;
|
||||||
|
@ -158,9 +151,11 @@ enum codec_status codec_start(struct codec_api* api)
|
||||||
rb->memcpy(iramstart, iramcopy, iramend-iramstart);
|
rb->memcpy(iramstart, iramcopy, iramend-iramstart);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
rb->configure(CODEC_DSP_ENABLE, (bool *)true);
|
||||||
rb->configure(DSP_DITHER, (bool *)false);
|
rb->configure(DSP_DITHER, (bool *)false);
|
||||||
rb->configure(DSP_SET_SAMPLE_DEPTH, (int *)(16));
|
rb->configure(DSP_SET_SAMPLE_DEPTH, (long *) (24));
|
||||||
|
rb->configure(DSP_SET_CLIP_MAX, (long *) ((1 << 24) - 1));
|
||||||
|
rb->configure(DSP_SET_CLIP_MIN, (long *) -((1 << 24) - 1));
|
||||||
/* Note: These are sane defaults for these values. Perhaps
|
/* Note: These are sane defaults for these values. Perhaps
|
||||||
* they should be set differently based on quality setting
|
* they should be set differently based on quality setting
|
||||||
*/
|
*/
|
||||||
|
@ -245,8 +240,8 @@ enum codec_status codec_start(struct codec_api* api)
|
||||||
rb->seek_time = 0;
|
rb->seek_time = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read host-endian signed 16 bit PCM samples */
|
/* Read host-endian signed 24-bit PCM samples */
|
||||||
n=ov_read(&vf,pcmbuf,sizeof(pcmbuf),¤t_section);
|
n=ov_read_fixed(&vf,&pcm,1024,¤t_section);
|
||||||
|
|
||||||
/* Change DSP and buffer settings for this bitstream */
|
/* Change DSP and buffer settings for this bitstream */
|
||||||
if ( current_section != previous_section ) {
|
if ( current_section != previous_section ) {
|
||||||
|
@ -262,7 +257,8 @@ enum codec_status codec_start(struct codec_api* api)
|
||||||
} else if (n < 0) {
|
} else if (n < 0) {
|
||||||
DEBUGF("Error decoding frame\n");
|
DEBUGF("Error decoding frame\n");
|
||||||
} else {
|
} else {
|
||||||
while (!rb->pcmbuf_insert(pcmbuf, n)) {
|
while (!rb->pcmbuf_insert_split(pcm[0], pcm[1],
|
||||||
|
n * sizeof(ogg_int32_t))) {
|
||||||
rb->sleep(1);
|
rb->sleep(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
136
apps/dsp.c
136
apps/dsp.c
|
@ -16,11 +16,14 @@
|
||||||
* KIND, either express or implied.
|
* KIND, either express or implied.
|
||||||
*
|
*
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
#include <inttypes.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "dsp.h"
|
#include "dsp.h"
|
||||||
#include "kernel.h"
|
#include "kernel.h"
|
||||||
#include "playback.h"
|
#include "playback.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
/* The "dither" code to convert the 24-bit samples produced by libmad was
|
/* The "dither" code to convert the 24-bit samples produced by libmad was
|
||||||
* taken from the coolplayer project - coolplayer.sourceforge.net
|
* taken from the coolplayer project - coolplayer.sourceforge.net
|
||||||
|
@ -35,6 +38,7 @@
|
||||||
#define NATIVE_DEPTH 16
|
#define NATIVE_DEPTH 16
|
||||||
#define SAMPLE_BUF_SIZE 256
|
#define SAMPLE_BUF_SIZE 256
|
||||||
#define RESAMPLE_BUF_SIZE (256 * 4) /* Enough for 11,025 Hz -> 44,100 Hz*/
|
#define RESAMPLE_BUF_SIZE (256 * 4) /* Enough for 11,025 Hz -> 44,100 Hz*/
|
||||||
|
#define DEFAULT_REPLAYGAIN 0x01000000
|
||||||
|
|
||||||
#if defined(CPU_COLDFIRE) && !defined(SIMULATOR)
|
#if defined(CPU_COLDFIRE) && !defined(SIMULATOR)
|
||||||
|
|
||||||
|
@ -50,11 +54,26 @@
|
||||||
: [t] "=r" (t) : [a] "r" (x), [b] "r" (y)); \
|
: [t] "=r" (t) : [a] "r" (x), [b] "r" (y)); \
|
||||||
t; \
|
t; \
|
||||||
})
|
})
|
||||||
|
/* Multiply 2 32-bit integers and of the 40 most significat bits of the
|
||||||
|
* result, return the 32 least significant bits. I.e., like FRACMUL with one
|
||||||
|
* of the arguments shifted 8 bits to the right.
|
||||||
|
*/
|
||||||
|
#define FRACMUL_8(x, y) \
|
||||||
|
({ \
|
||||||
|
long t; \
|
||||||
|
long u; \
|
||||||
|
asm volatile ("mac.l %[a], %[b], %%acc0\n\t" \
|
||||||
|
"move.l %%accext01, %[u]\n\t" \
|
||||||
|
"movclr.l %%acc0, %[t]\n\t" \
|
||||||
|
: [t] "=r" (t), [u] "=r" (u) : [a] "r" (x), [b] "r" (y)); \
|
||||||
|
(t << 8) | (u & 0xff); \
|
||||||
|
})
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#define INIT()
|
#define INIT()
|
||||||
#define FRACMUL(x, y) (long) (((((long long) (x)) * ((long long) (y))) >> 32))
|
#define FRACMUL(x, y) (long) (((((long long) (x)) * ((long long) (y))) >> 32))
|
||||||
|
#define FRACMUL_8(x, y) (long) (((((long long) (x)) * ((long long) (y))) >> 24))
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -63,11 +82,17 @@ struct dsp_config
|
||||||
long frequency;
|
long frequency;
|
||||||
long clip_min;
|
long clip_min;
|
||||||
long clip_max;
|
long clip_max;
|
||||||
|
long track_gain;
|
||||||
|
long album_gain;
|
||||||
|
long track_peak;
|
||||||
|
long album_peak;
|
||||||
|
long replaygain;
|
||||||
int sample_depth;
|
int sample_depth;
|
||||||
int sample_bytes;
|
int sample_bytes;
|
||||||
int stereo_mode;
|
int stereo_mode;
|
||||||
int frac_bits;
|
int frac_bits;
|
||||||
bool dither_enabled;
|
bool dither_enabled;
|
||||||
|
bool new_gain;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct resample_data
|
struct resample_data
|
||||||
|
@ -197,8 +222,6 @@ static long downsample(long *dst, long *src, int count,
|
||||||
int pos = phase >> 16;
|
int pos = phase >> 16;
|
||||||
int i = 1;
|
int i = 1;
|
||||||
|
|
||||||
INIT();
|
|
||||||
|
|
||||||
/* Do we need last sample of previous frame for interpolation? */
|
/* Do we need last sample of previous frame for interpolation? */
|
||||||
if (pos > 0)
|
if (pos > 0)
|
||||||
{
|
{
|
||||||
|
@ -232,8 +255,6 @@ static long upsample(long *dst, long *src, int count, struct resample_data *r)
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int pos;
|
int pos;
|
||||||
|
|
||||||
INIT();
|
|
||||||
|
|
||||||
while ((pos = phase >> 16) == 0)
|
while ((pos = phase >> 16) == 0)
|
||||||
{
|
{
|
||||||
*dst++ = last_sample + FRACMUL((phase & 0xffff) << 15,
|
*dst++ = last_sample + FRACMUL((phase & 0xffff) << 15,
|
||||||
|
@ -352,6 +373,40 @@ static long dither_sample(long sample, long bias, long mask,
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Apply a constant gain to the samples (e.g., for ReplayGain). May update
|
||||||
|
* the src array if gain was applied.
|
||||||
|
* Note that this must be called before the resampler.
|
||||||
|
*/
|
||||||
|
static void apply_gain(long* src[], int count)
|
||||||
|
{
|
||||||
|
if (dsp.replaygain)
|
||||||
|
{
|
||||||
|
long* s0 = src[0];
|
||||||
|
long* s1 = src[1];
|
||||||
|
long* d0 = &sample_buf[0];
|
||||||
|
long* d1 = (s0 == s1) ? d0 : &sample_buf[SAMPLE_BUF_SIZE / 2];
|
||||||
|
long gain = dsp.replaygain;
|
||||||
|
long i;
|
||||||
|
|
||||||
|
|
||||||
|
src[0] = d0;
|
||||||
|
src[1] = d1;
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
*d0++ = FRACMUL_8(*s0++, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s0 != s1)
|
||||||
|
{
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
*d1++ = FRACMUL_8(*s1++, gain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void write_samples(short* dst, long* src[], int count)
|
static void write_samples(short* dst, long* src[], int count)
|
||||||
{
|
{
|
||||||
long* s0 = src[0];
|
long* s0 = src[0];
|
||||||
|
@ -397,11 +452,14 @@ long dsp_process(char* dst, char* src[], long size)
|
||||||
int samples;
|
int samples;
|
||||||
|
|
||||||
size /= dsp.sample_bytes * factor;
|
size /= dsp.sample_bytes * factor;
|
||||||
|
INIT();
|
||||||
|
dsp_set_replaygain(false);
|
||||||
|
|
||||||
while (size > 0)
|
while (size > 0)
|
||||||
{
|
{
|
||||||
samples = convert_to_internal(src, size, tmp);
|
samples = convert_to_internal(src, size, tmp);
|
||||||
size -= samples;
|
size -= samples;
|
||||||
|
apply_gain(tmp, samples);
|
||||||
samples = resample(tmp, samples);
|
samples = resample(tmp, samples);
|
||||||
write_samples((short*) dst, tmp, samples);
|
write_samples((short*) dst, tmp, samples);
|
||||||
written += samples;
|
written += samples;
|
||||||
|
@ -514,9 +572,14 @@ bool dsp_configure(int setting, void *value)
|
||||||
dsp.stereo_mode = STEREO_NONINTERLEAVED;
|
dsp.stereo_mode = STEREO_NONINTERLEAVED;
|
||||||
dsp.clip_max = ((1 << WORD_FRACBITS) - 1);
|
dsp.clip_max = ((1 << WORD_FRACBITS) - 1);
|
||||||
dsp.clip_min = -((1 << WORD_FRACBITS));
|
dsp.clip_min = -((1 << WORD_FRACBITS));
|
||||||
|
dsp.track_gain = 0;
|
||||||
|
dsp.album_gain = 0;
|
||||||
|
dsp.track_peak = 0;
|
||||||
|
dsp.album_peak = 0;
|
||||||
dsp.frequency = NATIVE_FREQUENCY;
|
dsp.frequency = NATIVE_FREQUENCY;
|
||||||
dsp.sample_depth = NATIVE_DEPTH;
|
dsp.sample_depth = NATIVE_DEPTH;
|
||||||
dsp.frac_bits = WORD_FRACBITS;
|
dsp.frac_bits = WORD_FRACBITS;
|
||||||
|
dsp.new_gain = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DSP_DITHER:
|
case DSP_DITHER:
|
||||||
|
@ -524,9 +587,74 @@ bool dsp_configure(int setting, void *value)
|
||||||
dsp.dither_enabled = (bool) value;
|
dsp.dither_enabled = (bool) value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DSP_SET_TRACK_GAIN:
|
||||||
|
dsp.track_gain = (long) value;
|
||||||
|
dsp.new_gain = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DSP_SET_ALBUM_GAIN:
|
||||||
|
dsp.album_gain = (long) value;
|
||||||
|
dsp.new_gain = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DSP_SET_TRACK_PEAK:
|
||||||
|
dsp.track_peak = (long) value;
|
||||||
|
dsp.new_gain = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DSP_SET_ALBUM_PEAK:
|
||||||
|
dsp.album_peak = (long) value;
|
||||||
|
dsp.new_gain = true;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dsp_set_replaygain(bool always)
|
||||||
|
{
|
||||||
|
if (always || dsp.new_gain)
|
||||||
|
{
|
||||||
|
long gain = 0;
|
||||||
|
|
||||||
|
dsp.new_gain = false;
|
||||||
|
|
||||||
|
if (global_settings.replaygain || global_settings.replaygain_noclip)
|
||||||
|
{
|
||||||
|
long peak;
|
||||||
|
|
||||||
|
if (global_settings.replaygain)
|
||||||
|
{
|
||||||
|
gain = (global_settings.replaygain_track || !dsp.album_gain)
|
||||||
|
? dsp.track_gain : dsp.album_gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
peak = (global_settings.replaygain_track || !dsp.album_peak)
|
||||||
|
? dsp.track_peak : dsp.album_peak;
|
||||||
|
|
||||||
|
if (gain == 0)
|
||||||
|
{
|
||||||
|
/* So that noclip can work even with no gain information. */
|
||||||
|
gain = DEFAULT_REPLAYGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (global_settings.replaygain_noclip && (peak != 0)
|
||||||
|
&& ((((int64_t) gain * peak) >> 24) >= DEFAULT_REPLAYGAIN))
|
||||||
|
{
|
||||||
|
gain = (((int64_t) DEFAULT_REPLAYGAIN << 24) / peak);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gain == DEFAULT_REPLAYGAIN)
|
||||||
|
{
|
||||||
|
/* Nothing to do, disable processing. */
|
||||||
|
gain = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp.replaygain = gain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -33,5 +33,6 @@ long dsp_input_size(long size);
|
||||||
long dsp_output_size(long size);
|
long dsp_output_size(long size);
|
||||||
int dsp_stereo_mode(void);
|
int dsp_stereo_mode(void);
|
||||||
bool dsp_configure(int setting, void *value);
|
bool dsp_configure(int setting, void *value);
|
||||||
|
void dsp_set_replaygain(bool always);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -3185,3 +3185,56 @@ eng: "Restarting playback..."
|
||||||
voice: "Restarting playback..."
|
voice: "Restarting playback..."
|
||||||
new:
|
new:
|
||||||
|
|
||||||
|
id: LANG_REPLAYGAIN
|
||||||
|
desc: in replaygain
|
||||||
|
eng: "Replaygain"
|
||||||
|
voice "Replaygain"
|
||||||
|
new:
|
||||||
|
|
||||||
|
id: LANG_REPLAYGAIN_ENABLE
|
||||||
|
desc: in replaygain
|
||||||
|
eng: "Enable replaygain"
|
||||||
|
voice "Enable replaygain"
|
||||||
|
new:
|
||||||
|
|
||||||
|
id: LANG_REPLAYGAIN_NOCLIP
|
||||||
|
desc: in replaygain
|
||||||
|
eng: "Prevent clipping"
|
||||||
|
voice "Prevent clipping"
|
||||||
|
new:
|
||||||
|
|
||||||
|
id: LANG_REPLAYGAIN_MODE
|
||||||
|
desc: in replaygain
|
||||||
|
eng: "Replaygain type"
|
||||||
|
voice "Replaygain type"
|
||||||
|
new:
|
||||||
|
|
||||||
|
id: LANG_TRACK_GAIN
|
||||||
|
desc: in replaygain
|
||||||
|
eng: "Track gain"
|
||||||
|
voice "Track gain"
|
||||||
|
new:
|
||||||
|
|
||||||
|
id: LANG_ALBUM_GAIN
|
||||||
|
desc: in replaygain
|
||||||
|
eng: "Album gain"
|
||||||
|
voice "Album gain"
|
||||||
|
new:
|
||||||
|
|
||||||
|
id: LANG_ID3_TRACK_GAIN
|
||||||
|
desc: in browse_id3
|
||||||
|
eng: "[Track gain]"
|
||||||
|
voice ""
|
||||||
|
new:
|
||||||
|
|
||||||
|
id: LANG_ID3_ALBUM_GAIN
|
||||||
|
desc: in browse_id3
|
||||||
|
eng: "[Album gain]"
|
||||||
|
voice ""
|
||||||
|
new:
|
||||||
|
|
||||||
|
id: LANG_ID3_NO_GAIN
|
||||||
|
desc: in browse_id3
|
||||||
|
eng: "<No gain>"
|
||||||
|
voice ""
|
||||||
|
new:
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
#include "mp3_playback.h"
|
#include "mp3_playback.h"
|
||||||
#include "logf.h"
|
#include "logf.h"
|
||||||
#include "atoi.h"
|
#include "atoi.h"
|
||||||
|
#include "replaygain.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
/* Simple file type probing by looking filename extension. */
|
/* Simple file type probing by looking filename extension. */
|
||||||
int probe_file_format(const char *filename)
|
int probe_file_format(const char *filename)
|
||||||
|
@ -283,7 +285,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
|
||||||
/* We now need to search for the last page in the file - identified by
|
/* We now need to search for the last page in the file - identified by
|
||||||
by ('O','g','g','S',0) and retrieve totalsamples */
|
by ('O','g','g','S',0) and retrieve totalsamples */
|
||||||
|
|
||||||
lseek(fd, -32*1024, SEEK_END);
|
lseek(fd, -64*1024, SEEK_END); /* A page is always < 64 kB */
|
||||||
eof=0;
|
eof=0;
|
||||||
j=0; /* The number of bytes currently in buffer */
|
j=0; /* The number of bytes currently in buffer */
|
||||||
i=0;
|
i=0;
|
||||||
|
@ -300,6 +302,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
|
||||||
while (i < (j-5)) {
|
while (i < (j-5)) {
|
||||||
if (memcmp(&buf[i],"OggS",5)==0) {
|
if (memcmp(&buf[i],"OggS",5)==0) {
|
||||||
if (i < (j-17)) {
|
if (i < (j-17)) {
|
||||||
|
/* Note that this only reads the low 32 bits of a 64 bit value */
|
||||||
totalsamples=(buf[i+6])|(buf[i+7]<<8)|(buf[i+8]<<16)|(buf[i+9]<<24);
|
totalsamples=(buf[i+6])|(buf[i+7]<<8)|(buf[i+8]<<16)|(buf[i+9]<<24);
|
||||||
last_serialno=(buf[i+14])|(buf[i+15]<<8)|(buf[i+16]<<16)|(buf[i+17]<<24);
|
last_serialno=(buf[i+14])|(buf[i+15]<<8)|(buf[i+16]<<16)|(buf[i+17]<<24);
|
||||||
j=0; /* We can discard the rest of the buffer */
|
j=0; /* We can discard the rest of the buffer */
|
||||||
|
@ -761,7 +764,7 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
|
||||||
int comment_length;
|
int comment_length;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
unsigned char temp[300];
|
unsigned char temp[300];
|
||||||
int buffer_remaining = sizeof(entry->id3v2buf);
|
int buffer_remaining = sizeof(entry->id3v2buf) + sizeof(entry->id3v1buf);
|
||||||
char *buffer = entry->id3v2buf;
|
char *buffer = entry->id3v2buf;
|
||||||
char **p = NULL;
|
char **p = NULL;
|
||||||
int segments;
|
int segments;
|
||||||
|
@ -884,6 +887,34 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
|
||||||
} else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) {
|
} else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) {
|
||||||
name_length = 11;
|
name_length = 11;
|
||||||
p = &(entry->track_string);
|
p = &(entry->track_string);
|
||||||
|
} else if ((strncasecmp(temp, "RG_RADIO=", 9) == 0)
|
||||||
|
&& !entry->track_gain) {
|
||||||
|
entry->track_gain = get_replaygain(&temp[9]);
|
||||||
|
name_length = 8;
|
||||||
|
p = &(entry->track_gain_str);
|
||||||
|
} else if (strncasecmp(temp, "REPLAYGAIN_TRACK_GAIN=", 22) == 0) {
|
||||||
|
entry->track_gain = get_replaygain(&temp[22]);
|
||||||
|
name_length = 21;
|
||||||
|
p = &(entry->track_gain_str);
|
||||||
|
} else if ((strncasecmp(temp, "RG_AUDIOPHILE=", 14) == 0)
|
||||||
|
&& !entry->album_gain) {
|
||||||
|
entry->album_gain = get_replaygain(&temp[14]);
|
||||||
|
name_length = 13;
|
||||||
|
p = &(entry->album_gain_str);
|
||||||
|
} else if (strncasecmp(temp, "REPLAYGAIN_ALBUM_GAIN=", 22) == 0) {
|
||||||
|
entry->album_gain = get_replaygain(&temp[22]);
|
||||||
|
name_length = 21;
|
||||||
|
p = &(entry->album_gain_str);
|
||||||
|
} else if ((strncasecmp(temp, "RG_PEAK=", 8) == 0)
|
||||||
|
&& !entry->track_peak) {
|
||||||
|
entry->track_peak = get_replaypeak(&temp[8]);
|
||||||
|
p = NULL;
|
||||||
|
} else if (strncasecmp(temp, "REPLAYGAIN_TRACK_PEAK=", 22) == 0) {
|
||||||
|
entry->track_peak = get_replaypeak(&temp[22]);
|
||||||
|
p = NULL;
|
||||||
|
} else if (strncasecmp(temp, "REPLAYGAIN_ALBUM_PEAK=", 22) == 0) {
|
||||||
|
entry->album_peak = get_replaypeak(&temp[22]);
|
||||||
|
p = NULL;
|
||||||
} else {
|
} else {
|
||||||
p = NULL;
|
p = NULL;
|
||||||
}
|
}
|
||||||
|
@ -902,4 +933,3 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,11 @@ enum {
|
||||||
DSP_SET_SAMPLE_DEPTH,
|
DSP_SET_SAMPLE_DEPTH,
|
||||||
DSP_SET_STEREO_MODE,
|
DSP_SET_STEREO_MODE,
|
||||||
DSP_RESET,
|
DSP_RESET,
|
||||||
DSP_DITHER
|
DSP_DITHER,
|
||||||
|
DSP_SET_TRACK_GAIN,
|
||||||
|
DSP_SET_ALBUM_GAIN,
|
||||||
|
DSP_SET_TRACK_PEAK,
|
||||||
|
DSP_SET_ALBUM_PEAK
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Not yet implemented. */
|
/* Not yet implemented. */
|
||||||
|
|
|
@ -1274,7 +1274,11 @@ bool browse_id3(void)
|
||||||
struct mp3entry* id3 = audio_current_track();
|
struct mp3entry* id3 = audio_current_track();
|
||||||
int button;
|
int button;
|
||||||
int menu_pos = 0;
|
int menu_pos = 0;
|
||||||
|
#if CONFIG_HWCODEC == MASNONE
|
||||||
|
int menu_max = 12;
|
||||||
|
#else
|
||||||
int menu_max = 10;
|
int menu_max = 10;
|
||||||
|
#endif
|
||||||
bool exit = false;
|
bool exit = false;
|
||||||
char scroll_text[MAX_PATH];
|
char scroll_text[MAX_PATH];
|
||||||
|
|
||||||
|
@ -1381,6 +1385,21 @@ bool browse_id3(void)
|
||||||
lcd_puts(0, 0, str(LANG_ID3_PATH));
|
lcd_puts(0, 0, str(LANG_ID3_PATH));
|
||||||
lcd_puts_scroll(0, 1, id3->path);
|
lcd_puts_scroll(0, 1, id3->path);
|
||||||
break;
|
break;
|
||||||
|
#if CONFIG_HWCODEC == MASNONE
|
||||||
|
case 11:
|
||||||
|
lcd_puts(0, 0, str(LANG_ID3_TRACK_GAIN));
|
||||||
|
lcd_puts(0, 1, id3->track_gain_str
|
||||||
|
? id3->track_gain_str
|
||||||
|
: (char*) str(LANG_ID3_NO_GAIN));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 12:
|
||||||
|
lcd_puts(0, 0, str(LANG_ID3_ALBUM_GAIN));
|
||||||
|
lcd_puts(0, 1, id3->album_gain_str
|
||||||
|
? id3->album_gain_str
|
||||||
|
: (char*) str(LANG_ID3_NO_GAIN));
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
lcd_update();
|
lcd_update();
|
||||||
|
|
||||||
|
|
|
@ -416,6 +416,9 @@ static const struct bit_entry hd_bits[] =
|
||||||
|
|
||||||
#if CONFIG_HWCODEC == MASNONE
|
#if CONFIG_HWCODEC == MASNONE
|
||||||
{2, S_O(crossfade), 0, "crossfade type", "off,crossfade,mix"},
|
{2, S_O(crossfade), 0, "crossfade type", "off,crossfade,mix"},
|
||||||
|
{1, S_O(replaygain), false, "replaygain", off_on },
|
||||||
|
{1, S_O(replaygain_track), false, "replaygain type", "track,album" },
|
||||||
|
{1, S_O(replaygain_noclip), false, "replaygain noclip", off_on },
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* new stuff to be added at the end */
|
/* new stuff to be added at the end */
|
||||||
|
|
|
@ -328,6 +328,12 @@ struct user_settings
|
||||||
|
|
||||||
bool next_folder; /* move to next folder */
|
bool next_folder; /* move to next folder */
|
||||||
bool runtimedb; /* runtime database active? */
|
bool runtimedb; /* runtime database active? */
|
||||||
|
|
||||||
|
#if CONFIG_HWCODEC == MASNONE
|
||||||
|
bool replaygain; /* enable replaygain */
|
||||||
|
bool replaygain_track; /* true for track gain, false for album gain */
|
||||||
|
bool replaygain_noclip; /* scale to prevent clips */
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
enum optiontype { INT, BOOL };
|
enum optiontype { INT, BOOL };
|
||||||
|
|
|
@ -66,6 +66,7 @@ void dac_line_in(bool enable);
|
||||||
#if CONFIG_HWCODEC == MASNONE
|
#if CONFIG_HWCODEC == MASNONE
|
||||||
#include "pcmbuf.h"
|
#include "pcmbuf.h"
|
||||||
#include "pcm_playback.h"
|
#include "pcm_playback.h"
|
||||||
|
#include "dsp.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_CHARGING
|
#ifdef HAVE_CHARGING
|
||||||
|
@ -1187,6 +1188,56 @@ static bool runtimedb(void)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CONFIG_HWCODEC == MASNONE
|
||||||
|
static bool replaygain(void)
|
||||||
|
{
|
||||||
|
bool result = set_bool(str(LANG_REPLAYGAIN_ENABLE),
|
||||||
|
&global_settings.replaygain);
|
||||||
|
|
||||||
|
dsp_set_replaygain(true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool replaygain_mode(void)
|
||||||
|
{
|
||||||
|
bool result = set_bool_options(str(LANG_REPLAYGAIN_MODE),
|
||||||
|
&global_settings.replaygain_track,
|
||||||
|
STR(LANG_TRACK_GAIN),
|
||||||
|
STR(LANG_ALBUM_GAIN),
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
dsp_set_replaygain(true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool replaygain_noclip(void)
|
||||||
|
{
|
||||||
|
bool result = set_bool(str(LANG_REPLAYGAIN_NOCLIP),
|
||||||
|
&global_settings.replaygain_noclip);
|
||||||
|
|
||||||
|
dsp_set_replaygain(true);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool replaygain_settings_menu(void)
|
||||||
|
{
|
||||||
|
int m;
|
||||||
|
bool result;
|
||||||
|
|
||||||
|
static const struct menu_item items[] = {
|
||||||
|
{ ID2P(LANG_REPLAYGAIN_ENABLE), replaygain },
|
||||||
|
{ ID2P(LANG_REPLAYGAIN_NOCLIP), replaygain_noclip },
|
||||||
|
{ ID2P(LANG_REPLAYGAIN_MODE), replaygain_mode },
|
||||||
|
};
|
||||||
|
|
||||||
|
m=menu_init( items, sizeof(items) / sizeof(*items), NULL,
|
||||||
|
NULL, NULL, NULL);
|
||||||
|
result = menu_run(m);
|
||||||
|
menu_exit(m);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static bool playback_settings_menu(void)
|
static bool playback_settings_menu(void)
|
||||||
{
|
{
|
||||||
int m;
|
int m;
|
||||||
|
@ -1203,6 +1254,7 @@ static bool playback_settings_menu(void)
|
||||||
#if CONFIG_HWCODEC == MASNONE
|
#if CONFIG_HWCODEC == MASNONE
|
||||||
{ ID2P(LANG_CROSSFADE), crossfade },
|
{ ID2P(LANG_CROSSFADE), crossfade },
|
||||||
{ ID2P(LANG_CROSSFADE_DURATION), crossfade_duration },
|
{ ID2P(LANG_CROSSFADE_DURATION), crossfade_duration },
|
||||||
|
{ ID2P(LANG_REPLAYGAIN), replaygain_settings_menu },
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_SPDIF_POWER
|
#ifdef HAVE_SPDIF_POWER
|
||||||
{ ID2P(LANG_SPDIF_ENABLE), spdif },
|
{ ID2P(LANG_SPDIF_ENABLE), spdif },
|
||||||
|
|
|
@ -129,6 +129,9 @@ drivers/uda1380.c
|
||||||
#if (CONFIG_HWCODEC == MASNONE) && !defined(SIMULATOR)
|
#if (CONFIG_HWCODEC == MASNONE) && !defined(SIMULATOR)
|
||||||
pcm_playback.c
|
pcm_playback.c
|
||||||
#endif
|
#endif
|
||||||
|
#if CONFIG_HWCODEC == MASNONE
|
||||||
|
replaygain.c
|
||||||
|
#endif
|
||||||
#if defined(HAVE_UDA1380) && !defined(SIMULATOR)
|
#if defined(HAVE_UDA1380) && !defined(SIMULATOR)
|
||||||
pcm_record.c
|
pcm_record.c
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -179,6 +179,11 @@ SECTIONS
|
||||||
_iramcopy = .;
|
_iramcopy = .;
|
||||||
} > DRAM
|
} > DRAM
|
||||||
|
|
||||||
|
/DISCARD/ :
|
||||||
|
{
|
||||||
|
*(.eh_frame)
|
||||||
|
}
|
||||||
|
|
||||||
.iram IRAMORIG : AT ( _iramcopy)
|
.iram IRAMORIG : AT ( _iramcopy)
|
||||||
{
|
{
|
||||||
_iramstart = .;
|
_iramstart = .;
|
||||||
|
|
|
@ -115,6 +115,17 @@ struct mp3entry {
|
||||||
short voladjust;
|
short voladjust;
|
||||||
long playcount;
|
long playcount;
|
||||||
long lastplayed;
|
long lastplayed;
|
||||||
|
|
||||||
|
/* replaygain support */
|
||||||
|
|
||||||
|
#if CONFIG_HWCODEC == MASNONE
|
||||||
|
char* track_gain_str;
|
||||||
|
char* album_gain_str;
|
||||||
|
long track_gain; /* 7.24 signed fixed point. 0 for no gain. */
|
||||||
|
long album_gain;
|
||||||
|
long track_peak; /* 7.24 signed fixed point. 0 for no peak. */
|
||||||
|
long album_peak;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
|
Loading…
Reference in a new issue