/*! * \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 */ // --- 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 // 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 ; ichannels ; 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(maxpowmaxpow) 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=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>MAD_F_FRACBITS // which is senseless in the case of following <>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 ; ichannels ; 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; }