AVAudioRecorder 不生成正确的 WAV 文件头

12 投票
2 回答
8609 浏览
提问于 2025-04-16 19:12

我正在做一个iPhone项目,使用AVAudioRecorder从设备的麦克风录音,然后对录音进行处理。

为了确保我正确读取文件中的音频样本,我使用Python的wave模块来检查它是否返回相同的样本。

但是,当我尝试打开AVAudioRecorder保存的wav文件时,Python的wave模块却返回“缺少fmt块和/或数据块”的错误。

这是我用来录制文件的设置:

[audioSettings setObject:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
[audioSettings setObject:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey];
[audioSettings setObject:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsBigEndianKey];
[audioSettings setObject:[NSNumber numberWithFloat:4096] forKey:AVSampleRateKey];
[audioSettings setObject:[NSNumber numberWithInt:1] forKey:AVNumberOfChannelsKey];
[audioSettings setObject:[NSNumber numberWithBool:YES] forKey:AVLinearPCMIsNonInterleaved];
[audioSettings setObject:[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsFloatKey]; 

之后,我只是调用recordForDuration来实际进行录音。

录音是成功的——我可以播放这个文件,也可以使用AudioFile服务读取样本,但我无法验证,因为我无法用Python的wave模块打开这个文件。

这是文件的前128个字节的样子:

1215N:~/Downloads$ od -c --read-bytes 128 testFile.wav
0000000   R   I   F   F   x   H 001  \0   W   A   V   E   f   m   t    
0000020 020  \0  \0  \0 001  \0 001  \0   @ 037  \0  \0 200   >  \0  \0
0000040 002  \0 020  \0   F   L   L   R 314 017  \0  \0  \0  \0  \0  \0
0000060  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0000200

你知道我需要做什么才能确保AVAudioRecorder写出正确的WAV文件头吗?

2 个回答

1

你在磁盘上录音的文件叫什么名字?我之前也遇到过类似的问题,后来我只是把.wav加到文件名的末尾就解决了。我想AVAudioRecorder需要这个扩展名才能搞清楚事情。

43

苹果的软件通常会在WAVE文件中创建一个非标准的(但符合“规格”)"FLLR"子块,这个子块位于"fmt "子块之后和"data"子块之前。我猜“FLLR”代表“填充”,这个子块的目的是为了实现某种数据对齐优化。这个子块的大小通常约为4000字节,但实际长度会根据前面的数据长度有所不同。

在WAVE文件中添加任意子块通常被认为是符合规范的,因为WAVE是< a href="http://en.wikipedia.org/wiki/Resource_Interchange_File_Format" rel="noreferrer">RIFF的一部分,而在处理RIFF文件时,通常会忽略那些标识符不被识别的块和子块。"FLLR"这个标识符是“非标准”的,因此任何遇到它的软件都应该忽略它。

有很多软件对WAVE格式的处理比实际需要的要严格,我怀疑你使用的库可能就是其中之一。例如,我见过一些软件假设音频字节总是从偏移量44开始——这是一个错误的假设。

实际上,在WAVE文件中找到音频字节必须通过查找RIFF中的"data"子块的位置和大小来完成;这是正确定位WAVE文件中音频字节的方法。

正确读取WAVE文件实际上应该从定位和识别RIFF子块开始。RIFF子块有一个8字节的头部:4字节用于标识符/名称字段,通常用人类可读的ASCII字符填充(例如"fmt "),另外4字节是一个小端无符号整数,指定子块数据负载的字节数——子块的数据负载紧接在其8字节头部之后。

WAVE文件格式保留了一些特定的子块标识符(或“名称”),这些标识符对WAVE格式是有意义的。每个WAVE文件中至少必须出现两个子块:

  1. "fmt " - 这个标识符的子块包含描述音频格式基本信息的负载:采样率、位深度等。
  2. "data" - 这个标识符的子块包含实际的音频字节。

"fact"是下一个最常见的子块标识符。它通常出现在使用压缩编解码器的WAVE文件中,比如μ-law。想了解更多关于各种子块标识符的信息,可以查看这个爱好者网页,里面有关于它们的负载结构的信息。

从RIFF的角度来看,子块在文件中不需要以任何特定的顺序出现,也不需要在任何特定的固定偏移量处出现。然而,在实际操作中,几乎所有软件都期望"fmt "子块是第一个子块。这是出于实用考虑:提前知道WAVE包含什么格式的音频是很方便的——这使得从网络流播放WAVE文件变得更容易。如果WAVE文件使用压缩格式,比如μ-law,通常会假设"fact"子块会紧接在"fmt "之后。

在格式指定的子块处理完后,关于子块的位置、顺序和命名的假设应该被放弃。此时,软件应该仅通过名称(例如"data")来定位预期的子块。如果遇到不被识别的名称的子块(例如"FLLR"),这些子块应该被简单地跳过和忽略。跳过一个子块需要读取它的长度,以便你可以跳过正确数量的字节。

苹果在"FLLR"子块上所做的事情有点不寻常,我并不惊讶有些软件会因此出错。我怀疑你使用的库根本没有准备好处理"FLLR"子块的存在。我认为这是这个库的一个缺陷。库的作者可能犯的错误大概是:

  1. 他们可能期望"data"子块出现在文件开头的前N字节内,其中N小于约4kB。如果需要扫描太远的文件,他们可能会放弃查找。苹果的"FLLR"子块将"data"子块推到了文件中大于约4kB的位置。

  2. 他们可能期望"data"子块在RIFF中的特定顺序或字节偏移位置。也许他们期望"data"会紧接在"fmt "之后。然而,这种处理RIFF文件的方式是错误的。"data"子块的顺序和/或偏移位置不应该被假设。

既然我们在讨论正确的WAVE文件处理,我还想提醒大家,音频字节(data子块的负载)可能并不会正好到达文件的末尾。允许在data负载之后插入子块。有些程序利用这一点在文件末尾存储文本“注释”字段。如果你从data负载的开始盲目读取直到文件结束,你可能会把一些元数据子块当作音频读取,这样在播放结束时会听到“咔嗒”声。你需要遵循data子块的长度字段,一旦消耗了整个数据负载就停止读取音频——而不是在遇到文件结束时停止。

撰写回答