单独编译使用WebRTC的音频处理模块 - android

来源:互联网 发布:微信红包的随机算法 编辑:程序博客网 时间:2024/05/22 16:55
单独编译使用WebRTC的音频处理模块 - android
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://billhoo.blog.51cto.com/2337751/1213801

更新

   【2014514日】

   昨天有幸在 Google 论坛里询问到 AECM 模块的延迟计算一事,Project member 说捣腾这个延迟实际上对 AECM的效果没有帮助,这个延迟值仅在 AECM 启动时加快内置延迟估算器的收敛,如果更新的延迟有误,甚至会使 AECM内置的延迟估算器出现错误的偏移,他建议我使用一个理论上的定值,Chrome 中他们用了 100ms。我之前也在AECM 里看到了它对内置的 delay_estimator 二进制延迟估算器有很大的依赖,近端与远端数据匹配与否,几乎全部仰仗这个延迟估算器的结果。因此,对于 AECM 的使用,是否还需要花时间去计算这个系统延迟,bill 不再置评,个中效果大家自己实践和把握。

   其次,AECM 产生的唧唧声 Project member 澄清这不是 bug,而是 AECM 算法本来就有此表现,属正常现象。

   在这里仅希望大家知道此事,以免被我一家之言误导。


   【201458日】

   本文已有一年之久,随着自己在学习过程中认识的加深,以及期间和各位在文后的讨论,担心后来者照着这一年前的文章走弯路,bill 觉得有必要对文章做一个更新,点出自己走的弯路,以免误导后来者。

   1. 自己最开始是把 AECM、NS、VAD、AGC 各个模块单独提取出来使用,现在看来实属麻烦,且效果也不甚理想。如果大家的项目没有特殊的要求,大可将整个语音引擎 VoiceEngine 编译出来使用。就我个人而言,目前的解决方案是独立编译使用音频处理单元 AudioProcessingModule,因为 APM 是一个纯净的音频处理单元,其接口仅与音频处理有关,APM编译请见此文。APM的使用加上上层代码的优化,可以保证基本的通话效果(离完美还很远),回声基本是没有的。主要会存在两个问题,一是AECM出来的效果会有唧唧声,这个声音可以通过对延迟计算的不断优化而得到改善,最终可以做到说几句话之后有1~2次唧唧声。二是通话过程中声音会忽大忽小,目前我是怀疑由AECMdouble talk处理引起的,具体的还要自己去倒腾。

   2. 关于回声消除滤波器延迟的计算,之前自己一直认为只要这个延迟计算准确,就能得到理想的回声消除效果,现在发现这个想法太幼稚,一是AECM算法本身有一定局限性,二是Android上的采集延迟没有系统API支持,很难计算准确,而播放端的API又不能保证其准确性。目前我的能力只能做到尽量优化上层的延迟计算,尽量减少由Android音频API对延迟造成的影响。

   3. 在 Android 上层优化计算系统音频延迟的代码达到一定瓶颈后,可以将优化目标转向 1)AECM 算法。 2)优化AEC(PC)(使其能在手机上正常运行,目前AECPC默认滤波器长度为12块,每块64个点,即AECPC仅能处理48ms的单声道16kHz延迟的数据,而Android的音频系统延迟大多在100ms以上,因此既要增加AECPC滤波器长度又要保证其运行效率是优化的重点) 3)其他模块的优化(比如抖动缓冲区等)。

   4. 文后的源码列表已经过时,由于我目前不再支持单独编译这些模块,恕我不再更新该列表,如确有独立编译需求的,可自行在WebRTC项目对应目录中找到需要的文件。


附言

   WebRTC是时下比较热门的新技术,由于bill接触时间尚短,对该项目的理解和认知定存在不足甚或偏差,文中有描述不当之处还望各位悉心指出,感激不尽。


