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