ffmpeg4.1 源码学习之-转封装

前言

  • 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);
    
        // 如果获取到的参数是“--”,则记录位置,并且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;
    

    }

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();
#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;
}

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_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;
}

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;
        // 初始化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;
    

    }

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 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;
    

    }

你可能感兴趣的:(ffmpeg,学习,ffmpeg)