视频编解码基础

1,631 阅读16分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

前言

学习视频编解码需要有一些基础的概念,先了解这些基础的概念,才能更好的了解整个视频编解码的过程,只有了解了编解码的整个过程,才能知道如何去开发,如何实现某一步的功能,它是一个很庞大的体系,所以涉及的一些基础会比较多,高级的做法还会涉及openGL去对视频做处理,所以了解这些基础的理论能更好的进入这个领域。我个人也是自学的,所以有说得不对的地方,希望能在评论区指正。

色彩空间

我们常说的RGB和YUV都是色彩空间中的一种。

RGB就是我们常用到的光的三原色模型(红绿蓝),一般的图片都是RGB,简单来说3种原色通过不同的比例组成不同的颜色。而RGB色彩空间又会细分成不同的色彩空间,比如sRGB、Adobe RGB等,他们所包含的颜色范围不同,这又称为色域。
这些在生活中都有运用到的,比如显示器支持不同的色域,总之这不是这篇文章的重点,只需要简单理解RGB是什么以及我们的图片往往都是使用的RGB。

YUV(也称YCbCr)用于编码和存储更相对于RGB更好,所以一般会用在视频。YUV涉及到HVS(人类视觉系统对色彩空间的感知能力),简单来说就是我这个颜色的信息有这么多,你人是接收不完的,只能接收一部分,那我就把信息弄成人能接收的那部分。

这里主要讲YUV,YUV分为3个数值,Y表示亮度,U表示色调,V表示饱和度,一般Y比较重要,有Y分量就能 显示出图片,不过是黑白。根据采样不同YUV又分为YUV 4:4:4,YUV 4:2:2和YUV 4:2:0。一般我我们的手机摄像头的采样都是使用YUV420,而YUV420又根据格式分为NV12和YU12,简单来说他们都是420的采样,但是数据的排布不同,比如我去网上找的图

54b35546e9cf2dee21881cf1ec16de0.png

YU12中放入Y分量,再放入U分量,最后放入V分量

e8b54dd90457f51ab3a3e9403b4d26d.png

NV12中先放入Y分量,然后U分量和V分量交叉放入

当然除了这两种之外还有YV12和NV21,这里就不扩展了,也是差不多的意思。那在现实中是如何体现不同的?拿Android来举例,虽然摄像头采集都用的YUV420,但是他不同的摄像头都是用的不同的硬件厂商,所以最终会出现的格式也会不同,而Android的解码器MediaCoder是用的NV12(没记错的话),所以在判断数据是YU12的情况下需要转成NV12(这个是可以互转的)

最后说说YUV和RGB的不同,YUV需要存储的数据小于RGB。YUV和RGB可以通过公式进行转换:

Y = 0.299R + 0.587G + 0.114B
U = -0.147
R - 0.289G + 0.436B = 0.492*(B-Y)
V = 0.615R - 0.515G - 0.100B = 0.877(R-Y)

R = Y + 1.140V
G = Y - 0.394
U - 0.581V
B = Y + 2.032
U

视频的播放简单来理解就是每隔多少时间显示一张图片,每张图片都相当于一个帧。当然真实的概念并不完全是这样的,只是这样想会比较容易理解。

1秒播放30张图片我们称为1秒30帧,1秒播放60张图片我我们称为1秒60帧,也可以称为60fps。结合生活,我们平常玩游戏觉得卡顿是因为前一段时间的帧率平稳,但是突然帧率变低,所以导致我们感官反馈到卡顿。

视频的帧又分为I帧,P帧和B帧,这当然是很重要的一个概念,为什么要分为3种不同的帧呢?简单来说是为了压缩,当理解每种帧的含义之后,就能知道为什么和这样设计。

I帧
I帧又称为关键帧,它无需参考其它帧,由自己生成,对帧内的数据进行压缩后生成,自己就能显示出图像。

P帧
P帧会向前参考I帧生成,所以没有I帧的情况下仅有P帧的情况下是无法显示图像。

B帧
B帧会向前参考I帧,向后参考B帧生成,所以没有I帧和P帧的情况下B帧无法显示图像。

这是他们的一个大致区别,可能刚这样看还理解不了,没关系,等到下面讲解视频的编码流程的时候就能理解了。

GOP和IDR

这是和帧相关的概念,但不是十分重要的,所以简单了解就行。

GOP全称Group of picture即图像组,每两个I帧之间是一个GOP,所以GOP是包含一个I帧和多个P帧和B帧。那这个概念会在什么地方体现出用处呢?直播,直播的时候,数据传输,你拿到一个GOP后进行播放,如果不是一个GOP,比如你刚开始打开直播的时候是推了P帧给你,你这时候没有I帧,只拿P帧是显示不出完整图片的。你也可以简单理解成I帧之后的多个P帧和B帧依赖于当前I帧,但是他们并不依赖于其它I帧,所以他们形成了一个组。

IDR是一个和刷新相关的概念,它的翻译是立即刷新图像,所以它一定是一个I帧。举个例子(我也不知道这个例子对不对,没涉及过直播相关的),你看直播的时候卡顿了,但是卡顿好的之后会有个跳帧的效果,不会接着卡顿的地方继续播放,其实就是接收到了一个IDR,会把前面没有播放的帧丢掉,直接播放这个I帧。

