Android如何实时获取声音频率?

20
我一直在尝试使用FFT实时获取声音频率(数字),但出现了运行时错误。有人可以帮忙吗?
package com.example.recordsound;

import edu.emory.mathcs.jtransforms.fft.DoubleFFT_1D;

import ca.uol.aig.fftpack.RealDoubleFFT;

public class MainActivity extends Activity implements OnClickListener{

int audioSource = MediaRecorder.AudioSource.MIC;    // Audio source is the device MIC
int channelConfig = AudioFormat.CHANNEL_IN_MONO;    // Recording in mono
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT; // Records in 16bit

private DoubleFFT_1D fft;                           // The fft double array
private RealDoubleFFT transformer;
int blockSize = 256;                               // deal with this many samples at a time
int sampleRate = 8000;                             // Sample rate in Hz
public double frequency = 0.0;                      // the frequency given

RecordAudio recordTask;                             // Creates a Record Audio command
TextView tv;                                        // Creates a text view for the frequency
boolean started = false;
Button startStopButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv = (TextView)findViewById(R.id.textView1);  
    startStopButton= (Button)findViewById(R.id.button1);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}


private class RecordAudio extends AsyncTask<Void, Double, Void>{
    @Override
    protected Void doInBackground(Void... params){      

        /*Calculates the fft and frequency of the input*/
        //try{
            int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioEncoding);                // Gets the minimum buffer needed
            AudioRecord audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioEncoding, bufferSize);   // The RAW PCM sample recording



            short[] buffer = new short[blockSize];          // Save the raw PCM samples as short bytes

          //  double[] audioDataDoubles = new double[(blockSize*2)]; // Same values as above, as doubles
       //   ----------------------------------------------- 
            double[] re = new double[blockSize];
            double[] im = new double[blockSize];
            double[] magnitude = new double[blockSize];
       //   ----------------------------------------------------
            double[] toTransform = new double[blockSize];

            tv.setText("Hello");
           // fft = new DoubleFFT_1D(blockSize);


            try{
            audioRecord.startRecording();  //Start
            }catch(Throwable t){
                Log.e("AudioRecord", "Recording Failed");
            }

            while(started){
                /* Reads the data from the microphone. it takes in data 
                 * to the size of the window "blockSize". The data is then
                 * given in to audioRecord. The int returned is the number
                 * of bytes that were read*/

                int bufferReadResult = audioRecord.read(buffer, 0, blockSize);

                // Read in the data from the mic to the array
                for(int i = 0; i < blockSize && i < bufferReadResult; i++) {

                    /* dividing the short by 32768.0 gives us the 
                     * result in a range -1.0 to 1.0.
                     * Data for the compextForward is given back 
                     * as two numbers in sequence. Therefore audioDataDoubles
                     * needs to be twice as large*/

                   // audioDataDoubles[2*i] = (double) buffer[i]/32768.0; // signed 16 bit
                    //audioDataDoubles[(2*i)+1] = 0.0;
                    toTransform[i] = (double) buffer[i] / 32768.0; // signed 16 bit

                }

                //audiodataDoubles now holds data to work with
               // fft.complexForward(audioDataDoubles);
                transformer.ft(toTransform);
   //------------------------------------------------------------------------------------------
                // Calculate the Real and imaginary and Magnitude.
                for(int i = 0; i < blockSize; i++){
                    // real is stored in first part of array
                    re[i] = toTransform[i*2];
                    // imaginary is stored in the sequential part
                    im[i] = toTransform[(i*2)+1];
                    // magnitude is calculated by the square root of (imaginary^2 + real^2)
                    magnitude[i] = Math.sqrt((re[i] * re[i]) + (im[i]*im[i]));
                }

                double peak = -1.0;
                // Get the largest magnitude peak
                for(int i = 0; i < blockSize; i++){
                    if(peak < magnitude[i])
                        peak = magnitude[i];
                }
                // calculated the frequency
                frequency = (sampleRate * peak)/blockSize;
//----------------------------------------------------------------------------------------------
                /* calls onProgressUpdate
                 * publishes the frequency
                 */
                publishProgress(frequency);
                try{
                    audioRecord.stop();
                }
                catch(IllegalStateException e){
                    Log.e("Stop failed", e.toString());

                }
            }

    //    } 
        return null;
    }

    protected void onProgressUpdate(Double... frequencies){
        //print the frequency 
        String info = Double.toString(frequencies[0]);
        tv.setText(info);
    }

}

@Override
public void onClick(View v) {
    // TODO Auto-generated method stub
    if(started){
           started = false;
           startStopButton.setText("Start");
           recordTask.cancel(true);
       } else {
           started = true;
           startStopButton.setText("Stop");
           recordTask = new RecordAudio();
           recordTask.execute();
       }

}

}

