有人在iPhone应用程序中使用过Apple FFT
吗?或者知道我可以在哪里找到如何使用它的示例应用程序吗?我知道苹果公司已经发布了一些示例代码,但我不太确定如何将其实现到实际项目中。
有人在iPhone应用程序中使用过Apple FFT
吗?或者知道我可以在哪里找到如何使用它的示例应用程序吗?我知道苹果公司已经发布了一些示例代码,但我不太确定如何将其实现到实际项目中。
我刚刚为一个iPhone项目编写的FFT代码已经可以使用:
您可能还需要从info.plist中删除一个条目,该条目告诉项目加载xib,但我90%确定您不需要担心这个问题。
注意:程序输出到控制台,结果显示为0.000,这不是错误-它只是非常非常快
这段代码真的非常晦涩难懂;它有大量注释,但这些注释实际上并没有使生活变得更轻松。
基本上,它的核心是:
vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD);
vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_INVERSE);
对n个实数浮点数进行FFT,然后反转以回到起始位置。 ip代表就地操作,这意味着&A将被覆盖。 这就是为什么需要所有这些特殊的打包技巧的原因--这样我们可以将返回值压缩到与发送值相同的空间中。
为了提供一些背景(比如:我们为什么首先要使用此函数?),假设我们想对麦克风输入执行音高检测,并且我们已经设置好,每当麦克风获取1024个浮点数时,就会触发某个回调。假设麦克风采样率为44.1kHz,那么这大约是每秒44帧。
因此,我们的时间窗口是1024个样本的时间持续时间,即1/44秒。
因此,我们将从麦克风中填充A中的1024个浮点数,将log2n设置为10(2^10=1024),预先计算一些bobbins(setupReal),然后:
vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD);
bin[1].idealFreq = 44Hz -- 即我们可以可靠地检测到的最低频率是该窗口内的一个完整波,即44Hz。
bin[2].idealFreq = 2 * 44Hz
等等。
bin[512].idealFreq = 512 * 44Hz -- 我们可以检测到的最高频率(称为奈奎斯特频率)是每对点表示一波的地方,即窗口内有512个完整波,即512*44Hz,或:n/2*bin[1].idealFreq
实际上还有一个额外的Bin,Bin[0]通常称为“DC偏移”。碰巧的是,Bin[0]和Bin[n/2]的复分量始终为0,因此使用A[0].realp存储Bin[0],A[0].imagp存储Bin[n/2]
每个复数的大小是振动该频率周围的能量量。
因此,正如您所看到的,它不会成为非常好的音高检测器,因为它没有足够精细的细分度。有一个巧妙的技巧使用帧之间的相位变化从FFT Bin中提取精确频率来获取给定Bin的精确频率。
好了,现在进入代码:
请注意vDSP_fft_zrip中的'ip',表示“就地”即输出覆盖A('r'表示它接受实数输入)。for (i = 0; i < n; i++)
originalReal[i] = (float) (i + 1);
接下来我们将它们打包成 A,作为 n/2 个复数:
// 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));
接下来我们进行预计算。
数学专业的快速DSP课程: 傅里叶理论需要很长时间才能理解(我已经看了几年了)
顺式体是:
z = exp(i.theta) = cos(theta) + i.sin(theta)
即复平面上单位圆上的一个点。
当你乘以复数时,角度会相加。因此 z^k 将继续在单位圆周围跳动;z^k 可以在角度 k.theta 处找到
选择 z1 = 0+1i,即从实轴旋转1/4圈,并注意 z1^2 z1^3 z1^4 每个都会再旋转1/4圈,所以 z1^4 = 1
选择 z2 = -1,即旋转1/2圈。也有 z2^4 = 1,但此时 z2 已完成了两个周期(z2^2 也等于 1)。因此,您可以将 z1 视为基本频率,将 z2 视为第一谐波
同样,z3 = “三分之四的旋转”点,即 -i 正好完成了 3 个周期,但每次向前走 3/4 实际上等于每次向后走 1/4
即 z3 只是 z1 的相反方向 -- 这被称为别名效应
z2 是最高有效频率,因为我们选择了 4 个样本来表示一个完整的波。
你可以用z0 z1和z2的线性组合来表示任何4点信号 也就是说,你将其投影到这些基向量上
但我听到你在问“将信号投影到cisoid上意味着什么?”
你可以这样想:指针绕cisoid旋转,因此在第k个样本处,指针指向k.theta方向,长度为signal[k]。与cisoid完全匹配频率的信号将在某个方向上使结果形状凸起。因此,如果将所有贡献相加,将得到一个强大的结果向量。 如果频率接近匹配,则凸起会变小,并且会缓慢地沿着圆圈移动。 对于不匹配频率的信号,贡献将互相抵消。
http://complextoreal.com/tutorials/tutorial-4-fourier-analysis-made-easy-part-1/ 将帮助您获得直观的理解。
但要点是:如果我们选择将1024个样本投影到{z0,...,z512},我们将预先计算z0到z512,这就是这个预计算步骤的含义。
// 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);
vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD);
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);
以下是一个真实案例:使用Accelerate的vDSP fft例程在Remote IO音频单元的输入上执行自相关。使用这个框架相当复杂,但文档并不是太糟糕。
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);
}
}
vDSP_zvmags
,因为它的虚部实际上是奈奎斯特桶的实部。难道你不只是要平方A.realp[0]
和A.imagp[0]
,而不是清零A.imagp[0]
吗? - pat虽然我会说苹果的FFT框架很快...但你需要知道FFT的工作原理才能进行准确的音高检测(即在每个连续的FFT上计算相位差以找到精确的音高,而不是最强信号的音高)。
我不知道这是否有帮助,但我上传了我的音高检测器对象(来自我的调音应用程序musicianskit.com/developer.php)。还提供了一个示例xCode 4项目进行下载(这样您就可以看到实现的工作方式)。
我正在努力上传一个FFT实现的示例 - 因此请继续关注,我会更新这篇文章。
Happy coding!