Skip to main content

Echo Cancellation

If your app plays audio and captures the microphone at the same time — a voice assistant, a call, any kind of barge-in — the microphone hears the speaker, and the far end hears itself. Acoustic echo cancellation removes it.

There are two ways to get it, and they can be combined:

  • The device's own canceller, configured on the Realtime engine. Costs no CPU, but the implementation belongs to the platform. On iOS this is one switch; on Android it is several settings that have to agree, and results vary by device.
  • A software AEC node in your graph, from the Speex or Superpowered extensions. Behaves identically everywhere, at the cost of CPU.

This page covers the device canceller on each platform.


iOS

One key:

{
"type": "Switchboard.Realtime",
"config": {
"microphoneEnabled": true,
"voiceProcessingEnabled": true,
"graph": { }
}
}

This enables Apple's voice processing IO, which applies echo cancellation and automatic gain control to the microphone input. No audio-session setup or permissions beyond microphone access are needed.

It does change the output path, so note the constraints:

  • Only mono audio output is supported.
  • Various filters are applied to the output signal.
  • The user cannot set the volume to zero.

Android

Android has no single switch. Two keys do the work, and the device canceller only engages when both are set:

{
"type": "Switchboard.Realtime",
"config": {
"microphoneEnabled": true,
"inputPreset": "VOICE_COMMUNICATION",
"audioManagerMode": "MODE_IN_COMMUNICATION",
"speakerphoneOn": true,
"graph": { }
}
}
KeyWhy it is needed
inputPreset: "VOICE_COMMUNICATION"Asks the capture path for echo cancellation, noise suppression and close-talk gain control
audioManagerMode: "MODE_IN_COMMUNICATION"Puts the process in a voice session, which is what arms the canceller on most devices. Some devices engage it from the input preset alone; others do not
speakerphoneOn: trueOptional. Routes to the loudspeaker rather than the earpiece — there is little echo to cancel on the earpiece, so it matters when testing

The common failure is setting only one of them. MODE_NORMAL leaves the canceller unarmed no matter how the input is configured, and inputPreset defaults to GENERIC, which does not request echo cancellation — so audioManagerMode on its own is not enough either.

outputUsage is not required. Setting it to "USAGE_VOICE_COMMUNICATION" puts playback on in-call volume and voice routing, which is usually what you want in a voice session, but it is a routing and volume choice rather than part of enabling the canceller.

Requirements

Add the permission to your application's AndroidManifest.xml:

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

Without it, audioManagerMode silently does nothing. The SDK does not declare it for you.

These keys also need an application context. Switchboard.initialize(context, ...) provides one; if you initialize from C++ with Switchboard::initialize(...) — a React Native C++ TurboModule, a Flutter FFI plugin, or a plain NDK app — no Kotlin runs and the SDK never receives one, so hand it over once:

Switchboard.setApplicationContext(context.applicationContext)

Without a context both keys are ignored and a warning is logged; the engine still starts.

This is best-effort

Android's specification says a device should provide echo cancellation for VOICE_COMMUNICATION capture — not must. The implementation is the manufacturer's, so results vary by device and OS version even with identical configuration. Known cases:

  • Some devices need MODE_IN_COMMUNICATION on top of the input preset; others engage the canceller from the preset alone.
  • VOICE_COMMUNICATION may only work in mono on some devices, and can prevent a low-latency stream in stereo.
  • A handful of devices return silence for VOICE_COMMUNICATION capture entirely, and need a different preset.

If you need echo cancellation that behaves identically everywhere, use the software AEC node instead of, or in addition to, the device canceller.


Reading

Android references for the accepted values: