设计思路
根据文件的记录音频的开始时间,可以将多个音频文件混合成一个
限制
只针对单通道位深为16位的wav或pcm文件。其它位深可自行修改
实现代码 java
package cn.pt2000.smart.talk.common.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
public class WavUtils {
/**
* 混音多个文件
*
* @param upFilePath 设备端上行音频文件
* @param downFiles 设备端下行音频文件
* @throws Exception
*/
public static void mixWav(String upFilePath, File[] downFiles) throws Exception {
if (downFiles.length == 0) {
return;
}
if (downFiles.length == 1) {
mixWav(upFilePath, downFiles[0], false);
return;
}
orderFileByFileName(downFiles);
for (int i = 0; i < downFiles.length; i++) {
if (i == 0) {
mixWav(upFilePath, downFiles[0], false);
} else {
mixWav(upFilePath, downFiles[0], true);
}
}
}
/**
* 合并上下行音频
*
* @param upFilePath
* @param downFile
* @param 是否需要上行数据的前缀
* @throws Exception
*/
public static void mixWav(String upFilePath, File downFile, boolean skipHead) throws Exception {
File upFile = new File(upFilePath);
long upTime = getFileCreateTimeByFileName(upFile.getName());
long downTime = getFileCreateTimeByFileName(downFile.getName());
if (upTime > downTime) {
long cat = upTime - downTime;
long catSize = cat * 32;
mixWav(downFile, upFile, catSize, skipHead);
} else {
long cat = downTime - upTime;
long catSize = cat * 32;
mixWav(upFile, downFile, catSize, skipHead);
}
}
/**
* 合并音频文件
*
* @param headFile
* @param tailFile
* @param headerSize 领先的字节数
* @param skipHead
*/
public static void mixWav(File headFile, File tailFile, long headerSize, boolean skipHead) throws Exception {
long headLength = headFile.length();
long tailLength = tailFile.length();
if (headLength < headerSize) {
return;
}
// 定义输出文件
String outFilePath = tailFile.getAbsolutePath().replace("up", "mix").replace("down", "mix");
File outPutFile = new File(outFilePath);
outPutFile.createNewFile();
// 创建输出流
OutputStream os = new FileOutputStream(outPutFile);
os.write(new byte[44]); // 写入44字节,占位
// 创建输入流
InputStream headIs = new FileInputStream(headFile);
InputStream tailIs = new FileInputStream(tailFile);
headIs.read(new byte[44]);
tailIs.read(new byte[44]);
// 长度都减去44
headLength -= 44;
tailLength -= 44;
if (!skipHead) {
headIs.readNBytes((int) headerSize);
}
headLength = headLength - headerSize;
long maxLen = headLength > tailLength ? headLength : tailLength;
for (int i = 0; i < maxLen; i += 2) {
byte[] data1 = null;
if (i < headLength) {
data1 = headIs.readNBytes(2);
}
byte[] data2 = null;
if (i < tailLength) {
data2 = tailIs.readNBytes(2);
}
byte[] mixData = mixData(data1, data2);
os.write(mixData);
}
headIs.close();
tailIs.close();
os.close();
// 修正wav文件参数
reviseWavFile(outFilePath);
}
/**
* 修正wav文件的参数
*
* @param filePath
* @throws Exception
*/
public static void reviseWavFile(String filePath) throws Exception {
// 修改文件头,生成wav文件
RandomAccessFile file = new RandomAccessFile(filePath, "rw");
file.seek(0L);
file.writeBytes("RIFF");
file.write(LittleEndianUtils.writeUnsignedInt32(file.length()));
file.writeBytes("WAVE");
file.writeBytes("fmt ");
file.write(LittleEndianUtils.writeUnsignedInt32(16));
file.write(LittleEndianUtils.writeUnsignedInt16(1));
file.write(LittleEndianUtils.writeUnsignedInt16(1));
file.write(LittleEndianUtils.writeUnsignedInt32(16000));
file.write(LittleEndianUtils.writeUnsignedInt32(32000));
file.write(LittleEndianUtils.writeUnsignedInt16(2));
file.write(LittleEndianUtils.writeUnsignedInt16(16));
file.writeBytes("data");
file.write(LittleEndianUtils.writeUnsignedInt32(file.length() - 44));
file.close();
}
/**
* 根据文件名获取文件的创建时间
*
* @param fileName
* @return
*/
private static long getFileCreateTimeByFileName(String fileName) {
int index = fileName.indexOf("_");
String timeStr = fileName.substring(0, index);
return Long.parseLong(timeStr);
}
/**
* 将两个wav音频合并
*
* @param src1
* @param src2
* @param dest
* @throws Exception
*/
public static void mixWav(String src1, String src2, String dest) throws Exception {
File file1 = new File(src1);
File file2 = new File(src2);
long file1Len = file1.length() / 2 - 22;
long file2Len = file2.length() / 2 - 22;
long maxLen = file1Len > file2Len ? file1Len : file2Len;
InputStream is1 = new FileInputStream(src1);
InputStream is2 = new FileInputStream(src2);
byte[] head1 = is1.readNBytes(44);
byte[] head2 = is2.readNBytes(44);
FileOutputStream os = new FileOutputStream(dest);
if (file1Len > file2Len) {
os.write(head1);
} else {
os.write(head2);
}
for (int i = 0; i < maxLen; i++) {
byte[] data1 = null;
if (i < file1Len) {
data1 = is1.readNBytes(2);
}
byte[] data2 = null;
if (i < file2Len) {
data2 = is2.readNBytes(2);
}
byte[] mixData = mixData(data1, data2);
os.write(mixData);
}
is1.close();
is2.close();
os.close();
}
/**
* 混淆数据
*
* @param data
* @param data2
* @return
*/
public static byte[] mixData(byte[] d1, byte[] d2) {
if (null == d1) {
d1 = new byte[] { 0, 0 };
}
if (null == d2) {
d2 = new byte[] { 0, 0 };
}
int data1 = LittleEndianUtils.readInt16(d1);
int data2 = LittleEndianUtils.readInt16(d2);
int temp1 = Math.abs(data1);
int temp2 = Math.abs(data2);
int all = temp1 + temp2;
double result = data1 * (temp1 * 2.0 / all) + data2 * (temp2 * 1.0 / all);
short tempResult = (short) Math.ceil(result);
// 防止溢出
if (tempResult > 32760) {
tempResult = 32760;
}
if (tempResult < -32760) {
tempResult = -32760;
}
return LittleEndianUtils.writeInt16(tempResult);
}
/**
* 按照文件创建时间先后排序
*
* @param files
*/
private static void orderFileByFileName(File[] files) {
for (int i = 0; i < files.length - 1; i++) {// 外层循环控制排序趟数
for (int j = 0; j < files.length - 1 - i; j++) {// 内层循环控制每一趟排序多少次
long ct1 = getFileCreateTimeByFileName(files[j].getName());
long ct2 = getFileCreateTimeByFileName(files[j + 1].getName());
if (ct1 > ct2) {
File temp = files[j];
files[j] = files[j + 1];
files[j + 1] = temp;
}
}
}
}
}