Wav文件格式解析
参考资料:
https://en.wikipedia.org/wiki/WAV
http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
http://www.lightlink.com/tjweber/StripWav/Canon.html
Wav是RIFF的一种音频格式,所以
开始的4个字节一定是RIFF这4个char
接下来4个字节是接下来的文件的总长度,加上之前的RIFF占了8字节,所以文件目测总长度应该是这个字节的数值+8,在Wav中所有的数值类的值都是int,字节序为little endian
接下来4个字节是WAVE这个4个Char
接下来4个字节是fmt 这4个Char,是的,是4个,后面有个空格别漏了
但是,也有例外,如果这个地方出现的不是fmt 这个字符串,那么可能还会有别的信息继续被放在了文件头,但是它一定是如下的格式:
4个字节的信息头名称,我这里见过的有(JUNK和FAKE)
4个字节的附加信息头长度(int)
对应长度的chunk信息
在这之后一定还会出现fmt 这4个char的组合,即回到正轨
接下来4个字节是整个Wave文件fmt头的长度, 类型是int,可选择的是16、18或者40
接下来4个字节是WAVE文件的编码方式,类型是int,一般来说是1、3、6、7或者65534,1代表PCM,3代表IEEE浮点,6代表8-bit ITU-T G.711 A-law,8代表8-bit ITU-T G.711 μ-law,65534代表在后面的subFormat中定义,除了1之外,后面的都没有见过实例,也无法得知它的实际是个什么样子了,PCM也是用得最多的
接下来2个字节是音频的声道数量,类型是short,1=单声道,2=stereo=立体声,其他多声道的可能也有,但是我暂时没看到实例
接下来4个字节是采样率数值,类型是int,一般都是44100,代表一秒钟记录多少个信息
接下来4个字节是一个能自己算出来的数值了,类型为int,它应该被理解为每秒钟有多少个byte,它的数值等于采样率 * 单位Chunk长度(楼下这个值)
接下来2个字节同样是一个能自己算出来的数值,类型为short,它应该被理解为单位Chunk的长度,它的数值等于声道数 * 位深 / 8
接下来2个字节是位深,类型是short,可以理解为这是一个声音在某个极短的时间是一个浮点表示的,用多少位的浮点来表示这个时候的声音,就是位深,理论上位深越深越越能够还原声音的原本信息,但是太多也就超过人耳的上限了就是,一般都是24、32
如果fmt头长度超过了16,那么这里应该会有其他的信息,剩余的长度就是fmt头长度的数值 - 16,具体的我也是没见过实例的,只有这个可能性列举在楼下:
如果上面那个PCM的值取到了非1的值,那么这里应该需要多出一个如下的信息头:
4个字节的信息头名称,应该是fact这4个char
4个字节的信息头总长度,int
对应长度的chunk详细信息
接下来4个字节又是标识符,char类型,应该等于'data'
接下来4个字节是int类型,标志data的总长度
然后剩下的就全是data了
最后可能会出现的是Wav文件的Meta信息,比如一些编辑器会把图片、Artist之类的放在文件末尾,同样是以4个字节的开头介绍、4字节的长度信息、对应长度的具体信息这种模式存放的
继续补全信息
说完了以上的理想状态,再说说实际中怎么用
用Python来解析这个其实很容易:
首先,因为Java没有太好用的像Python一样的struct类,你只能把它变成ByteArray,但是变成了ByteArray之后,又面临两个问题:
一、ByteArray默认是有符号的,而我们需要无符号的数据
二、如果你强制转Int,大部分时候比如ByteBuffer.toInt()按照的是大端序去做转换的,但是不巧的是Wav文件用的是小端序
注意这俩坑点之后就简单了
然后文件内部data的格式就容我之后再补充了
https://en.wikipedia.org/wiki/WAV
http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
http://www.lightlink.com/tjweber/StripWav/Canon.html
Wav是RIFF的一种音频格式,所以
开始的4个字节一定是RIFF这4个char
接下来4个字节是接下来的文件的总长度,加上之前的RIFF占了8字节,所以文件目测总长度应该是这个字节的数值+8,在Wav中所有的数值类的值都是int,字节序为little endian
接下来4个字节是WAVE这个4个Char
接下来4个字节是fmt 这4个Char,是的,是4个,后面有个空格别漏了
但是,也有例外,如果这个地方出现的不是fmt 这个字符串,那么可能还会有别的信息继续被放在了文件头,但是它一定是如下的格式:
4个字节的信息头名称,我这里见过的有(JUNK和FAKE)
4个字节的附加信息头长度(int)
对应长度的chunk信息
在这之后一定还会出现fmt 这4个char的组合,即回到正轨
接下来4个字节是整个Wave文件fmt头的长度, 类型是int,可选择的是16、18或者40
接下来4个字节是WAVE文件的编码方式,类型是int,一般来说是1、3、6、7或者65534,1代表PCM,3代表IEEE浮点,6代表8-bit ITU-T G.711 A-law,8代表8-bit ITU-T G.711 μ-law,65534代表在后面的subFormat中定义,除了1之外,后面的都没有见过实例,也无法得知它的实际是个什么样子了,PCM也是用得最多的
接下来2个字节是音频的声道数量,类型是short,1=单声道,2=stereo=立体声,其他多声道的可能也有,但是我暂时没看到实例
接下来4个字节是采样率数值,类型是int,一般都是44100,代表一秒钟记录多少个信息
接下来4个字节是一个能自己算出来的数值了,类型为int,它应该被理解为每秒钟有多少个byte,它的数值等于采样率 * 单位Chunk长度(楼下这个值)
接下来2个字节同样是一个能自己算出来的数值,类型为short,它应该被理解为单位Chunk的长度,它的数值等于声道数 * 位深 / 8
接下来2个字节是位深,类型是short,可以理解为这是一个声音在某个极短的时间是一个浮点表示的,用多少位的浮点来表示这个时候的声音,就是位深,理论上位深越深越越能够还原声音的原本信息,但是太多也就超过人耳的上限了就是,一般都是24、32
如果fmt头长度超过了16,那么这里应该会有其他的信息,剩余的长度就是fmt头长度的数值 - 16,具体的我也是没见过实例的,只有这个可能性列举在楼下:
如果上面那个PCM的值取到了非1的值,那么这里应该需要多出一个如下的信息头:
4个字节的信息头名称,应该是fact这4个char
4个字节的信息头总长度,int
对应长度的chunk详细信息
接下来4个字节又是标识符,char类型,应该等于'data'
接下来4个字节是int类型,标志data的总长度
然后剩下的就全是data了
最后可能会出现的是Wav文件的Meta信息,比如一些编辑器会把图片、Artist之类的放在文件末尾,同样是以4个字节的开头介绍、4字节的长度信息、对应长度的具体信息这种模式存放的
继续补全信息
说完了以上的理想状态,再说说实际中怎么用
用Python来解析这个其实很容易:
with open(self.__wave_file, 'rb') as wav: flag = wav.read(4) if flag != 'RIFF': return 0 file_length, = struct.unpack('i', wav.read(4)) print(file_length)这样其实没有太多的难度,但是如果你用Java写,就很蛋疼了
首先,因为Java没有太好用的像Python一样的struct类,你只能把它变成ByteArray,但是变成了ByteArray之后,又面临两个问题:
一、ByteArray默认是有符号的,而我们需要无符号的数据
二、如果你强制转Int,大部分时候比如ByteBuffer.toInt()按照的是大端序去做转换的,但是不巧的是Wav文件用的是小端序
注意这俩坑点之后就简单了
import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; public class WavHeader { public static void HeaderParser(String fileUri) throws IOException { RandomAccessFile wavFile; try { wavFile = new RandomAccessFile(fileUri, "r"); } catch (FileNotFoundException e) { e.printStackTrace(); return; } byte[] buffer4 = new byte[4]; wavFile.read(buffer4); assert byteArrayToString(buffer4).equals("RIFF"); // 向后跳到获取sample,这个一般是44100,比较好确认解析正确 wavFile.seek(12); wavFile.read(buffer4); while (!byteArrayToString(buffer4).equals("fmt ")) { // 一般来说头文件不会超过500字节的,也可以通过文件大小限制来避免空指针,这里图简单 if (wavFile.getFilePointer() + 4 >= 500) { wavFile.close(); return; } wavFile.read(buffer4); wavFile.seek(byteArrayToInt(buffer4)); wavFile.read(buffer4); } wavFile.seek(wavFile.getFilePointer() + 8); wavFile.read(buffer4); assert byteArrayToIntUseBuffer(buffer4) == 44100; assert byteArrayToInt(buffer4) == 44100; } private static String byteArrayToString(byte[] byteArray) { return new String(byteArray); } // 两种不同的方式来获取Int值,这里为原始手段 private static int byteArrayToInt(byte[] byteArray) { int total = 0; for (int i = 0; i < byteArray.length; i++) { total += (byteArray[i] & 0xFF) << (i * 8); } return total; } // 显得很高级的手段,其实原理相同 private static int byteArrayToIntUseBuffer(byte[] bytes) { ByteBuffer buffer = ByteBuffer.wrap(bytes); buffer.order(ByteOrder.LITTLE_ENDIAN); return buffer.getInt(); } }
然后文件内部data的格式就容我之后再补充了
评论
发表评论