单独编译使用WebRTC的音频处理模块
2016年12月08日 14:26:58 starRTC免费IM直播会议一对一视频 阅读数:8360
版权声明:starRTC免费im直播会议一对一视频,by elesos.com & starRTC.com https://blog.youkuaiyun.com/elesos/article/details/53517656
不推荐单独编译 WebRTC 中的各个模块出来使用。
昨天有幸在 Google 论坛里询问到 AECM 模块的延迟计算一事,Project member 说捣腾这个延迟实际上对 AECM 的效果没有帮助,这个延迟值仅在 AECM 启动时加快内置延迟估算器的收敛,如果更新的延迟有误,甚至会使 AECM 内置的延迟估算器出现错误的偏移,他建议我使用一个理论上的定值,Chrome 中他们用了 100ms。我之前也在 AECM 里看到了它对内置的 delay_estimator 二进制延迟估算器有很大的依赖,近端与远端数据匹配与否,几乎全部仰仗这个延迟估算器的结果。因此,对于 AECM 的使用,是否还需要花时间去计算这个系统延迟,bill 不再置评,个中效果大家自己实践和把握。
其次,AECM 产生的唧唧声 Project member 澄清这不是 bug,而是 AECM 算法本来就有此表现,属正常现象。
本文已有一年之久,随着自己在学习过程中认识的加深,以及期间和各位在文后的讨论,担心后来者照着这一年前的文章走弯路,bill 觉得有必要对文章做一个更新,点出自己走的弯路,以免误导后来者。
1. 自己最开始是把 AECM、NS、VAD、AGC 各个模块单独提取出来使用,现在看来实属麻烦,且效果也不甚理想。如果大家的项目没有特殊的要求,大可将整个语音引擎 VoiceEngine 编译出来使用。就我个人而言,目前的解决方案是独立编译使用音频处理单元 AudioProcessingModule,因为 APM 是一个纯净的音频处理单元,其接口仅与音频处理有关,APM的使用加上上层代码的优化,可以保证基本的通话效果(离完美还很远),回声基本是没有的。主要会存在两个问题,一是AECM出来的效果会有唧唧声,这个声音可以通过对延迟计算的不断优化而得到改善,最终可以做到说几句话之后有1~2次唧唧声。二是通话过程中声音会忽大忽小,目前我是怀疑由AECM的double talk处理引起的,具体的还要自己去倒腾。
2. 关于回声消除滤波器延迟的计算,之前自己一直认为只要这个延迟计算准确,就能得到理想的回声消除效果,现在发现这个想法太幼稚,一是AECM算法本身有一定局限性,二是Android上的采集延迟没有系统API支持,很难计算准确,而播放端的API又不能保证其准确性。目前我的能力只能做到尽量优化上层的延迟计算,尽量减少由Android音频API对延迟造成的影响。
3. 在 Android 上层优化计算系统音频延迟的代码达到一定瓶颈后,可以将优化目标转向 1)AECM 算法。 2)优化AEC(PC)(使其能在手机上正常运行,目前AEC-PC默认滤波器长度为12块,每块64个点,(12*64=768采样)即AEC-PC仅能处理48ms的单声道16kHz延迟的数据,而Android的音频系统延迟大多在100ms以上,因此既要增加AEC-PC滤波器长度又要保证其运行效率是优化的重点) 3)其他模块的优化(比如抖动缓冲区等)。
4. 文后的源码列表已经过时,由于我目前不再支持单独编译这些模块,恕我不再更新该列表,如确有独立编译需求的,可自行在WebRTC项目对应目录中找到需要的文件。
前言
最近一直在捣腾如何在android和iOS上使用Google的WebRTC——一个无疑大力推动了互联网即时通信以及VoIP发展的开源项目。(elesos注:连谷歌都访问不了的国家,活该落后!)
WebRTC提供一套音频处理引擎VOE,但VOE在 android 和 iOS 上的整体编译一直是一个比较繁琐且恼火的问题,于是单独提取了VOE中的NS(Noise Suppression 噪声抑制)、VAD(Voice Activity Detection 静音检测)、AECM(Acoustic Echo Canceller for Mobile 声学回声消除)以及 AGC(Auto Gain Control 自动增益控制)等模块进行编译并捣鼓其使用方法。
经过自己两月有余的捣腾和测试,终于在 android 和 iOS 上成功编译出各模块并在项目中使用了NS/VAD/AECM三大模块,效果比较不错。
回过头来看看,这几大模块的编译其实非常简单,不过两月前的自己也着实为这个花了一番力气。
正文
由于几大模块的编译方式相同,故本文仅以 NS 模块为例,其余模块请读者自行摸索和实验。
Step 1 - 下载 google WebRTC 源码
WebRTC目前的开发版主线版本已经到了 r4152 - 3.32,但这几大模块并未有大的修改,故本文依旧按bill当时的版本 3.31 进行讲解,请自行使用SVN同步以下目录(至于同步的方法,请自行google):
http://webrtc.googlecode.com/svn/branches/3.31/ 可访问
Step 2 - 提取WebRTC - NS模块代码
同步源码后,进入目录 \webrtc\modules\audio_processing\ns ,将NS模块的源码拷贝出来,下面是单独编译NS时的参考源码列表(部分头文件在WebRTC项目其他目录下,请自行搜索提取):
defines.h
signal_procession_library.h
spl_inl.h
typdefs.h
windows_private.h
fft4g.h / fft4g.c
noise_suppression.h / noise_suppression/c
ns_core.h / ns_core.c
除了上述WebRTC源码外,如果要在android的Java代码中使用,还需自行编写JNI包装文件:
ns_jni_wrapper.c(此为自定义的 jni 包装文件,详情请见 此文)
ADDED(billhoo - 2013-6-14)
鉴于有朋友询问JNI Wrapper的编写,下面提供NS模块create以及initialize函数(这两个函数足以说明问题)的wrapper源码及注释,希望对大家有所帮助。更详细的编写步骤请参考 Oracle官方文档 或 此文或 此文。
WebRtcNs_Create 包装函数及注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
WebRtcNs_Initiate 包装函数及注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
[END OF ADDED]
Step 3 - 编译WebRTC - NS模块
此步请参照 bill之前的文章将刚才提取的NS代码添加进eclipse工程进行编译即可。以下为NS模块的Android.mk文件:
1 2 3 4 5 6 7 8 9 |
|
编译完成后,将项目中的 webrtc_ns.so 动态库拷贝出来以备后续使用。
Step 4 - 加载编译好的NS模块动态库
接下来只需要按照 此文 的描述在 android 的JAVA代码中使用刚才编译好的 webrtc_ns.so 动态库便大功告成。
Step 5 - 几大模块的使用及注意事项
前四步已经完成了几大音频处理模块在android上的单独编译过程,并分别生成了 webrtc_ns.so、webrtc_vad.so、webrtc_aecm.so 以及 webrtc_agc.so 四个动态库,下面bill简要介绍对NS、VAD以及AECM三个库的函数使用方法及注意事项:
5.1 - NS库函数的使用及注意事项
这个很简单,参照 noise_suppression.h 头文件中对各API的描述即可,首先使用 WebRtcNs_Create 创建NS实体,然后 WebRtcNs_Init 初始化该实体,WebRtcNs_set_policy 设置噪声抑制的级别(bill使用的是最高级别 2,效果比较理想),设置完成后便可调用 WebRtcNs_Process循环对10ms(8000Hz、16000Hz)音频帧进行NS处理,注意最后别忘了调用 WebRtcNs_Free 将NS实体销毁。
5.2 - VAD库函数的使用及注意事项
VAD的使用和NS区别不大,唯一需要注意的是VAD仅仅只是检测,返回结果1表示VAD检测此帧为活动帧,0表示此帧为静音帧,至于判断为静音后该进行何种处理,就和你自己的项目相关了。
5.3 - AECM库函数的使用及注意事项
AECM实体的创建、初始化和销毁工作与上述相同,之后需要在远端和近端分别调用WebRtcAecm_BufferFarend (注:farend远端)以及 WebRtcAecm_Process,对于AECM的使用,需要注意的重点在于Process函数的参数msInSndCardBuf,该参数在audio_procession.h头文件中以名为delay的变量呈现,该延迟的计算确为一难点(对于单独使用AECM模块来说),不过只要严格按照delay的描述进行操作即可。
附:
其他几大模块单独编译时需要的源文件列表(所有依赖头文件略,请自行根据报错添加):
WebRTC - VAD 模块源文件列表
注意:VAD的编译需要宏 WEBRTC_POSIX 的支持,而该宏是否有实现,由 WEBRTC_ANDROID 等宏是否被定义决定,若你在编译时提示 once 函数未定义等错误, 请自行添加对 WEBRTC_ANDROID宏的定义。
webrtc_vad.c
vad_core.c
vad_filterbank.c
vad_gmm.c
vad_sp.c
real_fft.c
division_operations.c
complex_bit_reverse.c
cross_correlation.c
complex_fft.c
downsample_fast.c
vector_scaling_operations.c
get_scaling_square.c
energy.c
min_max_operations.c
spl_init.c
WebRTC - AECM 模块源文件列表
randomization_functions.c
spl_sqrt_floor.c
division_operations.c
min_max_operations.c
ring_buffer.c
delay_estimator.c
delay_estimator_wrapper.c
complex_bit_reverse.c
complex_fft.c
aecm_core.c
echo_control_mobile.c
WebRTC - AGC 模块源文件列表
spl_sqrt.c
copy_set_operations.c
division_operations.c
dot_product_with_scale.c
resample_by_2.c
analog_agc.c
digital_agc.c
下面是elesos从评论中摘抄的部分信息:
使用
WebRtcAecm_Process
nearendNoisy 表示带有噪声的buf
nearendClean 表示经过降噪处理的 buf
建议首先使用NS将采集到的buf降噪,然后将原buf传给nearendNoisy ,降噪后的buf传给
nearendClean,如果不降噪,直接将buf传给nearendNoisy ,nearendClean置为NULL即可。
NS每次处理10ms的数据
都處理10ms數據(8000HZ的sample是80, 16000HZ的sample是160)、謝謝!
先消回声再降噪效果比先降噪再消回声好。建议参考WebRTC AudioPreprocessing 模块里面的 ProcessStream 的实现顺序。
WebRtcAecm_Init( &aecm , 8000 );
While ( aecProcessing )
{
WebRtcAecm_BufferFarend( speakerBuffer );
WebRtcAecm_Process( aecm , micBuffer , NULL , aecBuffer , 160 , 200 );
}
上面的200ms最好不要用常量,需要this delay is always changes, you should estimate it every
1 second or shorter.
在audio_processing.h中有描述
// Sets the |delay| in ms between AnalyzeReverseStream() receiving a far-end
// frame and ProcessStream() receiving a near-end frame containing the
// corresponding echo. On the client-side this can be expressed as
// delay = (t_render - t_analyze) + (t_process - t_capture)
// where,
// - t_analyze is the time a frame is passed to AnalyzeReverseStream() and
// t_render is the time the first sample of the same frame is rendered by
// the audio hardware.
// - t_capture is the time the first sample of a frame is captured by the
// audio hardware and t_pull is the time the same frame is passed to
// ProcessStream().
virtual int set_stream_delay_ms(int delay) = 0;
延迟你只能根据自己的buffer实现进行计算。
四个时间点均在调用之前得到即可。
的第一个采样被播放的时间,总感觉取不到,我们能取到的只有播放完整个语音帧的时间点吧:我们确
实不能直接得到第一个采样点的时间,但你可以根据自己上层的 buffer 大小以及底层的硬件 buffer
大小估算出缓冲区中总共缓冲了多少帧,这样一来便可计算出第一个采样点的时间。
请教一下你在android上层设计buffer并计算出delay时间的解决办法。
尽量不要在java层做AECM,如果非要在java层做,delay的计算只有根据你自己的buffer设计来,总体
思路就是系统底层硬件延迟 + 你的buffer延迟。
每发送一帧就更新延迟值。
可以采用OpenSE等JNI层的采集库,而不是android上层的AudioRecord以及AudioTrack。
目前采用一半底层buffer大小作为采集的固定延迟。
T1(系统的播放延迟) = 帧A被硬件播放出来的时刻 - 帧A被放进 farend 的时刻;
T2(系统的采集延迟) = 帧B被放进 process 的时刻 - 帧B被硬件采集的时刻;
total delay = T1 + T2;
这个延迟的计算方法可以参考WebRTC主线版本目录 webrtc\modules\audio_device\android\java\src\org\webrtc\voiceengine\
下的WebRTCAudioTrack和WebRTCAudioRecord
对于AECM的测试,首先可以使用同一个PCM文件,分别放入FAREND和PROCESS,如果出来的结果接近全零
,则验证你提取的AECM模块工作正常。
在验证AECM模块能够正常工作的前提下,你需要两台设备,每台设备两个线程,线程一用来采集和
PROCESS,线程二用来播放和FAREND。
“从手机SD卡中读取一个pcm文件,送入到扬声器,同时调用WebRtcAecm_BufferFarend(), 然后读取麦
克风采集到的数据,调用Process。再将out写入到aec.pcm文件。分析这个aec.pcm文件。”
远端数据就是网络传过来的数据,近端就是本机采集到的数据。
farend是远端数据,即VoIP通信中接收到的对端的、即将被本地扬声器播放的音频A。
nearend是本地数据,即刚刚被本地麦克风采集到的音频B,而音频B可能包含上述音频A(麦克风采集到了扬声器播放的A),于是 A和B 一起发送给远端,致使远端又听到了自己刚才发送的A,也就产生了所谓的回声。
发现你这里面根本不存在“原声”,何来原声被消除呢?
你使用固定文件作为声音来源,我们假设输入PCM数据为“A”,那么你送到扬声器的声音就为“A”,
而麦克风采集到的声音为扬声器的“A”再加上背景噪声“B”(现在的B才是实际意义上的“原声”,
假设你没说话),AECM的处理结果就是从“A+B”中找到被扬声器播放出去的“A”并进行消除,留下
了“B”,而背景噪声也许比较小,结果也就仍然接近全零了,绕了一圈,你做的这个流程和我用同一
个PCM文件分别放入farend和process是一个道理。
应该在运行时说话,而不是放音乐。
VAD检测出是静音,你可以采取以下两种方式:1.不发送这一段静音的音频 2.将这一段VAD认为静音的
音频全部置零然后发送。
专注webrtc、kurento音视频开发
qq:911921258