关于 WAV 文件格式

Posted by Xiphoray on May 20, 2020

老样子先是传送门:

XiCutAudio

XiWave 库

之前在我的 MSc Project 里,有一部分是涉及音频处理的内容,大概就是将音频中的特征点提取,然后转换成颜色的变换这样一个操作。那时是直接对 wav 文件作处理的,于是我就顺手看了看相关的资料。

wav 文件全称 wave,顾名思义就是直接存储声音波形数据的文件。这种音乐文件的数据基本上是不经过压缩的,也就是说可以不依赖解码器,在直接对文件中的数据进行处理。

偶然我在少数派里看到一个介绍 Timebolt 的文章,这是一个可以自动剪除视频里的静音片段的软件。受此启发,我就想,反正资料也看了,就别浪费了,顺便拿来练练手好了。适逢最近开始喜欢听播客,就想到播客录制的时候,也许很容易出现空白的时间,后期需要慢慢去听然后剪出来有点麻烦。于是就开始做这个可以自动剪除 wav 音频文件里的空白片段的软件。

一开始想得很简单,就是把码解出来,然后根据波形变化,把低于某个阈值的那段时间记录下来,在输出的时候直接跳过这些片段就可以了。然而最难的问题却出在了解码上。

wav 文件格式

当然也不能说是难,只是坑很多。关于 wav 文件的文件头,网上的资料通常都会给出这个图,但其实这图是有问题的。在某些文件头格式简单的 wav 文件里确实可以直接用图里的地址来直接提取数据,但是有很多别的 wav 文件,它会在 「fmt」和「data」之间加很多奇奇怪怪的东西,也就是存在一些可选的拓展格式块,这使得「data」的地址在不同的 wav 文件里是不一样的,同样也不会和图片里的那样一直是在 0x24 的地址位。

wav 文件对比

比如这两个 wav 文件,显然「data」的位置就完全不一样,中间夹杂了很多其他信息。这些信息都是录制软件或者转码软件加入进去的,原理和之前 Apple 系统曝出的图片入侵漏洞的原理一样。所以读取这些信息的正确方法是,将「fmt」、「data」这些格式头识别出来,即「0x666d74」和「0x64617461」,然后再以此为基准对地址进行偏移。这个大坑弄了我很长时间,因为我一开始是用一段有简单文件头格式的音频文件来测试的,做完之后拿别的音频文件一试才发现有问题,而且网上绝大多数资料都对这点语焉不详。我也是通过两个文件的数据比对才发现这个问题的。

另外一个坑就出现在数据读取上。一开始我是用 char 数据类型来读取数据的,但是验证数据的时候,我用 printf("%x",char) 来打印对应的数据,结果发现有某些数据中会莫名其妙地在前面补 0xfff。网上查来之后我发现是 signed char 导致的,于是我就将全部读取数据的变量都改成了 unsigned char,这样输出结果就和我用 Hex 看的文件数据一样了。

到这里看起来调试都很顺利,结果到了我处理 data 数据的时候,问题就出现了:我死活不能正确获取音乐波形数据。获取的十六进制码是正确的,但是转变成对应的波形数值却是错的。我以为是样品提取的格式有问题,但无论我怎么调整都是徒劳的。

我想起在 MSc Project 中,我用 Matlab 导入音频之后,它会将数据转为矩阵形式保存。于是我就去那里看看这些数据到底是怎么样的,然后就看到波形的数值是有正有负的。这本来是很正常的,毕竟我们平时看到的波形图都是线在 0 上下跳动的。但在看到符号后,我突然就意识到,之前改了数据格式,相当于是强制转换,也许将某些信息丢掉,导致数据有问题。结果果然如此,在我改回 char 之后,数值就正常了。

最后说一下这个软件。用法和支持的东西都写在了 readme 里面,短期内也不会有更新了。配备的 wav 文件读写库我也单独放在了一个仓库里,方便以后有用的时候直接加进去。当然这只是个练手的东西,算法上并没有太多的优化,测试的时候也只是用了十几秒左右的音频文件做测试,不知道对于大文件会不会有问题。(应该是会有问题的,因为我是直接将数据存储在一个向量里面,估计会受内存限制)




Share