You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确读取所有符合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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 12:24:04