音频格式wav
Waveform Audio File Format
(WAVE
,又或者是因为WAV
后缀而被大众所知的),它采用RIFF
(Resource Interchange File Format)文件格式结构。通常用来保存PCM
格式的原始音频数据,所以通常被称为无损音频。也可以支持一些编码格式的数据,比如最近流行的AAC编码。
wav格式的实质就是在PCM文件的前面加了一个文件头。WAVE文件是一种RIFF格式的文件。其基本块名称是“WAVE”,其中包含了两个子块“fmt“和“data”。从编程的角度简单说来就是由WAVE_HEADER、WAVE_FMT、WAVE_DATA、采样数据共4个部分组成。
wav采样率一般是44.1K,16bit采样精度,存储成WAV格式大小 = 44.1KHz(采样率) X 16bit(采样位数) X 2(双声道) X 播放时间。
RIFF
RIFF,全称Resource Interchange File Format,是一种按照标记区块存储数据的通用文件存储格式,多用于存储音频、视频等多媒体数据。Microsoft在Windows下的WAV、AVI等都是基于RIFF实现的。
一个标准的RIFF规范规范文件,最小存储单位为“块”(Chunk),每个块(Chunk)包含以下三个信息:辨别码,数据大小和数据。
名称 | 大小 | 类型 | 端序 | 含义 |
---|---|---|---|---|
FOURCC | 4 | 字符 | 大端 | 用于标识Chunk ID或chunk 类型,通常为Chunk ID |
Data Field Size | 4 | 整形 | 小端 | 特别注意,该长度不包含其本身,以及FOURCC |
Data Field | - | - | - | 数据域,如果Chunk ID为"RIFF"或"LIST",则开始四个字节为类型码 |
只有ID为"RIFF"或者"LIST"的块允许拥有子块(SubChunk)。RIFF文件的第一个块的ID必须是"RIFF",也就是说ID为"LIST"的块只能是子块(SubChunk),他们和各个子块形成了复杂的RIFF文件结构。
RIFF数据域的的起始位置四个字节为类型码(Form Type),用于说明数据域的格式,比如WAV文件的类型码为"WAVE"。
"LIST"块的数据域的起始位置也有一个四字节类型码(List Type),用于说明LIST数据域的数据内容。比如,类型码为"INFO"时,其数据域可能包括"ICOP"、"ICRD"块,用于记录文件版权和创建时间信息。
wav头
一个典型的WAV文件头部长度为44字节,包含了采样率,通道数,位深等信息,如下表所示。
偏移位置 | 大小 | 类型 | 端序 | 含义 |
---|---|---|---|---|
0x00-0x03 | 4 | 字符 | 大端 | "RIFF"块(0x52494646),标记为RIFF文件格式 |
0x04-0x07 | 4 | 整型 | 小端 | 块数据域大小(Chunk Size),即从下一个地址开始,到文件末尾的总字节数,或者文件总字节数-8。从0x08开始一直到文件末尾,都是ID为"RIFF"块的内容,其中会包含两个子块,"fmt "和"data" |
0x08-0x0B | 4 | 字符 | 大端 | 类型码(Form Type),WAV文件格式标记,即"WAVE"四个字母 |
0x0C-0x0F | 4 | 字符 | 大端 | "fmt "子块(0x666D7420),注意末尾的空格 |
0x10-0x13 | 4 | 整形 | 小端 | 子块数据域大小(SubChunk Size),默认0x10,扩展时0x12 |
0x14-0x15 | 2 | 整形 | 小端 | 编码格式(Audio Format),1代表PCM无损格式 |
0x16-0x17 | 2 | 整形 | 小端 | 声道数(Channels),1或2 |
0x18-0x1B | 4 | 整形 | 小端 | 采样率(Sample Rate) |
0x1C-0x1F | 4 | 整形 | 小端 | 传输速率(Byte Rate),每秒数据字节数,SampleRate * Channels * BitsPerSample / 8 |
0x20-0x21 | 2 | 整形 | 小端 | 每个采样所需的字节数BlockAlign,BitsPerSample*Channels/8 |
0x22-0x23 | 2 | 整形 | 小端 | 单个采样位深(Bits Per Sample),可选8、16或32 |
0x24-0x27 | 4 | 字符 | 大端 | "data"子块 (0x64617461) |
0x28-0x2B | 4 | 整形 | 小端 | 子块数据域大小(SubChunk Size) |
0x2C-eos | N | PCM |
上表为典型的WAV头部格式,从0x00到0x2B总共44字节,从0x2C开始一直到文件末尾都是PCM音频数据。所以如果你已经知道了PCM的采样信息,那么可以直接跳过头部的解析,直接从0x2C开始读取PCM即可,但是对于另一些无损的WAV文件却是不行的。
typedef struct WAVE_HEADER{ char fccID[4]; unsigned long dwSize; char fccType[4]; }WAVE_HEADER; typedef struct WAVE_FMT{ char fccID[4]; unsigned long dwSize; unsigned short wFormatTag; unsigned short wChannels; unsigned long dwSamplesPerSec; unsigned long dwAvgBytesPerSec; unsigned short wBlockAlign; unsigned short uiBitsPerSample; }WAVE_FMT; typedef struct WAVE_DATA{ char fccID[4]; unsigned long dwSize; }WAVE_DATA;
在写入WAVE文件头的时候给其中的每个字段赋上合适的值就可以了。但是有一点需要注意:WAVE_HEADER和WAVE_DATA中包含了一个文件长度信息的dwSize字段,该字段的值必须在写入完音频采样数据之后才能获得。因此这两个结构体最后才写入WAVE文件中。
WAV扩展
有一些WAV的头部并不仅仅只有44个字节,比如通过FFmpge编码而来的WAV文件头部信息通常大于44个字节。这是因为根据WAV规范,其头部还支持携带附加信息,所以只按照44个字节的长度去解析WAV头部信息是不一定正确的,还需要考虑附加信息。那么如何知道一个WAV文件头部是否包含附加信息呢?
根据"fmt "子块长度来判断即可。
如果fmt SubChunk Size等于0x10(16),表示头部不包含附加信息,即WAV头部信息长度为44;如果等于0x12(18),则包含附加信息,此时头部信息长度大于44。
当WAV头部包含附加信息时,fmt SubChunk Size长度为18,并且紧随是另一个子块,这个包含了一些自定义的附加信息,接着往下才是"data"子块,格式如下:
偏移位置 | 大小 | 类型 | 端序 | 含义 |
---|---|---|---|---|
0x00-0x03 | 4 | 字符 | 大端 | "RIFF"块(0x52494646),标记为RIFF文件格式 |
0x04-0x07 | 4 | 整型 | 小端 | 块数据域大小(Chunk Size),即从下一个地址开始,到文件末尾的总字节数,或者文件总字节数-8。从0x08开始一直到文件末尾,都是ID为"RIFF"块的内容,其中会包含两个子块,"fmt "和"data" |
0x08-0x0B | 4 | 字符 | 大端 | 类型码(Form Type),WAV文件格式标记,即"WAVE"四个字母 |
0x0C-0x0F | 4 | 字符 | 大端 | "fmt "子块(0x666D7420),注意末尾的空格 |
0x10-0x13 | 4 | 整形 | 小端 | 子块数据域大小(SubChunk Size),这里为0x12 |
0x14-0x15 | 2 | 整形 | 小端 | 编码格式(Audio Format),1代表PCM无损格式 |
0x16-0x17 | 2 | 整形 | 小端 | 声道数(Channels),1或2 |
0x18-0x1B | 4 | 整形 | 小端 | 采样率(Sample Rate) |
0x1C-0x1F | 4 | 整形 | 小端 | 传输速率(Byte Rate),每秒数据字节数,SampleRate * Channels * BitsPerSample / 8 |
0x20-0x21 | 2 | 整形 | 小端 | 每个采样所需的字节数BlockAlign,BitsPerSample*Channels/8 |
0x22-0x23 | 2 | 整形 | 小端 | 单个采样位深(Bits Per Sample),可选8、16或32 |
0x24-0x25 | 2 | |||
0x26-不定 | - | - | - | 可选附加信息,标准RIFF Chunk |
不定 | 4 | 字符 | 大端 | "data"子块 (0x64617461) |
不定 | 4 | 整形 | 小端 | 子块数据域大小(SubChunk Size) |
不定 | N | PCM |
如果一个无损WAV文件头部包含了附加信息,那么PCM音频所在的位置就不确定了,但由于附加信息也是一个子块(SubChunk),根据RIFF规范,该子块也必然记录着其长度信息,所以我们还是有办法能够动态计算出其位置,下面是计算步骤:
- 判断fmt块中subChunk是否为18。
- 如果subChunk为18,那么必然从0x26位置开始为附加信息块,0x30-0x33位置记录着该子块长度。
- 根据步骤2获取的子块长度,假定为N(16进制),那么PCM音频信息开始位置为:0x34 + N + 8。
参考:
1. 视音频数据处理入门:PCM音频采样数据处理 leixiaohua