ffmpeg.c(4.3.1)源码剖析(二)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: ffmpeg.c(4.3.1)源码剖析(二)

ffmpeg.c(4.3.1)源码剖析(一)https://developer.aliyun.com/article/1473997

三、main 函数主要流程分析

main 函数如下:

int main_ffmpeg431(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);
    if(argc>1 && !strcmp(argv[1], "-d")){
        run_as_daemon=1;
        av_log_set_callback(log_callback_null);
        argc--;
        argv++;
    }
#if CONFIG_AVDEVICE
    avdevice_register_all();
#endif
    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);
    }
    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、解析命令行参数
  • 3、编码
  • 4、收尾

四、ffmpeg_parse_options

下面是 ffmpeg_parse_options 的调用关系

1、命令行例子

ffmpeg -i abc.mp4 -i bbb.avi -vcodec libx264 -acodec aac -vf scale=640:480 -f flv -y abc.flv
  • 命令行包括三个部分:输入参数,输出参数,和全局选项。
  • -i /home/ron/music/avm.mp4 是输入参数,a.mp4 是输出参数。输入/输出参数可以有专属的选项,这些选项应该紧挨着放在输入输出参数前面。如 -vf “split [main][tmp]…[main][flip]” 就是输出参数 a.mp4 的选项。
  • 全局选项的位置不需要限定, 因为选项是以选项名字查找的。
  • 可以有多组输入参数和多组输出参数。

①、解析命令行 split_commandline()

split_commandline() 负责解析命令行。

/**
 * Split the commandline into an intermediate form convenient for further
 * processing.
 *
 * The commandline is assumed to be composed of options which either belong to a
 * group (those with OPT_SPEC, OPT_OFFSET or OPT_PERFILE) or are global
 * (everything else).
 *
 * A group (defined by an OptionGroupDef struct) is a sequence of options
 * terminated by either a group separator option (e.g. -i) or a parameter that
 * is not an option (doesn't start with -). A group without a separator option
 * must always be first in the supplied groups list.
 *
 * All options within the same group are stored in one OptionGroup struct in an
 * OptionGroupList, all groups with the same group definition are stored in one
 * OptionGroupList in OptionParseContext.groups. The order of group lists is the
 * same as the order of group definitions.
 */
int split_commandline(OptionParseContext *octx, int argc, char *argv[],
                      const OptionDef *options,
                      const OptionGroupDef *groups, int nb_groups);

解析的结果保存在 OptionParseContext 中。解析时需要参考 OptionDef 和 OptionGroupDef。OptonDef[] 是支持 ffmpeg 的选项列表,OptionGroupDef[] 是支持的组列表,包括输入类和输出类,前者以 -i 开头,加上设备名。后者只有文件名。

下面的类图显示了涉及的类:

  • OptionGroup 保存一个输入(或输出)和它的选项列表。Option 表示一个选项。
  • OptionParseContext 中包括多个 OptionGroup。全局选项保存在 global_opts 中。所有输入设备的选项保存在一个 OptionGroupList 实例中,所有输出设备的选项保存在另一个实例中。 两者合起来组成数组 groups。

split_commandline() 在一个循环中解析命令行,主要涉及如下函数。

函数 功能
find_option() 查询支持的 option 列表, 检查当前元素是否一个option
add_option() 将 option 加入一个临时组。(因为 option 先于 group 出现,还不知道应该加入到哪个组。)
match_group_separator() 查询支持的 group 列表,检查当前元素是否是一个 Group
finish_group() 设置临时组的参数,并用它填充 OptionParseContext.groups(现在知道应该加入哪个组了)

②、parse_optgroup()

parse_optgroup() 负责将 OptionGroup 转换成 OptionsContext。

/**
 * Parse an options group and write results into optctx.
 *
 * @param optctx an app-specific options context. NULL for global options group
 */
int parse_optgroup(void *optctx, OptionGroup *g);
  • OptionGroup 保存的选项值是字符串,而 OptionsContext 保存的值是由 OptionDef 定义的实际类型。 parse_optgroup() 的第一个参数 optctx 实际上是 OptonsContext。

