如何检测 WAV 文件的标头是 44 字节还是 46 字节?

Posted

技术标签:

【中文标题】如何检测 WAV 文件的标头是 44 字节还是 46 字节?【英文标题】:How can I detect whether a WAV file has a 44 or 46-byte header? 【发布时间】:2013-11-28 06:36:13 【问题描述】:

我发现在采样开始之前假设所有 PCM wav 音频文件都有 44 字节的标头数据是很危险的。尽管这很常见,但许多应用程序(例如 ffmpeg)会生成带有 46 字节标头的 wav,并且在处理时忽略这一事实将导致文件损坏且无法读取。但是如何检测标头实际有多长?

显然有一种方法可以做到这一点,但我搜索并发现对此的讨论很少。根据作者自己的上下文,许多音频项目假设为 44(或相反,46)。

【问题讨论】:

我有很多 WAV 文件,其中数据完全从其他地方开始:可能是从文件开头开始的数百个字节,谁知道呢? WAV 块头实际上很容易解析,你没有理由不解析它们。 确实没有任何借口可以解析标题,但是有很多关于这样做的错误信息。在 Google 上搜索“wav parser”,许多热门搜索都包含假定 44 字节长度的代码,没有讨论。 SO 仅包含对更大事物的暗示。我试图让下一个沮丧的人关注这个问题。 我一直发现音乐和声学计算机研究中心 (斯坦福) 网站上的 WAVE PCM soundfile format 页面是处理这类事情的有用资源。 【参考方案1】:

您应该检查所有标题数据以查看实际大小。广播波形格式文件将包含更大的扩展子块。 Pro Tools 中的 WAV 和 AIFF 文件具有更多未记录的扩展块以及音频之后的数据。如果您想确定示例数据的开始和结束位置,您需要实际查找数据块(WAV 文件的“数据”和 AIFF 的“SSND”)。

作为评论,所有 WAV 子块都符合以下格式:

子块描述符(4 个字节) 子块大小(4 字节整数,小端序) 子块数据(大小为子块大小)

这很容易处理。您需要做的就是读取描述符,如果不是您要查找的描述符,请读取数据大小并跳到下一个。执行此操作的简单 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 文件:

找到“RIFF”描述符。
找到'bext'描述符。
找到“fmt”描述符。
找到“minf”描述符。
找到“elm1”描述符。
找到“数据”描述符。
找到“regn”描述符。
找到“ovwf”描述符。
找到“umid”描述符。
文件结束。

值得注意的是,这里的“fmt”和“数据”都合法地出现在其他块之间,因为Microsoft's RIFF specification 表示子块可以以任何顺序出现。甚至我所知道的一些主要的音频系统也会出现这种错误并且没有考虑到这一点。

因此,如果您想找到某个块,请遍历文件检查每个描述符,直到找到您要查找的那个。

【讨论】:

Radiodef,感谢您的评论!我以前从未使用过位移,也无法在 web 中找到实际使用示例。你能解释一下这个表达式为什么在这里使用位移吗? (字节[0] & 0xFF) | (字节[1] & 0xFF) 先谢谢你了! @RomanM 它将 4 个字节转换为 32 位整数。例如,一个 32 位整数 00000000000000001000000010000001(十进制 32897)可以分成四个字节,00000000000000001000000010000001。具有位移位的代码获取字节并从中创建一个 32 位整数,方法是将每个字节移入到位,然后将它们与按位 OR 组合。 &amp; 0xFF 部分特定于 Java,explained here。【参考方案2】:

除了Radiodef的出色回复,我想补充3点不明显的东西。

    WAV 文件的唯一规则是 FMT 块位于 DATA 块之前。除此之外,您会在开始时、DATA 块之前和之后发现您不知道的块。您必须阅读每个块的标题才能向前跳以查找下一个块。

    FMT 块通常有 16 字节和 18 字节的变体,但规范实际上也允许超过 18 字节。 如果 FMT 块的标头大小字段大于 16,则字节 17 和 18 还指定有多少额外的字节,因此如果它们都为零,则最终会得到一个与 16 字节相同的 18 字节 FMT 块。 只读取 FMT 块的前 16 个字节并解析它们是安全的,而不再忽略。 为什么这很重要? - 不多了,但 Windows XP 的媒体播放器能够播放 16 位 WAV 文件,但只有 FMT 块是扩展(18+ 字节)版本时才能播放 24 位 WAV 文件。曾经有很多抱怨说“Windows 不播放我的 24 位 WAV 文件”,但如果它有一个 18 字节的 FMT 块,它会......微软在 Windows 7 早期的某个时候修复了这个问题,所以24 位和 16 字节 FMT 文件现在可以正常工作了。

    (新添加)奇数大小的块大小经常出现。多见于制作 24 位单声道文件时。从规范中不清楚,但块大小指定了实际数据长度(奇数),并且在块之后和下一个块开始之前添加了一个填充字节(零)。所以块总是从偶数边界开始,但块大小本身存储为实际的奇数值。

【讨论】:

【参考方案3】:

诀窍是查看“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;
        

【讨论】:

以上是关于如何检测 WAV 文件的标头是 44 字节还是 46 字节?的主要内容,如果未能解决你的问题,请参考以下文章

字节 [] 到 wav 文件

将字节数组转换为 Wav 文件 [重复]

如何使用 C# NAudio 操作字节?

如何查找 .wav 文件的数据(样本)大小?

如何从WAV文件中获取一个字节的对应时间?

如何转换 wav 文件-> 类似字节的对象?