当前位置: 首页 > news >正文

建设工程造价信息网站头条今日头条新闻

建设工程造价信息网站,头条今日头条新闻,生活分享网站源码 博客风格分享小清新php源码,大连 做网站前言 ffmpeg 的源码量非常的多,而且非常繁杂,非常多的函数,如果一个函数一个函数看的话要花费比较多的时间。所以本文通过跟踪ffmpeg转封装的过程来学习ffmpeg的源码具体转封装的命令: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(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;
}
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] || 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;
    

    }

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 && (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_AVRESAMPLEif ((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;}
#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;// 根据不同的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;
}
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 (i=0; i<ic->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;
    

    }

http://www.hengruixuexiao.com/news/34444.html

相关文章:

  • xp做网站服务器seo排名优化方式方法
  • 网站建设与维护笔记站长之家关键词挖掘工具
  • 网站图片怎么换导购网站怎么推广
  • 网站后台管理代码百度一下百度下载
  • 威海高端网站建设长沙搜索排名优化公司
  • 自助网站制作系统源码最近的新闻热点时事
  • 手机端网站建设公司百度seo排名软件
  • 做网站的思路怎么写今天最新军事新闻视频
  • 怎样选择网站建设千锋教育可靠吗
  • 做ppt的网站有哪些内容专业seo培训
  • 360度搜索建站网怎么做百度网页推广
  • 网站建设政府泉州关键词快速排名
  • 网站关键字字数做seo前景怎么样
  • 域名申请好怎么做网站廊坊seo外包
  • 网络彩票建立网站广东疫情最新数据
  • 南通企业做网站百度搜索排名购买
  • 网站建设答辩ppt要点搜狗网站提交入口
  • 如何选择大良网站建设抖音关键词排名查询
  • 如何安装wordpress到usbwebserverseo服务顾问
  • 莆田外贸建站5118站长网站
  • 网页动画制作软件seo社区
  • 5188站长平台恶意点击软件哪个好
  • 日本做苹果壁纸的网站网站的优化seo
  • 全屏网站 图片优化新产品推广方式有哪些
  • 响应式商场网站百度网页版入口
  • 昆明网站建设yn119培训课程安排
  • 凌源网站建设二手交易平台
  • 建网站的 公司公司怎么推广网络营销
  • 普通电脑可以做网站服务器吗新闻热点
  • 网站开发的标准流程搜什么关键词能找到网站