Saturday, April 26, 2014

AudioRecord and AudioTrack, and to implement voice changer

This example show how to use android.media.AudioRecord and android.media.AudioTrack, to record and playback audio. And also show how to implement voice changer by recording and playing audio in different sampling frequency. (Actually, it is almost same as my old example)


AndroidAudioRecordActivity.java
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_CONFIGURATION_MONO, 
     AudioFormat.ENCODING_PCM_16BIT);
   
   short[] audioData = new short[minBufferSize];
   
   AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
     sampleFreq,
     AudioFormat.CHANNEL_CONFIGURATION_MONO,
     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();
   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];
  
  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];

   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,
     sampleFreq,
     AudioFormat.CHANNEL_CONFIGURATION_MONO,
     AudioFormat.ENCODING_PCM_16BIT,
     bufferSizeInBytes,
     AudioTrack.MODE_STREAM);
   
   audioTrack.play();
   audioTrack.write(audioData, 0, bufferSizeInBytes);

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

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Sampling Frequency" />
    <Spinner
        android:id = "@+id/frequency"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/startrec"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Start Recording" />
    <Button
        android:id="@+id/stoprec"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Stop Recording" />
    <Button
        android:id="@+id/playback"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Play Back" />

</LinearLayout>

Permission of "android.permission.RECORD_AUDIO" and "android.permission.WRITE_EXTERNAL_STORAGE" are needed in AndroidManifest.xml.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.exercise.AndroidAudioRecord"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="10" />
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AndroidAudioRecordActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

download filesDownload the files.

Download and try the APK HERE.

3 comments:

a-rational man said...

it would be better to use

audioRecord.release();

after
audioRecord.stop();

in order to prevent crashes

Xuan nguyen truong said...

How to save audio with sample rate?

Ashish Dudeja said...

Hello

Greetings of the day.

Firstly thank you for this post.
my question is, how can i save the changed voice file into any of the android supported audio format.

Regards
Ashish Dudeja