我发现在样本开始之前假设所有PCM wav音频文件都有44个字节的标题数据是危险的。虽然这很常见,但许多应用程序(例如ffmpeg)将生成带有46字节标头的wav,并忽略此事实,而处理将导致文件损坏且不可读。但是,如何判断标题实际存在多长时间?
显然有一种方法可以做到这一点,但我搜索并发现很少有关于此的讨论。根据作者自己的背景,很多音频项目假设44(或相反,46)。
答案 0 :(得分:26)
您应该检查所有标题数据以查看实际大小。广播波格式文件将包含更大的扩展子块。来自Pro Tools的WAV和AIFF文件具有更多未扩展的扩展块以及音频后的数据。如果您想确定样本数据的开始和结束位置,您需要实际查找数据块(WAV文件的“数据”和AIFF的“SSND”)。
作为评论,所有WAV子块都符合以下格式:
Subchunk Descriptor (4 bytes) Subchunk Size (4 byte integer, little endian) Subchunk Data (size is Subchunk Size)
这很容易处理。您需要做的就是读取描述符,如果它不是您要查找的描述符,请读取数据大小并跳到下一个。一个简单的Java例程就是这样的:
//
// Quick note for people who don't know Java well:
// 'in.read(...)' returns -1 when the stream reaches
// the end of the file, so 'if (in.read(...) < 0)'
// is checking for the end of file.
//
public static void printWaveDescriptors(File file)
throws IOException {
try (FileInputStream in = new FileInputStream(file)) {
byte[] bytes = new byte[4];
// Read first 4 bytes.
// (Should be RIFF descriptor.)
if (in.read(bytes) < 0) {
return;
}
printDescriptor(bytes);
// First subchunk will always be at byte 12.
// (There is no other dependable constant.)
in.skip(8);
for (;;) {
// Read each chunk descriptor.
if (in.read(bytes) < 0) {
break;
}
printDescriptor(bytes);
// Read chunk length.
if (in.read(bytes) < 0) {
break;
}
// Skip the length of this chunk.
// Next bytes should be another descriptor or EOF.
int length = (
Byte.toUnsignedInt(bytes[0])
| Byte.toUnsignedInt(bytes[1]) << 8
| Byte.toUnsignedInt(bytes[2]) << 16
| Byte.toUnsignedInt(bytes[3]) << 24
);
in.skip(Integer.toUnsignedLong(length));
}
System.out.println("End of file.");
}
}
private static void printDescriptor(byte[] bytes)
throws IOException {
String desc = new String(bytes, "US-ASCII");
System.out.println("Found '" + desc + "' descriptor.");
}
例如,这里有一个随机的WAV文件:
Found 'RIFF' descriptor. Found 'bext' descriptor. Found 'fmt ' descriptor. Found 'minf' descriptor. Found 'elm1' descriptor. Found 'data' descriptor. Found 'regn' descriptor. Found 'ovwf' descriptor. Found 'umid' descriptor. End of file.
值得注意的是,这里'fmt'和'data'合法地出现在其他块之间,因为Microsoft's RIFF specification表示子块可以按任何顺序出现。甚至我所知道的一些主要音频系统也会出错,并没有考虑到这一点。
因此,如果要查找某个块,请遍历文件检查每个描述符,直到找到您要查找的那个。
答案 1 :(得分:10)
诀窍是查看“Subchunk1Size”,它是从头的字节16开始的4字节整数。在正常的44字节wav中,此整数将为16 [10,0,0,0]。如果它是一个46字节的标题,如果有额外的可扩展元数据,那么这个整数将是18 [12,0,0,0]甚至更高(罕见?)。
额外数据本身(如果存在)从字节36开始。
因此,检测标头长度的简单C#程序如下所示:
static void Main(string[] args)
{
byte[] bytes = new byte[4];
FileStream fileStream = new FileStream(args[0], FileMode.Open, FileAccess.Read);
fileStream.Seek(16, 0);
fileStream.Read(bytes, 0, 4);
fileStream.Close();
int Subchunk1Size = BitConverter.ToInt32(bytes, 0);
if (Subchunk1Size < 16)
Console.WriteLine("This is not a valid wav file");
else
switch (Subchunk1Size)
{
case 16:
Console.WriteLine("44-byte header");
break;
case 18:
Console.WriteLine("46-byte header");
break;
default:
Console.WriteLine("Header contains extra data and is larger than 46 bytes");
break;
}
}
答案 2 :(得分:2)
除了Radiodef的优秀回复之外,我还想添加3件不太明显的东西。
WAV文件的唯一规则是FMT块在DATA块之前。除此之外,你会发现在开始时,在DATA块之前和之后都不知道的块。您必须读取每个块的标题才能跳过以查找下一个块。
FMT块通常在16字节和18字节变体中找到,但规范实际上也允许超过18个字节。 如果FMT大块&#39;标题大小字段大于16,字节17和18还指定了多少额外字节,因此如果它们都为零,则最终得到一个18字节的FMT块,与16字节的字节相同。 只读取FMT块的前16个字节并解析它们是安全的,而忽略了它们。 为什么这很重要? - 不多了,但Windows XP的媒体播放器能够播放16位WAV文件,但只有当FMT块是扩展(18+字节)版本时才能播放24位WAV文件。曾经有很多抱怨说Windows不能播放我的24位WAV文件&#34;但是如果它有一个18字节的FMT块,它会...微软在某些时候修复了在Windows 7的早期阶段,所以24位的16字节FMT文件现在工作正常。
(新增)奇数尺寸的块尺寸经常发生。主要是在制作24位单声道文件时看到的。从规范中不清楚,但是块大小指定了实际数据长度(奇数值),并且在块之后和下一个块开始之前添加了填充字节(零)。所以块总是从偶数边界开始,但块大小本身存储为实际的奇数值。