Tuesday, June 5, 2012

Implement Voice Changer, by changing sampleRateInHz of AudioRecord and AudioTrack

Refer to the last previous exercise of "AudioRecord and AudioTrack". We can assign sampling frequency (sampleRateInHz) on AudioRecord and AudioTrack.

With different sampleRateInHz assigned on AudioRecord and AudioTrack, we can implement Voice Changer easily.

Selectable sampleRateInHz on AudioRecord and AudioTrack


Please note that currently 44100Hz is currently the only rate that is guaranteed to work on all devices, but other rates such as 22050, 16000, and 11025 may work on some devices .

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 {
 
 Integer[] freqset = {11025, 16000, 22050, 44100};
 private ArrayAdapter<Integer> 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<Integer>(this, android.R.layout.simple_spinner_item, freqset);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spFrequency.setAdapter(adapter);

    }
    
    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();

  }};
  
 OnClickListener stopRecOnClickListener
 = new OnClickListener(){
  
  @Override
  public void onClick(View arg0) {
   recording = false;
  }};
  
 OnClickListener playBackOnClickListener
     = new OnClickListener(){

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

  File file = new File(Environment.getExternalStorageDirectory(), "test.pcm"); 
  
  int sampleFreq = (Integer)spFrequency.getSelectedItem();
    
  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 sampleFreq = (Integer)spFrequency.getSelectedItem();
   
   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();
  }
 }
 
}


<?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.

Download the files.

updated with demo video HERE.

11 comments:

Edwin said...

Hi, could you maybe extend your example with the new 4.1 preprocessing effects? For example including the Noise Suppressor (http://developer.android.com/reference/android/media/audiofx/NoiseSuppressor.html )?

Ramon said...

Thank you, bud. Needed something that could quickly introduce me to Android audio handling, and your tutorial was all about it!

Anonymous said...

Hi Androider! How can we change the voice to another one like a cat or a robot .. similar to talking tom cat i was eager to know the way they process our voice .. I have searched in internet but no proper explanation was available ...

Erik said...

Hello Anonymous,

as I know, to generate robot sound, mainlly remove high frequency components. Some voice changer swap high frequency components and low frequency components. I have no any idea to implement right now.

Anonymous said...

Hi, currently when click on the spinner it displays the freq but i want to know how i can change to make the spinner display some names instead of the showing the freq like "normal voice" something like that? Thank You

Erik said...

That means you want Spinner with different display text and return value.

Anonymous said...

Hi Android-re thank you so much for the prompt reply.. :) It really helpful for my project.

Anonymous said...

Hi,after i implement "Spinner with different display text and return value" to the voice changer function(followed your voice changer using frequency tutorial) the app crashes abruptly whenever i click on the record button..could you pls help me on this? stuck on this problem for 2 days unable to proceed on other things.

Erik said...

please read here.

Anonymous said...

Thank you, its working now.

Mirza said...

Thanx, it works perfectly,
I am trying to apply apply diffrent sound effects in this code
like echoing, Robotic sound etc
will you suggest me how can i achieve this.