summaryrefslogtreecommitdiff
path: root/muggle-plugin/vdr_sound.c
diff options
context:
space:
mode:
authorLarsAC <LarsAC@e10066b5-e1e2-0310-b819-94efdf66514b>2005-03-22 06:47:53 +0000
committerLarsAC <LarsAC@e10066b5-e1e2-0310-b819-94efdf66514b>2005-03-22 06:47:53 +0000
commite2de0c5ed7bbbe4b236246e8bfd71cc87c8d974f (patch)
tree616f2f0a482597e3968e281ccf8adcfd04f45bbc /muggle-plugin/vdr_sound.c
parent101360901576c7e91196de60e2e6ebd6a4b145dd (diff)
downloadvdr-plugin-muggle-0.1.6-BETA.tar.gz
vdr-plugin-muggle-0.1.6-BETA.tar.bz2
Added 0.1.6 beta tag0.1.6-BETA
git-svn-id: https://vdr-muggle.svn.sourceforge.net/svnroot/vdr-muggle/tags/0.1.6-BETA@586 e10066b5-e1e2-0310-b819-94efdf66514b
Diffstat (limited to 'muggle-plugin/vdr_sound.c')
-rw-r--r--muggle-plugin/vdr_sound.c753
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;
+}