H264视频--x264编解码原理详解

1.一般使用ffmpeg来对音视频数据进行处理,视频编码的底层为x264该链接为FFMPEG的具体使用

2.关于X264的解码原理来自该链接:x264代码剖析

3.关于自己对X264编解码原理的总结。

函数总图如下:

第一阶段:x264的入口函数为main()。main()函数首先调用parse()解析输入的参数,parse()首先调用x264_param_default()为保存参数的x264_param_t结构体赋默认值;然后在一个大循环中通过getopt_long()解析通过命令行传递来的存储在argv[]中的参数,并作相应的设置工作;最后调用select_input()和select_output()完成输入文件格式(yuv,y4m等)和输出文件格式(裸流,mp4,mkv,FLV等)的设置。

第二阶段:然后调用encode()编码YUV数据。encode()首先调用x264_encoder_open()打开编码器;接着在一个循环中反复调用encode_frame()一帧一帧地进行编码;最后在编码完成后调用x264_encoder_close()关闭编码器。encode_frame()则调用x264_encoder_encode()将存储YUV数据的x264_picture_t编码为存储H.264数据的x264_nal_t。

1.main函数分析:主要是parse( argc, argv, ¶m, &opt)和encode( ¶m, &opt )函数。

2.parse()函数功能:

x264_param_default( &defaults );

int c = getopt_long( argc, argv, short_options, long_options, NULL );判断命令行输入是否正确

x264_param_default_preset( param, preset, tune ) < 0 )

int c = getopt_long( argc, argv, short_options, long_options, &long_options_index );解析命令行

output_filename = optarg;

opt->qpfile = x264_fopen( optarg, "rb" );

2.encode()编码YUV为H.264码流,主要流程为:

(1)调用x264_encoder_open()打开H.264编码器;

h = x264_encoder_open( param );

(2)调用x264_encoder_parameters()获得当前的参数集x264_param_t,用于后续步骤中的一些配置;

x264_encoder_parameters( h, param );

(3)调用输出格式(H.264裸流、FLV、mp4等)对应cli_output_t结构体的set_param()方法,为输出格式的封装器设定参数。其中参数源自于上一步骤得到的x264_param_t;

cli_output.set_param( opt->hout, param )

(4)如果不是在每个keyframe前面都增加文件头(SPS/PPS/SEI)的话,就调用x264_encoder_headers()在整个码流前面加输出文件头(SPS/PPS/SEI);将文件头写入到输出文件opt->out.

(i_file = cli_output.write_headers( opt->hout, headers ))

(5)进入一个循环中进行一帧一帧的将YUV编码为H.264,主要大概程序流程如下:

for( ; !b_ctrl_c && (i_frame < param->i_frame_total || !param->i_frame_total); i_frame++ )
    {
        //从输入源中获取1帧YUV数据,存于cli_pic  
        //cli_vid_filter_t可以认为是x264一种“扩展”后的输入源,可以在像素域对图像进行拉伸裁剪等工作。  
        //原本代表输入源的结构体是cli_input_t
		if( filter.get_frame( opt->hin, &cli_pic, i_frame + opt->i_seek ) )
            break;
 
		//初始化x264_picture_t结构体pic
        x264_picture_init( &pic );
 
		//cli_pic到pic
        convert_cli_to_lib_pic( &pic, &cli_pic );
 
		//编码pic中存储的1帧YUV数据
        i_frame_size = encode_frame( h, opt->hout, &pic, &last_dts );	
        
		//释放处理完的YUV数据
        if( filter.release_frame( opt->hin, &cli_pic, i_frame + opt->i_seek ) )
            break;
        
}

(6)编码即将结束的时候,进入另一个循环,输出编码器中缓存的视频帧:

       a)不再传递新的YUV数据,直接调用encode_frame(),将编码器中缓存的剩余几帧数据编码输出出来。
       b)调用print_status()输出一些统计信息。

(7)调用x264_encoder_close()关闭H.264编码器。

encode_fram调用了x264_encoder_encode()函数是其核心部分,具体的H.264视频编码算法均在此模块。

