【总结】Nginx-rtmp-module模块源码学习

Posted on Posted in 计算机11,627 views

零、说明

    1、系统

        Linux VM-122-135-ubuntu 3.13.0-36-generic

        #63-Ubuntu SMP Wed Sep 3 21:30:07 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

    2、nginx

        版本:nginx-1.10.2

        源码:http://nginx.org/en/download.html

        配置文件:nginx.conf.txt

    3、nginx-rtmp-module

        版本:1.1.4

        源码:https://github.com/arut/nginx-rtmp-module

一、文件结构

    首先通过源码之间的引用关系进行分析如下,因为ngx_rtmp、ngx_config、ngx_core等的引用关系过多,所以舍弃掉那些关系,得到如下图:

1483792596845880.png

二、函数调用

    为了清楚nginx_rtmp_module的运行过程,对其源码进行了修改,让每个函数输出其所在的文件和函数名称,源码参见:https://git.oschina.net/evan-xia/nginx-rtmp-module/repository/archive/v_1_0_notes

2.1、启动nginx,初始化过程

    nginx启动时主要完成一些配置,和加载各个模块。通过对输出的函数进行分析,nginx_rtmp_module对应的初始化过程顺序如下:

        1、配置:ngx_rtmp_auto_push_module.c -> ngx_rtmp_auto_push_create_conf,将全局变量ngx_cycle_t

        2、执行:ngx_rtmp.c -> ngx_rtmp_block,是正规rtmp模块初始化的入口。

        3、通过2中函数源码,我们发现这一步是依次初始化各个rtmp模块中的各个module。

            a) 首先设置rtmp main_conf context的ngx_rtmp_conf_ctx_t;

            b) 然后设置server{} block。

                 i. 通过for循环依次配置各个模块,调用各模块的接口:create_main_conf、create_srv_conf、create_app_conf来配置。注释说 通过输出可看出顺序为:

                    ngx_rtmp_core_module

                    ngx_rtmp_codec_module

                    ngx_rtmp_access_module

                    ngx_rtmp_record_module

                    ngx_rtmp_live_module

                    ngx_rtmp_play_module

                    ngx_rtmp_netcall_module

                    ngx_rtmp_relay_module

                    ngx_rtmp_exec_module

                    ngx_rtmp_notify_module

                    ngx_rtmp_limit_module

                ii. 然后通过for循环依次通过各个模块的preconfiguration接口应用模块的配置,顺序如下:

                    ngx_rtmp_core_server

                    ngx_rtmp_core_listen

                    ngx_rtmp_record_recorder

                    ngx_rtmp_record_recorder

                    ngx_rtmp_core_application

                    ngx_rtmp_play_url

                    ngx_rtmp_core_application

                    ngx_rtmp_play_url

                    ngx_rtmp_core_application

            c)进入rtmp{} block设置 // init rtmp{} main_conf's, merge the server{}s' srv_conf's

                i. 通过for循环中,先调用各个模块的init_main_conf初始化各个模块的main_conf;

                ii. 然后内层for循环,通过merge_srv_conf、merge_app_conf来合并server{}的srv_conf、app_conf;

            d)通过ngx_rtmp_init_events来初始化event;

            e)通过for循环来postconfiguration各个模块的配置;

            f)ngx_rtmp_init_event_handlers来初始化event_handlers

            g)然后通过for循环ngx_rtmp_add_ports来添加监听端口;    

            h)调用ngx_rtmp_optimize_servers来进一步配置servers

            i)最后通过ngx_rtmp_init_process来初始化进程;记录结果显示有两个进程,一个负责处理连接时的业务,另一个在退出时会处理一些业务,详细参见附录1;

    整个初始化入口是如下函数:

        ngx_rtmp_auto_push_module.c -> ngx_rtmp_auto_push_create_conf

        ngx_rtmp.c -> ngx_rtmp_block

        ngx_rtmp.c -> ngx_rtmp_optimize_servers

        ngx_rtmp.c -> ngx_rtmp_init_process

    整个初始化过程函数调用记录init.txt

2.2、ffmpeg推送时函数调用关系

    利用ffmpeg推送视频,其流程关系如下图所示,这部分主要是涉及到NGINX左边部分,ffmpeg将mp4文件推送到nginx服务器。同样利用运行时的函数的顺序辅助理解。

1483793390805581.png

