WebRTC AEC回声消除算法拆解

WebRTC AEC算法流程分析——时延估计(一)

其实,网上有很多类似资料,各个大厂研发不同应用场景设备的音频工程师基本都对其进行了拆解,有些闪烁其词,有些却很深奥,笔者随着对WebRTC了解的深入,逐渐感觉其算法构思巧夺天工,算法逻辑的设计也采用了很多技巧,这就导致没有一定的C语言基础,很难理解其精妙之处,笔者精读完之后不禁让拍案叫绝,作为交流,本系列文章有感而记之,并为来者谏。(笔者C代码水平有限,难免所有错误)


文章目录

  • WebRTC AEC算法流程分析——时延估计(一)
  • 前言
  • 一、far_end 远端信号的处理
  • 二、时延估计
    • 2.程序分析
    • 3.参数说明


前言

WebRTC AEC主要分为三大部分:延时估计,频域分块NLMS滤波器和非线性滤波,三部分数据处理相互关联,不可分割,且每一部分对整个语音信号处理来说至关重要,本篇文章主要针对时延估计模块进行分析,其他模块会陆续进行展开。


一、far_end 远端信号的处理

远端数据处理是一个单独的模块,主要包括对信号的分帧、加窗以及对应的傅里叶变换,并将处理之后的不同数据分配到不同的Buffer中,这里的数据块主要采用环形数组。
首先对一些参数进行初始化:

    WebRtcAec_Create(&aecmInst);WebRtcAec_Init(aecmInst, 16000, 16000);config.nlpMode = kAecNlpAggressive;WebRtcAec_set_config(aecmInst, config);

用fread(far_frame, sizeof(short), NN, fp_far)函数读入远端数据,并通过WebRtcAec_BufferFarend(aecmInst, far_frame, NN)函数对远端数据进行处理:

