ffmpeg面向对象——参数配置秘密探索及其设计模式
目录概览
- 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       (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; 
这个是参数抽象出来的类,里面包含了各种信息,其中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;
}
 
有几点要关注:
 (1)av_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 = 1<<20 }, 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 (PTS==0)", 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 shouldn't 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偏移就是指的是该参数在可配参数业务类中的偏移量。这个也是参数最终达到的内存地址。
相关文章:
ffmpeg面向对象——参数配置秘密探索及其设计模式
目录概览 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 类…...
华为eNSP使用详解
eNSP(Enterprise Network Simulation Platform)是华为提供的一款网络仿真平台,它允许用户在没有真实设备的情况下进行网络实验和学习网络技术。eNSP可以模拟各种网络设备,如交换机、路由器、防火墙等,并支持创建多种网…...
一文入门生成式AI(理解ChatGPT的原理)
一、什么是生成式AI? 以ChatGPT为代表的生成式AI,是对已有的数据和知识进行向量化的归纳,总结出数据的联合概率。从而在生成内容时,根据用户需求,结合关联字词的概率,生成新的内容。 可以这么联想&#x…...
C# 中Faker
在 C# 中,Faker 类通常用于生成模拟数据(也称为虚拟数据、测试数据),这对于开发、测试以及演示应用程序非常有用。一个流行的库叫做 Faker,它提供了一种简单的方式来生成各种随机数据。 安装 Faker 库 要使用 Faker …...
数据权限的设计与实现系列9——前端筛选器组件Everright-filter集成框架开发2
功能实现  规则转换为 SQL 片段 规则解析 首先我们来构造一个典型的规则,包括两个条件组,每个组由两个条件组成,由且与或两种逻辑关系,如下图: 然后看看生成的规则,如下: {"filt…...
鸿蒙Harmony-Next 徒手撸一个日历控件
本文将介绍如何使用鸿蒙Harmony-Next框架实现一个自定义的日历控件。我们将创建一个名为CalendarView的组件(注意,这里不能叫 Calendar因为系统的日历叫这个),它具有以下功能: 显示当前月份的日历支持选择日期显示农历日期可以切换上一月和下一月 组件…...
直播音频解决方案
音频解决方案公司具体解决的是什么样的问题?什么样的客户需要找音频方案公司?相信还是有很多人不是很了解。音频解决方案公司工作就像是为音频设备“量身定制衣服”,帮助客户解决各种音频相关的问题。无论你是音响制造商、会议设备商、耳机品…...
Git基本用法总结
设置全局用户名 git config --global user.name xxx #设置全局用户名 设置全局邮箱地址 git config --global user.email xxxxxx.com #设置全局邮箱地址 查看所有的 Git 配置,包括用户信息 git config --list #查看所有的 Git 配置,包括用户信…...
SQLite的入门级项目学习记录(四)
性能评估和测试 规划项目 1、框架选择:前端交互和线程控制用pyside,SQLite作为数据库支持。 2、预估数据量:每秒10个数据,每个月约26000000(26M)条。 3、压力测试:首先用python脚本创建一个数据…...
Docker工作目录迁移
文章目录 前言一、迁移步骤1.停掉docker服务2.创建存储目录3.迁移docker数据4.备份5.添加软链接6.重启docker服务,测试 总结 前言 安装docker,默认的情况容器的默认存储路径会存储系统盘的 /var/lib/docker 目录下,系统盘一般默认 50G&#…...
【多维动态规划】64. 最小路径和(面试真题+面试官调整后的题目)
64. 最小路径和 难度:中等 力扣地址:https://leetcode.cn/problems/minimum-path-sum/description/ 1. 原题以及解法 1.1 题目 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和…...
Web后端开发技术:RESTful 架构详解
RESTful 是一种基于 REST(表述性状态转移,Representational State Transfer)架构风格的 API 设计方式,通常用于构建分布式系统,特别是在 Web 应用开发中广泛应用。REST 是一种轻量级的架构模式,利用标准的 …...
【Fastapi】参数获取,json和query
【Fastapi】参数获取,json和query 前言giteegithub query形式json传递同步方法使用json 前言 花了半个月的时间看了一本小说,懈怠了…今天更新下fastapi框架的参数获取 gitee https://gitee.com/zz1521145346/fastapi_frame.git github https://git…...
【Node.js】初识微服务
概述 Node.js 的微服务架构是一种通过将应用程序分解为独立的、松耦合的小服务的方式进行系统设计。 每个微服务负责处理一个特定的业务功能,并且这些服务可以独立开发、部署、扩展和管理,并且可以通讯。 它的核心思想就是解耦。 微服务和微前端是类…...
React项目实战(React后台管理系统、TypeScript+React18)
### 项目地址:(线上发布) (1)别人的项目地址 gitgitee.com:zqingle/lege-react-management.git (2)我自己的项目地址 gitgitee.com:huihui-999/lege-react-management.git ### B站讲解视频地址 https://www.bilibili.com/video/BV1FV4y157Zx?p37&spm_id_frompageDrive…...
【专题】2024中国生物医药出海现状与趋势蓝皮书报告合集PDF分享(附原数据表)
原文链接:https://tecdat.cn/?p37719 出海已成为中国医药产业实现提速扩容的重要途径。目前,中国医药产业发展态势良好,创新能力不断增强,然而也面临着医保政策改革和带量集采带来的压力。政府积极出台多项政策支持医药企业出海…...
【iOS】KVC
文章目录 KVC的定义 容器类中KVC的实现 KVC设值 KVC取值 KVC使用KeyPath KVC处理异常 KVC处理设值nil异常 KVC处理UndefinedKey异常 KVC处理数值和结构体类型属性 KVC键值验证 KVC处理集合 简单集合运算符 对象运算符 KVC处理字典 KVC应用 动态地取值和设值 用…...
【2024年华为杯研究生数学建模竞赛C题】完整论文与代码
这里写目录标题 基于数据驱动下磁性元件的磁芯损耗建模一、问题重述1.1问题背景1.2问题回顾 问题分析与模型假设模型建立与求解 基于数据驱动下磁性元件的磁芯损耗建模 一、问题重述 1.1问题背景 在现代电力电子和变压器设计中,磁性元件是确保能量高效传递和系统稳…...
svn回退到以前历史版本修改并上传
svn回退到以前版本,并在以前版本上修改代码后,上传到svn库当中,如下步骤: 3、 以回退到版本号4为例:选中版本号4,右键->Revert to this version,在出现的对话框中 点击yes! 4、 5、...
fiddler抓包07_抓IOS手机请求
课程大纲 前提:电脑和手机连接同一个局域网 (土小帽电脑和手机都连了自己的无线网“tuxiaomao”。) 原理如下: 电脑浏览器抓包时,直接就是本机网络。手机想被电脑Fiddler抓包,就要把Fiddler变成手机和网络…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
