4

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.

Alexandru Circus
  • 5,478
  • 7
  • 52
  • 89
  • Yes, rGain is let's say 2 but I do not want to multiply the sample 1789 with 2 and sample 4 with 2. Sample 1789 I think should be multiplied with a smaller value than 2. – Alexandru Circus May 14 '12 at 07:44
  • Still not understanding if you want a random (any) value, or some correlation between the multipliers – keyser May 14 '12 at 07:45
  • rGain is obtained from a user selection(seekBar), and I want it applied with a variation depending on the value of each sample. A higher value of sample I think should be multiplied with a smaller value than rGain(to avoid discontinues), and a small value of sample should be multiplied with rGain. See another post:http://stackoverflow.com/questions/10449963/android-how-to-add-gain-control-when-recording - "To prevent discontinuities in the output, make sure that you vary the multiplication factor smoothly from beginning to end." – Alexandru Circus May 14 '12 at 07:52
  • Then you don't want a random number. Also, sry, but I can't help. – keyser May 14 '12 at 07:54

3 Answers3

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);
Alexandru Circus
  • 5,478
  • 7
  • 52
  • 89
1

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.

Paul R
  • 208,748
  • 37
  • 389
  • 560
  • Thank you. One more question: gain_old and gain_new - how can I get these two? I have only 1 gain to apply(eg: 2), so how to get these 2 values? new_gain is 2(or what the user has selected) and old_gain is 1? – Alexandru Circus May 14 '12 at 08:06
  • Sorry - I was assuming that you were getting clicks when *changing* the gain, i.e. from some initial gain value, `gain_old` to some new gain value, `gain_new` - are you getting clicks even when not changing the gain ? – Paul R May 14 '12 at 08:32
  • I do not hear any clicks, but for example, I say "one, two, three, four, five" and when playback, each word is cut, and the playback duration is smaller than it should be. Also when gain is 1, quality is very good. But any other value causes problems, so I think multiplying each sample with a fixed value (!= than 1) is not a good practice. – Alexandru Circus May 14 '12 at 08:52
  • OK - the only other thing I can think of is that it's possible your samples are offset binary rather than two's complement, so when you multiply you are corrupting the values. – Paul R May 14 '12 at 08:53
  • 1
    Yes, my mistake. Was selected the option to record in PCM 8bit, rather than 16bit. Selecting 16 bit and applying gain works ok until now(no "clicks" hearing, just a small amount of noise). But, as you suggested, for high quality audio, I need to implement a raised cosine transition; I'll try to see how to deal with old_gain and new_gain. Thank you for your help. Really appreciate. – Alexandru Circus May 14 '12 at 09:01
  • @AlexandruCircus can you please share your results, we've been trying to do the same thing but it does not work for us right, we're getting the same walkie-talkie like noise. – Soham Apr 09 '14 at 09:58
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;
Hartmut Pfitzinger
  • 2,304
  • 3
  • 28
  • 48