整体框图

参考信号:送往时延估计、AEC的状态计算
采集信号:送往时延估计、线性滤波器、AEC的状态计算
线性滤波器使用时延对齐后的参考信号和采集信号进行处理;线性滤波器处理后的结果送往NLP模块,最终得到处理结果。

类调用关系图

EchoCanceller3(入口)

AnalyzeRender

该部分只是将AudioBuffer类型的参考信号insert到render_transfer_queue_,这是一个swapqueue,数据从结尾insert,从开头remove,这里没有什么可以说的【当然insert之前会经过一个高通滤波器,滤除甚低频部分】。

ProcessCapture

核心处理过程

近端采集数据的处理主要分为以下几步:
  1. 设置固定的时延。如果config_里面有delay的数据(config_.delay.fixed_capture_delay_samples),则对capture设置时延(block_delay_buffer_.DelaySignal(capture))。
  2. 取出参考信号队列里面的数据,存入render_buffer_。【下面介绍】
  3. ProcessCaptureFrameContent处理,具体处理什么需要进一步梳理确定。该函数执行的两遍,差别在于第五个参数,第一个为0,第二个为1。该函数的参数列表如下:
变量名
数据类型
数据含义
linear_output
AudioBuffer*
线性处理部分的输出
capture
AudioBuffer*
近端采集信号
level_change
bool
level是否发生变化【即近端路径改变】
saturated_microphone_signal
bool
近端是否爆音
sub_frame_index
size_t
第几个子帧(两遍处理的区别在此)
capture_blocker
FrameBlocker*
暂不清楚作用。类型说明:Class for producing 64 sample multiband blocks from frames consisting of 2 subframes of 80 samples
linear_output_framer
BlockFramer*
暂不清楚作用。类型说明:Class for producing frames consisting of 2 subframes of 80 samples each from 64 sample blocks. The class is designed to work together with the FrameBlocker class which performs the reverse conversion. Used together with that, this class produces output frames are the same rate as frames are received by the FrameBlocker class. Note that the internal buffers will overrun if any other rate of packets insertion is used.
output_framer
BlockFramer*
同上
block_processor
BlockProcessor*
块处理的实例,核心处理内容
linear_output_block
std::vector<std::vector<std::vector<float>>>*
暂不确定作用
linear_output_sub_frame_view
std::vector<std::vector<rtc::ArrayView<float>>>*
同上
capture_block
std::vector<std::vector<std::vector<float>>>*
同上
capture_sub_frame_view
std::vector<std::vector<rtc::ArrayView<float>>>*
同上

EmptyRenderQueue

该函数的作用是将AnalyzeRender里面insert到swapqueue中的参考buffer remove出来存储到render_buffer_中。

BufferRenderFrameContent做两遍,实际上是分别将两个sub_frame_view(80个点为一个subframe)的数据存入sub_frame_view,然后将sub_frame_view数据存入2维的vector数组block【语句为render_blocker->InsertSubFrameAndExtractBlock(*sub_frame_view, block),它是从多频带帧中插入一个 80 采样的多频带子帧,并提取一个 64 采样的多频带块。下面执行本语句的相同】,然后去执行BlockProcessor的insert函数,将远端参考数据存入到render_buffer_。该函数的参数列表如下:
变量名
数据类型
数据含义
render_frame
std::vector<std::vector<std::vector<float>>>*
render_queue_output_frame_
sub_frame_index
size_t
第0或1个子帧
render_blocker
FrameBlocker*
用于将sub_frame_view数据存入2维的vector数组block
block_processor
BlockProcessor*
块处理的实例,核心处理内容
block
std::vector<std::vector<std::vector<float>>>*
用于插入render_buffer_的block
sub_frame_view
std::vector<std::vector<rtc::ArrayView<float>>>*
子帧
BufferRemainingRenderFrameContent执行了render_blocker->ExtractBlock(block)和block_processor->BufferRender(*block),与BufferRenderFrameContent作用类似,但为何多这样一个步骤,还需要进一步确定

ProcessCaptureFrameContent

