3
votes

I have having an issue where when I recorded an audio, and I clicked on the "Play Back" button, it only plays for about 3 secs and not the fully recorded audio. I have tried increasing the buffer size but it crashes my app instead. Anyone could advise please? Thanks

Below is my code
package com.exercise.AndroidAudioRecord;

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.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

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.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.Toast;

public class AndroidAudioRecordActivity extends Activity {

    String[] freqText = {"11.025 KHz (Lowest)", "16.000 KHz", "22.050 KHz", "44.100 KHz (Highest)"};
    Integer[] freqset = {11025, 16000, 22050, 44100};
    private ArrayAdapter<String> adapter;

    Spinner spFrequency;
    Button startRec, stopRec, playBack;

    Boolean recording;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        startRec = (Button)findViewById(R.id.startrec);
        stopRec = (Button)findViewById(R.id.stoprec);
        playBack = (Button)findViewById(R.id.playback);

        startRec.setOnClickListener(startRecOnClickListener);
        stopRec.setOnClickListener(stopRecOnClickListener);
        playBack.setOnClickListener(playBackOnClickListener);

        spFrequency = (Spinner)findViewById(R.id.frequency);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, freqText);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spFrequency.setAdapter(adapter);

        stopRec.setEnabled(false);
    }

    OnClickListener startRecOnClickListener
    = new OnClickListener(){

        @Override
        public void onClick(View arg0) {

            Thread recordThread = new Thread(new Runnable(){

                @Override
                public void run() {
                    recording = true;
                    startRecord();
                }

            });

            recordThread.start();
            startRec.setEnabled(false);
            stopRec.setEnabled(true);

        }};

    OnClickListener stopRecOnClickListener
    = new OnClickListener(){

        @Override
        public void onClick(View arg0) {
            recording = false;
            startRec.setEnabled(true);
            stopRec.setEnabled(false);
        }};

    OnClickListener playBackOnClickListener
        = new OnClickListener(){

            @Override
            public void onClick(View v) {
                playRecord();
            }

    };

    private void startRecord(){

        File file = new File(Environment.getExternalStorageDirectory(), "test.pcm"); 

        int selectedPos = spFrequency.getSelectedItemPosition();
        int sampleFreq = freqset[selectedPos];

        final String promptStartRecord = 
                "startRecord()\n"
                + file.getAbsolutePath() + "\n"
                + (String)spFrequency.getSelectedItem();

        runOnUiThread(new Runnable(){

            @Override
            public void run() {
                Toast.makeText(AndroidAudioRecordActivity.this, 
                        promptStartRecord, 
                        Toast.LENGTH_LONG).show();
            }});

        try {
            file.createNewFile();

            OutputStream outputStream = new FileOutputStream(file);
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
            DataOutputStream dataOutputStream = new DataOutputStream(bufferedOutputStream);

            int minBufferSize = AudioRecord.getMinBufferSize(sampleFreq, 
                    AudioFormat.CHANNEL_IN_STEREO, 
                    AudioFormat.ENCODING_PCM_16BIT);

            short[] audioData = new short[minBufferSize];

            AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                    sampleFreq,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minBufferSize);

            audioRecord.startRecording();

            while(recording){
                int numberOfShort = audioRecord.read(audioData, 0, minBufferSize);
                for(int i = 0; i < numberOfShort; i++){
                    dataOutputStream.writeShort(audioData[i]);
                }
            }

            audioRecord.stop();
            audioRecord.release();
            dataOutputStream.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }




    void playRecord(){

        File file = new File(Environment.getExternalStorageDirectory(), "test.pcm");

        int shortSizeInBytes = Short.SIZE/Byte.SIZE;
        int bufferSizeInBytes = (int)(file.length()/shortSizeInBytes);

        short[] audioData = new short[bufferSizeInBytes];
        //int  buflen=bufferSizeInBytes/2; 
        try {
            InputStream inputStream = new FileInputStream(file);
            BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
            DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);

            int i = 0;
            while(dataInputStream.available() > 0){
                audioData[i] = dataInputStream.readShort();
                i++;
            }

            dataInputStream.close();

            int selectedPos = spFrequency.getSelectedItemPosition();
            int sampleFreq = freqset[selectedPos];
            int sampleFreq1 = 12000;
            int sampleFreq2 = 15000;
            final String promptPlayRecord = 
                    "PlayRecord()\n"
                    + file.getAbsolutePath() + "\n"
                    + (String)spFrequency.getSelectedItem();

            Toast.makeText(AndroidAudioRecordActivity.this, 
                    promptPlayRecord, 
                    Toast.LENGTH_LONG).show();

            AudioTrack audioTrack = new AudioTrack(
                    AudioManager.STREAM_MUSIC,
                    sampleFreq1,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    bufferSizeInBytes,
                    AudioTrack.MODE_STREAM);

            int stereo=audioTrack.setStereoVolume(0.0f, 1.0f);

            audioTrack.write(audioData, 0, bufferSizeInBytes);
            audioTrack.play();


            AudioTrack audioTrack2 = new AudioTrack(
                    AudioManager.STREAM_MUSIC,
                    sampleFreq2,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    bufferSizeInBytes,
                    AudioTrack.MODE_STREAM);

            int stereo2=audioTrack.setStereoVolume(1.0f, 0.0f);

            audioTrack2.write(audioData, 0, bufferSizeInBytes);
            audioTrack2.play();




        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Thanks all!

3

3 Answers

2
votes

First, I'm assuming that the recorded PCM file is of the expected size and that makes all of the code you dumped related to recording the file irrelevant.

I can see one issue in the playback code that will result in the playback being 1/2 of the expected length.

int shortSizeInBytes = Short.SIZE/Byte.SIZE;
int bufferSizeInBytes = (int)(file.length()/shortSizeInBytes);

bufferSizeInBytes should simply file.length() and fileLength/2 is the number of samples.

Where you define short[] audioData you want to use the number of samples:

short[] audioData = new short[file.length()/2];

And where you construct the AudioTrack you'll want to use the number of bytes:

AudioTrack audioTrack = new AudioTrack(
                AudioManager.STREAM_MUSIC,
                sampleFreq1,
                AudioFormat.CHANNEL_OUT_STEREO,
                AudioFormat.ENCODING_PCM_16BIT,
                file.length(),
                AudioTrack.MODE_STREAM);

And finally, where you call audioTrack.write you need to use number of samples again.

EDIT: From the stack trace you've added to the question it is clear that the AudioTrack constructor is failing with an error. You can check this error by calling audioTrack.status after the constructor call. I can think a couple possible issues.

First, you are specifying AudioTrack.MODE_STREAM which typically means that you are going to specify a smallish (a few kBytes maybe) buffer for the audio track and then make successive calls to the write function to playback the entire source buffer. AudioTrack.MODE_STATIC on the other hand is used to play back an entire buffer in one shot - as you are attempting to do. Given that the goal in MODE_STREAM is to use the smallest buffer possible it is conceivable that specifying a buffer size of 1.64MB (20secs * 2 bytes per sample * 44100) is not supported. So the first thing I would do is change the mode to MODE_STATIC. I don't know the API well enough to tell you if a buffer size that large is even supported in that mode. If not then your going to have to stream the proper way.

1
votes

Logcat Output

09-22 01:23:42.940: I/Reverb(22286):  getpid() 22286, IPCThreadState::self()->getCallingPid() 22286
09-22 01:23:42.955: E/AudioTrack(22286): AudioFlinger could not create track, status: -12
09-22 01:23:42.955: E/AudioTrack-JNI(22286): Error initializing AudioTrack
09-22 01:23:42.965: E/AudioTrack-Java(22286): [ android.media.AudioTrack ] Error code -20 when initializing AudioTrack.
09-22 01:23:42.965: D/AndroidRuntime(22286): Shutting down VM
09-22 01:23:42.965: W/dalvikvm(22286): threadid=1: thread exiting with uncaught exception (group=0x40eed2a0)
09-22 01:23:42.985: E/AndroidRuntime(22286): FATAL EXCEPTION: main
09-22 01:23:42.985: E/AndroidRuntime(22286): java.lang.IllegalStateException: play() called on uninitialized AudioTrack.
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.media.AudioTrack.play(AudioTrack.java:887)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at com.example.android2.MainActivity.playRecord(MainActivity.java:225)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at com.example.android2.MainActivity$3.onClick(MainActivity.java:99)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.view.View.performClick(View.java:4211)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.view.View$PerformClick.run(View.java:17267)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.os.Handler.handleCallback(Handler.java:615)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.os.Handler.dispatchMessage(Handler.java:92)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.os.Looper.loop(Looper.java:137)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.app.ActivityThread.main(ActivityThread.java:4898)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at java.lang.reflect.Method.invokeNative(Native Method)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at java.lang.reflect.Method.invoke(Method.java:511)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1006)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at dalvik.system.NativeStart.main(Native Method)