PTS和DTS

这个也是和帧有关的,但它也和编解码过程有关,它们都是时间戳,PTS表示显示时间戳,它告诉视频要按照这个PTS的顺序去播放,DTS表示解码时间戳,它告诉文件要按照这个DTS的顺序去存储。

这个下面讲编解码流程的时候可能会更容易理解,这里简单来说就是我们一般视频都是一I B B B P 这种帧的顺序去播放,但是编码之后存储的是 I P B B B 这样的顺序。

码率

这是一个数据传输的指标,码率又叫作比特率,即数据传输时单位时间传送的数据位数,一般用kbps、Mbps表示。比特率越高,每秒传送数据就越多,画质就越清晰。

说到Mbps,很多人就知道是什么了,没错,就是我们的宽度,多少兆宽度就是多少Mbps,经过换算8Mbps = 1MB/s,但是这是理想的情况,加上损耗,大概会10Mbps = 1MB/s,看到这个相信大家就都熟悉了,为什么你家网速在电信拉的是100兆,下载的时候却只有10兆每秒,这就是一个换算过程。

那为什么比特率越高,影响画质呢,这其实是和视频文件大小有关,比如你视频文件越大,帧数越高,分辨率越高,那你的视频文件就越大,所以你需要更高的比特率来传输。那如果文件大我比特率低会怎样?会卡顿,你播放这一帧的时候下一帧还没下好,这不就是你平时怒喷网速的原因吗?

视频流

这个对于基础来说也不是主要的概念,简单了解一下就行。一般像视频这种比较大的文件都是流文件,拿图片来做对比就能看出比较明显的差别。视频和图片在磁盘中存储的文件都是压缩过的文件.mp4 ,.png这些。但是图片能变成位图(原始图片)全部放到内存中,但是视频可以吗?当然不行,你把整个视频放到内存中那内存不炸掉,有的人说,一个视频只有几十MB,应该Hold得住啊,你那个是压缩过后的大小,实际的大小后面会说,但是内存是装不下的,所以视频的播放和传输需要用到流的形式。

视频编解码

进入正题了,理解上面的概念会对编解码的过程更容易理解。上面也说了,我们平时存储在本地的视频,是压缩过的。那如果视频不压缩,原始的数据会有多大呢?通过一个计算过程去巩固上面的基础知识。

比如我们视频的分辨率是1920 * 1080,视频是60fps,采样我就算用YUV420,这个视频的时长100秒,取个整数好计算。
我们假设未压缩的情况,先计算1帧的大小(YUV420占用1.5个字节)
1920 * 1080 * 1.5 = 3110400 = 2.96MB ,光一帧就2.96MB,一秒就是177.6MB,整个视频就是17.3GB
你家网速100兆10MB/S,下1秒钟的视频要17秒。压缩过后的就是秒下好,这就是视频为什么要编码。

视频编解码流程

cc619a69f2f51f5fd14c5c7caee8885.png 这是我在网上随便找的图,视频的编解码流程大概就是这样子,假如我们有个mp4文件,mp4文件其实就是一个压缩包,你可以把它的后缀改成zip然后解压,你就能看到里面的编码后的视频文件和编码后的音频文件(可以把编码简单理解成压缩)。

我们的设备在播放mp4文件的时候,会拿到里面的编码后的视频文件和编码后的音频文件,分别用视频解码器和音频解码器进行解码(你可以先简单理解解码器是一个硬件设备),解码后也都是流文件,再进行音视频同步播放,一般是视频配合音频实现同步,这就是视频播放的一个流程。

这个编码后的视频文件的格式一般是H246,H265,MJPG等,编码后的音频格式一般是AAC,MP3等。这时就有人问了,我没有mp4文件,我只有H264文件,能直接播放视频吗?当然能,没声音而已,你可以解压mp4文件,拿里面的.h264文件丢到播放器看看能不能播放。

这个流程没画出前面的部分,比如我们录像的时候,也是视频和音频分开录,由视频编码器和音频编码器分别编码成h264和aac等,然后再进行打包成mp4,avi等格式的文件。

视频编码文件

我这边因为内容比较多,主要只讲视频,音频的话以后单独写一篇文章。

是这样的,我们做任何东西都有一套标准,而视频压缩也是有标准,那这个标准是谁来定的呢,肯定是某某组织定的,谁做得快,谁牛逼,谁来定。所以有两个组织定义了视频编码的规则,一个叫ISO(国际标准组织),另一个叫ITU(国际电传视讯联盟),他们两个比较牛逼,所以他们说得算。

用这两个组织的规范去压缩的视频得到的格式是不同的,解码时也要用相应的规范去解码。ISO主打的是MPEG系列,ITU主打的是H.26X系列。因为我们现实中一般用到H.26X比较多,所以主要讲这个,目前用得比较广泛的是H264和H265,就是我们进行视频编码后会得到一个.h264或者.h265的文件。

h264又称为AVC,他们是同一个东西,h265又称为HEVC。

