ch.ethz.coss.nervousnet.vm.sensors.NoiseSensor.java Source code

Java tutorial

Introduction

Here is the source code for ch.ethz.coss.nervousnet.vm.sensors.NoiseSensor.java

Source

/*******************************************************************************
 * *     Nervousnet - a distributed middleware software for social sensing.
 * *      It is responsible for collecting and managing data in a fully de-centralised fashion
 * *
 * *     Copyright (C) 2016 ETH Zrich, COSS
 * *
 * *     This file is part of Nervousnet Framework
 * *
 * *     Nervousnet is free software: you can redistribute it and/or modify
 * *     it under the terms of the GNU General Public License as published by
 * *     the Free Software Foundation, either version 3 of the License, or
 * *     (at your option) any later version.
 * *
 * *     Nervousnet is distributed in the hope that it will be useful,
 * *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 * *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * *     GNU General Public License for more details.
 * *
 * *     You should have received a copy of the GNU General Public License
 * *     along with NervousNet. If not, see <http://www.gnu.org/licenses/>.
 * *
 * *
 * *    Contributors:
 * *    Prasad Pulikal - prasad.pulikal@gess.ethz.ch  -  Initial API and implementation
 *******************************************************************************/
package ch.ethz.coss.nervousnet.vm.sensors;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder.AudioSource;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.v4.content.ContextCompat;
import android.util.Log;

import ch.ethz.coss.nervousnet.lib.NoiseReading;
import ch.ethz.coss.nervousnet.vm.NNLog;
import ch.ethz.coss.nervousnet.vm.NervousnetVMConstants;
import ch.ethz.coss.nervousnet.vm.utils.FFT;

public class NoiseSensor extends BaseSensor {
    public static final int BANDCOUNT = 12;
    public static final int SAMPPERSEC = 8000; // 8000Hz sampling rate, the
    // minimum
    public static final int NYQUIST = 4000; // Take this 4000Hz regardless of
    // SAMPPERSEC
    public static final float BANDLOGBASE = (float) Math.exp(Math.log(NYQUIST) / BANDCOUNT); // Get
    // basis
    // bands
    public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    public static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO;
    private static final String LOG_TAG = NoiseSensor.class.getSimpleName();
    private HandlerThread hthread;
    private Handler handler;
    private short[] buffer;
    private int samplesRead;
    private int buflen;
    private int fftlen;
    private int buffersize;
    private AudioRecord audioRecord;
    private Context mContext;

    public NoiseSensor(byte sensorState, Context context) {
        this.sensorState = sensorState;
        this.mContext = context;

    }

    public void startRecording(long duration) {
        new AudioTask().execute(duration);
    }

    private int binlog(int bits) {
        int log = 0;
        if ((bits & 0xffff0000) != 0) {
            bits >>>= 16;
            log = 16;
        }
        if (bits >= 256) {
            bits >>>= 8;
            log += 8;
        }
        if (bits >= 16) {
            bits >>>= 4;
            log += 4;
        }
        if (bits >= 4) {
            bits >>>= 2;
            log += 2;
        }
        return log + (bits >>> 1);
    }

    @Override
    public boolean start() {

        if (sensorState == NervousnetVMConstants.SENSOR_STATE_NOT_AVAILABLE) {
            NNLog.d(LOG_TAG, "Cancelled NoiseSensor sensor as Sensor is not available.");
            return false;
        } else if (sensorState == NervousnetVMConstants.SENSOR_STATE_AVAILABLE_PERMISSION_DENIED) {
            NNLog.d(LOG_TAG, "Cancelled NoiseSensor sensor as permission denied by user.");
            return false;
        } else if (sensorState == NervousnetVMConstants.SENSOR_STATE_AVAILABLE_BUT_OFF) {
            NNLog.d(LOG_TAG, "Cancelled NoiseSensor sensor as Sensor state is switched off.");
            return false;
        }

        if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(mContext,
                Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            setSensorState(NervousnetVMConstants.SENSOR_STATE_AVAILABLE_PERMISSION_DENIED);
            return false;
        }

        NNLog.d(LOG_TAG, "Starting NoiseSensor sensor with state = " + sensorState);

        hthread = new HandlerThread("HandlerThread");
        hthread.start();
        handler = new Handler(hthread.getLooper());
        final Runnable run = new Runnable() {
            @Override
            public void run() {
                startRecording(500);
                if (handler != null)
                    handler.postDelayed(this, 1000);// NervousnetVMConstants.sensor_freq_constants[3][sensorState
                // - 1]); // TODO: test this
            }

        };

        boolean flag = handler.postDelayed(run, 500);

        return true;
    }

