aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJ. Alexander Treuman <jat@spatialrift.net>2006-07-27 00:50:59 +0000
committerJ. Alexander Treuman <jat@spatialrift.net>2006-07-27 00:50:59 +0000
commit22b16884a2fb054ed219f07774cc16ec3af34c26 (patch)
tree605a3313d64b7a048c9cab56b2db210eaf7d40a5
parentbf5336cebd9e31e0eb0664ef62ce5c6751aa31a3 (diff)
Use AudioCompress for volume normalization
git-svn-id: https://svn.musicpd.org/mpd/trunk@4474 09075e82-0dd4-0310-85a5-a0d7c8717e4f
-rw-r--r--AUTHORS1
-rw-r--r--src/Makefile.am2
-rw-r--r--src/compress.c392
-rw-r--r--src/compress.h29
-rw-r--r--src/conf.c2
-rw-r--r--src/main.c3
-rw-r--r--src/normalize.c89
-rw-r--r--src/normalize.h11
-rw-r--r--src/outputBuffer.c21
-rw-r--r--src/playlist.c11
10 files changed, 462 insertions, 99 deletions
diff --git a/AUTHORS b/AUTHORS
index 734779e4..6efbe8ce 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -7,3 +7,4 @@ command.c and signal handling cleanup -> mackstann
replayGain -> AliasMrJones
mp4ff copyrighted by M. Bakker, Ahead Software AG, http://www.nero.com
+compress.[ch] copyrighted by M. Hari Nezumi <magenta@trikuare.cx>
diff --git a/src/Makefile.am b/src/Makefile.am
index 08f7ed01..e86c73ea 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -50,6 +50,7 @@ mpd_headers = \
mpd_types.h \
myfprintf.h \
normalize.h \
+ compress.h \
outputBuffer.h \
path.h \
pcm_utils.h \
@@ -95,6 +96,7 @@ mpd_SOURCES = \
metadataChunk.c \
myfprintf.c \
normalize.c \
+ compress.c \
outputBuffer.c \
path.c \
pcm_utils.c \
diff --git a/src/compress.c b/src/compress.c
new file mode 100644
index 00000000..e6c6cd6f
--- /dev/null
+++ b/src/compress.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
+}
+
diff --git a/src/compress.h b/src/compress.h
new file mode 100644
index 00000000..1993b47a
--- /dev/null
+++ b/src/compress.h
@@ -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
diff --git a/src/conf.c b/src/conf.c
index 995cbf89..6fbc4734 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -373,6 +373,8 @@ int getBoolConfigParam(char *name)
if (strcmp("yes", param->value) == 0) return 1;
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;
}
diff --git a/src/main.c b/src/main.c
index 7dcc5240..40e0274e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -40,6 +40,7 @@
#include "dbUtils.h"
#include "../config.h"
#include "utils.h"
+#include "normalize.h"
#include <stdio.h>
#include <sys/select.h>
@@ -552,6 +553,7 @@ int main(int argc, char *argv[])
initPaths();
initAudioConfig();
initAudioDriver();
+ initNormalization();
initPlaylist();
openDB(&options, argv[0]);
@@ -601,6 +603,7 @@ int main(int argc, char *argv[])
finishPlaylist();
freePlayerData();
+ finishNormalization();
finishAudioDriver();
finishAudioConfig();
finishVolume();
diff --git a/src/normalize.c b/src/normalize.c
index 8c48f13b..31394cbf 100644
--- a/src/normalize.c
+++ b/src/normalize.c
@@ -16,85 +16,32 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-#include <math.h>
-#include <limits.h>
-
+#include "compress.h"
#include "conf.h"
#include "normalize.h"
-#include "playlist.h"
-
-/* silence level, apparently this is Wrong (tm) */
-#define SILENCE_LEVEL (SHRT_MAX * 0.01)
-/* not sure what this is :) */
-#define MID (SHRT_MAX * 0.25)
-#define MUL_MIN 0.1
-#define MUL_MAX 5.0
-#define NSAMPLES 128
-#define MIN_SAMPLE_SIZE 32000
+#include <stdlib.h>
-#define clamp(a,min,max) (((a)>(max))?(max):(((a)<(min))?(min):(a)))
+int normalizationEnabled;
-void normalizeData(char *buffer, int bufferSize, AudioFormat *format)
+void initNormalization()
{
- static float multiplier = 1.0;
- 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 */
- 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);
+ normalizationEnabled = getBoolConfigParam(CONF_VOLUME_NORMALIZATION);
+ if (normalizationEnabled == -1) normalizationEnabled = 0;
+ else if (normalizationEnabled < 0) exit(EXIT_FAILURE);
- /* 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 (normalizationEnabled)
+ CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS);
+}
- 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);
- }
- }
+void finishNormalization()
+{
+ if (normalizationEnabled) CompressFree();
+}
- /* scale and clamp the samples */
- for (i = 0; i < length; i++) {
- temp = data[i] * multiplier;
- data[i] = clamp(temp, SHRT_MIN, SHRT_MAX);
- }
+void normalizeData(char *buffer, int bufferSize, AudioFormat *format)
+{
+ if ((format->bits != 16) || (format->channels != 2)) return;
- mem[current_id].len = bufferSize / 2;
- mem[current_id].avg = multiplier * root_mean_square;
- current_id = (current_id + 1) % NSAMPLES; /* increment current_id */
+ CompressDo(buffer, bufferSize);
}
diff --git a/src/normalize.h b/src/normalize.h
index f131669a..73e44288 100644
--- a/src/normalize.h
+++ b/src/normalize.h
@@ -16,6 +16,17 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#ifndef NORMALIZE_H
+#define NORMALIZE_H
+
#include "audio.h"
+extern int normalizationEnabled;
+
+void initNormalization();
+
+void finishNormalization();
+
void normalizeData(char *buffer, int bufferSize, AudioFormat *format);
+
+#endif /* !NORMALIZE_H */
diff --git a/src/outputBuffer.c b/src/outputBuffer.c
index 90f2ef2d..fe868d54 100644
--- a/src/outputBuffer.c
+++ b/src/outputBuffer.c
@@ -78,22 +78,6 @@ int sendDataToOutputBuffer(OutputBuffer * cb, InputStream * inStream,
size_t datalen;
static char *convBuffer = NULL;
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) {
data = dataIn;
@@ -115,11 +99,10 @@ int sendDataToOutputBuffer(OutputBuffer * cb, InputStream * inStream,
&(cb->audioFormat), data);
}
- if (replayGainInfo && (replayGainState != REPLAYGAIN_OFF)) {
+ if (replayGainInfo && (replayGainState != REPLAYGAIN_OFF))
doReplayGain(replayGainInfo, data, datalen, &cb->audioFormat);
- } else if (normalEnable) {
+ else if (normalizationEnabled)
normalizeData(data, datalen, &cb->audioFormat);
- }
while (datalen) {
if (currentChunk != cb->end) {
diff --git a/src/playlist.c b/src/playlist.c
index 37c2aeb5..2f4feee7 100644
--- a/src/playlist.c
+++ b/src/playlist.c
@@ -167,15 +167,8 @@ void initPlaylist(void)
}
playlist_saveAbsolutePaths = getBoolConfigParam(CONF_SAVE_ABSOLUTE_PATHS);
- if (playlist_saveAbsolutePaths == -1) {
- /* not set */
- 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);
- }
+ if (playlist_saveAbsolutePaths == -1) playlist_saveAbsolutePaths = 0;
+ else if (playlist_saveAbsolutePaths < 0) exit(EXIT_FAILURE);
playlist.songs = malloc(sizeof(Song *) * playlist_max_length);
playlist.songMod = malloc(sizeof(mpd_uint32) * playlist_max_length);