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:
Magnus Holmgren 2005-07-24 15:32:28 +00:00
parent 6bd8e5db08
commit 4a53787992
17 changed files with 404 additions and 38 deletions

View file

@ -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,
int *bitstream);
extern long ov_read_fixed(OggVorbis_File *vf,ogg_int32_t ***pcm_channels,
int length,int *bitstream);
#ifdef __cplusplus
}

View file

@ -1604,3 +1604,47 @@ long ov_read(OggVorbis_File *vf,char *buffer,int bytes_req,int *bitstream){
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);
}
}
}

View file

@ -23,6 +23,7 @@
#include "playback.h"
#include "codeclib.h"
#include "xxx2wav.h"
#include "id3.h"
struct codec_api *local_rb;
@ -34,3 +35,11 @@ int codec_init(struct codec_api* rb)
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);
}

View file

@ -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);
int codec_init(struct codec_api* rb);
void codec_set_replaygain(struct mp3entry* id3);

View file

@ -105,16 +105,11 @@ bool vorbis_set_codec_parameters(OggVorbis_File *vf)
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);
codec_set_replaygain(rb->id3);
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) {
rb->configure(DSP_SET_STEREO_MODE, (int *)STEREO_MONO);
}
@ -129,14 +124,12 @@ extern char iramend[];
#endif
/* reserve the PCM buffer in the IRAM area */
static char pcmbuf[4096] IDATA_ATTR;
/* this is the codec entry point */
enum codec_status codec_start(struct codec_api* api)
{
ov_callbacks callbacks;
OggVorbis_File vf;
ogg_int32_t** pcm;
int error;
long n;
@ -157,10 +150,12 @@ enum codec_status codec_start(struct codec_api* api)
#ifdef USE_IRAM
rb->memcpy(iramstart, iramcopy, iramend-iramstart);
#endif
rb->configure(DSP_DITHER, (bool *)false);
rb->configure(DSP_SET_SAMPLE_DEPTH, (int *)(16));
rb->configure(CODEC_DSP_ENABLE, (bool *)true);
rb->configure(DSP_DITHER, (bool *)false);
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
* they should be set differently based on quality setting
*/
@ -244,9 +239,9 @@ enum codec_status codec_start(struct codec_api* api)
}
rb->seek_time = 0;
}
/* Read host-endian signed 16 bit PCM samples */
n=ov_read(&vf,pcmbuf,sizeof(pcmbuf),&current_section);
/* Read host-endian signed 24-bit PCM samples */
n=ov_read_fixed(&vf,&pcm,1024,&current_section);
/* Change DSP and buffer settings for this bitstream */
if ( current_section != previous_section ) {
@ -262,9 +257,10 @@ enum codec_status codec_start(struct codec_api* api)
} else if (n < 0) {
DEBUGF("Error decoding frame\n");
} 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->set_offset(ov_raw_tell(&vf));
rb->set_elapsed(ov_time_tell(&vf));

View file

