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 单声道
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
本文件中无可选数据块。
参考文献
WAV文件格式详解 WAVE文件格式分析
|