本文是用Java写的,C#原理一样并已经验证
最近工作需要检测设备的是否有音频输出,找了很多资料,但是关于1Khz的验证并没有多少,所以我就自己查WAV的文件格式,并手撸了代码,来检测是否1Khz有声音,也可以检测WAV是否有声音
首先,我们需要将WAV文件读出来并将它的头文件分析,关于头文件的网上有很多文章,但是都不是很全面,头文件总会多一些或少一些无关紧要的信息,导致分析出错,下面是我读取头文件的代码(get 和 set方法自行补全)
public void readAudioFile(){
String filePath = "C:\\Users\\1002212\\Music\\baiduAudio.wav";
File file = new File(filePath);
if(!file.exists()){
System.out.println("文件不存在,请检查");
return;
}
FileInputStream fileInputStream;
try{
fileInputStream = new FileInputStream(file);
RiffFormat riffFormat = parseRiffFormat(fileInputStream);
System.out.println(riffFormat);
parse1KHZ(riffFormat);
}catch (Exception e){
e.printStackTrace();
}
}
下面是分析WAV文件的主要代码 ,因为有一些信息不一定有,所以要做一些判断,下面的代码中CDsize也是不一定有的,需要自己判断下(我比较懒,就不写了),关于分析的都是些什么,可以查看RiffFormat类
private RiffFormat parseRiffFormat(FileInputStream fileInputStream) throws Exception{
RiffFormat riffFormat = new RiffFormat();
riffFormat.setCkid(readByteBuffer(fileInputStream,4));
riffFormat.setFileSize(readInt(fileInputStream));
riffFormat.setFccType(readByteBuffer(fileInputStream,4));
byte[] buffer = readByteBuffer(fileInputStream, 4);
if(buffer[0]==0x66 && buffer[1]==0x6D && buffer[2]==0x74 && buffer[3]==0x20){
riffFormat.setWaveFormat(buffer);
} else {
riffFormat.setUnknowChar(buffer);
riffFormat.setUnknowChar2(readByteBuffer(fileInputStream, 32));
riffFormat.setWaveFormat(readByteBuffer(fileInputStream,4));
}
riffFormat.setCkSize(readInt(fileInputStream));
riffFormat.setwFormatTag(readShort(fileInputStream,true));
riffFormat.setnChannels(readShort(fileInputStream,true));
riffFormat.setnSamplesPerSec(readInt(fileInputStream));
riffFormat.setnAvgBytesPerSec(readInt(fileInputStream));
riffFormat.setnBlockAlign(readShort(fileInputStream,true));
riffFormat.setwBitsPerSample(readShort(fileInputStream,true));
buffer = readByteBuffer(fileInputStream, 2);
if(buffer[0] == 0x66 && buffer[1] == 0x61){
//如果是fact
byte[] bufferFactEnd = readByteBuffer(fileInputStream, 2);
byte[] bufferFact = new byte[4];
bufferFact[0] = buffer[0];
bufferFact[1] = buffer[1];
bufferFact[2] = bufferFactEnd[0];
bufferFact[3] = bufferFactEnd[1];
riffFormat.setwFact(bufferFact);
riffFormat.setnFactDataLength(readInt(fileInputStream));
riffFormat.setwFactData(readByteBuffer(fileInputStream, riffFormat.getnFactDataLength()));
buffer = readByteBuffer(fileInputStream,4);
} else if(buffer[0] == 0x64 && buffer[1] == 0x61){
// 如果是data
byte[] bufferDataEnd = readByteBuffer(fileInputStream, 2);
byte[] bufferData = new byte[4];
bufferData[0] = buffer[0];
bufferData[1] = buffer[1];
bufferData[2] = bufferDataEnd[0];
bufferData[3] = bufferDataEnd[1];
buffer = bufferData;
} else{
byte CbSize0 = buffer[0];
buffer[0] = buffer[1];
buffer[1] = CbSize0;
short CbSize = byteArrayToShort(buffer);
riffFormat.setCbSize(CbSize);
buffer = readByteBuffer(fileInputStream,4);
if(buffer[0] == 0x66 && buffer[1] == 0x61 && buffer[2] == 0x63 && buffer[3] == 0x74){
//如果是fact
riffFormat.setwFact(buffer);
riffFormat.setnFactDataLength(readInt(fileInputStream));
riffFormat.setwFactData(readByteBuffer(fileInputStream, riffFormat.getnFactDataLength()));
buffer = readByteBuffer(fileInputStream,4);
}
}
riffFormat.setWdid(buffer);
riffFormat.setWdSize(readInt(fileInputStream));
int wdSize = riffFormat.getWdSize();
riffFormat.setWdbuf(readByteBuffer(fileInputStream,wdSize));
return riffFormat;
}
下面是RiffFormat类,对应头文件的一些信息,分析出来的信息就对应下面的类
public class RiffFormat {
byte[] ckid; //RIFF标识
int fileSize; //文件大小
byte[] fccType; //WAVE标志
byte[] unknowChar = new byte[4];
byte[] unknowChar2 = new byte[32];
byte[] waveFormat; //WAVE格式标志
int CkSize; //块大小
short wFormatTag;//音频格式一般为WAVE_FORMAT_PCM
short nChannels;//采样声道数
int nSamplesPerSec;//采样率
int nAvgBytesPerSec;//每秒字节数 通道数*采样率*采样精度/8(字节位数)
short nBlockAlign;//块大小 采样字节*声道数
short wBitsPerSample;//采样精度 采样精度/8 = 采样字节
short cbSize; //预留字节 一般为0扩展域有的没有
byte[] wdid; //data 标志
int wdSize; //块大小
byte[] wdbuf; //数据指针 有符号
byte[] wFact; //Fact 标志
int nFactLength; //Fact数据块的长度
byte[] wFactData; // Fact数据块
@Override
public String toString() {
return "ckid=" + byte2Ascii(ckid) + "\n" +
"fileSize=" + fileSize + "\n" +
"fccType=" + byte2Ascii(fccType) + "\n" +
"unknowChar=" + Arrays.toString(unknowChar) + "\n" +
"unknowChar2=" + Arrays.toString(unknowChar2) + "\n" +
"waveFormat=" + byte2Ascii(waveFormat) + "\n" +
"CkSize=" + CkSize + "\n" +
"wFormatTag= " + wFormatTag + "\n" +
"nChannels= " + nChannels + "\n" +
"nSamplesPerSec=" + nSamplesPerSec + "\n" +
"nAvgBytesPerSec=" + nAvgBytesPerSec + "\n" +
"nBlockAlign= " + nBlockAlign + " Byte\n" +
"wBitsPerSample= " + wBitsPerSample + " Bit\n" +
"cbSize=" + cbSize + "\n" +
"wFact=" + (wFact==null?"":byte2Ascii(wFact)) + "\n" +
"nFactLength=" + nFactLength + "\n" +
"wFactData=" + (wFactData==null?"":Arrays.toString(wFactData)) + "\n" +
"wdid=" + byte2Ascii(wdid) + "\n" +
"wdSize=" + wdSize + "\n";
}
/**
* byte转Ascii码
*/
public static String byte2Ascii(byte[] byteArr){
Charset cs = StandardCharsets.UTF_8;
ByteBuffer bb = ByteBuffer.allocate(byteArr.length);
bb.put(byteArr);
bb.flip();
CharBuffer cb = cs.decode(bb);
return new String(cb.array());
}
}
文件读取完了,就该来分析文件数据了,原理是先将音频文件分声道,然后读取一个声道的1秒的数据,分析其波峰是不是有1000左右(误差在15左右吧),如果不是1000再读取下一秒的数据,如果是的话就分析下一个声道
波峰计算是将声道数据拖到21长度的数组中,判断中间的数字(第10位)是否比左边和右边的数据都大,如果都大,那它是波峰无疑,如果你感觉不精确,可以将数组的长度加大,如41,但是数组的长度必须是奇数,因为好找中间数
下面就是分析音频的代码
private void parse1KHZ(RiffFormat riffFormat){
List<Integer> points = new ArrayList<>();
int sizeCheck = riffFormat.getnAvgBytesPerSec();
int channel = riffFormat.getnChannels();
byte[] content = new byte[sizeCheck];
System.arraycopy(riffFormat.getWdbuf(),0,content,0,sizeCheck);
int bitPerSample = riffFormat.getwBitsPerSample();
int blockSize = riffFormat.getnBlockAlign();
int[][] audioData = new int[channel][sizeCheck/channel/(bitPerSample/8)];
int blockCount = sizeCheck/blockSize;
for(int m = 0; m < blockCount; m++){
for(int ch = 0 ; ch < channel ; ch ++) {
//
if(bitPerSample == 16) {
audioData[ch][m] = byteArrayToShort(new byte[]{content[m * blockSize + 1 + ch * 2], content[m * blockSize + ch * 2]});
}else if(bitPerSample == 32){
audioData[ch][m] = byteArrayToInt(new byte[]{content[m * blockSize + 3 + ch * 4], content[m * blockSize + 2 + ch * 4],content[m * blockSize + 1 + ch * 4],content[m * blockSize + ch * 4]});
}
}
}
// nSamplesPerSec : 44100Hz
int cacheLength = 25;
if(riffFormat.getnSamplesPerSec() == 44100)
cacheLength = 37;
else if(riffFormat.getnSamplesPerSec() == 48000)
cacheLength = 43;
int[] cache;
for(int ch = 0 ; ch < channel ; ch ++) {
cache = new int[cacheLength];
points.clear();
for (int value : audioData[ch]) {
int[] cacheNew = new int[cacheLength];
System.arraycopy(cache, 1, cacheNew, 0, cacheLength - 1);
cacheNew[cacheLength - 1] = value;
try {
boolean tp = isTerminalPoint(cacheNew);
if (tp) {
points.add(cacheNew[cacheLength / 2]);
}
cache = cacheNew;
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("第" + ch + "声道 *****" + points.size());
}
}
好了。分析就结束了,赶紧去试试吧