FFMPEG源码分析通过ffmpeg截图命令分析ffmpeg.c源码流程

Posted 猿来如此yXy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FFMPEG源码分析通过ffmpeg截图命令分析ffmpeg.c源码流程相关的知识,希望对你有一定的参考价值。

环境搭建

  1. Ubuntu 20.04
  2. 开启外设摄像头
  3. 截图命令:
    ffmpeg -f video4linux2 -s 640x480 -i /dev/video0 -ss 0:0:2 -frames 1 /data/ffmpeg-4.2.7/exe_cmd/tmp/out2.jpg

参数解析

int main(int argc, char **argv)

    .................................;
#if CONFIG_AVDEVICE
    avdevice_register_all();
#endif
    avformat_network_init();
    show_banner(argc, argv, options);
    //1. 重点看下参数解析
    ffmpeg_parse_options(argc, argv);
    //2. 这里面进行具体的数据处理
    if (transcode() < 0)
        exit_program(1);
    
    .................................;

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 */
    split_commandline(&octx, argc, argv, options, groups,
                            FF_ARRAY_ELEMS(groups));

    /* apply global options */
    parse_optgroup(NULL, &octx.global_opts);
    
    /* configure terminal and setup signal handlers */
    term_init();

    /* open input files */
    open_files(&octx.groups[GROUP_INFILE], "input", open_input_file);
   
    init_complex_filters();
    /* open output files */
    open_files(&octx.groups[GROUP_OUTFILE], "output", open_output_file);
    .....................................;
    check_filter_outputs();
    .....................................;
    return ret;

split_commandline

//全局变量 设置给split_commandline
const OptionDef options[] = 
    /* main options */
    CMDUTILS_COMMON_OPTIONS
     "f",              HAS_ARG | OPT_STRING | OPT_OFFSET |
                        OPT_INPUT | OPT_OUTPUT,                       .off       = OFFSET(format) ,
        "force format", "fmt" ,
     "y",              OPT_BOOL,                                                  &file_overwrite ,
        "overwrite output files" ,
    ...............................;


enum OptGroup 
    GROUP_OUTFILE,
    GROUP_INFILE,
;

//全局变量 设置给split_commandline
static const OptionGroupDef groups[] = 
    [GROUP_OUTFILE] =  "output url",  NULL, OPT_OUTPUT ,
    [GROUP_INFILE]  =  "input url",   "i",  OPT_INPUT ,
;


int split_commandline(OptionParseContext *octx, int argc, char *argv[],
                      const OptionDef *options,
                      const OptionGroupDef *groups, int nb_groups)

    int optindex = 1;
    int dashdash = -2;

    //针对windows上输入参数字符集的转换如:utf-8等,不必深入。
    prepare_app_arguments(&argc, &argv);

    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;

        if (opt[0] == '-' && opt[1] == '-' && !opt[2]) 
            dashdash = optindex;
            continue;
        
        // -i之后的作为output的一个group,即全局变量groups中的GROUP_OUTFILE
        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++;

#define GET_ARG(arg)                                                           \\
do                                                                            \\
    arg = argv[optindex++];                                                    \\
    if (!arg)                                                                 \\
        av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'.\\n", opt);\\
        return AVERROR(EINVAL);                                                \\
                                                                              \\
 while (0)
       //-i 以及-i 之前的参数全部存入全局变量groups中的GROUP_INFILE
        /* named group separators, e.g. -i */
        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 */
        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";
            

            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 */
        if (argv[optindex]) 
            ret = opt_default(NULL, opt, argv[optindex]);
            if (ret >= 0) 
                av_log(NULL, AV_LOG_DEBUG, " matched as AVOption '%s' with "
                       "argument '%s'.\\n", opt, argv[optindex]);
                optindex++;
                continue;
             else if (ret != AVERROR_OPTION_NOT_FOUND) 
                av_log(NULL, AV_LOG_ERROR, "Error parsing option '%s' "
                       "with argument '%s'.\\n", opt, argv[optindex]);
                return ret;
            
        

        /* boolean -nofoo options */
        if (opt[0] == 'n' && opt[1] == 'o' &&
            (po = find_option(options, opt + 2)) &&
            po->name && po->flags & OPT_BOOL) 
            add_opt(octx, po, opt, "0");
            av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with "
                   "argument 0.\\n", po->name, po->help);
            continue;
        

        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;

上述代码主要是将ffmpeg命令行参数解析到OptionParseContext中。OptionParseContext的成员变量OptionGroupList *groups由两个,一个为input OptionGroupList另一个为output OptionGroupList。一个OptionGroupList中由多个OptionGroup组成,而一个OptionGroup由多个Option组成。具体关系如下图:

OptionGroupList输入/输出参数以及option的遍历代码如下:

OptionParseContext octx;
 for(int i = 0; i < octx.nb_groups; i++) 
    OptionGroupList *ll = &octx.groups[i];
    av_log(NULL,AV_LOG_ERROR, "OptionGroupList[%d] def:%s\\n",i, ll->group_def->name);
    for(int j = 0; j < ll->nb_groups; j++) 
        OptionGroup *g = &ll->groups[j];
        av_log(NULL,AV_LOG_ERROR,"g[%d][%d],arg:%s, def: %s \\n",i,j, g->arg, g->group_def->name);
        for (int x = 0; x < g->nb_opts; x++) 
            Option *opt = &g->opts[x];
            av_log(NULL,AV_LOG_ERROR,"g[%d][%d], opts[%d],key:%s, value:%s, def: %s \\n",i,j,x, opt->key, opt->val, opt->opt->name);
        
    
 

如命令行: ffmpeg -f video4linux2 -s 640x480 -i /dev/video0 -ss 0:0:2 -frames 1 /data/ffmpeg-4.2.7/exe_cmd/tmp/out2.jpg 解析后遍历结果如下:

OptionGroupList[0] def:output url
g[0][0],arg:/data/ffmpeg-4.2.7/exe_cmd/tmp/out2.jpg, def: output url
g[0][0], opts[0],key:ss, value:0:0:2, def: ss
g[0][0], opts[1],key:frames, value:1, def: frames
OptionGroupList[1] def:input url
g[1][0],arg:/dev/video0, def: input url
g[1][0], opts[0],key:f, value:video4linux2, def: f
g[1][0], opts[1],key:s, value:640x480, def: s

parse_optgroup

这个函数的作中是存储全局的option,如: -v debug 对所有模块都适用

int parse_optgroup(void *optctx, OptionGroup *g)

    int i, ret;
    for (i = 0; i < g->nb_opts; i++) 
        Option *o = &g->opts[i];
        ret = write_option(optctx, o->opt, o->key, o->val);
        if (ret < 0)
            return ret;
    
    return 0;

open_files

参数解析完成之后,就是通过input url和output url找到对应的AVInputFormat和AVOutputFormat。

int ffmpeg_parse_options(int argc, char **argv) 
    ................................................;
    //将input arg group传入进去
    open_files(&octx.groups[GROUP_INFILE], "input", open_input_file)
    ................................................;


static int open_files(OptionGroupList *l, const char *inout,
                      int (*open_file)(OptionsContext*, const char*))

    int i, ret;
    for (i = 0; i < l->nb_groups; i++) 
        OptionGroup *g = &l->groups[i];
        OptionsContext o;
        init_options(&o);
        o.g = g;
        ret = parse_optgroup(&o, g);
        av_log(NULL, AV_LOG_DEBUG, "Opening an %s file: %s.\\n", inout, g->arg);
        //将input arg option group赋值到OptionsContext后调用open_input_file
        ret = open_file(&o, g->arg);
        uninit_options(&o);
    
    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->format) 
        //在本例中通过video4linux2 找到ff_v4l2_demuxer
        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);
        
    
    ............................;
    //分配AVFormatContext
    ic = avformat_alloc_context();
    ..............FFMPEG源码分析从ffplay源码摸清ffmpeg框架

FFmpeg源码简单分析:结构体成员管理系统-AVOption

FFmpeg-4.2.4的filter: overlay源码分析

FFmpeg-4.2.4的filter: overlay源码分析

FFmpeg源码简单分析:libswscale的sws_scale()

怎么通过ffmpeg给MP4截图一个命令截5张图