x264_encoder_encode()的流程大致如下:

 x264_frame_pop_unused():获取1个x264_frame_t类型结构体fenc。如果frames.unused[]队列不为空,就调用x264_frame_pop()从unused[]队列取1个现成的;否则就调用x264_frame_new()创建一个新的。

        x264_frame_copy_picture():将输入的图像数据拷贝至fenc。

        x264_frame_expand_border_mod16( h, fenc );对图像进行插值确保图像的长宽均为宏块16的倍数。

        x264_lookahead_put_frame(h,fenc):将fenc放入lookahead.next.list[]队列,等待确定帧类型。

        h->i_frame++;

        x264_lookahead_get_frames(h):通过lookahead分析帧h->frames.current[0]类型。该函数调用了x264_slicetype_decide(),x264_slicetype_analyse()和x264_slicetype_frame_cost()等函数。经过一些列分析之后,最终确定了帧类型信息,并且将帧放入frames.current[]队列。

        x264_frame_shift():从frames.current[]队列取出1帧h->fenc用于编码。是栈还是队列?队列。

        x264_reference_update():更新参考帧队列。frames.reference[]。

       根据fenc帧的类型来做选择:

        x264_reference_reset():如果fenc帧为IDR帧,调用该函数清空参考帧列表。

        x264_reference_hierarchy_reset():如果是非IDR的I帧、P帧、B帧(可做为参考帧),调用该函数。

        x264_reference_build_list():创建参考帧列表list0和list1。

        x264_ratecontrol_start():开启码率控制。

        x264_slice_init():创建 Slice Header。

        x264_slices_write():编码数据(最关键的步骤)。其中调用了x264_slice_write()完成了编码的工作(注意“x264_slices_write()”和“x264_slice_write()”名字差了一个“s”)。

        x264_encoder_frame_end():编码结束后做一些后续处理,例如记录一些统计信息。其中调用了x264_encoder_encapsulate_nals()封装NALU(添加起始码),调用x264_frame_push_unused()将fenc重新放回frames.unused[]队列,并且调用x264_ratecontrol_end()结束码率控制。

调用x264_slices_write()进行编码。该部分是libx264的核心,在后续文章中会详细分析。H.264并没有明确规定一个编解码器如何实现,而是规定了一个编码后的视频比特流的句法和比特流的解码方法,在实现上有较大的灵活性。

   H.264/AVC编码器的功能组成如下图所示,接下来是如何解释该图:

H264编码原理:首先对每一帧图像进行宏块划分,有不同的划分方式,然后对相邻图像进行分组。在这样一组帧中,经过编码后,我们只保留第一帖的完整数据,其它帧都通过参考上一帧计算出来。我们称第一帧为IDR/I帧,其它帧我们称为P/B帧,这样编码后的数据帧组我们称为GOP。一个GOP中可以有很多的I帧,但只能有一个IDR帧。IDR帧也属于I帧。所以把几帧图像分为一组(GOP,也就是一个序列),为防止运动变化,帧数不宜取多。然后将每组内各帧图像定义为三种类型,即I帧、B帧和P帧;定义好之后以I帧做为基础帧,以I帧预测P帧,再由I帧和P帧预测B帧,最后将I帧数据与预测的差值信息(p帧b帧)进行存储和传输。

H264存储原理:在H264中图像以序列为单位进行组织,一个序列是一段图像编码后的数据流,以I帧开始,到下一个I帧结束。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。一个序列就是一段内容差异不太大的图像编码后生成的一串数据流。当运动变化比较少时,一个序列可以很长,因为运动变化少就代表图像画面的内容变动很小,所以就可以编一个I帧,然后一直P帧、B帧了。当运动变化多时,可能一个序列就比较短了,比如就包含一个I帧和3、4个P帧。

I帧:基本帧,帧内编码帧 ,关键帧,完整保留了该图像的所有信息,相比原始数据帧,压缩率7~10;解码时只需要本帧数据就可以完成(因为包含完整画面),不需要其他帧作为参考同时是PB帧的参考帧,I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧;

P帧:前向预测编码帧(只参考前面最靠近它的I帧或P帧)。是在I 帧的基础上取与I 帧的差异,压缩率20;P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面(P帧的预测与重构:P帧是以I帧为参考帧,在I帧中找出P帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加以得到P帧“某点”样值,从而可得到完整的P帧。)。也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据。P帧是I帧后面相隔1~2帧的编码帧;

