ffmpeg结构体解析-AVClass 和 AVOption

2020-12-20  本文已影响0人  kotlon

在 AVFormatContext 或者 AVCodecContext 等类里面,第一个字段都是 AVClass 指针,以AVFormatContext示例,如下:

//avformat.h
typedef struct AVFormatContext {
    /**
     * A class for logging and @ref avoptions. Set by avformat_alloc_context().
     * Exports (de)muxer private options if they exist.
     */
    const AVClass *av_class;
    //省略其它代码

根据API的介绍,AVClass 就是一个用于打印log 和AVOption的引用,由avformat_alloc_context() 函数创建。怎么理解这个引用呢?

就是说,AVClass 建立起了 AVOption 和 Context 之间的桥梁。

AVOption 结构体

那么这样一说,必须先来看下 AVOption 这个结构体。

先看 API 对 AVOption 结构体的说明,如下:

* AVOptions provide a generic system to declare options on arbitrary structs
* ("objects"). An option can have a help text, a type and a range of possible
* values. Options may then be enumerated, read and written to.
*
* @section avoptions_implement Implementing AVOptions
* This section describes how to add AVOptions capabilities to a struct.
*
* All AVOptions-related information is stored in an AVClass. Therefore
* the first member of the struct should be a pointer to an AVClass describing it.
* The option field of the AVClass must be set to a NULL-terminated static array
* of AVOptions. Each AVOption must have a non-empty name, a type, a default
* value and for number-type AVOptions also a range of allowed values. It must
* also declare an offset in bytes from the start of the struct, where the field
* associated with this AVOption is located. Other fields in the AVOption struct
* should also be set when applicable, but are not required.

AVOption 提供了一种泛型系统用于描述任意结构体的 Option。所有 AVOption 相关的信息都是存储在 AVClass 里面的。因此所以AVOption 描述的结构体的第一个字段必定是 AVClass,其次AVClass 必须包含一个静态的 AVOption 数组。

AVOption 的定义在 libutils/opts.h 头文件中,如下:

/**
 * AVOption
 */
typedef struct AVOption {
    const char *name;

    /**
     * short English help text
     * @todo What about other languages?
     */
    const char *help;

    /**
     * The offset relative to the context structure where the option
     * value is stored. It should be 0 for named constants.
     */
    int offset;
    enum AVOptionType type;

    /**
     * the default value for scalar options
     */
    union {
        int64_t i64;
        double dbl;
        const char *str;
        /* TODO those are unused now */
        AVRational q;
    } default_val;
    double min;                 ///< minimum valid value for the option
    double max;                 ///< maximum valid value for the option

    int flags;
#define AV_OPT_FLAG_ENCODING_PARAM  1   ///< a generic parameter which can be set by the user for muxing or encoding
#define AV_OPT_FLAG_DECODING_PARAM  2   ///< a generic parameter which can be set by the user for demuxing or decoding
#define AV_OPT_FLAG_AUDIO_PARAM     8
#define AV_OPT_FLAG_VIDEO_PARAM     16
#define AV_OPT_FLAG_SUBTITLE_PARAM  32
/**
 * The option is intended for exporting values to the caller.
 */
#define AV_OPT_FLAG_EXPORT          64
/**
 * The option may not be set through the AVOptions API, only read.
 * This flag only makes sense when AV_OPT_FLAG_EXPORT is also set.
 */
#define AV_OPT_FLAG_READONLY        128
#define AV_OPT_FLAG_BSF_PARAM       (1<<8) ///< a generic parameter which can be set by the user for bit stream filtering
#define AV_OPT_FLAG_RUNTIME_PARAM   (1<<15) ///< a generic parameter which can be set by the user at runtime
#define AV_OPT_FLAG_FILTERING_PARAM (1<<16) ///< a generic parameter which can be set by the user for filtering
#define AV_OPT_FLAG_DEPRECATED      (1<<17) ///< set if option is deprecated, users should refer to AVOption.help text for more information
#define AV_OPT_FLAG_CHILD_CONSTS    (1<<18) ///< set if option constants can also reside in child objects
//FIXME think about enc-audio, ... style flags

    /**
     * The logical unit to which the option belongs. Non-constant
     * options and corresponding named constants share the same
     * unit. May be NULL.
     */
    const char *unit;
} AVOption;

来看下 AVOption 结构体的字段:

AVClass

先来看 AVClass 的结构如下:

/**
 * Describe the class of an AVClass context structure. That is an
 * arbitrary struct of which the first field is a pointer to an
 * AVClass struct (e.g. AVCodecContext, AVFormatContext etc.).
 */
typedef struct AVClass {
    /**
     * The name of the class; usually it is the same name as the
     * context structure type to which the AVClass is associated.
     */
    const char* class_name;

    /**
     * A pointer to a function which returns the name of a context
     * instance ctx associated with the class.
     */
    const char* (*item_name)(void* ctx);

    /**
     * a pointer to the first option specified in the class if any or NULL
     *
     * @see av_set_default_options()
     */
    const struct AVOption *option;

    /**
     * LIBAVUTIL_VERSION with which this structure was created.
     * This is used to allow fields to be added without requiring major
     * version bumps everywhere.
     */

    int version;

    /**
     * Offset in the structure where log_level_offset is stored.
     * 0 means there is no such variable
     */
    int log_level_offset_offset;

    /**
     * Offset in the structure where a pointer to the parent context for
     * logging is stored. For example a decoder could pass its AVCodecContext
     * to eval as such a parent context, which an av_log() implementation
     * could then leverage to display the parent context.
     * The offset can be NULL.
     */
    int parent_log_context_offset;

    /**
     * Return next AVOptions-enabled child or NULL
     */
    void* (*child_next)(void *obj, void *prev);

    /**
     * Return an AVClass corresponding to the next potential
     * AVOptions-enabled child.
     *
     * The difference between child_next and this is that
     * child_next iterates over _already existing_ objects, while
     * child_class_next iterates over _all possible_ children.
     */
    const struct AVClass* (*child_class_next)(const struct AVClass *prev);

    /**
     * Category used for visualization (like color)
     * This is only set if the category is equal for all objects using this class.
     * available since version (51 << 16 | 56 << 8 | 100)
     */
    AVClassCategory category;

    /**
     * Callback to return the category.
     * available since version (51 << 16 | 59 << 8 | 100)
     */
    AVClassCategory (*get_category)(void* ctx);

    /**
     * Callback to return the supported/allowed ranges.
     * available since version (52.12)
     */
    int (*query_ranges)(struct AVOptionRanges **, void *obj, const char *key, int flags);
} AVClass;

根据 API 的意思,AVClass 就是用于描述任意包含 AVClass 的一个 Context 的信息类,并且会出现在该 Context 类的第一个字段。然后看下 AVClass 结构体的字段如下:

AVoption 使用

根据之前 API 的描述, AVOption 是描述任意的结构体,提供一种泛型能力。

现在来看下怎么使用 AVOption 来描述我们自己的结构体。

首先,我们按照在头文件定义一些结构体或者 const 常量:

#include <libavutil/log.h>

//定义的 Context 类型结构体,包含一个 AVClass 指针
typedef struct test_context_struct {
    AVClass aClass;
    int mId;
    char *name;
} test_struct;

//作为 AVClass 里面的 AVOption
static const AVOption test_options[] = {
        {"test_int",  "A Int filed,for id",          offsetof(test_struct,
                                                              mId), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX},
        {"test_name", "A char array filed,for name", offsetof(test_struct, name)}

};

static const AVClass test_av_class = {
        .class_name = "test_av_class",
        .item_name = av_default_item_name,
        .option = test_options,
        .version = LIBAVUTIL_VERSION_INT

};

然后定义相关的函数:

#include <libavutil/opt.h>
test_struct *alloc_test_struct(void) {
    test_struct *testStruct = static_cast<test_struct *>(av_mallocz(sizeof(*testStruct)));
    testStruct->aClass = test_av_class;
    av_opt_set_defaults(testStruct);
    return testStruct;
}

void free_test_struct(test_struct *testStruct) {
    av_opt_free(testStruct);
    av_free(testStruct);
}

最终,使用的使用,你可以使用 alloc_test_struct() 函数获得默认的一个结构体对象,如下:

test_struct* context_struct = alloc_test_struct();

AVFormatContext 分析

我们可以通过 avformat_alloc_context() 函数获取一个默认的 AVFormatContext 对象,如下:

AVFormatContext *avFormatContext;
avformat_network_init();
//ffmpeg 4.0 之后可以忽略这个函数
av_register_all();
avFormatContext = avformat_alloc_context();

avformat_alloc_context() 函数位于 libavformat/option.c 下,如下:

AVFormatContext *avformat_alloc_context(void)
{
    AVFormatContext *ic;
    AVFormatInternal *internal;
    ic = av_malloc(sizeof(AVFormatContext));
    if (!ic) return ic;

    internal = av_mallocz(sizeof(*internal));
    if (!internal) {
        av_free(ic);
        return NULL;
    }
    avformat_get_context_defaults(ic);
    ic->internal = internal;
    ic->internal->offset = AV_NOPTS_VALUE;
    ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
    ic->internal->shortest_end = AV_NOPTS_VALUE;

    return ic;
}

前面的逻辑主要是分配申请内存,关键是 avformat_get_context_defaults() 这一行代码,如下:

static void avformat_get_context_defaults(AVFormatContext *s)
{
    memset(s, 0, sizeof(AVFormatContext));

    s->av_class = &av_format_context_class;

    s->io_open  = io_open_default;
    s->io_close = io_close_default;

    av_opt_set_defaults(s);
}

这个avformat_get_context_defaults()函数里面,通过调用 av_format_context_class 这个 Const 常量对象,给 AVFormatContext 对象的类型为 AVClass 的 av_class 字段赋值,然后对 io_open 和 io_close 赋值,最后使用 av_opt_set_defaults() 对 AVFormatContex 进行初始化赋值。

其中,av_format_context_class 常量定义在 libavutil/opt.c 文件下。

static const AVClass av_format_context_class = {
    .class_name     = "AVFormatContext",
    .item_name      = format_to_name,
    .option         = avformat_options,
    .version        = LIBAVUTIL_VERSION_INT,
    .child_next     = format_child_next,
#if FF_API_CHILD_CLASS_NEXT
    .child_class_next = format_child_class_next,
#endif
    .child_class_iterate = format_child_class_iterate,
    .category       = AV_CLASS_CATEGORY_MUXER,
    .get_category   = get_category,
};

关键字段介绍如下:

其它Context 也是类似的,例如 AVCodecContext 也是类型的,这里不再一一概述。

上一篇下一篇

猜你喜欢

热点阅读