前言

   最近一直在捣腾如何在androidiOS上使用GoogleWebRTC——一个无疑大力推动了互联网即时通信以及VoIP发展的开源项目。

   虽然WebRTC主要目标是为互联网提供高质量的富媒体即时通信,但其源码为C/C++所写,且其开发版中也包含对android 和 iOS 等移动设备的支持,因此对于如今飞速发展的移动互联网,WebRTC也能推波助澜大显神通。

   WebRTC提供一套音频处理引擎VOE(本文不涉及视频处理引擎VIE),但VOE在 android 和 iOS 上的整体编译一直是一个比较繁琐且恼火的问题,于是单独提取了VOE中的NS(Noise Suppression 噪声抑制)、VADVoice Activity Detection 静音检测)、AECMAcoustic Echo Canceller for Mobile 声学回声消除)以及 AGCAuto 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源码外,如果要在androidJava代码中使用,还需自行编写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
/***
 * Summary
 * types:
 *   NSinst_t : the type of noise suppression instance structure.
 *   NsHandle : actually the same type of NSinst_t, defined in
 *              "noise_suppression.h" as a empty struct type named
 *              "NsHandleT".
 *
 *   Note:
 *    1.You have no need to pass env and jclazz to these functions,
 *      cus' JVM will does it for you.
 *    2.We only support 10ms frames, that means you can only input 320
 *      Bytes a time.
 **/
/**
 * This function wraps the "WebRtcNs_Create" function in "noise_suppression.c".
 * Input:
 *        none.
 * Output:
 *        the handler of created noise suppression instance.
 * Return value:
 *        -1 : error occurs.
 *        other value : available handler of created NS instance.
 *
 * @author billhoo
 * @version 1.0 2013-1-29
 */
