x264程序框架流程分析
x264编码详细文字全过程
x264编码详细文字全过程(转)((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=paramvui信息主要包括帧率、图像尺寸等信息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 )读取一帧,并把这帧设为prev3. 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 );重点。
x264程序框架流程分析
1、x264程序框架流程分析(1) 进入x264.c 的int main( int argc, char **argv ) 函数main 函数中主要有以下三个主要的步骤,分别调用了3个函数。
第一步、对编码器进行参数设定。
函数实现如下:x264_param_default( x264_param_t *param );此函数在common.c中定义,完成一个x264_param_t 结构体的初始化。
第二步、分析参数,读入运行参数完成文件打开。
函数实现如下:Parse( argc, argv, x264_param_t *param, &opt );此函数在x264.c 中定义。
VC的运行参数我们可以设置:“-o out.264 football.yuv 352x288”则具体到Parse函数,其输入的参数argc == 5 ,这个数值大小等于要放下下面的argv所需要的二维数组的行数。
参数argc和argv在进入main 函数之前就已经确定了。
argv 为字符串char **的类型的,相当于一个二维数组,其具体内容如下:argv[0][] ==“F:\x264-snapshot-20070331-2245\build\win32\bin\x264.exe”argv[1][] == “-o”argv[2][] == “out.264”argv[3][] == “football.yuv”argv[4][] == “352x288”第三步、开始编码。
函数实现如下:Encode( x264_param_t *param, &opt ); 此函数在x264.c 中定义。
(2) static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *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_long()解析入口地址的向量,最后int c 得到的是运行参数(“-o out.264 football.yuv 352x288”)中前面“-o”中“o”的ASCII值即c = 111 。
MP4介绍与基本AVC编码(x264)教程
首先简单介绍一下MPEG是什么:MPEG是Motion Picture Expert Group的缩写,简单讲就是个行业里的组织,专门对数字内容做出业界规范的组织。
其实从MPEG1开始我们就广泛认识到这个组织和他们的标准了。
VCD就是其中最主要的代表。
在当时亚洲国家VCD格式十分流行,如果没记错VCD这个具体的格式是从日本而来的,并遵守MPEG1规格。
之后便是MPEG2,具体代表是DVD。
知道现在都是主流数码格式。
说道这里大家应该开始明白MPEG这个组织其实他的责任就是推广每一代新的数字媒体规范或是规格,而不是实际的产品。
换句白话就是说,政府来规定符合什么样标准的汽车可以上路,然后各个汽车公司按照这个具体的标准来制作自己的汽车,通过政府规定的汽车才可以上路。
张三李四都可以开发自己符合mpeg规格的codec和container(这个是什么我之后会解释),并且理论上拿到别人同样按照这个规格开发的产品上照样可以工作。
具体例子就好比制作DVD的方法千千万万,好莱坞用来做大片,个人也可以把自家拍的DV刻成DVD。
理论上讲都可以在放在任何DVD机里播放(这里不考虑个别不兼容问题)。
这也是为什么明明XVID编码的dvdrip大家用ffdshow也可以照样看。
所以说这就是规格统一的好处!!说了这么多转入正题:我们要讨论的MP4格式。
从名字就可以看出来她是高于MPEG1、2的新一代数字媒体格式(本人到现在还不知道为什么没有3直接从2跳到4了,知道达人请在后面的帖子里补充)现在就具体介绍一下MP4的具体规格:(很多内容来源与,英文好的朋友我强烈建议去那里看看,消费层用户里面那里是很权威的)- ISO 14496-1 (Systems) - 户动界面(有点像DVD里的菜单)- ISO 14496-2 (Video) - ASP(Advanced Simple Profile)就是其中一种,代表产品有Xvid,Divx5等等。
中文版MeGUI的x264编码配置详细解读
中文版MeGUI的x264编码配置详细解读有人向往和膜拜科班出身的,这个我同意,但若无视艹根的存在,那么我很生气,往往是民间高手如林,自己何必妄自菲薄。
一早我也说过不敢在压制的方向前进,那是因为这是个苦力不讨好的工作,有空我还不如多看看美女……只要关注,网络上的东西随处可以学习到,就对这个x264编码配置来说,网络上的资源多如脚毛。
我嫉妒恨的是科班出身的总会先天就有优势,民间人士门要蛋定,不要比,不要计较,自己玩自己的,不用理正统不正统。
我们的优势在于,科班人士再厉害他也没有拍电影也没做导演也没潜了哪个女优,嘎嘎,扯远了。
本文主要讲解x264编码的设置问题,是对应本人推荐的中文版MeGUI所做的一次详细配置论述,请用英文版的英雄路过不要取笑,也应该不要盲目的路过。
我还是传统的图文方式来一起研究,当然我说的不一定对,那是因为网络上也是这么教的,可能一直都错着我也用错的来说,在这次解释中,我会用一些自己理解的来说话,也许更能让民间人士理解。
普及一下别的知识,在各大影视论坛基本不允许发布以RMVB格式的影片,你只要用上x264的编码就意味着你和RMVB说88了。
民间人士普遍认为RMVB体积小画面清晰,但科班的人士会用理论的知识告诉你,同样的片源和同样的码率下,x264的mkv绝对比rmvb画质更好,文件更小。
这是为什么?你要问这是为什么?那是因为x264的编码机制发挥了无法想像的作用,你只要实践了就会明白这是真的,打住不说了反正就像磁带被光盘取代,windows98被xp顶上。
科班厉害之处是懂得比较多,早前的x264.exe是命令参数实现压制的,DOS级的工具很怕黑的就不要尝试了。
GUI,是图形界面操作,适合民间高手使用。
曾被科班出身的嘲笑过,说使用MeGU压制的都是水货,嘎嘎,我笑笑不能回答。
科班告诉我就这GUI不是只有Me 的,这下你明白没?像ripbot、staxrip 等都是GUI……行了行了,民间人士也不用惭愧,至少MeGUI能自动更新编码,还有汉化版可以用,batch的是高手,不和他们一起玩!安装MeGUI简体中文版(请回顾本人以前的大作,这里略去几百万字……)仿图挂,可下载中文版MeGUI的x264编码配置详细解读或是到QQ空间查阅软件下载转到论坛页面以前的中文版本MeGUI太不专业了,直到你碰到我这个民间人士才集合了比较强大的中文版MeGUI,不要怕出错,出错不是你的错。
x264代码分析
1.X264相关代码分析编码时,维持着三个队列:frame_next队列(临时缓存,帧类型没确定,待编码的帧队列),frame_current队列(按编码顺序排放,已经确定了帧的类型,正在编码的帧队列)和frame_unused队列(空白队列,将要编码的帧放入该队列)。
1. x264_reference_update:(更新参考帧队列,若为B帧则不更新)将上一个参考帧放入参考帧队列,并从空闲队列中取出一帧作为当前参考工作帧;2. x264_frame_pop_unused:(获取一帧的空间fenc,用来存放待编码的帧)若unused队列不为空,则将取出的帧放入unused队列(x264_frame_pop),否则,分配一帧空间(x264_frame_new);3. x264_frame_copy_picture:将该帧图像拷贝到fenc中4. 判断是否需要进行边界扩展(x264_frame_expand_border_mod16),不能被16整除的都需要进行扩展。
5. 将fenc放入frame_next中(x264_frame_push)6. 如果用到半精度亮度块,需要进行1/2像素扩展(x264_frame_init_lowres)7. 若frame.current[0]==NULL(当前队列中没有帧需要编码)I.若frame.next[0]==NULL,结束编码(x264_encoder_frame_end)II.判断帧类型(x264_slicetype_decide)III.将帧类型确定的帧重新排列存放在frame.current队列中8. 调整当前队列中帧的顺序,开始编码9. 对编码之后的nal封装成NAL单元(x264_nal_encode);10.将NALU单元写入输出文件(p_write_nalu)1. 根据帧类型设置NAL的类型和优先级,若是IDR帧,则清空所有参考帧(x264_reference_reset)2. 初始化参考队列(x264_reference_build_list),list0 前向参考队列,P帧参考;list1,后向参考队列(B帧参考list0和list1)3. 码率控制初始化(x264_ratecontrol_start),得到该帧所使用的量化步长QP(x264_ratecontrol_qp)4. .创建片头数据(x264_slice_init)5. 初始化比特流(bs_init)当前帧为IDR帧时,NAL单元更新SPS(x264_sps_write)和PPS(x264_pps_write)6. 写片操作,返回编完一帧之后的比特流(x264_slices_write)7. 恢复CPU状态(x264_cpu_restore)8. 若帧类型是P帧,分别计算帧内编码开销和帧间编码开销,若帧间>帧内,则将P 帧重新按I帧进行编码(若图片组的大小>=最小关键帧的个数,则按IDR帧编码)。
x264流程详细分析(1)
帧内帧间编码部分关键函数1.1 x264_encoder_open (x264_param_t *param )功能分配编码器所需结构体。
检测参数有效性。
初始化sps和pps。
分配编码器所需memory空间,包括参考帧和缓冲帧。
初始化macroblock cache相应空间。
分配编码器所需memory空间,包括参考帧和缓冲帧。
根据cpu类型,初始化各指令集加速函数。
输入项目x264_param_t *param输出项目x264_t *h存储分配x264_t *h = x264_malloc( sizeof( x264_t ) );h->out.p_bitstream = x264_malloc( h->out.i_bitstream );h->frames.unused[i] = x264_frame_new( h );h->frames.reference[i] = x264_frame_new( h );x264_ratecontrol_new( h ) ;1.2 x264_encoder_encode(x264_t *h, x264_nal_t **pp_nal, int *pi_nal, x264_picture_t *pic)功能处理编码帧队列,并设置编码帧类型。
初始化参考列表、码率控制等相关信息。
根据编码信息,写sps、pps位流。
调用int x264_slices_write( x264_t *h )函数编码一帧。
更新码率控制、噪声抑制等参数。
输入项目x264_t *hx264_nal_t **pp_nalint *pi_nalx264_picture_t *pic输出项目Log文件等。
程序逻辑如附图1所示。
附图1:x264_encoder_encode流程图1.3 x264_slice_write(x264_t *h, int i_nal_type, int i_nal_ref_idc) 功能调用x264_slice_header_write函数向码流写入slice headerLoad宏块到cache宏块分析宏块编码宏块写cavlc/cabac保存宏块到cache输入项目x264_t *hint i_nal_typeint i_nal_ref_idc程序逻辑如附图2所示:附图2:x264_slice_write流程图1.4 x264_macroblock_analyse(x264_t *h )功能初始化;对于I Slice,遍历所有16x16和4x4预测模式,得到最小Cost所对应的分块方式和预测模式;对于P Slice,如果宏块周围有Skip模式的宏块,检测当前块是否是Skip 块,如果不是Skip块,遍历所有可能的块划分模式,对每种模式进行运动估计并计算Cost,计算intra对应的16x16和4x4的Cost(包括色度),比较所有的Cost,选最小的Cost对应的模式为最终模式。
X264编码流程详细分析
量化放缩 判断是否为skip模式, 并进行相关处理 x264_mb_encode_8x8
色度编码结束
计算亮度色度句型,以及非零计数
存储cbp: coded_ block_ pattern
检查P/B跳过情况, 进行相应处理
宏块编码结束
Encoder.c: x264_mb_encode_i16x16
反量化所有dct[1][][]中的系数
是
i<16
否 对dct[0][][]中的所有dc系数 进行量化和扫描,解雇存放于 h->dct.luma16x16_dc
重建块(idct,反量化),并将结果 存放入h->mb.pic.p_fdec[0]
宏块编码结束
Encoder.c: x264_mb_encode_i4×4 对4*4子块dct变换从p_src和p_dst 取出数据变换以后放在dct4*4中
X264.c: main
设置打开文件的方式
_setmode
x264_param_default 设置编码器默认参数
读取命令行并分析 Parse 编码Encode
X264.c:Encode
得到总帧数 p_get_frame_total 初始化编码器 x264_encoder_open 设置输出文件参数 p_set_outfile_param 为新图分配空间 x264_picture_alloc
选择帧类型
移动一些B 帧 和一个非B帧到编 码队列中去
获得要被编码 的帧
编码 Do encode
判断是否存放 重建的帧
计算和打印 统计值
更新编码器状态
设置输出 图片属性
Encoder.c: do encode
设置帧上下文 (初始化帧类型) 初始化 (参考列表,bit率控制)
ffmpeg-x264代码解析
ffmpeg-x264代码解析主要就是以下几个知识点:1. v4l2接口:2. X11的本地回显:3. 使用libswscale 进行拉伸:4. 使用libx264 压缩:1. v4l2接口: 大眼一看, 密密丫丫的VIDIOC_XXXX, 其实静下心来, 也没多少, 很清晰, 大体流程如下:capture_open(name)open /dev/video0 // 打开设备check driver caps // 检查一些capsVIDIOC_REQBUFS // 使用streaming mode, mmap mode, 分配VIDIOC_QUERYBUF// 获取分配的buf, 并且mmap到进程空间mmapVIDIOC_QBUF // buf 入列VIDIOC_STREAMON // 开始使用的数据结构[c-sharp] view plaincopyprint?struct Buffer{void *start;size_t length;};typedef struct Buffer Buffer;struct Ctx{int vid;int width, height; // 输出图像大小struct SwsContext *sws; // 用于转换int rows; // 用于sws_scale()int bytesperrow; // 用于cp到pic_srcAVPicture pic_src, pic_target; // 用于sws_scaleBuffer bufs[2]; // 用于mmap};typedef struct Ctx Ctx;capture_open(...) 打开设备[cpp] view plaincopyprint?void *capture_open (const char *dev_name, int t_width, int t_height){int id = open(dev_name, O_RDWR);if (id < 0) return 0;Ctx *ctx = new Ctx;ctx->vid = id;// to query capsv4l2_capability caps;ioctl(id, VIDIOC_QUERYCAP, &caps);if (caps.capabilities &V4L2_CAP_VIDEO_CAPTURE) {if (caps.capabilities &V4L2_CAP_READWRITE) {// TODO: ...}if (caps.capabilities &V4L2_CAP_STREAMING) {// 检查是否支持MMAP, 还是USERPTRv4l2_requestbuffers bufs;memset(&bufs, 0, sizeof(bufs));bufs.count = 2;bufs.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;bufs.memory = V4L2_MEMORY_MMAP;if (ioctl(id, VIDIOC_REQBUFS, &bufs) < 0) {fprintf(stderr, "%s: don't support MEMORY_MMAP mode!/n", __func__);close(id);delete ctx;return 0;}fprintf(stderr, "%s: using MEMORY_MMAP mode, buf cnt=%d/n", __func__, bufs.count);// mmapfor (int i = 0; i < 2; i++) {v4l2_buffer buf;memset(&buf, 0, sizeof(buf));buf.type = bufs.type;buf.memory = bufs.memory;if (ioctl(id, VIDIOC_QUERYBUF,&buf) < 0) {fprintf(stderr, "%s:VIDIOC_QUERYBUF ERR/n", __func__);close(id);delete ctx;return 0;}ctx->bufs[i].length = buf.length;ctx->bufs[i].start = mmap(0, buf.length, PROT_READ|PROT_WRITE,MAP_SHARED, id,buf.m.offset);}}else {fprintf(stderr, "%s: can't support read()/write() mode and streaming mode/n", __func__);close(id);delete ctx;return 0;}}else {fprintf(stderr, "%s: can't support video capture!/n", __func__);close(id);delete ctx;return 0;}int rc;// enum all support image fmtv4l2_fmtdesc fmt_desc;uint32_t index = 0;// 看起来, 不支持plane fmt, 直接使用yuyv 吧, 然后使用libswscale 转换#if 0do {fmt_desc.index = index;fmt_desc.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;rc = ioctl(id, VIDIOC_ENUM_FMT,&fmt_desc);if (rc >= 0) {fprintf(stderr, "/t support %s/n",fmt_desc.description);}index++;} while (rc >= 0);#endif // 0v4l2_format fmt;fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;rc = ioctl(id, VIDIOC_G_FMT, &fmt);if (rc < 0) {fprintf(stderr, "%s: can't VIDIOC_G_FMT.../n",__func__);return 0;}PixelFormat pixfmt = PIX_FMT_NONE;switch (fmt.fmt.pix.pixelformat) {case V4L2_PIX_FMT_YUYV:pixfmt = PIX_FMT_YUYV422;break;}if (pixfmt == PIX_FMT_NONE) {fprintf(stderr, "%s: can't support %4s/n", __func__,(char*)&fmt.fmt.pix.pixelformat);return 0;}// 构造转换器fprintf(stderr, "capture_width=%d, height=%d, stride=%d/n", fmt.fmt.pix.width, fmt.fmt.pix.height,fmt.fmt.pix.bytesperline);ctx->width = t_width;ctx->height = t_height;ctx->sws = sws_getContext(fmt.fmt.pix.width, fmt.fmt.pix.height, pixfmt,ctx->width, ctx->height,PIX_FMT_YUV420P, // PIX_FMT_YUV420P 对应X264_CSP_I420SWS_FAST_BILINEAR, 0, 0, 0);ctx->rows = fmt.fmt.pix.height;ctx->bytesperrow = fmt.fmt.pix.bytesperline;avpicture_alloc(&ctx->pic_target,PIX_FMT_YUV420P, ctx->width, ctx->height);// queue buffor (int i = 0; i < sizeof(ctx->bufs)/sizeof(Buffer); i++) {v4l2_buffer buf;memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = i;if (ioctl(id, VIDIOC_QBUF, &buf) < 0) {fprintf(stderr, "%s: VIDIOC_QBUF err/n",__func__);exit(-1);}}int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(id, VIDIOC_STREAMON, &type) < 0) { fprintf(stderr, "%s: VIDIOC_STREAMON err/n", __func__);exit(-1);}return ctx;}capture_get_pic()VIDIOC_DQBUF // 出列,sws_scale// 格式转换/拉伸到PIX_FMT_YUV420P, 准备方便压缩VIDIOC_QBUF // 重新入列capture_get_picture(...) 从摄像头得到一帧图片[cpp] view plaincopyprint?int capture_get_picture (void *id, Picture *pic){// 获取, 转换Ctx *ctx = (Ctx*)id;v4l2_buffer buf;memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;if (ioctl(ctx->vid, VIDIOC_DQBUF, &buf) < 0) {fprintf(stderr, "%s: VIDIOC_DQBUF err/n",__func__);return -1;}// _save_pic(ctx->bufs[buf.index].start, buf.length);// __asm("int $3");ctx->pic_src.data[0] = (unsignedchar*)ctx->bufs[buf.index].start;ctx->pic_src.data[1] = ctx->pic_src.data[2] =ctx->pic_src.data[3] = 0;ctx->pic_src.linesize[0] = ctx->bytesperrow;ctx->pic_src.linesize[1] = ctx->pic_src.linesize[2] = ctx->pic_src.linesize[3] = 0;// sws_scaleint rs = sws_scale(ctx->sws, ctx->pic_src.data, ctx->pic_src.linesize,0, ctx->rows, ctx->pic_target.data,ctx->pic_target.linesize);// outfor (int i = 0; i < 4; i++) {pic->data[i] = ctx->pic_target.data[i];pic->stride[i] = ctx->pic_target.linesize[i];}// re queue bufif (ioctl(ctx->vid, VIDIOC_QBUF, &buf) < 0) {fprintf(stderr, "%s: VIDIOC_QBUF err/n", __func__);return -1;}return 1;}2. X11 的本地回显: 采用XShm, 效率还行vs_open ()XOpenDisplay()XCreateSimpleWindow()XCreateGC()XMapWindow()XShmCreateImage()shmget()shmat()使用的数据结构[cpp] view plaincopyprint?struct Ctx{Display *display;int screen;Window window;GC gc;XVisualInfo vinfo;XImage *image;XShmSegmentInfo segment;SwsContext *sws;PixelFormat target_pixfmt;AVPicture pic_target;int v_width, v_height;int curr_width, curr_height;};typedef struct Ctx Ctx;vs_open(...) 打开设备[cpp] view plaincopyprint?void *vs_open (int v_width, int v_height){Ctx *ctx = new Ctx;ctx->v_width = v_width;ctx->v_height = v_height;// windowctx->display = XOpenDisplay(0);ctx->window =XCreateSimpleWindow(ctx->display, RootWindow(ctx->display, 0),100, 100, v_width, v_height, 0, BlackPixel(ctx->display, 0),WhitePixel(ctx->display, 0));ctx->screen = 0;ctx->gc = XCreateGC(ctx->display,ctx->window, 0, 0);XMapWindow(ctx->display, ctx->window);// current screen pix fmtWindow root;unsigned int cx, cy, border, depth;int x, y;XGetGeometry(ctx->display, ctx->window, &root, &x, &y, &cx, &cy,&border, &depth);// visual infoXMatchVisualInfo(ctx->display, ctx->screen,depth, DirectColor, &ctx->vinfo);// imagectx->image = XShmCreateImage(ctx->display, ctx->vinfo.visual, depth, ZPixmap, 0,&ctx->segment, cx, cy);if (!ctx->image) {fprintf(stderr, "%s: can't XShmCreateImage !/n", __func__);exit(-1);}ctx->segment.shmid = shmget(IPC_PRIVATE,ctx->image->bytes_per_line *ctx->image->height,IPC_CREAT | 0777);if (ctx->segment.shmid < 0) {fprintf(stderr, "%s: shmget err/n", __func__);exit(-1);}ctx->segment.shmaddr =(char*)shmat(ctx->segment.shmid, 0, 0);if (ctx->segment.shmaddr == (char*)-1) {fprintf(stderr, "%s: shmat err/n", __func__);exit(-1);}ctx->image->data = ctx->segment.shmaddr;ctx->segment.readOnly = 0;XShmAttach(ctx->display, &ctx->segment);PixelFormat target_pix_fmt = PIX_FMT_NONE;switch (ctx->image->bits_per_pixel) {case 32:target_pix_fmt = PIX_FMT_RGB32;break;case 24:target_pix_fmt = PIX_FMT_RGB24;break;default:break;}if (target_pix_fmt == PIX_FMT_NONE) {fprintf(stderr, "%s: screen depth format err/n",__func__);delete ctx;return 0;}// swsctx->target_pixfmt = target_pix_fmt;ctx->curr_width = cx;ctx->curr_height = cy;ctx->sws = sws_getContext(v_width, v_height,PIX_FMT_YUV420P,cx, cy, target_pix_fmt,SWS_FAST_BILINEAR, 0, 0, 0);avpicture_alloc(&ctx->pic_target,target_pix_fmt, cx, cy);XFlush(ctx->display);return ctx;}vs_show()sws_scale() // 拉伸到当前窗口大小, 转换格式XShmPutImage() // 显示, 呵呵, 真的很简单vs_show(...) 主要代码都是处理窗口变化的[cpp] viewplaincopyprint?int vs_show (void *ctx, unsigned char *data[4], int stride[4]) {// 首选检查sws 是否有效, 根据当前窗口大小决定Ctx *c = (Ctx*)ctx;Window root;int x, y;unsigned int cx, cy, border, depth;XGetGeometry(c->display, c->window,&root, &x, &y, &cx, &cy,&border, &depth);if (cx != c->curr_width || cy != c->curr_height) { avpicture_free(&c->pic_target);sws_freeContext(c->sws);c->sws = sws_getContext(c->v_width,c->v_height, PIX_FMT_YUV420P,cx, cy, c->target_pixfmt,SWS_FAST_BILINEAR, 0, 0, 0);avpicture_alloc(&c->pic_target,c->target_pixfmt, cx, cy);c->curr_width = cx;c->curr_height = cy;// re create imageXShmDetach(c->display,&c->segment);shmdt(c->segment.shmaddr);shmctl(c->segment.shmid, IPC_RMID, 0);XDestroyImage(c->image);c->image = XShmCreateImage(c->display, c->vinfo.visual, depth, ZPixmap, 0,&c->segment, cx, cy);c->segment.shmid = shmget(IPC_PRIVATE,c->image->bytes_per_line *c->image->height,IPC_CREAT | 0777);c->segment.shmaddr =(char*)shmat(c->segment.shmid, 0, 0);c->image->data = c->segment.shmaddr;c->segment.readOnly = 0;XShmAttach(c->display,&c->segment);}//sws_scale(c->sws, data, stride, 0, c->v_height,c->pic_target.data, c->pic_target.linesize);// cp to imageunsigned char *p = c->pic_target.data[0], *q = (unsigned char*)c->image->data;int xx = MIN(c->image->bytes_per_line,c->pic_target.linesize[0]);for (int i = 0; i < c->curr_height; i++) {memcpy(q, p, xx);p += c->image->bytes_per_line;q += c->pic_target.linesize[0];}// 显示到X 上XShmPutImage(c->display, c->window, c->gc, c->image, 0, 0, 0, 0, c->curr_width, c->curr_height, 1);return 1;}3. libswscale: 用于picture格式/大小转换, 占用cpu挺高:), 用起来很简单, 基本就是sws = sws_getContext(....);sws_scale(sws, ...)4. libx264 压缩: 考虑主要用于互动, 所以使用preset=fast, tune=zerolatency, 320x240, 10fps, 300kbps, jj实测延迟很低, 小于100ms使用的数据结构[cpp] view plaincopyprint?struct Ctx{x264_t *x264;x264_picture_t picture;x264_param_t param;void *output; // 用于保存编码后的完整帧int output_bufsize, output_datasize;int64_t pts; // 输入ptsint64_t (*get_pts)(struct Ctx *);int64_t info_pts, info_dts;int info_key_frame;int info_valid;};vc_open(...) 设置必要的参数, 打开编码器[cpp] view plaincopyprint?void *vc_open (int width, int height){Ctx *ctx = new Ctx;// 设置编码属性//x264_param_default(&ctx->param);x264_param_default_preset(&ctx->param, "fast", "zerolatency");ctx->param.i_width = width;ctx->param.i_height = height;ctx->param.b_repeat_headers = 1; // 重复SPS/PPS 放到关键帧前面ctx->param.b_cabac = 1;ctx->param.i_fps_num = 10;ctx->param.i_fps_den = 1;ctx->param.i_keyint_max = 30;ctx->param.i_keyint_min = 10;// rcctx->param.rc.i_rc_method = X264_RC_CRF;ctx->param.rc.i_bitrate = 300;//ctx->param.rc.f_rate_tolerance = 0.1;//ctx->param.rc.i_vbv_max_bitrate =ctx->param.rc.i_bitrate * 1.3;//ctx->param.rc.f_rf_constant = 600;//ctx->param.rc.f_rf_constant_max =ctx->param.rc.f_rf_constant * 1.3;#ifdef DEBUGctx->param.i_log_level = X264_LOG_WARNING; #elsectx->param.i_log_level = X264_LOG_NONE;#endif // releasectx->x264 =x264_encoder_open(&ctx->param);if (!ctx->x264) {fprintf(stderr, "%s: x264_encoder_open err/n", __func__);delete ctx;return 0;}x264_picture_init(&ctx->picture);ctx->picture.img.i_csp = X264_CSP_I420;ctx->picture.img.i_plane = 3;ctx->output = malloc(128*1024);ctx->output_bufsize = 128*1024;ctx->output_datasize = 0;ctx->get_pts = first_pts;ctx->info_valid = 0;return ctx;}vc_compress(...) 压缩, 如果成功, 得到串流[c-sharp] view plaincopyprint?static int encode_nals (Ctx *c, x264_nal_t *nals, int nal_cnt) {char *pout = (char*)c->output;c->output_datasize = 0;for (int i = 0; i < nal_cnt; i++) {if (c->output_datasize + nals[i].i_payload > c->output_bufsize) {// 扩展c->output_bufsize =(c->output_datasize+nals[i].i_payload+4095)/4096*4096;c->output = realloc(c->output,c->output_bufsize);}memcpy(pout+c->output_datasize,nals[i].p_payload, nals[i].i_payload);c->output_datasize += nals[i].i_payload;}return c->output_datasize;}int vc_compress (void *ctx, unsigned char *data[4], int stride[4], const void **out, int *len){Ctx *c = (Ctx*)ctx;// 设置picture 数据for (int i = 0; i < 4; i++) {c->picture.img.plane[i] = data[i];c->picture.img.i_stride[i] = stride[i];}// encodex264_nal_t *nals;int nal_cnt;x264_picture_t pic_out;c->picture.i_pts = c->get_pts(c);#ifdef DEBUG_MOREstatic int64_t _last_pts = c->picture.i_pts;fprintf(stderr, "DBG: pts delta = %lld/n",c->picture.i_pts - _last_pts);_last_pts = c->picture.i_pts;#endif //x264_picture_t *pic = &c->picture;do {// 这里努力消耗掉delayed frames ???// 实际使用zerolatency preset 时, 效果足够好了int rc = x264_encoder_encode(c->x264,&nals, &nal_cnt, pic, &pic_out);if (rc < 0) return -1;encode_nals(c, nals, nal_cnt);} while (0);*out = c->output;*len = c->output_datasize;if (nal_cnt > 0) {c->info_valid = 1;c->info_key_frame = pic_out.b_keyframe;c->info_pts = pic_out.i_pts;c->info_dts = pic_out.i_dts;}else {fprintf(stderr, ".");return 0; // 继续}#ifdef DEBUG_MOREstatic size_t _seq = 0;fprintf(stderr, "#%lu: [%c] frame type=%d, size=%d/n", _seq,pic_out.b_keyframe ? '*' : '.',pic_out.i_type, c->output_datasize);_seq++;#endif // debugreturn 1;}main.cpp 主流程capture.cpp, capture.h 获取v4l2 的图像帧vcompress.cpp vcompress.h 实现x264 的压缩vshow.cpp vsho.h 用X11 显示实时图像。
X264编码过程解析
初始化static av_cold int X264_init(AVCodecContext *avctx);(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 )/* Create a copy of param */ h->param=param/* VUI */vui信息主要包括帧率、图像尺寸等信息/* Init x264_t */x264_sps_init( h->sps, 0, &h->param );序列图像集x264_pps_init( h->pps, 0, &h->param, h->sps);图像参数集/* Init frames. */ 初始化并开辟帧空间/* init mb cache */ 对前一宏块的信息保存,因为是初始化,所以作为第一个宏块的参考,后面会有x264_macroblock_cache_load( h, i_mb_x, i_mb_y );它是将要编码的宏块的周围的宏块的值读进来, 要想得到当前块的预测值,要先知道上面,左面的预测值/* init cabac adaptive model *//* init CPU functions */ 初始化cpu对各种分块的参数设定/* rate control */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 )读取一帧,并把这帧设为prev3. 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.返回,对下一帧进行编码编码static int X264_frame(AVCodecContext *ctx, AVPacket *pkt, const AVFrame *frame, int *got_packet)(1).x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out )对帧进行编码1./* 1: Copy the picture to a frame and move it to a buffer */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 ); /* Slice header */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 );重点。
x.264入门
首先介绍一下要使用到的工具。
由于这是新手教程,因此我们暂且不提命令行压制,而采用GUI方式进行。
x264是一款免费的H.264编码器。
有VFW(适用于VDM)和CLI(命令行界面)两种。
mencoder可以用来调用x264的库进行编码。
meGUI - x264 CLI & mencoder (XviD, lavc, Snow) GUI with (HE)AAC e ncoding and MP4 mux,看名字就知道,是x264 CLI、mencoder的GUI界面,而且同时可以进行(he-)aac音频编码和mp4的视频/音频合并工作。
我们的压制工作将使用meGUI挂接mencoder/x264 CLI来完成。
首先请下载压制所需要的工具(并解压或者安装):.NET Framework 1.1 .net运行库- 如果你没有安装 2003的话,还是下载吧……最新版本的x264 CLI最新版本的mplayer 需要注意的是,最新版本是在最下面。
请选用适合你的C PU的版本。
mplayer内附有mencoder程序。
mencoder必须的一个动态链接库下载完后,请把它安放在与mencoder相同的目录下。
否则是无法压制x264的。
meGUI 不好意思忘记了最重要的东东的链接……就在那贴的附件里解压完成以后,请运行meGUI,在tools - settings中定位好mencoder和x26 4的路径。
下方的default priority请选择low。
x264 encoder这个选项与你想要的输出格式有关。
输出mp4请使用x264,输出avi则使用mencoder。
注意:关于输出格式:mp4:通常情况下mp4输出是第一选择。
因为通常输出mp4以后,可以将视频和音频、字幕等一起封装在mkv中(推荐)。
mmg对mp4的支持是native mode,兼容性很好;而对于avi中的avc则不能很好地封装。
x264源代码概述框架分析架构分析
x264源代码概述框架分析架构分析函数背景⾊函数在图中以⽅框的形式表现出来。
不同的背景⾊标志了该函数不同的作⽤:⽩⾊背景的函数:不加区分的普通内部函数。
浅红背景的函数:libx264类库的接⼝函数(API)。
粉红⾊背景函数:滤波函数(Filter)。
⽤于环路滤波,半像素插值,SSIM/PSNR的计算。
黄⾊背景函数:分析函数(Analysis)。
⽤于帧内预测模式的判断,或者帧间预测模式的判断。
绿⾊背景的函数:宏块编码函数(Encode)。
通过对残差的DCT变换、量化等⽅式对宏块进⾏编码。
紫⾊背景的函数:熵编码函数(Entropy Coding)。
对宏块编码后的数据进⾏CABAC或者CAVLC熵编码。
蓝⾊背景函数:汇编函数(Assembly)。
做过汇编优化的函数。
图中主要画出了这些函数的C语⾔版本,此外这些函数还包含MMX版本、SSE版本、NEON版本等。
浅蓝⾊背景函数:码率控制函数(Rate Control)。
对码率进⾏控制的函数。
具体的⽅法包括了ABR、CBR、CRF等。
区域整个关系图可以分为以下⼏个区域:最左边区域——x264命令⾏程序函数区域。
左边中间区域——libx264内部函数区域。
右上⽅粉红⾊区域——滤波模块。
其中包括了环路滤波,半像素插值,SSIM/PSNR计算。
右上⽅黄⾊区域——分析模块。
其中包含了帧内预测模式分析以及帧间运动估计等。
右中间绿⾊区域——宏块编码模块。
其中包含了针对编码帧的DCT变换,量化,Hadamard变换等;以及针对重建帧的DCT反变换,反量化,Hadamard反变换等。
右下⽅紫⾊区域——熵编码模块。
其中包含了CABAC或者CAVLC熵编码。
箭头线箭头线标志了函数的调⽤关系:⿊⾊箭头线:不加区别的调⽤关系。
粉红⾊的箭头线:滤波函数(Filter)之间的调⽤关系。
黄⾊箭头线:分析函数(Analysis)之间的调⽤关系。
绿⾊箭头线:宏块编码函数(Encode)之间的调⽤关系。
x264代码剖析(十七):核心算法之熵编码(EntropyEncoding)
x264代码剖析(十七):核心算法之熵编码(EntropyEncoding)熵编码是无损压缩编码方法,它生产的码流可以经解码无失真地恢复出原始数据。
熵编码是建立在随机过程的统计特性基础上的。
本文对熵编码中的CAVLC(基于上下文自适应的可变长编码)和CABAC (基于上下文的自适应二进制算术熵编码)进行简单介绍,并给出x264中熵编码对应的代码分析。
在H.264的CAVLC中,通过根据已编码句法元素的情况,动态调整编码中使用的码表,取得了极高的压缩比。
CAVLC用于亮度和色度残差数据的编码,CAVLC充分利用残差经过整数变换、量化后数据的特性进行压缩,进一步减少数据中的冗余信息,为H.264的编码效率的提升奠定了基础。
CAVLC的编码过程主要包括以下五个步骤:(1)对非零系数的数目(TotalCoeffs)以及拖尾系数的数目(TrailingOnes)进行编码;(2)对每个拖尾系数的符号进行编码;(3)对除了拖尾系数之外的非零系数的幅值(Levels)进行编码;(4)对最后一个非零系数前零的数目(TotalZeros)进行编码;(5)对每个非零系数前零的个数(RunBefore)进行编码。
在最新国际视频编码标准(High Efficiency Video Coding, HEVC)中,熵编码模块摒弃了CAVLC(基于上下文自适应的可变长编码),而仅仅采用了CABAC(基于上下文的自适应二进制算术熵编码),故本文只分析CAVLC在x264中的代码,对于CABAC的原理以及代码分析将在x265相关的代码分析文章中进行介绍。
在x264中,熵编码模块对应的函数关系图如下图所示:从图中可以看出,熵编码模块包含两个函数x264_macroblock_write_cabac()和x264_macroblock_write_cavlc()。
如果输出设置为CABAC编码,则会调用x264_macroblock_write_cabac();如果输出设置为CAVLC编码,则会调用x264_macroblock_write_cavlc()。
x264VFW配置详解
x264VFW配置详解上图为x264vfw图形化配置图。
由于没有汉化版本,使得许多使用者受到约束,望而却步。
下面结合配置图做一个能供非专业者参考的详解。
(蓝色字体是专门为非专业视频编辑者建议的推荐设置)注意:当前网络上常见的如A VI;TS;MKV等,这些视频封装格式,它们只是封装容器并视频非编码格式。
采用X246编码时视频分辨率及码率设置:全高清:(1920 x 1080),推荐7,000-8,000 Kbps常用高清:(1280 x 720)推荐5,000-6,000 Kbps普通:(640 x 480)推荐1,000-2,000 Kbps互联网:(320 x 240)推荐300-500 Kbps高清作为一种视频格式,但它却体现了拍摄、编辑、储存、传输、显示、音响效果等许多技术环节。
千万不要简单地认为使用了高清编码格式、使用了高清封装格式、使用了高清推荐的分辨率,渲染出来的视频就一定很清晰!视频是否清晰直接与素材相关,有了高清视频素材,也只能是能渲染出高清视频的第一步!好了,闲话少谈,进入x264vfw正题:H.264又称为MPEG4-A VC,MPEG系列的标准归属于ISO/IEC,但另一方面以制订国际通讯标准为主的机构:ITU-T,在完成H.263(针对视频会议之用的串流视频标准)后展开了更先进的H.264制订,且新制订是与ISO/IEC机构连手合作,由两机构共同成立一个名为JVT(Joint Video Team)的联合工作小组,以MPEG-4技术为基础进行更适于视频会议(Video Conference)运用的衍生发展,也因为是联合制订,因此在ITU-T方面称为H.264,在ISO/IEC的MPEG方面就称为MPEG-4 Part 10(第10部分,也叫ISO/IEC 14496-10),MPEG-4 Part 10的另一个代称是MPEG-4 A VC(Advanced Video Coding,先进视频编码),多个名称其实是一个意思,即H.264=MPEG-4 Part 10=ISO/IEC 14496-10=MPEG-4 A VC。
如何阅读x264代码
如何阅读代码最近我也开始看 X264 的代码了,于是想到把我读代码的过程记录下来,因为总有很多新手问如何读代码,我这个帖子就是专为这些人写的。
至于会读代码的人就完全没有必要看了。
下面当然是以 X264 为例了。
JM 以及其他代码的学习方法和技巧都是完全一样的。
我所用的版本是在帖子在VS2008下编译最新版的x264连接错误里上传的版本。
最新版本的代码基本结构应该变化不大。
首先肯定是要把 X264 编译通过了,这个我就不多说了,论坛帖子VS2008下最新X264(svn 2009.9)编译不过的解决办法(附编译通过+修改内存泄露版本)里讲得很清楚。
编译通过之后第一步就是设置编码参数,一开始尽量从最基本的学起,因此采用最简单的选项,我的参数是:-q 28 -o test.264f:/sequences/qcif/foreman_qcif.yuv 176x144 --no-asm,在哪里设置呢?看下面的截图(在第一个图中选择 properties 后就会出现第二个图了)。
至于各个参数的意义嘛,看一看 X264 的帮助信息(x264.c 里面的 Help 函数),这里说说我用的这些参数的意思。
-q 28 指定采用固定 QP = 28 进行编码,-o test.264 指定输出的码流文件名为 test.264,f:/sequences/qcif/foreman_qcif.yuv 这个当然是指定原始待编码图像序列了,176x144 指定待编码图像的宽高,--no-asm 指定不采用汇编优化,全 C 代码才方便我们跟踪噻。
对了,差点忘记了,要调试运行还要设置一下下面第二个截图的红色框内的内容。
==================================================================== ==============好了,配置完了就要开始调试运行了哦。
在 x264.c 文件中找到 main 函数,在x264_param_default 函数所在这一行上下断点(即把光标移到这一行,然后按F9),然后调试运行编码器(即按 F5),程序就会停在断点处(如下面截图)。
习笔记(一)(x264编码流程)
习笔记(一)(x264编码流程)经过一段时间的学习我对h264也有了一个初步的大体的了解,今天在这里说一下h264中x264的开源code的编码的解析并附一张我自己画的流程图便于大家理解,又不对的地方清大家指教一二,偶必定三顾茅庐寻得真理。
:)首先我们进入x264.c中的main函数.刚开始是读取默认参数,如果你设置了参数的话会修改param的.i_ret = Encode( ¶m, fin, fout );这条语句使过程进入x264.c中的Encode函数. (这个函数就是x264的编码程序)X.264_encode函数.A 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 )}这段调用了fseek()函数,对输入的视频文件计算其总帧数。
B. 函数 h = x264_encoder_open( param )对不正确的参数进行修改,并对各结构体参数和cabac编码,预测等需要的参数进行初始化.然后才能进行下一步的编码。
C. 函数 pic = x264_picture_new( h );定义在\CORE\common.c 中.此函数的作用是分给能容纳sizeof(x264_picture_t)字节数的空间,然后进行初始化.这里说明一下x264_picture_t和x264_frame_t的区别.前者是说明一个视频序列中每帧的特点.后者存放每帧实际的象素值.D. 调用fread()函数一次读入一帧,分亮度和色度分别读取.这里要看到c语言中的File文件有一个文件位置指示器,调用fread()函数会使文件指示器自动移位,这就是一帧一帧读取的实现过程.for( i_frame = 0, i_file = 0; i_ctrl_c == 0 ; i_frame++ ){int i_nal;x264_nal_t *nal;int i;/* read a frame */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 ){break;}这里文件已经指示器发生了位移if( x264_encoder_encode( h, &nal, &i_nal, pic ) < 0 ){fprintf( std err, “x264_encoder_encode failed\n” );}……}E. 进入x264_encoder_encode( h, &nal, &i_nal, pic )函数,该函数定义在/Enc/encoder.c中.函数中先定义了如下三个参数:int i_nal_type; nal存放的数据类型, 可以是sps,pps等多种.int i_nal_ref_idc; nal的优先级,nal重要性的标志位.int i_slice_type; slice的类型的这里先说明一下:我们假设一个视频序列如下:I B B P B B P我们编码是按I P B B P B B的顺序,这就是frame的编号但是编码器如何来区分他们并把他们重新排序呢?我们来看看编码器是如何区分读入的一帧是I帧,P帧,或者B帧?以I B B P B B P为例.if( h->i_frame % (h->param.i_iframe * h->param.i_idrframe) == 0 ){确定这是立即刷新片.}if( h->param.i_bframe > 0 )//判断h是否为B帧然后对其进行下一步操作.我们编完I帧后碰到了一个B帧,这时我们先不对它进编码.而是采用frame= x264_encoder_frame_put_from_picture( h, h->frame_next, pic )函数将这个B帧放进h->frame_next中.在h中同时定义了下面几个帧数组用以实现帧的管理.x264_frame_t *bframe_current[X264_BFRAME_MAX]; /* store the sequence of b frame being encoded */x264_frame_t *frame_next[X264_BFRAME_MAX+1]; /* store the next sequence of frames to be encoded *///这个是定义下一个帧,但不一定是B帧. x264_frame_t *frame_unused[X264_BFRAME_MAX+1]; /* store unused frames */同时还有下面4个函数(定义在\ENCODER\encoder.c中).x264_encoder_frame_put_from_picture();x264_encoder_frame_put() ();x264_encoder_frame_get();x264_frame_copy_picture();这3个数组和4个函数可以说完成了整个帧的类型的判定问题.在不对P帧进行编码之前,我们不对B帧进行编码,只是把B帧放进缓冲区(就是前面提到的数组).例如视频序列:I B B P B B P先确立第一个帧的类型,然后进行编码.然后是2个B帧,我们把它放进缓冲区数组.然后是P帧,我们可以判定它的类型并进行编码.同时,我们将缓冲区的B帧放进h->bframe_current[i],不过这时P帧前的两个B帧并没有编码.当读到P帧后面的第一个B帧时,我们实际上才将h->bframe_current数组中的第一个B帧编码,也就是将在I帧后面的第一个B帧编码.依此类推.(帧的有关理解学习笔记(二))F. 建立参考帧列表的操作,这里调用了函数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帧的话,就更新参考帧列表.G. 下面是写slice的操作./* Init bitstream context */h->out.i_nal = 0;//out的声明在bs.h中.bs_init( &h->out.bs, h->out.p_bitstream, h->out.i_bitstream );//空出8位./* Write SPS and PPS */if( i_nal_type == NAL_SLICE_IDR ){/* generate sequence parameters */x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST ); x264_sps_write( &h->out.bs, h->sps );x264_nal_end( h );/* generate picture parameters */x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST ); x264_pps_write( &h->out.bs, h->pps );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_height; mb_xy++ )//h->sps->i_mb_width指的是从宽度上说有多少个宏快. { const int i_mb_y = mb_xy / h->sps->i_mb_width;const int i_mb_x = mb_xy % h->sps->i_mb_width;//这两个变量是定义宏块的位置../* load cache */x264_macroblock_cache_load( h, i_mb_x, i_mb_y );//是把当前宏块的up宏块和left宏块的intra4×4_pred_mode,non_zero_count加载进来,放到一个数组里面,这个数组用来直接得到当前宏块的左侧和上面宏块的相关值.要想得到当前块的预测值,要先知道上面,左面的预测值,它的目的是替代getneighbour函数./* analyse parameters* Slice I: choose I_4×4 or I_16×16 mode* Slice P: choose between using P mode or intra (4×4 or 16×16)* */TIMER_START( i_mtime_analyse );x264_macroblock_analyse( h );//定义在analyse.h中.TIMER_STOP( i_mtime_analyse );/* encode this macrobock -> be carefull it can change the mb type to P_SKIP if needed */TIMER_START( i_mtime_encode );x264_macroblock_encode( h );//定义在Enc/encoder.c中.TIMER_STOP( i_mtime_encode );到这就已经完成编码的主要过程了,后面就是熵编码的过程了.。
x264代码剖析(十四):核心算法之宏块编码函数x264_macroblock_encode()
x264代码剖析(十四):核心算法之宏块编码函数x264_macroblock_encode()x264代码剖析(十四):核心算法之宏块编码函数x264_macroblock_encode()2016年03月22日 23:05:27 成长Bar 阅读数:1844更多所属专栏: x264代码剖析版权声明:本文为博主原创文章,转载请标注转载网址:ht t p:///frd2009041510 ht t ps:///FRD2009041510/art icle/det ails/50959404x264代码剖析(十四):核心算法之宏块编码函数x264_mac 宏块编码函数x264_macroblock_encode()是完成变换与量化的主要函数,而x264_macroblock_encode()调用了x264_macroblock_encode_internal()函数,在x264_macroblock_encode_internal()函数中x264_macroblock_encode_skip():编码Skip类型宏块。
x264_mb_encode_i16x16():编码Intra16x16类型的宏块。
该函数除了进行DCT变换之外,还对16个小块的DC系数进行了Hadamard变换。
x264_mb_encode_i4x4():编码Intra4x4类型的宏块。
帧间宏块编码:这一部分代码直接写在了函数体里面。
x264_mb_encode_chroma():编码色度块。
x264_macroblock_encode()函数与x264_macroblock_encode_internal()函数都处于encoder文件夹内的macroblock.c中,其调用关系图如下所示:1、x264_macroblock_encode()函数x264_macroblock_encode()函数处于encoder文件夹内的macroblock.c中,x264_macroblock_encode()封装了x264_macroblock_encode_internal()。
X264的多线程过程
X264的多线程过程,也可以说是并行编码过程。
1. 编译并行编码的x264从X264的帮助命令行可以看到,添加--threads项可以调整运行的线程数,可是当我完成X264编译,视图对手头的YUV进行编码的时候,发现在自己的双核计算机上,只能发挥50%的效率,即使使用--thr eads n 也无济于事,提示就是没有打开pthr ead支持。
Pthr eads定义了一套 C程序语言类型、函数与常量,它以pthread.h头文件和一个线程库实现。
【1】下面就把我在windows上实现pthr ead版本的X264编译过程写作如下:2009年3月的66版本1. 从http://sourcew /pthreads-w in32/下载pthr ead的win32版本,把其中的include和lib加入到VC++的引用目录中去。
2. 在项目属性的“C/C++ -> 预处理器 ->预处理器”中加入HAVE_PTHREAD。
3. 在osdep.h文件,紧接着#ifdef USE_REAL_PTHREAD加入#pragma c omment(lib, "pthr eadVC2.lib")引用pthr eadVC2.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代码随处充斥着的多线程处理和码率控制两方面的代码,所以,这里将先简化过程,忽略掉这些非主体代码。
x264的nal流程
x264中的NAL流程目前,主要是在分析NAL,做抓包实验,所以对NAL的格式要求比较高,这个过程中读了《新一代视频编码》,以前也读过,这是没有遇到实际的问题,读的时候也是似懂非懂的,囫囵吞枣,现在要分析,要用了才知道这些相关文档是要好好读的,ES流也是要好好分析的。
在上一篇中关于函数指针和指针函数的理论知识中,我主要是来看看x264中的NAL是怎么在封装的。
在x264中使用的函数指针,关于NAL部分的下面的一些:static int (*p_write_nalu)( hnd_t handle, uint8_t *p_nal, int i_size );,在这里可以看到p_write_nalu)是一个指针函数,p_write_nalu = write_nalu_bsf;即对于p_write_nalu其所指向的函数write_nalu_bsf,类似的还有:p_write_nalu = write_nalu_mp4;p_write_nalu = write_nalu_mkv;//每一个NALU都是由header+payload组成的,在header的结构是可以参考264的相关文档enum nal_unit_type_e{NAL_UNKNOWN = 0,NAL_SLICE = 1,NAL_SLICE_DPA = 2,NAL_SLICE_DPB = 3,NAL_SLICE_DPC = 4,NAL_SLICE_IDR = 5, /* ref_idc != 0 */NAL_SEI = 6, /* ref_idc == 0 */NAL_SPS = 7,NAL_PPS = 8,NAL_AUD = 9,/* ref_idc == 0 for 6,9,10,11,12 */};enum nal_priority_e{NAL_PRIORITY_DISPOSABLE = 0,NAL_PRIORITY_LOW = 1,NAL_PRIORITY_HIGH = 2,NAL_PRIORITY_HIGHEST = 3,};//NAL结构typedef struct{int i_ref_idc; /* nal_priority_e */int i_type; /* nal_unit_type_e *//* Size of payload in bytes. */int i_payload; //负载的大小/* If param->b_annexb is set, Annex-B bytestream with 4-byte startcode.* Otherwise, startcode is replaced with a 4-byte size.* This size is the size used in mp4/similar muxing; it is equal to i_payload-4 */uint8_t *p_payload;//如果是字节流格式的NAL时所用到的前缀4bytes} x264_nal_t;下面主要是跟踪x264,得到NAL的封装流程,如下:1.//NALstatic void x264_nal_start( x264_t *h, int i_type, int i_ref_idc ){x264_nal_t *nal = &h->out.nal[h->out.i_nal];nal->i_ref_idc = i_ref_idc;nal->i_type = i_type;nal->i_payload= 0;nal->p_payload= &h->out.p_bitstream[bs_pos( &h->out.bs ) / 8];}//下面是对bs_pos函数的注解static inline int bs_pos( bs_t *s ){return( 8 * (s->p - s->p_start) + (WORD_SIZE*8) - s->i_left ); / /获取当前的NALU的地址?????????- s->i_left}//bs_s的结构typedef struct bs_s{uint8_t *p_start;uint8_t *p;uint8_t *p_end;intptr_t cur_bits;int i_left; /* i_count number of available bits */int i_bits_encoded; /* RD only */} bs_t;2.static int x264_encoder_encapsulate_nals( x264_t *h ) //NAL封装int nal_size = 0, i;uint8_t *nal_buffer;for( i = 0; i < h->out.i_nal; i++ )nal_size += h->out.nal[i].i_payload;/* Worst-case NAL unit escaping: reallocate the buffer if it's too small. */if( h->nal_buffer_size < nal_size * 3/2 + h->out.i_nal * 4 ){uint8_t *buf = x264_malloc( nal_size * 2 + h->out.i_nal * 4 );if( !buf )return -1;x264_free( h->nal_buffer );h->nal_buffer = buf;}nal_buffer = h->nal_buffer;for( i = 0; i < h->out.i_nal; i++ ){int size = x264_nal_encode( nal_buffer, h->param.b_annexb, &h->out.nal[i] );h->out.nal[i].i_payload = size;h->out.nal[i].p_payload = nal_buffer;nal_buffer += size;}return nal_buffer - h->nal_buffer;}3.在2.中有:int x264_nal_encode( uint8_t *dst, int b_annexb, x264_nal_t *nal ){uint8_t *src = nal->p_payload; //为了同意结构还是将字节流格式的前缀作为指针的初始值uint8_t *end = nal->p_payload + nal->i_payload;uint8_t *orig_dst = dst;int i_count = 0, size;/* long nal start code (we always use long ones) */if( b_annexb ) //这里是进行字节流格式的码流编码,有开始前缀码,对于RTP封装则不需要前缀码{*dst++ = 0x00;*dst++ = 0x00;*dst++ = 0x00;*dst++ = 0x01;else /* save room for size later */dst += 4;/* nal header */*dst++ = ( 0x00 << 7 ) | ( nal->i_ref_idc << 5 ) | nal->i_type;//第一个bit的设置是由编码器自己控制的while( src < end ){if( i_count == 2 && *src <= 0x03 ){*dst++ = 0x03;//伪起始码i_count = 0;}if( *src == 0 )i_count++;elsei_count = 0;*dst++ = *src++;}size = (dst - orig_dst) - 4; //减4主要是前面的前缀码所占的字节/* Write the size header for mp4/etc */if( !b_annexb ){/* Size doesn't include the size of the header we're writing now. */orig_dst[0] = size>>24;orig_dst[1] = size>>16;orig_dst[2] = size>> 8;orig_dst[3] = size>> 0;}return size+4; //+4}4.static int x264_nal_end( x264_t *h ){x264_nal_t *nal = &h->out.nal[h->out.i_nal];nal->i_payload = &h->out.p_bitstream[bs_pos( &h->out.bs )/ 8] - nal ->p_payload;h->out.i_nal++;/* if number of allocated nals is not enough, re-allocate a larger one. */if( h->out.i_nal >= h->out.i_nals_allocated ){x264_nal_t *new_out = x264_malloc( sizeof(x264_nal_t) * (h->out.i_nals_allocated*2) );if( !new_out )return -1;memcpy( new_out, h->out.nal, sizeof(x264_nal_t) * (h->out.i_nals_allocated) );x264_free( h->out.nal );h->out.nal = new_out;h->out.i_nals_allocated *= 2;}return 0;}5.p_write_naluint write_nalu_bsf( hnd_t handle, uint8_t *p_nalu, int i_size ){if( fwrite( p_nalu, i_size, 1, (FILE*)handle ) > 0 ) //就是把p_nalu里面的1*i_size的字节输出到handle里面return i_size;return -1;}实验跟踪:在编码第一个I帧的时候,要编码的NALU的个数为4个,这里主要是指编码的类型为:SEI,SPS,PPS,I 帧的NAL的编码,对于一个I帧,也就是对这个GOP中的图像序列参数,图像参数进行编码,即有如下:/* Write SPS and PPS */if( i_nal_type == NAL_SLICE_IDR && h->param.b_repeat_headers ){if( h->fenc->i_frame == 0 ){/* identify ourself */x264_nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );if( x264_sei_version_write( h, &h->out.bs ) )return -1;if( x264_nal_end( h ) )return -1;overhead += h->out.nal[h->out.i_nal-1].i_payload + NALU_OVERHEAD;}/* generate sequence parameters */x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST );x264_sps_write( &h->out.bs, h->sps );if( x264_nal_end( h ) )return -1;overhead += h->out.nal[h->out.i_nal-1].i_payload + NALU_OVERHEAD;/* generate picture parameters */x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST );x264_pps_write( &h->out.bs, h->pps );if( x264_nal_end( h ) )return -1;overhead += h->out.nal[h->out.i_nal-1].i_payload + NALU_OVERHEAD; }int x264_encoder_headers( x264_t *h, x264_nal_t **pp_nal, int *pi_nal ) {int frame_size = 0;/* init bitstream context */h->out.i_nal = 0;bs_init( &h->out.bs, h->out.p_bitstream, h->out.i_bitstream );/* Write SEI, SPS and PPS. */x264_nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );if( x264_sei_version_write( h, &h->out.bs ) )return -1;if( x264_nal_end( h ) )return -1;/* generate sequence parameters */x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST );x264_sps_write( &h->out.bs, h->sps );if( x264_nal_end( h ) )return -1;/* generate picture parameters */x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST );x264_pps_write( &h->out.bs, h->pps );if( x264_nal_end( h ) )return -1;bs_flush( &h->out.bs );frame_size = x264_encoder_encapsulate_nals( h );/* now set output*/*pi_nal = h->out.i_nal;*pp_nal = &h->out.nal[0];h->out.i_nal = 0;return frame_size;}总结一下:对于NALU,并不是所谓的一帧对应一个NALU,而是对于SLICE而言,一个slice就封装层一个nal,具体一帧中有几个nalu则是可以再pps中参数中进行设定的,每遇到一个IDR,则此时就将对应的SPS,PPS进行一个更新,NAL的生成过程:先是对header里面的8个bits进行设定,然后是Payload,中间的细节,过于复杂,目前还不能够钻的很深,就先理解到这里。
X264参数设定详细解释
X264参数设定详细解释x264 core:65 r1074M b6bb3d4Syntax: x264 [options] -o outfile infile [widthxheight]语法(命令行写法):x264 [参数] -o 输出文件名输入文件名[宽x高]范例:x264 --crf 26 --ref 3 --mixed-refs --bframes 3 --b-adapt 2 --b-pyramid --weightb --deblock -1:-1 --trellis 2 --partitions all --8x8dct --me umh --threads auto --thread-input --aud --progress --no-psnr --no-ssim -o output.mp4 input.avs 720x480 Infile can be raw YUV 4:2:0 (in which case resolution is required),or YUV4MPEG 4:2:0 (*.y4m),or AVI or Avisynth if compiled with AVIS support (yes).输入文件可以是RAW YUV 4:2:0(在某些情况下分辨率是必需的)或者YUV4MPEG 4:2:0 (*.y4m)或者AVI 或Avisynth(后面的yes说明支持AVS输入)Outfile type is selected by filename:.264 -> Raw bytestream.mkv -> Matroska.mp4 -> MP4 if compiled with GPAC support (yes)输出文件类型由输出文件名指定:.264 -> Raw 格式.mkv -> MKV格式.MP4 -> MP4格式(yes说明支持MP4输出)Options:可选参数:-h, --help List the more commonly used options --longhelp List all options-h, --help 显示常用参数--longhelp 显示全部参数注:-h,--help代表-h与--help两者都可以,-h是简化写法举个例子:x264 -h与x264 --help两者效果等价——————————————————————————————————————Frame-type options:帧-类型选项:-I, --keyint Maximum GOP size [250]说明:指定两个IDR帧之间的最大间隔,默认250推荐值:默认或者FPS的10倍范例:--keyint 300注:[250]代表默认值为250提示:若想使用默认值,不使用参数即可-i, --min-keyint Minimum GOP size [25]说明:指定两个IDR帧之间的最小间隔,默认25推荐值:默认或者FPS的大小范例:--min-keyint 30问题:如何查看FPS?/zh-CN去上面的网站下载mediainfo查看--scenecut How aggressively to insert extra I-frames [40]说明:指定强制使用IDR帧的阀值,值越大强度越高,默认为40推荐值:默认范例:--scenecut 40--pre-scenecut Faster, less precise scenecut detection.Required and implied by multi-threading.说明:效果同scenecut,速度比scenecut快,但是精度稍低,默认设定为当threads>1时,永远使用--pre-scenecut 推荐值:默认-b, --bframes Number of B-frames between I and P [0]说明:设定I帧与P帧之间的最大B帧数量,范围0~16推荐值:3-6范例:--bframes 3--b-adapt Adaptive B-frame decision method [1]Higher values may lower threading efficiency.- 0: Disabled- 1: Fast- 2: Optimal (slow with high --bframes)说明:B帧自适应方法,默认为1- 0: 关闭- 1: 高速- 2: 最优化(--bframes的值越高速度越慢)推荐值:2范例:--b-adapt 2--b-bias Influences how often B-frames are used [0]说明:影响B帧使用的频繁程度,默认为0推荐值:0范例:--b-bias 0--b-pyramid Keep some B-frames as references说明:允许其它帧参考B帧,默认不使用推荐值:开启范例:--b-pyramid--no-cabac Disable CABAC说明:关闭CABAC,默认不使用-r, --ref Number of reference frames [1]说明:设定参考帧的数量,范围0~16,默认值为1,过大的值可能导致无法硬解,参考以下公式计算良好硬解的最大参考帧最大参考帧数量计算公式:maximum ref = 12288 * 1024 / ( width * height * 1.5)推荐值:3-6范例:--ref 3--no-deblock Disable loop filter说明:关闭deblock filter,默认不使用推荐值:默认范例:--no-deblock-f, --deblock Loop filter AlphaC0 and Beta parameters [0:0]说明:设定deblock filter参数,alpha为Deblocking strength,beta为Deblockingthreshold,值越大deblocking效果越好,画面越干净,但是会损失一些细节并有些许模糊,反之亦然,上下限不要超过-3,3,默认0,0推荐值:默认范例:--deblock 0:0--interlaced Enable pure-interlaced mode说明:隔行编码模式,默认关闭范例:--interlaced——————————————————————————————————————Ratecontrol:压缩比控制:-q, --qp Set QP (0=lossless) [26]说明:固定量化模式,值越小质量越好,默认为26,qp = crf + --qcomp 1推荐值:使用crf,见crf部分范例:--qp 26-B, --bitrate Set bitrate (kbit/s)说明:目标码率模式,生成的视频码率大小为指定的bitrate 的值,一般搭配--pass使用推荐值:720P以下码率为800-2100kbps之间,720P为3-6Mbps,1080P为8-15Mbps以上范例:--bitrate 1000--crf Quality-based VBR (nominal QP) 说明:固定压缩因子模式,值越小质量越好,一般搭配--qcomp使用推荐值:16-26范例:--crf 26--qcomp QP curve compression: 0.0 => CBR, 1.0 => CQP [0.60]说明:压缩曲线,范围为0~1之间,数值越小曲线越平坦,与crf搭配使用,默认为0.6推荐值:默认范例:--qcomp 0.6--vbv-maxrate Max local bitrate (kbit/s) [0]说明:设定VBV模式的最大码率,如果需要硬解必须开启VBV模式,默认为0范例:--vbv-maxrate 50000--vbv-bufsize Enable CBR and set size of the VBV buffer (kbit) [0]说明:设定VBV缓冲区的最大尺寸,其大小一般由硬件设备决定,默认为0范例:--vbv-bufsize 50000--vbv-init Initial VBV bufferoccupancy [0.9]说明:设定VBV缓冲区的初始填充尺寸,默认为0.9范例:--vbv-init 0.9--qpmin Set min QP [10]说明:设定qp的下限,默认为10范例:--qpmin 10--qpmax Set max QP [51]说明:设定qp的上限,默认为51范例:--qpmax 51--qpstep Set max QP step [4]说明:设定qp的最大步长,默认为4范例:--qpstep 4--ratetol Allowed variance of average bitrate [1.0]说明:允许最终码率偏离指定平均码率的百分比,只在1pass 中起作用,默认为1.0范例:--ratetol 1.0--ipratio QP factor between I and P [1.40]说明:设定I帧相对于P帧的量化比推荐值:默认范例:--ipratio 1.40--pbratio QP factor between P and B [1.30]说明:设定P帧相对于B帧的量化比推荐值:默认范例:--ipratio 1.30--chroma-qp-offset QP difference between chroma and luma [0]说明:chroma 与luma 的QP差异值,这个值会随着--psy-rd的使用自动调整为-2推荐值:默认范例:--chroma-qp-offset 0--aq-mode AQ method [1]- 0: Disabled- 1: Variance AQ (complexity mask)说明:自适应量化方法,可以改善某些场景过于模糊等问题,默认开启- 0: 关闭- 1: 可变AQ推荐值:默认范例:--aq-mode 1--aq-strength Reduces blocking and blurring in flat andtextured areas. [1.0]- 0.5: weak AQ- 1.5: strong AQ说明:指定AQ的强度,减小低细节宏块的量化值,默认1.0 - 0.5: 较弱的AQ- 1.5: 较强的AQ推荐值:默认范例:--aq-strength 1.0-p, --pass Enable multipass ratecontrol- 1: First pass, creates stats file- 2: Last pass, does not overwrite stats file- 3: Nth pass, overwrites stats file说明:多重压缩模式,1 pass 或N pass生成stats文件,2pass调用生成的stats文件对压缩进行优化,更合理的分配码率,一般没必要进行N pass- 1: 第1 pass,生成stats文件- 2: 最终pass,不覆盖stats文件- 3: 第N pass,覆盖stats文件推荐值:2范例:--pass 2--stats Filename for 2 pass stats["x264_2pass.log"]说明:指定stats文件名,默认为"x264_2pass.log"推荐值:默认范例:--stats "x264_2pass.log"--cplxblur Reduce fluctuations in QP (before curve compression) [20.0]说明:减小QP的波动(在曲线压缩以前),范围0~999推荐值:默认范例:--cplxblur 20--qblur Reduce fluctuations in QP (after curve compression) [0.5]说明:减小QP的波动(在曲线压缩之后),范围0~99推荐值:默认范例:--qblur 0.5--zones //... Tweak the bitrate of someregions of the videoEach zone is of the form,,where is eitherq= (force QP)or b= (bitrate multiplier)说明:调整视频中某一范围内的码率每个区域已以下形式出现,,为下面的任意一个q= (强制QP)或b= (指定bitrate)范例:--zone 0,1000,qp=30/30000,32000,b=0.5--qpfile Force frametypes and QPs说明:强制指定帧类型与QP推荐值:默认Analysis:分析:-A, --partitions Partitions to consider["p8x8,b8x8,i8x8,i4x4"]- p8x8, p4x4, b8x8, i8x8, i4x4- none, all(p4x4 requires p8x8. i8x8 requires --8x8dct.)说明:宏块分割方式,默认["p8x8,b8x8,i8x8,i4x4"]- p8x8, p4x4, b8x8, i8x8, i4x4- none, all(p4x4 需要p8x8. i8x8 需要--8x8dct.)推荐值:默认范例:--partitions "p8x8,b8x8,i8x8,i4x4"--direct Direct MV prediction mode ["spatial"]- none, spatial, temporal, auto说明:Direct预测方法,默认"spatial"- none, spatial, temporal, auto推荐值:"auto"范例:--direct "auto"--direct-8x8 Direct prediction size [1] - 0: 4x4- 1: 8x8- -1: smallest possible according to level说明:Direct预测大小,默认为-1- 0: 4x4- 1: 8x8- -1: 根据Level确定一个最小值范例:--direct 1-w, --weightb Weighted prediction forB-frames说明:允许对B帧进行加权预测范例:--weightb--me Integer pixel motion estimation method ["hex"]- dia: diamond search, radius 1 (fast)- hex: hexagonal search, radius 2- umh: uneven multi-hexagon search- esa: exhaustive search- tesa: hadamard exhaustive search (slow)说明:全像素动态预测方法,越往下精度越高,速度越慢,默认"hex"- dia: 菱形搜索, 半径1 (高速)- hex: 六边形搜索, 半径2- umh: 不规则多边形搜索- esa: 全面搜索- tesa: hadamard变换全面搜索(最慢)推荐值:"umh"范例:--me "umh"--merange Maximum motion vector search range [16]说明:最大动态矢量搜索范围,结合--me使用,对于dia与hex,允许的范围为4~16,umh以上可以超过16,值越大编码速度越慢,默认为16范例:--merange 16--mvrange Maximum motion vector length [-1 (auto)]说明:最大动态矢量长度推荐值:默认范例:--mvrange -1 --mvrange-thread Minimum buffer between threads [-1 (auto)]说明:线程之间的最小缓冲区大小推荐值:默认范例:--mvrange-thread -1 -m, --subme Subpixel motion estimation and mode decision [6]- 0: fullpel only (not recommended)- 1: SAD mode decision, one qpel iteration- 2: SATD mode decision- 3-5: Progressively more qpel- 6: RD mode decision for I/P-frames- 7: RD mode decision for all frames- 8: RD refinement for I/P-frames- 9: RD refinement for all frames说明:子像素动态预测模式策略,值越大效果越好,速度越慢,默认6- 0: 仅fullpel (不推荐)- 1: SAD模式策略, 1 qpel迭代- 2: SATD模式策略- 3-5: 依次qpel增加- 6: I/P-帧RD模式策略- 7: 所有帧RD模式策略- 8: I/P-帧RD refinement模式策略- 9: 所有帧RD refinement模式策略推荐值:6以上范例:--subme 7--psy-rd Strength of psychovisual optimization ["1.0:0.0"]#1: RD (requires subme>=6)#2: Trellis (requires trellis, experimental)说明:视觉优化,--psy-rd 1.0:0.0 代表#1为1.0,#2为0.0,#2还在测试阶段,默认1.0:0.0#1: RD (需要subme>=6)#2: Trellis (需要trellis, 测试阶段)推荐值:#1 (0~1.0),#2 0.0范例:--psy-rd 1.0:0.0--mixed-refs Decide references on a per partition basis说明:对每个宏块区进行参考帧判断,开启后可以提升质量,但会降低速度推荐值:开启范例:--mixed-refs--no-chroma-me Ignore chroma in motion estimation说明:在动态预测中忽略chroma推荐值:默认范例:--no-chroma-me-8, --8x8dct Adaptive spatial transform size说明:自适应空间变换大小推荐值:使用范例:--8x8dct-t, --trellis Trellis RD quantization. Requires CABAC. [0]- 0: disabled- 1: enabled only on the final encode of a MB- 2: enabled on all mode decisions说明:Trllis RD量化.需要CABAC,值越大速度越慢,默认0- 0:关闭- 1:基于宏块- 2:在所有模式策略中使用推荐值:1范例:--trellis 1--no-fast-pskip Disables early SKIP detection on P-frames说明:关闭早期的P帧快速检测,开启可以提升质量,但会减低一些速度,默认不使用推荐值:使用范例:--no-fast-pskip--no-dct-decimate Disables coefficientthresholding on P-frames说明:关闭P帧的系数阀值,默认不使用推荐值:默认范例:--no-dct-decimate--nr Noise reduction [0]说明:降噪,默认0推荐值:默认范例:--nr 0--deadzone-inter Set the size of the inter luma quantization deadzone [21]--deadzone-intra Set the size of the intra luma quantization deadzone [11]Deadzones should be in the range 0 - 32.说明:设定inter/intra luma deadzone 量化值的大小,范围0 - 32推荐值:默认范例:--deadzone-inter 21 --deadzone-intra 11--cqm Preset quant matrices ["flat"]- jvt, flat说明:预设量化矩阵- jvt, flat推荐值:默认范例:--cqm "flat"--cqmfile Read custom quant matricesfrom a JM-compatible fileOverrides any other --cqm* options.说明:读取自定义JM兼容的量化矩阵文件,无视任何以--cqm开头的参数范例:--cqm "mycqm"--cqm4 Set all 4x4 quant matrices Takes a comma-separated list of 16 integers.说明:设定所有4x4量化矩阵,它是一张以逗号分割的16个整数的表范例:--cqm"16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16"--cqm8 Set all 8x8 quant matricesTakes a comma-separated list of 64 integers.说明:设定所有8x8量化矩阵,它是一张以逗号分割的64个整数的表范例:参考--cqm4--cqm4i, --cqm4p, --cqm8i, --cqm8pSet both luma and chroma quant matrices说明:设定luma与chroma的量化矩阵范例:参考--cqm4--cqm4iy, --cqm4ic, --cqm4py, --cqm4pc Set individual quant matrices说明:单独设定量化矩阵范例:参考--cqm4Video Usability Info (Annex E):The VUI settings are not used by the encoder but aremerely suggestions tothe playback equipment. See doc/vui.txt for details. Use at your own risk.视频可用性信息:VUI设置在编码的时候不会用到,它仅仅作用于回放设备。
x264学习笔记(1)-函数调用流程
/* 1: Copy the picture to a frame and move it to a buffer */
x264_stack_align( x264_slicetype_decide, h )
/* 2: Select frame types */
x264_frame_push()
/* 3: move some B-frames and 1 non-B to encode queue */
i_file = 0; b_ctrl_c == 0 &&
(i_frame < i_frame_total || i_frame_total ==
0); )
parse_qpfile() Encode_frame()
i_frame++;
分析量化表,如果有 编码一帧
打印输出信息
编码循环结束 Encode_frame( )
Flush delayed B-frames
x264_mdate() x264_picture_clean( &pic );
x264_encoder_close( h ); x264_free( mux_buffer ); p_close_infile( opt->hin ); p_close_outfile( opt->hout );
Encode()函 数,编码器
主循环
main() (x264.c)
x264_param_default()
Parse()
分析命令行参数,并 打开输入输出文件
Encode()
x264_encoder_open()
初始化编码器参数,以及 编码器用到的函数
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
1、x264程序框架流程分析
(1) 进入x264.c 的int main( int argc, char **argv ) 函数
main 函数中主要有以下三个主要的步骤,分别调用了3个函数。
第一步、对编码器进行参数设定。
函数实现如下:
x264_param_default( x264_param_t *param );此函数在common.c中定义,完成一个x264_param_t 结构体的初始化。
第二步、分析参数,读入运行参数完成文件打开。
函数实现如下:
Parse( argc, argv, x264_param_t *param, &opt );此函数在x264.c 中定义。
VC的运行参数我们可以设置:“-o out.264 football.yuv 352x288”
则具体到Parse函数,其输入的参数argc == 5 ,这个数值大小等于要放下下面的argv所需要的二维数组的行数。
参数argc和argv在进入main 函数之前就已经确定了。
argv 为字符串char **的类型的,相当于一个二维数组,其具体内容如下:
argv[0][] ==
“F:\x264-snapshot-20070331-2245\build\win32\bin\x264.exe”
argv[1][] == “-o”
argv[2][] == “out.264”
argv[3][] == “football.yuv”
argv[4][] == “352x288”
第三步、开始编码。
函数实现如下:
Encode( x264_param_t *param, &opt ); 此函数在x264.c 中定义。
(2) static int Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *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_long()解析入口地址的向量,最后int c 得到的是运行参数(“-o out.264 football.yuv 352x288”)中前面“-o”中“o”的ASCII值即c = 111 。
这个getopt_long函数的实现在官方网站上下的程序中没有实现,需要自己来写。
Parse 在解析了入口地址后,用下面的语句打开文件
p_open_infile( psz_filename, &opt->hin, param )
{
yuv_input_t *h = malloc(sizeof(yuv_input_t));
h->width = p_param->i_width;
h->height = p_param->i_height;
h->next_frame = 0;
if( !strcmp(psz_filename, "-") )
h->fh = stdin;
else
h->fh = fopen(psz_filename, "rb");
if( h->fh == NULL )
return -1;
*p_handle = (hnd_t)h;
return 0;
}
这样就在下面Encode函数中一开始,得到总的编码帧数,调用这个函数
int get_frame_total_yuv( hnd_t handle )
{
yuv_input_t *h = handle;
int i_frame_total = 0;
if( !fseek( h->fh, 0, SEEK_END ) )
{
uint64_t i_size = ftell( h->fh );//得到文件的大小。
fseek( h->fh, 0, SEEK_SET );
i_frame_total = (int)(i_size / ( h->width * h->height * 3 / 2 ));
//计算总的帧数
/// 这里乘以1.5是因为一个编码单位是一个亮度块加2个色度块,大小上等于1.5个亮度块
}
return i_frame_total;
}
(3) static int Encode( x264_param_t *param, cli_opt_t *opt )
前面都是读取默认参数,如果设置了参数的话,可以修改param的。
编码的主要部分还是在这个函数里面进行调用。
Encode 函数主要分为以下几步:
第一步、读取输入文件的总帧数,存放在param->i_frame_total。
第二步、调用h = x264_encoder_open( param ) 函数对不正确的264_t结构体(h的类型是264_t * )参数进行修改,并对各结构体参数、编码、预测等需要的参数进行初始化。
第三步、构造一个图像帧的初始化空间。
x264_picture_alloc( &pic, X264_CSP_I420, param->i_width,
param->i_height );
第四步、利用下面一个for循环,开始一帧一帧进行编码。
for( i_frame = 0, i_file = 0, i_progress = 0;
b_ctrl_c == 0 && (i_frame < i_frame_total || i_frame_total == 0); )
在这个for循环中,
读取p_read_frame( &pic, opt->hin, i_frame + opt->i_seek )
编码并保存i_file += Encode_frame( h, opt->hout, &pic );
(4) static int Encode_frame( x264_t *h, hnd_t hout, x264_picture_t *pic )
这个函数主要是调用了x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out ) 来实现编码。
这个函数也是x264的核心部分了。