【FFMPEG源码分析】ffmpeg中context与AVClass,AVOption之间的关系

通过前面三篇文章的分析大致了解了ffmpeg中demuxer/decoder模块的内部大致结构和数据处理流程。在阅读源码的过程中经常会看到XXXContext,AVClass xxx_class, AVOption XXX_options。本篇文章将来分析下它们三者之间的联系以及它们的作用。

Context,AVClass, AVOption三者之间关系

在搞清楚三者的关系之前,先想想它们是什么。以下是个人的理解。
Context: 用来描述一个功能模块的属性的对象。一般有多态性质的Context中第一个成员就是AVClass。
**AVClass:**是Context与AVOption的一个纽带。通过Context的AVClass成员找到AVOption,用于动态设置参数选项。
AVOption: ffmpeg中设置动态参数的一种方式。有很好用的接口来set/get具体的参数值。
通过上面的描述它们三者之间的关系可以简单描述下:
Context是用于描述一个模块,里面定义该模块的一些参数,变量,子模块,成员函数等。AVClass只存在于Context中,作为Context的一个成员。而AVOption一般只存在于AVClass中,用于定义模块的动态参数。即通过Context找到AVClass, 通过AVClass找到AVOption。其目的是定义一套标准接口设置各个模块的动态参数。

上面的描述可能有些抽象,下面举一个实际的例子。

  • AVFormatContext 定义与分配
    可以看到AVFormatContext中第一个成员就是AVClass
struct AVFormatContext {
   const AVClass *av_class;
   const struct AVInputFormat *iformat;
   void *priv_data;
   .........其他成员变量.......;
}

AVFormatContext *avformat_alloc_context(void)
{
    FFFormatContext *const si = av_mallocz(sizeof(*si));
    AVFormatContext *s;
    s = &si->pub;
    s->av_class = &av_format_context_class; //赋值
    s->io_open  = io_open_default;
    s->io_close = ff_format_io_close_default;
    s->io_close2= io_close2_default;
    av_opt_set_defaults(s);
    ..............................;
    return s;
}
  • AVClass定义与实现
    可以看到AVClass中成员option即为AVOption
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,
    .child_class_iterate = format_child_class_iterate,
    .category       = AV_CLASS_CATEGORY_MUXER,
    .get_category   = get_category,
};

AVOption的定义与实现
可以看到avformat_options中定义了多个动态参数,这些option可通过ap_opt_xxx 系列函数才访问/修改这些option。

typedef struct AVOption {
    const char *name;
    const char *help;
    int offset;
    enum AVOptionType type;
    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;
    const char *unit;
} AVOption;

static const AVOption avformat_options[] = {
{"avioflags", NULL, OFFSET(avio_flags), AV_OPT_TYPE_FLAGS, {.i64 = DEFAULT }, INT_MIN, INT_MAX, D|E, "avioflags"},
{"direct", "reduce buffering", 0, AV_OPT_TYPE_CONST, {.i64 = AVIO_FLAG_DIRECT }, INT_MIN, INT_MAX, D|E, "avioflags"},
{"probesize", "set probing size", OFFSET(probesize), AV_OPT_TYPE_INT64, {.i64 = 5000000 }, 32, INT64_MAX, D},
..................,
}

使用Context实现多态

还是以AVFormatContext 为例子,详细说明下。众所周知,AVFormatContext负责描述demux/mux功能模块。但音视频的container很多,有MP4, AV1,ts等,不同的格式数据内容差异很大,demux过程逻辑完全不同,但使用FFMPEG进行demux时,使用这只会看见AVFormatContext而无需关心数据格式是什么。那么AVFormatContext是怎么做到的呢 ?这是不是与C++中的多态很像。AVFormatContext是一个父类,各个具体格式的数据demux时是调用子类的实现。

例子AVInputFormat

我们再来回顾下AVFormatContext 的定义:

struct AVFormatContext {
   const AVClass *av_class;
   const struct AVInputFormat *iformat; //demuxer, AVInputFormat如果命名为AVInputFormatContext更合适
   const struct AVOutputFormat *oformat; //muxer, AVOutputFormat 如果命名为AVOutputFormatContext更合适
   void *priv_data;
   .........其他成员变量.......;
}

