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