之前要用 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 文件,的却遵循这个数据结构,但是一旦你用了一些第三方转码工具把某个音频文件转换成 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 的下面这张图片:
图中 WAVEFORMATEXTENSIBLE 主要用于高于 2 Channel 以及更高的 Sample Rate 的 WAVE 文件,一般我们不会接触到高于 2 声道以及高于 44100Hz 取样率的音频数据,特别是做游戏,这简直就是浪费硬件资源(笑);
完了,我意思是,我的心得就是这些了,毕竟是个人心得,不是详尽的教材,所以关于更多的 WAVE 格式介绍,博主我还是直接推荐大家看官方文档,或者 MSDN,可以少走很多弯路(笑);