ffmpeg -i 1_cut.flv -c copy -f mp4 1.mp4
libavformat、libavcodec、libavutils
等库的主要函数,将单独写一篇文章进行分析源文件:fftools/ffmpeg.c
功能:完成动态库加载、退出函数注册、日志输出、以及avdevice、avformat、network等模块的初始化,命令行参数解析,转码转封装等操作
下面开启main函数源码分析
init_dynload
函数,在window
系统下调用了SetDllDirectory("")
函数将当前路径从动态库搜索中删除,这么做的目的是为了避免动态库恶意副本供给,详细可参考动态库链接安全性
register_exit
函数,将退出函数注册给了一个静态全局函数指针program_exit
,当程序需要退出时会调用该函数指针指向的函数
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);
// 判断第一个参数是不是"-d",如果是,则作为守护进程
if(argc>1 && !strcmp(argv[1], "-d")){
run_as_daemon=1;
av_log_set_callback(log_callback_null);
argc--;
argv++;
}
#if CONFIG_AVDEVICE
// 初始化libavdevice,并且注册所有的输入输出设备
// 这个函数单独写文章分析,这里不再赘述
avdevice_register_all();
#endif
// 完成网络初始化,这里主要是针对windows平台,因为windows
// 使用网络首先需要调用WSAStartup函数去初始化
// 此外还对TLS协议进行了初始化
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);
}
// 查看输出url中是否有rtp协议
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;
}
该函数中调用的Option相关结构体将在单独的文章列举
split_commandline
函数重点参数说明
options
:全局常量OptionDef
结构体数组,里面预先定义了好多命令参数,比如'c','codec','ss'
等,并且包含相关参数的说明和解释option
参考数据{ “ss”, HAS_ARG | OPT_TIME | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(start_time) }, “set the start time offset”, “time_off” }
groups
:全局常量OptionGroupDef
结构体数组,预先定义了需要匹配的分离器
下面是初始化数据
[GROUP_OUTFILE] = { “output url”, NULL, OPT_OUTPUT },
[GROUP_INFILE] = { “input url”, “i”, OPT_INPUT },
下面是该函数的源码
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));
/* apply global options */
// 解析ctx中全局group
ret = parse_optgroup(NULL, &octx.global_opts);
/* configure terminal and setup signal handlers */
// 配置信号句柄,详细的就不看了
// 同时也枚举ctrl-c信号
term_init();
/* open input files */
// 打开输入文件,主要是通过open_input_file来打开
ret = open_files(&octx.groups[GROUP_INFILE], "input", open_input_file);
/* create the complex filtergraphs */
ret = init_complex_filters();
/* open output files */
// 打开输出文件
ret = open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file);
check_filter_outputs();
fail:
uninit_parse_context(&octx);
return ret;
}
源文件:fftools\cmdutils.c
功能:分离命令行命令
下面是源码
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 */
// 在windows平台下,这个函数负责将命令行参数从宽字符转换成UTF8,
// 这样就可以解决中文的问题
prepare_app_arguments(&argc, &argv);
// 初始化octx,根据groups的内容来初始化octx里的groups
// 同时初始化global_opts
init_parse_context(octx, groups, nb_groups);
av_log(NULL, AV_LOG_DEBUG, "Splitting the commandline.\n");
// 下面开始解析命令行参数
while (optindex < argc) {
// 从命令行中获取参数
const char *opt = argv[optindex++], *arg;
const OptionDef *po;
int ret;
av_log(NULL, AV_LOG_DEBUG, "Reading option '%s' ...", opt);
// 如果获取到的参数是“--”,则记录位置,并且continue
if (opt[0] == '-' && opt[1] == '-' && !opt[2]) {
dashdash = optindex;
continue;
}
// 如果是没有分隔符的参数,或者只有一个字符,或者是dashdash
// 后面对应的参数则直接保存,并且统一将这个参数保存到ctx中groups中的第一个OptionGroupList中
// 后面将单独分析这个函数,或者可以做一个表这样更明显一些
/* unnamed group separators, e.g. output filename */
if (opt[0] != '-' || !opt[1] || dashdash+1 == optindex) {
finish_group(octx, 0, opt);
av_log(NULL, AV_LOG_DEBUG, " matched as %s.\n", groups[0].name);
continue;
}
opt++;
// 这是一个宏,可以从argv中获取参数
#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)
/* named group separators, e.g. -i */
// 是否匹配具名的分隔符,简单来说就是将输入的分隔符or关键字在
// 默认的groups数组中查找,若能查找到则保存到octx中,这里主要是-i选项
// 按照当前的配置 groups中只有两个成员
if ((ret = match_group_separator(groups, nb_groups, opt)) >= 0) {
GET_ARG(arg);
finish_group(octx, ret, arg);
av_log(NULL, AV_LOG_DEBUG, " matched as %s with argument '%s'.\n",
groups[ret].name, arg);
continue;
}
/* normal options */
// 将输入的命令行参数关键字在options中开始查找,options保存预先定义好的
// 参数关键字,比如:“-c”等
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";
}
// 根据相应的条件,将参数保存到octx中的cur_group或者global_opts中
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 */
// 没有明确被处理的选项将通过opt_default函数作为AVOption来处理
if (argv[optindex]) {
// 下面将讲解opt_default()函数
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;
}
}
/* boolean -nofoo options */
// 如果起始两个字符是no,除去no之后剩余的字符能够在options中匹配到
// 并且flag中有`OPT_BOOL`标记。比如
if (opt[0] == 'n' && opt[1] == 'o' &&
(po = find_option(options, opt + 2)) &&
po->name && po->flags & OPT_BOOL) {
// 选项添加到octx中的cur_group或者global_opts中
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;
}
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;
}
finish_group()
函数源码分析finish_group()
负责将和输入url、输出url相关的参数保存到OptionParseContext.groups
中
/*
Finish parsing an option group.
@param group_idx which group definition should this group belong to
@param arg argument of the group delimiting option
*/
static void finish_group(OptionParseContext *octx, int group_idx,
const char *arg)
{
OptionGroupList *l = &octx->groups[group_idx];
OptionGroup *g;
// 在groups的基础上重新分配一个array。
GROW_ARRAY(l->groups, l->nb_groups);
g = &l->groups[l->nb_groups - 1];
// 从octx的cur_group中获取到内容
// 然后开始初始化
// -i filename之前配置的参数,如果不是保存在octx->global_group中,就会暂时保存在octx->cur_group中,
//这个时候有输入或者输出文件了,我们将cur_group中的数据保存到octx->optionGroupList中的optionGroup中,和输入文件挂靠在一起
*g = octx->cur_group;
g->arg = arg;
g->group_def = l->group_def;
g->sws_dict = sws_dict;
g->swr_opts = swr_opts;
g->codec_opts = codec_opts;
g->format_opts = format_opts;
g->resample_opts = resample_opts;
codec_opts = NULL;
format_opts = NULL;
resample_opts = NULL;
sws_dict = NULL;
swr_opts = NULL;
init_opts();
memset(&octx->cur_group, 0, sizeof(octx->cur_group));
}
add_opt()
函数解析add_opt()
将和options
中相关的参数保存到OptionParseContext.cur_groups或者OptionParseContext.global_groups
中
下面是源码
/*
Add an option instance to currently parsed group.
*/
static void add_opt(OptionParseContext *octx, const OptionDef *opt,
const char *key, const char val)
{
// 如果选项中的flags标记为有下列 OPT_ 三个中的一个,就不是一个全局选项,比如codec或者c参数就包含标记OPT_SPEC
int global = !(opt->flags & (OPT_PERFILE | OPT_SPEC | OPT_OFFSET));
OptionGroup *g = global ? &octx->global_opts : &octx->cur_group;
// 将选项存放到对应的OptionGroup中
GROW_ARRAY(g->opts, g->nb_opts);
g->opts[g->nb_opts - 1].opt = opt;
g->opts[g->nb_opts - 1].key = key;
g->opts[g->nb_opts - 1].val = val;
}
opt_default()
函数源码解析#define FLAGS (o->type == AV_OPT_TYPE_FLAGS && (arg[0]=='-' || arg[0]=='+')) ? AV_DICT_APPEND : 0
int opt_default(void *optctx, const char *opt, const char *arg)
{
const AVOption *o;
int consumed = 0;
char opt_stripped[128];
const char *p;
// avcodec_get_class()函数返回静态全局常量 static const AVClass av_codec_context_class的指针
const AVClass *cc = avcodec_get_class(), *fc = avformat_get_class();
#if CONFIG_AVRESAMPLE
// 下列函数已经被弃用了,暂时不分析,猜测也是返回类似codec的 AVClass类型静态全局变量指针
const AVClass *rc = avresample_get_class();
#endif
#if CONFIG_SWSCALE
// 返回全局常量 const AVClass ff_sws_context_class的指针
const AVClass *sc = sws_get_class();
#endif
#if CONFIG_SWRESAMPLE
// 返回静态全局常量 static const AVClass av_class的指针
const AVClass *swr_class = swr_get_class();
#endif
if (!strcmp(opt, "debug") || !strcmp(opt, "fdebug"))
av_log_set_level(AV_LOG_DEBUG);
// 获取':'首次在opt中出现的位置
if (!(p = strchr(opt, ':')))
// 若没有p指针设置为opt的字符串尾部
p = opt + strlen(opt);
// 将opt中的字符串copy到opt_stripped中
av_strlcpy(opt_stripped, opt, FFMIN(sizeof(opt_stripped), p - opt + 1));
// 从av_codec_context_class.option查找匹配的选项
if ((o = opt_find(&cc, opt_stripped, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ)) ||
((opt[0] == 'v' || opt[0] == 'a' || opt[0] == 's') &&
(o = opt_find(&cc, opt + 1, NULL, 0, AV_OPT_SEARCH_FAKE_OBJ)))) {
// 如果找到了则将其设置进入全局变量codec_opts中
av_dict_set(&codec_opts, opt, arg, FLAGS);
consumed = 1;
}
// 同上,从av_format_context_class.option查找匹配的选项
// 后面的代码与这个类似就不再细看了
if ((o = opt_find(&fc, opt, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) {
av_dict_set(&format_opts, opt, arg, FLAGS);
if (consumed)
av_log(NULL, AV_LOG_VERBOSE, "Routing option %s to both codec and muxer layer\n", opt);
consumed = 1;
}
#if CONFIG_SWSCALE
if (!consumed && (o = opt_find(&sc, opt, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) {
struct SwsContext *sws = sws_alloc_context();
int ret = av_opt_set(sws, opt, arg, 0);
sws_freeContext(sws);
if (!strcmp(opt, "srcw") || !strcmp(opt, "srch") ||
!strcmp(opt, "dstw") || !strcmp(opt, "dsth") ||
!strcmp(opt, "src_format") || !strcmp(opt, "dst_format")) {
av_log(NULL, AV_LOG_ERROR, "Directly using swscale dimensions/format options is not supported, please use the -s or -pix_fmt options\n");
return AVERROR(EINVAL);
}
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error setting option %s.\n", opt);
return ret;
}
av_dict_set(&sws_dict, opt, arg, FLAGS);
consumed = 1;
}
#else
if (!consumed && !strcmp(opt, "sws_flags")) {
av_log(NULL, AV_LOG_WARNING, "Ignoring %s %s, due to disabled swscale\n", opt, arg);
consumed = 1;
}
#endif
#if CONFIG_SWRESAMPLE
if (!consumed && (o=opt_find(&swr_class, opt, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) {
struct SwrContext *swr = swr_alloc();
int ret = av_opt_set(swr, opt, arg, 0);
swr_free(&swr);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error setting option %s.\n", opt);
return ret;
}
av_dict_set(&swr_opts, opt, arg, FLAGS);
consumed = 1;
}
#endif
#if CONFIG_AVRESAMPLE
if ((o=opt_find(&rc, opt, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) {
av_dict_set(&resample_opts, opt, arg, FLAGS);
consumed = 1;
}
#endif
if (consumed)
return 0;
return AVERROR_OPTION_NOT_FOUND;
}
parse_optgroup()
函数分析OptionGroup
结构体int parse_optgroup(void *optctx, OptionGroup *g)
{
int i, ret;
av_log(NULL, AV_LOG_DEBUG, "Parsing a group of options: %s %s.\n",
g->group_def->name, g->arg);
for (i = 0; i < g->nb_opts; i++) {
Option *o = &g->opts[i];
// 如果OptionGroup.group_def.flags和OptionGroup.opts[i].flags不匹配,则返回错误
if (g->group_def->flags &&
!(g->group_def->flags & o->opt->flags)) {
av_log(NULL, AV_LOG_ERROR, "Option %s (%s) cannot be applied to "
"%s %s -- you are trying to apply an input option to an "
"output file or vice versa. Move this option before the "
"file it belongs to.\n", o->key, o->opt->help,
g->group_def->name, g->arg);
return AVERROR(EINVAL);
}
av_log(NULL, AV_LOG_DEBUG, "Applying option %s (%s) with argument %s.\n",
o->key, o->opt->help, o->val);
// 若匹配则将其写入到option中
ret = write_option(optctx, o->opt, o->key, o->val);
if (ret < 0)
return ret;
}
av_log(NULL, AV_LOG_DEBUG, "Successfully parsed a group of options.\n");
return 0;
}
write_option()
函数源码分析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*/
// 获取存储位置
// 这里分两种情况,OptionDef.flags中含有OPT_OFFSET和OPT_SPEC标记时,OptionDef.u.off保存的
// 是该参数对应在 OptionContext结构体中的偏移,如果不包含这些标记的话,就是使用
// OptionDef.u.dst_ptr保存的变量指针
// 下面举个例子吧: { "f",HAS_ARG | OPT_STRING | OPT_OFFSET |OPT_INPUT | OPT_OUTPUT,{ .off = OFFSET(format)},
// 对于option ‘f’来说,它flag含有OPT_OFFSET所以需要使用OptionDef.u.off中保存的指针,在上面这个例子就是OFFSET(format)
// 而这个宏的定义是这样的:#define OFFSET(x) offsetof(OptionsContext, x),就是获取format在OptionContext结构体中的偏移
// 另外一个例子: { "y",OPT_BOOL,{&file_overwrite },"overwrite output files" }
// 对弈option ‘y’来说,它的flag不包含OPT_OFFSET、OPT_SPEC,所以使用OptionDef.u.dst_ptr的值,也就是&file_overwrite
void *dst = po->flags & (OPT_OFFSET | OPT_SPEC) ?
(uint8_t *)optctx + po->u.off : po->u.dst_ptr;
int *dstcount;
// 根据不同的flags,dst是一个不同的类型的指针,后续的代码就不在看了,理解上面那行代码就ok了
// 这里需要单独讲一下:在OptionsContext结构体的成员中每一个SpecifierOpt* 成员后面都有一个int型的成员
// 比如: SpecifierOpt *audio_channels;
// int nb_audio_channels;
// SpecifierOpt *audio_sample_rate;
// int nb_audio_sample_rate;
// 那个int成员用来表明上面那个指针指向的数组有多少个成员
if (po->flags & OPT_SPEC) {
SpecifierOpt **so = dst;
char *p = strchr(opt, ':');
char *str;
dstcount = (int *)(so + 1);
// 这个函数里面,如果grow_array()函数调用成功,则dstcount的值会变成`discount+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处
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;
}
open_files
函数分析源文件:fftools/ffmpeg_opt.c
下面直接分析源码吧
static int open_files(OptionGroupList *l, const char inout,
int (open_file)(OptionsContext, const char))
{
int i, ret;
// 循环遍历OptionGroupList中的OptionGroup
for (i = 0; i < l->nb_groups; i++) {
OptionGroup *g = &l->groups[i];
OptionsContext o;
// 初始化OptionsContext
init_options(&o);
o.g = g;
// 解析OptionGroup,并且存储数据到o中
ret = parse_optgroup(&o, g);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error parsing options for %s file "
"%s.\n", inout, g->arg);
return ret;
}
av_log(NULL, AV_LOG_DEBUG, "Opening an %s file: %s.\n", inout, g->arg);
// 调用传入的open_file函数指针打开文件
// open_file主要的两个可能的值是:open_input_file()函数指针和 open_output_file()函数指针
// 这两个函数下面将开始讲解
ret = open_file(&o, g->arg);
// 反初始化OptionsContext
uninit_options(&o);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error opening %s file %s.\n",
inout, g->arg);
return ret;
}
av_log(NULL, AV_LOG_DEBUG, "Successfully opened the file.\n");
}
return 0;
}
open_input_file
函数解析这个函数是用来打开输入文件
下面是源码
static int open_input_file(OptionsContext *o, const char *filename)
{
InputFile *f;
AVFormatContext *ic;
AVInputFormat *file_iformat = NULL;
int err, i, ret;
int64_t timestamp;
AVDictionary *unused_opts = NULL;
AVDictionaryEntry *e = NULL;
char * video_codec_name = NULL;
char * audio_codec_name = NULL;
char *subtitle_codec_name = NULL;
char * data_codec_name = NULL;
int scan_all_pmts_set = 0;
// 相应参数的修正
if (o->stop_time != INT64_MAX && o->recording_time != INT64_MAX) {
o->stop_time = INT64_MAX;
av_log(NULL, AV_LOG_WARNING, "-t and -to cannot be used together; using -t.\n");
}
if (o->stop_time != INT64_MAX && o->recording_time == INT64_MAX) {
int64_t start_time = o->start_time == AV_NOPTS_VALUE ? 0 : o->start_time;
if (o->stop_time <= start_time) {
av_log(NULL, AV_LOG_ERROR, "-to value smaller than -ss; aborting.\n");
exit_program(1);
} else {
o->recording_time = o->stop_time - start_time;
}
}
// 如果指定了格式,则直接调用av_find_input_format()函数而不需要去探测输入文件的格式了
// 比如通过这个命令就可以指定输入文件的封装格式:ffmpeg -f flv -i G:/1.flv -c copy -f mp4 G:/2.mp4
// -f flv 就指定了输入文件的格式为flv格式
if (o->format) {
// 根据用户指定的输入格式来查找对应的处理类,如果没有找到则报错,退出应用程序
if (!(file_iformat = av_find_input_format(o->format))) {
av_log(NULL, AV_LOG_FATAL, "Unknown input format: '%s'\n", o->format);
exit_program(1);
}
}
// 判断输入文件的格式是不是'-',如果是则认为是pipe输入
// 输入文件
if (!strcmp(filename, "-"))
filename = "pipe:";
// 如果文件名是pipe或者/dev/stdin,则认为是通过标准输入来进行互动
stdin_interaction &= strncmp(filename, "pipe:", 5) &&
strcmp(filename, "/dev/stdin");
/* get default parameters from command line */
// 获取AVFormatContext实例,这个会有单独的文章分析
ic = avformat_alloc_context();
if (!ic) {
print_error(filename, AVERROR(ENOMEM));
exit_program(1);
}
// 如果指定了音频采样率的,则将采样率保存到octx中的OptionGroup中的format_opts中
if (o->nb_audio_sample_rate) {
av_dict_set_int(&o->g->format_opts, "sample_rate", o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i, 0);
}
if (o->nb_audio_channels) {
/* because we set audio_channels based on both the "ac" and
* "channel_layout" options, we need to check that the specified
* demuxer actually has the "channels" option before setting it */
if (file_iformat && file_iformat->priv_class &&
av_opt_find(&file_iformat->priv_class, "channels", NULL, 0,
AV_OPT_SEARCH_FAKE_OBJ)) {
av_dict_set_int(&o->g->format_opts, "channels", o->audio_channels[o->nb_audio_channels - 1].u.i, 0);
}
}
if (o->nb_frame_rates) {
/* set the format-level framerate option;
* this is important for video grabbers, e.g. x11 */
if (file_iformat && file_iformat->priv_class &&
av_opt_find(&file_iformat->priv_class, "framerate", NULL, 0,
AV_OPT_SEARCH_FAKE_OBJ)) {
av_dict_set(&o->g->format_opts, "framerate",
o->frame_rates[o->nb_frame_rates - 1].u.str, 0);
}
}
if (o->nb_frame_sizes) {
av_dict_set(&o->g->format_opts, "video_size", o->frame_sizes[o->nb_frame_sizes - 1].u.str, 0);
}
if (o->nb_frame_pix_fmts)
av_dict_set(&o->g->format_opts, "pixel_format", o->frame_pix_fmts[o->nb_frame_pix_fmts - 1].u.str, 0);
// 这是一个宏,这个宏从OptionsContext(o).codec_names中查找有没有后最后一个参数匹配的媒体类型
// 如果有就将里面的值赋值给第三个参数
MATCH_PER_TYPE_OPT(codec_names, str, video_codec_name, ic, "v");
MATCH_PER_TYPE_OPT(codec_names, str, audio_codec_name, ic, "a");
MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, ic, "s");
MATCH_PER_TYPE_OPT(codec_names, str, data_codec_name, ic, "d");
// 如果指定了编码,则直接查找编码对应的处理类,如果没有找到则直接退出应用程序了
// 相关查找函数稍后分析
if (video_codec_name)
ic->video_codec = find_codec_or_die(video_codec_name , AVMEDIA_TYPE_VIDEO , 0);
if (audio_codec_name)
ic->audio_codec = find_codec_or_die(audio_codec_name , AVMEDIA_TYPE_AUDIO , 0);
if (subtitle_codec_name)
ic->subtitle_codec = find_codec_or_die(subtitle_codec_name, AVMEDIA_TYPE_SUBTITLE, 0);
if (data_codec_name)
ic->data_codec = find_codec_or_die(data_codec_name , AVMEDIA_TYPE_DATA , 0);
ic->video_codec_id = video_codec_name ? ic->video_codec->id : AV_CODEC_ID_NONE;
ic->audio_codec_id = audio_codec_name ? ic->audio_codec->id : AV_CODEC_ID_NONE;
ic->subtitle_codec_id = subtitle_codec_name ? ic->subtitle_codec->id : AV_CODEC_ID_NONE;
ic->data_codec_id = data_codec_name ? ic->data_codec->id : AV_CODEC_ID_NONE;
ic->flags |= AVFMT_FLAG_NONBLOCK;
if (o->bitexact)
ic->flags |= AVFMT_FLAG_BITEXACT;
// 指定中断callback函数
ic->interrupt_callback = int_cb;
if (!av_dict_get(o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
av_dict_set(&o->g->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
scan_all_pmts_set = 1;
}
/* open the input file with generic avformat function */
// 打开输入文件,这个函数单独写一篇文章分析
err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts);
if (err < 0) {
print_error(filename, err);
if (err == AVERROR_PROTOCOL_NOT_FOUND)
av_log(NULL, AV_LOG_ERROR, "Did you mean file:%s?\n", filename);
exit_program(1);
}
if (scan_all_pmts_set)
av_dict_set(&o->g->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE);
remove_avoptions(&o->g->format_opts, o->g->codec_opts);
assert_avoptions(o->g->format_opts);
/* apply forced codec ids */
for (i = 0; i < ic->nb_streams; i++)
choose_decoder(o, ic, ic->streams[i]);
if (find_stream_info) {
AVDictionary **opts = setup_find_stream_info_opts(ic, o->g->codec_opts);
int orig_nb_streams = ic->nb_streams;
/* If not enough info to get the stream parameters, we decode the
first frames to get it. (used in mpeg case for example) */
ret = avformat_find_stream_info(ic, opts);
for (i = 0; i < orig_nb_streams; i++)
av_dict_free(&opts[i]);
av_freep(&opts);
if (ret < 0) {
av_log(NULL, AV_LOG_FATAL, "%s: could not find codec parameters\n", filename);
if (ic->nb_streams == 0) {
avformat_close_input(&ic);
exit_program(1);
}
}
}
if (o->start_time != AV_NOPTS_VALUE && o->start_time_eof != AV_NOPTS_VALUE) {
av_log(NULL, AV_LOG_WARNING, "Cannot use -ss and -sseof both, using -ss for %s\n", filename);
o->start_time_eof = AV_NOPTS_VALUE;
}
if (o->start_time_eof != AV_NOPTS_VALUE) {
if (o->start_time_eof >= 0) {
av_log(NULL, AV_LOG_ERROR, "-sseof value must be negative; aborting\n");
exit_program(1);
}
if (ic->duration > 0) {
o->start_time = o->start_time_eof + ic->duration;
if (o->start_time < 0) {
av_log(NULL, AV_LOG_WARNING, "-sseof value seeks to before start of file %s; ignored\n", filename);
o->start_time = AV_NOPTS_VALUE;
}
} else
av_log(NULL, AV_LOG_WARNING, "Cannot use -sseof, duration of %s not known\n", filename);
}
timestamp = (o->start_time == AV_NOPTS_VALUE) ? 0 : o->start_time;
/* add the stream start time */
if (!o->seek_timestamp && ic->start_time != AV_NOPTS_VALUE)
timestamp += ic->start_time;
/* if seeking requested, we execute it */
if (o->start_time != AV_NOPTS_VALUE) {
int64_t seek_timestamp = timestamp;
if (!(ic->iformat->flags & AVFMT_SEEK_TO_PTS)) {
int dts_heuristic = 0;
for (i=0; inb_streams; i++) {
const AVCodecParameters *par = ic->streams[i]->codecpar;
if (par->video_delay) {
dts_heuristic = 1;
break;
}
}
if (dts_heuristic) {
seek_timestamp -= 3*AV_TIME_BASE / 23;
}
}
ret = avformat_seek_file(ic, -1, INT64_MIN, seek_timestamp, seek_timestamp, 0);
if (ret < 0) {
av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n",
filename, (double)timestamp / AV_TIME_BASE);
}
}
/* update the current parameters so that they match the one of the input stream */
add_input_streams(o, ic);
/* dump the file content */
av_dump_format(ic, nb_input_files, filename, 0);
GROW_ARRAY(input_files, nb_input_files);
f = av_mallocz(sizeof(*f));
if (!f)
exit_program(1);
input_files[nb_input_files - 1] = f;
f->ctx = ic;
f->ist_index = nb_input_streams - ic->nb_streams;
f->start_time = o->start_time;
f->recording_time = o->recording_time;
f->input_ts_offset = o->input_ts_offset;
f->ts_offset = o->input_ts_offset - (copy_ts ? (start_at_zero && ic->start_time != AV_NOPTS_VALUE ? ic->start_time : 0) : timestamp);
f->nb_streams = ic->nb_streams;
f->rate_emu = o->rate_emu;
f->accurate_seek = o->accurate_seek;
f->loop = o->loop;
f->duration = 0;
f->time_base = (AVRational){ 1, 1 };
#if HAVE_THREADS
f->thread_queue_size = o->thread_queue_size > 0 ? o->thread_queue_size : 8;
#endif
/* check if all codec options have been used */
unused_opts = strip_specifiers(o->g->codec_opts);
for (i = f->ist_index; i < nb_input_streams; i++) {
e = NULL;
while ((e = av_dict_get(input_streams[i]->decoder_opts, "", e,
AV_DICT_IGNORE_SUFFIX)))
av_dict_set(&unused_opts, e->key, NULL, 0);
}
e = NULL;
while ((e = av_dict_get(unused_opts, "", e, AV_DICT_IGNORE_SUFFIX))) {
const AVClass *class = avcodec_get_class();
const AVOption *option = av_opt_find(&class, e->key, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
const AVClass *fclass = avformat_get_class();
const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
if (!option || foption)
continue;
if (!(option->flags & AV_OPT_FLAG_DECODING_PARAM)) {
av_log(NULL, AV_LOG_ERROR, "Codec AVOption %s (%s) specified for "
"input file #%d (%s) is not a decoding option.\n", e->key,
option->help ? option->help : "", nb_input_files - 1,
filename);
exit_program(1);
}
av_log(NULL, AV_LOG_WARNING, "Codec AVOption %s (%s) specified for "
"input file #%d (%s) has not been used for any stream. The most "
"likely reason is either wrong type (e.g. a video option with "
"no video streams) or that it is a private option of some decoder "
"which was not actually used for any stream.\n", e->key,
option->help ? option->help : "", nb_input_files - 1, filename);
}
av_dict_free(&unused_opts);
for (i = 0; i < o->nb_dump_attachment; i++) {
int j;
for (j = 0; j < ic->nb_streams; j++) {
AVStream *st = ic->streams[j];
if (check_stream_specifier(ic, st, o->dump_attachment[i].specifier) == 1)
dump_attachment(st, o->dump_attachment[i].u.str);
}
}
input_stream_potentially_available = 1;
return 0;
}