JNIEXPORT jint JNICALL
Java_你的类限定名_createNSInstance(JNIEnv *env,
        jclass jclazz) {
    NsHandle *hNS = NULL; //create a pointer to NsHandle on native stack.
    if (WebRtcNs_Create(&hNS) == -1) { //allocate dynamic memory on native heap for NS instance pointed by hNS.
        return -1;  //error occurs
    else {
        return ((int) (NSinst_t *) hNS); //returns the address of NS instance on native heap.
    }
}


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
/**
 * This function wraps the "WebRtcNs_Init" function in
 * "noise_suppression.c".
 * Initializes a NS instance and has to be called before any other
 * processing is made.
 *
 * Input:
 *        - nsHandler   - Handler of NS instance that should be
 *                        initialized.
 *        - sf          - sampling frequency, only 8000, 16000, 32000
 *                        are available.
 * Output:
 *         nsHandler  - the handler of initialized instance.
 * Return value:
 *         0                - OK
 *         -1               - Error
 *
 * @author billhoo
 * @version 1.0 2013-1-29
 */
JNIEXPORT jint JNICALL
Java_你的类限定名_initiateNSInstance(JNIEnv *env,
        jclass jclazz, jint nsHandler, jlong sf) {
    NsHandle *hNS = (NsHandle*) nsHandler;
    return WebRtcNs_Init(hNS, sf);
}


[END OF ADDED]



Step 3 - 编译WebRTC - NS模块

   此步请参照 bill之前的文章将刚才提取的NS代码添加进eclipse工程进行编译即可。以下为NS模块的Android.mk文件:


1
2
3
4
5
6
7
8
9
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := webrtc_ns
LOCAL_SRC_FILES := \
        noise_suppression.c \
        ns_core.c \
        fft4g.c \
        ns_jni_wrapper.c
include $(BUILD_SHARED_LIBRARY)


   编译完成后,将项目中的 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 循环对10ms8000Hz、16000Hz音频帧进行NS处理,注意最后别忘了调用 WebRtcNs_Free 将NS实体销毁。


5.2 - VAD库函数的使用及注意事项

VAD的使用和NS区别不大,唯一需要注意的是VAD仅仅只是检测,返回结果1表示VAD检测此帧为活动帧,0表示此帧为静音帧,至于判断为静音后该进行何种处理,就和你自己的项目相关了。


5.3 - AECM库函数的使用及注意事项

AECM实体的创建、初始化和销毁工作与上述相同,之后需要在远端和近端分别调用 WebRtcAecm_BufferFarend以及 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


本文出自 “Bill_Hoo专栏” 博客,请务必保留此出处http://billhoo.blog.51cto.com/2337751/1213801

分享至
一键收藏,随时查看,分享好友!
 yxmsw2007、wanglimingyin、xubingnihao
39人
了这篇文章
类别:流媒体┆阅读(13480)┆评论(234) ┆ 返回博主首页┆返回博客首页
上一篇 【如何在Xcode4上创建并使用iOS的静态库】 下一篇 【android工程转为lib工程后提示'R.id.xxx..

文章评论

 <<   1   2   3   4   5    >>   页数 ( 1/16 )  
2013-06-14 13:52:06
你好,请问能把ns_jni_wrapper.c发给我参考下怎么写好么?我的总是编译不成so文件,谢谢帮助

2013-06-14 16:22:58
回复 wanglimingyin:[1楼]

你好,本文已经更新,请参见[ADDED]标签处。希望对你有所帮助。

2013-06-14 17:18:33
回复 Bill_Hoo:[2楼]

谢谢你,bill。主要是我对c不熟悉,好久没用了。现在有个需求就是需要拿到pcm音频数据然后进行降噪,可是不知到怎么使用里面的方法

2013-06-26 19:54:24
BillHoo你好, 我是从StackOverflow追到这里来的。有一个问题不知道楼主有没有遇到过: WebRtcAecm_Process在去掉回声的同时把与回声重叠的那部分的原声也去掉了。还有一个问题啊,麦克风采集的PCM的buffer是作为WebRtcAecm_Process的第二个参数吗?不胜感激。

2013-06-26 21:07:14
回复 WebRtc_Aecm:[4楼]

你好,很高兴你能追到这里来 1)我没有弄懂你说的回声和原声重叠是什么意思。消回声把原声消掉了,这种情况我仅在回环路径测试时遇到过,两台设备进行测试不会有此问题。 2)process函数原型如下 int32_t WebRtcAecm_Process(void* aecmInst,
                  const int16_t* nearendNoisy,
                  const int16_t* nearendClean,
                  int16_t* out,
                  int16_t nrOfSamples,
                  int16_t msInSndCardBuf);
其中 nearendNoisy 表示带有噪声的buf,nearendClean 表示经过降噪处理的 buf,建议首先使用NS将采集到的buf降噪,然后将原buf传给nearendNoisy ,降噪后的buf传给 nearendClean,如果不降噪,直接将buf传给nearendNoisy ,nearendClean置为NULL即可。

2013-06-27 09:54:30
回复 Bill_Hoo:[5楼]

多谢你的回复!
回声和原声重叠的意思是回声和原声同一时刻进入到麦克风,我将它称之为“重叠”
回环路径测试是怎样的场景和配置呢?
我现在的测试环境是这样搭的: 从手机SD卡中以80字节为单位顺序读取一个pcm文件,送入到扬声器,同时调用WebRtcAecm_BufferFarend(), 然后读取麦克风采集到的数据的80字节,调用Process。再将out写入到aec.pcm文件。分析这个aec.pcm文件时,发现回声是被消除了,但是与回声处于同一时刻的原声也被消除了。
我这种测试思路有问题吗?
还有一个问题,降噪与否对回声抑制的效果影响大吗?
多谢 :)

2013-06-27 11:47:35
回复 WebRtc_Aecm:[6楼]

