FFRMPEG4.1源码分析之 av_register_all() && avdevice_register_all()

目录

0 前言

1 av_register_all

1.1 基本信息

1.2 函数声明

1.3 源码

1.3.1 av_ register_all && av_register_input_format && av_register_output_format

1.3.2 ff_thread_once

1.3.3 av_format_init_next

1.3.4 avdevice_register_all

2 新旧版本的av_register_all简单对比

 


0 前言


liavformat库是ffmpeg的多媒体容器,主要负责协议/解协议,封装/解封装。该库对外暴露3个头文件,分别是avformat.h,avio.h, version.h。

  • version.h :libavformat版本定义头文件,都是宏定义,包含libavformat的major,minor,micro版本的定义,以及可能会随主版本变化而消失的一些公共api接口宏定义;
  • avio.h:如名,是IO处理相关的头文件,包含重要的IO上下文结构体AVIOContext,目录相关结构体AVIODirContext,AVIODirEntry;以及IO处理相关的API函数,open,read,write,close,seek等等;
  • avformat.h:包含重要的结构体,封装上下文AVFormatContext,封装上下文内部结构体AVFormatInternal,输入文件格式结构体AVInputFormat,输出文件结构体AVOutputFormat,文件探测结构体AVProbeData,流结构体AVStream等等;以及封装相关处理API函数。

本文以ffmpeg4.1的libavformat库为基础进行分析,ffmpeg4.1之前的版本请参考雷神的博客:ffmpeg 源代码简单分析 : av_register_all()

1 av_register_all


1.1 基本信息


  • 所属库: libavformat   
  • 头文件: avformat.h 
  • 源文件: allformat.c

1.2 函数声明


#if FF_API_NEXT
/**
 * Initialize libavformat and register all the muxers, demuxers and
 * protocols. If you do not call this function, then you can select
 * exactly which formats you want to support.
 *
 * @see av_register_input_format()
 * @see av_register_output_format()
 */
attribute_deprecated
void av_register_all(void);

attribute_deprecated
void av_register_input_format(AVInputFormat *format);
attribute_deprecated
void av_register_output_format(AVOutputFormat *format);
#endif

从函数声明中可以获知以下信息:

  • 该函数会初始化libavformat并且注册所有的封装器,解封装器以及协议。
  • 并且在不调用av_register_all的情况下,可以通过使用av_register_input_format或者av_register_output_format精确选择某个需要支持的格式。但是注意一点:这3个函数源码是一摸一样的,也许是因为以后这些函数都要弃用,所以如此处理。
  • 函数定义在#if FF_API_NEXT .... #endif之间,而FF_API_NEXT是在version.h中定义,暗示了这之间定义的public api将会在未来的某个版本给drop掉。
  • 注意函数已经被标记为  attribute_deprecated,告知编译器函数已过时。关于FFMPEG中过时代码管理的分析见文章:FFMPEG过时代码管理 attribute_deprecated

1.3 源码


1.3.1 av_ register_all && av_register_input_format && av_register_output_format


#if FF_API_NEXT
FF_DISABLE_DEPRECATION_WARNINGS
static AVOnce av_format_next_init = AV_ONCE_INIT;

void av_register_all(void)
{
    ff_thread_once(&av_format_next_init, av_format_init_next);
}

void av_register_input_format(AVInputFormat *format)
{
    ff_thread_once(&av_format_next_init, av_format_init_next);
}

void av_register_output_format(AVOutputFormat *format)
{
    ff_thread_once(&av_format_next_init, av_format_init_next);
}
FF_ENABLE_DEPRECATION_WARNINGS
#endif

查看源码,发掘以下几个信息:

  • 函数定义以FF_DISABLE_DEPRECATION_WARNINGS和FF_ENABLE_DEPRECATION_WARNINGS包围,这两个函数起到抑制编译器告警的作用,具体分析见文章:FFMPEG过时代码管理 attribute_deprecated
  • 后两个API的输入参数直接是被忽略的
  • 这三个函数均只调用了ff_thread_once(&av_format_next_init, av_format_init_next);
  • ff_thread_once的第一个参数定义为:static AVOnce av_format_next_init = AV_ONCE_INIT;  其类型AVOnce经过层层宏定义跳转发现为pthread_once_t,实质是void*;而其值AV_ONCE_INIT经过层层宏定义跳转之后发现为{0}。

