Nginx RTMP源码分析--ngx_rtmp_live_module源码分析之音视频数据分发

16 篇文章 6 订阅
13 篇文章 7 订阅

直播音视频数据分发

我知道,在直播过程中会有成千上万的用户同时观看,对应到Nginx RTMP到实现中,就是支持一个publisher,同时有多个subscriber进行拉流播放。本节我们将重点介绍ngx_rtmp_live_module模块是如何实现将直播音视频数据分发给多个subscriber的。

注册音视频数据回调处理函数
static ngx_int_t
ngx_rtmp_live_postconfiguration(ngx_conf_t *cf)
{
......
    /* register raw event handlers */

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

    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);
    *h = ngx_rtmp_live_av;
......
}

ngx_rtmp_live_module在postconfiguration阶段,将ngx_rtmp_live_av添加进音频和视频事件的处理函数链中。当推流端开始推流直播时,RTMP端ngx_rtmp_receive_message会接收到音视频数据,会依次调用cmcf->events中已经注册的事件,会执行本模块端ngx_rtmp_live_av函数。

ngx_rtmp_live_av函数分析
  • 判断是否打开直播配置,并且输入的ngx_chain_t是有效的。
    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        return NGX_ERROR;
    }

    if (!lacf->live || in == NULL  || in->buf == NULL) {
        return NGX_OK;
    }

  • 向推流端发送直播开始指令
    if (!ctx->stream->active) {
        ngx_rtmp_live_start(s);
    }

  • 设置publisher推流idle事件
    if (ctx->idle_evt.timer_set) {
        ngx_add_timer(&ctx->idle_evt, lacf->idle_timeout);
    }

如果在配置文件中设置了publisher的idle的超时时间,就会设置idle的定时器。在每次接收到音视频数据时,更新idle定时器。如果在超时时间内没有接收到数据,就会触发定时器事件,执行相应的idle处理函数,即ngx_rtmp_live_idle。

  • 发送消息时,需要使用的优先级
    prio = (h->type == NGX_RTMP_MSG_VIDEO ?
            ngx_rtmp_get_video_frame_type(in) : 0);
  • 设置chunk stream数据
    csidx = !(lacf->interleave || h->type == NGX_RTMP_MSG_VIDEO);

    cs  = &ctx->cs[csidx];

    ngx_memzero(&ch, sizeof(ch));

    ch.timestamp = h->timestamp;
    ch.msid = NGX_RTMP_MSID;
    ch.csid = cs->csid;
    ch.type = h->type;

    lh = ch;

    if (cs->active) {
        lh.timestamp = cs->timestamp;
    }

    clh = lh;
    clh.type = (h->type == NGX_RTMP_MSG_AUDIO ? NGX_RTMP_MSG_VIDEO :
                                                NGX_RTMP_MSG_AUDIO);

    cs->active = 1;
    cs->timestamp = ch.timestamp;
  • 预处理
    rpkt = ngx_rtmp_append_shared_bufs(cscf, NULL, in);

    ngx_rtmp_prepare_message(s, &ch, &lh, rpkt);

    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);

    if (codec_ctx) {

        if (h->type == NGX_RTMP_MSG_AUDIO) {
            header = codec_ctx->aac_header;

            if (lacf->interleave) {
                coheader = codec_ctx->avc_header;
            }

            if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC &&
                ngx_rtmp_is_codec_header(in))
            {
                prio = 0;
                mandatory = 1;
            }

        } else {
            header = codec_ctx->avc_header;

            if (lacf->interleave) {
                coheader = codec_ctx->aac_header;
            }

            if (codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264 &&
                ngx_rtmp_is_codec_header(in))
            {
                prio = 0;
                mandatory = 1;
            }
        }

        if (codec_ctx->meta) {
            meta = codec_ctx->meta;
            meta_version = codec_ctx->meta_version;
        }
    }

1、将输入的in,拷贝一份到rpkt。
2、使用ngx_rtmp_prepare_message对rpkt进行处理,主要是对rtmp的数据信息头进行处理。
3、获取ngx_rtmp_codec_module中解析的codec信息。并判断当前转发的数据是不是codec数据。
4、对于header和coheader,只有在配置lacf->sync(即上节中介绍的用于同步音视频信息的超时时间),才会被使用到。并且在lacf->interleave为true时,即配置了在同一个chunk stream中传输时,音频头和视频头会被一起发送到subscriber,否则,先接收到音频就先转发音频头,先接收到视频就先转发视频头。这时,将mandatory=1,即这个数据包是必须要发送成功的。

  • 向每个subscriber转发数据
    /* broadcast to all subscribers */

    for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {
        if (pctx == ctx || pctx->paused) {
            continue;
        }

        ss = pctx->session;
        cs = &pctx->cs[csidx];

        /* send metadata */

        if (meta && meta_version != pctx->meta_version) {
            ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                           "live: meta");

            if (ngx_rtmp_send_message(ss, meta, 0) == NGX_OK) {
                pctx->meta_version = meta_version;
            }
        }
......
......
        /* send relative packet */

        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                       "live: rel %s packet delta=%uD",
                       type_s, delta);

        if (ngx_rtmp_send_message(ss, rpkt, prio) != NGX_OK) {
            ++pctx->ndropped;

            cs->dropped += delta;

            if (mandatory) {
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                               "live: mandatory packet failed");
                ngx_rtmp_finalize_session(ss);
            }

            continue;
        }

        cs->timestamp += delta;
        ++peers;
        ss->current_time = cs->timestamp;
    }

1、遍历stream对应的ngx_rtmp_live_ctx_t ctx链表。上节中我们介绍了一个直播流的publisher和subscriber是由ngx_rtmp_live_ctx_t ctx链表串联起来的,因此遍历ctx链表,可以获取到每个subscriber。
2、判断当前遍历到到节点是否是publiser自身,或者这个节点已经是paused状态。如果是,则跳过。
3、如果是向这个subscriber首次转发音视频数据,则先发送meta信息。
4、将当前到音视频数据rpkt调用ngx_rtmp_send_message发送出去。如果发送失败,进行drop数据统计,如果mandatory=1,即这个rpkt是必须确保发送成功的数据,则关闭这个subscriber连接session。
5、中间省略的部分是当配置了sync时,当dropped超过sync的值时,从重新发送video/audio的codec信息。

  • 更新统计信息
    ngx_rtmp_update_bandwidth(&ctx->stream->bw_in, h->mlen);
    ngx_rtmp_update_bandwidth(&ctx->stream->bw_out, h->mlen * peers);

    ngx_rtmp_update_bandwidth(h->type == NGX_RTMP_MSG_AUDIO ?
                              &ctx->stream->bw_in_audio :
                              &ctx->stream->bw_in_video,
                              h->mlen);
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值