Created
January 28, 2011 21:11
-
-
Save tommedema/800956 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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