文章目录
WAV格式文件分析
WAV格式简介
WAV是最常见的声音文件格式之一,是微软公司专门为Windows开发的一种标准数字音频文件,该文件能记录各种单声道或立体声的声音信息,并能保证声音不失真。它符合资源互换文件格式(RIFF)规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持。Wave格式支持MSADPCM、CCITT A律、CCITT μ律和其他压缩算法,支持多种音频位数、采样频率和声道,是PC机上最为流行的声音文件格式;但其文件尺寸较大,多用于存储简短的声音片段。
来源:百度百科
WAV格式组成
WAV文件遵循RIFF规则,其内容以chunk
为最小单位进行存储。WAV文件一般由三个区块组成:RIFF chunk
,Format chunk
和Data chunk
。同时,文件中也可能存在一些可选的区块,比如:Fact chunk
,PlayList chunk
等。在分析的过程中,我们重点分析前三种区块:RIFF chunk
,Format chunk
和Data chunk
。
下面详细给出各区块的组成结构:
RIFF Chunk
名称 | 偏移地址 | 字节数 | 端序 | 内容 |
---|---|---|---|---|
ID | 0x00 | 4 | 大端 | RIFF (0x52494646) |
Size | 0x04 | 4 | 小端 | fileSize - 8 |
Type | 0x08 | 4 | 大端 | WAVE(0x57415645) |
- 以
RIFF
为标识 Size
是指的整个文件的大小减去ID
和Size
的长度。故是 f i l e s i z e − 8 filesize - 8 filesize−8Type
为Wave
表示后面需要有两个子块:Format
和Data
Format Chunk
名称 | 偏移地址 | 字节数 | 端序 | 内容 |
---|---|---|---|---|
ID | 0x00 | 4 | 大端 | fmt (0x666D7420) |
Size | 0x04 | 4 | 小端 | 16/18 |
AudioFormat | 0x08 | 2 | 小端 | 音频格式 |
NumChannels | 0x0A | 2 | 小端 | 声道数 |
SampleRate | 0x0C | 4 | 小端 | 采样率 |
ByteRate | 0x10 | 4 | 小端 | 每秒数据字节数 |
BlockAlign | 0x14 | 2 | 小端 | 数据块对齐 |
BitsPerSample | 0x16 | 2 | 小端 | 采样位数 |
- 以
fmt
为标识 Size
表示该区块数据的长度(不包含ID和Size的长度)为16时WAV
头部不包含附加信息。AudioFormat
表示Data
区块存储的音频数据的格式,PCM
音频数据的值为1NumChannels
表示音频数据的声道数,1:单声道,2:双声道SampleRate
表示音频数据的采样率ByteRate
每秒数据字节数 S a m p l e R a t e ∗ N u m C h a n n e l s ∗ B i t s P e r S a m p l e / 8 SampleRate * NumChannels * BitsPerSample / 8 SampleRate∗NumChannels∗BitsPerSample/8BlockAlign
每个采样所需的字节数 N u m C h a n n e l s ∗ B i t s P e r S a m p l e / 8 NumChannels * BitsPerSample / 8 NumChannels∗BitsPerSample/8BitsPerSample
每个采样存储的bit数,取值有8,16,32
Data Chunk
名称 | 偏移地址 | 字节数 | 端序 | 内容 |
---|---|---|---|---|
ID | 0x00 | 4 | 大端 | data(0x64617461) |
Size | 0x04 | 4 | 小端 | 视实际情况而定 |
Data | 0x08 | 视文件大小而定 | 小端 | 音频数据 |
Data
为标识Size
表示音频的长度 B y t e R a t e ∗ S e c o n d s ByteRate * Seconds ByteRate∗SecondsData
表示数据
对于Data Chunk
,声道数和采样率不同,造成不同的数据布局:(每列1Byte大小)
8 bit 单声道
采样1 | 采样2 |
---|---|
数据1 | 数据2 |
8 bit 双声道
采样1 | 采样2 | ||
---|---|---|---|
声道1 数据1 | 声道2 数据1 | 声道1 数据2 | 声道2 数据2 |
16 bit 单声道
采样1 | 采样2 | ||
---|---|---|---|
数据1 低字节 | 数据1 高字节 | 数据2 低字节 | 数据2 高字节 |
16 bit 双声道
采样1 | |||
---|---|---|---|
声道1 数据1 低字节 | 声道1 数据1 高字节 | 声道2 数据1 低字节 | 声道2 数据1 高字节 |
采样2 | |||
声道1 数据2 低字节 | 声道1 数据2 高字节 | 声道2 数据2 低字节 | 声道2 数据2 高字节 |
下面解释一下在上述内容中经常出现的大小端序问题
大小端端序
Wave文件以小端端序来存储数据
- 大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中,如PNG文件格式;
- 小端模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。
下面我们分析一个具体的.wav文件:
实际文件分析
RIFF Chunk
名称 | 实际数据 | 说明 |
---|---|---|
ID | 和上述内容一致 | |
Size | 整个文件大小为45340字节 | |
Type | 文件类型为WAVE |
Format Chunk
名称 | 实际数据 | 说明 |
---|---|---|
ID | 和上文描述一致 | |
Size | 大小为16,头部不含附加信息 | |
AudioFormat | 为PCM音频数据 | |
NumChannels | 单声道音频 | |
SampleRate | 采样率为22050 | |
ByteRate | 每秒数据字节数为44100 | |
BlockAlign | 每个采样所需字节数为2 | |
BitsPerSample | 每个采样存储16bit |
Data Chunk
名称 | 实际数据 | 说明 |
---|---|---|
ID | 和上文描述一致 | |
Size | 数据长度为45304字节 | |
Data | 好多不放了就… | 实际存储的数据 |
是否存在其他可选区块?
为了验证该文件是否存在可选区块,加入了以下代码:
#include <iostream>
#include <fstream>
#include <cstdio>
#include <vector>
#include <map>
#include <set>
#define uchar unsigned char
using namespace std;
const string path = "test.wav";
vector<string> ans;
struct RiffHeader
{
string id = "";
string type = "";
unsigned int length = 0;
uchar len[4];
uchar Type[4];
void GetHead(ifstream & in) {
uchar* buffer = new uchar[4];
in.read((char *)buffer, 4);
for (int i = 0;i < 4;i ++) id += (int)buffer[i];
in.read((char *)len, 4);
length = (len[1] + (len[0] << 8)) + ((len[3] + (len[2] << 8)) << 8);
in.read((char *)Type, 4);
for (auto i : Type) type += (int)i;
return ;
}
};
struct FormatHeader
{
string id = "";
uchar data[20];
void GetHead(ifstream & in) {
uchar* buffer = new uchar[4];
in.read((char*)buffer, 4);
for (int i = 0;i < 4;i ++) id += (int)buffer[i];
in.read((char*)data, 20);
return ;
}
};
struct DataHeader
{
string id = "";
uchar* data;
unsigned int length = 0;
void GetHead(ifstream & in) {
uchar* buffer = new uchar[4];
in.read((char*)buffer, 4);
for (int i = 0;i < 4;i ++) id += (int)buffer[i];
uchar* len = new uchar[4];
in.read((char*)len, 4);
length = (len[1] + (len[0] << 8)) + ((len[3] + (len[2] << 8)) << 8);
data = new uchar[length];
in.read((char *)data, length);
return ;
}
};
int main()
{
ifstream in(path, ios :: binary);
RiffHeader riff;
FormatHeader format;
riff.GetHead(in);
ans.push_back(riff.id);
format.GetHead(in);
ans.push_back(format.id);
while (!in.eof()) {
DataHeader data;
data.GetHead(in);
ans.push_back(data.id);
}
cout << "All chunks : " << endl;
for (auto i : ans) cout << "Chunk id : " << i << endl;
return 0;
}
得到结果:
All chunks :
Chunk id : RIFF
Chunk id : fmt
Chunk id : data
本文件中无可选数据块。
参考文献
AVI思考题
打开一个AVI文件,回答下述问题:
- 音频和视频的数据是如何放置的?
- 一个视频帧大概占多少字节?一个音频帧大概占多少字节?
以二进制方式打开AVI文件和,可以发现,代表视频帧的00dc
数据块和代表音频帧的00wb
数据块是交织放置的。
第一帧视频帧长度为61 05 00 00
而AVI的数据是按照小端序排列的,所以应该为0x00000561
,十进制为1377字节。
第一帧音频帧长度为DA 06 00 00
,实际数据为0x06DA
,十进制为1754字节。