【FFMPEG】学习笔记 (2)命令行参数解析

    在看源代码之前,先对命令行的参数有一个了解。程序的main函数中的参数就是命令行传入的,所以先了解命令参数的规则有助于以后的源代码分析。
    这部分内容参考ffmpeg.org的文档部分,如果只是记录翻译内容达不到理解的目的。主要不是解释每个参数什么意思,而是理解输入参数的格式,代码中会映射成各种结构体。这里总结了对于这部分文档的理解,可能存在错误,欢迎指正。

一、基础命令格式

命令行的结构

文档中给出了通用格式:ffmpeg [global_options] {[input_file_options] -i input_url} … {[output_file_options] output_url} … 这个格式大致可以分成四个部分

  1. ffmpeg:这个是可执行程序本身,可以是绝对路径,如上一篇中的/opt/ffmpeg/_build/bin/ffmpeg
  2. [global_options]:这个是全局参数部分,如指定日志级别等
  3. {[input_file_options] -i input_url}:这部分是输入参数部分,也可以理解为视频源部分,这部分可以指定一个或多个视频源及参数,需要注意的是参数在前,-i 后面是视频源的路径
  4. {[output_file_options] output_url}:这部分是输入部分,指定输出的格式及其参数,同样可以指定一个或多个,参数在前,输出文件在后,但这里没有“-i”这样的标记参数

示例说明

用文档中的几个小示例来理解一下这个结构

ffmpeg -i input.avi -b:v 64k -bufsize 64k output.avi
  1. ffmpeg:可执行程序名称
  2. 全局变量:这里没有
  3. 输入部分:(没有参数)-i input.avi(输入的文件是当前目录下的input.avi文件)
  4. 输出部分:-b:v 64k -bufsize 64k(参数:视频比特率为64k,缓存大小64k)output.avi(输出文件是当前目录下的output.avi文件)
ffmpeg -r 1 -i input.m2v -r 24 output.avi
  1. ffmpeg:可执行程序名称
  2. 全局变量:这里没有
  3. 输入部分:-r 1(参数:设置帧率为1)-i input.avi(输入的文件是当前目录下的input.m2v文件)
  4. 输出部分:-r 24(参数:设置帧率为24)output.avi(输出文件是当前目录下的output.avi文件)

理解了参数的格式,如果想知道参数的含义直接查询文档就可以了

二、选择视频流

视频流选择分成两种方式:自动选择和手动选择。在命令行参数中通过指定 map 参数来区分,如果含有 map 参数则被解释为手动选择。这部分内容在官方文档里单独做了说明,结合官方的示例来理解一下。

  1. 自动选择
    在没有指定 map 参数的时候,ffmpeg程序会检查输出格式,根据输出格式来选择输入文件中可用的视频、音频和字幕等。自动选择会依据一些默认的规则:
    a. 视频选择分辨率最高的那个,如示例中B.mp4的stream 0
    b. 音频选择声道最多的那个,如示例中B.mp4中的stream 3
    c. 字幕选择第一个发现的字幕数据
    d. 其他数据(除视频、音频和字幕)不能自动选择

示例说明

假设我们有三个不同格式的视频文件分别是:A.avi、B.mp4、C.mkv,不同的视频格式我们也可以理解为不同的容器,每个容器里可能包含:视频、音频、字母等数据,分别用stream0~n来表示:

input file 'A.avi'
      stream 0: video 640x360        #一个分辨率为640 x 360的视频数据
      stream 1: audio 2 channels     #双声道音频数据

input file 'B.mp4'
      stream 0: video 1920x1080      #一个分辨率为1920x1080的视频数据
      stream 1: audio 2 channels     #双声道音频数据
      stream 2: subtitles (text)     #文本字幕数据1(中文)
      stream 3: audio 5.1 channels   #5.1声道的音频数据
      stream 4: subtitles (text)     #文本字幕数据2(英文)

input file 'C.mkv'
      stream 0: video 1280x720       #一个分辨率为1280x720的视频数据
      stream 1: audio 2 channels     #双声道音频数据
      stream 2: subtitles (image)    #图片字幕数据

命令1

