本文是用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());
        }
    }

好了。分析就结束了,赶紧去试试吧