WebRTC AEC回声消除算法拆解

news2024/12/23 1:44:21

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_DUMP
  WebRtc_MoveReadPtr(aec->far_time_buf, elements);
#endif
  aec->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.coloradmin.cn/o/1303421.html

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

相关文章

修改汽车的控制系统实现自动驾驶,基于一个开源的汽车驾驶辅助系统实现全自动驾驶

修改汽车的控制系统实现自动驾驶,基于一个开源的汽车驾驶辅助系统实现全自动驾驶。 自动驾驶汽车依靠人工智能、视觉计算、雷达、监控装置和全球定位系统协同合作,让电脑可以在没有任何人类主动的操作下,自动安全地操作机动车辆。 演示视频: Openpilot :一个开源的汽车驾…

2020年第九届数学建模国际赛小美赛C题亚马逊野火解题全过程文档及程序

2020年第九届数学建模国际赛小美赛 C题 亚马逊野火 原题再现&#xff1a; 野火是指发生在乡村或荒野地区的可燃植被中的任何不受控制的火灾。这样的环境过程对人类生活有着重大的影响。因此&#xff0c;对这一现象进行建模&#xff0c;特别是对其空间发生和扩展进行建模&…

[算法基础 ~排序] Golang 实现

文章目录 排序什么是排序排序的分类1. 冒泡1.1 冒泡排序1.2. 快速排序 2. 选择2.1 简单选择排序2.2 堆排序 3. 插入3.1 直接插入3.2 折半插入3.3 希尔排序 4. 归并排序代码实现 5. 基数排序 排序图片就不贴了吧 排序 什么是排序 以下部分动图来自CSDN ::: tip 稳定性的概念 …

网络测试工具:tcping-测试端口连接

网络测试工具&#xff1a;tcping-测试端口连接 平常使用的ping&#xff0c;是通过icmp协议去测试网络连通性的&#xff0c;tcping是通过tcp三次握手测试端口的连通性。总的来说&#xff0c;ping测试的是L3的连通性&#xff0c;tcping测试的是L4的连通性。 tcping工具下载 htt…

Go语言学习:第1天

一、为什么开始学go语言 我自己是做测试的&#xff0c;所测试项目使用的是go语言。开始学习go语言的原因有两个&#xff1a;一方面&#xff0c;为了更好的做好工作&#xff1b; 另一方面&#xff0c;为了提高自己的核心竞争力。 二、第1天学习到的内容 2.1 Go是怎么解决包依…

打包less

接HTML和css之后对less进行打包 1.在之前的文件夹里的src文件夹创建一个less文件 2.打开webpack——>中文文档——>Loader——>less—loader 3.复制下图代码到终端 4.复制下图内容到webpack.config.js脚本 5.在src里的js文件年引入less文件 6.在终端运行 npm run te…

[原创]如何正确的部署R语言开发环境(含动图演示).

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XX QQ联系: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、D…

arm-linux设备fsck命令移植

arm-linux设备fsck命令移植 文章目录 **arm-linux设备fsck命令移植**1、下载e2fsprogs-源码2、解压3、进入源码目录4、配置编译环境&#xff1a;使用以下命令配置交叉编译环境5、测试 1、下载e2fsprogs-源码 首先要确定自己的文件系统格式&#xff0c;IG2000的文件系统是ext4&…

基于OpenCV+CNN+IOT+微信小程序智能果实采摘指导系统——深度学习算法应用(含python、JS工程源码)+数据集+模型(四)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python环境TensorFlow 环境Jupyter Notebook环境Pycharm 环境微信开发者工具OneNET云平台 模块实现1. 数据预处理2. 创建模型并编译3. 模型训练及保存1&#xff09;模型训练2&#xff09;模型保存 4. 上传结果1&#xff09;…

【AI】ChatGLM3-6B上手体验

之前写过ChatGLM2-6B大语言模型的部署安装文档&#xff0c;现在ChatGLM模型已经更新迭代到第三代了&#xff0c;从官方公布的数据来看&#xff0c;模型的能力是得到了进一步的增强。 这次写文章主要是来记录一下使用过程&#xff0c;方便回头查看。 ChatGLM3-6B官方的视频教程…

