WAVE 文件格式的一些小小心得

之前要用 DirectSound 8 编写一个音频播放功能,然后 DirectSound 自身并不支持音频文件的解码操作,只支持最原始的音频采样数据操作,WAVE 文件在没有压缩之前就是这种原始的音频采样数据,而且博主很懒不喜欢学各种各样的音频库,只好自己动手写一个简易的 WAVE 文件读取功能,一开始是照抄书上代码的,而且也能够运行,直到我播放一个用转码工具转码后的非标准 WAVE 文件时,我发现出现问题了,手动调试跟进去一看,读取的音频数据大小与真实音频数据大小相差甚远,随后网上各种翻看 WAVE 文件格式,最后终于搞定;

WAVE 文件以 Chunk 作为单位来组织文件内容,每一个 Chunk 都包含两个必须的数据成员,分别是 Chunk Sig 和 Chunk Size,这两个数据成员组成一个 Chunk 的头部,随后就是 Chunk 自身的数据内容,我们看一看 Chunk 头的定义方式:

// chunk header
struct D3D9WAVECHUNKHEADER {
	char sig[ 4 ];
	int chunk_size;
};

不同的 Chunk,其自身的数据内容以及数据大小都是不相同的,当然也有可能有两个 Chunk 大小相同,但是内容必定不同,因为 WAVE 文件就是根据 Chunk 的 sig 来识别不同的 Chunk;

而一个标准的 WAVE 文件,都必须以一个 RIFF Chunk、FMT Chunk 开头,并且以 DATA Chunk 结尾,标准的 WAVE 文件格式如下:

-------------------------------

<RIFF Chunk Header>

RIFF Chunk Data ...

<FMT Chunk Header>

FMT Chunk Data ...

<DATA Chunk Header>

DATA Chunk Data ...

-------------------------------

很多教你读取 WAVE 格式的文章都会给你下面这个数据结构,让你直接一次性读取 RIFF Chunk、FMT Chunk 以及 Data Chunk 的头部以及 Chunk 数据,比方说《DirectX 角色扮演游戏编程》这本教材(注意,对于读取 WAVE 数据,下面这个数据结构是不标准的):

WAVE 文件格式的一些小小心得_第1张图片

对于标准的 WAVE 文件,的却遵循这个数据结构,但是一旦你用了一些第三方转码工具把某个音频文件转换成 WAVE 格式时,大部分转码工具转出来的 WAVE 文件,都不遵循这个标准,因为它们需要在自己创建的 WAVE 文件里面写入一些自定义数据,不同的转码工具写入的自定义数据都不尽相同,比方说博主用 FormatFactory 把一个 mp3 转成 wav 时,就会在 FMT Chunk 和 DATA Chunk 之间插入一个 LIST Chunk,而且也有人会告诉你一些转码工具会帮你生成一个 FACT Chunk,用来描述压缩后的音频数据信息等等;

那么怎么从众多 Chunk 之中,快速找到我们需要的 DATA Chunk 呢,读者用了一个很简单的方法,如下所示:

// chunk header
struct D3D9WAVECHUNKHEADER {
	char sig[ 4 ];
	int chunk_size;
};

// 'RIFF' chunk
struct D3D9WAVERIFFCHUNK {
	char wave_sig[ 4 ];	// 'WAVE'
};

// 'fmt ' chunk
struct D3D9WAVEFMTCHUNK {
	short format_tag; // WAVE_FORMAT_PCM
	short channel; // 1、2
	int sample_rate; // 11025hz、22050hz、44100hz
	int bytes_per_sec; // sample_rate * block_align
	short block_align; // channel * ( bits_per_sample / 8 )
	short bits_per_sample; // 8、16
};

D3D9WAVECHUNKHEADER chunk_hdr = { 0 };
D3D9WAVERIFFCHUNK riff_chunk = { 0 };
D3D9WAVEFMTCHUNK fmt_chunk = { 0 };
WORD cbSize = 0;
DWORD wave_data_size = 0;

// riff chunk
::fread( & chunk_hdr, 1, sizeof( chunk_hdr ), wave_file );
::fread( & riff_chunk, 1, sizeof( riff_chunk ), wave_file );

// fmt chunk
::fread( & chunk_hdr, 1, sizeof( chunk_hdr ), wave_file );
::fread( & fmt_chunk, 1, sizeof( fmt_chunk ), wave_file );

// for WAVEFORMATEX's cbSize member ( 2 bytes )
if( chunk_hdr.chunk_size > 16 ){
    ::fread( & cbSize, 2, 1, wave_file );
}

// find data chunk
while( true ){
	::fread( & chunk_hdr, 1, sizeof( chunk_hdr ), wave_file );
	if( 0 == ::memcmp( "data", chunk_hdr.sig, 4 ) ){
		wave_data_size = chunk_hdr.chunk_size;
		break;
	}
	::fseek( wave_file, chunk_hdr.chunk_size, SEEK_CUR );
}

// 接下来的都是音频数据,音频数据的大小为 wave_data_size 个字节(Byte)

chunk_hdr 代表一个 Chunk 的头部(Header),WAVE 文件开头两个 Chunk 必须是 RIFF Chunk 和 FMT Chunk,这点大家可以放心去读,如果读出来的数据较检失败,那么所读取的绝对不是 WAVE 文件,有一点需要注意的是,根据 MSDN 里面的说明,新式的 FMT Chunk 里面其实封装了 WAVEFORMATEX 结构体,而老式的 FMT Chunk 里面封装的则是 PCMWAVEFORMAT 结构体,两个结构体之间唯一的不同之处在于 EX 版的结构体多出一个 cbSize 数据成员,这个成员大小为 2 Byte,也就是一个 WORD 大小,所以我们要检查读取的 FMT Chunk 的 chunk_size 数值,作为扩展,大家可以参考一下出自 MSDN 的下面这张图片:

WAVE 文件格式的一些小小心得_第2张图片

图中 WAVEFORMATEXTENSIBLE 主要用于高于 2 Channel 以及更高的 Sample Rate 的 WAVE 文件,一般我们不会接触到高于 2 声道以及高于 44100Hz 取样率的音频数据,特别是做游戏,这简直就是浪费硬件资源(笑);

完了,我意思是,我的心得就是这些了,毕竟是个人心得,不是详尽的教材,所以关于更多的 WAVE 格式介绍,博主我还是直接推荐大家看官方文档,或者 MSDN,可以少走很多弯路(笑);

你可能感兴趣的:(WAVE 文件格式的一些小小心得)