目录
- 一、前言
- 二、接口
- 三、具体步骤
- 四、完整源码
一、前言
一开始本来在网上找代码,不过改了好几个都不是很好用。因为很多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;
}