Skip to main content

Notice

Please note that most of the software linked on this forum is likely to be safe to use. If you are unsure, feel free to ask in the relevant topics, or send a private message to an administrator or moderator. To help curb the problems of false positives, or in the event that you do find actual malware, you can contribute through the article linked here.
Topic: 16 bit LPCM audio gain adjustment (Read 12093 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

16 bit LPCM audio gain adjustment

If I have a 16 bit LPCM (WAV) sample, how would I go about adjusting the volume?  I had assumed that it was a signed 16 bit value and that I could just multiply by 0.5 to lower the volume.  This didn't work for me -- the result was full of static and pops.

Before I get too far, I'm decoding this audio on a micro controller without a FPU.  I tried it with fixed integer multiplication, but to rule out an error on my part, I used the software floating point support.  The result was the same.  Am I missing something with PCM?  This feels like it should be quite simple!  :-)


16 bit LPCM audio gain adjustment

Reply #1
I'm sorry if this is obvious, but did you try simply dividing by 2?

16 bit LPCM audio gain adjustment

Reply #2
I'm sorry if this is obvious, but did you try simply dividing by 2?


Multiply by 0.5, divide by 2.. It all compiles to the same thing.  :-)

16 bit LPCM audio gain adjustment

Reply #3
How about right shift one bit, either signed or unsigned? IIRC 8-bit PCM is unsigned while 16-bit is signed.

16 bit LPCM audio gain adjustment

Reply #4
How about right shift one bit, either signed or unsigned? IIRC 8-bit PCM is unsigned while 16-bit is signed.


This works.  Thanks.  My buffer was a buffer of unsigned values, so I had to cast the value to a signed 16 bit value before I did the shift.


16 bit LPCM audio gain adjustment

Reply #5
Multiply by 0.5, divide by 2.. It all compiles to the same thing.  :-)
Not necessarily. Integer divide by 2 will prevent any of the floating-point nonsense that you were worried about.


16 bit LPCM audio gain adjustment

Reply #7
If I have a 16 bit LPCM (WAV) sample, how would I go about adjusting the volume?  I had assumed that it was a signed 16 bit value and that I could just multiply by 0.5 to lower the volume.  This didn't work for me -- the result was full of static and pops.

Before I get too far, I'm decoding this audio on a micro controller without a FPU.  I tried it with fixed integer multiplication, but to rule out an error on my part, I used the software floating point support.  The result was the same.  Am I missing something with PCM?  This feels like it should be quite simple!  :-)


Adjusting the volume of a digital data stream is non-trivial because there is a hidden requantization (reduction in precision) of the input data. 

Whenever digital data is requantized, errors that are a function of the input data are created. These errors are called "Quantization Errors", "Quantization Distortion" or (somewhat confusingly) "Quantization Noise".

You can see this more intuitively if you consider attenting by 2 or 256.  If you attenuate by 2 or 6 dB., its the same basic thing as stirpping off the lowest bit. If you attenuate by a factor of 256, you are converting 16 bits to 8 bits by means of truncation.

Quantization errors form a signal that is a nonlinear function of the input signal. IOW, our naive implementation of a digital volume control inherently adds nonlinear distortion. 

Quantization distortion is generally aharmonic. So it strongly tends to  *not*  be masked by the ear or the harmonic structure of the music. Therefore  is it pretty ugly.

The static and pops that you heard are probably due to quantization distortion. One common audible kind of artifact  you didn't mention are the bleeps, birdies, and whoops. Just a matter of the right kind of music at the right level! ;-)

Quantization distortion becomes a more signficant addition to the attenuated output signal as the attenuation increases, because the distortion's amplitude remains about the same ( about 1 LSB), but the signal is getting weaker and weaker. At really high attenuations, the amplitude of the quantization distortion approaches the amplitude of the output signal. We're talking really high percentages of a basically ugly form of distortion.

The usual solution is to randomize the quantization distortion so it is heard as just a slight increase in the background noise, or not noticed at all. Some kind of dither noise or other form of randomization of the quantization error needs to be added to the attenuation process.

A possible procedure for attenuation would be to put the input signal word into a register, multiply it by the attenuation constant, add a randomizing sample of a dither noise signal, and then store out the results, truncating off the least signfiicant bits.

Re: 16 bit LPCM audio gain adjustment

Reply #8
I have written an audio gain program using PortAudio to boost microphone levels and it works wonderfully with signed 16-bit samples. The background noise in the room is louder because EVERYTHING is louder, including the desired signal.

However, when I go to 24-bit samples, everything goes to pieces and the audio is awful. It sounds like a rattlesnake is in the room. Now I know why. Converting the 24-bit samples to float doesn't help.

Re: 16 bit LPCM audio gain adjustment

Reply #9
Post your code and explain the format (packed etc) expected of the 24 bit samples.

Re: 16 bit LPCM audio gain adjustment

Reply #10
Here is the code. It is written in PureBasic.

It works fine if the gain is unity (1). Any other gain value results in breakup.

The 16-bit version works well when any value of gain is applied. Not so with 24 bits (packed).

It only gives me trouble with 24-bit samples and when the gain is other than unity, say, 2 (6 dB). Otherwise it works fine.

Does the problem have something to do with quantization error?

Code: [Select]
recordedBytes = frameCount * bytesPerFrame
For ct = 0 To sampleRate/10 Step 3

CopyMemory(*recordBuffer + ct, @samp24,3) ;COPY 3 BYTES FROM BUFFER INTO VARIABLE

temp.f = samp24 ;CONVERT 24-BIT SAMPLE TO FLOAT

