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, 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
} }

View file

@ -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);
}
}
}

View file

@ -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);
}

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); 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);

View file

@ -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),&current_section); n=ov_read_fixed(&vf,&pcm,1024,&current_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);
} }

View file

@ -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;
}
}

View file

@ -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

View file

@ -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:

View file

@ -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;
} }

View file

@ -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. */

View file

@ -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();

View file

@ -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 */

View file

@ -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 };

View file

@ -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 },

View file

@ -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

View file

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

View file

@ -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 {