如何使用Node.js+TypeScript检测WAV文件是否有效或已损坏
WAV文件校验逻辑优化方案
现有代码存在的问题
- 缺少Buffer最小长度前置校验:如果传入的Buffer长度小于44字节,调用readIntLE等方法会直接抛出异常,导致服务报错
- 固定写死data块起始位置为44字节:仅适用于无扩展块的标准PCM格式WAV,如果文件包含LIST元数据块、非PCM格式的fmt扩展块,data块的起始位置会偏移,此时计算realDataLength的逻辑完全错误
- 存在浮点数精度风险:用除法计算校验值时如果出现小数,和整数存储的byteRate、blockAlign比对可能出现意料之外的不匹配
- 缺少音频格式校验:未校验fmt块中的音频格式字段,若收到压缩格式的WAV(比如MP3编码的WAV封装),即使头信息校验通过也不符合常规业务的使用要求
- 缺少采样精度合法范围校验:未校验bitsPerSample的常见合法值,出现异常值时无法识别
优化后代码
function isWavValid(fileBuffer: Buffer): boolean { // 前置校验最小长度,避免读取越界 const MIN_WAV_LENGTH = 44; if (fileBuffer.length < MIN_WAV_LENGTH) return false; // 基础头标识校验 if (fileBuffer.toString('utf8', 0, 4) !== 'RIFF') return false; if (fileBuffer.toString('utf8', 8, 12) !== 'WAVE') return false; if (fileBuffer.toString('utf8', 12, 16) !== 'fmt ') return false; // 校验音频格式,1代表未压缩PCM,如有其他格式需求可调整允许范围 const audioFormat = fileBuffer.readIntLE(20, 2); if (audioFormat !== 1) return false; // 读取基础参数 const numChannels = fileBuffer.readIntLE(22, 2); const sampleRate = fileBuffer.readIntLE(24, 4); const byteRate = fileBuffer.readIntLE(28, 4); const reportedBlockAlign = fileBuffer.readIntLE(32, 2); const bitsPerSample = fileBuffer.readIntLE(34, 2); // 参数合法性校验 if (numChannels < 1 || numChannels > 65535) return false; // 校验采样精度为常见合法值,可根据业务需求调整允许范围 const validBitsPerSample = [8, 16, 24, 32]; if (!validBitsPerSample.includes(bitsPerSample)) return false; if (byteRate !== sampleRate * numChannels * bitsPerSample / 8) return false; if (reportedBlockAlign !== numChannels * bitsPerSample / 8) return false; // 动态查找data块起始位置,兼容带扩展块的WAV let dataStartOffset = 36; let foundDataChunk = false; let reportedDataLength = 0; while (dataStartOffset + 8 <= fileBuffer.length) { const chunkId = fileBuffer.toString('utf8', dataStartOffset, dataStartOffset + 4); const chunkSize = fileBuffer.readIntLE(dataStartOffset + 4, 4); if (chunkId === 'data') { foundDataChunk = true; reportedDataLength = chunkSize; break; } // 跳过当前块,块长度如果是奇数需要补1个字节对齐 dataStartOffset += 8 + (chunkSize % 2 === 0 ? chunkSize : chunkSize + 1); } if (!foundDataChunk) return false; // 校验RIFF块大小 const reportedChunkSize = fileBuffer.readIntLE(4, 4); if (reportedChunkSize !== fileBuffer.length - 8) return false; // 校验实际数据长度匹配 const realDataLength = fileBuffer.length - (dataStartOffset + 8); if (reportedDataLength !== realDataLength) return false; return true; }
补充说明
如果你的业务明确只收无扩展块的标准PCM WAV,不需要兼容带元数据的文件,可以去掉动态查找data块的逻辑,保留固定偏移的写法即可,性能更高。如果需要做更严格的损坏校验,可以进一步读取data块的内容,校验每个采样点是否在对应采样精度的合法取值范围内,但这个操作对大文件性能损耗较大,可以根据业务场景选择是否开启。
内容的提问来源于stack exchange,提问作者Ali
相关产品推荐
相关产品推荐

