来源
:Kranky Geek WebRTC 2022

内容整理
:尹文沛

Kranky Geek 是一个关于如何使用 WebRTC 构建实时通信应用的免费开发者大会,成立七年以来,该大会致力于帮助开发者将实时音视频纳入应用。本文介绍 Kranky Geek WebRTC 2022 秋季大会的 9-10 节(共 10 节)内容。
相关系列文章:
目录
  • 应用 FEC 以提升游戏流质量
  • 音频编码的发展
    • Lyra and Satin
    • 展望

应用 FEC 以提升游戏流质量

WebRTC 最常见的用途就是视频会议。通常是传输分辨率为 720p,帧率为 30 fps 的视频流,带宽要求不过几兆而已。当网络状况不太好时,会发生丢包,于是视频就会出现卡顿。你可以通过重传来解决丢包的问题,这样视频流就不会卡顿。但这种情况严重时,会导致视频画面卡顿持续到几秒的量级。当然这样的事情我们每天都会遇到,在视频会议场景中这是可以忍受的。
但是对游戏场景来说,视频传输的要求就大大高于视频会议场景。游戏画面要求很高的分辨率(QHD),码率平均高达 30 Mbit/s,远远高于视频会议视频流的码率。Nvidia 在 chromebook 上支持 120 fps 的高帧率游戏画面传输,这就要求 WebRTC 能够以超低延迟传送高帧率的视频流。而在游戏场景中,卡顿是难以忍受的,想象你在玩一个射击游戏,游戏画面无法实时响应你的外设操作,那你射击的时候会打不中你的目标甚至被敌方玩家射死。
图 1
图 2
让我们来分析通常情况下什么原因会导致卡顿。图 2 是一个传统的收发包时间线,这个图展示了等待重传来恢复包是特别缓慢的。当右边的收端丢失一个包的时候,它必须得发送一个 NACK 反馈给发端,在能完整复原一帧之前需要等待一个完整的 RTT 时间,于是帧延迟就增大了。而在左边的发端,Frame Duration 是可变的,取决于你设置的视频帧率。当你设置的帧率为 120 fps 时,发端每隔 8.3 ms 就需要发送一个新的帧。如果仅使用重传来恢复包,那么要在收端保持和发端一样的帧率 (120 fps),你就需要确保你的网络 RTT 要比 Frame Duration (8.3 ms)(图 2 中的黄色线条)小很多。也许你并不清楚你的网络 RTT 有多差,那让我们来看几个例子。
图 3 RTT 测试
图 3 是集成在 nvidia Gforce 上的网络测试工具,左侧是从我的本机 ping 到本机的服务器,可以看到在带宽大于 50 Mbps 的时候,延迟是 13 ms,在视频会议场景中,30 fps 的视频流的 Frame duration 有 33 ms,但是仍旧比游戏场景中 120 fps 的 Frame duration 8.3 ms 高。情况更坏的是,如果你尝试连接距离较远的服务器,如图 3 右图所示,在带宽大于 50 Mbps 的情况下延迟有 32 ms,远高于 8.3 ms。不是每个用户都能很靠近服务器,所以仅靠重传是无法解决传输高码率高帧率的游戏视频流的。
让我们来了解一下 FEC 是怎么解决这个问题的。FEC 全称为 forward error correction。FEC 通过发送冗余数据或者冗余包以在收端恢复丢失的包。可以认为 FEC 是一个主动的包恢复机制,他主动防止包丢失,而不是出现了丢包再去解决。如图 4 所示,FEC 包与视频数据包一起发送到对端起到保护作用。如此,即使丢失了视频数据包,只要你收到了足够多的视频包和 FEC 包,你就可以恢复出丢失的包而不需要重传。相比重传情况,收端节省了一个 RTT 时间,从而大大减少了视频帧的组装时间,从而降低了帧延迟。
图 4 FEC传输示意图
图5展示了单个 FEC 包如何恢复出单个丢失的视频包。发端将一个完整的视频帧分成 4 个 RTP 包发送到收端,这 4 个包均通过 FEC 编码器,四个包的数据通过异或机制生成一个 FEC 包。然后这 4 个包和 FEC 包一起通过网络发送到收端。而收端仅收到 4 个包时,就会检查出丢失了第 3 个 RTP 包,这时将收到的视频数据包和 FEC 包通过 FEC 解码器,通过异或机制就可以恢复出第 3 个丢失的视频数据包。FEC 率通常设置为 FEC 占视频数据包的百分之多少。在这个例子中,4 个数据包对应一个 FEC 包,所以 FEC 率为 25%。
图 5 FEC 工作流程
但是总体带宽是有限制的。使用 FEC 必定会使传输视频数据包的带宽减小,过度使用 FEC 会导致视频质量下降,但是如果 FEC 率过低则只能恢复有限的丢包。所以就带来了下一个问题,我们应该使用多大的 FEC 率呢?
图 6 FEC 率的选择
仅仅是使用一个固定的 FEC 率是不行的。可能会被短期网络状况误导,以致于生成一个不符合实际网络状况的 FEC 率。最安全的方法就是根据丢包报告来实时调整 FEC 率。即通过收端周期性地反馈丢包情况,发端周期性地调整 FEC 率。图 7 展示了一个如何动态选择 FEC 的流程框图。当然你可以给动态 FEC 控制器的输入多样化,不仅包括丢包情况,还可以有 RTT 时间,可用带宽以及丢包图案(突发丢失,恒定丢包率,随机丢包等)。但是在浏览器中想要做到这么多反馈输入很困难,因为缺乏合适的 API。
图 7 如何选择一个 FEC
FEC 在 WebRTC 中叫做 FlexFEC。Flex 源自于其灵活的  bitmask。这个 bit mask 可以指出这个 FEC 包可以恢复的包序列 x, y, z... 所以当收端收到视频包和 FEC 包时,他能够知道哪些视频包对应哪些特定的 FEC 包。这种方式给发端提供了很大的灵活性,发端可以灵活调整 FEC 率而不需要打破任何现有的协议进行编码,所以命名为 FlexFEC。
图 8 FlexFEC 包头格式
早期的 RFC 草案已经在 libWebrtc 中实现了,但是默认设置是不使用 FEC 的。所以早期的 WebRTC 默认的恢复机制只有重传机制。Nvidia 完善了 libWebRTC 中的 FEC 功能并默认使用 FEC。同时 libWebRTC 可以自定义你自己的 Dynamic FEC Controller 而不是使用其默认的 FEC 编码。当然你也可以选择不同实现的 WebRTC 来部署 FEC 策略。
最后看一眼我们在浏览器上应用 FlexFEC 的一些结果。实验采用 120 fps 的视频流,在 Chrome 109 上进行实验。视频的卡顿显著减少,因为帧的组装时间大幅减少如图 9 所示。NACK 和 PLI 的数量变少。而视频质量的降低在可接受范围内。
图 9 FlexFEC 的实验结果
总结起来,FEC 是重传恢复包机制的一种补充机制,在用户需求低延迟零卡顿的场景下使用十分理想。但是它会占据一部分可用带宽,可能会导致视频质量下降。但是总体来说,服务质量是提高的。
附上演讲视频:

