您现在的位置是:首页 >学无止境 >[旧日谈]音频领域中,比FFT更快的RealFFT-算法及代码网站首页学无止境

[旧日谈]音频领域中,比FFT更快的RealFFT-算法及代码

扳机纪律 2025-03-24 00:01:03
简介[旧日谈]音频领域中,比FFT更快的RealFFT-算法及代码

音频领域中,比FFT更快的RealFFT-算法及代码

我们知道FFT需要使用蝶式的方式计算,但是实际上,我们在开发音频的过程中,所有的数字都是实数,所以可能我们并不需要使用到这么多的计算次数,就可以实现FFT,具体做法接下来我会给出推导和证明。

FFT-最原始的FFT

首先我们知道DFT,有:

X [ k ] = ∑ n = 0 N − 1 x [ n ] ⋅ e − j 2 π N k n , k = 0 , 1 , … , N − 1 X[k] = sum_{n=0}^{N-1} x[n] cdot e^{-j frac{2pi}{N} kn}, quad k = 0, 1, dots, N-1 X[k]=n=0N1x[n]ejN2πkn,k=0,1,,N1

其中x[n]为输入信号,而X[k]为DFT出来的频谱数据,这里具体证明流程不过多赘述。

然后我们来说明一下FFT,FFT 利用了 DFT 的对称性和周期性,将 DFT 分解为更小的子问题,从而减少计算量。

我们假设 N N N是2 的幂,然后将DFT分解为两个规模为N/2的DFT

需要做的事为以下几件:

  1. 我们需要将时域信号拆分为奇偶部分,方式如下:

x even [ m ] = x [ 2 m ] , x odd [ m ] = x [ 2 m + 1 ] , m = 0 , 1 , … , N 2 − 1 x_{ ext{even}}[m] = x[2m], quad x_{ ext{odd}}[m] = x[2m+1], quad m = 0, 1, dots, frac{N}{2}-1 xeven[m]=x[2m],xodd[m]=x[2m+1],m=0,1,,2N1

  1. 根据DFT的性质,我们可以将DFT公式拆分为奇偶部分,比如:

x even [ m ] = x [ 2 m ] , x odd [ m ] = x [ 2 m + 1 ] , m = 0 , 1 , … , N 2 − 1 x_{ ext{even}}[m] = x[2m], quad x_{ ext{odd}}[m] = x[2m+1], quad m = 0, 1, dots, frac{N}{2}-1 xeven[m]=x[2m],xodd[m]=x[2m+1],m=0,1,,2N1

X [ k ] = ∑ m = 0 N / 2 − 1 x even [ m ] ⋅ e − j 2 π N / 2 k m + e − j 2 π N k ∑ m = 0 N / 2 − 1 x odd [ m ] ⋅ e − j 2 π N / 2 k m X[k] = sum_{m=0}^{N/2-1} x_{ ext{even}}[m] cdot e^{-j frac{2pi}{N/2} km} + e^{-j frac{2pi}{N} k} sum_{m=0}^{N/2-1} x_{ ext{odd}}[m] cdot e^{-j frac{2pi}{N/2} km} X[k]=m=0N/21xeven[m]ejN/22πkm+ejN2πkm=0N/21xodd[m]ejN/22πkm

这下我们就得到两个规模为N/2的DFT了,如下:

X even [ k ] = ∑ m = 0 N / 2 − 1 x even [ m ] ⋅ e − j 2 π N / 2 k m X_{ ext{even}}[k] = sum_{m=0}^{N/2-1} x_{ ext{even}}[m] cdot e^{-j frac{2pi}{N/2} km} Xeven[k]=m=0N/21xeven[m]ejN/22πkm

X odd [ k ] = ∑ m = 0 N / 2 − 1 x odd [ m ] ⋅ e − j 2 π N / 2 k m X_{ ext{odd}}[k] = sum_{m=0}^{N/2-1} x_{ ext{odd}}[m] cdot e^{-j frac{2pi}{N/2} km} Xodd[k]=m=0N/21xodd[m]ejN/22πkm

