2
votes

Bear with me as Im very new with working with audio and I have been googling for days for a solution and not finding any.

So i retrieve the byte array of a .wav file with this (source: Wav file convert to byte array in java)

ByteArrayOutputStream out = new ByteArrayOutputStream();
BufferedInputStream in = new BufferedInputStream(new FileInputStream(WAV_FILE));

int read;
byte[] buff = new byte[1024];
while ((read = in.read(buff)) > 0)
{
     out.write(buff, 0, read);
}
out.flush();
byte[] audioBytes = out.toByteArray();

And then i convert the byte array to a float array and normalize it from -1.0 to 1.0. (source: Convert wav audio format byte array to floating point)

ShortBuffer sbuf =
ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
short[] audioShorts = new short[sbuf.capacity()];
sbuf.get(audioShorts);

float[] audioFloats = new float[audioShorts.length];
for (int i = 0; i < audioShorts.length; i++) {
    audioFloats[i] = ((float)audioShorts[i])/0x8000;
}
return audioFloats;

Later i convert this to line drawings which outputs the waveform using java.swing

class Panel2 extends JPanel {
float[] audioFloats;

    Dimension d;
    public Panel2(Dimension d, float[] audioFloats) {
        // set a preferred size for the custom panel.
        this.d = d;
        setPreferredSize(d);
        this.audioFloats = audioFloats;
    }


    @Override
    public void paint(Graphics g) {
        //super.paintComponent(g);
        super.paint(g); 

        //shift by 45 because first 44 bytes used for header
        for (int i = 45; i<audioFloats.length; i++){

            Graphics2D g2 = (Graphics2D) g;
            float inc = (i-45)*((float)d.width)/((float)(audioFloats.length-45-1));
            Line2D lin = new Line2D.Float(inc, d.height/2, inc, (audioFloats[i]*d.height+d.height/2));
            g2.draw(lin);

        }


    }
}

The waveform only looks right for 16 bit wav files (ive cross checked with goldwave and both my waveform and their waveform look similar for 16 bits).

How do i do this for 8 bit .wav files?

Because this is for homework, my only restriction is read the wav file byte by byte.

I also know the wav files are PCM coded and have the first 44 bytes reserved as the header

2

2 Answers

0
votes

You need to adapt this part of the code:

ShortBuffer sbuf =
  ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
short[] audioShorts = new short[sbuf.capacity()];
sbuf.get(audioShorts);

float[] audioFloats = new float[audioShorts.length];
for (int i = 0; i < audioShorts.length; i++) {
    audioFloats[i] = ((float)audioShorts[i])/0x8000;
}

You don't need ByteBuffer at all—you already have your byte array. So just convert it to floats:

float[] audioFloats = new float[audioBytes.length];
for (int i = 0; i < audioBytes.length; i++) {
    audioFloats[i] = ((float)audioBytes[i])/0x80;
}
0
votes

Audio streams are usually interleaved with one channel of data then the opposite channel of data. So for example the first 16 bits would be the left channel, then the next 16 bits would be the right channel. Each of these is considered 1 frame of data. I would make sure that your 8 bit stream is only one channel because it looks like the methods are only set up to read one channel.

Also in your example to convert the frames you are grabbing the individual channel as a short then finding a decimal by dividing that by 0x8000 hex or the maximum value of a signed short.

short[] audioShorts = new short[sbuf.capacity()];
sbuf.get(audioShorts);
...
audioFloats[i] = ((float)audioShorts[i])/0x8000;

My guess is that you need to read the 8 byte stream as a type 'byte' instead of a short then divide that by 128 or the maximum value of a signed 8 bit value. This will involve making a whole new method that processes 8 bit streams instead of 16 bit streams. With the following changes.

byte[] audioBytes = new byte[sbuf.capacity()];
sbuf.get(audioBytes);
...
audioFloats[i] = ((float)audioBytes[i])/0x80;