android、ios、windows等平台回声消除的解决方案

2024-04-09 06:32

本文主要是介绍android、ios、windows等平台回声消除的解决方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

回声消除介绍

http://www.qttaudio.com/aecjsgs.html

在即时通讯应用中,需要进行双方,或是多方的实时语音交流,在要求较高的场合,通常都是采用外置音箱放音,这样必然会产生回音,即一方说话后,通过对方的音箱放音,然后又被对方的Mic采集到回传给自己。如果不对回音进行处理,将会影响通话质量和用户体验,更严重的还会形成震荡,产生啸叫。

​回声消除就是在Mic采集到声音之后,将本地音箱播放出来的声音从Mic采集的声音数据中消除掉,使得Mic录制的声音只有本地用户说话的声音。

传统的回声消除都是采用硬件方式,在硬件电路上集成DSP处理芯片,如我们常用的固定电话、手机等都有专门的回音消除处理电路,而采用软件方式实现回声消除一直存在技术难点。

回声消除已经成为即时通讯中提供全双工语音的标准方法。声学回声消除是通过消除或者移除本地话筒中拾取到的远端的音频信号来阻止远端的声音返回去的一种处理方法。这种音频的移除都是通过数字信号处理来完成的。回声消除技术是数字信号处理的典型应用之一。

Android平台回声消除方法

 

QttAudio
  1. QttAudio解决了什么问题?
  • Android、iOS、Windows、(嵌入式)Linux系统的回音消除问题:通话再无回声啸叫,高清音质
  • 多语音流混音问题:多人会议模式很简单
  • 内置多种语音编解码格式:摆脱烦人的编解码开发工作量
  • 跨平台,接口一致性,集成简单:合四为一,加速应用开发,助您抢占先机
  • 与服务器无关:摆脱云服务绑架,私有云部署,更安全,更划算
  1. 一个代码小例子

Java(Android )

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);try {//初始化QttAudioEngineQttAudioEngine.me().init(this, "your appkey");//创建QttAudioStreamQttAudioStream stream = QttAudioEngine.me().createStream();//设置mic的编码参数,pcm,单声道,48000hz采样率QttAudioEngine.me().setMicCodecParams("pcm", 1, 48000, 0);//设置mic采集回调函数QttAudioEngine.me().setMicCaptureCb(new QttCaptureCallbak() {@Overridepublic void onCapture(byte[] buf, int bufLength, Object userdata) {QttAudioStream tmpStream = (QttAudioStream) userdata;//将mic采集到的播放出来,实现音频回放功能tmpStream.writePayload(buf, 0, bufLength);}}, stream);//启动stream,开始工作stream.start();} catch (QttException e) {e.printStackTrace();}}
}

C (iOS、Linux、Windows)

static void onCapture(char *buf, size_t len, void *userdata) {QttAudioStream *stream = (QttAudioStream *)userdata;//将mic采集到的播放出来,实现音频回放功能qtt_stream_write_payload(stream, buf, len);
}int main() {//创建QttAudioEngineQttAudioEngine *engine = qtt_engine_init("your appkey");if (engine == NULL) {fprintf(stderr, "qtt_engine_init fail\n");return -1;}//创建QttAudioStreamQttAudioStream *stream = qtt_engine_create_stream(engine);if (stream == NULL) {fprintf(stderr, "qtt_engine_create_stream fail\n");return -1;}//设置mic的编码参数,pcm,单声道,48000hz采样率qtt_engine_set_mic_codec_params(engine, "pcm", 1, 48000, 0);//设置mic采集回调函数qtt_engine_set_mic_capture_callback(engine, onCapture, stream);//启动stream,开始工作qtt_stream_start(stream);char c = getchar();qtt_stream_stop(stream);qtt_stream_free(stream);qtt_engine_free(engine);return 0;
}
系统API

Android在4.1(API level 16)的时候增加了一个API:AcousticEchoCanceler

下面简单的介绍下AcousticEchoCanceler的使用方法:

