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