音频编码的发展

如果我们看 WebRTC 近十年的音频编码,那其编码器其实就是 Opus。当然也有 G.711 音频编码,但是 WebRTC 并不采用它。图 10 是 Opus 主页一张很有名的图,是 Opus 和其他音频编码器的比较,证明其具有最佳的编码性能。这张图横坐标是比特率,纵坐标是质量,相同比特率下当然质量越高,编码器性能越佳。
图 10 WebRTC 音频编码
Opus 可以视作两种编码器的组合。SILK 为语音编码的性能与 Opus 曲线前半段相近,而 CELT 是专门为音乐音频编码的编码器,其性能在高码率段与 Opus 相近。SILK 有三种设置,一种为窄频带,一种为宽频带,另外一种为全频带。CELT 也有两种模式,一种是全频带音频,一种是全频带立体声。
Opus 的性能是逐年提升的。在 2017 年,V1.2 在我们的政策中全频带范围达到 14 kbps,音乐音频的频带达到 32kbps。在 2018 年, V1.3 中宽频带达到 9kbps,而 SILK 扩展到 5 kbps。在 2022 年我们有了两个新的编码方式,一个是 Lyra,分别在 2021 和 2022 发布了两个版本。另外一个是在 2021 年由微软推动的 Satin。如图 11 所示,这两个编码器在左下角低码率处(图中红色箭头处)大大提高了编码质量,但是这两个编码器的优化对 CPU 和终端电池造成了极大的负担。
图 11 Lyra 和 Satin

