C++解析wav
略游 人气:2一、前言
一开始本来在网上找代码,不过改了好几个都不是很好用。因为很多wav文件的fmt块后面并不是data块,经常还带有其他块,正确的方法应该是按MSDN的方法,找到data块再读取。
二、接口
最后接口如下:
class AudioReader { public: struct PCM { int _numChannel;//通道数 1,2 AL_FORMAT_MONO8,AL_FORMAT_STEREO8 int _bitPerSample;//采样数 8,16 byte* _data; size_t _size; size_t _freq;//采样率 void Delete() { delete[] _data; } }; static bool ReadWAV(string_view path_name, PCM& pcm); };
三、具体步骤
打开文件,这里就是普通的文件流,按二进制、只读打开文件即可:
ifstream ifs; if (!g_file->GetFile(path_name, ifs)) { debug_err(format("打开文件失败:{}", path_name)); return false; }
查找riff块:
uint32_t dwChunkSize; uint32_t dwChunkPosition; //查找riff块 FindChunk(ifs, fourccRIFF, dwChunkSize, dwChunkPosition); uint32_t filetype; ReadChunkData(ifs, &filetype, sizeof(uint32_t), dwChunkPosition); if (filetype != fourccWAVE) { debug_err(format("匹配标记失败(fourccWAVE):{}", path_name)); return false; }
其中fourccRIFF和fourccWAVE是我们定义的标记,也就是处理了下大小端,如下:
#ifdef DND_ENDIAN_BIG #define fourccRIFF 'RIFF' #define fourccDATA 'data' #define fourccFMT 'fmt ' #define fourccWAVE 'WAVE' #define fourccXWMA 'XWMA' #define fourccDPDS 'dpds' #endif #ifdef DND_ENDIAN_LITTLE #define fourccRIFF 'FFIR' #define fourccDATA 'atad' #define fourccFMT ' tmf' #define fourccWAVE 'EVAW' #define fourccXWMA 'AMWX' #define fourccDPDS 'sdpd' #endif
而FindChunk和ReadChunkData两个函数,分别是查找一个块,和读取一个块。代码实现有点长,可以参考后面我给出的完整源码。
接着,查找并读取fmt块,这个块描述了wav文件的音频属性,结构如下(部分字段会用到):
//16字节 struct WAVEFormat { int16_t audioFormat; int16_t numChannels; int32_t sampleRate; int32_t byteRate; int16_t blockAlign; int16_t bitsPerSample; };
//查找fmt块 if (!FindChunk(ifs, fourccFMT, dwChunkSize, dwChunkPosition)) { debug_err(format("查找块失败(fourccFMT):{}", path_name)); return false; } //读wave信息 WAVEFormat wave_format; if (!ReadChunkData(ifs, &wave_format, dwChunkSize, dwChunkPosition)) { debug_err(format("读取块失败(wave_format):{}", path_name)); return false; };
接下来查找data块,根据返回的大小分配内存:
//查找音频数据 if (!FindChunk(ifs, fourccDATA, dwChunkSize, dwChunkPosition)) { debug_err(format("查找块失败(fourccDATA):{}", path_name)); return false; }; pcm._data = new byte[dwChunkSize];
然后读取data块,将数据读取到我们分配的内存pcm._data。然后记录下一些重要的字段。由于OpenaAL不能直接播放32位(只8、16)的数据,这里简单返回失败。
if (!ReadChunkData(ifs, pcm._data, dwChunkSize, dwChunkPosition)) { debug_err(format("读取块失败(pcm数据):{}", path_name)); pcm.Delete(); return false; }; pcm._size = dwChunkSize; pcm._numChannel = wave_format.numChannels; pcm._bitPerSample = wave_format.bitsPerSample; pcm._freq = wave_format.sampleRate; if (pcm._bitPerSample == 32) { debug_err(format("不支持32位:{}", path_name)); pcm.Delete(); return false; } return true;
四、完整源码
可以此处获取最新的源码(我将来会添加ogg格式的解析),也可以用下面的:传送门
//.h class AudioReader { public: struct PCM { int _numChannel;//通道数 1,2 AL_FORMAT_MONO8,AL_FORMAT_STEREO8 int _bitPerSample;//采样数 8,16 byte* _data; size_t _size; size_t _freq;//采样率 void Delete() { delete[] _data; } }; static bool ReadWAV(string_view path_name, PCM& pcm); }; //16字节 struct WAVEFormat { int16_t audioFormat; int16_t numChannels; int32_t sampleRate; int32_t byteRate; int16_t blockAlign; int16_t bitsPerSample; }; //.cpp #ifdef DND_ENDIAN_BIG #define fourccRIFF 'RIFF' #define fourccDATA 'data' #define fourccFMT 'fmt ' #define fourccWAVE 'WAVE' #define fourccXWMA 'XWMA' #define fourccDPDS 'dpds' #endif #ifdef DND_ENDIAN_LITTLE #define fourccRIFF 'FFIR' #define fourccDATA 'atad' #define fourccFMT ' tmf' #define fourccWAVE 'EVAW' #define fourccXWMA 'AMWX' #define fourccDPDS 'sdpd' #endif bool FindChunk(ifstream& ifs, uint32_t fourcc, uint32_t& size, uint32_t& pos) { bool ret = true; ifs.seekg(0); if (ifs.fail()) return false; uint32_t dwChunkType; uint32_t dwChunkDataSize; uint32_t dwRIFFDataSize = 0; uint32_t dwFileType; uint32_t bytesRead = 0; uint32_t dwOffset = 0; while (ret) { ifs.read((char*)&dwChunkType, sizeof(uint32_t)); ifs.read((char*)&dwChunkDataSize, sizeof(uint32_t)); switch (dwChunkType) { case fourccRIFF: dwRIFFDataSize = dwChunkDataSize; dwChunkDataSize = 4; ifs.read((char*)&dwFileType, sizeof(uint32_t)); break; default: ifs.seekg(dwChunkDataSize, std::ios::cur); if (ifs.fail()) return false; break; } dwOffset += sizeof(uint32_t) * 2; if (dwChunkType == fourcc) { size = dwChunkDataSize; pos = dwOffset; return true; } dwOffset += dwChunkDataSize; if (bytesRead >= dwRIFFDataSize) return false; } return true; } bool ReadChunkData(ifstream& ifs, void* buffer, uint32_t size, uint32_t pos) { ifs.seekg(pos); if (ifs.fail()) return false; ifs.read((char*)buffer, size); return true; } bool AudioReader::ReadWAV(string_view path_name, PCM& pcm) { ifstream ifs; if (!g_file->GetFile(path_name, ifs)) { debug_err(format("打开文件失败:{}", path_name)); return false; } uint32_t dwChunkSize; uint32_t dwChunkPosition; //查找riff块 FindChunk(ifs, fourccRIFF, dwChunkSize, dwChunkPosition); uint32_t filetype; ReadChunkData(ifs, &filetype, sizeof(uint32_t), dwChunkPosition); if (filetype != fourccWAVE) { debug_err(format("匹配标记失败(fourccWAVE):{}", path_name)); return false; } //查找fmt块 if (!FindChunk(ifs, fourccFMT, dwChunkSize, dwChunkPosition)) { debug_err(format("查找块失败(fourccFMT):{}", path_name)); return false; } //读wave信息 WAVEFormat wave_format; if (!ReadChunkData(ifs, &wave_format, dwChunkSize, dwChunkPosition)) { debug_err(format("读取块失败(wave_format):{}", path_name)); return false; }; //查找音频数据 if (!FindChunk(ifs, fourccDATA, dwChunkSize, dwChunkPosition)) { debug_err(format("查找块失败(fourccDATA):{}", path_name)); return false; }; pcm._data = new byte[dwChunkSize]; if (!ReadChunkData(ifs, pcm._data, dwChunkSize, dwChunkPosition)) { debug_err(format("读取块失败(pcm数据):{}", path_name)); pcm.Delete(); return false; }; pcm._size = dwChunkSize; pcm._numChannel = wave_format.numChannels; pcm._bitPerSample = wave_format.bitsPerSample; pcm._freq = wave_format.sampleRate; if (pcm._bitPerSample == 32) { debug_err(format("不支持32位:{}", path_name)); pcm.Delete(); return false; } return true; }
加载全部内容