找回密码
 立即注册
首页 业界区 业界 从0构建WAV文件:读懂计算机文件的本质

从0构建WAV文件:读懂计算机文件的本质

焦和玉 2026-1-23 13:10:00
从0构建WAV文件:读懂计算机文件的本质

虽然接触计算机有一段时间了,但是我的视野一直局限于一个较小的范围之内,往往只能看到于算法竞赛相关的内容,计算机各种文件在我看来十分复杂,认为构建他们并能达到目的是一件困难的事情,然而近期我观看了油管上Magicalbat大神的视频,发现其实它们的本质都惊人地简单:所有计算机文件,都是按特定规则组织的二进制数据,是人为规定好格式再由计算机解析,对于我们来说,只要根据规定格式进行编辑,就能够成功构建。
今天,我们就从最朴素的方式入手,通过手动构建一个WAV音频文件,拆解WAV格式的底层逻辑,同时理解一个核心认知:只要掌握了文件的格式规范,任何类型的文件都能像搭积木一样,一行行代码“拼”出来。
先认识WAV:WAV文件的格式

WAV是微软开发的无损音频格式,相比于压缩后的MP3,它的结构更直白,没有复杂的编码压缩,因此我们能够通过C++文件写入的方式直接完成wav文件的构建,wav文件的核心由三个关键的“数据块(Chunk)”组成:

  • RIFF块:文件的“身份卡”,告诉计算机“我是一个WAV文件”;
  • fmt块:音频的“参数说明”,记录采样率、声道数、位深等核心参数;
  • data块:真正的音频数据,存储着声音的数字信号。
而每个块的内容又如下图所示:
1.jpeg

RIFF:
字段名字节数数据类型固定值/计算规则ChunkID4ASCII字符固定为"RIFF"(无终止符,严格4字节)ChunkSize432位无符号整数取值 = 整个WAV文件大小 - 8字节(减去ChunkID和ChunkSize自身的8字节)Format4ASCII字符固定为"WAVE"(无终止符,严格4字节)fmt:
字段名字节数数据类型固定值/计算规则ChunkID4ASCII字符固定为"fmt "(末尾空格,无终止符)ChunkSize432位无符号整数PCM编码(最常用)下固定为16(代表后续字段的总字节数,不含ChunkID和ChunkSize)AudioFormat(代码中Tag)216位无符号整数编码格式:1=PCM(无压缩,通用);3=IEEE浮点;6=μ律;7=A律等NumChannels(代码中Chnnels,拼写笔误)216位无符号整数声道数:1=单声道;2=立体声;>2=多声道SampleRate432位无符号整数采样率(每秒采样次数):常见44100Hz(CD音质)、48000Hz、22050Hz等ByteRate432位无符号整数每秒音频数据字节数 = SampleRate × NumChannels × BitsPerSample / 8BlockAlign(代码中BloclAlign,拼写笔误)216位无符号整数每个“采样帧”的字节数 = NumChannels × BitsPerSample / 8(播放器一次读取的最小单位)BitsPerSample(代码中BitsperSample)216位无符号整数采样位深(每个采样点的比特数):8/16/24/32,16位最常用data:
字段名字节数数据类型固定值/计算规则ChunkID(代码中DataId)4ASCII字符固定为"data"(无终止符,严格4字节)DataSize432位无符号整数音频数据总字节数 = 采样总数 × BlockAlign;采样总数 = SampleRate × 音频时长音频数据区可变二进制流PCM编码下为线性整数/浮点数:16位位深对应int16_t,8位对应uint8_t,32位浮点对应float我们接下来的代码,就是严格按照这个模板,把每个部分的二进制数据“写”进文件里。
从零构建WAV:一行代码拆解核心逻辑

