上周,我们针对音频弱网对抗技术中的前向纠错技术、后向纠错技术和 OPUS 编解码抗弱网特性 分享。文本分享。 WebRTC 使用的抗抖动模块 NetEQ。
抖动是指到达接收端的数据在不同时间段的不平衡;或者接收端接收数据包的时间间隔有大有小。
,公式如下:
Ji定义为时刻 i 测量的抖动,E(T) 表示包到达时间间隔的平均值,Ti表示时刻 i 上次收到的包间隔。
Ji> 0 说明数据包提前到达,因此抖动缓存区数据会积累,容易造成缓冲区数据溢出,导致端到端时间延迟增加;Ji< 0 说明数据包迟到或丢失,会增加延迟;无论早到晚到,都可能导致丢包,增加延迟。
抖动的大小根据收到的包但未播放的缓冲语音数据的大小进行评估。
原则上,通过测量网络延迟,以最大延迟设置抖动缓冲区的大小,然后使每个包在网络中的延迟和抖动缓冲区的延迟保持相等,消除抖动,控制抖动缓冲区的语音包以相对稳定的速度播放音频数据。
下图[1]说明了抖动消除的核心思想:
(抖动消除的核心思路)
A、B、C、D 发送端分别包裹 30ms 间隔发送,即分别在 30ms、60ms、90ms、120ms 接收端接收这些包的对应时间是 40ms、90ms、100ms、130ms;这样,它们在网络中的延迟分别是 10ms、30ms、10ms、10ms;包到达的间隔分别为 50ms、10ms、30ms,即抖动。
因此可以使包 A、C、D 在抖动缓存中延迟 20ms 再播放,即 A、B、C、D 播放时间为 60ms、90ms、120ms、150ms,这样可以保持平稳的播放间隔。
NetEQ 根据网络传输延迟估算网络传输延迟 95% 分位调整抖动缓冲区;这使得 NetEQ 考虑最小延迟,消除抖动缓冲的影响。
下图[2]为官方对 NetEQ 与消除抖动带来的延迟相比,可以看出 NetEQ 在消除抖动时,可以保持很低的时间延迟。
(NetEQ 与其他消除抖动延迟的技术相比)
下图[1]概要描述 WebRTC 语音引擎架构。红色区域为 NetEQ 部分,可见 NetEQ 包含在接收端 jitter buffer 和解码器、PLC 等相关模块。接收端从网络接收语音包后,语音包将先进入NetEQ 抖动消除模块、隐藏丢包、解码等操作,最后将音频数据输出到声卡播放。
(WebRTC 语音引擎架构图)
NetEQ 见下图[1]:
(NetEQ 模块)
NetEQ 核心模块有和,MCU 模块负责往 jitter buffer 在缓存中插入数据和取数据,并给出 DSP 的操作;DSP 该模块负责语音信息号的处理,包括解码、加速、减速、集成、PLC 等。
同时 MCU 模块从 jitter buffer 中取包受到 DSP 相关反馈影响模块。
包括进入音频包 buffer,音频包从 buffer 取出,通过语音包到达间隔评估网络传输时间延迟 DSP 模块执行什么操作(加速、减速、PLC、merge)等。
包括解码,解码后的语音 PCM 相关操作数据,评估网络抖动 level,向 play buffer 传输可播放数据等。
下面详细分析MCU 和 DSP 相关模块。
从网络收到音频包后,插入抖动缓存 packet_buffer_中,最多缓存 200 个包;当缓存满了,缓存中的所有包都会刷新,最多缓存最近 5 过早清除秒音频数据。
若收到的包是 red 包,会将 red 包中的每个独立包分析并存储在缓存队列中,包在缓存队列中 timestamp 升序存储,即最近的音频包存储在队列后面,旧包存储在队列前面。
收到音频包后会更新,该模块主要通过包的序列号来判断是否有丢包。如果有丢包并重新传输请求,则需要启动发送端 nack 请求发送端重发丢失的音频包。
将包插入抖动缓存时,根据包到达时间间隔估计网络延迟。这里将是 WebRTC网络延迟按包到达时间间隔计算,WebRTC 计算音频网络延时,主要考虑如下几点:
上述公式计算了当前包和上一包之间的时差,以及 seqnum 计算每个差值 seq 包占有的时间戳范围,即 packet_per_time,然后通过当前包和上一个包收包间隔 iat_ms,用包的数量来计算收包时间间隔的延迟。
正常包到达时 iat = 1,iat = 0 表示提前到了,iat = 2.表示一个包延迟, 最后,用包的数量来衡量延迟时间。
每算出一个 iat 之后,插入相应的直方图,记录直方图 0-64 个包,即记录 0-64 个包的延迟概率分布。所有延迟概率和 1。
计算当前包的到达间隔 iat,插入直方图,更新每个延迟的概率,每个延迟分布乘以一个遗忘因素 f,f 小于 1, 更新过程如下:
上述公式的意义是确保每次计算一个包到达间隔延迟后,增加其概率,忘记其他延迟分布的概率,使整体和保持 1。
最后取其 95% 目标延迟分为延迟,即有 95% 延迟小于目标延迟。
统计多包达到间隔峰值,最多 8 个,在一定情况下,以峰值作为网络延时估计。每次计算到包到达时间间隔 iat 和按 95% 分位计算的目标延迟(记录target_level)后来,判断峰值。
认为该 iat 延迟峰值的条件是:
iat > target_level threshold, 或 iat > 2 * target_level, 这里的 threshold 为 3
上次峰值时间间隔小于阈值 5s
当判断该 iat 在延迟峰值时,将其添加到峰值容器中,每个元素记录两个值,一个是峰值 iat 另一个是当前峰值距离上次峰值的时间间隔(即 period_ms);
当峰值容器超过两个时,当前时间距离上次发现峰值时已经过去(记录为 eplase_time)最大记录在峰值容器元素中的最小记录period_ms 认为峰值是有效的,需要从峰值容器中取最大的 iat(记为 max_iat),目标延迟值:
当 target_level 取值是 max_iat 峰值的理论有效时间可以达到 40s 这里有优化空间。
根据设定的最低延迟,调整目标延迟估计,确保目标延迟估计不低于最低延迟;最小延迟是音视频同步计算的结果。
目标延迟小于最小延迟,会导致音视频不同步。
WebRTC 默认情况下,最大的抖动缓冲区 因此,目标延迟为:
即 target_level 不大于 150 也就是说,目标延迟不大于 3s 延时。
播放线程尝试次获取 10ms 数据来播放,要是抖动缓冲中没有数据,则播放静音数据。
从 packet_buffer_ 中获取的数据首先需要经过解码器解码,然后再根据反馈的操作进行对应的 DSP 操作(加速、减速、丢包隐藏、PLC、融合等)。最后将 DSP 处理完的数据填入到 sync_buffer_ 中待扬声器播放,并从 sync_buffer_ 中取 10ms 音频数据进行播放。
根据抖动缓冲区 packet_buffer_ 和 sync_buffer_ 中未播放完的音频数据总延时 total_dealy,来更新计算抖动缓存延时 filtered_level
若经过加减速操作,需要从 filtered_level 中消除加减速操作后引入的延时变化,如下所示:
下图中的 packet_buffer_ 和 sync_buffer_ 中未播放的数据大小可以理解为抖动缓冲区大小,从图中可看出数据从 packet_buffer_ 取出后,经解码器解码、DSP 处理,最后进入 sync_buffer_中待播放。
low_limit = 3/4 *target_level
high_limit = max(target_level, low_limit + window_20ms
(抖动缓冲区)
但进行何种 DSP 操作处理呢?
NetEQ 将根据 packet_buffer_和 sync_buffer_中未播放的音频数据总延时(记为 total_delay),以及 sync_buffer_ 中最后待播放的音频数据的时间(记为 endtimestamp),和 packet_buffer_首包的时间(记为 avaibleTimestamp),以及 target_level 和 filtered_level 的关系,来综合判断接下来执行何种 DSP 操作。
下面将对进行简要说明:
满足以下条件之一,将执行 normal 操作
1. 若原本要做 expand 操作,但是 sync_buffer_ 待播放的数据量大于 10ms。
2. 连续 expand 操作次数超过阈值。
3.当前帧和前一帧都正常到达,且 fitlered_level 在 low_limit 和 higth_limit 之间。
4. 上一次操作是 PLC,当前帧正常,但当前帧来得太早,执行 normal 操作。
5. 当前帧和前一帧都正常到达, 原本通过 filtered_level > high_limit 判断,要加速操作,但是 sync_buffer_ 待播放的音频数据大于小于 30ms。
当前数据包丢失或者还未到达时,同时在 sync_buffer_ 待播放的音频数据小于 10ms 时,则满足下面 4 个条件中任何一个,都将执行 expand 操作。
1. packet_buffer_ 没有可获取的音频数据。
2.上一次是 expand 操作,且当前 total_delay < 0.5* target_level。
3. 上一次是 expand 操作,且当前包(即availbeTimestamp - endtimestamp 大于一定阈值)来的太早,同时当前 filtered_level 小于 target_level。
4. 上一次包非 expand(加速、减速或 normal),且 availbeTimestamp - endtimestamp >0,即中间存在丢包。
上一个包和当前包都正常到达,filtered_level 大于 hight_limit,且 sync_buffer_ 中待播放的数据量大于 30ms。
上一个包和当前包都正常到达,filtered_level 小于 low_limit。
上一次为 expand 操作,当前包正常到达。
基音是指发浊音时声带振动所引起的周期性对应的信号基本谐波,基音周期等于声带振动频率的倒数。一般的声音都是由发音体发出的一系列频率、振幅各不相同的振动复合而成的。
这些振动中有一个频率最低的振动,由它发出的音就是基音,其余为泛音。基音携带了大部分能量,决定了音高。
基音的周期提取,一般使用短时自相关函数,因为自相关函数一般在基于周期上表现出有极大值的特点。在 NetEQ 的 DSP 信号处理中,基音的提取是一个至关重要的步骤。
对语音的拉伸变速,有时域方法和频域方法。计算量相对频域方法少,这种场景;,如音乐。
NetEQ 中对语音的拉伸(加速或减速)使用的是 WSOLA 算法,即通过波形相似叠加的方法来变速;该算法在拉伸语音时能够保证。同时该算法是时域算法,对语音有比较好的效果,下图是其大致原理和流程:
(WSOLA 算法原理)
WSOLA 算法大致流程:
从 packet_buffer_ 中获取数据,正常解码,解码数据存储到 decoded_buffer_ 中。
NetEQ 中,当 sync_buffer_ 和 packet_buffer_ 中的待播放数据总延时堆积过多,且前一帧和当前帧都正常时,需要通过加速播放操作,来降低抖动缓冲区待播放的数据量,以达到降低时延的目的,否则容易导致抖动缓冲区溢出,出现丢包。而加速是要求变速不变调的,WSOLA 算法通过寻找相似波形并对相似波形进行叠加来实现变速目的。
这里对 NetEQ 中的加速过程做简要描述:
⾸先做加速要求最少需要 30ms 数据,数据一般来源于从 decoder_buffer_, 当 decoded_buffer_中得的数据不足 30ms,需要从 sync_buffer_中借一部分数据,凑满 30ms。
30ms 数据对应 240 个样本,将这 240 个样本下采样到 110 个样本;将下采样 110 个样本分成两部分 seq1 和seq2,seq2 为固定末尾 50 个样本,即[60,110]; seq1 也为 50 个样本,滑动范围[0,100],计算 seq1 和 seq2 的自相关性。
根据计算出来的自相关性结果,以抛物线拟合来找到自相关性峰值和位置,极大值出现的位置,即为上图中的 offset,即 seq1 窗口向左滑动的距离,所以基音周期 T = offset + 10。
在要加速的 30ms 样本中,取 15ms 和 15ms - T 这两个基音周期信号,分别记为 XY 计算这两个基音周期信号的匹配度。
当 best_correlation 大于 0.9 时,则执行将两个基音周期信号合并为⼀个基音周期信号,起到加速作用
加速完的数据,如下图的 output 将存储到 algorithm_buffer_中,要是之前因为 decoded_buffer_中的数据量不够,从 sync_buffer_中借了部分数据,则需要从 algorithm_buffer_中将借的数据 copu 回 sync_buffer_对应的位置(目的是保证音频的平滑过渡),同时将 algorithm_buffer_中剩下的数据 copy 到 sync_buffer_末尾,如下图所示:
当 NetEQ 抖动缓冲中待播放的数据延时水平小于目标延时下限时,说明待播放的数据量小,为了达到播放端的最佳音质体验,需要将现有数据拉伸,适当增加播放数据量;这和加速是相反的操作,底层技术相同。同样下面对其做简要分析:
前 4 步和加速基本上是一样的,这里不再赘述
最后一步,是将 15ms - T 和 15ms 两个基音周期交叉渐变合并的混合基音插入到 15ms 后一个周期前。达到增加一个基音周期数据的目的,以此来增加播放数据量,同时需要从算法缓冲区中将借的数据返还给 sync_buffer_。
具体如下图所示:
当前包丢失时,在 NetEQ 中会触发丢包补偿来预测丢失的包;丢包补偿有两种方式,一种是通过编解码器来预测重构丢失的包,另一种是通过 NetEQ 模块来预测重构丢失的包;对应分别是 kModeCodecPlc 和 kModeExpand。
不论是何种模式,前提要求是 sync_buffer_ 中待播放的音频数据量小于当前请求要播放的数据量,才能执行该操作;丢包补偿通过最近的历史数据,来重构 PLC 相关参数,然后通过历史数据和 PLC 相关参数进行线性预测,恢复丢失的包,最后再叠加一定的噪声。
当连续多次 PLC 操作时,失真将会加大,所以多次操作后,将会降低 PLC 语音能量值。
下图是 NetEQ expand 操作的核心步骤。
(构建 PLC 参数步骤,点击查看大图)
(构建 PLC 包,点击查看大图)
PLC 操作中,首要任务是计算基音周期,这里使用了和两种方式来计算基音周期,可通过下图简要说明。
A 为末端 60 个样本信号
B 为滑动窗口,窗口包含 60 个样本信号,窗口起始位置滑动范围[0, 54]
通过计算 A 和 B 的自相关系数,得到 54 个自相关结果,对这 54 个自相关结果使用抛物线拟合找到三个最大值的位置 peak1_idx1、peak_idx2、peak_idx3 为三个自相关系数最大的值的位置
认为基音是周期信号,极值出现的位置为周期上
故基音周期 T
T1 = peak_idx3 + 10
T2 = peak_idx2 + 10
T3 = peak_idx1 + 10
根据上面通过自相关及抛物线拟合找到的三个极值,得到了三个基音周期
取末端 20 个信号作为 A,滑动窗口 B 也是 20 个样本(2.5ms),B 在距 A 一个基音周期前后 4 个样本范围内(0.5ms)滑动
故有三个窗口滑动范围,计算三个范围内的窗口 A 和窗口 B 的失真度
取最小失真度三个极值,这三个极值作为通过失真最小计算的基音周期 T'
1, T'2, T'3
最小失真的衡量是以 A 和 B 对应元素差值绝对值之和最小为依据的
在得到基于自相关计算出来的三个基⾳周期 T1,T2,T3 和基于失真度最小得到的三个基⾳周期 T'1,T'2,T'3
通过比较这三对的 ratio = 自相值/失真度,当 ratio 最大时,则认为这对基音周期最佳
PLC 操作中,expand_vector0 和 expand_vector1 的计算细节如下图:
(从历史数据中构造 expand_vector0、expand_vector1)
构造 AR 滤波器参数,通过获取一组(7 个)自相关值,对这组自相关值通过 LevinsonDurbin 算法计算,预测出 AR 滤波器参数。
AR 滤波器是线性预测器,通过历史数据来预测当前数据;在构建 PLC 包时,通过对历史数据进行 AR 滤波,来预测丢失的包信息。
下图是对这个过程的简要说明:
AR 滤波器公式如下所示:
这里 k = 7;e(n)为预测值和实际值的误差;AR 滤波就是通过使用最近的历史数据来预测当前时刻数据;LevinsonDurbin 算法通过自相性值的计算来预测 ck,使得 e(n) 最小。
自相关性越大,说明 e(n) 越小;
LevinsonbDurbin 算法通过使用自相关性值来预测 AR 滤波器参数,可以理解为使⽤ e(n)来预测。
融合操作一般是上一帧丢失了,但当前帧正常;
上一帧是通过预测生成的 PLC 包,和当前帧需要做平滑处理,防止两种衔接出现明显变化;
融合就是完成该功能,融合的组要过程有:
需要 202 个扩展样本信号,主要是向 sync_buffer_ 未播放的信号借用 sync_buffer_ 未播放的信号不足 202,通过生成足够 PLC 数据来凑满
通过扩展样本信号和输入信号,计算期它们的自相关性值,及通过抛物线拟合来获取基音周期
对 input 数据进行 Ramp 信号变换
对扩展信号和 input 信号部分段进行混合
从缓存算法取归还借用的信号数据到 sync_buffer_
将算法缓冲区剩余的平滑处理的信号追加到 sync_buffer_
下图对 NetEQ 中融合过程的简要说明:
(基音周期和 mute_factor,点击查看大图)
(混合 expand 和 input 信号,点击查看大图)
当前帧可以正常播放,也就是可以直接将这段信号送⼊到 sync_buffer_ 中, 但是因为上一帧可能是 expand 等,需要进行相关的平滑操作。
主要步骤如下:
将输入信号拷贝到算法缓冲区生成一段 PLC 数据包
根据背景噪声和输入语音信号的能量比值,计算 mute_factor
根据 mute_factor 对算法缓冲区数据按照能量从弱到强的趋势修正
将 expand 和算法缓冲区的头 1ms 音频数据进行平滑混合,结果存在算法缓冲区;
相关混合平滑公式如下:
将最终的算法缓冲区数据最加到 sync_buffer_ 后
下图对 normal 的操作流程做补充说明:
(normal 操作流程,点击查看大图)
NetEQ 为了消除抖动、解码音频数据、对解码数据进行 DSP 处理(加减速、PLC、融合、平滑数据),以及平滑播放等,使用了多个缓存 buffer,下面是对这些 buffer 的简要说明:
用来接收网络中收到的音频包数据,也可称为抖动缓存,会定时删除当前时间 5 秒前的包,同时最多缓存 200 个包,即 4 秒(这⾥以每个包 20ms 计算)。
: 当播放线程每次获取音频数据来播放时,会根据目标延时和抖动缓存延时来判断是否需要从 packet_buffer_中获取音频数据进行解码,解码后的数据存放到 decoded_buffer_中。最多可缓存 120ms 数据。
: decoded_buffer_中的数据经过 DSP 处理后,将存放到该缓存,一般每处理一次会清空一次。
: 一般是从算法缓存拷贝过来的数据,是待播放的数据;sync_buffer_中有两个变量,一个是 next_index,表示当前播放的位置,next_index 前的数据表示已经播放,后面的数据表示待播放;另一个是 endtimestamp, 表示 sync_buffer_中最后一个待播放的数据,也就是最近的音频数据量。
该 buffer 可最多缓存 180ms 数据,是一个循环缓存;播放线程每次会从 sync_buffer_中取 10ms 数据进行播放。
关于何时从 packet_buffer_中取数据解码说明:
一般情况下每次从 packet_buffer_中取 10ms 数据进行解码。
当要执行 expand 操作时,但 sync_buffer_中有 10ms 以上数据,不从 packet_buffer_中取数据解码。
当做加速操作时,若 sync_buffer_中有 30ms 以上数据,不从 packet_buffer_中取数据;或若 sync_buffer_中有 10ms 以上待播放数据,同时上次解码了 30ms 数据,则不从 packet_buffer_中获取。
当加速操作,若 sync_buffer_待播放数据小于 20ms,同时上次解码的数据小于 30ms,从 packet_buffer_中获取 20ms 数据待解码。
当要做减速操作时,若 sync_buffer_中待播放的数据大于 30ms;或待播放的数据小于 10ms,但上一次解码的数据多余 30ms, 则不从 packet_buffer_中获取数据。
当要做减速操作,sycn_buffer_中待播放的数据量小于 20ms, 且上次解码的数据量少于 30ms,获取 20ms 数据进行解码。
NetEQ 能很好地跟踪网络抖动,同时在消除抖动时保证延时尽量小,对音频体验提升明显;结合上一篇关于 ,可明显提升音频在弱网环境下的体验。
参考资料:
[1]: WebRTC 语音引擎中 NetEQ 技术的研究
[2]:http://www.gipscorp.alcatrazconsulting.com/files/english/datasheets/NetEQ.pdf
[3]: 基音 - 搜狗百科
[4]:A Review of Time-Scale Modification of Music Signals