1*0Sstevel@tonic-gate /*
2*0Sstevel@tonic-gate * CDDL HEADER START
3*0Sstevel@tonic-gate *
4*0Sstevel@tonic-gate * The contents of this file are subject to the terms of the
5*0Sstevel@tonic-gate * Common Development and Distribution License, Version 1.0 only
6*0Sstevel@tonic-gate * (the "License"). You may not use this file except in compliance
7*0Sstevel@tonic-gate * with the License.
8*0Sstevel@tonic-gate *
9*0Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10*0Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing.
11*0Sstevel@tonic-gate * See the License for the specific language governing permissions
12*0Sstevel@tonic-gate * and limitations under the License.
13*0Sstevel@tonic-gate *
14*0Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each
15*0Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16*0Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the
17*0Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying
18*0Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner]
19*0Sstevel@tonic-gate *
20*0Sstevel@tonic-gate * CDDL HEADER END
21*0Sstevel@tonic-gate */
22*0Sstevel@tonic-gate /*
23*0Sstevel@tonic-gate * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
24*0Sstevel@tonic-gate * Use is subject to license terms.
25*0Sstevel@tonic-gate */
26*0Sstevel@tonic-gate
27*0Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI"
28*0Sstevel@tonic-gate
29*0Sstevel@tonic-gate #include <stdio.h>
30*0Sstevel@tonic-gate #include <malloc.h>
31*0Sstevel@tonic-gate #include <math.h>
32*0Sstevel@tonic-gate #include <errno.h>
33*0Sstevel@tonic-gate #include <memory.h>
34*0Sstevel@tonic-gate #include <sys/param.h>
35*0Sstevel@tonic-gate #include <sys/types.h>
36*0Sstevel@tonic-gate #include <sys/ioctl.h>
37*0Sstevel@tonic-gate
38*0Sstevel@tonic-gate #include <AudioGain.h>
39*0Sstevel@tonic-gate #include <AudioTypePcm.h>
40*0Sstevel@tonic-gate
41*0Sstevel@tonic-gate #define irint(d) ((int)d)
42*0Sstevel@tonic-gate
43*0Sstevel@tonic-gate
44*0Sstevel@tonic-gate // initialize constants for instananeous gain normalization
45*0Sstevel@tonic-gate const double AudioGain::LoSigInstantRange = .008;
46*0Sstevel@tonic-gate const double AudioGain::HiSigInstantRange = .48;
47*0Sstevel@tonic-gate
48*0Sstevel@tonic-gate // initialize constants for weighted gain normalization
49*0Sstevel@tonic-gate const double AudioGain::NoSigWeight = .0000;
50*0Sstevel@tonic-gate const double AudioGain::LoSigWeightRange = .001;
51*0Sstevel@tonic-gate const double AudioGain::HiSigWeightRange = .050;
52*0Sstevel@tonic-gate
53*0Sstevel@tonic-gate // u-law max value converted to floating point
54*0Sstevel@tonic-gate const double AudioGain::PeakSig = .9803765;
55*0Sstevel@tonic-gate
56*0Sstevel@tonic-gate // XXX - patchable dc time constant: TC = 1 / (sample rate / DCfreq)
57*0Sstevel@tonic-gate int DCfreq = 500;
58*0Sstevel@tonic-gate const double AudioGain::DCtimeconstant = .1;
59*0Sstevel@tonic-gate
60*0Sstevel@tonic-gate // patchable debugging flag
61*0Sstevel@tonic-gate int debug_agc = 0;
62*0Sstevel@tonic-gate
63*0Sstevel@tonic-gate
64*0Sstevel@tonic-gate // Constructor
65*0Sstevel@tonic-gate AudioGain::
AudioGain()66*0Sstevel@tonic-gate AudioGain():
67*0Sstevel@tonic-gate clipcnt(0), DCaverage(0.), instant_gain(0.),
68*0Sstevel@tonic-gate weighted_peaksum(0.), weighted_sum(0.),
69*0Sstevel@tonic-gate weighted_avgsum(0.), weighted_cnt(0),
70*0Sstevel@tonic-gate gain_cache(NULL)
71*0Sstevel@tonic-gate {
72*0Sstevel@tonic-gate }
73*0Sstevel@tonic-gate
74*0Sstevel@tonic-gate // Destructor
75*0Sstevel@tonic-gate AudioGain::
~AudioGain()76*0Sstevel@tonic-gate ~AudioGain()
77*0Sstevel@tonic-gate {
78*0Sstevel@tonic-gate if (gain_cache != NULL) {
79*0Sstevel@tonic-gate delete gain_cache;
80*0Sstevel@tonic-gate }
81*0Sstevel@tonic-gate }
82*0Sstevel@tonic-gate
83*0Sstevel@tonic-gate // Return TRUE if we can handle this data type
84*0Sstevel@tonic-gate Boolean AudioGain::
CanConvert(const AudioHdr & hdr) const85*0Sstevel@tonic-gate CanConvert(
86*0Sstevel@tonic-gate const AudioHdr& hdr) const
87*0Sstevel@tonic-gate {
88*0Sstevel@tonic-gate return (float_convert.CanConvert(hdr));
89*0Sstevel@tonic-gate }
90*0Sstevel@tonic-gate
91*0Sstevel@tonic-gate // Return latest instantaneous gain
92*0Sstevel@tonic-gate double AudioGain::
InstantGain()93*0Sstevel@tonic-gate InstantGain()
94*0Sstevel@tonic-gate {
95*0Sstevel@tonic-gate return ((double)instant_gain);
96*0Sstevel@tonic-gate }
97*0Sstevel@tonic-gate
98*0Sstevel@tonic-gate // Return latest weighted gain
99*0Sstevel@tonic-gate double AudioGain::
WeightedGain()100*0Sstevel@tonic-gate WeightedGain()
101*0Sstevel@tonic-gate {
102*0Sstevel@tonic-gate double g;
103*0Sstevel@tonic-gate
104*0Sstevel@tonic-gate // Accumulated sum is averaged by the cache size and number of sums
105*0Sstevel@tonic-gate if ((weighted_cnt > 0) && (gain_cache_size > 0.)) {
106*0Sstevel@tonic-gate g = weighted_avgsum / gain_cache_size;
107*0Sstevel@tonic-gate g /= weighted_cnt;
108*0Sstevel@tonic-gate g -= NoSigWeight;
109*0Sstevel@tonic-gate if (g > HiSigWeightRange) {
110*0Sstevel@tonic-gate g = 1.;
111*0Sstevel@tonic-gate } else if (g < 0.) {
112*0Sstevel@tonic-gate g = 0.;
113*0Sstevel@tonic-gate } else {
114*0Sstevel@tonic-gate g /= HiSigWeightRange;
115*0Sstevel@tonic-gate }
116*0Sstevel@tonic-gate } else {
117*0Sstevel@tonic-gate g = 0.;
118*0Sstevel@tonic-gate }
119*0Sstevel@tonic-gate return (g);
120*0Sstevel@tonic-gate }
121*0Sstevel@tonic-gate
122*0Sstevel@tonic-gate // Return latest weighted peak
123*0Sstevel@tonic-gate // Clears the weighted peak for next calculation.
124*0Sstevel@tonic-gate double AudioGain::
WeightedPeak()125*0Sstevel@tonic-gate WeightedPeak()
126*0Sstevel@tonic-gate {
127*0Sstevel@tonic-gate double g;
128*0Sstevel@tonic-gate
129*0Sstevel@tonic-gate // Peak sum is averaged by the cache size
130*0Sstevel@tonic-gate if (gain_cache_size > 0.) {
131*0Sstevel@tonic-gate g = weighted_peaksum / gain_cache_size;
132*0Sstevel@tonic-gate g -= NoSigWeight;
133*0Sstevel@tonic-gate if (g > HiSigWeightRange) {
134*0Sstevel@tonic-gate g = 1.;
135*0Sstevel@tonic-gate } else if (g < 0.) {
136*0Sstevel@tonic-gate g = 0.;
137*0Sstevel@tonic-gate } else {
138*0Sstevel@tonic-gate g /= HiSigWeightRange;
139*0Sstevel@tonic-gate }
140*0Sstevel@tonic-gate } else {
141*0Sstevel@tonic-gate g = 0.;
142*0Sstevel@tonic-gate }
143*0Sstevel@tonic-gate weighted_peaksum = 0.;
144*0Sstevel@tonic-gate return (g);
145*0Sstevel@tonic-gate }
146*0Sstevel@tonic-gate
147*0Sstevel@tonic-gate // Return TRUE if signal clipped during last processed buffer
148*0Sstevel@tonic-gate Boolean AudioGain::
Clipped()149*0Sstevel@tonic-gate Clipped()
150*0Sstevel@tonic-gate {
151*0Sstevel@tonic-gate Boolean clipped;
152*0Sstevel@tonic-gate
153*0Sstevel@tonic-gate clipped = (clipcnt > 0);
154*0Sstevel@tonic-gate return (clipped);
155*0Sstevel@tonic-gate }
156*0Sstevel@tonic-gate
157*0Sstevel@tonic-gate // Flush gain state
158*0Sstevel@tonic-gate void AudioGain::
Flush()159*0Sstevel@tonic-gate Flush()
160*0Sstevel@tonic-gate {
161*0Sstevel@tonic-gate clipcnt = 0;
162*0Sstevel@tonic-gate DCaverage = 0.;
163*0Sstevel@tonic-gate instant_gain = 0.;
164*0Sstevel@tonic-gate weighted_peaksum = 0.;
165*0Sstevel@tonic-gate weighted_sum = 0.;
166*0Sstevel@tonic-gate weighted_avgsum = 0.;
167*0Sstevel@tonic-gate weighted_cnt = 0;
168*0Sstevel@tonic-gate if (gain_cache != NULL) {
169*0Sstevel@tonic-gate delete gain_cache;
170*0Sstevel@tonic-gate gain_cache = NULL;
171*0Sstevel@tonic-gate }
172*0Sstevel@tonic-gate }
173*0Sstevel@tonic-gate
174*0Sstevel@tonic-gate // Process an input buffer according to the specified flags
175*0Sstevel@tonic-gate // The input buffer is consumed if the reference count is zero!
176*0Sstevel@tonic-gate AudioError AudioGain::
Process(AudioBuffer * inbuf,int type)177*0Sstevel@tonic-gate Process(
178*0Sstevel@tonic-gate AudioBuffer* inbuf,
179*0Sstevel@tonic-gate int type)
180*0Sstevel@tonic-gate {
181*0Sstevel@tonic-gate AudioHdr newhdr;
182*0Sstevel@tonic-gate AudioError err;
183*0Sstevel@tonic-gate
184*0Sstevel@tonic-gate if (inbuf == NULL)
185*0Sstevel@tonic-gate return (AUDIO_ERR_BADARG);
186*0Sstevel@tonic-gate
187*0Sstevel@tonic-gate if (Undefined(inbuf->GetLength())) {
188*0Sstevel@tonic-gate err = AUDIO_ERR_BADARG;
189*0Sstevel@tonic-gate process_error:
190*0Sstevel@tonic-gate // report error and toss the buffer if it is not referenced
191*0Sstevel@tonic-gate inbuf->RaiseError(err);
192*0Sstevel@tonic-gate inbuf->Reference();
193*0Sstevel@tonic-gate inbuf->Dereference();
194*0Sstevel@tonic-gate return (err);
195*0Sstevel@tonic-gate }
196*0Sstevel@tonic-gate
197*0Sstevel@tonic-gate // Set up to convert to floating point; verify all header formats
198*0Sstevel@tonic-gate newhdr = inbuf->GetHeader();
199*0Sstevel@tonic-gate if (!float_convert.CanConvert(newhdr)) {
200*0Sstevel@tonic-gate err = AUDIO_ERR_HDRINVAL;
201*0Sstevel@tonic-gate goto process_error;
202*0Sstevel@tonic-gate }
203*0Sstevel@tonic-gate newhdr.encoding = FLOAT;
204*0Sstevel@tonic-gate newhdr.bytes_per_unit = 8;
205*0Sstevel@tonic-gate if ((err = newhdr.Validate()) || !float_convert.CanConvert(newhdr)) {
206*0Sstevel@tonic-gate err = AUDIO_ERR_HDRINVAL;
207*0Sstevel@tonic-gate goto process_error;
208*0Sstevel@tonic-gate }
209*0Sstevel@tonic-gate
210*0Sstevel@tonic-gate // Convert to floating-point up front, if necessary
211*0Sstevel@tonic-gate if (inbuf->GetHeader() != newhdr) {
212*0Sstevel@tonic-gate err = float_convert.Convert(inbuf, newhdr);
213*0Sstevel@tonic-gate if (err)
214*0Sstevel@tonic-gate goto process_error;
215*0Sstevel@tonic-gate }
216*0Sstevel@tonic-gate
217*0Sstevel@tonic-gate // Reference the resulting buffer to make sure it gets ditched later
218*0Sstevel@tonic-gate inbuf->Reference();
219*0Sstevel@tonic-gate
220*0Sstevel@tonic-gate // run through highpass filter to reject DC
221*0Sstevel@tonic-gate process_dcfilter(inbuf);
222*0Sstevel@tonic-gate
223*0Sstevel@tonic-gate if (type & AUDIO_GAIN_INSTANT)
224*0Sstevel@tonic-gate process_instant(inbuf);
225*0Sstevel@tonic-gate
226*0Sstevel@tonic-gate if (type & AUDIO_GAIN_WEIGHTED)
227*0Sstevel@tonic-gate process_weighted(inbuf);
228*0Sstevel@tonic-gate
229*0Sstevel@tonic-gate inbuf->Dereference();
230*0Sstevel@tonic-gate return (AUDIO_SUCCESS);
231*0Sstevel@tonic-gate }
232*0Sstevel@tonic-gate
233*0Sstevel@tonic-gate // Run the buffer through a simple, dc filter.
234*0Sstevel@tonic-gate // Buffer is assumed to be floating-point double PCM
235*0Sstevel@tonic-gate void AudioGain::
process_dcfilter(AudioBuffer * inbuf)236*0Sstevel@tonic-gate process_dcfilter(
237*0Sstevel@tonic-gate AudioBuffer* inbuf)
238*0Sstevel@tonic-gate {
239*0Sstevel@tonic-gate int i;
240*0Sstevel@tonic-gate Boolean lastpeak;
241*0Sstevel@tonic-gate double val;
242*0Sstevel@tonic-gate double dcweight;
243*0Sstevel@tonic-gate double timeconstant;
244*0Sstevel@tonic-gate AudioHdr inhdr;
245*0Sstevel@tonic-gate double *inptr;
246*0Sstevel@tonic-gate size_t frames;
247*0Sstevel@tonic-gate
248*0Sstevel@tonic-gate inhdr = inbuf->GetHeader();
249*0Sstevel@tonic-gate inptr = (double *)inbuf->GetAddress();
250*0Sstevel@tonic-gate frames = (size_t)inhdr.Time_to_Samples(inbuf->GetLength());
251*0Sstevel@tonic-gate clipcnt = 0;
252*0Sstevel@tonic-gate lastpeak = FALSE;
253*0Sstevel@tonic-gate
254*0Sstevel@tonic-gate // Time constant corresponds to the number of samples for 500Hz
255*0Sstevel@tonic-gate timeconstant = 1. / (inhdr.sample_rate / (double)DCfreq);
256*0Sstevel@tonic-gate dcweight = 1. - timeconstant;
257*0Sstevel@tonic-gate
258*0Sstevel@tonic-gate // loop through the input buffer, rewriting with weighted data
259*0Sstevel@tonic-gate // XXX - should deal with multi-channel data!
260*0Sstevel@tonic-gate // XXX - for now, check first channel only
261*0Sstevel@tonic-gate for (i = 0; i < frames; i++, inptr += inhdr.channels) {
262*0Sstevel@tonic-gate val = *inptr;
263*0Sstevel@tonic-gate
264*0Sstevel@tonic-gate // Two max values in a row constitutes clipping
265*0Sstevel@tonic-gate if ((val >= PeakSig) || (val <= -PeakSig)) {
266*0Sstevel@tonic-gate if (lastpeak) {
267*0Sstevel@tonic-gate clipcnt++;
268*0Sstevel@tonic-gate } else {
269*0Sstevel@tonic-gate lastpeak = TRUE;
270*0Sstevel@tonic-gate }
271*0Sstevel@tonic-gate } else {
272*0Sstevel@tonic-gate lastpeak = FALSE;
273*0Sstevel@tonic-gate }
274*0Sstevel@tonic-gate
275*0Sstevel@tonic-gate // Add in this value to weighted average
276*0Sstevel@tonic-gate DCaverage = (DCaverage * dcweight) + (val * timeconstant);
277*0Sstevel@tonic-gate val -= DCaverage;
278*0Sstevel@tonic-gate if (val > 1.)
279*0Sstevel@tonic-gate val = 1.;
280*0Sstevel@tonic-gate else if (val < -1.)
281*0Sstevel@tonic-gate val = -1.;
282*0Sstevel@tonic-gate *inptr = val;
283*0Sstevel@tonic-gate }
284*0Sstevel@tonic-gate }
285*0Sstevel@tonic-gate
286*0Sstevel@tonic-gate // Calculate a single energy value averaged from the input buffer
287*0Sstevel@tonic-gate // Buffer is assumed to be floating-point double PCM
288*0Sstevel@tonic-gate void AudioGain::
process_instant(AudioBuffer * inbuf)289*0Sstevel@tonic-gate process_instant(
290*0Sstevel@tonic-gate AudioBuffer* inbuf)
291*0Sstevel@tonic-gate {
292*0Sstevel@tonic-gate int i;
293*0Sstevel@tonic-gate double val;
294*0Sstevel@tonic-gate double sum;
295*0Sstevel@tonic-gate double sv;
296*0Sstevel@tonic-gate AudioHdr inhdr;
297*0Sstevel@tonic-gate double *inptr;
298*0Sstevel@tonic-gate size_t frames;
299*0Sstevel@tonic-gate
300*0Sstevel@tonic-gate inhdr = inbuf->GetHeader();
301*0Sstevel@tonic-gate inptr = (double *)inbuf->GetAddress();
302*0Sstevel@tonic-gate frames = (size_t)inhdr.Time_to_Samples(inbuf->GetLength());
303*0Sstevel@tonic-gate
304*0Sstevel@tonic-gate // loop through the input buffer, calculating gain
305*0Sstevel@tonic-gate // XXX - should deal with multi-channel data!
306*0Sstevel@tonic-gate // XXX - for now, check first channel only
307*0Sstevel@tonic-gate sum = 0.;
308*0Sstevel@tonic-gate for (i = 0; i < frames; i++, inptr += inhdr.channels) {
309*0Sstevel@tonic-gate // Get absolute value
310*0Sstevel@tonic-gate sum += fabs(*inptr);
311*0Sstevel@tonic-gate }
312*0Sstevel@tonic-gate sum /= (double)frames;
313*0Sstevel@tonic-gate
314*0Sstevel@tonic-gate // calculate level meter value (between 0 & 1)
315*0Sstevel@tonic-gate val = log10(1. + (9. * sum));
316*0Sstevel@tonic-gate sv = val;
317*0Sstevel@tonic-gate
318*0Sstevel@tonic-gate // Normalize to within a reasonable range
319*0Sstevel@tonic-gate val -= LoSigInstantRange;
320*0Sstevel@tonic-gate if (val > HiSigInstantRange) {
321*0Sstevel@tonic-gate val = 1.;
322*0Sstevel@tonic-gate } else if (val < 0.) {
323*0Sstevel@tonic-gate val = 0.;
324*0Sstevel@tonic-gate } else {
325*0Sstevel@tonic-gate val /= HiSigInstantRange;
326*0Sstevel@tonic-gate }
327*0Sstevel@tonic-gate instant_gain = val;
328*0Sstevel@tonic-gate
329*0Sstevel@tonic-gate if (debug_agc != 0) {
330*0Sstevel@tonic-gate printf("audio_amplitude: avg = %7.5f log value = %7.5f, "
331*0Sstevel@tonic-gate "adjusted = %7.5f\n", sum, sv, val);
332*0Sstevel@tonic-gate }
333*0Sstevel@tonic-gate }
334*0Sstevel@tonic-gate
335*0Sstevel@tonic-gate // Calculate a weighted gain for agc computations
336*0Sstevel@tonic-gate // Buffer is assumed to be floating-point double PCM
337*0Sstevel@tonic-gate void AudioGain::
process_weighted(AudioBuffer * inbuf)338*0Sstevel@tonic-gate process_weighted(
339*0Sstevel@tonic-gate AudioBuffer* inbuf)
340*0Sstevel@tonic-gate {
341*0Sstevel@tonic-gate int i;
342*0Sstevel@tonic-gate double val;
343*0Sstevel@tonic-gate double nosig;
344*0Sstevel@tonic-gate AudioHdr inhdr;
345*0Sstevel@tonic-gate double *inptr;
346*0Sstevel@tonic-gate size_t frames;
347*0Sstevel@tonic-gate Double sz;
348*0Sstevel@tonic-gate
349*0Sstevel@tonic-gate inhdr = inbuf->GetHeader();
350*0Sstevel@tonic-gate inptr = (double *)inbuf->GetAddress();
351*0Sstevel@tonic-gate frames = (size_t)inhdr.Time_to_Samples(inbuf->GetLength());
352*0Sstevel@tonic-gate sz = (Double) frames;
353*0Sstevel@tonic-gate
354*0Sstevel@tonic-gate // Allocate gain cache...all calls will hopefully be the same length
355*0Sstevel@tonic-gate if (gain_cache == NULL) {
356*0Sstevel@tonic-gate gain_cache = new double[frames];
357*0Sstevel@tonic-gate for (i = 0; i < frames; i++) {
358*0Sstevel@tonic-gate gain_cache[i] = 0.;
359*0Sstevel@tonic-gate }
360*0Sstevel@tonic-gate gain_cache_size = sz;
361*0Sstevel@tonic-gate } else if (sz > gain_cache_size) {
362*0Sstevel@tonic-gate frames = (size_t)irint(gain_cache_size);
363*0Sstevel@tonic-gate }
364*0Sstevel@tonic-gate // Scale up the 'no signal' level to avoid a divide in the inner loop
365*0Sstevel@tonic-gate nosig = NoSigWeight * gain_cache_size;
366*0Sstevel@tonic-gate
367*0Sstevel@tonic-gate // For each sample:
368*0Sstevel@tonic-gate // calculate the sum of squares for a window around the sample;
369*0Sstevel@tonic-gate // save the peak sum of squares;
370*0Sstevel@tonic-gate // keep a running average of the sum of squares
371*0Sstevel@tonic-gate //
372*0Sstevel@tonic-gate // XXX - should deal with multi-channel data!
373*0Sstevel@tonic-gate // XXX - for now, check first channel only
374*0Sstevel@tonic-gate
375*0Sstevel@tonic-gate for (i = 0; i < frames; i++, inptr += inhdr.channels) {
376*0Sstevel@tonic-gate val = *inptr;
377*0Sstevel@tonic-gate val *= val;
378*0Sstevel@tonic-gate weighted_sum += val;
379*0Sstevel@tonic-gate weighted_sum -= gain_cache[i];
380*0Sstevel@tonic-gate gain_cache[i] = val; // save value to subtract later
381*0Sstevel@tonic-gate if (weighted_sum > weighted_peaksum)
382*0Sstevel@tonic-gate weighted_peaksum = weighted_sum; // save peak
383*0Sstevel@tonic-gate
384*0Sstevel@tonic-gate // Only count this sample towards the average if it is
385*0Sstevel@tonic-gate // above threshold (this attempts to keep the volume
386*0Sstevel@tonic-gate // from pumping up when there is no input signal).
387*0Sstevel@tonic-gate if (weighted_sum > nosig) {
388*0Sstevel@tonic-gate weighted_avgsum += weighted_sum;
389*0Sstevel@tonic-gate weighted_cnt++;
390*0Sstevel@tonic-gate }
391*0Sstevel@tonic-gate }
392*0Sstevel@tonic-gate }
393