AVFormatContext 中的AVClass *av_class定义了demux/mux模块公有的option,参数和功能接口。
再来看下AVInputFormat的定义:

typedef struct AVInputFormat {
    const AVClass *priv_class; ///< AVClass for the private context
    int priv_data_size;
    const char *name;
    const char *long_name;
    int flags;
    const char *extensions;
    const struct AVCodecTag * const *codec_tag;
    const char *mime_type;
    int raw_codec_id;
    int flags_internal;
    int (*read_probe)(const AVProbeData *);
    int (*read_header)(struct AVFormatContext *);
    int (*read_close)(struct AVFormatContext *);
    int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,int64_t *pos, int64_t pos_limit);
    int (*read_play)(struct AVFormatContext *);
    int (*read_pause)(struct AVFormatContext *);
    int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
} AVInputFormat;

通过AVInputFormat的定义可以看到定义了demuxer模块公有的成员变量和成员函数。重点看下priv_class和priv_data_size的作用。

typedef struct AVInputFormat {
    const AVClass *priv_class; //某个具体container demuxer的context中的class
    int priv_data_size; //某个具体container demuxer的context中的size
    .................;
}

struct AVFormatContext {
   const AVClass *av_class;
   const struct AVInputFormat *iformat; //demuxer
   const struct AVOutputFormat *oformat; //muxer
   void *priv_data //某个具体container demuxer/muxer的context的指针,子类需要的参数变量
   .........其他成员变量.......;
}

如果当前需要处理的是ts格式的数据,那么这些变量具体的含义如下:

typedef struct AVInputFormat {
    const AVClass *priv_class = &mpegts_class; //静态变量mpegts_class的地址
    int priv_data_size = sizeof(MpegTSContext); //MpegTSContext结构体的大小
    .................;
}

struct AVFormatContext {
   const AVClass *av_class;
   const struct AVInputFormat *iformat; //demuxer
   const struct AVOutputFormat *oformat; //muxer
   void *priv_data = av_malloc(sizeof(MpegTSContext)); //分配一个MpegTSContext
   .........其他成员变量.......;
}

上面的这些具体是如何实现的?上代码:
在ffmpeg\libavformat\demux.c文件中的avformat_open_input(…)函数有如下一段代码,

   //AVFormatContext *s
  if (s->iformat->priv_data_size > 0) {
        if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        if (s->iformat->priv_class) {
            *(const AVClass **) s->priv_data = s->iformat->priv_class;
            av_opt_set_defaults(s->priv_data);
            if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
                goto fail;
        }
    }

此时假如iformat为AVInputFormat ff_mpegts_demuxer,那么s->iformat->priv_data_size和s->iformat->priv_class是什么呢 ?
priv_data_size值就是sizeof(MpegTSContext), priv_class的值是静态变量mpegts_class的地址。

const AVInputFormat ff_mpegts_demuxer = {
    .name           = "mpegts",
    .long_name      = NULL_IF_CONFIG_SMALL("MPEG-TS (MPEG-2 Transport Stream)"),
    .priv_data_size = sizeof(MpegTSContext),
    .read_probe     = mpegts_probe,
    .read_header    = mpegts_read_header,
    .read_packet    = mpegts_read_packet,
    .read_close     = mpegts_read_close,
    .read_timestamp = mpegts_get_dts,
    .flags          = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,
    .priv_class     = &mpegts_class,
};

例子AVInputFormat用c++实现

如果将AVInputFormat用c++来实现,就是这样:

class AVInputFormat {
public:
    const char *name;
    const char *long_name;
    int flags;
    const char *extensions;
    const struct AVCodecTag * const *codec_tag;
    const char *mime_type;
    int raw_codec_id;
    int flags_internal;
    virtual int (*read_probe)(......) = 0;
    virtual  int (*read_header)(......) = 0;
    virtual int (*read_close)(......) = 0 ;
    ...............;
};

class AVInputFormatMpegTS:public AVInputFormat {
public:
    struct MpegTSContext ctx;
    int (*read_probe)(......) override;
    int (*read_header)(......) override;
    int (*read_close)(......) override ;
    ...............;
};

你可能感兴趣的:(多媒体,ffmpeg,视频编解码,c++,AVClass,AVOption)