Skip to content

WAV文件头信息解析

RIFF的组织结构

引用自参考资料【1】RIFF文件浅析

RIFF是Microsoft提出的一种多媒体文件的存储方式,不同编码的音频、视频文件,可以按照它定义的存储规则来保存、记录各自不同的数据,如:数据内容、采样信息、视窗大小、编码方式等。在播放器或者其他提取工具读取文件的时候,就可以根据RIFF的规则来分析文件,合理的解析出音频、视频信息,正确的播放它们。在现实中,常见的这类文件有WAV文件、AVI文件。它们都是遵循RIFF的方式保存自己的播放信息和播放数据的。

在RIFF的文件存储规则中,主要有几个重要的概念需要理解。它们是FOURCC、RIFF文件头、CHUNK、LIST。RIFF的组成元素就是它们,分析一个按RIFF规则组织的文件,都可以把它划分成CHUNK、LIST、RIFF这几个部分。

RIFF的数据存储形式是一种仿Microsoft文件系统的组织形式。在一个Microsoft的文件系统中,有盘符、目录、文件的概念。系统可以有几个盘符,每个盘符又可以有多个目录,在目录的下面则可以有子目录或是许多的文件。文件是保存数据的基本单元,而盘符目录是用来组织文件的。在RIFF的组织中,也借用了这些思想。在RIFF文件中,数据保存的基本单元是CHUNK,相当于Microsoft中的文件用他来保存一个一个代表实际意义的数据块。多个CHUNK可以用一个LIST组织起来。LIST相当于Microsoft中的目录。一个目录下面可以存放多个文件、子目录。同样,在一个LIST下面可以有几个CHUNK文件或子LIST。而多个CHUNK、LIST又由一个“RIFF头”来统领。在“RIFF头”中记录一个RIFF文件的各种信息。相当于Microsoft中的盘符。

> FOURCC

一个FOURCC(four-character code)是一个占32位四个字节的数据,一般表示为4个ASCII字符。例如:一个FOURCC“abcd”在系统中就表示为 “x64636261”。FOURCC可以包括空格,所以“abc ”也是一个有效的FOURCC,在RIFF文件格式中,用FOURCC代码来标识数据流的格式、数据块的含义及其他信息。

1. 一个CHUNK数据块的数据结构如下:

Chunkid chunkSize  ChunkData

Chunkid是一个FOURCC,表示这个CHUNK记录的是那些内容,相似与Microsoft中的文件名,ChunkSize占用4字节,表示这个CHUNK中数据内容的大小。ChunkData则是这个CHUNK中实质性的东西,保存CHUNK的具体数据内容。一个CHUNK保存的数据可以是关于声音文件的编码方式、音频采样等信息。也可以是声音文件的声音数据。具体表示的是哪类数据则通过CHUNKID来标识。

2. 一个List数据块的数据结构如下:

“LIST“ listSize listType listData

在这里,“LIST”也是一个FOURCC,而且是固定的,每个LIST都是以“list”为开头,标识它是一个LIST,就像在Microsoft文件系统中有一个标志来标识 目录一样。ListType则是这个“目录”的名字,要求是一个FOURCC。listSize占有4字节,表示这个“目录”下保存的数据有多大。而listData则是这个目录下保存的数据,由chunk和list来组成,它们的个数和组成次序是可以不确定的。注意,listSize的值是listType的大小(即4个字节)加上listData的长度。不包括“LIST”和listSize的长度。

3. RIFF文件头是数据结构如下:

“RIFF” fileSize fileType fileData

这里,“RIFF”是一个字符串,也是一个FOURCC,表示是一个RIFF格式文件。fileSize是一个4个字节的数据,给出文件的大小。fileType是一个FOURCC的数据,用来说明的文件类型,如:“WAV”、“AVI”等(WAV、AVI文件都是基于RIFF的文件格式的)。请注意,fileSize表示的文件大小,是不包括“RIFF”和它自己所占的8个字节的,是RIFF文件头后面跟的数据大小再加上fileType的4个字节。FileData部分是用来保存他统领的内容的,可以是LIST也可以是CHUNK。

(以上内容引用自 参考资料【1】RIFF文件浅析)

wav文件格式

wav文件是非常简单的一种RIFF文件,它的格式类型为"WAVE"。RIFF块包含两个子块,这两个子块的ID分别是"fmt"和"data"

字段解析