    @Override
    public boolean stopAndRestart(byte state) {
        if (state == NervousnetVMConstants.SENSOR_STATE_NOT_AVAILABLE) {
            NNLog.d(LOG_TAG, "Cancelled NoiseSensor sensor as Sensor is not available.");
            return false;
        } else if (state == NervousnetVMConstants.SENSOR_STATE_AVAILABLE_PERMISSION_DENIED) {
            NNLog.d(LOG_TAG, "Cancelled NoiseSensor sensor as permission denied by user.");
            return false;
        } else if (state == NervousnetVMConstants.SENSOR_STATE_AVAILABLE_BUT_OFF) {
            setSensorState(state);
            NNLog.d(LOG_TAG, "Cancelled NoiseSensor sensor as Sensor state is switched off.");
            return false;
        }

        stop(false);
        setSensorState(state);
        NNLog.d(LOG_TAG, "Restarting NoiseSensor with state = " + sensorState);
        start();
        return true;
    }

    @Override
    public boolean stop(boolean changeStateFlag) {
        if (sensorState == NervousnetVMConstants.SENSOR_STATE_NOT_AVAILABLE) {
            NNLog.d(LOG_TAG, "Cancelled stop NoiseSensor sensor as Sensor state is not available ");
            return false;
        } else if (sensorState == NervousnetVMConstants.SENSOR_STATE_AVAILABLE_PERMISSION_DENIED) {
            NNLog.d(LOG_TAG, "Cancelled stop NoiseSensor sensor as permission denied by user.");
            return false;
        } else if (sensorState == NervousnetVMConstants.SENSOR_STATE_AVAILABLE_BUT_OFF) {
            NNLog.d(LOG_TAG, "Cancelled stop NoiseSensor sensor as Sensor state is switched off ");
            return false;
        }
        setSensorState(NervousnetVMConstants.SENSOR_STATE_AVAILABLE_BUT_OFF);
        NNLog.d(LOG_TAG, "Stopped NoiseSensor with state = " + sensorState);
        this.reading = null;
        if (handler != null)
            handler.removeCallbacks(hthread);
        hthread = null;
        handler = null;

        return true;
    }

    public class AudioTask extends AsyncTask<Long, Void, Void> {
        private long recordTime;
        private float rms;
        private float spl;
        private float[] bands;
        private int[] mSampleRates = new int[] { 8000, 11025, 22050, 44100 };

