开篇:
WAV(Waveform Audio File Format)是一种常见的音频文件格式,被广泛用于存储音频数据,如音乐、语音和声效等。它以其无损的特性和跨平台的兼容性而受到欢迎。 下面这张图很经典了,来自soundfile.sapp.org/doc/WaveFor…,它完整说明了一个wav文件的结构。
用表格补充/翻译一下:
域名 | 偏移(字节) | 长度(字节) | 字节序 | 描述 |
---|---|---|---|---|
ChunkID | 0 | 4 | 大端 | 资源交换文件标识符,通常为 "RIFF" |
ChunkSize | 4 | 4 | 小端 | 下个地址开始到文件尾总字节数,即文件大小-8 |
Format | 8 | 4 | 大端 | wav文件标志,通常为 "WAVE" |
Subchunk1ID | 12 | 4 | 大端 | 波形格式标志,通常为 "fmt ",注意最后有空格,因为要补齐4个字节。 |
Subchunk1Size | 16 | 4 | 小端 | 过滤字节,通常为 0x10 = 16 |
AudioFormat | 20 | 2 | 小端 | 格式类别,1表示PCM形式采样数据 |
NumChannels | 22 | 2 | 小端 | 声道数 |
SampleRate | 24 | 4 | 小端 | 采样率 |
ByteRate | 28 | 4 | 小端 | 波特率,即声道数 × 采样频率 × 采样位数 / 8 |
BlockAlign | 32 | 2 | 小端 | 声道数 × 采样位数 / 8 |
BitsPerSample | 34 | 2 | 小端 | 采样位数 |
Subchunk2ID | 36 | 4 | 大端 | 数据标识符,通常为 "data" |
Subchunk2Size | 40 | 4 | 小端 | 采样数据总数,即数据总大小-44 |
Data | 44 | 可变 | 小端 | 实际PCM音频样本数据 |
实例
光看说明可能有一些不实在,下面放一张真实的音频文件用文本编辑器打开的样子:
可以看到ChunkID
/Format
/Subchunk1ID
/Subchunk2ID
这几个明文(ASCII),可以直接在右边把明文显示;声道数/采样方式也在图上标出来了。
代码
下面用一段js代码简单实现一下组装一个wav:
function generateWAVData(sampleRate, numChannels, bitsPerSample, audioData) {
const header = new ArrayBuffer(44);
const view = new DataView(header);
// ChunkID: "RIFF"
writeString(view, 0, "RIFF");
// ChunkSize: 文件大小 - 8
view.setUint32(4, audioData.byteLength + 36, true);
// Format: "WAVE"
writeString(view, 8, "WAVE");
// Subchunk1ID: "fmt "
writeString(view, 12, "fmt ");
// Subchunk1Size: 16
view.setUint32(16, 16, true);
// AudioFormat: PCM
view.setUint16(20, 1, true);
// NumChannels
view.setUint16(22, numChannels, true);
// SampleRate
view.setUint32(24, sampleRate, true);
// ByteRate
view.setUint32(28, sampleRate * numChannels * bitsPerSample / 8, true);
// BlockAlign
view.setUint16(32, numChannels * bitsPerSample / 8, true);
// BitsPerSample
view.setUint16(34, bitsPerSample, true);
// Subchunk2ID: "data"
writeString(view, 36, "data");
// Subchunk2Size: 音频数据部分大小
view.setUint32(40, audioData.byteLength, true);
const wavBuffer = new Uint8Array(header.byteLength + audioData.byteLength);
wavBuffer.set(new Uint8Array(header), 0);
wavBuffer.set(audioData, header.byteLength);
return wavBuffer;
}
function writeString(view, offset, str) {
for (let i = 0; i < str.length; i++) {
view.setUint8(offset + i, str.charCodeAt(i));
}
}
上面JS代码中的ArrayBuffer和DataView相关操作,我有另一篇博客《字节和字节序(前端视角)》谈到了。
参考:
developer.mozilla.org/zh-CN/docs/…
developer.mozilla.org/zh-CN/docs/…