当前位置: 首页 > news >正文

MeteorSeed

从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

我们接下来的代码,就是严格按照这个模板,把每个部分的二进制数据“写”进文件里。

从零构建WAV:一行代码拆解核心逻辑

下面是完整的C++代码(新手也能看懂),我们逐段拆解,看如何从0生成一个能播放的440Hz正弦波WAV文件:

#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

f32 t = (f32)i/HZ; // 计算当前时间点(秒)

// 生成440Hz正弦波的数值(声音的本质是振动,正弦波模拟声波)

f32 y =sinf(t*440.0f*2.0f*3.1415926f);

// 转换为16位整数(适配16位位深的音频)

i16 sample = (i16)(y*INT16_MAX);

// 写入单个音频样本(2字节)

fwrite(&sample,sizeof(i16),1,fp);

}

fclose(fp); // 关闭文件

return 0;

}

所有文件,都是“按规则写二进制”的产物

写完这段代码,你可能会发现:生成WAV文件的过程,就是“按格式规范往文件里写二进制数据”的过程。而这个逻辑,适用于所有计算机文件:

TXT文档:本质是字符的ASCII/UTF-8编码(比如字符'A'对应二进制01000001),我们按顺序写入这些编码,就成了TXT文件;

BMP图片:由文件头(记录宽、高、位深)+ 像素数据(每个像素的RGB值)组成,按BMP格式写这些数据,就能生成图片;

MP4视频:哪怕是压缩过的视频,也是按MP4的格式规范,把编码后的视频帧、音频帧组织成二进制数据;

EXE可执行文件:遵循PE格式,把指令、数据、资源按规则写入,操作系统就能识别并运行。

计算机之所以能“看懂”不同的文件,不是因为文件有“魔法”,而是因为程序员提前约定了“格式规范”——就像我们约定“RIFF”开头的是WAV文件,播放器读到这个标识,就按WAV的规则解析后续数据。

计算机的本质是“朴素的规则”

对刚接触计算机的人来说,各种文件、软件、系统看似复杂,但拆解到最底层,都是“数据+规则”的组合,

只要我们对着格式手册,即便使用最朴素的方式,也能够成功构建出可以使用的音频文件。计算机的世界没有想象中那般复杂,计算机只在乎那最终排好队的 0 和 1。

进一步思考:从文件到软件

了解了各类文件本质,我们自然能理解计算机中各个编辑软件的原理是什么了,就比如今天举的wav的例子,如果我们将示例程序改进一下,加入输入,那么这是否就成了一个简单的音频编辑软件了呢,所有的复杂软件(如 Photoshop、Premiere),底层逻辑都是如此:读取特定规则的二进制 -> 在内存中加工处理 -> 按规则写回二进制。当你不再把文件看作“黑盒”,你便拥有了重塑数字世界的能力。该纶斯婆

http://www.jsqmd.com/news/579450/

相关文章:

  • 基于S7-1200PLC的物业供水控制系统设计》 PLC触摸屏,图纸,博图16 一、设计任务书...
  • C++ STL 容器线程安全机制研究
  • 彻底搞懂大模型“图谱推理”底层逻辑!TPAMI神作全解(非常详细)
  • 像素剧本圣殿效果展示:8-Bit像素风界面中实时生成的动画分镜脚本
  • Graphormer部署教程:Docker Compose编排Graphormer+Redis缓存服务
  • OpenClaw私人健身教练:Qwen2.5-VL-7B分析运动视频与生成计划
  • 忍者像素绘卷实战案例:16-Bit忍者风海报生成全流程详解
  • OpenClaw+千问3.5-9B爬虫方案:智能解析与数据入库
  • 网络资源爬取代码分享
  • 2026年靠谱的东台冷库门封/装卸货门封厂家精选合集 - 品牌宣传支持者
  • 基于LS-DYNA ANSA的汽车碰撞CAE联合仿真教程:软件操作与模型搭建
  • AI Agent火了,但你的基础设施真的能扛住吗?先看清这3个代价
  • OpenClaw跨设备控制方案:百川2-13B-4bits量化版中继服务搭建
  • Pixel Couplet Gen实战教程:对接企业微信机器人自动推送春联
  • MacBook高效办公方案:OpenClaw+千问3.5-35B-A3B-FP8自动处理会议纪要
  • OpenClaw飞书机器人集成:千问3.5-9B对话触发详解
  • 从导航软件到无人机飞控:UTM坐标系在C++项目中的3种高阶用法
  • FreeRTOS 启动流程详解:从复位到任务调度
  • 学术研究助手:用OpenClaw+Phi-3-vision-128k-instruct自动解析论文图表
  • SecGPT-14B压缩版本:在OpenClaw中运行轻量级安全模型
  • UE5蓝图实战:用VaRest插件5分钟搞定DeepSeek API调用(含完整JSON处理流程)
  • Pixel Epic部署案例:私有化部署于政务内网环境的安全加固配置详解
  • 2026年热门的装卸货门封/卸货平台门封/码头门封优质公司推荐 - 品牌宣传支持者
  • 氮化镓技术:解锁电源设计新维度的关键
  • Pixel Epic应用场景:生物医药企业用其生成临床试验方案摘要报告
  • comsol激光熔覆仿真模型案例,选用固体传热,固体力学,热对流和热辐射等,激光定向能量沉积温...
  • ESP32/ESP8266嵌入式契约生成库:轻量级设备可信声明方案
  • 从消费者心理角度看图片翻译:为什么本地语言商品图能带来更高的点击和转化
  • Pixel Language Portal效果展示:Hunyuan-MT-7B在低资源语种(如斯瓦希里语)表现
  • 面向 Context 编程:从代码结构到可推理闭包