首页 > 其他 > 详细

3.FFmpeg日志,结构体管理

时间:2020-07-15 14:17:01      阅读:41      评论:0      收藏:0      [点我收藏+]

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()函数的源代码。它的源代码位于libavutil\log.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\n",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函数。它的声明位于libavutil\log.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()的定义位于libavutil\log.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(),位于libavutil\bprint.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结构体(定义位于libavformat\options.c)。而AVClass类型的av_format_context_class结构体中的option变量指向了全局静态数组avformat_options(定义位于libavformat\options_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

  

 

3.FFmpeg日志,结构体管理

原文:https://www.cnblogs.com/wddx5/p/13304417.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!