在使用讯飞的语音合成(文字转语音)功能时,发现讯飞提供的SDK最终生成的音频文件是pcm格式,此文件格式前端浏览器无法直接播放,需要转为常用的音频格式才行。
最先想到的是mp3格式,但尝试一番后,发现pcm转wav格式更容易,甚至不需要第三方工具,jdk自身就能搞定。
原理
看上图,其实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文件也分享给大家,方便测试。