使用Apple FFT和加速框架

有没有人使用Apple FFT的iPhone应用程序,或知道在哪里可以find一个示例应用程序如何使用它? 我知道苹果有一些示例代码发布,但我不知道如何实现它到一个实际的项目。

我刚刚获得了一个iPhone项目的FFT代码:

  • 创build一个新的项目
  • 删除除main.m和xxx_info.plist以外的所有文件
  • 去项目设置和searchPCH,并停止尝试加载.pch(看到我们刚刚删除它)
  • 将代码示例复制粘贴到main.m文件中
  • 删除#include的Carbon行。 碳是用于OSX的。
  • 删除所有的框架,并添加加速框架

您可能还需要从info.plist中删除一个条目,告诉项目加载一个xib,但是我90%确定您不需要打扰。

注意:编程输出到控制台,结果出来为0.000这不是一个错误 – 它只是非常非常快

这段代码真的很晦涩难懂, 它是慷慨的评论,但评论实际上并没有使生活更容易。

基本上它的核心是:

 vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD); vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_INVERSE); 

FFT实际浮点数,然后反转回到我们开始的地方。 ip代表in-place,这意味着&A被覆盖,这就是所有这些特殊包装malarkey的原因 – 这样我们可以将返回值压缩到与发送值相同的空间。

为了给出一些观点(比如,为什么我们首先要使用这个function?),假设我们要对麦克风input进行音高检测,并且设置了每次都触发一些callback麦克风进入1024浮点数。 假设麦克风采样率是44.1kHz,那么〜44帧/秒。

所以,我们的时间窗口是1024个样本的持续时间,即1/44秒。

所以我们要用麦克风1024个浮点数来包装A,设置log2n = 10(2 ^ 10 = 1024),预先计算一些线轴(setupReal)和:

 vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD); 

现在A将包含n / 2个复数。 这些代表了n / 2个频率箱:

  • bin [1] .idealFreq = 44Hz – 即我们可以可靠检测到的最低频率是在该窗口内的一个完整的波形,即44Hz的波形。

  • bin [2] .idealFreq = 2 * 44Hz

  • 等等

  • bin [512] .idealFreq = 512 * 44Hz – 我们可以检测到的最高频率(称为Nyquist频率)是每一对点代表一个波形,即窗口内512个完整的波形,即512 * 44Hz,或者: n / 2 * bin [1] .idealFreq

  • 实际上有一个额外的Bin,Bin [0],通常被称为“DC Offset”。 Bin [0]和Bin [n / 2]总是会有复数的0,所以A [0] .realp被用来存储Bin [0]和A [0] .imagp被用来存储Bin [ N / 2]

每个复数的大小是在该频率附近振动的能量的大小。

所以,正如你所看到的那样,它不是一个很好的音高检测器,因为它没有足够精细的粒度。 有一个狡猾的技巧使用帧之间的相位变化从FFT箱中提取精确的频率,以获得给定箱的精确频率。

好的,现在到代码:

注意vDSP_fft_zrip中的'ip',='in place',即输出覆盖A('r'表示需要实际input)

看看vDSP_fft_zrip上的文档,

实数据以分裂复数forms存储,奇数实数存储在分裂复数forms的虚数侧,甚至存储在实数侧。

这可能是最难理解的。 我们在整个过程中一直使用相同的容器(&A)。 所以一开始我们要用n个实数来填充。 在FFT之后将会持有n / 2个复数。 然后我们把它转换成逆变换,并希望得到我们原来的n个实数。

现在是它的复杂值设置的结构。 所以vDSP需要规范如何将实数打包到其中。

所以我们首先生成n个实数:1,2,…,n

 for (i = 0; i < n; i++) originalReal[i] = (float) (i + 1); 

接下来我们把它们打包成一个n / 2个复杂的#s:

 // 1. masquerades n real #s as n/2 complex #s = {1+2i, 3+4i, ...} // 2. splits to // A.realP = {1,3,...} (n/2 elts) // A.compP = {2,4,...} (n/2 elts) // vDSP_ctoz( (COMPLEX *) originalReal, 2, // stride 2, as each complex # is 2 floats &A, 1, // stride 1 in A.realP & .compP nOver2); // n/2 elts 

你真的需要看看如何分配A来获得这个,也许在文档中查找COMPLEX_SPLIT。

 A.realp = (float *) malloc(nOver2 * sizeof(float)); A.imagp = (float *) malloc(nOver2 * sizeof(float)); 

接下来我们做一个预先计算。


math课的快速DSP课: 傅里叶理论需要很长的时间才能得到你的头(我已经看了好几年了)

一个顺stream是:

 z = exp(i.theta) = cos(theta) + i.sin(theta) 

即复平面上的单位圆上的点。

当你复数复数时,angular度增加。 所以z ^ k会继续在单位圆周跳跃; 可以在angular度kθ处findz ^ k

  • selectz1 = 0 + 1i,即从实轴开始的四分之一转,并注意到z1 ^ 2 z1 ^ 3 z1 ^ 4每个都给出了另一个四分之一转,所以z1 ^ 4 = 1

  • selectz2 = -1,即半转。 也z2 ^ 4 = 1,但z2已完成2个周期在这一点(z2 ^ 2也= 1)。 所以你可以把z1看作基频,把z2看作一次谐波

  • 类似地,z3 =“四分之三转”点,即-i完成3个周期,但实际上前进3/4每次与每次倒退1/4相同

即z3只是z1,但在相反的方向 – 这就是所谓的混叠

z2是最有意义的频率,因为我们select4个样本来保持全波。

  • z0 = 1 + 0i,z0 ^(任何)= 1,这是直stream偏移

你可以将任何4点信号表示为z0 z1和z2的线性组合, 也就是说,你将它投影到这些基向量上

