目录
1.3.1 av_ register_all && av_register_input_format && av_register_output_format
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也一并串起来挂在后头。逻辑相当简单,但有以下几个需要注意的地方:
- 为了使该函数并发安全,进行了上锁处理,主要逻辑处于 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}。
- 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 };
- 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, };
- 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, };
- 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都有什么:
-
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初始化代码在哪儿?