调谐器应用中的频率不正确

时间:2014-12-31 21:38:40

标签: java android fft frequency-analysis

我目前正在开发一个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赫兹的正弦波,它的输出是很多零星的数字,但其中一些是430赫兹。这是一个示例输出:

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

如何才能获得更稳定和准确的结果?

1 个答案:

答案 0 :(得分:2)

DFT的一个问题是,如果你的峰值很宽并且位于两个(或更多)的箱子上并且它会稍微移动(由于多普勒或其他原因),你会得到能量波动。两个箱子。

一种方法是增加FFT的区间数(FFT的点数)。您也可以考虑Zero-padding

如果您不想弄乱FFT的点数,另一种方法是从FFT数据中插入峰值(和频率)。 This article gives这种技术的良好方法。

我不确定准确性,但这应该有助于我认为结果的稳定性(减少波动)。