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

音视频 同时被 2 个专栏收录
31 篇文章 4 订阅
5 篇文章 2 订阅
不推荐单独编译  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次唧唧声。二是通话过程中声音会忽大忽小,目前我是怀疑由AECMdouble 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项目对应目录中找到需要的文件。

  

前言

   最近一直在捣腾如何在androidiOS上使用GoogleWebRTC——一个无疑大力推动了互联网即时通信以及VoIP发展的开源项目。(elesos注:连谷歌都访问不了的国家,活该落后!)

   WebRTC提供一套音频处理引擎VOE,但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   (注: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认为静音的

音频全部置零然后发送。


出处http://billhoo.blog.51cto.com/2337751/1213801



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



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


[4楼]      [匿名]WebRtc_Aecm  回复
2013-06-26 19:54:24
BillHoo你好, 我是从StackOverflow追到这里来的。有一个问题不知道楼主有没有遇到过: WebRtc Aecm_Process 在去掉回声的同时把与回声重叠的那部分的原声也去掉了。

还有一个问题啊,麦克风 采集的PCM 的buffer是作为WebRtcAecm_Process的第二个参数吗?不胜感激。


[5楼]楼主      Bill_Hoo  回复
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即可。


[6楼]      [匿名]WebRtc_Aecm  回复
2013-06-27 09:54:30
回复 Bill_Hoo: [5楼]



多谢你的回复!

回声和原声重叠的意思是回声和原声同一时刻进入到麦克风,我将它称之为“重叠”

回环路径测试是怎样的场景和配置呢?

我现在的测试环境是这样搭的: 从手机SD卡中以80字节为单位顺序读取一个pcm文件,送入到 扬声器,同时调用WebRtcAecm_BufferFarend() , 然后读取麦克风采集到的数据的80字节,调用Process。再将out写入到aec.pcm文件。分析这个aec.pcm文件时,发现回声是被消除了,但是与回声处于同一时刻的原声也被消除了。

我这种测试思路有问题吗?

还有一个问题,降噪与否对回声抑制的效果影响大吗?

多谢 :)


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



1.首先,我在stackoverflow里看到你调用时 传的是8000Hz的采样率,也就是说你每次应该传入80个采样点,(编者注:10ms数据需要传80,1000ms即1s为8000,所以10ms对应80*2=160字节)一个采样点是2个字节,你应该采集160字节/次才对。

注:  http://stackoverflow.com/questions/17319574/audio-is-also-be-cancelled-which-overlap-with-acoustic-echo-when-using-webrtc-ae/17333797#17333797

2. 对于AECM的测试 ,首先可以使用同一个PCM文件,分别放入FAREND和PROCESS,如果出来的结果接近全零,则验证你提取的AECM模块工作正常。

3.在验证AECM模块能够正常工作的前提下,你需要两台设备,每台设备两个线程,线程一用来采集和PROCESS,线程二用来播放和FAREND。

4.降噪与否对回声消除的效果:我的实验结果为: 先消回声再降噪效果 比先降噪再消回声好。


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



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


[9楼]      [匿名]WebRtcAecm  回复
2013-06-27 13:17:45
回复 Bill_Hoo: [8楼]



对于第1点,我也测试过传160字节,效果和80字节是差不多的。

对于第2点,结果会接近全零,但是在本地也有声音进入麦克风的情况下,与回声重叠的那部分原声也会被消掉(预期应该是不会被消掉的,这和你的回环路径测试不一样,因为进入麦克风的"Normal Voice"没有再次进入扬声器,也就算不上是回声,而我猜你的回环路径测试是会再次被扬声器播放出来吧?)。

这就是我遇到的最主要问题。AECM是可以工作的,但是结果没有达到预期。我看了你的StackOverflow的回复,这一点应该是和时延没有关系的。


[10楼]楼主      Bill_Hoo  回复
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是一个道理。

如果需要做回声测试,你需要两台设备,如我上一个回复所说。

希望对你有所帮助。


[11楼]      [匿名]WebRtcAecm  回复
2013-06-27 14:40:17
回复 Bill_Hoo: [10楼]



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


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



也就是说你的问题不在AECM这个模块上,而是你的测试思路。

你的问题就在于 —— 你的测试方法测出来本来就应该是这个结果,而你的预期却错误地认为会是另外一个结果。

搞清什么是“原声”,什么是“回声”就OK了。


Elesos.com注:他的音乐其实还是送到扬声器的声音A吧,是回声。是需要消除的。


[13楼]      [匿名]WebRtcAecm  回复
2013-06-27 14:59:41
回复 Bill_Hoo: [12楼]



预期:"AECM的处理结果就是从“A+B”中找到被扬声器播放出去的“A”并进行消除,留下了“B”"

现在的结果是: B没有被完全留下,有一部分被消掉了。你假设我没有说话,实际我放了音乐并且被麦克风采集到了啊, 这个音乐不是从扬声器出来的 ,这不算回声吗?我觉得这个音乐应该是被保留的吧?现在被部分消掉了。


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


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



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





回复 Bill_Hoo: [15楼]

注: http://stackoverflow.com/questions/15302339/webrtc-aec-algorithm/16182942#16182942

多谢你的回复 :)
最后一个问题,那个时延是怎么计算的? 我看了你在StackOverflow上的发言,根据AudioProcessing.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().

1). 你是根据这个公式来计算的吗?
2). 这4个值都是什么意思啊?这段话我看了很多次了,总是看不懂。

2013-06-27 22:13:30
回复 WebRtcAecm: [16楼]

你好,是根据这个来计算,而且需要严格根据这个的描述来
t_analyze 表示你对音频帧A调用 farend 的时刻,t_render 表示[硬件]真正播放出帧A的时刻。
t_capture 表示[硬件]采集到音频帧B(注意跟A没关系了)的时刻,t_pull 表示帧B被传入 Process的时刻。
其实就是计算系统硬件buffer + 软件 buffer 的总延迟。
这个延迟在android系统中比较大, 有100~200左右不等, 需要你根据自己的buffer进行计算。计算方法就跟你android上层的buffer设计相关了。





2013-07-01 14:30:12
问下楼主,
我参照楼主的做法,第二步中AECM已经可以了。
2.对于AECM的测试,首先可以使用同一个PCM文件,分别放入FAREND和PROCESS,如果出来的结果接近全零,则验证你提取的AECM模块工作正常。

现在我的项目遇到的问题是,本机测试是可以的,但是双机通讯下,会有非常大的噪声,是不是AECM的msInSndCardBuf的参数错了。这个到底该怎么计算呢?
顺便问个小白的问题,在双机通信中,我认为从网络上接收的音频为远端音频,用WebRtcAecm_BufferFarend,本地录音的音频为近端,用process。这个没错吧。还有,我并没有用NS,AGC和其他模块,自用了AECM。这样可以吗?请楼主点播下,多谢。我的QQ:553270954

2013-07-01 18:08:35
回复 cstriker1407: [21楼]

你好:
1.非常大的噪声:你确定那是噪声而不是啸叫么? 我这边AECM局域网通信没有你说的很大的噪声,只是之前由于delay计算错误会产生很大的啸叫。delay的计算公式在audio_procession.h里面说的很清楚, 那个延迟你只能根据自己的buffer实现进行计算。
2.远端数据就是网络传过来的数据,近端就是本机采集到的数据。
3.只用AECM模块是可以的。

2013-07-03 14:31:59
Bill_Hoo:
你好:
  我现在在做回音消除这块,但是不能准备计算delay的时间,虽然audio_precession.h头文件中的公式说得很清楚,但是对于android平台的java层始终不能很好的计算出delay的时间,所以想 请教一下你在android上层设计buffer并计算出delay时间的解决办法。

2013-07-03 14:39:21
回复 Bill_Hoo: [22楼]

多谢楼主回复,问题(注: 双机通讯下,会有非常大的噪声)我已经发现并解决了。不是webrtc的问题。

2013-07-03 21:08:27
回复 51CTO游客: [23楼]

你好,如果可能, 尽量不要在java层做AECM ,java到android底层中间还夹着jni,据说还有一个AudioFlinger的延迟,这个我也没有深入,所以不敢乱讲。如果你的项目不是必须在java层做音频的采集和播放,那么AECM还是放在底层做的好。
如果非要在java层做,delay的计算只有根据你自己的buffer设计来, 这个我没办法帮上忙, 总体思路就是系统底层硬件延迟 + 你的buffer延迟。

2013-08-01 14:25:30
您好 我也在做webrtc的AEC 请问AudioRecord和AudioTrack如何的阻塞时间如何计算

long timestamp = System.nanoTime();
mAudioTrack.write(data, 0, data.length);
long renderTime = System.nanoTime() - timestamp;
native _set_audioRenderBlockTime(renderTime);

请问这样做能得到准确的阻塞时间嘛

2013-08-02 15:14:00
请问楼上,AECM、VAD、AGC三个模块使用的顺序是怎样?如果VAD检测出是静音模块,那么AECM该如何调用啊?

2013-08-02 15:33:47
回复 hsfhzkd: [27楼]

VAD检测出是静音,你可以采取以下两种方式:1.不发送这一段静音的音频 2.将这一段VAD认为静音的音频全部置零然后发送。

2013-08-02 15:34:46
回复 w33222885: [26楼]

你好,你这样做可以 得到 write 的阻塞时间,但这个时间并不是 renderTime (如果你的这个变量表示 渲染延迟 的话)。

2013-08-05 10:01:54
回复 Bill_Hoo: [29楼]

您好 请问渲染时间是一个固定的延迟吗,是否跟 audioflinger的lantency 有关。



2013-08-05 13:28:25
回复 w33222885: [30楼]

据我的实验结果,该延迟并不是固定的,需要周期性更新。我当时看源码时,AudioFlinger层的latency没有办法在上层获取,所以没有考虑这个。不过不知道现在4.1、4.2的源码发生了怎样的变化,是否有类似 iOS的直接获取底层硬件延迟的接口我也不清楚。

2013-08-05 13:47:52
回复 Bill_Hoo: [31楼]

现在aecm已经可以工作,但是最后的效果始终有嘶嘶的杂音,不知道您最终的效果如何~

2013-08-06 13:14:27
回复 w33222885: [32楼]

单独提取的AECM模块达不到完美的效果,但也不会始终存在嘶嘶声。也许你可以配合NS和VAD模块一起使用。

2013-08-21 02:21:29
你好Bill Hoo,我对NS 模块的 WebRtcNs_Process 这个函数有点疑问。int  WebRtcNs_Process (NsHandle* NS_inst, short* spframe, short* spframe_H,short* outframe, short* outframe_H)。 最后面四个参数 是指什么?我不太懂. noise_suppresion.h 里面说 buffer for L band, buffer for H band。  L band 和 H band 是什么? ? 多谢了。

2013-08-21 10:15:18
回复 51CTO游客: [34楼]

可以参考下 http://what-when-how.com/voip/wideband-voice-voip/。
以及维基 http://en.wikipedia.org/wiki/H_band
      http://en.wikipedia.org/wiki/L_band

L band  is the 1 to 2  GHz  range of the  radio spectrum .


2013-08-21 22:51:03
回复 Bill_Hoo: [35楼]

多谢,楼主。我的audio input format 是
PCM signed (wav file format)
16 bit encoding  
8000 Hz sample rate
Mono (1 channel)
Big endian format
但是参考资料都是说把16khz 的audio  用qmf 分成2个 8khz的audio。 那我要怎么去用 WebRtcNs_Process 这个函数呢??只把audio 放进spframe, buffer for L band 么,不管buffer for H band?

2013-08-22 08:59:28
回复 51CTO游客: [36楼]

早上好, 8KHz sample 直接入 L band 就OK了,我目前最高用到 16KHz,均只入的L band,至于 H band  对于话音的噪声消除个人觉得应该没有用处。 不过对于 32000 Hz 的sample就说不好了, 一般单声道的应用可能都不会触及到 Hband,如果你发现在某个环境下会用到 H Band,还请回来告知一声哦。

2013-08-22 17:23:51
Bill_Hoo你好,我又来了。之前一个月在做voip的其他功能,现在又开始做AEC了。现在遇到的问题是怎么计算系统硬件buffer的延迟,这个Android有什么接口吗?

2013-08-22 18:05:12
回复 51CTO游客: [38楼]

你好, iOS有API直接提供硬件层的采集和播放延迟,但据我的实践和了解,android目前并没有提供直接的硬件延迟。 因此你需要根据自己的上层工作buffer对底层buffer进行估算,从而得到一个较为精确的硬件延迟。但该延迟始终不精确,因为经过了几处buffer的缓冲。 如果要更精确, 可以采用OpenSE等JNI层的采集库,而不是android上层的AudioRecord以及AudioTrack。不知道这样回答是否解决了你的问题。

2013-08-23 02:10:14
回复 Bill_Hoo: [37楼]

恩, 谢拉。 L band 和 H band 估计跟坏境噪音的频率有关吧。 我想问问你是怎么wrap WebRtcNs_Process? 我成功把 .so build出来。 然后放到另一个app应用的时候出现了这个错误 Fatal signal 11 (SIGSEGV) at 0x1e90001d (code = 1). 我估计是我wrapper 写错了。 我的是这样的
JNIEXPORT jint JNICALL
Java_research_webrtc_NoiseSuppression_process(JNIEnv *env, jclass jclazz,
           jint nsHandler,  jshortArray  spframe, jshortArray spframe_H,
           jshortArray outframe, jshortArray outframe_H) {
     NsHandle *hNS = (NsHandle*) nsHandler;

     // Step 1: Convert the incoming JNI jintarray to C's jint[]
     jshort *inCArrayIn = (*env)->GetShortArrayElements(env, spframe, NULL );
     jshort *inCArrayOut = (*env)->GetShortArrayElements(env, outframe, NULL );
     //dummy variable for buffer for H band, contains no content
     jshort *inCArrayIn_H = (*env)->GetShortArrayElements(env, spframe_H, NULL );
     jshort *inCArrayOut_H = (*env)->GetShortArrayElements(env, outframe_H,
                 NULL );
     if (NULL == inCArrayIn || NULL == inCArrayOut || NULL == inCArrayIn_H
                 || NULL == inCArrayOut_H)
           return -1;
     jsize length = (*env)->GetArrayLength(env, spframe);

     // Step 2: Perform its intended operations
     jint result = 0;
     result = WebRtcNs_Process(hNS, spframe, spframe_H, outframe, outframe_H);

     // Step 3: Copy the content of C's Native jshort[] to JNI jshortarray, and release resources
     (*env)->ReleaseShortArrayElements(env, spframe, inCArrayIn, 0);
     (*env)->ReleaseShortArrayElements(env, outframe, inCArrayOut, 0);

     (*env)->ReleaseShortArrayElements(env, spframe_H, inCArrayIn_H, 0);
     (*env)->ReleaseShortArrayElements(env, outframe_H, inCArrayOut_H, 0);

     return result;
}

2013-08-23 22:34:41
回复 Bill_Hoo: [37楼]

原来是WebRtcNs_Process的input放错了。 问题已解决。

2013-08-23 22:37:50
回复 51CTO游客: [40楼]

你好,如果按照我的调用方式, H band的两个参数我都会传递 NULL 指针 ,而你的
if (NULL == inCArrayIn || NULL == inCArrayOut || NULL == inCArrayIn_H
          || NULL == inCArrayOut_H)
就直接 return -1了;

再者,题外话,如果没有这句if判断,那么错误会发生在  
(*env)->ReleaseShortArrayElements(env, spframe_H, inCArrayIn_H, 0);
Release要求传递进去的指针不能为NULL,对NULL指针进行内存释放是非法操作。

不知有没有找到你的bug呢。

2013-08-23 22:49:02
回复 51CTO游客: [41楼]

哦哈哈,看来我在打字的时候你也在打字..................... 恭喜、

2013-09-02 15:22:24
回复 Bill_Hoo: [17楼]

"
你好,是根据这个来计算,而且需要严格根据这个的描述来
t_analyze 表示你对音频帧A调用 farend 的时刻,t_render 表示[硬件]真正播放出帧A的时刻。
t_capture 表示[硬件]采集到音频帧B(注意跟A没关系了)的时刻,t_pull 表示帧B被传入 Process的时刻。
其实就是计算系统硬件buffer + 软件 buffer 的总延迟。
这个延迟在android系统中比较大,有100~200左右不等,需要你根据自己的buffer进行计算。计算方法就跟你android上层的buffer设计相关了。
"

