/*! * \file vdr_setup.c * \brief Sound manipulation classes for a VDR media plugin (muggle) * * \version $Revision: 1.2 $ * \date $Date$ * \author Ralf Klueber, Lars von Wedel, Andreas Kellner * \author Responsible author: $Author$ * * $Id$ * * 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; 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; } // adjust for the smoothing window size and root double level = sqrt (maxpow / (double) POW_WIN); 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)<>MAD_F_FRACBITS // which is senseless in the case of following <> LIM_SHIFT; } } 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: cScale(); 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); }; cScale::cScale() { #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; }