我应该如何使用 Accelerate 框架在 iOS 上的 Swift 中计算真实信号的 FFT?
网络上的可用示例
Apple 的 Accelerate 框架似乎提供了有效计算信号 FFT 的功能。
不幸的是,互联网上可用的大多数示例,例如 Swift-FFT-Example和 TempiFFT , 如果广泛测试并调用 Objective C API 会崩溃。
Apple documentation回答了许多问题,但也引出了其他一些问题(这篇文章是强制性的吗?为什么我需要这个电话来转换?)。
Stack Overflow 上的线程
很少有线程通过具体示例来解决 FFT 的各个方面。值得注意的是FFT Using Accelerate In Swift , DFT result in Swift is different than that of MATLAB和 FFT Calculating incorrectly - Swift .
他们都没有直接解决“从 0 开始做这件事的正确方法是什么”的问题?
我花了一天时间才弄清楚如何正确地做到这一点,所以我希望这个帖子可以清楚地解释您应该如何使用 Apple 的 FFT,展示要避免的陷阱,并帮助开发人员节省宝贵的时间时间。
最佳答案
TL ; DR :如果您需要一个有效的实现来复制过去 here is a gist .
什么是FFT?
快速傅立叶变换是一种算法,它采用时域中的信号——在规则的、通常很小的时间间隔内进行的测量集合——并将其转换为表示到相位域中的信号(频率的集合) )。
能够表达由变换丢失的信号随时间的变化(变换是可逆的,这意味着通过计算 FFT 不会丢失任何信息,您可以应用 IFFT 来获取原始信号),但我们能够区分信号包含的频率。这通常用于显示您在各种硬件和 YouTube 视频上收听的音乐的频谱图。
FFT 适用于 complexe numbers .如果您不知道它们是什么,让我们假设它是半径和角度的组合。二维平面上的每个点有一个复数。实数(通常的浮点数)可以看作是一条线上的一个位置(左边是负数,右边是正数)。
注意:FFT(FFT(FFT(FFT(X))) = X(最多一个常数,取决于您的 FFT 实现)。
如何计算真实信号的 FFT。
通常您想计算音频信号的小窗口的 FFT。为了这个例子,我们将采用一个小的 1024 个样本窗口。您还希望使用 2 的幂,否则事情会变得更加困难。
var signal: [Float] // Array of length 1024
首先,您需要为计算初始化一些常量。
// The length of the input
length = vDSP_Length(signal.count)
// The power of two of two times the length of the input.
// Do not forget this factor 2.
log2n = vDSP_Length(ceil(log2(Float(length * 2))))
// Create the instance of the FFT class which allow computing FFT of complex vector with length
// up to `length`.
fftSetup = vDSP.FFT(log2n: log2n, radix: .radix2, ofType: DSPSplitComplex.self)!
按照苹果的文档,我们首先需要创建一个复杂的数组作为我们的输入。
不要被教程误导。您通常想要的是将您的信号复制为输入的实部,并将复数部分保持为空。
// Input / Output arrays
var forwardInputReal = [Float](signal) // Copy the signal here
var forwardInputImag = [Float](repeating: 0, count: Int(length))
var forwardOutputReal = [Float](repeating: 0, count: Int(length))
var forwardOutputImag = [Float](repeating: 0, count: Int(length))
注意,FFT 函数不允许同时使用相同的 splitComplex 作为输入和输出。如果您遇到崩溃,这可能是原因。这就是我们定义输入和输出的原因。
现在,我们必须小心并“锁定”指向这四个数组的指针,如文档示例中所示。如果您只是使用
&forwardInputReal
作为您的 DSPSplitComplex
的论据,指针可能会在下一行失效,您可能会遇到应用程序偶发性崩溃的情况。 forwardInputReal.withUnsafeMutableBufferPointer { forwardInputRealPtr in
forwardInputImag.withUnsafeMutableBufferPointer { forwardInputImagPtr in
forwardOutputReal.withUnsafeMutableBufferPointer { forwardOutputRealPtr in
fforwardOutputImag.withUnsafeMutableBufferPointer { forwardOutputImagPtr in
// Input
let forwardInput = DSPSplitComplex(realp: forwardInputRealPtr.baseAddress!, imagp: forwardInputImagPtr.baseAddress!)
// Output
var forwardOutput = DSPSplitComplex(realp: forwardOutputRealPtr.baseAddress!, imagp: forwardOutputImagPtr.baseAddress!)
// FFT call goes here
}
}
}
}
现在,压轴台词:调用您的 fft:
fftSetup.forward(input: forwardInput, output: &forwardOutput)
您的 FFT 结果现在可在
forwardOutputReal
中获得和 forwardOutputImag
.如果你只想要每个频率的幅度,而不关心实部和虚部,你可以在输入和输出旁边声明一个额外的数组:
var magnitudes = [Float](repeating: 0, count: Int(length))
在您的 fft 计算每个“bin”的幅度之后立即添加:
vDSP.absolute(forwardOutput, result: &magnitudes)
关于ios - 如何快速使用苹果的 Accelerate 框架来计算真实信号的 FFT?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60120842/