        @Override
        protected Void doInBackground(Long... params) {
            // Recording
            long duration = params[0];
            long sampleDurationProd = duration * SAMPPERSEC;
            int exp1 = binlog((int) (sampleDurationProd / 1000)) + 1;
            int exp2 = binlog(AudioRecord.getMinBufferSize(SAMPPERSEC, CHANNEL, ENCODING)) + 1;
            buflen = (int) Math.pow(2, Math.max(exp1, exp2));
            buffersize = buflen * 2;
            fftlen = buflen / 2;
            buffer = new short[buffersize];

            audioRecord = findAudioRecord();// new
            // AudioRecord(MediaRecorder.AudioSource.MIC,
            // SAMPPERSEC, CHANNEL, ENCODING,
            // buffersize);
            if (audioRecord != null) {
                long startTime = System.currentTimeMillis();
                audioRecord.startRecording();
                samplesRead = audioRecord.read(buffer, 0, buffersize);
                audioRecord.stop();
                audioRecord.release();
                long stopTime = System.currentTimeMillis();

                long recordTime = startTime + (stopTime - startTime) / 2;

                // Arrays for FFT
                double[] re = new double[buflen];
                double[] im = new double[buflen];

                // SPL, RMS
                double spl = 0.0;
                double rms = 0.0;

                for (int i = 0; i < buflen; i++) {
                    re[i] = ((buffer[i])) / 32768.d;
                    rms = rms + Math.abs(buffer[i]);
                }
                rms = rms / buflen;

                // See for the pressure formula:
                // http://de.wikipedia.org/wiki/Schalldruckpegel
                // See
                // http://www.reddit.com/r/androiddev/comments/14bnrp/how_to_find_microphone_modelspec_of_android_device/
                // See "Android 4.0 compability definition guideline", chapter
                // 5.3
                // Basically devices are required to conform to the equation:
                // --------- 20.d * Math.log10(gain * 2500) == 90.d
                // Thus deriving
                // --------- gain = Math.pow(10.0, 90.0 / 20.0) / 2500.0
                // and
                // --------- spl = 20.d * Math.log10(gain * rms);
                // (This is as close as we can get to absolute sound pressure
                // levels
                // (spl). The accuracy depends on how close the device is to
                // Googles
                // requirements.)
                //
                double gain = Math.pow(10.0, 90.0 / 20.0) / 2500.0;
                spl = 20.d * Math.log10(gain * rms);

                // FFT
                FFT fft = new FFT(buflen);
                double[] window = fft.getWindow();
                fft.fft(re, im);

                double[] power = new double[fftlen];
                for (int i = 0; i < fftlen; i++) {
                    power[i] = (re[i] * re[i] - im[i] * im[i]) / buflen;
                }

                double freqFact = (double) (SAMPPERSEC) / (double) buflen;
                // double freq = freqFact * peaki;

                // Logarithmic structured band counting, from 0Hz to 4000Hz
                // Frequency splits (given 12 bands): 2, 4, 8, 16, 32, 63, 126,
                // 252,
                // 503, 1004, 2004, 4000 (in Hz)
                float[] bands = new float[BANDCOUNT];
                for (int i = 0; i < BANDCOUNT; i++) {
                    float avg = 0;
                    int lowFreq = Math.round((float) Math.pow(BANDLOGBASE, i));
                    int hiFreq = Math.round((float) Math.pow(BANDLOGBASE, i + 1));
                    int fromIndex = (int) Math.round((lowFreq) / freqFact);
                    int toIndex = Math.min((int) Math.round((hiFreq) / freqFact), fftlen);
                    for (int j = fromIndex; j < toIndex; j++) {
                        avg += Math.abs(power[j]);
                    }
                    avg = (toIndex > fromIndex) ? avg / (toIndex - fromIndex) : avg;
                    bands[i] = avg;
                }

                // Pass data to listeners
                // Data: PCM RMS raw value, total noise level (spl in dB), log
                // structured frequency bands
                this.recordTime = recordTime;
                this.rms = (float) rms;
                this.spl = (float) spl;
                this.bands = bands;

            }

            return null;
        }

        @Override
        public void onPostExecute(Void params) {
            dataReady(new NoiseReading(recordTime, spl));
        }

        public AudioRecord findAudioRecord() {
            for (int rate : mSampleRates) {
                for (short audioFormat : new short[] { AudioFormat.ENCODING_PCM_8BIT,
                        AudioFormat.ENCODING_PCM_16BIT }) {
                    for (short channelConfig : new short[] { AudioFormat.CHANNEL_IN_MONO,
                            AudioFormat.CHANNEL_IN_STEREO }) {
                        try {
                            NNLog.d("NoiseSensor", "Attempting rate " + rate + "Hz, bits: " + audioFormat
                                    + ", channel: " + channelConfig);
                            int bufferSize = AudioRecord.getMinBufferSize(rate, channelConfig, audioFormat);

                            if (bufferSize != AudioRecord.ERROR_BAD_VALUE) {
                                // check if we can instantiate and have a
                                // success
                                AudioRecord recorder = new AudioRecord(AudioSource.DEFAULT, rate, channelConfig,
                                        audioFormat, bufferSize);

                                if (recorder.getState() == AudioRecord.STATE_INITIALIZED)
                                    return recorder;
                            }
                        } catch (Exception e) {
                            Log.e("NoiseSensor", rate + "Exception, keep trying.", e);
                        }
                    }
                }
            }
            return null;
        }

    }

}