1. 如何用nginx+ffmpeg实现苹果HLS协议
//处理window的情况
prepare_app_arguments(&argc,&argv);
optindex=1;
//循环处理命令行参数
while(optindex< argc){
opt = argv[optindex++];
//如果传入的参数是“-”打头
if(handleoptions&& opt[0]=='-'&& opt[1]!='\0'){
//如果传入的参数是“--”打头
if(opt[1]=='-'&& opt[2]=='\0'){
handleoptions =0;
//略过
continue;
}
//丢弃第一个字符”-”
opt++;
//解析命令行参数
//eg–acodec
//对应的 opt和 argv[optindex]为 “acodec” “”
if((ret= parse_option(optctx, opt, argv[optindex], options))<0)
exit_program(1);
optindex += ret;
}else{
//此时 opt的值为输出文件名如 test.ts
if(parse_arg_function)
//处理输出文件的相关内容,如 struct OutputFile的初始化
parse_arg_function(optctx, opt);
}
}
在此,ffmpeg 默认的处理输出文件名参数为:
staticvoid opt_output_file(void*optctx,constchar*filename)
2.2处理命令行参数
int parse_option(void*optctx,constchar*opt,constchar*arg, const OptionDef*options)
2.2.1查找匹配的Option
const OptionDef*po;
int bool_val=1;
int*dstcount;
void*dst;
//从全局变量options数组中查找opt对应的OptionDef
po = find_option(options, opt);
//如果未找到且以”no”打头
//不需要传递参数的选项是bool类型的选项,默认为true
//如果需要设置为false,则需要加上”no”,以下的if则是处理这种情况
if(!po->name&& opt[0]=='n'&& opt[1]=='o'){
//去掉开头的”no”重新查找
po = find_option(options, opt +2);
//如果仍未找到或者找到的选项不是bool类型
if(!(po->name&&(po->flags& OPT_BOOL)))
//报错
goto unknown_opt;
bool_val =0;
}
//如果未找到且不是以上的”no”打头情况
if(!po->name)
//寻找默认配置进行处理
po = find_option(options,"default");
//default配置也未找到,报错
if(!po->name){
unknown_opt:
av_log(NULL, AV_LOG_ERROR,"Unrecognizedoption '%s'\n", opt);
return AVERROR(EINVAL);
}
//如果选项必须有参数但是没有可用的参数,报错
if(po->flags& HAS_ARG&&!arg){
av_log(NULL, AV_LOG_ERROR,"Missingargument for option '%s'\n", opt);
return AVERROR(EINVAL);
}
现在来查看一下find_option方法的实现:
staticconst OptionDef*find_option(const OptionDef*po,constchar*name)
根据name在全局变量options数组中查找OptionDef
//这里先处理参数带有冒号的情况。比如 codec:a codec:v等
constchar*p= strchr(name,':');
int len= p? p- name: strlen(name);
//遍历options
while(po->name!=NULL){
//比较option的名称与name是否相符。
//这里 codec 与 codec:a相匹配
if(!strncmp(name, po->name, len)&& strlen(po->name)== len)
break;
po++;
}
return po;
2.2.2寻找选项地址
以下的代码用于将 void*dst变量赋值。让dst指向需要赋值的选项地址。
//如果选项在OptionContext中是以偏移量定位或者是 SpecifierOpt*数组的类型
dst= po->flags&(OPT_OFFSET| OPT_SPEC)?
//dst指向从 optctx地址偏移u.off的位置
(uint8_t*)optctx+ po->u.off:
//否则直接指向 OptionDef结构中定义的位置
po->u.dst_ptr;
//如果选项是SpecifierOpt*数组
if(po->flags& OPT_SPEC){
//数组首地址
SpecifierOpt **so= dst;
char*p= strchr(opt,':');
//这里是取得数组的当前长度+1
//请回顾 1.1中的描述:
//SpecifierOpt *xxx;
//int nb_xxx;
//当so指向xxx时刻,so+1指向nb_xxx
dstcount =(int*)(so+1);
//动态增长数组
*so = grow_array(*so,sizeof(**so), dstcount,*dstcount+1);
//将创建的SpecifierOpt结构体中的specifier赋值
//如codec:v 则specifier值为 “v”
(*so)[*dstcount-1].specifier= av_strp(p? p+1:"");
//dst指针指向数组新增的SpecifierOpt中的 u地址
//此时dstcount的值已经变作新数组的长度,亦即原数组长度+1
dst =&(*so)[*dstcount-1].u;
2. 【NGINX入门】3.Nginx的缓存服务器proxy_cache配置
本文介绍NGINX缓存机制,配置和参数说明。
如图所示,nginx缓存,可以在一定程度上,减少源服务器的处理请求压力。因为静态文件(比如css,js, 图片)中,很多都是不经常更新的。nginx使用proxy_cache将用户的请求缓存到本地一个目录。下一个相同请求可以直接调取缓存文件,就不用去请求服务器了。毕竟,IO密集型服务的处理是nginx的强项。
Nginx的缓存加速功能是由proxy_cache(用于反向代理和静态缓存)和fastcgi_cache(PHP动态缓存)两个功能模块完成。
Nginx缓存特点:
先上个例子:
因为我是在一台服务器上做试验,所以用了两个端口 80 和 90 进行模拟两台服务器之间的交互。
接下来讲一下配置项:
这里我设置了 图片 、 css 、 js 静态资源进行缓存。
当用户输入 http://wangxiaokai.vip 域名时,解析得到 ip:port 的访问地址。 port 默认为80。所以页面请求会被当前server截取到,进行请求处理。
当解析到上述文件名结尾的静态资源,会到缓存区获取静态资源。
如果获取到对应资源,则直接返回数据。
如果获取不到,则将请求转发给 proxy_pass 指向的地址进行处理。
这里直接处理 90 端口接受到的请求,到服务器本地目录 /mnt/blog 下抓取资源进行响应。
细心的读者应该发现,我在第二段例子里,留了个彩蛋 add_header wall "hey!guys!give me a star." 。
add_header 是用于在报头设置自定义的信息。
所以,如果缓存有效的话,那么静态资源返回的报头,一定会带上这个信息。
(1)Nginx系列教程(3)nginx缓存服务器上的静态文件
https://yq.aliyun.com/articles/752967
(2)proxy_cache
nginx 反向代理之 proxy_cache https://www.cnblogs.com/yyxianren/p/10832172.html
(3)Nginx使用upstream负载均衡和proxy_cache缓存实现反向代理
https://blog.51cto.com/13770206/2163952
3. 双层nginx 架构之分发层与应用层
一般情况下会部署多个nginx,在里面都会放一些缓存,但是命中率比较低。因此,会采用分发层与应用层的双层nginx架构方案。
上图已经很清楚的描述了nginx 缓存命中率低下的原因,为了解决这个问题,采用相同的id 路由到相同的nginx 服务器中就可以了。
上图描述了双层nginx 架构:分发层与应用层。
最前端的nginx服务器,被称之为分发服务器;后端的nginx服务器,就称之为应用服务器。
在实际的生产环境中,可以大幅度提升nginx本地缓存命中率,减少redis后端的压力,大量提升服务性能。
4. nginx 缓存机制
Nginx缓存的基本思路
基本思想是利用客户访问的时间局部性原理,对客户已经访问过的内容在Nginx服务器本地建立副本,这样在一段时间内再次访问该数据,就不需要通过Nginx服务器再次向后端服务器发出请求,所以能够减少Nginx服务器与后端服务器之间的网络流量,减轻网络拥塞,同时还能减小数据传输延迟,提高用户访问速度。同时,当后端服务器宕机时,Nginx服务器上的副本资源还能够回应相关的用户请求,这样能够提高后端服务器的鲁棒性。
对于缓存,我们大概会有以下问题:
(1)缓存文件放在哪儿?
(2)缓存的空间大小是否可以限定?
(3)如何指定哪些请求被缓存?
(4)缓存的有效期是多久?
(5)对于某些请求,是否可以不走缓存?
解决这些问题后,nginx的缓存也就基本配置完成了,下面看详细配置过程
开启缓存
要使用缓存,首先要使用 proxy_cache_path 这个指令(必须放在 http 上下文的顶层位置),然后在目标上下文中使用 proxy_cache 指令
配置示例
proxy_cache_path 有两个必填参数,第一个参数为 缓存目录,第二个参数keys_zone指定缓存名称和占用内存空间的大小(注:示例中的10m是对内存中缓存内容元数据信息大小的限制,如果想限制缓存总量大小,需要用 max_size 参数)
proxy_cache 的参数为之前指定的缓存名称
缓存管理的相关进程
在缓存工作中有两个附加进程:
(1)缓存管理器
定期检查缓存状态,看缓存总量是否超出限制,如果超出,就移除其中最少使用的部分
(2)缓存加载器
加载器只在nginx启动后运行一次,把缓存内容的元数据信息加载到内存空间,如果一次性加载全部缓存信息,会大量消耗资源,使nginx在启动后的几分钟里变慢,为避免此问题,有3种加载策略:
loader_threshold – 指定每次加载执行的时间
loader_files – 每次最多加载的数量
loader_sleeps – 每次加载的延时
例如:
proxy_cache_path /data/nginx/cache keys_zone=one:10m loader_threshold=300 loader_files=200;
指定缓存哪些请求
nginx默认会缓存所有 get 和 head 方法的请求结果,缓存的key默认使用请求字符串
(1)自定义key
例如 proxy_cache_key " request_uri cookie_nocache arg_comment;
如果任何一个参数值不为空,或者不等于0,nginx就不会查找缓存,直接进行代理转发
综合示例
nginx 缓存机制
三分钟看懂Nginx服务器的缓存原理和机制
5. nginx优化以及多级缓存
配置文件:
lua是一个小巧的脚本语言,由标准C编写而成,几乎在所有操作系统和平台上都可以编译运行。其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
应用场景:游戏开发、独立应用脚本、redis中嵌套调用实现类似事务的功能,web容器汇总处理NGINX的过滤缓存等等逻辑
OpenResty是一个基于Nginx与Lua的高性能web平台,由中国人章亦春发起,其内部集成了大量精良的Lua库、第三方模块以及大多数的依赖项。用于方便搭建能处理超高并发、扩展性极高的动态Web应用、web服务和动态网关
OpenResty简单理解成就相当于封装了NGINX,并且集成了LUA脚本,开发人员只需要简单的使用其提供了模块就可以实现相关的逻辑,而不像之前,还需要在NGINX中编写lua的脚本。
1.拉取一个openresty的镜像
2.随便构建一个容器用于拷贝配置文件
3.进入容器,查找配置文件路径(这里直接给出)
4.退出容器,复制容器中配置文件到宿主机
5.修改配置文件
1、使用Lua查询Nginx缓存,如果有缓存,则直接将缓存中的分类数据返回
2、如果Nginx缓存中没有分类数据,则通过Lua脚本查询Redis,如果Redis中有数据,则将数据存入到Nginx缓存中,并返回查询到的数据
3、如果Redis中也没有缓存,则此时通过Lua脚本查询Mysql,如果Mysql中有数据,将分类数据存入到Redis缓存,并返回数据
6. 如何用nginx+ffmpeg实现苹果HLS协议
HLS协议的理解nginx ffmpeg的编译 安装 调试,以及工具的选择 使用 gdb等
nginx模块开发
ffmpeg的开发
重点将集中在 ffmpeg 的开发上。
HLS协议的实现有很多的细节,比如我在实际的开发过程中,就面临将多种不同格式的视频源文件(来源于不同的编码器以及有不同的profile)动态切片输出。而现有能在网上找到的方式基本都是对视频文件做了预先处理,比如用ffmpeg将视频文件先转换成物理存储的mpeg2ts文件,然后用nginx进行动态切片输出。这对开发带来了很大的困难。
如果我们将问题简化的话,即 输入文件为 mp4 (isom512 , 2 channels stereo),那么最简单的实现方式是如下命令行:
avconv -i input_file.mp4 -vcodec -acodec -vbsfh264_mp4toannexb –ss00:00:00–t00:00:10 output_file.ts
然后通过 对 参数 –ss00:00:00 –t00:00:10 的调整,获得多个物理切片,提供给nginx输出。
这里需要提供一个细节,即 处理的性能。 所以在上述的命令行中,仅仅进行了 remux 而没有任何 ecode 和 decode 的操作。
我们要做的,就是将这行命令变成 可供 nginx 调用的 api。
当然,任然可以选择最简单的作法,nginx模块里面调用系统命令。不过这样子,貌似有点儿寒碜吧。呵呵。
7. windows使用nginx+rtmp推流hls时候,无法生成m3u8,ts文件的解决办法
主要问题描述:使用ffmpeg进行hls推流的时候,发现无法保存m3u8,ts文件,多度以为是路径问题的同学看这篇文章就够了。
windows搭建nginx+rtmp不在详述,各种文章都写了。这边主要针对解释一下其他文章忽略的一些点。
可以看到, hls 出现了四次,其中两个是保存m3u8和ts的文件夹。还有两个对应于ffmpeg推流命令:
请看nginx-rtmp-mole下的 README.md
8. 基于Nginx设置浏览器协商缓存过程详解
一、强缓存与协商缓存的区别
强缓存:浏览器不与服务端协商直接取浏览器缓存
协商缓存:浏览器会先向服务器确认资源的有效性后才决定是从缓存中取资源还是重新获取资源
二、协商缓存运作原理
现在有一个这样的业务情景:后端的静态资源会不定时地发生更新,而因为浏览器默认使用强缓存,会默认从浏览器缓存中取到过时的资源。
现在我们希望浏览器每次获取资源的时候都向后端确认资源是否更新,就要设置浏览器使用协商缓存
那么后端如何判断资源是否更新了呢?这时就要用到Etag和Last-Modified两项响应头。
每次收到一个静态资源的请求时,后端都将资源的最后修改时间(Last-Modified)、根据资源内容计算出来的Etag放在响应头给前端。
前端收到响应后将这两项缓存起来,然后在下次请求同样资源的时候,将这两项的内容放到If-Modified-Since和If-None-Match这两项请求头中。
服务端收到这两项后,会与资源当前生成的Etag和Last-Modified做比较,如果两者都一致,说明资源没有更新,服务端会返回304空响应;否则,说明资源有更新,服务端会将完整的资源内容返回
实现
那么如何实现这样一个复杂的过程呢?其实很简单,只要使用nginx作为静态资源的服务器,再在响应头加上Cache-Control:no-cache就可以了。
下面来分步骤实现一下
1. 使用nginx作为静态资源的服务器
在nginx的配置中,将对静态资源的请求映射到资源的磁盘路径上
http {
server {
listen 80;
...
location /picture/ {
alias D:/luozixi/tcp_test/picture/;
# alias是重定义路径
# 比如访问127.0.0.1/picture/1_new.gif,则会映射为访问D:/luozixi/tcp_test/picture/1_new.gif
# web应用根本不会收到请求,picture的请求都被nginx处理了
# alias是替换,root是拼接
autoindex on;
}
}
}
2. 重新加载nginx配置
3. 此时,请求静态资源的时候nginx会自动在response头中加上Etag和Last-Modified两项
4. 但是这时发现,如果不配置Cache-Contrl: no-cache,浏览器在下次请求这个资源的时候不会将请求发向后端,而是直接从缓存中获取资源
5. 在nginx中配置
location /picture/ {
add_header Cache-Control no-cache;
alias D:/luozixi/tcp_test/picture/;
}
6.清除浏览器缓存后第一次发起请求,会得到一个正常的200 Response,而且响应头里已经有了Cache-Control: no-cache,表示使用协商缓存
7.再次发起请求后,会发现请求头已经带上了If-Modified-Since和If-None-Match两项
8.服务端(nginx)收到这两项后,会与资源当前生成的Etag和Last-Modified做比较,如果两者都一致,说明资源没有更新,服务端会返回304空响应;否则,说明资源有更新,服务端会将完整的资源内容返回
另外,服务器验证If-Modified-Since的方式只是简单的字符串比较,即使资源的Last-Modified比If-Modified-Since要早,服务端仍认为资源有更新
9.浏览器在收到304响应后,会从浏览器缓存中取资源。因此速度非常块
三、no-cache与no-store的区别
no-cache表示不缓存过期资源,缓存会向服务器进行有效处理确认之后处理资源
而no-store才是真正的不进行缓存。
9. Nginx作为缓存服务
上一篇文章讲了Nginx作为代理服务的使用方式,这篇文章我们讲一讲Nginx作为缓存服务是怎么工作的,以及实战的使用。
先看一张图:
面对第一次客户端的应用Nginx需要从后端的服务获取数据,对于后续的请求,Nginx若进行了缓存就不再从后端服务获取数据。
语法:proxy_cache_path path [levels=levels].只能用在http中。
proxy_cache zone | off。默认是关闭的,可以用在http,server,location中。
访问zzm这个路径的时候,会返回配置文件中的spring.s项,具体值可以参考我们的启动设置:
三个后台金正分别对应6000端口,6001端口,6002端口
所以我们访问ip:port/zzm的时候会自动去访问后台
我们首先注释掉proxy_cache zzm_cache;进行访问,也就是没有缓存的情况下,访问3次:
我们可以看到没有缓存的情况下,会进行轮询访问,每次访问的结果不一样,而且我们的缓存路径什么都没有,让我想起了一首歌空空如也:
好奇的看了下目录下的文件内容:
add_header Nging-Cache "$upstream_cache_status";
当我们没有缓存的时候,我们可以看到应答会是MISS:
现在nginx.conf中加入新的配置项:
此时我们怒刷前端页面,会发现后端日志如下:
Nginx的缓存服务就讲到这里,欢迎大家指正
10. 如何用nginx+ffmpeg实现苹果HLS协议
avconv -i input_file.mp4 -vcodec -acodec -vbsfh264_mp4toannexb –ss00:00:00 –t00:00:10 output_file.ts
为例说明ffmpeg如何将命令行参数解析处理。
int main(int argc,char**argv)
{
//初始化参数容器
OptionsContext o={0};
//重置参数
reset_options(&o);
//解析参数
parse_options(&o, argc, argv, options,opt_output_file);
}
1.重置参数
staticvoid reset_options(OptionsContext*o)
依次进行了以下步骤:
1.1第一步:释放特殊类型
释放所有的 OPT_SPEC(对应struct SpecifierOpt)和 OPT_STRING (对应 char*)类型的 OptionDef
代码如下:
//指向全局变量options
const OptionDef*po= options;
//遍历options
while(po->name){
//dest指针指向当前option对应的OptionContext中的位置
void*dst=(uint8_t*)o+ po->u.off;
//判断是否是SpecifierOpt类型
if(po->flags& OPT_SPEC){
//so指向SpecifierOpt*的首地址
SpecifierOpt **so= dst;
//获得数组长度
int i,*count=(int*)(so+1);
//循环遍历SpecifierOpt*数组
for(i=0; i<*count; i++){
//释放SpecifierOpt的specifier(char*类型)
av_freep(&(*so)[i].specifier);
//如果OPT类型是字符串,释放SpecifierOpt的u.str(char*类型)
if(po->flags& OPT_STRING)
av_freep(&(*so)[i].u.str);
}
//释放SpecifierOpt*指针数组
av_freep(so);
//重置计数器
*count=0;
}
//判断是否是char*类型
elseif(po->flags& OPT_OFFSET&& po->flags& OPT_STRING)
av_freep(dst);
po++;
}
这里需要对OptionContext的内容做一些说明:
OptionContext 包含了在视频编转码过程中需要用到的参数,这些参数来自于命令行的输入。
参数在OptionContext中的存储形式有:
#defineOPT_INT 0x0080
#defineOPT_FLOAT 0x0100
#defineOPT_INT64 0x0400
#defineOPT_TIME 0x10000
#defineOPT_DOUBLE 0x20000
等,详情参见 structOptionDef
在上述代码中,主要循环释放的是OPT_SPEC(对应struct SpecifierOpt)和 OPT_STRING
在OptionContext中,OPT_SPEC类型是成对出现的,如下:
typedefstructOptionsContext{
int64_t start_time;
constchar*format;
SpecifierOpt *codec_names;
int nb_codec_names;
SpecifierOpt *audio_channels;
int nb_audio_channels;
即:
SpecifierOpt *xxx_vars;
int nb_xxx_vars; //nb_读作number_意思是xxx_vars数组的长度
然后我们来分析对SpecifierOpt*数组的遍历:
SpecifierOpt **so= dst;
int i,*count=(int*)(so+1);
for(i=0; i<*count; i++){
这里可以这么理解:
so —指向—> SpecifierOpt *xxx_vars;
so+1—指向—> int nb_xxx_vars;
so+1 的含义:so是个SpecifierOpt指针,指针+1则移动了sizeof(SpecifierOpt)的位置,即跳到nb_xxx_vars的位置。
1.2释放其他类型
av_freep(&o->stream_maps);
av_freep(&o->meta_data_maps);
av_freep(&o->streamid_map);
这里说一下 av_freep 的用法。
void av_freep(void*arg)
{
void**ptr=(void**)arg;
av_free(*ptr);
*ptr=NULL;
}
相比传统的free方法,这里主要多做了一步工作:将释放free之后,指针设置为NULL
同时,要注意到:
Object *obj;
free(obj);
等价用法为:
av_freep(&obj);
在ffmpeg中,封装了对应free的方法为:
void av_free(void*ptr)
{
#ifCONFIG_MEMALIGN_HACK
if(ptr)
free((char*)ptr-((char*)ptr)[-1]);
#else
free(ptr);
#endif
}
这里除了考虑内存对齐之外,跟传统的free方法没有任何变化。
1.3第三步:设置初始值
memset(o,0,sizeof(*o));
o->mux_max_delay =0.7;
o->recording_time= INT64_MAX;
o->limit_filesize= UINT64_MAX;
o->chapters_input_file= INT_MAX;
不需要过多解释。
o->mux_max_delay =0.7;
这一行内容以后在视频切片中会用到。可以调整到更小。
1.4重新初始化特殊参数
uninit_opts();
init_opts();
这两行代码对应cmtils.c 文件中的代码段:
struct SwsContext*sws_opts;
AVDictionary*format_opts,*codec_opts;
void init_opts(void)
{
#if CONFIG_SWSCALE
sws_opts= sws_getContext(16,16,0,16,16,0, SWS_BICUBIC,
NULL,NULL,NULL);
#endif
}
void uninit_opts(void)
{
#ifCONFIG_SWSCALE
sws_freeContext(sws_opts);
sws_opts=NULL;
#endif
av_dict_free(&format_opts);
av_dict_free(&codec_opts);
}
主要进行: SwsContext*sws_opts,AVDictionary*format_opts,*codec_opts三个全局变量的创建和释放工作。
2.解析命令行参数
void parse_options(void*optctx,int argc,char**argv,const OptionDef *options,void(*parse_arg_function)(void*,constchar*))
void*optctx,——OptionContext
int argc,——命令行参数个数
char**argv,——命令行参数列表
const OptionDef*options,——选项列表
void(*parse_arg_function)(void*,constchar*)——自定义的解析方法
2.1总览
constchar*opt;
int optindex, handleoptions=1, ret;
//处理window的情况
prepare_app_arguments(&argc,&argv);
optindex=1;
//循环处理命令行参数
while(optindex< argc){
opt = argv[optindex++];
//如果传入的参数是“-”打头
if(handleoptions&& opt[0]=='-'&& opt[1]!='\0'){
//如果传入的参数是“--”打头
if(opt[1]=='-'&& opt[2]=='\0'){
handleoptions =0;
//略过
continue;
}
//丢弃第一个字符”-”
opt++;
//解析命令行参数
//eg–acodec
//对应的 opt和 argv[optindex]为 “acodec” “”
if((ret= parse_option(optctx, opt, argv[optindex], options))<0)
exit_program(1);
optindex += ret;
}else{
//此时 opt的值为输出文件名如 test.ts
if(parse_arg_function)
//处理输出文件的相关内容,如 struct OutputFile的初始化
parse_arg_function(optctx, opt);
}
}
在此,ffmpeg 默认的处理输出文件名参数为:
staticvoid opt_output_file(void*optctx,constchar*filename)
2.2处理命令行参数
int parse_option(void*optctx,constchar*opt,constchar*arg, const OptionDef*options)
2.2.1查找匹配的Option
const OptionDef*po;
int bool_val=1;
int*dstcount;
void*dst;
//从全局变量options数组中查找opt对应的OptionDef
po = find_option(options, opt);
//如果未找到且以”no”打头
//不需要传递参数的选项是bool类型的选项,默认为true
//如果需要设置为false,则需要加上”no”,以下的if则是处理这种情况
if(!po->name&& opt[0]=='n'&& opt[1]=='o'){
//去掉开头的”no”重新查找
po = find_option(options, opt +2);
//如果仍未找到或者找到的选项不是bool类型
if(!(po->name&&(po->flags& OPT_BOOL)))
//报错
goto unknown_opt;
bool_val =0;
}
//如果未找到且不是以上的”no”打头情况
if(!po->name)
//寻找默认配置进行处理
po = find_option(options,"default");
//default配置也未找到,报错
if(!po->name){
unknown_opt:
av_log(NULL, AV_LOG_ERROR,"Unrecognizedoption '%s'\n", opt);
return AVERROR(EINVAL);
}
//如果选项必须有参数但是没有可用的参数,报错
if(po->flags& HAS_ARG&&!arg){
av_log(NULL, AV_LOG_ERROR,"Missingargument for option '%s'\n", opt);
return AVERROR(EINVAL);
}
现在来查看一下find_option方法的实现:
staticconst OptionDef*find_option(const OptionDef*po,constchar*name)
根据name在全局变量options数组中查找OptionDef
//这里先处理参数带有冒号的情况。比如 codec:a codec:v等
constchar*p= strchr(name,':');
int len= p? p- name: strlen(name);
//遍历options
while(po->name!=NULL){
//比较option的名称与name是否相符。
//这里 codec 与 codec:a相匹配
if(!strncmp(name, po->name, len)&& strlen(po->name)== len)
break;
po++;
}
return po;
2.2.2寻找选项地址
以下的代码用于将 void*dst变量赋值。让dst指向需要赋值的选项地址。
//如果选项在OptionContext中是以偏移量定位或者是 SpecifierOpt*数组的类型
dst= po->flags&(OPT_OFFSET| OPT_SPEC)?
//dst指向从 optctx地址偏移u.off的位置
(uint8_t*)optctx+ po->u.off:
//否则直接指向 OptionDef结构中定义的位置
po->u.dst_ptr;
//如果选项是SpecifierOpt*数组
if(po->flags& OPT_SPEC){
//数组首地址
SpecifierOpt **so= dst;
char*p= strchr(opt,':');
//这里是取得数组的当前长度+1
//请回顾 1.1中的描述:
//SpecifierOpt *xxx;
//int nb_xxx;
//当so指向xxx时刻,so+1指向nb_xxx
dstcount =(int*)(so+1);
//动态增长数组
*so = grow_array(*so,sizeof(**so), dstcount,*dstcount+1);
//将创建的SpecifierOpt结构体中的specifier赋值
//如codec:v 则specifier值为 “v”
(*so)[*dstcount-1].specifier= av_strp(p? p+1:"");
//dst指针指向数组新增的SpecifierOpt中的 u地址
//此时dstcount的值已经变作新数组的长度,亦即原数组长度+1
dst =&(*so)[*dstcount-1].u;