偏移地址 大小字节 数据块类型 内容 00H~03H 4 4字符 资源交换文件标志(RIFF) 04H~07H 4 长整数 从下个地址开始到文件尾的总字节数 08H~0BH 4 4字符 WAV文件标志(WAVE) 0CH~0FH 4 4字符 波形格式标志(fmt ),最后一位空格。 10H~13H 4 整数 值表示的是后面有一段数据的总长度字节(见下面的实例解析的加粗部分) 14H~15H 2 整数 编码格式,值为1时,表示数据为线性PCM编码 16H~17H 2 整数 声道数,单声道为1,双声道为2 18H~1BH 4 长整数 采样频率,00003E80 H =16000,0000AC44 H = 44100 1CH~1FH 4 长整数 波形数据传输速率(每秒平均字节数),即(比特率) / 8 = (声道数×采样频率×每样本的数据位数) / 8 20H~21H 2 整数 块对齐=声道数×每次采样得到的样本位数 / 8 22H~23H 2 整数 样本数据的位数 或 位深度,16或8 24H~27H 4 4字符 “data”或“LIST”,当其他格式的音频经过格式转换成WAV文件时,这部分会是“LIST” 28H~2BH 4 长整型 如果前面4个字节是data,则这里表示DATA总数据长度字节,2CH开始是音频数据(见实例1);如果前面4个字节不是data,则后面记录一些格式转换的信息(见实例2) 2CH… … DATA数据块

实例

笔者使用UltraEdit编辑器打开wav文件

  1. 标准的wav文件,如图1(小端模式): 图1  标准的wav文件 img

偏移地址 字节 说明 00H ~ 03H 52 49 46 46 资源交换文件标志符RIFF 04H ~ 07H 24 FA 00 00 表示后面文件的大小,小端模式是0000FA24=64036,这个数字加上00H~07H的8个字节,等于64044字节,是wav文件的总大小,在右键“属性–>大小”里显示,如图2 08H ~ 0BH 57 41 56 45 标示符WAVE 0CH ~ 0FH 66 6D 74 20 波形格式标示符fmt 10H ~ 13H 10 00 00 00 小端模式是00000010 = 16,表示后面有一段数据的长度是16个字节,即,从14H到23H 14H ~ 15H 01 00 小端模式是0001 = 1 ,表示线性的PCM编码 16H ~ 17H 01 00 小端模式是0001 = 1 ,表示单声道,MONO 18H ~ 1BH 80 3E 00 00 小端模式是00003E80 = 16000,表示采样率是16000Hz 1CH ~ 1FH 00 7D 00 00 小端模式是00007D00 = 32000 Bps,波形数据传输率,字节每秒 20H ~ 21H 02 00 小端模式是0002 = 2,块对齐 22H ~ 23H 10 00 小端模式是0010 = 16,样本数据的位数,表示用16位表示一个样本 24H ~ 27H 64 61 74 61 标志符data 28H ~ 2BH 00 FA 00 00 小端模式是0000FA00 = 64000,表示data的数据大小是64000 B 2CH ~ … 音频数据

计算细节

波形数据传输率:采样率(16000) × 位深度(16) × 声道数(1) / 8 = 256(kbps) / 8 = 256000(bps) / 8 = 32000 Bps(字节每秒) 块对齐:声道数(1) × 每次采样得到的样本位数(16) / 8 = 2 播放时间 = dataSize字节 / (比特率/ 8) = 64000B / (32000 Bps) = 2秒(图3)

图2 播放时间、文件总大小(64044字节=62.5KB)、比特率 img

  1. 经过格式转换的wav文件,如图3(小端模式): 图3 经过格式转换的wav文件 img

