Catalog
  1. 1. 什么是抖动
  2. 2. NetEQ
    1. 2.1. 工作过程
  3. 3. 算法
    1. 3.1. 网络延时
    2. 3.2. 加速
    3. 3.3. 减速
    4. 3.4. 丢包补偿 & 融合
webrtc 音频抖动处理之 NetEQ

什么是抖动

在实时音视频通信中,理想情况下,接受端收到的每个包与上一个包的间隔应该是一致的。比如三个包的到达时间为 t1、t2、t3, 那么 t3 - t2 = t2 - t1 = pack_len_ms (以 ms 为单位的帧长, 后面我们用 IAT 表示到达间隔), 这种情况下,接收端收到的音视频包间隔是均匀的,播放也是非常流畅的。当 IAT != packet_len_ms (IAT 可能大于,也可能小于 pack_len_ms) ,我们就称之为抖动。

#抖动带来的影响

如果发生了抖动,理论上会出现音频接收方播放断断续续,延迟累积,通话体验将会非常差。
抖动是如何产生的
目前研究下来,发现两个因素会造成抖动

  1. 网络波动(图示虚线以上)。
  2. 采集延迟(图示虚线以下)

20201106151324.png

网络波动很好理解,数据包在公网上传输的情况非常复杂多变,所以会有一定范围的波动。采集延迟是如何产生的,这里我发现了一种情况。简单的解释一下,大概可以理解为采集缓冲区和处理缓冲区大小不一致所导致的。由于步调不一致,所以会产生周期性的抖动。比如 P1 的 IAT = 75ms 而 P2 的 IAT = 50ms,都与帧长 60ms 不匹配, 那么接收端会判断为抖动进行处理。

20201106151442.png

NetEQ

NetEQ 就是 webrtc 中用于处理音频抖动的模块。他会根据抖动的情况,对音频包进行播放的加减速、融合等操作,达到低延时和较好的听感。这个过程中,涉及到两个算法,一个是网络延时的计算,一个是抖动延时的计算。根据这两个值的结果,对音频帧进行 DSP 处理, 也就是加速播放、减速播放、融合等操作。

工作过程

NetEQ 模块中有 4 个 buffer, 他们各司其职,共同完成抖动处理。先大概介绍一下这 4 个 buffer,以及其职能。

  1. packet_buffer_ 抖动缓冲区, 从网络收到数据包首先放入这里
  2. decoded_buffer_ 解码缓冲区,从 packet_buffer_ 中取得数据包,解码后放入这里
  3. algorithm_buffer_ 根据抖动情况,对 decoded_buffer_ 中的 pcm 进行 DSP 处理后,放入这里。 其初始大小很小,后期会根据据帧大小扩容。
  4. sync_buffer_ 经过 DSP 处理后的 algorithm_buffer_ 数据最后放入这里,音频播放设备从这里获取数据进行播放

这四个 buffer 中,只有 packet_buffer_ 是以 pack 为单位的队列,剩余三个是单位为 16bit 大小的数组,他们的容量各不相同。

下面图示其工作过程

20201106151609.png

网络收到的数据包经过预处理之后,提取音频数据帧放入 packet_buffer_, 这里以帧作为单位。当音频播放设备来请求音频数据时,首先从 sync_buffer_ 中取数据,如果 sync_buffer_ 中的数据不满足请求的数据大小,那么就从 packet_buffer_ 中提取一个音频包 (如果有的话),进行解码,解码得到的 PCM 字节流放入 decode_buffer_。下一步,将这一帧 PCM 数据 copy 到 algorithm_buffer_ (此前会清空 algorithm_buffer_), 然后根据两个值(抖动延时&网络延时,下面会有介绍)来决定要对这一帧数据进行何种 DSP 处理,这个处理直接在 algorithm_buffer_ 中进行。经过 DSP 处理后的 PCM 数据 copy 到 sync_buffer_,最后满足音频播放设备的数据请求。

算法

下列两个算法,我目前无法理解算法是如何作用的,也就是这个算法的数学原型是如何得出的, 所以我只能给出其计算的过程。

网络延时

20201106153513.png

先解释几个数值的含义

  1. packet_len_ms 表示音频帧长,比如 20ms 、60ms 等等,也就是一个音频包的长度
  2. iat (inter-arrival time)当前包距离上一次收到的包的时间差
  3. iat_delay 当前 iat 与帧长的差值, 可能为负数

接下来的计算需要使用到 iat_delay, 如下图所示,将 iat_delay 压入一个历史记录队列,这个队列仅记录简单的数据包信息和 iat_delay, 同时丢弃超出范围的历史记录。