Below is the amended code

void playRecord(){

        File file = new File(Environment.getExternalStorageDirectory(), "test.pcm");

        short[] audioData = new short[(int) (file.length()/2)];
        try {
            InputStream inputStream = new FileInputStream(file);
            BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
            DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);

            int i = 0;
            while(dataInputStream.available() > 0){
                audioData[i] = dataInputStream.readShort();
                i++;
            }

            dataInputStream.close();

            int selectedPos = spFrequency.getSelectedItemPosition();
            int sampleFreq = freqset[selectedPos];
            int sampleFreq1 = 44100;
            int sampleFreq2 = 44400;
            final String promptPlayRecord = 
                    "PlayRecord()\n"
                    + file.getAbsolutePath() + "\n"
                    + (String)spFrequency.getSelectedItem();

            Toast.makeText(MainActivity.this, 
                    promptPlayRecord, 
                    Toast.LENGTH_LONG).show();

            AudioTrack audioTrack = new AudioTrack(
                    AudioManager.STREAM_MUSIC,
                    sampleFreq1,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    (int)file.length(),
                    AudioTrack.MODE_STREAM);

            int stereo=audioTrack.setStereoVolume(0.0f, 1.0f);

            audioTrack.write(audioData, 0, (int)(file.length()/2));
            audioTrack.play();

            AudioTrack audioTrack2 = new AudioTrack(
                    AudioManager.STREAM_MUSIC,
                    sampleFreq2,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    (int)file.length(),
                    AudioTrack.MODE_STREAM);

            int stereo2=audioTrack2.setStereoVolume(1.0f, 0.0f);

            audioTrack2.write(audioData, 0, (int)(file.length()/2));
            audioTrack2.play();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
1
votes

You are read block of data only once here.

        while(dataInputStream.available() > 0){
            audioData[i] = dataInputStream.readShort();
            i++;
        }

Since available it is not a remain size, but size which available to be read without blocking (i.e. it could be zero while more data left unread), you need to read until read method returns less than zero (i.e. EOF). For example:

// Do here AudioTrack initialization
// ...
InputStream inputStream = new FileInputStream(file);
byte[] byteBuf = new byte[32768]; // just any even size
short[] shortBuf = new short[byteBuf.length / 2]; // buffer of shorts
audioTrack.play(); // start playback. Will wait until first data is written
for(;;) {
    int l = inputStream.read(byteBuf);
    if (l < 0) break; // finish reading when EOF is truly reached 
    int o = 0;
    for (int i = 0; i < l; i += 2) {
        shortBuf[o] = (short)((byteBuf[i] << 8) | (byteBuf[i + 1] & 0xFF)); // convert two big-endian bytes to one short
        o++;
    }
    int res = audioTrack.write(shortBuf, 0, o); // write next portion
    if (res < 0) { 
         // handle audio writing error;
         break;
    }
}
// ...