钾肥喵的窝

我在 CODING 部署的 Hexo 博客

0%

C++写入WAV文件

WAV格式解析

Waveform Audio File Format(WAVE, 或者是经常看到的扩展名WAV), 是巨硬与IBM共同开发的存储音频流的编码格式, 属于资源交换文件格式(Resource Interchange File Format, RIFF)的一种应用.

下面简要介绍一下RIFF文件, RIFF文件由一个简单的表头 (header) 跟随着多个 “chunks” 组成, 详见Wikipedia页面.

接下来介绍WAV文件, 以下仅介绍最简单的形式, 不包含扩展信息.

文件头

字节位置 区块 区块大小 端序 区块内容
0 区块编号
ChunkID
4 “RIFF”
4 总区块大小
ChunkSize
4 = N + 36
8 文件格式
Format
4 “WAVE”
12 子区块1标签
Subchunk1ID
4 “fmt “(此处有空格, 请注意)
16 子区块1大小
Subchunk1Size
4 16
20 音频格式
AudioFormat
4 1(PCM)
22 声道数量
NumChannels
4 1(单声道) 2(双声道)
24 采样率
SampleRate
4 采样点/秒(Hz)
28 数据传送速率
ByteRate
4 = 采样率 * 声道数 * 位深度 / 8
32 区块对齐
BlockAlign
4 4
34 位深度
BitsPerSample
4 采样位深度
36 子区块2标签
Subchunk2ID
4 “data”
40 子区块2大小
Subchunk2Size
4 = N (= 数据传送速率 * 秒数 * 声道数)
44 音频数据
Data
= N <音频数据由此开始>

PCM数据格式

PCM(Pulse Code Modulation), 脉冲编码调制. PCM中的声音数据是未被压缩的.

采样数据按下表顺序存入:

位深度 声道数 存储顺序(0字节) 存储顺序(1字节) 存储顺序(2字节) 存储顺序(3字节) 存储顺序(4字节)
8 1 [0声道] [0声道] […] […] […]
8 2 [0声道(左)] [1声道(右)] [0声道(左)] [1声道(右)] […]
16 1 [0声道(低字节)] [1声道(高声道)] [0声道(低字节)] [1声道(高声道)] […]
16 2 [0声道(左, 低字节)] [0声道(左, 高字节)] [1声道(右, 低字节)] [1声道(右, 高字节)] […]

PCM的每个采样数据包含在一个整数中.

样本大小 数据格式 最小值 最大值
8 uint8_t 0 255
16 int16_t -32767 32767

代码实现

二进制文件读写

C++的文件操作在头文件<fstream>中, 流的打开模式之间用或连接, 比如ofstream fout("XXX.XX", ios::app|ios::binary), 其中binary即为二进制模式.

二进制流的读写可以使用read()write()函数. 需要注意的是这两个函数的参数类型.

示例:

二进制数据的表示

数据块可以使用结构体来实现, 这里可能出现内存对齐问题, 所以需要设置对齐块的大小.

这里还有一个不容易被注意到的小问题: 基本数据类型在不同实现上可能有不同的长度. 因此这里统一使用<cstdint>typedef的数据类型来保证位长是固定的.
另外考虑到对大小端序的兼容性, 结构体中统一使用unsigned数据类型.

大小端序的转换

写完才发现在X86平台下不用考虑大小端序的问题, 但还是放出转换代码.
以下代码以32位整数为例.

参考资料

https://en.wikipedia.org/wiki/WAV
https://zh.wikipedia.org/wiki/WAV
https://www.cnblogs.com/lidabo/p/3729615.html
https://zhuanlan.zhihu.com/p/27338283

完整代码