Skip to content

Instantly share code, notes, and snippets.

@tommedema
Created January 28, 2011 21:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tommedema/800956 to your computer and use it in GitHub Desktop.
Save tommedema/800956 to your computer and use it in GitHub Desktop.
package com.tommedema.testapp;
import java.util.ArrayList;
import android.app.Service;
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
public class MicInfoService extends Service implements Runnable {
//messages
public static final int MSG_CLIENT_REGISTER = 1; //command to the service to register a client, receiving callbacks from service - messenger's replyTo field must be a messenger of the client
public static final int MSG_CLIENT_UNREGISTER = 2;
public static final int MSG_MONITOR_START = 3;
public static final int MSG_MONITOR_STOP = 4;
public static final int MSG_NEW_AMPLITUDE = 5;
public static final int MSG_NEW_MAX_AMPLITUDE = 6;
public static final int MSG_NEW_MIN_AMPLITUDE = 7;
//connection
public ArrayList<Messenger> serviceClients = new ArrayList<Messenger>();
public final Messenger incomingMessenger = new Messenger(new IncomingHandler()); //messenger used for incoming messages from clients
//service
private static final long precisionNanoSec = 250000000; //250,000,000 nanoseconds equals 250 milliseconds
private static int minBufferSizeMultiplier = 1;
private volatile Thread analysisThread;
private AudioRecord recorder;
private boolean isMonitoring = false;
private int curAmplitude = 0;
private int sampleRate;
private int channelMode;
private int encodingMode;
private int bufferSize;
//handler of incoming messages from clients
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CLIENT_REGISTER:
Log.v("MicInfoService", "Client registered.");
serviceClients.add(msg.replyTo);
break;
case MSG_CLIENT_UNREGISTER:
Log.v("MicInfoService", "Client unregistered.");
serviceClients.remove(msg.replyTo);
break;
case MSG_MONITOR_START:
Log.v("MicInfoService", "Client requested monitor start.");
startMonitor();
break;
case MSG_MONITOR_STOP:
Log.v("MicInfoService", "Client requested monitor stop.");
stopMonitor();
default:
Log.v("MicInfoService", "Received msg: " + msg.what);
super.handleMessage(msg);
}
}
}
@Override
public void onCreate() {
Log.v("MicInfoService", "MicInfoService created.");
startForeground(0, null); //keep this service running
}
@Override
public void onDestroy() {
Log.v("MicInfoService", "MicInfoService destroyed.");
}
//when binding to service, we return an interface to our messenger for sending messages to service
@Override
public IBinder onBind(Intent intent) {
return incomingMessenger.getBinder();
}
//when clients unbind check if we still need to run
@Override
public boolean onUnbind(Intent intent) {
if (serviceClients.size() == 0) this.stopSelf();
return false;
}
//starts monitoring
public synchronized void startMonitor() {
if (isMonitoring) return; //already monitoring
//we can only start monitoring if the microphone is available
if (!MediaUtil.getMicrophoneAvailable(this)) {
Log.v("MicInfoService", "microphone is not available!");
return;
}
//update bool
isMonitoring = true;
//prepare recorder
sampleRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_SYSTEM);
channelMode = AudioFormat.CHANNEL_IN_MONO;
encodingMode = AudioFormat.ENCODING_PCM_16BIT; //only 16bit encoding is supported at time of development
bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelMode, encodingMode) * minBufferSizeMultiplier;
Log.v("MicInfoService", "bufferSize: " + bufferSize + ", sampleRate: " + sampleRate);
//setup recorder
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelMode, encodingMode, bufferSize);
//start recording
recorder.startRecording();
//start analysis thread if needed, set this thread as important
startThread();
//TODO: fix exception on activity destroy -> create
//TODO: stop service when application has quit
//TODO: properly handle exceptions
//TODO: check if microphone is available, don't update otherwise
}
private synchronized void startThread() {
if (analysisThread == null) {
analysisThread = new Thread(this);
analysisThread.setDaemon(true);
}
if (!analysisThread.isAlive()) analysisThread.start();
}
private synchronized void stopThread() {
if (analysisThread != null) {
Thread moribund = analysisThread;
analysisThread = null;
moribund.interrupt();
}
}
//run method used for analysis thread
public void run() {
while ((Thread.currentThread() == analysisThread) && (!Thread.currentThread().isInterrupted())) { //while analysisThread is set and not interrupted
byte[] audioData = new byte[bufferSize]; //prepare bytes array
long startTime = System.nanoTime();
recorder.read(audioData, 0, bufferSize); //returns amount of bytes read
if (System.nanoTime() - startTime < precisionNanoSec) {
//the blocking read method returned faster than our precision interval, we should increase buffer size to prevent buffer overflows
Log.v("MicInfoService", "Updating minBufferSizeMultiplier to " + minBufferSizeMultiplier);
minBufferSizeMultiplier++;
stopMonitor();
startMonitor();
}
//calculate amplitude root mean square (average) for this 16-bit audio byte array
int audioRMS = 0;
int i = 0;
for (int curIndex = 1; curIndex < audioData.length; curIndex = (i++ * 2 + 1)) {
audioRMS += Math.abs(audioData[curIndex]);
}
audioRMS = audioRMS / i;
//update current amplitude
if (curAmplitude != audioRMS) updateCurAmplitude(audioRMS);
//see if we need to interrupt this thread
try {
this.wait(1);
} catch (InterruptedException e) {
Log.v("MicInfoService", "run interrupted exception");
return;
}
}
}
private synchronized void updateCurAmplitude(int newAmplitude) {
curAmplitude = newAmplitude;
for (int i = serviceClients.size() - 1; i >= 0; i--) {
try {
serviceClients.get(i).send(Message.obtain(null, MSG_NEW_AMPLITUDE, curAmplitude, 0));
} catch (RemoteException e) {
Log.v("MicInfoService", "client " + i + " caused remote exception");
serviceClients.remove(i);
}
}
}
public synchronized void stopMonitor() {
//stop thread
stopThread();
//stop recorder
if (recorder != null) {
if (recorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
Log.v("MicInfoService", "recorder.stop");
try {
recorder.stop();
} catch (Exception e) {
Log.v("MicInfoService", "Exception: recorder stop has illegal state.");
}
}
if (recorder.getState() == AudioRecord.STATE_INITIALIZED) {
Log.v("MicInfoService", "recorder.release");
recorder.release();
}
recorder = null;
}
isMonitoring = false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment