您现在的位置是:首页 >学无止境 >[旧日谈]音频领域中,比FFT更快的RealFFT-算法及代码网站首页学无止境
[旧日谈]音频领域中,比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=0N−1x[n]⋅e−jN2πkn,k=0,1,…,N−1
其中x[n]为输入信号,而X[k]为DFT出来的频谱数据,这里具体证明流程不过多赘述。
然后我们来说明一下FFT,FFT 利用了 DFT 的对称性和周期性,将 DFT 分解为更小的子问题,从而减少计算量。
我们假设 N N N是2 的幂,然后将DFT分解为两个规模为N/2的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,…,2N−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,…,2N−1
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=0∑N/2−1xeven[m]⋅e−jN/22πkm+e−jN2πkm=0∑N/2−1xodd[m]⋅e−jN/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=0∑N/2−1xeven[m]⋅e−jN/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=0∑N/2−1xodd[m]⋅e−jN/22πkm
这里我需要说明一下,有关这个 e − j 2 π N k e^{-j frac{2pi}{N} k} e−jN2πk,现在我们假定这玩意为 W N k = e − j 2 π N k W^{k}_N=e^{-j frac{2pi}{N} k} WNk=e−jN2π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]+e−jN2πk⋅Xodd[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]−e−jN2πk⋅Xodd[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]+W40∗O[0]
X
[
1
]
=
E
[
1
]
+
W
4
1
∗
O
[
1
]
X[1] = E[1] + W_4^1 * O[1]
X[1]=E[1]+W41∗O[1]
X
[
2
]
=
E
[
0
]
−
W
4
0
∗
O
[
0
]
X[2] = E[0] - W_4^0 * O[0]
X[2]=E[0]−W40∗O[0]
X
[
3
]
=
E
[
1
]
−
W
4
1
∗
O
[
1
]
X[3] = E[1] - W_4^1 * O[1]
X[3]=E[1]−W41∗O[1]
也就是说,我们本来需要计算N^2次,即每一个X[n]都需要对应N个x[n],现在优化了之后,我们只需要对整个时域流进行一次DFT,然后将其通过 W N k W^k_N WNk的旋转因子将其组合拼接起来即可。
RealFFT-实数序列化的快速傅里叶变换
算法流程:
- 将实数序列转换为复数序列:
- 将偶数索引和奇数索引的值组合成一个复数序列
- 计算长度为N/2的复数 FFT
- 使用共轭对称性恢复完整的N点FFT结果
- 只保留前N/2 + 1个结果(剩余部分可由共轭对称性推得)
展开内容
对于实数FFT,我们需要用到一个特别的性质,就是DFT的结果满足共轭对称性:
X ( N − k ) = X ∗ ( k ) X(N-k)=X^*(k) X(N−k)=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=0∑N−1x[n]e−jN2πkn
考虑共轭对称性,我们求 X [ N − k ] X[N-k] X[N−k]:
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[N−k]=n=0∑N−1x[n]e−jN2π(N−k)n
利用 e − j 2 π n = 1 e^{-j 2pi n} = 1 e−j2π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[N−k]=n=0∑N−1x[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=0∑N−1x[n]ejN2πkn
比较可得: X [ N − k ] = X ∗ [ k ] X[N-k] = X^*[k] X[N−k]=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=0N−1x[n]⋅e−jN2πkn,k=0,1,…,N−1
根据刚刚的推导,我们又有了一个重要性质 X ( N − k ) = X ∗ ( k ) X(N-k)=X^*(k) X(N−k)=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/2−1
然后对该复数序列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=0N−1z[n]⋅e−jN2πkn,k=0,1,…,N−1
其中 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)+e−j2π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(N−k)=Xe(k)−e−j2πk/NXo(k)
然后具体计算如下:
-
计算 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)
-
计算其余频率分量:
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/2−k)−je−j2πk/N(Z(k)−Z∗(N/2−k))]
- 由共轭对称性得到 X ( N − k ) = X ∗ ( k ) X(N-k)=X*(k) X(N−k)=X∗(k)
Example
- 示例信号
假设有一个长度为 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]
- 将实数信号转换为复数信号
将 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]
- 计算复数信号的 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=0N−1xc(n)e−jN2π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)=−2−2j
- 利用共轭对称性提取实数信号的 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∗(N−k)0≤k≤N/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∗(4−3)=Xc∗(1)=−2−2j
- 结果
实数信号 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,−2−2j]