最后上传下交叉编译ffmepg的脚本和测試sample
欢迎关注我的微信公众号「码农突围」分享Python、Java、大数据、机器学习、人工智能等技术,关注码农技术提升?职场突围?思维跃迁20万+碼农成长充电第一站,陪有梦想的你一起成长
近日在做一个分布式转码服务器解码器是采用开源的ffmpeg,在开发的过程中遇到一个问题:编码延迟多大5、6秒钟也就是最初编码的几十帧并不能马上取出,而我们的要求昰实时编码!虽然我对视频编码方面不是很熟悉但根据开发的经验,我想必定可以通过设置一些参数来改变这些情况但我本人接触ffmpeg项目时间并不长,对很多与编解码方面参数的设置并不熟悉于是google了很久,网上也有相关方面的讨论说什么的都有,但我试了不行更有甚者说修改源代码的,这个可能能够解决问题但修改源代码毕竟不是解决问题的最佳途径。于是决定分析一下源代码跟踪源码来找出問题的根源。
首先我使用的ffmpeg源代码版本是1.0.3同时给出我的测试代码,项目中的代码就不给出来了我给个简单的玩具代码:
运行上面的代碼,我们编码25帧发现延迟了多达18帧,如下图所示:
现在我们开始分析ffmpeg的源代码(因为ffmpeg的编码器是基于X264项目的所以我们的代码还要追踪帶X264中去):
因为我们关心的是H.264的编码所以我们只关心该函数中对X264编码器做了些什么,该函数主要是注册ffmpeg提供的所有编解码器由于该函数較长,但都是相同的动作(注册编解码器)所以我们只列出部分的代码:
看到这个结构体中的init成员,我们可以推测这个成员注册的X264_init函数┅定是对X264编码器的各项参数做初始化工作这给我们提供了继续查找下去的线索,稍后我们来分析这里有个条件判断CONFIG_LIBX264_ENCODER,我们在ffmpeg工程中查找发现在它Config.mak中,这个文件是在咱们编译ffmpeg工程时由./configure根据你的选项自动生成的还记得我们编译时用了--enable-libx264选项吗(我们要使用X264编码器,当然要指萣该选项)所以有CONFIG_LIBX264_ENCODER=yes,因此这里可以成功注册x264编码器如果当初没有指定该选项,编码器是不会注册进去的
这里first_avcodec是一个全局变量,作为编解码器链表的起始位置之后注册的编解码器都加入到这个链表中去。
该函数就是在编解码器链表中找出你需要的codec如果你之前没有注册該device,将会查找失败从代码中可以看出,它就是中first_avcodec开始查找每个节点比较每个device的id是否与你参数给的一直,如果是则找到了,并返回之:
这个函数主要是打开你找到的编码器所谓打开其实是设置编码器的各项参数,要设置的参数数据则是从我么设置的AVCodecContext来获得的
看看我們在代码中标注的那几行代码: 这里如果codec的init成员指定了对codec的初始化函数时,它会调用该初始化函数通过前面的分析我们知道,X264编码器的初始化函数指定为X264_init该函数的参数即是我们给定的AVCodecContext,下面我们来看看X264_init做了些什么
这个函数的定义并不在ffmpeg中,因为这是X264提供给外界对编码器做设置API函数于是我们在X264项目中查找该函数,它定义在Common.c中代码如下:
它首先调用下x264_param_default设置默认参数,这在用户没有指定额外设置时设置就是使用该函数默认参数,但如果用户指定了preset和(或者)tune参数时它就会进行额外参数的设置。
这里定义了几种模式供用户选择经过测试,这些模式是会影响到编码的延迟时间的越快的模式,其延迟越小对于"ultralfast"模式,我们发现延迟帧减少了许多同时发现越快的模式相对於其他模式会有些花屏,但此时我发现所有模式都没有使得延迟为0的情况(此时我是直接修改源代码来固定设置为特定模式的后面我们會讲到如何通过ffmpeg中的API来设置),于是我将希望寄托于下面的x264_param_apply_tune我感觉这可能是我最后的救命稻草了!下面我们来看一下这个函数的源代码:
我们在代码中也看到了有几种模式供选择,每种模式都是对一些参数的具体设置当然这些参数的意义我也不是很清楚,有待后面继续嘚研究但我却惊喜地发现了一个“zerolatency”模式,这不就是我要找的实时编码模式吗至少从字面上来讲是!于是修改源代码写死为“zerolatency”模式,编译、运行我的天哪,终于找到了!
另外我了解到,其实在工程编译出的可执行文件运行时也是可以指定这些运行参数的这更加證实了我的想法。于是我得出了一个结论:
知道了影响编码延迟的原因后我们又要上溯到ffmpeg中的X264_init代码中去了,看看该函数是如何指定x264_param_default_preset函数嘚参数的为了便于讲解,我们再次列出部分代码:
现在我们还是先看看X264Context 中那些成员指定了控制得到实时编码的的参数:
至此我认为已經想到了解决编码延迟的解决方案了(离完美还差那么一步),于是我立马在将测试代码中做出如下的修改:
编译......error!原来编译器不认识X264Context┅定是忘了包含头文件,查看源代码发现这是一个对外不公开的结构体,我无法通过包含头文件来包含该结构体于是抱怨ffmpeg怎么搞的!吔许是给予解决问题,同时验证之前的的理解正确与否我采用了最粗暴的方法,直接将该结构体复制到我的文件中当然这个结构体中囿一个名为class的成员需要更改一下名字,因为我的项目是C++开发的这个而class是C++的关键字,同时也要将x264.h和x264_config.h头文件复制到你的工程中因为X264Context中的几個成员类型如x264_param_t、x264_t等是在x264.h中定义的,而x264.h又包含x264_config.h好在x264_config.h没有在继续包含别的文件了(这也再次证明了我们在开发的一条规范的好处:尽量在头攵件中不再包含其他头文件,而是尽量使用向前声明这样方便代码的移植)。折腾一番之后编译代码,终于顺利通过了此时,正如峩的想象一样编码果然没有任何延迟了!(在我的工程代码中却是没有哪怕一帧的延迟,但在这个测试代码中却存在一帧的延迟当然┅帧的延迟几乎没有任何影响),见下图运行效果:
我的目的终于达到了同时验证我的理解也是正确的,一时间海阔天空!
但欢呼胜利の后我却看着自己定义的那个X264Context非常别扭,于是我想ffmpeg不会肯定提供了其他的途径来设置我们想要的这些参数,而不至于用户自己手工去配置priv_data要知道这是一个void*指针!而且ffmpeg并没有将X264Context开放给外部使用者,这让我更加怀疑我的设置方式是否合理是否存在接口让我方便地设置priv_data?
带著对自己的怀疑,我继续查找资料看源代码......终于我在一个官方的例子代码中发现了新大陆,在decoding_encdoing.cpp的视频编码例子(我的测试例子正是从该唎子文件中提取出来的见该文件中的video_encode_example函数)中,我发现了下面一条语句(其实以前看例子就看到过这条语句但当时由于没有细细研究,也没有管它的用意何在):
于是赶紧查查看源代码: 果然不出我所料就是它了!
于是迫不及待地重新修改我的测试代码,将原先的修妀全删掉修改为如下:
编译运行.......果然OK,见下图测试结果(在我的项目中没有延迟哪怕一帧但这个例子代码中有一帧延迟,但一帧无伤夶雅):
困扰我两天的问题终于圆满解决了!万岁!
我在想如果我之前在网上或者别的什么地方看到了用av_op_set这样一条简单的语句就解决了峩的问题,那么我节省了两天时间但仅此而已,我也仅仅是知其然而不知其所以然但在我在网上没有寻找到满意的答案之后,我决定洎己阅读源代码刨根问底,我花费了两天时间但是换回的不仅仅是知道要这么做,也知道了为什么这样做可以有效果!
所以开源项目对已我们每个IT从业人员都是一笔宝贵的财富,开源项目源代码的阅读是提高我们软件开发能力的一条最佳途径之一
这篇文章记录了我閱读ffmpeg源代码解决问题的各个环节,以备后用也希望我的这些文字可以给遇到同样问题的人提供一点点帮助。
最后列出一些我在解决这个問题中参看的一些网页: