Thursday 10 March 2016

Audio Visualization for A Simple Android Recorder

In this post Im going to extent an app from my previous post A Simple Android Recorder by adding a visualization to it so the first thing to do would be create a class named MyVisualiser and coppy the following code to it.
You can download the entire code here.

MyVisualiser.java

package avinash.pet.project.androidrecorder;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;


public class MyVisualizer extends View{

    private Paint mForePaint = new Paint();
    private byte[] mBytes;
    private float[] mPoints;
    private Rect mRect = new Rect();

    public MyVisualizer(Context context) {
        super(context);
        initialize();
    }
    public MyVisualizer(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }
    public MyVisualizer(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize();
    }

    void initialize(){
        mBytes=null;
        mForePaint.setStrokeWidth(1f);
        mForePaint.setAntiAlias(true);
        mForePaint.setColor(Color.rgb(0, 128, 255));
    }

    public void updateVisualizer(byte[] bytes) {
        mBytes = bytes;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mBytes == null) {
            return;
        }

        if (mPoints == null || mPoints.length < mBytes.length * 4) {
            mPoints = new float[mBytes.length * 4];
        }

        mRect.set(0, 0, getWidth(), getHeight());

        for (int i = 0; i < mBytes.length - 1; i++) {
            mPoints[i * 4] = mRect.width() * i / (mBytes.length - 1);
            mPoints[i * 4 + 1] = mRect.height() / 2 + ((byte) (mBytes[i] + 128)) * (mRect.height() / 2) / 128;
            mPoints[i * 4 + 2] = mRect.width() * (i + 1) / (mBytes.length - 1);
            mPoints[i * 4 + 3] = mRect.height() / 2 + ((byte) (mBytes[i + 1] + 128)) * (mRect.height() / 2) / 128;
        }

        canvas.drawLines(mPoints, mForePaint);
    }

}

now make some changes to the xml file activity_main.xml to add the Visualizer view to it as follows

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <avinash.pet.project.androidrecorder.MyVisualizer
        android:id="@+id/visualizer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button            android:id="@+id/btntoggelRec"
            android:layout_width="0dp"
            android:layout_height="80dp"
            android:layout_weight="1"
            android:text="Record"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textStyle="bold" />

        <Button
            android:id="@+id/btnPlayRec"
            android:layout_width="0dp"
            android:layout_height="80dp"
            android:layout_weight="1"
            android:text="Play"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textStyle="bold" />
    </LinearLayout>

</LinearLayout>

The layout should look like the following Image

 

the last thing is the Java code in the MainActivity

MainActivity.java

package avinash.pet.project.androidrecorder;

import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.media.audiofx.Visualizer;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private MediaRecorder audioRecorder;
    private boolean mIsRecording;
    private boolean isRecording;
    private boolean isPlaying;
    private MediaPlayer player;
    private MyVisualizer visualizerView;
    private Visualizer mVisualizer;
    private MediaPlayer.OnCompletionListener completeListener;

    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Button record = (Button) findViewById(R.id.btntoggelRec);
        final Button playBack = (Button) findViewById(R.id.btnPlayRec);
        visualizerView = (MyVisualizer) findViewById(R.id.visualizer);

        // set recording boolean flag to false        isRecording = false;
        // set playing flag to false        isPlaying = false;

        //get the output file where you want the recording to be stored
        final String outputFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/recording.3gp";

        // step 1 : setup MediaRecorder        audioRecorder = new MediaRecorder();
        audioRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        audioRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        audioRecorder.setAudioEncoder(MediaRecorder.OutputFormat.AMR_NB);
        audioRecorder.setOutputFile(outputFile);
        // End step 1
        //step 2 : setup recording start/stop
        record.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isRecording) {
                    Toast.makeText(MainActivity.this, "Recording Stoped", Toast.LENGTH_SHORT).show();
                    audioRecorder.stop();
                    audioRecorder.release();
                    isRecording = false;
                    playBack.setEnabled(true);
                    ((Button) v).setText("Record");


                } else {
                    try {
                        Toast.makeText(MainActivity.this, "Recording Started", Toast.LENGTH_SHORT).show();
                        audioRecorder.prepare();
                        audioRecorder.start();
                        isRecording = true;
                        ((Button) v).setText("Stop");
                        playBack.setEnabled(false);
                    } catch (Exception ex) {
                        Toast.makeText(MainActivity.this, "Recording Error", Toast.LENGTH_SHORT).show();
                    }
                }
            }
        });
        // end of step 2
        // step 3 : setup playback start/Stop
        playBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isPlaying) {
                    player.stop();
                    player.release();
                    isPlaying = false;
                    record.setEnabled(true);
                    mVisualizer.setEnabled(false);
                    ((Button) v).setText("Play");
                } else {

                    try {
                        record.setEnabled(false);
                        player = new MediaPlayer();
                        player.setDataSource(outputFile);
                        player.setOnCompletionListener(completeListener);
                        player.prepare();
                        setupVisualizerFxAndUI();
                        player.start();
                        mVisualizer.setEnabled(true);
                        isPlaying = true;
                        ((Button) v).setText("Stop");
                        Toast.makeText(MainActivity.this, "PlayBack Started", Toast.LENGTH_SHORT).show();
                    } catch (IOException e) {
                        e.printStackTrace();
                        Toast.makeText(MainActivity.this, "No recording to play please record first", Toast.LENGTH_SHORT).show();
                    }
                }
            }
        });

        completeListener = new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                player.stop();
                player.release();
                isPlaying = false;
                record.setEnabled(true);
                mVisualizer.setEnabled(false);
                playBack.setText("Play");
            }
        };

    }

    private void setupVisualizerFxAndUI() {

        // Create the Visualizer object and attach it to our media player.        mVisualizer = new Visualizer(player.getAudioSessionId());
        mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
        mVisualizer.setDataCaptureListener(
                new Visualizer.OnDataCaptureListener() {
                    public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {
                        visualizerView.updateVisualizer(bytes);
                    }

                    public void onFftDataCapture(Visualizer visualizer,
                                                 byte[] bytes, int samplingRate) {
                    }
                }, Visualizer.getMaxCaptureRate() / 2, true, false);
    }


}

