ffmpeg 的帮助系统

-----------------------------------------------
author: hjjdebug
date:   2023年 07月 31日 星期一 14:32:15 CST
ffmpeg  的帮助系统
目的: 搞清楚它都打印了什么? 它是怎样实现的.
-----------------------------------------------
$ffprobe -h  1996行输出
$ffmpeg -h    111行输出
$ffplay -h    8492行输出

########################################
甲: 先分析ffprobe -h
########################################
$ffprobe -h  1996行输出
剥皮之后来到
void show_help_default(const char *opt, const char *arg)
它包含3个部分:
show_help_options(options, "Main options:", 0, 0, 0); // 把options 选项的help 信息全部打印出来,68行输出,实现是枚举这个options表
show_help_children(avformat_get_class(), AV_OPT_FLAG_DECODING_PARAM); // 把av_format_context_class类及其子类解码参数的帮助信息打印, 长到1270
show_help_children(avcodec_get_class(), AV_OPT_FLAG_DECODING_PARAM); // 把av_codec_context_class类及其子类解码参数的帮助信息打印, 长到1994

show_help_options() 函数后面三参数是3个flag, 包含flag,抛弃flag,alt_flag, 可看后面描述, 为0是不考虑的意思.
目前可简单理解为把 ffprobe 的主选项 的帮助信息(或说描述信息)都打印出来
------------------------------------------------------------
看第2部分, avformat 类及其子类的帮助
show_help_children(avformat_get_class(), AV_OPT_FLAG_DECODING_PARAM); // 把av_format_context_class类族解码参数的帮助信息打印, 长到1270
------------------------------------------------------------
void show_help_children(const AVClass *class, int flags, int level) // 这是一个递归调用函数, level 是我加上的参数,为了搞清楚它的层次调用参数
{
    if (class->option) { //它传来的AVClass 是名叫AVFormatContext的类. 根据该对象的options 表位置,
        printf("----level %d ----\n",level);
        av_opt_show2(&class, NULL, flags, 0);  // 先处理自己, 依据flags, 漂亮的组织打印内容.
        printf("\n");
    }

    void *iter = NULL;
    const AVClass *child;
    while (child = av_opt_child_class_iterate(class, &iter)) //然后枚举它的子类,
    {
        show_help_children(child, flags,++level); //递归调用该函数.
        level--;
    }
}
先处理自己,再枚举递归叫先序遍历. 反之叫后序遍历.
处理很简单(代码简单,递归),但关系很复杂,这么多类(几百个类)它们的关系是什么?
我看了一下,大部分都是一级(儿子), 少部分有层级关系,最大3级.
例如:

----level 1 ----
AVIOContext AVOptions:
  -protocol_whitelist <string>     .D......... List of protocols that are allowed to be used

----level 2 ----
URLContext AVOptions:
  -protocol_whitelist <string>     .D......... List of protocols that are allowed to be used
  -protocol_blacklist <string>     .D......... List of protocols that are not allowed to be used
  -rw_timeout        <int64>      ED......... Timeout for IO operations (in microseconds) (from 0 to I64_MAX) (default 0)

----level 3 ----
Async AVOptions:

----level 3 ----
cache AVOptions:
  -read_ahead_limit  <int>        .D......... Amount in bytes that may be read ahead when seeking isn't supported, -1 for unlimited (from -1 to INT_MAX) (default 65536)

----level 3 ----
crypto AVOptions:
  -key               <binary>     ED......... AES encryption/decryption key
  -iv                <binary>     ED......... AES encryption/decryption initialization vector
  -decryption_key    <binary>     .D......... AES decryption key
  -decryption_iv     <binary>     .D......... AES decryption initialization vector

----level 3 ----
ffrtmphttp AVOptions:
  -ffrtmphttp_tls    <boolean>    .D......... Use a HTTPS tunneling connection (RTMPTS). (default false)

