zoukankan      html  css  js  c++  java
  • 3.FFmpeg日志,AVClass、AVOption

    FFmpeg源代码解析之日志、结构体目录:

     8.日志输出系统(av_log()等)

    9.结构体成员管理系统AVClass

    10.结构体成员管理系统AVOption

    FFmpeg源代码的其他部分

     8.日志输出系统(av_log()等)

       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

    9.结构体成员管理系统AVClass

       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就基本上分析完毕了。

      

    10.结构体成员管理系统AVOption

      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相关的源代码基本上就分析完毕了。
      

     

  • 相关阅读:
    哈希表
    矩阵加速(数列)
    线段树
    python
    vue 中防抖
    Windows版本与Internet Explorer版本对照
    一个怂女婿的成长笔记【二十三】
    一个怂女婿的成长笔记【二十一】
    vue xml数据格式化展示,展示在textarea里可编辑,和高亮处理方法
    substring 截取 第三个字符(/)后的字符串
  • 原文地址:https://www.cnblogs.com/wddx5/p/13304417.html
Copyright © 2011-2022 走看看