第九篇 WAV文件格式

WAVE PCM soundfile format        

        WAV即WAVE,WAVE文件是计算机领域最常用的数字化声音文件格式之一,它是微软专门为Windows系统定义的波形文件格式(Waveform Audio),其扩展名为"*.wav"。

        最基本的WAVE文件是PCM(脉冲编码调制)格式的,这种文件直接存储采样的声音数据没有经过任何的压缩,是声卡直接支持的数据格式,要让声卡正确播放其它被压缩的声音数据,就应该先把压缩的数据解压缩成PCM格式,然后再让声卡来播放。

        WAVE文件还有很多种有损压缩格式,比如IMA/DVI ADPCM,Microsoft ADPCM,AAC编码等。被压缩的声音数据,需要先解压成PCM格式,才能用声卡来播放

        如果是PCM,则为无损格式,文件会比较大,并且大小相对固定,可以使用以下公式计算文件大小。

FileSize = HeadSize + TimeInSecond * SampleRate * Channels * BitsPerSample / 8

其中:

  • HeadSize为WAV文件头部长度;
  • SampleRate,即采样率,可选8000、16000、32000、44100或48000;
  • Channels表示声道数量,通常为1或2;
  • BitsPerSample代表单个Sample的位深,可选8、16以及32,其中32位时可以是float类型。

关于RIFF

        RIFF,全称Resource Interchange File Format,是一种按照标记区块存储数据的通用文件存储格式,多用于存储音频、视频等多媒体数据。Microsoft在Windows下的WAV、AVI等都是基于RIFF实现的。一个标准的RIFF规范规范文件,最小存储单位为“块”(Chunk),每个块(Chunk)包含以下三个信息:

         只有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格式遵循RIFF规范(Resource Interchange File Format 资源交互文件格式) 。RIFF文件结构可以看作是树状结构,其基本构成是称为“块”(Chunk)的单元,最顶端是一个“RIFF”块,下面的每个块有“类型块标识(可选)”,"标志符“,”数据大小“及”数据“等项所组成,块的结构如表1所示:

        上面说到的 “类型块标识”只在部分bhunk中用到,如”WAVE"chunk中,这时表示下面嵌套有别的chunk,当使用了 “类型块标识”时,该chunk就没有别的项(如"标志符“,”数据大小“等),它只作为文件读取时的一个标识,先找到这个 “类型块标识”,再以它为起读取它下面嵌套的其他chunk。

非PCM格式的文件会至少多加入一个“fact”块,它用来记录数据解压缩后的大小。(注意是数据而不是文件)这个“fact”块一般加在“data”块的前面。

每个WAV文件由文件头和数据体两大部分组成,数据体的记录方式是小端(little-endian), 以最简单的无损WAV格式文件为例,此时文件的音频数据体为PCM,比较简单,重点在于WAV的文件头。

1  WAV文件头结构 

        WAV文件是非常简单的一种RIFF文件,它的文件头包含三部分,RIFF,fmt,fact(fact是非必需有的) 


typedef __packed struct
{
        ChunkRIFF riff;        //riff块
        ChunkFMT fmt;          //fmt块
        ChunkFACT fact;        //fact块 在线性PCM,没有这个结构体         
        ChunkDATA data;        //data块                 
}__WaveHeader;
 1.1. “RIFF”Chunk的内部组织

        RIFF块的格式类型(Format)为“WAVE ”。RIFF块包含两个子块(Subchunk),这两个子块的ID分别是“fmt ”和“data ” 。其中“fmt ”子块由结构PCMWAVEFORMAT所组成,其子块的大小就是sizeof(PCMWAVEFORMAT),数据组成就是PCMWAVEFORMAT结构中的数据。      

typedef __packed struct
{
    u32 ChunkID;                  //chunk id;这里固定为"RIFF",即0X46464952
    u32 ChunkSize ;               //集合大小;文件总大小-8
    u32 Format;                   //格式;WAVE,即0X45564157
}ChunkRIFF ;
 1.2. “FMT”Chunk的内部组织
