bitstream文件怎样压缩 解压文件显示内存不足足

 上传我的文档
 下载
 收藏
该文档贡献者很忙,什么也没留下。
 下载此文档
正在努力加载中...
基于Ambarella A2视频监控系统的设计与实现
下载积分:5000
内容提示:基于Ambarella A2视频监控系统的设计与实现
文档格式:PDF|
浏览次数:12|
上传日期: 04:27:38|
文档星级:
该用户还上传了这些文档
基于Ambarella A2视频监控系统的设计与实现
官方公共微信(转)X264多线程分析
http://jmvc./.html&&&
---------------------svc学习笔记
很长一段时间没有写博客了,原因很多。SVC相关的研究和代码推进都暂时停止了,&因为有太多的其它学习和开发。SVC在视频的灵活传送和错误隐藏当面是有很大潜力的,但并非意味着它能适合于真正的网络工程应用。最近随着“阿凡达”的热播,很多人对3D视频的兴趣都热情起来,立体视频时代由这样的错觉3D开始,也将打开它的大幕了。&
3D视频的制作超出了编解码范畴,更重要的利用视差进行渲染和视频特技制作,希望内行的朋友去看“阿发达”的时候,不仅仅是看热闹。
不要紧跟这些热闹的话题,今年还是踏踏实实的去探索一些基础知识的研究。打算继续对X264和FFMPEG相关内容的深入探讨,打算重新开展MVC
和机器视觉结合学习,打算把基础图像处理的算法重新复习,并且写作小结博客。等待一切的浮华被拂去,视频技术还将艰难的像更高级的目标,全息视频迈进。
这一次的文章将分析X264的多线程过程,也可以说是并行编码过程。
1. 编译并行编码的x264
从X264的帮助命令行可以看到,添加--threads项可以调整运行的线程数,可是当我完成X264编译,视图对手头的YUV进行编码的时候,
发现在自己的双核计算机上,只能发挥50%的效率,即使使用--threads n
也无济于事,提示就是没有打开pthread支持。Pthreads定义了一套 C程序语言类型、函数与常量,它以
头文件和一个线程库实现。【1】
下面就把我在windows上实现pthread版本的X264编译过程写作如下:
2009年3月的66版本
下载pthread的win32版本,把其中的include和lib加入到VC++的引用目录中去。
2. 在项目属性的“C/C++ -& 预处理器
-&预处理器”中加入HAVE_PTHREAD。
3. 在osdep.h文件,紧接着#ifdef USE_REAL_PTHREAD加入
#pragma comment(lib, "pthreadVC2.lib")
引用pthreadVC2.lib,重新编译。
2009年10月的77版本
4. 在项目属性的“C/C++ -& 预处理器
-&预处理器”中加入SYS_MINGW。
其它版本请自己根据可能的编译错误随机应变。调整项目属性意味着同时调整libx264和x264两处的属性。
经过如上调整编译出的X264就可以在--threads n
//n&=2的时候用完CPU的潜力了。
2. X264的编码基本流程
(1)接口变更
&以前曾经写过文章介绍X264的编程架构并且分析了它的接口,现在进一步看看x264是怎么把YUV图像编程H.264编码的。在代码分析中,最
容易让人头疼的是X264代码随处充斥着的多线程处理和码率控制两方面的代码,所以,这里将先简化过程,忽略掉这些非主体代码。需要说明的是,本文分析的
是版本77,2009年10月的版本。
这里的API比版本66少了x264_nal_encode(...),该函数是将码率封装成NAL,现在它被放到static int
x264_encoder_encapsulate_nals( x264_t *h
)中,不再作为单独API出现。而x264_encoder_encapsulate_nals(...)分别被
x264_encoder_headers(...)和x264_encoder_frame_end(...)所调用,分别用于封装参数
(sps,pps)和其它数据的码流。
&(2)main函数
从代码的main()函数开始,
这个函数很简单,就是读取参数,然后编码。到了版本77,相对于66版本而已,增加了参数--preset,用于定义一些预设的参数,究竟是哪个版本引入
的可自行考证。在调试程序的时候,可以根据需要选择预设参数值,如果采用默认状态,编码的FPS会比较慢。
现在重点考察编码函数static int& Encode( x264_param_t
*param, cli_opt_t *opt ),
在这个函数里,将会使用到X264的API,从代码带注释直接装贴过来,就不解释了。
首先,代码通过x264_encoder_open( param
)&和&x264_picture_alloc(
)来初始化编码器和分配内存功输入YUV图像使用。接下来可以看到由两个注释隔开的代码块,它们的功能如下
&while(输入图像中的正常编码帧){
&编码正常的码流
编码因为B帧而残余的码流(在B帧编码中,需要参考最后一个P帧的那些B帧,这时,输入帧已经结束,而编码帧尚未结束)
Encode()最后的代码是进行编码器关闭和内存謇恚⑼臣票嗦胫∈亢虵PS等。
(3)帧编码函数Encode_frame()
在上面的两个编码代码块中,主体函数是
static int& Encode_frame( x264_t *h, hnd_t
hout, x264_picture_t *pic )
这个函数将输入每帧的YUV数据,然后谕涑鰊al包。编码码流的具体工作交由API
x264_encoder_encode( x264_t *h,x264_nal_t **pp_nal, int
*pi_nal,x264_picture_t *pic_in,
x264_picture_t *pic_out )
来完成,它应该是X264中最重要的函数了。
(4)分析x264_encoder_encode()
&首先遇到参考帧调整好书如下,
static inline int x264_reference_update( x264_t *h )
&它会在h-&frames.reference
保留需要的参考帧,然后根据参考帧队列的大小限制,移除不使用的参考帧。
&然后根据注释把代码块逐个往下分析:
&&把帧输入的YUV数据传入&x264_frame_t
*fenc中,然后进行一些码率控制方式的初始化。
&&把fenc放到slice决定队列中,也输入码率控制的一部分
&&分析slice类型,具体的类型决定工作将在函数void
x264_slicetype_decide( x264_t *h )中处理。
&&后面做码率控制分析的时候再详述。
&&去处编码帧,放置在h-&fenc中,并重新设置编码参数。
&&根据帧类型设置i_nal_type,i_nal_ref_idc,h-&sh.i_type
,如果是IDR帧,重置参考帧队列。
&&根据当前帧建立参考帧队列,当前参考帧按编码帧类型分别写在h-&fref0和h-&fref1中。并整理好他们的排列顺序,h-&fref0按poc从高到低,h-&fref1反之。
&写NAL码流
&初始化slice header参数
&输出slice header和slice data
&函数最后调用
&static int x264_encoder_frame_end( x264_t *h,
x264_t *thread_current,x264_nal_t **pp_nal, int *pi_nal,
x264_picture_t *pic_out )
&来做NAL装,并且调整编码器状态和输出本帧编码的统计数据。
(5)static void *x264_slices_write( x264_t *h )
这个函数被x264_encoder_encode()调用作为处理slice header和slice
data的编码,这个函数主要是分出slice group中的一个slice,具体做slice编码则在
static int x264_slice_write( x264_t *h )
这个函数的代码块划分如下:
step1. 初始化NAL,调用x264_slice_header_write()根据前面的参数设置输出slice
header码流,
step2. 如果是用CABAC,则初始化其上下文。
step3. 进入宏块,逐个宏块编码:
宏块编码重要的是以下两个函数:
x264_macroblock_analyse( h );
x264_macroblock_encode( h );
其之前的代码是做宏块数据的导入,其后的代码是对编码数据进行熵编码,根据slicedata协议写入码流,更新coded_block_pattern,处理码率控制状态和更新CABAC上下文数据等。代码分析到宏块级了,就看看这个基本的编码单位是怎么被处理的吧。
(6)x264_macroblock_analyse( h )
这个函数就是分析宏块以确定其宏块分区模式,对I帧进行帧内预测和对P/B帧进行运动估计就发生在此函数,首先进行亮度编码,紧接着是色度。同样来一步步分析其实现。
进行码率控制准备,x264_mb_analyse_init()函数的功能包括:初始化码率控制的模型参数(码率控制依然基于Lagrangian率失
真优化算法,所以初始化lambda系数),把各宏块分类的Cost设为COST_MAX,计算MV范围,快速决定Intra宏块。
根据h-&sh.i_type的类型(I,P,B)来分别计算宏块模式的率失真代价,代价计算使用SATD方法,【2】中有相关介绍。通过计算SATD可以大致估计编码码流,作为宏块选择的依据。
随机取h-&mb.i_type == I_8x8的情况来分析,
&&&&&&&&&&&
if( h-&mb.b_lossless )
&&&&&&&&&&&&&&&
x264_predict_lossless_8x8( h, p_dst, i, i_mode, edge );
&&&&&&&&&&&
&&&&&&&&&&&&&&&
h-&predict_8x8[i_mode]( p_dst, edge );
&&&&&&&&&&&
x264_mb_encode_i8x8( h, i, i_qp );
predict_8x8[i_mode]( p_dst, edge )将进行帧内预测,x264_mb_encode_i8x8( h,
i, i_qp )进行DCT编码和量化,同时进行反量化和逆DCT编码,以备重建图像使用。
对于I8x8和I4x4的情况一般会进行分别做3 个或15个块的预测和编码,留下一个块在x264_macroblock_encode(
h )中再预测编码,原因是前面的块将作为后面编码块的预测依据。具体说会导致&i_pred_mode
= x264_mb_predict_intra4x4_mode( h, 4*idx )的计算值发生变化。
P/B帧的帧间预测将在接下来的代码段发生,具体的运动估计算法不在详述,以后将补充X264运动估计分析。
step3. 根据i_mbrd的不同,做一些后续运算。
(7)x264_macroblock_encode( h )
在确定了宏块分区模式后,在本函数将对I帧剩余的宏块分区进行预测和编码,而对P/B帧的运动补偿和残差编码主要发生在这里。
基本流程分析到这里已经算结束了,在代码中,会发现宏块的预测和编码会散布在不同的函数发生,原因是对率失真优化的要求(对P/B帧)。所以,在X264中参考帧管理,码率控制,帧间预测和多线程编码都是比较有趣的探索对象。
3. 多线程代码分析
(1)文档解读
分析完X264的基本架构,来看看多线程发挥力量的地方。X264自带的多线程介绍文档是本课题的必读文档,它存放在X264的DOC文件夹下。本
文描述的大意是:当前的X264多线程模式已经放弃基于slice的并行编码,转而采用帧级和宏块级的并行,原因是slice并行需要采用slice
group,会引入而外冗余降低编码效率。摘抄一段原文如下:
New threading method: frame-based
application calls x264
x264 runs B-adapt and ratecontrol (serial to the application, but
parallel to the other x264 threads)
spawn a thread for this frame
thread runs encode in 1 slice, deblock, hpel filter
meanwhile x264 waits for the oldest thread to finish
return to application, but the rest of the threads continue running
in the background
No additional threads are needed to decode the input, unless
decoding+B-adapt is slower than slice+deblock+hpel, in which case
an additional input thread would allow decoding in parallel to
B-adapt.【3】
以上的说明意味着,X264采用B帧在编码时不作为参考帧,所以适宜对其进行并行。
(2)运行状况分析
先来看看x264_pthread_create被调用的地方,只有这些地方才实实在在的创建了线程。
&x264_pthread_create(
&h-&thread_handle, NULL,
(void*)x264_slices_write, h )
&x264_pthread_create(
&look_h-&thread_handle, NULL, (void
*)x264_lookahead_thread, look_h )
&x264_pthread_create(
&h-&tid, NULL,
(void*)read_frame_thread_int, h-&next_args )
&由上图的运行可以看出,在开启了--threads
4后。x264_slices_write()可以开启4个线程同时编码,而同时存在一个主线程和一个x264_lookahead_thread()线
程。x264_slices_write()的优先级为低,原因是调用了
if( h-&param.i_sync_lookahead )
x264_lower_thread_priority( 10 );
调低本线程的优先级。read_frame_thread_int()是读磁盘上的流数据信息,因为I/O和内存的不同步,所以应该分开线程处理。
在x264_encoder_open()中可以找到一下代码,可以看到对于x264_slices_write()和x264_lookahead_thread()都有被分配了专有的上下文变量,供单一线程使用。
&&& for( i =
1; i & h-&param.i_threads +
!!h-&param.i_sync_ i++ )
CHECKED_MALLOC( h-&thread[i], sizeof(x264_t) );
(3)如何确保按指定线程数来开启线程编码?
按打印实验可以看到,假设使用--threads
4的参数选项,代码会同时开启4个x264_slices_write()线程,然后每编完一个帧(前面的一个线程返回后),一个新的被产生出来,使得
x264_slices_write()线程总数保持在4个,这一过程的相关代码如下:
x264_encoder_encode( x264_t *h,x264_nal_t **pp_nal, int
*pi_nal,x264_picture_t *pic_in,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&
x264_picture_t *pic_out )
h-&param.i_threads & 1)
int i = ++h-&i_thread_
int t = h-&param.i_
thread_current = h-&thread[ i%t ];
thread_prev&&& =
h-&thread[ (i-1)%t ];
thread_oldest& = h-&thread[ (i+1)%t
x264_thread_sync_context( thread_current, thread_prev );
x264_thread_sync_ratecontrol( thread_current, thread_prev,
thread_oldest );
h = thread_
h-&param.i_threads & 1 )
printf("x264_pthread_create\n");
if( x264_pthread_create(
&h-&thread_handle, NULL,
(void*)x264_slices_write, h ) )
&&&&&&&&&&&
return -1;
h-&b_thread_active = 1;
if( (intptr_t)x264_slices_write( h ) )
&&&&&&&&&&&
return -1;
&&& return
x264_encoder_frame_end( thread_oldest, thread_current, pp_nal,
pi_nal, pic_out );
static int x264_encoder_frame_end( x264_t *h, x264_t
*thread_current,x264_nal_t **pp_nal, int *pi_nal, x264_picture_t
*pic_out )
h-&b_thread_active )
void *ret = NULL;
x264_pthread_join( h-&thread_handle,
if( (intptr_t)ret )
&&&&&&&&&&&
return (intptr_t)
h-&b_thread_active = 0;
从以上两个函数的代码段可以看到,h上下文中保持的线程不会多于4个,
x264_pthread_create()根据主线程的调用,创建出x264_slices_write线程,然后thread_oldest被指定并被率控函数判断重设,当前的线程数还不足4的时候,thread_oldest指向新线程,h-&b_thread_active为0,不能进入x264_encoder_frame_end()的相关代码,主线程继续循环创建x264_slices_write线程,当线程总数为4,这时thread_oldest指向4个线程中被判断最快返回的那个,这时h-&b_thread_active=1将进入x264_pthread_join(),那样,该线程就将主线至于阻塞状态,直至thread_oldest完成,才能重现创建新线程,以此机制,保持指定数码的编码线程数。
(4)x264_lookahead_thread()线程的作用
在分析这个线程之前,来看看两个重要的线程控制函数:
//唤醒等待该条件变量的所有线程。如果没有等待的线程,则什么也不做。
#define x264_pthread_cond_broadcast&
pthread_cond_broadcast
//自动解锁互斥量(如同执行了 pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用
时间,直到条件变量被触发。在调用 pthread_cond_wait
之前,应用程序必须加锁互斥量。pthread_cond_wait 函数返回前,自动重新对互斥量加锁(如同执行了
pthread_lock_mutex)。
x264_pthread_cond_wait&&&&&&
pthread_cond_wait
以下的代码是X264中x264_lookahead_thread代码经常阻塞的地方,
**************************代码段A********************************************
if( h-&lookahead-&next.i_size
h-&lookahead-&i_slicetype_length
&&&&&&&&&&&
while( !h-&lookahead-&ifbuf.i_size
!h-&lookahead-&b_exit_thread )
&&&&&&&&&&&&&&&
x264_pthread_cond_wait(
&h-&lookahead-&ifbuf.cv_fill,
&h-&lookahead-&ifbuf.mutex
&&&&&&&&&&&
x264_pthread_mutex_unlock(
&h-&lookahead-&ifbuf.mutex
&&&&&&&&&&&
x264_pthread_mutex_unlock(
&h-&lookahead-&ifbuf.mutex
&&&&&&&&&&&
x264_lookahead_slicetype_decide( h );
这里是等待满足!h-&lookahead-&ifbuf.i_size
!h-&lookahead-&b_exit_thread
的条件,后一条件在正常编码过程是TRUE,因为不会无故退出线程。那么这里等待的其实是ifbuf.i_size为非0.查找相关代码,
ifbuf.i_size条件是在x264_synch_frame_list_push()得到满足的,这里在得到一个输入的新编码帧后将发出信号。
slist-&list[ slist-&i_size++ ] =
x264_pthread_cond_broadcast(
&slist-&cv_fill );
在 代码段A中,if(
h-&lookahead-&next.i_size
h-&lookahead-&i_slicetype_length
)条件中,i_slicetype_length表示为了进行slice
type的判断而缓存的帧,它的值有取决于h-&frames.i_delay,由代码的初始化设定值决定(默认为40)。也就是说预存40帧的数
值,进行slice type决定用。暂时不详细分析slice
type判断的具体实现,它的大概思想是根据码率,GOP和失真状况的权衡,来进行帧类型选择,在类似实时通信场合,不允许B帧的使用,也不可能预存那么多帧,这样的处理没有意义。
回头看这里的处理意义,是阻塞线程,等待后续的输入帧,然后利用处理规则来决定其slice
type,为slice编码准备帧。
(5)宏块级别的并行
在数据结构x264_frame_t中,有变量x264_pthread_cond_t&
该变量分别在下面的两个函数里被封装了阻塞和唤醒:
void&&&&&&&&&
x264_frame_cond_broadcast( x264_frame_t *frame, int
i_lines_completed );
void&&&&&&&&&
x264_frame_cond_wait( x264_frame_t *frame, int i_lines_completed
考查它们被调用的地方,
************代码B****************from x264_macroblock_analyse(
)-&x264_mb_analyse_init()
int thresh = pix_y +
h-&param.analyse.i_mv_range_
for( i = (h-&sh.i_type == SLICE_TYPE_B); i
&= 0; i-- )
&&& x264_frame_t
**fref = i ? h-&fref1 :
&&& int i_ref =
i ? h-&i_ref1 : h-&i_ref0;
&&& for( j=0;
j&i_ j++ )
x264_frame_cond_wait( fref[j], thresh );
thread_mvy_range = X264_MIN( thread_mvy_range,
fref[j]-&i_lines_completed - pix_y );
**************************代码C************************************from
x264_fdec_filter_row()
if( h-&param.i_threads & 1
h-&fdec-&b_kept_as_ref )
x264_frame_cond_broadcast( h-&fdec, mb_y*16 + (b_end
? 10000 : -(X264_THREAD_HEIGHT
&&h-&sh.b_mbaff))
从 上面的代码段可以看到没完成图像一行的编码,便会使用mb_y*16
-X264_THREAD_HEIGH的值来尝试唤醒x264_pthread_cond_wait(
&frame-&cv,
&frame-&mutex ),要判断的条件是
mb_y*16 -X264_THREAD_HEIGH & thresh = pix_y +
h-&param.analyse.i_mv_range_
后者作为一个设想的阈值,用于确保依赖于本帧的后续帧在编码时,本帧已经编码出若干行宏块,以后续编码帧的基础,那样可以设想的情形如下图,不过X264是以编码完整行为单位的。
本文的分析道这里告一段落,对于帧间多线程分析和宏块的并行优化,或按自己的应用做代码裁剪,可以通过改正上面的(4)(5)代码段来实现,在当前(四核CPU)的X264测试中,已有代码确实能够很好的利用多核资源,并行编码的话题会随硬件的升级而不断探索下去。
【参考文献】
1.&维基百科,
2. MVC学习的第四周小结(-)-运动估计2,
http://jmvc./.html&
3. X264 document,threads.txt
------------------------------------------------------------------------

使用版本:& x264-cvs-
这次的分析基本上已经将代码中最难理解的部分做了阐释,对代码的主线也做了剖析,如果这个主线理解了,就容易设置几个区间,进行分工阅读,将各个区间击破了.
需要学习的知识:
编码器的工作流程.
H.264的码流结构,像x264_sps_t,x264_pps_t等参数的定义基本上都完全符合标准文档中参数集的定义,抓住主要参数,次要参数也应该有所了解.
数学知识,对dct变换等与数学相关的知识的编程实现要有较好理解.
C语言的知识.涉及到c语言的较多不经常用的特性,如函数指针数组,移位运算,结构体的嵌套定义等.
耐心,对h.264的复杂性要有清醒的认识.
3.参考资料:
新一代视频压缩编码标准-h.264/avc& 毕厚杰主编,人民邮电出版社.
网上的流媒体论坛,百度,google等搜索引擎.
4. 阅读代码的方法:
较好的方法是利用vc的调试器,如果对某个函数感兴趣,可以将断点设置在它的前面.然后采用step into,step
over等方法进去该函数一步步分析.当然本身要对程序执行流程要有较清楚认识,不然不知道何时step into,何时step
建议应该先对照标准弄清各个结构体成员的意义.
源代码主要过程分析:
进入x264.c中的main函数.
刚开始是读取默认参数,如果你设置了参数的话会修改param的.
&& i_ret = Encode(
&param, fin, fout );
这条语句使过程进入x264.c中的Encode函数.
X.264的encode函数.
i_frame_total = 0;
if( !fseek( fyuv, 0, SEEK_END ) )
int64_t i_size = ftell( fyuv );
fseek( fyuv, 0, SEEK_SET );
i_frame_total = i_size / ( param-&i_width *
param-&i_height * 3 / 2 )
上面这段计算出输入文件的总帧数.
h = x264_encoder_open( param
)这个函数是对不正确的参数进行修改,并对各结构体参数和cabac编码,预测等需要的参数进行初始化.
pic = x264_picture_new( h );
该函数定义在\CORE\common.c中.首先分给能容纳sizeof(x264_picture_t)字节数的空间,然后进行初始化.
这里看一下x264_picture_t和x264_frame_t的区别.前者是说明一个视频序列中每帧的特点.后者存放每帧实际的象素值.注意区分.
for( i_frame = 0, i_file = 0; i_ctrl_c == 0 ; i_frame++ )
int&&&&&&&&
x264_nal_t& *
&int&&&&&&&&
if( fread( pic-&plane[0], 1,
param-&i_width * param-&i_height,
fyuv ) &= 0 ||
&&&&&&&&&&&
fread( pic-&plane[1], 1,
param-&i_width * param-&i_height / 4,
fyuv ) &= 0 ||
&&&&&&&&&&&
fread( pic-&plane[2], 1,
param-&i_width * param-&i_height / 4,
fyuv ) &= 0 )
&&&&&&&&&&&
//文件位置指示器自己变化了.
if( x264_encoder_encode( h, &nal,
&i_nal, pic ) & 0 )
&&&&&&&&&&&
fprintf( stderr, "x264_encoder_encode failed\n" );
凡是出现for循环的地方都应该认真对待,这是我的一个体会,也是进入分层结构认真分析的好方法.
fread()函数一次读入一帧,分亮度和色度分别读取.这里要看到c语言中的File文件有一个文件位置指示器,调用fread()函数会使文件指示器自动移位,这就是一帧一帧读取的实现过程.
然后进入x264_encoder_encode( h, &nal,
&i_nal, pic )函数,该函数定义在/Enc/encoder.c中.
开始进入比较复杂的地方了.
这个函数前面有一段注释(如下):
****************************************************************************
&* x264_encoder_encode:
i_poc&& : is the poc of the
current given picture
i_frame : is the number of the frame being coded
&*& ex:& type
0&& 2*0//poc是实际的帧的位置.
1&& 2*3//frame是编码的顺序.
&****************************************************************************/
要搞清poc和frame的区别.
假设一个视频序列如下:
我们编码是按I& P&
B& B的顺序,这就是frame的编号.
而我们视频序列的播放序号是POC的序号,这里是乘以了2.
函数中先定义了如下三个参数:
nal存放的数据类型,
可以是sps,pps等多种.&&&&&&&&&&&&&&&&&&
i_nal_ref_
nal的优先级,nal重要性的标志位.
前面两个参数虽然简单,但如果不参照标准,也不容易理解,所以标准中的句法表是很重要的,可以说是最关键的.
slice的类型,在x264中我的感觉好像一帧只有一个slice.如果确定了帧的类型,slice的类型也就确定了.
我们来看看编码器是如何区分读入的一帧是I帧,P帧,或者B帧,这个过程需要好好理解.
if( h-&i_frame %
(h-&param.i_iframe *
h-&param.i_idrframe) == 0 ){
确定这是立即刷新片.
这里很好理解.
但到了if( h-&param.i_bframe & 0
)//可以B帧编码时.
就有问题了.
注意我们编完I帧后碰到了一个B帧,这时我们先不对它进编码.而是采用frame =
x264_encoder_frame_put_from_picture( h,
h-&frame_next, pic
)函数将这个B帧放进h-&frame_next中.
好,这里出现了h-&frame_next,在h中同时定义了下面几个帧数组用以实现帧的管理.
x264_frame_t&&
*bframe_current[X264_BFRAME_MAX];
x264_frame_t&&&
*frame_next[X264_BFRAME_MAX+1];&&
//搞清意义,下一个帧,而不一定是B帧.
x264_frame_t&&&
*frame_unused[X264_BFRAME_MAX+1];
注意区分这3个数组.
同时还有下面4个函数(定义在\ENCODER\encoder.c中).
x264_encoder_frame_put_from_picture();
x264_encoder_frame_put();
x264_encoder_frame_get();
x264_frame_copy_picture();
这3个数组和4个函数可以说完成了整个帧的类型的判定问题.这个里面if
,else语句较多,容易使人迷惑.但我们只要把握下面一个观点就可以看清实质:在不对P帧进行编码之前,我们不对B帧进行编码,只是把B帧放进缓冲区(就是前面提到的数组).
比如视频序列:I&&&&
先确立第一个帧的类型,然后进行编码.然后是2个B帧,我们把它放进缓冲区数组.然后是P帧,我们可以判定它的类型并进行编码.同时,我们将缓冲区
的B帧放进h-&bframe_current[i],不过这时P帧前的两个B帧并没有编码.当读到P帧后面的第一个B帧时,我们实际上才将
h-&bframe_current数组中的第一个B帧编码,也就是将在I帧后面的第一个B帧(说成P帧前面的第一个B帧容易误解J)编码.
依此类推,把握好上面4个函数的调用流程和指针操作的用法,就可以将帧的类型判定这个问题搞明白了.
然后是速率控制(先不说这个,因为它对编码的流程影响不大),看看建立参考帧列表的操作,也就是
x264_reference_build_list( h,
h-&fdec-&i_poc );
(定义在\ENCODER\encoder.c中).
光看这个函数是不行的,它是和后面的这个函数(如下)一起配合工作的.
if( i_nal_ref_idc != NAL_PRIORITY_DISPOSABLE )//B帧时.
x264_reference_update( h );
If条件是判断当前帧是否是B帧,如果是的话就不更新参考列表,因为B帧本来就不能作为参考帧嘛!如果是I帧或P帧的话,我们就更新参考帧列表.
我们看到了一个for循环,两个do—while循环.这是实现的关键,具体看代码,不好用语言说明白.
进入另一个复杂的领域:写slice的操作,刚开使挺简单,如我下面的注释.
h-&out.i_nal = 0;//out的声明在bs.h中.
&&& bs_init(
&h-&out.bs,
h-&out.p_bitstream,
h-&out.i_bitstream );//空出8位.
i_nal_type == NAL_SLICE_IDR )//不是每次都要写SPS and PPS,只有碰见立即刷新片时才写.
x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST );
x264_sps_write( &h-&out.bs,
x264_nal_end( h );
x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST );
x264_pps_write( &h-&out.bs,
x264_nal_end( h );
不过看下面那个函数(就进入了复杂的领域).
x264_slice_write()(定义在\ENCODER\encoder.c中),这里面是编码的最主要部分,下面仔细分析.
前面不说,看下面这个循环,它是采用for循环对一帧图像的所有块依次进行编码.
for( mb_xy = 0, i_skip = 0; mb_xy &
h-&sps-&i_mb_width *
h-&sps-&i_mb_ mb_xy++
)//h-&sps-&i_mb_width指的是从宽度上说有多少个宏快.对于宽度也就是288
const int i_mb_y = mb_xy /
h-&sps-&i_mb_
const int i_mb_x = mb_xy %
h-&sps-&i_mb_//这两个变量是定义宏块的位置.而不是指宏块中元素的位置.
x264_macroblock_cache_load( h, i_mb_x, i_mb_y
);//是把当前宏块的up宏块和left宏块的intra4x4_pred_mode,non_zero_count加载进来,放到一个数组里面,这个
数组用来直接得到当前宏块的左侧和上面宏块的相关值.要想得到当前块的预测值,要先知道上面,左面的预测值,它的目的是替代getneighbour函
TIMER_START( i_mtime_analyse );
x264_macroblock_analyse( h );//定义在analyse.h中.
TIMER_STOP( i_mtime_analyse );
TIMER_START( i_mtime_encode );
x264_macroblock_encode( h );//定义在Enc/encoder.c中.
TIMER_STOP( i_mtime_encode );
截止到这就已经完成编码的主要过程了,后面就是熵编码的过程了(我也没看到那,但认为前面才是编码的主要过程).下面对这个过程进行分析.
x264_macroblock_cache_load( h, i_mb_x, i_mb_y
);它是将要编码的宏块的周围的宏块的值读进来,
要想得到当前块的预测值,要先知道上面,左面的预测值,它的作用相当于jm93中的getneighbour函数.
进入x264_macroblock_analyse( h
)函数(定义在\Enc\analyse.c中,这里涉及到了函数指针数组,需要好好复习,个人认为这也是x264代码最为复杂的一个地方了).既然已经
将该宏块周围的宏块的值读了出来,我们就可以对该宏块进行分析了(其实主要就是通过计算sad值分析是否要将16*16的宏块进行分割和采用哪种分割方式
看似很复杂,但我们只要把握一个东西就有利于理解了:
举个生活中的例子来说:
如果你有2元钱,你可以去买2袋1元钱的瓜子,也可以买一袋2元钱的瓜子,如果2袋1元钱的瓜子数量加起来比1袋2元钱的瓜子数量多,你肯定会买2袋1元的.反之你会去买那2元1袋的.
具体来说,对于一个16*16的块,
如果它是I帧的块,我们可以将它分割成16个4*4的块,如果这16个块的sad加起来小于按16*16的方式计算出来的sad值,我们就将这个
16*16的块分成16个4*4的块进行编码(在计算每个4*4的块的最小sad值时已经知道它采用何种编码方式最佳了),否则采用16*16的方式编码
(同样我们也已知道对它采用哪种编码方式最为合适了.
如果它是P帧或B帧的块,同样是循环套循环,但更为复杂了,可以看我在analyse.c中的注释.
这里还要注意的是提到了
x264_predict_t&&&&&
predict_16x16[4+3];
typedef void (*x264_predict_t)( uint8_t *src, int i_stride
这是函数指针数组,有很多对它的调用.
退出x264_macroblock_analyse( h )函数,进入x264_macroblock_encode(
)函数(定义在\ENCODER\macroblock.c中).
我拿宏块类型为I_16*16为例.
if( h-&mb.i_type == I_16x16 )
const int i_mode = h-&mb.i_intra16x16_pred_
h-&predict_16x16[i_mode](
h-&mb.pic.p_fdec[0],
h-&mb.pic.i_fdec[0] );//这两个参数的关系.
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//涉及到x264_predict_t(函数指针数组),声明在core/predict.h中,core/predict.c里有不同定义.
x264_mb_encode_i16x16( h, i_qscale );//
我们看到h-&predict_16x16[i_mode](
h-&mb.pic.p_fdec[0],
h-&mb.pic.i_fdec[0]
);只调用了一次,这是因为在x264_macroblock_analyse(&
)中我们已经确定了采用4种方式中的哪种最合适.而在x264_macroblock_analyse(&
)中判定一个块是否为I_16*16,我们调用了四次.这是因为当时我们需要拿最小的sad值进行比较.
继续,是x264_mb_encode_i16x16( h, i_qscale
)函数(定义在\ENCODER\macroblock.c中).在这个函数中我们就可以看到量化,zig-扫描等函数了,这些都是直来直去的,需要的只是我们的细心和对数学知识的掌握了
到这里还没完,我们接着看
void x264_macroblock_encode( x264_t *h ){
…….前面省略.
执行到下面这条语句,看看下面是干啥的.
&&& i_qscale
= i_chroma_qp_table[x264_clip3( i_qscale +
h-&pps-&i_chroma_qp_index_offset, 0,
IS_INTRA( h-&mb.i_type ) )
const int i_mode = h-&mb.i_chroma_pred_
h-&predict_8x8[i_mode](
h-&mb.pic.p_fdec[1],
h-&mb.pic.i_fdec[1] );
h-&predict_8x8[i_mode](
h-&mb.pic.p_fdec[2],
h-&mb.pic.i_fdec[2] );
h-&mb.i_chroma_pred_mode =
x264_mb_pred_mode8x8_fix[i_mode];
x264_mb_encode_8x8( h, !IS_INTRA( h-&mb.i_type ),
i_qscale );//对色度块进行编码了.
到这我们可以看到原来我们在这前面是对宏块中的亮度系数进行了编码,我们到上面那个函数才开始对色度系数进行编码.进入x264_mb_encode_8x8()函数看到for循环里面有个2可以证明是对2个色度系数进行编码,想法没错.
那下面这些又是干啥的呢?它们是计算cbp系数看需要对残差(包括ac,dc)中的哪个系数进行传输的.
h-&mb.i_type == I_16x16 )
h-&mb.i_cbp_luma = 0x00;
for( i = 0; i & 16; i++ )
&&&&&&&&&&&
const int nz = array_non_zero_count(
h-&dct.block[i].residual_ac, 15 );
&&&&&&&&&&&
h-&mb.cache.non_zero_count[x264_scan8[i]] =
&&&&&&&&&&&
if( nz & 0 )
&&&&&&&&&&&
&&&&&&&&&&&&&&&
h-&mb.i_cbp_luma = 0x0f;
&&&&&&&&&&&
h-&mb.i_cbp_luma = 0x00;
for( i = 0; i & 16; i++ )
&&&&&&&&&&&
const int nz = array_non_zero_count(
h-&dct.block[i].luma4x4, 16 );//统计非0个数.
&&&&&&&&&&&
h-&mb.cache.non_zero_count[x264_scan8[i]] =
&&&&&&&&&&&
if( nz & 0 )
&&&&&&&&&&&
&&&&&&&&&&&&&&&
h-&mb.i_cbp_luma |= 1
&& (i/4);// 的意义.
&&&&&&&&&&&
//色度的cbp有3种方式.
h-&mb.i_cbp_chroma = 0x00;
&&& for( i =
0; i & 8; i++ )
const int nz = array_non_zero_count(
h-&dct.block[16+i].residual_ac, 15 );
h-&mb.cache.non_zero_count[x264_scan8[16+i]] =
if( nz & 0
)&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&
h-&mb.i_cbp_chroma =
h-&mb.i_cbp_chroma == 0x00
( array_non_zero_count( h-&dct.chroma_dc[0], 4 )
& 0 || array_non_zero_count(
h-&dct.chroma_dc[1], 4 ) ) & 0 )
h-&mb.i_cbp_chroma =
h-&param.b_cabac )
if( h-&mb.i_type == I_16x16
&& array_non_zero_count(
h-&dct.luma16x16_dc, 16 ) & 0 )
&&&&&&&&&&&
i_cbp_dc = 0x01;
&&&&&&&&&&&
i_cbp_dc = 0x00;
if( array_non_zero_count( h-&dct.chroma_dc[0], 4 )
&&&&&&&&&&&
i_cbp_dc |= 0x02;
if( array_non_zero_count( h-&dct.chroma_dc[1], 4 )
&&&&&&&&&&&
i_cbp_dc |= 0x04;
h-&mb.cbp[h-&mb.i_mb_xy] =
(i_cbp_dc && 8) |
(h-&mb.i_cbp_chroma &&
4) | h-&mb.i_cbp_
到这,基本上x264_macroblock_encode( h
)(定义在Enc/encoder.c)基本上就分析完了.剩下的就是熵编码的部分了.以后的部分更需要的应该是耐心和数学知识吧,相对前面来说应该简单些.
我对代码的理解应该还算比较深入,把代码的主线已经分析了出来,对代码中几个最难理解的地方(最难理解的地方就是帧的类型的判定,参考帧是如何管理的,一
个16*16的块是采用到底需不需要分割,分割的话分成什么大小的,子块又采用何种预测方式,这些实际上就是整个编码的主线.)基本上已经明白,但有些过
分复杂的函数的实现(或者涉及数学知识较多的地方)还有待深入研究,但我相信沿着这条主线应该能够继续深入下去,自己需要的是更多的时间和耐心.
自己需要的是更多的时间和耐心,争取以后能写出更详细更准确的流程分析,并尽量思考能改进的地方.
2.层次性,就像网络的7层结构一样,每一帧图像也可以分成很多层,只有对每层的语法结构(具体来说就是各个结构体中变量的意思)有了很好的理解,才有可能真正认清代码,这需要对标准认真研习.比如说量化参数,就在3个地方有定义,不读标准根本不会明白意思.
很多过分复杂的东西不容易在本文中表达出来(比如说预测部分),只有通过自己的钻研才能真正悟到,直觉也很重要,还有就是信心了.看这种程序的收获就好像是真地肉眼看到了原子那样.
4.由于代码过分复杂,对某些函数的实现过程还没能彻底理解,比如说x264_macroblock_cache_load(
)函数的具体实现过程,我只是知道它的功能,实现过程还有待认真理解.dct变换是如何实现的,是如何计算残差的等等,这些都需要很多功夫,当然这里也需要大家的共同学习和交流.实现分工阅读不同代码部分并进行交流,才有可能对代码做到彻底的理解.
-----------------------------------------------------------------------------------------------------------------------------
x264代码分析
Main(int argc,char *argv[]);
为了方便起见,不妨改写为:
Main(void){
"main","-o","test.264","foreman.yuv","352x288"&&
====================================
1.x264_param_default( &param );
这部分设置编码参数的缺省值
附部分变量的意义:
param-&i_csp&&&&&&&&&
= X264_CSP_I420; // 设置输入的视频采样的格式
param-&vui.i_sar_width = 0;&
//VUI:video usability information
param-&i_fps_num&&&&&&
= 10; //帧率
param-&i_fps_den&&&&&&
= 1;& //用两个整型的数的比值,来表示帧率
param-&i_frame_reference = 1; //参考帧的最大帧数。
param-&i_bframe =
0;&&&&&&&&&
//两个参考帧之间的B帧数目。
param-&b_deblocking_filter = 1; //去块效应相关
param-&b_cabac =
0;&&&&&&&&&&&
//cabac的开关
param-&i_cabac_init_idc = -1;
param-&rc.b_cbr =
1;&&&&&&&&&&
//constant bitrate 恒定码率控制模式
param-&rc.i_bitrate =
//默认的码率
param-&rc.i_rc_buffer_size =
0;&& //buffer的大小
param-&rc.i_rc_init_buffer =
param-&rc.i_rc_sens =
100;&&&&&&
// //整个param的一个log文件
param-&analyse.intra = X264_ANALYSE_I4x4 |
X264_ANALYSE_I8x8;&
//桢内分析
param-&analyse.inter = X264_ANALYSE_I4x4 |
X264_ANALYSE_I8x8&&&&&&&&&&&&&&&&&&&&&&&&&&
| X264_ANALYSE_PSUB16x16 | X264_ANALYSE_BSUB16x16;
//桢间分析
param-&analyse.i_direct_mv_pred =
X264_DIRECT_PRED_SPATIAL;
//预测模式
param-&analyse.i_me_method =
X264_ME_HEX;&&&&&
//运动估计模式
param-&analyse.i_me_range =
16;&&&&&&&&&&&&&
//运动估计范围
param-&analyse.i_subpel_refine = 5;
param-&analyse.b_chroma_me = 1;
param-&analyse.i_mv_range_thread = -1;
param-&analyse.i_mv_range = -1; // set from
param-&analyse.i_direct_8x8_inference = -1; // set
from level_idc
param-&analyse.i_chroma_qp_offset = 0;
param-&analyse.b_fast_pskip = 1;
param-&analyse.b_dct_decimate = 1;
param-&analyse.i_luma_deadzone[0] = 21;
param-&analyse.i_luma_deadzone[1] = 11;
param-&analyse.b_psnr = 1;
param-&analyse.b_ssim = 1;
param-&i_cqm_preset =
X264_CQM_FLAT;& //自定义量化矩阵(CQM),初始化量化模式为flat
2.Parse( argc, argv, &param, &opt )
&& 解析函数int c = getopt_long( argc,
argv, "8A:B:b:f:hI:i:m:o:p:q:r:t:Vvw",
&& long_options,
&long_options_index);
得到入口地址的向量与方式的选择&
&& _getopt_internal (argc, argv,
optstring, longopts, longind,
long_only);&&&&&&&&&&&&&&&&&
&& //解析入口地址向量
3.Encode( &param, &opt ) ;
3.1.x264_encoder_open( param ){
x264_t *h = x264_malloc( sizeof( x264_t )
//为h分配空间
memcpy( &h-&param, param, sizeof(
x264_param_t ) );
3.1.1.x264_validate_parameters( h );& //验证参数
&h-&sps_array[0];
3.1.2.x264_sps_init( h-&sps,
h-&param.i_sps_id,
&h-&param );
typedef struct
//本序列参数集的id号
i_profile_&&
//指明所用的profile
i_level_&&&&
//指明所用的level
b_constraint_set0;&&
//其值等于时,表示必须遵从附录A.2.1所指明的所有约束条件
b_constraint_set1;&&
//其值等于时,表示必须遵从附录A.2.2所指明的所有约束条件
b_constraint_set2;&&
//其值等于时,表示必须遵从附录A.2.3所指明的所有约束条件
i_log2_max_frame_&&
//这个句法元素只要是为读取frame_num服务的
//指明poc的编码方法,poc标识图像的播放顺序
i_log2_max_poc_&&
//指明了变量i_poc_lsb的max值
b_delta_pic_order_always_&&&
//其值为时,i_delta_poc[0]和i_delta_poc[1]不在片头出现,并且它们的默认值为;当本句法元素为时,上述两个句法元素将在片头出现
i_offset_for_non_ref_&&&&&&&&
//用于计算非参考帧或场的poc
i_offset_for_top_to_bottom_ //用于计算帧的底场的poc
i_num_ref_frames_in_poc_&&&
//被用来解码poc
i_offset_for_ref_frame[256];&&&&&
//当i_poc_type=时用于解码poc,本句法元素对循环i_num_ref_frames_in_poc_cycle中的每一个元素指定一个偏移
i_num_ref_&&&&&&
//指定参考帧队列可能达到的最大长度,解码器根据这个句法元素的值开辟存储区,这个存储区用于存放已解码的参考帧
b_gaps_in_frame_num_value_&&
//这个句法元素等于时,表示允许句法元素frame_num可以不连续;当传输信道堵塞时,允许丢弃若干帧
i_mb_&&&&&&
//图像的宽度,以宏块为单位
i_mb_&&&&&
//图像的高度,以宏块为单位
b_frame_mbs_&&&&
//本句法元素等于时,表示本序列中所有的图像编码模式都是帧,没有其他编码模式存在;当为时,表示本序列中图像的编码模式可能是帧,也可能是场或帧场自适应
b_mb_adaptive_frame_&&
//指明本序列是否属于帧场自适应模式
b_direct8x8_&&&&
//指明b片的直接和skip模式下运动矢量的预测方法
//指明解码器是否要将图像裁剪后输出,如果是的话,后面紧跟的四个句法元素分别指出左、右、上、下裁剪的宽度
&&& struct
b_&& //指明vui子结构是否出现在码流中
&&& struct
int b_qpprime_y_zero_transform_
} x264_sps_t;
3.1.3.x264_pps_init( h-&pps,
h-&param.i_sps_id,
&h-&param,
typedef struct
//本参数集的序号,在片头被引用
i_sps_& //本图像参数集所引用的序列参数集的序号
//0时使用cavlc,时使用cabac
//poc的三种计算方法在片层还各需要用一些句法元素作为参数;当等于时,表示在片头会有句句法元素指明这些参数;当为时,表示片头不会给出这些参数
i_num_slice_&&
//图像中片组的个数
i_num_ref_idx_l0_&&&
//指明目前参考帧队列的长度,即有多少各参考帧(短期和长期),用于list0
i_num_ref_idx_l1_&&&
//指明目前参考帧队列的长度,即有多少各参考帧(短期和长期),用于list1
b_weighted_&&&&&&&
//指明是否允许p和sp片的加权预测
b_weighted_&&&&&
//指明是否允许b片的加权预测
i_pic_init_&&&&&&&&&
//亮度分量的量化参数的初始值
i_pic_init_&&&&&&&&&
//亮度分量的量化参数的初始值,用于SP和SI
i_chroma_qp_index_&&&
//色度分量的量化参数是根据亮度分量的量化参数计算出来的,本句法元素用以指明计算时用到的参数
b_deblocking_filter_&&
//编码器可以通过句法元素显式地控制去块滤波的强度
b_constrained_intra_&&&&&
//在p和b片中,帧内编码的宏块的邻近宏块可能是采用的帧间编码
b_redundant_pic_&&&&&&&&&
b_transform_8x8_
uint8_t *scaling_list[6];
} x264_pps_t;
x264_validate_levels( h );&
//////3.1.4.x264_cqm_init( x264_t *h ){
h-&quant4_mf[i] =
x264_malloc(52*size*sizeof(uint16_t) );
h-&dequant4_mf[i] = x264_malloc( 6*size*sizeof(int)
h-&unquant4_mf[i] = x264_malloc(52*size*sizeof(int)
h-&quant4_bias[i] =
x264_malloc(52*size*sizeof(uint16_t) );
//////3.1.4 end
h-&thread[i] = x264_malloc( sizeof(x264_t) );
h-&thread[i]-&fdec =
x264_frame_pop_unused( h );
h-&thread[i]-&out.p_bitstream =
x264_malloc( h-&out.i_bitstream );
//定义要做nal的那些码流的地方,i_bitstream是大小,p_bitstream是指针
3.1.5 x264_frame_pop_unused( h ){
&&& x264_frame_t
h-&frames.unused[0] )
frame = x264_frame_pop( h-&frames.unused
);& //从unused队列中//取出最后位置的一个桢
frame = x264_frame_new( h
//分配一帧所需要的空间
///////3.1.5.1 x264_frame_new( h ){
for( i = 0; i & 3; i++
//分配frame的yuv空间,带边框
int i_divh = 1;
int i_divw = 1;
if( i & 0 )
if( h-&param.i_csp == X264_CSP_I420 )
i_divh = i_divw = 2;
else if( h-&param.i_csp == X264_CSP_I422 )
i_divw = 2;
frame-&i_stride[i] = i_stride / i_
frame-&i_lines[i] = i_lines / i_
CHECKED_MALLOC( frame-&buffer[i],
frame-&i_stride[i] * (
frame-&i_lines[i] + 2*i_padv / i_divh ) );
frame-&plane[i] =
((uint8_t*)frame-&buffer[i]) +
frame-&i_stride[i] * i_padv / i_divh + PADH /
//plane指向除去buffer中图像边框的yuv真实数据
///////3.1.5.1 end
&&& assert(
frame-&i_reference_count == 0 );
frame-&i_reference_count = 1;
if( x264_macroblock_cache_init( h-&thread[i] )
& 0 ) return NULL;
3.1.6.x264_macroblock_cache_init( x264_t *h ){
//分配x264_t结构体下子结构体mb对应的qp、cbp、skipbp、mb_transform_size、intra4x4_pred_mode、non_zero_count等等在宏块编码时用于控制和传输等用到的表!
CHECKED_MALLOC( h-&mb.qp, i_mb_count *
sizeof(int8_t) );
CHECKED_MALLOC( h-&mb.cbp, i_mb_count *
sizeof(int16_t) );
CHECKED_MALLOC( h-&mb.skipbp, i_mb_count *
sizeof(int8_t) );
CHECKED_MALLOC( h-&mb.mb_transform_size, i_mb_count
* sizeof(int8_t) );
CHECKED_MALLOC( h-&mb.intra4x4_pred_mode, i_mb_count
* 7 * sizeof(int8_t) );
CHECKED_MALLOC( h-&mb.non_zero_count, i_mb_count *
24 * sizeof(uint8_t) );
//以4*4块为单位,24=4*4+2*2+2*2(yuv)
CHECKED_MALLOC( h-&mb.nnz_backup,
h-&sps-&i_mb_width * 4 * 16 *
sizeof(uint8_t) );
h-&param.b_cabac )
CHECKED_MALLOC( h-&mb.chroma_pred_mode, i_mb_count *
sizeof(int8_t) );
CHECKED_MALLOC( h-&mb.mvd[0], 2*16 * i_mb_count *
sizeof(int16_t) );
//mvd[0]指x方向,2*16中的2指前向和后向,16表示mv以4*4为单位
CHECKED_MALLOC( h-&mb.mvd[1], 2*16 * i_mb_count *
sizeof(int16_t) );
&&& for( i=0;
i&2; i++ )
int i_refs = X264_MIN(16, (i ? 1 :
h-&param.i_frame_reference) +
h-&param.b_bframe_pyramid)
h-&param.b_
for( j=0; j & i_ j++ )
&&&&&&&&&&&
CHECKED_MALLOC( h-&mb.mvr[i][j], 2 * i_mb_count *
sizeof(int16_t) );
&&& for( i=0;
i&=h-&param.b_ i++ )
for( j=0; j&3; j++ )
&&&&&&&&&&&
CHECKED_MALLOC( h-&mb.intra_border_backup[i][j],
h-&fdec-&i_stride[j] );
&&&&&&&&&&&
h-&mb.intra_border_backup[i][j] += 8;
///////3.1.6 end
if( x264_ratecontrol_new( h ) & 0 ) return
//码率控制初始化
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 复制文件提示内存不足 的文章

 

随机推荐