个体户营业执照可以做网站吗,河南网站推广,信息公开 强化网站建设,制作网站的钱目录概览 0.参数配置对象流程图0.1 用到的设计模式0.2 与朴素思想的对比 1.参数传递部分1.1 AVDictionary字典容器类1.1.1 类定义及类图1.1.2 构造函数1.1.3 析构函数1.1.4 设置/读取等配置参数 1.2 参数配置实例 2.参数配置生效部分2.1参数过滤模块2.1.1 AVOption类2.1.1.1 类… 目录概览 0.参数配置对象流程图0.1 用到的设计模式0.2 与朴素思想的对比 1.参数传递部分1.1 AVDictionary字典容器类1.1.1 类定义及类图1.1.2 构造函数1.1.3 析构函数1.1.4 设置/读取等配置参数 1.2 参数配置实例 2.参数配置生效部分2.1参数过滤模块2.1.1 AVOption类2.1.1.1 类定义2.1.1.2 相关操作函数2.1.1.3 查看支持的参数配置 2.1.2 AVClass类 2.2 可配参数业务类2.2.1 类定义及类图2.2.2 相关操作函数 2.3 实例2.3.1 参数传递2.3.2 拷贝传递字典到新字典2.3.3 第1个参数配置业务类对象2.3.3 第2个参数配置业务类对象 3.小结 ffmpeg支持很多参数配置——拉流配置推流配置等等——那么庞大繁杂的配置项如果是你该如何实现呢 其实看过一点点源码不用全部后发现就是它的实现也是遵循一个朴素的思想——所谓“大道至简”“万变不离其宗”——就算再多的参数按照我们简单的思想最开始的思维最直接的思维如何实现目的很简单——把一个个的输入参数映射到对象的成员变量里或者全局变量里。这是一个非常简单的思想及其朴素的思想——但是实现手段可以千变万化——fffmpeg的实现也是这样的同样的目的只是经历的实现过程比较“千变万化”、比较“繁杂”、比较“迷人眼”而已。
朴素的表示就是 输入配置参数 —— 对象成员变量/全局变量
如下图
思想很朴素目的很简单。但ffmpeg的实现很复杂。 先说一个小复杂: ffmpeg把输入参数统一抽象成键值对且键和值都用字符串表示传递到内部时再转换成对应格式然后映射到具体业务对象的成员变量里。
再看下它复杂实现的对象流程图——这属于总—分—分的描述写法了先结论再原因。
0.参数配置对象流程图 为了实现ffmpeg的参数配置体系/机制ffmpeg抽象了如上图5类细分AVDictionary字典容器类AVDictionaryEntry字典实体类参数支持表AVOption类参数配置装饰器AVClass类继承AVClass *class的可配参数业务类比如AVCodecContext/RTSPState等类;
这5大类其中AVDictionary字典容器类AVDictionaryEntry字典实体类作为参数传递的载体。后面3类是参数配置生效的类。
前面4类是基础、工具类、公共模块供其他模块使用所以放到了工具箱目录——libavutil目录下。
第5类是需要开放参数配置的业务类在业务功能模块里定义比较灵活谁需要谁装配所以就不放到工具类了——第一个成员必须是AVClass *类型的因为ffmpeg配置参数的实现是建立在它是这样的位置的假设的不能随意改不然得改源码。
还有个重要的AVOption类的成员offset这个偏移相对的是谁如上图offset虚线箭头指向——就是AVClass *所在宿主类对象地址——可配参数实体业务类对象的地址。——当然可以引入linux内核第一宏就不用把AVClass放到第一个成员了但是要改源码了。
0.1 用到的设计模式
ffmpeg将AVDictionary字典容器类对象里的参数映射到的是参数配置的业务类这一过程中增加了参数支持配置表AVOption类而AVOption类是被AVClass类管理的——AVClass类是个啥东西我觉得称之为装饰器类因为这用到了设计模式的装饰器设计模式——谁想增加参数可配置的功能谁就戴上AVClass类就行了。装饰器就是谁想有什么能力就去戴上那个能力就行了。
因此AVClass类是可配置参数能力的装饰器。
0.2 与朴素思想的对比
下图是ffmpeg与朴素思想进行对比它的实现只是朴素思想实现的演化或者复杂化——但万变不离其宗。 上图左边虚框里是第一步保存参数配置到字典容器里下面会有详解——相当于寄存器或者寄存地。 上图右边虚框里是第二步将参数配置落地——把字典容器里的参数设置到可配参数业务类对象里对应的参数成员里——最终落脚地参数去的目的地。
从这看出ffmpeg也是万变不离其中和我们最初的梦想一样都是把参数保存到全局变量或者对象的成员变量里面去等运行的时候直接拿来使用。 初心不改只是过程复杂。——或者说本质原理是极其简单的一点也不复杂——复杂的是实现手段一堆弯弯绕绕。
对比朴素、简单的思想来说为啥变成了这么多类呢输入参数搞成字典类保存参数的变量搞成了可配参数业务类——由参数配置装饰器类AVClass组合而成参数配置器类AVClass主要管理参数支持表类AVOption。搞的这么弯弯绕绕这样耦合性降低同时增加了一个参数过滤的过程不支持的参数不会配置。
1.参数传递部分
这一部分ffmpeg把参数暂存到字典类中涉及到两类AVDictionary字典容器类和AVDictionaryEntry字典实体类。可以把这两类合并叫字典类。
1.1 AVDictionary字典容器类
AVDictionary字典容器类——ffmpeg粗暴且低效地实现了python中的字典概念或者cpp中的map容器概念——是个键值对容器。 它和AVDictionaryEntry字典实体类是什么关系聚合关系根据面向对象的思想——具体见下面对象图。
1.1.1 类定义及类图
libavutil/dict.c中
//字典容器类定义管理字典实体类count是管理的个数。
struct AVDictionary {int count;AVDictionaryEntry *elems;
};
libavutil/dict.h中
//字典实体类键值对的内存结构也是存放地
typedef struct AVDictionaryEntry {char *key;char *value;
} AVDictionaryEntry;//字典容器类对外的声明好被别的模块拿去用
typedef struct AVDictionary AVDictionary;从如上类图/数据结构中它粗暴低效的实现在于它在内存中搞了个指针数组存放一个个的键值对每次新增都会扩展这个指针数组每次查找都是循环遍历指针数组来匹配。
如果想加入一个键值对不是链表形式而是调用realloc扩展指针数组的内存即elems成员指向的那块连续内存。
又粗暴又低效不过能用。
1.1.2 构造函数
oopc的构造也是类似c的但c的类对象的内存开辟编程人员看不到由编译器编译时添加。
ffmpeg的实现是这个AVDictionary对象直接调用av_dict_set方法来构造。 里面包含了内存开辟。所以直接使用即可。比如
AVDictionary *opts NULL;av_dict_set(opts, stimeout, 10000000, 0);另外一个av_dict_copy也包含了构造函数。
int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags)
{AVDictionaryEntry *t NULL;while ((t av_dict_get(src, , t, AV_DICT_IGNORE_SUFFIX))) {int ret av_dict_set(dst, t-key, t-value, flags);if (ret 0){return ret;}}return 0;
}可以看到其实也是因为调用了av_dict_set函数才具有构造功能。所以使用av_dict_copy时也可以这样 AVDictionary *tmp NULL;av_dict_copy(tmp, *options, 0);
这样就拷贝到tmp这个字典指向的对象了。
1.1.3 析构函数
av_dict_free(opts);1.1.4 设置/读取等配置参数
//设置键值对到字典类对象中——包含了构造。
int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);//获取字典类对象中的键值对。
AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key,const AVDictionaryEntry *prev, int flags);//拷贝一个字典对象的键值对到另一个字典对象中深拷贝包含了构造函数。
int av_dict_copy(AVDictionary **dst, const AVDictionary *src, int flags);
按照oopc来说它这些方法就是这个类的方法模拟的面向对象的类方法定义第一个形参可以看着是this指针。
1.2 参数配置实例 AVDictionary *opts NULL;av_dict_set(opts, stimeout, 10000000, 0); //设置超时断开连接时间 usav_dict_set(opts, buffer_size, 102400, 0); //设置缓存大小 byteav_dict_set(opts, rtsp_transport, tcp, 0); //设置rtsp以tcp/udp方式打开av_dict_set(opts, threads, 0, 0); //设置自动开启线程数av_dict_set(opts, probesize, 2097152, 0); //设置探测输入流数据包大小av_dict_set(opts, max_delay, 1000, 0); //接收包间隔最大延迟 usav_dict_set(opts, analyzeduration, 1000000, 0); //设置分析输入流所需时间 usav_dict_set(opts, max_analyze_duration, 1000, 0); //设置分析输入流最大时长 us
这样呢就把这些参数变成了键值对存放到了opts所指向的字典管理类对象中。那么接下来ffmpeg就可以拿着opts去配置下去了。
到此第0章的参数配置对象流程图中参数配置传递完毕接下来所讲的就是参数配置到业务对象的成员变量中的“弯弯绕绕”“繁杂”的过程。
2.参数配置生效部分
该部分是参数配置最终到达的目的地首先是在生效过程增加了参数过滤匹配到支持的参数才会去设置还有记录了参数要映射到地址偏移最后才能找到设置的内存地址最终参数去的地方。 ffmpeg把这一过程弄的有点复杂原因在于去耦合模块化采用了装饰器模式谁需要参数配置功能谁就得按照这个规则包含装饰器类AVClass类指针——且作为第一个成员。同时也加入了过滤参数的功能。
其实配置生效部分主要是2大块一块是参数配置最终去的地方——参数配置业务类对象中各个参数成员。另一块是参数过滤模块。这块就是AVClass参数配置装饰器类。
因为AVClass参数配置装饰器类的出现就和最终放参数的业务配置类对象解耦了。
AVClass和最终参数去的对象的关系是AVClass中AVOption表格里的offset要和最终对象的参数成员偏移保持一直相匹配否则配置会出错。
还是分成配置过滤模块和配置业务类对象模块吧。
2.1参数过滤模块
我把AVClass类和AVOption类放到这一个模块吧。AVClass主要是管理AVOption支持参数配置表的。ffmpeg通过AVClass类的AVOption表格来过滤参数是否支持或者可配置。
2.1.1 AVOption类
AVDictionary 负责保存用户传递进来的参数统一抽象为键值对那么传递进来后先不说配置到哪里先说配置到目的地的时候是不是得过滤下不然你瞎写参数ffmpeg都没有支持也能配置AVOption类应运而生——是ffmpeg能支持的参数配置表或者叫参数过滤识别表。
ffmpeg中每个支持参数配置的业务类对象都有自己的AVOption类配置支持项表格——以表明这个业务只能支持哪些参数配置——这样很具有扩展性什么样的业务定义什么样的配置表——是提前定义好的不是随便写一个配置动态现编的程序没有那么智能——除非是那种未来高级AI程序可以自我编程动态随时随地修改自己运行的代码的那种。
2.1.1.1 类定义
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 optiondouble max; /// maximum valid value for the optionint 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 (18) /// a generic parameter which can be set by the user for bit stream filtering
#define AV_OPT_FLAG_RUNTIME_PARAM (115) /// a generic parameter which can be set by the user at runtime
#define AV_OPT_FLAG_FILTERING_PARAM (116) /// a generic parameter which can be set by the user for filtering
#define AV_OPT_FLAG_DEPRECATED (117) /// set if option is deprecated, users should refer to AVOption.help text for more information
#define AV_OPT_FLAG_CHILD_CONSTS (118) /// 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;
这个是参数抽象出来的类里面包含了各种信息其中offset偏移是比较重要的是参数配置最终的落脚点。
2.1.1.2 相关操作函数
//循环遍历获取AVOption表格中的一个个AVOption类成员的迭代器。
const AVOtion *av_opt_next(const void *obj, const AVOption *last)它类似一个迭代器把表格里的所有配置项编辑出来使用例程如下:
AVOtion *opt NULL;
while(opt av_opt_next(obj, opt))
{
//循环遍历出一个个配置项和c python等高级语言的迭代器是一模一样的模拟了它们高级语言的特性
}2.1.1.3 查看支持的参数配置
针对具体业务ffmpeg支持那些参数配置看完本节就不用网上搜了。 通过源码查找AVOption类的参数支持表就知道了也知道怎么配置了。
比如想配置rtsp的参数那么可以找到rtsp的AVOption类的配置表格如下看看它支持的配置项:
static const AVClass rtsp_demuxer_class {.class_name RTSP demuxer,.item_name av_default_item_name,.option ff_rtsp_options,.version LIBAVUTIL_VERSION_INT,
};
可以看到rtsp的AVOption类的配置表格是ff_rtsp_options如下
const AVOption ff_rtsp_options[] {{ initial_pause, do not start playing the stream immediately, OFFSET(initial_pause), AV_OPT_TYPE_BOOL, {.i64 0}, 0, 1, DEC },FF_RTP_FLAG_OPTS(RTSPState, rtp_muxer_flags),{ rtsp_transport, set RTSP transport protocols, OFFSET(lower_transport_mask), AV_OPT_TYPE_FLAGS, {.i64 0}, INT_MIN, INT_MAX, DEC|ENC, rtsp_transport }, \{ udp, UDP, 0, AV_OPT_TYPE_CONST, {.i64 1 RTSP_LOWER_TRANSPORT_UDP}, 0, 0, DEC|ENC, rtsp_transport }, \{ tcp, TCP, 0, AV_OPT_TYPE_CONST, {.i64 1 RTSP_LOWER_TRANSPORT_TCP}, 0, 0, DEC|ENC, rtsp_transport }, \{ udp_multicast, UDP multicast, 0, AV_OPT_TYPE_CONST, {.i64 1 RTSP_LOWER_TRANSPORT_UDP_MULTICAST}, 0, 0, DEC, rtsp_transport },{ http, HTTP tunneling, 0, AV_OPT_TYPE_CONST, {.i64 (1 RTSP_LOWER_TRANSPORT_HTTP)}, 0, 0, DEC, rtsp_transport },{ https, HTTPS tunneling, 0, AV_OPT_TYPE_CONST, {.i64 (1 RTSP_LOWER_TRANSPORT_HTTPS )}, 0, 0, DEC, rtsp_transport },RTSP_FLAG_OPTS(rtsp_flags, set RTSP flags),{ listen, wait for incoming connections, 0, AV_OPT_TYPE_CONST, {.i64 RTSP_FLAG_LISTEN}, 0, 0, DEC, rtsp_flags },{ prefer_tcp, try RTP via TCP first, if available, 0, AV_OPT_TYPE_CONST, {.i64 RTSP_FLAG_PREFER_TCP}, 0, 0, DEC|ENC, rtsp_flags },{ satip_raw, export raw MPEG-TS stream instead of demuxing, 0, AV_OPT_TYPE_CONST, {.i64 RTSP_FLAG_SATIP_RAW}, 0, 0, DEC, rtsp_flags },RTSP_MEDIATYPE_OPTS(allowed_media_types, set media types to accept from the server),{ min_port, set minimum local UDP port, OFFSET(rtp_port_min), AV_OPT_TYPE_INT, {.i64 RTSP_RTP_PORT_MIN}, 0, 65535, DEC|ENC },{ max_port, set maximum local UDP port, OFFSET(rtp_port_max), AV_OPT_TYPE_INT, {.i64 RTSP_RTP_PORT_MAX}, 0, 65535, DEC|ENC },{ listen_timeout, set maximum timeout (in seconds) to wait for incoming connections (-1 is infinite, imply flag listen), OFFSET(initial_timeout), AV_OPT_TYPE_INT, {.i64 -1}, INT_MIN, INT_MAX, DEC },{ timeout, set timeout (in microseconds) of socket I/O operations, OFFSET(stimeout), AV_OPT_TYPE_INT64, {.i64 0}, INT_MIN, INT64_MAX, DEC },COMMON_OPTS(),{ user_agent, override User-Agent header, OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str LIBAVFORMAT_IDENT}, 0, 0, DEC },{ NULL },
};
这个AVOption表格记录了rtsp支持的参数配置其中offset的偏移量是相对RTSPState来说的除常量外常量不允许配置。
int avformat_open_input(AVFormatContext **ps, const char *filename,const AVInputFormat *fmt, AVDictionary **options)
{AVFormatContext *s *ps;……/* Allocate private data. */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;}}……
}
avformat_open_input中有私有数据的业务类基本都安装了装饰器AVClass类offset值是相对私有数据的地址的——也即可配参数业务类对象地址。比如rtsp的私有数据是RTSPState类所以rtsp的AVOption类的配置表格ff_rtsp_options中的offset偏移量就是相对RTSPState类对象起始地址的偏移量如果理解不了可以直接看表格上面的宏定义。
2.1.2 AVClass类
AVClass类是可配置参数能力的装饰器。 它主要是管理上面AVOption类的且AVOption类的offset是相对于AVClass所在宿主类的偏移。
有些业务类想要拥有参数可配置的能力它的第一个成员就得是AVClass 类型的指针成员如果不想有这个能力把这个成员置为NULL即可。
我在想为啥各个需要参数可配置的业务类的第一个成员要放AVClass *class这样指针形式的呢为何不直接包含呢——AVClass class这样子后来想想如果包含了你又不想拥有这样的能力了咋办占空间不说是不是还得加上一个标志标识是否具备这个能力。但是一旦是指针哎呀耦合就不深了——俗称解耦。——说到这发现其实这也包含了软件五大设计原则中的依赖倒置原则。建议采用组合模式而非继承模式来设计类降低类之间的耦合性。
再说回采用指针那么写成NULL就是不支持这个能力了占用空间小且就算改变这个AVClass类的结构也不影响其宿主内存结构非常之好。同时也符合软件设计模式里的装饰器设计模式。 非NULL就具备能力NULL就不具备能力。比较灵活。
AVClass包含了AVOtion的形式也是采用指针——这样这2类耦合性也降低了AVClass类由AVOtion类组合而成。
我曾想为何不选择AVOtion类作为装饰器呢直接去掉AVClass类不好么后来想AVOtion类太单薄了——单纯的对参数信息的抽象不具有别的功能——其实遵循了软件设计原则中的单一职责原则——可扩展性太小于是换谁都是再抽象一层——软件上遇事不决就抽象出一个中间层——AVClass可以再包含AVClass子类灵活性大大提升。
2.2 可配参数业务类
自己起的名字比如AVFormatContext/AVCodecContext/RTSPState等类为典型代表。
当经过参数配置过滤模块后ffmpeg支持的参数到底要配置到哪里呢总得有个落脚点吧于是可配参数业务类应运而生。我运行的时候怎么使用它暂时不讨论
2.2.1 类定义及类图
这类的形式是如下的 如果想要拥有可配参数能力那么就定义个这个业务的参数支持装饰器AVClass否则就把成员class置为NULL。
比如想要给rtsp拉流添加可配置参数功能则需要定义一个rtsp参数业务配置类第1个成员必须是AVClass类的指针类型再实例化AVClass类对象和AVOption类对象——支持可配参数的表格等然后绑定一起如下 rtsp的可配参数装饰器AVClass类实例化是rtsp_demuxer_class对象全局变量AVOption类实例化是ff_rtsp_options表格全局变量。
2.2.2 相关操作函数
//把配置的参数设置到可配参数业务对象的成员变量里
//obj就是可配置参数业务对象的地址即this指针比如AVCodecContext/RTSPState等的地址
int av_opt_set_dict(void *obj, AVDictionary **options)有意思的是obj其实是this指针——可配置参数业务对象的地址类图如下 av_opt_set_dict的关键调用链如下: av_opt_set_dict ⇒ av_opt_set_dict2 ⇒ av_opt_set⇒ av_opt_find2
具体
int av_opt_set_dict(void *obj, AVDictionary **options)
{return av_opt_set_dict2(obj, options, 0);
}
int av_opt_set_dict2(void *obj, AVDictionary **options, int search_flags)
{AVDictionaryEntry *t NULL;AVDictionary *tmp NULL;int ret;if (!options)return 0;while ((t av_dict_get(*options, , t, AV_DICT_IGNORE_SUFFIX))) {ret av_opt_set(obj, t-key, t-value, search_flags);if (ret AVERROR_OPTION_NOT_FOUND){ret av_dict_set(tmp, t-key, t-value, 0);if (ret 0) {av_log(obj, AV_LOG_ERROR, Error setting option %s to value %s.\n, t-key, t-value);av_dict_free(tmp);return ret;}}}av_dict_free(options);*options tmp;return 0;
}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);
……dst ((uint8_t *)target_obj) o-offset;switch (o-type) {case AV_OPT_TYPE_BOOL:return set_string_bool(obj, o, val, dst);}
}const AVOption *av_opt_find2(void *obj, const char *name, const char *unit,int opt_flags, int search_flags, void **target_obj)
{const AVClass *c;const AVOption *o NULL;if(!obj)return NULL;c *(AVClass**)obj;if (!c)return NULL;if (search_flags AV_OPT_SEARCH_CHILDREN) {if (search_flags AV_OPT_SEARCH_FAKE_OBJ) {void *iter NULL;const AVClass *child;while (child av_opt_child_class_iterate(c, iter))if (o av_opt_find2(child, name, unit, opt_flags, search_flags, NULL))return o;} else {void *child NULL;while (child av_opt_child_next(obj, child))if (o av_opt_find2(child, name, unit, opt_flags, search_flags, target_obj))return o;}}while (o av_opt_next(obj, o)) {if (!strcmp(o-name, name) ((o-flags opt_flags) opt_flags) ((!unit o-type ! AV_OPT_TYPE_CONST) ||(unit o-type AV_OPT_TYPE_CONST o-unit !strcmp(o-unit, unit)))) {if (target_obj) {if (!(search_flags AV_OPT_SEARCH_FAKE_OBJ))*target_obj obj;else*target_obj NULL;}return o;}}return NULL;
}有几点要关注 1av_opt_set_dict这个接口会过滤参数支持表AVOption 中的常量值不允许配置常量值。 因为av_opt_find2函数中有个过滤条件就是把常量过滤掉了只会返回非常量的参数配置表的配置项如下。
2在av_opt_set_dict2中会把不支持的非常量的参数配置放到新的字典容器中配置完成后把传进来的字典容器给改写了。
2.3 实例
结合前面字典类暂存参数配置然后到这部分的参数生效来写个实例然后挑重点分析下。代码如下 AVDictionary *opts NULL;av_dict_set(opts, probesize, 2097152, 0); //设置探测输入流数据包大小av_dict_set(opts, max_delay, 1000, 0); //接收包间隔最大延迟 usav_dict_set(opts, fastseek, 1, 0); //设置常量不会生效过滤掉了av_dict_set(opts, timeout, 10000000, 0); //设置超时断开连接时间 usav_dict_set(opts, rtsp_transport, tcp, 0); //设置rtsp以tcp/udp方式打开av_dict_set(opts, udp, 0, 0); //设置常量不会生效过滤掉了AVFormatContext *fmtCtx NULL;avformat_open_input(fmtCtx, rtsp://192.168.1.46/0, NULL, opts);看到rtsp://192.168.1.46/0 就知道这是rtsp拉流了。然后我们关注opt字典参数是如何配置下去的。
这个实例是最终配置到的目标对象是我也是FFFormatContext对象用户看到的是其父类AVFormatContext和RTSPState类对象。
2.3.1 参数传递
实例第一部分是参数传递: av_dict_set设置完这些字符串就变成了键值对保存在了opts指向的字典容器对象里了。图可以参考第1章。
2.3.2 拷贝传递字典到新字典
然后就调用avformat_open_input把参数配置下去那么第一次配置参数在哪里并且陪到到那个业务对象里呢如下 在第240行时拷贝字典容器类的键值对到新键值对容器对象tmp中原因是av_opt_set_dict会返回新的字典容器对象也就是会改变传入进去的字典对象所以先拷贝到tmp中。
2.3.3 第1个参数配置业务类对象
1-5标记可以看到第一次参数配置是到哪个对象哇是FFFormatContext对象但是FFFormatContext对象包含AVFormatContext父类oopc的继承所以说是AVFormatContext也对反正首地址都是一样只需强转就能改变访问权限范围。而ffmpeg实际上把参数配置放到了AVFormatContext里可能以为5.x才分出来FFFormatContext历史缘故。其对应的参数装饰器类实例化的对象是av_format_context_class这个全局变量然后就找到了其支持的参数配置表格avformat_options具体如下
#define OFFSET(x) offsetof(AVFormatContext,x)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},
{formatprobesize, number of bytes to probe file format, OFFSET(format_probesize), AV_OPT_TYPE_INT, {.i64 PROBE_BUF_MAX}, 0, INT_MAX-1, D},
{packetsize, set packet size, OFFSET(packet_size), AV_OPT_TYPE_INT, {.i64 DEFAULT }, 0, INT_MAX, E},
{fflags, NULL, OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 AVFMT_FLAG_AUTO_BSF }, INT_MIN, INT_MAX, D|E, fflags},
{flush_packets, reduce the latency by flushing out packets immediately, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_FLAG_FLUSH_PACKETS }, INT_MIN, INT_MAX, E, fflags},
{ignidx, ignore index, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_FLAG_IGNIDX }, INT_MIN, INT_MAX, D, fflags},
{genpts, generate pts, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_FLAG_GENPTS }, INT_MIN, INT_MAX, D, fflags},
{nofillin, do not fill in missing values that can be exactly calculated, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_FLAG_NOFILLIN }, INT_MIN, INT_MAX, D, fflags},
{noparse, disable AVParsers, this needs nofillin too, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_FLAG_NOPARSE }, INT_MIN, INT_MAX, D, fflags},
{igndts, ignore dts, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_FLAG_IGNDTS }, INT_MIN, INT_MAX, D, fflags},
{discardcorrupt, discard corrupted frames, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_FLAG_DISCARD_CORRUPT }, INT_MIN, INT_MAX, D, fflags},
{sortdts, try to interleave outputted packets by dts, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_FLAG_SORT_DTS }, INT_MIN, INT_MAX, D, fflags},
{fastseek, fast but inaccurate seeks, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_FLAG_FAST_SEEK }, INT_MIN, INT_MAX, D, fflags},
{nobuffer, reduce the latency introduced by optional buffering, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_FLAG_NOBUFFER }, 0, INT_MAX, D, fflags},
{bitexact, do not write random/volatile data, 0, AV_OPT_TYPE_CONST, { .i64 AVFMT_FLAG_BITEXACT }, 0, 0, E, fflags },
{shortest, stop muxing with the shortest stream, 0, AV_OPT_TYPE_CONST, { .i64 AVFMT_FLAG_SHORTEST }, 0, 0, E, fflags },
{autobsf, add needed bsfs automatically, 0, AV_OPT_TYPE_CONST, { .i64 AVFMT_FLAG_AUTO_BSF }, 0, 0, E, fflags },
{seek2any, allow seeking to non-keyframes on demuxer level when supported, OFFSET(seek2any), AV_OPT_TYPE_BOOL, {.i64 0 }, 0, 1, D},
{analyzeduration, specify how many microseconds are analyzed to probe the input, OFFSET(max_analyze_duration), AV_OPT_TYPE_INT64, {.i64 0 }, 0, INT64_MAX, D},
{cryptokey, decryption key, OFFSET(key), AV_OPT_TYPE_BINARY, {.dbl 0}, 0, 0, D},
{indexmem, max memory used for timestamp index (per stream), OFFSET(max_index_size), AV_OPT_TYPE_INT, {.i64 120 }, 0, INT_MAX, D},
{rtbufsize, max memory used for buffering real-time frames, OFFSET(max_picture_buffer), AV_OPT_TYPE_INT, {.i64 3041280 }, 0, INT_MAX, D}, /* defaults to 1s of 15fps 352x288 YUYV422 video */
{fdebug, print specific debug info, OFFSET(debug), AV_OPT_TYPE_FLAGS, {.i64 DEFAULT }, 0, INT_MAX, E|D, fdebug},
{ts, NULL, 0, AV_OPT_TYPE_CONST, {.i64 FF_FDEBUG_TS }, INT_MIN, INT_MAX, E|D, fdebug},
{max_delay, maximum muxing or demuxing delay in microseconds, OFFSET(max_delay), AV_OPT_TYPE_INT, {.i64 -1 }, -1, INT_MAX, E|D},
{start_time_realtime, wall-clock time when stream begins (PTS0), OFFSET(start_time_realtime), AV_OPT_TYPE_INT64, {.i64 AV_NOPTS_VALUE}, INT64_MIN, INT64_MAX, E},
{fpsprobesize, number of frames used to probe fps, OFFSET(fps_probe_size), AV_OPT_TYPE_INT, {.i64 -1}, -1, INT_MAX-1, D},
{audio_preload, microseconds by which audio packets should be interleaved earlier, OFFSET(audio_preload), AV_OPT_TYPE_INT, {.i64 0}, 0, INT_MAX-1, E},
{chunk_duration, microseconds for each chunk, OFFSET(max_chunk_duration), AV_OPT_TYPE_INT, {.i64 0}, 0, INT_MAX-1, E},
{chunk_size, size in bytes for each chunk, OFFSET(max_chunk_size), AV_OPT_TYPE_INT, {.i64 0}, 0, INT_MAX-1, E},
/* this is a crutch for avconv, since it cannot deal with identically named options in different contexts.* to be removed when avconv is fixed */
{f_err_detect, set error detection flags (deprecated; use err_detect, save via avconv), OFFSET(error_recognition), AV_OPT_TYPE_FLAGS, {.i64 AV_EF_CRCCHECK }, INT_MIN, INT_MAX, D, err_detect},
{err_detect, set error detection flags, OFFSET(error_recognition), AV_OPT_TYPE_FLAGS, {.i64 AV_EF_CRCCHECK }, INT_MIN, INT_MAX, D, err_detect},
{crccheck, verify embedded CRCs, 0, AV_OPT_TYPE_CONST, {.i64 AV_EF_CRCCHECK }, INT_MIN, INT_MAX, D, err_detect},
{bitstream, detect bitstream specification deviations, 0, AV_OPT_TYPE_CONST, {.i64 AV_EF_BITSTREAM }, INT_MIN, INT_MAX, D, err_detect},
{buffer, detect improper bitstream length, 0, AV_OPT_TYPE_CONST, {.i64 AV_EF_BUFFER }, INT_MIN, INT_MAX, D, err_detect},
{explode, abort decoding on minor error detection, 0, AV_OPT_TYPE_CONST, {.i64 AV_EF_EXPLODE }, INT_MIN, INT_MAX, D, err_detect},
{ignore_err, ignore errors, 0, AV_OPT_TYPE_CONST, {.i64 AV_EF_IGNORE_ERR }, INT_MIN, INT_MAX, D, err_detect},
{careful, consider things that violate the spec, are fast to check and have not been seen in the wild as errors, 0, AV_OPT_TYPE_CONST, {.i64 AV_EF_CAREFUL }, INT_MIN, INT_MAX, D, err_detect},
{compliant, consider all spec non compliancies as errors, 0, AV_OPT_TYPE_CONST, {.i64 AV_EF_COMPLIANT | AV_EF_CAREFUL }, INT_MIN, INT_MAX, D, err_detect},
{aggressive, consider things that a sane encoder shouldnt do as an error, 0, AV_OPT_TYPE_CONST, {.i64 AV_EF_AGGRESSIVE | AV_EF_COMPLIANT | AV_EF_CAREFUL}, INT_MIN, INT_MAX, D, err_detect},
{use_wallclock_as_timestamps, use wallclock as timestamps, OFFSET(use_wallclock_as_timestamps), AV_OPT_TYPE_BOOL, {.i64 0}, 0, 1, D},
{skip_initial_bytes, set number of bytes to skip before reading header and frames, OFFSET(skip_initial_bytes), AV_OPT_TYPE_INT64, {.i64 0}, 0, INT64_MAX-1, D},
{correct_ts_overflow, correct single timestamp overflows, OFFSET(correct_ts_overflow), AV_OPT_TYPE_BOOL, {.i64 1}, 0, 1, D},
{flush_packets, enable flushing of the I/O context after each packet, OFFSET(flush_packets), AV_OPT_TYPE_INT, {.i64 -1}, -1, 1, E},
{metadata_header_padding, set number of bytes to be written as padding in a metadata header, OFFSET(metadata_header_padding), AV_OPT_TYPE_INT, {.i64 -1}, -1, INT_MAX, E},
{output_ts_offset, set output timestamp offset, OFFSET(output_ts_offset), AV_OPT_TYPE_DURATION, {.i64 0}, -INT64_MAX, INT64_MAX, E},
{max_interleave_delta, maximum buffering duration for interleaving, OFFSET(max_interleave_delta), AV_OPT_TYPE_INT64, { .i64 10000000 }, 0, INT64_MAX, E },
{f_strict, how strictly to follow the standards (deprecated; use strict, save via avconv), OFFSET(strict_std_compliance), AV_OPT_TYPE_INT, {.i64 DEFAULT }, INT_MIN, INT_MAX, D|E, strict},
{strict, how strictly to follow the standards, OFFSET(strict_std_compliance), AV_OPT_TYPE_INT, {.i64 DEFAULT }, INT_MIN, INT_MAX, D|E, strict},
{very, strictly conform to a older more strict version of the spec or reference software, 0, AV_OPT_TYPE_CONST, {.i64 FF_COMPLIANCE_VERY_STRICT }, INT_MIN, INT_MAX, D|E, strict},
{strict, strictly conform to all the things in the spec no matter what the consequences, 0, AV_OPT_TYPE_CONST, {.i64 FF_COMPLIANCE_STRICT }, INT_MIN, INT_MAX, D|E, strict},
{normal, NULL, 0, AV_OPT_TYPE_CONST, {.i64 FF_COMPLIANCE_NORMAL }, INT_MIN, INT_MAX, D|E, strict},
{unofficial, allow unofficial extensions, 0, AV_OPT_TYPE_CONST, {.i64 FF_COMPLIANCE_UNOFFICIAL }, INT_MIN, INT_MAX, D|E, strict},
{experimental, allow non-standardized experimental variants, 0, AV_OPT_TYPE_CONST, {.i64 FF_COMPLIANCE_EXPERIMENTAL }, INT_MIN, INT_MAX, D|E, strict},
{max_ts_probe, maximum number of packets to read while waiting for the first timestamp, OFFSET(max_ts_probe), AV_OPT_TYPE_INT, { .i64 50 }, 0, INT_MAX, D },
{avoid_negative_ts, shift timestamps so they start at 0, OFFSET(avoid_negative_ts), AV_OPT_TYPE_INT, {.i64 -1}, -1, 2, E, avoid_negative_ts},
{auto, enabled when required by target format, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_AVOID_NEG_TS_AUTO }, INT_MIN, INT_MAX, E, avoid_negative_ts},
{disabled, do not change timestamps, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_AVOID_NEG_TS_DISABLED }, INT_MIN, INT_MAX, E, avoid_negative_ts},
{make_non_negative, shift timestamps so they are non negative, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE }, INT_MIN, INT_MAX, E, avoid_negative_ts},
{make_zero, shift timestamps so they start at 0, 0, AV_OPT_TYPE_CONST, {.i64 AVFMT_AVOID_NEG_TS_MAKE_ZERO }, INT_MIN, INT_MAX, E, avoid_negative_ts},
{dump_separator, set information dump field separator, OFFSET(dump_separator), AV_OPT_TYPE_STRING, {.str , }, 0, 0, D|E},
{codec_whitelist, List of decoders that are allowed to be used, OFFSET(codec_whitelist), AV_OPT_TYPE_STRING, { .str NULL }, 0, 0, D },
{format_whitelist, List of demuxers that are allowed to be used, OFFSET(format_whitelist), AV_OPT_TYPE_STRING, { .str NULL }, 0, 0, D },
{protocol_whitelist, List of protocols that are allowed to be used, OFFSET(protocol_whitelist), AV_OPT_TYPE_STRING, { .str NULL }, 0, 0, D },
{protocol_blacklist, List of protocols that are not allowed to be used, OFFSET(protocol_blacklist), AV_OPT_TYPE_STRING, { .str NULL }, 0, 0, D },
{max_streams, maximum number of streams, OFFSET(max_streams), AV_OPT_TYPE_INT, { .i64 1000 }, 0, INT_MAX, D },
{skip_estimate_duration_from_pts, skip duration calculation in estimate_timings_from_pts, OFFSET(skip_estimate_duration_from_pts), AV_OPT_TYPE_BOOL, {.i64 0}, 0, 1, D},
{max_probe_packets, Maximum number of packets to probe a codec, OFFSET(max_probe_packets), AV_OPT_TYPE_INT, { .i64 2500 }, 0, INT_MAX, D },
{NULL},
};
哎呦我写的实例代码前3项的配置刚好在这个表格里我真会举例。
回顾下前3项配置
av_dict_set(opts, probesize, 2097152, 0); //设置探测输入流数据包大小av_dict_set(opts, max_delay, 1000, 0); //接收包间隔最大延迟 usav_dict_set(opts, fastseek, 1, 0); //设置常量不会生效过滤掉了这个时候呢前2项分别映射到AVFormatContext中的probesize和max_delay成员里了那么第3项是常量不允许设置过滤掉了。
这是配置对象流程图
2.3.3 第2个参数配置业务类对象
然后还剩下3项注意这个时候av_opt_set_dict(s, tmp)) 返回的tmp指向的地址已经是新的地址了它指向的字典容器类对象已经是个全新的对象了里面就剩下3项了因为前3项被领走了匹配到就各回各家各找各妈。
然后继续看剩余这3项配置到哪里去了。 原来配置到私有数据里面了这篇说过如果是rtsp的话它匹配到的参数配置对象是RTSPState对象参数装饰器类对象是rtsp_demuxer_class这个全局变量然后就找到了其支持的参数配置表格ff_rtsp_options具体如下
#define OFFSET(x) offsetof(RTSPState, x)const AVOption ff_rtsp_options[] {{ initial_pause, do not start playing the stream immediately, OFFSET(initial_pause), AV_OPT_TYPE_BOOL, {.i64 0}, 0, 1, DEC },FF_RTP_FLAG_OPTS(RTSPState, rtp_muxer_flags),{ rtsp_transport, set RTSP transport protocols, OFFSET(lower_transport_mask), AV_OPT_TYPE_FLAGS, {.i64 0}, INT_MIN, INT_MAX, DEC|ENC, rtsp_transport }, \{ udp, UDP, 0, AV_OPT_TYPE_CONST, {.i64 1 RTSP_LOWER_TRANSPORT_UDP}, 0, 0, DEC|ENC, rtsp_transport }, \{ tcp, TCP, 0, AV_OPT_TYPE_CONST, {.i64 1 RTSP_LOWER_TRANSPORT_TCP}, 0, 0, DEC|ENC, rtsp_transport }, \{ udp_multicast, UDP multicast, 0, AV_OPT_TYPE_CONST, {.i64 1 RTSP_LOWER_TRANSPORT_UDP_MULTICAST}, 0, 0, DEC, rtsp_transport },{ http, HTTP tunneling, 0, AV_OPT_TYPE_CONST, {.i64 (1 RTSP_LOWER_TRANSPORT_HTTP)}, 0, 0, DEC, rtsp_transport },{ https, HTTPS tunneling, 0, AV_OPT_TYPE_CONST, {.i64 (1 RTSP_LOWER_TRANSPORT_HTTPS )}, 0, 0, DEC, rtsp_transport },RTSP_FLAG_OPTS(rtsp_flags, set RTSP flags),{ listen, wait for incoming connections, 0, AV_OPT_TYPE_CONST, {.i64 RTSP_FLAG_LISTEN}, 0, 0, DEC, rtsp_flags },{ prefer_tcp, try RTP via TCP first, if available, 0, AV_OPT_TYPE_CONST, {.i64 RTSP_FLAG_PREFER_TCP}, 0, 0, DEC|ENC, rtsp_flags },{ satip_raw, export raw MPEG-TS stream instead of demuxing, 0, AV_OPT_TYPE_CONST, {.i64 RTSP_FLAG_SATIP_RAW}, 0, 0, DEC, rtsp_flags },RTSP_MEDIATYPE_OPTS(allowed_media_types, set media types to accept from the server),{ min_port, set minimum local UDP port, OFFSET(rtp_port_min), AV_OPT_TYPE_INT, {.i64 RTSP_RTP_PORT_MIN}, 0, 65535, DEC|ENC },{ max_port, set maximum local UDP port, OFFSET(rtp_port_max), AV_OPT_TYPE_INT, {.i64 RTSP_RTP_PORT_MAX}, 0, 65535, DEC|ENC },{ listen_timeout, set maximum timeout (in seconds) to wait for incoming connections (-1 is infinite, imply flag listen), OFFSET(initial_timeout), AV_OPT_TYPE_INT, {.i64 -1}, INT_MIN, INT_MAX, DEC },{ timeout, set timeout (in microseconds) of socket I/O operations, OFFSET(stimeout), AV_OPT_TYPE_INT64, {.i64 0}, INT_MIN, INT64_MAX, DEC },COMMON_OPTS(),{ user_agent, override User-Agent header, OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str LIBAVFORMAT_IDENT}, 0, 0, DEC },{ NULL },
};现在剩余3项了回顾下 av_dict_set(opts, timeout, 10000000, 0); //设置超时断开连接时间 usav_dict_set(opts, rtsp_transport, tcp, 0); //设置rtsp以tcp/udp方式打开av_dict_set(opts, udp, 0, 0); //设置常量不会生效过滤掉了这剩余的3项刚刚好在这里面而且前2项还刚好是非常量其分别映射到了RTSPState类对象的stimeout成员和lower_transport_mask成员。第3项也是最后一项是常量不让设置过滤掉了。
同样其参数配置对象流程图如下
所以当 av_opt_set_dict(s-priv_data, tmp)执行完毕tmp指向为NULL。
在avformat_open_input最后看怎么处理
此时tmp是NULL这样就把avformat_open_input传入的字典容器对象释放了。 如果瞎写的参数那么此时tmp就会保存你这瞎写的参数不为NULL这个时候avformat_open_input调用返回后你可以把它输入的字典容器类对象里的键值对打印出来看看还剩余哪些参数没有配置成功
3.小结
可配参数业务类比如AVFormatContext/AVCodecContext/RTSPState等类与AVClass、AVOption的关系: 可配参数业务类与AVClass类是组合关系与AVClass类中的AVOption成员才是具有千丝万缕的关系——AVOption的offset偏移就是指的是该参数在可配参数业务类中的偏移量。这个也是参数最终达到的内存地址。