temp.f = temp.f * 1 ;APPLY GAIN

samp24 = temp.f ;CONVERT FLOAT TO 24-BIT INTEGER

CopyMemory(@samp24,*recordBuffer + ct,3) ;COPY 3 BYTES BACK TO BUFFER

Next

Pa_WriteStream (*outStream, *recordBuffer,recordedBytes / bytesPerFrame) ;WRITE TO PORTAUDIO


Re: 16 bit LPCM audio gain adjustment

Reply #11
I don't know that language ( used visual basic long ago ), but several things come to mind:

First: there are several ways to store and/or transmit a 24bit value.  First one is the one you assume, which is 3 bytes. Another one is with 4 bytes with the LSB padded with zero (a 32bit value representation of the 24bit value maintaining the 0dbFS).  There is also the inverse of this, i.e. 4 bytes with the MSB padded with zero. (i.e. simply a 32bit number using only the 24bit range).

I have no idea what your recordBuffer has, so I cannot be sure if your assumption to read only three bytes is correct.
Also, I have no idea what the effect of the copyMemory has over samp24. Concretely, computers do not have a 24bit data format. It is either 16 or 32, so I don't know what is happening to the 8 bits that you are not copying/initializing.
And finally, I don't know which audio format did you tell PortAudio to use, which can be one of the three options named above, but usually it is NOT the first.

Re: 16 bit LPCM audio gain adjustment

Reply #12
The PortAudio documentation makes it clear that we are dealing with packed 24-bit samples. I think we can thus rule out the two 32-bit possibilities.

Re: 16 bit LPCM audio gain adjustment

Reply #13
Google says paint24 is a fixed point format with the same min/max as int32, so that cast to float divides it by 256. However for simple multiplication, that won't matter, and casting back should multiply it by 256. I don't understand that language, but I think the way you repack should also work on little endian machines.

Have you tried printing the output to a file and checking that the samples are correct?

Re: 16 bit LPCM audio gain adjustment

Reply #14
variables and types
Quote
By default, when a data type is not indicated, the data is an integer.   [...] If you don't assign an initial value to the variable, their value will be 0.
Quote
Integer   .i   4 bytes (using 32-bit compiler)   -2147483648 to +2147483647
Integer   .i   8 bytes (using 64-bit compiler)   -9223372036854775808 to +9223372036854775807

I still haven't found what does the "@" sign mean, but after thinking a lot about it, I might have understood your problem.

Your samp24 variable most probably holds a 32bit signed value.  What you are reading from the buffer is a 24bit signed value. For that 24bit signed value to be converted to a 32bit signed value, the sign bit needs to be shifted from the bit 23 to the bit 31.
So, with your code, whenever you alter the value, any negative value gets damaged and converted to a positive value. Probably the compiler is implemented in a way that altering the float value by 1 leaves it unaltered so the conversion back to samp24 is keeping the same bits.

Re: 16 bit LPCM audio gain adjustment

Reply #15
samp24 is a signed 32-bit integer. I should have been more clear about that. What you don't see in my snippet is that it is declared earlier as samp24.l which makes it 32-bits signed throughout. PureBasic lacks unsigned 32- and 64-bit variable types. The "@" symbol means "address of" and returns a pointer for CopyMemory to store samp24. The "*" makes the associated variable a pointer.

It all works fine until a gain value other than unity (1) is applied. It makes no difference if I multiply a float or an int by the integer gain multiplier. In the 16-bit version I can multiply the sample, a signed 16-bit word, by an integer gain value and it works great. The gain is increased as expected and there are no audible artifacts.

Re: 16 bit LPCM audio gain adjustment

Reply #16
I think you didn't understand what I mean.
You start with:
samp24  00000000 Hex

On the buffer, you have, on little endian (on big endian would be different, but the logic would fail too):
[3F0000][7F4516][77F4F4] [4F7080]

which represent the numbers:
00003F Hex = 63
16457F Hex = 1459583
F4F477 Hex = -658313
80704F Hex = -8359856

then, you read them into samp24, one each iteration:
0000003F Hex = 63
0016457F Hex = 1459583
00F4F477 Hex = 75172167
0080704F Hex = 8417359

There is even the possibility that now you multiply by 1.2, and then what you get back into samp24 for the third value is this:
05607188 Hex = 90206600   (of which, this will be written back to the buffer: 607188 Hex =6320520 )
Now, if the samp24 variable is not reset to zero each loop iteration, when you read the fourth value from the buffer, this is what you actually get:
0580704F Hex

In the opposite case, if you multiply by 0.9, you will not get the above problem, but since the data is saved in two's-complement, the multiplication to the (initially) negative value X would actually move it into the opposite direction ( FFFFFF = -1  800000 = -8388608 )

So it has nothing to do with what this topic was about. Said that, i hope you can fix it with this help.

Re: 16 bit LPCM audio gain adjustment

Reply #17
So you're saying to clear samp24 to zero each iteration?

Re: 16 bit LPCM audio gain adjustment

Reply #18
That was my first worry, but it's not the primary concern.

What I said is that when you are operating those numbers, they are being threated as unsigned, not as signed, so only the numbers that were positive from start will get correctly multiplied. The negative values will get multiplied incorrectly.

Re: 16 bit LPCM audio gain adjustment

Reply #19
I see what Jaz means.  You're not handling converting to int correctly.

If you just took samp24 and left shifted it 8 bits (so that the MSB of the 24 bit sample and the 32 bit output were the same), and then convert back by taking the 3 most significant bytes (instead of the 3 least), does it fix the problem?