1.3.2 ff_thread_once


  • 所属库: libavutil
  • 头文件: thread.h
  • 源文件: ff_thread_once被定义为pthread_once
  • 功能说明:其实由函数的名称就可以知道,其保证在多线程的环境下,av_format_init_next只运行一次。ff_thread_once也是一个宏定义,其定义在libavutil\thread.h中,定义为pthread_once。
#define ff_thread_once(control, routine) pthread_once(control, routine)
  • pthread_once:thread.h中根据平台不同引入不同的线程库,删减掉不必要的代码,引入库的顺序如下代码所示:发现优先使用pthread.h库->其次os2threads.h库->再次w32pthreads.h。pthread_once在不同的平台下映射到不同线程库中的pthread_once函数。
#if HAVE_PTHREADS
#include <pthread.h>
#elif HAVE_OS2THREADS
#include "compat/os2threads.h"
#else
#include "compat/w32pthreads.h"
#endif
  • pthread_once:Windows Visual Studio环境下,使用的是w32pthreads库,其对pthread_once的定义如下
static av_unused int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
{
    BOOL pending = FALSE;
    InitOnceBeginInitialize(once_control, 0, &pending, NULL);
    if (pending)
        init_routine();
    InitOnceComplete(once_control, 0, NULL);
    return 0;
}

调用了Windows平台相关的函数InitOnceBeginInitialize和InitOnceComplete,来确保init_routine方法(在当前情况下是av_format_init_next方法)在多线程环境下只会被调用一次,至于InitOnceBeginInitialize和InitOnceComplete如何保证这一点的,可以查阅微软的开发说明文档:InitOnceComplete function以及InitOnceBeginInitialize function

  • 小测试:看看反复调用pthread_once,init_routine将会被调用几次
#include <windows.h>
#include <process.h>
#include <stdio.h>

static int count = 0;

static void init_routine() {
	count++;
}

static int pthread_once(LPINIT_ONCE once_control, void(*init_routine)(void))
{
	BOOL pending = FALSE;
	InitOnceBeginInitialize(once_control, 0, &pending, NULL);
	if (pending)
		add_count();
	InitOnceComplete(once_control, 0, NULL);
	return 0;
}

int main(int argc, char* argv[]) {
	void* once = 0;
	LPINIT_ONCE once_control = (LPINIT_ONCE)&once;

	printf("before once_control:%d  and count:%d\n", *once_control, count);

	pthread_once(once_control, init_routine);
    printf("after1 once_control:%d and count:%d\n", *once_control, count);

	pthread_once(once_control, init_routine);
	pthread_once(once_control, init_routine);
	printf("after2 once_control:%d and count:%d\n", *once_control, count);

	getchar();
	return 0;
}

结果如下:可以看见,在执行一次pthread_once后,once_control 已经变为2,init_routine执行一次,count自增1。后续不论调用几次pthread_once,once_control不变,init_routine也不会被执行。当然此处最好是让后续两次pthread_once的执行放在线程中来做,模拟并发的情况。

1.3.3 av_format_init_next


  • 所属库:libavformat
  • 头文件:无,静态函数
  • 源文件:allformat.c
static void av_format_init_next(void)
{
    AVOutputFormat *prevout = NULL, *out;
    AVInputFormat *previn = NULL, *in;

    ff_mutex_lock(&avpriv_register_devices_mutex);

    for (int i = 0; (out = (AVOutputFormat*)muxer_list[i]); i++) {
        if (prevout)
            prevout->next = out;
        prevout = out;
    }

    if (outdev_list) {
        for (int i = 0; (out = (AVOutputFormat*)outdev_list[i]); i++) {
            if (prevout)
                prevout->next = out;
            prevout = out;
        }
    }

    for (int i = 0; (in = (AVInputFormat*)demuxer_list[i]); i++) {
        if (previn)
            previn->next = in;
        previn = in;
    }

    if (indev_list) {
        for (int i = 0; (in = (AVInputFormat*)indev_list[i]); i++) {
            if (previn)
                previn->next = in;
            previn = in;
        }
    }

    ff_mutex_unlock(&avpriv_register_devices_mutex);
}

