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