gitbook/攻克视频技术/docs/470072.md

175 lines
17 KiB
Markdown
Raw Permalink Normal View History

2022-09-03 22:05:03 +08:00
# 13SVC如何实现视频编码可伸缩
你好,我是李江。
前面我们用了4节课的时间分别讲述了如何将视频编码码流打包成H264如何预测网络带宽如何做好码控来控制视频发送的速率如何分析视频的花屏和卡顿等问题。基本上循序渐进地将视频传输中最重要的一些知识点都讲解了一遍并对里面几个重要的算法进行了深入的研究。
今天我们再讲述一个视频会议场景中经常会使用的视频编码传输相关的技术——SVC编码也叫做可伸缩视频编码。它的作用是可以实现在一个码流里面包含多个可解码的子码流服务器可以根据接收端的网络状况下发对应码率的码流从而实现可伸缩性。
## 为什么需要SVC
2020年全球爆发新冠疫情很多公司为了员工的安全实行在家办公的政策。视频会议一时成为了工作中必不可少的日常工作活动。很多大型公司可能会出现一次几十、上百个人参加视频会议的情况。对于视频会议技术商来说如何提供几十、上百个人的高质量视频通话技术是一个难题。为什么呢
比如说,我和你两个人进行视频通话,我是发送端,网络非常好,你是接收端,网络比较差。发送端和接收端之间的视频通路如下图所示:
![图片](https://static001.geekbang.org/resource/image/bb/d2/bb477d3a740c1b5a4a9c41e33c1043d2.jpeg?wh=1920x620)
在[带宽预测](https://time.geekbang.org/column/article/467073)这节课里面我们讲过,由于服务器到接收端的网络比较差,那么最后会引起:
* 一组视频RTP包的接收时长很长而一组视频RTP包的发送时长比较小
* 或者发送端的视频RTP包发送给接收端之后网络中丢包率很高。
如果不做带宽预测和码控的话最终接收端看到发送端的画面会非常卡。
当然,我们肯定是会做带宽预测和码控的。遇到这种情况,发送端通过基于延时和基于丢包的带宽预测算法估算出发送端到接收端之间的网络带宽值。得到这个带宽值之后,参考[第11讲](https://time.geekbang.org/column/article/468091)发送端的视频码控算法就会将码率降下来同时码率下降引起QP上升画面质量下降但是流畅性变好不会一直卡死。
这是1对1视频会议场景遇到网络不好时的拥塞控制策略。
想象一下现在有10个人参加视频会议我是主持人也是视频发送端。我们简化一下场景我的画面需要发给其他的9个观众而观众的画面不会发送给其他人。10个人都在自己的家里面办公每个人的网络状况都不一样。有的人网络非常好带宽有100M而有的人网络非常差只有500600k而且还因为使用无线网络丢包率很高。而我的网络比较中等2M带宽有线网络丢包率非常小。
那现在开始视频会议带宽预测算法开始工作预测出我和其他9个人之间的链路带宽有2M、1M、500k、800k等很多个不同的带宽大小。假设忽略音频和其它数据占用的码率所有的带宽都设置给视频的话那请问现在设置给视频码控算法的目标码率应该是多少
![图片](https://static001.geekbang.org/resource/image/d5/c5/d51443baf475861fbbc96ddf28ec10c5.jpeg?wh=1920x1080)
设置为2M那么带宽只有500k、800k的人看到的画面就是一直卡死的。那我们选择最小值500k不就可以了吗是的选择最小值500k那么所有人都可以流畅地看到画面。可是画面非常糊质量很差。
想象一下如果100人参加视频会议99个人的网络带宽是100M一个人带宽是500k最后我们选择500k的视频码率合理吗其他99个人为这一个人的网络不好一直观看质量非常差的画面。这太不公平了。有什么办法能很好地解决这个问题呢这就是今天的主角——SVC。
## 什么是SVC
SVC是指一个码流当中我们可以分成好几层。比如说分成三层
* 第0层是最底层可以独立进行编解码不依赖第1层和第2层
* 第1层编解码依赖于第0层但是不依赖于第2层
* 第2层的编解码需要依赖于第0层和第1层
并且,**第0层质量最低第0层加第1层次之三层加在一起的时候质量最高。**注意这里的质量不是直接指的画面质量,而是帧率、分辨率的高低所代表的质量。
这样分层有什么好处呢好处就是我们编码一个码流可以组合出好几个不同的可解码码流出来。比如说上面三层SVC的例子第0层就是一个可以独立解码的码流第0层加上第1层也是一个可以独立解码的码流第0层加上第1层和第2层也是一个可以解码的码流。
对于网络差的人服务器给他转发第0层码流对应的RTP包对于网络中等的人来说服务器给他转发第0层加第1层码流对应的RTP包对于网络很好的人服务器给他直接转发所有层码流的RTP包。这样是不是就对大家都比较公平了。那具体怎么实现SVC分层编码呢服务器又怎么转发呢这里我给出我的思路可供你参考。
## SVC的分类
根据是在帧率上做SVC还是在分辨率上做SVC我们可以将SVC分为时域SVC和空域SVC两种。接下来我们逐一看一下。
### 时域SVC
首先第一种SVC分层编码方式是我们可以在帧率上做SVC这种SVC称之为时域SVC。
帧率上做SVC是什么意思呢我们在前面的课中讲过一般我们在RTC场景中选择使用连续参考的参考结构来做编码。如下图所示
![图片](https://static001.geekbang.org/resource/image/80/f3/801bc1fa0198e04020b8ef14803cb9f3.jpeg?wh=1920x363)
这种参考结构非常简单,但是有一个很大的问题就是只要有一帧被丢弃或不完整,就会导致后面的帧都不能解码,强行解码就会出现花屏(可以参考[第07讲](https://time.geekbang.org/column/article/463775)和[第12讲](https://time.geekbang.org/column/article/469197))。
因此,如果是这种编码参考结构的话,就没有可伸缩性了。也就会产生前面多人视频会议的问题。我们把参考帧结构稍微换一下,隔一帧参考一帧,变成一个两层的结构,就可以解决连续参考的问题,如下图所示:
![图片](https://static001.geekbang.org/resource/image/8c/5a/8c345746fc50cc31ac9305981e5f9e5a.jpeg?wh=1920x497)
在图中帧0是I帧不需要参考且是第0层的帧。帧1是P帧参考帧0且是第1层的帧。帧2是P帧参考帧0不参考帧1是第0层的帧。帧3是P帧参考帧2是第1层的帧。一直用这种模式不断地循环下去。
下面我们再来看一个三层时域SVC编码的参考帧结构如下图所示
![图片](https://static001.geekbang.org/resource/image/e0/d3/e0dd56dda75da348a203df34633a69d3.jpeg?wh=1920x606)
在图中帧0是I帧不需要参考是第0层的帧。帧1是P帧参考帧0与两层时域SVC不同它是第2层的帧。帧2是P帧参考帧0不参考帧1是第1层的帧。帧3是P帧参考帧1是第2层的帧。帧4是P帧参考帧0是第0层的帧帧5是P帧参考帧4是第2层的帧。不断按照这个模式循环下去。
这个就是时域SVC编码。它的优点是什么呢**它通过调整参考帧结构就能实现分层编码。低层的帧不会参考高层的帧。**如果我们丢弃高层的帧,低层的帧也是可以顺利地完成解码而不会出现花屏的,只是帧率会降低。但是相比连续参考结构中丢失一帧就直接卡死的体验要好很多。
同时,因为只需要调整一下参考结构,本身常用的编码标准都支持这种参考帧选择的方式,是符合常规标准的。因此,解码器都支持,没有兼容性问题。
但是它也有缺点。我们在[第07讲](https://time.geekbang.org/column/article/463775)中提到过,一般自然运动是连续的,选择前一帧作为参考帧一般压缩率会比较高,因为前后相邻的两帧很相似。**而时域SVC这种跨帧参考的方式会使得压缩率有一定的下降。**两层SVC编码效率大概下降10%三层大概下降15%。
### 空域SVC
下面我们介绍另一种SVC编码空域SVC。**空域SVC是在分辨率上做分层。**比如说我们现在需要编码一个720P的视频。我们分成两层第0层是360P的分辨率第0层加第1层是720P的分辨率。如下图所示
![图片](https://static001.geekbang.org/resource/image/d3/79/d35b23ccaa472b80f32cc20944229679.jpeg?wh=1920x669)
**空域SVC的优点也是我们可以在一个码流当中分出多个码流出来。**比如说两层空域SVC第0层是一个可以独立解码的码流只是分辨率是360P。第1层依赖于第0层两个层次加起来是720P分辨率的码流。每个不同的分辨率都对应不同的码率。因此也可以用来解决多人视频会议的问题只是丢弃了高层次的层之后分辨率会变小。
但是我必须要说明一下,**H264、H265、VP8这些常用的编码标准除了扩展都是不支持空域SVC的。**因此市面上的绝大多数的解码器也都不支持空域SVC这种一个码流里面含有多种分辨率的视频码流解码。所以现在很少会使用空域SVC也很少有编码器实现空域SVC。并且这种多分辨率的空域SVC相比多个编码器编码不同分辨率的方式在压缩率上也没有多少优势而且还不符合常规的标准。
因此在WebRTC中直接使用多个编码器编码多种分辨率的方式代替空域SVC。
![图片](https://static001.geekbang.org/resource/image/68/21/688080e0f0bbb7447e04231cd44ebc21.jpeg?wh=1920x621)
所以我们接下来不会对空域SVC展开讨论。你可以当作是一个知识点了解一下就可以。
## 时域SVC如何实现可伸缩
下面我们再来看一下时域SVC如何做到给不同带宽的接收端转发不同帧率和码率的视频流。当然这个只是我的一些经验之谈。你可以参考一下。
首先我们需要一些字段来描述码流中当前帧的层号、帧序号等SVC信息。因为这些字段只有在编码器编码的时候才知道。我们需要在编码出来一帧之后在RTP包里面打包上这些信息发送给服务器和接收端。为什么需要告诉服务器和接收端呢我们先来讲讲服务器如何根据网络情况做分层转发策略。
一般来说,视频会议使用如下的架构做视频数据转发。
![图片](https://static001.geekbang.org/resource/image/f7/c9/f74ae2e58c41e5fdba50d29a35096bc9.jpeg?wh=1920x1080)
服务器到接收端的链路上,服务器是发送端,在服务器上也需要做带宽预测,预测算法是一样的(可以参考[第10讲](https://time.geekbang.org/column/article/467073))。
服务器会预测得到每一个接收端和服务器之间链路的带宽值。发送端发送RTP包到服务器服务器需要通过计算RTP包的大小和当前RTP包所属的帧属于哪一层得到每一层对应的码率。这样服务器在转发的时候就可以根据到接收端之间链路的带宽值和对应的每一层的码率来选择到底转发几层。
比如说视频的码率是2M时域SVC编码总共是3层总帧率是24fps。第一层帧率是6fps码率是500k第二层帧率是6fps码率是500k第三层帧率是12fps码率是1M这里假设码率按帧数平均分配
假设某一个接收端只有600k那服务器就只转发第一层给它第二层第三层不转发。另一个接收端有1.5M那我们就转发第一层和第二层给它而第三层不转发。还有一个接收端是10M的带宽我们就转发一二三层给它。这就是时域SVC的服务器转发逻辑。
这个有一个重要的点就是服务器如何知道每一个RTP包对应帧所在的层号以及接收端如何知道当前帧可不可以解码因为接收端是不知道服务器到底给自己转发几层的码流的。
这里我们可以参考VP8编码的RTP协议标准。**VP8的RTP协议在RTP头和VP8码流数据的中间还有一个RTP描述头这个描述头主要用来放帧号、层号等信息的。**具体如下图所示:
![图片](https://static001.geekbang.org/resource/image/62/fc/62908b385ab2637b92f80f67192005fc.jpeg?wh=1766x710 "图片来源VP8的RTP文档")
其中,几个重要的字段的解释如下:
* I占1位表示有没有PictureID字段为1表示有
* L占1位表示有没有TL0PICIDX字段为1表示有
* T占1位表示有没有Tid和Y字段为1表示有
* M占1位表示PictureID字段占7位还是15位为1表示占15位
* PictureID占7位或者15位表示帧序号
* Tid占2位表示层号
* TL0PICIDX占8位表示当前帧所属的SVC单元每过一个Tid为0的帧 TL0PICIDX加1
* Y占1位表示当前帧是不是只参考Tid=0的帧。
服务器可以从RTP描述头得到RTP包对应的层号。这样服务器就可以通过RTP的层号和RTP的包大小来估算每一层的码率了。
而接收端可以根据帧号、层号和层同步标志位等信息来判断当前帧是不是可以解码,而不用去解码视频码流。
![图片](https://static001.geekbang.org/resource/image/a7/e6/a7d19637f8d089bd5b500d6b279ab3e6.jpeg?wh=1920x791)
从上图我们可以看到:
* 帧0是IDR帧只要完整了就一定可以解码
* 帧1是P帧由于它的Y标志位为1代表它只参考了同一个TL0PICIDX中Tid=0的帧也就是帧0因此只要帧0可解帧1就可以解码
* 帧2判断逻辑同帧1只要帧0可解帧2就可以解码不依赖于帧1是不是可解
* 帧3也是P帧但是由于它的Y=0代表它不是只参考了Tid=0的帧因此只有同一个TL0PICIDX中前面的帧都可解了才认为是可解的也就是说只有帧0、帧1、帧2都可解它才可解这里注意一下因为帧3可以多参考它可以同时参考帧1和帧2只是图中没有画出来
* 帧4是P帧但是它的Tid=0因此它只参考前面的帧0所以只要TL0PICIDX-1的Tid=0的帧可以解码它就可以解码。也就是帧0可以解码帧4就可以解码
* 对于帧5判断同帧1帧6判断同帧2帧7判断同帧3一直循环下去
我们可以看到帧1、帧3丢弃了的话并不影响帧0和帧2的可解码性判断。帧1、帧2、帧3都丢失了也不会影响帧4的可解码性的判断。因此**我们的服务器就可以通过丢层的方式来实现对不同带宽的接收端下发不同帧率码率的码流了。**
上面是VP8的时域SVC的RTP协议。那H264呢H264其实在标准的附录G直接定义了SVC的相关字段也就是说在H264的编码码流里面就可以有SVC信息。如下图所示
![图片](https://static001.geekbang.org/resource/image/80/eb/8051abb8d14e0a9f95abc81eb7c8e9eb.jpeg?wh=1920x928 "图片来源H264标准文档")
但是由于是附录G的内容实现这一部分的解码器很少。因此不推荐使用这种方式传递SVC的相关信息。因为这种码流结构很多常规的H264解码器是不支持解码的通用性不好所以**我们建议使用RTP扩展头来传输时域SVC的信息。**
我们可以直接使用VP8的RTP描述头的格式且编码码流还是保持常规标准的码流就可以这样常规的H264解码器都能解码。服务器和接收端直接从RTP扩展头里面读取相关的SVC信息就可以了。而对于SVC编码openh264已经实现了最大4层的时域SVC。你可以直接使用openh264就可以实现SVC编码了。
## 小结
总结一下今天我们通过多人视频会议如何设置编码码率的问题引出了为什么需要使用SVC编码。SVC编码可以在一个码流当中包含多个可以解码的子码流这样服务器就可以根据接收端的带宽转发合适码率的子码流给接收端从而达到可伸缩性。
并且我们还介绍了两种类型的SVC主要包括时域SVC和空域SVC。在之后我们对服务器如何做时域SVC码流的转发做了详细的介绍。同时我们还讨论了如何在RTP协议里面携带SVC信息用于服务器做转发逻辑和接收端做解码性判断使用。
我们知道服务器会预测得到每一个接收端和服务器之间链路的带宽值并通过计算RTP包的大小和当前RTP包携带的层号得到每一层对应的码率。然后服务器再根据到接收端之间链路的带宽值和对应的每一层的码率来选择到底转发几层。
最后接收端再根据RTP包携带的SVC信息来判断帧组完整之后可不可以解码可以解码才能送解码器不然就不能送去解码防止出现花屏。这样我们就实现了可伸缩编码。
## 思考题
通过前面的学习,你知道哪些弱网对抗手段?
欢迎你在留言区和我分享你的思考和疑惑,你也可以把今天所学分享给身边的朋友,邀请他加入探讨,共同进步。下节课再见。