音视频技术--H.264代码与标准如何对应

简介:
总是有人说自己把 代码 和标准对应不起来。其实是因为你要么不知道标准各个章节讲的什么,要么不知道代码中各个 函数 的功能,或者两者都不知道。今天再以   X264   的帧内 编码 为例让大家体会一下读代码时该如何与标准对应。此贴是帖子 [原创]如何阅读代码 的延续,因此采用的代码与 编译 环境设置与其一样,此处不再赘述。

上贴说过  Encode_frame  函数包含最核心的编码代码,那么我们现在就  F11  进去看看。遇到的第一个函数是  x264_encoder_encode ,再  F11  进去,执行到  x264_reference_update ,它在干什么呢?顾名思义猜测一定是更新帧间参考要用到的一些内存空间,因为我们现在还没有编码,所以  F11  进去后没执行什么操作就出来了。

继续  F10 ,执行到  x264_frame_pop_unused F11  跟进,然后  F10 ,发现它走了  x264_frame_new  的分支( x264_frame_pop  分支干什么用的呢?暂时先别管。跟着流程走,管多了就迷茫了), F11  跟进  x264_frame_new  发现通篇都是对变量 结构 体指针  frame  里的成员变量执行  CHECKED_MALLOC ,由此我们可以初步判断它是在为帧结构体分配内存空间。

step out  跳出  x264_frame_pop_unused F10   x264_frame_copy_picture F11  进去读读代码我们就知道这个函数的功能是将待编码图像从  pic_in  复制到  fenc->plane 。继续  F10 ,到了  x264_frame_push ,通过阅读该函数的代码我们知道它的功能是将当前帧结构体从  fenc  移到  h->frames.next  中。后面的函数  x264_frame_init_lowres x264_adaptive_quant_frame x264_encoder_frame_end  都未被执行。既然没被执行,那我们现在暂时就不管它们。

F10   x264_stack_align( x264_slicetype_decide, h ); x264_stack_align  顾名思义无非就是平台优化方面考虑的对齐操作,因此这里我们要关心的是函数  x264_slicetype_decide F11  我们会发现进不到  x264_slicetype_decide  里。怎么办呢?见下面第一个截图,将光标点到  x264_slicetype_decide  上,点鼠标右键选择  go to definition ,然后先在里面的第一行代码下断点(见下面第二个截图),然后再按  F10  就可以进入到  x264_slicetype_decide  函数了。该函数顾名思义是来决定当前  slice  的编码类型的,即到底是  I  片还是   片或  B  片。通过浏览其代码,我们也会发现代码所做也正是这样。

 

 

step out  跳出  x264_slicetype_decide ,继续  F10  执行到  x264_frame_push ,这里实际要执行两个函数,因为  x264_frame_push  的第二个参数是函数  x264_frame_shift ,所以会先执行它。 F11  首先进入的就是  x264_frame_shift ,然后  step out  跳出  x264_frame_shift ,继续  F11  就进入了  x264_frame_push ,通过阅读这两个简短的函数的代码,我们知道它们执行的操作是将当前编码帧结构体从  h->frames.next  移到  h->frames.current

继续  F10 ,又到了一个  x264_frame_shift  函数, F11  进去通过阅读代码我们可以知道该函数的功能将当前帧结构体从  h->frames.current  移到  h->fenc (我有点奇怪,为什么  X264  要这么麻烦地把一个变量移来移去呢?一次搞定不行么?)。继续  F10 ,到了  x264_reference_reset ,其功能顾名思义,也有英文注释,具体有什么用,现在我还不知道,暂时不管吧。

继续  F10 ,到了  x264_reference_build_list ,顾名思义,参考列表构建,在  JM  里叫做参考列表初始化( JM86   对应的函数是  init_lists )。参考列表初始化的作用即构建帧间编码图像所需要用到的参考图像列表。那么如何初始化呢?如果大家记得  H.264  标准的各个章节的功能,那么就该知道  200503  版的  8.2.4  小节正是讲的这部分内容。这样这个函数就与标准的内容对应起来了。至于通过代码是如何实现的,先看懂了标准的这个部分再来读这个函数的代码吧。

继续  F10 ,到了  x264_ratecontrol_start ,顾名思义进行码率控制的一些准备工作。

继续  F10 ,到了  x264_slice_init ,顾名思义片初始化,做了哪些工作呢? F11  进去执行了分支  x264_slice_header_init ,通过浏览其代码,我们发现通篇都是对结构体指针  sh  内的成员变量的赋值操作。后面的  x264_macroblock_slice_init  函数在干什么,大家自己  F11  进去看,看不懂没关系,反正就是给一些变量赋初值嘛。继续  F10 ,到了  bs_init ,顾名思义是对码流相关的变量进行初始化,因为  bs  就是  bit stream  嘛。

继续  F10 ,到了  if( i_nal_type == NAL_SLICE_IDR && h->param.b_repeat_headers ) ,注意前面的英文注释  /* Write SPS and PPS */ ,意思就是这里在向码流中写  SPS   PPS 。这里的三组函数,顾名思义第一组是在写  SEI 、第二组是在写  SPS 、第三组是在写  PPS 。那么如何写码流呢?当然是要遵循语法表了。下面以写  SPS  为例简要说明一下,如果大家记得  H.264  标准的各个章节的功能,那么就该知道  200503  版的  7.3.2.1  小节就是  SPS  语法表。因为  7.3.2.1  规定了  SPS  在码流中的第一个语法元素是  profile_idc ,因此当我们  F11  进入  x264_sps_write  的时候会发现该函数第一行代码正是在写  sps->i_profile_idc ,标准规定  SPS  第二个语法元素是  constraint_set0_flag ,因此该函数的第二行代码就是在写  sps->b_constraint_set0 ,其他同理。这里说的是写的顺序,那么写的方式是什么呢? H.264  中的熵编码方式细分起来有很多,每个语法表的最后一列  descriptor  规定的就是对应的语法元素采用哪种熵编码方法。例如: profile_idc   u(8) ,因此它采用  8  位无符号整数编码; constraint_set0_flag   u(1) ,因此它采用  1  比特无符号整数编码。 各种熵编码方法在  200503   7.2  小节最后都有说明,此处不再赘述。好了,我们知道了各个语法元素采用什么方式编码,自然也就知道了代码中各个熵编码函数对应的是什么编码方式。例如:对  profile_idc  编码采用的是  bs_write  函数,当然这个函数的功能就是无符号熵编码了,对  i_id  编码采用的是  bs_write_ue  函数,当然这个函数的功能就是  ue(v)—— 无符号哥伦布编码。其他同理。其实这些我在帖子 [原创如何读标准和代码 中已经讲过了。

顺便提一下,对码流的读写操作都要依据语法表所定义的语法元素顺序和熵编码类型。上面讲的是编码的具体例子, 解码 的具体例子我以前用  JM  讲过,参考帖子 如何结合标准看JM代码(JM86 。好了,继续  F10 ,到了  x264_slices_write F11  进入,再  F10 ,到了  x264_stack_align( x264_slice_write, h ); 我们关心的是  x264_slice_write ,进入该函数,方法在上面已经说过了。 x264_slice_write  第一个函数为系统函数  memset ,下一个为  x264_nal_start ,其功能看下代码就知道是在设置将要写入码流的  NALU  的第一个字节的值。第二个函数  x264_slice_header_write  顾名思义是在向码流中写入片头。如果大家记得  H.264  标准的各个章节的功能,那么就该知道  200503  版的  7.3.3  小节就是片头的语法表。写码流的过程与上面  SPS  的过程同理,此处不再赘述。

F10  到了  while  循环,顾名思义根据  while  循环的循环条件猜测一下该  while  循环的功能,肯定就是循环对整个图像的每个宏块一次编码了。要验证一下猜测很简单,在  while  循环体的第一行下断点,按一次  F5  就观察一下  mb_xy  变量的值的变化情况。另外还有个信息说明了这一点, h->sh.i_last_mb  变量的值刚好等于待编码图像的总宏块数。

F10  到了  x264_fdec_ filter _row ,顾名思义猜测该函数的功能是去块滤波。如果大家记得  H.264  标准的各个章节的功能,那么就该知道  200503  版的  8.7  小节正是讲的这部分内容。要读懂这个函数的代码就先学习一下  8.7  小节吧。

F10  到了  x264_macroblock_cache_load ,通过浏览代码我们知道是在对一些变量赋值,各个变量的含义顾名思义。这也属于编码前的准备工作。继续  F10  到了  x264_macroblock_analyse ,看见英文注释了吧?不用我们顾名思义就知道它的功能了,是在进行模式选择。 F11  进入该函数。第一个被调用的函数是  x264_ratecontrol_qp ,顾名思义获取当前宏块  QP 。第二个被调用的函数是  x264_mb_analyse_init F11  进去后发现只有非  I  片才进行一些操作,那暂时就不管它。

F10  到了  x264_mb_cache_fenc_satd F11  进去。一开始是个  4*4  的双重循环。我们现在是在对一个宏块进行操作,这里又出现  4*4  的循环,那么很明显了这个双重循环肯定是在计算每个  4*4  的块,下面的  2*2  的双重循环肯定是在计算  8*8  的块。因为宏块的尺寸是  16*16  嘛,宽高分成  4  份不正好是  4*4 ,分成  2  份不正好是  8*8  么?做 视频 的人应该对  4 8 16  等常用的数字敏感。先分析第一个  4*4  的双重循环。注意, for  循环里的  h->pixf.satd   h->pixf.sad  都是函数指针,因此要用  F11  跟进。 h->pixf.satd  的两个输入是  zero   fenc ,跟进之后的函数  pixel_satd_wxh  在计算他们之差,然后作  Hadamard  变换,然后计算  SATD 。由此可以猜测  fenc  里存放的是原始待编码宏块(到底是不是呢?读者自己反回去找到  h->mb.pic.p_fenc[0]  被赋值的地方看看就知道了)。后面代码的功能类似了,不重复叙述。总的来说, x264_mb_cache_fenc_satd  这个函数就是计算原始待编码宏块  4*4   8*8   STAD 。算来做什么?暂时还不知道。

step out ,跳出  x264_mb_cache_fenc_satd  函数,继续  F10 ,到了  x264_mb_analyse_intra F11  进入。 F10  到了  predict_16x16_ mode _available ,顾名思义并结合该函数代码,可以确定它是在检查当前宏块有几种可用的  16*16  帧内 预测 模式。继续  F10  到了  for  循环  for( i = 0; i < i_max; i++ ) ,其循环条件  i_max  是函数  predict_16x16_mode_available  的返回值,那么很显然这个  for  循环是在循环计算可用预测模式了。继续  F10 ,进循环体到了  h->predict_16x16[i_mode] ,这又是个函数指针,顾名思义并结合改函数代码,可以确定它是在取得  16*16  块当前预测模式下的帧内预测块。如果大家记得  H.264  标准的各个章节的功能,那么就该知道  200503  版的  8.3.3  小节正是讲了  16*16  块的各种预测模式下如何进行帧内预测的。要读懂这个函数的代码就先学习一下  8.3.3  小节吧。继续  F10 ,到了  h->pixf.mbcmp[PIXEL_16x16] ,又是个函数指针,其功能大家自己跟进吧。该  for  循环完成后就把  16*16  块的最佳预测模式计算出来并存储起来了。

继续  F10 ,到了帧内  4*4  的预测模式选择部分。 for  循环  for( idx = 0;; idx++ ) idx  是什么?因为这是帧内  4*4  预测,所以我们很自然应该联想到  idx  就应该是  16   4*4  块的编号,这个决定了  16   4*4  块的处理顺序,这个顺序可不是乱来的哦, 200503  版标准 /  6-10  对顺序做了规定。继续  F10 ,到了  x264_mb_predict_intra4x4_mode  顾名思义并结合该函数代码可以确定它是在获得最可能预测模式,如果大家记得  H.264  标准的各个章节的功能,那么就该知道  200503  版的  8.3.1.1  小节正是讲的这部分内容。要读懂这个函数的代码就先学习一下  8.3.1.1  小节吧。继续  F10  到了  predict_4x4_mode_available  跟上面  16*16  块类似,功能顾名思义就不多说了。继续  F10 ,进入第二个  for  循环  for( ; i<i_max; i++ ) ,一看就知道该  for  循环跟上面  16*16  块同理是在计算当前  4*4  块的最佳预测模式。继续  F10 ,进入循环体到了  h->predict_4x4[i_mode]  顾名思义并结合该函数代码可以确定它是在取得当前  4*4  块在当前可用预测模式下的帧内预测块,如果大家记得  H.264  标准的各个章节的功能,那么就该知道  200503  版的  8.3.1.2  小节正是讲的这部分内容。要读懂这个函数的代码就先学习一下  8.3.1.2  小节吧。继续  F10 ,到了  h->pixf.mbcmp[PIXEL_4x4] ,也跟上面  16*16  块类似,功能顾名思义。

继续  F10 ,第二个  for  循环执行完后就把当前  4*4  块的最佳预测模式计算出来并存储起来了,到了函数指针  h->predict_4x4[a->i_predict4x4[idx]] ,很显然是在取得当前  4*4  块的最佳预测模式下的预测块了。算来干什么?从  H.264  帧内宏块编码的原理上我们知道帧内预测要以相邻块的重建值为参考,不先计算预测块,残差从哪里来?不得到残差,又哪里得到重建呢?(所以这里也体现了,读代码前要对编码原理和框架熟悉,否则你咋能明白这里为什么要取得预测块呢?)。继续  F10 ,到了  x264_mb_encode_i4x4 ,顾名思义并联想帧内编码原理和框架,我们猜测它是在进行当前  4*4  块的重建。 F11  进去验证一下我们的猜测是否正确。 x264_mb_encode_i4x4  函数里依次执行了  h->dctf.sub4x4_dct x264_quant_4x4 h->zigzagf.scan_4x4 h->quantf.dequant_4x4 h->dctf.add4x4_idct ,各函数功能顾名思义,的确验证了我们对  x264_mb_encode_i4x4  这个函数的功能的猜测。那么这些函数为什么要以这些顺序调用呢?因为编码原理和框架就是这样(这也再次体现了,读代码前要对编码原理和框架熟悉)。

step out ,跳出  x264_mb_analyse_intra  函数,继续  F10 ,到了  x264_intra_rd F11  跟进。继续  F10 ,到了函数  x264_analyse_update_cache ,顾名思义无法猜测其功能, F11  跟进之后发现它只调用了一个函数  x264_mb_analyse_intra_chroma ,这个函数又是什么功能呢?留给读者自己去跟进吧。 step out ,跳出  x264_analyse_update_cache  函数,继续  F10 ,到了  x264_rd_cost_mb ,顾名思义猜测是进行  RDO  模式选择。这种方法的失真测度通常是使用  SSD ,即原始像素与重建像素的误差平方和。那么如果我们对  x264_rd_cost_mb  的功能猜测正确,其函数中必然有编码宏块的代码和计算  SSD  的代码。 F11  跟进去验证我们的猜测, x264_rd_cost_mb  里的确调用了  x264_macroblock_encode  ssd_mb 。这两个函数是否是在执行编码和计算  SSD  的功能呢?留给读者自己去验证吧。提醒一句, X264  在这里用的失真测度不仅仅是  SSD ,另外还有什么成分,读者自己去跟踪  ssd_mb  函数。 x264_rd_cost_mb  函数最后执行的函数是  x264_macroblock_size_cavlc ,顾名思义是在对当前宏块进行熵编码了。为什么要熵编码,因为  RDO  的率失真准则中要用到编码比特数啊。

step out ,跳出  x264_rd_cost_mb  函数,后面的代码不说大家也知道了。 step out ,跳出  x264_intra_rd  函数,该函数下面的  6  行代码(见下图)的功能大家得弄清楚。因为算了这么多模式,这么多代价,最后编码到底选哪个模式呢?答案就这里了。

 

step out ,跳出  x264_macroblock_analyse  函数,到了  x264_macroblock_encode ,顾名思义并结合编码流程可以确定这里调用这个函数就是在用最终选定的那个最优的模式对当前宏块进行实际编码了。继续  F10 ,到了  x264_bitstream_check_buffer ,顾名思义猜测是进行写码流前的一些准备工作。继续  F10 ,到了  x264_macroblock_write_cavlc ,顾名思义并联想编码流程,很明显是在将最后的编码结果写入码流了。

至此,一个宏块帧内编码的过程就剖析完了。相信大家看完这么长的帖子之后,应该对我以前提出的学习建议中的两点有了深刻体会:1、读代码前一定要熟悉编码原理和框架;2、弄清楚标准各个章节讲的什么内容。当然这也是怎么看标准,怎么用标准的问题——先很粗略地了解各个章节是讲的什么,等到需要详细了解其内容时候再去细读相关章节。当然,语言功底在读代码过程中也是必须的,否则像函数指针这些东西你都搞不清楚怎么回事。





本文转自 fanxiaojun 51CTO博客,原文链接:http://blog.51cto.com/2343338/1064702,如需转载请自行联系原作者

相关文章
|
16天前
|
编解码 监控 网络协议
如何使用FFmpeg实现RTSP推送H.264和H.265(HEVC)编码视频
本文详细介绍了如何使用FFmpeg实现RTSP推送H.264和H.265(HEVC)编码视频。内容涵盖环境搭建、编码配置、服务器端与客户端实现等方面,适合视频监控系统和直播平台等应用场景。通过具体命令和示例代码,帮助读者快速上手并实现目标。
58 6
|
编解码 开发工具 Android开发
Android平台如何实现第三方模块编码后(H.264/H.265/AAC/PCMA/PCMU)数据实时预览播放
Android平台如何实现第三方模块编码后(H.264/H.265/AAC/PCMA/PCMU)数据实时预览播放
102 0
|
编解码 Android开发 数据安全/隐私保护
Android平台如何实现外部编码后(H.264/H.265)数据实时预览播放
我们在对接开发者的时候,遇到这样的诉求:除了正常的RTMP、RTSP直播播放外,有些硬件设备输出编码后(H.264/H.265)的数据,比如无人机或类似硬件产品,回调出来的H.264/H.265数据,除了正常转推到RTMP、轻量级RTSP服务或GB28181外,还需要本地预览甚至重新对数据做二次处理,基于这样的场景诉求,我们开发了外部编码后数据实时预览播放模块。
|
编解码 监控 网络协议
Android平台GB28181设备接入侧(编码前|编码后|RTSP|RTMP)支持功能浅析
在之前,我有写过Android平台GB28181设备接入模块的好多blog,包括参数设置、功能支持与扩展等,以数据接入为例,支持的数据类型涉及编码前、编码后或直接流数据(RTSP或RTMP流)。可用于如智能监控、智慧零售、智慧教育、远程办公、生产运输、智慧交通、车载或执法记录仪等场景。
168 0
|
编解码 Android开发 开发者
Android平台GB28181设备接入模块如何实现实时视频和本地录像双码流编码
我们在做Android平台GB28181设备接入模块的时候,遇到这样的场景,比如执法记录仪或智慧工地等场景下,由于GB28181设备接入模块,注册到国标平台后,平时只是心跳保持,或还有实时位置订阅,查看视频的时候,是按需看,而且有时候,网络环境并不是太好,所以,催生了这样一个诉求:部分开发者希望能本地录像的时候,录制高分辨率(比如1920*1080),国标平台侧发起实时视频查看请求的时候,上传低分辨率(如1280*720)数据,有点类似于IPC的主码流和子码流。
|
编解码 Android开发
Android平台GB28181设备接入、RTMP推送模块如何实现高效率的视频编码
我们在做Android平台RTMP推送、轻量级RTSP服务和GB28181设备接入模块的时候,有一个点是逃不掉的:如何高效率的实现视频数据编码?
187 0
|
Web App开发 存储 编解码
视频压缩标准的介绍以及视频压缩标准H.26x各个版本的介绍
视频压缩标准的介绍以及视频压缩标准H.26x各个版本的介绍
694 0
关于如何转换视频格式的解决方案
关于如何转换视频格式的解决方案
119 0
|
存储 编解码 开发框架
主流视频编码技术H.264简介
  前戏   在之前的调研中,发现还是有些朋友对流媒体感兴趣,所以本人准备几篇文章讲解下流媒体技术。本文呢,讲解下H264,为之后的文章做个铺垫。感谢各位!   H.264简介
349 0
|
编解码 Shell API