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

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

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

FFT-最原始的FFT

首先我们知道DFT,有:

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

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

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

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

需要做的事为以下几件:

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

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

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

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

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

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

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

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

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

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

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

若k>N/2 则有:

X[k+N/2]=Xeven[k]−e−j2πNk⋅Xodd[k]X[k + N/2] = X_{\text{even}}[k] - e^{-j \frac{2\pi}{N} k} \cdot X_{\text{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]=DFT(x[0],x[2])E[k] = DFT(x[0], x[2])E[k]=DFT(x[0],x[2])
O[k]=DFT(x[1],x[3])O[k] = DFT(x[1], x[3])O[k]=DFT(x[1],x[3])

第三步: 通过蝶式运算合并
X[0]=E[0]+W40∗O[0]X[0] = E[0] + W_4^0 * O[0]X[0]=E[0]+W40O[0]
X[1]=E[1]+W41∗O[1]X[1] = E[1] + W_4^1 * O[1]X[1]=E[1]+W41O[1]
X[2]=E[0]−W40∗O[0]X[2] = E[0] - W_4^0 * O[0]X[2]=E[0]W40O[0]
X[3]=E[1]−W41∗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,然后将其通过WNkW^k_NWNk的旋转因子将其组合拼接起来即可。

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=0N−1x[n]e−j2πNknX[k] = \sum_{n=0}^{N-1} x[n] e^{-j \frac{2\pi}{N} kn}X[k]=n=0N1x[n]ejN2πkn

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

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

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

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

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

X∗[k]=∑n=0N−1x[n]ej2πNknX^*[k] = \sum_{n=0}^{N-1} x[n] e^{j \frac{2\pi}{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=0N−1x[n]⋅e−j2πNkn,k=0,1,…,N−1X[k] = \sum_{n=0}^{N-1} x[n] \cdot e^{-j \frac{2\pi}{N} kn}, \quad k = 0, 1, \dots, N-1X[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(2n)+jx(2x+1),n=0,1,2...,N/2−1z(n)=x(2n)+jx(2x+1), n=0,1,2...,N/2-1z(n)=x(2n)+jx(2x+1),n=0,1,2...,N/21

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

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

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

X(k)=Xe(k)+e−j2πk/NXo(k)X(k)=X_e(k)+e^{-j2\pi k/N}X_o(k)X(k)=Xe(k)+ej2πk/NXo(k)

X(N−k)=Xe(k)−e−j2πk/NXo(k)X(N-k)=X_e(k)-e^{-j2\pi k/N}X_o(k)X(Nk)=Xe(k)ej2πk/NXo(k)

然后具体计算如下:

  1. 计算X(0)=Xe(0)+Xo(0)X(0)=X_e(0)+X_o(0)X(0)=Xe(0)+Xo(0)X(N/2)=Xe(N/2)−Xo(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)−je−j2πk/N(Z(k)−Z∗(N/2−k))]X(k)=1/2[Z(k)+Z^*(N/2-k)-je^{-j2\pi 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=4N=4N=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:xc(n)=[1+0j,2+0j,3+0j,4+0j]x_c(n) = [1+0j, 2+0j, 3+0j, 4+0j]xc(n)=[1+0j,2+0j,3+0j,4+0j]

  1. 计算复数信号的 FFT

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

计算得到:

Xc(0)=10+0jX_c(0) = 10 + 0jXc(0)=10+0j
Xc(1)=−2+2jX_c(1) = -2 + 2jXc(1)=2+2j
Xc(2)=−2+0jX_c(2) = -2 + 0jXc(2)=2+0j
Xc(3)=−2−2jX_c(3) = -2 - 2jXc(3)=22j

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

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

X(k)={Xc(k)0≤k≤N/2Xc∗(N−k)N/2<k<NX(k) = \begin{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)=Xc(0)=10+0jX(0) = X_c(0) = 10 + 0jX(0)=Xc(0)=10+0j
X(1)=Xc(1)=−2+2jX(1) = X_c(1) = -2 + 2jX(1)=Xc(1)=2+2j
X(2)=Xc(2)=−2+0jX(2) = X_c(2) = -2 + 0jX(2)=Xc(2)=2+0j
X(3)=Xc∗(4−3)=Xc∗(1)=−2−2jX(3) = X_c^*(4-3) = X_c^*(1) = -2 - 2jX(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+0j,−2+2j,−2+0j,−2−2j]X(k) = [10+0j, -2+2j, -2+0j, -2-2j]X(k)=[10+0j,2+2j,2+0j,22j]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值