该函数的作用是将AudioBuffer类型的capture以及linear_output与std::vector<std::vector<rtc::ArrayView<float>>>*类型的子帧subframeview建立起地址间的绑定关系。然后拿着subframeview去转成std::vector<std::vector<std::vector<float>>>*类型的block,然后进行AEC处理后,再将block转为subframe。这样不需要再更新capture或linear_output就可以获取处理后的结果。

ProcessRemainingCaptureFrameContent

ProcessRemainingCaptureFrameContent的操作与ProcessCaptureFrameContent类似。但为何多这样一个步骤,还需要进一步确定
至此,EchoCanceller3这个类主体内容以及分析完毕。后续操作就交给了BlockProcessor这个类来进行操作了。

BlockProcessorImpl::ProcessCapture

该函数被ProcessCaptureFrameContent和ProcessRemainingCaptureFrameContent调用。参数列表如下:
变量名
数据类型
数据含义
echo_path_gain_change
bool
回声路径是否发生改变
capture_signal_saturation
bool
是否为爆音信号
linear_output
std::vector<std::vector<std::vector<float>>>*
线性输出的block
capture_block
std::vector<std::vector<std::vector<float>>>*
近端输入的block
用到的结构体:
struct EchoPathVariability {
enum class DelayAdjustment {//delay_change的一个状态
kNone,//初始状态
kBufferReadjustment,//buffer重新调整
kBufferFlush,//刷新了buffer
kDelayReset,//delay被reset
kNewDetectedDelay//新检测到延迟
};
EchoPathVariability(bool gain_change,
DelayAdjustment delay_change,
bool clock_drift);
bool AudioPathChanged() const {
return gain_change || delay_change != DelayAdjustment::kNone;
}
bool gain_change;
DelayAdjustment delay_change;
bool clock_drift;
};
如果接收参考信号帧已经开始(render_properly_started_==true):说明需要做AEC,则接下来判断采集开始状态是否为false(capture_properly_started_==false),若为false,则代表AEC的采集帧还没过来,就将capture_properly_started_置为true,render_buffer_给清空,delay_controller_给reset掉;若为true,说明之前AEC就开始了,则继续进行就好了。若render_properly_started_==false,说明远端没有参考信号,则不需要做AEC,直接return就好。
新建一个EchoPathVariability类型的echo_path_variability,用echo_path_gain_change、EchoPathVariability::DelayAdjustment::kNone(delay_change的初始状态)和false来初始化。
接下来就是看render_buffer有没有满(如果满了的话,在BufferRender会将render_event_置为kRenderOverrun),如果render_buffer满了且render_properly_started_是true(肯定是有参考信号才行),则需要将render_buffer清空、delay_controller_清空,这个时候需要将echo_path_variability的delay_change置为kBufferFlush,代表刷新了。无论render_buffer有没有满,之后都要将render_event_置为kNone。
再接下来就是对render_buffer进行预处理得到一个buffer_event(PrepareCaptureProcessing)。若buffer_event为underrun,则代表render_buffer空了,则需要将delay_controller_reset掉,传入false。否则的话就继续往下进行。
然后根据config判断是否有额外输入的delay数据,如果有,则直接根据额外的delay数据进行对齐(render_buffer_->AlignFromExternalDelay());否则的话,使用内建的GetDelay()函数得到estimated_delay_,然后对齐参考信号和输入采集信号,如果delay发生改变,则将echo_path_variability.delay_change修改为kNewDetectedDelay。
然后就是最关键的一步,根据有没有使用内建的getdelay()得到时延(has_delay_estimator)或者 render_buffer_->HasReceivedBufferDelay()判断是否进行核心的回声消除,若两者有一个,则执行(echo_remover_->ProcessCapture()),否则不执行。
最后更新下metrics(metrics_.UpdateCapture(false);)。
这里面最关键是的使用匹配滤波器得到时延的GetDelay()和进行回声消除的echo_remover_->ProcessCapture()。接下来细细讲来。
以上是第一部分内容,接下来将是第二部分内容,在下一篇文章更新。由于博客格式问题较多,想更好的学习或者有什么问题,欢迎到飞书文档进行浏览和留言。点击这里:飞书文档链接: https://qouwscohey.feishu.cn/docs/doccnUcSGDg8ehjisuw1ww5KmGi,访问密码可通过关注我的公众号,并回复“AEC3”获得。