int32_t WebRtcAec_BufferFarend(void* aecInst,const int16_t* farend,int16_t nrOfSamples) {

这里的处理主要包括两部分:

  for (i = 0; i < newNrOfSamples; i++) {tmp_farend[i] = (float)farend_ptr[i];}WebRtc_WriteBuffer(aecpc->far_pre_buf, farend_float, (size_t)newNrOfSamples);// Transform to frequency domain if we have enough data.while (WebRtc_available_read(aecpc->far_pre_buf) >= PART_LEN2) {// We have enough data to pass to the FFT, hence read PART_LEN2 samples.WebRtc_ReadBuffer(aecpc->far_pre_buf, (void**)&farend_float, tmp_farend, PART_LEN2);WebRtcAec_BufferFarendPartition(aecpc->aec, farend_float);// Rewind |far_pre_buf| PART_LEN samples for overlap before continuing.WebRtc_MoveReadPtr(aecpc->far_pre_buf, -PART_LEN);

第一步:通过WebRtc_WriteBuffer函数将远端数据读入到环形数组中,环形数组的定义为:

RingBuffer* far_pre_buf;  // Time domain far-end pre-buffer.

第二步就是通过While循环对数组进行缓存,由于读入的是10ms的数据,但是算法处理的帧长为64,重叠率50%,因此对数据进行了缓存,使fft变换的数据长度为128,通过WebRtc_MoveReadPtr(aecpc->far_pre_buf, -PART_LEN)函数对缓存数据进行更新,保证重叠率为50%;
数组缓存之后,通过WebRtcAec_BufferFarendPartition函数接口需要对数组进行FFT:

void WebRtcAec_BufferFarendPartition(AecCore* aec, const float* farend) {float fft[PART_LEN2];float xf[2][PART_LEN1];// Check if the buffer is full, and in that case flush the oldest data.if (WebRtc_available_write(aec->far_buf) < 1) {WebRtcAec_MoveFarReadPtr(aec, 1);}// Convert far-end partition to the frequency domain without windowing.memcpy(fft, farend, sizeof(float) * PART_LEN2);TimeToFrequency(fft, xf, 0);WebRtc_WriteBuffer(aec->far_buf, &xf[0][0], 1);// Convert far-end partition to the frequency domain with windowing.memcpy(fft, farend, sizeof(float) * PART_LEN2);TimeToFrequency(fft, xf, 1);WebRtc_WriteBuffer(aec->far_buf_windowed, &xf[0][0], 1);
}

考虑到后续回声消除和非线性处理会用到加窗和不加窗两部分不同的数组,因此,这里对缓存的数组进行加窗和不加窗两种处理方式,并放置在不同的buffer中。
以上是对远端数据的一个基本操作,但这里还有一点需要注意,由于初始缓存是160个数据,也就是10ms的数据,因此导致系统存在一个延迟,通过:

  WebRtcAec_SetSystemDelay(aecpc->aec,WebRtcAec_system_delay(aecpc->aec) + newNrOfSamples);

函数进行设置system_delay=160。这个在ProcessNormal函数中会用到。

  msInSndCardBuf =msInSndCardBuf > kMaxTrustedDelayMs ? kMaxTrustedDelayMs : msInSndCardBuf;// TODO(andrew): we need to investigate if this +10 is really wanted.msInSndCardBuf += 10;

二、时延估计

在网友博客webrtc aecd算法解析
中,罗列了导致延时的三种类型,可以作为参考:
在这里插入图片描述
此外,这里还说到,硬件方面的延迟很容易确定,倒是软件方面的延迟比较难以估计。至于为什么这里以后会补充。

2.程序分析

其实,但从数据来说,系统的延迟时间前期可以通过仿真确定,手机,会议机以及耳机等终端设备的不同会导致不同程度的信号延迟,在前期仿真中可以通过互相关函数确定延迟的具体值。在WebRTC中,有两种模式对延迟信号进行估计。
首先通过WebRtcAec_Process函数将近端数据进行输入:

 WebRtcAec_Process(aecmInst, near_frame, NULL, out_frame, NULL, NN, 40, 0);

这里的40就是前期估计的延迟时间,WebRtcAec_Process的函数为:

nt32_t WebRtcAec_Process(void* aecInst,const int16_t* nearend,const int16_t* nearendH,int16_t* out,int16_t* outH,int16_t nrOfSamples,int16_t msInSndCardBuf,int32_t skew) 

这里关注以下msInSndCardBuf参数,WebRTC的给的解释是:

 * int16_t       msInSndCardBuf Delay estimate for sound card and*                              system buffers

这个参数就是前期代码仿真时通过互相关函数确定的延迟时间,在extended_filter_enabled=1的时候,以下会使能:

  if (WebRtcAec_delay_correction_enabled(aecpc->aec)) {ProcessExtended(aecpc, nearend, nearendH, out, outH, nrOfSamples, msInSndCardBuf, skew);

然后会根据输入的msInSndCardBuf计算目标延迟,通过仿真完全可以和延迟时间对得上:

   int target_delay = startup_size_ms  * self->rate_factor * 8;int overhead_elements = (WebRtcAec_system_delay(self->aec) - target_delay) / PART_LEN;WebRtcAec_MoveFarReadPtr(self->aec, overhead_elements);

并根据计算出的overhead_elements 对信号进行延迟补充,WebRtcAec_MoveFarReadPtr函数如下:

int WebRtcAec_MoveFarReadPtr(AecCore* aec, int elements) {int elements_moved = WebRtc_MoveReadPtr(aec->far_buf_windowed, elements);WebRtc_MoveReadPtr(aec->far_buf, elements);
#ifdef WEBRTC_AEC_DEBUG_DUMPWebRtc_MoveReadPtr(aec->far_time_buf, elements);
#endifaec->system_delay -= elements_moved * PART_LEN;return elements_moved;
}

这里其实对加窗和不加窗数据进行move。
以上是根据msInSndCardBuf参数进行的延迟计算,当然在线性滤波器阶段,本身还存一个延时估计:

if (aec->delayEstCtr == 0) {wfEnMax = 0;aec->delayIdx = 0;for (i = 0; i < aec->num_partitions; i++) {pos = i * PART_LEN1;wfEn = 0;for (j = 0; j < PART_LEN1; j++) {wfEn += aec->wfBuf[0][pos + j] * aec->wfBuf[0][pos + j] +aec->wfBuf[1][pos + j] * aec->wfBuf[1][pos + j];}if (wfEn > wfEnMax) {wfEnMax = wfEn;aec->delayIdx = i;}}}

这里会估计出一个aec->delayIdx,那么和moved element就构成所有的延迟时间。
第二种方式通过一端时间的迭代,Web给出6帧数据,判断延迟是否稳定,在ProcessNormal中进行处理。

      // Before we fill up the far-end buffer we require the system delay// to be stable (+/-8 ms) compared to the first value. This// comparison is made during the following 6 consecutive 10 ms// blocks. If it seems to be stable then we start to fill up the// far-end buffer.

待数据稳定之后就会通计算给出要移动的数据个数移动。

overhead_elements =WebRtcAec_system_delay(aecpc->aec) / PART_LEN - aecpc->bufSizeStart;WebRtcAec_MoveFarReadPtr(aecpc->aec, overhead_elements);

并在EstBufDelayNormal函数中进行一定条件的筛选。
至此,延迟主要的逻辑基本叙述完毕,其实Web真正深奥的倒是那些约束条件,这个不经过一定的实践和及深厚的理论基础是很难想得到,有机会再做补充。

3.参数说明

对于16k采用率,每帧处理的采样点数为64,对于的FFT变化的长度为128;

#define FRAME_LEN 80
#define PART_LEN 64               // Length of partition
#define PART_LEN1 (PART_LEN + 1)  // Unique fft coefficients
#define PART_LEN2 (PART_LEN * 2)  // Length of partition * 2

但是为了能够覆盖大部分延迟时间,数据开闭的buffer为1s的数据块:

static const size_t kBufSizePartitions = 250;  // 1 second of audio in 16 kHz.

由于一帧数为64/16=4ms,那么16块对应64ms,12块数据对应48ms数据,因此也基本上能够覆盖一般的穿戴设备。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/223640.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

韵达快递查询入口,将途经指定城市的单号筛选出来

批量查询韵达快递单号的物流信息&#xff0c;并将途经指定城市的单号筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 韵达快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的伙伴记得先注册&#x…

生物分子相互作用的奥秘与挑战:探索未来药物设计的新方向

在生命科学领域&#xff0c;生物分子相互作用的研究日益受到关注。这种相互作用涉及蛋白质、核酸、脂质和糖类等生物分子之间的相互识别、结合和调控。理解这些相互作用对于揭示生命现象、疾病机制和治疗策略具有重要意义。 然而&#xff0c;生物分子相互作用的奥秘也带来了诸…

探索“超级服务器” TON:SDK 应用与开发入门

TON 是一个由多个组件构成的去中心化和开放的互联网平台&#xff0c;聚焦于实现广泛的跨链互操作性&#xff0c;同时在高可扩展性的安全框架中运作。TON 区块链被设计为分布式超级计算机或“超级服务器&#xff08;superserver&#xff09;”&#xff0c;旨在提供各种产品和服务…

在Node.js中MongoDB更新数据的方法

本文主要介绍在Node.js中MongoDB更新数据的方法。 目录 Node.js中MongoDB更新数据使用原生 MongoDB 驱动程序更新数据使用 Mongoose 更新数据 Node.js中MongoDB更新数据 在Node.js中&#xff0c;可以使用原生的 MongoDB 驱动程序或者使用 Mongoose 来更新 MongoDB 数据。 下面…

【腾讯云 HAI 域探秘】释放生产力:基于 HAI 打造团队专属的 AI 编程助手

文章目录 前言一、HAI 产品介绍二、HAI 应用场景介绍三、HAI 生产力场景探索&#xff1a;基于 HAI 打造团队专属的 AI 编程助手3.1 申请 HAI 内测资格3.2 购买 HAI 实例3.3 下载 CodeShell-7B-Chat 模型3.4 部署 text-generation-inference(TGI)推理服务3.4.1 下载 text-genera…

Linux 使用 Anaconda+Uwsgi 部署 Django项目和前端项目

一、安装Anaconda 使用Anaconda创建python环境的优点&#xff1a; virtualenv只能创建系统原有的python版本&#xff0c;而不能创建创建任意版本的环境 而Anaconda的虚拟环境中&#xff0c;你可以指定任意现存可使用的python环境&#xff08;包括比原环境版本高的python版本&a…

代码签名证书:数字安全世界的守门员

在这个信息化的时代&#xff0c;如果说互联网是高速流动的信息海洋&#xff0c;那么软件便是承载这些信息的庞大船队。而让人倍感安心地乘坐这些船的&#xff0c;正是被称为代码签名证书的重要安全措施。 你可以把代码签名证书想象成是软件世界的一位神秘守门员。它存在的目的&…

使用Python爬取公众号的合集

文章目录 前言讲解爬取思路开爬爬取文章url文章爬取结果爬取图片图片爬取结果优化下载图片代码 声明结尾关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游…

激活Windows过程及报错解决: 0x803f7001 在运行Microsoft Windows 非核心版本的计算机上, 运行“ slui.exe 0x2a 0x803f7001 “以显示错误文本

激活Windows过程及报错问题解决: 0x803f7001 在运行Microsoft Windows 非核心版本的计算机上&#xff0c;运行“ slui.exe 0x2a 0x803f7001 “以显示错误文本。 前言 最近在激活Windows过程中&#xff0c;遇到了报错: 0x803f7001 在运行Microsoft Windows 非核心版本的计算机上…

【数据分析与可视化】利用Python对学生成绩进行可视化分析实战(附源码)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 下面对学生成句和表现等数据可视化分析 1&#xff1a;导入模块 import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt plt.rcParams[font.sans-serif][simhei] plt.rcParams[f…

【Android】使用 Glide 给 ImageView 加载图像的简单案例

前言 Android Glide是一个用于在Android应用中加载和显示图片的流行开源库。它提供了简单易用的API&#xff0c;可以帮助开发者高效地加载远程图片、本地图片以及GIF动画&#xff0c;并提供了缓存、内存管理等功能&#xff0c;使得图片加载在移动应用中更加流畅和高效。Glide还…

小白菜QQ云端机器人源码-去除解密授权

小白菜QQ云端机器人源码分享&#xff1a;解密授权学习版已去除 这款源码是专为群机器人爱好者设计的&#xff0c;它基于挂机宝机器人框架构建的网页站点。 用户可以通过网页登录QQ账号至挂机宝框架中&#xff0c;无需通过机器人实现登录。 而且&#xff0c;该源码解决了一个…

运筹学经典问题(三):最大流问题

问题描述 给定一个图网络 G ( V , E ) G(V, E) G(V,E)&#xff0c;网络中连边的权重代表最大容量&#xff0c;在这个图中找出从起点到终点流量最大的路径。 数学建模 集合&#xff1a; I I I&#xff1a;点的集合&#xff1b; E E E&#xff1a;边的集合。 常量&#x…

使用代理IP时的并发请求是什么意思?

很多做过数据采集的技术们应该都有所了解&#xff0c;在选择代理IP时会有一个并发请求的参数&#xff0c;这个参数是什么意思呢&#xff1f;可能有很多新手不是很了解&#xff0c;其实代理IP的并发请求就是指同时发送多个请求到目标服务器&#xff0c;以提高请求的效率和速度。…

docker-consul(容器的自动发现与注册)

1、微服务&#xff08;容器&#xff09;、容器的注册和发现&#xff1a;是一种分布式管理系统&#xff0c;用于定位服务的方法 &#xff08;1&#xff09;在传统的架构中&#xff0c;应用程序之间直连到已知的服务&#xff0c;设备提供的网络&#xff08;ip地址、基于tcp/ip的…

android 13.0 去掉recovery模式UI操作页面的菜单选项

1.概述 在13.0进行系统rom定制化开发中,在进行一些定制化开发中,会根据需要在进入recovery模式的时候,去掉recovery模式的一些菜单选项, Reboot to bootloader,Enter rescue等菜单项,经过分析得知, 就是在device.cpp去掉一些菜单选项就可以了,接下来就来分析实现相关功…

《PySpark大数据分析实战》-04.了解Spark

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

python进行描述性统计分析,python怎么做描述性统计

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;python语言的描述错误的选项&#xff0c;python描述算法的方法有几种&#xff0c;今天让我们一起来看看吧&#xff01; 一、描述符是什么 描述符&#xff1a;是一个类&#xff0c;只要内部定义了方法__get__, __set__, …

strtok()的用法及实现哦

1. 用法 1. 声明&#xff1a;char *strtok(char *str, const char *delim) str -- 要被分解成一组小字符串的字符串。第一次调用 strtok() 时&#xff0c;这个参数应该是你想要分割的字符串。随后的调用应该将此参数设置为NULL&#xff0c;以便继续从上次的位置分割。delim -- …

接口自动化测试框架搭建

一、原理及特点 参数放在XML文件中进行管理用httpClient简单封装一个httpUtils工具类测试用例管理使用了testNg管理&#xff0c;使用了TestNG参数化测试&#xff0c;通过xml文件来执行case。测试报告这里用到第三方的包ReportNG 项目组织用Maven 二、准备 使用工具&#xff1…