从学龄前开始解读FFMPEG代码 之 avcodec_register_all函数
开始avcodec_register函数学习前想说的一些话
首先声明源码是我在github上直接clone并checkout到了4.2的版本。其次这篇文章仅限于这个函数,AVCodec这个复杂的结构体后续应该还会另外开一个时间去解读吧…(大概率不会坑?)
从avcodec_register_all()开始
该函数是用于注册编解码器(即AVCodec)的函数,源代码在libavcodec/allcodecs.c文件里。在旧版(3.x早期)的ffmpeg中该函数也几乎是所有使用编解码器的开始。值得注意的是,在该函数定义的上方和下方,有两个宏暂时性地解除了avcodec_register_all这个函数以及其他三个函数的DEPRECATION_WARNING。如果没有这两个deprecation解除的话,这四个函数的使用依然会出现,函数被声明为deprecated的警告。这就意味着这几个函数在新版的FFMPEG中其实也是处于某种将要被弃用的状态,但是这几个函数被解除了DEPRECATION_WARNING,所以其实并不妨碍它们的正常使用,也不会出现deprecated警告。(毕竟现在依然有很多编解码代码会在代码开始时使用avcodec_register_all()这个函数,仿佛在吃饭前一定要进行一个什么祈祷一样)
回到代码本身吧,来看看这一段被取消了DEPRECATION_WARNING的代码都在做些什么。
#if FF_API_NEXT
FF_DISABLE_DEPRECATION_WARNINGS
static AVOnce av_codec_next_init = AV_ONCE_INIT;
static void av_codec_init_next(void)
{
AVCodec *prev = NULL, *p;
void *i = 0;
while ((p = (AVCodec*)av_codec_iterate(&i))) {
if (prev)
prev->next = p;
prev = p;
}
}
av_cold void avcodec_register(AVCodec *codec)
{
ff_thread_once(&av_codec_next_init, av_codec_init_next);
}
AVCodec *av_codec_next(const AVCodec *c)
{
ff_thread_once(&av_codec_next_init, av_codec_init_next);
if (c)
return c->next;
else
return (AVCodec*)codec_list[0];
}
void avcodec_register_all(void)
{
ff_thread_once(&av_codec_next_init, av_codec_init_next);
}
FF_ENABLE_DEPRECATION_WARNINGS
#endif
首先第一个FF_API_NEXT,实际上是一个FFMPEG的版本检查,其宏定义是跳转到libavcodec/version.h中,对libavcodec的major版本进行检查
#define FF_API_NEXT (LIBAVCODEC_VERSION_MAJOR < 59)
而本文所解析的代码的MAJOR为58,也就是中间这几个函数都会被启用。将来的版本中如果VERSION_MAJOR发生变动,可能这几个函数就不被定义了。
之后先结束了DEPRECATION_WARNING,然后可以注意到,avcodec_register,av_codec_next,avcodec_register_all这三个函数都类似于av_register_all函数,也使用了单次运行的保护机制,ff_thread_once去进行注册(这一部分的内容可以看上一篇文章),实际真正的注册函数在av_codec_init_next()函数当中。当然,av_codec_next在此之后还做了些其他操作,也非常简单,就是将链表中的c确认并返回c的next 的AVCodec(当前传入的编解码器的下一个编解码器),如果没有那么返回codec_list中的第一个编解码器。
好消息,也就是说我们研究好av_codec_init_next函数就能了解avcodec_register_all()的主要内容了。
avcodec_init_next()做了啥
更好的消息,av_codec_init_next内容也非常的短:
static void av_codec_init_next(void)
{
AVCodec *prev = NULL, *p;
void *i = 0;
while ((p = (AVCodec*)av_codec_iterate(&i))) {
if (prev)
prev->next = p;
prev = p;
}
}
这个函数创建了两个指针,然后使用了av_codec_iterate函数依次取出可AVCodec指针变量,使用while循环把这些指针给串联到一起去了。**每次使用p指针取出一个AVCodec,然后将prev的next指针指向刚取出的p,然后prev前进一个指针,指向刚刚取出并链接好的p。**通过一个循环将AVCodec链接在一起。那么现在就是看一下av_codec_iterate函数做了啥了。
av_codec_iterate函数以及它调用的av_codec_init_static()
好消息接踵而至,这个函数也非常短,它的定义就在刚刚的DEPRECATION_WARNING宏定义上面。
const AVCodec *av_codec_iterate(void **opaque)
{
uintptr_t i = (uintptr_t)*opaque;
const AVCodec *c = codec_list[i];
ff_thread_once(&av_codec_static_init, av_codec_init_static);
if (c)
*opaque = (void*)(i + 1);
return c;
}
在av_codec_init_next中是传入了一个 i 变量计数到这个函数内部的,那么在这个函数内,它被转换为uint类型,然后取出了codec_list[i]表中第i个AVCodec,赋值给c,然后同样使用了一次性执行函数,执行的函数对象为av_codec_init_static,在执行完成后,将 i 计数增加了1,然后返回了刚刚取出的第 i 个AVCodec。
再到av_codec_init_static函数中,它对刚刚取出的第 i 个编码器做了什么呢?看一下它的代码吧。
static void av_codec_init_static(void)
{
for (int i = 0; codec_list[i]; i++) {
if (codec_list[i]->init_static_data)
codec_list[i]->init_static_data((AVCodec*)codec_list[i]);
}
}
它对codec_list数组中的所有编解码器(AVCodec)都执行了一编它们自身的init_static_data。而看函数名就知道,顾名思义,这个函数是将AVCodec对象内部的static数据进行初始化的。也就是说**注册在codec_list中的编解码器,如果它自身定义了init_static_data函数的话,都会在这里被调用并用来初始化它们的static数据。**在此之后,av_codec_iterate再将该函数返回。
到这里,整个流程就已经很清楚了。avcodec_register_all()通过函数执行,将codec_list中的所有编解码器的静态数据初始化一遍,并作为链表上的一员把它们串在了一起。
关于codec_list和“丢失的”codec_list.c
在上面的代码解读中,我们多次看见codec_list这个数组。这个数组是哪来的呢?在直接clone下来的代码中是看不见的。codec_list列表是存在于libavcodec/codec_list.c文件中的,而该文件在下载之后的ffmpeg的source code中是找不到的,必须要执行ffmpeg根目录的configure才能生成。同样原理的还有根目录的configure.h文件以及demux_list.c等等。在codec_list.c中,内容就是一大串的AVCodec数组。
static const AVCodec * const codec_list[] = {
&ff_a64multi_encoder,
&ff_a64multi5_encoder,
&ff_alias_pix_encoder,
&ff_amv_encoder,
....,
NULL
};
所以如果直接把代码clone下来,什么都不做直接开始解读,去找codec_list,那就直接僵住了。