- ifft(outFFTData, g_fft_temp, inFFTData, g_twiddle_ifft, twiddle_stride, F_WLEN);
-
- //思考ifft输出的复数结果怎么给到硬件输出
之前一直关注FFT后信号奔赴到频域处理,那么频域处理完后,怎么重建信号呢?
给出一段FFT和IFFT函数源码
-
- int nFrameRunCount = 0;
-
- #pragma section("ss_fw_code_fast")
- void ToneChip_TestFFT_Process(float *Din, float *Dout, int nLen)
- {
- float freqResol; //频点间隔,单位:Hz
- int i = 0;
- int j = 0;
- int k = 0;
- float *pDataIn = (float *)Din;
- float *pDataOut = (float *)Dout;
- int nDataInLen = nLen;
- int twiddle_stride = 1;
- float amp = 0.0f; //幅值
-
- float n2r = 2.0 / F_WLEN; //计算幅值时,相乘
- complex_float *inFFTData = (complex_float *)g_fft_input;
- complex_float *outFFTData = (complex_float *)g_fft_output;
- float *inFrameData = (float * )g_inFrameData;
- float fAmpMax = 0.0;
-
- freqResol = (SAMPLING_RATE_48K * 1.0) / (F_WLEN * 1.0); //得到频率分辨率
- memset(inFFTData, 0, sizeof(complex_float) * (F_WLEN) );
- memset(outFFTData, 0, sizeof(complex_float) * (F_WLEN) );
-
- // NHS处理帧旧数据的移动(最老的数据放在inFrameData[0],最新的数据放在了inFrameData[F_WLEN])
- fft_sp_blk_move((float *)&inFrameData[nDataInLen], (float *)inFrameData, (F_WLEN - nDataInLen));
-
- // 用新采样的数据更新NHS处理帧的sampleNumber个长度空间
- fft_sp_blk_move((float *)pDataIn, (float *)&inFrameData[F_WLEN - nDataInLen], nDataInLen);
- nFrameRunCount++;
-
- //前面4帧就不处理
- if(nFrameRunCount < F_WLEN / nDataInLen)
- {
- return;
- }
-
- //2021年10月29日在加窗前对数据进行一次overlap
- for(i = 0; i < F_WLEN; i++)
- {
- inFFTData[i].re = inFrameData[i] * (g_win[i]); //数据加窗hanning
- inFFTData[i].im = 0.0f;
- }
-
- //在ADSP21489中调用FFT库函数
- cfft(inFFTData, g_fft_temp, outFFTData, g_twiddle_fft, twiddle_stride, F_WLEN);
- //如果inFFTData可以数据是临时性可以被重写,那么可以把g_fft_temp省出来
- // cfft(inFFTData, NULL, outFFTData, g_twiddle_fft, twiddle_stride, F_WLEN);
-
- fAmpMax = -F_FLT_MAX;
- //计算频点的功率
-
- for(i = 1; i < F_WLEN / 2; i++)
- {
- amp = sqrtf(outFFTData[i].re * outFFTData[i].re + outFFTData[i].im * outFFTData[i].im );
- amp = amp * n2r * 2.0;
-
- if (fAmpMax < amp)
- {
- fAmpMax = amp; //找出频点中最大的值
- k = i;
- }
- }
-
- cfft_mag(outFFTData, g_fftSpectrum, F_WLEN);
-
- memset(inFFTData, 0, sizeof(complex_float) * F_WLEN);
- memset(g_fft_temp, 0, sizeof(complex_float) * F_WLEN);
- twiddle_stride = 1;
- ifft(outFFTData, g_fft_temp, inFFTData, g_twiddle_ifft, twiddle_stride, F_WLEN);
-
- //思考ifft输出的复数结果怎么给到硬件输出
- for(i = 0; i < nLen; i++)
- {
- pDataOut[i] = inFFTData[i].re; //重建信号,只取实部
- // pDataOut[i] = pDataIn[i]; //直通测试
- }
-
- }
原始波形
经过fft和iff处理后上述代码输出的波形
输出不正常,原因分析:
1.原始信号经过了hanning加窗,信号有衰减,经过加窗后,能量有所衰减。
查阅了相关资料后了解到
在做FFT计算时,有时候需要用到平顶汉宁窗,如下:
//Tukey window: for 20ms
/*
The Tukey window, also known as the cosine-tapered window,
can be regarded as a cosine lobe of width N*alpha/2 (spanning N*alpha/2 + 1 observations)
that is convolved with a rectangular window of width N(1 - alpha/2).
w(n) = 1/2 * (1 - cos(2*pi*n/(alpha*N))); 0 <= n <= alpha*N/2;
w(n) = 1; alpha*N/2 <= n <= N/2;
w(N-n) = w(n); 0 <= n <= N/2;
At α = 0 it becomes rectangular, and at α = 1 it becomes a Hann window.
*/
平顶汉宁窗的使用场景是:当2帧数据叠在一起时,当前帧大于上一帧,即当前帧所有数据和上一帧的部分数据一起叠帧时,此时就需要使用平顶汉宁窗函数。例如每一帧长320点,当前帧的320点和上一帧的后192点的数据一起叠帧,做512点的FFT,此时就需要使用平顶汉宁窗,其中平顶的数据长度为320-192+1(汉宁窗本身就有1个值为1).
添加的窗函数还必须满足另外一个条件,即重叠缓冲区的部分的窗口的平方和必须为1,
即:w(N)^2 + w(M+N)^2 = 1;
虽然加窗能减少频谱泄露,但是加窗也存在一个问题:加窗衰减了每帧信号的能量,特别是边界处。为了解决这一个问题,就引入了合成窗的概念。
在iSTFT中,合成窗是在iFFT之后,对时域信号做的。此次加窗,并不是为了减少频谱泄露,因为之后不再做FFT,不需要满足周期性序列条件。而是为解决分析窗导致能量衰减问题的。
总结就是要对IFFT后的数据进行重建,需要注意
根据完美重建公式,合成窗的选择,不仅跟分析窗有关,还和overlap有关。不同的overlap要采用不同的合成窗。
重点总结
对ifft后的数据要加一个合成窗函数,该函数满足重叠缓冲区的部分的窗口的平方和必须为1,即分析窗和合成窗平方和等于1,(另外测试如果对原始信号不加窗,那么ifft后可以得到正常信号)
参考文档:使用多特征建模分析语音噪声可能性的噪声抑制方法和装置.caj (专利文档:0059,0060)