下面的类图显示了涉及的类:

  • SpecifierOpt 保存实际类型的选项。OptionsContext 有若干个 SpecifierOpt 数组的成员。每个 specfier 数组保存一类选项。如 filters 保存 ”filter” 选项。但 filter 可以是 ”filter:v”,属于 video,也可以是“filter:a”,属于 audio。SpecifierOpt.specifier 成员就是用来标记这个选项应该属于谁的。对于”filter:v”,SpecifierOpt.specifier 就是”v”。
  • 这里顺便提一下 AVDictionary。解析过程没有用到它。用户设置的选项可能不成功,而选项的最终值会保存在这里。用 av_dict_set()函数设置它。

parse_optgroup() 函数遍历 OptonGroup 中的 Option,调用 write_option() 将其写入 OptionsContext。

  • 对于基本的选项,它的 OptionDef 中定义了它在 OptionsContext 的偏移,所以将字符串转化后,直接写入就好了。比如”filter:v”。
  • 有的选项可能是其他选项的别名。这时它的 OptionDef 指定了一个回调函数。这个函数会重定向到所指向的选项上去。如”vf”就是”filter:v”的别名,它的 OptionDef 指定了回调函数 opt_video_filter()。这个函数会调用 parse_option() 和 find_option() 查找”filter:v”对应的 OptionDef,并再次调用 write_option()。
  • 全局选项。它的 OptionDef 也定义了一个回调函数。这个函数直接设置全局变量。如 loglevel,它的 OptionDef 定义了 opt_loglevel()。这个函数调用 av_log_set_level() 设置日志输出等级。

③、MATCH_PER_XXX_OPT()

宏 MATCH_PER_TYPE_OPT() 和 MATCH_PER_STREAM_OPT() 用于从 OptionsContext 读值。

  • 前者指定参数 mediatype,用它跟 OptionsContext.spcifier 比较,找出 option 并读出。
  • 后者指定参数 AVStream,调用 check_stream_specifier(),用 AVStream 的属性与
    OptionContext.specifier 匹配,找出 option 并读出。
#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st)\
{\
    int i, ret, matches = 0;\
    SpecifierOpt *so = NULL;\
    for (i = 0; i < o->nb_ ## name; i++) {\
        char *spec = o->name[i].specifier;\
        if ((ret = check_stream_specifier(fmtctx, st, spec)) > 0) {\
            outvar = o->name[i].u.type;\
            so = &o->name[i];\
            matches++;\
        } else if (ret < 0)\
            exit_program(1);\
    }\
    if (matches > 1)\
       WARN_MULTIPLE_OPT_USAGE(name, type, so, st);\
}
#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype)\
{\
    int i;\
    for (i = 0; i < o->nb_ ## name; i++) {\
        char *spec = o->name[i].specifier;\
        if (!strcmp(spec, mediatype))\
            outvar = o->name[i].u.type;\
    }\
}

2、vf 选项解析

下图是 avfilter_graph_parse2() 的函数调用关系。

①、filters

如下是 filters 的一个例子。 它来自 ffmpeg 的文档:https://ffmpeg.org//ffmpeg-filters.html#Filtergraph-description

ffmpeg -i INPUT -vf “split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2” OUTPUT

对应 FilterGraph 的结构示意图如下。矩形框内是 vf 的内容对应的部分。其中 split 应该导出到 inputs 中,overlay 应该导出到 outputs 中。

②、vf 术语

描述 vf 的解析过程需要使用一些术语。其中一部分是关于 vf 语法的,另外一部分是关于生成的 FilterGraph 结构的。

