Ercksen
Ercksen

Reputation: 669

Change an audio stream's volume by changing bytes directly

For a peer-to-peer audio client, I need to have the ability to change the output volume to a desired level. In my case, the volume is a floating point number between zero and one.

I modify the audio stream this way:

void play(byte[] buf)
{
    for (int i = 0; i < buf.length; i += 2)
    {
        // sample size is 2 bytes, so convert to int and then back
        int data = ((buf[i + 1] & 0xFF) << 8) | (buf[i] & 0xFF);
        data = (int) (data * outputVolume);
        buf[i] = (byte) (data & 0xFF);
        buf[i + 1] = (byte) (data >> 8);
    }
    line.write(buf, 0, Math.min(buf.length, line.available()));
}

Now, when outputVolume is set to 0, the output is silent. When it is set to 1, it behaves normal and quality is fine (as it is not modified). But any numbers between 0 and 1 produce a horrible noise which is louder than the expected stream itself. At 0.5, the noise reaches it's loudest point.

I don't want to use the controls of the audio mixer itself (like gain control or volume control) because I had compatibility problems this way and later on, I want to modify the byte stream even more so I have to iterate through the stream anyway.

Upvotes: 0

Views: 443

Answers (1)

Radiodef
Radiodef

Reputation: 37835

Assuming the audio data is signed (because I think it would be pretty unusual to have unsigned 16-bit samples), there is a mistake in that code, because you also need to sign extend the sample.

You can remove the & 0xFF from the high byte which will let sign extension happen automatically:

int data = (buf[i + 1] << 8) | (buf[i] & 0xFF);

If for some reason you couldn't modify the and-shift-or expression, you could do sign extension like this:

int data = ((buf[i + 1] & 0xFF) << 8) | (buf[i] & 0xFF);
data = (data << 16) >> 16;

The result of the shifting expression is equivalent to this:

if (data > 32767)
    data -= 65536;

And this:

if ((i & 0x80_00) != 0)
    i |= 0xFF_FF_00_00;

(Those would also work.)

However, in your case you can just remove the & 0xFF from the high byte.


For a quick explanation, if you had some 16-bit sample like this (which is -1):

11111111_11111111

If you just convert to 32-bit without sign extending, you would get:

00000000_00000000_11111111_11111111

But that's 65536, not -1. Sign extension fills the upper bits with 1s if the MSB in the 16-bit value was set:

11111111_11111111_11111111_11111111

Upvotes: 2

Related Questions