Update 2010.1.5: 其实研究ffmpeg不用找什么教程,第一步应该是下载ffmpeg的源码包。下面提到的确
实有讲解,但是教程总是跟不上代码的变化的,所以直接看可工作代码最好;ffmpeg的结构很分明,后台是几个库:libxxx,前台是三个程序
ffmpeg, ffplay,
ffserver,那篇教程说的就是ffplay的实现。一个播放器,其实重点不是解码,解码的东西是lib去做的,主要是做声音视频的时钟同步。
ffplay的代码可以说是一个可用播放器最简单的实现了,源码里面有个output_example.c,可以说是最基本的api示范吧。ffmpeg
是转换编码解码转换程序,因为涉及重新采样等等,所以代码量也不少的。
这两天"调研"了下ffmpeg的API,不得不承认被雷倒:ffmpeg又是一个很geek的项目,纯社区开发,基于逆向,功能强大,但是文档极度有限,想了解API?看源码去…… 网上关于ffmpeg API的资料,无非是ffmpeg文档里面的两个链接, by
Martin Böhme()跟
by
Stephen Dranger;两个tutorial基于ffmpeg
0.4.8,现在ffmpeg发布的版本是0.5.0,好像数值相差不大,不过0.4.8是5年前的了(相比之下wine用了15年版本号才到达1.0,
有过之余无不及),两个教程里面的代码在0.5.0下一编译,哇,一堆错误,可不仅有些api函数变了,有些结构成员压根就没了,头文件的位置更是不一样
(各个库分家了)……所以我调试了好几个小时,终于把例子的代码弄好(其实Martin Böhme那篇有一段09年加入的更新说明,,我一开始没注意,几个小时自己解决,不过也有收获)。
最后我调试好的代码流程:打开一个视频文件,抓取前5帧保存为文件;
【】
av_register_all();//初始化ffmpeg库,如果系统里面的ffmpeg没配置好这里会出错
av_open_input_file();
av_find_stream_info();//查找文件的流信息
dump_format();//dump只是个调试函数,输出文件的音、视频流的基本信息了,帧率、分辨率、音频采样等等
for(...);//遍历文件的各个流,找到第一个视频流,并记录该流的编码信息
sws_getContext();//根据编码信息设置渲染格式
avcodec_find_decoder();//在库里面查找支持该格式的解码器
avcodec_open();//打开解码器
pFrame=avcodec_alloc_frame();//分配一个帧指针,指向解码后的原始帧
pFrameRGB=avcodec_alloc_frame();//分配一个帧指针,指向存放转换成RGB后的帧
avpicture_fill(pFrameRGB);//给pFrameRGB帧加上分配的内存;
while true{
av_read_frame();//读取一个帧(到最后帧则break)
avcodec_decode_video();//解码该帧
sws_scale();//把该帧转换(渲染)成RGB
SaveFrame();//对前5帧保存成ppm图形文件(这个是自定义函数,非API)
av_free_packet();//释放本次读取的帧内存
}
avcodec_close();
av_close_input_file();
用到的API就这么多,当然实际代码稍复杂一点;ppm图像是类似BMP的非压缩格式,SaveFrame就是相当于把pFrameRGB的内存拷贝进文件,写文件并不复杂;
调试过程的问题,首先是头文件,ffmpeg 0.5.0的API已经拆分成好几个独立的库,用pacman -Ql ffmpeg看了下文件分布,在include下好几个目录都是它的,看名字可以大概猜出他们的功能:
libavcodec:CODEC其实是Coder/Decoder的缩写,也就是编码解码器;
libavdevice:对输出输入设备的支持;
libavformat:对音频视频格式的解析
libavutil:集项工具;
libpostproc:后期效果处理;
libswscale:视频场景比例缩放、色彩映射转换;
修改好头文件包含,终于少了些not declared错误;
Martin Böhme那篇教程的代码是使用g++编译的,虽然代码是C风格;在我修改了头文件以及一些错误之后,居然链接出错,av_register_all什么 的函数统统undefined reference,想到ffmpeg是纯C实现,以及以前用g++编译GTK出现回呼函数找不到的经历,相信又是g++的function mangling搞的,Google了一下,。
代码里面的错误还涉及一些结构成员的变化,比如
pFormatCtx->streams[i]->codec.codec_type==CODEC_TYPE_VIDEO
就有个类型错误,因为0.5.0里面的codec已经是指针,而不是结构了,要把.换成->,而相应地获得解码器指针,不再需要&:
pCodecCtx=&pFormatCtx->streams[videoStream]->codec;
原教程提到一个视频码率的rate的Hack:
// Hack to correct wrong frame rates that seem to be generated by some codecs
if(pCodecCtx->frame_rate>1000 && pCodecCtx->frame_rate_base==1)
pCodecCtx->frame_rate_base=1000;
好吧现在frame_rate跟frame_rate_base压根就没了,去掉算了;
最麻烦的变化还是原代码里面的img_convert,就是解码出一个帧的数据后,需要转换成RGB格式才能写入文件,然而这个函数在0.5.0里 面彻底没了。尝试把img_convert完全注释掉,把原始帧img_convert写入文件,还算可喜的是能够看到图形,只是被分成三个画面的通道图 形罢了;
Google了一番,还是回到 Stephen Dranger的,介绍了swscale的接口,虽然里面的例子是转换成SDL所用的YUV,而不是RGB;注意到其使用的参数PIX_FMT_YUV420P,跟img_convert所用的PIX_FMT_RGB24有相同前序,就试试照样花虎了;
给
sws_getContext传入源格式的H/W,格式,输出格式的H/W,PIX_FMT_RGB24格式,其中有个参数flags的解释是
specify which algorithm and options to use for
rescaling,是选择在缩放过程中是使用线性还是双立方等算法(参数文档没说,要找看源码去),这里照抄了例子里面的SWS_BICUBIC。获得
这个SwsContext,类似python的re.compile,再用这个转换器去转换每一个帧,所以后面每次解码了帧后,调用sws_scale,
跟原来的img_convert倒是挺像;
也就是说,以后如果需要对视频进行4:3跟16:9的转换,就是在sws_getContext的参数里面做设置了;
最后整个程序正常,会把视频的前5帧抓成图像了,只是会有个小警告:
[swscaler @ 0x1d8f670]No accelerated colorspace conversion found.
估计是转换成RGB,swscale里面没有特别优化的算法?
目前发现程序运行的时候CPU占用很高,是因为程序还没有控制帧速的时钟,只会用尽CPU的性能不断的读取解码;
PT修改过可编译通过(ffmpeg 5.0/gcc 4.4.2/ubuntu 9.10)的前三个Tutorial代码可在。(编译方法请看各个文件头部的注释说明)