Dynamic parsing of wav audio file header -- java language

Some relatively uncommon audio formats have been processed before, and many pits have been ignored. Here is a brief record. The following may be further updated with the increase of audio types. For example, the wav format recording containing LIST data blocks has been recorded before, which is the missing point found during debugging.
Before that, sort out the basic structure of the regular audio file header, as shown in the following figure:

You can see that in the file header, bytes at different positions represent different data blocks. Relatively speaking, in most cases, the information of some data blocks is of low concern, such as LIST data blocks, while others, such as audio length, format, bit length, sampling rate, etc., are of high concern. Therefore, in the process of processing, the required data blocks can be defined as a general structure, and the corresponding result return of the structure can be set after parsing. For WAV files, chunks are the basic storage unit. A wav file contains three necessary chunks and one optional chunk. The order in the file is RIFF chunk, Format chunk, Fact chunk (optional) and Data chunk.
Therefore, when reading file headers from front to back and parsing them, riff chunk and fm chunk are fixed. Chunks are divided as follows:

According to the order, length and byte length of different data blocks, the structure of a fixed riff and fm chunk can be defined as follows:
{ "name" : "riff_id", "index" : 0, "type" : "chars", "len" : 4, "value" : "RIFF" }, { "name" : "file_size", "index" : 1, "type" : "int", "len" : 4, "value" : 0 }, { "name" : "riff_type", "index" : 2, "type" : "chars", "len" : 4, "value" : "WAVE" }, { "name" : "fmt_hdr_id", "index" : 3, "type" : "chars", "len" : 4, "value" : "fmt " },
1, Each data block is described by a binaryElement, including name, insex,type,len,value and other attributes. The type corresponds to the value type of each data block, which affects the conversion process after data reading.
The conversion process of different data types is as follows:
`/**
*byte to int
*
*@param b byte array
*@return resolved number
*/
public static int toInt(byte[] b) {
return b[0] & 0xff | (b[1] & 0xff) << 8 | (b[2] & 0xff) << 16
| (b[3] & 0xff) << 24;
}

/**
 * byte Turn to long
 *
 * @param b Byte array
 * @return Parsed number
 */
public static long toUnsignedInt(byte[] b) {
    return  b[0] & 0xff  | (b[1] & 0xff) << 8 | (b[2] & 0xff) << 16
            | (b[3] << 24);
}

/**
 * byte To short
 *
 * @param b Byte array
 * @return Parsed number
 */
public static  short toShort(byte[] b) {
    return (short) (((b[1] << 8) | b[0] & 0xff));
}`