@ -16,11 +16,14 @@
* KIND, either express or implied.
*
****************************************************************************/
#include <inttypes.h>
#include <string.h>
#include "dsp.h"
#include "kernel.h"
#include "playback.h"
#include "system.h"
#include "settings.h"
#include "debug.h"
/* The "dither" code to convert the 24-bit samples produced by libmad was
* taken from the coolplayer project - coolplayer.sourceforge.net
@ -35,6 +38,7 @@
#define NATIVE_DEPTH 16
#define SAMPLE_BUF_SIZE 256
#define RESAMPLE_BUF_SIZE (256 * 4) /* Enough for 11,025 Hz -> 44,100 Hz*/
#define DEFAULT_REPLAYGAIN 0x01000000
#if defined(CPU_COLDFIRE) && !defined(SIMULATOR)
@ -45,16 +49,31 @@
#define FRACMUL(x, y) \
({ \
long t; \
asm volatile ("mac.l %[a], %[b], %%acc0\n\t" \
asm volatile ("mac.l %[a], %[b], %%acc0\n\t" \
"movclr.l %%acc0, %[t]\n\t" \
: [t] "=r" (t) : [a] "r" (x), [b] "r" (y)); \
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
#define INIT()
#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
@ -63,11 +82,17 @@ struct dsp_config
long frequency;
long clip_min;
long clip_max;
long track_gain;
long album_gain;
long track_peak;
long album_peak;
long replaygain;
int sample_depth;
int sample_bytes;
int stereo_mode;
int frac_bits;
bool dither_enabled;
bool new_gain;
};
struct resample_data
@ -197,8 +222,6 @@ static long downsample(long *dst, long *src, int count,
int pos = phase >> 16;
int i = 1;
INIT();
/* Do we need last sample of previous frame for interpolation? */
if (pos > 0)
{
@ -232,8 +255,6 @@ static long upsample(long *dst, long *src, int count, struct resample_data *r)
int i = 0;
int pos;
INIT();
while ((pos = phase >> 16) == 0)
{
*dst++ = last_sample + FRACMUL((phase & 0xffff) << 15,
@ -352,6 +373,40 @@ static long dither_sample(long sample, long bias, long mask,
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)
{
long* s0 = src[0];
@ -397,11 +452,14 @@ long dsp_process(char* dst, char* src[], long size)
int samples;
size /= dsp.sample_bytes * factor;
INIT();
dsp_set_replaygain(false);
while (size > 0)
{
samples = convert_to_internal(src, size, tmp);
size -= samples;
apply_gain(tmp, samples);
samples = resample(tmp, samples);
write_samples((short*) dst, tmp, samples);
written += samples;
@ -514,9 +572,14 @@ bool dsp_configure(int setting, void *value)
dsp.stereo_mode = STEREO_NONINTERLEAVED;
dsp.clip_max = ((1 << WORD_FRACBITS) - 1);
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.sample_depth = NATIVE_DEPTH;
dsp.frac_bits = WORD_FRACBITS;
dsp.new_gain = true;
break;
case DSP_DITHER:
@ -524,9 +587,74 @@ bool dsp_configure(int setting, void *value)
dsp.dither_enabled = (bool) value;
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:
return 0;
}
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;
}
}

View file

@ -33,5 +33,6 @@ long dsp_input_size(long size);
long dsp_output_size(long size);
int dsp_stereo_mode(void);
bool dsp_configure(int setting, void *value);
void dsp_set_replaygain(bool always);
#endif

View file

@ -3185,3 +3185,56 @@ eng: "Restarting playback..."
voice: "Restarting playback..."
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:

View file

@ -25,6 +25,8 @@
#include "mp3_playback.h"
#include "logf.h"
#include "atoi.h"
#include "replaygain.h"
#include "debug.h"
/* Simple file type probing by looking filename extension. */
int probe_file_format(const char *filename)
@ -271,7 +273,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
channels=buf[39];
if ( !get_vorbis_comments(&(track->id3), fd) ) {
logf("get_vorbis_comments failed");
logf("get_vorbis_comments failed");
return(false);
}
@ -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
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;
j=0; /* The number of bytes currently in buffer */
i=0;
@ -300,6 +302,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
while (i < (j-5)) {
if (memcmp(&buf[i],"OggS",5)==0) {
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);
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 */
@ -761,7 +764,7 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
int comment_length;
int i = 0;
unsigned char temp[300];
int buffer_remaining = sizeof(entry->id3v2buf);
int buffer_remaining = sizeof(entry->id3v2buf) + sizeof(entry->id3v1buf);
char *buffer = entry->id3v2buf;
char **p = NULL;
int segments;
@ -884,8 +887,36 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
} else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) {
name_length = 11;
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 {
p = NULL;
p = NULL;
}
if (p) {
@ -899,7 +930,6 @@ static bool get_vorbis_comments (struct mp3entry *entry, int fd)
}
}
}
return true;
}

View file

@ -38,7 +38,11 @@ enum {
DSP_SET_SAMPLE_DEPTH,
DSP_SET_STEREO_MODE,
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. */

View file

@ -1274,7 +1274,11 @@ bool browse_id3(void)
struct mp3entry* id3 = audio_current_track();
int button;
int menu_pos = 0;
#if CONFIG_HWCODEC == MASNONE
int menu_max = 12;
#else
int menu_max = 10;
#endif
bool exit = false;
char scroll_text[MAX_PATH];
@ -1381,6 +1385,21 @@ bool browse_id3(void)
lcd_puts(0, 0, str(LANG_ID3_PATH));
lcd_puts_scroll(0, 1, id3->path);
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();

View file

@ -416,6 +416,9 @@ static const struct bit_entry hd_bits[] =
#if CONFIG_HWCODEC == MASNONE
{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
/* new stuff to be added at the end */

View file

@ -328,6 +328,12 @@ struct user_settings
bool next_folder; /* move to next folder */
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 };

View file

@ -66,6 +66,7 @@ void dac_line_in(bool enable);
#if CONFIG_HWCODEC == MASNONE
#include "pcmbuf.h"
#include "pcm_playback.h"
#include "dsp.h"
#endif
#ifdef HAVE_CHARGING
@ -1187,6 +1188,56 @@ static bool runtimedb(void)
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)
{
int m;
@ -1203,6 +1254,7 @@ static bool playback_settings_menu(void)
#if CONFIG_HWCODEC == MASNONE
{ ID2P(LANG_CROSSFADE), crossfade },
{ ID2P(LANG_CROSSFADE_DURATION), crossfade_duration },
{ ID2P(LANG_REPLAYGAIN), replaygain_settings_menu },
#endif
#ifdef HAVE_SPDIF_POWER
{ ID2P(LANG_SPDIF_ENABLE), spdif },

View file

@ -129,6 +129,9 @@ drivers/uda1380.c
#if (CONFIG_HWCODEC == MASNONE) && !defined(SIMULATOR)
pcm_playback.c
#endif
#if CONFIG_HWCODEC == MASNONE
replaygain.c
#endif
#if defined(HAVE_UDA1380) && !defined(SIMULATOR)
pcm_record.c
#endif

View file

@ -179,6 +179,11 @@ SECTIONS
_iramcopy = .;
} > DRAM
/DISCARD/ :
{
*(.eh_frame)
}
.iram IRAMORIG : AT ( _iramcopy)
{
_iramstart = .;

View file

@ -53,13 +53,13 @@ enum {
struct mp3entry {
char path[MAX_PATH];
char *title;
char *artist;
char *album;
char* genre_string ;
char* track_string ;
char* year_string ;
char* composer ;
char* title;
char* artist;
char* album;
char* genre_string;
char* track_string;
char* year_string;
char* composer;
int tracknum;
int version;
int layer;
@ -115,6 +115,17 @@ struct mp3entry {
short voladjust;
long playcount;
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 {