上图标出了 vf 语法的术语。

  • 过滤器。过滤器用红色标出,包括它的名字和参数。如”split”,只有名字。又如”overlay=0:H/2”,overlay 是名字, ”0:H/2”是参数。名字和参数用 = 连接。
  • 位置点。有两类位置点,有名的和无名的。有名位置点用绿色标出,名字用 [] 包住,如 main, flip, tmp。无名位置点不必标出,如下图所示。
  • 路径。路径是一条从位置点开始,中间过滤器和位置点交错,在位置点结束的处理流程。多条路径组成整个 filtergraph。中间的位置点都是无名的,开始和结束的位置点应该是有名的,除非这条路径在 filtergraph 的开始和结束位置。路径之间用;隔开。 如 [tmp] crop=iw:ih/2:0:0, vflip [flip]。以 tmp 开始,中间包括 crop 和 vflip 和一个无名位置点,在 flip 结束。有名位置点是该路径与其他路径的连接点,所以需要有一个名字来标记,而无名位置点只存在该路径内部的两个过滤器之间,是隐含的,所以不需要名字。

下图是 FilterGraph 的结构图。

  • FilterGraph 是由一系列的过滤器,Pad 和 Pad Link 构成的。
  • 过滤器来自 FilterGraph 语法中的过滤器,它有一组 In Pad 和一组 OutPad,Pad 与语法中的位置点对应。过滤器之间通过 Pad 联系,Pad Link 用来将一个 In Pad 连接到一个 OutPad。Pad Link 没有对应的语法元素。
  • Input/Output 用于解析过程,也用于保存整个解析的结果,以返回给调用者。open_inputs 标记当前还没有解析(与其他 OutPad 连接)的 InPad,open_outputs 标记当前还没有解析的 OutPad,curr_inputs 标记当前将要解析的 InPad。

③、avfilter_graph_parse2()

avfilter_graph_parse2() 负责解析 vf 选项内容。

/**
 * Add a graph described by a string to a graph.
 *
 * @param[in]  graph   the filter graph where to link the parsed graph context
 * @param[in]  filters string to be parsed
 * @param[out] inputs  a linked list of all free (unlinked) inputs of the
 *                     parsed graph will be returned here. It is to be freed
 *                     by the caller using avfilter_inout_free().
 * @param[out] outputs a linked list of all free (unlinked) outputs of the
 *                     parsed graph will be returned here. It is to be freed by the
 *                     caller using avfilter_inout_free().
 * @return zero on success, a negative AVERROR code on error
 *
 * @note This function returns the inputs and outputs that are left
 * unlinked after parsing the graph and the caller then deals with
 * them.
 * @note This function makes no reference whatsoever to already
 * existing parts of the graph and the inputs parameter will on return
 * contain inputs of the newly parsed part of the graph.  Analogously
 * the outputs parameter will contain outputs of the newly created
 * filters.
 */
int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
                          AVFilterInOut **inputs,
                          AVFilterInOut **outputs);

输入参数 filters 是 vf 选项内容。输出参数 Inputs 是导出的输入接口,outputs 是 filters 导出的输出接口。

avfilter_graph_parse2() 主要调用四个函数进行解析。

函数 功能
parse_input() 选取若干 open_outputs,以更新 curr_inputs
parse_filter() 解析过滤器
link_filter_inouts() 将新的过滤器连入当前的 curr_inputs,并更新 curr_inputs
parse_output() 结束当前的 curr_inputs,加入 open_outputs。

④、FilterGraph 类

下面的类图显示了 FilterGraph 各元素对应的类。

  • AVFilterContext 表示过滤器。AVFilter 是它的属性类。
  • AVFilterPad 是 Pad 类。一个 AVFilterContext 实例包括 AVFilterPad 的一组 In Pad 实例和一组 Out Pad 实例。 AVFilterLink 是 Pad Link 类,它连接两个 AVFilterPad 实例。
  • AVFilterLink 有一个 FFFrameQueue,用于保存过滤的中间结果。这是一个 frame 的数据通道。
  • AVFilterContext 有一个空间,用于保存该特定类型 Filter 的私有信息,可以是 CropContext,SplitContext 或其他 filter 的一种。
  • AVFilterInOut 用于解析过程标记 open_iputs, open_ouputs 和 curr_inputs。它没有直接引用 AVFilterPad,而是引用 AVFilterContext,和用序号间接指向 AVFilterPad。


