package root.gast.audio.record;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.AudioRecord.OnRecordPositionUpdateListener;
import android.media.MediaRecorder.AudioSource;
import android.os.AsyncTask;
import android.util.Log;
/**
* record an audio clip and pass it to the listener
*
* @author gmilette
*
*/
public class AudioClipRecorder
{
private static final String TAG = "AudioClipRecorder";
private AudioRecord recorder;
private AudioClipListener clipListener;
/**
* state variable to control starting and stopping recording
*/
private boolean continueRecording;
public static final int RECORDER_SAMPLERATE_CD = 44100;
public static final int RECORDER_SAMPLERATE_8000 = 8000;
private static final int DEFAULT_BUFFER_INCREASE_FACTOR = 3;
private AsyncTask task;
private boolean heard;
public AudioClipRecorder(AudioClipListener clipListener)
{
this.clipListener = clipListener;
heard = false;
task = null;
}
public AudioClipRecorder(AudioClipListener clipListener, AsyncTask task)
{
this(clipListener);
this.task = task;
}
/**
* records with some default parameters
*/
public boolean startRecording()
{
return startRecording(RECORDER_SAMPLERATE_8000,
AudioFormat.ENCODING_PCM_16BIT);
}
/**
* start recording: set the parameters that correspond to a buffer that
* contains millisecondsPerAudioClip milliseconds of samples
*/
public boolean startRecordingForTime(int millisecondsPerAudioClip,
int sampleRate, int encoding)
{
float percentOfASecond = (float) millisecondsPerAudioClip / 1000.0f;
int numSamplesRequired = (int) ((float) sampleRate * percentOfASecond);
int bufferSize =
determineCalculatedBufferSize(sampleRate, encoding,
numSamplesRequired);
return doRecording(sampleRate, encoding, bufferSize,
numSamplesRequired, DEFAULT_BUFFER_INCREASE_FACTOR);
}
/**
* start recording: Use a minimum audio buffer and a read buffer of the same
* size.
*/
public boolean startRecording(final int sampleRate, int encoding)
{
int bufferSize = determineMinimumBufferSize(sampleRate, encoding);
return doRecording(sampleRate, encoding, bufferSize, bufferSize,
DEFAULT_BUFFER_INCREASE_FACTOR);
}
private int determineMinimumBufferSize(final int sampleRate, int encoding)
{
int minBufferSize =
AudioRecord.getMinBufferSize(sampleRate,
AudioFormat.CHANNEL_IN_MONO, encoding);
return minBufferSize;
}
/**
* Calculate audio buffer size such that it holds numSamplesInBuffer and is
* bigger than the minimum size<br>
*
* @param numSamplesInBuffer
* Make the audio buffer size big enough to hold this many
* samples
*/
private int determineCalculatedBufferSize(final int sampleRate,
int encoding, int numSamplesInBuffer)
{
int minBufferSize = determineMinimumBufferSize(sampleRate, encoding);
int bufferSize;
// each sample takes two bytes, need a bigger buffer
if (encoding == AudioFormat.ENCODING_PCM_16BIT)
{
bufferSize = numSamplesInBuffer * 2;
}
else
{
bufferSize = numSamplesInBuffer;
}
if (bufferSize < minBufferSize)
{
Log.w(TAG, "Increasing buffer to hold enough samples "
+ minBufferSize + " was: " + bufferSize);
bufferSize = minBufferSize;
}
return bufferSize;
}
/**
* Records audio until stopped the {@link #task} is canceled,
* {@link #continueRecording} is false, or {@link #clipListener} returns
* true <br>
* records audio to a short [readBufferSize] and passes it to
* {@link #clipListener} <br>
* uses an audio buffer of size bufferSize * bufferIncreaseFactor
*
* @param recordingBufferSize
* minimum audio buffer size
* @param readBufferSize
* reads a buffer of this size
* @param bufferIncreaseFactor
* to increase recording buffer size beyond the minimum needed
*/
private boolean doRecording(final int sampleRate, int encoding,
int recordingBufferSize, int readBufferSize,
int bufferIncreaseFactor)
{
if (recordingBufferSize == AudioRecord.ERROR_BAD_VALUE)
{
Log.e(TAG, "Bad encoding value, see logcat");
return false;
}
else if (recordingBufferSize == AudioRecord.ERROR)
{
Log.e(TAG, "Error creating buffer size");
return false;
}
// give it extra space to prevent overflow
int increasedRecordingBufferSize =
recordingBufferSize * bufferIncreaseFactor;
recorder =
new AudioRecord(AudioSource.MIC, sampleRate,
AudioFormat.CHANNEL_IN_MONO, encoding,
increasedRecordingBufferSize);
final short[] readBuffer = new short[readBufferSize];
continueRecording = true;
Log.d(TAG, "start recording, " + "recording bufferSize: "
+ increasedRecordingBufferSize
+ " read buffer size: " + readBufferSize);
//Note: possible IllegalStateException
//if audio recording is already recording or otherwise not available
//AudioRecord.getState() will be AudioRecord.STATE_UNINITIALIZED
recorder.startRecording();
while (continueRecording)
{
int bufferResult = recorder.read(readBuffer, 0, readBufferSize);
//in case external code stopped this while read was happening
if ((!continueRecording) || ((task != null) && task.isCancelled()))
{
break;
}
// check for error conditions
if (bufferResult == AudioRecord.ERROR_INVALID_OPERATION)
{
Log.e(TAG, "error reading: ERROR_INVALID_OPERATION");
}
else if (bufferResult == AudioRecord.ERROR_BAD_VALUE)
{
Log.e(TAG, "error reading: ERROR_BAD_VALUE");
}
else
// no errors, do processing
{
heard = clipListener.heard(readBuffer, sampleRate);
if (heard)
{
stopRecording();
}
}
}
done();
return heard;
}
public boolean isRecording()
{
return continueRecording;
}
public void stopRecording()
{
continueRecording = false;
}
/**
* need to call this when completely done with recording
*/
public void done()
{
Log.d(TAG, "shut down recorder");
if (recorder != null)
{
recorder.stop();
recorder.release();
recorder = null;
}
}
/**
* @param audioData
* will be filled when reading the audio data
*/
private void setOnPositionUpdate(final short[] audioData,
final int sampleRate, int numSamplesInBuffer)
{
// possibly do it that way
// setOnNotification(audioData, sampleRate, numSamplesInBuffer);
OnRecordPositionUpdateListener positionUpdater =
new OnRecordPositionUpdateListener()
{
@Override
public void onPeriodicNotification(AudioRecord recorder)
{
// no need to read the audioData again since it was just
// read
heard = clipListener.heard(audioData, sampleRate);
if (heard)
{
Log.d(TAG, "heard audio");
stopRecording();
}
}
@Override
public void onMarkerReached(AudioRecord recorder)
{
Log.d(TAG, "marker reached");
}
};
// get notified after so many samples collected
recorder.setPositionNotificationPeriod(numSamplesInBuffer);
recorder.setRecordPositionUpdateListener(positionUpdater);
}
}