#ifndef __KERNEL__ #include #include #include #include #include #include #include #include #endif /* Volume adjustment * By r@rjlov.com */ /* * The interesting stuff in this file is voladj_* * The main function gives an indication of how to use * the functions. Basically, call voladj_init with the arguments * you want, and then pass the address of the returned structure * into all subsequent calls. * * To look ahead, call voladj_check() on the future buffer, and then * voladj_scale on the current buffer. * * To not look ahead, call voladj_check() on the current buffer, and * then voladj_scale on the current buffer. * * If we decide that the look ahead doesn't improve the sound at all, * then we can pretty much remove voladj_check, and it becomes * even more trivial. * * To figure out the desired multiplier, first we figure out the * desired output from the given input, and then calculate the * multiplier that would give that output. * * Currently, we use * desired_out = exp( log( input ) / constant ) * which has the property that * if in_a:in_b == in_b:in_c * then out_a:out_b == out_b:out_c * i.e. The dynamic range is stretched, but not otherwise distorted. * * To calculate the constant above, we use the fact that we * have a specified minimum output volume, and a specified * minimum input volume to scale to that value. * * This gives us: * exp( log( fake_silence ) / factor ) = minvol; * or * factor = log( fake_silence ) / log( minvol ); * * Simply from our fixed point routines, we require that * minvol / fake_silence < 1 << MULT_INTBITS; * Otherwise, we can't make our multiplier big enough! */ #define AUDIO_FILLSZ 4608 #define BLOCKSIZE AUDIO_FILLSZ #define MAXSAMPLES 32767 #define MULT_POINT 12 #define SHORT_FRAC_POINT MULT_POINT #define SHORT_FRAC_VAL ((double)(1 << SHORT_FRAC_POINT)) #define MULT_INTBITS (30 - (MULT_POINT + SHORT_FRAC_POINT)) #define MULT_TO_16 (MULT_POINT + MULT_INTBITS - 16) #define RESULT_SHIFT MULT_POINT struct voladj_state { int output_multiplier; int desired_multiplier; int buf_size; short real_silence; short fake_silence; short log_scale; unsigned short headroom; short minvol; short increase; short decrease; unsigned short max_sample; }; int voladj_exp(int inval) { int result = (1 << MULT_POINT); int i = 1; int curval = (1 << MULT_POINT); for (i = 1; curval != 0; i++) { curval = ((curval * inval) / i) >> RESULT_SHIFT; result += curval; } /* printf("expiters: %d\n",i); */ return result; } /* * These two define ln(2) and 1/ln(2) in our fixed point scheme. * They are used because then it's much easier to scale our * input number for voladj_log() to a number close to 1 * */ #define LOGTWO (0x0000b172 >> (16 - MULT_POINT)) #define INVLOGTWO (0x00017154 >> (16 - MULT_POINT)) int voladj_log(int inval) { int aftercount = 0; int result = 0; int x = 0; int i = 1; int curval = 1 << MULT_POINT; while (inval < (1 << (MULT_POINT - 1)) ) { inval = inval << 1; aftercount ++; } while (inval > (1 << MULT_POINT)) { inval = inval >> 1; aftercount --; } /* printf("inval: %d\n",inval); */ x = (1 << MULT_POINT) - inval; for (i = 1; curval != 0; i++) { result += ((curval * x) / i) >> RESULT_SHIFT; curval = (curval * x) >> RESULT_SHIFT; } /* printf("logiters: %d\n",i); */ result = ((((result * INVLOGTWO) >> RESULT_SHIFT) + (aftercount << MULT_POINT)) * LOGTWO) >> RESULT_SHIFT; result = -1 * result; return result; } /* * Initialise all our stuff. * This converts easy to understand parameters into easy to use ones. * It also attempts to fix any parameters that are set badly, given * our range of precision. * voladj_intinit( &(dev->voladj), AUDIO_BUFFER_SIZE, buffersize ((1 << MULT_POINT) * 2), factor_per_second ((1 << MULT_POINT) / 10), minvol (((1 << MULT_POINT) * 3) / 4), headroom 30, real_silence 80 fake_silence ); */ struct voladj_state* voladj_intinit( struct voladj_state *initial, int buf_size, /* (Fixed) size of blocks in bytes to process */ int factor_per_second, /* Maximum rate of volume change (ratio/sec << MULT_POINT) */ int minvol, /* Minimum volume to attempt to maintain (0 - 1 << MULT_POINT) */ int headroom, /* Headroom multiplier (0 - 1 << MULT_POINT) */ int real_silence, /* Threshold below which we gradually return to normal (Number of samples) */ int fake_silence /* Threshold below which we do no further scaling (Number of samples) */ ) { int log_scale; int temp_fake; #ifdef __KERNEL__ unsigned long flags; save_flags_cli(flags); #endif initial->output_multiplier = 0x1 << MULT_POINT; initial->desired_multiplier = initial->output_multiplier; initial->buf_size = buf_size; initial->headroom = 3 << (SHORT_FRAC_POINT - 2); initial->real_silence = real_silence; initial->fake_silence = fake_silence; initial->increase = voladj_exp( (voladj_log( factor_per_second ) *buf_size) / (4 * 44100)); initial->decrease = (1 << (MULT_POINT * 2)) / initial->increase; temp_fake = (MAXSAMPLES * minvol) >> (MULT_INTBITS + MULT_POINT); if (initial->fake_silence < temp_fake) { initial->fake_silence = temp_fake; } if (minvol > (1 << SHORT_FRAC_POINT)) { initial->minvol = 1 << SHORT_FRAC_POINT; } else if (minvol < 0) { initial->minvol = 0; } else { initial->minvol = minvol; } if (initial->minvol == 0) { initial->log_scale = (1 << MULT_POINT); log_scale = 1.0; } else { log_scale = -1 * (voladj_log( (MAXSAMPLES << MULT_POINT) / initial->fake_silence ) << MULT_POINT) / voladj_log( minvol ); initial->log_scale = log_scale; } if (headroom < 0) { initial->headroom = 0; } else { initial->headroom = headroom; } initial->max_sample = 0; #ifdef __KERNEL__ restore_flags(flags); #endif /* fprintf(stderr, "minvol: %x headroom: %x increase: %x decrease %x\n", initial->minvol, initial->headroom, initial->increase, initial->decrease); fprintf(stderr, "MULT_TO_16: %d MULT_POINT: %d MULT_INTBITS: %d SHORT_FRAC_VAL: %f \n", MULT_TO_16, MULT_POINT, MULT_INTBITS, SHORT_FRAC_VAL); fprintf(stderr, "fake_silence: %d LOG_SCALE: %d \n", initial->fake_silence, log_scale >> MULT_POINT ); fprintf(stderr, "factor_per_second: %x minvol: %x fake: %x log(minvol) %x\n", factor_per_second, initial->minvol, initial->fake_silence, voladj_log( (MAXSAMPLES << MULT_POINT) / initial->fake_silence )); */ return initial; }; /* * Here we figure out what multiplier we want, based on the minimum * volume that we are aiming for, and the maximum sample that we * can see. */ int voladj_get_multiplier( struct voladj_state *state, unsigned short sample ) { int frac,desired_out, desired_mult; frac = (sample << MULT_POINT) / MAXSAMPLES; if (frac == 0) frac = 1; if (sample == 0) sample = 1; /* * This is the old linear way desired_out = ((int)(state->minvol) * (int)MAXSAMPLES) + (((int)frac) * ( (1 << SHORT_FRAC_POINT) - (int)(state->minvol) )) ; */ desired_out = MAXSAMPLES * voladj_exp( (voladj_log( frac ) << MULT_POINT) / state->log_scale); desired_mult = desired_out / sample ; if (state->headroom <= (1 << SHORT_FRAC_POINT)) { desired_mult = (desired_mult * (int)state->headroom) >> RESULT_SHIFT; } if (desired_mult > (1 << (MULT_POINT + MULT_INTBITS))) { desired_mult = (1 << (MULT_POINT + MULT_INTBITS)); } if (desired_mult < (1 << MULT_POINT)) { desired_mult = (1 << MULT_POINT); } return desired_mult; } /* * Integer square root * Used to convert power to equivalent sample value */ unsigned int voladj_sqrt( unsigned int num ) { unsigned int res = 0; unsigned int bit = 1 << 30; while (bit > num) { bit >>= 2; } while (bit != 0) { if (num >= res + bit) { num = num - (res + bit); res = (res >> 1) + bit; } else { res >>= 1; } bit >>= 2; } return res; } /* * This bit of code searches for any samples that will cause clipping * in the future. The reason we read ahead is so that we never * have to do abrupt volume changes. I'm not so sure that this is * necessary, because even gradual volume changes over 4k of data * are pretty abrupt to the ear. Still, I have this nagging feeling * that suddenly changing the scaling factor from 100 to 1 would * produce some high frequency components. */ int voladj_check( struct voladj_state *state, short *lookaheadbuf ) { int outmult; int desired_multiplier; int upmult; int downmult; unsigned short max_sample; unsigned short cur_sample; unsigned short max_la_sample; unsigned int scale_sample; int num_samples = state->buf_size / 2; int i, outputvalue; int energyshift = 0; unsigned int energy = 0; max_la_sample = 0; for( i = 0; i < num_samples; i++ ) { cur_sample = abs( lookaheadbuf[ i ] ); energy += ((unsigned int)(cur_sample * cur_sample)) >> energyshift; if (energy & 0x80000000) { energy = energy >> 1; energyshift ++; } if (cur_sample > max_la_sample) { max_la_sample = cur_sample; } } max_sample = max_la_sample; if (state->max_sample > max_sample) { max_sample = state->max_sample; } /* Headroom > 1 is actually an RMS multiplier */ if (state->headroom > (1 << SHORT_FRAC_POINT)) { /* Power based scaling */ scale_sample = voladj_sqrt( (energy / num_samples) << energyshift ); /* * This scaling factor is equivalent to reducing the silence, headroom * and minvol values by the same factor. * Helps keep things in sensible range for all the fixed point stuff, * and means that the original silence values still make some sort of sense */ scale_sample *= state->headroom >> SHORT_FRAC_POINT; if (scale_sample >= MAXSAMPLES) { scale_sample = MAXSAMPLES; } } else { /* Peak amplitude scaling */ scale_sample = max_sample; } outmult = state->output_multiplier; upmult = (outmult * state->increase) >> RESULT_SHIFT; downmult = (outmult * state->decrease) >> RESULT_SHIFT; outputvalue = ( ( (upmult >> MULT_TO_16 ) * max_sample ) >> (MULT_POINT - MULT_TO_16 ) ); desired_multiplier = 0; if (outputvalue > MAXSAMPLES ) { desired_multiplier = (((MAXSAMPLES + 1) << MULT_POINT) / max_sample); /* fprintf(stderr, "avoiding clipping, output_multiplier: %x, new desired_multiplier: %x, max_sample: %x\n", state->output_multiplier, desired_multiplier, max_sample); */ } else if (scale_sample < state->real_silence) { desired_multiplier = downmult; if (desired_multiplier < (1 << MULT_POINT)) { desired_multiplier = (1 << MULT_POINT); } } else if (scale_sample < state->fake_silence) { scale_sample = state->fake_silence; } if (desired_multiplier == 0) { desired_multiplier = voladj_get_multiplier( state, scale_sample ); if ((desired_multiplier > (1 << (MULT_POINT + MULT_INTBITS)))) { desired_multiplier = downmult; } else { if (desired_multiplier > upmult) { desired_multiplier = upmult; } if (desired_multiplier < downmult) { desired_multiplier = downmult; } } } /* fprintf(stderr,"sampes prv: %x la: %x comb: %d energy: %x shift: %x avg: %d desmult: %x\n", state->max_sample, max_la_sample, max_sample, energy, energyshift, scale_sample, desired_multiplier); fprintf(stderr,"up: %x down: %x outval: %x\n", state->increase, state->decrease, outputvalue); */ state->desired_multiplier = desired_multiplier; state->max_sample = max_la_sample; return desired_multiplier; } void voladj_scale( struct voladj_state *state, int desired_multiplier, short *scalebuf ) { int outmult = state->output_multiplier; int output_value,i; short output_sample; int num_samples = state->buf_size / 2; /* If there is no scaling to be done, just return immediately. * There's no point going through multiplying every sample by 1! */ if ((outmult == (1 << MULT_POINT)) && (desired_multiplier == (1 << MULT_POINT))) { return; } /* * In the previous call to voladj_check we made sure that the * output multiplier was set to a value that will not cause * clipping for any of the samples, so we don't have to worry * about that here. */ for( i = 0; i < num_samples; i++ ) { if (desired_multiplier != outmult) { outmult = outmult + ((desired_multiplier - outmult) / (num_samples - i)); } output_value = ((outmult >> MULT_TO_16) * scalebuf[ i ]) >> (MULT_POINT - MULT_TO_16 ) ; /* if ((output_value > MAXSAMPLES)||(output_value < (-1 * (MAXSAMPLES+1)))) { fprintf(stderr, "Arghh! Got clipping, output_multiplier: %x, insamp: %x, outsamp %x\n", outmult, scalebuf[i], output_value); fprintf(stderr, " output_multiplier: %x, abs(insamp): %x, abs(outsamp) %x\n", outmult, abs(scalebuf[i]), abs(output_value)); } */ output_sample = (short)(0x0000ffff & output_value); scalebuf[ i ] = output_sample; } state->output_multiplier = outmult; } #ifndef __KERNEL__ /* * Initialise all our stuff. One of the main things this does is * convert easy to read floats into fixed point. */ struct voladj_state voladj_init( int buf_size, /* (Fixed) size of blocks in bytes to process */ double db_per_second, /* Maximum rate of volume change, in dB/sec */ double minvol, /* Minimum volume to attempt to maintain (0 - 1) */ double headroom, /* Headroom multiplier (0-1), RMS multiplier (1-x) */ double real_silence, /* Threshold below which we gradually return to normal */ double fake_silence /* Threshold below which we do no further scaling */ ) { struct voladj_state initial; double factor_per_second; int intfactor_per_second,intminvol,intheadroom,intreal_silence; int intfake_silence; factor_per_second = pow( 10.0 , db_per_second / 10.0 ); intfactor_per_second = factor_per_second * SHORT_FRAC_VAL; intminvol = (minvol * SHORT_FRAC_VAL); intheadroom = (headroom * SHORT_FRAC_VAL); intreal_silence = real_silence * (double)MAXSAMPLES; intfake_silence = fake_silence * (double)MAXSAMPLES; voladj_intinit( &initial, buf_size, intfactor_per_second, intminvol, intheadroom, intreal_silence, intfake_silence ); fprintf(stderr, "minvol: %x headroom: %x increase: %x decrease %x\n", initial.minvol, initial.headroom, initial.increase, initial.decrease); fprintf(stderr, "MULT_TO_16: %d MULT_POINT: %d MULT_INTBITS: %d SHORT_FRAC_VAL: %f \n", MULT_TO_16, MULT_POINT, MULT_INTBITS, SHORT_FRAC_VAL); return initial; }; int main(int argc, char *argv[]) { static char buffer1[BLOCKSIZE]; static char buffer2[BLOCKSIZE]; char *actualbuff, *lookaheadbuff, *tempbuf; int i,lookahead; unsigned int len,lastlen; struct voladj_state state; state = voladj_init( BLOCKSIZE, /* (Fixed) size of blocks in bytes to process */ 3.0, /* Maximum rate of volume change, in dB/sec */ 0.5, /* Minimum volume to attempt to maintain (0 - 1) */ 8, /* Headroom multiplier < 1, rms multiplier > 1 */ 0.002, 0.006 ); for (i = 0; i < BLOCKSIZE; i++) { buffer1[ i ] = 0; } for (i = 0; i < BLOCKSIZE; i++) { buffer2[ i ] = 0; } lookahead = 1; actualbuff = buffer1; lookaheadbuff = buffer2; lastlen = 0; while ((len = fread(lookaheadbuff, 4, BLOCKSIZE / 4, stdin))) { if (lookahead) { voladj_check( &state, (short *)lookaheadbuff ); } else { actualbuff = lookaheadbuff; voladj_check( &state, (short *)actualbuff ); state.output_multiplier = state.desired_multiplier; lastlen = len; } voladj_scale( &state, state.desired_multiplier, (short *)actualbuff ); fwrite( actualbuff, 4, lastlen, stdout ); tempbuf = actualbuff; actualbuff = lookaheadbuff; lookaheadbuff = tempbuf; lastlen = len; } if ( lookahead) { voladj_scale( &state, state.desired_multiplier, (short *)actualbuff ); fwrite( actualbuff, 4, lastlen, stdout ); } return 0; } #endif