目录  

1  配置:
2  整体流程图
3  启动&Event
4 目录结构
5 通信
6 publish,play,relay,转推 4个scenario
7 模块定义三段式
8 总体流程
9 源码分析

正文

1 总体流程

FFmpeg推送结构图

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

nginx rtmp 多个 nginx rtmp原理_nginx

rtmp信令流程图:

nginx rtmp 多个 nginx rtmp原理_音视频_02

HANDSHAKE-->建立rtmp connect–>建立网络连接creatstream–>指令交互play,publish,_result–>map/push callback分发-->publish,pull,转推3个分支传输媒体数据。

notes:ngx_rtmp_relay_on_result是result转发中心.其中play协议代表命令,它也可以是publish协议


2 配置:

主要部分,模块调用的入口就是配置文件

nginx rtmp 多个 nginx rtmp原理_运维_03

含有play 等config:

#allows you to play your recordings of your live streams using a URL like "rtmp://my-ip:1935/vod/filename.flv"
application vod {
    play /video_recordings;
}

3  基于消息的流程图 

Nginx rtmp模块:可以实现rtmp推流,rtmp拉流和hls拉流.
rtmp是应用层,不是平台层.这层不用考虑模块之间通信,封装内存&数据结构等平台型工作.

nginx rtmp 多个 nginx rtmp原理_服务器_04

步骤:connect=>handshake=>ngx_rtmp_recv=>ngx_rtmp_amf_message_handler=>
ngx_rtmp_live_publish/ngx_rtmp_live_play=>ngx_rtmp_live_join

4  启动&event

  • 启动

nginx启动:主要完成一些配置,和加载各个模块。
RTMP启动做了什么?就是调用ngx_rtmp_block()做初始化工作.(和ngx_http_block类似),开始各个RTMP服务侦听,注册连接到时,执行ngx_rtmp_init_connection的回调

ngx_rtmp_block详解:

  1. preconfiguration
  2. ngx_conf_parse
  3. merge config
  4. ngx_rtmp_init_events
  5. postconfiguration
  6. ngx_rtmp_init_event_handlers
  7. ngx_rtmp_optimize_servers-->ngx_rtmp_init_connection

epoll event (chaint 管理): 同nginx.

5 目录结构

nginx-Rtmp属于应用层项目,nginx才是平台项目.只需要掌握配置,callback.
rtmp,HLS是子module,有路由中心,case不同的消息,调用不同函数,实现业务逻辑
nginx-rtmp的两个核心模块
1): ngx_rtmp_live_module:直播模块:推拉流同点, 不涉及回源
2): ngx_rtmp_relay_module:中继模块:  a):回源pull  b): 转推: push

6 通信

1). 对消息的处理 (push event ....)
ngx_rtmp_receive_message 里面,对消息的头部h的type做判断之后,会调用cmcf->events[h->type]里面的handler,而这些handler是模块注册的
ngx_rtmp.c里面,解析配置时,可能注册

h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO])//设置1
ngx_rtmp_amf_message_handler...//设置2
    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);
    *h = ngx_rtmp_http_flv_av;
ngx_int_t ret = (*evh)(s, h, in);//调用

2). 对命令的处理 (request 指令-->ngx_rtmp_cmd_map)

  • ngx_rtmp_cmd_module 中注册了以下命令的处理方法:
    connect,createStream...publish
static ngx_rtmp_amf_handler_t ngx_rtmp_cmd_map[] = {
    { ngx_string("connect"),            ngx_rtmp_cmd_connect_init           },
    { ngx_string("createStream"),       ngx_rtmp_cmd_create_stream_init     },
  ....
};
  • ngx_rtmp_amf_message_handler等handler

3). 命令的chain_handler (ngx_rtmp_play,next_play链表)
一个模块xxx,在postconfiguration的时候,对于一个命令yyy,可能会定义一个函数ngx_rtmp_xxx_yyy来处理这个命 令,将全局的ngx_rtmp_yyy指向ngx_rtmp_xxx_yyy,并定义一个next_yyy,指向原来的ngx_rtmp_yyy
例如:
next_play = ngx_rtmp_play;
ngx_rtmp_play = ngx_rtmp_live_play;