2.2.1、建立rtmp连接

    ffmpeg推送数据之前也需要建立rtmp连接,在nginx-rtmp模块中的入口为ngx_rtmp_init.c -> ngx_rtmp_init_connection,其中关键数据类型ngx_connection_t作为传入参数,其过程如下:

            1、ngx_connection_local_sockaddr建立连接获取链接信息;

            2、设置ngx_rtmp_addr_conf_t;

            3、利用ngx_rtmp_init_session来初始化session;

           4、判断,如果ngx_rtmp_addr_conf_t是proxy_protocol则调用ngx_rtmp_proxy_protocol,否则需要调用ngx_rtmp_handshake来完成rtmp握手来建立连接;

        对于ngx_rtmp_init_session函数,以ngx_connection_t和ngx_rtmp_addr_conf_t作为参数,处理中关键是初始化ngx_rtmp_session_t结构,最后利用ngx_rtmp_fire_event激发事件NGX_RTMP_CONNECT。其中ngx_rtmp_session_t内容如下:

typedef struct {
    uint32_t                signature;  /* "RTMP" */ /* <-- FIXME wtf */
    ngx_event_t             close;
    void                  **ctx;
    void                  **main_conf;
    void                  **srv_conf;
    void                  **app_conf;
    ngx_str_t              *addr_text;
    int                     connected;
#if (nginx_version >= 1007005)
    ngx_queue_t             posted_dry_events;
#else
    ngx_event_t            *posted_dry_events;
#endif
    /* client buffer time in msec */
    uint32_t                buflen;
    uint32_t                ack_size;
    /* connection parameters */
    ngx_str_t               app;
    ngx_str_t               args;
    ngx_str_t               flashver;
    ngx_str_t               swf_url;
    ngx_str_t               tc_url;
    uint32_t                acodecs;
    uint32_t                vcodecs;
    ngx_str_t               page_url;
    /* handshake data */
    ngx_buf_t              *hs_buf;
    u_char                 *hs_digest;
    unsigned                hs_old:1;
    ngx_uint_t              hs_stage;
    /* connection timestamps */
    ngx_msec_t              epoch;
    ngx_msec_t              peer_epoch;
    ngx_msec_t              base_time;
    uint32_t                current_time;
    /* ping */
    ngx_event_t             ping_evt;
    unsigned                ping_active:1;
    unsigned                ping_reset:1;
    /* auto-pushed? */
    unsigned                auto_pushed:1;
    unsigned                relay:1;
    unsigned                static_relay:1;
    /* input stream 0 (reserved by RTMP spec)
     * is used as free chain link */
    ngx_rtmp_stream_t      *in_streams;
    uint32_t                in_csid;
    ngx_uint_t              in_chunk_size;
    ngx_pool_t             *in_pool;
    uint32_t                in_bytes;
    uint32_t                in_last_ack;
    ngx_pool_t             *in_old_pool;
    ngx_int_t               in_chunk_size_changing;
    ngx_connection_t       *connection;
    /* circular buffer of RTMP message pointers */
    ngx_msec_t              timeout;
    uint32_t                out_bytes;
    size_t                  out_pos, out_last;
    ngx_chain_t            *out_chain;
    u_char                 *out_bpos;
    unsigned                out_buffer:1;
    size_t                  out_queue;
    size_t                  out_cork;
    ngx_chain_t            *out[0];
} ngx_rtmp_session_t;

            

        因为推送时先建立连接需要握手,即4中调用的函数为ngx_rtmp_handshake,传入参数为ngx_rtmp_session_t。函数位于ngx_rtmp_handshake.c文件,过程如下:

            1、获取ngx_rtmp_session_t中的connection,然后设置ngx_connection_t读写handler,这是两个关键的处理函数:

c->read->handler =  ngx_rtmp_handshake_recv;
c->write->handler = ngx_rtmp_handshake_send;

            2、然后利用ngx_rtmp_alloc_handshake_buffer为ngx_rtmp_session_t分配hs_buf;

            3、调用ngx_rtmp_handshake_recv开始进行握手;

        4、在握手完成之后,会调用ngx_rtmp_free_handshake_buffers来释放hs_buf,然后调用ngx_rtmp_fire_event来激发了事件NGX_RTMP_HANDSHAKE_DONE,最后调用ngx_rtmp_cycle来重新设置ngx_connection_t读写handler,这两个函数将完成接下来nginx的收发处理:

c->read->handler =  ngx_rtmp_recv;
c->write->handler = ngx_rtmp_send;

        在完成连接后,就是rtmp数据的接收了。而握手流程如下图:

1487207743574959.jpg

2.2.2、建立网络连接

        在建立rtmp连接后,ngx_rtmp_recv函数利用ngx_event_t结构会获取推送过来的原始数据,接受到的数据会存储在ngx_rtmp_stream_t的in链表结构中。ngx_rtmp_recv的主要逻辑就是解析chunk为RTMP包,并调用处理包的函数。主要逻辑如下:

            1、接收chunk数据(默认最大为128);

            2、分析处理chunk数据,如果message还没哟接收完,继续步骤1,否则继续;

            3、将chunk组成一个rtmp message,然后交给ngx_rtmp_receive_message处理,这个函数根据消息的类型来找到对应的handler。

        在流程上,建立网络连接后,会建立网络连接,即客户端和服务端的连接信息。这部分流程如下:

            1、从客户端(推送端)接收amf的command message信息,利用ngx_rtmp_amf_message_handler函数处理数据;

            2、利用ngx_rtmp_cmd_connect_init初始化网络连接;先利用ngx_rtmp_receive_amf获取amf信息,然后ngx_rtmp_cmd_fill_args设置参数;

            3、利用ngx_rtmp_notify_connect来通知客户端参数,具体步骤如下:

                    A、ngx_rtmp_send_ack_size

                    B、ngx_rtmp_send_bandwidth

                    C、ngx_rtmp_send_chunk_size

                    D、ngx_rtmp_send_amf

            完成这些后网络连接即建立完成,具体交互图如下:

        

1487207782671100.jpg

2.2.3、建立网络流

        在建立完网络连接后,就需要建立网络流了,网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,且多个网络流可以复用这一个网络连接。

        和2.2.2一样,ngx_rtmp_recv接收数据后交给ngx_rtmp_receive_message,然后由message类型跳转到相应的处理函数。建立网络流过程如下:

            1、接收message后跳转到处理函数ngx_rtmp_protocol_message_handler。它会根据类型再次区别处理。此时客户端发送NGX_RTMP_MSG_CHUNK_SIZE类型的消息。

            2、接收message后跳转到处理函数ngx_rtmp_amf_message_handler。处理客户端发送的amf控制消息。此时连续接收3个amf数据;

            3、调用ngx_rtmp_cmd_create_stream_init开始初始化网络流。利用ngx_rtmp_receive_amf获取接收的amf数据;

            4、调用ngx_rtmp_cmd_create_stream创建网络流。利用ngx_rtmp_send_amf向客户端发送amf控制消息。

        到此网络流建立完成,大致具体过程如下图红色标注部分:

1487209074169813.jpg

2.2.4、传输媒体数据

        在完成以上步骤后客户端和服务器建立了一个网络流,接下来就可以传输媒体数据了。大致流程如上图红色标注下面部分。一般来讲媒体数据分为两部分,一部分是meta元信息,另一部分是音视频数据。首先传送的是meta元信息。客户端推送媒体,服务器处理如下:

            1、从客户端(推送端)接收一个amf命令消息,然后服务器会利用ngx_rtmp_cmd_publish_init来初始化发布环境。该函数会读取刚接收端amf信息,然后设置参数;

            2、利用ngx_rtmp_auto_push_publish、ngx_rtmp_notify_publish、ngx_rtmp_exec_publish来建立发布环境,其中利用ngx_rtmp_live_get_stream来获取媒体信息,会向客户端发送一个amf命令消息;

            3、如果conf中配置了record命令记录媒体文件,此时会调用ngx_rtmp_record_publish来初始化记录环境;

            4、执行ngx_rtmp_access_publish;

I、meta信息:

            5、接收客户端amf,并调用ngx_rtmp_codec_meta_data来获取meta元信息;

            6、结合服务器的参数信息,利用ngx_rtmp_codec_reconstruct_meta接口来重构meta元信息,并调用函数ngx_rtmp_prepare_message组成消息保存,不会调用free释放;

II、媒体数据:

            7、接收客户端推送,根据类型转到ngx_rtmp_codec_av来处理音视频(avc/aac)数据;其中利用函数ngx_rtmp_codec_parse_avc_header来处理头部信息,ngx_rtmp_append_shared_bufs来存储;

            8、调用record模块记录数据,接口为ngx_rtmp_codec_av;

            9、调用live模块来广播发布数据,接口为ngx_rtmp_live_av,广播到所有观看者;第一次调用时会调用接口ngx_rtmp_live_start创建广播,之后直接调用ngx_rtmp_append_shared_bufs。

