您现在的位置是:首页 >技术杂谈 >【Android】Android语音通话回音消除(AEC)技术实现网站首页技术杂谈

【Android】Android语音通话回音消除(AEC)技术实现

devnn 2024-06-07 12:00:03
简介【Android】Android语音通话回音消除(AEC)技术实现

一、前言

在语音聊天、语音通话、互动直播、语音转文字类应用或者游戏中,需要采集用户的麦克风音频数据,然后将音频数据发送给其它终端或者语音识别服务。如果直接使用采集的麦克风数据,就会存在回音问题。所谓回音就是在语音通话过程中,如果用户开着扬声器,那么自己讲话的声音和对方讲话的声音(即是扬声器的声音)就会混在一起,如果没有消除对方的声音,那么对方听到的就是带有回音的声音,这样的声音就会有问题。因此采集麦克风数据后,必须要消除回音,才能得到良好的用户体验。

回音消除的英文专业术语叫Acoustic Echo Cancellation,简称AEC。如何实现回音消除,技术细节实现上是一个比较复杂的数学问题。一般手机厂商都提供了底层的回音消除技术实现,app只需要调用相关api即可。OS上的回音消除比较复杂一些,Android相对来说比较简单,本文主要对Android设备上如何实现回音消除的相关知识进行梳理。

关于iOS上的回音消除可以参考:iOS语音通话回音消除(AEC)技术实现

二、Android的音频框架概览

Android提供的音频框架有:MediaRecorder 、AudioRecord、AudioTrack、MediaPlayer,其中AudioRecord只能录制音频,MediaRecorder用于录制视频(包括音频),AudioTrack是用来播放PCM音频,MediaPlayer用来播放视频(包括音频)。我们需要使用支持音频录制的API来实现AEC。

1、MediaRecorder

集成了音频采集、视频采集、编码、压缩等,支持少量的录音音频格式,无法实时处理音频,一般用于输出音频和视频混合格式,比如MP4、3GP。

	MediaRecorder recorder = new MediaRecorder();
	recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
	recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
	recorder.setOutputFile(fileName);
	recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
	try {
	    recorder.prepare();
	} catch (IOException e) {
	    Log.e(LOG_TAG, "prepare() failed");
	}
	recorder.start();

2、AudioRecord

AudioRecord是专业的音频采集框架。采集到的是未经压缩的原始PCM音频。它可以设置音频采集来源,比如麦克风风原始数据、录视频时的麦克风数据、语音识别、VoIP(Voice on Internet Protocal)等。其中VoIP是支持AEC的音频来源。AudioRecorder主要用于音频的实时处理,或者实现边录边播(AudioRecord+AudioTrack)功能。如果保存成音频文件,是不能够被播放器播放的,需要写代码实现数据编码及压缩。

三、AudioRecord实现回音消除

实现回音消除时,只需要在构造AudioRecord时将audioSource(音频源)设置成VOICE_COMMUNICATION:

AudioRecord audioRecorder = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, SAMPLE_RATE,AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);

获取AudioRecord录制的音频是通过从 AudioRecord 对象“拉”(读取)数据来实现的。应用程序负责使用以下三种方法之一及时轮询采集到的音频:

int read(byte[], int, int)
int read(short[], int, int)
int read(java.nio.ByteBuffer, int)

选择使用哪种方法取决于对开发者来说最方便的音频数据存储格式。
创建AudioRecord之后,AudioRecord 对象会初始化其关联的音频缓冲区,它将用新的音频数据填充该缓冲区。在构造AudioRecord时指定的此缓冲区的大小。数据应以小于总记录缓冲区大小的块的形式从音频硬件中读取。

构造AudioRecord时需要指定音频来源,audio source有以下几种:

/** Default audio source **/
public static final int DEFAULT = 0;

/** Microphone audio source */
public static final int MIC = 1;

/** Voice call uplink (Tx) audio source.
 * <p>
 * Capturing from <code>VOICE_UPLINK</code> source requires the
 * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT} permission.
 * This permission is reserved for use by system components and is not available to
 * third-party applications.
 * </p>
 */
public static final int VOICE_UPLINK = 2;

/** Voice call downlink (Rx) audio source.
 * <p>
 * Capturing from <code>VOICE_DOWNLINK</code> source requires the
 * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT} permission.
 * This permission is reserved for use by system components and is not available to
 * third-party applications.
 * </p>
 */
public static final int VOICE_DOWNLINK = 3;

/** Voice call uplink + downlink audio source
 * <p>
 * Capturing from <code>VOICE_CALL</code> source requires the
 * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT} permission.
 * This permission is reserved for use by system components and is not available to
 * third-party applications.
 * </p>
 */
public static final int VOICE_CALL = 4;

/** Microphone audio source tuned for video recording, with the same orientation
 *  as the camera if available. */
public static final int CAMCORDER = 5;

/** Microphone audio source tuned for voice recognition. */
public static final int VOICE_RECOGNITION = 6;

/** 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.
 */
public static final int VOICE_COMMUNICATION = 7;

可见,VOICE_COMMUNICATION是用于VoIP这种需要回音消除的场景。

使用以上方式已经可以实现录制音频同时消除回音了。但是AEC在设备上不一定可用(可能是少数低端设备),需要结合AcousticEchoCanceler这个类检测AEC是否可用。

AcousticEchoCanceler的使用很简单,在创建时传入以上AudioRecord的audioSessionID:
获取AudioSessionId():
android.media.AudioRecord#getAudioSessionId()

创建AcousticEchoCanceler:
create(audioSession: Int)

检测是否可用:
boolean acousticEchoCanceler.isAvailable()

参考官方文档:AcousticEchoCanceler

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。