WAV文件格式详解

110 阅读2分钟

开篇:

WAV(Waveform Audio File Format)是一种常见的音频文件格式,被广泛用于存储音频数据,如音乐、语音和声效等。它以其无损的特性和跨平台的兼容性而受到欢迎。 下面这张图很经典了,来自soundfile.sapp.org/doc/WaveFor…,它完整说明了一个wav文件的结构。

image.png

用表格补充/翻译一下:

域名偏移(字节)长度(字节)字节序描述
ChunkID04大端资源交换文件标识符,通常为 "RIFF"
ChunkSize44小端下个地址开始到文件尾总字节数,即文件大小-8
Format84大端wav文件标志,通常为 "WAVE"
Subchunk1ID124大端波形格式标志,通常为 "fmt ",注意最后有空格,因为要补齐4个字节。
Subchunk1Size164小端过滤字节,通常为 0x10 = 16
AudioFormat202小端格式类别,1表示PCM形式采样数据
NumChannels222小端声道数
SampleRate244小端采样率
ByteRate284小端波特率,即声道数 × 采样频率 × 采样位数 / 8
BlockAlign322小端声道数 × 采样位数 / 8
BitsPerSample342小端采样位数
Subchunk2ID364大端数据标识符,通常为 "data"
Subchunk2Size404小端采样数据总数,即数据总大小-44
Data44可变小端实际PCM音频样本数据

实例

光看说明可能有一些不实在,下面放一张真实的音频文件用文本编辑器打开的样子:

image.png

可以看到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/…

soundfile.sapp.org/doc/WaveFor…

www.zhuyuntao.cn/js%E5%AE%9E…