Jameson
Jameson

Reputation: 4266

Android: Get more precise frequencies through fft

I'm using this FFTBasedSpectrumAnalyzer to analyze sound gathered by the mic. However, the FFTBasedSpectrumAnalyzer created a graph whereas I want a single frequency I could place in a label, so I am trying to get the frequency of the peak by this formula: mFreq = (((1.0 * frequency) / (1.0 * blockSize)) * mPeakPos)/2. I'm also getting the magnitude (and therefore the peak and peak frequency) through this formula:

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;
                    }
                }

However, I have two problems. First, the max frequency is off by 10-40 Hz and varies even as I play a constant tone. Second, I can only analyze audio up to 4000 Hz. Is there a way to make this more accurate and/or analyze audio up to 22 kHz? Perhaps by editing block size to be something other than 256 or frequency other than 8000 (even though when I try this, mFreq drops to 0 and mMaxFFTSample becomes -2, typically). Thank you.

Here is the complete code:

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;
    }
}

Upvotes: 0

Views: 1352

Answers (2)

KillaKem
KillaKem

Reputation: 1025

In order to increase the maximum frequency you can analyse you will need to increase the sampling frequency to twice the highest frequency you wish to analyse (Nyquist Frequency).

In order to improve frequency resolution you can increase the number of time domain samples you have by observing the signal for a longer period of time.Instead of increasing the resolution, you can opt to interpolate between frequency bins by either padding the input signal before performing the FFT or by performing some sort of interpolation between frequency bins after the FFT.(for more information on zero padding and on frequency resolution see this post.

Upvotes: 0

jaket
jaket

Reputation: 9341

The maximum frequency that can be represented by a digital signal is always samplerate/2. This is known as the Nyquist frequency. If you need to measure signals beyond 4kHz then the only possible solution is to increase the sampling rate.

The next issue is the frequency resolution of the FFT, which is a function of the FFT size and the sampling rate.

 binWidthInHz = sampleRate / numBins;

In your case you have a sampleRate of 8000 and 256 bins so each bin is 31.25 Hz wide. The only way to increase the resolution is to a) decrease the sampling rate or b) increase the fft size.

One last point. It doesn't appear that you are applying any windowing to your signal. The result is that your peaks will be smeared out due to spectral leakage. Applying a window function such as the Hann function to your time domain signal will counter act this. In essence, the FFT algorithm treats the signal as if it were infinitely long by concatenating copies of the signal together. Unless your signal meets certain conditions there is most likely a big jump between the last sample of the buffer and the first sample. The window function applies a taper to the start and end of the buffer to smooth it out.

Upvotes: 2

Related Questions