----level 3 ----
file AVOptions:
  -follow            <int>        .D......... Follow a file as it is being written (from 0 to 1) (default 0)
  -seekable          <int>        ED......... Sets if the file is seekable (from -1 to 0) (default -1)

----level 3 ----
ftp AVOptions:
  -timeout           <int>        ED......... set timeout of socket I/O operations (from -1 to INT_MAX) (default -1)
  -ftp-anonymous-password <string>     ED......... password for anonymous login. E-mail address should be used.
  -ftp-user          <string>     ED......... user for FTP login. Overridden by whatever is in the URL.
  -ftp-password      <string>     ED......... password for FTP login. Overridden by whatever is in the URL.

----level 3 ----
http AVOptions:
  -seekable          <boolean>    .D......... control seekability of connection (default auto)
  -http_proxy        <string>     ED......... set HTTP proxy to tunnel through
  -headers           <string>     ED......... set custom HTTP headers, can override built in default headers
  -content_type      <string>     ED......... set a specific content type for the POST messages

我终于明白为什么要标注代码了,
因为上层描述,不足以描述清楚其细节.
代码之精妙,需要一步步标注其代码才能说清其中的关系,
并且还要用更多语言描述其精妙之处,包括参数,返回值,甚至直达bit

下面把代码copy 过来标注:

//分析av_opt_child_class_iterate 函数, 这类似于c++代理模式, 你让我干什么,我就让代理干什么.
//而这里的代理就是调用者自己, 你让我干什么,传给我函数,我让函数干什么, 这也就c的回调函数
const AVClass *av_opt_child_class_iterate(const AVClass *class, void **iter)
{
    if (class->child_class_iterate) // 如果传来的类参数包含枚举函数,
        return class->child_class_iterate(iter); // 就调用这个类参数包含的枚举函数并返回
    return NULL;
}

每一个层次对孩子们的枚举方式不同,我们看看AVFormatContext 的枚举方式, 这是AVFormatContext 的枚举回调
enum {
    CHILD_CLASS_ITER_AVIO = 0,
    CHILD_CLASS_ITER_MUX,
    CHILD_CLASS_ITER_DEMUX,
    CHILD_CLASS_ITER_DONE,

};
#define ITER_STATE_SHIFT 16
static const AVClass *format_child_class_iterate(void **iter)
{
    // we use the low 16 bits of iter as the value to be passed to
    // av_(de)muxer_iterate()
    // 用val 作为muxer,demuxer 枚举下一个的索引
    void *val = (void*)(((uintptr_t)*iter) & ((1 << ITER_STATE_SHIFT) - 1)); //就是说val不会超过16位,它实际上是表格的索引!!
    //用state (状态), 描述它目前处于那个层次
    unsigned int state = ((uintptr_t)*iter) >> ITER_STATE_SHIFT;
    const AVClass *ret = NULL;

    if (state == CHILD_CLASS_ITER_AVIO) {
        ret = &ff_avio_class;  // avio 只会进来一次, 但avio 还会有自己的孩子们
        state++;
        goto finish;
    }

    if (state == CHILD_CLASS_ITER_MUX) {
        const AVOutputFormat *ofmt;

        while ((ofmt = av_muxer_iterate(&val))) { // muxer 都是format 的儿子, val为下一个的索引
            ret = ofmt->priv_class;  //找到包含类的地址,返回
            if (ret)
                goto finish;
        }

        val = NULL;
        state++;
    }

    if (state == CHILD_CLASS_ITER_DEMUX) {
        const AVInputFormat *ifmt;

        while ((ifmt = av_demuxer_iterate(&val))) { // muxer 都是format 的儿子, val为下一个的索引
            ret = ifmt->priv_class; //找到包含类的地址,返回
            if (ret)
                goto finish;
        }
        val = NULL;
        state++;
    }

finish:
    // make sure none av_(de)muxer_iterate does not set the high bits of val
    av_assert0(!((uintptr_t)val >> ITER_STATE_SHIFT));
    *iter = (void*)((uintptr_t)val | (state << ITER_STATE_SHIFT));
    return ret;
}

