视频技术
视频基础
色彩格式
- GRAY 色彩空间:使用8位数据的来表示颜色
- YUV 色彩空间:Y 表示视频的灰阶值、UV 表示色彩度
- RGB 色彩空间:使用三个 8 位无符号整数(0 到 255)表示红色、绿色和蓝色的强度
- HSL 和 HSV 色彩空间
- HSL,就是色相(Hue)、饱和度( Saturation)、亮度( Lightness)
- HSV 是色相(Hue)、饱和度( Saturation)和明度(Value)
色相(H)是色彩的基本属性,就是颜色名称,如红色、黄色等;饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取 0~100% 的数值;明度(V)和亮度(L),同样取 0~100% 的数值
色彩空间
也叫色域,指某种表色模式用所能表达的颜色构成的范围区域
帧率
一秒钟刷新的视频图像帧数
码率
指视频在单位时间内的数据量的大小,一般是 1 秒钟内的数据量,其单位一般是 Kb/s 或者 Mb/s
分辨率
分辨率通常由宽、高与像素点占用的位数组成,计算方式为图像的宽乘以高
Stride
指的是图像存储时内存中每行像素所占用的空间,为了能够快速读取一行像素,我们一般会对内存中的图像实现内存对齐,比如 16 字节对齐
如果读取时没有跳过这些填充的区域,那么渲染出来的图像就完全错了,产生花屏
缩放算法
目前绝大多数图像的缩放还是通过插值算法:都是使用周围已有的像素值通过一定的加权运算得到“插值像素值”
插值算法:
最近邻插值:将目标图像中的目标像素位置,映射到原图像的映射位置;找到原图像中映射位置周围的 4 个像素;取离映射位置最近的像素点的像素值作为目标像素
- 得到的放大图像大概率会出现块状效应,而缩小图像容易出现锯齿
双线性插值:先通过两次线性插值得到两个中间值,然后再通过对这两个中间值进行一次插值得到最终的结果
- 取待插值像素周围的 4 个像素,将这 4 个像素值插值得到中间像素 m 和 n 的像素值,再对这m n插值,得到最终的像素值
- 双三次插值:通过 BiCubic 基函数计算得到待插值像素周围 16 个像素的权重,然后将 16 个像素加权平均就可以得到最终的待插值像素
视频编码
原理
视频由一帧帧图象个构成,而对于每一帧图像,又是划分成一个个块来进行编码
图像一般都是有数据冗余:
- 空间冗余:相邻的块很多时候都有比较明显的相似性
- 时间冗余:两帧图像的变化是比较小的
- 视觉冗余:人眼对图像中的高频信息不敏感
- 信息熵冗余:即文件压缩
帧内预测
在当前编码图像内部已经编码完成的块中找到与将要编码的块相邻的块,再用编码块减去每一个预测块得到一个个残差块。最后,我们取这些算法得到的残差块中像素的绝对值加起来最小的块为预测块
帧又是由一块块数据组成的,对于块有如下规则:
- 宏块大小是 16 x 16,其中亮度块为 16 x 16,色度块为 8 x 8
- 亮度块和色度块是分开独立进行预测的
- 16 x 16 的亮度块可以继续划分成 16 个 4 x 4 的子块
在实际帧内预测的时候就会分为:4 x 4 亮度块的预测、16 x 16 亮度块的预测、8 x 8 色度块的预测
4 x 4 亮度块的 9 种预测模式:
- Vertical 模式:当前编码亮度块的每一列的像素值,都是复制上边已经编码块的最下面那一行的对应位置的像素值
- Horizontal 模式:当前编码亮度块的每一行的像素值,都是复制左边已经编码块的最右边那一列的对应位置的像素值
- DC 模式:当前编码亮度块的每一个像素值,是上边已经编码块的最下面那一行和左边已编码块右边最后一列的所有像素值的平均值
- Diagonal Down-Left 模式:上边块和右上块的像素通过插值得到
- Diagonal Down-Right 模式:过上边块、左边块和左上角对角的像素通过插值得到。如果这三个有一个不存在则该模式无效
- Vertical-Right 模式:通过上边块、左边块以及左上角对角的像素插值得到的。必须要这三个都有效才能使用,否则该模式无效
- Horizontal-Down 模式:通过上边块、左边块以及左上角对角的像素插值得到。必须要这三个都有效才能使用,否则该模式无效
- Vertical-Left 模式:通过上边块和右上块最下面一行的像素通过插值得到
- Horizontal-Up 模式:通过左边块的像素通过插值得到的。如果左边块不存在,则该模式不可用
16 x 16 亮度块总共有4种预测模式。它们分别是Vertical模式,Horizontal模式、DC 模式和Plane 模式
8 x 8 色度块的帧内预测模式:DC 模式、Vertical 模式,Horizontal 模式、Plane 模式
帧间预测
- 帧间预测是在其他已经编码的图像中去寻找参考像素块的
- 帧间预测是可以在多个已经编码的图像里面去寻找参考像素块的
- 帧间预测既可以参考前面的图像也可以参考后面的图像,只参考前面图像的帧我们称为P 帧,参考后面的图像或者前面后面图像都参考的帧叫做 B 帧
帧间预测的块划分类型要多很多。宏块大小 16 x 16,可以划分为 16 x 8,8 x 16, 8 x 8 三种,其中 8 x 8 可以继续划分成 8 x 4,4 x 8 和 4 x 4
通常在 RTC 场景中,P 帧中的所有块都参考同一个参考帧。并且一般会选择当前编码帧的前一帧来作为参考帧,这是由图像运动的局部性原理导致的:自然界的运动一般是连续的,同时在短时间之内的变化相对比较小,在这种参考里,会使用运动矢量来表示参考块在上一帧以及当前帧的相对位置
为了找到参考块,就必须遍历参考帧中的每块图像块,找到残差块值最小的那块图像作为参考块,为了提高搜索参考块的效率,有钻石搜索算法、六边形搜索算法提高搜索效率,这些算法的基本原理还是利用了运动的局部性原理,就是在编码块的附近进行搜索,结果可能是局部最优,而非全局最优,但相比全搜索算法拥有很高的时间效率
由于图像块移动的便宜可能跟图像块大小匹配不上,所以需要通过插值的方式计算这种移动一半的图像,计算方式称之为亚像素插值,插值完成之后就可以继续进行运动搜索了
图像块的运动矢量并不是直接编码到视频流中,而是通过周围相邻块的运动矢量预测一个预测运动矢量,称为 MVP。将当前运动矢量与 MVP 的残差称之为 MVD,然后编码到码流中去,MVP+MVD 就是运动矢量
如果参考块与编码块直接没有移动,那就是一种帧间模式,称为 SKIP 模式,这种模式非常省码率,且压缩效率非常高
变换量化
分离图像块的高频和低频信息:DCT变换使得高频信息在量化的过程中能够比较容易被减少
去除掉大部分高频信息:量化,其实就是一个除法操作。通过除法操作就可以将幅值变小,而高频信息幅值比较小,就比较容易被量化成 0,起到压缩的目的
编解码器
编码标准 | 块大小和划份方式 | 帧内编码 | 帧间编码 | 变换 | 熵编码 | 滤波和后处理 |
---|---|---|---|---|---|---|
H264 | 最大16x16,可划分成8X16、16X8、8X8、4X8、8X4、4X4 | 8个方向模式+planar+DC模式 | 中值MVP | DCT 4X4/8X8 | CAVLC、CABAC | 去块滤波 |
H265 | 最大支持64x64,四叉树划分 | 33个方向模式+planar+DC模式 | Merget模式,AMVP模式 | DCT 4X4/8X8/16X16/32X32,DST 4X4 | CABAC | 去块滤波,SAO滤波 |
AV1 | 最大支持128x128,四叉树划分 | 56个方向模式+3个平滑模式+递归Filterlntra模式+色度CFL模式+调色板模式+帧内块拷贝模式 | OBMC+扭曲运动补偿+高级复合预测+复合帧内预测 | 4x4-64x64正方形+1:2/2:1+1:4/4:1矩形DCT/ADST/flipADST/IDTX | 多符号算术编码 | 去块滤波,CDEF,LR滤波,Frame超分,Film Grain |
H264 和 H265 是需要专利费的,而 VP8 和 VP9 完全免费
在软件编码下的情况下,相同码率下,AV1 清晰度稍好于 H265,而 H264 最差,但是编码耗时则相反,AV1 最高,H265 次之,H264 速度最快
h264
如果范围内帧大部分都是相同的,那刷新第一帧图像后,从第二帧开始,我们只要刷新正中心字母区域的内容即可。这个叫局部更新
- I 帧作为关键帧,仅由帧内预测的宏块组成
- P 帧代表预测帧,通过使用已经编码的帧进行运动估计
- B 帧则可以参考自己前后出现的帧
H264 编码标准中规定,IDR 帧之后的帧不能再参考 IDR 帧之前的帧
压缩后的视频码率也就变得比常规图像更高一些,这个码率的波动通常时高时低,具有可变性,我们一般称之为可变码率(VBR)
封装
容器格式:在容器格式的内部会同时存储音频、视频的数据
- 交错存储:对顺序读取比较友好
- 分区存储:这种对磁盘、网络流不友好,因为需要不断跳跃读取数据
GOP
GOP(图像组):从一个 IDR 帧开始到下一个 IDR 帧的前一帧为止,这个间隔叫做关键帧间隔
GOP 太大,也会导致 IDR 帧距离太大,点播场景时进行视频的 seek 操作就会不方便
Slice
为了并行编码设计的,可以将一帧图像划分成几个 Slice,Slice 之间相互独立,就可以多线程并行对多个 Slice 进行编码
码流结构
Annexb格式:
使用起始码来表示一个编码数据的开始,一种是 4 字节的“00 00 00 01”,一种是 3 字节的“00 00 01”,为了避免编码的数据中有跟起始码一样,需要做一下替换
- “00 00 00”修改为“00 00 03 00”
- “00 00 01”修改为“00 00 03 01”
- “00 00 02”修改为“00 00 03 02”
- “00 00 03”修改为“00 00 03 03”
MP4 格式没有起始码,而是在图像编码数据的开始使用了 4 个字节作为长度标识
帧在码流中实际上是以 Slice 的形式呈现的,H264 的码流主要是由 SPS、PPS、I Slice、P Slice和B Slice 组成的
为了区分这几种数据,使用一个名为NALU的结构来封装数据
视频传输
RTP&RTCP
视频码流被打包成一个个 RTP 包进行发送,同时这个协议还能实现告知当前视频码流是哪种视频编码标准,按照什么速度播放视频等额外的信息
宇段 | 占用位数 | 含义 |
---|---|---|
V | 2 | RTP版本号。当前版本号必须是2 |
P | 1 | 填充标志位。当为1时,表示在RTP包的最后面有若干个填充宇节。注意填充字节只用于字节对齐填充,不是视频数据 |
X | 1 | 扩展标志位。当为1时,表示在RTP默认头部后面会有一个用于自定义的扩展头部 |
СС | 4 | CSRC个数。表示CSRC标识符个数。 |
M | 1 | Marker位。对于不同的有效载荷Marker位含义不同。对于视频,表示当前RTP包是一帧的最后一个包。 |
Payload Type | 7 | 有效载荷类型。用于说明RTP报文中有效载荷的类型,比如说可以是H264、VP8等,用于接收端进行解析RTP的有效载荷 |
Sequence Number | 16 | 序列号。用于表示当前RTP包的序号,每发送一个RTP包,序 列号加1。它是RTP包的标识。接收端可以使用它来告诉发送端RTP包丢失了,要求发送端重传。序列号的初始值是随机的 |
Timestamp | 32 | 时间戳。时问戳反映了该RTP包所属视频帧的采样时间。接收 端使用时间戳来控制播放速度和音视频同步。使用90000Hz的时间基(就是说时间戳的单位是1/90000秒) |
SSRC | 32 | 同步信源。比如说当前RTP用于传输H264视频数据和OPUS音频数据。H264的RTP包使用同一个SSRC,OPUS的RTP包使用同一个SSRC。接收端根据SSRC标识符来区分当前RTP包是H264RTP包还是OPUSRTP包 |
CSRCs | 每一个占用32位,数量由CC指定 | 提供信源,可以有0~15个CSRC。每个CSRC标识了包含在RTP 包有效载荷中的所有提供信源。主要用在混合器中 |
RTCP则是用来辅助 RTP 协议使用的,RTCP 报文有很多种,分别负责不同的功能。常用的报文有发送端报告(SR)、接收端报告(RR)、RTP 反馈报告(RTPFB)等
H264 RTP 打包:
使用不同打包方式取决于打包完成之后的RTP包是否超过1500字节,这个主要跟UDP的MTU有关
带宽预测
基于延迟预测
通过计算一组 RTP 包它们的发送时长和接收时长,来判断当前延时的变化趋势,并根据当前的延时变化趋势来调整更新预测的带宽值
计算延迟:
发送端与接受端分别记录包的发送时间与接受时间,来计算出前后两组包之间的发送时长和接收时长,接受端再通过Transport-CC 报文把统计信息发送给发送端
为了过滤掉噪声,引入一个叫做 Trendline Filter 的滤波器,它保存了 20 个最近的延时数据,通过线性回归的方式平滑掉延迟,避免偶发的噪声导致尖刺得到一个延迟趋势k
网络状态判断:
将延时趋势 k 乘以一个固定增益 4 和包组的数量(包组数量最大是 60)作为当前的修改后延时值。将当前的修改后延时值跟延时阈值进行比较,然后根据比较的结果来判断网络状态(过载、正常、欠载)
带宽调整更新:
通过一个状态机,有三个状态,分别是上升、保持和下降状态。当处于上升状态时,速率控制器需要提升带宽值;当处于下降状态时,需要降低带宽值;当处于保持状态时,则不更新带宽值,如果之前状态机处于下降状态,则更改为保持状态;如果状态机之前处于保持状态,则更改为上升状态;如果是上升状态那就不用变化,基本原理就是要不断逼近极限
基于丢包预测
根据 Transport-CC 报文反馈的信息计算丢包率,然后再根据丢包率的多少直接进行带宽调整更新
- 如果丢包率 < 2 %,认为当前网络状况很好,需要调高带宽值,带宽值等于过去 1 秒钟所有预测得到的带宽值的最小值 * 1.08
- 如果 2% < 丢包率 < 10%,认为当前网络状况正常,不做带宽调整
- 如果丢包率 > 10%,认为网络状况不好,需要降低带宽值,带宽值等于当前预估带宽值 * (1 - 0.5 * 丢包率)
在网络变差的时候,预估带宽会快速的被下调,但是网络变好的时候预估带宽会比较缓慢的上升,这点跟TCP的慢启动原理是一样的
最大带宽探测
在程序刚开始启动的时候使用并在程序运行的过程中进行周期性的探测,基本原理是通过发送探测包与Transport-CC包记录发送时长得到发送端与接受端的最大带宽取最小值
码控算法
用算法来控制编码器输出码流的大小,码控就是为每一帧编码图像选择一个合适的 QP 值的过程
VBR
画面复杂码率就高点,简单码率就低点,比较适合视频点播和短视频场景
CQP
从头到尾每一个画面都是用同一个 QP 值去编码
CRF
画面运动大的时候,它会根据具体算法提高 QP 值;在画面运动小的时候,它会降低 QP 值
CBR
需要设置一个目标码率值给编码器。编码器在编码的时候不管图像画面复杂或简单、运动多或运动少的时候,都尽量使得输出的码率接近设置的目标码率,这种算法适合RTC场景下的视频传输
SVC
指一个码流当中,将数据包分为多层,低层数据包质量较低,高层的数据包解码要依赖低层的数据包,这样针对不同的网络状况,就可以自适应解码不同质量的视频
时域SVC:
通过调整参考帧结构就能实现分层编码。低层的帧不会参考高层的帧,质量低的表现就是帧率低
空域SVC:在一个码流当中分出多个码流出来,第 0 层是一个可以独立解码的码流,只是分辨率是 360P。第 1 层依赖于第 0 层,两个层次加起来是 720P 分辨率的码流...绝大多数的解码器都不支持空域 SVC
卡顿与花屏
卡顿可能的原因:
- 采集或设置的帧率不够时,会造成两帧之间的时间间隔过长
- 性能不够,导致前处理或者编码耗时太长,从而导致卡顿
- 输出码率超过实际网络带宽
- 复杂帧编码后过大或者 I 帧比较大,导致分包太多发送之后网络丢包,可以在编码打包之后、发送之前,加一个平滑发送的模块来平滑地发送视频包。这个模块在 WebRTC 中叫做 PacedSender
- 网络本身丢包
- 重传也没有收到包,导致帧不完整,继而导致没有帧可以解码成功,需要发送关键帧请求报文给发送端,得到一个IDR帧来作为后续解码的参考
花屏可能的原因:
- 帧没接收完整,为了保证接收的帧的完整,有两种方法
- 参考帧不完整,这点跟卡顿是一样的
- 解码格式搞错了
- Stride 问题
封装
将一帧帧视频和音频数据按照对应封装的标准有组织地存放在一个文件里面,并且再存放一些额外的基础信息,比如说分辨率、采样率等信息
FLV
播放的速度还有音视频同步都需要依赖时间戳的值,这个时间戳的单位是 ms。而 RTP 的时间戳单位是 1/90000 秒
有三种类型的 Tag Data 数据:
- Script:存放的是 MetaData 数据,主要包括宽、高、时长、采样率等
- 音频
- 视频
MP4
视频的一帧和音频的一段编码数据称为一个 sample。连续的几个 sample 称之为 chunk,而视频的所有 sample 称为一个视频 track,同样音频的所有 sample 也称为一个音频 track
MP4 主要由最外层的三个 box组成,分别是 File Type box(ftyp box)、Movie box(moov box)和 Media Data box(mdat box),moov box 里面存放了音视频的基本信息和每一个音视频数据的具体位置
- moov box:用来存放 Metadata 信息
- mvhd box:存放文件的基本信息,比如说 MP4 文件的创建时间、时间单位、总时长等信息
- trak box:音频和视频各有一个
- tkhd box:表示 track 的一些基本信息,比如说视频的宽高信息和音频的音量信息等
- mdhd box:里面最重要的一个值就是时间单位 time scale,这个时间单位是 sample 的时间戳的单位,控制播放速度和音视频同步都需要使用到这个值
- hdlr box:包含了 track 的类型信息,表明是音频还是视频 track
- stbl box:存放着可以计算得到每一个 chunk 的偏移地址、每一个 sample 在文件中的地址信息和大小、每一个 sample 的时间戳和每一个视频 IDR 帧的地址信息
- stts box:放置的是每一个 sample 的时长
- stss box 中放置的是哪些 sample 是关键帧
- stsc box 中放置的是 sample 到 chunk 的映射表
- stco box 或 co64 box 中放置着每个 chunk 在文件中的偏移地址
- stsz box 中放置着每一个 sample 的大小
音画同步
PTS 表示的是视频帧的显示时间,DTS 表示的是视频帧的解码时间。对于同一帧来说,DTS 和 PTS 可能是不一样的,主要是因为 B 帧可以同时参考前后的帧造成的
视频同步到音频
这种方式最常用。
音频是指音频按照自己的节奏播放,不需要调节。如果视频相对音频快了的话,就延长当前播放视频帧的时间,以此来减慢视频帧的播放速度。如果视频相对音频慢了的话,就加快视频帧的播放速度,甚至通过丢帧的方式来快速赶上音频
- 如果视频快了。延长正在播放的视频帧的播放时间
- 如果视频慢了。加快视频帧的渲染速度
音频同步到视频
视频按照自己的节奏播放,主要是调节音频的速度,但由于音频调整会导致音调变化,这点人耳是很敏感的,所以不太会使用
音频和视频都做调整同步
音频快了就将音频的速度调低一些或者将视频的速度调高一些,视频快了就将视频的速度调低一些或者将音频的速度调高一些。这种一般在非 RTC 场景不怎么使用