wav格式与处理
pcm格式简介
PCM(脉冲编码调制)就是把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在信道中传输。脉冲编码调制就是对模拟信号先抽样,再对样值幅度量化,编码的过程。也就是前边的说过的采样-量化过程,这个过程可以较好的存储原始的音频模拟信号,并真实还原。但是PCM并不是一种友好的存储格式,所以一些机构定制了一些准则来记录PCM信号,包括声道、采样率、位宽等信息,比较通用的就是windows平台下的wav格式和osx平台下的aiff格式,这两种都可以以一种友好可读的方式保存PCM信息。
wav格式简介
以下内容来源百度:WAV
为微软公司(Microsoft)开发的一种声音文件格式,它符合RIFF(Resource Interchange File Format)
文件规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持,该格式也支持MSADPCM,CCITT A LAW等多种压缩运算法,支持多种音频数字,取样频率和声道,标准格式化的WAV文件和CD格式一样,也是44.1K的取样频率,16位量化数字,因此在声音文件质量和CD相差无几! WAV打开工具是WINDOWS的媒体播放器。
通常使用三个参数来表示声音,量化位数,取样频率和采样点振幅。量化位数分为8位,16位,24位三种,声道有单声道和立体声之分,单声道振幅数据为n1矩阵点,立体声为n2矩阵点,取样频率一般有11025Hz(11kHz) ,22050Hz(22kHz)和44100Hz(44kHz) 三种,不过尽管音质出色,但在压缩后的文件体积过大!相对其他音频格式而言是一个缺点,其文件大小的计算方式为:WAV格式文件所占容量(B) = (取样频率 X量化位数X 声道) X 时间 / 8 (字节= 8bit) 每一分钟WAV格式的音频文件的大小为10MB,其大小不随音量大小及清晰度的变化而变化。
WAV是最接近无损的音乐格式,所以文件大小相对也比较大。
一张经典的wav格式图来简单讲解一下:
这张图表明了wav个基本结构和存储信息的格式:
- 所有的字符采用big-endian存储,所有的数字采用little-endian存储。
- headerchunk下包含fmtchunk和datachunk
- 每个chunk包含chunkId,chunkSize和chunkData
- headerchunk指明了RIFF格式的具体格式,wav格式必须是“WAVE”
- fmtchunk指明了pcm文件的一些基本格式,采样率、声道、位宽等
- datachunk是用来存储具体的PCM数据
1. headerchunk介绍
headerchunk是一个总章,包含了最基本的wav格式的信息:
字段名称 | 字段长度 | 大小端 | 表示信息 |
---|---|---|---|
chunkId | 4byte | big-endian | 这个地方必须是“RIFF” |
chunkSize | 4byte | little-endian | 表示该文件除了chunkId和chunkSize以外的文件剩余大小 |
Format | 4byte | big-endian | wav文件必须是“WAVE” |
上表可以看出来headerchunk总共占24byte
2.formatchunk介绍
formatchunk是wav格式中最重要的信息,包含了该文件在读取时的所有信息。
字段名称 | 字段长度 | 大小端 | 表示信息 |
---|---|---|---|
chunkId | 4byte | big-endian | 这个地方必须是“fmt ”(注意最后是空格补齐) |
chunkSize | 4byte | little-endian | 表示该文件除了chunkId和chunkSize以外的文件剩余大小 |
AudioFormat | 2byte | little-endian | 表示音频的编码格式,一般pcm编码用1表示见表 |
Num channels | 2byte | little-endian | 表示音频的声道个数,1表示单声道(MONO),2表示双声道(STEREO) |
SampleRate | 4byte | little-endian | 表示音频的采样率,比如44100Hz |
ByteRate | 4byte | little-endian | 表示音频的比特率 |
BlockAlign | 2byte | little-endian | 表示音频的块长度,也就是单元长度 |
bitPerSample | 2byte | little-endian | 表示位宽,一般是8或者16,或者更高 |
extensionChunk | 长度不定 | 由chunkSize决定 |
chunksize一般是是16,表示出chunkid和chunksize以外的所有formatchunk的字节。假如是16的时候则不会有extensionchunk,大于16的时候则会产生这个字段,这个字段的长度就是chunksize-16,后边有介绍这个字段。
ByteRate表示音频数据的比特率,也就是每秒的数据大小,这个值=Numchannels*SampleRate*BitPerSample/8.
BlockAlign表示一个音频数据块的长度,取决于位宽(bitPerSample)和声道(NumChannels),这个值=NumbleChannels*bitPerSample/8.
下表表示AudioFormat的详细内容,注意写入的时候小端字节序
formatcode | data | fact |
---|---|---|
0x0001 | PCM | |
0x0003 | IEEE float | yes |
0x0006 | 8-bitITU G.711 A-law | yes |
0x0007 | 8-bitITU G.711 µ-law | yes |
0xFFFE | 由extensionchunk中的subformat决定 |
3.datachunk介绍
datachunk记录了真实的pcm数据并将它简单封装
字段名称 | 字段长度 | 大小端 | 表示信息 |
---|---|---|---|
chunkId | 4byte | big-endian | 这个地方必须是“data” |
chunkSize | 4byte | little-endian | 表示data的数据长度 |
data |
4.extensionchunk介绍
该部分是可选部分,由formatchunk部分的chunksize决定,当chunksize大于16的时候,必然包含该块。它本身是属于formatchunk,但是我为了方便专门提取出来。
字段名称 | 字段长度 | 大小端 | 表示信息 |
---|---|---|---|
chunksize | 2byte | little-endian | 可以是0或者22 |
ValidBitsPerSample | 2byte | little-endian | |
ChannelMask | 4byte | big-endian | 表示扬声器的位置 |
SubFormat | 16byte | GUID,包含数据的编码格式,最后14byte是固定的\x00\x00\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71. |
5.factchunk介绍
在Rev.3之后,所有的非PCM压缩格式都必须包含factchunk。这个部分最少包含一个值,就是采样数量
字段名称 | 字段长度 | 大小端 | 表示信息 |
---|---|---|---|
chunkId | 4byte | big-endian | 这个地方必须是“fact” |
chunkSize | 4byte | little-endian | 最小值是4 |
samplelength | 4byte | big-endian | 每个声道的采样数量 |
其实在Rev.3之后,所有的新wav文件建议有fac这个字段,IEEE float格式必须有这个字段,但PCM格式的编码并不一定必须有这个字段。
6.peakchunk介绍
这个字段基本很少见了,
|字段名称|字段长度|大小端|表示信息
|—|—|—|—
|chunkId|4byte|big-endian|这个地方必须是“PEAK”
|chunkSize|4byte|little-endian|表示该文件除了chunkId和chunkSize以外的文件剩余大小
|data|不定|我也不知道啥意思
实例分析
我现在从cd上考过来一首音乐-17.wav,这个文件总共有48627746字节,是wav格式编码的。具体内容如下:
1 | 00000000: 5249 4646 1a00 e602 5741 5645 666d 7420 RIFF....WAVEfmt |
这是通过vim打开并转换为16进制查看格式来表示的一段代码。
我们来详细分析这一段:
5249 4646 1a00 e602 5741 5645
这段是headerchunk字段,前四个字节和后四个字节可以转换为asc码
1 | 52 - R 49 - I 46 -F 46 - F //这段表示RIFF |
666d 7420 1000 0000 0100 0200 44ac 0000 10b1 0200 0400 1000
这段表示fmt字段:
1 | 666d 7420 //转换为asc为fmt |
PCM数据的单声道和双声道
音频在存储的时候可以根据mic的数量来决定录入的轨道是双声道还是单声道,但是现代技术也可以用1个mic录制双声道音频。
单声道PCM数据是按采样的时间顺序排列,双声道的PCM数据是按采样的时间交替存储两个声道的数据,在存储的时候是按下边的图排列的:
1 |
|
在播放的时候回根据设置好的blockalign读取一块数据,这块数据会根据对应的声道数分发给扬声器。利用这个特性我们可以提取双声道的数据为单声道,也可以将单声道数据合并为双声道数据,同时也可以将双声道设置为只播放左耳和只播放右耳。
这里主要讲的是声音的采集,暂时不讲声音的播放。
安卓实现PCM录制并封装为wav格式
上一篇中讲解的是利用MediaRecord来录制音频,但是这种录制方式得到的是最终的文件,假如我们需要录制无损音频,然后包装为wav格式,这种方式就不支持了。AudioRecorder是系统提供的一个api,这个api虽然不是很好用,但是用来录制音频还算可以。
AudioRecord类是在sdk提供的java层的音频录制工具。在api21的时候这个类的方法还比较少。当我们使用这个类的时候,必须使用read方法来从缓冲区不断的拉出数据来,假如未能及时从缓冲区取出数据,则数据在缓冲区会积累并抛出异常。我曾经尝试在read的时候讲左声道的数值全部置为0,并且采用的是同步的处理方式,左声道置零的操作时间过长,导致数据溢出抛出异常,根本不能工作。
简单贴一下代码:
1 | private void startRecord() throws IOException { |
我是在最开始的时候将chunk写入文件,录制完成后计算差值来更改chunk部分的数值大小。
1 |
|
在上述的代码中主要是注意大小端的问题,关于这个问题可以参考我的另一篇文章:
java中大小端问题研究
参考资料