简介
RTMP的状态模块和其他的模块不太一样,其是Nginx HTTP模块,它应该被设置在http{}块中。状态模块的主要作用是获取当前RTMP服务中的直播流状态,包括推流和拉流的具体信息。可以通过配置rtmp_stat_stylesheet,使获取到的状态信息以XML格式显示,这样更具可视化。
源码分析
接收到HTTP请求后的主要处理函数是ngx_rtmp_stat_handler。我们就来看看这个函数的具体实现逻辑。
slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module);
if (slcf->stat == 0) {
return NGX_DECLINED;
}
首先判断是否在http{}中配置了stat,如果没有配置,就直接返回NGX_DECLINED。
cmcf = ngx_rtmp_core_main_conf;
if (cmcf == NULL) {
goto error;
}
接下来是获取ngx_rtmp_core_main_conf信息。我们知道RTMP直播服务的所有信息都是保存在ngx_rtmp_core_main_conf配置中的。
NGX_RTMP_STAT_L("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n");
if (slcf->stylesheet.len) {
NGX_RTMP_STAT_L("<?xml-stylesheet type=\"text/xsl\" href=\"");
NGX_RTMP_STAT_ES(&slcf->stylesheet);
NGX_RTMP_STAT_L("\" ?>\r\n");
}
设置XML格式信息。
NGX_RTMP_STAT_L("<nginx_rtmp_version>" NGINX_RTMP_VERSION "</nginx_rtmp_version>\r\n");
获取RTMP版本信息。
NGX_RTMP_STAT_L("<built>" __DATE__ " " __TIME__ "</built>\r\n");
获取RTMP编译的时间信息。
NGX_RTMP_STAT_L("<pid>");
NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf),
"%ui", (ngx_uint_t) ngx_getpid()) - nbuf);
NGX_RTMP_STAT_L("</pid>\r\n");
获取响应HTTP请求的worker进程的进程号。接下来的所有信息都是针对这一个进程的。如果Nginx开启了多个worker,通过一次静态HTTP请求是不能够获取所有的worker服务状态的。
NGX_RTMP_STAT_L("<uptime>");
NGX_RTMP_STAT(tbuf, ngx_snprintf(tbuf, sizeof(tbuf),
"%T", ngx_cached_time->sec - start_time) - tbuf);
NGX_RTMP_STAT_L("</uptime>\r\n");
获取更新时间。其实也是worker自启动以来的执行时间。
NGX_RTMP_STAT_L("<naccepted>");
NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf),
"%ui", ngx_rtmp_naccepted) - nbuf);
NGX_RTMP_STAT_L("</naccepted>\r\n");
获取当前worker已经接收到的rtmp连接数量。
ngx_rtmp_stat_bw(r, lll, &ngx_rtmp_bw_in, "in", NGX_RTMP_STAT_BW_BYTES);
ngx_rtmp_stat_bw(r, lll, &ngx_rtmp_bw_out, "out", NGX_RTMP_STAT_BW_BYTES);
获取worker的流入字节数和流出字节数。
cscf = cmcf->servers.elts;
for (n = 0; n < cmcf->servers.nelts; ++n, ++cscf) {
ngx_rtmp_stat_server(r, lll, *cscf);
}
开始遍历在rtmp{}模块中配置的每个server{}的信息。
NGX_RTMP_STAT_L("</rtmp>\r\n");
len = 0;
for (l = cl; l; l = l->next) {
len += (l->buf->last - l->buf->pos);
}
ngx_str_set(&r->headers_out.content_type, "text/xml");
r->headers_out.content_length_n = len;
r->headers_out.status = NGX_HTTP_OK;
ngx_http_send_header(r);
(*ll)->buf->last_buf = 1;
return ngx_http_output_filter(r, cl);
将最终结果返回给HTTP客户端。
ngx_rtmp_stat_server
然后我们来介绍静态模块中针对每个server{}的处理。
NGX_RTMP_STAT_L("<server>\r\n");
#ifdef NGX_RTMP_POOL_DEBUG
ngx_rtmp_stat_dump_pool(r, lll, cscf->pool);
#endif
cacf = cscf->applications.elts;
for (n = 0; n < cscf->applications.nelts; ++n, ++cacf) {
ngx_rtmp_stat_application(r, lll, *cacf);
}
NGX_RTMP_STAT_L("</server>\r\n");
需要遍历server{}配置中的每个application{}来获取详细信息。
NGX_RTMP_STAT_L("<application>\r\n");
NGX_RTMP_STAT_L("<name>");
NGX_RTMP_STAT_ES(&cacf->name);
NGX_RTMP_STAT_L("</name>\r\n");
获取到application的名称。
if (slcf->stat & NGX_RTMP_STAT_LIVE) {
ngx_rtmp_stat_live(r, lll,
cacf->app_conf[ngx_rtmp_live_module.ctx_index]);
}
if (slcf->stat & NGX_RTMP_STAT_PLAY) {
ngx_rtmp_stat_play(r, lll,
cacf->app_conf[ngx_rtmp_play_module.ctx_index]);
}
这里根据配置文件中的stat参数,决定是获取所有的直播信息或者只获取play的信息。在这里我们主要介绍ngx_rtmp_stat_live,因为ngx_rtmp_stat_play获取到的信息相当于是ngx_rtmp_stat_live中的子集。
ngx_rtmp_stat_live
slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module);
NGX_RTMP_STAT_L("<live>\r\n");
total_nclients = 0;
for (n = 0; n < lacf->nbuckets; ++n) {
for (stream = lacf->streams[n]; stream; stream = stream->next) {
......
......
}
}
我们在RTMP的live模块中已经介绍了,RTMP的所有会话是根据会话名称的哈希值保存到不同的bucket中,并且每个bucket中有一个stream链表来保存流名称哈希冲突的会话。所以我们需要遍历所有的bucket及bucket链表来获取到所有的RTMP会话。针对同一个流名称的RTMP会话,我们会获取以下信息。
NGX_RTMP_STAT_L("<name>");
NGX_RTMP_STAT_ECS(stream->name);
NGX_RTMP_STAT_L("</name>\r\n");
NGX_RTMP_STAT_L("<time>");
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%i",
(ngx_int_t) (ngx_current_msec - stream->epoch))
- buf);
NGX_RTMP_STAT_L("</time>");
ngx_rtmp_stat_bw(r, lll, &stream->bw_in, "in",
NGX_RTMP_STAT_BW_BYTES);
ngx_rtmp_stat_bw(r, lll, &stream->bw_out, "out",
NGX_RTMP_STAT_BW_BYTES);
ngx_rtmp_stat_bw(r, lll, &stream->bw_in_audio, "audio",
NGX_RTMP_STAT_BW);
ngx_rtmp_stat_bw(r, lll, &stream->bw_in_video, "video",
NGX_RTMP_STAT_BW);
获取流名称,流的存活时间,输入字节数,输出字节数,输入音频流量,输入的视频流量。
for (ctx = stream->ctx; ctx; ctx = ctx->next, ++nclients) {
}
接下来我们会遍历同一个流名称的所有RTMP会话,我们已经知道相同名称的RTMP会话是由一个stream链表保存的,其中只有一个是publish状态,其余的都是play状态。我们还会通过nclients统计RTMP会话数量。
if (ctx->publishing) {
codec = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
}
如果这个RTMP会话是推流的,那么我们看到它还会将RTMP的codec信息展示出来。
if (codec) {
NGX_RTMP_STAT_L("<meta>");
NGX_RTMP_STAT_L("<video>");
NGX_RTMP_STAT_L("<width>");
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
"%ui", codec->width) - buf);
NGX_RTMP_STAT_L("</width><height>");
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
"%ui", codec->height) - buf);
NGX_RTMP_STAT_L("</height><frame_rate>");
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
"%ui", codec->frame_rate) - buf);
NGX_RTMP_STAT_L("</frame_rate>");
cname = ngx_rtmp_get_video_codec_name(codec->video_codec_id);
if (*cname) {
NGX_RTMP_STAT_L("<codec>");
NGX_RTMP_STAT_ECS(cname);
NGX_RTMP_STAT_L("</codec>");
}
if (codec->avc_profile) {
NGX_RTMP_STAT_L("<profile>");
NGX_RTMP_STAT_CS(
ngx_rtmp_stat_get_avc_profile(codec->avc_profile));
NGX_RTMP_STAT_L("</profile>");
}
if (codec->avc_level) {
NGX_RTMP_STAT_L("<compat>");
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
"%ui", codec->avc_compat) - buf);
NGX_RTMP_STAT_L("</compat>");
}
if (codec->avc_level) {
NGX_RTMP_STAT_L("<level>");
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
"%.1f", codec->avc_level / 10.) - buf);
NGX_RTMP_STAT_L("</level>");
}
NGX_RTMP_STAT_L("</video>");
NGX_RTMP_STAT_L("<audio>");
cname = ngx_rtmp_get_audio_codec_name(codec->audio_codec_id);
if (*cname) {
NGX_RTMP_STAT_L("<codec>");
NGX_RTMP_STAT_ECS(cname);
NGX_RTMP_STAT_L("</codec>");
}
if (codec->aac_profile) {
NGX_RTMP_STAT_L("<profile>");
NGX_RTMP_STAT_CS(
ngx_rtmp_stat_get_aac_profile(codec->aac_profile,
codec->aac_sbr,
codec->aac_ps));
NGX_RTMP_STAT_L("</profile>");
}
if (codec->aac_chan_conf) {
NGX_RTMP_STAT_L("<channels>");
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
"%ui", codec->aac_chan_conf) - buf);
NGX_RTMP_STAT_L("</channels>");
} else if (codec->audio_channels) {
NGX_RTMP_STAT_L("<channels>");
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
"%ui", codec->audio_channels) - buf);
NGX_RTMP_STAT_L("</channels>");
}
if (codec->sample_rate) {
NGX_RTMP_STAT_L("<sample_rate>");
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
"%ui", codec->sample_rate) - buf);
NGX_RTMP_STAT_L("</sample_rate>");
}
NGX_RTMP_STAT_L("</audio>");
NGX_RTMP_STAT_L("</meta>\r\n");
}
这段代码会获取视频和音频的编码信息,主要包括视频宽高、视频帧率、视频编码类型、音频编码类型、音频声道、音频帧率等信息。
总结
RTMP状态模块通过遍历rtmp{}块配置中所有的server{},和server{}中所有的application{},来获取当前worker进程中每个RTMP会话的信息,并可以用个XML格式进行可视化展示。