如何从零开始构建一个WAV音频文件?
摘要:从0构建WAV文件:读懂计算机文件的本质 虽然接触计算机有一段时间了,但是我的视野一直局限于一个较小的范围之内,往往只能看到于算法竞赛相关的内容,计算机各种文件在我看来十分复杂,认为构建他们并能达到目的是一件困难的事情,然而近期我观看了油管
从0构建WAV文件:读懂计算机文件的本质
虽然接触计算机有一段时间了,但是我的视野一直局限于一个较小的范围之内,往往只能看到于算法竞赛相关的内容,计算机各种文件在我看来十分复杂,认为构建他们并能达到目的是一件困难的事情,然而近期我观看了油管上Magicalbat大神的视频,发现其实它们的本质都惊人地简单:所有计算机文件,都是按特定规则组织的二进制数据,是人为规定好格式再由计算机解析,对于我们来说,只要根据规定格式进行编辑,就能够成功构建。
今天,我们就从最朴素的方式入手,通过手动构建一个WAV音频文件,拆解WAV格式的底层逻辑,同时理解一个核心认知:只要掌握了文件的格式规范,任何类型的文件都能像搭积木一样,一行行代码“拼”出来。
先认识WAV:WAV文件的格式
WAV是微软开发的无损音频格式,相比于压缩后的MP3,它的结构更直白,没有复杂的编码压缩,因此我们能够通过C++文件写入的方式直接完成wav文件的构建,wav文件的核心由三个关键的“数据块(Chunk)”组成:
RIFF块:文件的“身份卡”,告诉计算机“我是一个WAV文件”;
fmt块:音频的“参数说明”,记录采样率、声道数、位深等核心参数;
data块:真正的音频数据,存储着声音的数字信号。
而每个块的内容又如下图所示:
RIFF:
字段名
字节数
数据类型
固定值/计算规则
ChunkID
4
ASCII字符
固定为"RIFF"(无终止符,严格4字节)
ChunkSize
4
32位无符号整数
取值 = 整个WAV文件大小 - 8字节(减去ChunkID和ChunkSize自身的8字节)
Format
4
ASCII字符
固定为"WAVE"(无终止符,严格4字节)
fmt:
字段名
字节数
数据类型
固定值/计算规则
ChunkID
4
ASCII字符
固定为"fmt "(末尾空格,无终止符)
ChunkSize
4
32位无符号整数
PCM编码(最常用)下固定为16(代表后续字段的总字节数,不含ChunkID和ChunkSize)
AudioFormat(代码中Tag)
2
16位无符号整数
编码格式:1=PCM(无压缩,通用);3=IEEE浮点;6=μ律;7=A律等
NumChannels(代码中Chnnels,拼写笔误)
2
16位无符号整数
声道数:1=单声道;2=立体声;>2=多声道
SampleRate
4
32位无符号整数
采样率(每秒采样次数):常见44100Hz(CD音质)、48000Hz、22050Hz等
ByteRate
4
32位无符号整数
每秒音频数据字节数 = SampleRate × NumChannels × BitsPerSample / 8
BlockAlign(代码中BloclAlign,拼写笔误)
2
16位无符号整数
每个“采样帧”的字节数 = NumChannels × BitsPerSample / 8(播放器一次读取的最小单位)
BitsPerSample(代码中BitsperSample)
2
16位无符号整数
采样位深(每个采样点的比特数):8/16/24/32,16位最常用
data:
字段名
字节数
数据类型
固定值/计算规则
ChunkID(代码中DataId)
4
ASCII字符
固定为"data"(无终止符,严格4字节)
DataSize
4
32位无符号整数
音频数据总字节数 = 采样总数 × BlockAlign;采样总数 = SampleRate × 音频时长
音频数据区
可变
二进制流
PCM编码下为线性整数/浮点数:16位位深对应int16_t,8位对应uint8_t,32位浮点对应float
我们接下来的代码,就是严格按照这个模板,把每个部分的二进制数据“写”进文件里。
