Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java - Convert 16-bit signed pcm audio data array to double array

I'm working on a project involved audio processing.

I'm taking a piece of audio from a file, and then would like to do some processing on it. The issue is that I get the audio data as byte array, while my processing is on double array (and later on Complex array as well...).

My question is that how can I correctly convert the byte array I receive to double array to go on?

Here's my input code:

AudioFormat format = new AudioFormat(8000, 16, 1, true, true);
AudioInputStream in = AudioSystem.getAudioInputStream(WAVfile);
AudioInputStream din = null;
AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 
                        8000,
                        16,
                        1,
                        2,
                        8000,
                        true);
din = AudioSystem.getAudioInputStream(decodedFormat, in);
TargetDataLine fileLine = AudioSystem.getTargetDataLine(decodedFormat);
fileLine .open(format);
fileLine .start();

int numBytesRead;
byte[] targetData = new byte[256]; // (samplingRate / 1000) * 32ms

while (true) {
    numBytesRead = din.read(targetData, 0, targetData.length);

    if (numBytesRead == -1) {
        break;
    }

    double[] convertedData;
    // Conversion code goes here...

    processAudio(convertedData);
}

So far I've looked into different answers to different question around this site and others. I've tried to use ByteBuffer and bit conversion, but both of them didn't give me results that seems right (another member in my them has done the same thing on the same file in Python so I have a reference what the results should approximately be...

What am I missing? How can I correctly convert the bytes to doubles? If I want to capture in targetData only 32ms of the file, what should be the length of targerData? What then will be the length of convertedData?

Thanks in advance.

like image 379
DanielY Avatar asked Jun 07 '16 07:06

DanielY


1 Answers

The conversion using NIO buffers shouldn’t be so hard. All you have to do, is to apply a factor to normalize from a 16 Bit range, to a [-1.0…1.0] range.

Well, it isn’t so easy, but for most practical purposes, deciding for one factor is sufficient:

AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 
                                            8000, 16, 1, 2, 8000, true);
try(AudioInputStream in  = AudioSystem.getAudioInputStream(WAVfile);
    AudioInputStream din = AudioSystem.getAudioInputStream(decodedFormat, in);
    ReadableByteChannel inCh = Channels.newChannel(din)) {

    ByteBuffer inBuf=ByteBuffer.allocate(256);
    final double factor=2.0/(1<<16);
    while(inCh.read(inBuf) != -1) {
        inBuf.flip();
        double[] convertedData=new double[inBuf.remaining()/2];
        DoubleBuffer outBuf=DoubleBuffer.wrap(convertedData);
        while(inBuf.remaining()>=2) {
            outBuf.put(inBuf.getShort()*factor);
        }
        assert !outBuf.hasRemaining();
        inBuf.compact();
        processAudio(convertedData);
    }
}

The solution above effectively uses the …/(double)0x8000 variant. Since I don’t know what processAudio does with the supplied buffer, e.g. whether it keeps a reference to it, the loop allocates a new buffer in each iteration, but it should be easy to change it to a reusable buffer. You only have to take care about the actual number of read/converted doubles, when using a pre-allocated buffer.

like image 77
Holger Avatar answered Oct 16 '22 19:10

Holger