这里我需要说明一下,有关这个 e − j 2 π N k e^{-j frac{2pi}{N} k} ejN2πk,现在我们假定这玩意为 W N k = e − j 2 π N k W^{k}_N=e^{-j frac{2pi}{N} k} WNk=ejN2πk,那么由于我们知道欧拉公式, e π ∗ j = − 1 e^{pi*j}=-1 eπj=1

我们需要知道一个性质: W N k + N / 2 = − W N k W^{k+N/2}_N=-W^k_N WNk+N/2=WNk,要证明也很简单,将这个公式拆开,然后带入欧拉公式就可以了,这里不过多赘述,只需要知道,由上述这个式子推导出来如下结论:

原始的DFT可以拆解为:
若0<k<N/2,则有:
X [ k ] = X even [ k ] + e − j 2 π N k ⋅ X odd [ k ] X[k] = X_{ ext{even}}[k] + e^{-j frac{2pi}{N} k} cdot X_{ ext{odd}}[k] X[k]=Xeven[k]+ejN2πkXodd[k]

若k>N/2 则有:

X [ k + N / 2 ] = X even [ k ] − e − j 2 π N k ⋅ X odd [ k ] X[k + N/2] = X_{ ext{even}}[k] - e^{-j frac{2pi}{N} k} cdot X_{ ext{odd}}[k] X[k+N/2]=Xeven[k]ejN2πkXodd[k]

我们称这种DFT计算为蝶式运算,举个例子如下:

输入序列: x[0], x[1], x[2], x[3]

第一步: 拆分为两个子序列
偶数部分: x[0], x[2]
奇数部分: x[1], x[3]

第二步: 计算子序列的 DFT
E [ k ] = D F T ( x [ 0 ] , x [ 2 ] ) E[k] = DFT(x[0], x[2]) E[k]=DFT(x[0],x[2])
O [ k ] = D F T ( x [ 1 ] , x [ 3 ] ) O[k] = DFT(x[1], x[3]) O[k]=DFT(x[1],x[3])

第三步: 通过蝶式运算合并
X [ 0 ] = E [ 0 ] + W 4 0 ∗ O [ 0 ] X[0] = E[0] + W_4^0 * O[0] X[0]=E[0]+W40O[0]
X [ 1 ] = E [ 1 ] + W 4 1 ∗ O [ 1 ] X[1] = E[1] + W_4^1 * O[1] X[1]=E[1]+W41O[1]
X [ 2 ] = E [ 0 ] − W 4 0 ∗ O [ 0 ] X[2] = E[0] - W_4^0 * O[0] X[2]=E[0]W40O[0]
X [ 3 ] = E [ 1 ] − W 4 1 ∗ O [ 1 ] X[3] = E[1] - W_4^1 * O[1] X[3]=E[1]W41O[1]

也就是说,我们本来需要计算N^2次,即每一个X[n]都需要对应N个x[n],现在优化了之后,我们只需要对整个时域流进行一次DFT,然后将其通过 W N k W^k_N WNk的旋转因子将其组合拼接起来即可。

RealFFT-实数序列化的快速傅里叶变换

算法流程:

  1. 将实数序列转换为复数序列:
    • 将偶数索引和奇数索引的值组合成一个复数序列
  2. 计算长度为N/2的复数 FFT
  3. 使用共轭对称性恢复完整的N点FFT结果
  4. 只保留前N/2 + 1个结果(剩余部分可由共轭对称性推得)

展开内容

对于实数FFT,我们需要用到一个特别的性质,就是DFT的结果满足共轭对称性:

X ( N − k ) = X ∗ ( k ) X(N-k)=X^*(k) X(Nk)=X(k)

这里需要从数学上解释一下为什么会满足这个性质:

对于DFT公式,可以见到:
X [ k ] = ∑ n = 0 N − 1 x [ n ] e − j 2 π N k n X[k] = sum_{n=0}^{N-1} x[n] e^{-j frac{2pi}{N} kn} X[k]=n=0N1x[n]ejN2πkn

考虑共轭对称性,我们求 X [ N − k ] X[N-k] X[Nk]

