Android中实现多段wav⾳频⽂件拼接
WAV为微软公司开发的⼀种声⾳⽂件格式,它符合RIFF(Resource Interchange File Format)⽂件规范,⽤于保存Windows平台的⾳频信息资源,被Windows平台及其应⽤程序所⼴泛⽀持。
由于项⽬中需要接⼊讯飞的语⾳听写进⾏快速录⼊,并且同时保存语⾳⽂件。讯飞语⾳听写的SDK只⽀持保存语⾳⽂件为pcm或者wav这两种格式。讯飞的语⾳听写服务有很多限制,⽐如前后端点允许静⾳最长10秒、⼀次听写连续不能超过60秒。项⽬中需要⽀持长时间不间断语⾳听写,和产品怼了很久,经过不懈的抗争,最后还是我妥协了。讯飞语⾳听写的SDK提供了⼀些回调,在超时中断时,会回调onEndOfSpeech⽅法,这样我们就可以在这⾥马上重新开始启动听写。但是这会引起另⼀个问题,录制的⾳频⽂件最后是⼀段⼀段的,最后还得把他们进⾏拼接。第⼀次使⽤讯飞的语⾳听写SDK,不是很熟,不知道有没有哪位⼤神有更好的解决办法,求赐教啊啊啊啊。。。寻找了很久,在Android的API中没找到可以实现wav拼接的⽅法,只能⾃⼰去实现了。万幸的是wav格式的结构还⽐较简单。
WAV⽂件格式
onmonday本来是使⽤table编辑的表格,简书上竟然不⽀持,没办法只能截了个图放上来了)
可以看出,WAV⽂件主要是以四种chunk组成,这⾥我们分别称呼为riff chunk、fmt chunk、fact chunk和data chunk,其中fact chunk不是必须的,⼤部分时候的没有。所以我在查阅资料的额时候,发现很多解析WAV⽂件的代码都直接认为其只有固定的44字节的头部。
此格式来源于百度百科,奇怪的是维基百科中也认为WAV具有⼀个44字节的固定头部,如果哪位⼤神知道的,可以告诉我⼀下。
朝露WAV拼接实现⽅法
由于这⾥采集的⾳频相关参数⼀致,做我们去其中⼀段的头部作为拼接后的⾳频的头部。但是也不是这样就可以了。从上⾯WAV的格式中可以看出,头部中两个位置的数据需要修改。1、riff chunk中的size值;2、data chunk的size值。因此可以先将其他数据的data chunk 部分的数据追加到结果⽂件中,最后写⼊这两个地⽅的值。
好了,是时候上代码了。。。
实现代码
public class WavMergeUtil {
public static void mergeWav(List<File> inputs, File output) throws IOException {
if (inputs.size() < 1) {
return;
}
FileInputStream fis = new (0));
FileOutputStream fos = new FileOutputStream(output);
言简意赅byte[] buffer = new byte[2048];
int total = 0;
int count;
while ((count = ad(buffer)) > -1) {
fos.write(buffer, 0, count);
total += count;
}
fis.clo();
for (int i = 1; i < inputs.size(); i++) {
File file = (i);
Header header = resolveHeader(file);
FileInputStream dataInputStream = header.dataInputStream;
while ((count = ad(buffer)) > -1) {
fos.write(buffer, 0, count);
total += count;
}
dataInputStream.clo();
}
fos.flush();
fos.clo();
Header outputHeader = resolveHeader(output);
outputHeader.dataInputStream.clo();
RandomAccessFile res = new RandomAccessFile(output, "rw");
res.ek(4);
byte[] fileLen = intToByteArray(total + outputHeader.dataOfft - 8);
res.write(fileLen, 0, 4);
res.ek(outputHeader.dataSizeOfft);
byte[] dataLen = intToByteArray(total);
res.write(dataLen, 0, 4);
res.clo();
}
/**
* 解析头部,并获得⽂件指针指向数据开始位置的InputStreram,记得使⽤后需要关闭
*/
private static Header resolveHeader(File wavFile) throws IOException {
FileInputStream fis = new FileInputStream(wavFile);
byte[] byte4 = new byte[4];
byte[] buffer = new byte[2048];
int readCount = 0;
Header header = new Header();
readCount += 8;
header.fileSizeOfft = 4;
header.fileSize = byteArrayToInt(byte4);
readCount += 12;
int fmtLen = byteArrayToInt(byte4);
readCount += fmtLen;
ad(byte4);//data or fact
readCount += 4;
if (isFmt(byte4, 0)) {//包含fmt段
int factLen = byteArrayToInt(byte4);
readCount += 8 + factLen;
}
int dataLen = byteArrayToInt(byte4);
header.dataSize = dataLen;
header.dataSizeOfft = readCount;
readCount += 4;
header.dataOfft = readCount;
header.dataInputStream = fis;
return header;
}
private static boolean isRiff(byte[] bytes, int start) {
if (bytes[start + 0] == 'R' && bytes[start + 1] == 'I' && bytes[start + 2] == 'F' && bytes[start + 3] == 'F') { return true;
} el {
return fal;
}
}
private static boolean isFmt(byte[] bytes, int start) {
if (bytes[start + 0] == 'f' && bytes[start + 1] == 'm' && bytes[start + 2] == 't' && bytes[start + 3] == ' ') { return true;
} el {
return fal;
}
}
private static boolean isData(byte[] bytes, int start) {
if (bytes[start + 0] == 'd' && bytes[start + 1] == 'a' && bytes[start + 2] == 't' && bytes[start + 3] == 'a') { return true;rearm
} el {
return fal;
}
}
/**
* 将int转化为byte[]
6级改革*/
private static byte[] intToByteArray(int data) {
return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(data).array();
}
/**
index是什么意思* 将short转化为byte[]
*/
have怎么读private static byte[] shortToByteArray(short data) {
return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(data).array();
}
/**
* 将byte[]转化为short
*/
private static short byteArrayToShort(byte[] b) {
private static short byteArrayToShort(byte[] b) {
return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getShort(); }
/**
* 将byte[]转化为int
*/
英语等级考试网private static int byteArrayToInt(byte[] b) {
return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getInt(); }
/**
* 头部部分信息
*/
static class Header {
public int fileSize;ripley
public int fileSizeOfft;
public int dataSize;
public int dataSizeOfft;
public int dataOfft;
public FileInputStream dataInputStream;
}
}
这⾥int、short相互转化的时候需要考虑⼤⼩端的问题。