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

  • 转载请注明出处,谢谢您!https://blog.csdn.net/ljrsunshine/article/details/89320026
  • 对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~03H44字符资源交换文件标志(RIFF)
04H~07H4长整数从下个地址开始到文件尾的总字节数
08H~0BH44字符WAV文件标志(WAVE)
0CH~0FH44字符波形格式标志(fmt ),最后一位空格。
10H~13H4整数值表示的是后面有一段数据的总长度字节(见下面的实例解析的加粗部分)
14H~15H2整数编码格式,值为1时,表示数据为线性PCM编码
16H~17H2整数声道数,单声道为1,双声道为2
18H~1BH4长整数采样频率,00003E80 H =16000,0000AC44 H = 44100
1CH~1FH4长整数波形数据传输速率(每秒平均字节数),即(比特率) / 8 = (声道数×采样频率×每样本的数据位数) / 8
20H~21H2整数块对齐=声道数×每次采样得到的样本位数 / 8
22H~23H2整数样本数据的位数 或 位深度,16或8
24H~27H44字符“data”或“LIST”,当其他格式的音频经过格式转换成WAV文件时,这部分会是“LIST”
28H~2BH4长整型如果前面4个字节是data,则这里表示DATA总数据长度字节,2CH开始是音频数据(见实例1);如果前面4个字节不是data,则后面记录一些格式转换的信息(见实例2
2CH…DATA数据块

实例

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

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

标准的wav文件

图1  标准的wav文件
图2  wav文件“属性-->大小”截图

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

图4 经过格式转换的wav文件
图5 wav文件“属性-->大小”截图

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

  • 点赞是一种鼓励!
  • 32
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要将WAV音频文件转换为C语言代码程序,你可以按照以下步骤进行: 1. 首先,你需要了解WAV音频文件结构和格式。WAV是一种标准的音频文件格式,它由头部信息和音频数据组成。头部信息包含文件标识符、文件大小、音频格式等。音频数据是以二进制形式存储的实际音频样本。 2. 接下来,你可以使用C语言的文件操作函数来读取WAV文件并提取音频数据。你可以使用fopen函数打开WAV文件,然后使用fread函数逐步读取文件内容。请注意,在读取音频数据之前,你需要跳过头部信息部分。 3. 读取音频数据后,你可以将其存储在C语言的数组变量中。你需要根据音频数据的采样位数和声道数来确定数组的数据类型和长度。例如,如果音频数据是16位整数型,且单声道,你可以定义一个short类型的一维数组来存储。 4. 接下来,你可以利用C语言的文件操作函数,将提取的音频数据写入到一个新的C语言代码文件中。你需要使用fprintf函数来写入C语言的数据格式,以便能够在程序中直接使用。你可以将音频数据分为一定的段落,并使用循环结构来实现。 5. 最后,你可以保存并编译这个C语言代码文件。在编译成功后,你就可以使用该程序来下载并处理WAV音频文件了。 总结:将WAV音频文件转换为C语言代码程序需要你了解WAV文件的结构和格式,并熟悉C语言的文件操作函数。你需要读取WAV文件的音频数据并存储在C语言的数组变量中,然后将其写入到一个新的C语言代码文件中。完成后,你可以保存并编译这个C语言代码文件,然后使用该程序来下载和处理WAV音频文件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值