nginx-rtmp-module summary

##项目主页:
https://github.com/arut/nginx-rtmp-module

##项目介绍:
nginx-rtmp-module是为nginx开发的一组模块,实现基于nginx的流媒体服务器,包括流发布,转发,录制等功能。具体参考项目主页及项目wiki。

注意,该项目正在开发中,某些功能尚不稳定,代码结构也可能在将来发生较大的变化。

##总结

###配置解析
配置解析要从rtmp命令开始,这是一个顶级命令,和http、events等平级。通过阅读代码能看出来,该命令的解析参考了http的解析流程,具体代码位于nginx_rtmp.c中,如果以前对http命令解析过程有了解,相信这里也能很快理解。

nginx-rtmp-module项目实现的是一组模块,这里为了描述方便,仅用短命名表示各模块,比如core模块,hls模块等,需要注意的是nginx_rtmp.c中声明的模块,这里称呼他为根模块。

这是一份常见也很典型的rtmp模块配置:

rtmp {
    server {

        listen 1935; # 监听1935端口

        application live {
            live on; # 启用直播   

            hls on;  # 启用HLS
            hls_path /tmp/tv26; # HLS缓存路径

            recorder record_tv26{ # 启用录制
                record all; # 收录音视频
                record_path /tmp/rec; # 收录路径
            }   
        }   
    }   

具体解析部分如下:

/* rtmp{}块解析函数,一切与rtmp有关的配置解析从这里开始 */
static char *
ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    char                        *rv;
    ngx_uint_t                   i, m, mi, s;
    ngx_conf_t                   pcf;
    ngx_array_t                  ports;
    ngx_rtmp_listen_t           *listen;
    ngx_rtmp_module_t           *module;
    ngx_rtmp_conf_ctx_t         *ctx;
    ngx_rtmp_core_srv_conf_t    *cscf, **cscfp;
    ngx_rtmp_core_main_conf_t   *cmcf;

    /* 
    typedef struct {
        void                  **main_conf;
        void                  **srv_conf;
        void                  **app_conf;
    } ngx_rtmp_conf_ctx_t;
     * 这个结构体再配置解析过程中见到非常多,server命令和application命令解析时也会创建一个这样的结构体,需要注意的时,这个结构体中main_conf是一次rtmp{}块解析过程中唯一的,其他命令解析到的配置将直接修改该main_conf,与另两个结构体不同。
     */
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    *(ngx_rtmp_conf_ctx_t **) conf = ctx;

    /* count the number of the rtmp modules and set up their indices */
    /* 计算RTMP模块总数,并为模块编号 */

    ngx_rtmp_max_module = 0;
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
            continue;
        }

        ngx_modules[m]->ctx_index = ngx_rtmp_max_module++;
    }


    /* the rtmp main_conf context, it is the same in the all rtmp contexts */
    /* 再次强调,创建main_conf上下文变量,全局唯一 */
    ctx->main_conf = ngx_pcalloc(cf->pool,
                                 sizeof(void *) * ngx_rtmp_max_module);// 这里使用了max创建数组
    if (ctx->main_conf == NULL) {
        return NGX_CONF_ERROR;
    }


    /*
     * the rtmp null srv_conf context, it is used to merge
     * the server{}s' srv_conf's
     * 创建srv_conf上下文变量
     */

    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
    if (ctx->srv_conf == NULL) {
        return NGX_CONF_ERROR;
    }


    /*
     * the rtmp null app_conf context, it is used to merge
     * the server{}s' app_conf's
     * 创建app_conf上下文变量
     */

    ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
    if (ctx->app_conf == NULL) {
        return NGX_CONF_ERROR;
    }


    /*
     * create the main_conf's, the null srv_conf's, and the null app_conf's
     * of the all rtmp modules
     */
    /* 遍历所有的RTMP模块,保证所有模块对应的conf不为空 */

    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;
        mi = ngx_modules[m]->ctx_index;

        // 分别调用各模块的:
        // create_main_conf,
        // create_srv_conf
        // create_app_conf
        if (module->create_main_conf) {
            ctx->main_conf[mi] = module->create_main_conf(cf);
            if (ctx->main_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_srv_conf) {
            ctx->srv_conf[mi] = module->create_srv_conf(cf);
            if (ctx->srv_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }

        if (module->create_app_conf) {
            ctx->app_conf[mi] = module->create_app_conf(cf);
            if (ctx->app_conf[mi] == NULL) {
                return NGX_CONF_ERROR;
            }
        }
    }

    // 备份ngx_conf_t,在rtmp{}块解析完成后恢复
    pcf = *cf;
    cf->ctx = ctx;

    // 调用各RTMP模块的preconfiguration
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;

        if (module->preconfiguration) {
            if (module->preconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }

    /* parse inside the rtmp{} block */
    /* 进入rtmp{}块中继续解析配置,仅解析cmd_type=NGX_RTMP_MAIN_CONF的指令,比如server{}块。
     * 这里要回顾一下ngx_rtmp_conf_ctx_t结构体,因为接下来的解析过程,实际上就是填充这三个结构体的过程。
     * 结构体包括三个数组,分别是main_conf,srv_conf和app_conf,
     * main_conf实际上一个ngx_rtmp_*_main_conf_t的数组,其中最重要的可能就是core模块的结构,

        typedef struct {
            ngx_array_t             servers;    // ngx_rtmp_core_srv_conf_t 
            ngx_array_t             listen;     // ngx_rtmp_listen_t 

            ngx_array_t             events[NGX_RTMP_MAX_EVENT];

            ngx_hash_t              amf_hash;
            ngx_array_t             amf_arrays;
            ngx_array_t             amf;
        } ngx_rtmp_core_main_conf_t;

     * 解析到的server{}块配置会保存到core模块的servers数组中
     * 同样的,srv_conf是一个ngx_rtmp_*_srv_main_t的数组,其中最重要的是core模块中针对server配置的结构体,

        typedef struct ngx_rtmp_core_srv_conf_s {
            ngx_array_t             applications; // ngx_rtmp_core_app_conf_t
            ...            
            ngx_rtmp_conf_ctx_t    *ctx;
        } ngx_rtmp_core_srv_conf_t;

     * 解析到的application{}块会保存到相应server块的applications数组中 */

    cf->module_type = NGX_RTMP_MODULE;
    cf->cmd_type = NGX_RTMP_MAIN_CONF;
    rv = ngx_conf_parse(cf, NULL);

    if (rv != NGX_CONF_OK) {
        *cf = pcf;
        return rv;
    }


    /* init rtmp{} main_conf's, merge the server{}s' srv_conf's */

    // cmcf => core module conf
    cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];
    cscfp = cmcf->servers.elts;

    /* 合并各RTMP模块配置 
     * 这里的合并有一点需要注意的是,core模块的配置必须首先被合并,这个通过写config文件来实现,只有这样,才能保证上一轮解析中的配置全部合并到ctx的三大数组中。
     * 跟模块不在这一轮合并中出现,因为根模块的类型不是NGX_RTMP_MODULE而是NGX_CORE_MODULE
     */
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;
        // mi => module_index
        mi = ngx_modules[m]->ctx_index;

        /* init rtmp{} main_conf's */
        /* 调用各main模块的init_main_conf */

        cf->ctx = ctx;

        // 调用各RTMP main模块的init_main_conf,因为全局统一使用一个main_conf数组,因此这里不需要“merge”
        if (module->init_main_conf) {
            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
            if (rv != NGX_CONF_OK) {
                *cf = pcf;
                return rv;
            }
        }

        /* 分别调用各模块merge_srv_conf和merge_app_conf,如果需要的话
         * ngx_parse_conf产生的servers用在这里*/
        for (s = 0; s < cmcf->servers.nelts; s++) {

            /* merge the server{}s' srv_conf's */

            cf->ctx = cscfp[s]->ctx;

            if (module->merge_srv_conf) {
                rv = module->merge_srv_conf(cf,
                                            ctx->srv_conf[mi],
                                            cscfp[s]->ctx->srv_conf[mi]);
                if (rv != NGX_CONF_OK) {
                    *cf = pcf;
                    return rv;
                }
            }

            if (module->merge_app_conf) {

                /* merge the server{}'s app_conf */

                /*ctx->app_conf = cscfp[s]->ctx->loc_conf;*/

                rv = module->merge_app_conf(cf, 
                                            ctx->app_conf[mi],
                                            cscfp[s]->ctx->app_conf[mi]);
                if (rv != NGX_CONF_OK) {
                    *cf = pcf;
                    return rv;
                }

                /* merge the applications{}' app_conf's 
                 * 解析applications数组
                 * cscfp = core server conf pointer*/

                cscf = cscfp[s]->ctx->srv_conf[ngx_rtmp_core_module.ctx_index];

                /* 递归的解析applications数组,因为application有可能有子块,比如recorder{}块 */
                rv = ngx_rtmp_merge_applications(cf, &cscf->applications,
                                            cscfp[s]->ctx->app_conf,
                                            module, mi);
                if (rv != NGX_CONF_OK) {
                    *cf = pcf;
                    return rv;
                }
            }

        }
    }


    /* 初始化events数组和amf数组 */
    if (ngx_rtmp_init_events(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    /* 调用各RTMP模块的postconfiguration */
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;

        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }

    // 恢复conf
    *cf = pcf;

    /* 初始化RTMP各事件响应函数,AMF各事件响应函数及hash表 */
    if (ngx_rtmp_init_event_handlers(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    /* 初始化端口数组 */
    if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_rtmp_conf_port_t))
        != NGX_OK)
    {
        return NGX_CONF_ERROR;
    }

    listen = cmcf->listen.elts;

    // 添加listen地址到port数组
    for (i = 0; i < cmcf->listen.nelts; i++) {
        if (ngx_rtmp_add_ports(cf, &ports, &listen[i]) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }

    /* 将port中的监听地址添加到nginx的监听列表 */
    return ngx_rtmp_optimize_servers(cf, &ports);
}

