承接上一篇文章,要对取出的PCM数据进行处理还原,做傅里叶变换(这里采用FFT),如果对傅里叶变换不了解的同学,可以参见一下:https://blog.csdn.net/qq_36568418/article/details/89467717 。有个大致的了解即可,毕竟我们不是专门做数学的。
对于FFT算法库,我网上有很多开源的库可以参考,这里我也用了一个开源库地址:
https://download.csdn.net/download/qq_36568418/11237568
METER_FREQUENCY中为音频频率值
//HZ范围
const int METER_FREQUENCY[] = { 30, 60, 100, 160, 240, 300, 350, 400, 440, 500, 600, 800, 1000, 1500, 2000, 2600, 3000, 4000, 6000, 8000, 10000, 14000, 16000,18000,20000,24000 };
const int NUM_FREQUENCY = sizeof(METER_FREQUENCY)/sizeof(int);
const double FFT_SPEED = 0.06;
//计算缓冲区大小
m_nBufferSize = FFT::NextPowerOfTwo( static_cast(fmt.nAvgBytesPerSec * FFT_SPEED) );
//计算每个声道所占的数据量大小
m_nNumSamples = m_nBufferSize / fmt.nBlockAlign;
m_ReadBuf = new unsigned char[m_nBufferSize];//计算每次读取的数据大小
m_RealIn_RT.resize( m_nNumSamples );
m_RealIn_LT.resize( m_nNumSamples );
m_RealOut.resize( m_nNumSamples );
m_ImagOut.resize( m_nNumSamples );
m_Ampl.resize( m_nNumSamples );
m_MeterData.resize(NUM_FREQUENCY);
int m_Tim = data.WdSize/m_nBufferSize;
for(int i = 0; i < m_Tim ;i++)
{
memset(m_ReadBuf,0,m_nBufferSize);
memcpy(m_ReadBuf,data.Wdbuf+i*m_nBufferSize,m_nBufferSize);
if(GetAudioData(m_ReadBuf,m_nBufferSize,fmt))//取数据
{
//进行FFT计算
FFT::Compute(m_nNumSamples, &m_RealIn_RT[0], NULL, &m_RealOut[0], &m_ImagOut[0]);
size_t index = 0;
// 跳过一半的镜像数据
FFT::Norm(m_nNumSamples/2, &m_RealOut[0], &m_ImagOut[0], &m_Ampl[0]); //复数模 - 为幅值
double maxAmpl = (fmt.wBitsPerSample == 8) ? (127.0*127.0) : (32767.0*32767.0); //可以表示的幅值范围
int centerFreq = static_cast(fmt.nSamplesPerSec/2);//类型转换为int 采样频率是实际样本的两倍
for(int i=0; i < NUM_FREQUENCY; ++i)
{
if ( METER_FREQUENCY[i] > centerFreq )//频率大于样本频率
m_MeterData[i] = 0;
else
{ // 假设采样频率为Fs,采样点数为N,做FFT之后,某一点n(n从1开始)表示的频率为:Fn=(n-1)*Fs/N;
int indice = static_cast( METER_FREQUENCY[i] * m_nNumSamples / fmt.nSamplesPerSec );//计算该频率的下标
int value = static_cast( 20.0*log10(m_Ampl[indice]/maxAmpl) ); //求分贝值 //用方根计算能量值 20log10(幅值/幅值范围)
m_MeterData[i] = value;
}
}
//取分贝数据范围0-100
SetData(&m_MeterData[0],NUM_FREQUENCY);
//显示分贝数据到图谱
//updatechart(true, false);
m_nNumSamples = 0;
Sleep(100);
}
}
bool CTest::GetAudioData(const unsigned char* pbData, unsigned int cbSize, const WAVE_FORMAT& wfmt)//处理音频数据
{
bool samplesReady = false;
switch(wfmt.wBitsPerSample) //采样精度
{
case 8: //8位取样精度 无符号 0-255 实际范围应为-127~128
{
if ( wfmt.nChannels == 1 ) // 单声道
{
for (size_t i = 0; i < cbSize; ++i)
{
m_RealIn_RT[i] = static_cast((pbData[i] - 128) << 6);// Out = (In-128)*64
m_RealIn_LT[i] = m_RealIn_RT[i];
}
m_nNumSamples = cbSize;
}
else if ( wfmt.nChannels == 2 ) // 立体声道 有左右两个声道
{
size_t Samples = cbSize >> 1; //除以2
for (size_t i = 0, j=0; i < Samples; ++i, j+=2)
{
m_RealIn_RT[i] = static_cast((pbData[j] - 128) << 6); // Out = (In-128)*64
m_RealIn_LT[i] = static_cast((pbData[j+1] - 128) <<6); // Out = (In-128)*64
}
m_nNumSamples = Samples;
}
samplesReady = (m_nNumSamples != 0);
}
break;
case 16:
{
const short *pfData = reinterpret_cast(pbData); //指针类型转换 把 char 转为 short
if ( wfmt.nChannels == 1 ) //仅有一个声道
{
size_t Samples = cbSize >> 1; //除以2
for (size_t i = 0; i < Samples; ++i)
{
m_RealIn_RT[i] = static_cast(pfData[i]);
m_RealIn_LT[i] = m_RealIn_RT[i];
}
m_nNumSamples = Samples;
}
else if ( wfmt.nChannels == 2 ) // stereo //立体声道 有左右两个通道
{
size_t Samples = cbSize >> 2; //除以4
for (size_t i = 0, j=0; i < Samples; ++i, j+=2)
{
m_RealIn_RT[i] = static_cast( pfData[j] );
m_RealIn_LT[i] = static_cast( pfData[j+1] );
}
m_nNumSamples = Samples;
}
samplesReady = (m_nNumSamples != 0);
}
break;
default:
assert( false ); // not supported
break;
}
return samplesReady;
}
计算完成后的分贝值还需进行取值操作,范围限定于0-100之间。
bool CTest::SetData(const int ArrayValue[], int nSize)
{
if(ArrayValue == NULL)
return false;
memset(m_data,0,sizeof(double));
int Value = 0;
for(int i=0; i < nSize; i++)
{
Value = __min(ArrayValue[i], 100);//取0-100 之间得数
Value = __max(Value, 0);
m_data[i] = Value;
}
return true;
}
如此计算完成后的分贝值存放于m_data 中,即可采用chartdirector进行绘制显示。也可以自己绘制。
至于chartdirector中图谱绘制的代码不再贴出,比较简单了。
运行示例: