/**
* Parse the command line arguments.
*
* @param optctx an opaque options context
* @param argc number of command line arguments
* @param argv values of command line arguments
* @param options Array with the definitions required to interpret every
* option of the form: -option_name [argument]
* @param parse_arg_function Name of the function called to process every
* argument without a leading option name flag. NULL if such arguments do
* not have to be processed.
*/
void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
void (* parse_arg_function)(void *optctx, const char*));
main函数会传递argc,argv等命令行参数给parse_options,argc是参数个数,argv是二维数组保存参数内容,options是ffplay提供的可供选择的参数设置数组,而parse_arg_function将被static void opt_input_file(void *optctx, const char *filename)函数指针赋值,保存输入文件
相关的options参数如下:
static const OptionDef options[] = {
CMDUTILS_COMMON_OPTIONS
{ "x", HAS_ARG, { .func_arg = opt_width }, "force displayed width", "width" },
{ "y", HAS_ARG, { .func_arg = opt_height }, "force displayed height", "height" },
{ "s", HAS_ARG | OPT_VIDEO, { .func_arg = opt_frame_size }, "set frame size (WxH or abbreviation)", "size" },
{ "fs", OPT_BOOL, { &is_full_screen }, "force full screen" },
{ "an", OPT_BOOL, { &audio_disable }, "disable audio" },
{ "vn", OPT_BOOL, { &video_disable }, "disable video" },
{ "sn", OPT_BOOL, { &subtitle_disable }, "disable subtitling" },
{ "ast", OPT_STRING | HAS_ARG | OPT_EXPERT, { &wanted_stream_spec[AVMEDIA_TYPE_AUDIO] }, "select desired audio stream", "stream_specifier" },
{ "vst", OPT_STRING | HAS_ARG | OPT_EXPERT, { &wanted_stream_spec[AVMEDIA_TYPE_VIDEO] }, "select desired video stream", "stream_specifier" },
{ "sst", OPT_STRING | HAS_ARG | OPT_EXPERT, { &wanted_stream_spec[AVMEDIA_TYPE_SUBTITLE] }, "select desired subtitle stream", "stream_specifier" },
{ "ss", HAS_ARG, { .func_arg = opt_seek }, "seek to a given position in seconds", "pos" },
{ "t", HAS_ARG, { .func_arg = opt_duration }, "play \"duration\" seconds of audio/video", "duration" },
{ "bytes", OPT_INT | HAS_ARG, { &seek_by_bytes }, "seek by bytes 0=off 1=on -1=auto", "val" },
{ "seek_interval", OPT_FLOAT | HAS_ARG, { &seek_interval }, "set seek interval for left/right keys, in seconds", "seconds" },
{ "nodisp", OPT_BOOL, { &display_disable }, "disable graphical display" },
{ "noborder", OPT_BOOL, { &borderless }, "borderless window" },
{ "alwaysontop", OPT_BOOL, { &alwaysontop }, "window always on top" },
{ "volume", OPT_INT | HAS_ARG, { &startup_volume}, "set startup volume 0=min 100=max", "volume" },
{ "f", HAS_ARG, { .func_arg = opt_format }, "force format", "fmt" },
{ "pix_fmt", HAS_ARG | OPT_EXPERT | OPT_VIDEO, { .func_arg = opt_frame_pix_fmt }, "set pixel format", "format" },
{ "stats", OPT_BOOL | OPT_EXPERT, { &show_status }, "show status", "" },
{ "fast", OPT_BOOL | OPT_EXPERT, { &fast }, "non spec compliant optimizations", "" },
{ "genpts", OPT_BOOL | OPT_EXPERT, { &genpts }, "generate pts", "" },
{ "drp", OPT_INT | HAS_ARG | OPT_EXPERT, { &decoder_reorder_pts }, "let decoder reorder pts 0=off 1=on -1=auto", ""},
{ "lowres", OPT_INT | HAS_ARG | OPT_EXPERT, { &lowres }, "", "" },
{ "sync", HAS_ARG | OPT_EXPERT, { .func_arg = opt_sync }, "set audio-video sync. type (type=audio/video/ext)", "type" },
{ "autoexit", OPT_BOOL | OPT_EXPERT, { &autoexit }, "exit at the end", "" },
{ "exitonkeydown", OPT_BOOL | OPT_EXPERT, { &exit_on_keydown }, "exit on key down", "" },
{ "exitonmousedown", OPT_BOOL | OPT_EXPERT, { &exit_on_mousedown }, "exit on mouse down", "" },
{ "loop", OPT_INT | HAS_ARG | OPT_EXPERT, { &loop }, "set number of times the playback shall be looped", "loop count" },
{ "framedrop", OPT_BOOL | OPT_EXPERT, { &framedrop }, "drop frames when cpu is too slow", "" },
{ "infbuf", OPT_BOOL | OPT_EXPERT, { &infinite_buffer }, "don't limit the input buffer size (useful with realtime streams)", "" },
{ "window_title", OPT_STRING | HAS_ARG, { &window_title }, "set window title", "window title" },
{ "left", OPT_INT | HAS_ARG | OPT_EXPERT, { &screen_left }, "set the x position for the left of the window", "x pos" },
{ "top", OPT_INT | HAS_ARG | OPT_EXPERT, { &screen_top }, "set the y position for the top of the window", "y pos" },
#if CONFIG_AVFILTER
{ "vf", OPT_EXPERT | HAS_ARG, { .func_arg = opt_add_vfilter }, "set video filters", "filter_graph" },
{ "af", OPT_STRING | HAS_ARG, { &afilters }, "set audio filters", "filter_graph" },
#endif
{ "rdftspeed", OPT_INT | HAS_ARG| OPT_AUDIO | OPT_EXPERT, { &rdftspeed }, "rdft speed", "msecs" },
{ "showmode", HAS_ARG, { .func_arg = opt_show_mode}, "select show mode (0 = video, 1 = waves, 2 = RDFT)", "mode" },
{ "default", HAS_ARG | OPT_AUDIO | OPT_VIDEO | OPT_EXPERT, { .func_arg = opt_default }, "generic catch all option", "" },
{ "i", OPT_BOOL, { &dummy}, "read specified file", "input_file"},
{ "codec", HAS_ARG, { .func_arg = opt_codec}, "force decoder", "decoder_name" },
{ "acodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &audio_codec_name }, "force audio decoder", "decoder_name" },
{ "scodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &subtitle_codec_name }, "force subtitle decoder", "decoder_name" },
{ "vcodec", HAS_ARG | OPT_STRING | OPT_EXPERT, { &video_codec_name }, "force video decoder", "decoder_name" },
{ "autorotate", OPT_BOOL, { &autorotate }, "automatically rotate video", "" },
{ "find_stream_info", OPT_BOOL | OPT_INPUT | OPT_EXPERT, { &find_stream_info },
"read and decode the streams to fill missing information with heuristics" },
{ "filter_threads", HAS_ARG | OPT_INT | OPT_EXPERT, { &filter_nbthreads }, "number of filter threads per graph" },
{ NULL, },
};
这里以find_stream_info参数的设置过程,以及代码是如何分析的,进行详细说明
1)设置find_stream_info的参数:ffplay -i rtsp://admin:admin12345@192.168.18.204:554/h264/ch1/main/av_stream -fflags nobuffer -nofind_stream_info
-nofind_stream_info就是作为命令行传递的参数
OPT_BOOL代表这是一个布尔型的变量,0和1,最终将把结果赋值给find_stream_info
2)调用变量分析
void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
void (*parse_arg_function)(void *, const char*))
{
const char *opt;
int optindex, handleoptions = 1, ret;
/* perform system-dependent conversions for arguments list */
prepare_app_arguments(&argc, &argv);
/* parse options */
optindex = 1;
//循环读取每一个参数的变量内容,目前以(ffplay -i rtsp://admin:admin12345@192.168.18.204:554/h264/ch1/main/av_stream -fflags nobuffer -nofind_stream_info)作为传递参数进行分析,具体分析第五个参数 -nofind_stream_info
while (optindex < argc) {
//当optindex=6,*opt= "-nofind_stream_info"
opt = argv[optindex++];
//handleoptions=1 opt[0]= '-' opt[1] !='\0',满足条件,进入里面if函数体
if (handleoptions && opt[0] == '-' && opt[1] != '\0') {
//因为opt[1]不满足要求,不进入
if (opt[1] == '-' && opt[2] == '\0') {
handleoptions = 0;
continue;
}
//自增,去掉-, *opt= "nofind_stream_info"
opt++;
if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0)
exit_program(1);
optindex += ret;
} else {
if (parse_arg_function)
parse_arg_function(optctx, opt);
}
}
}
//关键是如何分析nofind_stream_info
int parse_option(void *optctx, const char *opt, const char *arg,
const OptionDef *options)
{
const OptionDef *po;
int ret;
//第一次传递进去*opt = 'nofind_stream_info'
po = find_option(options, opt);
//返回po->name是NULL,而且由于opt第一个字节是n,第二个字节是o,第二次进入find_option
if (!po->name && opt[0] == 'n' && opt[1] == 'o') {
/* handle 'no' bool option */
//由于指针加2,这一次传递参数是find_stream_info
po = find_option(options, opt + 2);
if ((po->name && (po->flags & OPT_BOOL)))
//name对应find_stream_info flags对应262150,这个时候,获取到参数值0
arg = "0";
} else if (po->flags & OPT_BOOL)
arg = "1";
if (!po->name)
po = find_option(options, "default");
if (!po->name) {
av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'\n", opt);
return AVERROR(EINVAL);
}
if (po->flags & HAS_ARG && !arg) {
av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'\n", opt);
return AVERROR(EINVAL);
}
ret = write_option(optctx, po, opt, arg);
if (ret < 0)
return ret;
return !!(po->flags & HAS_ARG);
}
//这个函数是在参数列表里面,查看find_stream_info对应设置的参数对象
static const OptionDef *find_option(const OptionDef *po, const char *name)
{
//返回字符:之前的字符串,如果没有,p指向整个name字符串,例如fengyuzaitu:51cto,经过strchr返回字符:所在的指针,查找失败,返回空
//第一次进入,name是nofind_stream_info,p查询失败,返回NULL,len=18,po内容也是空,说明没有匹配上参数
//第二次进入,name是find_stream_info,p查询失败,返回NULL,len=16,po这一次匹配上参数列表中
//po->name "find_stream_info"
//po->flags 262150
//po->u 指向0x00007ff63e84f0a4 {ffplayd.exe!int find_stream_info}
//po->help “read and decode the streams to fill missing information with heuristics”
const char *p = strchr(name, ':');
int len = p ? p - name : strlen(name);
while (po->name) {
if (!strncmp(name, po->name, len) && strlen(po->name) == len)
break;
po++;
}
return po;
}
保存参数到对应的变量
static int write_option(void *optctx, const OptionDef *po, const char *opt,
const char *arg)
{
/* new-style options contain an offset into optctx, old-style address of
* a global var*/
//获取到find_stream-info变量的指针地址
void *dst = po->flags & (OPT_OFFSET | OPT_SPEC) ?
(uint8_t *)optctx + po->u.off : po->u.dst_ptr;
int *dstcount;
if (po->flags & OPT_SPEC) {
SpecifierOpt **so = dst;
char *p = strchr(opt, ':');
char *str;
dstcount = (int *)(so + 1);
*so = grow_array(*so, sizeof(**so), dstcount, *dstcount + 1);
str = av_strdup(p ? p + 1 : "");
if (!str)
return AVERROR(ENOMEM);
(*so)[*dstcount - 1].specifier = str;
dst = &(*so)[*dstcount - 1].u;
}
if (po->flags & OPT_STRING) {
char *str;
str = av_strdup(arg);
av_freep(dst);
if (!str)
return AVERROR(ENOMEM);
*(char **)dst = str;
} else if (po->flags & OPT_BOOL || po->flags & OPT_INT) {
//因为是布尔变量,所以最终在这里进行转换
*(int *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT_MIN, INT_MAX);
} else if (po->flags & OPT_INT64) {
*(int64_t *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT64_MIN, INT64_MAX);
} else if (po->flags & OPT_TIME) {
*(int64_t *)dst = parse_time_or_die(opt, arg, 1);
} else if (po->flags & OPT_FLOAT) {
*(float *)dst = parse_number_or_die(opt, arg, OPT_FLOAT, -INFINITY, INFINITY);
} else if (po->flags & OPT_DOUBLE) {
*(double *)dst = parse_number_or_die(opt, arg, OPT_DOUBLE, -INFINITY, INFINITY);
} else if (po->u.func_arg) {
int ret = po->u.func_arg(optctx, opt, arg);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR,
"Failed to set value '%s' for option '%s': %s\n",
arg, opt, av_err2str(ret));
return ret;
}
}
if (po->flags & OPT_EXIT)
exit_program(0);
return 0;
}
总结:本文仅仅是针对OPT_BOOL这种类型的参数进行了剖析,希望能够举一反三灵活运行