Also make sure that you have added permissions in the manifest files as in the previous post A Simple Android Recorder.
You can download the entire code here.

Well you are all set to build the app and check it for yourself.

Wednesday 9 March 2016

A Simple Android Recorder

Android has provided many different Media classes that are able to record media(audio) and play them one among them is the MediaRecorder Class.
In this post I'm going walk you through a recording app that uses MediaRecorder class.

Lets just start with designing a basic xml layout of the recording Activity
You can download the entire code here.

activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:paddingBottom="@dimen/activity_vertical_margin"
     android:paddingLeft="@dimen/activity_horizontal_margin" 
     android:paddingRight="@dimen/activity_horizontal_margin"  
     android:paddingTop="@dimen/activity_vertical_margin"
     tools:context=".MainActivity" >

        <Button 
             android:id="@+id/btntoggelRec" 
             android:layout_width="0dp"
             android:layout_height="80dp" 
             android:layout_weight="1"
             android:text="Record" 
             android:textAppearance="?android:attr/textAppearanceLarge" 
             android:textStyle="bold" />
        <Button 
             android:id="@+id/btnPlayRec"
             android:layout_width="0dp" 
             android:layout_height="80dp" 
             android:layout_weight="1" 
             android:text="Play" 
             android:textAppearance="?android:attr/textAppearanceLarge"
             android:textStyle="bold" />

</LinearLayout>

after using the above code you should have a layout as follows


as you see we have two buttons one for the recording start/stop and other for playback start/stop.

the next step now would be start with the Java code

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private MediaRecorder audioRecorder;
    private boolean mIsRecording;
    private boolean isRecording;
    private boolean isPlaying;
    private MediaPlayer player;

    @Override
     protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button record = (Button) findViewById(R.id.btntoggelRec);
        Button playBack = (Button) findViewById(R.id.btnPlayRec);

        // set recording boolean flag to false 
        isRecording = false;
        // set playing flag to false 
        isPlaying = false;

        //get the output file where you want the recording to be stored 
        final String outputFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/recording.3gp";

        // step 1 : setup MediaRecorder
        audioRecorder = new MediaRecorder();
        audioRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        audioRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        audioRecorder.setAudioEncoder(MediaRecorder.OutputFormat.AMR_NB);
        audioRecorder.setOutputFile(outputFile);
        // End step 1
        //step 2 : setup recording start/stop 
        record.setOnClickListener(new View.OnClickListener() {
            @Override            public void onClick(View v) {
                if(isRecording){
                    Toast.makeText(MainActivity.this,"Recording Stoped",Toast.LENGTH_SHORT).show();
                    audioRecorder.stop();
                    audioRecorder.release();
                    isRecording=false;
                    ((Button)v).setText("Record");


                }else {
                    try {
                        Toast.makeText(MainActivity.this,"Recording Started",Toast.LENGTH_SHORT).show();
                        audioRecorder.prepare();
                        audioRecorder.start();
                        isRecording=true;
                        ((Button)v).setText("Stop");
                    }catch (Exception ex){
                        Toast.makeText(MainActivity.this,"Recording Error",Toast.LENGTH_SHORT).show();
                    }
                }
            }
        });
        // end of step 2
        player = new MediaPlayer();

        // step 3 : setup playback start/Stop
        playBack.setOnClickListener(new View.OnClickListener() {
            @Override 
            public void onClick(View v) {
                if(isPlaying){
                    player.stop();
                    player.release();
                    isPlaying = false;
                    ((Button)v).setText("Play");
                }else {

                    try {
                        player.setDataSource(outputFile);
                        player.prepare();
                        player.start();
                        isPlaying=true;
                        ((Button)v).setText("Stop");
                        Toast.makeText(MainActivity.this, "PlayBack Started", Toast.LENGTH_SHORT).show();
                    } catch (IOException e) {
                        e.printStackTrace();
                        Toast.makeText(MainActivity.this,"No recording to play please record first",Toast.LENGTH_SHORT).show();
                    }
                }
            }
        });
        // end of step 3



    }
}

as you see the code I have  marked the start and end of steps I will explain each of them now
Step 1: Setup MediaRecorder
in this step I have created a MediaRecorder object and then initilized it with some compulsory fields that are
  1. source of audio
  2. output format
  3. audio encoder
  4. the path of the audio file to be stored
Step 2 : Setup Recording Start/Stop 
This step has a button click listener in which the recording is toggled according to the boolean flag (isRecording). If false start recording if true stop. Notice the exceptions thrown by the MediaRecorder functions.

Step 3 : Setup Playback Start/Stop
This step has smiler a button click listener in which the playback is toggled according to the boolean flag (isPlaying). If false start recording if true stop.
notice the class that is used to play is MediaPlayer class.

the last thing now you will have to do to get app runnung is adding the permissions in the manifest file in order to access the mic and the storage these permissions are as follows.

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

well no no can go ahead and try to run your recorder app.
You can download the entire code here.

In the next post we will extend this app to a little more features like visualization
Please feel free to comment.