From 4076523198655b1b459c8014c0ff8de76d720703 Mon Sep 17 00:00:00 2001 From: "J. Shagam" Date: Wed, 2 Dec 2009 18:11:53 +0100 Subject: [PATCH] compress: upgraded to AudioCompress 2.0 Copied sources from http://beesbuzz.biz/code/audiocompress/AudioCompress-2.0.tar.gz [mk: created this patch under fluffy's name and fixed some gcc signed/unsigned comparison warnings] --- Makefile.am | 4 +- NEWS | 1 + src/AudioCompress/compress.c | 184 ++++++++++++++++ src/AudioCompress/compress.h | 40 ++++ src/compress.c | 411 ----------------------------------- src/compress.h | 48 ---- src/normalize.c | 13 +- src/normalize.h | 2 +- 8 files changed, 236 insertions(+), 467 deletions(-) create mode 100644 src/AudioCompress/compress.c create mode 100644 src/AudioCompress/compress.h delete mode 100644 src/compress.c delete mode 100644 src/compress.h diff --git a/Makefile.am b/Makefile.am index 58d1964e4..3ca89e165 100644 --- a/Makefile.am +++ b/Makefile.am @@ -116,7 +116,7 @@ mpd_headers = \ src/mixer/pulse_mixer_plugin.h \ src/daemon.h \ src/normalize.h \ - src/compress.h \ + src/AudioCompress/compress.h \ src/buffer.h \ src/pipe.h \ src/chunk.h \ @@ -253,7 +253,7 @@ src_mpd_SOURCES = \ src/event_pipe.c \ src/daemon.c \ src/normalize.c \ - src/compress.c \ + src/AudioCompress/compress.c \ src/buffer.c \ src/pipe.c \ src/chunk.c \ diff --git a/NEWS b/NEWS index 60a19799a..ab17b672e 100644 --- a/NEWS +++ b/NEWS @@ -62,6 +62,7 @@ ver 0.16 (20??/??/??) - support .mpdignore files in the music directory - sort songs by album name first, then disc/track number - rescan after metadata_to_use change +* normalize: upgraded to AudioCompress 2.0 * log unused/unknown block parameters * removed the deprecated "error_file" option * save state when stopped diff --git a/src/AudioCompress/compress.c b/src/AudioCompress/compress.c new file mode 100644 index 000000000..bf72b2eab --- /dev/null +++ b/src/AudioCompress/compress.c @@ -0,0 +1,184 @@ +/* compress.c + * Compressor logic + * + * (c)2007 busybee (http://beesbuzz.biz/ + * Licensed under the terms of the LGPL. See the file COPYING for details. + */ + +#include +#include +#include + +#include "config.h" +#include "compress.h" + +struct Compressor { + //! The compressor's preferences + struct CompressorConfig prefs; + + //! History of the peak values + int *peaks; + + //! History of the gain values + int *gain; + + //! History of clip amounts + int *clipped; + + unsigned int pos; + unsigned int bufsz; +}; + +struct Compressor *Compressor_new(unsigned int history) +{ + struct Compressor *obj = malloc(sizeof(struct Compressor)); + + obj->prefs.target = TARGET; + obj->prefs.maxgain = GAINMAX; + obj->prefs.smooth = GAINSMOOTH; + + obj->peaks = obj->gain = obj->clipped = NULL; + obj->bufsz = 0; + obj->pos = 0; + + Compressor_setHistory(obj, history); + + return obj; +} + +void Compressor_delete(struct Compressor *obj) +{ + if (obj->peaks) + free(obj->peaks); + if (obj->gain) + free(obj->gain); + if (obj->clipped) + free(obj->clipped); + free(obj); +} + +static int *resizeArray(int *data, int newsz, int oldsz) +{ + data = realloc(data, newsz*sizeof(int)); + if (newsz > oldsz) + memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz)); + return data; +} + +void Compressor_setHistory(struct Compressor *obj, unsigned int history) +{ + if (!history) + history = BUCKETS; + + obj->peaks = resizeArray(obj->peaks, history, obj->bufsz); + obj->gain = resizeArray(obj->gain, history, obj->bufsz); + obj->clipped = resizeArray(obj->clipped, history, obj->bufsz); + obj->bufsz = history; +} + +struct CompressorConfig *Compressor_getConfig(struct Compressor *obj) +{ + return &obj->prefs; +} + +void Compressor_Process_int16(struct Compressor *obj, int16_t *audio, + unsigned int count) +{ + struct CompressorConfig *prefs = Compressor_getConfig(obj); + int16_t *ap; + unsigned int i; + int *peaks = obj->peaks; + int curGain = obj->gain[obj->pos]; + int newGain; + int peakVal = 1; + int peakPos = 0; + int slot = (obj->pos + 1) % obj->bufsz; + int *clipped = obj->clipped + slot; + unsigned int ramp = count; + int delta; + + ap = audio; + for (i = 0; i < count; i++) + { + int val = *ap++; + if (val < 0) + val = -val; + if (val > peakVal) + { + peakVal = val; + peakPos = i; + } + } + peaks[slot] = peakVal; + + + for (i = 0; i < obj->bufsz; i++) + { + if (peaks[i] > peakVal) + { + peakVal = peaks[i]; + peakPos = 0; + } + } + + //! Determine target gain + newGain = (1 << 10)*prefs->target/peakVal; + + //! Adjust the gain with inertia from the previous gain value + newGain = (curGain*((1 << prefs->smooth) - 1) + newGain) + >> prefs->smooth; + + //! Make sure it's no more than the maximum gain value + if (newGain > (prefs->maxgain << 10)) + newGain = prefs->maxgain << 10; + + //! Make sure it's no less than 1:1 + if (newGain < (1 << 10)) + newGain = 1 << 10; + + //! Make sure the adjusted gain won't cause clipping + if ((peakVal*newGain >> 10) > 32767) + { + newGain = (32767 << 10)/peakVal; + //! Truncate the ramp time + ramp = peakPos; + } + + //! Record the new gain + obj->gain[slot] = newGain; + + if (!ramp) + ramp = 1; + if (!curGain) + curGain = 1 << 10; + delta = (newGain - curGain) / (int)ramp; + + ap = audio; + *clipped = 0; + for (i = 0; i < count; i++) + { + int sample; + + //! Amplify the sample + sample = *ap*curGain >> 10; + if (sample < -32768) + { + *clipped += -32768 - sample; + sample = -32768; + } else if (sample > 32767) + { + *clipped += sample - 32767; + sample = 32767; + } + *ap++ = sample; + + //! Adjust the gain + if (i < ramp) + curGain += delta; + else + curGain = newGain; + } + + obj->pos = slot; +} + diff --git a/src/AudioCompress/compress.h b/src/AudioCompress/compress.h new file mode 100644 index 000000000..cc875c6da --- /dev/null +++ b/src/AudioCompress/compress.h @@ -0,0 +1,40 @@ +/*! compress.h + * interface to audio compression + * + * (c)2007 busybee (http://beesbuzz.biz/) + * Licensed under the terms of the LGPL. See the file COPYING for details. + */ + +#ifndef COMPRESS_H +#define COMPRESS_H + +#include + +//! Configuration values for the compressor object +struct CompressorConfig { + int target; + int maxgain; + int smooth; +}; + +struct Compressor; + +//! Create a new compressor (use history value of 0 for default) +struct Compressor *Compressor_new(unsigned int history); + +//! Delete a compressor +void Compressor_delete(struct Compressor *); + +//! Set the history length +void Compressor_setHistory(struct Compressor *, unsigned int history); + +//! Get the configuration for a compressor +struct CompressorConfig *Compressor_getConfig(struct Compressor *); + +//! Process 16-bit signed data +void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count); + +//! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed + +//! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring) +#endif diff --git a/src/compress.c b/src/compress.c deleted file mode 100644 index 27f223bd7..000000000 --- a/src/compress.c +++ /dev/null @@ -1,411 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Imported from AudioCompress by J. Shagam - */ - -#include "config.h" -#include "compress.h" - -#include - -#include -#include - -#ifdef USE_X -#include -#include - -static Display *display; -static Window window; -static Visual *visual; -static int screen; -static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC; -#endif - -static int *peaks; -static int gainCurrent, gainTarget; - -static struct { - int show_mon; - int anticlip; - int target; - int gainmax; - int gainsmooth; - unsigned buckets; -} prefs; - -#ifdef USE_X -static int mon_init; -#endif - -void CompressCfg(int show_mon, int anticlip, int target, int gainmax, - int gainsmooth, unsigned buckets) -{ - static unsigned lastsize; - - 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 = g_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 - - g_free(peaks); -} - -void CompressDo(void *data, unsigned int length) -{ - int16_t *audio = (int16_t *)data, *ap; - int peak; - unsigned int i, pos; - int gr, gf, gn; - static int pn = -1; -#ifdef STATS - static int clip; -#endif - static int clipped; - - 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)/(int)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 deleted file mode 100644 index 3e3afb565..000000000 --- a/src/compress.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Imported from AudioCompress by J. Shagam - */ - -#ifndef MPD_COMPRESS_H -#define MPD_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(). -- jat */ -#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, - unsigned buckets); - -void CompressDo(void *data, unsigned int numSamples); - -void CompressFree(void); - -#endif diff --git a/src/normalize.c b/src/normalize.c index f8304cd1f..f9201df64 100644 --- a/src/normalize.c +++ b/src/normalize.c @@ -19,7 +19,7 @@ #include "config.h" #include "normalize.h" -#include "compress.h" +#include "AudioCompress/compress.h" #include "conf.h" #include "audio_format.h" @@ -27,24 +27,27 @@ int normalizationEnabled; +static struct Compressor *compressor; + void initNormalization(void) { normalizationEnabled = config_get_bool(CONF_VOLUME_NORMALIZATION, DEFAULT_VOLUME_NORMALIZATION); if (normalizationEnabled) - CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS); + compressor = Compressor_new(0); } void finishNormalization(void) { - if (normalizationEnabled) CompressFree(); + if (normalizationEnabled) + Compressor_delete(compressor); } -void normalizeData(char *buffer, int bufferSize, +void normalizeData(void *buffer, int bufferSize, const struct audio_format *format) { if ((format->bits != 16) || (format->channels != 2)) return; - CompressDo(buffer, bufferSize); + Compressor_Process_int16(compressor, buffer, bufferSize / 2); } diff --git a/src/normalize.h b/src/normalize.h index a8144951d..2834f07fd 100644 --- a/src/normalize.h +++ b/src/normalize.h @@ -28,7 +28,7 @@ void initNormalization(void); void finishNormalization(void); -void normalizeData(char *buffer, int bufferSize, +void normalizeData(void *buffer, int bufferSize, const struct audio_format *format); #endif /* !NORMALIZE_H */