2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > java 中wav文件格式 Wav音频文件格式详解

java 中wav文件格式 Wav音频文件格式详解

时间:2019-07-13 13:57:14

相关推荐

java 中wav文件格式 Wav音频文件格式详解

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的格式就容我之后再补充了。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。