Bill, 对于这个回复我还是有几个问题弄不明白:
1. 帧B里面是不是包含帧A的声音( 帧A从speaker出来进入mic时就是帧B )?
2. 假如t_render < t_analyze呢? 我的理解是WebRtcAecm_BufFarend( Frame A )只要在WebRtcAecm_Process( Frame B )之前调用进行了。这种理解对吗?
3. 怎么能够找到帧A和帧B的对应关系呢(我感觉这是最困难的)?
3. 我又仔细读了你在StackOverflow的发言, 对于这两点:
  2.AudioRecord.read() and AudioTrack.write() sometimes block(due to minimized buffer size), so when you calc the delay, don't forget adding blocking time to it.

  3.the buffer of AudioRecord and AudioTrack also increases the total delay. so add it.
你是怎么知道blocking time和buffer of AudioRecord and AudioTrack的大小的呢?

问题有点多, 被AEC困扰挺久了。不胜感谢~

2013-09-02 19:07:20
Hi,Bill!
我最近在做一个Android的通过蓝牙传递语音数据的项目,用了WebRTC的NS后声音变得沙哑了。
我的实现过程很简单,先用AudioRecord录音,取出pcm数据,然后 传给ns_process,最好编码后发出去 ,我为了排除其它影响,特地在ns_process后将数据写入文件播放,跟传送到远端后类似,都是变沙哑了。
另外,不加NS,整个通话声音都是正常的,加上AECM也没有问题。
你有没有碰到类似的问题,NS需要什么特殊设置吗,还是我这个使用过程有问题?
麻烦你有空帮忙看看,谢谢!



2013-09-03 11:32:59
楼上可以试下nsx, ns是浮点运算, nsx是定点运算。 我用nsx效果挺好的。

2013-09-03 16:03:50
回复 51CTO游客: [46楼]

谢谢!找到原因了,不过不是因为浮点的原因。
我的参数:
16 bit encoding  
8000 Hz sample rate
Mono (1 channel)
20ms一帧,故每帧为160字节,之前将160字节直接传给NS,而 NS每次处理10ms的数据 ,现在我分成两次,每次传80字节就没有问题了。

2013-09-03 17:50:04
回复 51CTO游客: [44楼]

你好:
A1、帧A和帧B没有直接联系,我们只需要知道总延迟,至于这两帧是否为相互包含的音频没有关系。
A2、根据你的描述,感觉你的思路是这样的:记录帧A放进farend buffer的时刻,然后要让该时刻去匹配帧B传入 process 的时刻。如果你是这样想的,是因为你觉得帧A和帧B是有关联的。实际上这两帧并没有直接联系。如A1所述,我们需要知道的仅仅是两个时间: 
T1(系统的播放延迟) = 帧A被硬件播放出来的时刻 - 帧A被放进 farend 的时刻;
T2(系统的采集延迟) = 帧B被放进 process 的时刻 - 帧B被硬件采集的时刻;

total delay =  T1 + T2;

A3、AudioRecord.rad() and AudioTrack.write() 会阻塞,阻塞时间可以在 调用前和调用后粗略计时即可。 AudioRecord buffer的最小大小可使用其提供的API获取。详情请参见 http://developer.android.com/reference/android/media/AudioRecord.html#getMinBufferSize(int, int, int)

希望以上阐述能解答你的疑惑。



2013-09-03 17:53:05
回复 51CTO游客: [46楼]

这段时间都没接触WebRTC音频部分了,NS模块都有定点了 - -+



2013-09-22 11:33:59
回复 Bill_Hoo: [17楼]

你好博主,请教一下您在17楼关于aecm延迟的计算说明:
“t_analyze 表示你对音频帧A调用 farend 的时刻,t_render 表示[硬件]真正播放出帧A的时刻。
t_capture 表示[硬件]采集到音频帧B(注意跟A没关系了)的时刻,t_pull 表示帧B被传入 Process的时刻。”

我看了echo_control_mobile.h里的方法,关于四个时刻的点怎么确定不是很清楚,比如t_analyze的时间,应该是在调用WebRtcAecm_BufferFarend() 前计时还是调用后计时 呢?看英文原意四个时刻应该是之后吧?可是如果t_pull是在调用WebRtcAecm_Process()之后才计时的话,WebRtcAecm_Process()它本身就需要使用延迟参数(也就是说调用它时这参数还没计算出来呢),感觉又矛盾了,请博主指点迷津。

而关于render的时间,“t_render is the time the first sample of the same frame is rendered”,一个语音帧(80或160个采样)的 第一个采样被播放的时间,总感觉取不到,我们能取到的只有播放完整个语音帧的时间点吧 ,t_capture同理,您是怎么处理这两个时间点的呢?

2013-09-23 14:04:58
回复 emuman: [53楼]

你好emuman,
A1: 四个时间点均在调用之前得到即可。
A2:我们确实不能直接得到第一个采样点的时间, 但你可以根据自己上层的 buffer 大小以及底层的硬件 buffer 大小估算出缓冲区中总共缓冲了多少帧,这样一来便可计算出第一个采样点的时间。

2013-09-25 15:02:09
回复 Bill_Hoo: [54楼]

首先多谢博主答复,然后继续咨询一下。
这几天我在android上试验,用 jni+opensles来录音、放音,发现理论和实际情况大有不同。
参数是8khz、mono、16bit录音和放音,每个语音帧 80个sample(10ms)
1、首先单个延迟很小,t_render - t_analyze大约在25-55ms区间内,t_pull - t_capture对我来说约等于0,因为我采集后马上就进行process了;和博主说的大约100--200ms差距比较大。此外我是不断重新计算延迟值的,博主您是计算出一个值就固定使用还是也不断计算?
2、opensles的录音放音情况是:某个时间段内得到m个录音的语音帧,然后下一个时间段播放n个语音帧,如此反复。所以如果不变通,就会变成调用m次WebRtcAecm_BufferFarend()后再调用n次WebRtcAecm_Process,4个时间点怎么确定就很暧昧了:因为延迟值其实是给录音后的process用的,所以属于录音的t_capture和t_pull的时间点是明确的,但是放音的t_analyze和t_render用m个语音帧里的哪个呢?第一个、最后一个、平均值?我试验了最后一个(距离录音动作最近的上个播放延迟),发现出来的结果不对;直觉上第一个、平均值也应该不对。 
最后,从多次统计结果发现,m和n是同一个数量级,差距不大(这是显然的)。
3、从我这段时间的测试数据看,android机系统有自己的opensles缓冲区,比如录音,我设置录音缓冲区是80个sample,系统会先录制一段时间,数据充满它自己的缓冲区(系统缓冲区)后再调用回调函数拷贝到我设置的缓冲区(用户缓冲区),放音也同理(提供数据足够快的情况下),这就解释了上面第二点的情况:先提供m个录音语音帧,再消费n个放音语音帧。
这里有个问题,我翻遍开发文档发现系统没提供接口来得到系统内置缓冲区大小,我甚至在google play上找到个audio buffer size的app,它用自己的算法来计算系统opensles的内置缓冲区和采样率大小,但我的测试机算出来的结果和我的测试数据又没法对上。java层的AudioRecord.getMinBufferSize()以及api17后倒有个相关的AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER,这两个试验过发现也不对。
4、针对2和3的情况,我设想过能不能把m个录音帧当一个大帧、n个放音帧当一个大帧来处理。WebRtcAecm_BufferFarend()好像可以累积数据,只要长度不超过它的缓冲区最大值就好;但WebRtcAecm_Process()只能传进80或160个sample,这就矛盾了。
5、好像苹果机的机制是录一个帧再放一个帧,非常符合webrtc aecm的理论,甚至它都提供内置的回音处理了。android在4.0后虽说也提供内置的回音处理,但效果很多时候等于没有。这点是我的牢骚了,android还有很多地方要完善。

对opensles的这种情况,博主有没有什么经验?甚至提供个思考的方向都可以,我现在束手无策了

2013-09-26 10:02:34
回复 emuman: [55楼]

emuman你好:
A1: 100~200ms延迟是在 Java 层使用AudioRecord/AudioTrack得到的,你使用的 opensles在JNI层了,延迟在25-55ms正常。t_pull - t_capture不一定为0,你虽然采集之后马上就调用 process,但是t_captrue是 硬件采集到某帧的时刻,该帧从硬件进入底层buffer,然后你的 opensles 从底层buffer读取是有延迟的,这个延迟要算,在JNI层可能很小,但是也要算。最后延迟值的确需要不断更新。WebRTC源码里我看了下好像是每一秒更新一次,我自己的实验结果1秒太久了,我是 每发送一帧就更新延迟值。

A2:当初选型的时候我没用OpenslES,所以我不清楚他的工作流程。从你对他的描述中发现问题在于其录音、放音流程导致四个时间点不好捕捉,他自己缓存了录音帧和播放帧,但我们又没办法求得缓存大小以最终求得缓存延迟。个人暂时没想到好的直接可行的解决办法,因为如果我们没办法得知他缓存了多少数据,那如何求得对应的延迟?不过我隐约有个思路是我们需要 自己设计buffer来统一缓存数据,这样便可计算,但实现方案我没有思路。

A3:据你的描述,“我设置录音缓冲区是80个sample,系统会先录制一段时间,数据充满它自己的缓冲区(系统缓冲区)后再调用回调函数拷贝到我设置的缓冲区(用户缓冲区),放音也同理”。既然你可以设置缓冲区大小,为何不能计算出延迟呢?这点需要你再加以阐释。其次,AudioRecord.getMinBufferSize()所获得的大小是 系统最小录制缓冲区大小(如果你没有其他设置的话),这个值你需要在构造AudioRecord实例时传入,这样该实例对应的 底层buffer才会是这么大,之后根据这个大小是可以估算出底层buffer所造成的延迟的。AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER我没有研究过,不敢乱讲。

A4:“当成一个大帧”这种思路其实和我第二点提到的思路异曲同工了,我们的核心思想都是希望将小的、不可知的opensles底层buffer延迟中和起来,得到一个整体的、可计算的延迟,我直觉可行,但需要实践。 WebRtcAecm_BufferFarend()的确有自己的buffer,好像是用来做匹配算法的,回声处理的算法图可以在 CSDN 里找到。但该buffer大小不足以容纳太多的数据。

A5:iOS音频系统本身的延迟就很小,并且其具有直接获取硬件延迟的API, WebRTC源码里就是直接调用的该API。 VoiceProcessionUnit 也自带有回声消除选项,不过我没有使用过。至于 android 的 ES,我用过,效果不堪。android的的确确需要继续完善,尤其是音频API,不过 android 毕竟开源,个人觉得其生命力和活力都很不错,而且 Google 也在统一 android 市场了,跑题了- -、

希望以上回答能帮助你打开思路,我还有哪里没讲清楚的请留言。

2013-09-26 10:36:29
你好博主,感谢你的回复。
关于3,“既然你可以设置缓冲区大小,为何不能计算出延迟呢”,这里没讲清楚,opensles里初始化录音器和播放器时必须指定缓冲区大小以及个数,自己设置的缓冲区指的是这个用户缓冲区,它的延迟是可以计算的,但系统还有个缓冲区,那个现在看来无法直接取到它的大小(也就是延迟),问题是在这里。
opensles的工作方式里可以设置成给录音/放音提供 多个一样大小的缓冲区 (估计是为了数据处理连续性),这个缓冲区只是用户缓冲区而非系统缓冲区,然后指定录音/放音回调函数,每次回调就提供/消费一个用户缓冲区数据。
昨晚我有了新思路,这两天继续试验,有什么进展再过来讨论。

2013-09-26 19:46:52
你好,BillHoo!
我从StackOverflow追到这里,我最近在做基于ARM Linux板子的媒体通信项目,碰到回声问题,想用WebRtc的AEC,但有些问题看代码和网上资料没看懂,想跟你请教以下几个问题:
1.WebRtcAec_BufferFarend函数前面有注释:
Only Buffer L band for farend
是什么意思?只能放L Band数据还是放了W Band他也转换为L Band?我看了里面代码,就是时域转频域,然后写缓存;
另外,L Band和W Band是指的采样率的高低吗?
2.WebRtcAecc_Process函数中的最后一个参数skew是干什么用的?怎么设置?我没看明白。
3.AEC和AECM都有注释Not recommended to be enabled on the server-side.在aec_processing.h中,为什么?服务器端指的是什么?多点通信中负责混音的类似H.323中的MCU吗?超级节点?
4.AEC可以修改为44.1K采样率吗?Echo Tail Length想要修改的话要动哪里?
不好意思,一口气问了这么多,还望博主给予指点。
多谢!

2013-09-29 15:01:55
回复 emuman: [57楼]

好的,祝你成功。

2013-09-29 17:09:05
回复 Suitjune: [58楼]

你好 Suitjune, 我最近很忙,所以没办法详细回复你,抱歉。


2013-10-17 11:29:47
能再分析下neteq模块吗,貌似对音频质量有一定的提高

2013-10-18 08:52:14
回复 it206: [62楼]

it206 你好,neteq模块我还没有涉及,如果有可能涉及到,我会分享在这里的。谢谢你的建议 :)

2013-10-20 15:22:04
Bill Ho,还是要请教你相关问题:
1.我用WebRtc的AEC提取出来,在ARM-Linux的板子上运行,现在我做测试:
1.32K单声道的人声文件A,1次160采样点做BufferFarend,同时将这个文件去做Process,Output出来的文件几乎没有声音,都去掉了;
2.如果我是A去做BufferFarend,同时A+B,两个人声去做Process,则输出声音劣化严重,声音嗡嗡的;
3.如果我录制了一段很安静的声音,几乎没有声音的文件C去做BufferFarend,然后A+B人声做Process,那输出也是嗡嗡的;
这可能是什么问题导致的呢?谢谢!

2013-10-20 16:23:42
上面的问题应该是我自己弄粗了。

2013-10-21 21:33:39
楼主,您好,最近项目中要用到AECM,我在按照楼主提供的AECM模块源文件进行编译的时候(头文件都已添加),出现函数缺失的错误,具体的错误提示为“undefined reference to 'WebRtcSpl_MaxAbsValueW16'”...“undefined reference to 'WebRtcSpl_RealForwardFFT'“等等错误。我的解决方法是找到这些函数所在的源文件并添加到工程中,生成的库之后,编写Demo按照楼主方法进行检测,读取相同音频文件时依然有原声。楼主能否给个生成AECM库的完整文件,QQ 893077446,万分感谢。

2013-10-23 23:35:04
謝謝您的帖子!我正在使用webRTC中的AEC模塊、我想問一次WebRtcAec_BufferFarend和WebRtcAec_Process中的音頻信號的格式(就是nearend.pcm和farend.pam的格式):我現在是用:
8000HZ採樣、Signed-16 PCM、LITTLE_ENDIAN
我用audacity錄的farend.pcm、然後(僅為測試)我把farend.pcm直接拷貝生成nearend.pcm、這樣我希望AEC處理後生成的output.pcm應該不含有聲音信號(全部過濾)、但是我得到的output.pcm是很大的噪聲、所以我懷疑是我爸音頻格式設錯了。

請問:
輸入的farend.pcm和nearend.pcm的格式是8000HZ採樣、Signed-16 PCM、LITTLE_ENDIAN嗎?
AEC處理後輸出的output.pcm的格式也是8000HZ採樣、Signed-16 PCM、LITTLE_ENDIAN嗎?

另外附上我的測試代碼( 從sample中拷貝的 )、麻煩您幫我看一下問題在哪裡、謝謝!
。。。
     void * haec = NULL;        

     if( 0!=WebRtcAec_Create(&haec) )  
           printf("create error\n");  

     if( 0!=WebRtcAec_Init(haec,8000,8000) )
           printf("init error\n");

      short  farend[160],nearend[160],output[160];

     FILE *ffar, *fnear,*foutput;
     ffar = fopen("farend.pcm","r");
     fnear = fopen("nearend.pcm","r");
     foutput = fopen("realoutput.pcm","w");

     for(int i=0;i<500;i++)
     {
           fread(farend,2,160,ffar);
           fread(nearend,2,160,fnear);

           if( 0!= WebRtcAec_BufferFarend (haec,farend,160) )
                 printf("bufferfarend error\n");

           if( 0!= WebRtcAec_Process (haec,nearend,NULL,output,NULL,160,0,0) )
                 printf("process error\n");

           fwrite(output,2,160,foutput);
     }

     if( 0!=WebRtcAec_Free(haec) )
           printf("free error\n");

     fclose(ffar);
     fclose(fnear);
     fclose(foutput);
。。。

2013-10-24 02:34:44
問題已經找到了(粗心大意忘記 用2進制來讀寫測試文件 了)。現在把farend.pcm中的語音以100ms延遲混入nearend.pcm中,再用WebRtcAec_Process(haec,nearend,NULL,output,NULL,160, 100 ,0)來處理,基本可以去掉原來farend.pcm中的語音(迴聲),再次感謝您的帖子!



