Question

I am having some Accoustic Echo Cancellation experiments for a VoIP application and sound drives me mad. What I am trying to do is simple: I had recorded a sound before. Now I will play that sound and while playing, record another sound, which is the real case for a full duplex VoIP scenario. I use MedaiPlayer for playing sound and MediaRecorder for recording new sound. Below is the full code of the class, modified from Android SoundRecorder Sample. The important points are, mPlayer.setAudioStreamType(AudioManager.MODE_IN_COMMUNICATION); for Android docs say "In communication audio mode. An audio/video chat or VoIP call is established." and mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION); for Android docs say "Microphone audio source tuned for voice communications such as VoIP. It will for instance take advantage of echo cancellation or automatic gain control if available. It otherwise behaves like DEFAULT if no voice processing is applied." and this is so promising. But if I play anything coming out from speakers, I record almost nothing or only weird noises. I can't attach sound waves of recordings as I am a new user here, but the first one is a normal recording, which has normal sound waves. Second recording is the recording while playing the first one, which contains almost nothing, no sound waves. Android seems to turn mic off if there is any activity in speaker. I tried different possibilities for MediaRecorder and MediaPlayer, but no use. What is the correct way of implementing Accoustic Echo Cancellation in Android? I tried on Sony Tablet S and developed using Android 3.0 SDK. Thanks in advance.

package com.kadir.sample;
import android.app.Activity;
import android.widget.LinearLayout;
import android.widget.Toast;
import android.os.Bundle;
import android.os.Environment;
import android.view.ViewGroup;
import android.widget.Button;
import android.view.View;
import android.content.Context;
import android.util.Log;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.media.MediaPlayer;

import java.io.IOException;


public class AudioRecordTest extends Activity
{
    private static final String LOG_TAG = "AudioRecordTest";
    private static String mFileName = null;
    private static String mFileNameConst = null;

    private RecordButton mRecordButton = null;
    private MediaRecorder mRecorder = null;

    private PlayButton   mPlayButton = null;
    private MediaPlayer   mPlayer = null;
    private PlayConstButton mPlayConstButton = null;

    private void onRecord(boolean start) {
        if (start) {
            startRecording();
        } else {
            stopRecording();
        }
    }

    private void onPlay(boolean start) {
        if (start) {
            startPlaying();
        } else {
            stopPlaying();
        }
    }

    private void onPlayConst(boolean start) {
        if (start) {
            startConstPlaying();
        } else {
            stopConstPlaying();
        }
    }

    private void startPlaying() {
        mPlayer = new MediaPlayer();
        try {
            mPlayer.setDataSource(mFileName);
            mPlayer.prepare();
            mPlayer.start();
        } catch (IOException e) {
            Toast.makeText(this, "prepare() failed", Toast.LENGTH_LONG).show();
            Log.e(LOG_TAG, "prepare() failed");
        }
    }

    private void stopPlaying() {
        mPlayer.release();
        mPlayer = null;
    }

    private void startConstPlaying() {
        mPlayer = new MediaPlayer();
        try {
            mPlayer.setDataSource(mFileNameConst);
            mPlayer.setAudioStreamType(AudioManager.MODE_IN_COMMUNICATION);
            mPlayer.prepare();
            mPlayer.start();
        } catch (IOException e) {
            Toast.makeText(this, "prepare() failed", Toast.LENGTH_LONG).show();
            Log.e(LOG_TAG, "prepare() failed");
    }
}

private void stopConstPlaying() {
        mPlayer.release();
        mPlayer = null;
    }

    private void startRecording() {
        mRecorder = new MediaRecorder();
        mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mRecorder.setOutputFile(mFileName);
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

        try {
            mRecorder.prepare();
        } catch (IOException e) {
            Toast.makeText(this, "startRecording() failed", Toast.LENGTH_LONG).show();
            Log.e(LOG_TAG, "prepare() failed");
        }

        mRecorder.start();
    }

    private void stopRecording() {
        mRecorder.stop();
        mRecorder.release();
        mRecorder = null;
    }

    class RecordButton extends Button {
        boolean mStartRecording = true;

        OnClickListener clicker = new OnClickListener() {
            public void onClick(View v) {
                onRecord(mStartRecording);
                if (mStartRecording) {
                    setText("Stop recording");
                } else {
                    setText("Start recording");
                }
                mStartRecording = !mStartRecording;
            }
        };

