带你分析wav音频文件结构(实例+代码)

  • 转载请注明出处,谢谢您!https://blog.csdn.net/ljrsunshine/article/details/89320026
  • 对wav文件的一些探索,与大家一起学习

文章目录

      • RIFF的组织结构
      • wav文件格式
      • 实例
        • 1. 标准的wav文件,如图1(小端模式):
          • 计算细节
        • 2. 经过格式转换的wav文件,如图4(小端模式):
          • 计算细节
      • 代码
      • 参考资料

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(小端模式):

带你分析wav音频文件结构(实例+代码)_第1张图片

图1  标准的wav文件
带你分析wav音频文件结构(实例+代码)_第2张图片
图2  wav文件“属性-->大小”截图

偏移地址 字节 说明
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)
图3 播放时间、文件总大小(64044字节=62.5KB)、比特率

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

带你分析wav音频文件结构(实例+代码)_第3张图片

图4 经过格式转换的wav文件
带你分析wav音频文件结构(实例+代码)_第4张图片
图5 wav文件“属性-->大小”截图

偏移地址 字节 说明
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函数

感谢这些优秀的前辈!

2019.03.22

  • 点赞是一种鼓励!

你可能感兴趣的:(语音处理)