2015-09-10 75 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