2015-09-10 73 views
0

我使用this FFTBasedSpectrumAnalyzer来分析麦克风收集的声音。但是,FFTBasedSpectrumAnalyzer创建了一个图形,而我想要一个单独的频率,我可以将其放置在一个标签中,所以我试图通过以下公式获得峰值的频率:mFreq = (((1.0 * frequency)/(1.0 * blockSize)) * mPeakPos)/2。我也通过这个公式得到幅度(以及因此的峰值和峰值频率):Android:通过fft获取更精确的频率

int mPeakPos = 0; 
       double mMaxFFTSample = 150.0; 
       for (int i = 0; i < progress[0].length; i++) { 
        int x = i; 
        int downy = (int) (150 - (progress[0][i] * 10)); 
        int upy = 150; 
        //Log.i("SETTT", "X: " + i + " downy: " + downy + " upy: " + upy); 

        if(downy < mMaxFFTSample) 
        { 
         mMaxFFTSample = downy; 
         //mMag = mMaxFFTSample; 
         mPeakPos = i; 
        } 
       } 

但是,我有两个问题。首先,最大频率偏离10-40赫兹,即使我演奏的是稳定的音调也会变化。其次,我只能分析高达4000赫兹的音频。有没有办法让这个更准确和/或分析高达22 kHz的音频?也许通过编辑块大小为非256或8000以外的频率(即使当我尝试这样做时,mFreq会降至0,并且mMaxFFTSample通常会变为-2)。 谢谢。

下面是完整的代码:

public class FrequencyListener extends AppCompatActivity { 
    private double mFreq; 
    private double mMag; 
    private boolean mDidHitTargetFreq; 
    private View mBackgroundView; 

    int frequency = 8000; 
    int channelConfiguration = AudioFormat.CHANNEL_IN_MONO; 
    int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; 

    AudioRecord audioRecord; 
    private RealDoubleFFT transformer; 
    int blockSize; 
    boolean started = false; 
    boolean CANCELLED_FLAG = false; 


    RecordAudio recordTask; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     blockSize = 256; 
     transformer = new RealDoubleFFT(blockSize); 

     started = true; 
     CANCELLED_FLAG = false; 
     recordTask = new RecordAudio(); 
     recordTask.execute(); 
    } 

    private class RecordAudio extends AsyncTask<Void, double[], Boolean> { 

     @Override 
     protected Boolean doInBackground(Void... params) { 

      int bufferSize = AudioRecord.getMinBufferSize(frequency, 
        channelConfiguration, audioEncoding); 
      audioRecord = new AudioRecord(
        MediaRecorder.AudioSource.DEFAULT, frequency, 
        channelConfiguration, audioEncoding, bufferSize); 
      int bufferReadResult; 
      short[] buffer = new short[blockSize]; 
      double[] toTransform = new double[blockSize]; 
      try { 
       audioRecord.startRecording(); 
      } catch (IllegalStateException e) { 
       Log.e("Recording failed", e.toString()); 

      } 
      while (started) { 
       if (isCancelled() || (CANCELLED_FLAG == true)) { 

        started = false; 
        //publishProgress(cancelledResult); 
        Log.d("doInBackground", "Cancelling the RecordTask"); 
        break; 
       } else { 
        bufferReadResult = audioRecord.read(buffer, 0, blockSize); 

        for (int i = 0; i < blockSize && i < bufferReadResult; i++) { 
         toTransform[i] = (double) buffer[i]/32768.0; // signed 16 bit 
        } 

        transformer.ft(toTransform); 

        publishProgress(toTransform); 

       } 

      } 
      return true; 
     } 
     @Override 
     protected void onProgressUpdate(double[]...progress) { 

      int mPeakPos = 0; 
      double mMaxFFTSample = 150.0; 
      for (int i = 0; i < progress[0].length; i++) { 
       int x = i; 
       int downy = (int) (150 - (progress[0][i] * 10)); 
       int upy = 150; 
       //Log.i("SETTT", "X: " + i + " downy: " + downy + " upy: " + upy); 

       if(downy < mMaxFFTSample) 
       { 
        mMaxFFTSample = downy; 
        //mMag = mMaxFFTSample; 
        mPeakPos = i; 
       } 
      } 

      mFreq = (((1.0 * frequency)/(1.0 * blockSize)) * mPeakPos)/2; 
      Log.i("SETTT", "FREQ: " + mFreq + " MAG: " + mMaxFFTSample); 

     } 
     @Override 
     protected void onPostExecute(Boolean result) { 
      super.onPostExecute(result); 
      try{ 
       audioRecord.stop(); 
      } 
      catch(IllegalStateException e){ 
       Log.e("Stop failed", e.toString()); 

      } 
     } 
    } 

    @Override 
    protected void onPause() { 
     super.onPause(); 
     started = false; 
    } 

    @Override 
    protected void onResume() { 
     super.onResume(); 
     started = true; 
    } 
} 

回答

2

能够由数字信号表示的最大频率总是采样率/ 2。这被称为Nyquist frequency。如果您需要测量4kHz以上的信号,那么唯一可能的解决方案是提高采样率。

下一个问题是FFT的频率分辨率,它是FFT大小和采样率的函数。

binWidthInHz = sampleRate/numBins; 

你的情况,你有8000个256箱一个采样率,以便每个区间为31.25赫兹宽。增加分辨率的唯一方法是a)降低采样率或b)增加fft的大小。

最后一点。它似乎没有在你的信号上应用任何窗口。结果是由于spectral leakage,你的峰值将被抹掉。将窗口函数(例如Hann function)应用于时域信号将反击此行为。实质上,FFT算法通过将信号的副本连接在一起来将信号看作是无限长的。除非你的信号符合某些条件,否则最有可能在缓冲器的最后一个样本和第一个样本之间有一个大的跳跃。窗口函数将渐变应用于缓冲区的开始和结束处,以使其平滑。

+0

因此,如果我要将频率从8,000增加到,40,000我将能够检测到更高的频率,但他们会更不准确 我试图改变频率从8,000到4,000,但有一个错误:'无效au dio buffer size'在这部分代码中: 'int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration,audioEncoding); audioRecord =新的AudioRecord( MediaRecorder.AudioSource。默认,频率, channelConfiguration,audioEncoding,bufferSize);' – Jameson

+0

是的,这是正确的。但你总是可以增加FFT大小来补偿。关于这个错误,我猜想40000是不支持的速率,而'getMinBufferSize'返回一个你没有检查的错误。尝试44100或48000. – jaket

+0

在iOS上我是如何设计相同的东西的,但它能够检测高达21kHz的频率,同时保持高水平的准确度?通过增加垃圾箱数量和采样率?出于某种原因无法运作 - 频率我得到的都是0或接近0。 如果我不得不满足于出大的采样率和很高的精确度,是有办法,我可以只具有高精确度低于2 kHz?这只是减少采样率,并以某种方式避免我刚刚收到的错误? – Jameson

0

为了增加您可以分析的最大频率,您需要将采样频率提高到您希望分析的最高频率(奈奎斯特频率)的两倍。

为了提高频率分辨率,您可以通过观察信号较长时间来增加时域采样的数量。除了提高分辨率之外,您还可以选择通过填充输入信号在执行FFT之前或通过在FFT之后的频率点之间执行某种插值(有关零填充和频率分辨率的更多信息,请参阅this post