目录
1 配置:
2 整体流程图
3 启动&Event
4 目录结构
5 通信
6 publish,play,relay,转推 4个scenario
7 模块定义三段式
8 总体流程
9 源码分析
正文
1 总体流程
FFmpeg推送结构图
利用ffmpeg推送视频,其流程关系如下图所示,这部分主要是涉及到NGINX左边部分,
ffmpeg将mp4文件推送到nginx服务器。同样利用运行时的函数的顺序辅助理解。
rtmp的信令流程图:
HANDSHAKE-->建立rtmp connect–>建立网络连接creatstream–>指令交互play,publish,_result–>map/push callback分发-->publish,pull,转推3个分支传输媒体数据。
notes:ngx_rtmp_relay_on_result是result转发中心.其中play协议代表命令,它也可以是publish协议
2 配置:
主要部分,模块调用的入口就是配置文件
含有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是应用层,不是平台层.这层不用考虑模块之间通信,封装内存&数据结构等平台型工作.
步骤: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详解:
- preconfiguration
- ngx_conf_parse
- merge config
- ngx_rtmp_init_events
- postconfiguration
- ngx_rtmp_init_event_handlers
- 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
根据以上的结构体,可以构建出一个类似树的结构体,来组织直播服务器上的所有直播流.如图所示:
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 流程图:
此图在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模块是如何将新的直播流加入到系统中的。
从源码中函数的调用关系可以看出,添加新的直播流由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 代码比较类似