分享

人人网

 pgj555 2014-04-14

看了有一段时间了,把过程中的一些东西记下来,主要留给自己以后看。这些代码写的真的很赏心悦目(比ffmpeg包含的H264解码器好看很多),但是没有一分documentation就越来越恶心了,很多连变量的意义到现在还不清楚。

这次做还没有作深入,先做一个较为大体的框架性的阅读吧,现在先不弄NAL码流的写入,B-frame SI && SP ,deblocking filter,和RDO,当然这些好东西会在以后加上的,RDO必然不能缺,现在先这样着,先“形式化的描述一下”,那些当做模块在添加吧。我用的版本是x264-snapshot-20090216-2245,不算老吧。ok,let‘s go。

1.VS下的工程布局。整个工程分为两个部分,一个是从frame级向下的包括MB Mode selection,ME,MC,DCT及熵编码。这部分被封装成为libx264.lib;另外一部分就是主调部分了,提供参数初始化,根据参数构造编码器x264_t *h(其实这个工程面向对象的思想很浓重),其实我们也可以根据自己的需要来写主调部分使用封装好的libx264.lib。

 

2.好,进入函数吧

1)main=>x264_param_default,对param进行付default值,包括分辨率帧率,video Utility Information中的图像显示比;参考帧的数目,各个功能模块的开关rate control中的诸如method,qp的范围步长;模式分析的方法16x16--4x4,ME的方法范围,是否skip。

可以根据命令行参数更改param,这些将来会构造一个编码器,这部分在x264_encoder_open中,里面还进行了一些初始化比如将一些函数指针指向一些target,zigzag,dct初始化等等。

2)Encode内。思路:1.构造编码器2.打开视频序列3.在每一帧时进入核心部分,也就是刚才说的被封装到libx264里面的函数里。

Encode()

{

x264_picture_alloc( &pic, X264_CSP_I420, param->i_width, param->i_height );

for( i_frame = 0, i_file = 0; b_ctrl_c == 0 && (i_frame < i_frame_total || i_frame_total == 0); )

{

p_read_frame( &pic, opt->hin, i_frame + opt->i_seek );

Encode_frame( h, opt->hout, &pic );

}

do

{Encode_frame( h, opt->hout, NULL );}

while(something)

}

代码比人会说话,这是我截出来的。在x264_picture_alloc为一个pic分配空间,for里面每一帧读入到pic内,以后进入到frame内循环分块。需要指出YUV420的文件格式。一个字节是一个像素值的大小,Y为全采样,UV在水平和垂直方向都是半采样,在文件内存放时是这样子的结构,文件:{frame1,frame2,...frameEnd};frame:{Y,U,V},所以一帧大小会是width x height x 3/2(Bytes).

知道文件结构,只要会空指针在哪怎么转换,指针函数怎么使,就能读懂这里面诸如p_get_frame_total,p_read_frame等等函数。

 

另外在最后面的do-while是用来编码最后还在缓存内的B-frame的,可以看到Encode_frame有pic位置为NULL

 

3)进入libx264部分,开始重要了。

3.1)x264_encoder_encode,我们使用一个thread,我写出框架,一会填充。

x264_encoder_encode(x264_t *h, *pic_in,*pic_out)

{

x264_reference_update( h );//更新参考序列

if(!pic_in)

{

x264_frame_t *fenc = x264_frame_pop_unused( h );

x264_frame_copy_picture( h, fenc, pic_in );

x264_frame_push( h->frames.next, fenc );

}

if( h->frames.current[0] == NULL )

{

x264_stack_align( x264_slicetype_decide, h );

*&^*&%(//B-frame缓存,暂时不讲

}

h->fenc = x264_frame_shift( h->frames.current );

do_encode://goto 的标号

//根据帧类型来写slice-type和NAL的一些info

h->fdec->i_poc = h->fenc->i_poc = 2 * (h->fenc->i_frame - h->frames.i_last_idr);//主要是这句,用来建立list 0/1用的

x264_reference_build_list( h, h->fdec->i_poc );

//下面写了rate control策略,对B-frame的策略,想码流写入sps,pps。全都不讲呢

x264_slices_write(h);

//下面是由于编码P-frame代价过高和其他策略重新按照I-slice编码,有goto

x264_encoder_frame_end();

}

 

这部分主要是参考帧的管理当做3.2吧。

3.2)参考帧的管理,这里必须明白个变量的意义:

3.2.1)First. 4个数组,既是堆栈又是队列,分别命名为reference(真正参考队列),unused(垃圾站,我这么认为),next(未决定类型的帧队列),current(决定类型带编码的队列)

second. 变量fenc就是个临时变量,用来传递使用,真正被编码和解码的是h->fenc和h->fdec.

下面详解(这里流程比较乱,加上没有注释和doc,开始比较难懂,代码个人认为简单,所以讲逻辑):

进入Encoder_encode的前提是一帧已经编码完成了,那该帧h->fdec是否可以作为参考帧呢,这进入 x264_reference_update( h );如果是则将其push到refenence数组里,所以这里是时间顺序的。如果refenence数组满了,那把最早的参考帧扔进unused里面。然后h->fdec = x264_frame_pop_unused( h );这样就为h->fdec分配了空间,不至于每次都new 或者calloc一块空间,所以这个unused设计还是很赞的!