2, The overall parsing process is as follows:
First, analyze the fixed area, riff and fmchunk. There are 11 data blocks in total:
`/ / record the current read position
int readCur = 0;

        byte[] tmp;
        for (int i = 0; i < FixedStrutNum; i++) {
            BinaryProtocolElement ele = headerStrut.get(i);
            if (ele != null) {
                tmp = new byte[ele.getLength()];
                stream.read(tmp);
                //Conversion does not correspond to data type
                bytesToElement(tmp, ele, encoding);
                readCur += ele.getLength();
            }
        }`
  
  Then process fmt If any. This part can be passed fmchunk The length of determines whether there is an extension. That is chunk Total length of - The fixed field length is 16. If it is not 0, it indicates that there is an extension area and the parsing needs to be skipped.
  `//Extension length fmt for processing fmt_data_len - 16
        //You do not need the extension information of this section. You can skip it
        int extLength = Integer.parseInt(
                headerStrut.get(WaveHeaderEleDefine.FMT_HDR_LEN.getIndex()).getValue())
                - PCM_FMT_LEN;
        if (extLength != 0) {
            //Get the length of the expansion block data first
            BinaryProtocolElement dataLenExt = new BinaryProtocolElement();
            dataLenExt.setDataType(ProtocolDataType.SHORT);
            dataLenExt.setLength(2);
            tmp = new byte[dataLenExt.getLength()];
            stream.read(tmp);
            bytesToElement(tmp, dataLenExt, encoding);
            readCur += extLength;

            //Extension length is 0 or 22
            //If it is 22, manual skip is required
            int extDataLength;
            if ((extDataLength = Integer.parseInt(dataLenExt.getValue())) != 0) {
                readCur += extDataLength;
                stream.skip(extDataLength);
            }
        }`


  Then determine whether there is an option chunk--fack,You can use the following data_tag Label data block, if the following DATA_TAG by FACT,You need to get the chunk Length and skip resolution.
  `//According to data_tag judge whether there is an optional chunk fact
        tmp = new byte[dataTag.getLength()];
        stream.read(tmp);
        bytesToElement(tmp, dataTag, encoding);
        readCur += dataTag.getLength();

        //If there is a fact field, you need to skip this field and re resolve the tag
        //If data is not read_ Len
        if (TAG_CHUNK_FACT.equalsIgnoreCase(String.valueOf(dataTag.getValue()))) {
            tmp = new byte[dataLen.getLength()];
            stream.read(tmp);
            bytesToElement(tmp, dataLen, encoding);
            readCur += dataLen.getLength();


            //Skip the data field of the fact chunk
            readCur += Integer.parseInt(dataLen.getValue());
            stream.skip(Integer.parseInt(dataLen.getValue()));

            //Parsing the tag of the data field
            tmp = new byte[dataTag.getLength()];
            stream.read(tmp);
            bytesToElement(tmp, dataTag, encoding);
            readCur += dataTag.getLength();
        }`
  
  Similarly, subsequent verification is required chunk Yes no LIST chunk,If yes, you also need to skip parsing.
  `//For wav file after format conversion
        //There are LIST data blocks that retain some format conversion information
        //The data here has no business needs for the time being. Skip after parsing the LIST data block length
        //LIST data block structure includes listSize listType listData
        //listSize occupies 4 bytes. The value of listSize is the size of listType (that is, 4 bytes) plus the length of listData. The length of "LIST" and listSize are not included.
        if (TAG_CHUNK_LIST.equalsIgnoreCase(dataTag.getValue())) {
            //Resolve the list first_ Size
            BinaryProtocolElement listSize = new BinaryProtocolElement();
            listSize.setDataType(ProtocolDataType.INT);
            listSize.setLength(4);
            tmp = new byte[listSize.getLength()];
            stream.read(tmp);
            bytesToElement(tmp, listSize, encoding);
            readCur += extLength;

            //Then skip listType and listData
            readCur += Integer.parseInt(listSize.getValue());
            stream.skip(Integer.parseInt(listSize.getValue()));

            //Parsing the tag of the data field
            tmp = new byte[dataTag.getLength()];
            stream.read(tmp);
            bytesToElement(tmp, dataTag, encoding);
            readCur += dataTag.getLength();
        }`

  Finally, parse data chunk,Get the audio data length and return the file header length (if necessary)
  `//No data tag obtained
        //Check whether the audio header information is incorrect
        //Or there is a wrong shift
        if (!TAG_CHUNK_DATA.equalsIgnoreCase(dataTag.getValue())) {
            log.error("can not fetch the tag of data chunk, please check");
            return 0;
        }

        //Read data of data field_ Len
        tmp = new byte[dataLen.getLength()];
        stream.read(tmp);
        bytesToElement(tmp, dataLen, encoding);
        readCur += dataLen.getLength();
        //Here, the length should be adjusted because the corresponding extension area is skipped
        headerStrut.get(WaveHeaderEleDefine.FMT_HDR_LEN.getIndex())
                .setValue(PCM_FMT_LEN + "");
        //Last returned header length
        return readCur;`

References: https://www.jianshu.com/p/02c4df08c51c
https://www.jianshu.com/p/02c4df08c51c

Tags: Java

Posted by lokie538 on Tue, 31 May 2022 09:36:12 +0530