MediaRecorder has a variety of other methods available that we can use in relation to audio capture.
getMaxAmplitude: Allows us to request the maximum amplitude of audio that has been recorded by the MediaPlayer. The value is reset each time the method is called, so each call will return the maximum amplitude from the last time it is called. An audio level meter may be implemented by calling this method periodically.
setMaxDuration: Allows us to specify a maximum recording duration in milliseconds. This method must be called after the setOutputFormat method but before the prepare method.
setMaxFileSize: Allows us to specify a maximum file size for the recording in bytes. As with setMaxDuration, this method must be called after the setOutputFormat method but before the prepare method.
Here is an update to the custom recorder application we went through previously that includes a display of the current amplitude.
package com.apress.proandroidmedia.ch07.customrecorder;
public class CustomRecorder extends Activity implements OnClickListener, OnCompletionListener {
In this version, we have added a TextView called amplitudeTextView. This will display the numeric amplitude of the audio input.
TextView statusTextView, amplitudeTextView;
Button startRecording, stopRecording, playRecording, finishButton;
MediaRecorder recorder;
MediaPlayer player;
File audioFile;
We’ll need an instance of a new class called RecordAmplitude. This class is an inner class that is defined toward the end of this source code listing. It uses a Boolean called isRecording that will be set to true when we start the MediaRecorder.
RecordAmplitude recordAmplitude;
boolean isRecording = false;
@Override
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.main);
statusTextView = (TextView) this.findViewById(R.id.StatusTextView);
statusTextView.setText("Ready");
We’ll use a TextView to display the current amplitude of the audio as it is captured.
amplitudeTextView = (TextView) this
.findViewById(R.id.AmplitudeTextView);
amplitudeTextView.setText("0");
stopRecording = (Button) this.findViewById(R.id.StopRecording);
startRecording = (Button) this.findViewById(R.id.StartRecording);
playRecording = (Button) this.findViewById(R.id.PlayRecording);
finishButton = (Button) this.findViewById(R.id.FinishButton);
startRecording.setOnClickListener(this);
stopRecording.setOnClickListener(this);
playRecording.setOnClickListener(this);
finishButton.setOnClickListener(this);
stopRecording.setEnabled(false);
When we finish the recording, we set the isRecording Boolean to false and call cancel on our RecordAmplitude class. Since RecordAmplitude extends AsyncTask, calling cancel with true as the parameter will interrupt its thread if necessary.
isRecording = false;
"IOException in MediaPalyer.setDataSource", e);
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
File path = new File(Environment.getExternalStorageDirectory()
recorder.setOutputFile(audioFile.getAbsolutePath());
try {
After we start the recording, we set the isRecording Boolean to true and create a new instance of RecordAmplitude. Since RecordAmplitude extends AsyncTask, we’ll call the execute method to start the RecordAmplitude’s task running.
isRecording = true;
public void onCompletion(MediaPlayer mp) { playRecording.setEnabled(true);
stopRecording.setEnabled(false);
startRecording.setEnabled(true);
statusTextView.setText("Ready");
}
Here is the definition of RecordAmplitude. It extends AsyncTask, which is a nice utility class in Android that provides a thread to run long-running tasks without tying up the user interface or making an application unresponsive.
private class RecordAmplitude extends AsyncTask<Void, Integer, Void> {
The doInBackground method runs on a separate thread and is run when the execute method is called on the object. This method loops as long as isRecording is true and calls Thread.sleep(500), which causes it to not do anything for half a second. Once that is complete, it calls publishProgress and passes in the result of getMaxAmplitude on the MediaRecorder object.
The preceding call to publishProgress calls the onProgressUpdate method defined here, which runs on the main thread so it can interact with the user interface. In this case, it is updating the amplitudeTextView with the value that is passed in from the
publishProgress method call.
protected void onProgressUpdate(Integer... progress) { amplitudeTextView.setText(progress[0].toString());
} } }
Of course, we’ll need to update the layout XML to include the TextView for displaying the amplitude.
<?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="wrap_content" android:layout_height=
"wrap_content" android:id="@+id/StatusTextView" android:text="Status"
android:textSize="35dip"></TextView>
<TextView android:layout_width="wrap_content" android:layout_height=
"wrap_content" android:id="@+id/AmplitudeTextView" android:textSize="35dip"
android:text="0"></TextView>
<Button android:text="Start Recording" android:id="@+id/StartRecording"
android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
<Button android:text="Stop Recording" android:id="@+id/StopRecording"
android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
<Button android:text="Play Recording" android:id="@+id/PlayRecording"
android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
<Button android:layout_width="wrap_content" android:layout_height=
"wrap_content" android:id="@+id/FinishButton" android:text="Finish"></Button>
</LinearLayout>
As we can see, using an AsyncTask to do something periodically is a nice way to provide automatically updating information to the user while something else is in progress. This provides a nicer user experience for our MediaRecorder example. Using the
getMaxAmplitude method provides the user with some feedback about the recording that is currently happening.
In Android 2.2, Froyo, the following methods were made available:
setAudioChannels: Allows us to specify the number of audio channels that will be recorded. Typically this will be either one channel (mono) or two channels (stereo). This method must be called prior to the prepare method.
setAudioEncodingBitRate: Allows us to specify the number of bits per second that will be used by the encoder when compressing the audio.
This method must be called prior to the prepare method.
setAudioSamplingRate: Allows us to specify the sampling rate of the audio as it is captured and encoded. The applicable rates are
determined by the hardware and codec being used. This method must be called prior to the prepare method.
Inserting Audio into the MediaStore
Audio recordings may be put into the MediaStore content provider so they are available to other applications. The process is very similar to the process we used earlier to add images to the MediaStore. In this case though, we’ll add them after they are created.
We create a ContentValues object to hold the data that we’ll insert into the MediaStore.
A ContentValues object is made up of a series of key/value pairs. The keys that may be used are defined as constants in the MediaStore.Audio.Media class (and those classes it inherits from).
The MediaStore.Audio.Media.DATA constant is the key for the path to the recorded file. It is the only required pair in order to insert the file into the MediaStore.
To do the actual insert into the MediaStore, we use the insert method on a
ContentResolver object with the Uri to the table for audio files on the SD card and the ContentValues object containing the data. The Uri is defined as a constant in
MediaStore.Audio.Media named EXTERNAL_CONTENT_URI.
Here is a snippet that may be plugged into the CustomRecorder example just after the release method is called on the MediaRecorder (recorder.release()). It will cause the recording to be inserted into the MediaStore and made available to other applications that use the MediaStore for finding audio to play back.
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.TITLE, "This Isn't Music");
contentValues.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis());
contentValues.put(MediaStore.Audio.Media.DATA, audioFile.getAbsolutePath());
Uri newUri =
getContentResolver().insert(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues);
Of course, in order to use the foregoing snippet, we’ll need to add these imports:
import android.content.ContentValues;
import android.net.Uri;
import android.provider.MediaStore;