同样为临时变量fdec从unused内那一块空间,将pic_in拷贝到里面,然后对fenc进行操作,比如扩充成为16整数倍的分辨率等操作,然后将fenc空间push到next队列。

编码时我需要决定该帧为什么类型IBP,决定好之后放到current数组内(其实如果单进程的话,个人认为一个单位空间就够了)。从current取出后放入h->fenc即代码:

h->fenc = x264_frame_shift( h->frames.current );


3.2.2)在以上都做好了以后,我们需要对该帧建立一个参考队列list0/1,他们的区别是poc顺序不同:for example,当前帧为125,则list0可能为124、123、122、126、127、128;list1可能为126 127 128 124 123 122

建立的过程在x264_reference_build_list( h, h->fdec->i_poc );里面就是一些拷贝和小的优化的冒泡排序,比较简单。

 

4.下面就是宏块级别的了,进入x264_slice_write

while( (mb_xy = i_mb_x + i_mb_y * h->sps->i_mb_width) < h->sh.i_last_mb )

{

x264_macroblock_cache_load( h, i_mb_x, i_mb_y );

x264_macroblock_analyse( h );

x264_macroblock_encode( h );

x264_macroblock_cache_save( h );

}

在这里和下一级别的分析中有必要先讲一下这个h->mb.cache(没法讲,就是cache!)。

x264_macroblock_cache_load将参考帧中某位置的(重建后)数据保存进cache,供参考和反复使用。

x264_macroblock_cache_save在分析和编码后将当前块写进cache。

在这些操作里面会有一个很让人看不懂得地方,这时候需要知道它保存时按照什么保存的。x264设计了一个相当nice的一块内存来存放,并且为其编配了一个索引,这样又能直观的理解,又能节省空间增加效率。索引如下:

 

static const int x264_scan8[16+2*4+3] =

{

    /* Luma */

    4+1*8, 5+1*8, 4+2*8, 5+2*8,

    6+1*8, 7+1*8, 6+2*8, 7+2*8,

    4+3*8, 5+3*8, 4+4*8, 5+4*8,

    6+3*8, 7+3*8, 6+4*8, 7+4*8,

    /* Cb */

    1+1*8, 2+1*8,

    1+2*8, 2+2*8,

    /* Cr */

    1+4*8, 2+4*8,

    1+5*8, 2+5*8,

    /* Luma DC */

    4+5*8,

    /* Chroma DC */

    5+5*8, 6+5*8

};

 


cache中所有数据的存放都是按照上图中位置存放的,黄色和绿色是当前编码的Luma(Y) 和chroma(UV),而白色部分是从list0/1中取出参考帧,并且在x264_macroblock_cache_load时将对应块存放到这个空间里面去。cache里面包含一切有用的信息,这个设计真的很赞的!!

 

下面真的开始重要了!

4.1 x264_macroblock_analyse( h );对当前宏块(16x16)分析。因为帧内所走的路帧间都走了,并且帧间复杂一些,就只分析帧间了

x264_macroblock_analyse( h )

{

x264_mb_analysis_t analysis;

x264_mb_analyse_init( h, &analysis, h->mb.i_qp );

if(SLICE_TYPE_I)

{^*&^%(((&&;}//不分析

else if(SLICE_TYPE_P ){

h->mc.prefetch_ref( h->mb.pic.p_fref[0][0][h->mb.i_mb_x&3], h->mb.pic.i_stride[0], 0 );//当前块的数据已经从h->fenc整帧拷贝到h->mb里面。

x264_mb_analyse_load_costs( h, &analysis );

x264_mb_analyse_inter_p16x16( h, &analysis );

//子结构分析,分析方法类似16x16

 

x264_me_refine_qpel( h, &analysis.l0.me16x16 );

}

x264_analyse_update_cache( h, &analysis );

}

详细:

x264_mb_analyse_init里面初始化一些cost代价值,rdo对于不同量化参数所用的lamda,mv的范围等。

x264_mb_analyse_load_costs( h, &analysis );这里面主要根据qp的不同初始化了analyse->p_cost_mv,这个数组是一个4*4*2048的大小,表示mv里中心越偏离代价也就越大,代价的计算方法有数学公式的。

 

下面详细讲x264_mb_analyse_inter_p16x16( h, &analysis );

 

 

 

累了,休息。。。。

------------------------------

Complaint:用x264和ffmpeg,不算工程和各种没用的文件,两个加一起有近4MB的代码,哭死了,我才能读懂多少,一年能全看懂就不赖了,觉得更多的东西都是信息的学的,各种编码啊,原来做东西是用哪看哪吧,不过这东西联系很紧密,岂是能看一块就完了的,我是来学习的,什么都不学就让我做东西,研究生是什么啊,那岂不赤裸裸了啊,我只看一块,我怎么找工作,我为自己的将来没错吧泪奔不公平转圈哭大笑狂笑得意的笑


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章