如何正确读取所有符合RIFF WAV规范的wav文件的fmt头数据
问题原因分析
- 固定44字节头的假设不成立:RIFF WAV是基于块的嵌套结构,规范从未规定头总长度为44字节。很多WAV文件会在fmt(格式)块前后插入LIST、INFO等元数据块,直接读取44字节映射到固定结构会出现偏移错位,你读取到的异常声道数、采样位深本质是其他块的内容被错误映射到了结构成员。
- 结构对齐问题:C/C++的struct默认会做字节对齐填充,你定义的RiffWav结构成员之间的偏移可能和WAV文件中的实际存储偏移不一致,进一步导致读取错误。
- 现有代码逻辑缺陷:你将文件内容读入HeaderBuffer后,未将缓冲区数据拷贝到Header结构,直接访问Header成员读取的是栈上的随机脏数据。
兼容所有RIFF WAV的实现方案
不需要手动调整fseek偏移,只要按照RIFF块的规范遍历所有块,自动定位fmt块和data块即可,代码逻辑如下:
1. 头文件修正(取消结构对齐)
// WaveReader.h #pragma pack(push, 1) // MSVC下强制1字节对齐,GCC可替换为__attribute__((packed)) typedef struct { uint16_t AudioFormat; uint16_t NumChannels; uint32_t SampleRate; uint32_t BytesRate; uint16_t BlockAlign; uint16_t BitsPerSample; } WavFmtInfo; #pragma pack(pop)
2. 遍历块的读取逻辑
#include <stdio.h> #include <string.h> #include <stdint.h> // 成功返回0,失败返回负数 int readWavInfo(const char* filePath, WavFmtInfo* outFmt, uint32_t* outDataSize, uint32_t* outDataOffset) { FILE* fp = fopen(filePath, "rb"); if (!fp) return -1; // 读取最外层RIFF头 unsigned char riffId[4], waveTag[4]; uint32_t riffSize; if (fread(riffId, 1, 4, fp) != 4 || memcmp(riffId, "RIFF", 4) != 0) { fclose(fp); return -2; } fread(&riffSize, 4, 1, fp); if (fread(waveTag, 1, 4, fp) != 4 || memcmp(waveTag, "WAVE", 4) != 0) { fclose(fp); return -3; } unsigned char chunkId[4]; uint32_t chunkSize; int gotFmt = 0, gotData = 0; // 循环遍历所有子块 while (fread(chunkId, 1, 4, fp) == 4 && fread(&chunkSize, 4, 1, fp) == 1) { if (memcmp(chunkId, "fmt ", 4) == 0) { if (chunkSize < 16) { // 标准PCM格式的fmt块最小为16字节 fclose(fp); return -4; } fread(outFmt, 1, sizeof(WavFmtInfo), fp); // 跳过fmt块的扩展内容(如果存在) if (chunkSize > sizeof(WavFmtInfo)) { fseek(fp, chunkSize - sizeof(WavFmtInfo), SEEK_CUR); } gotFmt = 1; } else if (memcmp(chunkId, "data", 4) == 0) { *outDataOffset = ftell(fp); *outDataSize = chunkSize; gotData = 1; // 找到数据块即可终止遍历 break; } else { // 其他无关块直接跳过 fseek(fp, chunkSize, SEEK_CUR); } // RIFF规定所有块按2字节对齐,块大小为奇数时跳过1字节填充 if (chunkSize % 2 != 0) { fseek(fp, 1, SEEK_CUR); } } fclose(fp); return (gotFmt && gotData) ? 0 : -5; }
方案说明
上述逻辑中用到的fseek是代码自动根据块大小计算偏移,不需要你手动针对每个文件调整偏移参数,能够兼容所有符合RIFF规范的WAV文件,无论中间插入了多少自定义元数据块。你之前遇到的fseek(InFile, 12, SEEK_SET)才读取正确的情况,本质是对应文件在RIFF头之后直接就是fmt块,刚好匹配固定偏移,但有额外块的文件就会失效。
内容的提问来源于stack exchange,提问作者Omar
相关产品推荐
相关产品推荐