ffmpeg -i A.avi -i B.mp4 out1.mkv out2.wav -map 1:a -c:a copy out3.mov
  1. 两个输入:A.avi、B.avi,三个输出:out1.mkv、out2.wav、out3.mov
  2. 前两个输出out1.mkv、out2.wav没有map参数,使用自动选择。
  3. out1.mkv是Matroska容器格式,可以包含视频、音频和字幕文件,因此根据上面说的默认规则:视频选择B.mp4中的stream 0,音频选择B.mp4中的stream 3,字幕选择B.mp4中的stream(因为A.avi中没有字幕数据)
  4. out2.wav是个音频文件,所以只包含音频,选择B.mp4中的stream 3
  5. out3.mov使用了map参数,因此根据参数指定,-map 1:a这个参数指定了B.mp4的音频。
  6. 前两个输出包含的流会被自动选择编码器。第三个输出指定音频编码为copy,也就是不进行编码。

三、过滤器

在编码之前,ffmpeg可以通过过滤器对原始的音视频数据进行处理,libavfilter库是对各种过滤器的实现。一次处理可以使用多个过滤器,这些过滤器可以以图的形式展示。ffmpeg中把过滤器图分成两类:简单和复杂。

简单过滤器

简单的过滤器是指只有一个输入和输出,并且都是相同类型的过滤器。如下图,编码和解码之间插入一个附加步骤来对处理帧数据:

 _________                        ______________
|         |                      |              |
| decoded |                      | encoded data |
| frames  |\                   _ | packets      |
|_________| \                  /||______________|
             \   __________   /
  simple     _\||          | /  encoder
  filtergraph   | filtered |/
                | frames   |
                |__________|

简单过滤器通过vf和af参数分别指定视频和音频的过滤器,一个对视频的简单过滤其可以画成类似下面的示意图:

 _______        _____________        _______        ________
|       |      |             |      |       |      |        |
| input | ---> | deinterlace | ---> | scale | ---> | output |
|_______|      |_____________|      |_______|      |________|

小提示:有些过滤器只是改变了帧的属性而没有改变帧的内容,例如:fps过滤只是改变帧的数量,没有改变内容

复杂过滤器

复杂过滤不能像简单过滤那样线性的处理数据。例如有多个输入或者输出,输出的流和输入的流类型不一样的时候,可以用下图表示:

_________
|         |
| input 0 |\                    __________
|_________| \                  |          |
             \   _________    /| output 0 |
              \ |         |  / |__________|
 _________     \| complex | /
|         |     |         |/
| input 1 |---->| filter  |\
|_________|     |         | \   __________
               /| graph   |  \ |          |
              / |         |   \| output 1 |
 _________   /  |_________|    |__________|
|         | /
| input 2 |/
|_________|

复杂过滤器通过lavfi参数指定,需要注意的是这个参数是个全局参数,因为复杂过滤器不能明确的指定是哪个输入或者输出。例如overlay过滤器就是把两个视频合成为一个。

如果想了解具体参数的含义可以直接查询官方文档

解析流程

示例中的参数被split_commandline方法循环处理

数组下标012345678
参数值ffmpeg-re-iBird.mp4-ccopy-fflvrtmp://192.168.1.202:1935

主要的判断流程如图:

Created with Raphaël 2.2.0 开始 optindex < argc '--'起始的参数 '-'起始的参数 组分隔符 普通参数 AV相关参数 -nofoo参数 结束 no yes no no

具体解析方法的源代码:

int split_commandline(OptionParseContext *octx, int argc, char *argv[], const OptionDef *options,
                      const OptionGroupDef *groups, int nb_groups) {
    int optindex = 1;
    int dashdash = -2;

    init_parse_context(octx, groups, nb_groups);
    while (optindex < argc) {
        //opt[0]='-' opt[1]='r'
        const char *opt = argv[optindex++], *arg;
        const OptionDef *po;
        int ret;

        av_log(NULL, AV_LOG_DEBUG, "Reading option '%s' ...", opt);

        if (opt[0] == '-' && opt[1] == '-' && !opt[2]) {
            dashdash = optindex;
            continue;
        }

        /* 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++;
        
        if ((ret = match_group_separator(groups, nb_groups, opt)) >= 0) {
            do {
                arg = argv[optindex++];
                if (!arg) {
                    av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'.\n", opt);
                    return AVERROR(EINVAL);
                }
            } while (0);
            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) {
                do {
                    arg = argv[optindex++];
                    if (!arg) {
                        av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'.\n", opt);
                        return AVERROR(EINVAL);
                    }
                } while (0);
            } 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;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值