FFmpeg源代码解析之日志、结构体目录:
FFmpeg源代码的其他部分
av_log()是FFmpeg中输出日志的函数。随便打开一个FFmpeg的源代码文件,就会发现其中遍布着av_log()函数。一般情况下FFmpeg类库的源代码中是不允许使用printf()这种的函数的,所有的输出一律使用av_log()。
函数的声明为void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
函数最后一个参数是“…”。
在C语言中,在函数参数数量不确定的情况下使用“…”来代表参数。例如printf()的原型定义如: int printf (const char*, ...);
它的声明后面有一个av_printf_format(3, 4)。有关这个地方的左右还没有深入研究,网上资料中说它的作用是按照printf()的格式检查av_log()的格式。
av_log()每个字段的含义如下:
avcl:指定一个包含AVClass的结构体。
level:log的级别
fmt:和printf()一样。
由此可见,av_log()和printf()的不同主要在于前面多了两个参数。其中第一个参数指定该log所属的结构体,例如AVFormatContext、AVCodecContext等等。第二个参数指定log的级别,
/** * Print no output. */ #define AV_LOG_QUIET -8 /** * Something went really wrong and we will crash now. */ #define AV_LOG_PANIC 0 /** * Something went wrong and recovery is not possible. * For example, no header was found for a format which depends * on headers or an illegal combination of parameters is used. */ #define AV_LOG_FATAL 8 /** * Something went wrong and cannot losslessly be recovered. * However, not all future data is affected. */ #define AV_LOG_ERROR 16 /** * Something somehow does not look correct. This may or may not * lead to problems. An example would be the use of '-vstrict -2'. */ #define AV_LOG_WARNING 24 /** * Standard information. */ #define AV_LOG_INFO 32 /** * Detailed information. */ #define AV_LOG_VERBOSE 40 /** * Stuff which is only useful for libav* developers. */ #define AV_LOG_DEBUG 48
从定义中可以看出来,随着严重程度逐渐下降,一共包含如下级别:AV_LOG_PANIC,AV_LOG_FATAL,AV_LOG_ERROR,AV_LOG_WARNING,AV_LOG_INFO,AV_LOG_VERBOSE,AV_LOG_DEBUG。每个级别定义的数值代表了严重程度,数值越小代表越严重。默认的级别是AV_LOG_INFO。此外,还有一个级别不输出任何信息,即AV_LOG_QUIET。
当前系统存在着一个“Log级别”。所有严重程度高于该级别的Log信息都会输出出来。例如当前的Log级别是AV_LOG_WARNING,则会输出AV_LOG_PANIC,AV_LOG_FATAL,AV_LOG_ERROR,AV_LOG_WARNING级别的信息,而不会输出AV_LOG_INFO级别的信息。可以通过av_log_get_level()获得当前Log的级别,通过另一个函数av_log_set_level()设置当前的Log级别。
从代码中可以看出,以上两个函数主要操作了一个静态全局变量av_log_level。该变量用于存储当前系统Log的级别。它的定义如下所示。
static int av_log_level = AV_LOG_INFO;
下面回到av_log()函数的源代码。它的源代码位于libavutillog.c,如下所示。
void av_log(void* avcl, int level, const char *fmt, ...)
{
AVClass* avc = avcl ? *(AVClass **) avcl : NULL;
va_list vl;
va_start(vl, fmt);
if (avc && avc->version >= (50 << 16 | 15 << 8 | 2) &&
avc->log_level_offset_offset && level >= AV_LOG_FATAL)
level += *(int *) (((uint8_t *) avcl) + avc->log_level_offset_offset);
av_vlog(avcl, level, fmt, vl);
va_end(vl);
}
首先来提一下C语言函数中“…”参数的含义。与它相关还涉及到以下4个部分:
(1)va_list变量
(2)va_start()
(3)va_arg()
(4)va_end()
va_list是一个指向函数的参数的指针。va_start()用于初始化va_list变量。va_arg()用于返回可变参数。va_start()用于结束可变参数的获取。有关它们的用法可以参考一个小demo,如下所示。
#include <stdio.h>
#include<stdarg.h>
void fun(int a,...){
va_list pp;
va_start(pp,a);
do{
printf("param =%d
",a);
a=va_arg(pp,int);//使 pp 指向下一个参数,将下一个参数的值赋给变量 a
}
while (a!=0);//直到参数为 0 时停止循环
}
void main(){
fun(20,40,60,80,0);
}
有关这方面的知识很难用简短的语言描述清楚,因此不再详述。av_log()的源代码中,在va_start()和va_end()之间,调用了另一个函数av_vlog()。
av_vlog()是一个FFmpeg的API函数。它的声明位于libavutillog.h中,
void av_vlog(void *avcl, int level, const char *fmt, va_list vl);
从声明中可以看出,av_vlog()和av_log()的参数基本上是一模一样的。唯一的不同在于av_log()中的“…”变成了av_vlog()中的va_list。
av_vlog()的定义位于libavutillog.c中,从定义中可以看出,av_vlog()简单调用了一个函数指针av_log_callback。av_log_callback是一个全局静态变量,
从代码中可以看出,av_log_callback指针默认指向一个函数av_log_default_callback()。av_log_default_callback()即FFmpeg默认的Log函数。需要注意的是,这个Log函数是可以自定义的。按照指定的参数定义一个自定义的函数后,可以通过FFmpeg的另一个API函数av_log_set_callback()设定为Log函数。
av_log_set_callback()的声明为:void av_log_set_callback(void (*callback)(void*, int, const char*, va_list));
从声明中可以看出,需要指定一个参数为(void*, int, const char*, va_list),返回值为void的函数作为Log函数。
av_log_set_callback()的定义很简单,做了一个函数指针赋值的工作,
从声明中可以看出,需要指定一个参数为(void*, int, const char*, va_list),返回值为void的函数作为Log函数。
av_log_set_callback()的定义很简单,做了一个函数指针赋值的工作,如下所示。
void av_log_set_callback(void (*callback)(void*, int, const char*, va_list))
{
av_log_callback = callback;
}
例如,我们可以指定一个my_logoutput()函数作为Log的输出函数,就可以将Log信息输出到文本中(而不是屏幕上)。
void my_logoutput(void* ptr, int level, const char* fmt,va_list vl){
FILE *fp = fopen("my_log.txt","a+");
if(fp){
vfprintf(fp,fmt,vl);
fflush(fp);
fclose(fp);
}
}
av_log_set_callback(my_logoutput);
编辑好函数之后,使用av_log_set_callback()函数设置该函数为Log输出函数即可。
FFmpeg的默认Log输出函数av_log_default_callback(),
av_log_default_callback()的代码是比较复杂的。其实如果我们仅仅是希望把Log信息输出到屏幕上,远不需要那么多代码,只需要简单打印一下就可以了。av_log_default_callback()之所以会那么复杂,主要是因为他还包含了很多的功能,比如说根据Log级别的不同将输出的文本设置成不同的颜色等等。
下面看一下av_log_default_callback()的源代码大致的流程:
(1)如果输入参数level大于系统当前的日志级别av_log_level,表明不需要做任何处理,直接返回。
(2)调用format_line()设定Log的输出格式。
(3)调用colored_fputs()设定Log的颜色。
其他日志内容包括有:format_line()用于设定Log的输出格式,它本身并不是一个FFmpeg的API,但是FFmpeg有一个API函数av_log_format_line()调用了这个函数。
结构体AVBPrint,字符串的函数av_bprintf(),位于libavutilprint.c;
colored_fputs()函数用于将输出的文本“上色”并且输出。在这里有一点需要注意:Windows和Linux下控制台程序上色的方法是不一样的。Windows下是通过SetConsoleTextAttribute()方法给控制台中的文本上色;Linux下则是通过添加一些ANSI控制码完成上色。
从colored_fputs()的源代码中可以看出如下流程:
首先判定根据宏定义系统的类型,如果系统类型是Windows,那么就调用SetConsoleTextAttribute()方法设定控制台文本的颜色,然后调用fputs()将Log记录输出到stderr(注意不是stdout);如果系统类型是Linux,则通过添加特定字符串的方式设定控制台文本的颜色,然后将Log记录输出到stderr。
至此FFmpeg的日志输出系统的源代码就分析完毕了。
其他日志输出细节见:https://blog.csdn.net/leixiaohua1020/article/details/44243155
AVOption用于在FFmpeg中描述结构体中的成员变量。它最主要的作用可以概括为两个字:“赋值”。一个AVOption结构体包含了变量名称,简短的帮助,取值等等信息。
所有和AVOption有关的数据都存储在AVClass结构体中。如果一个结构体(例如AVFormatContext或者AVCodecContext)想要支持AVOption的话,它的第一个成员变量必须是一个指向AVClass结构体的指针。该AVClass中的成员变量option必须指向一个AVOption类型的静态数组。
AVOption是用来设置FFmpeg中变量的值的结构体。可能说到这个作用有的人会奇怪:设置系统中变量的值,直接使用等于号“=”就可以,为什么还要专门定义一个结构体呢?其实AVOption的特点就在于它赋值时候的灵活性。AVOption可以使用字符串为任何类型的变量赋值。传统意义上,如果变量类型为int,则需要使用整数来赋值;如果变量为double,则需要使用小数来赋值;如果变量类型为char *,才需要使用字符串来赋值。而AVOption将这些赋值“归一化”了,统一使用字符串赋值。例如给int型变量qp设定值为20,通过AVOption需要传递进去一个内容为“20”的字符串。
此外,AVOption中变量的名称也使用字符串来表示。结合上面提到的使用字符串赋值的特性,我们可以发现使用AVOption之后,传递两个字符串(一个是变量的名称,一个是变量的值)就可以改变系统中变量的值。
上文提到的这种方法的意义在哪里?我个人感觉对于直接使用C语言进行开发的人来说,作用不是很明显:完全可以使用等于号“=”就可以进行各种变量的赋值。但是对于从外部系统中调用FFmpeg的人来说,作用就很大了:从外部系统中只可以传递字符串给内部系统。比如说对于直接调用ffmpeg.exe的人来说,他们是无法修改FFmpeg内部各个变量的数值的,这种情况下只能通过输入“名称”和“值”这样的字符串,通过AVOption改变FFmpeg内部变量的值。由此可见,使用AVOption可以使FFmpeg更加适应多种多样的外部系统。
例如JavaEE开发JSP中的Servlet的时候经常需要将整数字符串手工转化成一个整型的变量。下面代码可以将字符串“123”转化成整数123。
int a=Integer.parseInt("123");
除了可以对FFmpeg常用结构体AVFormatContext,AVCodecContext等进行赋值之外,还可以对它们的私有数据priv_data进行赋值。这个字段里通常存储了各种编码器特有的结构体。而这些结构体的定义在FFmpeg的SDK中是找不到的。例如使用libx264进行编码的时候,通过AVCodecContext的priv_data字段可以对X264Context结构体中的变量进行赋值,设置preset,profile等。使用libx265进行编码的时候,通过AVCodecContext的priv_data字段可以对libx265Context结构体中的变量进行赋值,设置preset,tune等。
何为AVClass?
AVClass最主要的作用就是给结构体(例如AVFormatContext等)增加AVOption功能的支持。换句话说AVClass就是AVOption和目标结构体之间的“桥梁”。AVClass要求必须声明为目标结构体的第一个变量。
AVClass中有一个option数组用于存储目标结构体的所有的AVOption。举个例子,AVFormatContext结构体,AVClass和AVOption之间的关系如下图所示。
何为AVClass?
AVClass最主要的作用就是给结构体(例如AVFormatContext等)增加AVOption功能的支持。换句话说AVClass就是AVOption和目标结构体之间的“桥梁”。AVClass要求必须声明为目标结构体的第一个变量。
AVClass中有一个option数组用于存储目标结构体的所有的AVOption。举个例子,AVFormatContext结构体,AVClass和AVOption之间的关系如下图所示。
图中AVFormatContext结构体的第一个变量为AVClass类型的指针av_class,它在AVFormatContext结构体初始化的时候,被赋值指向了全局静态变量av_format_context_class结构体(定义位于libavformatoptions.c)。而AVClass类型的av_format_context_class结构体中的option变量指向了全局静态数组avformat_options(定义位于libavformatoptions_table.h)。
下面简单解释一下AVOption的几个成员变量:
name:名称。
help:简短的帮助。
offset:选项相对结构体首部地址的偏移量(这个很重要)。
type:选项的类型。
default_val:选项的默认值。
min:选项的最小值。
max:选项的最大值。
flags:一些标记。
unit:该选项所属的逻辑单元,可以为空。
其中,default_val是一个union类型的变量,可以根据选项数据类型的不同,取int,double,char*,AVRational(表示分数)几种类型。type是一个AVOptionType类型的变量。AVOptionType是一个枚举类型.
AVClass中存储了AVOption类型的数组option,用于存储选项信息。AVClass有一个特点就是它必须位于其支持的结构体的第一个位置。例如,AVFormatContext和AVCodecContext都支持AVClass,观察它们结构体的定义可以发现他们结构体的第一个变量都是AVClass。截取一小段AVFormatContext的定义的开头部分,如下所示。
下面简单解释一下AVClass的几个已经理解的成员变量:
class_name:AVClass名称。
item_name:函数,获取与AVClass相关联的结构体实例的名称。
option:AVOption类型的数组(最重要)。
version:完成该AVClass的时候的LIBAVUTIL_VERSION。
category:AVClass的类型,是一个类型为AVClassCategory的枚举型变量。
使用举例见下图
有关AVClass和AVOption的详细示例,见https://blog.csdn.net/leixiaohua1020/article/details/44268323
各种组件特有的AVClass
除了FFmpeg中通用的AVFormatContext,AVCodecContext,AVFrame这类的结构体之外,每种特定的组件也包含自己的AVClass。下面举例几个
libRTMP中根据协议类型的不同定义了多种的AVClass。由于这些AVClass除了名字不一样之外,其他的字段一模一样,所以AVClass的声明写成了一个名称为RTMP_CLASS的宏。
Libx264的AVClass定义如下所示。
static const AVClass x264_class = {
.class_name = "libx264",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
};
其中option字段指向的数组定义如下所示。这些option的使用频率还是比较高的。
static const AVOption options[] = {
{ "preset", "Set the encoding preset (cf. x264 --fullhelp)", OFFSET(preset), AV_OPT_TYPE_STRING, { .str = "medium" }, 0, 0, VE},
{ "tune", "Tune the encoding params (cf. x264 --fullhelp)", OFFSET(tune), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE},
{ "profile", "Set profile restrictions (cf. x264 --fullhelp) ", OFFSET(profile),
////还有很多不列出了
Libx265
官方代码中有关AVClass和AVOption的使用示例
typedef struct test_struct { AVClass *class; int int_opt; char str_opt; uint8_t bin_opt; int bin_len; } test_struct; static const AVOption test_options[] = { { "test_int", "This is a test option of int type.", offsetof(test_struct, int_opt), AV_OPT_TYPE_INT, { .i64 = -1 }, INT_MIN, INT_MAX }, { "test_str", "This is a test option of string type.", offsetof(test_struct, str_opt), AV_OPT_TYPE_STRING }, { "test_bin", "This is a test option of binary type.", offsetof(test_struct, bin_opt), AV_OPT_TYPE_BINARY }, { NULL }, }; static const AVClass test_class = { .class_name = "test class", .item_name = av_default_item_name, .option = test_options, .version = LIBAVUTIL_VERSION_INT, };
与AVClass相关的API很少。
AVFormatContext提供了一个获取当前AVClass的函数avformat_get_class()。它的代码很简单,直接返回全局静态变量av_format_context_class。
AVCodecContext也提供了一个获取当前AVClass的函数avcodec_get_class()。它直接返回静态变量av_codec_context_class。
FFmpeg的AVClass就基本上分析完毕了。
AVOption常用的API可以分成两类:用于设置参数的API和用于读取参数的API。
其中最有代表性的用于设置参数的API就是av_opt_set();
而最有代表性的用于读取参数的API就是av_opt_get()。除了记录以上两个函数之外,本文再记录一个在FFmpeg的结构体初始化代码中最常用的用于设置默认值的函数av_opt_set_defaults()。
通过AVOption设置参数最常用的函数就是av_opt_set()了。该函数通过字符串的方式(传入的参数是变量名称的字符串和变量值的字符串)设置一个AVOption的值。此外,还包含了它的一系列“兄弟”函数av_opt_set_XXX(),其中“XXX”代表了int,double这些数据类型。使用这些函数的时候,可以指定int,double这些类型的变量(而不是字符串)作为输入,设定相应的AVOption的值。
有关av_opt_set_XXX()函数的定义不再详细分析,在这里详细看一下av_opt_set()的源代码。av_opt_set()的定义位于libavutilopt.c,
从源代码可以看出,av_opt_set()首先调用av_opt_find2()查找AVOption。如果找到了,则根据AVOption的type,调用不同的函数(set_string(),set_string_number(),set_string_image_size()等等)将输入的字符串转化为相应type的数据并对该AVOption进行赋值。如果没有找到,则立即返回“没有找到AVOption”的错误。
av_opt_find2() / av_opt_find()
av_opt_find2()本身也是一个API函数,用于查找AVOption。
此外还有一个和av_opt_find2()“长得很像”的API函数av_opt_find(),功能与av_opt_find2()基本类似,与av_opt_find2()相比少了最后一个参数。从源代码中可以看出它只是简单调用了av_opt_find2()并把所有的输入参数原封不动的传递过去,并把最后一个参数设置成NULL。
下面先看一下av_opt_find2()函数的定义。该函数的定义位于libavutilopt.c中,这段代码的前半部分暂时不关注,前半部分的if()语句中的内容只有在search_flags指定为AV_OPT_SEARCH_CHILDREN的时候才会执行。后半部分代码是重点。后半部分代码是一个while()循环,该循环的条件是一个函数av_opt_next()。
av_opt_next()也是一个FFmpeg的API函数。使用它可以循环遍历目标结构体的所有AVOption,
从av_opt_next()的代码可以看出,输入的AVOption类型的last变量为空的时候,会返回该AVClass的option数组的第一个元素,否则会返回数组的下一个元素。
现在再回到av_opt_find2()函数。我们发现在while()循环中有一个strcmp()函数,正是这个函数比较输入的AVOption的name和AVClass的option数组中每个元素的name,当上述两个name相等的时候,就代表查找到了AVOption,接着就可以返回获得的AVOption。
现在再回到刚才的av_opt_set()函数。该函数有一个void型的变量dst用于确定需要设定的AVOption对应的变量的位置。具体的方法就是将输入的AVClass结构体的首地址加上该AVOption的偏移量offset。确定了AVOption对应的变量的位置之后,就可以根据该AVOption的类型type的不同调用不同的字符串转换函数设置相应的值了。
av_parse_video_size()是一个FFmpeg的API函数,用于解析出输入的分辨率字符串的宽高信息。例如,输入的字符串为“1920x1080”或者“1920*1080”,经过av_parse_video_size()的处理之后,可以得到宽度为1920,高度为1080;此外,输入一个“特定分辨率”字符串例如“vga”(还有很多其他特定字符),也可以得到宽度为640,高度为480。该函数不属于AVOption这部分的内容,而是整个FFmpeg通用的一个字符串解析函数。该函数的声明为:
int av_parse_video_size(int *width_ptr, int *height_ptr, const char *str);
从声明中可以看出,该函数输入一个字符串str,输出结果保存在width_ptr和height_ptr所指向的内存中。av_parse_video_size()定义位于libavutilparseutils.c中,
//解析分辨率 int av_parse_video_size(int *width_ptr, int *height_ptr, const char *str) { int i; int n = FF_ARRAY_ELEMS(video_size_abbrs); const char *p; int width = 0, height = 0; //先看看有没有“分辨率简称”相同的(例如vga,qcif等) for (i = 0; i < n; i++) { if (!strcmp(video_size_abbrs[i].abbr, str)) { width = video_size_abbrs[i].width; height = video_size_abbrs[i].height; break; } } //如果没有使用“分辨率简称”,而是使用具体的数值(例如“1920x1080”),则执行下面的步骤 if (i == n) { //strtol():字符串转换成整型,遇到非数字则停止 width = strtol(str, (void*)&p, 10); if (*p) p++; height = strtol(p, (void*)&p, 10); /* trailing extraneous data detected, like in 123x345foobar */ if (*p) return AVERROR(EINVAL); } //检查一下正确性 if (width <= 0 || height <= 0) return AVERROR(EINVAL); *width_ptr = width; *height_ptr = height; return 0; }
上述代码中包含了FFmpeg中两种解析视频分辨率的方法。FFmpeg中包含两种设定视频分辨率的方法:通过已经定义好的“分辨率简称”,或者通过具体的数值。代码中首先遍历“特定分辨率”的数组video_size_abbrs。
typedef struct { const char *abbr; int width, height; } VideoSizeAbbr; static const VideoSizeAbbr video_size_abbrs[] = { { "ntsc", 720, 480 }, { "pal", 720, 576 }, { "qntsc", 352, 240 }, /* VCD compliant NTSC */ { "qpal", 352, 288 }, /* VCD compliant PAL */ { "sntsc", 640, 480 }, /* square pixel NTSC */ { "spal", 768, 576 }, /* square pixel PAL */ { "film", 352, 240 }, { "ntsc-film", 352, 240 }, { "sqcif", 128, 96 }, { "qcif", 176, 144 }, { "cif", 352, 288 }, { "4cif", 704, 576 }, { "16cif", 1408,1152 }, { "qqvga", 160, 120 }, { "qvga", 320, 240 }, { "vga", 640, 480 }, { "svga", 800, 600 }, { "xga", 1024, 768 }, { "uxga", 1600,1200 }, { "qxga", 2048,1536 }, { "sxga", 1280,1024 }, { "qsxga", 2560,2048 }, { "hsxga", 5120,4096 }, { "wvga", 852, 480 }, { "wxga", 1366, 768 }, { "wsxga", 1600,1024 }, { "wuxga", 1920,1200 }, { "woxga", 2560,1600 }, { "wqsxga", 3200,2048 }, { "wquxga", 3840,2400 }, { "whsxga", 6400,4096 }, { "whuxga", 7680,4800 }, { "cga", 320, 200 }, { "ega", 640, 350 }, { "hd480", 852, 480 }, { "hd720", 1280, 720 }, { "hd1080", 1920,1080 }, { "2k", 2048,1080 }, /* Digital Cinema System Specification */ { "2kflat", 1998,1080 }, { "2kscope", 2048, 858 }, { "4k", 4096,2160 }, /* Digital Cinema System Specification */ { "4kflat", 3996,2160 }, { "4kscope", 4096,1716 }, { "nhd", 640,360 }, { "hqvga", 240,160 }, { "wqvga", 400,240 }, { "fwqvga", 432,240 }, { "hvga", 480,320 }, { "qhd", 960,540 }, };
通过调用strcmp()方法比对输入字符串的值与video_size_abbrs数组中每个VideoSizeAbbr元素的abbr字段的值,判断输入的字符串是否指定了这些标准的分辨率。如果指定了的话,则返回该分辨率的宽和高。
如果从上述列表中没有找到相应的“特定分辨率”,则说明输入的字符串应该是一个具体的分辨率的值,形如“1920*1020”,“1280x720”这样的字符串。这个时候就需要对这个字符串进行解析,并从中提取出数字信息。通过两次调用strtol()方法,从字符串中提取出宽高信息(第一次提取出宽,第二次提取出高)。
PS1:strtol()用于将字符串转换成整型,遇到非数字则停止。
PS2:从这种解析方法可以得到一个信息——FFmpeg并不管“宽{X}高”中间的那个{X}是什么字符,也就是说中间那个字符不一定非得是“*”或者“x”。后来试了一下,中间那个字符使用其他字母也是可以的。
av_opt_set_defaults()是一个FFmpeg的API,作用是给一个结构体的成员变量设定默认值。在FFmpeg初始化其各种结构体(AVFormatContext,AVCodecContext等)的时候,通常会调用该函数设置结构体中的默认值。其声明为:void av_opt_set_defaults(void *s);
可见只需要把包含AVOption功能的结构体(第一个变量是一个AVClass类型的指针)的指针提供给av_opt_set_defaults(),就可以初始化该结构体的默认值了。
下面看一下av_opt_set_defaults()的源代码,位于libavutilopt.c
av_opt_set_defaults()代码开始的时候有一个预编译指令还是挺奇怪的。怪就怪在#if和#endif竟然横跨在了两个函数之间。简单解读一下这个定义的意思:
当定义了FF_API_OLD_AVOPTIONS的时候,存在两个函数av_opt_set_defaults()和av_opt_set_defaults2(),而这两个函数的作用是一样的;
当没有定义FF_API_OLD_AVOPTIONS的时候,只存在一个函数av_opt_set_defaults()。估计FFmpeg这么做主要是考虑到兼容性的问题。
av_opt_set_defaults()主体部分是一个while()循环。该循环的判断条件是一个av_opt_next(),其作用是获得下一个AVOption
从av_opt_next()的代码可以看出,输入的AVOption类型的last为空的时候,会返回该AVClass的option数组的第一个元素,否则会返回下一个元素。
下面简单解读一下av_opt_set_defaults()中while()循环语句里面的内容。有一个void类型的指针dst用于确定当前AVOption代表的变量的位置。该指针的位置有结构体的首地址和变量的偏移量offset确定。然后根据AVOption代表的变量的类型type,调用不同的函数设定相应的值。例如type为AV_OPT_TYPE_INT的话,则会调用write_number();type为AV_OPT_TYPE_STRING的时候,则会调用set_string();type为AV_OPT_TYPE_IMAGE_SIZE的时候,则会调用set_string_image_size()。有关这些设置值的函数在前文中已经有所叙述,不再重复。需要注意的是,该函数中设置的值都是AVOption中的default_val变量的值。
av_opt_get()用于获取一个AVOption变量的值。需要注意的是,不论是何种类型的变量,通过av_opt_get()取出来的值都是字符串类型的。此外,还包含了它的一系列“兄弟”函数av_opt_get_XXX()(其中“XXX”代表了int,double这些数据类型)。通过这些“兄弟”函数可以直接取出int,double类型的数值
从av_opt_get()的定义可以看出,该函数首先通过av_opt_find2()查相应的AVOption,然后取出该变量的值,最后通过snprintf()将变量的值转化为字符串(各种各样类型的变量都这样处理)并且输出出来。
至此FFmpeg中和AVOption相关的源代码基本上就分析完毕了。