好了,对av_format_context_class 的枚举搞清楚了, 以后想在其下加一个context 也可以操作了.!
------------------------------------------------------------
看第3部分. avcodec 类的帮助
show_help_children(avcodec_get_class(), AV_OPT_FLAG_DECODING_PARAM); // 把av_codec_context_class类族解码参数的帮助信息打印, 长到1994
------------------------------------------------------------
有了第2部分的铺垫,第3部分就容易了,因为它们调用同一个函数,只是第一个参数class 指针不同而已.
void show_help_children(const AVClass *class, int flags, int level)
{
    if (class->option) {
        printf("----level %d ----\n",level);
        av_opt_show2(&class, NULL, flags, 0); //传来的类实例是AVCodecContext, 先打印自己,这是0级,
        printf("\n");
    }

    void *iter = NULL; // 它如果改为 int index=0, 读者会更容易理解, 因为*iter是按0,1,2,3...来改变的,并不是指针.
    const AVClass *child;
    while (child = av_opt_child_class_iterate(class, &iter)) //再枚举孩子们打印
    {
        show_help_children(child, flags,++level);
        level--;
    }
}

//再分析一次av_opt_child_class_iterate 函数, 这类似于c++代理模式, 你让我干什么,我就让代理干什么.
//而这里的代理就是调用者自己, 你让我干什么,传给我函数,我让函数干什么, 这也就c的回调函数
const AVClass *av_opt_child_class_iterate(const AVClass *class, void **iter)
{
    if (class->child_class_iterate) // 如果传来的类参数包含枚举函数,
        return class->child_class_iterate(iter); // 就调用这个类参数包含的枚举函数并返回
    return NULL;
}
我们看看这个回调函数,
static const AVClass *codec_child_class_iterate(void **iter) //这个传来的地址,包含着索引信息
{
    const AVCodec *c;
    /* find next codec with priv options */
    while (c = av_codec_iterate(iter)) //它就是很简单的表格(数组)枚举了.
        if (c->priv_class)
            return c->priv_class;
    return NULL;
}

//说它简单,功能确实简单,但也有可圈点之处.
const AVCodec *av_codec_iterate(void **opaque)
{
    uintptr_t i = (uintptr_t)*opaque; //怎样拿到索引号,靠参数
    const AVCodec *c = codec_list[i];
    if (c)
        *opaque = (void*)(i + 1); //把下一个索引号,还保留到参数中,参数是个地址. 地址是调用者的一个局部变量地址

    return c;
}

########################################
乙: 分析ffmpeg -h
########################################
ffmpeg 的分析多了一个split_commandline()函数, 它的实现不是这里分析的重点.执行结果是把argc,argv参数转移到了octx中
/* split the commandline into an internal representation */
ret = split_commandline(&octx, argc, argv, options, groups, FF_ARRAY_ELEMS(groups));
下一句完成所有打印输出工作.
 ret = parse_optgroup(NULL, &octx.global_opts); --> 关键是执行了下一句
 ret = write_option(octx, o->opt, o->key, o->val); --> 下面是根据option定义找到一个要执行的函数.

