目录
1 avformat_alloc_context() 源码分析
1.3 avformat_get_context_defaults()
1.3.1 av_format_context_class结构体
1.3.2 io_open_default() && io_close_default()
0 前言
liavformat库是ffmpeg的多媒体容器,主要负责协议/解协议,封装/解封装。该库对外暴露3个头文件,分别是avformat.h avio.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库为基础进行分析,先给出整体的思维导图
1 avformat_alloc_context() 源码分析
avformat_alloc_context() 函数声明:
- 该函数属于 libavformat(lavf)库
- 头文件:libavformat/avfromat.h
- 该函数用于分配空间创建一个AVFormatContext对象,并且强调使用avformat_free_context方法来清理并释放该对象的空间。
/**
* Allocate an AVFormatContext.
* avformat_free_context() can be used to free the context and everything
* allocated by the framework within it.
*/
AVFormatContext *avformat_alloc_context(void);
avformat_alloc_context() 源码:
-
源文件:libavformat/options.c
-
主要完成AVFormatContext的空间分配,注意分配在堆上;
-
给AVFormatContext的成员赋默认值;
-
完成AVFormatContext内部使用对象AVFormatInternal结构体的空间分配及其部分成员字段的赋值。关于AVFormatInternal结构体的详细描述见 FFRMPEG4.1源码分析之 AVFormatInternal结构体
AVFormatContext *avformat_alloc_context(void) { AVFormatContext *ic; ic = av_malloc(sizeof(AVFormatContext)); // 使用av_malloc分配空间 if (!ic) return ic; avformat_get_context_defaults(ic); // 给AVFormatContext的部分成员赋默认值 ic->internal = av_mallocz(sizeof(*ic->internal)); // 创建AVFormatContext->AVFormatInternal,给一些成员赋初值 if (!ic->internal) { avformat_free_context(ic); return NULL; } ic->internal->offset = AV_NOPTS_VALUE; ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE; ic->internal->shortest_end = AV_NOPTS_VALUE; return ic; }
1.1 av_malloc()
av_malloc() 声明:
- 所属库:libavutil,该库是ffmpeg的功能库,提供了线程,内存,文件,加密等功能
- 头文件:libavutil/mem.h
- 该函数作用在于给对象分配内存块,并且是内存对齐的
- 该函数由两个宏av_malloc_attrib && av_alloc_size(1)指定了一些编译器属性,作用如后面所描述,更详细的描述见 FFMPEG4.1源码分析之 内存分配
/**
* Allocate a memory block with alignment suitable for all memory accesses
* (including vectors if available on the CPU).
*
* @param size Size in bytes for the memory block to be allocated
* @return Pointer to the allocated block, or `NULL` if the block cannot
* be allocated
* @see av_mallocz()
*/
void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1);
- av_malloc_attrib是一个宏定义,如果是在编译器GCC3.1及以上版本的情况下,给方法av_malloc增加属性 __attribute__((__malloc__)),该属性指示编译器按照malloc函数来对待,并且可以对其实施相应的优化措施。后续在研究ffmpeg中内存分配与管理的源码之后,会再尝试写一篇文章详细介绍。此时,先给出几个网上关于该属性的描述相关网址:http://www.keil.com/support/man/docs/armcc/armcc_chr1359124975555.htm; https://stackoverflow.com/questions/18485447/gcc-attribute-malloc; https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007bmalloc_007d-function-attribute-3251
/** * @def av_malloc_attrib * Function attribute denoting a malloc-like function. * * @see <a href="https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007bmalloc_007d-function-attribute-3251">Function attribute `malloc` in GCC's documentation</a> */ #if AV_GCC_VERSION_AT_LEAST(3,1) #define av_malloc_attrib __attribute__((__malloc__)) #else #define av_malloc_attrib #endif
- av_alloc_size(1)也是一个宏定义,如果是在编译器GCC4.3及以上版本的情况下,给方法增加一个属性__attribute__((alloc_size(1))),告知编译器av_malloc(size_t size)方法的第一个参数,也即size是要分配的空间大小,关于__attribute__((alloc_size(__VA_ARGS__)))属性的详细描述可以见:https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007balloc_005fsize_007d-function-attribute-3220
av_malloc() 源码:
void *av_malloc(size_t size)
{
void *ptr = NULL;
/* let's disallow possibly ambiguous cases */
if (size > (max_alloc_size - 32))
return NULL;
#if HAVE_POSIX_MEMALIGN
if (size) //OS X on SDK 10.6 has a broken posix_memalign implementation
if (posix_memalign(&ptr, ALIGN, size))
ptr = NULL;
#elif HAVE_ALIGNED_MALLOC
ptr = _aligned_malloc(size, ALIGN);
#elif HAVE_MEMALIGN
#ifndef __DJGPP__
ptr = memalign(ALIGN, size);
#else
ptr = memalign(size, ALIGN);
#endif
/* Why 64?
* Indeed, we should align it:
* on 4 for 386
* on 16 for 486
* on 32 for 586, PPro - K6-III
* on 64 for K7 (maybe for P3 too).
* Because L1 and L2 caches are aligned on those values.
* But I don't want to code such logic here!
*/
/* Why 32?
* For AVX ASM. SSE / NEON needs only 16.
* Why not larger? Because I did not see a difference in benchmarks ...
*/
/* benchmarks with P3
* memalign(64) + 1 3071, 3051, 3032
* memalign(64) + 2 3051, 3032, 3041
* memalign(64) + 4 2911, 2896, 2915
* memalign(64) + 8 2545, 2554, 2550
* memalign(64) + 16 2543, 2572, 2563
* memalign(64) + 32 2546, 2545, 2571
* memalign(64) + 64 2570, 2533, 2558
*
* BTW, malloc seems to do 8-byte alignment by default here.
*/
#else
ptr = malloc(size);
#endif
if(!ptr && !size) { // 在size为0时,将分配一个字节的空间
size = 1;
ptr= av_malloc(1);
}
#if CONFIG_MEMORY_POISONING
if (ptr)
memset(ptr, FF_MEMORY_POISON, size);
#endif
return ptr;
}
- 该函数在不同的宏作用下使用了不同的内存分配函数,优先级为posix_memalign() ->_aligned_malloc() -> memalign() -> malloc()。这些宏定义在文件config.h中,这个文件不属于ffpmeg的任何库,这个是ffmpeg源码编译时,configure命令生成的配置文件,在不同的环境下,config.h文件中的宏定义值是不一样的。
- 就我当前的编译环境Windows系统,VisualStudio2013update5来说,这几个宏定义分别为如下源代码所示,因此将会使用_aligned_malloc()方法进行内存分配,而该方法定义在如下平台相关的文件中,见下图,另外该方法的详细描述见:MicroSoft Docs:_aligned_malloc
#define HAVE_POSIX_MEMALIGN 0 #define HAVE_ALIGNED_MALLOC 1 #define HAVE_MEMALIGN 0 __DJGPP__ 未定义
-
内存对齐:上述几个函数除了malloc外都具有内存对齐功能,另外还有一个宏ALIGN ,其根据另外两个宏HAVE_AVX512 和HAVE_AVX来决定是按多少位对齐,而HAVE_AVX和HAVE_AVX512指示环境中是否可以使用AVX CPU指令集和AVX512 CPU扩展指令集,能使用更高级的指令集意味着可以使用的寄存器大小会变大,因此对齐的数据位数也会变大。关于AVX与AVX512的详细描述见wikipedia:AVX指令集, AVX512
#define ALIGN (HAVE_AVX512 ? 64 : (HAVE_AVX ? 32 : 16))
-
注意到当size=0时,将分配一个字节的空间,这儿是令我奇怪的一点,为什么此处不直接返回NULL就行了?突然又想到在C/C++的空结构体/空类都会占据一个字节以作占位符,见如下示例代码以及结果展示。猜测这儿应该也是起同样的作用。
#include <stdio.h> struct EmptyStruct{}; class EmptyClass{}; int main(int argc, char* argv[]) { printf("EmptyStruct'size is %d\n", sizeof(EmptyStruct)); printf("EmptyClass'size is %d\n", sizeof(EmptyClass)); getchar(); return 0; }
-
最后,宏CONFIG_MEMORY_POISONING决定了分配后的数据是否需要使用数据填充,根据该宏的名称也知道是“毒化,中毒”的意思,通常我们分配内存后一般都是使用0去初始化这块儿内存,但是ffmpeg中如果配置了上述宏定义,那么会使用FF_MEMORY_POISON指定的0x2a去填充分配空间的每个字节。当然,就我目前的环境而言,config.h文件中的 CONFIG_MEMORY_POISONING 被定义为0,也即不进行内存“毒化”。
#define CONFIG_MEMORY_POISONING 0 #define FF_MEMORY_POISON 0x2a
1.2 av_mallocz()
av_mallocz() 声明:
void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1);
av_mallocz() 源码:
void *av_mallocz(size_t size)
{
void *ptr = av_malloc(size); 使用av_malloc分配内存
if (ptr)
memset(ptr, 0, size); 将分配的内存块所有字节置0
return ptr;
}
1.3 avformat_get_context_defaults()
avformat_get_context_defaults() 源码:
- 所属库:libavformat(lavf)
- 头文件:无,静态函数,ffmpeg内部使用
- 源文件:libavformat/options
static void avformat_get_context_defaults(AVFormatContext *s)
{
memset(s, 0, sizeof(AVFormatContext));
s->av_class = &av_format_context_class;
s->io_open = io_open_default;
s->io_close = io_close_default;
av_opt_set_defaults(s);
}
- 将AVFormatContext对象内存块全置为0;
- 将静态的AVClass对象av_format_context_class赋值给AVFormatContext.av_class;
- 将静态函数io_open_default,io_close_default赋值给AVFormatContext.io_open,AVFormatContext.io_close
- 使用av_opt_set_defaults(s)将根据赋值后的AVClass.option选项信息来填充AVFormatContext的成员字段
以下对2 3 4过程进行详细的描述.
1.3.1 av_format_context_class结构体
该静态类位于libavformat/options.c中,是AVClass对象,AVClass对象是定义在libavutil库中的对象,可以想象下,肯定是提供某种功能的工具类,关于AVClass对象的描述将在另外的文件中进行(FFMPEG4.1源码解析之 AVClass结构体),此处只简单的罗列各字段内容,并做介绍。
static const AVClass av_format_context_class = {
.class_name = "AVFormatContext",
.item_name = format_to_name,
.option = avformat_options,
.version = LIBAVUTIL_VERSION_INT,
.child_next = format_child_next,
.child_class_next = format_child_class_next,
.category = AV_CLASS_CATEGORY_MUXER,
.get_category = get_category,
};
- AVClass.class_name::AVClass类名称,通常与其关联的上下文对象的结构体类型名相同。此处关联的是AVFormatContext上下文对象,因此AVClass的名称也即为"AVFormatContext";
- AVClass.item_name:这是一个函数指针,该函数返回关联的上下文实例名称。此处的format_to_name是libavformat/options.c文件中的静态函数,返回的是AVFormatContext中已经保存好的输入文件格式名称或者是输出格式名称,若是解封装过程还没有探测到文件格式或者是封装过程中用户还未设置输出文件格式,那么将返回NULL。源码如下
static const char* format_to_name(void* ptr) { AVFormatContext* fc = (AVFormatContext*) ptr; if(fc->iformat) return fc->iformat->name; else if(fc->oformat) return fc->oformat->name; else return "NULL"; }
- AVClass.option:指向的是某个选项列表数组的第一个选项,这个是非常重要的一个参数,提供了以“以字符串来设置结构体成员字段的方式”,这儿会在专门介绍AVClass和AVOption的文章中重点论述。此处的avformat_options是定义在libavformat/options_table.h文件中定义的静态常量结构体,源码如下:
static const AVOption avformat_options[] = { {"avioflags", NULL, OFFSET(avio_flags), AV_OPT_TYPE_FLAGS, {.i64 = DEFAULT }, INT_MIN, INT_MAX, D|E, "avioflags"}, {"direct", "reduce buffering", 0, AV_OPT_TYPE_CONST, {.i64 = AVIO_FLAG_DIRECT }, INT_MIN, INT_MAX, D|E, "avioflags"}, {"probesize", "set probing size", OFFSET(probesize), AV_OPT_TYPE_INT64, {.i64 = 5000000 }, 32, INT64_MAX, D}, 此处省略几十行.... {NULL}, };
- AVClass.version:表示的是libavutil库的当前版本,该版本号有宏LIBAVUTIL_VERSION_INT制定,该宏定义在libavutil/version.h文件中,如下源码所示:
#define LIBAVUTIL_VERSION_MAJOR 56 #define LIBAVUTIL_VERSION_MINOR 19 #define LIBAVUTIL_VERSION_MICRO 100 #define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ LIBAVUTIL_VERSION_MINOR, \ LIBAVUTIL_VERSION_MICRO) #define AV_VERSION_INT(a, b, c) ((a)<<16 | (b)<<8 | (c))
- AVClass.child_next:该字段是一个函数指针,英文注释是“Return next AVOptions-enabled child or NULL”,目前还没有很好的理解该字段的意思,仅限于字面意思,也就是返回输入对象中包含的子对象,等源码盘的更圆润了专门写一篇文章来分析。此处关联的format_child_next是位于libavformat/options.c文件中的静态函数,源码如下:可知要么是AVFormatContext的priv_data存在且有效的情况返回这个字段的对象,如其注释所言,该字段在解封装过程中由avformat_open_input()函数来设置或者是封装过程中由avformat_write_header()函数来设置;要么是在返回的AVFormatContext的pb字段的AVIOContext对象。如函数注释所言,返回输入对象的子对象---注意这些对象必须存在且有效。
static void *format_child_next(void *obj, void *prev) { AVFormatContext *s = obj; if (!prev && s->priv_data && ((s->iformat && s->iformat->priv_class) || s->oformat && s->oformat->priv_class)) return s->priv_data; if (s->pb && s->pb->av_class && prev != s->pb) return s->pb; return NULL; } /** * Format private data. This is an AVOptions-enabled struct * if and only if iformat/oformat.priv_class is not NULL. * * - muxing: set by avformat_write_header() * - demuxing: set by avformat_open_input() */ void *priv_data;
-
AVClass.child_class_next:该字段也是一个函数指针,英文注释是“Return an AVClass corresponding to the next potential AVOptions-enabled child”,与上一个函数一样,对其作用的理解还不透彻,字面意思是返回返回一个AVClass对象,该对象与下一个可能的子对象相关,与上个函数的区别在于“上个函数迭代并返回的是已经存在的对象,而本函数在所有可能的对象中迭代并返回”。此处关联的format_child_class_next是位于libavformat/options.c中的静态函数,源码如下:分析见源码注释
static const AVClass *format_child_class_next(const AVClass *prev) { AVInputFormat *ifmt = NULL; AVOutputFormat *ofmt = NULL; // 查找输入为空,直接返回ff_avio_class对象,该对象的源码见后面 if (!prev) return &ff_avio_class; // 迭代查询串起来的AVInputFormat,还记否av_register_all函数干了啥? // 看prev是否是某个输入格式 while ((ifmt = av_iformat_next(ifmt))) if (ifmt->priv_class == prev) break; // 迭代查询串起来的AVOutputFormat对象,看prev是否是某个输出格式 if (!ifmt) while ((ofmt = av_oformat_next(ofmt))) if (ofmt->priv_class == prev) break; // 如果不是某个输出格式,那么取出下一个AVInputFormat.priv_class存在的输入格式,并返回 // AVInputFormat.priv_class if (!ofmt) while (ifmt = av_iformat_next(ifmt)) if (ifmt->priv_class) return ifmt->priv_class; // 如果所有的AVInputFormat.priv_class均不存在的情况下,那么取出下一个 // AVOutputFormat.priv_class存在的输入格式,并返回 AVOutputFormat.priv_class while (ofmt = av_oformat_next(ofmt)) if (ofmt->priv_class) return ofmt->priv_class; // 如果输入prev不为空,且所有的AVInputFormat.priv_class以及AVOutputFormat.priv_class // 均未空的情况下,则返回空 return NULL; } const AVClass ff_avio_class = { .class_name = "AVIOContext", .item_name = av_default_item_name, .version = LIBAVUTIL_VERSION_INT, .option = ff_avio_options, .child_next = ff_avio_child_next, .child_class_next = ff_avio_child_class_next, };
-
AVClass.category:该字段是一个AVClassCategory枚举类型,作用是“Category used for visualization (like color)”,用于控制视觉特性,比如颜色,在ffmpeg日志系统中将使用该值。此处设置的值为AV_CLASS_CATEGORY_MUXER,具体的AVClassCategory分类信息如下源码所示
typedef enum { AV_CLASS_CATEGORY_NA = 0, AV_CLASS_CATEGORY_INPUT, AV_CLASS_CATEGORY_OUTPUT, AV_CLASS_CATEGORY_MUXER, AV_CLASS_CATEGORY_DEMUXER, AV_CLASS_CATEGORY_ENCODER, AV_CLASS_CATEGORY_DECODER, AV_CLASS_CATEGORY_FILTER, AV_CLASS_CATEGORY_BITSTREAM_FILTER, AV_CLASS_CATEGORY_SWSCALER, AV_CLASS_CATEGORY_SWRESAMPLER, AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40, AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT, AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT, AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT, AV_CLASS_CATEGORY_DEVICE_OUTPUT, AV_CLASS_CATEGORY_DEVICE_INPUT, AV_CLASS_CATEGORY_NB ///< not part of ABI/API }AVClassCategory;
-
AVClass.get_category:该字段是个函数指针,返回输入对象的AVClassCategory枚举类别。此处get_category是libavformat/options.c中的静态函数,源码如下所示:如果AVFormatContext的输入文件格式存在,那么AVClassCategory类别为解封装类目AV_CLASS_CATEGORY_DEMUXER,若不存在那么为封装类目AV_CLASS_CATEGORY_MUXER
static AVClassCategory get_category(void *ptr) { AVFormatContext* s = ptr; if(s->iformat) return AV_CLASS_CATEGORY_DEMUXER; else return AV_CLASS_CATEGORY_MUXER; }
1.3.2 io_open_default() && io_close_default()
io_open_default() && io_close_default() 源码:
- 所属库:libavformat
- 头文件:无,静态函数
- 源文件:libavformat/options.c
- io_open_default 主要作用是首先打印一条日志,告知现在要打开某个URL了,用于读还是用于写;然后会根据URL来创建文件读写的AVIOContext对象,如果旧接口还未被淘汰并且用户设置了打开IO的回调函数open_cb,那么该工作由用户设置的s->open_sb函数来完成; 如果旧接口已经被淘汰,那么该工作由ffio_open_whitelist()来完成。
- io_close_default主要作用是调用avio_close()方法来正确的关闭AVIOContext这个IO上下文对象。
- 整个过程看似就这么简单,如果深纠下去了解open_cb() && ffio_open_whitelist()的具体实现,将是长篇论述,详见 FFRMPEG4.1源码分析之 io_open_default() && io_close_default()
static int io_open_default(AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options) { int loglevel; if (!strcmp(url, s->url) || s->iformat && !strcmp(s->iformat->name, "image2") || s->oformat && !strcmp(s->oformat->name, "image2") ) { loglevel = AV_LOG_DEBUG; } else loglevel = AV_LOG_INFO; av_log(s, loglevel, "Opening \'%s\' for %s\n", url, flags & AVIO_FLAG_WRITE ? "writing" : "reading"); #if FF_API_OLD_OPEN_CALLBACKS FF_DISABLE_DEPRECATION_WARNINGS if (s->open_cb) return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options); FF_ENABLE_DEPRECATION_WARNINGS #endif return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist); } static void io_close_default(AVFormatContext *s, AVIOContext *pb) { avio_close(pb); }
1.3.3 av_opt_set_defaults()
av_opt_set_defaults() 声明:
- 所属库:libavutil(lavu),由于该函数属于lavu这个库,可以想见是个ffmpeg通用的功能
- 头文件:libavutil/opt.h
- 作用:“Set the values of all AVOption fields to their default values.”,注意这个入参的说明,入参对象的第一个成员必须是AVClass对象的指针。
/** * Set the values of all AVOption fields to their default values. * * @param s an AVOption-enabled struct (its first member must be a pointer to AVClass) */ void av_opt_set_defaults(void *s); /** * Set the values of all AVOption fields to their default values. Only these * AVOption fields for which (opt->flags & mask) == flags will have their * default applied to s. * * @param s an AVOption-enabled struct (its first member must be a pointer to AVClass) * @param mask combination of AV_OPT_FLAG_* * @param flags combination of AV_OPT_FLAG_* */ void av_opt_set_defaults2(void *s, int mask, int flags);
av_opt_set_defaults() 源码:
- 源文件:libavutil/opt.c
- 作用:一言以蔽之,就是不断取出对应的AVOption对象,将AVOption对象中存储的初始值赋值给s对象(此处为AVFormatContext)的对应的成员
- av_opt_set_defaults() 直接调用了 av_opt_set_defaults2(),而该函数逻辑也很简单:见源码注释以及后续文字解释。
void av_opt_set_defaults(void *s) { av_opt_set_defaults2(s, 0, 0); } void av_opt_set_defaults2(void *s, int mask, int flags) { const AVOption *opt = NULL; while ((opt = av_opt_next(s, opt))) { // 获取AVOption对象 // s->av_class->option与s的成员是对应关系; // AVOption的offset属性定义了s对象对应的成员相对于s对象首地址的偏移offset, // 从而求得该AVOption所映射的s对象成员的地址dst void *dst = ((uint8_t*)s) + opt->offset; // mask和flags用以控制需要修改设置哪些参数 if ((opt->flags & mask) != flags) continue; // 如果属性是只读,那么不设置 if (opt->flags & AV_OPT_FLAG_READONLY) continue; // 根据AVOption对象的type属性可以映射的s对象的成员的类型 // 根据不同类型,调用不同的函数将AVOption的default_val属性提供的默认值 // 来设置s的对象的对应成员。 switch (opt->type) { case AV_OPT_TYPE_CONST: /* Nothing to be done here */ break; case AV_OPT_TYPE_BOOL: case AV_OPT_TYPE_FLAGS: case AV_OPT_TYPE_INT: case AV_OPT_TYPE_INT64: case AV_OPT_TYPE_UINT64: case AV_OPT_TYPE_DURATION: case AV_OPT_TYPE_CHANNEL_LAYOUT: case AV_OPT_TYPE_PIXEL_FMT: case AV_OPT_TYPE_SAMPLE_FMT: write_number(s, opt, dst, 1, 1, opt->default_val.i64); break; case AV_OPT_TYPE_DOUBLE: case AV_OPT_TYPE_FLOAT: { double val; val = opt->default_val.dbl; write_number(s, opt, dst, val, 1, 1); } break; case AV_OPT_TYPE_RATIONAL: { AVRational val; val = av_d2q(opt->default_val.dbl, INT_MAX); write_number(s, opt, dst, 1, val.den, val.num); } break; case AV_OPT_TYPE_COLOR: set_string_color(s, opt, opt->default_val.str, dst); break; case AV_OPT_TYPE_STRING: set_string(s, opt, opt->default_val.str, dst); break; case AV_OPT_TYPE_IMAGE_SIZE: set_string_image_size(s, opt, opt->default_val.str, dst); break; case AV_OPT_TYPE_VIDEO_RATE: set_string_video_rate(s, opt, opt->default_val.str, dst); break; case AV_OPT_TYPE_BINARY: set_string_binary(s, opt, opt->default_val.str, dst); break; case AV_OPT_TYPE_DICT: /* Cannot set defaults for these types */ break; default: av_log(s, AV_LOG_DEBUG, "AVOption type %d of option %s not implemented yet\n", opt->type, opt->name); } } }
- 在3.2.1中我们已经分析了AVFormatContext->AVClass->option被初始化指向了一个静态的AVOption数组,av_opt_next() 作用就是获取下AVFormatContext->AVClass->option数组中,由last指定的AVOption对象的下一个。具体av_opt_next() 如何实现这一点的,见下面源码以及注释:
const AVOption *av_opt_next(const void *obj, const AVOption *last) { const AVClass *class; // 若obj对象为空,自然option也肯定不存在,返回空 if (!obj) return NULL; // 这个是重点: // obj是一个对象的指针,其指向的对象实体的第一个参数是一个指向AVClass对象的指针 // 如下的强制转换就是取出obj第一个参数值,也即指向obj对象关联的AVClass对象的地址 // 赋值给class class = *(const AVClass**)obj; // 如果last为空,也即没有指定AVOption对象 // 并且class不为空,也即obj这个对象第一个参数确实是AVClass对象指针 // 并且AVClass->option不为空,也即指向有效的AVOption数组 // 并且class->option[0].name不为空,也即AVOption数组的第一项的name成员不为空 if (!last && class && class->option && class->option[0].name) return class->option; // 返回AVOption数组的第一个成员 // 如果last不为空,即已被指定 // 并且last[1].name不为空,即注意理解这个last[1]的数组操作:last指向的AVOption的下一个AVOption的name成员不为空。 if (last && last[1].name) return ++last; // 返回AVOtion数组的last的下一个AVOption,注意一般AVOption数组的最后一项是NULL。 // 若之前没找到合适的AVOption,那么返回NULL return NULL; }
- 取得AVOption选项后,由于AVOption对象与s对象的成员处于映射关系,一个AVOption对象映射到s的一个成员:AVOption对象的offset属性存储着对应成员相对于s首地址的偏移offset,因此可以根据s的地址以及offset得到对应的成员;AVOption对象的default_val属性存储着对应成员的默认值;AVOption对象的type属性存储着对应成员的类型;由上述三个属性就可以可以s对象的相应成员赋默认值,当然对于不同类型的成员,还需要调用不同的方法,见源码,这儿不展开论述。
- 上述过程涉到ffmpeg中很重要的两个结构体AVClass && AVOption,以及相关的set_tring_*和write_number函数。会在另外一篇文章(FFMPEG4.1源码解析之 AVClass结构体 && AVOption结构体)中做专门的阐述。
2 avformat_alloc_context()总结
最后,经过上述还算细致的源码分析,总结下avformat_alloc_context()的作用以及大致的流程
作用:创建封装/解封装上下问对象AVFormatContext对象,并初始化尽量多的成员
流程:此处以文章开头的一张思维导图来展示