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中的声音数据是未被压缩的.
采样数据按下表顺序存入:
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()
函数.
需要注意的是这两个函数的参数类型.
示例:
点我查看完整代码
1 2 3
| ofstream fout("Wave.wav", ios::binary); RIFF riff(1764000); fout.write((char *)(&riff), sizeof(riff));
|
二进制数据的表示
数据块可以使用结构体来实现, 这里可能出现内存对齐问题,
所以需要设置对齐块的大小.
点我查看完整代码
1 2 3 4 5 6 7
| #pragma pack(1) struct XX{ body; };
#pragma pack()
|
这里还有一个不容易被注意到的小问题:
基本数据类型在不同实现上可能有不同的长度.
因此这里统一使用<cstdint>
中typedef
的数据类型来保证位长是固定的.
另外考虑到对大小端序的兼容性, 结构体中统一使用unsigned数据类型.
大小端序的转换
写完才发现在X86平台下不用考虑大小端序的问题, 但还是放出转换代码.
以下代码以32位整数为例.
点我查看完整代码
1 2 3 4 5 6 7 8
| uint32_t endianSwap(uint32_t n){ uint32_t changed = 0; for (int i = 0; i < 4; i++){ changed <<= 8; changed |= (n << ((3 - i) * 8)) >> 24; } return changed; }
|
参考资料
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
完整代码
点我查看完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| #include <cmath> #include <cstdint> #include <fstream> #include <iomanip> #include <iostream>
using namespace std;
uint32_t endianSwap(uint32_t n){ uint32_t changed = 0; for (int i = 0; i < 4; i++){ changed <<= 8; changed |= (n << ((3 - i) * 8)) >> 24; } return changed; }
#pragma pack(1) struct RIFF{ char chunkID[4]; uint32_t chunkSize; char format[4]; RIFF(uint32_t n){ chunkID[0] = 'R'; chunkID[1] = 'I'; chunkID[2] = 'F'; chunkID[3] = 'F'; uint32_t ChunkSize = n + 36; chunkSize = ChunkSize; format[0] = 'W'; format[1] = 'A'; format[2] = 'V'; format[3] = 'E'; } }; struct FMT{ char subchunk1ID[4]; uint32_t subchunk1Size; uint16_t audioFormat; uint16_t numChannels; uint32_t sampleRate; uint32_t byteRate; uint16_t blockAlign; uint16_t bitsPerSample; FMT(uint32_t NumChannels, uint32_t SampleRate, uint32_t BitsPerSample): subchunk1Size(16), audioFormat(1), numChannels(NumChannels), sampleRate(SampleRate), blockAlign(4), bitsPerSample(BitsPerSample){ subchunk1ID[0] = 'f'; subchunk1ID[1] = 'm'; subchunk1ID[2] = 't'; subchunk1ID[3] = ' '; byteRate = SampleRate * NumChannels * BitsPerSample >> 3; } }; struct DATA_HEADER{ char subchunk2ID[4]; uint32_t subchunk2Size; DATA_HEADER(uint32_t n): subchunk2Size(n){ subchunk2ID[0] = 'd'; subchunk2ID[1] = 'a'; subchunk2ID[2] = 't'; subchunk2ID[3] = 'a'; } }; struct DATA_BLOCK{ uint16_t left; uint16_t right; DATA_BLOCK(uint16_t Left, uint16_t Right): left(Left), right(Right){ } }; #pragma pack()
const double PI = acos(-1.0);
int main(){ ofstream fout("Wave.wav", ios::binary); RIFF riff(1764000); FMT fmt(2, 44100, 16); DATA_HEADER dataHeader(1764000); fout.write((char *)(&riff), sizeof(riff)); fout.write((char *)(&fmt), sizeof(fmt)); fout.write((char *)(&dataHeader), sizeof(dataHeader)); for (int i = 0; i < 441000; i++){ double Sin = sin(261.63 * 2 * PI * i / 44100); int16_t Data = (int16_t)(Sin * 32767); uint16_t * data = (uint16_t *)&Data; DATA_BLOCK block(*data, *data); fout.write((char *)(&block), sizeof(block)); } fout.close(); return 0; }
|