7 publish,play,relay,转推 4个scenario

nginx-rtmp的3个核心模块

  • 1: ngx_rtmp_live_module:直播模块:推拉流同点, 不涉及回源
  • 2: ngx_rtmp_relay_module:中继模块:  1:回源pull  2: 转推: push
  • 3 ngx_rtmp_play_play  点播

拉流的本质,就是在server上subscribes数组增加一项,然后server for循环转流的时候接收,这个设计很巧妙。这就是server主动的统一发流的设计,不同于异步的信令epoll交互.
详见系列文章

8 源码分析

8.1 模块定义三段式

定义nginx模块需要定义三个变量:command,ctx,module
RTMP此三段式在rtmp.c文件中,模块参考代码如下:

static ngx_command_t  ngx_rtmp_commands[] = {
  { ngx_string("rtmp"),
    NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
    ngx_rtmp_block,
    .....
};
static ngx_core_module_t  ngx_rtmp_module_ctx = {
  ngx_string("rtmp"),
 ....
}
ngx_module_t  ngx_rtmp_module = {
  &ngx_rtmp_module_ctx,                  /* module context */
  ngx_rtmp_commands,                     /* module directives */
.......
};

8.2 流的组织结构

直播流主要依赖以下三个struct组织在一起:ngx_rtmp_live_ctx_s、ngx_rtmp_live_stream_s、ngx_rtmp_live_app_conf_t.....

stream,ctx 关系?
    app->stream指向stream链表的头,stream->ctx指向ctx 链表头.stream是流名称,ctx是publish,play

根据以上的结构体,可以构建出一个类似树的结构体,来组织直播服务器上的所有直播流.如图所示:

nginx rtmp 多个 nginx rtmp原理_服务器_05

nginx rtmp 多个 nginx rtmp原理_运维_06

live_stream_a->next--->live_stream_b-->next....
live_stream_a->ctx=live_ctx_a1->next-->live_ctx_a2->next--->live_ctx_a3....
整体结构:采用hash表+链表的结构;
横向:同一个流名的链表:用play串起来;纵向:不同流名用next串起来 

更多内容:请看补充部分

8.3 流程图:

nginx rtmp 多个 nginx rtmp原理_服务器_07

此图在handshake部分,rtmp和publisher之间应该有连线 

配置ngx_rtmp_block函数

通过ngx_rtmp_module中的配置ngx_rtmp_block函数(rtmp模块初始化的入口),去建立对应的ngx_listening_t对象,并且将回调设置为ngx_rtmp_init_connection函数

2) 开始监听
rtmp在一开始解析配置的时候会调用ngx_rtmp_optimize_servers函数用于向event中放入需要监听ip和端口,如果向具体了解一个链接怎么建立的可以参考上一篇文章nginx各个模块的监听怎么建立.设置了回调为ngx_rtmp_init_connection函数。

while (i < last) {
           /* 省略无关代码 */
            /*这里外层有一个循环,为所有配置创建一个对应的监听*/
            ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);
            ls->addr_ntop = 1;
            ls->handler = ngx_rtmp_init_connection;
        }

calladder:
main->
ngx_single_process_cycle ->ngx_process_events_and_timers ->ngx_epoll_process_events
->ngx_event_accept ->ngx_rtmp_init_connection->ngx_rtmp_handshake 

  • 1) ngx_rtmp_init_connection里面调用了ngx_rtmp_init_session和ngx_rtmp_handshake。ngx_rtmp_init_session先调用ngx_rtmp_set_chunk_size
  • 2) 调用 ngx_rtmp_fire_event激发了事件 NGX_RTMP_CONNECTngx_rtmp_set_chunk_size,第一次将chunk-size设为128(NGX_RTMP_DEFAULT_CHUNK_SIZE),不需要发包。
  • ngx_rtmp_fire_event的handler可以搜索:NGX_RTMP_CONNECT,定义在:ngx_rtmp_limit_postconfiguration
  • 3)、调用各个模块 postconfiguration,主要是注册回调
  • 4)、 ngx_rtmp_init_event_handlers 初始化事件处理,主要是AMF消息,特殊消息,处理回调注册
