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

{

https://blog.csdn.net/qq_36568418/article/details/91536032?spm=1001.2014.3001.5502

}

 

{

承接上一篇文章,要对取出的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<size_t>(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<float>(m_nNumSamples, &m_RealIn_RT[0], NULL, &m_RealOut[0], &m_ImagOut[0]);
            size_t index = 0;
            // 跳过一半的镜像数据
            FFT::Norm<float>(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<int>(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<int>( METER_FREQUENCY[i] * m_nNumSamples / fmt.nSamplesPerSec );//计算该频率的下标
                    int value  = static_cast<int>( 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<float>((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<float>((pbData[j] - 128) << 6); // Out = (In-128)*64
                        m_RealIn_LT[i] = static_cast<float>((pbData[j+1] - 128) <<6); // Out = (In-128)*64
                    }
                    m_nNumSamples = Samples;
                }
                samplesReady = (m_nNumSamples != 0);
            }
            break;
        case 16:
            {
                const short *pfData = reinterpret_cast<const short*>(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<float>(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<float>( pfData[j] );
                        m_RealIn_LT[i] = static_cast<float>( 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进行绘制显示。也可以自己绘制。

}

posted @ 2022-12-21 16:25  YZFHKMS-X  阅读(396)  评论(0编辑  收藏  举报