Lyra and Satin

Lyra 是专门为低带宽时设计的音频编码器。从图 12 中可以看到,10 kbps 内 Lyra 的性能都要优于 Opus。当带宽大于 10kbps 的时候,你就可以切换 Opus,因为 Lyra 是一种独立编码仅针对低带宽的场景。LyraV2 相较于 LyraV1 也实现了大的性能提升,LyraV1 的编码算法延迟仍然有 100ms,在 RTC 场景下难以使用,而 LyraV2 仅需要 20ms。
Lyra 已经有人在浏览器中实现了 demo, 你可以尝试在低码率条件下运行该 demo,仍然能感受到听到的音频质量不错。
图 12 Lyra 与 Opus 比较
图 13 Lyra demo 实验结果
Satin 与 Lyra 不同,Satin 可以说是 Opus 的一个扩展。Satin 分为两种模式,低码率和高码率模式,并且可以实现这两种模式的无缝切换。Satin 在不同的帧大小下,维持一个稳定的性能,且延迟仅有 20ms 可以满足 RTC 要求。同时其可以在 6 kbps 下支持超宽频带如图 14 所示,并且性能与 Opus 和 G.711 不相上下。
图 14 Satin 超宽频带
所以我们接下来该怎么做?等待新版本 Opus 1.4?还是像 Google 开发 Lyra 这样重新研发一种新的音频编码方式?还是采用多音频编码策略?
其实不用这么麻烦。首先让我们来看 Opus 的格式,如图 15 所示。Opus 的格式其实是一种容器格式。第一个 bit 描述了剩下包的格式。而且不需要 SDP 协商,因为协商是通过带间通信实现的。
图 15 Opus 包格式
即使你拥有了完美的音频编码技术,丢包是你仍然需要考虑的问题。就像图 16 所示,当发生了丢包,音频波形会中间会出现 20 ms 的空白。我们可以通过伪装该丢包并未发生,这种技术叫做 PLC(packet loss concealment)。可以将该段用白噪声或者不发声片段来代替,不过这已经是 20 年前的方法了。或者像 WebRTC jitter buffer 中的 Neteq 在丢包期间一直重复上一个音频帧,幅度与原始音频帧略有不同。这种方法对 20ms 的丢包间隔有用,但是如果有 60 ms 的丢包间隔,就会失效。最近两年比较新颖的方法,是用 AI 模型根据已有音频波形生成一段新波形来替代丢失的音频轨,已经应用在 Microsoft team 和 Google Duo 的 Neteq 中。
图 16 丢包问题
从另外一个角度考虑,如果发生了丢包,发端可以做些什么?在 WebRTC 中有几种方法可以解决丢包问题,被称为 Audio Resilience。最基本的方法就是丢包发生时,由收端向发端反馈 NACK,开销一般较小,适用于低带宽低延迟零星丢包的网络环境,由 SDP 控制。WebRTC 并没有 NACK 标准,且默认不开启重传。音频丢包也可以使用 FEC 方法恢复,FEC 在上面已经介绍过,不再赘述。
还有一种方法叫做 RED。RED 就是将过去的几个包做一个完全复制,然后一起附在原始包发送后发送。这种方法仅适用于高带宽,高丢包的情况,且开销很大,由可插入的流控制,WebRTC 仅支持部分 Red 标准。
图 17 audo resilence
图 18 丢包恢复方法比较

展望

音频编码未来将会怎样发展呢?我们目前能做的有哪些?
可以考虑:
  1. 使用带有 WASM 的 Lyra
  2. 将 Lyra 整合进 Opus
  3. 使用低开销的冗余策略
  4. 直接使用可插入流
附上演讲视频:
继续阅读
阅读原文