钢蛋
发布于 2025-01-02 / 33 阅读
1
0

java实现音频文件格式转换——pcm转wav

在使用讯飞的语音合成(文字转语音)功能时,发现讯飞提供的SDK最终生成的音频文件是pcm格式,此文件格式前端浏览器无法直接播放,需要转为常用的音频格式才行。

最先想到的是mp3格式,但尝试一番后,发现pcm转wav格式更容易,甚至不需要第三方工具,jdk自身就能搞定。

原理

pcm-and-wav.png

看上图,其实wav格式就是为pcm格式文件添加个头部信息即可,头部后面的数据保持不变。就好像小明一开始是pcm,给小明带顶帽子,就成了wav。

实现

话不多说,直接上代码。

package cn.steggy.demos.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * pcm转为wav
 */
public class PcmToWavUtil {

    /**
     * 采样率
     */
    private static final Integer RATE = 16000; // 常用8000或16000,根据讯飞语音合成的实际参数进行调整

    /**
     * 声道
     */
    private static final Integer CHANNELS = 1; // 单声道,根据讯飞语音合成的实际参数进行调整

    /**
     * pcm转wav
     */
    public static byte[] pcmToWav(byte[] pcmBytes) {
        return addHeader(pcmBytes, buildHeader(pcmBytes.length));
    }

    /**
     * 添加头部
     */
    private static byte[] addHeader(byte[] pcmBytes, byte[] headerBytes) {
        byte[] result = new byte[44 + pcmBytes.length];
        System.arraycopy(headerBytes, 0, result, 0, 44);
        System.arraycopy(pcmBytes, 0, result, 44, pcmBytes.length);
        return result;
    }

    /**
     * 构建头部(共44字节)
     */
    private static byte[] buildHeader(Integer dataLength) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            writeChar(bos, new char[]{'R', 'I', 'F', 'F'});
            writeInt(bos, dataLength + (44 - 8));
            writeChar(bos, new char[]{'W', 'A', 'V', 'E'});
            writeChar(bos, new char[]{'f', 'm', 't', ' '});
            writeInt(bos, 16);
            writeShort(bos, 0x0001);
            writeShort(bos, CHANNELS);
            writeInt(bos, RATE);
            writeInt(bos, (short) (CHANNELS * 2) * RATE);
            writeShort(bos, (short) (CHANNELS * 2));
            writeShort(bos, 16);
            writeChar(bos, new char[]{'d', 'a', 't', 'a'});
            writeInt(bos, dataLength);
            return bos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 写入2字节short数据
     */
    private static void writeShort(ByteArrayOutputStream bos, int s) throws IOException {
        byte[] arr = new byte[2];
        arr[1] = (byte) ((s << 16) >> 24);
        arr[0] = (byte) ((s << 24) >> 24);
        bos.write(arr);
    }

    /**
     * 写入4字节int数据
     */
    private static void writeInt(ByteArrayOutputStream bos, int n) throws IOException {
        byte[] buf = new byte[4];
        buf[3] = (byte) (n >> 24);
        buf[2] = (byte) ((n << 8) >> 24);
        buf[1] = (byte) ((n << 16) >> 24);
        buf[0] = (byte) ((n << 24) >> 24);
        bos.write(buf);
    }

    /**
     * 写入1字节char数据
     */
    private static void writeChar(ByteArrayOutputStream bos, char[] id) {
        for (char c : id) {
            bos.write(c);
        }
    }

}

测试

写个方法测试一下,把test.pcm文件转为test.wav文件。

package cn.steggy.demos.util;

import cn.hutool.core.io.FileUtil;

public class Test {

    public static void main(String[] args) {
        byte[] pcm = FileUtil.readBytes("D:/test.pcm");
        byte[] wav = PcmToWavUtil.pcmToWav(pcm);
        FileUtil.writeBytes(wav, "D:/test.wav");
    }
    
}

这里把测试用的test.pcm文件也分享给大家,方便测试。

test.pcm


评论