ffmpeg源码简析(九)av_log(),AVClass,AVOption

1.av_log()

av_log()是FFmpeg中输出日志的函数。随便打开一个FFmpeg的源代码文件,就会发现其中遍布着av_log()函数。一般情况下FFmpeg类库的源代码中是不允许使用printf()这种的函数的,所有的输出一律使用av_log()。 
av_log()的声明位于libavutil\log.h,如下所示。

void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
  • 1

这个函数的声明有两个地方比较特殊: 
(1)函数最后一个参数是“…”。 
在C语言中,在函数参数数量不确定的情况下使用“…”来代表参数。

v_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 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

从定义中可以看出来,随着严重程度逐渐下降,一共包含如下级别: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。

可以通过av_log_get_level()获得当前Log的级别,通过另一个函数av_log_set_level()设置当前的Log级别。

int av_log_get_level(void)  
{  
    return av_log_level;  
} 

void av_log_set_level(int level)  
{  
    av_log_level = level;  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2.AVClass

AVOption用于在FFmpeg中描述结构体中的成员变量。它最主要的作用可以概括为两个字:“赋值”。一个AVOption结构体包含了变量名称,简短的帮助,取值等等信息。

所有和AVOption有关的数据都存储在AVClass结构体中。如果一个结构体(例如AVFormatContext或者AVCodecContext)想要支持AVOption的话,它的第一个成员变量必须是一个指向AVClass结构体的指针。该AVClass中的成员变量option必须指向一个AVOption类型的静态数组。

何为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更加适应多种多样的外部系统。

现在回到AVOption。其实除了可以对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。

AVOption有关的API 
AVOption常用的API可以分成两类:用于设置参数的API和用于读取参数的API。其中最有代表性的用于设置参数的API就是av_opt_set();而最有代表性的用于读取参数的API就是av_opt_get()。除了记录以上两个函数之外,本文再记录一个在FFmpeg的结构体初始化代码中最常用的用于设置默认值的函数av_opt_set_defaults()。

av_opt_set() 
通过AVOption设置参数最常用的函数就是av_opt_set()了。该函数通过字符串的方式(传入的参数是变量名称的字符串和变量值的字符串)设置一个AVOption的值。此外,还包含了它的一系列“兄弟”函数av_opt_set_XXX(),其中“XXX”代表了int,double这些数据类型。使用这些函数的时候,可以指定int,double这些类型的变量(而不是字符串)作为输入,设定相应的AVOption的值。

int av_opt_set         (void *obj, const char *name, const char *val, int search_flags);  
int av_opt_set_int     (void *obj, const char *name, int64_t     val, int search_flags);  
int av_opt_set_double  (void *obj, const char *name, double      val, int search_flags);  
int av_opt_set_q       (void *obj, const char *name, AVRational  val, int search_flags);  
int av_opt_set_bin     (void *obj, const char *name, const uint8_t *val, int size, int search_flags);  
int av_opt_set_image_size(void *obj, const char *name, int w, int h, int search_flags);  
int av_opt_set_pixel_fmt (void *obj, const char *name, enum AVPixelFormat fmt, int search_flags);  
int av_opt_set_sample_fmt(void *obj, const char *name, enum AVSampleFormat fmt, int search_flags);  
int av_opt_set_video_rate(void *obj, const char *name, AVRational val, int search_flags);  
int av_opt_set_channel_layout(void *obj, const char *name, int64_t ch_layout, int search_flags);  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

有关av_opt_set_XXX()函数的定义不再详细分析,在这里详细看一下av_opt_set()的源代码。av_opt_set()的定义位于libavutil\opt.c,如下所示。

int av_opt_set(void *obj, const char *name, const char *val, int search_flags)  
{  
    int ret = 0;  
    void *dst, *target_obj;  
    //查找  
    const AVOption *o = av_opt_find2(obj, name, NULL, 0, search_flags, &target_obj);  
    if (!o || !target_obj)  
        return AVERROR_OPTION_NOT_FOUND;  
    if (!val && (o->type != AV_OPT_TYPE_STRING &&  
                 o->type != AV_OPT_TYPE_PIXEL_FMT && o->type != AV_OPT_TYPE_SAMPLE_FMT &&  
                 o->type != AV_OPT_TYPE_IMAGE_SIZE && o->type != AV_OPT_TYPE_VIDEO_RATE &&  
                 o->type != AV_OPT_TYPE_DURATION && o->type != AV_OPT_TYPE_COLOR &&  
                 o->type != AV_OPT_TYPE_CHANNEL_LAYOUT))  
        return AVERROR(EINVAL);  

    if (o->flags & AV_OPT_FLAG_READONLY)  
        return AVERROR(EINVAL);  
    //dst指向具体的变量  
    //注意:offset的作用  
    dst = ((uint8_t*)target_obj) + o->offset;  
    //根据AVOption不同的类型,调用不同的设置函数  
    switch (o->type) {  
    case AV_OPT_TYPE_STRING:   return set_string(obj, o, val, dst);  
    case AV_OPT_TYPE_BINARY:   return set_string_binary(obj, o, val, dst);  
    case AV_OPT_TYPE_FLAGS:  
    case AV_OPT_TYPE_INT:  
    case AV_OPT_TYPE_INT64:  
    case AV_OPT_TYPE_FLOAT:  
    case AV_OPT_TYPE_DOUBLE:  
    case AV_OPT_TYPE_RATIONAL: return set_string_number(obj, target_obj, o, val, dst);  
    case AV_OPT_TYPE_IMAGE_SIZE: return set_string_image_size(obj, o, val, dst);  
    case AV_OPT_TYPE_VIDEO_RATE: return set_string_video_rate(obj, o, val, dst);  
    case AV_OPT_TYPE_PIXEL_FMT:  return set_string_pixel_fmt(obj, o, val, dst);  
    case AV_OPT_TYPE_SAMPLE_FMT: return set_string_sample_fmt(obj, o, val, dst);  
    case AV_OPT_TYPE_DURATION:  
        if (!val) {  
            *(int64_t *)dst = 0;  
            return 0;  
        } else {  
            if ((ret = av_parse_time(dst, val, 1)) < 0)  
                av_log(obj, AV_LOG_ERROR, "Unable to parse option value \"%s\" as duration\n", val);  
            return ret;  
        }  
        break;  
    case AV_OPT_TYPE_COLOR:      return set_string_color(obj, o, val, dst);  
    case AV_OPT_TYPE_CHANNEL_LAYOUT:  
        if (!val || !strcmp(val, "none")) {  
            *(int64_t *)dst = 0;  
        } else {  
#if FF_API_GET_CHANNEL_LAYOUT_COMPAT  
            int64_t cl = ff_get_channel_layout(val, 0);  
#else  
            int64_t cl = av_get_channel_layout(val);  
#endif  
            if (!cl) {  
                av_log(obj, AV_LOG_ERROR, "Unable to parse option value \"%s\" as channel layout\n", val);  
                ret = AVERROR(EINVAL);  
            }  
            *(int64_t *)dst = cl;  
            return ret;  
        }  
        break;  
    }  

    av_log(obj, AV_LOG_ERROR, "Invalid option type.\n");  
    return AVERROR(EINVAL);  
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

从源代码可以看出,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。它的声明位于libavutil\opt.h中

av_opt_get() 
av_opt_get()用于获取一个AVOption变量的值。需要注意的是,不论是何种类型的变量,通过av_opt_get()取出来的值都是字符串类型的。此外,还包含了它的一系列“兄弟”函数av_opt_get_XXX()(其中“XXX”代表了int,double这些数据类型)。通过这些“兄弟”函数可以直接取出int,double类型的数值。av_opt_get()的声明如下所示。

int av_opt_get         (void *obj, const char *name, int search_flags, uint8_t   **out_val);  
int av_opt_get_int     (void *obj, const char *name, int search_flags, int64_t    *out_val);  
int av_opt_get_double  (void *obj, const char *name, int search_flags, double     *out_val);  
int av_opt_get_q       (void *obj, const char *name, int search_flags, AVRational *out_val);  
int av_opt_get_image_size(void *obj, const char *name, int search_flags, int *w_out, int *h_out);  
int av_opt_get_pixel_fmt (void *obj, const char *name, int search_flags, enum AVPixelFormat *out_fmt);  
int av_opt_get_sample_fmt(void *obj, const char *name, int search_flags, enum AVSampleFormat *out_fmt);  
int av_opt_get_video_rate(void *obj, const char *name, int search_flags, AVRational *out_val);  
int av_opt_get_channel_layout(void *obj, const char *name, int search_flags, int64_t *ch_layout); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

av_opt_get()的定义,如下所示。

    int av_opt_get(void *obj, const char *name, int search_flags, uint8_t **out_val)  
    {  
        void *dst, *target_obj;  
        //查找  
        const AVOption *o = av_opt_find2(obj, name, NULL, 0, search_flags, &target_obj);  
        uint8_t *bin, buf[128];  
        int len, i, ret;  
        int64_t i64;  

        if (!o || !target_obj || (o->offset<=0 && o->type != AV_OPT_TYPE_CONST))  
            return AVERROR_OPTION_NOT_FOUND;  
        //注意:offset的使用  
        dst = (uint8_t*)target_obj + o->offset;  
        //使用sprintf()转换成字符串,存入buf  
        buf[0] = 0;  
        switch (o->type) {  
        case AV_OPT_TYPE_FLAGS:     ret = snprintf(buf, sizeof(buf), "0x%08X",  *(int    *)dst);break;  
        case AV_OPT_TYPE_INT:       ret = snprintf(buf, sizeof(buf), "%d" ,     *(int    *)dst);break;  
        case AV_OPT_TYPE_INT64:     ret = snprintf(buf, sizeof(buf), "%"PRId64, *(int64_t*)dst);break;  
        case AV_OPT_TYPE_FLOAT:     ret = snprintf(buf, sizeof(buf), "%f" ,     *(float  *)dst);break;  
        case AV_OPT_TYPE_DOUBLE:    ret = snprintf(buf, sizeof(buf), "%f" ,     *(double *)dst);break;  
        case AV_OPT_TYPE_VIDEO_RATE:  
        case AV_OPT_TYPE_RATIONAL:  ret = snprintf(buf, sizeof(buf), "%d/%d",   ((AVRational*)dst)->num, ((AVRational*)dst)->den);break;  
        case AV_OPT_TYPE_CONST:     ret = snprintf(buf, sizeof(buf), "%f" ,     o->default_val.dbl);break;  
        case AV_OPT_TYPE_STRING:  
            if (*(uint8_t**)dst)  
                *out_val = av_strdup(*(uint8_t**)dst);  
            else  
                *out_val = av_strdup("");  
            return 0;  
        case AV_OPT_TYPE_BINARY:  
            len = *(int*)(((uint8_t *)dst) + sizeof(uint8_t *));  
            if ((uint64_t)len*2 + 1 > INT_MAX)  
                return AVERROR(EINVAL);  
            if (!(*out_val = av_malloc(len*2 + 1)))  
                return AVERROR(ENOMEM);  
            if (!len) {  
                *out_val[0] = '\0';  
                return 0;  
            }  
            bin = *(uint8_t**)dst;  
            for (i = 0; i < len; i++)  
                snprintf(*out_val + i*2, 3, "%02X", bin[i]);  
            return 0;  
        case AV_OPT_TYPE_IMAGE_SIZE:  
            //分辨率  
            ret = snprintf(buf, sizeof(buf), "%dx%d", ((int *)dst)[0], ((int *)dst)[1]);  
            break;  
        case AV_OPT_TYPE_PIXEL_FMT:  
            //像素格式  
            ret = snprintf(buf, sizeof(buf), "%s", (char *)av_x_if_null(av_get_pix_fmt_name(*(enum AVPixelFormat *)dst), "none"));  
            break;  
        case AV_OPT_TYPE_SAMPLE_FMT:  
            //采样格式  
            ret = snprintf(buf, sizeof(buf), "%s", (char *)av_x_if_null(av_get_sample_fmt_name(*(enum AVSampleFormat *)dst), "none"));  
            break;  
        case AV_OPT_TYPE_DURATION:  
            //时长  
            i64 = *(int64_t *)dst;  
            ret = snprintf(buf, sizeof(buf), "%"PRIi64":%02d:%02d.%06d",  
                           i64 / 3600000000, (int)((i64 / 60000000) % 60),  
                           (int)((i64 / 1000000) % 60), (int)(i64 % 1000000));  
            break;  
        case AV_OPT_TYPE_COLOR:  
            ret = snprintf(buf, sizeof(buf), "0x%02x%02x%02x%02x",  
                           (int)((uint8_t *)dst)[0], (int)((uint8_t *)dst)[1],  
                           (int)((uint8_t *)dst)[2], (int)((uint8_t *)dst)[3]);  
            break;  
        case AV_OPT_TYPE_CHANNEL_LAYOUT:  
            i64 = *(int64_t *)dst;  
            ret = snprintf(buf, sizeof(buf), "0x%"PRIx64, i64);  
            break;  
        default:  
            return AVERROR(EINVAL);  
        }  

        if (ret >= sizeof(buf))  
            return AVERROR(EINVAL);  
        //拷贝  
        *out_val = av_strdup(buf);  
        return 0;  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
从av_opt_get()的定义可以看出,该函数首先通过av_opt_find2()查相应的AVOption,然后取出该变量的值,最后通过snprintf()将变量的值转化为字符串(各种各样类型的变量都这样处理)并且输出出来。
至此FFmpeg中和AVOption相关的源代码基本上就分析完毕了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值