WAVE绘制频谱图(三)——PCM数据处理以及图谱显示

 

承接上一篇文章,要对取出的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中图谱绘制的代码不再贴出,比较简单了。

运行示例:

WAVE绘制频谱图(三)——PCM数据处理以及图谱显示_第1张图片

你可能感兴趣的:(音视频)