Raw Audio Capture and Playback Example

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()Image
                .getAbsolutePath() + "/Android/data/com.apress.proandroidmedia.ch07Image
.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=Image
    "@+id/StatusTextView"/>

    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"Image
     android:text="Start Recording" android:id="@+id/StartRecordingButton"></Button>
    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"Image
     android:text="Stop Recording" android:id="@+id/StopRecordingButton"></Button>
    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"Image
     android:text="Start Playback" android:id="@+id/StartPlaybackButton"></Button>
    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"Image
     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">Image
</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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.57.164