ngx_rtmp_live_module 概述

ngx_rtmp_live_module模块负责直播音视频数据的转发。它将从publisher推流上来的音视频数据,转发给每个player进行播放。本章主要包括以下两个方面:

live module数据结构分析

Nginx RTMP服务器支持多个直播同时推流,并且支持每个直播有多个客户端进行播放观看。那么RTMP是如何将这些直播流组织在一起的?
直播流主要依赖以下三个struct组织在一起:ngx_rtmp_live_ctx_s、ngx_rtmp_live_stream_s、ngx_rtmp_live_app_conf_t,我们按照源码中的顺序依次介绍。

ngx_rtmp_live_ctx_s
struct ngx_rtmp_live_ctx_s {
    ngx_rtmp_session_t                 *session;        /*rtmp session*/
    ngx_rtmp_live_stream_t             *stream;         /*当前直播流*/
    ngx_rtmp_live_ctx_t                *next;           /*相同流名称的直播流通过next链表串联起来*/
    ngx_uint_t                          ndropped;       /*丢包数量*/
    ngx_rtmp_live_chunk_stream_t        cs[2];          /*当前转发的音视频包相关数据*/
    ngx_uint_t                          meta_version;   /*meta版本号*/
    ngx_event_t                         idle_evt;       /*推流IDLE定时器timer*/
    unsigned                            active:1;       /*推流状态*/
    unsigned                            publishing:1;   /*判断是否publish流*/
    unsigned                            silent:1;       /*是否本地relay*/
    unsigned                            paused:1;       /*暂停*/
};
ngx_rtmp_live_stream_s
struct ngx_rtmp_live_stream_s {
    u_char                              name[NGX_RTMP_MAX_NAME];/*直播流名称*/
    ngx_rtmp_live_stream_t             *next;                   /*不同的直播流通过next链表串联起来*/
    ngx_rtmp_live_ctx_t                *ctx;                    /*相同名称的直播流通过ctx串联起来*/
    ngx_rtmp_bandwidth_t                bw_in;                  /*入口带宽*/
    ngx_rtmp_bandwidth_t                bw_in_audio;            /*音频入口带宽*/
    ngx_rtmp_bandwidth_t                bw_in_video;            /*视频入口带宽*/
    ngx_rtmp_bandwidth_t                bw_out;                 /*出口带宽*/
    ngx_msec_t                          epoch;                  /*直播流创建时间*/
    unsigned                            active:1;               /*直播流状态*/
    unsigned                            publishing:1;           /*是否已经开始推流*/
};
ngx_rtmp_live_app_conf_t
typedef struct {
    ngx_int_t                           nbuckets;       /*根据流名称进行hash时,bucket的数量*/
    ngx_rtmp_live_stream_t            **streams;        /*每个bucket中,对应ngx_rtmp_live_stream_t*数组*/
    ngx_flag_t                          live;
    ngx_flag_t                          meta;
    ngx_msec_t                          sync;
    ngx_msec_t                          idle_timeout;
    ngx_flag_t                          atc;
    ngx_flag_t                          interleave;
    ngx_flag_t                          wait_key;
    ngx_flag_t                          wait_video;
    ngx_flag_t                          publish_notify;
    ngx_flag_t                          play_restart;
    ngx_flag_t                          idle_streams;
    ngx_msec_t                          buflen;
    ngx_pool_t                         *pool;           /*app conf的内存池*/
    ngx_rtmp_live_stream_t             *free_streams;   /*空闲的streams*/
} ngx_rtmp_live_app_conf_t;

ngx_rtmp_live_app_conf_t中的字段对应的是Nginx配置文件RTMP application域中的配置项。

live

直播开关,on或off。

meta

设置metadata的发送模式。默认值:on
on:订阅者接收包含预定义字段的重新构建的metadata数据包,包含宽高等。
copy:订阅者接收推流端精确的metadata副本,包含标准字段和自定义字段。
off:关闭向订阅者发送任何metadata。

sync timeout

用于同步音视频流。如果订阅者的带宽不足以按照推流端的发送速率接收数据,服务器就会丢弃一些帧。这会导致同步问题。当时间戳差异超过设置当同步参数时,将发送一个绝对帧来修复差异。默认值是300毫秒。

idle_timeout

idle_timeout是drop_idle_publisher设置的值。当推流端在一定时间没有发送音视频数据时,会关闭连接。