//代码有简化
int show_help(void *optctx, const char *arg)
{
    av_log_set_callback(log_callback_help);
    char *topic = arg
    char *par = strchr(topic, '=');  // -h 后面参数, =前面是topic, =后面是arg, 无参数 *topic 为0
    if (par) *par++ = 0;

    if (!*topic) {
        show_help_default(topic, par);  // -h 无参数走的是这路, *topic=0
    } else if (!strcmp(topic, "decoder")) { //例 -h decoder=h264, =后必需要跟名字, 名字可以用 -decoders查询
        show_help_codec(par, 0);
    } else if (!strcmp(topic, "encoder")) {  //例 -h encoder=h264, =后必需要跟名字,名字可以用 -encoders查询
        show_help_codec(par, 1);
    } else if (!strcmp(topic, "demuxer")) {//例 -h demuxer=h264, =后必需要跟名字,名字可以用 -demuxers查询
        show_help_demuxer(par);
    } else if (!strcmp(topic, "muxer")) {//例 -h muxer=h264, =后必需要跟名字,名字可以用 -muxers查询
        show_help_muxer(par);
    } else if (!strcmp(topic, "protocol")) {//例 -h protocol=file =后必需要跟名字,名字可以用 -protocols查询
        show_help_protocol(par);
#if CONFIG_AVFILTER
    } else if (!strcmp(topic, "filter")) { //例 -h filter=overlay =后必需要跟名字,名字可以用 -filters查询
        show_help_filter(par);
#endif
    } else if (!strcmp(topic, "bsf")) { //例 -h bsf=h264_mp4toannexb =后必需要跟名字,名字可以用 -bsfs查询
        show_help_bsf(par);
    } else {
        show_help_default(topic, par); //默认走这里
    }

    av_freep(&topic);
    return 0;
}


void show_help_default(const char *opt, const char *arg)
{
    int show_advanced = 0, show_avoptions = 0;
    if (opt && *opt) {
        if (!strcmp(opt, "long"))
            show_advanced = 1;
        else if (!strcmp(opt, "full"))
            show_advanced = show_avoptions = 1;
        else
            av_log(NULL, AV_LOG_ERROR, "Unknown help option '%s'.\n", opt);
    }

    show_usage(); // 打印一行台头

    printf("Getting help:\n"      //打印如何获取帮助. -h, -h long, -h full, -h type=name
           "    -h      -- print basic options\n"
           "    -h long -- print more options\n"
           "    -h full -- print all options (including all format and codec specific options, very long)\n"
           "    -h type=name -- print all options for the named decoder/encoder/demuxer/muxer/filter/bsf/protocol\n"
           "    See man %s for detailed description of the options.\n"
           "\n", program_name);

    //打印具体包含哪些type, type 是个类,知道了type, 还可以用 -types 查看该type下具体包含哪些种. 这样 -h type=name 就完整了.
    show_help_options(options, "Print help / information / capabilities:", OPT_EXIT, 0, 0);

    /* per-file options have at least one of those set */
    const int per_file = OPT_SPEC | OPT_OFFSET | OPT_PERFILE;
    //req_flags 为0,所有项参与,rej_flags=per_file |OPT_EXIT|OPT_EXPERT, 有这些标志的项均不显示
    //此为显示全局标志
    show_help_options(options, "Global options (affect whole program" "instead of just one file):", 0, per_file | OPT_EXIT | OPT_EXPERT, 0);
    if (show_advanced)
        show_help_options(options, "Advanced global options:", OPT_EXPERT, per_file | OPT_EXIT, 0); //显示OPT_EXPERT项,但不包含per_file|OPT_EXIT标志

    //要求有per_file标志,但过滤了一些标志
    //此为显示每个文件都具有的主选项,项fmt,codec,start time,duration, frame等
    show_help_options(options, "Per-file main options:", 0, OPT_EXPERT | OPT_AUDIO | OPT_VIDEO | OPT_SUBTITLE | OPT_EXIT, per_file);
    if (show_advanced)
        show_help_options(options, "Advanced per-file options:", OPT_EXPERT, OPT_AUDIO | OPT_VIDEO | OPT_SUBTITLE, per_file);

    //要求是OPT_VIDEO,过滤了OPT_EXPERT|OPT_AUDIO
    //此为video 选项
    show_help_options(options, "Video options:", OPT_VIDEO, OPT_EXPERT | OPT_AUDIO, 0);
    if (show_advanced)
        show_help_options(options, "Advanced Video options:", OPT_EXPERT | OPT_VIDEO, OPT_AUDIO, 0); //添一部分,但不能是OPT_AUDIO flag

    //此为audio 选项
    show_help_options(options, "Audio options:", OPT_AUDIO, OPT_EXPERT | OPT_VIDEO, 0); // 类似处理
    if (show_advanced)
        show_help_options(options, "Advanced Audio options:", OPT_EXPERT | OPT_AUDIO, OPT_VIDEO, 0);

    //此为subtitle 选项
    show_help_options(options, "Subtitle options:", OPT_SUBTITLE, 0, 0); //要求是OPT_SUBTITLE flag
    printf("\n");

    if (show_avoptions) { // 如果打开了 show_avoptions, 还会显示子类的帮助信息,这里就不分析了.
        int flags = AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_ENCODING_PARAM;
        show_help_children(avcodec_get_class(), flags,0);
        show_help_children(avformat_get_class(), flags,0);
        show_help_children(avfilter_get_class(), AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM,0);
        show_help_children(av_bsf_get_class(), AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_BSF_PARAM,0);
    }
}