不难看出,该函数的作用就是将muxer_list中的输出封装结构AVOutputFormat挨个串起来形成单向链表,当outdev_list不为空的时候,同时将outdev_list中的AVOutputFormat也一并串在后头。同理将demuxer_list中的输入封装结构体AVInputFormat挨个穿起来形成单向链表,同时在indev_list不为空的时候,将indev_list中的AVInputFormat也一并串起来挂在后头。逻辑相当简单,但有以下几个需要注意的地方:

  1.  为了使该函数并发安全,进行了上锁处理,主要逻辑处于 ff_mutex_lock(&avpriv_register_devices_mutex); 与ff_mutex_unlock(&avpriv_register_devices_mutex);之间。avpriv_register_devices_mutex定义为
    static AVMutex avpriv_register_devices_mutex = AV_MUTEX_INITIALIZER;

    AVMutex是个宏定义,追踪其定义为 #define AVMutex pthread_mutex_t,该语句在libavutil/thread.h中,其与上文所述的ptread_once函数一样,对于不同的平台使用不同的线程库,映射到相应的逻辑上去,此处不再赘述。对于该锁的初始化值AV_MUTEX_INITIALIZER,追踪其定义为{0}。

  2. muxer_list和demuxer_list为两个大的静态数组,前者位于libavformat/muxer_list.c源文件中,后者位于libavformat/demuxer_list.c源文件中。具体内容如下所示:
    static const AVOutputFormat *muxer_list[] = {
        &ff_a64_muxer,
        此处省略几十上百行...
        &ff_yuv4mpegpipe_muxer,
        NULL };
    
    static const AVInputFormat *demuxer_list[] = {
        &ff_aa_demuxer,
        此处省略几十上百行...
        &ff_libmodplug_demuxer,
        NULL };

     

  3. muxer_list的单个元素进行简要分析,以ff_adts_muxer封装器为例:首先,ff_adts_muxer定义在libavformat/adtsenc.c源文件中,注意源文件命名规则,一般都是"format+enc".c;当然也会有例外,好几个功能类似封装放一个.c源文件中,并以类似的这个"功能+enc".c来命名源文件,比如ff_ac3_muxer,ff_adx_muxer等都在libavformat/rawenc.c中。其次ff_adts_muxer的类型为AVOutputFormat,对于AVOutputFormat类的分析将在其他文章中体现,查看下ff_adts_muxer对象的定义如下,将音频数据封装成adts格式所必要的字段都进行了初始化,尤其是最后的几个函数指针adts_init,adts_write_header,adts_write_packet,adts_write_trailer都在同一文件中定义。
    AVOutputFormat ff_adts_muxer = {
        .name              = "adts",  
        .long_name         = NULL_IF_CONFIG_SMALL("ADTS AAC (Advanced Audio Coding)"),
        .mime_type         = "audio/aac",
        .extensions        = "aac,adts",
        .priv_data_size    = sizeof(ADTSContext),
        .audio_codec       = AV_CODEC_ID_AAC,
        .video_codec       = AV_CODEC_ID_NONE,
        .init              = adts_init,
        .write_header      = adts_write_header,
        .write_packet      = adts_write_packet,
        .write_trailer     = adts_write_trailer,
        .priv_class        = &adts_muxer_class,
        .flags             = AVFMT_NOTIMESTAMPS,
    };
  4. demuxer_list中单个元素进行简要分析,以ff_aac_demuxer解封装器为例:首先,ff_aac_demuxer定义在libavformat/aacdec.c源文件中,文件名命名形式“format+dec”.c;也有以format.c形式命名的。其次,ff_aac_demuxer类型为AVInputFormat,同理对AVInputFormat结构体的分析将在其他文章中做专门的解析。查看ff_aac_demuxer源码定义如下,注意adts_aac_probe,adts_aac_read_header,adts_aac_read_packet几个函数的定义也在同一文件中。
    AVInputFormat ff_aac_demuxer = {
        .name         = "aac",
        .long_name    = NULL_IF_CONFIG_SMALL("raw ADTS AAC (Advanced Audio Coding)"),
        .read_probe   = adts_aac_probe,
        .read_header  = adts_aac_read_header,
        .read_packet  = adts_aac_read_packet,
        .flags        = AVFMT_GENERIC_INDEX,
        .extensions   = "aac",
        .mime_type    = "audio/aac,audio/aacp,audio/x-aac",
        .raw_codec_id = AV_CODEC_ID_AAC,
    };
  5. outdev_list && indev_list的定义同样在allformat.c,如下源码所示:
    static const AVInputFormat * const *indev_list = NULL;
    static const AVOutputFormat * const *outdev_list = NULL;

    一开始对这个很有疑惑,因为二者皆为空,后来发现allformat.c文件中最后有一个函数avpriv_register_devices,由名称就可知该函数不是public api,应该是供ffmpeg内部调用,查看其声明的头文件,为libavformat/internal.h。

    void avpriv_register_devices(const AVOutputFormat * const o[], const AVInputFormat * const i[])
    {
        ff_mutex_lock(&avpriv_register_devices_mutex);
        outdev_list = o;
        indev_list = i;
        ff_mutex_unlock(&avpriv_register_devices_mutex);
    #if FF_API_NEXT
        av_format_init_next();
    #endif
    }
    

    这下就清楚了,原来这两个列表是由外部来进行初始化的,由于是输入输出设备列表,因此avpriv_register_devices很可能libavdevice库进行调用。搜索avpriv_register_devices在源码中的引用,发现其唯一被调用的位置在libavdevice/alldevice.c的avdevice_register_all函数中

    void avdevice_register_all(void)
    {
        avpriv_register_devices(outdev_list, indev_list);
    }

     