interleave

在此模式下,音频和视频数据在同一RTMP块流上传输。

wait_key

确保视频流从关键帧开始

wait_video

在第一个视频帧到来前,忽略所有的音频帧。和wait_key同时使用,确保订阅者首先接收到关键帧。可以调整编码器的gop减少延迟。

publish_notify

向订阅者发送NetStream.Play.PublishNotify和NetStream.Play.UnpublishNotify。

play_restart

on:nginx-rtmp在publisher每次开始和结束推流时都向每个订阅者发送NetStream.Play.Start和NetStream.Play.Stop。
off:每个订阅者只有在开始和结束播放时,才会接收到NetStream.Play.Start和NetStream.Play.Stop。

idle_streams

如果禁用,nginx rtmp则禁止订阅者连接到空闲或不存在的直播流,并在推流断开连接时断开所有订阅者的连接。

流的组织结构

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

nginx网络推流配置 nginx推流多个_直播流

添加直播流

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





ngx_rtmp_live_publish

ngx_rtmp_live_join

ngx_rtmp_live_play

ngx_rtmp_live_get_stream


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

ngx_rtmp_live_join函数分析
  • 获取rtmp session的ngx_rtmp_live_module模块上下文。
    判断ngx_rtmp_live_ctx_t ctx是否为NULL,如果为NULL,则新创建一个ngx_rtmp_live_ctx_t结构,并调用ngx_rtmp_set_ctx进行设置。
lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        return;
    }

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (ctx && ctx->stream) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                       "live: already joined");
        return;
    }

    if (ctx == NULL) {
        ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_live_ctx_t));
        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_live_module);
    }
  • 获取rtmp session的ngx_rtmp_live_stream_t结构。

stream = ngx_rtmp_live_get_stream(s, name, publisher || lacf->idle_streams);
  • 对于subscriber,判断是否已经开始推流并且是否允许存在空闲的播放流,如果都为false,则关闭subscriber的连接。
if (stream == NULL ||
        !(publisher || (*stream)->publishing || lacf->idle_streams))
    {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "live: stream not found");

        ngx_rtmp_send_status(s, "NetStream.Play.StreamNotFound", "error",
                             "No such stream");

        ngx_rtmp_finalize_session(s);

        return;
    }
  • 对于publisher,判断是否已经存在推流。Nginx RTMP默认在同一个worker进程,同一个application下不允许同一个流名称存在多个publisher,即禁止重复推流。
if (publisher) {
        if ((*stream)->publishing) {
            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                          "live: already publishing");

            ngx_rtmp_send_status(s, "NetStream.Publish.BadName", "error",
                                 "Already publishing");

            return;
        }

        (*stream)->publishing = 1;
    }
  • 将新创建的ngx_rtmp_live_ctx_t加入到stream的ctx链表中。
ctx->stream = *stream;
    ctx->publishing = publisher;
    ctx->next = (*stream)->ctx;

    (*stream)->ctx = ctx;
  • 其他处理
if (lacf->buflen) {
        s->out_buffer = 1;
    }

    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链表(可能存在多个不同的直播流)。
lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        return NULL;
    }

    len = ngx_strlen(name);
    stream = &lacf->streams[ngx_hash_key(name, len) % lacf->nbuckets];
  • 和链表中的stream逐一比较流名称。如果匹配到相同流名称到stream,则直接返回。
for (; *stream; stream = &(*stream)->next) {
        if (ngx_strcmp(name, (*stream)->name) == 0) {
            return stream;
        }
    }
  • 判断是否需要新创建stream,如果是publisher,允许新创建stream,对于subscriber,如果配置idle_streams为true,也允许新创建stream。
if (!create) {
        return NULL;
    }
  • 从空闲free_streams获取stream,或者直接从内存池中创建新的stream,并对stream进行初始化。
if (lacf->free_streams) {
        *stream = lacf->free_streams;
        lacf->free_streams = lacf->free_streams->next;
    } else {
        *stream = ngx_palloc(lacf->pool, sizeof(ngx_rtmp_live_stream_t));
    }
    ngx_memzero(*stream, sizeof(ngx_rtmp_live_stream_t));
    ngx_memcpy((*stream)->name, name,
            ngx_min(sizeof((*stream)->name) - 1, len));
    (*stream)->epoch = ngx_current_msec;