关键是show_help_options() 函数
不懂的东西都在代码里,怪不得要标注代码呢. 一下就明白req, rej 是如何起作用的了.
void show_help_options(const OptionDef *options, const char *msg, int req_flags, int rej_flags, int alt_flags)
{
    const OptionDef *po;
    int first;

    first = 1;
    for (po = options; po->name; po++) {
        char buf[128];

//option flag 必需要包含 req_flag,如果alt_flag存在,也必需包含alt_flag, 不能包含rej_flag 才会打印输出
        if (((po->flags & req_flags) != req_flags) ||
            (alt_flags && !(po->flags & alt_flags)) ||
            (po->flags & rej_flags))
            continue;

        if (first) {
            printf("%s\n", msg);
            first = 0;
        }
        av_strlcpy(buf, po->name, sizeof(buf));
        if (po->argname) {
            av_strlcat(buf, " ", sizeof(buf));
            av_strlcat(buf, po->argname, sizeof(buf)); //把选项名称,选项参数名称连接起来输出
        }
        printf("-%-17s  %s\n", buf, po->help);
    }
    printf("\n");
}
########################################
丙: 分析ffplay -h
########################################
show_help_default(topic, par);
但这次连接的是ffplay.c 下的, 代码非常简洁,但打印的内容很多,8千多行输出. 所以你要会看主要的.
void show_help_default(const char *opt, const char *arg)
{
    av_log_set_callback(log_callback_help);
    show_usage(); //一行台头
    show_help_options(options, "Main options:", 0, OPT_EXPERT, 0); // ffplay主选项输出
    show_help_options(options, "Advanced options:", OPT_EXPERT, 0, 0); // ffplay高级选项输出
    printf("\n");
    show_help_children(avcodec_get_class(), AV_OPT_FLAG_DECODING_PARAM,0);  //AVCodecContext 类族的帮助信息
    show_help_children(avformat_get_class(), AV_OPT_FLAG_DECODING_PARAM,0); //AVFormatContext 类族的帮助信息
#if !CONFIG_AVFILTER
    show_help_children(sws_get_class(), AV_OPT_FLAG_ENCODING_PARAM,0);
#else
    show_help_children(avfilter_get_class(), AV_OPT_FLAG_FILTERING_PARAM,0); //AVFilterContext 类族的帮助信息
#endif
    printf("\nWhile playing:\n"                                //这是ffplay 的操控按键, 很有用!
           "q, ESC              quit\n"
           "f                   toggle full screen\n"
           "p, SPC              pause\n"
           "m                   toggle mute\n"
           "9, 0                decrease and increase volume respectively\n"
           "/, *                decrease and increase volume respectively\n"
           "a                   cycle audio channel in the current program\n"
           "v                   cycle video channel\n"
           "t                   cycle subtitle channel in the current program\n"
           "c                   cycle program\n"
           "w                   cycle video filters or show modes\n"
           "s                   activate frame-step mode\n"
           "left/right          seek backward/forward 10 seconds or to custom interval if -seek_interval is set\n"
           "down/up             seek backward/forward 1 minute\n"
           "page down/page up   seek backward/forward 10 minutes\n"
           "right mouse click   seek to percentage in file corresponding to fraction of width\n"
           "left double-click   toggle full screen\n"
           );
}
在 /home/hjj/FFmpeg-n4.4/fftools/cmdutils.c 下调用 show_help_default() 函数, 根据执行文件的不同,它可能会连接到
ffprobe.c 或 ffmpeg.c 或 ffplay.c , 而这三个执行文件中各有各的实现. 这属于不同的执行文件,没什么好说的.
它们各自调用的函数,主要还是show_help_options(), show_help_children() 前面已经详细分析过了,如此帮助系统分析完毕!

