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

Posted xfc_1939

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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; i<ic->nb_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;
    

(转)OpenFire源码学习之六:用户注册

转:http://blog.csdn.net/huwenfeng_2011/article/details/43413509

用户注册

注册流程:

1、客户端进行握手给服务端发送连接消息:

 

[html] view plain copy
 
  1. <stream:stream to="192.168.2.104" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0"></stream:stream>  

 

2、服务端回执:

[html] view plain copy
 
  1. <?xml version=‘1.0‘ encoding=‘UTF-8‘?><stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="hytest240" id="9fd61155" xml:lang="en" version="1.0">  
  2. <stream:features>  
  3. <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">  
  4. <mechanism>DIGEST-MD5</mechanism>  
  5. <mechanism>JIVE-SHAREDSECRET</mechanism>  
  6. <mechanism>PLAIN</mechanism>  
  7. <mechanism>ANONYMOUS</mechanism>  
  8. <mechanism>CRAM-MD5</mechanism>  
  9. </mechanisms>  
  10. <compression xmlns="http://jabber.org/features/compress">  
  11. <method>zlib</method>  
  12. </compression><auth xmlns="http://jabber.org/features/iq-auth"/>  
  13. <register xmlns="http://jabber.org/features/iq-register"/>  
  14. </stream:features>  

3、客户端发送注册申请

[html] view plain copy
 
  1. <iq id="69Bxy-0" to="hytest240" type="get">  
  2. <query xmlns="jabber:iq:register"></query>  
  3. </iq>  

4、服务端给出注册需要的填写的信息,相当与给客户端发送一个申请单

[html] view plain copy
 
  1. <iq type="result" id="69Bxy-0" from="hytest240">  
  2. <query xmlns="jabber:iq:register">  
  3. <username/><password/><email/><name/>  
  4. <xmlns="jabber:x:data" type="form">  
  5. <title>XMPP Client Registration</title>  
  6. <instructions>Please provide the following information</instructions>  
  7. <field var="FORM_TYPE" type="hidden">  
  8. <value>jabber:iq:register</value>  
  9. </field>  
  10. <field var="username" type="text-single" label="Username">  
  11. <required/></field>  
  12. <field var="name" type="text-single" label="Full name"/>  
  13. <field var="email" type="text-single" label="Email"/>  
  14. <field var="password" type="text-private" label="Password">  
  15. <required/>  
  16. </field>  
  17. </x>  
  18. </query>  
  19. </iq>  

5、客户端接收到服务端发送的申请单后,并填写回复:

[html] view plain copy
 
  1. <iq id="69Bxy-1" to="hytest240" type="set">  
  2. <query xmlns="jabber:iq:register">  
  3. <username>test</username>  
  4. <email></email>  
  5. <name></name>  
  6. <password>123456</password>  
  7. </query>  
  8. </iq>  

6、注册完成后,服务端返回成功这里没有做出任何消息,仅仅只是回复。

[html] view plain copy
 
  1. <iq type="result" id="69Bxy-1" from="hytest240" to="hytest240/9fd61155"/>  

 

IQRegisterHandler

IQRegisterHandler位于org.jivesoftware.openfire.handler中。该类主要处理客户端注册信息。

该类中有两个比较重要方法initialize、handleIQ。接下来看这两个方法。

initialize

该方法主要是做初始化注册模板。这个注册模板就是在上面提到的需要发送给客户端申请注册的深表表单。源码如下:

[java] view plain copy
 
  1. @Override  
  2.     public void initialize(XMPPServer server) {  
  3.         super.initialize(server);  
  4.         userManager = server.getUserManager();  
  5.         rosterManager = server.getRosterManager();  
  6.   
  7.         if (probeResult == null) {  
  8.             // Create the basic element of the probeResult which contains the basic registration  
  9.             // information (e.g. username, passoword and email)  
  10.             probeResult = DocumentHelper.createElement(QName.get("query", "jabber:iq:register"));  
  11.             probeResult.addElement("username");  
  12.             probeResult.addElement("password");  
  13.             probeResult.addElement("email");  
  14.             probeResult.addElement("name");  
  15.   
  16.             // Create the registration form to include in the probeResult. The form will include  
  17.             // the basic information plus name and visibility of name and email.  
  18.             // TODO Future versions could allow plugin modules to add new fields to the form   
  19.             final DataForm registrationForm = new DataForm(DataForm.Type.form);  
  20.             registrationForm.setTitle("XMPP Client Registration");  
  21.             registrationForm.addInstruction("Please provide the following information");  
  22.   
  23.             final FormField fieldForm = registrationForm.addField();  
  24.             fieldForm.setVariable("FORM_TYPE");  
  25.             fieldForm.setType(FormField.Type.hidden);  
  26.             fieldForm.addValue("jabber:iq:register");  
  27.   
  28.             final FormField fieldUser = registrationForm.addField();  
  29.             fieldUser.setVariable("username");  
  30.             fieldUser.setType(FormField.Type.text_single);  
  31.             fieldUser.setLabel("Username");  
  32.             fieldUser.setRequired(true);  
  33.   
  34.             final FormField fieldName = registrationForm.addField();   
  35.             fieldName.setVariable("name");  
  36.             fieldName.setType(FormField.Type.text_single);  
  37.             fieldName.setLabel("Full name");  
  38.             if (UserManager.getUserProvider().isNameRequired()) {  
  39.                 fieldName.setRequired(true);  
  40.             }  
  41.   
  42.             final FormField fieldMail = registrationForm.addField();  
  43.             fieldMail.setVariable("email");  
  44.             fieldMail.setType(FormField.Type.text_single);  
  45.             fieldMail.setLabel("Email");  
  46.             if (UserManager.getUserProvider().isEmailRequired()) {  
  47.                 fieldMail.setRequired(true);  
  48.             }  
  49.   
  50.             final FormField fieldPwd = registrationForm.addField();  
  51.             fieldPwd.setVariable("password");  
  52.             fieldPwd.setType(FormField.Type.text_private);  
  53.             fieldPwd.setLabel("Password");  
  54.             fieldPwd.setRequired(true);  
  55.   
  56.             // Add the registration form to the probe result.  
  57.             probeResult.add(registrationForm.getElement());  
  58.         }  
  59.         // See if in-band registration should be enabled (default is true).  
  60.         registrationEnabled = JiveGlobals.getBooleanProperty("register.inband", true);  
  61.         // See if users can change their passwords (default is true).  
  62.         canChangePassword = JiveGlobals.getBooleanProperty("register.password", true);  
  63.     }  