ffmpeg.c(4.3.1)源码剖析(三)https://developer.aliyun.com/article/1473999

目录
相关文章
|
7月前
|
编解码 API
ffmpeg.c(4.3.1)源码剖析(一)
ffmpeg.c(4.3.1)源码剖析(一)
139 2
|
2月前
|
Ubuntu 应用服务中间件 nginx
Ubuntu安装笔记(三):ffmpeg(3.2.16)源码编译opencv(3.4.0)
本文是关于Ubuntu系统中使用ffmpeg 3.2.16源码编译OpenCV 3.4.0的安装笔记,包括安装ffmpeg、编译OpenCV、卸载OpenCV以及常见报错处理。
198 2
Ubuntu安装笔记(三):ffmpeg(3.2.16)源码编译opencv(3.4.0)
|
7月前
|
缓存 网络协议 Windows
FFmpeg开发笔记(六)如何访问Github下载FFmpeg源码
在国内访问GitHub不稳定时,可以采取三种解决方法。首先,通过网站(<https://ping.chinaz.com/github.com>)找到快速响应的GitHub IP,将其添加到本地hosts文件,然后刷新DNS缓存以正常访问。其次,使用代下载网站如(<https://d.serctl.com/>)下载GitHub上的压缩包。最后,可从国内镜像站点,如码云(<https://gitee.com/mirrors/ffmpeg>),下载FFmpeg等开源代码。这些方法有助于绕过访问限制,确保FFmpeg学习与开发的顺利进行。
159 3
FFmpeg开发笔记(六)如何访问Github下载FFmpeg源码
|
7月前
|
编解码 容器
ffmpeg.c(4.3.1)源码剖析(三)
ffmpeg.c(4.3.1)源码剖析(三)
56 1
|
7月前
|
存储 编解码 缓存
FFmpeg之旅:深入解析FFplay源码
FFmpeg之旅:深入解析FFplay源码
656 0
|
7月前
|
编解码 网络协议 API
ffmpeg命令行工具源码之结构体分析1-命令行参数(未完结,持续更新)
ffmpeg作为多媒体文件转换工具,至少需要有一个要转换的输入文件信息(不仅仅是普通文件,还可以是摄像头设备,网络流等),和通常至少需要一个输出格式的文件(输出文件不仅仅指普通的文件,网络协议比如RTP协议,RTSP协议都可以理解为输出文件),ffmpeg的文件的转换过程主要由以下几个流程 (1)解封装 (2)解码 (3)过滤器 (4)编码 (5)封装 因此ffmpeg工具涉及的结构体主要就从这几个方面来说明这些结构体的含义。
80 0
|
7月前
|
Linux API C++
音视频windows安装ffmpeg6.0并使用vs调试源码笔记
音视频windows安装ffmpeg6.0并使用vs调试源码笔记
251 0
|
编译器 开发工具 C语言
FFmpeg开发笔记(一):ffmpeg介绍、windows开发环境搭建(mingw和msvc,无需源码编译)
FFmpeg开发笔记(一):ffmpeg介绍、windows开发环境搭建(mingw和msvc,无需源码编译)
FFmpeg开发笔记(一):ffmpeg介绍、windows开发环境搭建(mingw和msvc,无需源码编译)
|
C++ 编解码
把自定义的decoder加入ffmpeg源码
第一步: 在libavcodec目录下新建mkdecoder.c,并加入一下代码: [cpp] view plain copy   /*   *实现一个自己的decoder,编码工作其实就是把pkt的数据拷贝到frame  *作者:缪国凯(MK)   *821486004@qq.
1520 0
|
C++
把自定义的demuxer加入ffmpeg源码
.简介:把上一篇文章中的demuxer加入ffmpeg源码中去,使可以用命令行方式调用自定义的demuxer 第一步: 在libavformat目录下新建mkdemuxer.c和mkdemuxer.
1633 0