H264 编码详解(收集转载)
(1) x264_param_default( x264_param_t *param )
作用: 对编码器进行参数设定
cqm:量化表相关信息
csp:
量化表相关信息里的memset( param->cqm_4iy, 16, 16 );
memset( param->cqm_4ic, 16, 16 );
memset( param->cqm_4py, 16, 16 );
memset( param->cqm_4pc, 16, 16 );
memset( param->cqm_8iy, 16, 64 );
memset( param->cqm_8py, 16, 64 );
(2)static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt ) 初始化
1. getopt_long(nargc, nargv, options, long_options, idx) 得到入口地址的向量与方式的选择
2. getopt_internal(nargc, nargv, options) 解析入口地址向量
(3) static int Encode( x264_param_t *param, cli_opt_t *opt )
h->param=param
vui信息主要包括帧率、图像尺寸等信息
x264_sps_init( h->sps, 0, &h->param );
x264_pps_init( h->pps, 0, &h->param, h->sps);
初始化并开辟帧空间
对前一宏块的信息保存,因为是初始化,所以作为第一个宏块的参考,后面会有x264_macroblock_cache_load( h, i_mb_x, i_mb_y );它是将要编码的宏块的周围的宏块的值读进来, 要想得到当前块的预测值,要先知道上面,左面的预测值
初始化cpu对各种分块的参数设定
1. x264_t *x264_encoder_open ( x264_param_t *param ) 这个函数是对不正确的参数进行修改,并对各结构体参数和cabac编码,预测等需要的参数进行初始化
2、p_read_frame( &pic, opt->hin, i_frame + opt->i_seek, param->i_width, param->i_height )
读取一帧,并把这帧设为prev
3. i_file += Encode_frame( h, opt->hout, &pic );进入核码层
核心编码层的总流程图:(x264.c)
1. x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out )对帧进行编码
2. i_size = x264_nal_encode( data, &i_data, 1, &nal[i] )
网络打包编码
3. i_file += p_write_nalu( hout, data, i_size )
把网络包写入到输出文件中去
4. 返回,对下一帧进行编码
下面一页是详细的流程图:
一.帧内详细流程图:
1. x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out )对帧进行编码
1.
x264_frame_t*fenc=x264_frame_get( h->frames.unused );
x264_frame_copy_picture( h, fenc, pic_in );
fenc->i_frame = h->frames.i_input++;
x264_frame_put( h->frames.next, fenc );
x264_frame_init_lowres( h->param.cpu, fenc );//里面包含低象素的扩展,很多for循环,应该是抽头计算和半精度象素的扩展,要认真看
2. 264_slicetype_decide( h );对slice类型的判定,里面也要看一下
3. while( IS_X264_TYPE_B( h->frames.next[bframes]->i_type ) )
bframes++;
x264_frame_put(h->frames.current,x264_frame_get( &h->frames.next[bframes] ) );这主要是因为B帧必须等后面的非B帧编码结束后才能编码,所以把暂时不编的一系列B帧存入队列中,一直到非B帧才取出进行编码,之后再进行前面的B帧编码
do_encode:
4. 建立list0 & list1.我感觉
x264_reference_build_list( h, h->fdec->i_poc, i_slice_type );
比特率控制初始化
x264_ratecontrol_start(h, i_slice_type, h->fenc->i_qpplus1 );
5. 创建slice的头部数据
x264_slice_init( h, i_nal_type, i_slice_type, i_global_qp );
6 i_frame_size = x264_slices_write( h );这是编码的关键了
1. x264_slice_header_write(&h->out.bs,&h->sh,h->i_nal_ref_idc );
2. 一些初始化工作
3. for(mb_xy=h->sh.i_first_mb, i_skip = 0; mb_xy < h->sh.i_last_mb; mb_xy++ )对一个slice中每个宏块进行循环遍历编码,其中const int i_mb_y = mb_xy / h->sps->i_mb_width;和const int i_mb_x = mb_xy % h->sps->i_mb_width;是对宏块位置在slice中的x,y坐标的定位,这个for语句几乎覆盖了整个x264_slices_write()函数
4. x264_macroblock_cache_load( h, i_mb_x, i_mb_y ); 它是将要编码的宏块的周围的宏块的值读进来, 要想得到当前块的预测值,要先知道上面,左面的预测值!
5. *****x264_macroblock_analyse( h );重点。通过一系列的SAD算出最优化方案,例如把I帧16×16的宏块分成16个4×4分别计算SAD和与原16×16SAD比较我感觉,在下面一层再详细分析。
a. x264_mb_analyse_intra( h, &analysis, COST_MAX );我感觉是在一个16×16的SAD,4个8×8的SAD和,16个4×4SAD和中选出最优方式进行,可能我的理解不对,里面的x264_mb_encode_i4x4( h, idx, a->i_qp );i8×8几个函数的跟踪有问题,跟得我都找不到,要仔细看(现在又能跟到了)
这边好像如果是直流分量在这里就进行量化ZIGZAG扫描了,不用等到x264_macroblock_encode( h )再完成了
b. x264_analyse_update_cache( h, &analysis ); 有对色度块的模式选择的计算,好像也有更新信息以为下次的预测作为参考
6. x264_macroblock_encode( h );
a. 判断宏块的类型
b. 根据判断的类型进行DCT,量化,ZIGZAG,并记录当前的模式为下次编码宏块(亚宏块)做参考
ZIGZAG的实现不明白(原来ZIGZAG有宏定义,在上面,现在明白了),反量化和IDCT的过程跟不进去,应该是汇编了!函数如下:( I 4×4 中 x264_mb_encode_i4x4( h, i, i_qp );)
x264_mb_dequant_4x4( dct4x4, h->dequant4_mf[CQM_4IY], i_qscale );
h->dctf.add4x4_idct( p_dst, i_stride, dct4x4 );
还有,这个函数跟踪不进去,应该是重构图像的反变换吧
h->dctf.add4x4_idct( p_dst, i_stride, dct4x4 );
h->mb.cache.intra4x4_pred_mode[x264_scan8[i]]=x264_mb_pred_mode4x4_fix(i_mode);这个值到底是怎么根据前面的模式改变的,可能是上面两个函数没能更进去所以模糊
c. 对色度块进行编码,QP限制在0-51之间,选定预测模式(DC的话值全为128)
x264_mb_encode_8x8_chroma( h, !IS_INTRA( h->mb.i_type ), i_qp );里面对两个色度信号分别编码,与亮度信号类似
d. 求亮度和色度的cbp,完全不明白是怎么求的,需要解决!现在有点明白,每个比特代表子块是不是全为0,但还没有全部明白,色度块cbp中0x02表示有AC,DC 0x01表示只有DC,
e.利用CBP判断要不要SKIP.,里面还关系到向量预测,明天好好看一下。 其中
h->mb.qp[h->mb.i_mb_xy] = h->mb.i_last_qp;这个为读下一个 qp的保存,不然解码端是读不出下一个 qp的,
关于CBP的理解还存在问题,他的8位比特各个代表的意思还不是十分明确,反正是对DC,AC的编码的选择。185页有介绍(新一代视频压缩标准毕厚杰)
7. 选用CABAC还是CAVLC
CABAC的原理实现没仔细看
8. x264_macroblock_cache_save( h );保存以为下次的预测作为参考
9. 一些收尾工作,为下次宏块作准备(看的比较粗)
一视频编码介绍(H264)
1.1 视频压缩编码的目标
1)保证压缩比例
2)保证恢复的质量
3)易实现,低成本,可靠性
1.2 压缩的出发点(可行性)
1)时间相关性
在一组视频序列中,相邻相邻两帧只有极少的不同之处,这便是时间相关性。
2)空间相关性
在同一帧中,相邻象素之间有很大的相关性,两象素越近,侧相关性越强。
根据采用的信源的模型分类:
1)基于波形的编码
如果采用“一幅图像由许多象素构成”的信源模型,这种信源模型的参数就是象素的亮度和色度的幅度值,对这些参数进行编码的技术即为基于波形编码。
2)基于内容的编码
如果采用一个分量有几个物体构成的信源模型,这种信源模型的参数事各个物体的形状,纹理,运动,对这些参数进行编码的技术就是基于内容的编码。
h264应用可分为3个级别:
1)基本档次:(简单版本, 应用面广 , 支持帧内和帧间编码,基于可变程度的熵编码.)
应用领域:视频会话,会议电视, 无线通信等实时通信.
2)主要档次:(采用了多项提高图像质量和增加压缩比的技术措施, 支持隔行视频, 支持基于上下文的自适应的算术编码.)
应用领域: 数字广播与数字视频存储
3)扩展档次: 应用领域: 可用于各种网络的视频流传输,视频点播
二视频编码的原理
2.1 一个图像或者一个视频序列进行压缩,产生码流。
对图像的处理即是:帧内预测编码
其预测值P,是由已编码的图像做参考,经运动补偿得到的。预测图像P和当前帧Fn相减,得到两图像的残差值Dn,Dn在经过转换T,量化Q,去处空间冗余,得到系数X,将X重排(使数据更加紧凑),熵编码(加入运动矢量。。。一些图像相关得信息),得到nal数据。
对视频序列的处理:帧间预测编码
预测值P,是由当前片中,己编码的宏块预测得到的(亮度4×4或者16×16预测,色度8×8预测)。当前待处理的块,减去预测值P,得残差值Dn,Dn在经过转换T,量化Q,得到系数X,将X重排(使数据更加紧凑),熵编码,得到nal数据
2.2 场、帧、图像
场:隔行扫描的图像,偶数行成为顶场行。奇数行成为底场行。
所有顶场行称为顶场。所有底场行称为底场.
帧:逐行扫描的图像
图像:场和帧都可认为是图像.
2.3宏块、片
宏块(MB):一个宏块由一个16×16亮度块、一个8×8Cb和一个8×8Cr组成
片(slice):一个图像可以划分成一个或多个片,一个片由一个或多个宏块组成。
三H264结构和应用
H.264从框架结构上将NAL与VCL分离,主要有两个目的:
其一,可以定义VCL视频压缩处理与NAL网络传输机制的接口,这样允许视频编码层VCL的设计可以在不同的处理器平台进行移植,而与NAL层的数据封装格式无关;
其二,VCL和NAL都被设计成工作于不同的传输环境,异构的网络环境并不需要对VCL比特流进行重构和重编码。
3.1 H264的编码格式
h264的功能分为两层,视频编码层(VCL)和网络提取层(NAL)
VCL功能是进行视频编解码,包括运动补偿预测,变换编码和熵编码等功能;
NAL用于采用适当的格式对VCL视频数据进行封装打包
1)VCL数据即被压缩编码后的视频数据序列。
在VCL数据要封装到NAL单元中之后,才可以用来传输或存储。
2)NAL单元格式
NAL单元由1字节的头,3个定长的字段和一个字节数不定的编码段组成。
头标的语法:NALU类型(5bit)、重要性指示位(2bit)、禁止位(1bit)。
NALU类型:1~12由H.264使用,24~31由H.264以外的应用使用。
重要性指示:标志该NAL单元用于重建时的重要性,值越大,越重要。
禁止位:网络发现NAL单元有比特错误时可设置该比特为1,以便接收方丢掉该单元
Nal头 |
Rbsp |
Nal头 |
Rbsp |
Nal头 |
Rbsp |
(1)NAL Units:视频数据封装在整数字节的NALU中,它的第一个字节标志该单元中数据的类型。H.264定义了两种封装格式。基于包交换的网络(如H.323系统)可以使用RTP封装格式封装NALU。而另外一些系统可能要求将NALU作为顺序比特流传送,为此H.264定义了一种比特流格式的传输机制,使用start_code_prefix将NALU封装起来,从而确定NAL边界。
(2)参数集:以往视频编解码标准中GOB\GOP\图像等头信息是至关重要的,包含这些信息的包的丢失常导致与这些信息相关的图像不能解码。为此H.264将这些很少变化并且对大量VCL NALU起作用的信息放在参数集中传送。参数集分为两种,即序列参数集和图像参数集。为适应多种网络环境,参数集可以带内传送,也可以采用带外方式传送。
序列的参数集(SPS):包括了一个图像序列的所有信息,
图像的参数集(PPS):包括了一个图像所有片的信息。
3.2 H264的网络传输
H.264能够在基于RTP/UDP/IP、H.323/M、MPEG-2传输和H.320协议的网络中使用
H.264的RTP封装参考RFC 3550,载荷类型(PT)域未作规定
3.3数据的划分
通常情况下,一个宏块的数据是存放在一起而组成片的,数据划分使得一个片中的宏块数据重新组合,把宏块语义相关的数据组成一个划分,由划分来组装片。在H.264中有三种不同的数据划分。
(1)头信息划分:包含片中宏块的类型,量化参数和运动矢量,是片中最重要的信息。
(2)帧内信息划分:包含帧内CBPs和帧内系数,帧内信息可以阻止错误的蔓延。
(3)帧间信息划分:包含帧间CBPs和帧间系数,通常比前两个划分要大得多。
帧内信息划分结合头信息解出帧内宏块,帧间信息划分结合头信息解出帧间宏块。帧间信息划分的重要性最低,对重同步没有贡献。当使用数据划分时,片中的数据根据其类型被保存到不同的缓存,同时片的大小也要调整,使得片中最大的划分小于MTU尺寸。
解码端若获得所有的划分,就可以完整重构片;解码端若发现帧内信息或帧间信息划分丢失,可用的头信息仍然有很好的错误恢复性能。这是因为宏块类型和宏块的运动矢量含有宏块的基本特征。
3.4灵活的宏块次序(FMO)
通过设置宏块次序映射表(MBAmap)来任意地指配宏块到不同的片组,FMO模式打乱了原宏块顺序,降低了编码效率,增加了时延,但增强了抗误码性能。FMO模式划分图像的模式各种各样,重要的有棋盘模式、矩形模式等。当然FMO模式也可以使一帧中的宏块顺序分割,使得分割后的片的大小小于无线网络的MTU尺寸。经过FMO模式分割后的图像数据分开进行传输,以棋盘模式为例,当一个片组的数据丢失时可用另一个片组的数据(包含丢失宏块的相邻宏块信息)进行错误掩盖。实验数据显示,当丢失率为(视频会议应用时)10%时,经错误掩盖后的图像仍然有很高的质量。
四 H264的网络传输
NAL支持众多基于包的有线/无线通信网络,诸如H.320、MPEG-2和RTP/IP等。但目前,绝大部分的视频应用所采用的网络协议层次是RTP/UDP/IP,因此在下面的描述中主要基于这个传输框架。下面首先分析NAL层的基本处理单元NALU以及它的网络封装、分割和合并的方法。
4.1. NAL单元
每个NAL单元是一个一定语法元素的可变长字节字符串,包括包含一个字节的头信息(用来表示数据类型),以及若干整数字节的负荷数据。一个NAL单元可以携带一个编码片、A/B/C型数据分割或一个序列或图像参数集。
NAL单元按RTP序列号按序传送。其中,T为负荷数据类型,占5bit;R为重要性指示位,占2个bit;最后的F为禁止位,占1bit。具体如下:
(1)NALU类型位
可以表示NALU的32种不同类型特征,类型1~12是H.264定义的,类型24~31是用于H.264以外的,RTP负荷规范使用这其中的一些值来定义包聚合和分裂,其他值为H.264保留。
(2)重要性指示位
用于在重构过程中标记一个NAL单元的重要性,值越大,越重要。值为0表示这个NAL单元没有用于预测,因此可被解码器抛弃而不会有错误扩散;值高于0表示此NAL单元要用于无漂移重构,且值越高,对此NAL单元丢失的影响越大。
(3)禁止位
编码中默认值为0,当网络识别此单元中存在比特错误时,可将其设为1,以便接收方丢掉该单元,主要用于适应不同种类的网络环境(比如有线无线相结合的环境)。例如对于从无线到有线的网关,一边是无线的非IP环境,一边是有线网络的无比特错误的环境。假设一个NAL单元到达无线那边时,校验和检测失败,网关可以选择从NAL流中去掉这个NAL单元,也可以把已知被破坏的NAL单元前传给接收端。在这种情况下,智能的解码器将尝试重构这个NAL单元(已知它可能包含比特错误)。而非智能的解码器将简单地抛弃这个NAL单元。NAL单元结构规定了用于面向分组或用于流的传输子系统的通用格式。在H.320和MPEG-2系统中,NAL单元的流应该在NAL单元边界内,每个NAL单元前加一个3字节的起始前缀码。在分组传输系统中,NAL单元由系统的传输规程确定帧界,因此不需要上述的起始前缀码。一组NAL单元被称为一个接入单元,定界后加上定时信息(SEI),形成基本编码图像。该基本编码图像(PCP)由一组已编码的NAL单元组成,其后是冗余编码图像(RCP),它是PCP同一视频图像的冗余表示,用于解码中PCP丢失情况下恢复信息。如果该编码视频图像是编码视频序列的最后一幅图像,应出现序列NAL单元的end,表示该序列结束。一个图像序列只有一个序列参数组,并被独立解码。如果该编码图像是整个NAL单元流的最后一幅图像,则应出现流的end。
H.264采用上述严格的接入单元,不仅使H.264可自适应于多种网络,而且进一步提高其抗误码能力。序列号的设置可发现丢的是哪一个VCL单元,冗余编码图像使得即使基本编码图像丢失,仍可得到较“粗糙”的图像。
4.2. H.264中的RTP
上面阐述了NAL单元的结构和实现,这里要详细讨论RTP的载荷规范和抗误码性能。RTP可通过发送冗余信息来减少接收端的丢包率,会增加时延,与冗余片不同的是它增加的冗余信息是个别重点信息的备份,适合于非平等保护机制。相应的多媒体传输规范有:
(1)分组复制多次重发,发送端对最重要的比特信息分组进行复制重发,使得保证接收端能至少正确接收到一次,同时接收端要丢弃已经正确接收的分组的多余备份。
(2)基于分组的前向纠错,对被保护的分组进行异或运算,将运算结果作为冗余信息发送到接收方。由于时延,不用于对话型应用,可用于流媒体。
(3)音频冗余编码,可保护包括视频在内的任何数据流。每个分组由头标、载荷以及前一分组的载荷组成,H.264中可与数据分割一起使用。
RTP的封装规范总结如下:
(1)额外开销要少,使MTU尺寸在100~64千字节范围都可以;
(2)易于区分分组的重要性,而不必对分组内的数据解码;
(3)载荷规范应当保证不用解码就可识别由于其他比特丢失而造成的分组不可解码;
(4)支持将NALU分割成多个RTP分组;
(5)支持将多个NALU汇集在一个RTP分组中。
H.264采用了简单打包的方案,即一个RTP分组里放入一个NALU,将NALU(包括同时作为载荷头标的NALU头)放入RTP的载荷中,设置RTP头标值。理想情况下,VCL不会产生超过MTU尺寸的NAL单元,来避免IP层的分拆。在接收端,通过RTP序列信息识别复制包并丢弃,取出有效RTP包里的NAL单元。基本档次和扩展档次允许片的无序解码,这样在抖动缓存中就不必对包重新排序。在使用主档次时(不允许片的乱序),要通过RTP序列信息来对包重新排序,解码顺序号(DON)的概念现正在IETF的讨论中。
存在如下情况,例如当使用内容预编码时,编码器不了解底层网络的MTU大小,将产生许多大于MTU尺寸的NALU。这就需要涉及NALU的分割和合并。
(1)NALU的分割
虽然IP层的分割可以使数据块小于64千字节,但无法在应用层实现保护,从而降低了非平等保护方案的效果。由于UDP数据包小于64千字节,而且一个片的长度对某些应用场合来说太小,所以应用层打包是RTP打包方案的一部分。目前的拆分方案正在IETF的讨论之中,大致具有以下特点:①NALU的分块以按RTP次序号升序传输;②能够标记第一个和最后一个NALU分块;③可以检测丢失的分块。
(2)NALU的合并
一些NALU如SEI、参数集等非常小,将它们合并在一起有利于减少头标开销。现有的两种集合分组:①单一时间集合分组(STAP),按时间戳进行组合,一般用于低时延环境;②多时间集合分组(MTAP),不同时间戳也可以组合,一般用于高时延环境,比如流应用