【问题标题】:Incorrect Frequency in tuner app调谐器应用程序中的频率不正确
【发布时间】: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


【解决方案1】:

DFT 的一个问题是,如果您的峰值很宽并且位于两个(或更多)区间上并且它的移动非常轻微(由于多普勒或其他原因),那么您将在两个垃圾箱。

一种方法是增加 FFT 的 bin 数量(FFT 的点数)。你也可以考虑Zero-padding

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多