贴上一个我精心打造的测试代码,只依赖于libav库代码,不依赖fftools下文件.你只要连接库文件就可以了.

执行生成的文件,它会输出AVCodec 家族 800多行的帮助信息.

/***************************************************
 * author: hjjdebug
 * date: 2023年 09月 10日 星期日 18:03:21 CST
 * description: AVOption help 测试简单实例,
 * 仅依赖libav代码,不依赖fftools/下代码
 ************************************************* */
#include <stdio.h>
extern "C"
{
//有AV_OPT_FLAG_DECODING_PARAM,av_opt_show2(),av_opt_child_class_iterate定义	
#include <libavutil/opt.h>  
#include <libavcodec/avcodec.h> // 有avcodec_get_class()定义
#include <libavutil/log.h> // 有AVClass 定义
}

void show_help_children(const AVClass *obj, int flags, int level)
{
    if (obj->option) { //对象有选项表,
		printf("----level %d ----\n",level);
        av_opt_show2(&obj, NULL, flags, 0); //打印该类的所有选项
        printf("\n");
    }

    void *iter = NULL;
    const AVClass *child; //cpp 文件,可以就近定义变量
    while ((child = av_opt_child_class_iterate(obj, &iter)))
	{//递归,因为先打印,再递归到下一层,故为前序遍历
        show_help_children(child, flags,++level); 
		level--;
	}
}

int main()
{
    show_help_children(avcodec_get_class(), AV_OPT_FLAG_DECODING_PARAM,0);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
安卓是一种广泛使用的移动操作系统,具有开放性和灵活性,因此我们选择开发安卓项目源码,以满足开发人员的需求。 首先,我们的安卓项目源码提供了丰富的功能和模块,可以满足各种类型的安卓应用需求。无论是社交、娱乐、教育还是商务,我们的项目源码都提供了相应的功能和界面设计,使得开发人员可以快速构建符合需求的安卓应用。 其次,我们的安卓项目源码具有良好的可扩展性和可定制性。我们将项目源码设计为模块化的结构,开发人员可以根据自己的需求选择和定制所需的功能模块。同时,我们还提供了丰富的文档和示例代码,以帮助开发人员理解和使用项目源码。 我们的安卓项目源码还采用了一些常用的技术和工具,以提高开发效率和代码质量。例如,我们使用了Android Studio作为开发工具,它提供了一系列的功能和工具,如代码编辑器、调试器和模拟器,使得开发人员可以快速开发和测试安卓应用。我们还使用了Java作为主要的编程语言,它是安卓应用开发的主流语言,具有丰富的库和框架,使得开发人员可以轻松实现各种功能和特性。 此外,我们的安卓项目源码还注重用户体验和界面设计。我们提供了一系列的界面模板和样式库,使得开发人员可以轻松创建漂亮而用户友好的安卓界面。我们还提供了一些常用的界面组件和动画效果,以增加安卓应用的交互性和吸引力。 总之,我们的安卓项目源码旨在帮助开发人员快速构建各种类型的安卓应用。无论是社交、娱乐、教育还是商务,我们相信我们的项目源码将能够提供强大的支持和帮助。感谢您对我们项目的关注和支持!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值