Enable Acoustic Echo Cancellation
If you ever hear yourself speaking a few hundred milliseconds after you have spoken, this is most likely because the remote peer does not have Acoustic Echo Cancellation (AEC). The remote peer is playing your audio stream through the speakers, then picking it up with the microphone and streaming it back to you. Acoustic Echo Cancellation solves this by keeping track of the audio data you send. If someone retransmits data that is similar enough to the data you have just sent, it is recognized as an echo and is removed from the audio data before playback.
LiveSwitch provides an acoustic echo cancellation implementation. It is included with the standard LiveSwitch distribution as a separate library. Read on for information on how to add this to your projects.
Include the Library
Here we cover the inclusion of the audio processing libraries specifically, full instructions on setting up project structure are covered in the Starting a New Project section. The acoustic echo cancellation implementation can be found in the FM.LiveSwitch.AudioProcessing
library and associated native libraries. There are no libraries for iOS and macOS because Apple implements their own echo cancellation so there is no need for LiveSwitch to re-invent the wheel. Similarly, this is not needed on web platforms as each browser has its own implementation.
Implement AecContext
The FM.LiveSwitch.AecContext
class is an abstract class that provides baseline acoustic echo cancellation capability. You need to implement a platform-specific version of this for each of your projects. There are two methods to implement, CreateOutputMixerSink
and CreateProcessor
.
You must first implement the CreateOutputMixerSink
method. This method creates a sink where the mixed audio output can be played. Generally, this corresponds to your system speakers. In your implementation, instantiate a sink appropriate for your platform, as shown in the example below.
The next method you must implement is CreateProcessor
. For this, you should create and return an instance of FM.LiveSwitch.AudioProcessing.AecProcessor
. Its constructor requires you to specify two things: the audio settings and the tail length to use for echo cancellation. The tail length tells the library how long to keep track of audio data for echo cancellation. Generally, you want this to be the combined delay between the sender and receiver.
Once you've finished creating an AecContext
implementation you have to add it to your local and remote media objects. We'll cover that in the next section.
Note
For .NET, if supported, you can simply provide an instance of FM.LiveSwitch.Dmo.AecProcessor
. This processor uses DirectX Media Objects and generally provides better echo cancellation. The snippets assume you are using the FM.LiveSwitch.Dmo library in .NET, and the FM.LiveSwitch.NAudio library as a fallback.
Note that for .NET platforms, you should first attempt to use DMO (DirectX Media Objects) and then fall back to NAudio if it is not supported. DMO offers better sound quality and provides acoustic echo cancellation. If it is available, you should use it.
Caution
DMO and VMs
Note that in virtual environments DMO can still produce echo. For improved results turn off all enhancements to your virtual audio devices, but for best results you must not run in a VM.
public class AecContext : FM.LiveSwitch.AecContext
{
protected override FM.LiveSwitch.AecPipe CreateProcessor()
{
if (Dmo.AecProcessor.IsSupported())
{
return new Dmo.AecProcessor();
}
else
{
var config = new FM.LiveSwitch.AudioConfig(16000, 1);
var tailLength = FM.LiveSwitch.NAudio.Sink.GetBufferDelay(config) + FM.LiveSwitch.NAudio.Source.GetBufferDelay(config);
return new FM.LiveSwitch.AudioProcessing.AecProcessor(config, tailLength);
}
}
protected override FM.LiveSwitch.AudioSink CreateOutputMixerSink(FM.LiveSwitch.AudioConfig config)
{
return new FM.LiveSwitch.NAudio.Sink(config);
}
}
Use AecContext with Local and Remote Media
In the previous sections on local and remote media, you were shown how to implement the RtcLocalMedia
and RtcRemoteMedia
classes. At the time, we glanced over the constructors that required an AecContext
parameter. Now that you know how to implement AecContext
, you can implement these constructors as well.
You only need to create one AecContext
instance for all of your local and remote media objects. The context wraps a set of factory methods that create objects needed for echo cancellation, so you can safely reuse the context instance itself. The following is an example of how to integrate AecContext
into your local and remote media implementation.
public class LocalMedia : FM.LiveSwitch.RtcLocalMedia<...>
{
/// <summary>
/// Initializes a new instance of the LocalMedia class.
/// </summary>
/// <param name="disableAudio">Whether to disable audio.</param>
/// <param name="disableVideo">Whether to disable video.</param>
/// <param name="aecContext">Your singleton AEC context, if using software echo cancellation.</param>
public LocalMedia(bool disableAudio, bool disableVideo, AecContext aecContext)
: base(disableAudio, disableVideo, aecContext)
{
}
}
public class RemoteMedia : FM.LiveSwitch.RtcRemoteMedia<...>
{
/// <summary>
/// Initializes a new instance of the RemoteMedia class.
/// </summary>
/// <param name="disableAudio">Whether to disable audio.</param>
/// <param name="disableVideo">Whether to disable video.</param>
/// <param name="aecContext">Your singleton AEC context, if using software echo cancellation.</param>
public RemoteMedia(bool disableAudio, bool disableVideo, AecContext aecContext)
: base(disableAudio, disableVideo, aecContext)
{
}
}