X [ N − k ] = ∑ n = 0 N − 1 x [ n ] e − j 2 π N ( N − k ) n X[N-k] = sum_{n=0}^{N-1} x[n] e^{-j frac{2pi}{N} (N-k)n} X[Nk]=n=0N1x[n]ejN2π(Nk)n

利用 e − j 2 π n = 1 e^{-j 2pi n} = 1 ej2πn=1 ,可化简为:

X [ N − k ] = ∑ n = 0 N − 1 x [ n ] e j 2 π N k n X[N-k] = sum_{n=0}^{N-1} x[n] e^{j frac{2pi}{N} kn} X[Nk]=n=0N1x[n]ejN2πkn

如果 x [ n ] x[n] x[n] 是实值的,则x[n]的虚部为0,则其共轭满足:

X ∗ [ k ] = ∑ n = 0 N − 1 x [ n ] e j 2 π N k n X^*[k] = sum_{n=0}^{N-1} x[n] e^{j frac{2pi}{N} kn} X[k]=n=0N1x[n]ejN2πkn

比较可得: X [ N − k ] = X ∗ [ k ] X[N-k] = X^*[k] X[Nk]=X[k]

具体推导

我们现在需要对一个长度为N的离散信号x(n)进行DFT,则有

X [ k ] = ∑ n = 0 N − 1 x [ n ] ⋅ e − j 2 π N k n , k = 0 , 1 , … , N − 1 X[k] = sum_{n=0}^{N-1} x[n] cdot e^{-j frac{2pi}{N} kn}, quad k = 0, 1, dots, N-1 X[k]=n=0N1x[n]ejN2πkn,k=0,1,,N1

根据刚刚的推导,我们又有了一个重要性质 X ( N − k ) = X ∗ ( k ) X(N-k)=X^*(k) X(Nk)=X(k),则我们可以将一个长度为N的实数序列打包成一个长度为N/2的复数序列:

z ( n ) = x ( 2 n ) + j x ( 2 x + 1 ) , n = 0 , 1 , 2... , N / 2 − 1 z(n)=x(2n)+jx(2x+1), n=0,1,2...,N/2-1 z(n)=x(2n)+jx(2x+1),n=0,1,2...,N/21

然后对该复数序列z(n)计算长度为N/2的复数FFT: Z [ k ] = ∑ n = 0 N − 1 z [ n ] ⋅ e − j 2 π N k n , k = 0 , 1 , … , N − 1 Z[k] = sum_{n=0}^{N-1} z[n] cdot e^{-j frac{2pi}{N} kn}, quad k = 0, 1, dots, N-1 Z[k]=n=0N1z[n]ejN2πkn,k=0,1,,N1

其中 Z ( k ) = X e ( k ) + j X o ( k ) Z(k)=X_e(k)+jX_o(k) Z(k)=Xe(k)+jXo(k)其中: X e ( k ) X_e(k) Xe(k)是偶数索引的FFT, X o ( k ) X_o(k) Xo(k)是奇数索引的FFT

复数 FFT 计算后,我们需要恢复原始的N点 FFT 结果。利用 FFT 结果的对称性:

X ( k ) = X e ( k ) + e − j 2 π k / N X o ( k ) X(k)=X_e(k)+e^{-j2pi k/N}X_o(k) X(k)=Xe(k)+ej2πk/NXo(k)

X ( N − k ) = X e ( k ) − e − j 2 π k / N X o ( k ) X(N-k)=X_e(k)-e^{-j2pi k/N}X_o(k) X(Nk)=Xe(k)ej2πk/NXo(k)

然后具体计算如下:

  1. 计算 X ( 0 ) = X e ( 0 ) + X o ( 0 ) X(0)=X_e(0)+X_o(0) X(0)=Xe(0)+Xo(0) X ( N / 2 ) = X e ( N / 2 ) − X o ( N / 2 ) X(N/2)=X_e(N/2)-X_o(N/2) X(N/2)=Xe(N/2)Xo(N/2)

  2. 计算其余频率分量:

