WAV格式文件分析

WAV格式文件分析

WAV格式简介

WAV是最常见的声音文件格式之一,是微软公司专门为Windows开发的一种标准数字音频文件,该文件能记录各种单声道或立体声的声音信息,并能保证声音不失真。它符合资源互换文件格式(RIFF)规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持。Wave格式支持MSADPCM、CCITT A律、CCITT μ律和其他压缩算法,支持多种音频位数、采样频率和声道,是PC机上最为流行的声音文件格式;但其文件尺寸较大,多用于存储简短的声音片段。

来源:百度百科

WAV格式组成

WAV文件遵循RIFF规则,其内容以chunk为最小单位进行存储。WAV文件一般由三个区块组成:RIFF chunkFormat chunkData chunk。同时,文件中也可能存在一些可选的区块,比如:Fact chunkPlayList chunk等。在分析的过程中,我们重点分析前三种区块:RIFF chunkFormat chunkData chunk

下面详细给出各区块的组成结构:

RIFF Chunk

名称偏移地址字节数端序内容
ID0x004大端RIFF (0x52494646)
Size0x044小端fileSize - 8
Type0x084大端WAVE(0x57415645)
  • RIFF为标识
  • Size是指的整个文件的大小减去IDSize的长度。故是 f i l e s i z e − 8 filesize - 8 filesize8
  • TypeWave表示后面需要有两个子块:FormatData

Format Chunk

名称偏移地址字节数端序内容
ID0x004大端fmt (0x666D7420)
Size0x044小端16/18
AudioFormat0x082小端音频格式
NumChannels0x0A2小端声道数
SampleRate0x0C4小端采样率
ByteRate0x104小端每秒数据字节数
BlockAlign0x142小端数据块对齐
BitsPerSample0x162小端采样位数
  • fmt为标识
  • Size表示该区块数据的长度(不包含ID和Size的长度)为16时WAV头部不包含附加信息
  • AudioFormat表示Data区块存储的音频数据的格式,PCM音频数据的值为1
  • NumChannels表示音频数据的声道数,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 SampleRateNumChannelsBitsPerSample/8
  • BlockAlign每个采样所需的字节数 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 NumChannelsBitsPerSample/8
  • BitsPerSample每个采样存储的bit数,取值有8,16,32

Data Chunk

名称偏移地址字节数端序内容
ID0x004大端data(0x64617461)
Size0x044小端视实际情况而定
Data0x08视文件大小而定小端音频数据
  • Data为标识
  • Size表示音频的长度 B y t e R a t e ∗ S e c o n d s ByteRate * Seconds ByteRateSeconds
  • Data表示数据

对于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

本文件中无可选数据块。

参考文献

WAV文件格式详解
WAVE文件格式分析

AVI思考题

打开一个AVI文件,回答下述问题:

  • 音频和视频的数据是如何放置的?
  • 一个视频帧大概占多少字节?一个音频帧大概占多少字节?

以二进制方式打开AVI文件和,可以发现,代表视频帧的00dc数据块和代表音频帧的00wb数据块是交织放置的。

第一帧视频帧长度为61 05 00 00而AVI的数据是按照小端序排列的,所以应该为0x00000561,十进制为1377字节。
第一帧音频帧长度为DA 06 00 00,实际数据为0x06DA,十进制为1754字节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CUCKyrie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值