学校多语言网站建设,有了域名怎样做网站,有趣的网站源码,响应式网站导航前言 ffmpeg 的源码量非常的多#xff0c;而且非常繁杂#xff0c;非常多的函数#xff0c;如果一个函数一个函数看的话要花费比较多的时间。所以本文通过跟踪ffmpeg转封装的过程来学习ffmpeg的源码具体转封装的命令#xff1a;ffmpeg -i 1_cut.flv -c copy -f mp4 1.mp4在…前言 ffmpeg 的源码量非常的多而且非常繁杂非常多的函数如果一个函数一个函数看的话要花费比较多的时间。所以本文通过跟踪ffmpeg转封装的过程来学习ffmpeg的源码具体转封装的命令ffmpeg -i 1_cut.flv -c copy -f mp4 1.mp4在学习过程中如果遇到libavformat、libavcodec、libavutils等库的主要函数将单独写一篇文章进行分析为了减少篇幅源码的异常处理都删除了
源码之旅
1.1 main 函数分析 源文件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(argc1 !strcmp(argv[1], -d)){run_as_daemon1;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;
}1.2 ffmpeg_parse_options 源码分析 该函数中调用的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; }
1.3 split_commandline 函数分析 源文件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);// 如果获取到的参数是“--”则记录位置并且continueif (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] || dashdash1 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;}
1.4 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)); }
1.5 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; }
1.6 opt_default()函数源码解析 该函数负责解析与输入输出参数(-i)、options中的参数、dashdash(–)参数不匹配的其他参数下面是该函数的源码
#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();
#endifif (!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_SWSCALEif (!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;}
#elseif (!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_SWRESAMPLEif (!consumed (oopt_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_AVRESAMPLEif ((oopt_find(rc, opt, NULL, 0,AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) {av_dict_set(resample_opts, opt, arg, FLAGS);consumed 1;}
#endifif (consumed)return 0;return AVERROR_OPTION_NOT_FOUND;
}
1.7 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;
}
1.8 write_option()函数源码分析 write_option可以判断参数对应的arg是不是合法的下面是源码
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_overwritevoid *dst po-flags (OPT_OFFSET | OPT_SPEC) ?(uint8_t *)optctx po-u.off : po-u.dst_ptr;int *dstcount;// 根据不同的flagsdst是一个不同的类型的指针后续的代码就不在看了理解上面那行代码就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的值会变成discount1的值*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;
}
1.9 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;// 初始化OptionsContextinit_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);// 反初始化OptionsContextuninit_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;}
1.10 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 thefirst 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;}} elseav_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 (i0; iic-nb_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;}