diff options
Diffstat (limited to 'muggle-plugin/vdr_sound.c')
-rw-r--r-- | muggle-plugin/vdr_sound.c | 753 |
1 files changed, 753 insertions, 0 deletions
diff --git a/muggle-plugin/vdr_sound.c b/muggle-plugin/vdr_sound.c new file mode 100644 index 0000000..7c4304f --- /dev/null +++ b/muggle-plugin/vdr_sound.c @@ -0,0 +1,753 @@ +/*! + * \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 <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; + } + // 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)<<LIM_ACC; // this is not accurate as mad_f_mul() does >>MAD_F_FRACBITS +// which is senseless in the case of following <<LIM_ACC. + // better, don't know if works on all machines + x += ((long long) d * (long long) r) >> 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; +} |