广义编解码
我们知道,计算机世界就是二进制的组合,网络传输、磁盘保存也都是通过01这两个数字进行传输,那么如何用01表示文字、图片、视频、声音呢?
把文字、图片、视频、声音转化为二进制数字组合的过程就是广义上的编码,把二进制数字重新转化为文字、图片、视频、声音数据的过程就是广义上的解码。
这些二进制数字的转化规则有很多,每一个规则都是依赖大家共同遵守的协议、标准,按照大家共同认定的标准进行编解码才能正常接入庞大的网络世界。
- 从硬件层面说,各类设备无论是通信还是存储,无论有线传输还是无线传输,都需要通过端口来收发二进制,通过寄存器存储二进制,通过与或门运算二进制;
- 从软件层面说,计算机世界就是二进制加上协议构成。
举个例子,如何用二进制表示文字目前就有很多字符集,例如常用的有utf-8、gbk等,这些字符集规定了二进制与字符直接的对应关系,如果想识别一串0101001表示的文字,就需要知道约定的字符集协议。比如我们熟悉的Http请求,就需要Content-Type中指定charset类型,客户端和服务端才可以互相识别request和response,而Http协议本身也是在网络层、传输层、应用层等多种协议上一步步发展而来。
为什么要对视频编码
直接原始采集视频每一帧都是一张完整的图像,而且每一帧图像的每一个像素都是单独表示,这样视频就会非常大,例如一秒钟30帧率的1920*1080视频就有178MB。以目前的网络环境,这样的视频是没办法在网络传输的,必须要经过编码压缩的过程。
编码的根本目的就是为了压缩,这个我们已经都知道了,不过经过设备采集后拿到的原始视频数据已经经过了从摄像头到YUV的编码过程,我们平时所说的再对视频编码、压缩其实是指从YUV转化为其他格式的转码过程。
扩展:如何计算原始图像大小
一幅原始图像由多个像素组成,每一个像素用一个色值表示。
一个色值表示为0xFFFFFF,它以16进制表示RGB分别是FF、FF、FF,其中一个F转化为二进制是1111四位,FF即4*2=8位,也就是一字节,一个RGB像素值就是3个字节。
如果是带有透明度的ARGB呢?ARGB用16进制表示为0xFFFFFFFF,透明度A会单独占一个字节,也就是共占用4字节。
一个像素色值占有34个字节,一张1920*1080的图像就有1920*1080*34个字节,也就是5.93M或者7.91M(通过各种证据发现,GBMK都是描述字节byte,而不是位bit)。
扩展:电视机展示原理思考
说到图像显示,电视机也是展示图像的一种载体,它的原理是什么样的呢?其实也一样,也是通过RGB三基色去组成的。
小时候家里的老式电视机分辨率很低,我们趴在电视机上仔细看的话可以看到每一个像素都是由三个红绿蓝色的小竖管组成,就像这样
电视机在收到信号后同样会做解码,根据屏幕分辨率、视频流分辨率将某像素点匹配到显示器的具体位置,然后根据0xFFFFFF的RGB色值控制每个红绿蓝显像管的亮度,就可以控制该像素点的色值了,等所有像素点显示出来之后显示器的画面就出现了。
视频压缩原理
同样的一段视频,为什么经过编码之后数据量会变小呢?核心原理就是去除冗余数据,把一组像素值简化为一种描述,主要是从这几个维度去处理:
- 空间冗余:一帧图片的像素点在横向和纵向上大多都会有相关性,对于聚集的相近或相同像素点,可以采用描述的方式去替代它们,从而减少数据量。举个极端的例子,假设长为64的数组[fff, fff...fff]表示一张8*8的白图,我们可以用"8,8,fff"去代替,虽然表述最终都是一个意思,但数据量就会大幅下降;
- 时间冗余:连续的视频图像之间一般也会有很大的相关性,这里可以使用运动补偿算法去除掉这些冗余信息,H.264的IPB帧就是利用了此原理;
- 视觉冗余:人类视觉并不能捕捉到所有变化,对于无感知的像素变化可以过滤掉;
- 结构冗余:对于某些特定的图像区域,可以根据纹理推断出像素值的分布规律;
- 知识冗余:对于某些图像可以根据现有的经验找到其规律,从而根据经验推断出大量像素点;
- 信息熵冗余:一串数据并不全是有用的信息,对于附加数据可以简化;
编码方式和编码格式
编码方式有软编码和硬编码两种,软编码就是利用CPU做运算,硬编码就是专业图形处理芯片GPU做处理。
| 优点 | 缺点 | 使用方式 | 58Android实现 | |
|---|---|---|---|---|
| 软编解码 | 兼容性强,而且画面精细 | 速度稍慢,功耗高 | 第三方编码库 | faac/faad+open264 |
| 硬编解码 | 速度快,功耗低 | 兼容性略差 | 系统API调用硬件模块 | MediaCodec |
编码格式非常多,常见的有以下几类,这里就不做比较了。
- 视频:H.26X系列、MPEG系列;
- 音频:MP3、AAC、WMA、AMR、OGG、FLAC等。
H.264编码
H.264起源
行业中视频编解码技术有两套标准,分别是国际电联ITU-T、国际化标准组织ISO/国际电工委员会IEC的MPEG标准。为了创建一个在更低的比特率的情况下依然能够提供良好视频质量的编解码标准,ITU-T与MPEG共同开发了一种高级视频编码,即AVC(Advanced Video Coding)。AVC编码在ITU-T标准中被称为H.264,在MPEG标准中被称为MPEG-4第10部分,也就是MPEG-4P10,不过我们一般习惯称之为H.264。
H.264标准化的第一个版本于2003年5月完成,并在后续持续推出了多个新特性,可以通过配置编码器类型来指定支持的特性。H.264标准与以往标准相比有一系列新的特征,使得它比起以前的编解码器不但能够更有效的进行编码,还能在各种网络环境下的应用中使用。
H.264优势
H.264与之前编码相比有非常多的优点,这里挑几个说明一下(其实是只看懂了这几个):
- 多种优化的运动补偿算法:
- 采用多帧参考方式,以一个GOP(Group of picture,大小可以自行设置)作为一个最小缓存单元,解码PB帧时内部多个I帧都可以参考,而不是之前编码方式中只会参考到之前最后一个I帧;
- 计算运动补偿变化时采用可变的运动区域识别宏块,通过配置最大16x16至最小4x4的块来进行运动估计与运动补偿,能够对图像序列中的运动区域进行更精确的分割;
- 加权运动预测,指在运动补偿时可以使用增加变换参数的权重和偏移的方式,在淡入淡出等特殊提供相当大的编码增益。
- 三种熵编码(含义具体请见后续介绍)方案混用:
- 优先采用压缩率更高、计算复杂度稍高的熵编码方式——基于上下文的二元算数编码(CABAC);
- 对于不支持CABAC的部分,会采用基于上下文的变长编码(CAVLC);
- 对于不支持CABAC也不支持CAVLC的部分则使用指数哥伦布码(Exponential-Golomb,Exp-Golomb)熵编码方案。
- 通过网络抽象层(NAL)内多种单元配置提供了整体健壮性和灵活性,使得相同的视频语法可以适用于多种网络环境中,后续会具体介绍NAL;
- 使用环路滤波(Loop Filter),能够减轻其他视频编解码中普遍存在的块效应;
- 数据分区技术:将重要程度不同的数据分开传输,采用不同优先级保护,在各种丢数据等特殊场景下保证重要数据的数据;
- 冗余切片技术:针对重要的图像区域或整帧图像提供额外的另外一种低质编码数据流,以便主数据流出现解码失败或数据丢失的情况可以有一个兜底容错数据采用。
了解更多请查看维基百科
H.264编码结构
H.264将视频流编码数据分为了两个层面:视频编码层(VCL, Video Coding Layer)和网络抽象层(NAL, Network Abstraction Layer)。
视频编码层VCL
VCL表示有效视频数据的内容,包括核心压缩引擎和片/宏块的语法级别定义,设计目地是尽可能地独立于网络进行高效编码,整个视频图像的编码处理都由VCL负责。
经过VCL编码完成后,一帧图像会被切分为多个片(Slice),Slice数据最终会被封装为NAL单元进行传输。
网络抽象层NAL
NAL则负责格式化数据并提供头信息配置,以保证VCL数据、配置信息适合各种信道和存储介质上的传输。可以这么理解,VCL是视频编码部分,NAL是适应网络传输的封装层。
每个NAL单元分为头信息和内容两部分
- NAL头信息:固定占用1字节
- forbidden_zero_bit:校验位,占用1bit,NAL有效则为0,错误则为1,解码时丢弃此NAL;
- nal_ref_idc:标明该NAL的重要程度,占用2bit,上面所说H.264优势的第5条就是依赖此字段;
- nal_unit_type:NAL类型,占用5bit,即0
31,下面会介绍031类型的含义。
- NAL内容:可变长字节字符串,存储各个类型NALU的视频流等数据,该部分有SODB,RBSP和EBSP三种类型,他们的区别可以看下这个文档 —— 新一代视频压缩编码标准H.264.pdf。
NAL类型介绍
根据 H.264-AVC-ISO_IEC_14496-10.pdf 说明,NAL传输单元有以下类型
| NAL | Type Description | 是否包含VCL |
|---|---|---|
| 0 | 未规定 | - |
| 1 | 非DIR图像中不采用数据划分Slice | Yes |
| 2 | 非DIR图像中A类数据划分Slice | Yes |
| 3 | 非DIR图像中B类数据划分Slice | Yes |
| 4 | 非DIR图像中C类数据划分Slice | Yes |
| 5 | IDR图像的Slice, 编码视频序列访问单元 | Yes |
| 6 | 补充增强信息(SEI) | NO |
| 7 | 序列参数集(SPS) | NO |
| 8 | 图像参数集(PPS) | NO |
| 9 | 分割符(Access Unit Delimiter) | NO |
| 10 | 序列结束符(表示下一帧即IDR) | NO |
| 11 | 流结束符(End of Stream) | NO |
| 12 | 填充数据(Filler) | NO |
| 13..23 | 保留 | - |
| 24..31 | 未规定 | - |
1~5类型包含VCL数据,编码完成后的视频图像全部使用这5个类型存储,其中类型5表示IDR帧,1234对应的几种Slice与是否采用数据划分的上传顺序有关系。1234切片帧可以通过切片头内slice_type参数判断该切片时属于IPB帧的哪种类型,与NAL类型没有关系。
6类型用于补充视频码流之外的额外信息,比如编码器参数、视频版权信息、摄像头参数等,有可能对解码的容错/纠错有帮助,不过它并不是解码过程的必须项,甚至就算传递了该信息也可能会在传输、解码过程中被丢弃。
7~8类型SPS和PPS规定了初始化H.264解码器所需要的信息参数,包括编码所用的profile/level/图像的宽和高/滤波器等,所以解码器如果没有解析到这两个NAL时基本不可能解码成功。码流最开始的两个NAL就需要指定SPS和PPS,第三个NAL则是IDR(类型5),如果是直播视频流的话CDN会在流创建时额外补充SPS和PPS,否则中途加入直播的观众永远无法解码视频流。
9类型表示AU分隔符,两个AU之间包含一个或者多个NALU的集合,它们共同表示一个完整的帧。
10序列结束符表示一个GOP结束(一个GOP通常为2s),下一NAL为开始一个新的GOP,通常是一个IDR帧。
11流结束符在比特流中的最后,接收到流结束符后解码器认为该视频结束。
H.264编码步骤
经过上面的部分,基本概念我们已经了解了,下面介绍下编码的主流程。 H.264内部原理非常庞杂,这里只简单介绍以下几个主步骤,细节上不做过多深入。
1. NAL类型判断
当一帧YUV格式图像送入编码器时首先会通过帧类型预测模块确当该帧时属于什么帧类型,即I帧、P帧、B帧还是IDR帧。
帧类型预测模块通常的做法是:
- 首先对该帧图像做降采样处理降低其像素数,方便后续处理;
- 对降采样后的数据使用场景切换阈值算法判断,如果是场景切换则需要插入I帧,如果是同一场景的细小变化只需要PB帧即可;
- 后续对帧图像进行帧内帧间预测,估算出哪种帧类型压缩率最高;
2.帧内帧间预测
首先VCL层按照宏块大小配置(最大16*16,最小8*8)把一帧图像切分为多个宏块,后面帧间帧内压缩基本都是通过块匹配(Block Matching)比较宏块差异实现的。
- 帧间压缩:运动补偿算法,编码当前宏块时会查找已编码帧图像的所有宏块,找到与当前宏块最类似或者经过运动补偿可推测的一个块,后续只需要编码当前块与类似块的差值即可,无需编码当前块的全部内容;
- 帧内压缩:与帧间压缩类似,都是比较当前块与已编码块的差异,如果存在相似度较高的宏块时就选择一个差异最小的块计算差值,区别主要在于帧间压缩会参考到其他帧,帧内压缩只参考当前帧的宏块。
3. 变换和反变换,量化和反量化
经过帧间帧内压缩后还可以做进一步压缩,宏块内像素分布一般情况下都是杂乱无章的,经过DCT变换后可以基本把数据变换为一个有规律的矩阵,从左上角到右下角基本满足从小到大、从少到多的规律。这时候根据人眼对低频敏感、对高频不敏感这个原理,省掉右下部分的一些值的话,看起来实际上是没什么区别的。
4. 环路滤波
经过上一步变换/量化后,图像可能会出现块效应,即在块的边界会出现不连续的情况,马赛克就是块效应的一种。为了减轻块效应导致的图像视觉效果下滑,需要做一步滤波操作。
去块滤波器分为两种,分别是后置滤波器和环路滤波器。H.264采用的环路滤波(我也不知道为啥叫环路),它与后置滤波器相比有比较大的优势。
5. 熵编码
经过以上处理我们的视频数据已经可以被传输了,然而目前我们的数据仍然是像素值,还需要把它们装华为二进制数据,这样才能在网络中传输,从像素值转化为二进制的过程就是熵编码。
在上面讲H.264协议的优势时我们提到了它采用的编码方式有三种,他们分别用于编码不同类型的片。
简单总结
整个编码过程简单描述一下就是把每一帧图像切分为多个片,每个片切分多个宏块,基于宏块做大量压缩,然后滤波,最后编码为二进制进行传输。
58App中直播视频实现
58直播各模块分别如下,这也是目前业内最主流的直播方案
- 音频格式:mp4a-latm即AAC;
- 视频格式:AVC即H.264;
- 推流:RTMP;
- 拉流:默认是Http-flv,可以配置其他协议。
其中音视频编解码提供了软硬两种方式,硬编码又细分了普通硬编以及使用EGL(OpenGLES渲染API和本地窗口系统之间的一个中间接口层)的硬编码。
参考:
H.264-AVC-ISO_IEC_14496-10.pdf
新一代视频压缩编码标准H.264.pdf