初学ffmpeg,在网上找到许多ffmpeg的程序资料。前辈们的慷慨分享,让我能够快速上手,在这里先表示感谢。
由于ffmpeg更新太快,许多宏和函数都发生了变化。如果我们在旧程序引用ffmpeg新库文件,会产生一大堆错误;如果不引用新库,旧程序对应版本的库又难以找到。
真是一个令人头痛 的问题,无论是新人,还是老手。
在修改几个旧程序源码之后,想分享我处理此类问题的方法。
一、宏名的改变
可能是ffmpeg想进一步规范化,把之前很多宏定义都修改了。比如,CODEC_TYPE_VIDEO,编译会提示未声明定义的错误:
errorC2065: 'CODEC_TYPE_VIDEO': undeclared identifier
查找资料会发现CODEC_TYPE_VIDEO已经改成了AVMEDIA_TYPE_VIDEO,那解决方案有两个:
1) 直接替代,AVMEDIA_TYPE_VIDEO替换所有的CODEC_TYPE_VIDEO。
2) 如果不想破坏原代码,那就增加宏定义:
#define CODEC_TYPE_VIDEO AVMEDIA_TYPE_VIDEO
二、函数的改变
处理函数改变的问题,是本文的重点。我们先看看问题:
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(169): error C4996: 'avpicture_fill': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(5451): note: see declaration of 'avpicture_fill'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(180): error C4996: 'avpicture_fill': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(5451): note: see declaration of 'avpicture_fill'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(261): error C4996: 'AVStream::codec': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavformat\avformat.h(885): note: see declaration of 'AVStream::codec'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(353): error C4996: 'avpicture_fill': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(5451): note: see declaration of 'avpicture_fill'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(421): error C4996: 'av_free_packet': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(4471): note: see declaration of 'av_free_packet'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(811): error C4996: 'avpicture_fill': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(5451): note: see declaration of 'avpicture_fill'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(828): error C4996: 'avcodec_encode_video2': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(5321): note: see declaration of 'avcodec_encode_video2'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(909): error C4996: 'avcodec_decode_video2': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(4810): note: see declaration of 'avcodec_decode_video2'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(912): error C4996: 'av_free_packet': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(4471): note: see declaration of 'av_free_packet'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(980): error C4996: 'av_free_packet': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(4471): note: see declaration of 'av_free_packet'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(1042): error C4996: 'av_free_packet': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(4471): note: see declaration of 'av_free_packet'
1>d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\cameradevice.cpp(1067): error C4996: 'av_free_packet': was declared deprecated
1> d:\code\git_space\ffmpeg sample\testffmpeg\testffmpeg\include\libavcodec\avcodec.h(4471): note: see declaration of 'av_free_packet'
显然,这是库文件不同造成的,我引用的是官网上最新的ffmpeg开发包。我们先查看相关函数的声明,以avpicture_fill函数为例,其声明代码:
/**
* @deprecated use av_image_fill_arrays() instead.
*/
attribute_deprecated
int avpicture_fill(AVPicture *picture, const uint8_t *ptr,
enum AVPixelFormat pix_fmt, int width, int height);
上述声明,说已使用av_image_fill_arrays()替代了avpicture_fill()。并且使用attribute_deprecated,在编译时提醒此函数已过时。
到此,我想了两个解决方案。
解决方案一:修改原代码,用新函数替代旧函数。
为了新旧函数有个对照,我并没有删除原函数,而是用条件编译指令把它们分隔开。下面贴我部分修改后的代码:
#if IS_FFMPEG_VERSION_1 //对应当前FFMPEG版本
av_image_fill_arrays(m_pRGBFrame->data, m_pRGBFrame->linesize, m_pRGBBuffer,
AV_PIX_FMT_BGR24, w, h, 1);
#else//旧版本
avpicture_fill((AVPicture*)m_pRGBFrame, m_pRGBBuffer, AV_PIX_FMT_BGR24, w, h);
#endif
#if IS_FFMPEG_VERSION_1//对应当前FFMPEG版本
avcodec_parameters_to_context(m_pEncoderCtx, m_pOutVideoStream->codecpar);
#else//旧版本
m_pEncoderCtx = m_pOutVideoStream->codec;
#endif
#if IS_FFMPEG_VERSION_1//对应当前FFMPEG版本
ret = avcodec_send_packet(m_pDecoderCtx, m_pSrcPacket);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
{
av_packet_unref(m_pSrcPacket);
return FALSE;
}
//从解码器返回解码输出数据
ret = avcodec_receive_frame(m_pDecoderCtx, m_pSrcFrame);
if (ret < 0 && ret != AVERROR_EOF)
{
av_packet_unref(m_pSrcPacket);
return FALSE;
}
#else//旧版本
int gotData;
ret = avcodec_decode_video2(m_pDecoderCtx, m_pSrcFrame, &gotData, m_pSrcPacket);
if (ret < 0 || gotData == 0)
{
av_free_packet(m_pSrcPacket);
return FALSE;
}
#endif
上述对照可以看出,大部分函数其参数还是继承旧函数参数,修改起来难度不大,唯有codec对象。关于codec,ffmpeg说是用codecpar替代了。假如像下面一样修改代码,是错误的:
m_pDecoderCtx = m_pInFmtCtx->streams[i]->codec;//在旧版本是正确的
m_pDecoderCtx = m_pInFmtCtx->streams[i]->codecpar;//在新版本是错误的
错误提示: cannot convert from 'AVCodecParameters *' to 'AVCodecContext *'
追踪查看,原来是,codec的结构体是AVCodecContext,codecpar的结构体是AVCodeParameters,所以不能直接替代赋值。
如何填充编解码器上下文,成了一个不知如何着手的难题。网络搜索,没有找到相关答案。一个一个的查看英文源码,对新手来说,似乎是不可完成的任务。
怎么办?
对于“偷窃”别人代码多年的老油条,我还有一招——闺蜜搜索法。其原理是:如果你想知道一个人的详细信息,却没有她的直接联系方式,但你有途径知道她闺蜜的联系方式,那最好的突破口是先找她闺蜜。
就先找与codec相关联的代码,如下:
m_pDecoderCtx = m_pInFmtCtx->streams[m_iVideoStreamIndex]->codec;
AVCodec *pDecoder = avcodec_find_decoder(m_pDecoderCtx->codec_id);
ASSERT(pDecoder != NULL);
ASSERT(avcodec_open2(m_pDecoderCtx, pDecoder, NULL) == 0);//打开解码器
可以看出,如果要完成初始化工作,必然要找到和打开解码器,所以解码器上下文m_pDecoderCtx的“闺蜜”是avcodec_find_decoder()函数和avcodec_open2()函数。
ffmpeg开发包会带有一些sample code,那我们就搜索一下哪些源码带有这两个函数。开发包的doc\examples目录下,搜索avcodec_open2,如下图
第一个文件从名字看似乎是解码器相关的,就打开此源码。搜索一下,发现如下代码:
stream_index = ret;
st = fmt_ctx->streams[stream_index];
/* find decoder for the stream */
dec = avcodec_find_decoder(st->codecpar->codec_id);
if (!dec) {
fprintf(stderr, "Failed to find %s codec\n",
av_get_media_type_string(type));
return AVERROR(EINVAL);
}
/* Allocate a codec context for the decoder */
*dec_ctx = avcodec_alloc_context3(dec);
if (!*dec_ctx) {
fprintf(stderr, "Failed to allocate the %s codec context\n",
av_get_media_type_string(type));
return AVERROR(ENOMEM);
}
/* Copy codec parameters from input stream to output codec context */
if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0) {
fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n",
av_get_media_type_string(type));
return ret;
}
/* Init the decoders, with or without reference counting */
av_dict_set(&opts, "refcounted_frames", refcount ? "1" : "0", 0);
if ((ret = avcodec_open2(*dec_ctx, dec, &opts)) < 0) {
fprintf(stderr, "Failed to open %s codec\n",
av_get_media_type_string(type));
return ret;
}
avcodec_find_decoder()函数和avcodec_open2()函数,两位“闺蜜”都找到了!那显然填充解码器上下文的方式就是使用avcodec_parameters_to_context()函数,完美解决。即:
#if IS_FFMPEG_VERSION_1//对应当前FFMPEG版本
avcodec_parameters_to_context(m_pEncoderCtx, m_pOutVideoStream->codecpar);
#else//旧版本
m_pEncoderCtx = m_pOutVideoStream->codec;
#endif
上述修改花费了好多时间。在修改一个旧程序之后,我就想偷懒,有没有更简单的方法呢?于是,尝试了第二个解决方案。
这是最懒最简单的方法。ffmpeg虽然以attribute_deprecated警告我们函数过时了,但很多版本(我不确定是不是全部)依然支持旧函数的功能,那我们可以钻空子把attribute_deprecated屏蔽掉,就可以使用旧程序代码了。
当然,这种方法只建议在只想了解别人旧代码不想修改原代码的情况下使用。
如何屏蔽,不用我教了吧,如下:
//attribute_deprecated
int avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt,
const AVFrame *frame, int *got_packet_ptr);
(第一次嵌入代码,文字编排很乱,哎……)
//---------------------------------------------------------------------------------
后记(2016-11-03):
在写这篇文章时,为了表述如何从新版本代码找到对应函数,我灵光一闪,命名为闺蜜搜索法:)
在我杜撰闺蜜搜索法之后没几天,韩国总统朴槿惠遭遇闺蜜事件,被闺蜜拖下水,美国总统候选人希拉里亦遭遇闺蜜事件,邮件门重启,大选堪忧。
看来闺蜜搜索法是放之四海而皆准的真理啊!