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<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进行绘制显示。也可以自己绘制。

至于chartdirector中图谱绘制的代码不再贴出,比较简单了。

运行示例:

要使用Python绘制wav频谱图,你可以使用scipy库和pylab库来实现。下面是一个参考代码,你可以根据自己的需要进行调整: ```python import wave import struct import numpy from scipy import * from pylab import * def Plot_fft_freq_chart(filename, plot=False): wavefile = wave.open(filename, 'r') # 打开wav文件 nchannels = wavefile.getnchannels() # 获取声道数 sample_width = wavefile.getsampwidth() # 获取采样宽度 framerate = wavefile.getframerate() # 获取采样率 numframes = wavefile.getnframes() # 获取帧数 print("channel", nchannels) print("sample_width", sample_width) print("framerate", framerate) print("numframes", numframes) y = numpy.zeros(numframes) for i in range(numframes): val = wavefile.readframes(1) left = val = v Fs = framerate try: data, freqs, bins, im = specgram(y, NFFT=1024, Fs=Fs, noverlap=900) mm = data<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [使用python绘制wav 音频文件频谱图](https://blog.csdn.net/es15071848238/article/details/121827343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [使用python绘制音频的时频图、频谱图和MFCC特征图](https://blog.csdn.net/fuzekun/article/details/129112730)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值