android audioRecord- apply gain with variation

2019-05-16 19:03发布

问题:

I want to apply gain to my recordings(PCM 16bit). For this I have the following code:

for (int i=0; i<buffer.length/2; i++)
{ // 16bit sample size                      
  short curSample = getShort(buffer[i*2], buffer[i*2+1]);
  if(rGain != 1){
  //apply gain
  curSample *= rGain;
  //convert back from short sample that was "gained" to byte data
  byte[] a = getByteFromShort(curSample);
  buffer[i*2] = a[0];
  buffer[i*2 + 1] = a[1];
}

If applied like this(multiplying each sample with the fraction number), I get discontinues when playback(hearing like an old walkie-talkie). Is there some formula to vary my gain factor on each sample? I assume there is some maxValue and minValue for the range of samples (I guess [-32768, +32767]) and using these values in some formula I can get a variated gain factor to apply to the current sample.

//EDIT: added

if (curSample>32767) {curSample=32767;}
if (curSample<-32768) {curSample=-32768;}

full method

aRecorder.read(buffer, 0, buffer.length);
for (int i=0; i<buffer.length/2; i++)
                    { // 16bit sample size                      
                        short curSample = getShort(buffer[i*2], buffer[i*2+1]);
                        if(rGain != 1){
                            //apply gain
                            curSample *= rGain;
                            if (curSample>32767) {curSample=32767;}
                            if (curSample<-32768) {curSample=-32768;}
                            //convert back from short sample that was "gained" to byte data
                            byte[] a = getByteFromShort(curSample);
                            buffer[i*2] = a[0];
                            buffer[i*2 + 1] = a[1];
                        }

But still hears odd(noise + discontinues like an old walkie-talkie).

Any help would be appreciated,

Thanks.

回答1:

You have another bug in your source. The following line creates sample values from -32768..32767 which is the full range of s short variable:

short curSample = getShort(buffer[i*2], buffer[i*2+1]);

As you now apply a gain factor grater than 1 you "overflow" the short format:

curSample *= rGain;

This produces nasty cracks in the smooth signal, as e.g. 32767 * 1.5 is not 49150 as expected, but due to the "overflow" is interpreted as -16386 because you assign the result to a short variable again.

Thus the two lines

if (curSample>32767) {curSample=32767;}
if (curSample<-32768) {curSample=-32768;}

wouldn't change anything as curSample is never greater than 32767 or smaller than -32768.

To avoid this you have to use a temporary int variable:

short curSample = getShort(buffer[i*2], buffer[i*2+1]);
int temp = curSample * rGain;
if (temp>=32767)
    curSample=32767;
else if (temp<=-32768)
    curSample=-32768;
else
    curSample=(short)temp;


回答2:

Here is the final results...The algorithm is intersected with a VU-metering measuring... Disregard that part...

final int numFrames = getNumOfFrames(source.length);
62                          final int bytesPerSample = bitsPerSamples / 8;
63                          final int emptySpace=64-bitsPerSamples;
64                          int byteIndex=0;
65                          int byteIndex2 = 0;
66                  
67                  
68                          int temp = 0;
69                          int mLeftTemp = 0;
70                          int mRightTemp = 0;
71                          int a=0;
72                          int x = 0;
73                          
74                          for(int frameIndex=0; frameIndex<numFrames; frameIndex++){
75                                  for(int c=0; c<nChannels; c++){
76                                          if(rGain != 1){
77                                                  // gain
78                                                  long accumulator=0;
79                                                  for(int b=0; b<bytesPerSample; b++){
80                                                          accumulator+=((long)(source[byteIndex++]&0xFF))<<(b*8+emptySpace);
81                                                  }
82                                                  double sample = ((double)accumulator/(double)Long.MAX_VALUE);
83                                                  sample *= rGain;                                
84                                          
85                                                  int intValue = (int)((double)sample*(double)Integer.MAX_VALUE);                         
86                                                  for(int i=0; i<bytesPerSample; i++){
87                                                          source[i+byteIndex2]=(byte)(intValue >>> ((i+2)*8) & 0xff);
88                                                  }
89                                                  byteIndex2 += bytesPerSample;   
90                                          }
91                                          
92                                          //average
93                                          if(bytesPerSample == 2){
94                                                  x = frameIndex*nChannels*bytesPerSample+(c*bytesPerSample);
95                                                  a = Math.abs((short)(((data[x+1] & 0xFF) << 8) | (data[x] & 0xFF)));
96                                          }else{
97                                                  a = Math.abs(data[frameIndex*nChannels +c]);
98                                          }
99                                          
100                                         temp += a;
101                                         mLeftTemp += (c==0)? a : 0;
102                                         mRightTemp += (c==1)? a : 0;
103                                         }//end for(channel)
104                         }//end for(frameIndex)
105                         
106                         mAverage = temp / (data.length / bytesPerSample);
107 //                      System.out.println("result 1 is: "+mAverage);
108 //                      System.out.println("result 2 is: "+calculateAverageValue());
109                         
110                         mLeftChannelAverage = mLeftTemp / (data.length/bytesPerSample/nChannels);
111                         mRightChannelAverage = mRightTemp / (data.length/bytesPerSample/nChannels);
112                         Amplitude ampl = new Amplitude(mAverage, mLeftChannelAverage, mRightChannelAverage);
113                         AmplitudePollAPI.getInstance().onAmplitudeReached(ampl);


回答3:

When changing gain you need to do this smoothly over a period of typically around 10 ms, otherwise you will get audible discontinuities (i.e. clicks). The simplest transition is linear, e.g. ramp from old gain to new gain linearly over 10 ms, but for high quality audio you should use something like a raised cosine transition:

gain(t) = gain_old + (gain_new - gain_old) * 0.5 * (1 - cos(π * (t - t0) / (t1 - t0)))

where t0, t1 are the begin, end times for the transition.