private AcousticEchoCanceler canceler;
//判断当前机型是否支持AEC
public boolean isDeviceSupportAEC()
{return AcousticEchoCanceler.isAvailable();
}
//初始化AEC
public boolean initAEC(int audioSession)
{if (canceler != null){return false;}canceler = AcousticEchoCanceler.create(audioSession);canceler.setEnabled(true);return canceler.getEnabled();
}public boolean setAECEnabled(boolean enable)
{if (null == canceler){return false;}canceler.setEnabled(enable);return canceler.getEnabled();
}public boolean release()
{if (null == canceler){return false;}canceler.setEnabled(false);canceler.release();return true;
}

从以上代码可知,AcousticEchoCanceler的初始化需要一个sessionid,这个sessionid由初始化好的AudioRecord对象获得:

sessionid = audioRecord.getAudioSessionId();

初始化AudioTrack时,也需要额外的处理sessionid:

audioTrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL, frequency, channelOUT, audioEncoding, tmpSize, AudioTrack.MODE_STREAM, sessionid);

为什么AcousticEchoCanceler和AudioTrack初始化都需要AudioRecord对象的sessionid呢?这牵涉到理解回声消除的原理,请见这篇文章。

Speex

Speex提供开源的的回音消除算法,从提供的文档(https://speex.org/docs/manual/speex-manual/node7.html#SECTION00740000000000000000)和测试用例看,效果还是非常棒的。下面简单介绍speex echo cancellation api的使用方法,官方示例文件testecho.c:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "speex/speex_echo.h"
#include "speex/speex_preprocess.h"#define NN 128
#define TAIL 1024int main(int argc, char **argv)
{FILE *echo_fd, *ref_fd, *e_fd;short echo_buf[NN], ref_buf[NN], e_buf[NN];SpeexEchoState *st;SpeexPreprocessState *den;int sampleRate = 8000;if (argc != 4){//mic_signal是mic实时采集到的音频数据(包含speaker_signal),speaker_signal是由喇叭放出去的音频数据(也可称为远端数据),output是将mic_signal中去掉speaker_signal的数据,就得到了干净无回声的数据fprintf(stderr, "testecho mic_signal.sw speaker_signal.sw output.sw\n");exit(1);}echo_fd = fopen(argv[2], "rb");ref_fd  = fopen(argv[1],  "rb");e_fd    = fopen(argv[3], "wb");st = speex_echo_state_init(NN, TAIL);den = speex_preprocess_state_init(NN, sampleRate);speex_echo_ctl(st, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate);speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_ECHO_STATE, st);while (!feof(ref_fd) && !feof(echo_fd)){fread(ref_buf, sizeof(short), NN, ref_fd);fread(echo_buf, sizeof(short), NN, echo_fd);//ref_buf是采集到的声音,至少包含两部分:我们的说话声和喇叭放出来的声音(echo_buf)//echo_buf是喇叭放出来的声音//e_buf是干净的声音speex_echo_cancellation(st, ref_buf, echo_buf, e_buf);speex_preprocess_run(den, e_buf);fwrite(e_buf, sizeof(short), NN, e_fd);}speex_echo_state_destroy(st);speex_preprocess_state_destroy(den);fclose(e_fd);fclose(echo_fd);fclose(ref_fd);return 0;
}

 

以上代码是处理文件,而在实际的VOIP通话中,难点在于NN和TAIL的指定,也就是frame_size和tail_length,因为像android\ios、windows或linux等非实时系统,用户调用采集或者播放的接口,到实际硬件真正采集和播放声音,根据各硬件平台以及系统的不一样,这中间是有几十至几百毫秒的延迟,这对于回声处理增加了非常大的难度。

WebRTC

WebRTC,名称源自网页实时通信(Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的技术。

WebRTC源代码中设计了两个回声消除模块,AEC(Acoustic Echo Canceller)和AECM(Acoustic Echo Canceller Mobile),AEC是在电脑端使用的回声消除器,而AECM是在移动端使用。由于电脑与移动设备的差别比较明显,在处理速度上,编解码器的性能上和内存方面都有着较大的差异。

在VoIP实时语音通话中,之所以会产生声学回声的原因是,近端通话者的声音被自己的麦克风拾取后通过网络传到远端,远端扬声器播放出来的声音被麦克风拾取后通过网络又重新发回近端。加上网络和数据处理等各种延迟的影响,使得近端通话者能够从扬声器中听到自己的刚才所说的话,就产生了回声。

下面主要来简单介绍一下AECM模块 中echo_control_mobile.c的两个函数:

//farend 是远端传过来的音频数据,也就是将要从喇叭放出来的声音,类似于speex的echo_buf
WebRtcAecm_BufferFarend(void *aecmInst, const int16_t *farend, int16_t nrOfSamples)
//nearendNoisy 是从mic采集到的声音,类似于speex的ref_buf
//nearendClean 可以放入经过Noise Reduction的音频数据
//msInSndCarBuf 该值需要特别计算才能够取得良好的去回音效果,也即将听筒播放出来的声音被mic采集到时的一个精确的值,才能使AECM达到良好的回音消除效果
WebRtcAecm_Process(void *aecmInst, const int16_t *nearendNoisy, const int16_t *nearendClean, int16_t *out, int16_t nrOfSamples, int16_t msInSndCardBuf)
转自:http://www.qttaudio.com/android-aec.html

这篇关于android、ios、windows等平台回声消除的解决方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/887412

相关文章

usb接口驱动异常问题常用解决方案

《usb接口驱动异常问题常用解决方案》当遇到USB接口驱动异常时,可以通过多种方法来解决,其中主要就包括重装USB控制器、禁用USB选择性暂停设置、更新或安装新的主板驱动等... usb接口驱动异常怎么办,USB接口驱动异常是常见问题,通常由驱动损坏、系统更新冲突、硬件故障或电源管理设置导致。以下是常用解决

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl

Android实现两台手机屏幕共享和远程控制功能

《Android实现两台手机屏幕共享和远程控制功能》在远程协助、在线教学、技术支持等多种场景下,实时获得另一部移动设备的屏幕画面,并对其进行操作,具有极高的应用价值,本项目旨在实现两台Android手... 目录一、项目概述二、相关知识2.1 MediaProjection API2.2 Socket 网络

Windows Docker端口占用错误及解决方案总结

《WindowsDocker端口占用错误及解决方案总结》在Windows环境下使用Docker容器时,端口占用错误是开发和运维中常见且棘手的问题,本文将深入剖析该问题的成因,介绍如何通过查看端口分配... 目录引言Windows docker 端口占用错误及解决方案汇总端口冲突形成原因解析诊断当前端口情况解

Android实现悬浮按钮功能

《Android实现悬浮按钮功能》在很多场景中,我们希望在应用或系统任意界面上都能看到一个小的“悬浮按钮”(FloatingButton),用来快速启动工具、展示未读信息或快捷操作,所以本文给大家介绍... 目录一、项目概述二、相关技术知识三、实现思路四、整合代码4.1 Java 代码(MainActivi

Android Mainline基础简介

《AndroidMainline基础简介》AndroidMainline是通过模块化更新Android核心组件的框架,可能提高安全性,本文给大家介绍AndroidMainline基础简介,感兴趣的朋... 目录关键要点什么是 android Mainline?Android Mainline 的工作原理关键

Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案

《Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案》:本文主要介绍Vue3组件中getCurrentInstance()获取App实例,但是返回nu... 目录vue3组件中getCurrentInstajavascriptnce()获取App实例,但是返回n

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

如何解决idea的Module:‘:app‘platform‘android-32‘not found.问题

《如何解决idea的Module:‘:app‘platform‘android-32‘notfound.问题》:本文主要介绍如何解决idea的Module:‘:app‘platform‘andr... 目录idea的Module:‘:app‘pwww.chinasem.cnlatform‘android-32

Node.js 数据库 CRUD 项目示例详解(完美解决方案)

《Node.js数据库CRUD项目示例详解(完美解决方案)》:本文主要介绍Node.js数据库CRUD项目示例详解(完美解决方案),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考... 目录项目结构1. 初始化项目2. 配置数据库连接 (config/db.js)3. 创建模型 (models/