但是我听到你问:“把信号投射到一个圆锥体上意味着什么?”

你可以这样想:针旋转在圆环上,所以在样本k处,针指向k.θ方向,长度是信号[k]。 精确匹配顺槽频率的信号会在某个方向上凸出形成的形状。 所以如果你把所有的贡献加起来,你会得到一个强大的结果向量。 如果频率几乎匹配,比凸起会更小,并将围绕圆缓慢移动。 对于不符合频率的信号,贡献将相互抵消。

http://complextoreal.com/tutorials/tutorial-4-fourier-analysis-made-easy-part-1/ 将帮助你获得直观的理解。

但要点是; 如果我们select将1024个样本投影到{z0,…,z512}上,我们可以预先计算z0到z512, 这就是预先计算的步骤


请注意,如果您正在使用真实代码进行此操作,则可能需要在应用程序加载时执行此操作,并在退出时调用一次补充释放function。 不要做很多次 – 这是昂贵的。

 // let's say log2n = 8, so n=2^8=256 samples, or 'harmonics' or 'terms' // if we pre-calculate the 256th roots of unity (of which there are 256) // that will save us time later. // // Note that this call creates an array which will need to be released // later to avoid leaking setupReal = vDSP_create_fftsetup(log2n, FFT_RADIX2); 

值得注意的是,如果我们将log2n设置为例如8,那么可以将这些预先计算的值放入任何使用分辨率<= 2 ^ 8的fft函数中。 所以(除非你想要最终的内存优化)只需要创build一个你需要的最高分辨率的集合,并将其用于一切。

现在实际转换,利用我们刚刚预先计算的东西:

 vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD); 

此时A将包含n / 2个复数,实际上只有第一个实数是伪装成复数的两个实数(DC偏移,Nyquist#)。 文件概述解释了这个包装。 它非常简洁 – 基本上它允许将(复杂的)转换结果打包到与(真实但奇怪的打包)input相同的内存空间中。

 vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_INVERSE); 

然后再回来…我们仍然需要从A中打开我们原来的数组,然后我们比较一下,看看我们是否已经回到原来的位置,释放我们的预先计算好的线轴并完成了!

可是等等! 在解包之前,还有一件事情需要完成:

 // Need to see the documentation for this one... // in order to optimise, different routines return values // that need to be scaled by different amounts in order to // be correct as per the math // In this case... scale = (float) 1.0 / (2 * n); vDSP_vsmul(A.realp, 1, &scale, A.realp, 1, nOver2); vDSP_vsmul(A.imagp, 1, &scale, A.imagp, 1, nOver2); 

下面是一个真实世界的例子:c ++的一个片段,它使用Accelerate的vDSP fft例程来对远端IOaudio单元的input进行自相关。 使用这个框架是相当复杂的,但是文档不是糟糕。

 OSStatus DSPCore::initialize (double _sampleRate, uint16_t _bufferSize) { sampleRate = _sampleRate; bufferSize = _bufferSize; peakIndex = 0; frequency = 0.f; uint32_t maxFrames = getMaxFramesPerSlice(); displayData = (float*)malloc(maxFrames*sizeof(float)); bzero(displayData, maxFrames*sizeof(float)); log2n = log2f(maxFrames); n = 1 << log2n; assert(n == maxFrames); nOver2 = maxFrames/2; A.realp = (float*)malloc(nOver2 * sizeof(float)); A.imagp = (float*)malloc(nOver2 * sizeof(float)); FFTSetup fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2); return noErr; } void DSPCore::Render(uint32_t numFrames, AudioBufferList *ioData) { bufferSize = numFrames; float ln = log2f(numFrames); //vDSP autocorrelation //convert real input to even-odd vDSP_ctoz((COMPLEX*)ioData->mBuffers[0].mData, 2, &A, 1, numFrames/2); memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize); //fft vDSP_fft_zrip(fftSetup, &A, 1, ln, FFT_FORWARD); // Absolute square (equivalent to mag^2) vDSP_zvmags(&A, 1, A.realp, 1, numFrames/2); bzero(A.imagp, (numFrames/2) * sizeof(float)); // Inverse FFT vDSP_fft_zrip(fftSetup, &A, 1, ln, FFT_INVERSE); //convert complex split to real vDSP_ztoc(&A, 1, (COMPLEX*)displayData, 2, numFrames/2); // Normalize float scale = 1.f/displayData[0]; vDSP_vsmul(displayData, 1, &scale, displayData, 1, numFrames); // Naive peak-pick: find the first local maximum peakIndex = 0; for (size_t ii=1; ii < numFrames-1; ++ii) { if ((displayData[ii] > displayData[ii-1]) && (displayData[ii] > displayData[ii+1])) { peakIndex = ii; break; } } // Calculate frequency frequency = sampleRate / peakIndex + quadInterpolate(&displayData[peakIndex-1]); bufferSize = numFrames; for (int ii=0; ii<ioData->mNumberBuffers; ++ii) { bzero(ioData->mBuffers[ii].mData, ioData->mBuffers[ii].mDataByteSize); } } 

虽然我会说苹果的FFT框架是快速的…您需要知道FFT如何工作,以获得准确的基音检测(即计算每个连续的FFT相位差,以便find确切的音调,而不是大多数主宰斌)。

我不知道是否有任何帮助,但我从我的调音器应用程序(musicianskit.com/developer.php)上传了我的Pitch Detector对象。 还有一个例子xCode 4项目也可以下载(所以你可以看到实现如何工作)。

我正在上传一个示例FFT实现 – 所以请继续关注,我会更新一次这种情况发生。

快乐的编码!

这是另一个真实世界的例子: https : //github.com/krafter/DetectingAudioFrequency