iPhone上的FFT用于忽略背景噪音并找到更低的音调。

8
我已经为iPhone实现了Demetri的音高检测器项目,但遇到了两个问题。1)任何形式的背景噪音都会导致频率读数不准确;2)低频声音无法正确识别音高。我试图调音吉他,虽然高音弦可以工作,但调音器无法正确识别低E音。音高检测代码位于RIOInterface.mm文件中,大致如下...
// get the data
AudioUnitRender(...);

// convert int16 to float
Convert(...);

// divide the signal into even-odd configuration
vDSP_ctoz((COMPLEX*)outputBuffer, 2, &A, 1, nOver2);

// apply the fft
vDSP_fft_zrip(fftSetup, &A, stride, log2n, FFT_FORWARD);

// convert split real form to split vector
vDSP_ztoc(&A, 1, (COMPLEX *)outputBuffer, 2, nOver2);

接着,Demetri 根据以下方法确定“主导”频率:

float dominantFrequency = 0;
int bin = -1;
for (int i=0; i<n; i+=2) {
    float curFreq = MagnitudeSquared(outputBuffer[i], outputBuffer[i+1]);
    if (curFreq > dominantFrequency) {
        dominantFrequency = curFreq;
        bin = (i+1)/2;
    }
}
memset(outputBuffer, 0, n*sizeof(SInt16));

// Update the UI with our newly acquired frequency value.
[THIS->listener frequencyChangedWithValue:bin*(THIS->sampleRate/bufferCapacity)];

首先,我认为需要应用低通滤波器...但我不是FFT专家,也不确定在哪里或如何对从vDSP函数返回的数据执行该操作。我也不确定如何提高低频代码的准确性。似乎有其他算法可以确定主导频率 - 但再次,希望在使用苹果加速框架返回的数据时给出正确的方向指引。
更新:
加速框架实际上有一些窗口函数。我设置了一个基本的窗口,如下所示:
windowSize = maxFrames;
transferBuffer = (float*)malloc(sizeof(float)*windowSize);
window = (float*)malloc(sizeof(float)*windowSize);
memset(window, 0, sizeof(float)*windowSize);
vDSP_hann_window(window, windowSize, vDSP_HANN_NORM); 

我随后应用它,通过插入


vDSP_vmul(outputBuffer, 1, window, 1, transferBuffer, 1, windowSize); 

在vDSP_ctoz函数之前。然后我更改了其余的代码,使用“transferBuffer”而不是“outputBuffer”...但到目前为止,没有注意到最终音高猜测有任何显著变化。

我从几年前玩这个东西中记得两件事:你可以通过从预定数量中减去原始输入数据来设置背景声音的静噪级别(现在你可以猜测一下,直到你找到一个好的算法),将任何低于0的值设为0。而且,由于某种我无法记起来的原因,低频率不像高频率那样响亮,因此您需要在与较高主导频率进行比较之前按指数放大低频音量。 - jcomeau_ictx
在执行FFT之前,您似乎没有应用适当的窗口函数,因此功率谱中会有很多伪像,这可能会破坏任何尝试进行音高检测的努力。 - Paul R
2
这是一个几乎重复的问题,答案类似:https://dev59.com/Em855IYBdhLWcg3woWDM - hotpaw2
4个回答

8

音高并不等同于峰值频率(这是Accelerate框架中FFT直接提供的)。因此,任何峰值频率检测器都不能可靠地用于音高估计。当音符的基音缺失或非常微弱(一些人声、钢琴和吉他声音中很常见),或其频谱中有大量强大的倍频时,低通滤波器也无法起到帮助作用。

观察您的音乐声音的宽频谱或频谱图,您将看到问题所在。

其他方法通常需要用于更可靠地估计音高。其中一些方法包括自相关方法(AMDF、ASDF)、倒谱/倒频谱分析、谐波积谱、相位变压器以及/或复合算法,例如RAPT(鲁棒音高跟踪算法)和YAAPT。 FFT仅作为上述某些方法的子部分。