typedef __packed struct
{
    u32 ChunkID;            //chunk id;这里固定为"fmt ",即0X20746D66
    u32 ChunkSize ;         //子集合大小(不包括ID和Size);这里为:20.
    u16 AudioFormat;        //音频格式;0X01,表示线性PCM;0X11表示IMA ADPCM
    u16 NumOfChannels;  //通道数量;1,表示单声道;2,表示双声道;
    u32 SampleRate;     //采样率;0X1F40,表示8Khz
    u32 ByteRate;       //字节速率;
    u16 BlockAlign;     //块对齐(字节);
    u16 BitsPerSample;  //单个采样数据大小;4位ADPCM,设置为4
    u16 ByteExtraData;   //附加的数据字节;2个; 线性PCM,没有这个参数
    u16 sampleperblock;  //一般是一个数据块中的采样数量 如:0x01F9
}ChunkFMT;  

ByteExtraData这个数据重点说一下,在原始线性PCM编码格式,不需要这个参数,只有压缩的PCM编码格式,一般都是按块存储的,需要知道一个块内的采样数量。 

在IMA-ADPCM编码格式下的ByteExtraData和sampleperblock, 在data Chunk前面4个字节。

sampleperblock = 0x1F9,表示一个block中有 505个采样点。

1.3. “Fact”Chunk的内部组织
typedef __packed struct
{
    u32 ChunkID;                 //chunk id;这里固定为"fact",即0X74636166;
    u32 ChunkSize ;              //子集合大小(不包括ID和Size);这里为:4.
    u32 NumOfSamples;            //采样的数量;
}ChunkFACT;

”All (compressed) non-PCM formats must have a Fact chunk (Rev. 3documentation). The chunk contains at least one value, the number of samples in the file.”
虽然标准协议要求”所有非PCM编码的WAV文件要求有fact块,它用来记录数据解压缩后的大小。(注意是数据而不是文件)“

NumOfSamples是这个chunk中最重要的数据,如果这是某种压缩格式的声音文件,那么长这里可以知道它解压缩后的大小,对于解压时的计算会有很大的好处!

这个“fact”块一般加在“data”块的前面。但实际分析了两个wav文件的文件头,一种PCM编码,一种IMA-ADPCM编码,这两个文件都没有fact块,但是window可以正常播放;说明fact块对这两种类型的wav文件也不是必须的 

用GoldWave.exe导出的IMA-ADPCM,“fact”块是放在“data”块的后面

 1.4. WAV文件头解析

        例如:一个典型的WAV文件头部长度是44字节,包含了采样率,通道数,位深等信息。

请添加图片描述

  

例子:channel_1.wav的WAV文件头部

在“data”Chunk中描述音频数据的大小是0x43D4,此信息后的所有字节都是音频数据

第一个音频数据0xFF,起始地址是0x2c 

数据结束地址是0x43FF

 0x43FF-2C +1 正好是0x43D4, 0x2c是文档头

例子:

As an example, here are the opening 72 bytes of a WAVE file with bytes shown as hexadecimal numbers:

52 49 46 46 24 08 00 00 57 41 56 45 66 6d 74 20 10 00 00 00 01 00 02 00 
22 56 00 00 88 58 01 00 04 00 10 00 64 61 74 61 00 08 00 00 00 00 00 00 
24 17 1e f3 3c 13 3c 14 16 f9 18 f9 34 e7 23 a6 3c f2 24 f2 11 ce 1a 0d 

例子:波形文件的文件头,占用44Byte,文件头后为波形数据区 

