从学龄前开始解读FFMPEG代码 之 avcodec_register_all函数

本文详细解读了FFmpeg中用于注册编解码器的avcodec_register_all()函数,包括其工作原理、相关辅助函数的作用,如av_codec_iterate()和av_codec_init_next()。通过对这些函数的分析,揭示了FFmpeg如何初始化和链接编解码器的静态数据,以及codec_list数组的生成过程。
摘要由CSDN通过智能技术生成

开始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,那就直接僵住了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值