Use AudioCompress for volume normalization
git-svn-id: https://svn.musicpd.org/mpd/trunk@4474 09075e82-0dd4-0310-85a5-a0d7c8717e4f
This commit is contained in:
parent
bf5336cebd
commit
22b16884a2
1
AUTHORS
1
AUTHORS
|
@ -7,3 +7,4 @@ command.c and signal handling cleanup -> mackstann
|
||||||
replayGain -> AliasMrJones
|
replayGain -> AliasMrJones
|
||||||
|
|
||||||
mp4ff copyrighted by M. Bakker, Ahead Software AG, http://www.nero.com
|
mp4ff copyrighted by M. Bakker, Ahead Software AG, http://www.nero.com
|
||||||
|
compress.[ch] copyrighted by M. Hari Nezumi <magenta@trikuare.cx>
|
||||||
|
|
|
@ -50,6 +50,7 @@ mpd_headers = \
|
||||||
mpd_types.h \
|
mpd_types.h \
|
||||||
myfprintf.h \
|
myfprintf.h \
|
||||||
normalize.h \
|
normalize.h \
|
||||||
|
compress.h \
|
||||||
outputBuffer.h \
|
outputBuffer.h \
|
||||||
path.h \
|
path.h \
|
||||||
pcm_utils.h \
|
pcm_utils.h \
|
||||||
|
@ -95,6 +96,7 @@ mpd_SOURCES = \
|
||||||
metadataChunk.c \
|
metadataChunk.c \
|
||||||
myfprintf.c \
|
myfprintf.c \
|
||||||
normalize.c \
|
normalize.c \
|
||||||
|
compress.c \
|
||||||
outputBuffer.c \
|
outputBuffer.c \
|
||||||
path.c \
|
path.c \
|
||||||
pcm_utils.c \
|
pcm_utils.c \
|
||||||
|
|
|
@ -0,0 +1,392 @@
|
||||||
|
/* compress.c
|
||||||
|
** Compressor logic
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include "compress.h"
|
||||||
|
|
||||||
|
#ifdef USE_X
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
|
||||||
|
static Display *display;
|
||||||
|
static Window window;
|
||||||
|
static Visual *visual;
|
||||||
|
static int screen;
|
||||||
|
static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int *peaks = NULL;
|
||||||
|
static int gainCurrent, gainTarget;
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
int show_mon;
|
||||||
|
int anticlip;
|
||||||
|
int target;
|
||||||
|
int gainmax;
|
||||||
|
int gainsmooth;
|
||||||
|
int buckets;
|
||||||
|
} prefs;
|
||||||
|
|
||||||
|
#ifdef USE_X
|
||||||
|
static int mon_init = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void CompressCfg(int show_mon, int anticlip, int target, int gainmax,
|
||||||
|
int gainsmooth, int buckets)
|
||||||
|
{
|
||||||
|
static int lastsize = 0;
|
||||||
|
|
||||||
|
prefs.show_mon = show_mon;
|
||||||
|
prefs.anticlip = anticlip;
|
||||||
|
prefs.target = target;
|
||||||
|
prefs.gainmax = gainmax;
|
||||||
|
prefs.gainsmooth = gainsmooth;
|
||||||
|
prefs.buckets = buckets;
|
||||||
|
|
||||||
|
/* Allocate the peak structure */
|
||||||
|
peaks = realloc(peaks, sizeof(int)*prefs.buckets);
|
||||||
|
|
||||||
|
if (prefs.buckets > lastsize)
|
||||||
|
memset(peaks + lastsize, 0, sizeof(int)*(prefs.buckets
|
||||||
|
- lastsize));
|
||||||
|
lastsize = prefs.buckets;
|
||||||
|
|
||||||
|
#ifdef USE_X
|
||||||
|
/* Configure the monitor window if needed */
|
||||||
|
if (show_mon && !mon_init)
|
||||||
|
{
|
||||||
|
display = XOpenDisplay(getenv("DISPLAY"));
|
||||||
|
|
||||||
|
/* We really shouldn't try to init X if there's no X */
|
||||||
|
if (!display)
|
||||||
|
{
|
||||||
|
fprintf(stderr,
|
||||||
|
"X not detected; disabling monitor window\n");
|
||||||
|
show_mon = prefs.show_mon = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_mon && !mon_init)
|
||||||
|
{
|
||||||
|
XGCValues gcv;
|
||||||
|
XColor col;
|
||||||
|
|
||||||
|
gainCurrent = gainTarget = (1 << GAINSHIFT);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
screen = DefaultScreen(display);
|
||||||
|
visual = DefaultVisual(display, screen);
|
||||||
|
window = XCreateSimpleWindow(display,
|
||||||
|
RootWindow(display, screen),
|
||||||
|
0, 0, prefs.buckets, 128 + 8, 0,
|
||||||
|
BlackPixel(display, screen),
|
||||||
|
WhitePixel(display, screen));
|
||||||
|
XStoreName(display, window, "AudioCompress monitor");
|
||||||
|
|
||||||
|
gcv.foreground = BlackPixel(display, screen);
|
||||||
|
blackGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||||
|
gcv.foreground = WhitePixel(display, screen);
|
||||||
|
whiteGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||||
|
col.red = 0;
|
||||||
|
col.green = 0;
|
||||||
|
col.blue = 65535;
|
||||||
|
XAllocColor(display, DefaultColormap(display, screen), &col);
|
||||||
|
gcv.foreground = col.pixel;
|
||||||
|
blueGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||||
|
col.red = 65535;
|
||||||
|
col.green = 65535;
|
||||||
|
col.blue = 0;
|
||||||
|
XAllocColor(display, DefaultColormap(display, screen), &col);
|
||||||
|
gcv.foreground = col.pixel;
|
||||||
|
yellowGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||||
|
col.red = 32767;
|
||||||
|
col.green = 32767;
|
||||||
|
col.blue = 0;
|
||||||
|
XAllocColor(display, DefaultColormap(display, screen), &col);
|
||||||
|
gcv.foreground = col.pixel;
|
||||||
|
dkyellowGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||||
|
col.red = 65535;
|
||||||
|
col.green = 0;
|
||||||
|
col.blue = 0;
|
||||||
|
XAllocColor(display, DefaultColormap(display, screen), &col);
|
||||||
|
gcv.foreground = col.pixel;
|
||||||
|
redGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||||
|
mon_init = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mon_init)
|
||||||
|
{
|
||||||
|
if (show_mon)
|
||||||
|
XMapWindow(display, window);
|
||||||
|
else
|
||||||
|
XUnmapWindow(display, window);
|
||||||
|
XResizeWindow(display, window, prefs.buckets, 128 + 8);
|
||||||
|
XFlush(display);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressFree(void)
|
||||||
|
{
|
||||||
|
#ifdef USE_X
|
||||||
|
if (mon_init)
|
||||||
|
{
|
||||||
|
XFreeGC(display, blackGC);
|
||||||
|
XFreeGC(display, whiteGC);
|
||||||
|
XFreeGC(display, blueGC);
|
||||||
|
XFreeGC(display, yellowGC);
|
||||||
|
XFreeGC(display, dkyellowGC);
|
||||||
|
XFreeGC(display, redGC);
|
||||||
|
XDestroyWindow(display, window);
|
||||||
|
XCloseDisplay(display);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (peaks)
|
||||||
|
free(peaks);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CompressDo(void *data, unsigned int length)
|
||||||
|
{
|
||||||
|
int16_t *audio = (int16_t *)data, *ap;
|
||||||
|
int peak, pos;
|
||||||
|
int i;
|
||||||
|
int gr, gf, gn;
|
||||||
|
static int pn = -1;
|
||||||
|
#ifdef STATS
|
||||||
|
static int clip = 0;
|
||||||
|
#endif
|
||||||
|
static int clipped = 0;
|
||||||
|
|
||||||
|
if (!peaks)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (pn == -1)
|
||||||
|
{
|
||||||
|
for (i = 0; i < prefs.buckets; i++)
|
||||||
|
peaks[i] = 0;
|
||||||
|
}
|
||||||
|
pn = (pn + 1)%prefs.buckets;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
fprintf(stderr, "modifyNative16(0x%08x, %d)\n",(unsigned int)data,
|
||||||
|
length);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Determine peak's value and position */
|
||||||
|
peak = 1;
|
||||||
|
pos = 0;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
fprintf(stderr, "finding peak(b=%d)\n", pn);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ap = audio;
|
||||||
|
for (i = 0; i < length/2; i++)
|
||||||
|
{
|
||||||
|
int val = *ap;
|
||||||
|
if (val > peak)
|
||||||
|
{
|
||||||
|
peak = val;
|
||||||
|
pos = i;
|
||||||
|
} else if (-val > peak)
|
||||||
|
{
|
||||||
|
peak = -val;
|
||||||
|
pos = i;
|
||||||
|
}
|
||||||
|
ap++;
|
||||||
|
}
|
||||||
|
peaks[pn] = peak;
|
||||||
|
|
||||||
|
/* Only draw if needed, of course */
|
||||||
|
#ifdef USE_X
|
||||||
|
if (prefs.show_mon)
|
||||||
|
{
|
||||||
|
/* current amplitude */
|
||||||
|
XDrawLine(display, window, whiteGC,
|
||||||
|
pn, 0,
|
||||||
|
pn,
|
||||||
|
127 -
|
||||||
|
(peaks[pn]*gainCurrent >> (GAINSHIFT + 8)));
|
||||||
|
|
||||||
|
/* amplification */
|
||||||
|
XDrawLine(display, window, yellowGC,
|
||||||
|
pn,
|
||||||
|
127 - (peaks[pn]*gainCurrent
|
||||||
|
>> (GAINSHIFT + 8)),
|
||||||
|
pn, 127);
|
||||||
|
|
||||||
|
/* peak */
|
||||||
|
XDrawLine(display, window, blackGC,
|
||||||
|
pn, 127 - (peaks[pn] >> 8), pn, 127);
|
||||||
|
|
||||||
|
/* clip indicator */
|
||||||
|
if (clipped)
|
||||||
|
XDrawLine(display, window, redGC,
|
||||||
|
(pn + prefs.buckets - 1)%prefs.buckets,
|
||||||
|
126 - clipped/(length*512),
|
||||||
|
(pn + prefs.buckets - 1)%prefs.buckets,
|
||||||
|
127);
|
||||||
|
clipped = 0;
|
||||||
|
|
||||||
|
// target line
|
||||||
|
//XDrawPoint(display, window, redGC,
|
||||||
|
// pn, 127 - TARGET/256);
|
||||||
|
// amplification edge
|
||||||
|
XDrawLine(display, window, dkyellowGC,
|
||||||
|
pn,
|
||||||
|
127 - (peaks[pn]*gainCurrent
|
||||||
|
>> (GAINSHIFT + 8)),
|
||||||
|
pn - 1,
|
||||||
|
127 -
|
||||||
|
(peaks[(pn + prefs.buckets
|
||||||
|
- 1)%prefs.buckets]*gainCurrent
|
||||||
|
>> (GAINSHIFT + 8)));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (i = 0; i < prefs.buckets; i++)
|
||||||
|
{
|
||||||
|
if (peaks[i] > peak)
|
||||||
|
{
|
||||||
|
peak = peaks[i];
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine target gain */
|
||||||
|
gn = (1 << GAINSHIFT)*prefs.target/peak;
|
||||||
|
|
||||||
|
if (gn <(1 << GAINSHIFT))
|
||||||
|
gn = 1 << GAINSHIFT;
|
||||||
|
|
||||||
|
gainTarget = (gainTarget *((1 << prefs.gainsmooth) - 1) + gn)
|
||||||
|
>> prefs.gainsmooth;
|
||||||
|
|
||||||
|
/* Give it an extra insignifigant nudge to counteract possible
|
||||||
|
** rounding error
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (gn < gainTarget)
|
||||||
|
gainTarget--;
|
||||||
|
else if (gn > gainTarget)
|
||||||
|
gainTarget++;
|
||||||
|
|
||||||
|
if (gainTarget > prefs.gainmax << GAINSHIFT)
|
||||||
|
gainTarget = prefs.gainmax << GAINSHIFT;
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef USE_X
|
||||||
|
if (prefs.show_mon)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
|
||||||
|
/* peak*gain */
|
||||||
|
XDrawPoint(display, window, redGC,
|
||||||
|
pn,
|
||||||
|
127 - (peak*gainCurrent
|
||||||
|
>> (GAINSHIFT + 8)));
|
||||||
|
|
||||||
|
// gain indicator
|
||||||
|
XFillRectangle(display, window, whiteGC, 0, 128,
|
||||||
|
prefs.buckets, 8);
|
||||||
|
x = (gainTarget - (1 << GAINSHIFT))*prefs.buckets
|
||||||
|
/ ((prefs.gainmax - 1) << GAINSHIFT);
|
||||||
|
XDrawLine(display, window, redGC, x,
|
||||||
|
128, x, 128 + 8);
|
||||||
|
|
||||||
|
x = (gn - (1 << GAINSHIFT))*prefs.buckets
|
||||||
|
/ ((prefs.gainmax - 1) << GAINSHIFT);
|
||||||
|
|
||||||
|
XDrawLine(display, window, blackGC,
|
||||||
|
x, 132 - 1,
|
||||||
|
x, 132 + 1);
|
||||||
|
|
||||||
|
// blue peak line
|
||||||
|
XDrawLine(display, window, blueGC,
|
||||||
|
0, 127 - (peak >> 8), prefs.buckets,
|
||||||
|
127 - (peak >> 8));
|
||||||
|
XFlush(display);
|
||||||
|
XDrawLine(display, window, whiteGC,
|
||||||
|
0, 127 - (peak >> 8), prefs.buckets,
|
||||||
|
127 - (peak >> 8));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* See if a peak is going to clip */
|
||||||
|
gn = (1 << GAINSHIFT)*32768/peak;
|
||||||
|
|
||||||
|
if (gn < gainTarget)
|
||||||
|
{
|
||||||
|
gainTarget = gn;
|
||||||
|
|
||||||
|
if (prefs.anticlip)
|
||||||
|
pos = 0;
|
||||||
|
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
/* We're ramping up, so draw it out over the whole frame */
|
||||||
|
pos = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine gain rate necessary to make target */
|
||||||
|
if (!pos)
|
||||||
|
pos = 1;
|
||||||
|
|
||||||
|
gr = ((gainTarget - gainCurrent) << 16)/pos;
|
||||||
|
|
||||||
|
/* Do the shiznit */
|
||||||
|
gf = gainCurrent << 16;
|
||||||
|
|
||||||
|
#ifdef STATS
|
||||||
|
fprintf(stderr, "\rgain = %2.2f%+.2e ",
|
||||||
|
gainCurrent*1.0/(1 << GAINSHIFT),
|
||||||
|
(gainTarget - gainCurrent)*1.0/(1 << GAINSHIFT));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ap = audio;
|
||||||
|
for (i = 0; i < length/2; i++)
|
||||||
|
{
|
||||||
|
int sample;
|
||||||
|
|
||||||
|
/* Interpolate the gain */
|
||||||
|
gainCurrent = gf >> 16;
|
||||||
|
if (i < pos)
|
||||||
|
gf += gr;
|
||||||
|
else if (i == pos)
|
||||||
|
gf = gainTarget << 16;
|
||||||
|
|
||||||
|
/* Amplify */
|
||||||
|
sample = (*ap)*gainCurrent >> GAINSHIFT;
|
||||||
|
if (sample < -32768)
|
||||||
|
{
|
||||||
|
#ifdef STATS
|
||||||
|
clip++;
|
||||||
|
#endif
|
||||||
|
clipped += -32768 - sample;
|
||||||
|
sample = -32768;
|
||||||
|
} else if (sample > 32767)
|
||||||
|
{
|
||||||
|
#ifdef STATS
|
||||||
|
clip++;
|
||||||
|
#endif
|
||||||
|
clipped += sample - 32767;
|
||||||
|
sample = 32767;
|
||||||
|
}
|
||||||
|
*ap++ = sample;
|
||||||
|
}
|
||||||
|
#ifdef STATS
|
||||||
|
fprintf(stderr, "clip %d b%-3d ", clip, pn);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
fprintf(stderr, "\ndone\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* compress.h
|
||||||
|
** interface to audio compression
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COMPRESS_H
|
||||||
|
#define COMPRESS_H
|
||||||
|
|
||||||
|
/* These are copied from the AudioCompress config.h, mainly because CompressDo
|
||||||
|
* needs GAINSHIFT defined. The rest are here so they can be used as defaults
|
||||||
|
* to pass to CompressCfg. */
|
||||||
|
#define ANTICLIP 0 /* Strict clipping protection */
|
||||||
|
#define TARGET 25000 /* Target level */
|
||||||
|
#define GAINMAX 32 /* The maximum amount to amplify by */
|
||||||
|
#define GAINSHIFT 10 /* How fine-grained the gain is */
|
||||||
|
#define GAINSMOOTH 8 /* How much inertia ramping has*/
|
||||||
|
#define BUCKETS 400 /* How long of a history to store */
|
||||||
|
|
||||||
|
void CompressCfg(int monitor,
|
||||||
|
int anticlip,
|
||||||
|
int target,
|
||||||
|
int maxgain,
|
||||||
|
int smooth,
|
||||||
|
int buckets);
|
||||||
|
|
||||||
|
void CompressDo(void *data, unsigned int numSamples);
|
||||||
|
|
||||||
|
void CompressFree(void);
|
||||||
|
|
||||||
|
#endif
|
|
@ -373,6 +373,8 @@ int getBoolConfigParam(char *name)
|
||||||
if (strcmp("yes", param->value) == 0) return 1;
|
if (strcmp("yes", param->value) == 0) return 1;
|
||||||
else if (strcmp("no", param->value) == 0) return 0;
|
else if (strcmp("no", param->value) == 0) return 0;
|
||||||
|
|
||||||
|
ERROR("%s is not \"yes\" or \"no\" on line %i\n", name, param->line);
|
||||||
|
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
#include "dbUtils.h"
|
#include "dbUtils.h"
|
||||||
#include "../config.h"
|
#include "../config.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "normalize.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/select.h>
|
#include <sys/select.h>
|
||||||
|
@ -552,6 +553,7 @@ int main(int argc, char *argv[])
|
||||||
initPaths();
|
initPaths();
|
||||||
initAudioConfig();
|
initAudioConfig();
|
||||||
initAudioDriver();
|
initAudioDriver();
|
||||||
|
initNormalization();
|
||||||
initPlaylist();
|
initPlaylist();
|
||||||
openDB(&options, argv[0]);
|
openDB(&options, argv[0]);
|
||||||
|
|
||||||
|
@ -601,6 +603,7 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
finishPlaylist();
|
finishPlaylist();
|
||||||
freePlayerData();
|
freePlayerData();
|
||||||
|
finishNormalization();
|
||||||
finishAudioDriver();
|
finishAudioDriver();
|
||||||
finishAudioConfig();
|
finishAudioConfig();
|
||||||
finishVolume();
|
finishVolume();
|
||||||
|
|
|
@ -16,85 +16,32 @@
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <math.h>
|
#include "compress.h"
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
#include "normalize.h"
|
#include "normalize.h"
|
||||||
#include "playlist.h"
|
|
||||||
|
|
||||||
/* silence level, apparently this is Wrong (tm) */
|
#include <stdlib.h>
|
||||||
#define SILENCE_LEVEL (SHRT_MAX * 0.01)
|
|
||||||
/* not sure what this is :) */
|
|
||||||
#define MID (SHRT_MAX * 0.25)
|
|
||||||
|
|
||||||
#define MUL_MIN 0.1
|
int normalizationEnabled;
|
||||||
#define MUL_MAX 5.0
|
|
||||||
#define NSAMPLES 128
|
|
||||||
#define MIN_SAMPLE_SIZE 32000
|
|
||||||
|
|
||||||
#define clamp(a,min,max) (((a)>(max))?(max):(((a)<(min))?(min):(a)))
|
void initNormalization()
|
||||||
|
{
|
||||||
|
normalizationEnabled = getBoolConfigParam(CONF_VOLUME_NORMALIZATION);
|
||||||
|
if (normalizationEnabled == -1) normalizationEnabled = 0;
|
||||||
|
else if (normalizationEnabled < 0) exit(EXIT_FAILURE);
|
||||||
|
|
||||||
|
if (normalizationEnabled)
|
||||||
|
CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishNormalization()
|
||||||
|
{
|
||||||
|
if (normalizationEnabled) CompressFree();
|
||||||
|
}
|
||||||
|
|
||||||
void normalizeData(char *buffer, int bufferSize, AudioFormat *format)
|
void normalizeData(char *buffer, int bufferSize, AudioFormat *format)
|
||||||
{
|
{
|
||||||
static float multiplier = 1.0;
|
if ((format->bits != 16) || (format->channels != 2)) return;
|
||||||
static int current_id = 0;
|
|
||||||
float average = 0.0;
|
|
||||||
static int old_song = 0;
|
|
||||||
int new_song = 0;
|
|
||||||
int total_length = 0;
|
|
||||||
int temp = 0;
|
|
||||||
int i = 0;
|
|
||||||
float root_mean_square = 0.0; /* the rms of the data */
|
|
||||||
mpd_sint16 *data = (mpd_sint16 *) buffer; /* the audio data */
|
|
||||||
int length = bufferSize / 2; /* the number of samples */
|
|
||||||
static struct {
|
|
||||||
float avg; /* average sample 'level' */
|
|
||||||
int len; /* sample size (used to weigh sample) */
|
|
||||||
} mem[NSAMPLES];
|
|
||||||
|
|
||||||
/* operate only on 16 bit, 2 channel audio */
|
CompressDo(buffer, bufferSize);
|
||||||
if (format->bits != 16 && format->channels != 2) return;
|
|
||||||
|
|
||||||
/* calculate the root mean square of the data */
|
|
||||||
for (i = 0; i < length; i++)
|
|
||||||
root_mean_square += (float)(data[i] * data[i]);
|
|
||||||
|
|
||||||
root_mean_square = sqrt(root_mean_square / (float)length);
|
|
||||||
|
|
||||||
/* reset the multiplier if the song has changed */
|
|
||||||
if (old_song != (new_song = getPlaylistCurrentSong())) {
|
|
||||||
old_song = new_song;
|
|
||||||
/* re-zero 'mem' */
|
|
||||||
for (i = 0; i < NSAMPLES; i++) {
|
|
||||||
mem[i].avg = 0.0;
|
|
||||||
mem[i].len = 0;
|
|
||||||
}
|
|
||||||
current_id = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* and now do magic tricks */
|
|
||||||
for (i = 0; i < NSAMPLES; i++) {
|
|
||||||
average += mem[i].avg * (float)mem[i].len;
|
|
||||||
total_length += mem[i].len;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (total_length > MIN_SAMPLE_SIZE) {
|
|
||||||
average /= (float) total_length;
|
|
||||||
if (average >= SILENCE_LEVEL) {
|
|
||||||
multiplier = MID / average;
|
|
||||||
/* clamp multiplier */
|
|
||||||
multiplier = clamp(multiplier, MUL_MIN, MUL_MAX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* scale and clamp the samples */
|
|
||||||
for (i = 0; i < length; i++) {
|
|
||||||
temp = data[i] * multiplier;
|
|
||||||
data[i] = clamp(temp, SHRT_MIN, SHRT_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
mem[current_id].len = bufferSize / 2;
|
|
||||||
mem[current_id].avg = multiplier * root_mean_square;
|
|
||||||
current_id = (current_id + 1) % NSAMPLES; /* increment current_id */
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,17 @@
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifndef NORMALIZE_H
|
||||||
|
#define NORMALIZE_H
|
||||||
|
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
|
|
||||||
|
extern int normalizationEnabled;
|
||||||
|
|
||||||
|
void initNormalization();
|
||||||
|
|
||||||
|
void finishNormalization();
|
||||||
|
|
||||||
void normalizeData(char *buffer, int bufferSize, AudioFormat *format);
|
void normalizeData(char *buffer, int bufferSize, AudioFormat *format);
|
||||||
|
|
||||||
|
#endif /* !NORMALIZE_H */
|
||||||
|
|
|
@ -78,22 +78,6 @@ int sendDataToOutputBuffer(OutputBuffer * cb, InputStream * inStream,
|
||||||
size_t datalen;
|
size_t datalen;
|
||||||
static char *convBuffer = NULL;
|
static char *convBuffer = NULL;
|
||||||
static long convBufferLen = 0;
|
static long convBufferLen = 0;
|
||||||
static int normalEnable = -1;
|
|
||||||
ConfigParam *param;
|
|
||||||
|
|
||||||
if (normalEnable == -1) {
|
|
||||||
normalEnable = getBoolConfigParam(CONF_VOLUME_NORMALIZATION);
|
|
||||||
if (normalEnable == -1) {
|
|
||||||
/* not set */
|
|
||||||
normalEnable = 0;
|
|
||||||
} else if (normalEnable < 0) {
|
|
||||||
param = getConfigParam(CONF_VOLUME_NORMALIZATION);
|
|
||||||
WARNING("%s is not \"yes\" or \"no\" on line %i, "
|
|
||||||
"disabling\n", CONF_VOLUME_NORMALIZATION,
|
|
||||||
param->line);
|
|
||||||
normalEnable = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cmpAudioFormat(&(cb->audioFormat), &(dc->audioFormat)) == 0) {
|
if (cmpAudioFormat(&(cb->audioFormat), &(dc->audioFormat)) == 0) {
|
||||||
data = dataIn;
|
data = dataIn;
|
||||||
|
@ -115,11 +99,10 @@ int sendDataToOutputBuffer(OutputBuffer * cb, InputStream * inStream,
|
||||||
&(cb->audioFormat), data);
|
&(cb->audioFormat), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replayGainInfo && (replayGainState != REPLAYGAIN_OFF)) {
|
if (replayGainInfo && (replayGainState != REPLAYGAIN_OFF))
|
||||||
doReplayGain(replayGainInfo, data, datalen, &cb->audioFormat);
|
doReplayGain(replayGainInfo, data, datalen, &cb->audioFormat);
|
||||||
} else if (normalEnable) {
|
else if (normalizationEnabled)
|
||||||
normalizeData(data, datalen, &cb->audioFormat);
|
normalizeData(data, datalen, &cb->audioFormat);
|
||||||
}
|
|
||||||
|
|
||||||
while (datalen) {
|
while (datalen) {
|
||||||
if (currentChunk != cb->end) {
|
if (currentChunk != cb->end) {
|
||||||
|
|
|
@ -167,15 +167,8 @@ void initPlaylist(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist_saveAbsolutePaths = getBoolConfigParam(CONF_SAVE_ABSOLUTE_PATHS);
|
playlist_saveAbsolutePaths = getBoolConfigParam(CONF_SAVE_ABSOLUTE_PATHS);
|
||||||
if (playlist_saveAbsolutePaths == -1) {
|
if (playlist_saveAbsolutePaths == -1) playlist_saveAbsolutePaths = 0;
|
||||||
/* not set */
|
else if (playlist_saveAbsolutePaths < 0) exit(EXIT_FAILURE);
|
||||||
playlist_saveAbsolutePaths = 0;
|
|
||||||
} else if (playlist_saveAbsolutePaths < 0) {
|
|
||||||
param = getConfigParam(CONF_SAVE_ABSOLUTE_PATHS);
|
|
||||||
ERROR("%s is not \"yes\" or \"no\" on line %i\n",
|
|
||||||
CONF_SAVE_ABSOLUTE_PATHS, param->line);
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
playlist.songs = malloc(sizeof(Song *) * playlist_max_length);
|
playlist.songs = malloc(sizeof(Song *) * playlist_max_length);
|
||||||
playlist.songMod = malloc(sizeof(mpd_uint32) * playlist_max_length);
|
playlist.songMod = malloc(sizeof(mpd_uint32) * playlist_max_length);
|
||||||
|
|
Loading…
Reference in New Issue