2013-10-24 22:16:42
謝謝!我還想問您一個問題:在昨天的測試中,我剛開始使用8000HZ/Signed-16-PCM/LITTLE_Endian的語音數據、當延遲100ms的時候,迴聲基本可以消除;但是後來改為我們現在實際使用的16000HZ/Signed-16-PCM/LITTLE_Endian的語音數據時,發現迴聲殘留明顯比8000HZ的時候大很多。我在處理8000HZ數據的時候調用WebRtcAec_Process的參數是這樣的:
WebRtcAec_Process(haec, nearend, NULL, output, NULL, 160, 100, 0)

我覺的160應該是20ms的8000HZ/Signed-16數據,所以當處理16000HZ/Signed-16數據的時候,應該改成320,即這樣調用:
WebRtcAec_Init(haec,16000, 16000)
WebRtcAec_BufferFarend(haec, farend, 320)//20ms的16000HZ/Signed-16數據
WebRtcAec_Process(haec, nearend, NULL, output, NULL, 320, 100, 0)//100ms延時

但是當改成320的時候、WebRtcAec_BufferFarend和WebRtcAec_Process都報錯(返回值不為0),我想問一下:是否這2個函數能夠處理的sample數目最大就是160?您在處理16000HZ/Signed-16數據的時候、每次處理的是10ms(160)還是20ms(320),請問這個sample數目和消除迴聲的效果有關係嗎?謝謝!

2013-10-25 03:21:13
剛才看見WebRtcAec_Process的源碼(echo_cancellation.c)中:
。。。
  // number of samples == 160 for SWB input
   if (nrOfSamples != 80 && nrOfSamples != 160) {
    aecpc->lastError = AEC_BAD_PARAMETER_ERROR;
    return -1;
  }
。。。

所以可能是 都處理10ms數據(8000HZ的sample是80, 16000HZ的sample是160)、謝謝!

2013-10-25 08:53:35
回复 米牛牛: [71楼]

您好米牛牛,16000Hz,10ms就是 160 個 sample,您最後是算對了的。

2013-10-29 21:07:14
hi, bill
最近也在研究webRTC中的AEC模块,有个问题请教下
WebRtcAec_BufferFarend和WebRtcAec_Process中的参数farend和nearend是不是分别表示扬声器要发出的原始音频数据和麦克风录制的原始音频数据(这个有可能包括扬声器的回声)?另外参数 nrOfSamples是不是表示这两个函数一次最大处理的数据块?即只 有80和160这两个

2013-10-29 21:57:08
您好!我還想問一下關於drift compensation的問題:WebRtcAec_Process 最後的那個skew參數 、我現在一般都設為0、請問這個參數需要根據硬件的情況進行調整嗎?還是說都設為0?謝謝!

2013-10-29 22:12:10
這裡有一個skew參數的討論:
https://groups.google.com/forum/#!searchin/discuss-webrtc/skew|sort:relevance/discuss-webrtc/T8j0CT_NBvs/aLmJ3YwEiYAJ

其中有一段:
。。。but we're really just compensating for a mismatch
between 44.1 and 44 kHz sampling rates.。。。

這是不是說對於8000HZ和16000HZ來說、skew參數都可以設為0?還是說依然要設法計算當前硬件的drift?謝謝!


2013-10-30 09:28:28
回复 米牛牛: [75楼]

这个应该是对于不同平台的时钟补偿吧,因为不同平台的时钟不一样,会导致同频率有差异

2013-10-30 17:40:27
回复 icebery425: [73楼]

您好icebery,
farend是远端数据,即VoIP通信中接收到的对端的、即将被本地扬声器播放的音频A。
nearend是本地数据,即刚刚被本地麦克风采集到的音频B,而音频B可能包含上述音频A(麦克风采集到了扬声器播放的A),于是 A和B 一起发送给远端,致使远端又听到了自己刚才发送的A,也就产生了所谓的回声。
nrOfSamples 可以这么理解,具体的含义WebRTC源码里有说明的 :)

2013-10-30 17:43:46
回复 米牛牛: [74楼]

您好米牛牛,該參數在理想情況下的確為0,但硬件不同,對音頻進行渲染和播放的延遲就有所差異,這個差異在我之前的android設備上達到了100~200ms不等,如果始終保持為0,僅根據我的實踐結果,將得不到好的回聲消除效果。以我個人的經驗來說, 該參數是需要實時更新的。  :)

注:博主用繁体回复,真是很好的一个人啊

2013-10-30 19:56:18
hi, bill
最近我在研究一个消除超声波回声的项目,用WebRTC中的AEC时,发现在调用WebRtcAec_Init时,参数有一个需要传递采样数据频率只有8000, 16000,32000hz这几种频率,我这边需要用到的 采样数据频率为44100 ,当传这个值是就出错,应该是表示不能传其它频率的值,或更高的值,想问下,你有没碰到过这样的问题,或者类似的情况,需要消除超声波的回声?

2013-10-30 23:19:41
回复 Bill_Hoo: [78楼]

謝謝您的回复!您說的要實時更新的100~200ms的參數是下面的哪一個?是msInSndCardBuf還是skew?
WebRtcAec_Process(void *aecInst,  
const WebRtc_Word16 *nearend,
const WebRtc_Word16 *nearendH,  
WebRtc_Word16 *out,  
WebRtc_Word16 *outH,
WebRtc_Word16 nrOfSamples,  
WebRtc_Word16 msInSndCardBuf,  
WebRtc_Word32 skew)

msInSndCardBuf是用來適應延遲的、您說的要在100~200ms內實時更新的是指msInSndCardBuf嗎?請問是否skew也需要實時更新?是否skew目前只用於對於44100hz的 漂移補償 ?能介紹一下如何實時更新skew參數的步驟嗎?謝謝!

2013-10-31 09:08:55
回复 米牛牛: [80楼]

hi, 米牛牛,你有没偿试做过对44.1KHz采样数据的消回声事情?
见96楼

2013-10-31 09:16:50
回复 米牛牛: [80楼]

您好米牛牛:
我很抱歉的發現我們討論的不是一個模塊。我說的是 手機上進行AEC處理的模塊“AECM”,而你使用的是PC端的“AEC”模塊。手機上的AECM模塊是沒有skew參數的 ,我想這也是手機CPU瓶頸所致。而PC端的接口會有skew參數。這個參數我沒有研究。只是當初 在手機上使用PC端模塊AEC時,該參數被我設置為0。 之後就沒有對這個參數做任何學習了。




2013-11-01 02:03:13
回复 米牛牛: [84楼]


但是我發現一個情況:就是當我把AEC初始化為16K的時候、依然可以處理8K的數據(這時每次處理的數據依然是160個samples、即變為20ms的8K數據)、不知是否對您的44K項目有用

2013-11-01 10:59:40
Bill_Hoo,请教你一下,我在android上应用了那个aecm模块,回音消除的效果不错,但是,如果一个语音过长的话,比如说一次说七八个字,经过回音消除后,出现了语音丢失的情况,即第四五个字会丢失或者无法听清楚,不知道这个是什么原因呢,是和参数的设置有关吗?我的msInSndCardBuff设为240左右?这个问题困扰了好久了,最近一直在看源代码也没啥头绪。

2013-11-01 11:21:14
回复 lzg9099: [87楼]

您好lzg9099:
我在想,你是一直使用240这个定值么?再者,回声消除的级别是否开到了最大?降一个级别试试。如果你能提供更明确的信息,也许我们可以找到问题所在。

2013-11-01 11:50:49
回复 lzg9099: [87楼]

还有一个问题我突然想到,你是不是在做本机的回环(127.0.0.1)测试? 如果是的话,吞字是正常的。

2013-11-01 13:23:24
回复 Bill_Hoo: [89楼]

感谢你的回复:
1,我是一直使用的240这个定值,我换了几个手机测试时都是用的240,效果都很好,要是将该值设为其他的话,声音要么有杂音,要么就听不到声音了;2,关于回音消除的级别,我没有去设置,那应该是默认的级别吧,这个级别我也没弄太清楚;3,关于测试,我只是写了一个demo,通过将AudioRecord采集的声音,传给aecProcess()方法进行回音消除,然后利用AudioTrack将处理过后的声音播放出来,播放以后调用aecmBufferFarend()方法将处理后的声音处理一下。这样测试时,播放出来的声音是没有回音的,但是出现了吞字状况。


2013-11-02 07:43:28
Bill_Hoo 高手,你好,我按照你的思想把aecm抽取出来编译成so,通过jni给Android调用,回音消除效果如下:如果是前置喇叭(听筒)效果完美,如果是后置喇叭(播放音乐用的)效果不如前置喇叭,总体来说还不错。但是现在出现一个问题就是 当选择后置喇叭作为输出时,如果在一个很安静的房间,静静的几分钟不说话,就会产生啸叫,如果出现啸叫后就说话,这啸叫就立刻消失。想请教一下,这个啸叫怎么产生的,还有怎么样可以解决,你的软件会产生这样情况吗? 恭候QQ上讨论 扣扣 1281200395

2013-11-02 21:06:13
回复 AudioAEC: [91楼]

你好,首先,听筒放音是不会存在声学回声的。声学回声之所以产生,是因为扬声器播放出来的声音又被麦克风采集回去。而听筒播放的声音麦克风是采不到的。
其次,啸叫的问题需要借助 VAD 来进行静音检测,在静音的环境下就不需要向扬声器传递音频进行播放了。



2013-11-04 19:20:42
您好,我的AECM模块读文件测试正常。在开双线程测试时,一个线程用AudioTrack读文件播放,并调用bufferFarend;另一个线程中,用AudioRecord录取音频数据,并调用process将录取的数据进行处理,处理后的数据存入到pcm文件中。用Audition播放处理后的音频数据,为能完全消除掉回声,但是出现吞音的现象,且经常出现。用setConfig方法设置回音消除等级后,吞音现象微有减弱,但依然存在。
调试了很多,依然无法解决上面的问题,我怀疑是延时计算有问题,我的延时计算为:
硬件延时t1: bufferSize1 = AudioRecord.getMinBufferSize(...)
         t1 = bufferSize1 / frameLength * 10 ms
读取数据阻塞延时t2:
              startTime = System.currentTimeMillis()
              audioRecord.read(...)
              t2 = System.currentTimeMillis() - startTime

硬件延时t3: bufferSize2 = AudioTrack.getMinBufferSize(...)
        t3 = bufferSize2 / frameLength * 10 ms

读取数据阻塞延时t4 :  
            startTime = System.currentTimeMillis()
            inStream.read(...)
            t4 = System.currentTimeMillis() - startTime  

总延时是上述四个延时之和,不知道上述延时计算是否存在问题,请多指教。
我是从pcm文件读取数据送到AudioTrack播放,同时录音获取的数据直接送到process中。


2013-11-05 16:34:38
回复 icebery425: [81楼]

您好 icebery425:
我这段时间在往WebRTC音频的上边一层看,也就是APM(AudioProcessingModule),发现APM使用的抽象类型 AudioFrame 最高仅支持双声道 60ms 的 32KHz音频。

