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 !