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.
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;
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);
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.