Change an audio stream's volume by changing bytes directly - java

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.

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

Related

Android 8 bit to 16 bit representation for Negative Numbers

Refer to the question How to Convert two 8 bit represented byte to single 16 bit represented integer value in Android.
I got an answer like this
short yourinteger16 = (short)(((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF));
This answer is correct for the positive number. But in the case of the negative number, it's failing.
For example, I am sending the value from the BLE to the application as -10. The value will convert from the BLE as -10000 because of mAh/mV conversion of current and voltage. These values are split into two bytes and I am getting the byte value as -39 and -16 in my application. I am passing the byte to the method as like below.
short yourinteger16 = (short)(((-39 & 0xFF) << 8) | (-16 & 0xFF));
But I am getting the result as 9.77 as the float value of yourinteger16 .
Anyone has any idea about this?. Any solution please update me.
Full Code:
Integer ampValue = null;
if (mBleDataHashMap.containsKey(SuperMuttBleConst.RESP_I_HIGH) &&
mBleDataHashMap.containsKey(SuperMuttBleConst.RESP_I_LOW)) {
ampValue = get8ByteTo16Byte(mBleDataHashMap.get(SuperMuttBleConst.RESP_I_HIGH),
mBleDataHashMap.get(SuperMuttBleConst.RESP_I_LOW));
}
if (ampValue != null) {
float newAmp = ampValue.floatValue();
newAmp = newAmp/1000;
mAmpTextvw.setText("" + newAmp);
}
Method
protected Integer get8ByteTo16Byte(int firstValue, int secondValue) {
Short integerValue = (short)((((byte) firstValue & 0xFF) << 8) | ((byte) secondValue & 0xFF));
return new Integer(integerValue);
}
You're receiving -39 and -16 perfectly (the high and low bytes for -10000, respectively).
Use addition instead of ORing the high and low bytes.
Please try the following
short result = (short) (((short)(-39 & (byte)0xFF) << 8) + (short)(-16 & (byte)0xFF));
The negative low bytes is causing trouble for the high byte when dealing with 2's complement arithmetic.

Converting short-list to byte array, but only using last X bits

I have to compress a list of short-values into a byte array, but only the last X bits of the value.
Given this method:
byte[] compress(int bitsPerWord, List<Short> input){
...
}
The BitsPerWorld will never be bigger than the given values in the input field.
Example: 10 bits per word => maximum value 1023
I also may not waste bits, I have to save X bits in the first Y bytes, and then append the next X bits directly to them.
Example:
Input(Short) [ 500, 150, 100 ]
Input(Binary):0000000111110100 0000000001101000 0000000001100100
Output (10 bits per short): 0111110100 0001101000 0001100100
Output (As byte array):0111 1101 0000 0110 1000 0001 1001 0000
What the result should look like
Any way to do this efficiently? BitSet seems not fitting for this task, because i would have to set every single bit explicit.
Thanks
Efficient in what way?
In terms of work required, extending BitSet adding a bulk put method and an index is super efficient; little work and thinking required.
The alternative, shifting and masking bits is moderately complicated in terms of programming effort if you know your ways with bitwise operations. It may be a major obstacle if you don't.
Considering you already use wrapper types and collections, indicating troughput is not your major concern, extending BitSet is probably all you need.
You need to perform some bit manipulations, and for that to work you need to find a repeatable pattern. In this case, you have a list of "short" values, but actually you just use the rightmost 10 bits. Since you want to pack those into bytes, the minimum repeatable pattern is 40 bits long (5 bytes, 4 10-bit values). That is the "block size" for processing.
You would then have a loop that would do the main parsing of full blocks, plus maybe a special case at the end for the final incomplete block.
byte[] pack10(List<Short> source) {
final int nBlk = source.size() / 4;
final int remBits = (source.size() % 4) * 10;
final int remBytes = (remBits / 8) + (remBits % 8 > 0 ? 1 : 0);
byte[] ret = new byte[nBlk*5 + remBytes];
final short bitPat = (short)0b0000001111111111;
for (int iBlk = 0; iBlk < nBlk; ++iBlk) {
// Parse full blocks
List<Short> curS = source.subList(iBlk*4, (iBlk+1)*4);
ret[iBlk*5 ] = (byte) ((curS.get(0) & bitPat) >> 2);
ret[iBlk*5+1] = (byte) ((curS.get(0) & bitPat) << 6
| (curS.get(1) & bitPat) >> 4);
ret[iBlk*5+2] = (byte) ((curS.get(1) & bitPat) << 4
| (curS.get(2) & bitPat) >> 6);
ret[iBlk*5+3] = (byte) ((curS.get(2) & bitPat) << 2
| (curS.get(3) & bitPat) >> 8);
ret[iBlk*5+4] = (byte) (curS.get(3) & bitPat);
}
// Parse final values
List<Short> remS = source.subList(nBlocks*4, source.size());
if (remS.size() >= 1) {
ret[nBlk*5 ] = (byte) ((remS.get(0) & bitPat) >> 2);
ret[nBlk*5+1] = (byte) ((remS.get(0) & bitPat) << 6);
}
if (remS.size() >= 2) { // The first byte is appended to
ret[nBlk*5+1] |= (byte) ((remS.get(1) & bitPat) >> 4);
ret[nBlk*5+2] = (byte) ((remS.get(1) & bitPat) << 4);
}
if (remS.size() == 3) { // The first byte is appended to
ret[iBlk*5+2] |= (byte) ((curS.get(2) & bitPat) >> 6);
ret[iBlk*5+3] = (byte) ((curS.get(2) & bitPat) << 2);
}
return ret;
}
That is a specific version for 10-bit values; if you want a version with a generic number of values you'd have to generalise from that. The bit pattern operations changes, and all the system becomes less efficient if the pattern is computed at runtime (i.e. if the number of bits is a variable like in your example).
There are several people who have already written a BitOutputStream in Java. Pick one of them, wrap it in a ByteArrayOutputStream, and you’re done.

Bit operations converting to an integer

I have some binary operations that are not working like I expect.
I have byte array with the first 2 bytes having these values : 0x5, and 0xE0.
I want to combine them into an integer value that should be 0x5E0.
I tried doing :
int val = (b[i]) << 8 | b[i+1];
but the value is coming out 0xFFFFFFEE0 and the first byte 0x5 is getting lost
I thought this would be easy? What am I doing wrong?
Try: int val = ((b[i] & 0xff) << 8) | (b[i + 1] & 0xff). Bytes are (unfortunately) signed in Java, so if the high bit is set, it gets sign-extended when converted to an integer.
The problem is that byte data type is signed. Therefore, b[i+1] gets sign-extended before performing the operation, becoming 0xFFFFFFE0. When it gets OR-ed with 0x0500 from b[i]<<8, the 0x0500 gets lost.
You can fix this by AND-ing with 0xFF before performing the operation:
public static int toInt16(byte high, byte low) {
int res = (high << 8);
res |= (low & 0xFF);
return res & 0xFFFF;
}
Demo.

Audio file - Manipulate volume given byte frames - Java

I have a .au audio file that I am trying to copy to another audio file, and I want the copied audio file to have half the volume. I have written the following code and it produces the following audio file:
for (int i = 24; i < bytes.length; i++) {
// bytes is a byte[] array containing every byte in the .au file
if (i % 2 == 0) {
short byteFrame = (short) (((bytes[i - 0]&0xFF) << 8) | ((bytes[i - 1]&0xFF)));
byteFrame >>= 1;
bytes[i - 0] = (byte) (byteFrame);
bytes[i - 1] = (byte) (byteFrame >>> 8);
}
}
The data I get from that code is this:
The following code is the same as above, only 'bytes[i - 0]' and 'bytes[i - 1]' have switched places. When I do that, the information in the channels gets swapped to the other channel.
for (int i = 24; i < bytes.length; i++) {
// bytes is a byte[] array containing every byte in the .au file
if (i % 2 == 0) {
short byteFrame = (short) (((bytes[i - 0]&0xFF) << 8) | ((bytes[i - 1]&0xFF)));
byteFrame *= 0.5;
bytes[i - 1] = (byte) (byteFrame);
bytes[i - 0] = (byte) (byteFrame >>> 8);
}
}
The data I get from that code is this (Information in the channels has been swapped):
I need to reduce the volume in both channels by half. Below is the wikipedia page on the au file format. Any ideas on how to get it to work properly in reducing the volume? This file is encoding 1 (8-bit G.711 mu-law), 2 channels, 2 bytes per frame, and sample rate of 48000. (It works properly on Encoding 3 but not encoding 1.) Thanks in advance for any help offered.
http://en.wikipedia.org/wiki/Au_file_format
Use a ByteBuffer. It appears that you use 16 bit quantities in little endian order, and that you want to right shift them by 1.
Therefore:
final ByteBuffer orig = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
.asReadOnlyBuffer();
final ByteBuffer transformed = ByteBuffer.wrap(bytes.length)
.order(ByteOrder.LITTLE_ENDIAN);
while (orig.hasRemaining())
transformed.putShort(orig.getShort() >>> 1);
return transformed.array();
Note that the >>> is necessary; otherwise you carry the sign bit.
That is, trying to use >> 1 on:
1001 0111
will give:
1100 1011
ie, the sign bit (the most significant bit) is carried. This is why >>> exists in Java, which DOES NOT carry the sign bit, therefore using >>> 1 on the above will give:
0100 1011
As seems logical when doing bit shifting!

Divide byte into two smaller numbers

Like in topic. I am writing application where I must save as much RAM as possible. I want to split byte into two parts 4 bits each (numbers from 0 to 15) - how can I save and later read this values?
If what you want is store 2 numbers (range 0-15) in one byte, here's an example on how to save and restore them. Note that you have to make sure your original numbers are within the allowed range, otherwise this will not work.
// Store both numbers in one byte
byte firstNumber = 10;
byte secondNumber = 15;
final byte bothNumbers = (byte) ((firstNumber << 4) | secondNumber);
// Retreive the original numbers
firstNumber = (byte) ((bothNumbers >> 4) & (byte) 0x0F);
secondNumber = (byte) (bothNumbers & 0x0F);
Also worth noting that if you want to save as much memory as possible you shouldn't be using Java to start with. JVM already consumes memory by itself. Native languages are much more suited to this requirement.
You can get lower bits as
byte lower = b & 0xF;
higher bits as
byte higher = (b >> 4) & 0xF;
and back to one byte
byte b = (byte) (lower + (higher << 4));

Categories