2.2.5、断开推送

        客户端(推送端)关闭推送,会结束推送流,此时调用过程如下:

            1、从客户端接收amf结束命令;

            2、调用ngx_rtmp_cmd_delete_stream_init接口,读取amf命令;

            3、调用各个子模块的结束流处理接口,包括:

                ngx_rtmp_auto_push_delete_stream

                ngx_rtmp_relay_delete_stream

                ngx_rtmp_cmd_delete_stream

                ngx_rtmp_notify_close_stream

                ngx_rtmp_exec_close_stream

                ngx_rtmp_relay_close_stream

                ngx_rtmp_play_close_stream

                ngx_rtmp_live_close_stream

            4、利用ngx_rtmp_create_stream_eof创建eof消息,并向各个观看者发送状态amf消息;

            5、利用ngx_rtmp_record_close_stream接口关闭record模块的记录;

    此部分只给出了大题过程、关键函数和接口,具体实现参考源码。整个过程函数调用记录,由于多线程原因,日志顺序仅作为调用顺序的参考。init+ffmpeg推送.txt

2.3、客户端rtmp连接观看时函数调用

    和ffmpeg推送视频一样,客户端连接服务器观看时流程也分为:建立rtmp连接–>建立网络连接–>建立网络流–>传输媒体数据。前面建立rtmp连接和建立网络连接过程是相同的,区别在于建立网络流和传输媒体数据过程。

    此部分为客户端连接nginx-rtmp时的交互,没有推送视频流。其中建立rtmp连接和建立网络连接过程和2.2.1、2.2.2节相同,参考对应章节内容。

    而建立网络流过程与2.2.3区别于两点:

            1、第一步中,从客户端接收一个protocol_message,推送时该message转到ngx_rtmp_set_chunk_size处理,而观看时该message未被处理;

            2、第二步中,接收客户端一个amf控制消息,推送时是连续3个;

    而媒体数据传输步骤中,在服务器没有接收推送时,过程如下:

            1、从客户端(观看端)接收两个amf命令消息;

            2、然后服务器会利用ngx_rtmp_cmd_publish_init来初始化发布环境。该函数会读取刚接收端amf信息,然后设置参数;

            3、和2.2.4类似,初始化其他子模块:

                A、ngx_rtmp_notify_module.c -> ngx_rtmp_notify_play

                B、ngx_rtmp_exec_module.c -> ngx_rtmp_exec_play

                C、ngx_rtmp_relay_module.c -> ngx_rtmp_relay_play

                D、ngx_rtmp_live_module.c -> ngx_rtmp_live_play,会向客户端发送两个amf状态消息

                E、ngx_rtmp_access_module.c -> ngx_rtmp_access_play

                F、ngx_rtmp_cmd_module.c -> ngx_rtmp_cmd_play

            4、客户端向服务器发送set_buflen的用户消息,ngx_rtmp_user_message_handler将对其处理;

            5、因为没有推送数据,所以现在不会有媒体数据交互。

    当断开客户端(观看者)连接时,过程如下:

            1、服务器接收客户端断开连接请求,调用ngx_rtmp_finalize_session来ngx_post_event;

            2、然后触发ngx_rtmp_close_session_handler,利用ngx_rtmp_fire_event触发各个子模块的断开连接处理,包括:

                A、ngx_rtmp_cmd_module.c -> ngx_rtmp_cmd_disconnect_init

                B、ngx_rtmp_notify_module.c -> ngx_rtmp_notify_disconnect

                C、ngx_rtmp_cmd_module.c -> ngx_rtmp_cmd_disconnect

                D、ngx_rtmp_auto_push_module.c -> ngx_rtmp_auto_push_delete_stream

                E、ngx_rtmp_relay_module.c -> ngx_rtmp_relay_delete_stream

                F、ngx_rtmp_exec_module.c -> ngx_rtmp_exec_close_stream

                G、ngx_rtmp_play_module.c -> ngx_rtmp_play_close_stream

                H、ngx_rtmp_live_module.c -> ngx_rtmp_live_close_stream

                I、ngx_rtmp_record_module.c -> ngx_rtmp_record_close_stream

                J、ngx_rtmp_codec_module.c -> ngx_rtmp_codec_disconnect

                K、…

    对应的函数调用次序:init+play.txt

参考:

1、https://github.com/arut/nginx-rtmp-module/wiki/Directives

2、rtmp协议消息体介绍

3、RTMP协议从入门到放弃

4、揭开RTMP播放流程的神秘面纱

5、nginx rtmp代码架构1 hook点总结


转载标明出处:https://blog.evanxia.com/2017/02/1264

  1. 楼主,你好,在《【总结】Nginx-rtmp-module模块源码学习》文章中,请问“对应的函数调用次序”你是如何做的?如何让所有的函数调次序都打印到文档中?是手写的?还是,通过某个配置方式做到?
    我的邮箱是,liu00009090@sina.com。

  2. 你好,请问“对应的函数调用次序”你是如何做的?是手写的?还是,通过某个配置方式做到?

Comments are closed.