handleIQ

handleIQ方法,方法有四个步骤。

1、判断用户是否已经登陆。如果在session已经存在该用户发送错误消息反馈客户端:

   PacketError.Condition.internal_server_error

2、获取IQ消息包的消息类型,如果是type=get那就是客户端需要获取申请表了。然

   后,服务端封装这个表单,转成成XMPP消息发送给客户端。

3、当获取到的IQ消息包的消息类型给set(type=set)。那么就是客户点填写完了注册

   表单,发送给服务端了。上面描述注册流程的第4步就是提交表单了。源码就不在

   贴出来了,这个比较简单。只是一些判断校验比较多。

4、当所有的校验都正确,一切注册流程都正常的话。服务端就该返回第6点消息了。

   当然在代码执行过程(业务处理,消息校验等)可能会产生些异常。处理异常的信息

   这里把代码贴出来下,大家也可以自己去看源码:

[java] view plain copy
 
  1. catch (UserAlreadyExistsException e) {  
  2.                 reply = IQ.createResultIQ(packet);  
  3.                 reply.setChildElement(packet.getChildElement().createCopy());  
  4.                 reply.setError(PacketError.Condition.conflict);  
  5.             }  
  6.             catch (UserNotFoundException e) {  
  7.                 reply = IQ.createResultIQ(packet);  
  8.                 reply.setChildElement(packet.getChildElement().createCopy());  
  9.                 reply.setError(PacketError.Condition.bad_request);  
  10.             }  
  11.             catch (StringprepException e) {  
  12.                 // The specified username is not correct according to the stringprep specs  
  13.                 reply = IQ.createResultIQ(packet);  
  14.                 reply.setChildElement(packet.getChildElement().createCopy());  
  15.                 reply.setError(PacketError.Condition.jid_malformed);  
  16.             }  
  17.             catch (IllegalArgumentException e) {  
  18.                 // At least one of the fields passed in is not valid  
  19.                 reply = IQ.createResultIQ(packet);  
  20.                 reply.setChildElement(packet.getChildElement().createCopy());  
  21.                 reply.setError(PacketError.Condition.not_acceptable);  
  22.                 Log.warn(e.getMessage(), e);  
  23.             }  
  24.             catch (UnsupportedOperationException e) {  
  25.                 // The User provider is read-only so this operation is not allowed  
  26.                 reply = IQ.createResultIQ(packet);  
  27.                 reply.setChildElement(packet.getChildElement().createCopy());  
  28.                 reply.setError(PacketError.Condition.not_allowed);  
  29.             }  
  30.             catch (Exception e) {  
  31.                 // Some unexpected error happened so return an internal_server_error  
  32.                 reply = IQ.createResultIQ(packet);  
  33.                 reply.setChildElement(packet.getChildElement().createCopy());  
  34.                 reply.setError(PacketError.Condition.internal_server_error);  
  35.                 Log.error(e.getMessage(), e);  
  36.             }  

这里出现的PacketError这样的消息包错误对象,在以后的源码中会继续写博客,希望大家多多关照... 

注册表单配置

在上面讲解用户注册的流程的时候,相信大家都看到了,用户注册的时候服务端会发送很长的一连串表单要客户端来填写。实际上吗,在openfire官方也不一定确定,世界各地使用注册到底需要哪些属性,所以给出来的注册模板可能会不适合所有的人来使用。那么怎么来修改注册模板呢。

然而在openfire控制管理台,也提供了用户注册表单的配置。在管理台目录:

用户/组->RegistrationProperties这个目录下。截图如下:

技术分享

 

Ok,这里面有很多关于注册的使用相关信息。如下几个:

1、RegistrationSettings

2、RegistrationNotification Contacts

3、WelcomeMessage

4、DefaultGroup

5、Sign-Up PageHeader Text

在本章就,本人则挑几个给大家一起分析下。其余的大家可以自己跟踪下源码。

 

Registration Settings

RegistrationSettings是设置注册配置信息。

技术分享

 

这里有禁用使用email,当然也可以添加使用,其他的属性,比如地址,邮编等。

 

 

Welcome Message

注册完成后,反馈欢迎词等等。

 

Default Group

注册完成后,添加到哪些默认组。关于默认组,以后再说。

 

.......

好了,注册这块就不谈了。

以上是关于ffmpeg4.1 源码学习之-转封装的主要内容,如果未能解决你的问题,请参考以下文章

licode学习之编译篇--1

FPGA学习之数码管(封装)显示时间

Mybatis3源码学习之二JDBC

(转)OpenFire源码学习之六:用户注册

(转)OpenFire源码学习之十四:插件管理

(转)OpenFire源码学习之十五:插件开发