VSCode中如何查看EDI报文?

VSCode是开发人员常用的一款软件&#xff0c;为了降低EDI报文的阅读门槛&#xff0c;知行的开发人员设计了EDI插件&#xff0c;可以在VSCode中下载使用。 如何打开一个EDI报文——VSCode EDI插件介绍 EDI插件下载流程 进入VSCode&#xff0c;打开Extensions&#xff0c;在搜索…

STM32-01-认识单片机

文章目录 一、单片机简介二、Cortex-M系列介绍三、初识STM32四、STM32原理图设计五、搭建开发环境六、STM32初体验七、MDK5使用技巧 一、单片机简介 单片机是什么&#xff1f; 单片机&#xff1a;Single-Chip Microcomputer&#xff0c;单片微型计算机&#xff0c;是一种集成电…

Keepalived+Nginx实现高可用(上)

一、背景与简介 为了服务的高可用性&#xff0c;避免单点故障问题&#xff0c;通常我们使用"冗余设计思想"进行架构设计。冗余设计思想&#xff0c;本质就是将同一个应用或者服务放置在多台不同的服务器上[鸡蛋不放在同一个篮子里]&#xff0c;这样减少整体服务宕机的…

2023 年山东省职业院校技能大赛(高等职业教育) “信息安全管理与评估”样题

2023 年山东省职业院校技能大赛&#xff08;高等职业教育&#xff09; “信息安全管理与评估”样题 目录 任务 1 网络平台搭建&#xff08;50 分&#xff09; 任务 2 网络安全设备配置与防护&#xff08;250 分&#xff09; 模块二 网络安全事件响应、数字取证调查、应用程序安…

深入探索 Spring Boot:简化开发,加速部署的全方位利器

目录 导言 1. 自动配置&#xff08;Auto-Configuration&#xff09; 2. 起步依赖&#xff08;Starter Dependencies&#xff09; 3. 嵌入式 Web 服务器 4. Actuator 5. 外部化配置 6. 简化的安全性配置 7. Spring Boot CLI 8. Spring Boot DevTools 导言 在当今软件开…

散点图直方图折线图的替代

散点图直方图折线图的替代 seaborn官网 数据科学数据可视化&#xff0c;散点图 直方图 折线图的新方法 1.hexbinplot https://seaborn.pydata.org/examples/hexbin marginals.html相当于散点图做了聚合/分箱&#xff0c;使数据的分布展示更明显。Library: seaborn 2.瀑布图展示…

Pytorch-LSTM轴承故障一维信号分类(一)

目录 前言 1 数据集制作与加载 1.1 导入数据 第一步&#xff0c;导入十分类数据 第二步&#xff0c;读取MAT文件驱动端数据 第三步&#xff0c;制作数据集 第四步&#xff0c;制作训练集和标签 1.2 数据加载&#xff0c;训练数据、测试数据分组&#xff0c;数据分batch…

Qt Widgets 绘图行为逐步分析拆解

Qt 是目前C语言首选的框架库。之所以称为框架库而不单单是GUI库&#xff0c;是因为Qt提供了远远超过GUI的功能封装&#xff0c;即使不使用GUI的后台服务&#xff0c;也可以用Qt大大提高跨平台的能力。 仅就界面来说&#xff0c;Qt 保持各个平台绘图等效果的统一&#xff0c;并…

【EMNLP 2023】面向垂直领域的知识预训练语言模型

近日&#xff0c;阿里云人工智能平台PAI与华东师范大学数据科学与工程学院合作在自然语言处理顶级会议EMNLP2023上发表基于双曲空间和对比学习的垂直领域预训练语言模型。通过比较垂直领域和开放领域知识图谱数据结构的不同特性&#xff0c;发现在垂直领域的图谱结构具有全局稀…

Flink之迟到的数据

迟到数据的处理 推迟水位线推进: WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(2))设置窗口延迟关闭&#xff1a;.allowedLateness(Time.seconds(3))使用侧流接收迟到的数据: .sideOutputLateData(lateData) public class Flink12_LateDataC…