/* This class holds up to 60 ms of super-wideband (32 kHz) stereo audio. It
* allows for adding and subtracting frames while keeping track of the resulting
* states.

你所说的44.1应该会被处理掉的,具体的我还在看。

2013-11-06 22:40:50
想问下,vad模块的使用,为什么我的WebRtcVad_Process总是返回1,即使不说话时也是这样

2013-11-06 22:58:36
回复 Bill_Hoo: [96楼]

您好,delay的计算方法在之前的讨论中已经讨论过了。我看了下你的计算,应该还 没有计算底层 buffer 缓冲的数据 。最近我在看 APM 了,建议您参考下  audio_device_jni_android.cc 中的实现。

2013-11-06 22:59:42
回复 it206: [97楼]

您好,您给的条件好像少了点,我没办法判断返回值异常的原因。也许你的JNI实现有bug?也许VAD模式开到了最高级别?级别越高,返回1的概率越大。

2013-11-07 10:52:45
我是8000采样,pcm16,每次处理320 byte数据,局域网自环或者2台手机udp互通,情况一样,WebRtcVad_Create,VadApi_Init 都正常返回0,vad模式设置0或者最高都试过,在编码之前做静音检测,如下
    for(int i = 0; i < 2; ++i)
    {
      result = vad.VadApiValidRateAndFrameLength(8000, 160);
      System.out.println("VadApiValidRateAndFrameLength result = " + result);
      System.arraycopy(bufferSpeech, i * 160, intmp, 0, 160);
      result = vad.VadApiProcess(8000, intmp, 160);
      System.out.println("VadApiProcess result = " + result);
    }
VadApiValidRateAndFrameLength 返回0
VadApiProcess每次都返回1

感觉这样使用没有问题,但是无法检测静音

2013-11-08 23:40:35
我還有個問題想問一下:我在實驗中想處理2種迴聲:
第一種是別人的聲音進來後我不把它發出去(由我的麥克風造成的)、這種迴聲延時較短
第二種是我的聲音發出去之後又被別人echo回來、我不想聽到自己的聲音、這種迴聲延時較長

我現在可以消除第一種迴聲,但是第二種基本無法消除:請問你們有用webRTC-GIPS的這個API可以成功消除第二種迴聲的嗎?謝謝!

我現在的思路是:只要我能夠正確估算出延時、我就可以先buffer一段數據、然後在合適的時候再調用WebRtcAec_BufferFarend、如果這個思路可行的話、延時可能可以用某種算法(比如卡爾曼濾波)來自適應跟踪、不知這種思路是否可行?謝謝!

2013-11-09 03:09:53
另外請問一下:這篇論文討論的是不是我在前面說的第二種迴聲(就是我發出去的然後又返回來)?謝謝!
http://wenku.baidu.com/view/403b6739376baf1ffc4fadfc.html

2013-11-09 11:06:49
回复 米牛牛: [101楼]

您好米牛牛:
不知道你的實驗是基於PC的還是手機的?
首先我們討論的是VoIP中的聲學回聲的消除。WEBRTC的API足以保證聲學回聲的消除(只是如果延遲處理的不好,會有部份正反饋嘯叫)。
就我目前的知識水平來說,我認為你說的第一種情況不會產生、至少不會影響到正常通話的聲學回聲。麥克風造成的應該是硬件回聲吧?這跟WebRTC討論的聲學回聲應該不是一個範疇。
第二種情況,是 別人開了揚聲器外放 ,如果沒有做回聲消除,外放的聲音會被他的麥克風再次採集并發送給我們,這是聲學回聲的範疇,WebRTC是可以很好處理的。
如果你是基於PC的實驗,WebRTC上Windows端的回聲消除做得還是不錯的,他的延遲可以根據系統API直接計算出來,這個過 程WebRTC - win 的Demo中已經有了 ,可以直接參考著做。
如果你是基於手機的VoIP應用,本文提供的方法可以消除90%的回聲,剩餘的10%是因為delay計算的不准確導致的正反饋嘯叫。這一點我現在也正在努力,目前的思路是 上升一個級別的API,使用WebRTC - APM 并使用底層的OpenSLES進行音頻的播放和採集,這樣最大限度的減小延遲的誤算。

2013-11-10 16:08:07
Bill_Hoo,你好。
我现在也在做Aecm这块,我这边编译后, 在PC上测试, 一点效果都没有,用PCM文件测试,声音都变得很糟。是不是编译的时候需要打开什么宏啊?另外,你能提供一个Demo吗?
谢谢。

2013-11-11 00:41:41
楼主你好,请问如何利用 web rtc的AEC模块在本地采集音频并播放,从而验证消除回声的功能。我在使用windows 下的directshow 音频采集和 播放,在 Audio Capture 和 DSound renderer之间加入了自己写的AEC filter(内部利用Webrtc AEC来实现), 在AEC Filter内部 设置缓冲区存放之前的几个声音buffer, 同时将新来的buffer和老的buffer做回声消除。效果很差,仍然会出现回声啸叫。请问楼主这种方案可行么?问题出在什么地方?谢谢~


2013-11-11 00:44:58
楼主你好,请问使用webrtc 的回声消除功能,只需要使用它的 AEC 部分,编译成库文件即可么?还是需要整个了解设置 webrtc 的 Audio Processing 模块来使用(我看网上好多这么写的,但都没有实例)?希望楼主有时间解答一下,谢谢~

2013-11-11 12:03:08
楼主 你好!一直在关注你的回复,我现在在linux平台上测试webrtc的aecm模块,现在苦于延时的计算,我用的是alsa的接口aplay和arecord来进行放音和录音,很难计算出第一个帧从硬件放出来的时间和第一个帧被硬件采集到的时间,系统貌似没有提供合适的函数来计算这些时间,请问楼主能否给一些建议,非常感谢!

2013-11-12 02:27:53
回复 LuoZH: [104楼]


Bill_Hoo您好,

我在測試中發現2種迴聲都是存在的、具體如下:
第一種迴聲是我自己的設備產生的:即別人的聲音進到我的揚聲器後、又被我的麥克風捕獲後發給別人、這種迴聲延時較短。這時AEC的目的是不想讓別人(因為我的設別)而聽到他自己的聲音、這時farend是我收到的語音、nearend是我即將要發出的語音
第二種迴聲是別人的設備產生的:即我的聲音進到別人的揚聲器後、又被別人的麥克風捕獲後發給我、這種迴聲延時較長。這時AEC的目的是我自己不想因為別人的設備而聽到我自己的聲音、 這時farend是我即將要發出的語音、nearend是我收到的語音

我發現因為第一種情況延時較短所以AEC效果較好;而第二種情況延時較短所以AEC效果相對較差;請問您處理的是哪一種情況?您計算出來的延時大概是多少毫秒?能否告訴我一下您通常處理的這個延時數?謝謝!

2013-11-12 09:27:26
回复 米牛牛: [108楼]

您好米牛牛:
我懂到您的意思了, 您對farend和nearend的理解和我的理解不太一樣。
首先說說我的理解: 針對同一台設備,只存在一個 farend 和一個 nearend, 同一台設備上,從對方設備接收到的音頻數據為 farend(也即即將被本機播放的音頻);從本機採集到的音頻為 nearend (也即即將發送給對方的音頻)。而每一台設備僅針對自己設備上的farend和nearend進行AEC處理而 無需考慮對方( 因為對方也會在他的設備上做同樣的處理)。
綜上所述,所以我認為不存在您所說的具備兩種回聲。
不知道我這樣的理解是否有誤?您有什麽見解我們再一起討論。

其次,延遲參數在不同的設備上是不一樣的,這裡我僅有手機處理的經驗,所以就不談PC了。手機上,比如三星i9100延遲計算出來達到了150~230ms的樣子,而魅族MX延遲僅在80~120ms左右。當然i9100用的是android 2.3.1,而魅族MX是4.1.3,不過CPU處理能力也占一部份因素。延遲爲什麽會差距這麼大,就我目前的知識水品無法說清楚。

2013-11-12 09:33:52
回复 linux_aecm: [107楼]

您好:
linux上的ALSA接口我沒有测试过,我这段时间在看APM,好像针对ALSA有单独的编译选项,具体的我没有深究。这里仅说一下android上延迟的计算。android上也没有直接获取延迟的API,不过可以通过得到 底层缓冲区的大小和当前播放的位置从而估算出缓冲区造成的延迟,这一计算的具体细节我昨天刚在 WebRTC源码的android_test中找到。不知道linux上的接口是否有提供关于底层缓冲buffer大小的信息?

2013-11-12 09:37:57
回复 Sea_biscuit: [105楼]

您好Sea_biscuit:
PC上的AEC我没有涉足,不敢乱讲。不过PC上好像不用单独把AEC提取出来,因为没有CPU的限制,PC上是可以直接把WebRTC-WIN编译出来跑的。
至于您的第二个问题,在手机上,AECM模块是可以单独使用的。如果延迟计算的好,效果是可以接受的、不会产生反感情绪,但不够完美。因此近段时间我也一直在倒腾APM,现已经完成了APM整体模块的编译和测试,只是如何调优我还在倒腾中,所以也不敢乱讲。

2013-11-12 09:42:25
回复 it206: [100楼]

您好,初步看了下代码应该是没有问题的。建议您进行底层的调试以求找到问题所在。调试方法博客里有写。希望对您有所帮助。

2013-11-12 09:44:14
回复 LuoZH: [104楼]

您好,AECM是用于手机等移动设备的回声消除模块。PC您应该使用AEC才对。至于编译选项,不知道您是在PC环境下(WIN32宏)还是 在linux环境下(WEBRTC_ANDROID等宏 )编译的?

2013-11-12 12:07:10
回复 Bill_Hoo: [111楼]

楼主你好,谢谢你的回复。还有一些问题,就是你说的 PC上可以直接将 WebRTc-WIN编译出来跑是什么意思,是整个P2p的聊天系统么? 还有就是楼主能否讲一下如何将整个APM 编译使用么,是做成库还是啥?谢谢~

2013-11-12 18:18:24
回复 Sea_biscuit: [114楼]

您好,PC上WebRTC是有完整的DEMO的,只要编译出来就可以跑,然后你可以根据自己的需要进行提取。具体的我没有亲自实验过所以就不乱讲了。APM是可以直接编译成库进行使用的。

2013-11-12 20:36:11
回复 Bill_Hoo: [115楼]

楼主你好,谢谢你的回复。现在还有个问题麻烦你一下下,就是我编译出来 audioprocessing.lib 在程序中结合 audio_processing.h 使用。其中 static AudioProcessing* AudioProcessing::Create(int id);函数显示未定义还是啥
: error LNK2019: unresolved external symbol "public: static class webrtc::AudioProcessing * __stdcall webrtc::AudioProcessing::Create(int)" (?Create@AudioProcessing@webrtc@@SGPAV12@H@Z) referenced in function "public: __thiscall APM::APM(void)" (??0APM@@QAE@XZ)
就是这个。
请问楼主问题出在什么地方? 是使用方法不对还是什么,谢谢~

2013-11-12 21:20:07
回复 Sea_biscuit: [116楼]

您好,我没有编译PC上的APM,不过这个问题您可以借助GOOGLE进行分析解决,直接GOOGLE:error LNK2019:http://www.cppblog.com/longshen/archive/2010/04/02/111418.html

2013-11-12 22:46:59
回复 Bill_Hoo: [117楼]

谢谢楼主,您真是太耐心了~谢谢,问题我自己解决了。使用APM模块不光需要 audio_processing.lib还需要其他几个lib.再次感谢!
需要以下的几个lib
#pragma comment(lib, "webrtc\\audio_processing.lib")
#pragma comment(lib, "webrtc\\audio_processing_sse2.lib")
#pragma comment(lib, "webrtc\\system_wrappers.lib")
#pragma comment(lib, "webrtc\\protobuf_lite.lib")
#pragma comment(lib, "webrtc\\common_audio.lib")
#pragma comment(lib, "webrtc\\audioproc_debug_proto.lib")

2013-11-12 23:02:27
回复 Bill_Hoo: [110楼]

Bill_Hoo您好!是的一般都是處理第一種迴聲(我自己的設備產生的)、並且如果每一個設備都把自己的迴聲消除之後、就不會存在第二種迴聲了。只有當通話對方(或視頻會議中某一個或某幾個與會者)的設備沒有消除自己的迴聲時、別人才會聽到自己的迴聲(第二種)、正如您所說的這種情況比較少見、並且延時也較長不太容易處理(視不同的網絡情況、延時可能會超過1000ms)。

再次感謝您提供的手機延時數據!

2013-11-13 09:10:15
回复 米牛牛: [119楼]

您好米牛牛,您所說的第二種回聲是我沒有考慮到的。我一直假設的是每個設備都能自己把回聲處理掉,也就是說我假設參與會畫的雙方或多方都已經開啟了AEC模塊。感謝您提出的新思路。預祝您的試驗成功。

2013-11-14 10:49:31
回复 Bill_Hoo: [113楼]

Bill_Hoo你好!
你这篇文章帮我了大忙,我是在Linux下编译的,按你的方法编译了。之前播放和录音是在两个线程,然后没有正确的计算延时,所以效果很差,现在 播放和录音放在一个线程。 将buffer调整到尽量小,现在,消除效果很好。下下一步是在安卓和winCE上使用。
很感谢你们这些愿意分享知识的人,如果Lz后续能够继续完善后续的研究结果,一定会帮助许多人的。
谢谢Bill_Hoo.

2013-11-14 12:51:34
回复 LuoZH: [122楼]

您好,感谢您的反馈。如果您愿意,也可以将您在Linux上的经验写成博文分享给大家 :) 比如您提到的播放和录音在同一个线程里进行。我现在在android上仍然是两个线程,并且为计算延迟也花了不少精力,如果您的方法更有效,我想这对大家后续的开发都有好处。

2013-11-14 15:51:06
回复 Bill_Hoo: [123楼]

Bill_Hoo你好!
我这个因为场景特殊,所以才能在一个线程内播放和录音。
编译的AECM,使用了Linux的OSS接口,可以使用ioctl直接修改buffer(这个网上资料很丰富)。我是使用一个线程进行接收和发送。接收到数据后直接将数据放入一个队列,然后到另一个队列去取数据来发送。另一个线程处理录音和播放,线程首先去接收队列取数据,放入WebRtcAecm_Farend,并播放,然后立即录音,将录到的数据放入WebRtcAecm_Process进行处理,处理之后放入发送队列。你提到adnroid_test里面有对延迟的计算,但webRTC类太多层了,我找了很久也没找到这个计算,不知你能否对这部分计算的相关源码进行更多的讲解。

2013-11-14 16:20:46
回复 LuoZH: [124楼]

您好,android_test 中的延迟仍然使用的是2012年的版本,录制延迟仅仅采用了一个粗略的固定值,播放延迟的计算方法我比较赞同,也是我自己使用的方法,就是通过android AudioTrack的API估算出底层buffer缓冲的数据延迟。总的来说,仍然是尽可能严格地按照前面所说的那个延迟计算公式进行计算,计算得越严格,效果越好。

2013-11-14 18:00:48
回复 Bill_Hoo: [125楼]

你好,可否把你计算的这部分代码发给我?我的Email是LuoZhongYao@gmail.com

2013-11-15 08:53:48
回复 LuoZH: [126楼]

您好,本文仅讨论实现的思路,代码已经涉及实现细节,有所不便,都是技术人员,请谅解。 :)

2013-11-26 09:43:27
您好,看了您的博文很有启发,所以自己也弄了几个模块,编译成了.so文件。aec我也使用起来了,但现在我被两个问题所困扰:
1,我是在linux下的alsa环境下作的,所以测延时用了snd_pcm_delay函数。计算出来总共有60多毫秒,去回声效果还行,但是我把其中一台的delay设成0之后去回声效果反而还更好了些。这个就有点纳闷了,难道delay计算有错?
2,另一个问题是我在通话的过程当中偶尔会有声音忽然变小的现象出现,甚至小到听不到。然后又会恢复。我感觉有点像是回声消除的太多,把我说话的原音也消除了些。但是我把回音消除的级别降低后现象反而还更不好(我用的是aec,不是aecm,即对nlpMode调节)。

2013-12-04 20:15:39
回复 huangqizhen: [128楼]

您好 huangqizhen:
这段时间很忙没能及时回复。
A1:linux上的接口我没有使用过,不过根据 http://mailman.alsa-project.org/pipermail/alsa-devel/2008-June/008421.html 中的回复所说 —— In the driver implementation level, snd_pcm_delay() simply returns the
difference between appl_ptr and hw_ptr. It means how many samples are
ahead on the buffer from the point currently being played. 看样子 snd_pcm_delay 能够得到播放缓冲的延迟,但还差采集缓冲的所造成的延迟。至于你把delay置为0之后进行测试效果更好,我觉得这个属于硬件个别现象,固定的delay不能达到稳定的回声消除效果。

A2 忽然变小甚至小到 听不到,但是声音仍然存在?如果声音存在,只是 变小声了,是否是你使用了 AGC 且参数设置除了问题。

2013-12-04 20:18:05
回复 huangqizhen: [128楼]

这里的一些零星信息也许可以帮到你  
http://www.wavecn.com/content.php?id=198

2013-12-04 20:21:29
回复 huangqizhen: [128楼]

上文中提到“不要使用 snd_pcm_delay() 来查询播放缓冲区的填充情况。它只能用来作同步用途。”。也许这个才是你延迟计算的罪魁祸首。对于该API我没有话语权,你可以试着在网络上查找更多的信息。

2013-12-05 10:14:52
回复 Bill_Hoo: [130楼]

您好,Bill
我现在是直接使用WebRtcAec_GetDelayMetrics来获取当前延时偏差。并根据这个偏差来进行微调,效果在声音不大的时候还是比较稳定的。但是,声音一旦想要大起来就出现回音啸叫了(PS:使用电视与高灵敏度麦克风)。现在也没有使用snd_pcm_delay了。webrtc的最佳效果不知是否是这样的,我不太相信只是这样。我想是我没调节好。但是我现在也感觉没什么想法与路子了。
请教请教啊,呵呵。

2013-12-05 11:09:03
声音忽大忽小的现象现在倒是没那么明显了,我是把ns放到aec前面了。之前是ns放在aec的后面。

2013-12-05 11:12:02
另外我在audio_device_alsa_linux.cc中找到这个:
// calculate delay
        _playoutDelay = 0;
        _recordingDelay = 0;
        if (_handlePlayout)
        {
          err = LATE(snd_pcm_delay)(_handlePlayout,
            &_playoutDelay); // returned delay in frames
          if (err < 0)
          {
            // TODO(xians): Shall we call ErrorRecovery() here?
            _playoutDelay = 0;
            WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                      "playout snd_pcm_delay: %s",
                      LATE(snd_strerror)(err));
          }
        }

        err = LATE(snd_pcm_delay)(_handleRecord,
          &_recordingDelay); // returned delay in frames
        if (err < 0)
        {
          // TODO(xians): Shall we call ErrorRecovery() here?
          _recordingDelay = 0;
          WEBRTC_TRACE(kTraceError, kTraceAudioDevice, _id,
                  "capture snd_pcm_delay: %s",
                  LATE(snd_strerror)(err));
        }
所以说snd_pcm_delay也可以对录音计算延时才对的啊?



2013-12-19 19:19:11
你好,我的本科毕业设计是android webrtc的实时视频通讯系统,您能给我点思路吗,我现在不知道要先做什么

2013-12-19 21:04:07
回复 习惯在这里: [137楼]

您好,不清楚你们的毕设要求是什么,如果只是视频通讯,你只需要把WebRTC在android上整个儿编译出来就OK了。 WebRTC的Demo本身就是一个视频通讯系统 。已经编译好的Demo你可以在网上搜索得到。

2013-12-24 17:10:59
回复 Bill_Hoo: [129楼]

bill您好!
现在有个比较急的问题,就是那个声音有的时候会变小,甚至有点失真的现象。我没有使用AGC模块。现在一直找不到问题所在,基本上20个字有1个字声音会变小。很影响体验。求教大侠啊!!!

2014-01-09 11:24:24
回复 huangqizhen: [139楼]

您好,这几天没有关注博客,您提到的声音有时候会变小,是不是 AEC的等级过高导致自己的声音被当做回声一并消掉了?
我最近也遇到一个问题,为了适配各种手机,我找了一个折中的算法进行延迟的计算,但最后的效果就是折中了,双方通话时却能听到令人很不爽的唧唧声,不知你遇到这种问题没有,如何解决的呢?

2014-02-08 18:06:14
您好,bill,年前去做别的事情了,呵呵,所以没能及时关注。
1、声音变小应该是能量过低,比如说站在4米外讲话,造成语音状态检测的误判(然而检测阀值又不能太低),把较低能量的语音给消除掉了。现在经过调整好很多了,用手机的话应该是没有我这个问题的。
2、您说的唧唧声音是否身处的环境有各种机器的叫声?比如打印机,或着是在工厂里?如果是的话,我也不知道,这是一个难题,目前我也在为这个苦恼。使用低通滤波器,比如4K频率以下的通过,可以降低唧唧叫的几率。(我的采样率是16000)。
如果环境不是的话,那么我觉得还是延时的计算不对了。建议使用WebRtcAec_GetDelayMetrics去估计延时偏差。

2014-02-09 20:07:18
回复 huangqizhen: [141楼]

您好huangqizhen,我这边声音忽然变小的问题应该和能量没有关系,我都是距离手机十五公分左右测试的,这个距离比正常的视频、语音通话距离还近。这个问题我还要继续观察。
至于唧唧声,我的测试环境除了环境噪声、周围人声就没有别的异常声音了,这个 唧唧声应该是AECM算法收敛失败 造成的。
我会继续关注这几个问题的。希望大家都能早日解决。

2014-02-13 13:10:26
Bill_Hoo大神你好
我在做linphone上移植NetEQ功能,我想问您一下NetEQ是包括了解码麽?

2014-02-14 20:41:06
回复 lee_fat: [143楼]

您好lee_fat
由于项目的安排我没能接触到NetEQ模块,所以不敢妄言。

2014-02-21 14:54:17
您好!
看了你的文章,也想玩一玩WebRTC,不过死活卡在第一步下载源码...我这里网络环境不稳定,用gclient同步两天了总是会中断。你能帮忙把android上的所有源码打包发给我一下么?我的邮箱是ys.2963@gmail.com,如果太大的话网盘什么的也可以,多谢了

2014-02-26 13:13:16
回复 nicholashz: [145楼]

您好 nicholashz,直接 svn 同步 http://webrtc.googlecode.com/svn/trunk/ 主线即可。我没有花时间去同步整 android 的整体工程。

2014-02-28 10:49:10
Bill,
  您好,还是关于msInSndCardBuff这个参数的计算。
请问,您在Android中是采取何种方式去获取到音频硬件采集到帧A的时间以及播放出帧B的时间的。Android中好像没有这样的API呢,之前您说在WebRtc 源码中的android-test有那样的处理方法,恕我愚昧,找了很久也就理出个头绪来,故来向您取经求教,谢谢。

2014-03-01 14:37:41
回复 lzg9099: [147楼]

您好,这个延迟的计算方法可以参考WebRTC主线版本目录  webrtc\modules\audio_device\android\java\src\org\webrtc\voiceengine\
下的WebRTCAudioTrack和WebRTCAudioRecord

具体如何在程序中体现,还需要你自己进行程序结构的调优和测试。

2014-03-18 16:22:09
hello,Bill,我认真的看了前面的所有评论,还是有几个问题想要请教您
1.采集的时候audioRecord.read(tempBuf, 0, buffer_frame_size),tempBuf用short类型和byte类型有区别吗?80HZ 采集,如果用byte类型,buffer_frame_size是传递160吧?如果是short类型,是传递320?
因为我看到前面的很多人都是用short传递到process中的,所以有点蒙,不懂该怎么做才是合适的
2.jni层,aecm process函数像下面这样写有错吗?AudioRecode采集的时候是用byte类型存储的,我直接传递给nearendNoisy,WebRtcAecm_Process需要传递的类型是short*,所以我把jbyte*类型转换成short*,不知道有没有错?还有nrOfSamples传递的是160(80HZ的)?
jint....Process(JNIEnv *env,
           jclass jclazz, jint aecmInst,jbyteArray nearendNoisy,jbyteArray nearendClean,jbyteArray out,jint nrOfSamples,jint msInSndCardBuf) {
     void* handle = (void*) aecmInst;
     jbyte* nn = (*env)->GetByteArrayElements(env, nearendNoisy, NULL);
     jbyte* nc = (*env)->GetByteArrayElements(env, nearendClean, NULL);
     jbyte* sout = (*env)->GetByteArrayElements(env, out, NULL);

     jint result = 0;
     if(nearendClean==NULL)
           result= WebRtcAecm_Process(handle,(short *)nn,NULL,(short *)sout,nrOfSamples,msInSndCardBuf);
     else
           result= WebRtcAecm_Process(handle,(short *)nn,(short *)nc,(short *)sout,nrOfSamples,msInSndCardBuf);

     if (nn != NULL)
           (*env)->ReleaseByteArrayElements(env, nearendNoisy, nn, 0);
     if (nc != NULL)
                 (*env)->ReleaseByteArrayElements(env, nearendClean, nc, 0);
     if (sout != NULL)
                 (*env)->ReleaseByteArrayElements(env, out , sout, 0);
     return result;
}
3.关于降噪,如果是80HZ,160字节采集的话是要分成两次降噪吗,每次传递80byte?
4.目前我 播放和采集都在一个线程中执行,发送和接收在另一个线程 ,播放的时候去接收队列中取出数据,执行bufferFarend,然后采集,降噪,process,再静音检测,不知道这样的流程是否可行?

初次接触音频部分研究,还望多指点,感谢

2014-03-18 16:53:30
bill,您好:
关于那个延迟的计算,根据您的提示,我这边测试了一下,发现达不了预期的效果呢。我是将采集和播放放在一个线程里面来测试的,代码入下,不知道这样处理是不是正确的:
       while(iswork){
             long timeRecBegin = System.currentTimeMillis();
             rcdLen = rcdAudio.read(rcdBuff, 0, AUDIO_SAMPLE_LEN);
             long timeRecEnds = System.currentTimeMillis();
             Log.i(TAG, "Read one audio frame " + rcdLen);      
             if(AudioRecord.ERROR_INVALID_OPERATION == rcdLen) {
                   Log.i(TAG, "Read wrong audio data.");
                   continue;
             }
             //对采集到的音频进行噪音消除。
             m_audioNs.audioNsProcess(nsHandlerAddress, rcdBuff, null, ns_processed_out, null);
             
             //对噪音消除后的音频进行回音消除
             timeRecDelay = (short) (System.currentTimeMillis() -( timeRecBegin + ((timeRecEnds - timeRecBegin) * bufferedRecSamples)/80));
             processed_out = m_aecm.aecProcess(ns_processed_out, out, nrOfSamples, (short) (timeRecDelay+timePlayDelay));
             Log.i("TEST", "aecmProgress msInSndCardBuff is " + (timeRecDelay + timePlayDelay));
   
             int written = 0;
             long timePlayBegin = System.currentTimeMillis();
             written = trkAudio.write(processed_out, 0, processed_out.length);
             long timePlayEnds = System.currentTimeMillis();
             Log.i(TAG, "write one audio frame.....");
             if (processed_out.length != written){
                   Log.i(TAG, "write wrong audio data.");
                   continue ;
             }
             bufferedPlaySamples = countBufferedPlaySamples(written);
             Log.i("TEST","bufferedPlaySamples is " + bufferedPlaySamples);
             
             timePlayDelay = (short) (System.currentTimeMillis() - (timePlayBegin + ((timePlayEnds - timePlayBegin)* bufferedPlaySamples)/80));
             m_aecm.aecmBufferFarend(processed_out, nrOfSamples);
             Log.i("TEST","timePlayDelay is " + timePlayDelay);
       }
countBufferedPlaySamples这个函数是我用来计算播放时的延迟的:
public int countBufferedPlaySamples(int written){
       bufferedPlaySamples += (written >> 1);
       int pos = trkAudio. getPlaybackHeadPosition ();
       if (pos < playPosition){
             playPosition = 0;
       }
       bufferedPlaySamples -= (pos - playPosition);
       playPosition = pos;
       return bufferedPlaySamples;
希望您有空时候,能再给点建议,不胜感激。

2014-03-19 12:35:36
hello,Bill,我现在回声是消除了,但是声音开到最大声有时候还是有噪声和啸叫,我是80HZ,160byte采集的,执行顺序是: 回声消除-->噪音消除-->静音检测(如果是静音,就不发送) ,请问这样的流程对吗?
代码实例如下:
//frame_size : 160
short[] nsout=new short[frame_size];
                       short[] aecmout=new short[frame_size];      
                       short[] temp1=new short[frame_size/2];
                       short[] temp2=new short[frame_size/2];
//recData 为接收到的数据,结构short[160]
WebrtcAECM.Process(recData, null, aecmout, frame_size, 200); //回声消除                                    
                                   
                                   //噪声消除分成两段,一次80个字节
                                   System.arraycopy(aecmout, 0, temp1, 0, frame_size/2);                                    
                                   WebrtcNs.NSProcess(temp1, null, temp2, null); //噪音消除                                    
                                   System.arraycopy(temp2, 0, nsout, 0, frame_size/2);
                                   
                                   System.arraycopy(aecmout, frame_size/2, temp1, 0, frame_size/2);      
                                   WebrtcNs.NSProcess(temp1, null, temp2, null); //噪音消除                                    
                                   System.arraycopy(temp2, 0, nsout, frame_size/2, frame_size/2);
                                   
                                   int vadFlag=WebrtcVAD.process(sampleRate, nsout, frame_size); //静音检测,如果是静音就不发送

延迟时间目前暂时用固定值200,会影响到噪声消除吗?
感谢~

2014-03-20 11:18:52
回复 lzg9099: [150楼]

您好lzg9099,AudioRecord 以及 AudioTrack 的接口是阻塞式的,很可能影响了延迟的计算。
对于最终的效果,我从发布这篇文章到目前为止基本上都在做优化,虽然相比最初的版本有较大改善,但不论是单独使用AECM还是使用集成APM以及其他WebRTC组件,均达不到我预期的100%满意的效果。目前正在等待Google进一步的优化。之前看了下WebRTC的计划,貌似会废弃AECM模块,改用其他实现方式。

2014-03-20 11:29:50
回复 appleZYY: [151楼]

您好appleZYY,您说的采样率应该是8000Hz单声道吧。对应为160字节是没错的。执行顺序是可以自己调优的。 建议参考WebRTC AudioPreprocessing 模块里面的 ProcessStream 的实现顺序。 不过,有时间可以直接把APM用起来。
固定的延迟得不到较好的效果,对于同一台手机,可能由于线程调度、Audio API 阻塞等不可控因素导致延迟发生变化。对于不同手机,延迟基本上是不同的。
关于啸叫:回声消除算法未收敛成功,会导致最后一个尾音产生反馈啸叫。这种啸叫可以通过优化程序结构和延迟计算算法尽量的压制,但无法100%消除。

2014-03-20 12:27:19
回复 Bill_Hoo: [153楼]

hello,bill,感谢您的回复
我在stackoverflow里面有看到您的回复:
”2.AudioRecord.read() and AudioTrack.write() sometimes block(due to minimized buffer size), so when you calc the delay, don't forget adding blocking time to it.
3.the buffer of AudioRecord and AudioTrack also increases the total delay. so add it.“

read和write的block time是算调用read前后的时间差吗?我有测试过,采集160byte, 是0~60ms不等;
the buffer of AudioRecord and AudioTrack 这个我不知道该怎么算,可否指点一二?
我感觉好像都得不到真正的read和write第一帧的时间

2014-03-20 12:35:53
回复 appleZYY: [154楼]

您好,根据目前 android 的音频API确实得不到精确的底层第一帧的时间,但是 AudioTrack有API可以估算底层还有多少数据没有播放,这个可以参考下楼上lzg9099的代码 。至于AudioRecord,目前只能想办法少让它阻塞。
SO上面的回复已经是很早的事情了,该描述现在看来不甚准确,可以忽略。

2014-03-20 14:56:21
回复 Bill_Hoo: [152楼]

Bill,感谢您的回复。
按照我之前的估算延迟方法,我测试的时候有时候会发现延迟竟然是负数,这个问题真心困扰我了,怎么会是负数呢?而关于播放端的bufferedPlaySamples,在小米3上经常是在1千多,而在华为的手机上就只有六七百左右;另外,关于audioRecord的AudioSource参数, 我发现如果是设为VOICE_COMMUNICATION的话,有的手机刚开始时根本不用我们这个aecm也能够消除回音,但也会有啸叫声,这个时候是用到了手机自带的回音消除功能吗?而当设为MIC的话,我们的这个aecm显然是起作用了的 ,但是效果在不同手机上差别很大,而且即使消除了回音的手机上依然会存在唧唧唧唧的声音。

2014-03-20 16:09:14
回复 lzg9099: [156楼]

您好 lzg9099
A1:关于延迟是负数的情况,问题很有可能出在播放端延迟的计算上,因为你的 bufferedPlaySamples -= (pos - playPosition); 很有可能越界。这只是猜测,因为我之前自己也犯过这个毛病。
A2:bufferedPlaySamples 是随硬件实现而变的,底层buffer有多大,不同机型有很大差别。我在一台华为上,通过API得到的底层最小buffer尽然达到了7000+字节。
A3:VOICE_COMMUNICATION 是 Android API Level 11 之后才加入的新特性,它要求硬件厂商在API 11之后,对这个采集流实现 硬件回声消除。这几种模式的详细情况可以参照 CSipSimple 的开源代码。更换模式不失为一种有效的解决办法,但是不同的硬件厂商对该标准的支持很不一样, 尤其是三星系列,很多机型根本没有对API进行支持,甚至有选择该模式后直接卡死的情况。
A4:“唧唧”声也是我目前觉得比较棘手的问题。之前一直在对系统延迟的计算以及程序结构进行优化,能够不断减少这种声音的发生。但仍然无法完全消除。主要有两点原因:1)要在各种android机型上找到一个普遍适用的系统延迟算法确实比较困难,就WebRTC自身而言,其延迟计算也是估算的。2)即使系统延迟估算准确,手机上的回声消除模块本身存在其局限性,最终导致“唧唧”声的出现。
针对原因1,可以对您自己的算法不断的调优,对程序结构和线程调度不断调优,确实可以达到一定效果,甚至和QQ语音相差无几,但是QQ语音实际上也做得不是很好,相差YY语音很大一截。而我们希望要达到YY语音的效果,光从AECM模块入手恐怕是不行的,这个愿景在目前看来有点牵强。
针对原因2,我目前也卡在这里,时刻关注Google对AECM模块的优化或者方案变更。 AECM算法简介可以参考 http://blog.csdn.net/u012931018/article/details/17045077

2014-03-20 16:55:07
回复 Bill_Hoo: [157楼]

Bill,太感谢您的宝贵建议了。
再请教您一个问题,关于语音采集那儿一块儿的延迟,即bufferedRecSamples,您那边也是按照WebRtcAudioRecord里面的粗略估计值吗,即sampleRate / 200 ?

2014-03-20 17:00:06
回复 lzg9099: [158楼]

采集延迟这一块我目前也没有定下来,之前尝试了很多方法,始终不尽人意, 目前采用一半底层buffer大小作为采集的固定延迟。 如果您有新的发现,还请来此反馈。感谢。

2014-03-25 17:22:01
回复 Bill_Hoo: [159楼]

bill,我这边采集延迟也是用的固定的buffer = SAMPLERATE / 200 ; 看了一下算出来的msInSndCardBuff 在100左右到300左右,偶尔会高出500,大部分是在200 到 300 之间。
测试发现:当说话时,是不会听到自己的回音了,但是呢,随着时间的增长有一个“唧唧”声音,从很小很小变得越来越大,这种现象是每次都会出现的,运行一会儿就出现了,这是因为回音消除算法没有收敛成功吗?不知道您那边遇到过这种问题没有,如果遇到了,您处理的思路是如何的呢?感谢。

2014-03-25 21:22:24
回复 lzg9099: [160楼]

您好lzg9099,这个延迟算出来基本是稳定的,我没有遇到过超出500的情况。也许你的结构设计上哪里还有些问题。至于唧唧声,我这边也有,目前正在解决。不过,如果延迟计算准确,唧唧声不会每次都出现。总体来说,在一个准确的延迟范围内,AECM的效果会趋于平稳,这种状态下仍然可能出现唧唧声。这个声音应该是爆破音,你google一下  pop/clicking sound  应该有所收获,如何以程序的方式去除这个 clicking sound,还要再仔细研究。

2014-03-26 11:50:47
回复 Bill_Hoo: [161楼]

Bill,感谢您的回复,问一下您那边稳定后的范围是多少呢,我刚调试了一下,不会出现500以上的了,但是一直在40左右到200左右直接波动。

2014-03-26 18:10:29
回复 Bill_Hoo: [161楼]

Bill,采集延迟如果通过底层buffer大小的一半作为固定延迟的话,可能会有问题,我小米2s和小米3的手机的这个底层buffer是在1000多左右,这个时候算出来的msInSndBuffer会稳定下来,在200左右,但是当我换成一个华为的荣耀3后,这个buffer竟然为 7680 ,如果根据这个来算出采集端的延迟的话,msInSndBuffer稳定在 900多 左右,而webRtc源码里面, msInSndCardBuffer只适用于0--500之间 ,大于了500就无法处理了。我这边目前先暂时设定了一个值240,当msInSndCardBuffer大于500就直接设为240,但是这个时候的效果就没动态算出来的效果好。

2014-03-27 10:10:12
回复 lzg9099: [163楼]

您好,我这边华为底层buffer也是7000+,但是根据你算出来的延迟900多,我猜测你是否使用了双声道32KHz的采样率? 如果是单声道16KHz的话,7000bytes对应的延迟也才200多毫秒。 是否哪里计算错了呢?

2014-03-27 10:35:54
回复 Bill_Hoo: [164楼]

bill,您好,我这边是使用的8k,单声道。底层buffer 7000+ 的话,recBuffer 固定为 3500+ 个Samples,我是这样理解的,8个samples采集需1ms,那么3500 / 8 = 400+ ,这样的话光是采集就400多的延迟。按照这种算法,我在另外两个手机上测试的时候,即小米2s和小米3上测试,底层buffer为1200左右,算出来msInSndBuff就稳定在200左右,效果很好,唧唧的声音很小很小了。不知道您那边采集端是如何根据这个buffer计算延迟的。

2014-03-31 16:51:43
回复 lzg9099: [165楼]

您好,计算方法都是一样的。我使用的16KHz数据,所以延迟是你的一半左右。

2014-04-03 17:53:20
回复 Bill_Hoo: [166楼]

bill,你好,不知道你有没有解决唧唧的声音,还有音质也不太好

2014-04-10 15:10:00
hi, 请问 windows phone 是否也可以单独提取出来编译呢
或者说有没有试过编译webrtc给 win phone 使用
刚开始接触webrtc,发现和win ph 相关的信息太少了

2014-05-08 15:58:34
hi,bill,首先感谢你这片博客。我按照你的提示,单独提取出来AECM模块。目前在验证AECM的正常工作有点问题:
我是在Android上层做录音播放工作:
    初始化:  
    int aecmHandler;
     aecmHandler = mWebrtc.createAecm();
    int initAecmResult = mWebrtc.initAecm(aecmHandler, sampleRateInHz);
    验证:
    int frame_size=160;
    int sampleRateInHz=8000;
    private short[] recordedShorts = new short[frame_size];
    shortsRead = audioRecord.read(recordedShorts, 0,frame_size);
     mWebrtc.bufferFarend(aecmHandler, recordedShorts,sampleRateInHz);
     mWebrtc.processAecm(aecmHandler, recordedShorts, null,aecmOutFrame, frame_size, 0);
    audioTrack.write(aecmOutFrame, 0, frame_size);

如果AECM是正常的,这样验证,是不是应该没有声音?我是采用死循环,可是会出现很大噪音,应该是啸叫。AECM是没正常?还是我的验证有问题?谢谢指教



2014-05-08 19:49:05
回复 controler2013: [168楼]

您好,抱歉很久没回博客。
WinPhone我没有接触过,是说一下我了解的信息吧,WebRTC底层是C/C++的实现,Android上也是要用JNI去调用底层实现的。如果WinPhone没有这个能力调用C/C++实现,那这个事情可能就没办法。

2014-05-08 19:50:47
回复 HulkChen: [169楼]

理论上讲, 将同一音频放入Farend和Process,输出就是全零的。 你觉得你最好记录音频文件看一下问题出在哪里。

2014-05-09 18:10:08
回复 Bill_Hoo: [171楼]

谢谢bloger的解答,我单独编译AEC等几个模块,WINCE下面效果非常理想,Android下面我用OpenSLES接口,一直存在偶尔串回音,效果很差,后来我发现 Android已经自带libwebrtc_audio_preprocessing.so,使用这个so,效果非常好, 但是OpenSLES的延迟达450ms,十分可怕.

2014-05-10 16:47:20
bill你好,最近我也在搞webrtc的回声消除,看到你的文章对我的帮助很大。在看代码的过程中我有一些问题想请教一下你
1.aecm_core.c代码过程中屡次提到的Q-domain是什么意思?只是一种定点表示规范?
例:// @param aecm       [i/o]   Handle of the AECM instance.
// @param far_spectrum [in]   Pointer to farend spectrum.
// @param far_q     [in]   Q-domain of farend spectrum.
// @param nearEner   [in]   Near end energy for current block in
//                     Q(aecm->dfaQDomain).
// @param echoEst     [out]   Estimated echo in Q(xfa_q+RESOLUTION_CHANNEL16).
//
void WebRtcAecm_CalcEnergies(AecmCore_t * aecm,const WebRtc_UWord16* far_spectrum,const WebRtc_Word16 far_q,const WebRtc_UWord32 nearEner,WebRtc_Word32 * echoEst)
这当中的Pointer to farend spectrum.和 Q-domain of farend spectrum都是与远端有关的,怎么读都读不懂?

2.是不是加上噪声抑制一定会对噪声处理产生良好效果,我对此也进行了测试,感觉加在后面效果比在前面好,这是为什么呢?按照aecm中process函数的理解,当中既然为ns后的语音流留了接口,难道不是应该放在前面效果更好吗?

3.你对完成以后的唧唧声或啸叫声是怎样理解的呢?从理论上分析,当输入为小的噪声时,webrtc本身代码处理过程有可能自激产生啸叫吗?亦或是时延精度对这一现象造成的影响?webrtc中是否只有远近端对齐时的这一种时延要求,是否里面仍有不同类型的时延计算需求,时延一面看的糊里糊涂的。

问题问的有点多,麻烦你了,最近被这些问题困扰很久,非常感谢!

2014-05-12 09:15:05
回复 LuoZH: [173楼]

你好LuoZH,很高兴你能来此反馈,有几个问题向你请教一下:
Q1 不知道你提到的效果非常好是怎么个好法,还存在唧唧声吗?
Q2 libwebrtc_audio_preprocessing.so 是你用WebRTC编译出来的,还是其他什么地方得来的?
Q3 上述库的接口有头文件可以参考么?

2014-05-12 10:10:32
回复 shifu0803: [174楼]

您好shifu0803:
A1:对于 Q-domain 我也没能很好的理解,不过看 AECM 的头文件,应该用了一个叫做  DYNAMIC-Q 的优化算法。
A2:这几个独立模块的顺序可以参照 APM 里面的实现,如果没有特别的要求需要单独编译各个模块,其实可以直接把APM模块编译出来使用,参数的配置,接口的统一都方便很多。
A3:啸叫声应该是延迟没有计算准确导致的,最开始我也以为是AECM自己有问题,后来不断优化延迟的计算,啸叫声是不存在的。唧唧声按我现有的知识没法作出理论上的描述,我只能说一下我的感受,我觉得这个唧唧声很像本次回声消除后残留的一点点声音,这个声音没被AECM抵消掉,被播放了出来。
A4:只是按我的理解,这里的延迟有两个。我用 sys_delay 和 filter_delay 加以区分。sys_delay 是我们上层计算出来传给AECM接口的系统延迟,表示音频在android系统上的延迟值,一般为100~200ms。sys_delay 主要用于AECM里远端buffer 偏移量的计算,它被AECM拿去以20%的权重(另80%为旧值,以求延迟稳定)重新计算新的远端历史buffer偏移量。 filter_delay 用于表示AECM的滤波器偏移量,这个偏移量通过 delay_estimator 得到计算。最后拿去给滤波算法使用的是 filter_delay,但如果我们的 sys_delay 计算失误,会导致 delay_estimator 始终得不到一个正确的 filter_delay,所以上层计算延迟是需要注意和优化的。
针对AECM延迟的计算,更加详细的解释请参考这位博主的文章:
http://blog.csdn.net/u012931018/article/details/17045077
打开源码对比着理解就很容易了。
我音频专业知识不足,说错的地方还希望大家帮我指出来,谢谢。

2014-05-15 18:29:35
回复 Bill_Hoo: [172楼]

  非常感谢Bill的回答。将同一音频放入Farend和Process,讲话或者放歌的声音都没有了,但不是完全静音状态,还是有平静的小小的嗡嗡声,我是单独提取AECM及NS模块处理的,这代表AECM模块工作正常?
  另外想问下Bill,单独用了AECM,NS模块,如果严格计算延时参数,能达到的效果会是怎样的?我这边确实没有了没用AECM前,双向通话一打开,直接啸叫的现象。但是双方听到的声音都是有失真,断续的感觉,而且失真有点严重。我是一个线程将接收远的数据存入队列,另一个线程取队列数据播放及采集麦克风数据压缩传输。这是延时参数计算不是非常准确引起的还是我在AECM模块的提取存在问题?
  AGC模块利用上,对音质效果会有提示?AGC模块是利用gain_control.h里面的函数?因为对一些类似WebRtcAgc_Process函数参数利用存在一些疑惑。

2014-05-15 20:08:24
回复 HulkChen: [177楼]

您好 HulkChen,
A1:AECM在相同文件作为NearEnd和Farend输入时,基本全零就可以了,这只是拿文件来验证你的JNI是否有误。
A2:我不清楚你说的声音失真是什么,被削掉了一半,还是忽大忽小。
A3:延迟值计算准确的话,在每一台机型上都该是一个针对该机型的稳定值(上下波动不大),比如我这边魅族MX延迟稳定在90~100ms,三星9100延迟稳定在134ms上下,你可以记录你的延迟,如果不稳定,说明是有问题的,具体问题需要结合你的代码和设计自己查找。更多关于这个延迟的说明,请参见文前更新内容。
A4:断断续续的感觉可能跟你的队列设计有很大关系,你的队列由于在两个线程间共享,可能需要上锁,或者使用自带同步的队列数据结构,很可能会导致播放线程不能及时播放。这个需要你自己去确认。
A5:AGC模块我没有单独使用,在写此文后不久,我就整体编译APM出来跑了,AGC是在APM里写好的,所以单独对AGC的使用我没有经验,不敢乱说。

另,建议大家讲APM整体编译出来使用,条件允许的,请直接使用VOE,独立编译各个模块出来使用是我很早之前的做法了,不再推荐。

2014-05-23 13:24:32
回复 Bill_Hoo: [176楼]

Q1,效果已经达到产品级别,没有唧唧声,偶尔串一点回音
Q2 ,so在Android自带,在/system/lib/目录下
Q3,这个库 == audio_process,跟手动编译的使用是相同接口.头文件也是使用手动编译时的

2014-05-23 15:28:22
回复 LuoZH: [179楼]

恭喜恭喜,没有唧唧声是很不错的表现了。
你们测试了哪些机型呢?在没有 开启android硬件回声消除 的前提下也是这个效果吗?

2014-05-23 15:36:23
回复 LuoZH: [179楼]

对了LZY,你提及的动态库是从哪个版本的Android里导出的?   我现在的软件结构没办法直接用这个库了,不过我想自己建个工程去试试看。

2014-05-26 10:19:48
回复 Bill_Hoo: [181楼]

4.0.2

2014-05-30 17:45:28
解决延时问题,把aecm_defines.h里面的MAX_DELAY调大一点。啸叫问题尝试把CNG关掉试试看还有没有啸叫。msInSndCardBuf这个参数不要乱设。 和AGC配合时特别要注意AGC别把残余回声给放大了。

2014-05-30 20:17:11
回复 DarkEnergy: [183楼]

您好DarkEnergy,感谢,
MAX_DELAY 是底层历史远端数据的buffer大小,我之前有试着调大,但没有发现明显的改善。
CNG我在测试时发现它会出异常,AECM可能出现异常的噪声,很大声。所以一直关了的。
msInSndCardBuf 这个参数我尝试过定值,定值的时候,一旦出现残余回声(应该是AECM收敛失败)就很难再次收敛(或收敛速度慢),但如果我每次都更新一个计算后的值,在出现残余回声后,能够很快的将滤波器偏移修正回来。所以我自己觉得,这个值还是要算。
最后,我很感兴趣您提到的AGC将残余回声放大的问题。因为这是一个客观事实,残余回声会和正常语音一起被AGC放大。目前我的拙劣的解决办法是 降低AGC的增益级别 ,从而降低其对残余回声的影响。

所以很想请教您,如何避免AGC将残余回声放大?调整AGC在APM里面的执行顺序么?希望能得到您的指点。

2014-05-31 15:12:10
楼主,你好
最新刚刚一个项目需要,需要做回音消除。
1.目前模块都已编译工作,我在同一个线程里先将远端数据设为全0,结果发现Aecm process后的声音听起来有些问题,没有以前清楚,而且感觉上有变声?
2.另外虽说是8000HZ的,但我这边是512 short的长度来发送接收的。现在只有切开80 short长度的。
想问下,在一个线程连续调用WebRtcAecm_BufferFarend之后,再另外一个线程连续调用几次WebRtcAecm_Process,这样是不是就没有效果了?是不是一次BufferFarend,只对下一次的_Process有效?
3.因为在一个线程里做WebRtcAecm_BufferFarend,另外一个线程中做WebRtcAecm_Process,是否需要加什么锁之类的啊?

2014-06-03 09:53:52
回复 Bill_Hoo: [184楼]

AGC的问题需要修改代码,最好把AECM里面回声抵消的VAD判决拿几个参数出来,加入到你的AGC里面,相当于AGC要判断远端是否有声音,远端单端有声音的话就不作放大,但是这一块还是要和延时结合起来,因为回声是滞后的。这个也不难弄,因为你近端是有VAD的,而且AECM里面是能够判断出双端的。MAX_DELAY这个参数很重要,AECM是在MAX_DELAY这个长度范围内去寻找远端与近端最匹配的位置来做延时估计。要是做移动端的应用,参考和回声有时延时很大的。比如三星的某些手机可以达到600ms,如果MAX_DELAY小于延时,AECM做延时估计就永远返回错误的值了。AECM非常依赖延时检测,因为它做回声抵消的方法是将远端参考和回声对齐,然后计算HNL通过维纳滤波器抑制回声。还有一种可能情况是,MAX_DELAY刚好超过延时,但超过得不多,当延时出现抖动时(延时边长且超过MAX_DELAY),这个时候就没法再次收敛了,所以你修改msInSndCardBuf是能够解决问题的,但是是不是解决了根本,还得再研究研究,看是不是这种可能性



2014-06-03 11:00:23
回复 DarkEnergy: [187楼]

嗯,谢谢您花时间来此指点,AGC的问题我会参考您的建议自己下去试验的。
另,我看了下您说的关于MAX_DELAY和msInSndCardBuf的问题,这个就真得再研究研究了,说明我底层源码还没有吃透,还要花时间。

2014-06-03 21:06:17
回复 xubingnihao: [185楼]

您好 xubingnihao:
A1:不好意思我没有看懂这个问题,还请再组织一下语言。
A2: 这两个API是可以异步调用的,你可以在两个线程里分别调用两个API,没有问题。
A3:锁只是在有资源竞争的情况下加,我不清楚你的程序结构。光从这两个API来看,上层没有加锁的必要。

2014-06-04 11:21:21
您好Bill_Hoo:
  我也遇到186楼朋友的类似问题:
  1、我单独编译了aec模块,并封装提供了两个接口:1、添加远端数据,2、添加近端数据。然而把远端数据添加进去后做了混音处理后变成了异步数据,我想问下:把混音后的远端数据添加到WebRtcAecm_BufferFarend函数的操作是否一定要先于把近端数据添加到WebRtcAecm_Process函数的操作。

2014-06-04 14:31:49
回复 xdwanmei: [190楼]

您好,请参见 #190 的回复。两API的详细描述请参见 audio_processing.h 头文件中的官方说明, 两接口没有强制的先后顺序。
另,本文开头已不再建议独立编译各个子模块出来使用,请知晓。

2014-06-04 16:45:24
回复 Bill_Hoo: [191楼]

您好Bill_Hoo:
  由于某些原因,暂时只能单独使用所有,还有些问题想请教下你:
  在使用WebRtcAecm_BufferFarend函数时,远端数据是否要进行软件混音(注:远端的数据是采集到的数据)?

2014-06-04 17:56:23
回复 xdwanmei: [192楼]

您好,farend 数据指你从网络接收到的对端音频,nearend 才是指你本地采集到的音频数据。

2014-06-29 11:52:54
这样好麻烦啊,有没有整理出来的aecm模块?

2014-07-01 17:45:04
回复 huangqizhen: [132楼]

你好,我现在也在linux上用aec,也有你说的那个声音忽大忽小的问题,很苦恼,请问你是怎么调整的,盼指点,还有aec模块效率很低,在arm9上处理一帧10ms的音频需要4ms,不知道你遇到过这个问题没,是做汇编优化了吗

2014-07-03 11:22:40
bill你好,我现在在linux上用aec模块,遇到的问题就是算法效率很低,处理一帧10ms的音频16k采样率的话需要将近10ms,不知道你遇到这个问题没,是否需要做汇编优化或者 浮点转定点?

2014-07-03 19:22:27
回复 wangyaliss: [196楼]

Linux上我没有使用经验,不过Android上如果使用原始的AEC(PC),处理效率就是你这个样子。正常。

2014-07-04 15:48:42
回复 Bill_Hoo: [197楼]

bill谢谢你的回复,另外我还遇到另外一个问题,就是处理后的声音忽大忽小。像是去回声的时候过于敏感,把远端声音去除的同时把近端声音也去除了一部分,你遇到过这个问题吗

2014-07-04 16:15:56
回复 Bill_Hoo: [197楼]

我用aec模块和aecm模块都有上面说的那个声音忽大忽小的问题

2014-07-08 16:42:55
回复 wangyaliss: [198楼]

一般不会存在忽大的问题,仅有忽小的问题。忽小是因为AECM不支持双边通话,一旦出现双边通话的情况,AECM就会进行强制压制,导致本地处理出来的声音变形,减弱,对方听上去就忽小了。
至于AEC,我没有在android上使用,PC上不存在这个问题。

2014-07-10 17:07:46
bill你好,现在我在研究安卓的回声消除部分,不知道你对安卓4.1版本后 自带的回声消除模块 有没有研究, 就是把webrtc的回声消除部分加进了安卓系统。
我现在遇到的问题是加载完该模块后在开始的一段时间回声消除会起作用,但是在过几分钟后感觉回声消除的功能失效了,回声变的很清晰,而且无法恢复原有的无回声状态,
不知道你对此是否有研究?或者你认为会引起该状况的原因会是什么呢?
谢谢。

2014-07-14 19:19:35
回复 shifu0803: [201楼]

您好 shifu0803:
我没有使用自带的回声消除器的经验,仅仅在之前测试时 开启过一次,效果很糟,之后就没有使用过。
你提到的回声特别清晰,是否是一个回声之后就没有了?因为如果回声消除不起作用的话,你的通话会产生无限的回声反馈,到最后除了啸叫声,其他是听不清的。
还有一个,你的测试环境是怎样的?是否两台手机在两个房间(保证本地说话不会被另一方的麦克风直接采集到)?

2014-07-18 17:19:54
回复 Bill_Hoo: [202楼]

bill谢谢你的回复~
我提到的回声很糟的意思的确是一个回声之后就没有了,回声很清晰,但是并没有出现无限的回声反馈这种情况。
主要让我特别不理解的是开始的一段时间并没有这种情况,但是打了一会电话后就会出现清晰的回声。
我的测试环境可以保证一方说话不会被另一方采集到,一个在屋内一个在屋外。
不知道bill你觉得引起这种现象的原因大概会是什么的?
再次感谢~

2014-07-23 08:55:08
回复 shifu0803: [203楼]

如果没有出现无限的回声反馈,只是偶然出现一个清晰的回声,这个现象是可以存在的。
我听其他朋友说自带的EC底层也是WebRTC里的AECM算法。AECM出现偶尔的回声是正常的。

2014-08-14 10:28:47
你好 Bill:
  我想问下aec的那个delay该怎么算,我看了下提供的那个公司,这几个时间点都是什么时候标记出来,我是用了aec后回声变小了,是不是因为我delay设置的原因(我给了一个常值50ms)。

2014-08-14 16:12:32
你好 Bill:
我把问题又梳理了下:
A1:我想问下aec的那个delay该怎么算,我看了下提供的那个公式,这几个时间点都是什么时候标记出来,apm处理近端和远端数据函数每次只能接收一定的数据,t_pull 和t_analyze是每次调用函数前更新一次吗?
A2:如果使用了aec,过一会对方就听不到自己说话了,不使用aec对方就可以听到自己说话,这中现象有碰到过吗,有人说是delay的问题(delay我设了一个常值)?

2014-08-15 09:20:45
回复 xdwanmei: [206楼]

您好 xdwanmei:
A1:这几个时间点的含义源码注释已经写得比较清楚了,评论列表里也有好多网友提及过,你可以再看看。确实需要每次都更新。
A2:我没有实际测试过AEC(PC),我只使用过AECM,对于过一会就听不到说话了,那那个时候是什么声音?持续啸叫还是完全无声音 ?delay在AEC(PC)里面是需要动态更新的,常量值仅适用于AECM。

还有问题我们再讨论。

2014-08-15 10:47:58
Bill 你好:
  A1:我看了下你们的讨论,但是实际的操作让我有点困惑,比如:我采集的一帧数据9600个,但是apm每次处理的数据是远远小于一帧数据量的,我在采集这帧数据是记录一个时间,在每次调用process时都记录一个process时间点,去跟我这一帧采集时间点作比较吗?注:我看了下webrtc自带的一套音频引擎,看了下它里面只设置了播放和采集的delay,没有计算加入AnalyzeReverseStream 和ProcessStream的时间,是不是采集后或者播放前不做其他处理直接调用函数,这两个时间可以忽略。
  A2:是没有任何声音了,感觉要是把所有声音都消掉了。
麻烦您了。

2014-09-02 17:46:50
Bill 你好:
  可以邮件沟通下,2802716485@qq.com

2014-09-09 09:22:10
回复 xdwanmei: [208楼]

您好 xdwanmei,
这段时间较忙没来看博客,不好意思。
A1:如果在 Android 上,不使用OpenSLES的话,一般用API AudioRecord,一次读取10ms对应采样率的采样点数,如8kHz 80个Sample,16kHz 160个sample。由于Android的音频采集API没有提供能够精确计算延迟的信息,因此可参照WebRTC的粗略实现, 使用一个固定值作为采集延迟。 播放延迟可以通过 AudioTrack 的API getPlaybackHeadPosition[http://developer.android.com/reference/android/media/AudioTrack.html#getPlaybackHeadPosition()] 进行估算,估算当前底层播放缓冲中 还有多少采样点没有被硬件渲染,这些采样点就造成了主要的播放延迟。

A2:这个问题你光这么说我也不是很清楚,你可以说一下细节之类的。不过,我看你采集出来的一帧9600,不知道你这个一帧是指什么,10ms?AECM最高支持16kHz的回声压制,AEC我不熟悉,好像是48kHZ,超过了都会被重采样的。

2014-10-09 15:00:50
你好,Bill_Hoo.我有些问题想请教你的。
我在做aecm时,延时时间msInSndCardBuf都是给个固定值的(如100ms,250ms,490ms等等)。 效果是可以消除回音了 ,但是手机一直都有杂音(不知道是不是你说的唧唧音,不说话也一直有,而msInSndCardBuf都从0到490作为固定值一个一个的测还是一直有哪些唧唧音)。
1.msInSndCardBuf是不是一定时间要改变,才会减小唧唧音。但我看你说msInSndCardBuf是一个比较稳定值,那设为一个固定值应该也可以消除部分的唧唧音,但是我做的aecm一直都是开启就有唧唧音了。

2014-10-09 15:17:02
hi,Bill_Hoo.在185楼说的CNG关掉的CNG是什么来的。是WebRtcAecm_set_config函数参数的AecmConfig的echoMode吗

2014-10-09 16:48:26
hi,Bill_Hoo.我在做aecm计算的msInSndCardBuf时,做
long time = System.currentTimeMillis();
mHead.BufferFarend(outBytes, AudioRecordThread.SAMPLE);
mAudoiTrack.write(outBytes, 0, outBytes.length);
int playtime = System.currentTimeMillis()-time;
得到的playtime在0到2ms之间,我怎么都觉得不对啊!这播放延时间一般是多少?

2014-10-09 18:06:06
hi,Bill_Hoo.我是做aecm的用了2个线程,一个是播放线程一个是采集线程。这2个线程要不要什么同步的条件。现在这2个线程是播放线程WebRtcAecm_BufferFarend()m个,再到采集线程WebRtcAecm_Process()n个,每次m和n都不同的。这样是对的吗

2014-10-10 21:46:59
回复 keke274233971: [215楼]

您好,关于这个延迟值, AECM 模块使用定值是标准的做法,因为 AECM 内部有自己的延迟估算机制。
如果一定要计算这个 delay 值,其计算方法在 audio_processing.h 头文件中有详细的描述,仔细阅读便可 :)

2014-10-11 15:32:00
回复 Bill_Hoo: [217楼]

感谢Bill_Hoo,我是用了定值做的,只是运行时一直有唧唧声(音量开得大时)。我只用了噪音处理,没有用静音处理。是不是静音没有处理产生的?说话时唧唧声会变小或者消失。请求帮我分析原因,谢谢

2014-10-14 17:44:40
博主你好,谢谢分享。我现在也在用aecm来降噪读取的buffer大小是320(char数组),在一些手机比如小米2s上面效果很好,但是其他手机比如Nexus5上面效果很差,后来发现录音的 回掉 不均匀,不管是用opensles还是java的录音回掉的周期都不均匀,N5的Native Buffer 大小是960,我现在读取的是320,返回不均匀,但是小米的Natvie Bufer是320回掉就很均匀。
我看你在之前在googlecode上面也问过类似问题 https://code.google.com/p/android/issues/detail?id=53996
后来你是怎么解决的?
我自己在代码中加了定时器强制均匀时间但是效果扔没有改善。
这种情况要如何处理,谢谢!

2014-10-18 01:30:30
Hi, 我看webrtc 源码, 发现 msInSndCardBuf 似乎与所谓二进制延迟估算器无关, 而是用来计算 acem->knowndelay 用的, 这个 knowndelay 用来从 aecm->farbuf 定位具体字节偏移;具体来说, 就是没调用一次 process, 则从 farendbuf 缓冲区读对应的若干帧到 acem->farbuf, 这个不由任何 delay 控制, 就是顺序读;这个 farbuf 的最大大小是 PART_LEN4, 即 4 个 PART_LEN 大小, 或者说 256 字节, 而后, 再从这个 farbuf 里面读一个一个 part 来做信号处理;

aecm 跟踪一个名为 farBufReadPos 的变量, 其含义就是上次已经从 farbuf 里面读到了哪个字节; 经 msInSndCardBuf 计算出来的 knwndelay 即在这里发生作用, 具体来说, 就是 farBufReadPos - kowndelay 作为新的 farBufReadPos;   而二进制延迟估算器是在处理 part 时计算的, 这部分代码还没看, 但该 part 是 间接由 msInSndCardBuf 选定的;即msInSndCardBuf决定那部分数据作为参考帧, 选取完毕后进行的一系列计算, 包括二进制延迟估算器, 基本没msInSndCardBuf什么事情了;但我还没搞明白那个估算器是干什么用的,  

整体来看, 似乎是如下模型: webrtc 认为硬件足够可信, 即 8000 采样率的 8000 个点硬件一定是在 1 秒内播放或者捕获完毕;因此只要数据始终供应充足, 那么理论上。 若保持 farendbuf 大小正好是 api 头文件说明的那个值, 则每捕获一帧数据, 则farend 中的参考帧必然是 farendbuf 中 readpos 指向的那一帧; 即在一切理想的情况下, kowndelay 应该始终为 0;  

但这个函数却给我莫名其妙的感觉了:
static int WebRtcAecm_EstBufDelay(aecmob_t *aecm, short msInSndCardBuf)
{
  short delayNew, nSampSndCard;
  short nSampFar = (short) WebRtc_available_read(aecm->farendBuf);
  short diff;

  nSampSndCard = msInSndCardBuf * kSampMsNb * aecm->aecmCore->mult;

  delayNew = nSampSndCard - nSampFar;

  if (delayNew < FRAME_LEN)
  {
    WebRtc_MoveReadPtr(aecm->farendBuf, FRAME_LEN);
    delayNew += FRAME_LEN;
  }

  aecm->filtDelay = WEBRTC_SPL_MAX(0, (8 * aecm->filtDelay + 2 * delayNew) / 10);

  diff = aecm->filtDelay - aecm->knownDelay;
  if (diff > 224)
  {
    if (aecm->lastDelayDiff < 96)
    {
        aecm->timeForDelayChange = 0;
    } else
    {
        aecm->timeForDelayChange++;
    }
  } else if (diff < 96 && aecm->knownDelay > 0)
  {
    if (aecm->lastDelayDiff > 224)
    {
        aecm->timeForDelayChange = 0;
    } else
    {
        aecm->timeForDelayChange++;
    }
  } else
  {
    aecm->timeForDelayChange = 0;
  }
  aecm->lastDelayDiff = diff;

  if (aecm->timeForDelayChange > 25)
  {
    aecm->knownDelay = WEBRTC_SPL_MAX((int)aecm->filtDelay - 160, 0);
  }
  return 0;
}

按我理解,   delayNew = nSampSndCard - nSampFar; 若 nSampFar == nSampSndCard, 则是我所说的最理想情况, 此时计算出的 delayNew 为 0,  

若 没有
  if (delayNew < FRAME_LEN)
  {
    WebRtc_MoveReadPtr(aecm->farendBuf, FRAME_LEN);
    delayNew += FRAME_LEN;
  }
那么, 一切还是很完美, knowndelay 最终计算出 0, 然后把剩下的全交给硬件;

但 这个判断是什么意思呢, 为何要加上 FRAME_LEN, 若这么搞, 则最终意味者 farBufReadPos 将减去某个值, 而这个值初始化为 0, 减去的结果就是 0 + 256 - kowndelay, 那么, 第一个被分析的 part 的参考则可能根本就不是 farendbuf 中, 而是初始化时 memset 的 0;  

另外, nSampFar 也不等于 nSampSndCard, webrtc 启动阶段在 farendbuf 填充到 nSampSndCard * 3 / 4 时就结束了, 那么, nSampFar 小于 nSampSndCard 的可能性很大;

最终的效果, 就是这个算法弄了一系列无法摸清规律的因子进去, 实在搞不懂那是为什么。

2014-10-20 22:07:52
一直在跟看楼主的帖子,楼主好人。一直回答超过1年了。
我们现在也在用AECM做回音消除,现在发现几款手机的表现不一致。华为和小米表现较好,三星和Nexus表现不行,对比文件一直有一些尾音消不掉。
请教了一些人,有人说可能是时延和抖动不稳定的问题,好像类似220楼的兄弟问的那样?
也有说还是参数的配置问题?
楼主和各位兄弟有没有见过这种现象?多谢了。
急需一条明路。。。

2014-10-28 11:34:11
回复 keke274233971: [218楼]

Re[219]: 你好,近段时间没有跟进WebRTC的进度了,所以仅根据以前的经验回答,叽叽声应当是 AECM 算法本身的表现, 静音检测打开,在静音时发送全零数据,可以减少些许能量较弱的唧唧声。

2014-10-28 11:35:46
回复 642759382: [219楼]

您好,据我所知目前 Google 的该音频回调 API 是存在 bug 的,你 不能依靠这个回调 去保证你的采集时间。我采用的是 AudioRecord.read() 这个阻塞 API。

2014-10-28 11:38:00
回复 zylthinking: [220楼]

您好,这个函数我也看了很久,最后我以为,它仅仅作为稳定延迟,防止延迟突变的工具函数。仅个人理解。

2014-10-28 11:43:31
回复 阿弱德一号: [221楼]

您好,不同 Android 机型的表现不一是正常现象,这取决于其音频底层实现。
如果使用的是 AECM 模块,有些许尾音消不掉(或者出现扭曲的唧唧声)是正常现象,据我所知 Google 前日已经关闭 [issue 1067][https://code.google.com/p/webrtc/issues/detail?id=1067],表示不对此进行修复。

2014-11-12 14:21:36
可以請問一下AGC的使用方法嗎?
int32_t mic_level_out = 0;
int32_t mic_level_in = 0;
uint8_t sat;
WebRtcAgc_VirtualMic(iAGC, (int16_t*)pcm, NULL, 160, 0, &mic_level_in);
if(WebRtcAgc_Process(iAGC, (const int16_t*)pcm, NULL, 160, (int16_t*)agc_buffer, NULL, mic_level_in, &mic_level_out, 0, &sat) == -1)  
  QLOGE("webRTC_audio::PinIn AGC process ERR");


用這種寫法是正確的嗎?每個frame得到的mic_level_in和mic_level_out都是相同的

2014-11-14 08:58:26
回复 lance7: [226楼]

您好 lance7,AGC我之前单独提取出来使用过,效果不好,最后使用的是整体的 APM 模块 ,它自己已经架设好了这些独立模块之间的衔接。 本文开头已经提到过,不再提倡单独编译AGC、NS等模块进行处理。

2014-11-14 10:40:22
回复 Bill_Hoo: [22楼]

你好,就是用webrtc测试时,两个客户端啸叫的很厉害,这个怎么解决?有没有好的方法

2014-11-18 10:16:13
回复 guowei19862006: [228楼]

你好,两个客户端啸叫的很厉害,首先应当排除距离问题,两个客户端是否靠得太近导致双方均能直接采集到对方发出的声音?

2014-11-21 19:29:12
Bill,你好。我最近在做手机和网页视频通话的项目。在我目前在android手机端的语音模块用了webrtc,PC端没有加webrtc。通过实验,ns去噪现在效果显著,但是aecm去回声没有效果。我在PC端用耳机说话,可以听到自己的回声。这个回声应该是到达手机端,喇叭放出后再通过麦克风录入传回来的。我把手机端的喇叭静音,PC端再讲话就听不到自己声音了。aecm我尝试了各种delay值,没有通过计算。看到不少人在这里评论,写死数值也应该有一点效果的。webrtc去回音去的是那一部分,是不是我在PC端也要加上webrtc的消回音,这样我PC端才不会听到自己讲话的回声?期待你的帮助。

2014-11-25 15:50:51
Bill,你好。
我在github上看到了你共享出来的关于aecm的工程(https://github.com/lhc180/webrtc-based-android-aecm),我把他加入到了我的工程中,但是消回音几部手机测试下来普遍不理想,回应还在。我测试的方式是PC端讲话,通过流媒体发往手机端。如果我在PC端听不到自己的回声,那么就是去回声成功了。以三星S5为例,我采集的是8KHZ单声道,播放延迟和采集延迟总和是50ms以内,AudioTrack和AudioRecord通过getMinBufferSize()得到的buffer大小之和是1920,算下来延迟是120ms,msInSndCardBuf在120-170之间波动,但是回音还在,看到前面好多人评论说回音消掉了,可我却没消掉,希望Bill能帮助一下

2014-11-25 18:13:19
回复 zhu4272: [231楼]

你好,GitHub上的工程我已经移除了,你看到的应该是别人fork的分支,文章开头已经提到,不再建议单独使用AECM等模块,请使用APM或者VOE,它们能使底层的独立音频模块更好的协作。
看到你说回声消除没有效果,我看你的测试环境估计没有问题。AECM的两大接口是否有用对?在其头文件中有对接口的详细说明。

2014-12-08 15:53:38
楼主,您好。我想问你一下,webrtc中的自动增益控制(agc)是用来干什么的?

2014-12-17 16:26:38
回复 Bill_Hoo: [17楼]

t_analyze 表示你对音频帧A调用 farend 的时刻,t_render 表示[硬件]真正播放出帧A的时刻。
t_capture 表示[硬件]采集到音频帧B(注意跟A没关系了)的时刻,t_pull 表示帧B被传入 Process的时刻。
----------

看了以上讨论受益匪浅,有几点疑惑,希望能指点一下。

t_analyze 表示你对音频帧A调用 farend 的时刻:
这里是指调用WebRtcAecm_BufferFarend()的时刻吗,还是请求系统播放(AudioTrack.write)的时刻?
----------

t_render 表示[硬件]真正播放出帧A的时刻:
这里是指硬件开始播放帧A的时刻还是播放完帧A的时刻?如何得到这个时间?
如果是用AudioTrack的话,使用那个setMarkerPosition吗?
----------

t_capture 表示[硬件]采集到音频帧B(注意跟A没关系了)的时刻:
这个应该是AudioRecord.read返回时的时刻吧?还是用这个时刻减去所读buffer的size对应的时间?
----------

t_pull 表示帧B被传入 Process的时刻:
此处t_pull是指公式中的t_process吧
----------

另外AECM可以直接用多路进来的声音进行去回声吗 还是必须要先把多路合为一路以作为参考 ?
不胜感激

2015-02-12 19:35:33
楼主,我参照你的方法编译了agc模块,但是在调用agc的WebRtcAgc_Process函数时 有点疑问,
int WMWebRtcAgc_Process(void* agcInst,
              const int16_t* const* inNear,
              int16_t num_bands,
              int16_t samples,
              int16_t* const* out,
              int32_t inMicLevel,
              int32_t* outMicLevel,
              int16_t echo,
              uint8_t* saturationWarning);
不知道inNear要传入怎样的参数,看源码似乎要调用AudioBuffer的函数去获取按频率分段的一个数组,我现在只有一个buffer,单声道的,怎么去转换成这样一个数组

2015-02-15 13:05:24
回复 fingerplay: [235楼]

您好,前面已经说过,不再建议单独使用各种模块去进行音频处理,本文目前参考价值较小。

2015-04-11 16:20:02
楼主,您好。

非常感谢你的文章。我编译了单独的ns, 确实可以去掉一些背景噪音。但是同时说话的声音也变低了,这个可以理解。但是说话的声音也模糊不清楚了,还需要什么处理么。

多谢回复。

2015-04-24 21:26:59
Bill_Hoo,你好,我近期把webrtc的音频模块单独拿出来编译,是在ARM+LINUX平台下,音频驱动是OSS,扬声器那端我是接了个耳机, 当我说话的时候 我把耳机放到麦克风旁 产生很强的啸叫,这是延迟计算错误的原因吗?

2015-04-27 15:09:49
回复 Bill_Hoo: [17楼]

你说的(注意跟A没关系了)是什么意思?难道是不需要包含音频a, 可以是播放a之前就录好的音频帧吗?

2015-04-28 20:17:40
回复 Bill_Hoo: [236楼]

我先在回音消除, 发现有时候能够消除一些,比如连续说话,有些回音是可以消除的。但是不能够完全消除,大牛,有啥高见没?

2015-04-29 08:37:26
回复 zengwh513: [238楼]

没有明白你说的把耳机放到麦克风旁什么意思,让麦克风再次采集耳机里播放出来的声音?

2015-04-29 09:15:53
回复 Bill_Hoo: [241楼]

是的,这样是不是一定会产生啸叫

2015-07-13 17:23:59
"t_analyze 表示你对音频帧A调用 farend 的时刻,t_render 表示[硬件]真正播放出帧A的时刻。
t_capture 表示[硬件]采集到音频帧B(注意跟A没关系了)的时刻,t_pull 表示帧B被传入 Process的时刻。"

楼主可否将这4个值进行量化?我是指这几个值在具体实现时的数学表达式。

ps: 例如Android中 t_render 和t_capture 时怎么得到的?貌似上层不能获取到硬件的播放/录音时刻吧?

不推荐单独编译  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次唧唧声。二是通话过程中声音会忽大忽小,目前我是怀疑由AECMdouble 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项目对应目录中找到需要的文件。

  

前言

   最近一直在捣腾如何在androidiOS上使用GoogleWebRTC——一个无疑大力推动了互联网即时通信以及VoIP发展的开源项目。(elesos注:连谷歌都访问不了的国家,活该落后!)

   WebRTC提供一套音频处理引擎VOE,但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   (注: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认为静音的

音频全部置零然后发送。


出处http://billhoo.blog.51cto.com/2337751/1213801



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



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


[4楼]      [匿名]WebRtc_Aecm  回复
2013-06-26 19:54:24
BillHoo你好, 我是从StackOverflow追到这里来的。有一个问题不知道楼主有没有遇到过: WebRtc Aecm_Process 在去掉回声的同时把与回声重叠的那部分的原声也去掉了。

还有一个问题啊,麦克风 采集的PCM 的buffer是作为WebRtcAecm_Process的第二个参数吗?不胜感激。


[5楼]楼主      Bill_Hoo  回复
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即可。


[6楼]      [匿名]WebRtc_Aecm  回复
2013-06-27 09:54:30
回复 Bill_Hoo: [5楼]



多谢你的回复!

回声和原声重叠的意思是回声和原声同一时刻进入到麦克风,我将它称之为“重叠”

回环路径测试是怎样的场景和配置呢?

我现在的测试环境是这样搭的: 从手机SD卡中以80字节为单位顺序读取一个pcm文件,送入到 扬声器,同时调用WebRtcAecm_BufferFarend() , 然后读取麦克风采集到的数据的80字节,调用Process。再将out写入到aec.pcm文件。分析这个aec.pcm文件时,发现回声是被消除了,但是与回声处于同一时刻的原声也被消除了。

我这种测试思路有问题吗?

还有一个问题,降噪与否对回声抑制的效果影响大吗?

多谢 :)


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



1.首先,我在stackoverflow里看到你调用时 传的是8000Hz的采样率,也就是说你每次应该传入80个采样点,(编者注:10ms数据需要传80,1000ms即1s为8000,所以10ms对应80*2=160字节)一个采样点是2个字节,你应该采集160字节/次才对。

注:  http://stackoverflow.com/questions/17319574/audio-is-also-be-cancelled-which-overlap-with-acoustic-echo-when-using-webrtc-ae/17333797#17333797

2. 对于AECM的测试 ,首先可以使用同一个PCM文件,分别放入FAREND和PROCESS,如果出来的结果接近全零,则验证你提取的AECM模块工作正常。

3.在验证AECM模块能够正常工作的前提下,你需要两台设备,每台设备两个线程,线程一用来采集和PROCESS,线程二用来播放和FAREND。

4.降噪与否对回声消除的效果:我的实验结果为: 先消回声再降噪效果 比先降噪再消回声好。


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



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


[9楼]      [匿名]WebRtcAecm  回复
2013-06-27 13:17:45
回复 Bill_Hoo: [8楼]



对于第1点,我也测试过传160字节,效果和80字节是差不多的。

对于第2点,结果会接近全零,但是在本地也有声音进入麦克风的情况下,与回声重叠的那部分原声也会被消掉(预期应该是不会被消掉的,这和你的回环路径测试不一样,因为进入麦克风的"Normal Voice"没有再次进入扬声器,也就算不上是回声,而我猜你的回环路径测试是会再次被扬声器播放出来吧?)。

这就是我遇到的最主要问题。AECM是可以工作的,但是结果没有达到预期。我看了你的StackOverflow的回复,这一点应该是和时延没有关系的。


[10楼]楼主      Bill_Hoo  回复
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是一个道理。

如果需要做回声测试,你需要两台设备,如我上一个回复所说。

希望对你有所帮助。


[11楼]      [匿名]WebRtcAecm  回复
2013-06-27 14:40:17
回复 Bill_Hoo: [10楼]



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


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



也就是说你的问题不在AECM这个模块上,而是你的测试思路。

你的问题就在于 —— 你的测试方法测出来本来就应该是这个结果,而你的预期却错误地认为会是另外一个结果。

搞清什么是“原声”,什么是“回声”就OK了。


Elesos.com注:他的音乐其实还是送到扬声器的声音A吧,是回声。是需要消除的。


[13楼]      [匿名]WebRtcAecm  回复
2013-06-27 14:59:41
回复 Bill_Hoo: [12楼]



预期:"AECM的处理结果就是从“A+B”中找到被扬声器播放出去的“A”并进行消除,留下了“B”"

现在的结果是: B没有被完全留下,有一部分被消掉了。你假设我没有说话,实际我放了音乐并且被麦克风采集到了啊, 这个音乐不是从扬声器出来的 ,这不算回声吗?我觉得这个音乐应该是被保留的吧?现在被部分消掉了。


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


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



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





回复 Bill_Hoo: [15楼]

注: http://stackoverflow.com/questions/15302339/webrtc-aec-algorithm/16182942#16182942

多谢你的回复 :)
最后一个问题,那个时延是怎么计算的? 我看了你在StackOverflow上的发言,根据AudioProcessing.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().

1). 你是根据这个公式来计算的吗?
2). 这4个值都是什么意思啊?这段话我看了很多次了,总是看不懂。

2013-06-27 22:13:30
回复 WebRtcAecm: [16楼]

你好,是根据这个来计算,而且需要严格根据这个的描述来
t_analyze 表示你对音频帧A调用 farend 的时刻,t_render 表示[硬件]真正播放出帧A的时刻。
t_capture 表示[硬件]采集到音频帧B(注意跟A没关系了)的时刻,t_pull 表示帧B被传入 Process的时刻。
其实就是计算系统硬件buffer + 软件 buffer 的总延迟。
这个延迟在android系统中比较大, 有100~200左右不等, 需要你根据自己的buffer进行计算。计算方法就跟你android上层的buffer设计相关了。





2013-07-01 14:30:12
问下楼主,
我参照楼主的做法,第二步中AECM已经可以了。
2.对于AECM的测试,首先可以使用同一个PCM文件,分别放入FAREND和PROCESS,如果出来的结果接近全零,则验证你提取的AECM模块工作正常。

现在我的项目遇到的问题是,本机测试是可以的,但是双机通讯下,会有非常大的噪声,是不是AECM的msInSndCardBuf的参数错了。这个到底该怎么计算呢?
顺便问个小白的问题,在双机通信中,我认为从网络上接收的音频为远端音频,用WebRtcAecm_BufferFarend,本地录音的音频为近端,用process。这个没错吧。还有,我并没有用NS,AGC和其他模块,自用了AECM。这样可以吗?请楼主点播下,多谢。我的QQ:553270954

2013-07-01 18:08:35
回复 cstriker1407: [21楼]

你好:
1.非常大的噪声:你确定那是噪声而不是啸叫么? 我这边AECM局域网通信没有你说的很大的噪声,只是之前由于delay计算错误会产生很大的啸叫。delay的计算公式在audio_procession.h里面说的很清楚, 那个延迟你只能根据自己的buffer实现进行计算。
2.远端数据就是网络传过来的数据,近端就是本机采集到的数据。
3.只用AECM模块是可以的。

2013-07-03 14:31:59
Bill_Hoo:
你好:
  我现在在做回音消除这块,但是不能准备计算delay的时间,虽然audio_precession.h头文件中的公式说得很清楚,但是对于android平台的java层始终不能很好的计算出delay的时间,所以想 请教一下你在android上层设计buffer并计算出delay时间的解决办法。

2013-07-03 14:39:21
回复 Bill_Hoo: [22楼]

多谢楼主回复,问题(注: 双机通讯下,会有非常大的噪声)我已经发现并解决了。不是webrtc的问题。

2013-07-03 21:08:27
回复 51CTO游客: [23楼]

你好,如果可能, 尽量不要在java层做AECM ,java到android底层中间还夹着jni,据说还有一个AudioFlinger的延迟,这个我也没有深入,所以不敢乱讲。如果你的项目不是必须在java层做音频的采集和播放,那么AECM还是放在底层做的好。
如果非要在java层做,delay的计算只有根据你自己的buffer设计来, 这个我没办法帮上忙, 总体思路就是系统底层硬件延迟 + 你的buffer延迟。

2013-08-01 14:25:30
您好 我也在做webrtc的AEC 请问AudioRecord和AudioTrack如何的阻塞时间如何计算

long timestamp = System.nanoTime();
mAudioTrack.write(data, 0, data.length);
long renderTime = System.nanoTime() - timestamp;
native _set_audioRenderBlockTime(renderTime);

请问这样做能得到准确的阻塞时间嘛

2013-08-02 15:14:00
请问楼上,AECM、VAD、AGC三个模块使用的顺序是怎样?如果VAD检测出是静音模块,那么AECM该如何调用啊?

2013-08-02 15:33:47
回复 hsfhzkd: [27楼]

VAD检测出是静音,你可以采取以下两种方式:1.不发送这一段静音的音频 2.将这一段VAD认为静音的音频全部置零然后发送。

2013-08-02 15:34:46
回复 w33222885: [26楼]

你好,你这样做可以 得到 write 的阻塞时间,但这个时间并不是 renderTime (如果你的这个变量表示 渲染延迟 的话)。

2013-08-05 10:01:54
回复 Bill_Hoo: [29楼]

您好 请问渲染时间是一个固定的延迟吗,是否跟 audioflinger的lantency 有关。



2013-08-05 13:28:25
回复 w33222885: [30楼]

据我的实验结果,该延迟并不是固定的,需要周期性更新。我当时看源码时,AudioFlinger层的latency没有办法在上层获取,所以没有考虑这个。不过不知道现在4.1、4.2的源码发生了怎样的变化,是否有类似 iOS的直接获取底层硬件延迟的接口我也不清楚。

2013-08-05 13:47:52
回复 Bill_Hoo: [31楼]

现在aecm已经可以工作,但是最后的效果始终有嘶嘶的杂音,不知道您最终的效果如何~

2013-08-06 13:14:27
回复 w33222885: [32楼]

单独提取的AECM模块达不到完美的效果,但也不会始终存在嘶嘶声。也许你可以配合NS和VAD模块一起使用。

2013-08-21 02:21:29
你好Bill Hoo,我对NS 模块的 WebRtcNs_Process 这个函数有点疑问。int  WebRtcNs_Process (NsHandle* NS_inst, short* spframe, short* spframe_H,short* outframe, short* outframe_H)。 最后面四个参数 是指什么?我不太懂. noise_suppresion.h 里面说 buffer for L band, buffer for H band。  L band 和 H band 是什么? ? 多谢了。

2013-08-21 10:15:18
回复 51CTO游客: [34楼]

可以参考下 http://what-when-how.com/voip/wideband-voice-voip/。
以及维基 http://en.wikipedia.org/wiki/H_band
      http://en.wikipedia.org/wiki/L_band

L band  is the 1 to 2  GHz  range of the  radio spectrum .


2013-08-21 22:51:03
回复 Bill_Hoo: [35楼]

多谢,楼主。我的audio input format 是
PCM signed (wav file format)
16 bit encoding  
8000 Hz sample rate
Mono (1 channel)
Big endian format
但是参考资料都是说把16khz 的audio  用qmf 分