如何在Swift中使用Apple的Accelerate框架来计算真实的信号的FFT?

4sup72z8  于 2023-04-10  发布在  Swift
关注(0)|答案(1)|浏览(214)

我应该如何使用Accelerate框架在iOS上的Swift中计算真实的信号的FFT?

WEB示例

Apple的Accelerate框架似乎提供了有效计算信号FFT的功能。
不幸的是,互联网上的大多数示例,如Swift-FFT-ExampleTempiFFT,如果广泛测试并调用Objective C API,就会崩溃。
Apple documentation回答了许多问题,但也引出了其他一些问题(这部分是强制性的吗?为什么我需要这个调用来转换?)。

栈溢出线程

有几个线程用具体的例子来解决FFT的各个方面。特别是FFT Using Accelerate In SwiftDFT result in Swift is different than that of MATLABFFT Calculating incorrectly - Swift。它们都没有直接解决“从0开始,正确的方法是什么”的问题?
我花了一天的时间才弄清楚如何正确地做到这一点,所以我希望这篇文章能清楚地解释你应该如何使用苹果的FFT,展示需要避免的陷阱,并帮助开发人员保存宝贵的时间。

rqcrx0a6

rqcrx0a61#

TL ; DR:如果您需要一个工作实现来复制here is a gist

什么是FFT?

快速傅立叶变换是一种算法,它在时域中获取信号--在规则的、通常很小的时间间隔内获取的测量值的集合--并将其转换为在相域中表示的信号(频率的集合)。表示信号沿着时间的能力,因变换而丢失(变换是可逆的,这意味着通过计算FFT不会丢失任何信息,并且您可以应用IFFT来获得原始信号),但是我们得到了区分信号所包含的频率的能力。这通常用来显示你在各种硬件和YouTube视频上正在听的音乐的频谱图。
FFT可以处理complexe numbers。如果你不知道它们是什么,让我们假设它是半径和Angular 的组合。2D平面上的每个点都有一个复数。真实的(你常用的浮点数)可以被看作是一条线上的位置(左边是负数,右边是正数)。
Nb: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)!

按照apple的文档,我们首先需要创建一个复杂的数组作为我们的输入。不要被教程误导。你通常想要的是复制你的信号作为输入的真实的部,并保持复部为空。

// 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
          forwardOutputImag.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的结果现在在forwardOutputRealforwardOutputImag中可用。
如果你只需要每个频率的振幅,而不关心真实的部和虚部,你可以在输入和输出旁边声明一个额外的数组:

var magnitudes = [Float](repeating: 0, count: Int(length))

在你的fft计算每个“bin”的振幅后加上:

vDSP.absolute(forwardOutput, result: &magnitudes)

相关问题