20201106153529.png

接下来引进一个新的变量,每收到一个包,都要计算一次 relative_delay, 其计算过程如下。

20201106153601.png

遍历 iat_delay 队列,依次将其值进行累加,每累加一次都进行判断,使得结果不能小于 0, 最终得出一个值称为 relative_delay。
接下来,用 relative_delay 值来更新延迟统计直方图。直方图统计 2s 内的网络延迟情况,直方图划分为 2000ms/20ms = 100 个槽, 槽 index 从 0 ~ 99,每个槽记录的是延迟为 index * 20ms 的概率, index 就作为直方图的横坐标。通过 u_idx = relative_delay/20ms, 计算出此次延迟数据应该落在哪个槽里,更新所有槽的概率值 (关于概率是如何计算的这里按下不表,因为我不懂….)

20201106153544.png

前面那么多步骤,最终为了得到一个 target_level 值, 其计算方法如下:

  1. 所有槽的值加起来是 1。 接下来遍历直方图,依次累加每个槽的概率, 直到累计值 >= 0.95, 这意味着,当前 index 代表的延时,可以覆盖 95% 的数据包, 我们将找到的 index 记为 t_index。
  2. 计算 target_level = 1 + t_index * 20 / packet_len_ms

这里计算出的 target_level 就能反映网络延迟的情况。
抖动延时

filtered_current_level_ = level_factor_ * filtered_current_level_ +
(1 - level_factor_) * buffer_size_samples

上面的计算摘自 webrtc 的源码注释,下面大概解释下这几个值。

  1. level_factor_
    a. target_leve <=1 时, level_factor_ = 251
    b. 1< target_leve <=3 时, level_factor_ = 252
    c. 3 < target_leve <=7 时, level_factor_ = 253
    d. 7 < target_leve 时, level_factor_ = 254
    target_level 就是网络延时算法中计算得到的那个结果。
  2. buffer_size_samples 缓冲区(packet_buffer & sync_buffer)中未播放的采样数

值得注意的是, level_factor_ 和 filtered_current_level_ 是 Q8 格式。
buffer_size_samples 是 Q0 格式 (Q格式是 DSP 计算中常用的方法,可参考相关文档)

#DSP 处理

以上所有的步骤,其实并没有对抖动进行任何处理,其所有的工作都是为了进行计算来确定要对音频包进行何种 DSP 处理, 所以 DSP 处理才是用来处理抖动的手段。抖动产生的结果,无非就是数据包没有及时到达,产生了空白的时段,或者数据包提前到达,造成缓冲区的累积。所以我们就经由数字信号处理,对音频数据进行伸缩,尽量使得音频播放流畅,提升听觉感受。这部分涉及到数字信号处理,有点超纲,所以这里仅做功能上的解释,大概了解一下其职能。

加速

当抖动缓冲区数据包较多且有累积趋势时,加速音频包的播放。这是为了尽快消耗数据包,这样可以降低累积延时。下图假设 P2 提前 30ms 到达,如果正常按顺序播放的话,那么过程如上半部分的图,分别在 100、160、220 时刻播放音频包。而实际上,我们可以按照下半部分的思路来处理,由于 P2 提前到达了,并且后续的包都按时到到,那么我们可以稍微压缩一下 P2 的播放时间,比如将其压缩到 30ms, 那后续的包都可以被提前播放,这么一来,音频延时就被减少了 30ms。(图示仅为了说明加速情况,其实现并非如此)

20201106154041.png

减速

当抖动缓冲区数据包延迟到达或迟迟不来,减速当前音频包的播放。这里假设 P2 延迟了 20ms 到达,如果按照正常播放的话,那么播完 P1 后,就会有 20ms 的静默,直到 P2 来了才继续,如果这种情况很多,那么音频就会断断续续。所以,可以对 P1 进行适当的拉伸,比如延长其播放时间 20ms, 那么音频播放时连续的。(图示仅说明减速的原理,并不是实际的处理过程)

20201106154053.png

丢包补偿 & 融合

丢包隐藏的原因是要播放的包还没到,满足一些特定条件时,进行丢包补偿,是通过一些特殊的算法造出一个假的音频包。融合则是丢包补偿后,将下一个正常包与补偿包进行衔接,使得声音平滑过渡。

20201106154103.png

Author: 42
Link: http://blog.ikernel.cn/2020/11/06/%E9%9F%B3%E9%A2%91%E6%8A%96%E5%8A%A8%E5%A4%84%E7%90%86%E4%B9%8BNetEQ/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment