avconv -i input_file.mp4 -vcodeccopy -acodeccopy -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();这两行代码对应cmdutils.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,——OptionContextint 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 copy//对应的 opt和 argv[optindex]为 “acodec” “copy” 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_strdup(p? p+1:""); //dst指针指向数组新增的SpecifierOpt中的 u地址 //此时dstcount的值已经变作新数组的长度,亦即原数组长度+1 dst =&(*so)[*dstcount-1].u;