通过前面三篇文章的分析大致了解了ffmpeg中demuxer/decoder模块的内部大致结构和数据处理流程。在阅读源码的过程中经常会看到XXXContext,AVClass xxx_class, AVOption XXX_options。本篇文章将来分析下它们三者之间的联系以及它们的作用。
在搞清楚三者的关系之前,先想想它们是什么。以下是个人的理解。
Context: 用来描述一个功能模块的属性的对象。一般有多态性质的Context中第一个成员就是AVClass。
**AVClass:**是Context与AVOption的一个纽带。通过Context的AVClass成员找到AVOption,用于动态设置参数选项。
AVOption: ffmpeg中设置动态参数的一种方式。有很好用的接口来set/get具体的参数值。
通过上面的描述它们三者之间的关系可以简单描述下:
Context是用于描述一个模块,里面定义该模块的一些参数,变量,子模块,成员函数等。AVClass只存在于Context中,作为Context的一个成员。而AVOption一般只存在于AVClass中,用于定义模块的动态参数。即通过Context找到AVClass, 通过AVClass找到AVOption。其目的是定义一套标准接口设置各个模块的动态参数。
上面的描述可能有些抽象,下面举一个实际的例子。
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;
}
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},
..................,
}
还是以AVFormatContext 为例子,详细说明下。众所周知,AVFormatContext负责描述demux/mux功能模块。但音视频的container很多,有MP4, AV1,ts等,不同的格式数据内容差异很大,demux过程逻辑完全不同,但使用FFMPEG进行demux时,使用这只会看见AVFormatContext而无需关心数据格式是什么。那么AVFormatContext是怎么做到的呢 ?这是不是与C++中的多态很像。AVFormatContext是一个父类,各个具体格式的数据demux时是调用子类的实现。
我们再来回顾下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++来实现,就是这样:
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 ;
...............;
};