xref: /illumos-gate/usr/src/cmd/audio/utilities/AudioGain.cc (revision e5803b76927480e8f9b67b22201c484ccf4c2bcf)
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::
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::
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::
85 CanConvert(
86 	const AudioHdr&	hdr) const
87 {
88 	return (float_convert.CanConvert(hdr));
89 }
90 
91 // Return latest instantaneous gain
92 double AudioGain::
93 InstantGain()
94 {
95 	return ((double)instant_gain);
96 }
97 
98 // Return latest weighted gain
99 double AudioGain::
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::
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::
149 Clipped()
150 {
151 	Boolean		clipped;
152 
153 	clipped = (clipcnt > 0);
154 	return (clipped);
155 }
156 
157 // Flush gain state
158 void AudioGain::
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::
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::
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::
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::
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