Here is a full example that records using AudioRecord
and plays back using AudioTrack
. Each of these operations lives in their own thread through the use of AsyncTask
, so that they don't make the application become unresponsive by running in the main thread.
package com.apress.proandroidmedia.ch07.altaudiorecorder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class AltAudioRecorder extends Activity implements OnClickListener {
We have two inner classes defined—one for the recording and one for the playback. Each one extends AsyncTask
.
RecordAudio recordTask;
PlayAudio playTask;
Button startRecordingButton, stopRecordingButton, startPlaybackButton,
stopPlaybackButton;
TextView statusText;
File recordingFile;
We'll use Booleans to keep track of whether we should be recording and playing. These will be used in the loops in recording and playback tasks.
boolean isRecording = false;
boolean isPlaying = false;
Here are the variables that we'll use to define the configuration of both the AudioRecord
and AudioTrack
objects.
// These should really be constants themselves
int frequency = 11025;
int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
statusText = (TextView) this.findViewById(R.id.StatusTextView);
startRecordingButton = (Button) this .findViewById(R.id.StartRecordingButton);
stopRecordingButton = (Button) this .findViewById(R.id.StopRecordingButton);
startPlaybackButton = (Button) this .findViewById(R.id.StartPlaybackButton);
stopPlaybackButton = (Button) this.findViewById(R.id.StopPlaybackButton);
startRecordingButton.setOnClickListener(this);
stopRecordingButton.setOnClickListener(this);
startPlaybackButton.setOnClickListener(this);
stopPlaybackButton.setOnClickListener(this);
stopRecordingButton.setEnabled(false);
startPlaybackButton.setEnabled(false);
stopPlaybackButton.setEnabled(false);
The last thing we'll do in the constructor is create the file that we'll record to and play back from. In this case, we are creating the file in the preferred location for files associated with an application on the SD card.
File path = new File(Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/Android/data/com.apress.proandroidmedia.ch07
.altaudiorecorder/files/");
path.mkdirs();
try {
recordingFile = File.createTempFile("recording", ".pcm", path);
} catch (IOException e) {
throw new RuntimeException("Couldn't create file on SD card", e);
}
}
The onClick
method handles the Button presses generated by the user. Each one corresponds to a specific method.
public void onClick(View v) {
if (v == startRecordingButton) {
record();
} else if (v == stopRecordingButton) {
stopRecording();
} else if (v == startPlaybackButton) {
play();
} else if (v == stopPlaybackButton) {
stopPlaying();
}
}
To start playback, we construct a new PlayAudio
object and call its execute
method, which is inherited from AsyncTask
.
public void play() {
startPlaybackButton.setEnabled(true);
playTask = new PlayAudio();
playTask.execute();
stopPlaybackButton.setEnabled(true);
}
To stop playback, we set the isPlaying
Boolean to false
and that's it. This will cause the PlayAudio
object's loop to finish.
public void stopPlaying() {
isPlaying = false;
stopPlaybackButton.setEnabled(false);
startPlaybackButton.setEnabled(true);
}
To start recording, we construct a RecordAudio
object and call its execute
method.
public void record() {
startRecordingButton.setEnabled(false);
stopRecordingButton.setEnabled(true);
// For Fun
startPlaybackButton.setEnabled(true);
recordTask = new RecordAudio();
recordTask.execute();
}
To stop recording, we simply set the isRecording
Boolean to false
. This allows the RecordAudio
object to stop looping and perform any cleanup.
public void stopRecording() {
isRecording = false;
}
Here is our PlayAudio
inner class. This class extends AsyncTask
and uses an AudioTrack
object to play back the audio.
private class PlayAudio extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
isPlaying = true;
int bufferSize = AudioTrack.getMinBufferSize(frequency,
channelConfiguration, audioEncoding);
short[] audiodata = new short[bufferSize/4];
try {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(
recordingFile)));
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC, frequency,
channelConfiguration, audioEncoding, bufferSize,
AudioTrack.MODE_STREAM);
audioTrack.play();
while (isPlaying && dis.available() > 0) {
int i = 0;
while (dis.available() > 0 && i < audiodata.length) {
audiodata[i] = dis.readShort();
i++;
}
audioTrack.write(audiodata, 0, audiodata.length);
}
dis.close();
startPlaybackButton.setEnabled(false);
stopPlaybackButton.setEnabled(true);
} catch (Throwable t) {
Log.e("AudioTrack", "Playback Failed");
}
return null;
}
}
Last is our RecordAudio
class, which extends AsyncTask
. This class runs an AudioRecord
object in the background and calls publishProgress
to update the UI with an indication of recording progress.
private class RecordAudio extends AsyncTask<Void, Integer, Void> {
@Override
protected Void doInBackground(Void... params) {
isRecording = true;
try {
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(
recordingFile)));
int bufferSize = AudioRecord.getMinBufferSize(frequency,
channelConfiguration, audioEncoding);
AudioRecord audioRecord = new AudioRecord(
MediaRecorder.AudioSource.MIC, frequency,
channelConfiguration, audioEncoding, bufferSize);
short[] buffer = new short[bufferSize];
audioRecord.startRecording();
int r = 0;
while (isRecording) {
int bufferReadResult = audioRecord.read(buffer, 0,
bufferSize);
for (int i = 0; i < bufferReadResult; i++) {
dos.writeShort(buffer[i]);
}
publishProgress(new Integer(r));
r++;
}
audioRecord.stop();
dos.close();
} catch (Throwable t) {
Log.e("AudioRecord", "Recording Failed");
}
return null;
}
When publishProgress
is called, onProgressUpdate
is the method called.
protected void onProgressUpdate(Integer... progress) {
statusText.setText(progress[0].toString());
}
When the doInBackground
method completes, the following onPostExecute
method is called.
protected void onPostExecute(Void result) {
startRecordingButton.setEnabled(true);
stopRecordingButton.setEnabled(false);
startPlaybackButton.setEnabled(true);
}
}
}
Here is the layout XML for the foregoing example:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="Status" android:id=
"@+id/StatusTextView"/>
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Start Recording" android:id="@+id/StartRecordingButton"></Button>
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Stop Recording" android:id="@+id/StopRecordingButton"></Button>
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Start Playback" android:id="@+id/StartPlaybackButton"></Button>
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Stop Playback" android:id="@+id/StopPlaybakButton" ></Button>
</LinearLayout>
And, we'll need to add these permissions to AndroidManifest.xml
.
<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
</uses-permission>
As we have seen, using the AudioRecord
and AudioTrack
classes to create a capture and playback application is much more cumbersome than working with the MediaRecorder
and MediaPlayer
classes. But as we'll see in the next chapter, it is worth the effort when we need to do any type of audio processing or want to synthesize audio.
18.225.57.164