0

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;
    }
}
Jameson
  • 4,198
  • 4
  • 17
  • 31

2 Answers2

2

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.

jaket
  • 9,140
  • 2
  • 25
  • 44
  • So if I were to increase the frequency from 8,000 to, say, 40,000 I would be able to detect much higher frequencies, but they would be much less accurate. I tried to change the frequency from 8,000 to 4,000 but got an error: `Invalid audio buffer size` at this portion of the code: `int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding); audioRecord = new AudioRecord( MediaRecorder.AudioSource.DEFAULT, frequency, channelConfiguration, audioEncoding, bufferSize);` – Jameson Sep 10 '15 at 18:13
  • Yes, that's correct. but you could always increase the FFT size to compensate. Regarding the error, I would guess that 40000 is an unsupported rate and `getMinBufferSize` is returning an error which you're not checking for. Try 44100 or 48000. – jaket Sep 10 '15 at 18:18
  • How is it that on iOS I managed to program the same thing, but it detects frequencies up to 21kHz while maintaining a high level of accuracy? By increasing both the number of bins and the sample rate? For some reason that's not working--the frequencies I get are all 0 or close to 0. If I had to settle for either a large sample rate or high level of accuracy, is there a way I can just have high accuracy for under 2 kHz? Is that just decreasing the sample rate and somehow avoiding the error I just received? – Jameson Sep 10 '15 at 18:23
  • 1
    If you had a high level of accuracy and a high maximum frequency then the sample rate had to be high and you must of had enough bins. Plain physics. And yes, it is not an uncommon practice to decrease the sample rate to increase resolution in the low frequencies. You could keep the 8kHz sample rate, change your buffer size to 2048 and have 3.9Hz of resolution for example. p.s. I don't know why think you need to use `getMinBufferSize`. Just choose the size you prefer for your fft. – jaket Sep 10 '15 at 18:30
  • Ahh. It just seems I was using unsupported numbers. For example, 40000 instead of 44100. Thanks. – Jameson Sep 10 '15 at 18:30
  • Just out of curiosity, what's the downside to just making a ton of bins? Obviously these mics can't pick up much higher frequencies than 22kHz, but what's to limit the bins? – Jameson Sep 10 '15 at 18:33
0

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.

Community
  • 1
  • 1
KillaKem
  • 995
  • 1
  • 13
  • 29