当我使用OnClick运行程序时,它会立即崩溃。我尝试了两个fft库,但一次只运行一个以查看库是否有效。当它到达将块大小分配给FFT对象的行时,它就会崩溃。有人可以帮忙吗?


我正在尝试获取频率值而不是图形。 - Prerak Diwan
你能提供崩溃的logcat捕获吗?堆栈跟踪会很有帮助。在这种形式下,我可以看到可能会崩溃,因为“transformer”从未初始化,所以一旦到达“transformer.ft(toTransform)”行,它将崩溃并出现“NullPointerException”。 - Larry Schiefer
3个回答

12

尝试使用这个快速傅里叶变换(FFT):

public class FFT {

  int n, m;

  // Lookup tables. Only need to recompute when size of FFT changes.
  double[] cos;
  double[] sin;

  public FFT(int n) {
      this.n = n;
      this.m = (int) (Math.log(n) / Math.log(2));

      // Make sure n is a power of 2
      if (n != (1 << m))
          throw new RuntimeException("FFT length must be power of 2");

      // precompute tables
      cos = new double[n / 2];
      sin = new double[n / 2];

      for (int i = 0; i < n / 2; i++) {
          cos[i] = Math.cos(-2 * Math.PI * i / n);
          sin[i] = Math.sin(-2 * Math.PI * i / n);
      }

  }

  public void fft(double[] x, double[] y) {
      int i, j, k, n1, n2, a;
      double c, s, t1, t2;

      // Bit-reverse
      j = 0;
      n2 = n / 2;
      for (i = 1; i < n - 1; i++) {
          n1 = n2;
          while (j >= n1) {
              j = j - n1;
              n1 = n1 / 2;
          }
          j = j + n1;

          if (i < j) {
              t1 = x[i];
              x[i] = x[j];
              x[j] = t1;
              t1 = y[i];
              y[i] = y[j];
              y[j] = t1;
          }
      }

      // FFT
      n1 = 0;
      n2 = 1;

      for (i = 0; i < m; i++) {
          n1 = n2;
          n2 = n2 + n2;
          a = 0;

          for (j = 0; j < n1; j++) {
              c = cos[a];
              s = sin[a];
              a += 1 << (m - i - 1);

              for (k = j; k < n; k = k + n2) {
                  t1 = c * x[k + n1] - s * y[k + n1];
                  t2 = s * x[k + n1] + c * y[k + n1];
                  x[k + n1] = x[k] - t1;
                  y[k + n1] = y[k] - t2;
                  x[k] = x[k] + t1;
                  y[k] = y[k] + t2;
              }
          }
      }
  }
}

它应该涉及到你所想的内容。如果您决定重新使用它,请给作者适当的信用。

来源/作者:EricLarch


3
你能解释一下我在哪里可以使用这个FFT函数吗? - itzhar
嗨@Avanz,你能帮我吗?我需要创建声音频率检测的演示。我已经搜索了这个库https://android-arsenal.com/details/1/6086,但是没有得到很好的结果。你能否请指导一下你上面提到的解决方案? - Ravindra Kushwaha

6
如果你真的想进行实时音频分析,使用基于Java的方法是不行的。在2013年第四季度,我为公司完成了类似的任务,我们决定使用Kiss FFT(可能是具有BSD许可证的最简单的FFT库),使用NDK编译为Android版本。
本地的C/C++方法比Java的方法快得多。使用前者,我们能够在几乎所有中高端设备上进行实时音频解码和音频特征分析,而这显然是后者无法实现的。
强烈建议你考虑本地方法作为完成此任务的最佳选择。 Kiss FFT是一个非常简单的库(字面意思是Keep It Simple FFT),你不会在Android上编译和使用它时遇到太多麻烦。你不会对性能结果感到失望。

同意,不要使用Java方法,因为在某些设备上它可能会慢得多。 - Edison
请问您能简要介绍一下如何使用这个库吗? - Sahana Prabhakar

0

你解决了这个问题吗?崩溃是由于ArrayIndexOutOfBoundsException引起的。

因此,请修改您的代码为:

double[] re = new double[blockSize];
double[] im = new double[blockSize];
double[] magnitude = new double[blockSize];

// Calculate the Real and imaginary and Magnitude.
for(int i = 0; i < blockSize+1; i++){
    try {
        // real is stored in first part of array
        re[i] = toTransform[i * 2];
        // imaginary is stored in the sequential part
        im[i] = toTransform[(i * 2) + 1];
        // magnitude is calculated by the square root of (imaginary^2 + real^2)
        magnitude[i] = Math.sqrt((re[i] * re[i]) + (im[i] * im[i]));
    }catch (ArrayIndexOutOfBoundsException e){
        Log.e("test", "NULL");
    }
}

double peak = -1.0;
// Get the largest magnitude peak
for(int i = 0; i < blockSize; i++){
    if(peak < magnitude[i])
        peak = magnitude[i];
}
// calculated the frequency
frequency = Double.toString((sampleRate * peak)/blockSize);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接