4
votes

I have a pcm file, and I want to convert it to a wav file.

Is there any suitable api or code for this?

5

5 Answers

7
votes

it's My Code

/**
 * Write PCM data as WAV file
 * @param os  Stream to save file to
 * @param pcmdata  8 bit PCMData
 * @param srate  Sample rate - 8000, 16000, etc.
 * @param channel Number of channels - Mono = 1, Stereo = 2, etc..
 * @param format Number of bits per sample (16 here)
 * @throws IOException
 */
public void PCMtoFile(OutputStream os, short[] pcmdata, int srate, int channel, int format) throws IOException {
    byte[] header = new byte[44];
    byte[] data = get16BitPcm(pcmdata);

    long totalDataLen = data.length + 36;
    long bitrate = srate * channel * format;

    header[0] = 'R'; 
    header[1] = 'I';
    header[2] = 'F';
    header[3] = 'F';
    header[4] = (byte) (totalDataLen & 0xff);
    header[5] = (byte) ((totalDataLen >> 8) & 0xff);
    header[6] = (byte) ((totalDataLen >> 16) & 0xff);
    header[7] = (byte) ((totalDataLen >> 24) & 0xff);
    header[8] = 'W';
    header[9] = 'A';
    header[10] = 'V';
    header[11] = 'E';
    header[12] = 'f'; 
    header[13] = 'm';
    header[14] = 't';
    header[15] = ' ';
    header[16] = (byte) format; 
    header[17] = 0;
    header[18] = 0;
    header[19] = 0;
    header[20] = 1; 
    header[21] = 0;
    header[22] = (byte) channel; 
    header[23] = 0;
    header[24] = (byte) (srate & 0xff);
    header[25] = (byte) ((srate >> 8) & 0xff);
    header[26] = (byte) ((srate >> 16) & 0xff);
    header[27] = (byte) ((srate >> 24) & 0xff);
    header[28] = (byte) ((bitrate / 8) & 0xff);
    header[29] = (byte) (((bitrate / 8) >> 8) & 0xff);
    header[30] = (byte) (((bitrate / 8) >> 16) & 0xff);
    header[31] = (byte) (((bitrate / 8) >> 24) & 0xff);
    header[32] = (byte) ((channel * format) / 8); 
    header[33] = 0;
    header[34] = 16; 
    header[35] = 0;
    header[36] = 'd';
    header[37] = 'a';
    header[38] = 't';
    header[39] = 'a';
    header[40] = (byte) (data.length  & 0xff);
    header[41] = (byte) ((data.length >> 8) & 0xff);
    header[42] = (byte) ((data.length >> 16) & 0xff);
    header[43] = (byte) ((data.length >> 24) & 0xff);

    os.write(header, 0, 44);
    os.write(data);
    os.close();
}

EDIT: 2016-01-11

public byte[] get16BitPcm(short[] data) {
    byte[] resultData = new byte[2 * data.length];
    int iter = 0;
    for (double sample : data) {
        short maxSample = (short)((sample * Short.MAX_VALUE));
        resultData[iter++] = (byte)(maxSample & 0x00ff);
        resultData[iter++] = (byte)((maxSample & 0xff00) >>> 8);
    }
    return resultData;
}
4
votes

This should be quite simple to do because WAV = Metadata + PCM (in that order). This should work:

private void rawToWave(final File rawFile, final File waveFile) throws IOException {

byte[] rawData = new byte[(int) rawFile.length()];
DataInputStream input = null;
try {
    input = new DataInputStream(new FileInputStream(rawFile));
    input.read(rawData);
} finally {
    if (input != null) {
        input.close();
    }
}

DataOutputStream output = null;
try {
    output = new DataOutputStream(new FileOutputStream(waveFile));
    // WAVE header
    // see http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
    writeString(output, "RIFF"); // chunk id
    writeInt(output, 36 + rawData.length); // chunk size
    writeString(output, "WAVE"); // format
    writeString(output, "fmt "); // subchunk 1 id
    writeInt(output, 16); // subchunk 1 size
    writeShort(output, (short) 1); // audio format (1 = PCM)
    writeShort(output, (short) 1); // number of channels
    writeInt(output, 44100); // sample rate
    writeInt(output, RECORDER_SAMPLERATE * 2); // byte rate
    writeShort(output, (short) 2); // block align
    writeShort(output, (short) 16); // bits per sample
    writeString(output, "data"); // subchunk 2 id
    writeInt(output, rawData.length); // subchunk 2 size
    // Audio data (conversion big endian -> little endian)
    short[] shorts = new short[rawData.length / 2];
    ByteBuffer.wrap(rawData).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);
    ByteBuffer bytes = ByteBuffer.allocate(shorts.length * 2);
    for (short s : shorts) {
        bytes.putShort(s);
    }

    output.write(fullyReadFileToBytes(rawFile));
} finally {
    if (output != null) {
        output.close();
    }
}
}
byte[] fullyReadFileToBytes(File f) throws IOException {
int size = (int) f.length();
byte bytes[] = new byte[size];
byte tmpBuff[] = new byte[size];
FileInputStream fis= new FileInputStream(f);
try { 

    int read = fis.read(bytes, 0, size);
    if (read < size) {
        int remain = size - read;
        while (remain > 0) {
            read = fis.read(tmpBuff, 0, remain);
            System.arraycopy(tmpBuff, 0, bytes, size - remain, read);
            remain -= read;
        } 
    } 
}  catch (IOException e){
    throw e;
} finally { 
    fis.close();
} 

return bytes;
} 
private void writeInt(final DataOutputStream output, final int value) throws IOException {
output.write(value >> 0);
output.write(value >> 8);
output.write(value >> 16);
output.write(value >> 24);
}

private void writeShort(final DataOutputStream output, final short value) throws IOException {
output.write(value >> 0);
output.write(value >> 8);
}

private void writeString(final DataOutputStream output, final String value) throws IOException {
for (int i = 0; i < value.length(); i++) {
    output.write(value.charAt(i));
    }
}

How to use

It's quite simple to use. Just call it like this:

File f1 = new File("/sdcard/44100Sampling-16bit-mono-mic.pcm"); // The location of your PCM file
File f2 = new File("/sdcard/44100Sampling-16bit-mono-mic.wav"); // The location where you want your WAV file
try {
rawToWave(f1, f2);
} catch (IOException e) {
e.printStackTrace();
}

How all this works

As you can see, the WAV header is the only difference between WAV and PCM file formats. The assumption is that you are recording 16 bit PCM MONO audio (which according to your code, you are). The rawToWave function just neatly adds headers to the WAV file, so that music players know what to expect when your file is opened, and then after the headers, it just writes the PCM data from the last bit onwards.

Cool Tip

If you want to shift the pitch of your voice, or make a voice changer app, all you got to do is increase/decrease the value of writeInt(output, 44100); // sample rate in your code. Decreasing it will tell the player to play it at a different rate thereby changing the output pitch. Just a little extra 'good to know' thing. :)

2
votes

I cannot yet leave comments, but be aware that the get16BitPcm method in devflow's answer is [oddly] scaling the input data. If you already have 16 bit pcm data to write to the wav file, the method should look something like this:

public byte[] get16BitPcm(short[] data) {
    byte[] resultData = new byte[2 * data.length];
    int iter = 0;
    for (short sample : data) {
        resultData[iter++] = (byte)(sample & 0x00ff);
        resultData[iter++] = (byte)((sample & 0xff00) >>> 8);
    }
    return resultData;
}
0
votes

I know one called "OperateWav" that I used to develop a convertor(linux c/c++) in my first project during my first internship.I am not sure if this one exists per se and whether it supports java.Actually wav file is simply adding a wav format header on pcm raw data...

0
votes

This resource, WAVE PCM soundfile format, helped me to parse PCM data to WAVE. I've built a library based on it and it works fine for me.