AVAudioRecorder 不生成正确的 WAV 文件头
我正在做一个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 个回答
你在磁盘上录音的文件叫什么名字?我之前也遇到过类似的问题,后来我只是把.wav
加到文件名的末尾就解决了。我想AVAudioRecorder
需要这个扩展名才能搞清楚事情。
苹果的软件通常会在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文件中至少必须出现两个子块:
"fmt "
- 这个标识符的子块包含描述音频格式基本信息的负载:采样率、位深度等。"data"
- 这个标识符的子块包含实际的音频字节。
"fact"
是下一个最常见的子块标识符。它通常出现在使用压缩编解码器的WAVE文件中,比如μ-law。想了解更多关于各种子块标识符的信息,可以查看这个爱好者网页,里面有关于它们的负载结构的信息。
从RIFF的角度来看,子块在文件中不需要以任何特定的顺序出现,也不需要在任何特定的固定偏移量处出现。然而,在实际操作中,几乎所有软件都期望"fmt "
子块是第一个子块。这是出于实用考虑:提前知道WAVE包含什么格式的音频是很方便的——这使得从网络流播放WAVE文件变得更容易。如果WAVE文件使用压缩格式,比如μ-law,通常会假设"fact"
子块会紧接在"fmt "
之后。
在格式指定的子块处理完后,关于子块的位置、顺序和命名的假设应该被放弃。此时,软件应该仅通过名称(例如"data"
)来定位预期的子块。如果遇到不被识别的名称的子块(例如"FLLR"
),这些子块应该被简单地跳过和忽略。跳过一个子块需要读取它的长度,以便你可以跳过正确数量的字节。
苹果在"FLLR"
子块上所做的事情有点不寻常,我并不惊讶有些软件会因此出错。我怀疑你使用的库根本没有准备好处理"FLLR"
子块的存在。我认为这是这个库的一个缺陷。库的作者可能犯的错误大概是:
他们可能期望
"data"
子块出现在文件开头的前N字节内,其中N小于约4kB。如果需要扫描太远的文件,他们可能会放弃查找。苹果的"FLLR"
子块将"data"
子块推到了文件中大于约4kB的位置。他们可能期望
"data"
子块在RIFF中的特定顺序或字节偏移位置。也许他们期望"data"
会紧接在"fmt "
之后。然而,这种处理RIFF文件的方式是错误的。"data"
子块的顺序和/或偏移位置不应该被假设。
既然我们在讨论正确的WAVE文件处理,我还想提醒大家,音频字节(data
子块的负载)可能并不会正好到达文件的末尾。允许在data
负载之后插入子块。有些程序利用这一点在文件末尾存储文本“注释”字段。如果你从data
负载的开始盲目读取直到文件结束,你可能会把一些元数据子块当作音频读取,这样在播放结束时会听到“咔嗒”声。你需要遵循data
子块的长度字段,一旦消耗了整个数据负载就停止读取音频——而不是在遇到文件结束时停止。