struct FORMAT_WAV
{
    long ChunkID;               //“RIFF”
    long ChunkSize;           //chunk(大块)的数量,wav文件的总大小,单位字节
    long Format;                  //“WAVE”
    long Subchunk1ID;      //“fmt”  第一个chunk的ID
    long Subchunk1Size;   //第一个chunk的Size
    short AudioFormat;       //音频格式
    short NumChannels;   //声道的数量
    long SampleRate;         //采样率
    long ByteRate;               //比特率
    short BlockAlign;           //块对齐
    short BitsPerSample;  //每个采样点的位宽
    long Subchunk2ID;      //"data" 第二个chunk的ID
    long Subchunk2Size;   //第二个chunk的Size,波形数据的大小,单位为字节
}

2. WAV音频数据体(data chuck)结构
2.1.  "data"Chunk的内部组织
typedef __packed struct
{
    u32 ChunkID;                   //chunk id;这里固定为"data",即0X64617461
    u32 ChunkSize ;                // 音频数据块大小。(除去WAV头的所有数据)
}ChunkDATA;

        “data”chunk的前8个字节存储的是标志符“data”和后接数据大小size(DWORD)

        从“data”chunk的第9个字节开始,存储的就是声音信息的数据了,这些数据可能是压缩的,也可能是没有压缩的。 

        PCM的音频数据是原始数据没有被压缩,因此PCM格式的音频数据是以原始的音频流数据顺序存储下去的。如图所示:(它的基本组织单位是BYTE(8bit)或WORD(16bit)

2.2.   数据块BLOCK结构

        在IMA-ADPCM中,“data”chuck中的数据是以block形式来组织的,我把它叫做“段”,也就是说在进行压缩时,并不是依次把所有的数据进行压缩保存,而是分段进行的,这样有一个十分重要的好处:那就是在只需要文件中的某一段信息时,可以在解压缩时可以只解所需数据所在的段就行了,没有必要再从文件开始起一个一个地解压缩。这对于处理大文件将有相当的优势。同时,这样也可以保证声音效果。

        Block一般是由block header (block头) 和 data 两者组成的。Block在单声道下的定义如下:

//ADPCM压缩的数据块结构
typedef __packed struct
{
    u16 presample;               //block中第一个采样值(未压缩)
    u8 index ;                   //上一个数据块的最后一个 index,第一个block的index=0
    u8 rsv;                      //保留
    u8 dat[sampleperblock-1];    //数据
}DATA_BLOCK;

        为了数据存储对齐,方便处理,一般一个音频BLOCK的大小是16的整数倍;如果设置BLOCK大小为256Byte,减去数据块头长度4字节,还剩252字节,4bit表示一个采样的话,可存储共252x2+1=505个采样点(加上数据头里的一个采样值)。
        对于PCM编码的WAV文件,只需要按照顺序存储原始采样值即可,不需要分块。 

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"子块,格式如下:

        如果一个无损WAV文件头部包含了附加信息,那么PCM音频所在的位置就不确定了,但由于附加信息也是一个子块(SubChunk),根据RIFF规范,该子块也必然记录着其长度信息,所以我们还是有办法能够动态计算出其位置,下面是计算步骤:

  1. 判断fmt块长度是否为18。
  2. 如果fmt长度为18,那么必然从0x26位置开始为附加信息块,0x30-0x33位置记录着该子块长度。
  3. 根据步骤2获取的子块长度,假定为N(16进制),那么PCM音频信息开始位置为:0x34 + N + 8。 

读取WAV文件的方法

        在知道了WAV文件的内部数据组织后,可以直接通过FILE或HFILE来实现文件的读取,但由于WAV文件是以RIFF格式来组织的,所以用多媒体输入输出流来操作将更加方便,可以直接在文件中查找chunk并定位数据。

PCM和IMA-ADPCM编码的WAV实际例子 

 用GoldWave.exe 从pcm格式导出ima adpcm格式的wav头比较

wav文件头的大小: 

        PCM格式的wav,它文件头是44byte

        ADPCM格式的wav,它的文件头是48byte, 多了4byte的附加数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值