/* init amf events */
  for(n = 0; n < sizeof(amf_events) / sizeof(amf_events[0]); ++n) {
    eh = ngx_array_push(&cmcf->events[amf_events[n]]);
    *eh = ngx_rtmp_amf_message_handler;
  }
....

3) RTMP处理包
ngx_rtmp_recv收到一个包后 ,调用ngx_rtmp_receive_message处理, 这个函数根据消息的类型来找到对应的handler:
调用:

evhs = &cmcf->events[h->type];
     evh = evhs->elts;
     (*evh)(s, h, in)

注册1

h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);
    *h = ngx_rtmp_live_av;

注册2: 
消息处理的钩子函数的初始化是在ngx_rtmp_init_event_handlers,譬如AMF0的消息处理在:

static size_t               amf_events[] = {
        NGX_RTMP_MSG_AMF_CMD,
        NGX_RTMP_MSG_AMF_META, 
      ......

4)  ngx_rtmp_live_av实现业务逻辑
next_publish; next_play;这个是nginx特色,将所有同一个功能,eg:接收av模块放到同一个链表,从header开始执行,next找到下一个模块。
既实现了模块独立,又实现了模块调用
5)  通过ngx_rtmp_prepare_message将一个message进行拆成一个一个chunk
6) 调用ngx_rtmp_send将所有订阅一个一个传入
这样就结束了接收到发送的流程。
在一个用户需要播放的情况下,在解析到cmd是play的时候,回调用cmd的play回调链。
同样也是在ngx_rtmp_live_module模块中.把播放的连接放入到对应的流stream后面

补充: live module数据结构分析

RTMP的所有会话是根据会话名称的哈希值保存到不同的bucket中,并且每个bucket中有一个stream链表来保存流名称哈希冲突的会话。
所以我们需要遍历所有的bucket及bucket链表来获取到所有的RTMP会话 获取流名称,流的存活时间,输入字节数,输出字节数,输入音频流量,输入的视频流量。

for (ctx = stream->ctx; ctx; ctx = ctx->next, ++nclients) {
 }

notes: session list即bucket list-->每个bucket有stream list(stream 是流名称)

 添加直播流

本节我们介绍rtmp live模块是如何将新的直播流加入到系统中的。

nginx rtmp 多个 nginx rtmp原理_nginx_08

从源码中函数的调用关系可以看出,添加新的直播流由publish和play触发。
ngx_rtmp_live_publish和ngx_rtmp_live_play中加入新的直播流的处理逻辑基本一致,只是角色不同,
ngx_rtmp_live_publish是将直播流作为publisher加入,ngx_rtmp_live_play是将直播流作为subscriber加入.
添加直播流的主要处理流程是在ngx_rtmp_live_join中完成的。

ngx_rtmp_live_join函数分析:将新创建的ngx_rtmp_live_ctx_t加入到stream的ctx链表中。

ctx->stream = *stream;
    ctx->publishing = publisher;
    ctx->next = (*stream)->ctx;

    (*stream)->ctx = ctx;

    ctx->cs[0].csid = NGX_RTMP_CSID_VIDEO;
    ctx->cs[1].csid = NGX_RTMP_CSID_AUDIO;

    if (!ctx->publishing && ctx->stream->active) {
        ngx_rtmp_live_start(s);
    }

ngx_rtmp_live_get_stream函数分析

  • 根据流名称哈希到对应到ngx_rtmp_live_app_conf_t到streams数组的bucket。获取该bucket中的ngx_rtmp_live_stream_t链表(可能存在多个不同的直播流)

stream = &lacf->streams[ngx_hash_key(name, len) % lacf->nbuckets];

10 其他小feature

ngx_rtmp_auto_push_publish,static pull 
record
http_flv和rmtp 代码比较类似