summaryrefslogtreecommitdiff
path: root/vdr_sound.c
diff options
context:
space:
mode:
Diffstat (limited to 'vdr_sound.c')
-rw-r--r--vdr_sound.c609
1 files changed, 609 insertions, 0 deletions
diff --git a/vdr_sound.c b/vdr_sound.c
new file mode 100644
index 0000000..b06061c
--- /dev/null
+++ b/vdr_sound.c
@@ -0,0 +1,609 @@
+/*!
+ * \file vdr_setup.c
+ * \brief Sound manipulation classes for a VDR media plugin (muggle)
+ *
+ * \version $Revision: 1.2 $
+ * \date $Date: 2004/05/28 15:29:19 $
+ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner
+ * \author Responsible author: $Author: lvw $
+ *
+ * $Id: vdr_sound.c,v 1.2 2004/05/28 15:29:19 lvw Exp $
+ *
+ * Adapted from
+ * MP3/MPlayer plugin to VDR (C++)
+ * (C) 2001,2002 Stefan Huelswitt <huels@iname.com>
+ */
+
+// --- cResample ------------------------------------------------------------
+
+// The resample code has been adapted from the madplay project
+// (resample.c) found in the libmad distribution
+
+class cResample
+{
+private:
+ mad_fixed_t ratio;
+ mad_fixed_t step;
+ mad_fixed_t last;
+ mad_fixed_t resampled[MAX_NSAMPLES];
+public:
+ bool SetInputRate(unsigned int oldrate, unsigned int newrate);
+ unsigned int ResampleBlock(unsigned int nsamples, const mad_fixed_t *old);
+ const mad_fixed_t *Resampled(void) { return resampled; }
+ };
+
+bool cResample::SetInputRate(unsigned int oldrate, unsigned int newrate)
+{
+ if(oldrate<8000 || oldrate>newrate*6) { // out of range
+ esyslog("WARNING: samplerate %d out of range 8000-%d\n",oldrate,newrate*6);
+ return 0;
+ }
+ ratio=mad_f_tofixed((double)oldrate/(double)newrate);
+ step=0; last=0;
+#ifdef DEBUG
+ static mad_fixed_t oldratio=0;
+ if(oldratio!=ratio) {
+ printf("mad: new resample ratio %f (from %d kHz to %d kHz)\n",mad_f_todouble(ratio),oldrate,newrate);
+ oldratio=ratio;
+ }
+#endif
+ return ratio!=MAD_F_ONE;
+}
+
+unsigned int cResample::ResampleBlock(unsigned int nsamples, const mad_fixed_t *old)
+{
+ // This resampling algorithm is based on a linear interpolation, which is
+ // not at all the best sounding but is relatively fast and efficient.
+ //
+ // A better algorithm would be one that implements a bandlimited
+ // interpolation.
+
+ mad_fixed_t *nsam=resampled;
+ const mad_fixed_t *end=old+nsamples;
+ const mad_fixed_t *begin=nsam;
+
+ if(step < 0) {
+ step = mad_f_fracpart(-step);
+
+ while (step < MAD_F_ONE) {
+ *nsam++ = step ? last+mad_f_mul(*old-last,step) : last;
+ step += ratio;
+ if(((step + 0x00000080L) & 0x0fffff00L) == 0)
+ step = (step + 0x00000080L) & ~0x0fffffffL;
+ }
+ step -= MAD_F_ONE;
+ }
+
+ while (end - old > 1 + mad_f_intpart(step)) {
+ old += mad_f_intpart(step);
+ step = mad_f_fracpart(step);
+ *nsam++ = step ? *old + mad_f_mul(old[1] - old[0], step) : *old;
+ step += ratio;
+ if (((step + 0x00000080L) & 0x0fffff00L) == 0)
+ step = (step + 0x00000080L) & ~0x0fffffffL;
+ }
+
+ if (end - old == 1 + mad_f_intpart(step)) {
+ last = end[-1];
+ step = -step;
+ }
+ else step -= mad_f_fromint(end - old);
+
+ return nsam-begin;
+}
+
+// --- cLevel ----------------------------------------------------------------
+
+// The normalize algorithm and parts of the code has been adapted from the
+// Normalize 0.7 project. (C) 1999-2002, Chris Vaill <cvaill@cs.columbia.edu>
+
+// A little background on how normalize computes the volume
+// of a wav file, in case you want to know just how your
+// files are being munged:
+//
+// The volumes calculated are RMS amplitudes, which corre­
+// spond (roughly) to perceived volume. Taking the RMS ampli­
+// tude of an entire file would not give us quite the measure
+// we want, though, because a quiet song punctuated by short
+// loud parts would average out to a quiet song, and the
+// adjustment we would compute would make the loud parts
+// excessively loud.
+//
+// What we want is to consider the maximum volume of the
+// file, and normalize according to that. We break up the
+// signal into 100 chunks per second, and get the signal
+// power of each chunk, in order to get an estimation of
+// "instantaneous power" over time. This "instantaneous
+// power" signal varies too much to get a good measure of the
+// original signal's maximum sustained power, so we run a
+// smoothing algorithm over the power signal (specifically, a
+// mean filter with a window width of 100 elements). The max­
+// imum point of the smoothed power signal turns out to be a
+// good measure of the maximum sustained power of the file.
+// We can then take the square root of the power to get maxi­
+// mum sustained RMS amplitude.
+
+class cLevel {
+private:
+ double maxpow;
+ mad_fixed_t peak;
+ struct Power {
+ // smooth
+ int npow, wpow;
+ double powsum, pows[POW_WIN];
+ // sum
+ unsigned int nsum;
+ double sum;
+ } power[2];
+ //
+ inline void AddPower(struct Power *p, double pow);
+public:
+ void Init(void);
+ void GetPower(struct mad_pcm *pcm);
+ double GetLevel(void);
+ double GetPeak(void);
+ };
+
+void cLevel::Init(void)
+{
+ for(int l=0 ; l<2 ; l++) {
+ struct Power *p=&power[l];
+ p->sum=p->powsum=0.0; p->wpow=p->npow=p->nsum=0;
+ for(int i=POW_WIN-1 ; i>=0 ; i--) p->pows[i]=0.0;
+ }
+ maxpow=0.0; peak=0;
+}
+
+void cLevel::GetPower(struct mad_pcm *pcm)
+{
+ for(int i=0 ; i<pcm->channels ; i++) {
+ struct Power *p=&power[i];
+ mad_fixed_t *data=pcm->samples[i];
+ for(int n=pcm->length ; n>0 ; n--) {
+ if(*data < -peak) peak = -*data;
+ if(*data > peak) peak = *data;
+ double s=mad_f_todouble(*data++);
+ p->sum+=(s*s);
+ if(++(p->nsum)>=pcm->samplerate/100) {
+ AddPower(p,p->sum/(double)p->nsum);
+ p->sum=0.0; p->nsum=0;
+ }
+ }
+ }
+}
+
+void cLevel::AddPower(struct Power *p, double pow)
+{
+ p->powsum+=pow;
+ if(p->npow>=POW_WIN) {
+ if(p->powsum>maxpow) maxpow=p->powsum;
+ p->powsum-=p->pows[p->wpow];
+ }
+ else p->npow++;
+ p->pows[p->wpow]=pow;
+ p->wpow=(p->wpow+1) % POW_WIN;
+}
+
+double cLevel::GetLevel(void)
+{
+ if(maxpow<EPSILON) {
+ // Either this whole file has zero power, or was too short to ever
+ // fill the smoothing buffer. In the latter case, we need to just
+ // get maxpow from whatever data we did collect.
+
+ if(power[0].powsum>maxpow) maxpow=power[0].powsum;
+ if(power[1].powsum>maxpow) maxpow=power[1].powsum;
+ }
+ double level=sqrt(maxpow/(double)POW_WIN); // adjust for the smoothing window size and root
+ printf("norm: new volumen level=%f peak=%f\n",level,mad_f_todouble(peak));
+ return level;
+}
+
+double cLevel::GetPeak(void)
+{
+ return mad_f_todouble(peak);
+}
+
+// --- cNormalize ------------------------------------------------------------
+
+class cNormalize {
+private:
+ mad_fixed_t gain;
+ double d_limlvl, one_limlvl;
+ mad_fixed_t limlvl;
+ bool dogain, dolimit;
+#ifdef DEBUG
+ // stats
+ unsigned long limited, clipped, total;
+ mad_fixed_t peak;
+#endif
+ // limiter
+#ifdef USE_FAST_LIMITER
+ mad_fixed_t *table, tablestart;
+ int tablesize;
+ inline mad_fixed_t FastLimiter(mad_fixed_t x);
+#endif
+ inline mad_fixed_t Limiter(mad_fixed_t x);
+public:
+ cNormalize(void);
+ ~cNormalize();
+ void Init(double Level, double Peak);
+ void Stats(void);
+ void AddGain(struct mad_pcm *pcm);
+ };
+
+cNormalize::cNormalize(void)
+{
+ d_limlvl = (double)the_setup.LimiterLevel / 100.0;
+ one_limlvl = 1 - d_limlvl;
+ limlvl = mad_f_tofixed(d_limlvl);
+ printf( "norm: lim_lev=%f lim_acc=%d\n", d_limlvl, LIM_ACC );
+
+#ifdef USE_FAST_LIMITER
+ mad_fixed_t start=limlvl & ~(F_LIM_JMP-1);
+ tablestart=start;
+ tablesize=(unsigned int)(F_LIM_MAX-start)/F_LIM_JMP + 2;
+ table=new mad_fixed_t[tablesize];
+ if(table) {
+ printf("norm: table size=%d start=%08x jump=%08x\n",tablesize,start,F_LIM_JMP);
+ for(int i=0 ; i<tablesize ; i++) {
+ table[i]=Limiter(start);
+ start+=F_LIM_JMP;
+ }
+ tablesize--; // avoid a -1 in FastLimiter()
+
+ // do a quick accuracy check, just to be sure that FastLimiter() is working
+ // as expected :-)
+#ifdef ACC_DUMP
+ FILE *out=fopen("/tmp/limiter","w");
+#endif
+ mad_fixed_t maxdiff=0;
+ for(mad_fixed_t x=F_LIM_MAX ; x>=limlvl ; x-=mad_f_tofixed(1e-4)) {
+ mad_fixed_t diff=mad_f_abs(Limiter(x)-FastLimiter(x));
+ if(diff>maxdiff) maxdiff=diff;
+#ifdef ACC_DUMP
+ fprintf(out,"%0.10f\t%0.10f\t%0.10f\t%0.10f\t%0.10f\n",
+ mad_f_todouble(x),mad_f_todouble(Limiter(x)),mad_f_todouble(FastLimiter(x)),mad_f_todouble(diff),mad_f_todouble(maxdiff));
+ if(ferror(out)) break;
+#endif
+ }
+#ifdef ACC_DUMP
+ fclose(out);
+#endif
+ printf("norm: accuracy %.12f\n",mad_f_todouble(maxdiff));
+ if(mad_f_todouble(maxdiff)>1e-6)
+ {
+ esyslog("ERROR: accuracy check failed, normalizer disabled");
+ delete table; table=0;
+ }
+ }
+ else esyslog("ERROR: no memory for lookup table, normalizer disabled");
+#endif // USE_FAST_LIMITER
+}
+
+cNormalize::~cNormalize()
+{
+#ifdef USE_FAST_LIMITER
+ delete table;
+#endif
+}
+
+void cNormalize::Init(double Level, double Peak)
+{
+ double Target=(double)the_setup.TargetLevel/100.0;
+ double dgain=Target/Level;
+ if(dgain>MAX_GAIN) dgain=MAX_GAIN;
+ gain=mad_f_tofixed(dgain);
+ // Check if we actually need to apply a gain
+ dogain=(Target>0.0 && fabs(1-dgain)>MIN_GAIN);
+#ifdef USE_FAST_LIMITER
+ if(!table) dogain=false;
+#endif
+ // Check if we actually need to do limiting:
+ // we have to if limiter is enabled, if gain>1 and if the peaks will clip.
+ dolimit=(d_limlvl<1.0 && dgain>1.0 && Peak*dgain>1.0);
+#ifdef DEBUG
+ printf("norm: gain=%f dogain=%d dolimit=%d (target=%f level=%f peak=%f)\n",dgain,dogain,dolimit,Target,Level,Peak);
+ limited=clipped=total=0; peak=0;
+#endif
+}
+
+void cNormalize::Stats(void)
+{
+#ifdef DEBUG
+ if(total)
+ printf("norm: stats tot=%ld lim=%ld/%.3f%% clip=%ld/%.3f%% peak=%.3f\n",
+ total,limited,(double)limited/total*100.0,clipped,(double)clipped/total*100.0,mad_f_todouble(peak));
+#endif
+}
+
+mad_fixed_t cNormalize::Limiter(mad_fixed_t x)
+{
+// Limiter function:
+//
+// / x (for x <= lev)
+// x' = |
+// \ tanh((x - lev) / (1-lev)) * (1-lev) + lev (for x > lev)
+//
+// call only with x>=0. For negative samples, preserve sign outside this function
+//
+// With limiter level = 0, this is equivalent to a tanh() function;
+// with limiter level = 1, this is equivalent to clipping.
+
+ if(x>limlvl) {
+#ifdef DEBUG
+ if(x>MAD_F_ONE) clipped++;
+ limited++;
+#endif
+ x=mad_f_tofixed(tanh((mad_f_todouble(x)-d_limlvl) / one_limlvl) * one_limlvl + d_limlvl);
+ }
+ return x;
+}
+
+#ifdef USE_FAST_LIMITER
+mad_fixed_t cNormalize::FastLimiter(mad_fixed_t x)
+{
+// The fast algorithm is based on a linear interpolation between the
+// the values in the lookup table. Relays heavly on libmads fixed point format.
+
+ if(x>limlvl) {
+ int i=(unsigned int)(x-tablestart)/F_LIM_JMP;
+#ifdef DEBUG
+ if(x>MAD_F_ONE) clipped++;
+ limited++;
+ if(i>=tablesize) printf("norm: overflow x=%f x-ts=%f i=%d tsize=%d\n",
+ mad_f_todouble(x),mad_f_todouble(x-tablestart),i,tablesize);
+#endif
+ mad_fixed_t r=x & (F_LIM_JMP-1);
+ x=MAD_F_ONE;
+ if(i<tablesize) {
+ mad_fixed_t *ptr=&table[i];
+ x=*ptr;
+ mad_fixed_t d=*(ptr+1)-x;
+ //x+=mad_f_mul(d,r)<<LIM_ACC; // this is not accurate as mad_f_mul() does >>MAD_F_FRACBITS
+ // which is senseless in the case of following <<LIM_ACC.
+ x+=((long long)d*(long long)r)>>LIM_SHIFT; // better, don't know if works on all machines
+ }
+ }
+ return x;
+}
+#endif
+
+#ifdef USE_FAST_LIMITER
+#define LIMITER_FUNC FastLimiter
+#else
+#define LIMITER_FUNC Limiter
+#endif
+
+void cNormalize::AddGain(struct mad_pcm *pcm)
+{
+ if(dogain) {
+ for(int i=0 ; i<pcm->channels ; i++) {
+ mad_fixed_t *data=pcm->samples[i];
+#ifdef DEBUG
+ total+=pcm->length;
+#endif
+ if(dolimit) {
+ for(int n=pcm->length ; n>0 ; n--) {
+ mad_fixed_t s=mad_f_mul(*data,gain);
+ if(s<0) {
+ s=-s;
+#ifdef DEBUG
+ if(s>peak) peak=s;
+#endif
+ s=LIMITER_FUNC(s);
+ s=-s;
+ }
+ else {
+#ifdef DEBUG
+ if(s>peak) peak=s;
+#endif
+ s=LIMITER_FUNC(s);
+ }
+ *data++=s;
+ }
+ }
+ else {
+ for(int n=pcm->length ; n>0 ; n--) {
+ mad_fixed_t s=mad_f_mul(*data,gain);
+#ifdef DEBUG
+ if(s>peak) peak=s;
+ else if(-s>peak) peak=-s;
+#endif
+ if(s>MAD_F_ONE) s=MAD_F_ONE; // do clipping
+ if(s<-MAD_F_ONE) s=-MAD_F_ONE;
+ *data++=s;
+ }
+ }
+ }
+ }
+}
+
+// --- cScale ----------------------------------------------------------------
+
+// The dither code has been adapted from the madplay project
+// (audio.c) found in the libmad distribution
+
+enum eAudioMode { amRound, amDither };
+
+class cScale {
+private:
+ enum { MIN=-MAD_F_ONE, MAX=MAD_F_ONE - 1 };
+#ifdef DEBUG
+ // audio stats
+ unsigned long clipped_samples;
+ mad_fixed_t peak_clipping;
+ mad_fixed_t peak_sample;
+#endif
+ // dither
+ struct dither {
+ mad_fixed_t error[3];
+ mad_fixed_t random;
+ } leftD, rightD;
+ //
+ inline mad_fixed_t Clip(mad_fixed_t sample, bool stats=true);
+ inline signed long LinearRound(mad_fixed_t sample);
+ inline unsigned long Prng(unsigned long state);
+ inline signed long LinearDither(mad_fixed_t sample, struct dither *dither);
+public:
+ void Init(void);
+ void Stats(void);
+ unsigned int ScaleBlock(unsigned char *data, unsigned int size, unsigned int &nsamples, const mad_fixed_t * &left, const mad_fixed_t * &right, eAudioMode mode);
+ };
+
+void cScale::Init(void)
+{
+#ifdef DEBUG
+ clipped_samples=0; peak_clipping=peak_sample=0;
+#endif
+ memset(&leftD,0,sizeof(leftD));
+ memset(&rightD,0,sizeof(rightD));
+}
+
+void cScale::Stats(void)
+{
+#ifdef DEBUG
+ printf("mp3: scale stats clipped=%ld peak_clip=%f peak=%f\n",
+ clipped_samples,mad_f_todouble(peak_clipping),mad_f_todouble(peak_sample));
+#endif
+}
+
+// gather signal statistics while clipping
+mad_fixed_t cScale::Clip(mad_fixed_t sample, bool stats)
+{
+#ifndef DEBUG
+ if (sample > MAX) sample = MAX;
+ if (sample < MIN) sample = MIN;
+#else
+ if(!stats) {
+ if (sample > MAX) sample = MAX;
+ if (sample < MIN) sample = MIN;
+ }
+ else {
+ if (sample >= peak_sample) {
+ if (sample > MAX) {
+ ++clipped_samples;
+ if (sample - MAX > peak_clipping)
+ peak_clipping = sample - MAX;
+ sample = MAX;
+ }
+ peak_sample = sample;
+ }
+ else if (sample < -peak_sample) {
+ if (sample < MIN) {
+ ++clipped_samples;
+ if (MIN - sample > peak_clipping)
+ peak_clipping = MIN - sample;
+ sample = MIN;
+ }
+ peak_sample = -sample;
+ }
+ }
+#endif
+ return sample;
+}
+
+// generic linear sample quantize routine
+signed long cScale::LinearRound(mad_fixed_t sample)
+{
+ // round
+ sample += (1L << (MAD_F_FRACBITS - OUT_BITS));
+ // clip
+ sample=Clip(sample);
+ // quantize and scale
+ return sample >> (MAD_F_FRACBITS + 1 - OUT_BITS);
+}
+
+// 32-bit pseudo-random number generator
+unsigned long cScale::Prng(unsigned long state)
+{
+ return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
+}
+
+// generic linear sample quantize and dither routine
+signed long cScale::LinearDither(mad_fixed_t sample, struct dither *dither)
+{
+ unsigned int scalebits;
+ mad_fixed_t output, mask, random;
+
+ // noise shape
+ sample += dither->error[0] - dither->error[1] + dither->error[2];
+ dither->error[2] = dither->error[1];
+ dither->error[1] = dither->error[0] / 2;
+ // bias
+ output = sample + (1L << (MAD_F_FRACBITS + 1 - OUT_BITS - 1));
+ scalebits = MAD_F_FRACBITS + 1 - OUT_BITS;
+ mask = (1L << scalebits) - 1;
+ // dither
+ random = Prng(dither->random);
+ output += (random & mask) - (dither->random & mask);
+ dither->random = random;
+ // clip
+ output=Clip(output);
+ sample=Clip(sample,false);
+ // quantize
+ output &= ~mask;
+ // error feedback
+ dither->error[0] = sample - output;
+ // scale
+ return output >> scalebits;
+}
+
+// write a block of signed 16-bit big-endian PCM samples
+unsigned int cScale::ScaleBlock(unsigned char *data, unsigned int size, unsigned int &nsamples, const mad_fixed_t * &left, const mad_fixed_t * &right, eAudioMode mode)
+{
+ signed int sample;
+ unsigned int len, res;
+
+ len=size/OUT_FACT; res=size;
+ if(len>nsamples) { len=nsamples; res=len*OUT_FACT; }
+ nsamples-=len;
+
+ if(right) { // stereo
+ switch (mode) {
+ case amRound:
+ while (len--) {
+ sample = LinearRound(*left++);
+ *data++ = sample >> 8;
+ *data++ = sample >> 0;
+ sample = LinearRound(*right++);
+ *data++ = sample >> 8;
+ *data++ = sample >> 0;
+ }
+ break;
+ case amDither:
+ while (len--) {
+ sample = LinearDither(*left++,&leftD);
+ *data++ = sample >> 8;
+ *data++ = sample >> 0;
+ sample = LinearDither(*right++,&rightD);
+ *data++ = sample >> 8;
+ *data++ = sample >> 0;
+ }
+ break;
+ }
+ }
+ else { // mono, duplicate left channel
+ switch (mode) {
+ case amRound:
+ while (len--) {
+ sample = LinearRound(*left++);
+ *data++ = sample >> 8;
+ *data++ = sample >> 0;
+ *data++ = sample >> 8;
+ *data++ = sample >> 0;
+ }
+ break;
+ case amDither:
+ while (len--) {
+ sample = LinearDither(*left++,&leftD);
+ *data++ = sample >> 8;
+ *data++ = sample >> 0;
+ *data++ = sample >> 8;
+ *data++ = sample >> 0;
+ }
+ break;
+ }
+ }
+ return res;
+}