android AcousticEchoCanceler doesn't seem to work on most devices

6k views Asked by At

I am building an application which requires the mic to cancel any sound coming from the speaker. It seems that this issue is almost a conspiracy on-line as others with the exact same problem were never responded to for an extended duration.

Android's native hardware accelerated AcousticEchoCanceler does not seem to work on most devices. Tests where made on many devices and the ones that seemed to work Include Nexus 5, and Moto X while almost all Samsung devices tested could not remove background sound.Note: All phones tested return true for AcousticEchoCanceler.isAvailable()

However, there must be a solution since applications such as Skype or WhatsApp seem to cancel sounds outside their app context, i.e. a call is on speaker and the Microphone cancels any feedback received.

This simplified recording app records sound to a file and plays it later when play is clicked.

MainActivity.java

public class MainActivity extends Activity {

Button startRec, stopRec, playBack;
int minBufferSizeIn;
AudioRecord audioRecord;
short[] audioData;
Boolean recording;
int sampleRateInHz = 48000;
private String TAG = "TAG";

/**
 * Called when the activity is first created.
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_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);
    playBack.setEnabled(false);
    startRec.setEnabled(true);
    stopRec.setEnabled(false);

    minBufferSizeIn = AudioRecord.getMinBufferSize(sampleRateInHz,
            AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT);

    audioData = new short[minBufferSizeIn];

    audioRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION,
            sampleRateInHz,
            AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT,
            minBufferSizeIn);

}

OnClickListener startRecOnClickListener
        = new OnClickListener() {

    @Override
    public void onClick(View arg0) {
        playBack.setEnabled(false);
        startRec.setEnabled(false);
        stopRec.setEnabled(true);
        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) {
        playBack.setEnabled(true);
        startRec.setEnabled(false);
        stopRec.setEnabled(false);
        recording = false;
    }
};



OnClickListener playBackOnClickListener
        = new OnClickListener() {

    @Override
    public void onClick(View v) {
        playBack.setEnabled(false);
        startRec.setEnabled(true);
        stopRec.setEnabled(false);
        playRecord();
    }

};


@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void startRecord() {
    File file = new File(Environment.getExternalStorageDirectory(), "test.pcm");

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

        NoiseSuppressor ns;
        AcousticEchoCanceler aec;

        if (NoiseSuppressor.isAvailable()) {
            ns = NoiseSuppressor.create(audioRecord.getAudioSessionId());
            if (ns != null) {
                ns.setEnabled(true);
            } else {
                Log.e(TAG, "AudioInput: NoiseSuppressor is null and not enabled");
            }
        }

        if (AcousticEchoCanceler.isAvailable()) {
            aec = AcousticEchoCanceler.create(audioRecord.getAudioSessionId());
            if (aec != null) {
                aec.setEnabled(true);
            } else {
                Log.e(TAG, "AudioInput: AcousticEchoCanceler is null and not enabled");
            }
        }
        audioRecord.startRecording();

        while (recording) {
            int numberOfShort = audioRecord.read(audioData, 0, minBufferSizeIn);
            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 {
        FileInputStream 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();

        AudioTrack audioTrack = new AudioTrack(
                AudioManager.STREAM_MUSIC, sampleRateInHz,
                AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT,
                bufferSizeInBytes,
                AudioTrack.MODE_STREAM);
        while(audioTrack.getState() != AudioTrack.STATE_INITIALIZED){

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


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

}

}

activity_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_world" />
<Button
    android:id="@+id/startrec"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Start Recording Test" />
<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>

AndroidManfist.xml permissions

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

To Verify if the device works simply play something in the background and then click Start Recording record a small sector and then click Stop Recording at this point click Play Back and check if you hear the background sound. If you can hear the background sound then AEC is not working. But why is this inconsistency occurring, or how do I achieve echo cancellation (I am already using WebRTC within my app for noise cancellation within my apps context)

Any help would be appreciated !

2

There are 2 answers

1
houshunwei On

Problem:

AudioTrack audioTrack = new AudioTrack(
    AudioManager.STREAM_MUSIC, sampleRateInHz,
    AudioFormat.CHANNEL_OUT_MONO,
    AudioFormat.ENCODING_PCM_16BIT,
    bufferSizeInBytes,
    AudioTrack.MODE_STREAM);

should be:

AudioTrack audioTrack = new AudioTrack(
    AudioManager.STREAM_MUSIC, sampleRateInHz,
    AudioFormat.CHANNEL_OUT_MONO,
    AudioFormat.ENCODING_PCM_16BIT,
    bufferSizeInBytes,
    AudioTrack.MODE_STREAM
    sessionId); // this param is important, which is audioRecord. getAudioSessionId()
0
Jeff Gates On

I was having the same problem on my S6 device. I played with a variety of settings and found a set that seem to enable AEC. The differences between my and your setup seem to be:

  • 16k sample rate
  • audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);

For others, I'm not sure precisely what settings are needed to get AEC working. I do know that my same app with

  • 48k sample rate
  • NO audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
  • NO android.permission.MODIFY_AUDIO_SETTINGS

does not successfully AEC.