(一)ffmpeg主要函数源码剖析:ffmpeg

it2022-05-05  131

FFmpeg 命令使用形式:

ffmpeg [global_options] {[input_file_options] -i input_file}...{[output_file_options] output_file}... 例如:从yang.mp4中提取wav音频 ffmpeg -i yang.mp4 -f wav -ar 16000 yang.wav

先看ffmpeg的主函数入口 函数位于fftools/ffmpeg.c

int main(int argc, char **argv) { int i, ret; BenchmarkTimeStamps ti; init_dynload(); register_exit(ffmpeg_cleanup); setvbuf(stderr,NULL,_IONBF,0); /* win32 runtime needs this */ av_log_set_flags(AV_LOG_SKIP_REPEATED); parse_loglevel(argc, argv, options); if(argc>1 && !strcmp(argv[1], "-d")){ run_as_daemon=1; av_log_set_callback(log_callback_null); argc--; argv++; } #if CONFIG_AVDEVICE avdevice_register_all(); #endif avformat_network_init(); show_banner(argc, argv, options); /* parse options and open all input/output files */ ret = ffmpeg_parse_options(argc, argv); //函数调用 if (ret < 0) exit_program(1); if (nb_output_files <= 0 && nb_input_files == 0) { show_usage(); av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n", program_name); exit_program(1); } /* file converter / grab */ if (nb_output_files <= 0) { av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n"); exit_program(1); } // if (nb_input_files == 0) { // av_log(NULL, AV_LOG_FATAL, "At least one input file must be specified\n"); // exit_program(1); // } for (i = 0; i < nb_output_files; i++) { if (strcmp(output_files[i]->ctx->oformat->name, "rtp")) want_sdp = 0; } current_time = ti = get_benchmark_time_stamps(); if (transcode() < 0) exit_program(1); if (do_benchmark) { int64_t utime, stime, rtime; current_time = get_benchmark_time_stamps(); utime = current_time.user_usec - ti.user_usec; stime = current_time.sys_usec - ti.sys_usec; rtime = current_time.real_usec - ti.real_usec; av_log(NULL, AV_LOG_INFO, "bench: utime=%0.3fs stime=%0.3fs rtime=%0.3fs\n", utime / 1000000.0, stime / 1000000.0, rtime / 1000000.0); } av_log(NULL, AV_LOG_DEBUG, "%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n", decode_error_stat[0], decode_error_stat[1]); if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1]) exit_program(69); exit_program(received_nb_signals ? 255 : main_return_code); return main_return_code; }

1、函数调用的主要流程图:

2、ffmpeg_parse_options()函数分析:

函数位于fftools/ffmpeg_opt.c

int ffmpeg_parse_options(int argc, char **argv) { OptionParseContext octx; uint8_t error[128]; int ret; memset(&octx, 0, sizeof(octx)); /* split the commandline into an internal representation */ ret = split_commandline(&octx, argc, argv, options, groups, FF_ARRAY_ELEMS(groups)); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error splitting the argument list: "); goto fail; } /* apply global options */ ret = parse_optgroup(NULL, &octx.global_opts); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error parsing global options: "); goto fail; } /* configure terminal and setup signal handlers */ term_init(); /* open input files */ ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error opening input files: "); goto fail; } /* create the complex filtergraphs */ ret = init_complex_filters(); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error initializing complex filters.\n"); goto fail; } /* open output files */ ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file); if (ret < 0) { av_log(NULL, AV_LOG_FATAL, "Error opening output files: "); goto fail; } check_filter_outputs(); fail: uninit_parse_context(&octx); if (ret < 0) { av_strerror(ret, error, sizeof(error)); av_log(NULL, AV_LOG_FATAL, "%s\n", error); } return ret; }

调用了split_commandline()函数以及两次open_files()函数,分别打开输入输出文件。

下面先进行split_commandline()函数的分析。 先看split_commandline()源码:

int split_commandline(OptionParseContext *octx, int argc, char *argv[], const OptionDef *options, const OptionGroupDef *groups, int nb_groups) { int optindex = 1; int dashdash = -2; /* perform system-dependent conversions for arguments list */ prepare_app_arguments(&argc, &argv); init_parse_context(octx, groups, nb_groups); //分配内存空间,OptionGroupDef 与 OptionParseContext 建立起联系 av_log(NULL, AV_LOG_DEBUG, "Splitting the commandline.\n"); //下面开始解析参数,分别是①--XX XXX形式,跳过;②-XX XXX形式,输出文件;③-i XXX形式,输入文件 while (optindex < argc) { const char *opt = argv[optindex++], *arg; const OptionDef *po; int ret; av_log(NULL, AV_LOG_DEBUG, "Reading option '%s' ...", opt); //开始判断命令的参数 //如果参数的形式是:--X XXX ,两杠 //那么选择跳过,continue if (opt[0] == '-' && opt[1] == '-' && !opt[2]) { dashdash = optindex; continue; } //如果不是以“--”开头,那么接着判断下面这种形式:输出文件 /* unnamed group separators, e.g. output filename */ if (opt[0] != '-' || !opt[1] || dashdash+1 == optindex) { //finish_group作用就是把opt参数信息保存到octx结构体中 finish_group(octx, 0, opt); av_log(NULL, AV_LOG_DEBUG, " matched as %s.\n", groups[0].name); continue; } //如果上面的都不是,那么可以断定是标准形式了,指针++移动到下一个 //opt++的目的是为了去除掉- opt++; #define GET_ARG(arg) \ do { \ arg = argv[optindex++]; \ if (!arg) { \ av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'.\n", opt);\ return AVERROR(EINVAL); \ } \ } while (0) //看原有的注释就知道了,这是开始解析-i参数 /* named group separators, e.g. -i */ if ((ret = match_group_separator(groups, nb_groups, opt)) >= 0) { //GET_ARG(arg)作用就是检查argue是否为空,只是预防参数为空而作的一个保护检测措施 GET_ARG(arg); //finish_group作用就是把arg参数信息保存到octx结构体中 //解析完-i参数的输入文件路径后,就保存起来 finish_group(octx, ret, arg); av_log(NULL, AV_LOG_DEBUG, " matched as %s with argument '%s'.\n", groups[ret].name, arg); continue; } //到此就是开始解析option的部分了 /* normal options */ //find_option函数就是在定义的options中寻找opt参数,找到返回 po = find_option(options, opt); if (po->name) { if (po->flags & OPT_EXIT) { /* optional argument, e.g. -h */ arg = argv[optindex++]; } else if (po->flags & HAS_ARG) { GET_ARG(arg); } else { arg = "1"; } //判断opt的类型,存放到octx相应的成员变量中 add_opt(octx, po, opt, arg); av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with " "argument '%s'.\n", po->name, po->help, arg); continue; } //AVOptions :专属参数,对于ffmpeg没有的参数,那么可能是专属参数 /* AVOptions */ if (argv[optindex]) { ret = opt_default(NULL, opt, argv[optindex]); if (ret >= 0) { av_log(NULL, AV_LOG_DEBUG, " matched as AVOption '%s' with " "argument '%s'.\n", opt, argv[optindex]); optindex++; continue; } else if (ret != AVERROR_OPTION_NOT_FOUND) { av_log(NULL, AV_LOG_ERROR, "Error parsing option '%s' " "with argument '%s'.\n", opt, argv[optindex]); return ret; } } //匹配 -no 参数 /* boolean -nofoo options */ if (opt[0] == 'n' && opt[1] == 'o' && (po = find_option(options, opt + 2)) && po->name && po->flags & OPT_BOOL) { add_opt(octx, po, opt, "0"); av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with " "argument 0.\n", po->name, po->help); continue; } //若上面的都没有相应匹配,返回err av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'.\n", opt); return AVERROR_OPTION_NOT_FOUND; } //循坏结束 if (octx->cur_group.nb_opts || codec_opts || format_opts || resample_opts) av_log(NULL, AV_LOG_WARNING, "Trailing options were found on the " "commandline.\n"); av_log(NULL, AV_LOG_DEBUG, "Finished splitting the commandline.\n"); return 0; }

上面的相关参数做个大体分析:

* 解析命令行参数 octx: 通过解析将命令行中的参数分别存入此octx结构对应成员变量里面 options: 定义了默认参数的结构体 groups: 保存输入文件和输出文件相关参数

下面来看下结构体OptionParseContext *octx

typedef struct OptionParseContext { OptionGroup global_opts; // 全局命令分组 // 输入和输出的命令分组 (groups[0] 存放输出文件相关参数,groups[1] 存放输入文件相关参数) OptionGroupList *groups; int nb_groups; /* 临时数组,存储输出、输入相关参数 */ /* parsing state */ OptionGroup cur_group; } OptionParseContext;

默认参数options的结构体

typedef struct OptionDef { const char *name; int flags; #define HAS_ARG 0x0001 #define OPT_BOOL 0x0002 #define OPT_EXPERT 0x0004 #define OPT_STRING 0x0008 #define OPT_VIDEO 0x0010 #define OPT_AUDIO 0x0020 #define OPT_INT 0x0080 #define OPT_FLOAT 0x0100 #define OPT_SUBTITLE 0x0200 #define OPT_INT64 0x0400 #define OPT_EXIT 0x0800 #define OPT_DATA 0x1000 #define OPT_PERFILE 0x2000 /* the option is per-file (currently ffmpeg-only). implied by OPT_OFFSET or OPT_SPEC */ #define OPT_OFFSET 0x4000 /* option is specified as an offset in a passed optctx */ #define OPT_SPEC 0x8000 /* option is to be stored in an array of SpecifierOpt. Implies OPT_OFFSET. Next element after the offset is an int containing element count in the array. */ #define OPT_TIME 0x10000 #define OPT_DOUBLE 0x20000 #define OPT_INPUT 0x40000 #define OPT_OUTPUT 0x80000 union { void *dst_ptr; int (*func_arg)(void *, const char *, const char *); size_t off; } u; const char *help; const char *argname; } OptionDef;

后续研究再接着更新。


最新回复(0)