WAVE绘制频谱图(二)——WAVE文件解析提取PCM数据
{
https://blog.csdn.net/qq_36568418/article/details/91530563?spm=1001.2014.3001.5502
}
{
为了不太多依赖于平台环境,音频数据提取部分尽量都用c++实现。
话不多说,直接还是上代码,代码比较详细不再赘述。
WAVE格式头结构体:
//RIFF头的宏定义缩写,便于后面的比较 低字节-》高字节
#define ID_RIFF mmioFOURCC('R', 'I', 'F', 'F')
#define ID_WAVE mmioFOURCC('W', 'A', 'V', 'E')
#define ID_data mmioFOURCC('d', 'a', 't', 'a')
#define ID_fmt mmioFOURCC('f', 'm', 't', '\x20')
#define ID_fact mmioFOURCC('f', 'a', 'c', 't')
//WAVE文件一般有四种块,它们依次是:RIFF块、格式块、附加块(可选),数据块
struct RIFFFORMAT{//长度12
unsigned long Ckid; //RIFF标识
unsigned long FileSize; //文件大小
unsigned long FccType; //WAVE标志
};
//WAV音频头
struct WAVE_FORMAT
{
unsigned long Ckid; //fmt
unsigned long CkSize; //块大小
unsigned short wFormatTag;//音频格式一般为WAVE_FORMAT_PCM
unsigned short nChannels;//采样声道数
unsigned long nSamplesPerSec;//采样率
unsigned long nAvgBytesPerSec;//每秒字节数 采样率*采样精度/8(字节位数)
unsigned short nBlockAlign;//块大小 采样字节*声道数
unsigned short wBitsPerSample;//采样精度 采样精度/8 = 采样字节
unsigned short cbSize; //预留字节 一般为0扩展域有的没有
};
//wave数据块
struct WAVE_DATA{
unsigned long Wdid; //data 标志
unsigned long WdSize; //块大小
unsigned char* Wdbuf; //数据指针 有符号
};
读取PCM文件:
char* m_path = "../test.wav";
//音频头文件信息
RIFFFORMAT head;//RIFF头
WAVE_FORMAT fmt;//格式块
WAVE_DATA data;//数据块
fstream fs;
fs.open(m_path, std::ios::binary | std::ios::in);
fs.read((char*)&head, sizeof(RIFFFORMAT));
if(head.Ckid != ID_RIFF||head.FccType!=ID_WAVE) //不是wave格式
{
fs.close();
return;
}
fs.read((char*)&fmt, 24); //读取格式块
if(fmt.Ckid != ID_fmt||fmt.wFormatTag != WAVE_FORMAT_PCM)//只处理pcm 数据
{
fs.close();
return;
}
if(fmt.CkSize == 18) //说明含有保留字节
{
fs.read((char*)&(fmt.cbSize), 2);
}else
{
fmt.cbSize = 0; //不含有则置位0
}
//以下标志位有的wave包含fact 有的不包含 需要区分
unsigned long Sign;
fs.read((char*)&Sign, 4);
if(Sign == ID_fact) //如果有fact块则跳过
{
fs.seekp(4,ios::cur);//从当前位置跳过4个字节
fs.read((char*)&Sign, 4);//再读取
}
if(Sign == ID_data)//读取到data数据块
{
data.Wdid = Sign;
fs.read((char*)&(data.WdSize), 4);//数据块大小
data.Wdbuf = new unsigned char[data.WdSize];
fs.read((char*)data.Wdbuf, data.WdSize);//数据读取
fs.close(); //读取完成关闭文件
}
上述代码部分设计RIFF头比较,才用了Windows中的 mmioFOURCC()包含于 <MMSystem.h>文件中,当然也可以自己实现
实现过程如下:
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ))
一个简单的位操作,过程不复杂。
如上操作就已经把PCM数据取出来存放在data.wdbuf中了。
}