        public RecordButton(Context ctx) {
            super(ctx);
            setText("Start recording");
            setOnClickListener(clicker);
        }
    }

    class PlayButton extends Button {
        boolean mStartPlaying = true;

        OnClickListener clicker = new OnClickListener() {
            public void onClick(View v) {
                onPlay(mStartPlaying);
                if (mStartPlaying) {
                    setText("Stop playing");
                } else {
                    setText("Start playing");
                }
                mStartPlaying = !mStartPlaying;
            }
        };

        public PlayButton(Context ctx) {
            super(ctx);
            setText("Start playing");
            setOnClickListener(clicker);
        }
    }

    class PlayConstButton extends Button {
        boolean mStartPlaying = true;

        OnClickListener clicker = new OnClickListener() {
            public void onClick(View v) {
                onPlayConst(mStartPlaying);
                if (mStartPlaying) {
                    setText("Stop Constant playing");
                } else {
                    setText("Start Constant playing");
                }
                mStartPlaying = !mStartPlaying;
            }
        };

        public PlayConstButton(Context ctx) {
            super(ctx);
            setText("Start Constant playing");
            setOnClickListener(clicker);
        }
    }

    public AudioRecordTest() {
        mFileName = Environment.getExternalStorageDirectory().getAbsolutePath();
        mFileName += "/audiorecordtest.3gp";
        mFileNameConst = Environment.getExternalStorageDirectory().getAbsolutePath();
        mFileNameConst += "/constant.3gp";
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        LinearLayout ll = new LinearLayout(this);
        mRecordButton = new RecordButton(this);
        ll.addView(mRecordButton,
            new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                0));
        mPlayButton = new PlayButton(this);
        ll.addView(mPlayButton,
            new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                0));
        mPlayConstButton = new PlayConstButton(this);
        ll.addView(mPlayConstButton,
            new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                0));
        setContentView(ll);
    }

    @Override
    public void onPause() {
        super.onPause();
        if (mRecorder != null) {
            mRecorder.release();
            mRecorder = null;
        }

        if (mPlayer != null) {
            mPlayer.release();
            mPlayer = null;
        }
    }
}

UPDATE: I had a little R&D as follows: I change MediaRecorder and MediaPlayer parameters. For each value, I recorded myself, and during recording, I started another play. Then finished recording and listened what I've just recorded. For MediaRecorder, I tried these values: MediaRecorder.AudioSource.DEFAULT,MediaRecorder.AudioSource.MIC, MediaRecorder.AudioSource.VOICE_CALL,MediaRecorder.AudioSource.VOICE_COMMUNICATION,MediaRecorder.AudioSource.VOICE_DOWNLINK,MediaRecorder.AudioSource.VOICE_RECOGNITION,MediaRecorder.AudioSource.VOICE_UPLINK For MediaPlayer, I tried these values: AudioManager.MODE_NORMAL, AudioManager.MODE_CURRENT,AudioManager.MODE_IN_CALL,AudioManager.MODE_IN_COMMUNICATION, AudioManager.STREAM_VOICE_CALL,AudioManager.STREAM_MUSIC. But whatever I tried, I always had either silence or pure noise. I think MediaRecorder and MediaPlayer classes are not sufficient for VoIP. And Android's sound system is a little bit weird for a beginner like me.

Was it helpful?

Solution

I partially figured out. I was using Sony Tablet S with Android 3.2. I tried same program on Archos 70 with Android 2.3. When I record something while playing another thing, both sounds are recorded. That means I have a sound to apply AEC. On Sony, recording was nothing but noises. Now there are two possibilities: Either Sony Tablet S has a problem with microphone implementation (by the way, GTalk works perfect on Sony, but it can be something special as implemented in Android) or Android 3.2 has a problem with microphone implementation.

OTHER TIPS

The Echo cancellation in Android does not work very well in many cases. I think it is related to long echo tail. I know there are third party algorithms for this problem. Google for echo cancellation software and verify that the software supports long echo tail.

Why use a MediaRecorder for a streaming application? Use AudioRecorder instead.

I am currently playing with a similar use case and the AudioRecorder works flawlessly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top