B帧:双向预测内插编码帧(由前面的I或P帧和后面的P帧来进行预测的)。也就是B帧记录的是本帧与前后帧的差别,B帧的压缩率高,解码时CPU会比较累;压缩率50;要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面(B帧的预测与重构:B帧以前面的I或P帧和后面的P帧为参考帧,“找出”B帧“某点”的预测值和两个运动矢量,并取预测差值和运动矢量传送。接收端根据运动矢量在两个参考帧中“找出(算出)”预测值并与差值求和,得到B帧“某点”样值,从而可得到完整的B帧。)。

 我们若要学习H.264编码算法,最好就是按照帧内预测帧间预测变换与量化熵编码环路滤波的顺序依次学习。除此之外,网络适配层、率失真优化、码率控制也是非常重要的学习点。其中帧内预测与帧间预测处于Analysis模块,变换与量化处于Encode模块,熵编码处于Entropy Encoding模块,而重建过程中的滤波处于Filter模块。

1)帧间和帧内预测(Estimation)
帧内预测编码是H.264区别于其它编码标准的一个重要特征,用来缩减图像的空间冗余.为了提高H.264帧内编码的效率,帧内压缩是生成I帧的算法,所以压缩率不是很高。相对于直接对该帧编码而言,可以大大减小码率.

帧间预测编码利用连续帧中的时间冗余来进行运动估计和补偿.H.264的运动补偿支持以往的视频编码标准中的大部分关键特性,而且灵活地添加了更多的功能,帧间压缩是生成B帧和P帧的算法。它通过比较本帧与相邻帧之间的差异,仅记录本帧与其相邻帧的差值,这样可以大大减少数据量,压缩率较高。

宏块分析函数x264_macroblock_analyse(),该模块主要完成2大任务:一是对于帧内宏块,分析帧内预测模式;二是对于帧间宏块,进行运动估计,分析帧间预测模式。

2)变换(Transform)
在变换方面,H.264使用了基于4×4像素块的类似于DCT的变换,但使用的是以整数为基础的空间变换,不存在反变换.与浮点运算相比,整数DCT变换会引起一些额外的误差,但因为DCT变换后的量化也存在量化误差,与之相比,整数DCT变换引起的量化误差影响并不大.此外,整数DCT变换还具有减少运算量和复杂度,有利于向定点DSP移植的优点.变换后得到的DCT系数是与每个像素都相关的,这些系数代表了被变换数据的基础色调与细节.。

3)量化(Quantization)
量化是在不降低视觉效果的前提下减少图像编码长度,减少视觉恢复中不必要的信息。H264采用标量量化技术,它将每个图像样点编码映射成较小的数值。H.264中可选32种不同的量化步长,这与H.263中有31个量化步长很相似,但是在H.264中,步长是以12.5%的复合率递进的,而不是一个固定常数.在H.264中,变换系数的读出方式也有2种:之字形(Zigzag)扫描和双扫描.大多数情况下使用简单的之字形扫描;双扫描仅用于使用较小量化级的块内,有助于提高编码效率.h.264在DCT变换后对DCT系数进行了量化,量化能有效去除相邻像素间的空间冗余,也就是说会抹去元素数据的部分细节。比较理想的情况是量化抹去人眼无法识别的细节部分,但是在低码率的情况下就会导致原始数据的细节丢失过多。而且,DCT变换时基于块的,即将8x8或者4x4的像素残差进行变换后得到8x8或者4x4DCT系数,此时如果进行了低码率的量化,就会使得相邻两个块的相关性变差,从而出现块效应。.

4)环路滤波(Loop Filter)
为了减轻和消除视频图像中的块效应,通常会使用滤波器对块边界处的像素进行滤波以平滑像素值的突变.同时可以达到降低噪音的效果

5)熵编码
视频编码处理的最后一步就是熵编码,在H.264中采用了2种不同的熵编码方法:通用可变长编码(UVLC)和基于文本的自适应二进制算术编码(CABAC).熵编码即编码过程中按熵原理不丢失任何信息的编码。
 

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值