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