FFmpeg API 之 AVOption

options,可以翻译为“选项”,是 FFmpeg 中非常重要的一个概念。在 FFmpeg 中,我们可以对input , output, muxer, demuxer, encoder, decoder, device,protocols 等设置选项,以便于对它们的行为做控制。因此,options可以理解为是物体的属性或者特征。

首先,我们要区分一个概念,AVOption是用来描述一个选项的,它不是拥有选项的那个物体,也不是选项本身。比如,如果在FFmpeg中描述人,那么人可以拥有多个选项,比如年龄这个“选项”,而我们可以用一个AVOption实例描述这个选项,如:

“选项名称:年龄;取值类型:整数;可取范围:0~150;...”

同时,人还有其他的选项,比如身高,性别等等,那么必然有其他的AVOption实例来描述这些属性。所有的这些AVOption实例的集合,就成了对 人 的全部描述,即 人 拥有的全部属性的完整描述,在FFmpeg中,这个集合叫AVClass。

但要注意,AVClass 仅仅是描述而已,而不是具体的选项值。而具体的值在哪里?当然在 人 这个类中。

typedef Person
{
    AVClass *priv;

    int age;
    int height;
    char *name;
}

我们说,Person这个类是 options-enable 的,即它是有选项的。它的选项是什么?就是它的各个字段,如age,name等,当然不是说所有的字段都是选项。那么到底那些字段是选项,那些不是?这是由Person的第一个字段AVClass所描述的,我们知道AVClass中包含多个AVOption的实例,而每个AVOption实例则用于描述一个选项。

以上的说明还包含了这样一个结论:凡是第一个字段是AVClass的,都是 options-enable 的结构体;凡是 options-enable 的结构体,其第一个字段必须是AVClass类型

 

区分了 options-enable 结构体,option变量本身,描述option相关性质的AVOption之后,我们继续用规范的语言描述FFmpeg的option机制。

一、FFmpeg如何让一个结构体成为 options-enable 的?

AVOption 提供了一个为任意结构体或对象申明 option 的通用接口。AVOption用于描述一个选项的性质,它可以有 名称,帮助文本,类型,默认值,一个可取值的范围,选项所在字段在结构体中的偏移量等等。我们对 options 的操作有:枚举,读操作,写操作等。

如果我们要为某个结构体添加 options-enable 支持,我们会在其字段中添加一个 AVClass,而所有描述选项信息的 AVOption 实例都会保存在这一个 AVClass 中。因此,如果我们看到有 AVClass 字段的结构体,我们就知道,它们是 options-enable 的,比如名称中有context的结构体 。

因此 option-enable 的结构体的第一个字段,就是指向描述其 option 的 AVClass 指针。而 AVClass 的 option 字段则是一个以 NULL 结尾的静态 AVOptions 数组。每一个 AVOption 都有一个非空的 name, type,一个默认值,而对于 type 为数字类型的 option,还应该有一个可取值的范围。除此之外,还应该有一个 offset 字段。需要注意的是,AVOption 仅仅是描述一个 option,它不会具体保存这个 option 的变量,而是在结构体中对应的字段中保存这个变量,因此这个 offset 就表示这个 AVOption 关联的实际变量在结构体中位置的偏移量。

总而言之,一个 option-enable 的结构体,它会在 AVClass 字段中保存关于自己所有 option 的描述,但要注意也仅仅是描述而已,真正保存 option 值的还是结构体中的各个字段,而描述中的 offset 字段就表示这些真实保存 option 变量在结构体中的偏移量。

以下是一个 options-enable 结构体的简单示例:

//一个 option-enable 的结构体
//字段 class 中保存了对其 option 的描述,共有三个 option
//字段 int_opt,str_opt,bin_opt是实际保存这三个 option 的值
typedef struct test_struct {
      const AVClass *class;
      int      int_opt;
      char    *str_opt;
      uint8_t *bin_opt;
      int      bin_len;
  } test_struct;
 
//它是 AVClass 中的 option 字段,表示 option 的具体描述,详细列出了每一个选项的详细情况
  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 },
  };
 
//为 AVClass 赋值,然后将 AVClass 赋给 option-enable 结构体
  static const AVClass test_class = {
      .class_name = "test class",
      .item_name  = av_default_item_name,
     .option     = test_options,
      .version    = LIBAVUTIL_VERSION_INT,
  };

 

二、如何操作 option

首先要说明的是,只有对 options-enable 的结构体对象,才能对其使用 option 相关的 API。

对于一个 options-enable 结构体,我们在分配它的一个实例时,一定要注意,将其相关的 AVClass 设置正确。这是所有 option API 正常工作的前提。然后,我们必须调用函数 av_opt_set_defaults() 函数将其所有 option 设置为默认值。这样才算是完成了 option-enable 结构体的初始化。

而当我们使用完 option 后,可能需要对 option 进行清理,通过调用 av_opt_free() 可以自动释放所有的被分配出来的 string 和 binary 选项。

当然,以上描述的的 option 初始化和清理过程一般都在 option-enable 类型的初始化和清理函数中做了,不需要我们手动来调用,但要明白其中的原理,知道发生了什么。

例如针对上文的test_struct这个options-enable类型,其初始化和清理函数如下:

test_struct *alloc_test_struct(void)
{
    test_struct *ret = av_mallocz(sizeof(*ret));
    ret->class = &test_class;
    av_opt_set_defaults(ret);
    return ret;
}
void free_test_struct(test_struct **foo)
{
    av_opt_free(*foo);
    av_freep(foo);
}

 

一个 options-enable 结构体可能会将另一个 options-enable 的结构体作为成员。如 AVCodecContext 本身是一个 options-enable 结构体,它所包含的是编解码器的公共选项,而它的字段 priv_data 字段也是一个 option-enable 结构体,它所包含的则是当前类型的编解码器自己的私有选项。在这种情况下,为了方便,我们可以通过设置父结构体来导出子结构体的选项,为了做到这一点,我们必须要实现父结构体中 AVClass 的 AVClass.child_next() 和 AVClass.child_class_next() 两个函数。

我们将上述的test_struct作为父结构体,创建一个子的 options-enable 子结构体:

typedef struct child_struct {                                                                
    AVClass *class;                                                                          
    int flags_opt;                                                                           
} child_struct; 
                                                                             
static const AVOption child_opts[] = {                                                       
    { "test_flags", "This is a test option of flags type.",                                  
      offsetof(child_struct, flags_opt), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, INT_MIN, INT_MAX },
    { NULL },                                                                                
};   
                                                                                        
static const AVClass child_class = {                                                         
    .class_name = "child class",                                                             
    .item_name  = av_default_item_name,                                                      
    .option     = child_opts,                                                                
    .version    = LIBAVUTIL_VERSION_INT,                                                     
};                                                                                           
                                                                                             
void *child_next(void *obj, void *prev)                                                      
{                                                                                            
    test_struct *t = obj;                                                                    
    if (!prev && t->child_struct)                                                            
        return t->child_struct;                                                              
    return NULL                                                                              
}    
                                                                                        
const AVClass child_class_next(const AVClass *prev)                                          
{                                                                                            
    return prev ? NULL : &child_class;                                                       
}                                                                                            

在实现了父结构体中AVClass的这两个函数之后,我们就可以通过父结构体来获得子结构体的option。

我们可能会奇怪,为什么需要支持上面的两个函数,而不是一个。这是因为这两个函数在功能上还是有所区别的。child_next() 它会遍历所有的实际存在的 option-enable 子对象,而 child_class_next() 则会遍历所有可能的子AVClass 。

这是为什么呢?

我们仔细查看这两个函数,第一个是以options-enable object查找其子 options-enable object。另一个则是以 options-enable object 的AVClass查找其子options-enable object 的 AVClass。以AVCodecContext为例,它表示一个编解码器,当我们分配一个libx264的编解码时,子object仅仅只有libx264一个;而后者返回的是AVClass,它是对各个编解码器的描述,它是固定的,即使我们不分配任何实际的编解码器,它们也是存在的。因此会造成上述的不同。

例如,使用一个 codec 初始化生成的 AVCodecContext,它将会有该 codec 相关的私有选项。那么,此时调用 child_next() 则会返回 AVCodecContext.priv_data 然后结束遍历。而在 AVCodecContext.av_class 上调用 child_class_next() 则会遍历所有拥有私有选项的 codec。

事实上,我们也不会直接使用这两个函数,而是使用更上层的 av_opt_child_class_next() 和 av_opt_child_next() 函数。

 

以上是关于option这个主题的相关知识,下面我们讲述具体的options API。

由于选项是固有的属性,因此不存在增删改,仅有查看option这一个操作,以及设置,查看属性的具体值这三种操作。

 

我们首先看一下如何遍历一个对象的全部选项。

av_opt_next() 函数可以遍历 option-enable 结构体中的定义的所有 option。注意:它仅处理当前结构体,不去处理子 option-enable 字段

av_opt_find() 函数则可以按照给定的 name 去搜索 option。当在函数的参数中指定了 AV_OPT_SEARCH_CHILDREN ,那么也会在子 option-enable 结构体中去查找 option。

对于遍历,有两种情况。第一种是想要获取 option-enable 结构体 和 它所有子 option-enable 结构体上所有可能的,潜在的 option,此时我们应该在父结构体的 AVClass 上递归的调用 av_opt_child_class_next() 。第二种情况是,你已经有了一个被初始化后的结构体,想要获取所有可以实际读写的 option ,此时,你应该重复调用 av_opt_child_next() 。无论哪种情况,都要在返回的结果上使用 av_opt_next() 获取具体的 option。

 

然后是对具体的选项值进行读写。

av_opt_set() 函数以及其变种可以设置 option 的值,此时只需将值传递给函数就可以了,它会负责将对应的类型转化为字符串。

av_opt_get() 函数可以获取 option 的值,它会负责将对应的类型解析为字符串,然后返回。不要忘记释放返回的字符串,请使用函数 av_free()释放字符串。

在某些情况下,将所有选项放入一个 AVDictionary 并调用av_opt_set_dict(),这样可能更方便一些。

 

补充:option 的难点主要在于理解其具体的情况,API 则相对简单,因此这里没有具体讲解 API 函数。而关于 option 的函数其实还有不少,但常见的操作就是上面提到的,其他的很少用到。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值