【发布时间】:2014-12-31 21:38:39
【问题描述】:
我目前正在开发一个 OpenGL 调谐器应用程序。我目前在屏幕上有 FFT 图,我想计算最响亮的频率。这是我的代码的样子:
MainActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
transformer = new RealDoubleFFT(samples);
started = true;
recordAudio = new RecordAudio();
recordAudio.execute();
}
// ...
public class RecordAudio extends AsyncTask<Void, double[], Void> {
@Override
protected Void doInBackground(Void... arg0) {
try {
int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate,
channelConfig, audioFormat, bufferSize);
short[] buffer = new short[samples];
double[] fft = new double[samples];
audioRecord.startRecording();
while (started) {
int bufferReadResult = audioRecord.read(buffer, 0, samples);
for (int i = 0; i < samples && i < bufferReadResult; i++) {
fft[i] = (double) buffer[i] / 32768.0;
}
transformer.ft(fft);
publishProgress(fft);
}
audioRecord.stop();
} catch (Throwable t) {
t.printStackTrace();
Log.e("MainActivity.AudioRecord", "The audio recording has encountered an error.");
}
return null;
}
@Override
protected void onProgressUpdate(double[]... fft) {
int peak = (int) ((frequency / ((float) sampleRate / (float) samples)) * 2f);
int offset = 0;
for (int i = 0; i < samples; i++) {
fftVertices[offset++] = (((float) i / (float) samples) * 2f) - 1f;
fftVertices[offset++] = -1f;
float color = 0;
if (i < peak) {
color = (float) (peak - i) / (float) peak;
} else if (i > peak) {
color = (float) (i - peak) / (float) ((int) ((float) samples / 2f) - peak);
} else {
color = 0f;
}
color = 1f - color;
fftVertices[offset++] = color;
fftVertices[offset++] = color;
fftVertices[offset++] = color;
fftVertices[offset++] = (((float) i / (float) samples) * 2f) - 1f;
fftVertices[offset++] = ((float) fft[0][i] / 2f) - 1f;
fftVertices[offset++] = color;
fftVertices[offset++] = color;
fftVertices[offset++] = color;
if (i % 2 == 0) {
magnitude[(int) ((float) i / 2f)] = (float) Math.sqrt(fft[0][i] * fft[0][i] + fft[0][i + 1] * fft[0][i + 1]);
}
}
updateFrequency(magnitude);
}
}
private static void updateFrequency(float[] mag) {
int peak = 0;
for (int i = 0; i < (int) ((float) samples / 2f); i++) {
if (mag[i] >= mag[peak]) {
peak = i;
}
}
frequency = peak * sampleRate / samples;
Log.d("MainActivity", "Frequency: " + frequency + " Hz");
}
private static float[] getVertices() {
return fftVertices;
}
private static class Renderer implements GLSurfaceView.Renderer {
// ...
private float[] vertices = new float[samples * 2 * (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT)];
public Renderer(Context context) {
// ...
int offset = 0;
for (int i = 0; i < samples; i++) {
vertices[offset++] = (((float) i / (float) samples) * 2f) - 1f;
vertices[offset++] = -1f;
vertices[offset++] = 1f;
vertices[offset++] = 1f;
vertices[offset++] = 1f;
vertices[offset++] = (((float) i / (float) samples) * 2f) - 1f;
vertices[offset++] = ((float) Math.random() * 2f) - 1f;
vertices[offset++] = 1f;
vertices[offset++] = 1f;
vertices[offset++] = 1f;
}
// ...
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
// ...
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
glClear(GL_COLOR_BUFFER_BIT);
// ...
}
}
所以MainActivity 包含RecordAudio 类。我省略了一些不重要的行,比如 OpenGL 或应用程序的东西。基本上,我的应用程序读取音频输入并在屏幕上显示 FFT。我设置了频率计算如下:
我首先制作一个大小的浮点数组:
magnitude[(int) ((float) i / 2f)] = (float) Math.sqrt(fft[0][i] * fft[0][i] + fft[0][i + 1] * fft[0][i + 1]);
在这里,在一个遍历 FFT 的 for 循环中,每次索引为偶数的迭代,都会将一个幅度添加到数组中。然后最后计算频率:
private static void updateFrequency(float[] mag) {
int peak = 0;
for (int i = 0; i < (int) ((float) samples / 2f); i++) {
if (mag[i] >= mag[peak]) {
peak = i;
}
}
frequency = peak * (int) ((float) sampleRate / (float) samples);
Log.d("MainActivity", "Frequency: " + frequency + " Hz");
}
通过找到峰值位置,然后计算频率。
我的问题是:我正在向手机的麦克风播放 440 Hz 正弦波,它的输出是很多零星的数字,但其中一些是 430 Hz。这是一个示例输出:
12-31 16:34:24.992 387.59766 Hz
12-31 16:34:25.022 430.66406 Hz
12-31 16:34:25.042 387.59766 Hz
12-31 16:34:25.072 430.66406 Hz
12-31 16:34:25.122 387.59766 Hz
12-31 16:34:25.142 430.66406 Hz
12-31 16:34:25.162 387.59766 Hz
12-31 16:34:25.182 430.66406 Hz
12-31 16:34:25.182 387.59766 Hz
12-31 16:34:25.192 430.66406 Hz
12-31 16:34:25.222 430.66406 Hz
12-31 16:34:25.242 387.59766 Hz
12-31 16:34:25.262 430.66406 Hz
12-31 16:34:25.292 430.66406 Hz
12-31 16:34:25.312 387.59766 Hz
12-31 16:34:25.332 387.59766 Hz
12-31 16:34:25.372 430.66406 Hz
12-31 16:34:25.392 387.59766 Hz
12-31 16:34:25.422 430.66406 Hz
12-31 16:34:25.432 1722.6563 Hz
12-31 16:34:25.452 387.59766 Hz
12-31 16:34:25.472 430.66406 Hz
12-31 16:34:25.502 387.59766 Hz
12-31 16:34:25.522 430.66406 Hz
12-31 16:34:25.553 387.59766 Hz
12-31 16:34:25.573 387.59766 Hz
12-31 16:34:25.603 430.66406 Hz
12-31 16:34:25.623 387.59766 Hz
如何才能获得更稳定、更准确的结果?
【问题讨论】:
-
为了稳定:您可以查看峰值插值,因为峰值可能不完全在 bin 内。 dspguru.com/dsp/howtos/how-to-interpolate-fft-peak
-
谢谢,这确实纠正了稳定性和准确性。
-
哦!那挺好的。让我把它作为一个答案,以便人们可以使用它。
-
调谐器应用应该分析音高,而不是峰值频率。
-
@hotpaw2 - 我如何分析音高?我以为我是从峰值频率中得到的。
标签: java android fft frequency-analysis