视频编码流程

视频编码流程指的是如何从原视频数据变成H265文件(编码后文件)的过程。

这个过程中简单分为帧内压缩和帧间压缩。而整个视频编码的核心步骤又分为预测、变化、量化和熵编码 ,这个过程会涉及到一些计算,直接通过它来理解视频编码过程会更为复杂,而先了解大概的视频编码的原理,再去详细的看这些过程,就会更容易看懂每一步做了什么。举个例子,比如“变化”这个过程(我网上随便找张图)

5dec401e1a0edaed64fdeedaafc67f1.png

579058be60538d7a40e3e62625980ec.png

这个数据块做DCT前后的变化,这是会涉及到计算,如果没有视频编码的基础去看这些内容,我感觉意义不大,有了一定的基础再去看才是更好的做法。

那什么是基础,如何才能快速了解视频编码的过程,才是入门最重要的,这就要涉及到一个重要的概念:宏块

上面讲了这个过程中简单分为帧内压缩和帧间压缩,先讲帧内压缩,单独拿一帧来举例,它相当于一张图片,假如的尺寸是19201080,那它就是有19201080个像素,我们可以对它拆分成多个 n x n 的像素块。这个像素块就是宏块。这个过程可能不是很好理解,没关系,我用视频分析工具举例个例子

97f2e7c6759c0d1b48c66727a342207.png

能看到视频被均分成很多块(因为这个工具比较旧没办法定位到I帧,所以先不用管图中显示的S的部分),而宏块又分为很多不同的子块,有4 x 4的,有 8 x 8 的,有 16 x 16的,所以一个视频被划分成多个不同的类型的宏块 ,可以在图中看到当前这一帧,某个类型的宏块的数量,比如这里的16 x 16就有2486个。

这里可以简单扩展一下,划分的越大,压缩的效果就越好,比如16 x 16的效果好过8 x 8的,而我没记错的话,H264最大只能划分成16 x 16,而H265能划分到64 x 64。所以同一个视频,用H265编码出来的结果比H264占用的空间更小。

为什么宏块要划分成不同的类型,比如直接全部16 x 16不行吗?这个直接解释的话很难解释情况他的划分原理,但是我找到一张图,能让人一眼就看出为什么

7d9abd05112ddc34c546f1d0ab09cb6.png

那它是如何对一个宏块进行压缩的呢?
简单来说,相近的两个像素其实差别不大,宏块里面的像素差别都不是很大,所以它只会保留宏块里面第一行的像素,和第一列的像素,然后其它的像素都用这一行一列的像素去预测,而这个预测一共有9种方式。感觉用语言很难解释清楚,我又要借上面那位老哥的另一张图来说了。

1c93a492e490a8c058df32ab585101f.png

这样也能很明显的看出,通过不同的预测方式,我们就能用很少的像素推出这个宏块的其它像素。如果你理解了这里的内容,那你应该就能知道视频压缩的过程是有损压缩还是无损压缩?

大概了解了帧内压缩,我们再讲讲帧间压缩。上面我们说视频帧分为I帧、P帧和B帧,I帧就是关键帧,I帧不会参考其它帧,所以它是会把这一帧全部划分成宏块。P帧就是相似度和I帧在90%以上(实际并不是90%,这个资料我还不敢确定,反正以上就是相似度超过多少),P帧有一部分是I帧的宏块运动后的结果,有一部分是新产生的宏块,所以P帧只保存了新的宏块和部分老宏块运动相关的数据,而B帧的压缩率更高,它完全运动相关的数据。

我上面那张图就是P这么, 看到上面有些宏块显示S,这个就是表示运动的宏块。比如我们也可以用工具来分析运动相关的情况

c973e62a5765a9c16a633cbd8907f55.png

所以帧间的一个编码过程可以简单这样理解(实际肯定不会这样简单),第一帧肯定是I帧,编码后直接输出到文件,下一帧和I帧进行比较,如果相似度很低差别很大,那它就是I帧,继续输出到文件,如果相似度很高,超过多少以上,他就是B帧,B帧不会马上输出,可以简单理解放到一个缓冲区。如果相似度比较高,超过了多少以上,他就是P帧,P帧就会输出,P帧输出之后,B帧会对P帧进行参考,所以是一个双向预测,最后输出B帧。所以编码后的文件里面存的帧的顺序是IPBBB这样的,这个顺序就是DTS,而播放时会按照IBBBP的顺序去播,这个顺序就是PTS

总结

其实这个视频编码的相关的知识涉及的很多,特别是涉及到很多平常没接触过的概念,主要还是要把一些这个领域相关的概念先了解清楚,再去了解大致的一个流程,再去了解某一步操作相对详细的流程,再去看代码会好一点。

而且这个过程中最好是需要通过一些工具自己去分析,会更深刻,包括一些视频分析工具和ffmpeg,我这里因为是讲基础,所以没有举例用ffmpeg来操作,一般看视频参数,修改视频之类的,都能通过ffmpeg去实现,它的功能很强大。但是这一切还是首先需要理解这些概念,才能知道具体去怎么分析。