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;
}
函数位于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;
后续研究再接着更新。