1.3.4 avdevice_register_all


  • 所属库:libavdevice
  • 头文件:avdevice.h
  • 源文件:  alldevices.c
  • 函数声明:
    /**
     * Initialize libavdevice and register all the input and output devices.
     * 初始化libavdevice和注册所有输入输出设备
     */
    void avdevice_register_all(void);
  • 源码:avdevice_register_all代码很简单,就只有一行,即调用libavformat/allfomats.c文件中的avpriv_register_devices函数将outdev_list和indev_list注入进去,并启动了av_register_all的工作。
    void avdevice_register_all(void)
    {
        avpriv_register_devices(outdev_list, indev_list);
    }

    来查看下outdev_list和indev_list都有什么:

  1.  outdev_list定义在libavdevice/outdev_list.c,indev_list定义在libavdevice/indev_list.c。如此就不对列表中的单个项进行解释了,因为都是AVInputFormat和AVOutputFormat结构体对象。

    static const AVOutputFormat *outdev_list[] = {
        &ff_opengl_muxer,  // opengl渲染
        &ff_sdl2_muxer,    // sdl渲染
        NULL };
    
    static const AVInputFormat *indev_list[] = {
        &ff_dshow_demuxer,    // directshow采集
        &ff_gdigrab_demuxer,  // gdi采集
        &ff_lavfi_demuxer,    // lav filter ffmpeg滤镜
        &ff_vfwcap_demuxer,   // video for windows capture
        &ff_libcdio_demuxer,  // CD-ROM and CD image 
        NULL };

2 新旧版本的av_register_all简单对比


旧版本中,av_register_all几乎是所有ffmpeg程序的开端,因为其初始化了所有的协议/解协议,输入/输出设备,封装/解封装器,编码器/解码器,硬件加速器,Parser,Bitstream Filter等等。而ffmpeg4.1中的av_register_all中却只简单的初始化了封装/解封装器,并且av_register_all还被标记为deprecated,那么就很让人好奇之前av_register_all的工作都由谁分担去做了?在没有av_register_all的ffmpeg程序的开端应该是个什么流程?(未完待续)

疑问点一:协议/解协议的初始化代码在哪儿?

疑问点二:解码器/编码器的初始化代码在哪儿?

疑问点三:硬件加速器,Parse,Bitstream Filter初始化代码在哪儿?

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值