仅看根模块的rtmp{}块解析还无法全面了解配置解析过程,下面贴出另一个重要模块,core模块,的server{}块和applicaiton{}块解析过程,他们在ngx_rtmp_core_module.c中。

static char *
ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf/*srv_conf*/)
{
    char                       *rv;
    void                       *mconf;
    ngx_uint_t                  m;
    ngx_conf_t                  pcf;
    ngx_rtmp_module_t          *module;
    ngx_rtmp_conf_ctx_t        *ctx, *rtmp_ctx;
    ngx_rtmp_core_srv_conf_t   *cscf, **cscfp;
    ngx_rtmp_core_main_conf_t  *cmcf;

    // 每解析到一个server就创建一个新的conf_ctx
    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    rtmp_ctx = cf->ctx;
    // 使用rtmp级conf_ctx的main_conf覆盖新建的本server的main_conf,以保证唯一的main_conf数组
    ctx->main_conf = rtmp_ctx->main_conf;

    /* the server{}'s srv_conf */
    /* 为srv_conf和app_conf申请内存 */

    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
    if (ctx->srv_conf == NULL) {
        return NGX_CONF_ERROR;
    }

    ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
    if (ctx->app_conf == NULL) {
        return NGX_CONF_ERROR;
    }

    // 初始化srv_conf和app_conf
    // 调用各RTMP模块的create_srv_conf和create_app_conf
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
            continue;
        }

        module = ngx_modules[m]->ctx;

        if (module->create_srv_conf) {
            mconf = module->create_srv_conf(cf);
            if (mconf == NULL) {
                return NGX_CONF_ERROR;
            }

            ctx->srv_conf[ngx_modules[m]->ctx_index] = mconf;
        }

        if (module->create_app_conf) {
            mconf = module->create_app_conf(cf);
            if (mconf == NULL) {
                return NGX_CONF_ERROR;
            }

            ctx->app_conf[ngx_modules[m]->ctx_index] = mconf;
        }
    }

    /* the server configuration context */
    /* 将新申请的conf_ctx放入main_conf的servers数组
     * servers数组是ngx_rtmp_core_srv_conf_t的集合*/
    cscf = ctx->srv_conf[ngx_rtmp_core_module.ctx_index];
    cscf->ctx = ctx; // 自我链接,用于下一步放入servers
    /* 至此,解析server{}块配置的过程实际上就结束了,需要合并配置时,main_conf[core_module]->servers[...]->ctx->srv_conf[core_module]即可找到每一server{}块的配置,
     * application{}块的解析也同样如此,不同的时application块中也有applications[]数组,因此需要递归的合并子模块的配置
     */

    cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];

    cscfp = ngx_array_push(&cmcf->servers);/* 由于main_conf是直接覆盖而非另外生成,因此这里放入的servers数组就是输入的main_conf */
    if (cscfp == NULL) {
        return NGX_CONF_ERROR;
    }

    *cscfp = cscf;

    /* parse inside server{} 
     * 进入server{}块内部继续解析,这里仅解析NGX_RTMP_SRV_CONF类型的命令,包括listen和application等命令。
    */

    pcf = *cf;
    cf->ctx = ctx;
    cf->cmd_type = NGX_RTMP_SRV_CONF;

    rv = ngx_conf_parse(cf, NULL);

    *cf = pcf;

    return rv;
}

TODO 接受PUSH RTMP流:

##参考:
Nginx模块开发入门 http://blog.codinglabs.org/articles/intro-of-nginx-module-development.html

Emiller’s Guide To Nginx Module Development http://www.evanmiller.org/nginx-modules-guide.html