偏移地址 字节 说明 00H ~ 03H 52 49 46 46 资源交换文件标志符RIFF 04H ~ 07H 46 48 09 00 表示后面文件的大小,小端模式是00094846 = 608326B,这个数字加上00H~07H的8个字节,等于608334字节,是wav文件的总大小,在右键“属性–>大小”里显示,如图5 08H ~ 0BH 57 41 56 45 标示符WAVE 0CH ~ 0FH 66 6D 74 20 波形格式标示符fmt 10H ~ 13H 10 00 00 00 小端模式是00000010 = 16,表示后面有一段数据的长度是16个字节,即,从14H到23H 14H ~ 15H 01 00 小端模式是0001 = 1 ,表示线性的PCM编码 16H ~ 17H 02 00 小端模式是0002 = 2 ,表示双声道 18H ~ 1BH 44 AC 00 00 小端模式是0000AC44 = 44100,表示采样率是44100Hz 1CH ~ 1FH 10 B1 02 00 小端模式是0002B110 = 176400 Bps,波形数据传输率,字节每秒 20H ~ 21H 04 00 小端模式是0004 = 4,块对齐 22H ~ 23H 10 00 小端模式是0010 = 16,样本数据的位数,表示用16位表示一个样本 24H ~ 27H 4C 49 53 54 “LIST” 28H ~ 2BH 1A 00 00 00 小端模式是0000001A=26,表示后面有一段数据的长度是26字节,即2CH ~ 45H。 2CH ~ 45H 49 4E 46 4F ~ 30 00 这段数据记录的是格式转换的一些信息,具体没有研究过 46H ~ 49H 64 61 74 61 标志符data 4AH ~ 4DH 00 48 09 00 小端模式是00094800 = 608256,表示data的数据大小是608256 B 4EH ~ … 音频数据

计算细节

波形数据传输率:采样率(44.1k) × 位深度(16) × 声道数(2) / 8 = 1411(kbps) / 8 = 1411200 / 8 = 176400 Bps(字节每秒) 块对齐:声道数(2) × 每次采样得到的样本位数(16) / 8 = 4 播放时间 = dataSize字节 / (比特率/ 8) = 608256 B / (176400 Bps) = 3.4秒(图6)

图6 播放时间、文件总大小(608334字节=594KB)、比特率

代码

在实例分析里我们讲到,如果wav文件是由其他格式的音频文件经过格式转换而来,那么转换后得到的wav文件头会记录一些转换的信息。如果你理解了引用资料和实例2,就会发现wav文件中信息排列是有规律可循的。笔者参考HTK源码,整理了读取wav文件信息的c代码:

(测试的wav文件是PCM编码,单声道,16位深)

void Load(char path[])
{
    FILE *file = fopen(path, "rb");

    if (!file)
    {
        fprintf(stderr, "[Load] [%s not found]\n", path);
        return;
    }

    char magic[4];
    int lng;
    char c;

    // RIFF
    fread(magic, 4, 1, file);
    if (strncmp("RIFF", magic, 4))
    {
        fprintf(stderr, "[Load] [not RIFF]\n");
    }
    //printf("sizeof(int):%d\n", sizeof(int)); // 4
    fread(&lng, 4, 1, file);
    fread(magic, 4, 1, file);
    if (strncmp("WAVE", magic, 4))
    {
        fprintf(stderr, "[Load] [not WAVE]\n");
    }
    /* Look for "fmt " before end of file */
    while (1)
    {
        if (feof(file))
        {
            fprintf(stderr, "[Load] No data portion in WAVE file");
        }
        fread(magic, 4, 1, file);// fmt 或 data
        fread(&length_wav, 4, 1, file);// chunk size
        if (strncmp("data", magic, 4) == 0) break;
        if (strncmp("fmt ", magic, 4) == 0)
        {
            fread(&wave->FormatType, 2, 1, file); // 十进制是1 线性的PCM编码
            fread(&wave->Channels, 2, 1, file); /* Number of Channels */
            fread(&wave->SamplesPerSec, 4, 1, file); /* Sample Rate */
            fread(&wave->AvgBytesPerSec, 4, 1, file); /* Average bytes/second */
            fread(&wave->BlockAlign, 2, 1, file); /* Block align */
            fread(&wave->BitsPerSample, 2, 1, file); // 样本数据的位数 = 16
            if (wave->BitsPerSample != 16 && wave->BitsPerSample != 8)
            {
                fprintf(stderr, "[Load] Only 8/16 bit audio supported");
            }
            length_wav -= 16;
        }
        /* Skip chunk */
        for (; length_wav > 0; length_wav--) fread(&c, 1, 1, file);
    }
    // 循环结束后,length_wav的值就是声音数据的长度
    data = (unsigned char*)realloc(data, length_wav);
    fread(data, length_wav, 1, file);

    fclose(file);
}

参考资料

【1】RIFF文件浅析(百度文库,豆丁网均有这篇文章)

【2】WAVE PCM声音文件格式

【3】WAV文件格式分析(这篇是转载,在2009年,最早的原创文章找不到了)

【4】wav文件详解

【5】音频码率及大小计算

【6】HTK源码 HWave.c文件 GetWAVHeaderInfo函数

感谢这些优秀的前辈!