X ( k ) = 1 / 2 [ Z ( k ) + Z ∗ ( N / 2 − k ) − j e − j 2 π k / N ( Z ( k ) − Z ∗ ( N / 2 − k ) ) ] X(k)=1/2[Z(k)+Z^*(N/2-k)-je^{-j2pi k/N}(Z(k)-Z^*(N/2-k))] X(k)=1/2[Z(k)+Z(N/2k)jej2πk/N(Z(k)Z(N/2k))]

  1. 由共轭对称性得到 X ( N − k ) = X ∗ ( k ) X(N-k)=X*(k) X(Nk)=X(k)

Example

  1. 示例信号

假设有一个长度为 N = 4 N=4 N=4 的实数信号 x ( n ) x(n) x(n):

x ( n ) = [ 1 , 2 , 3 , 4 ] x(n) = [1, 2, 3, 4] x(n)=[1,2,3,4]

  1. 将实数信号转换为复数信号

x ( n ) x(n) x(n) 视为复数信号的实部,虚部为 0: x c ( n ) = [ 1 + 0 j , 2 + 0 j , 3 + 0 j , 4 + 0 j ] x_c(n) = [1+0j, 2+0j, 3+0j, 4+0j] xc(n)=[1+0j,2+0j,3+0j,4+0j]

  1. 计算复数信号的 FFT

使用 FFT 算法计算 x c ( n ) x_c(n) xc(n) 的 DFT,得到 X c ( k ) X_c(k) Xc(k)。这里我们使用 DFT 公式进行计算,实际应用中通常使用 FFT 算法: X c ( k ) = ∑ n = 0 N − 1 x c ( n ) e − j 2 π N k n X_c(k) = sum_{n=0}^{N-1} x_c(n) e^{-jfrac{2pi}{N}kn} Xc(k)=n=0N1xc(n)ejN2πkn

计算得到:

X c ( 0 ) = 10 + 0 j X_c(0) = 10 + 0j Xc(0)=10+0j
X c ( 1 ) = − 2 + 2 j X_c(1) = -2 + 2j Xc(1)=2+2j
X c ( 2 ) = − 2 + 0 j X_c(2) = -2 + 0j Xc(2)=2+0j
X c ( 3 ) = − 2 − 2 j X_c(3) = -2 - 2j Xc(3)=22j

  1. 利用共轭对称性提取实数信号的 DFT

根据共轭对称性,实数信号 x ( n ) x(n) x(n) 的 DFT X ( k ) X(k) X(k) 可以通过以下方式从 X c ( k ) X_c(k) Xc(k) 中提取:

X ( k ) = { X c ( k ) 0 ≤ k ≤ N / 2 X c ∗ ( N − k ) N / 2 < k < N X(k) = egin{cases} X_c(k) & 0 le k le N/2 \ X_c^*(N-k) & N/2 < k < N end{cases} X(k)={Xc(k)Xc(Nk)0kN/2N/2<k<N

因此,我们得到 X ( k ) X(k) X(k):

X ( 0 ) = X c ( 0 ) = 10 + 0 j X(0) = X_c(0) = 10 + 0j X(0)=Xc(0)=10+0j
X ( 1 ) = X c ( 1 ) = − 2 + 2 j X(1) = X_c(1) = -2 + 2j X(1)=Xc(1)=2+2j
X ( 2 ) = X c ( 2 ) = − 2 + 0 j X(2) = X_c(2) = -2 + 0j X(2)=Xc(2)=2+0j
X ( 3 ) = X c ∗ ( 4 − 3 ) = X c ∗ ( 1 ) = − 2 − 2 j X(3) = X_c^*(4-3) = X_c^*(1) = -2 - 2j X(3)=Xc(43)=Xc(1)=22j

  1. 结果

实数信号 x ( n ) = [ 1 , 2 , 3 , 4 ] x(n) = [1, 2, 3, 4] x(n)=[1,2,3,4] 的 DFT 为: X ( k ) = [ 10 + 0 j , − 2 + 2 j , − 2 + 0 j , − 2 − 2 j ] X(k) = [10+0j, -2+2j, -2+0j, -2-2j] X(k)=[10+0j,2+2j,2+0j,22j]

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。