--- mercury-kernel/drivers/char/audio_7212.c Sat May 27 19:15:15 2000 +++ mercury-kernel-ajn/drivers/char/audio_7212.c Sun Jan 20 22:16:15 2002 @@ -81,6 +81,429 @@ #define AUDIO_NOOF_BUFFERS (32) /* Number of audio buffers */ /* NOTE! Need to move this to a header file as audio_7212dma needs this too */ + +/* ajn */ + +#ifndef __KERNEL__ +#include +#include +#include +#include +#include +#include +#include +#endif + + +/* Volume adjustment + * By richard.lovejoy@ericsson.com.au + */ + + /* + * 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; + 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; + unsigned long flags; + + save_flags_cli(flags); + + 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 > (1 << SHORT_FRAC_POINT)) { + initial->headroom = 1 << SHORT_FRAC_POINT; + } else if (headroom < 0) { + initial->headroom = 0; + } else { + initial->headroom = headroom; + } + + initial->max_sample = 0; + restore_flags(flags); + + /* + 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 max_sample ) { + int max_frac,desired_out, desired_mult; + max_frac = (max_sample << MULT_POINT) / MAXSAMPLES; + if (max_frac == 0) max_frac = 1; + if (max_sample == 0) max_sample = 1; + /* + * This is the old linear way + desired_out = ((int)(state->minvol) * (int)MAXSAMPLES) + + (((int)max_frac) * ( (1 << SHORT_FRAC_POINT) - (int)(state->minvol) )) ; + */ + desired_out = MAXSAMPLES * + voladj_exp( (voladj_log( max_frac ) << MULT_POINT) / state->log_scale); + desired_mult = desired_out / max_sample ; + 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; +} + +/* + * 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; + + int num_samples = state->buf_size / 2; + int i, outputvalue; + + max_la_sample = 0; + for( i = 0; i < num_samples; i++ ) { + cur_sample = abs( lookaheadbuf[ i ] ); + 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; + } + + 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 (max_sample < state->real_silence) { + desired_multiplier = downmult; + if (desired_multiplier < (1 << MULT_POINT)) { + desired_multiplier = (1 << MULT_POINT); + } + } else if (max_sample < state->fake_silence) { + max_sample = state->fake_silence; + } + if (desired_multiplier == 0) { + desired_multiplier = voladj_get_multiplier( state, max_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; + } + } + } + + /* + printk("sampes prv: %x la: %x comb: %x desmult: %x\n", + state->max_sample, max_la_sample, max_sample, desired_multiplier); + printk("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; +} + +/* ajn */ + + #define AUDIO_BUFFER_SIZE (4608) /* Size of user buffer chunks */ /* Input channels */ @@ -134,6 +557,11 @@ /* Statistics */ audio_stats stats; + + /* ajn: added to port voladj */ + int prevhead; + struct voladj_state voladj; + } audio_dev; typedef struct @@ -412,6 +840,7 @@ { audio_dev *dev=file->private_data; int total=0; + int thisbufind=0; #if AUDIO_DEBUG_VERBOSE printk(AUDIO_NAME ": audio_write: count=%d, used=%d free=%d head=%d tail=%d\n", @@ -456,7 +885,9 @@ save_flags_clif(flags); dev->free--; restore_flags(flags); - + + thisbufind = dev->head; + /* Copy chunk of data from user-space. We're safe updating the head when not in cli() as this is the only place the head gets twiddled */ @@ -470,18 +901,43 @@ if(v < min) v = min; *bufout++ = v; } + if (dev->head==AUDIO_NOOF_BUFFERS) dev->head=0; #else - copy_from_user(dev->buffers[dev->head++].data,buffer,AUDIO_BUFFER_SIZE); -#endif + dev->head++; if (dev->head==AUDIO_NOOF_BUFFERS) dev->head=0; + copy_from_user(dev->buffers[thisbufind].data,buffer,AUDIO_BUFFER_SIZE); +#endif total+=AUDIO_BUFFER_SIZE; dev->stats.samples+=AUDIO_BUFFER_SIZE; count-=AUDIO_BUFFER_SIZE; + + dev->voladj.desired_multiplier = voladj_check( &(dev->voladj), + (short *) (dev->buffers[thisbufind].data) ); +#if AUDIO_DEBUG_VERBOSE + printk("mults: des=%x,out=%x\n", dev->voladj.desired_multiplier, dev->voladj.output_multiplier); +#endif + + + save_flags_cli(flags); + if (dev->used > 1) { + dev->used--; + restore_flags(flags); + voladj_scale( &(dev->voladj), + dev->voladj.desired_multiplier, + (short *) (dev->buffers[ dev->prevhead ].data) ); + save_flags_cli(flags); + dev->used++; + } else { + dev->voladj.output_multiplier = 1 << MULT_POINT; + } + + /* Now the buffer is ready, we can tell the FIQ section there's new data */ - save_flags_clif(flags); dev->used++; restore_flags(flags); + + dev->prevhead = thisbufind; } /* Update hwm */ @@ -883,6 +1339,17 @@ dev->zero = (audio_buf *) audio_bit_noise; #endif + /* Initialise volume adjustment */ + voladj_intinit( &(dev->voladj), + AUDIO_BUFFER_SIZE, /* buffersize */ + ((1 << MULT_POINT) * 2), /* factor_per_second */ + ((1 << MULT_POINT) / 10), /* minvol */ + (((1 << MULT_POINT) * 4) / 4), /* headroom */ + 30, /* real_silence */ + 80 /* fake_silence */ + ); + + /* Set up queue */ dev->head=dev->tail=dev->used=0; dev->free=AUDIO_NOOF_BUFFERS-1; @@ -891,6 +1358,9 @@ dev->fiqstack=(int*)((int)kmalloc(1024,GFP_KERNEL))+1024; /* Setup callback */ + + + dev->callback=(int)&newbuffer; /* Install DMA handler */