1.首先,我在stackoverflow里看到你调用时传的是8000Hz的采样率,也就是说你每次应该传入80个采样点,一个采样点是2个字节,你应该采集160字节/次才对。
2.对于AECM的测试,首先可以使用同一个PCM文件,分别放入FAREND和PROCESS,如果出来的结果接近全零,则验证你提取的AECM模块工作正常。
3.在验证AECM模块能够正常工作的前提下,你需要两台设备,每台设备两个线程,线程一用来采集和PROCESS,线程二用来播放和FAREND。
4.降噪与否对回声消除的效果:我的实验结果为:先消回声再降噪效果比先降噪再消回声好。

2013-06-27 12:47:50
回复 WebRtc_Aecm:[6楼]

还有个问题看掉了,我说的回环路径就是 127.0.0.1 的本机测试。

2013-06-27 13:17:45
回复 Bill_Hoo:[8楼]

对于第1点,我也测试过传160字节,效果和80字节是差不多的。
对于第2点,结果会接近全零,但是在本地也有声音进入麦克风的情况下,与回声重叠的那部分原声也会被消掉(预期应该是不会被消掉的,这和你的回环路径测试不一样,因为进入麦克风的"Normal Voice"没有再次进入扬声器,也就算不上是回声,而我猜你的回环路径测试是会再次被扬声器播放出来吧?)。
这就是我遇到的最主要问题。AECM是可以工作的,但是结果没有达到预期。我看了你的StackOverflow的回复,这一点应该是和时延没有关系的。

2013-06-27 14:05:17
回复 WebRtcAecm:[9楼]

你好,我仔细看了下你的思路

“从手机SD卡中读取一个pcm文件,送入到扬声器,同时调用WebRtcAecm_BufferFarend(), 然后读取麦克风采集到的数据,调用Process。再将out写入到aec.pcm文件。分析这个aec.pcm文件。”

发现你这里面根本不存在“原声”,何来原声被消除呢?
你使用固定文件作为声音来源,我们假设输入PCM数据为“A”,那么你送到扬声器的声音就为“A”,而麦克风采集到的声音为扬声器的“A”再加上背景噪声“B”(现在的B才是实际意义上的“原声”,假设你没说话),AECM的处理结果就是从“A+B”中找到被扬声器播放出去的“A”并进行消除,留下了“B”,而背景噪声也许比较小,结果也就仍然接近全零了,绕了一圈,你做的这个流程和我用同一个PCM文件分别放入farend和process是一个道理。
如果需要做回声测试,你需要两台设备,如我上一个回复所说。
希望对你有所帮助。

2013-06-27 14:40:17
回复 Bill_Hoo:[10楼]

是啊是啊,就是你说的这个意思。唯一不同的是我放音乐了,也就是说“B”里面有音乐,现在这段音乐与A重叠的部分就被消掉了 :(

2013-06-27 14:54:04
回复 WebRtcAecm:[11楼]

也就是说你的问题不在AECM这个模块上,而是你的测试思路。
你的问题就在于 —— 你的测试方法测出来本来就应该是这个结果,而你的预期却错误地认为会是另外一个结果。
搞清什么是“原声”,什么是“回声”就OK了。

2013-06-27 14:59:41
回复 Bill_Hoo:[12楼]

预期:"AECM的处理结果就是从“A+B”中找到被扬声器播放出去的“A”并进行消除,留下了“B”"
现在的结果是: B没有被完全留下,有一部分被消掉了。你假设我没有说话,实际我放了音乐并且被麦克风采集到了啊,这个音乐不是从扬声器出来的,这不算回声吗?我觉得这个音乐应该是被保留的吧?现在被部分消掉了。

2013-06-27 15:11:10
音乐是连续的音源,和人声是有很大区别的,连续乐音中很可能某一段的数据被认为是和A中的数据匹配从而导致被消除,如果你非要这样测,应该在运行时说话,而不是放音乐。

2013-06-27 15:14:19
回复 WebRtcAecm:[13楼]

可以参考一下 http://www.net130.com/netbass/voip/20040008009.htm

0 0
原创粉丝点击