谢谢@hotpaw2。我将不得不深入研究您在此列出的一些技术。感谢您的建议。 - Luther Baker
其他一些音高估计方法的描述参考可以在我的博客上找到:http://www.nicholson.com/rhn/dsp.html#1 - hotpaw2

3

至少在计算FFT之前,您需要对时间域数据应用窗函数。如果没有这一步骤,功率谱将包含伪影(请参见:频谱泄漏),这将干扰您提取音高信息的尝试。

一个简单的Hann(又称Hanning)窗应该就足够了。


2
好的回答和漂亮的头像图片 :D - some_id
无法完全同意这个评论。您需要在采样模拟信号之前应用窗口滤波器。一旦采样(即通过麦克风或WAV形式记录),您就不需要过滤任何内容,除非您正在寻找另一种效果。 - Ze Jibe
@ZeJibe:我认为你可能混淆了应用窗函数和使用抗混叠滤波器的概念? - Paul R
@ZeJibe:没问题 - 你能取消那个踩的操作吗? - Paul R

1

iPhone的频率响应函数在100-200 Hz以下会下降(请参见http://blog.faberacoustical.com/2009/ios/iphone/iphone-microphone-frequency-response-comparison/以获取示例)。

如果您正在尝试检测低吉他弦的基本模式,则麦克风可能会充当过滤器并抑制您感兴趣的频率。如果您有兴趣使用fft数据,有几个选项可供选择 - 您可以在频率域中窗口化围绕您要检测的音符的数据,以便即使第一个模式的幅度比较低,您也只能看到它(即有一个切换来调整第一根弦并将其置于此模式中)。

或者,您可以低通滤波声音数据 - 您可以在时间域中执行此操作,甚至更容易,因为您已经具有频率域数据,可以在频率域中执行此操作。非常简单的时间域低通滤波器是执行时间移动平均滤波器。非常简单的频率域低通滤波器是通过将fft幅度乘以具有低频范围中的1和高频中的线性(甚至是阶梯形)斜坡向下的向量来实现的。


1

你的采样频率和块大小是多少?低音E大约在80 Hz左右,因此你需要确保你的捕获块足够长,以便在这个频率下捕获多个周期。这是因为傅里叶变换将频谱分成几个宽度为几赫兹的区间。例如,如果你以44.1 kHz的采样率进行采样,并且有一个1024点的时域采样,那么每个区间的宽度将是44100/1024 = 43.07 Hz。因此,低音E将位于第二个区间。出于许多原因(与频谱泄漏和有限时间块的性质有关),实际上你应该极度怀疑FFT结果中前3或4个数据区间。

如果你将采样率降至8 kHz,则相同的块大小将给你7.8125 Hz宽的区间。现在低音E将位于第10或11个区间,这要好得多。你也可以使用更长的块大小。

正如Paul R指出,你必须使用窗口来减少频谱泄漏。


我已经尝试了22050和44100。我还尝试了1024、2048和4096的块大小,使用循环缓冲区保存每个回调的数据,直到达到“块大小”的数量。我确实理解这个想法,即在给定固定块大小的情况下,较低的采样率将给我略微更宽的频带,并使听到低E音稍微容易一些。不幸的是,我注意到一旦降到约295 Hz左右,调音器开始读取类似883和901的数字。非常接近真实音调的2和3的倍数。 - Luther Baker
我正在另一部闲置的iPhone上生成音调。我注意到,当我“调高”音量时,我的纯正正弦波开始听起来有点尖锐,调谐器失去了它原本显示的正确值,并开始偏离其他频率——不一定是我要寻找的基音的2或3倍数。 - Luther Baker
较长的块大小对于任何音高估计器来说都是一个很好的起点,但是对于任何具有缺失或弱基频率(即使是由于麦克风衰减)的声音,FFT幅度峰值仍然无法工作。 - hotpaw2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接