下面是完整的C++代码(新手也能看懂),我们逐段拆解,看如何从0生成一个能播放的440Hz正弦波WAV文件:
[code]#include using namespace std;// 类型别名:让代码更易读,明确数据的字节长度#define u32 uint32_t  // 32位无符号整数(4字节)#define u16 uint16_t  // 16位无符号整数(2字节)#define f32 float     // 32位浮点数(4字节)#define i16 int16_t   // 16位有符号整数(2字节)#define HZ 44100      // 采样率:每秒采集44100个声音样本(标准音频采样率)#define DURATION 5    // 音频时长:5秒// 1. 定义WAV的三个核心数据块结构(对应格式规范)// RIFF块:文件整体标识struct chunk1{    char ChunkID[4];   // 块标识,固定为"RIFF"    u32 ChunkSize;     // 从该字段到文件末尾的字节数(总字节数-8)    char Format[4];    // 格式类型,固定为"WAVE"}RIFF;// fmt块:音频参数配置struct chunk2{    char ChunkID[4];   // 块标识,固定为"fmt "(注意末尾有空格)    u16 Tag;           // 编码格式,1代表PCM(无压缩)    u32 ChunkSize;     // fmt块的大小,PCM格式固定为16    u16 Chnnels;       // 声道数:1=单声道,2=立体声    u32 SampleRate;    // 采样率    u32 ByteRate;      // 每秒数据量 = 采样率×声道数×位深/8    u16 BloclAlign;    // 每个采样的总字节数 = 声道数×位深/8    u16 BitsperSample; // 每个采样的位深:16位(常见)}Fmt;// data块:音频数据存储区struct chunk3{    char DataId[4];    // 块标识,固定为"data"    u32 DataSize;      // 音频数据的总字节数}Data;signed main(int argc,char* argv[]){    // 打开文件:"wb"表示以二进制模式写入(关键!文件本质是二进制)    FILE *fp = fopen("test.wav","wb");    // 计算总采样数:采样率×时长(5秒×44100=220500个样本)    u32 NumSamples = HZ * DURATION;    // 2. 填充RIFF块并写入文件    memcpy(RIFF.ChunkID,"RIFF",4);          // 写入块标识    RIFF.ChunkSize = NumSamples*sizeof(u16)+36; // 计算块大小    memcpy(RIFF.Format,"WAVE",4);           // 声明为WAVE格式    fwrite(RIFF.ChunkID,sizeof(char),4,fp); // 写入4个字符的ChunkID    fwrite(&RIFF.ChunkSize,sizeof(u32),1,fp); // 写入4字节的ChunkSize    fwrite(RIFF.Format,sizeof(char),4,fp); // 写入4个字符的Format    // 3. 填充fmt块并写入文件    memcpy(Fmt.ChunkID,"fmt ",4);    Fmt.ChunkSize = 16;          // PCM格式下fmt块固定16字节    Fmt.Tag = 1;                 // PCM无压缩编码    Fmt.Chnnels = 1;             // 单声道    Fmt.SampleRate = HZ;         // 44100Hz采样率    Fmt.ByteRate = HZ*sizeof(u16); // 每秒字节数:44100×2=88200    Fmt.BloclAlign = Fmt.Chnnels * sizeof(u16); // 每个采样2字节    Fmt.BitsperSample = 16;      // 16位位深    // 按顺序写入fmt块的所有参数(严格遵循格式规范)    fwrite(&Fmt.ChunkID,sizeof(char),4,fp);    fwrite(&Fmt.ChunkSize,sizeof(u32),1,fp);    fwrite(&Fmt.Tag,sizeof(u16),1,fp);    fwrite(&Fmt.Chnnels,sizeof(u16),1,fp);    fwrite(&Fmt.SampleRate,sizeof(u32),1,fp);    fwrite(&Fmt.ByteRate,sizeof(u32),1,fp);    fwrite(&Fmt.BloclAlign,sizeof(u16),1,fp);    fwrite(&Fmt.BitsperSample,sizeof(u16),1,fp);    // 4. 填充data块并写入文件    memcpy(Data.DataId,"data",4);    Data.DataSize = NumSamples * sizeof(u16); // 音频数据总字节数    fwrite(&Data.DataId,sizeof(char),4,fp);    fwrite(&Data.DataSize,sizeof(u32),1,fp);    // 5. 生成音频数据并写入(440Hz正弦波,标准A调)    for(int i=0;i 在内存中加工处理 -> 按规则写回二进制。当你不再把文件看作“黑盒”,你便拥有了重塑数字世界的能力。</p>
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册