예제 #1
0
static void
ngx_rtmp_ssl_handshake_handler(ngx_connection_t *c)
{
    ngx_rtmp_session_t *s;
    ngx_event_t        *rev;
    ngx_int_t          rc;

    s = c->data;

    if (c->ssl->handshaked) {
        ngx_rtmp_handshake(s);
        return;
    }

    if (c->read->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "SSL handshake timed out");
        ngx_rtmp_finalize_session(s);
        return;
    }

    if (c->read->error || c->write->error || c->error) {
        ngx_log_error(NGX_LOG_INFO, c->log, 0, "SSL handshake failed: c%d w%d r%d",
        c->error, c->write->error, c->read->error);
        ngx_rtmp_finalize_session(s);
        return;
    }

    rc = ngx_ssl_handshake(c);

    if (rc == NGX_AGAIN) {

        rev = c->read;

        if (!rev->timer_set) {
            ngx_add_timer(rev, s->timeout);
        }

        c->ssl->handler = ngx_rtmp_ssl_handshake_handler;
        return;
    }

    if (rc == NGX_OK) {
        ngx_rtmp_handshake(s);
        return;
    }

    ngx_rtmp_finalize_session(s);
    return;
}
static void
ngx_live_relay_static_handler(ngx_event_t *ev)
{
    ngx_rtmp_session_t                 *s;
    ngx_live_relay_app_conf_t          *lracf;
    ngx_live_relay_ctx_t               *ctx;
    ngx_live_relay_static_ctx_t        *sctx;
    ngx_live_relay_t                   *relay;

    s = ev->data;
    ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);

    if (!ctx->failed_delay && ev->timedout) { // connect timeout
        ngx_log_error(NGX_LOG_ERR, s->log, NGX_ETIMEDOUT,
                "static relay, relay timeout");
        s->finalize_reason = NGX_LIVE_RELAY_TIMEOUT;
        ngx_rtmp_finalize_session(s);

        return;
    }

    lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_module);

    ngx_add_timer(&ctx->reconnect, lracf->relay_reconnect);

    sctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_static_module);
    relay = sctx->relay->relay;

    ngx_live_relay_create(s, relay);
}
void
ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *s)
{
    ngx_event_t       *rev;
    ngx_connection_t  *c;

    c = s->connection;
    rev = c->read;
    rev->handler =  ngx_rtmp_proxy_protocol_recv;

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "proxy_protocol: start");

    if (rev->ready) {
        /* the deferred accept(), rtsig, aio, iocp */

        if (ngx_use_accept_mutex) {
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }

        rev->handler(rev);
        return;
    }

    ngx_add_timer(rev, s->timeout);

    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
        ngx_rtmp_finalize_session(s);
        return;
    }
}
void
ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async)
{
    ngx_connection_t           *c;

    c = s->connection;
    c->read->handler =  ngx_rtmp_handshake_recv;
    c->write->handler = ngx_rtmp_handshake_send;

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "handshake: start client handshake");

    s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);
    s->hs_stage = NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE;

    if (ngx_rtmp_handshake_create_challenge(s, 
                ngx_rtmp_client_version,
                &ngx_rtmp_client_partial_key) != NGX_OK) 
    {
        ngx_rtmp_finalize_session(s);
        return;
    }

    if (async) {
        ngx_post_event(c->write, &ngx_posted_events);
        return;
    }

    ngx_rtmp_handshake_send(c->write);
}
예제 #5
0
void
ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async)
{
    ngx_connection_t           *c;

    c = s->connection;
    c->read->handler =  ngx_rtmp_handshake_recv;
    c->write->handler = ngx_rtmp_handshake_send;

    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "handshake: start client handshake, addr_text=%V', tcurl='%V'", 
             s->addr_text, &s->tc_url);

    s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);
    s->hs_stage = NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE;

    if (ngx_rtmp_handshake_create_challenge(s,
                ngx_rtmp_client_version,
                &ngx_rtmp_client_partial_key) != NGX_OK)
    {
        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                "handshake: ngx_rtmp_handshake_create_challenge failed, addr_text=%V', tcurl='%V'",
                s->addr_text, &s->tc_url);
        ngx_rtmp_finalize_session(s);
        return;
    }

    if (async) {
        ngx_add_timer(c->write, s->timeout);
        if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
            ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                    "handshake: ngx_handle_write_event failed, addr_text=%V', tcurl='%V'",
                    s->addr_text, &s->tc_url);
            ngx_rtmp_finalize_session(s);
        }
        return;
    }

    ngx_rtmp_handshake_send(c->write);
}
ngx_int_t
ngx_live_relay_create_httpflv(ngx_rtmp_session_t *s, ngx_live_relay_t *relay,
        ngx_live_relay_url_t *url)
{
    ngx_live_relay_ctx_t       *rctx;
    // must use ngx_sockaddr_t, because sizeof(struct sockaddr)
    //   is not long enouph, content will be covered by other var
    ngx_sockaddr_t              nsa;
    struct sockaddr            *sa;
    socklen_t                   len;

    rctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
    if (rctx == NULL) {
        return NGX_ERROR;
    }

#define NGX_LIVE_RELAY_CTX(para)                                        \
    if (ngx_copy_str(s->pool, &rctx->para, &relay->para) != NGX_OK) {   \
        goto destroy;                                                   \
    }

    NGX_LIVE_RELAY_CTX(domain);
    NGX_LIVE_RELAY_CTX(app);
    NGX_LIVE_RELAY_CTX(name);
    NGX_LIVE_RELAY_CTX(pargs);
    NGX_LIVE_RELAY_CTX(referer);
    NGX_LIVE_RELAY_CTX(user_agent);
#undef NGX_LIVE_RELAY_CTX

    rctx->tag = relay->tag;

    // get address, host in url must be resolv sync
    sa = (struct sockaddr *) &nsa;
    len = ngx_dynamic_resolver_gethostbyname(&url->url.host, sa);
    if (len == 0) {
        ngx_log_error(NGX_LOG_ERR, s->log, 0,
                "relay httpflv: gethostbyname failed %V", &url->url.host);
        goto destroy;
    }

    // send http request
    if (ngx_live_relay_httpflv_send_request(s, url) != NGX_OK) {
        goto destroy;
    }

    return NGX_OK;

destroy:
    ngx_rtmp_finalize_session(s);

    return NGX_ERROR;
}
static ngx_int_t
ngx_rtmp_relay_create(ngx_rtmp_session_t *s, ngx_str_t *name,
        ngx_rtmp_relay_target_t *target,
        ngx_rtmp_relay_create_ctx_pt create_publish_ctx,
        ngx_rtmp_relay_create_ctx_pt create_play_ctx)
{
    ngx_rtmp_relay_app_conf_t      *racf;
    ngx_rtmp_relay_ctx_t           *publish_ctx, *play_ctx, **cctx;
    ngx_uint_t                      hash;


    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);
    if (racf == NULL) {
        return NGX_ERROR;
    }

    play_ctx = create_play_ctx(s, name, target);
    if (play_ctx == NULL) {
        return NGX_ERROR;
    }

    hash = ngx_hash_key(name->data, name->len);
    cctx = &racf->ctx[hash % racf->nbuckets];
    for (; *cctx; cctx = &(*cctx)->next) {
        if ((*cctx)->name.len == name->len
            && !ngx_memcmp(name->data, (*cctx)->name.data, 
                name->len))
        {
            break;
        }
    }

    if (*cctx) {
        play_ctx->publish = (*cctx)->publish;
        play_ctx->next = (*cctx)->play;
        (*cctx)->play = play_ctx;
        return NGX_OK;
    }

    publish_ctx = create_publish_ctx(s, name, target);
    if (publish_ctx == NULL) {
        ngx_rtmp_finalize_session(play_ctx->session);
        return NGX_ERROR;
    }

    publish_ctx->publish = publish_ctx;
    publish_ctx->play = play_ctx;
    play_ctx->publish = publish_ctx;
    *cctx = publish_ctx;

    return NGX_OK;
}
/* 空闲检查定时器回调处理  */
static void
ngx_rtmp_live_idle(ngx_event_t *pev)
{
    ngx_connection_t           *c;
    ngx_rtmp_session_t         *s;

    c = pev->data;
    s = c->data;

    ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                  "live: drop idle publisher");

    ngx_rtmp_finalize_session(s);
}
예제 #9
0
static void
ngx_rtmp_handshake_done(ngx_rtmp_session_t *s)
{
    ngx_rtmp_free_handshake_buffers(s);

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "handshake: done");

    if (ngx_rtmp_fire_event(s, NGX_RTMP_HANDSHAKE_DONE,
                NULL, NULL) != NGX_OK)
    {
        ngx_rtmp_finalize_session(s);
        return;
    }

    ngx_rtmp_cycle(s);
}
static void
ngx_rtmp_netcall_close(ngx_connection_t *cc)
{
    ngx_rtmp_netcall_session_t         *cs, **css;
    ngx_pool_t                         *pool;
    ngx_rtmp_session_t                 *s;
    ngx_rtmp_netcall_ctx_t             *ctx;
    ngx_buf_t                          *b;

    cs = cc->data;

    if (cc->destroyed) {
        return;
    }

    cc->destroyed = 1;

    if (!cs->detached) {
        s = cs->session;
        ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module);

        if (cs->in && cs->sink) {
            cs->sink(cs->session, cs->in);

            b = cs->in->buf;
            b->pos = b->last = b->start;

        }

        for(css = &ctx->cs; *css; css = &((*css)->next)) {
            if (*css == cs) {
                *css = cs->next;
                break;
            }
        }

        if (cs->handle && cs->handle(s, cs->arg, cs->in) != NGX_OK) {
            ngx_rtmp_finalize_session(s);
        }
    }

    pool = cc->pool;
    ngx_close_connection(cc);
    ngx_destroy_pool(pool);
}
예제 #11
0
void
ngx_rtmp_ssl_handshake(ngx_rtmp_session_t *s)
{
    ngx_connection_t  *c;
    ngx_rtmp_ssl_srv_conf_t       *sscf;

    c = s->connection;

    sscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_ssl_module);

    if (ngx_ssl_create_connection(&sscf->ssl, c, 0) != NGX_OK)
    {
        ngx_rtmp_finalize_session(s);
        return;
    }

    ngx_rtmp_ssl_handshake_handler(c);
    return;
}
static const char *
ngx_rtmp_control_drop_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s,
    ngx_rtmp_core_srv_conf_t *cscf,
    ngx_rtmp_core_app_conf_t **cacf)
{
    ngx_rtmp_control_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);

    ngx_log_debug(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "control: drop static_relay='%d'",
            s->static_relay);

    s->static_relay = 0;
    ngx_rtmp_finalize_session(s);

    ++ctx->count;

    return NGX_CONF_OK;
}
static void
ngx_rtmp_log_pre_write(ngx_rtmp_session_t *s, ngx_rtmp_log_t *log)
{
    ngx_rtmp_log_op_t          *op;
    u_char                      line[MAX_ACCESS_LOG_LINE_LEN], *p;
    size_t                      len;
    ngx_uint_t                  n;

    if (ngx_time() == log->disk_full_time) {
        /* FreeBSD full disk protection;
         * nginx http logger does the same */
        return;
    }

    len = 0;
    op = log->format->ops->elts;
    for (n = 0; n < log->format->ops->nelts; ++n, ++op) {
        len += op->getlen(s, op);
    }
    len += NGX_LINEFEED_SIZE;

    if (len > MAX_ACCESS_LOG_LINE_LEN) {
        ngx_log_error(NGX_LOG_ERR, s->log, 0,
                "Access line len %z greater than %d",
                len, MAX_ACCESS_LOG_LINE_LEN);
        ngx_rtmp_finalize_session(s);

        return;
    }

    p = line;
    op = log->format->ops->elts;
    for (n = 0; n < log->format->ops->nelts; ++n, ++op) {
        p = op->getdata(s, p, op);
    }

    ngx_linefeed(p);

    ngx_rtmp_log_write(s, log, line, p - line);
}
static void
ngx_live_relay_inner_handler(ngx_event_t *ev)
{
    ngx_rtmp_session_t                 *s;
    ngx_live_relay_app_conf_t          *lracf;
    ngx_live_relay_ctx_t               *ctx;
    ngx_live_relay_t                    relay;

    s = ev->data;
    ctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);

    if (!ctx->failed_delay && ev->timedout) { // connect timeout
        ngx_log_error(NGX_LOG_ERR, s->log, NGX_ETIMEDOUT,
                "inner relay, relay timeout");
        s->finalize_reason = NGX_LIVE_RELAY_TIMEOUT;
        ngx_rtmp_finalize_session(s);

        return;
    }

    // relay pull, no player or relay push no publisher
    if ((s->publishing && s->live_stream->play_ctx == NULL)
            || (!s->publishing && s->live_stream->publish_ctx == NULL))
    {
        return;
    }

    lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_relay_module);

    ngx_add_timer(&ctx->reconnect, lracf->relay_reconnect);

    if (ngx_live_relay_inner_create_relay(s, &relay, s->live_stream->pslot)
            != NGX_OK)
    {
        return;
    }

    ngx_live_relay_create(s, &relay);
}
static void
ngx_live_relay_httpflv_error(ngx_rtmp_session_t *s, ngx_uint_t status)
{
    ngx_live_stream_t          *st;
    ngx_rtmp_core_ctx_t        *cctx;
    char                       *code, *level, *desc;
    size_t                      i;

    for (i = 0; ngx_http_relay_status_code[i].status; ++i) {

        if (status != ngx_http_relay_status_code[i].status) {
            continue;
        }

        break;
    }

    code = ngx_http_relay_status_code[i].code;
    level = ngx_http_relay_status_code[i].level;
    desc = ngx_http_relay_status_code[i].desc;

    ngx_log_error(NGX_LOG_ERR, s->log, 0,
            "http relay transit, %d: level='%s' code='%s' description='%s'",
            status, level, code, desc);

    st = ngx_live_create_stream(&s->serverid, &s->stream);
    cctx = st->play_ctx;

    for (; cctx; cctx = cctx->next) {
        cctx->session->status = status;
        ngx_rtmp_send_status(cctx->session, code, level, desc);

        if (ngx_strcmp(level, "error") == 0 && !cctx->session->static_pull) {
            cctx->session->finalize_reason = NGX_LIVE_RELAY_TRANSIT;
            ngx_rtmp_finalize_session(cctx->session);
        }
    }
}
static ngx_int_t
ngx_rtmp_relay_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v)
{
    ngx_rtmp_relay_app_conf_t          *racf;
    ngx_rtmp_relay_ctx_t               *ctx, **cctx;
    ngx_uint_t                          hash;

    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
    if (ctx == NULL || ctx->publish == NULL) {
        goto next;
    }

    /* play end disconnect? */
    if (ctx->publish != ctx) {
        for (cctx = &ctx->publish->play; *cctx; cctx = &(*cctx)->next) {
            if (*cctx == ctx) {
                *cctx = ctx->next;
                break;
            }
        }

        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0, 
                "relay: play disconnect app='%V' name='%V'",
                &ctx->app, &ctx->name);

        /* push reconnect */
        if (ctx->relay && ctx->tag == &ngx_rtmp_relay_module && 
            !ctx->publish->push_evt.timer_set) 
        {
            ngx_add_timer(&ctx->publish->push_evt, racf->push_reconnect);
        }

#ifdef NGX_DEBUG
        {
            ngx_uint_t  n = 0;
            for (cctx = &ctx->publish->play; *cctx; cctx = &(*cctx)->next, ++n);
            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0, 
                "relay: play left after disconnect app='%V' name='%V': %ui",
                &ctx->app, &ctx->name, n);
        }
#endif

        if (ctx->publish->play == NULL && ctx->publish->relay) {
            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, 
                 ctx->publish->session->connection->log, 0, 
                "relay: publish disconnect empty app='%V' name='%V'",
                &ctx->app, &ctx->name);
            ngx_rtmp_finalize_session(ctx->publish->session);
        }

        ctx->publish = NULL;

        goto next;
    }

    /* publish end disconnect */
    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0, 
            "relay: publish disconnect app='%V' name='%V'",
            &ctx->app, &ctx->name);

    if (ctx->push_evt.timer_set) {
        ngx_del_timer(&ctx->push_evt);
    }

    for (cctx = &ctx->play; *cctx; cctx = &(*cctx)->next) {
        (*cctx)->publish = NULL;
        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, (*cctx)->session->connection->log, 
            0, "relay: play disconnect orphan app='%V' name='%V'",
            &(*cctx)->app, &(*cctx)->name);
        ngx_rtmp_finalize_session((*cctx)->session);
    }
    ctx->publish = NULL;

    hash = ngx_hash_key(ctx->name.data, ctx->name.len);
    cctx = &racf->ctx[hash % racf->nbuckets];
    for (; *cctx && *cctx != ctx; cctx = &(*cctx)->next);
    if (*cctx) {
        *cctx = ctx->next;
    }

next:
    return next_delete_stream(s, v);
}
/*
rtmp session加入直播流 
*/
static void
ngx_rtmp_live_join(ngx_rtmp_session_t *s, u_char *name, unsigned publisher)
{
    ngx_rtmp_live_ctx_t            *ctx;
    ngx_rtmp_live_stream_t        **stream;
    ngx_rtmp_live_app_conf_t       *lacf;

    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);
    }

    ngx_memzero(ctx, sizeof(*ctx));

    ctx->session = s;

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "live: join '%s'", name);

	/* 直播流 用name作key  */
    stream = ngx_rtmp_live_get_stream(s, name, publisher || lacf->idle_streams);

    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;
    }

    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;
    }

    ctx->stream = *stream;   /* 加入直播流指针 */
    ctx->publishing = publisher;
    /*  加入直播流 ctx链表  */
    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);
    }
}
static ngx_int_t
ngx_live_relay_static_relay(ngx_rtmp_session_t *s,
        ngx_live_relay_static_relay_t *r)
{
    ngx_rtmp_session_t                 *rs;
    ngx_live_relay_ctx_t               *ctx, *pctx;
    ngx_live_relay_app_conf_t          *lracf;
    ngx_live_relay_static_main_conf_t  *rsmcf;
    ngx_live_relay_static_ctx_t        *sctx;
    ngx_live_relay_t                   *relay;
    ngx_rtmp_addr_conf_t               *addr_conf;

    relay = r->relay;
    rsmcf = ngx_rtmp_cycle_get_module_main_conf(ngx_cycle,
                                                ngx_live_relay_static_module);
    addr_conf = ngx_rtmp_find_related_addr_conf((ngx_cycle_t *) ngx_cycle,
                                                &rsmcf->pull_port);
    if (addr_conf == NULL) {
        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
                "relay static, find related add_conf for %V failed",
                &rsmcf->pull_port);
        return NGX_DECLINED;
    }

    rs = ngx_rtmp_create_static_session(relay, addr_conf,
                                        &ngx_live_relay_static_module);
    if (rs == NULL) {
        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
                "relay static, create relay session %V failed", &relay->stream);
        return NGX_DECLINED;
    }
    r->session = rs;

    rs->publishing = 1;
    rs->live_stream = ngx_live_create_stream(&rs->domain, &rs->stream);
    ngx_live_create_ctx(rs, 1);

    sctx = ngx_pcalloc(rs->pool, sizeof(ngx_live_relay_static_ctx_t));
    if (sctx == NULL) {
        ngx_log_error(NGX_LOG_ERR, rs->log, 0,
                "relay static, create static relay ctx failed");
        ngx_rtmp_finalize_session(rs);

        return NGX_OK;
    }
    ngx_rtmp_set_ctx(rs, sctx, ngx_live_relay_static_module);
    sctx->relay = r;

    ctx = ngx_rtmp_get_module_ctx(rs, ngx_live_relay_module);
    ctx->reconnect.log = rs->log;
    ctx->reconnect.data = rs;
    ctx->reconnect.handler = ngx_live_relay_static_handler;

    if (s == NULL) {
        ngx_post_event(&ctx->reconnect, &ngx_posted_events);
        return NGX_OK;
    }

    // reconnect
    pctx = ngx_rtmp_get_module_ctx(s, ngx_live_relay_module);
    if (pctx->successd) { // prev relay successd
        ngx_post_event(&ctx->reconnect, &ngx_posted_events);
        return NGX_OK;
    }

    ctx->idx = pctx->idx;
    ctx->failed_reconnect = pctx->failed_reconnect;

    if (ctx->idx < relay->urls.nelts) { // retry backup url immediately
        ngx_post_event(&ctx->reconnect, &ngx_posted_events);
        return NGX_OK;
    }

    lracf = ngx_rtmp_get_module_app_conf(rs, ngx_live_relay_module);

    if (!pctx->reconnect.timer_set) { // prev relay timeout
        ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2,
                lracf->relay_reconnect);
        ngx_post_event(&ctx->reconnect, &ngx_posted_events);
        return NGX_OK;
    }

    if (pctx->failed_reconnect) {
        ctx->failed_reconnect = ngx_min(pctx->failed_reconnect * 2,
                lracf->relay_reconnect);
    } else {
        ctx->failed_reconnect = lracf->failed_reconnect;
    }

    ctx->failed_delay = 1;
    ngx_add_timer(&ctx->reconnect, ctx->failed_reconnect);

    return NGX_OK;
}
// merge all static pull into ngx_live_relay_static_main_conf_t;
static char *
ngx_live_relay_static_init_main_dconf(ngx_conf_t *cf, void *conf)
{
    ngx_live_relay_static_main_conf_t  *rsmcf;
    ngx_live_relay_static_main_dconf_t *rsmdcf;
    ngx_core_conf_t                    *ccf;
    ngx_live_relay_t                   *relay;
    ngx_live_relay_static_relay_t      *srelay, *old, *sl, *sln, **sll;
    ngx_live_relay_static_ctx_t        *ctx;
    ngx_live_relay_ctx_t               *rctx;
    ngx_map_node_t                     *node;
    unsigned                            used;
    char                               *rc;
    ngx_uint_t                          i, hash;

    rsmdcf = conf;
    rsmcf = ngx_rtmp_cycle_get_module_main_conf(ngx_cycle,
                                                ngx_live_relay_static_module);
    ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
                                           ngx_core_module);

    used = rsmcf->used? 0: 1;
    sl = NULL;
    sll = &sl;

    relay = rsmdcf->pulls.elts;
    for (i = 0; i < rsmdcf->pulls.nelts; ++i, ++relay) {
        // should static pull in current process?
        if (ngx_process == NGX_PROCESS_WORKER) {
            hash = ngx_hash_key_lc(relay->stream.data, relay->stream.len);
            if (hash % ccf->worker_processes != ngx_worker) {
                continue;
            }
        }

        // check static pull duplicate
        node = ngx_map_find(&rsmcf->pulls[used], (intptr_t) &relay->stream);
        if (node) {
            rc = "duplicate static pull";
            goto error;
        }

        srelay = ngx_live_relay_get_static_relay(rsmcf);
        if (srelay == NULL) {
            rc = "get static relay failed";
            goto error;
        }
        srelay->relay = relay;
        srelay->node.raw_key = (intptr_t) &relay->stream;
        ngx_map_insert(&rsmcf->pulls[used], &srelay->node, 0);

        // check static pull is exist
        node = ngx_map_find(&rsmcf->pulls[rsmcf->used],
                            (intptr_t) &relay->stream);
        if (node) {
            old = (ngx_live_relay_static_relay_t *) node;
            srelay->session = old->session;

            // link swap static pull
            *sll = old;
            sll = &(*sll)->next;
        }
    }

    // delete swap static pull from old
    while (sl) {
        sln = sl;
        sl = sl->next;
        ngx_map_delete(&rsmcf->pulls[rsmcf->used],
                       (intptr_t) &sln->relay->stream);
        ngx_live_relay_put_static_relay(rsmcf, sln);
    }

    // stop deleted static pull
    node = ngx_map_begin(&rsmcf->pulls[rsmcf->used]);
    while (node) {
        srelay = (ngx_live_relay_static_relay_t *) node;
        node = ngx_map_next(node);
        ngx_live_relay_put_static_relay(rsmcf, srelay);

        rctx = ngx_rtmp_get_module_ctx(srelay->session, ngx_live_relay_module);
        rctx->giveup = 1;
        srelay->session->finalize_reason = NGX_LIVE_NORMAL_CLOSE;
        ngx_rtmp_finalize_session(srelay->session);

        ngx_map_delete(&rsmcf->pulls[rsmcf->used],
                       (intptr_t) &srelay->relay->stream);
    }

    // new static relay
    node = ngx_map_begin(&rsmcf->pulls[used]);
    for (; node; node = ngx_map_next(node)) {
        srelay = (ngx_live_relay_static_relay_t *) node;
        if (srelay->session == NULL) {
            ngx_live_relay_static_relay(NULL, srelay);
        } else {
            ctx = ngx_rtmp_get_module_ctx(srelay->session,
                                          ngx_live_relay_static_module);
            ctx->relay = srelay;
        }
    }

    rsmcf->used = used;

    return NGX_CONF_OK;

error:
    // recycle static relay resource
    node = ngx_map_begin(&rsmcf->pulls[used]);
    while (node) {
        srelay = (ngx_live_relay_static_relay_t *) node;
        node = ngx_map_next(node);
        ngx_live_relay_put_static_relay(rsmcf, srelay);

        ngx_map_delete(&rsmcf->pulls[used],
                       (intptr_t) &srelay->relay->stream);
    }

    return rc;
}
ngx_rtmp_session_t *
ngx_rtmp_init_session(ngx_connection_t *c, ngx_rtmp_addr_conf_t *addr_conf)
{
    ngx_rtmp_session_t             *s;
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_rtmp_error_log_ctx_t       *ctx;

    s = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_session_t) + 
            sizeof(ngx_chain_t *) * ((ngx_rtmp_core_srv_conf_t *)
                addr_conf->ctx-> srv_conf[ngx_rtmp_core_module
                    .ctx_index])->out_queue);
    if (s == NULL) {
        ngx_rtmp_close_connection(c);
        return NULL;
    }

    s->main_conf = addr_conf->ctx->main_conf;
    s->srv_conf = addr_conf->ctx->srv_conf;

    s->addr_text = &addr_conf->addr_text;

    c->data = s;
    s->connection = c;

    ctx = ngx_palloc(c->pool, sizeof(ngx_rtmp_error_log_ctx_t));
    if (ctx == NULL) {
        ngx_rtmp_close_connection(c);
        return NULL;
    }

    ctx->client = &c->addr_text;
    ctx->session = s;

    c->log->connection = c->number;
    c->log->handler = ngx_rtmp_log_error;
    c->log->data = ctx;
    c->log->action = NULL;

    c->log_error = NGX_ERROR_INFO;

    s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_rtmp_max_module);
    if (s->ctx == NULL) {
        ngx_rtmp_close_connection(c);
        return NULL;
    }

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    s->out_queue = cscf->out_queue;
    s->out_cork = cscf->out_cork;
    s->in_streams = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_stream_t) 
            * cscf->max_streams);
    if (s->in_streams == NULL) {
        ngx_rtmp_close_connection(c);
        return NULL;
    }

    s->epoch = ngx_current_msec;
    s->timeout = cscf->timeout;
    ngx_rtmp_set_chunk_size(s, NGX_RTMP_DEFAULT_CHUNK_SIZE);


    if (ngx_rtmp_fire_event(s, NGX_RTMP_CONNECT, NULL, NULL) != NGX_OK) {
        ngx_rtmp_finalize_session(s);
        return NULL;
    }

    return s;
}
static ngx_int_t
ngx_rtmp_live_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
{
    ngx_rtmp_session_t             *ss;
    ngx_rtmp_live_ctx_t            *ctx, **cctx, *pctx;
    ngx_rtmp_live_stream_t        **stream;
    ngx_rtmp_live_app_conf_t       *lacf;

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        goto next;
    }

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (ctx == NULL) {
        goto next;
    }

    if (ctx->stream == NULL) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                       "live: not joined");
        goto next;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "live: leave '%s'", ctx->stream->name);

    if (ctx->stream->publishing && ctx->publishing) {
        ctx->stream->publishing = 0;
    }

    for (cctx = &ctx->stream->ctx; *cctx; cctx = &(*cctx)->next) {
        if (*cctx == ctx) {
            *cctx = ctx->next;
            break;
        }
    }

    if (ctx->publishing || ctx->stream->active) {
        ngx_rtmp_live_stop(s);
    }

    if (ctx->publishing) {
        ngx_rtmp_send_status(s, "NetStream.Unpublish.Success",
                             "status", "Stop publishing");
        if (!lacf->idle_streams) {
            for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {
                if (pctx->publishing == 0) {
                    ss = pctx->session;
                    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                                   "live: no publisher");
                    ngx_rtmp_finalize_session(ss);
                }
            }
        }
    }

    if (ctx->stream->ctx) {
        ctx->stream = NULL;
        goto next;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "live: delete empty stream '%s'",
                   ctx->stream->name);

    stream = ngx_rtmp_live_get_stream(s, ctx->stream->name, 0);
    if (stream == NULL) {
        goto next;
    }
    *stream = (*stream)->next;

    ctx->stream->next = lacf->free_streams;
    lacf->free_streams = ctx->stream;
    ctx->stream = NULL;

    if (!ctx->silent && !ctx->publishing && !lacf->play_restart) {
        ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", "Stop live");
    }

next:
    return next_close_stream(s, v);
}
/*
直播音视频处理 
*/
static ngx_int_t
ngx_rtmp_live_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                 ngx_chain_t *in)
{
    ngx_rtmp_live_ctx_t            *ctx, *pctx;
    ngx_rtmp_codec_ctx_t           *codec_ctx;
    ngx_chain_t                    *header, *coheader, *meta,
                                   *apkt, *aapkt, *acopkt, *rpkt;
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_rtmp_live_app_conf_t       *lacf;
    ngx_rtmp_session_t             *ss;
    ngx_rtmp_header_t               ch;   /* 当前rtmp header  */
    ngx_rtmp_header_t  				lh;   /*  上一个rtmp header*/
    ngx_rtmp_header_t   			clh;  /* 音频转化视频的rtmp header   */
    ngx_int_t                       rc, mandatory, dummy_audio;
    ngx_uint_t                      prio;
    ngx_uint_t                      peers;
    ngx_uint_t                      meta_version;
    ngx_uint_t                      csidx;
    uint32_t                        delta;
    ngx_rtmp_live_chunk_stream_t   *cs;
#ifdef NGX_DEBUG
    const char                     *type_s;

    type_s = (h->type == NGX_RTMP_MSG_VIDEO ? "video" : "audio");
#endif

    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;
    }
    
	/* 从会话中获取live ctx */
    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (ctx == NULL || ctx->stream == NULL) {
        return NGX_OK;
    }

    if (ctx->publishing == 0) {
        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                       "live: %s from non-publisher", type_s);
        return NGX_OK;
    }

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

    if (ctx->idle_evt.timer_set) {
        ngx_add_timer(&ctx->idle_evt, lacf->idle_timeout);
    }

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

    s->current_time = h->timestamp;

    peers = 0;
    apkt = NULL;
    aapkt = NULL;
    acopkt = NULL;
    header = NULL;
    coheader = NULL;
    meta = NULL;
    meta_version = 0;
    mandatory = 0;

    prio = (h->type == NGX_RTMP_MSG_VIDEO ?
            ngx_rtmp_get_video_frame_type(in) : 0);

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    csidx = !(lacf->interleave || h->type == NGX_RTMP_MSG_VIDEO); /* 视频 csidx为0  音频csidx为0  */

    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; /* 更新时戳 */

    delta = ch.timestamp - lh.timestamp;
/*
    if (delta >> 31) {
        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                       "live: clipping non-monotonical timestamp %uD->%uD",
                       lh.timestamp, ch.timestamp);

        delta = 0;

        ch.timestamp = lh.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;
        }
    }

    /* broadcast to all subscribers */

    for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {
        if (pctx == ctx || pctx->paused) {
        	/* 发布rtmp流本身或者该播放流已中止 */
            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;
            }
        }

        /* sync stream */

        if (cs->active && (lacf->sync && cs->dropped > lacf->sync)) {
            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                           "live: sync %s dropped=%uD", type_s, cs->dropped);

            cs->active = 0;
            cs->dropped = 0;
        }

        /* absolute packet */

        if (!cs->active) { /*  流没有启动 */

            if (mandatory) {
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                               "live: skipping header");
                continue;
            }
			/* 等待视频  */
            if (lacf->wait_video && h->type == NGX_RTMP_MSG_AUDIO &&
                !pctx->cs[0].active)
            {
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                               "live: waiting for video");
                continue;
            }
			/* 等待关键帧   */
            if (lacf->wait_key && prio != NGX_RTMP_VIDEO_KEY_FRAME &&
               (lacf->interleave || h->type == NGX_RTMP_MSG_VIDEO))
            {
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                               "live: skip non-key");
                continue;
            }

            dummy_audio = 0;
            /* 
            生成dummy audio 
			*/
            if (lacf->wait_video && h->type == NGX_RTMP_MSG_VIDEO &&
                !pctx->cs[1].active)
            {
                dummy_audio = 1;
                if (aapkt == NULL) {
                    aapkt = ngx_rtmp_alloc_shared_buf(cscf); /* aapk如果申请失败,需要作一下失败处理,避免访问空指针  */
                    if(NULL != aapkt){
                   	 ngx_rtmp_prepare_message(s, &clh, NULL, aapkt);
                    }
                }
            }
			/* 转码  */
            if (header || coheader) {

                /* send absolute codec header */

                ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                               "live: abs %s header timestamp=%uD",
                               type_s, lh.timestamp);

                if (header) {
                    if (apkt == NULL) {
                        apkt = ngx_rtmp_append_shared_bufs(cscf, NULL, header);
                        ngx_rtmp_prepare_message(s, &lh, NULL, apkt);
                    }

                    rc = ngx_rtmp_send_message(ss, apkt, 0);
                    if (rc != NGX_OK) {
                        continue;
                    }
                }

                if (coheader) {
                    if (acopkt == NULL) {
                        acopkt = ngx_rtmp_append_shared_bufs(cscf, NULL, coheader);
                        ngx_rtmp_prepare_message(s, &clh, NULL, acopkt);
                    }

                    rc = ngx_rtmp_send_message(ss, acopkt, 0);
                    if (rc != NGX_OK) {
                        continue;
                    }

                } else if (dummy_audio) {
                    ngx_rtmp_send_message(ss, aapkt, 0);
                }

                cs->timestamp = lh.timestamp;
                cs->active = 1;
                ss->current_time = cs->timestamp;

            } else {

                /* send absolute packet */

                ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                               "live: abs %s packet timestamp=%uD",
                               type_s, ch.timestamp);

                if (apkt == NULL) {
                    apkt = ngx_rtmp_append_shared_bufs(cscf, NULL, in);
                    ngx_rtmp_prepare_message(s, &ch, NULL, apkt);
                }

                rc = ngx_rtmp_send_message(ss, apkt, prio);
                if (rc != NGX_OK) {
                    continue;
                }

                cs->timestamp = ch.timestamp;
                cs->active = 1;
                ss->current_time = cs->timestamp;

                ++peers;

                if (dummy_audio) {
                    ngx_rtmp_send_message(ss, aapkt, 0);
                }

                continue;
            }
        }

        /* 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;
    }

    if (rpkt) {
        ngx_rtmp_free_shared_chain(cscf, rpkt);
    }

    if (apkt) {
        ngx_rtmp_free_shared_chain(cscf, apkt);
    }

    if (aapkt) {
        ngx_rtmp_free_shared_chain(cscf, aapkt);
    }

    if (acopkt) {
        ngx_rtmp_free_shared_chain(cscf, acopkt);
    }
    
	/* 更新直播流带宽信息 */
    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);

    return NGX_OK;
}
static void
ngx_live_relay_httpflv_recv_body(void *request, ngx_http_request_t *hcr)
{
    ngx_int_t                   n;
    ngx_rtmp_session_t         *s;
    ngx_chain_t                *cl, *l, *in;
    ngx_rtmp_header_t          *h;
    ngx_rtmp_stream_t          *st = NULL;

    s = request;

    n = ngx_http_client_read_body(hcr, &cl);

    if (n == 0 || n == NGX_ERROR) {
        s->finalize_reason = n == 0? NGX_LIVE_NORMAL_CLOSE:
                                     NGX_LIVE_FLV_RECV_ERR;
        ngx_log_error(NGX_LOG_INFO, s->log, ngx_errno,
                "http relay, recv body error");
        ngx_rtmp_finalize_session(s);
        return;
    }

    l = cl;
    for (;;) {
        if (l && l->buf->pos == l->buf->last) {
            l = l->next;
        }

        if (l == NULL) {
            return;
        }

        n = ngx_live_relay_httpflv_parse(s, l->buf);

        if (n == NGX_ERROR) {
            ngx_log_error(NGX_LOG_ERR, s->log, 0,
                    "http relay, parse flv frame failed in state %d",
                    s->flv_state);
            ngx_http_client_finalize_request(hcr, 1);

            return;
        }

        if (n == NGX_AGAIN) {
            continue;
        }

        /* NGX_OK */
        st = &s->in_streams[0];
        h = &st->hdr;
        in = st->in;

        if (ngx_rtmp_receive_message(s, h, in) != NGX_OK) {
            ngx_rtmp_finalize_session(s);
            return;
        }

        ngx_put_chainbufs(st->in);
        st->in = NULL;
    }
}
/*
关闭流 
*/
static ngx_int_t
ngx_rtmp_live_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
{
    ngx_rtmp_session_t             *ss;
    ngx_rtmp_live_ctx_t            *ctx;
    ngx_rtmp_live_ctx_t			   **cctx; 
    ngx_rtmp_live_ctx_t 		   *pctx;
    ngx_rtmp_live_stream_t        **stream;
    ngx_rtmp_live_app_conf_t       *lacf;

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        goto next;
    }

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (ctx == NULL) {
        goto next;
    }

    if (ctx->stream == NULL) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                       "live: not joined");
        goto next;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "live: leave '%s'", ctx->stream->name);

    if (ctx->stream->publishing && ctx->publishing) {
        ctx->stream->publishing = 0;
    }

	/* 从直播流中删除当前播放流  */
    for (cctx = &ctx->stream->ctx; *cctx; cctx = &(*cctx)->next) {
        if (*cctx == ctx) {
        	/* 为当前流,删除,指向当前流指向的下一条流  */
            *cctx = ctx->next; 
            break;
        }
    }
	/* 正在发布或者播放,则停止 */
    if (ctx->publishing || ctx->stream->active) {
        ngx_rtmp_live_stop(s);
    }

	/* 如果是发布端关闭,需要关闭对应的播放流 */
    if (ctx->publishing) {
        ngx_rtmp_send_status(s, "NetStream.Unpublish.Success",
                             "status", "Stop publishing");
        if (!lacf->idle_streams) {
            for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {
                if (pctx->publishing == 0) {
                    ss = pctx->session;
                    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                                   "live: no publisher");
                    ngx_rtmp_finalize_session(ss);
                }
            }
        }
    }

	
    if (ctx->stream->ctx) {
    	/*  该rtmp流对应的直播流还有其他rtmp流 */
        ctx->stream = NULL;
        goto next;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "live: delete empty stream '%s'",
                   ctx->stream->name);
	/*  该rtmp流对应的直播流没有其他rtmp流,回收直播流内存  */
    stream = ngx_rtmp_live_get_stream(s, ctx->stream->name, 0);
    if (stream == NULL) {
        goto next;
    }
   
    *stream = (*stream)->next;  /*  暂时无使用 */ 
	
    ctx->stream->next = lacf->free_streams; /* 将stream 作为头节点加入释放流链链表 */
    lacf->free_streams = ctx->stream; /*  更新释放流链表地址   */
    ctx->stream = NULL; /* 置NULL,不关联直播流 */

    if (!ctx->silent && !ctx->publishing && !lacf->play_restart) {
        ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", "Stop live");
    }

next:
    return next_close_stream(s, v);
}
예제 #25
0
static void
ngx_rtmp_handshake_recv(ngx_event_t *rev)
{
    ssize_t                     n;
    ngx_connection_t           *c;
    ngx_rtmp_session_t         *s;
    ngx_buf_t                  *b;

    c = rev->data;
    s = c->hls ? c->hls_data : c->data;

    if (c->destroyed) {
        return;
    }

    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                "handshake_recv: recv: client timed out");
        c->timedout = 1;
        ngx_rtmp_finalize_session(s);
        return;
    }

    if (rev->timer_set) {
        ngx_del_timer(rev);
    }

    b = s->hs_buf;

    while (b->last != b->end) {
        n = c->recv(c, b->last, b->end - b->last);

        if (n == NGX_ERROR || n == 0) {
            ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                    "handshake_recv: c->recv error");
            ngx_rtmp_finalize_session(s);
            return;
        }

        if (n == NGX_AGAIN) {
            ngx_add_timer(rev, s->timeout);
            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                ngx_rtmp_finalize_session(s);
                ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                        "handshake_recv: NGX_AGAIN, ngx_rtmp_finalize_session");
            }
            ngx_log_debug0(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                    "handshake_recv: NGX_AGAIN, return");
            return;
        }

        b->last += n;
    }

    if (rev->active) {
        ngx_del_event(rev, NGX_READ_EVENT, 0);
    }

    ++s->hs_stage;
    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
            "ngx_rtmp_handshake_recv:handshake: stage %ui", s->hs_stage);

    switch (s->hs_stage) {
        case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE:
            if (ngx_rtmp_handshake_parse_challenge(s,
                    &ngx_rtmp_client_partial_key,
                    &ngx_rtmp_server_full_key) != NGX_OK)
            {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                        "handshake: error parsing challenge");
                ngx_rtmp_finalize_session(s);
                return;
            }
            if (s->hs_old) {
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                        "handshake: old-style challenge");
                s->hs_buf->pos = s->hs_buf->start;
                s->hs_buf->last = s->hs_buf->end;
            } else if (ngx_rtmp_handshake_create_challenge(s,
                        ngx_rtmp_server_version,
                        &ngx_rtmp_server_partial_key) != NGX_OK)
            {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                        "handshake: error creating challenge");
                ngx_rtmp_finalize_session(s);
                return;
            }
            ngx_rtmp_handshake_send(c->write);
            break;

        case NGX_RTMP_HANDSHAKE_SERVER_DONE:
            ngx_rtmp_handshake_done(s);
            break;

        case NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE:
            if (ngx_rtmp_handshake_parse_challenge(s,
                    &ngx_rtmp_server_partial_key,
                    &ngx_rtmp_client_full_key) != NGX_OK)
            {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                        "handshake: error parsing challenge");
                ngx_rtmp_finalize_session(s);
                return;
            }
            s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
            ngx_rtmp_handshake_recv(c->read);
            break;

        case NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE:
            if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                        "handshake: response error");
                ngx_rtmp_finalize_session(s);
                return;
            }
            ngx_rtmp_handshake_send(c->write);
            break;
    }
}
static void
ngx_rtmp_gop_cache_send(ngx_rtmp_session_t *s)
{
    ngx_rtmp_session_t                 *rs;
    ngx_chain_t                        *pkt, *apkt, *meta, *header;
    ngx_rtmp_live_ctx_t                *ctx, *pub_ctx;
    ngx_http_flv_live_ctx_t            *hflctx;
    ngx_rtmp_gop_cache_ctx_t           *gctx;
    ngx_rtmp_live_app_conf_t           *lacf;
    ngx_rtmp_gop_cache_t               *cache;
    ngx_rtmp_gop_frame_t               *gf;
    ngx_rtmp_header_t                   ch, lh;
    ngx_uint_t                          meta_version;
    uint32_t                            delta;
    ngx_int_t                           csidx;
    ngx_rtmp_live_chunk_stream_t       *cs;
    ngx_rtmp_live_proc_handler_t       *handler;
    ngx_http_request_t                 *r;

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        return;
    }

    /* pub_ctx saved the publisher info */
    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (ctx == NULL || ctx->stream == NULL ||
        ctx->stream->pub_ctx == NULL || !ctx->stream->publishing) {
        return;
    }

    pkt = NULL;
    apkt = NULL;
    header = NULL;
    meta = NULL;
    meta_version = 0;

    pub_ctx = ctx->stream->pub_ctx;
    rs = pub_ctx->session;
    s->publisher = rs;
    handler = ngx_rtmp_live_proc_handlers[ctx->protocol];

    gctx = ngx_rtmp_get_module_ctx(rs, ngx_rtmp_gop_cache_module);
    if (gctx == NULL) {
        return;
    }

    for (cache = gctx->cache_head; cache; cache = cache->next) {
        if (ctx->protocol == NGX_RTMP_PROTOCOL_HTTP) {
            r = s->data;
            if (r == NULL || (r->connection && r->connection->destroyed)) {
                return;
            }

            hflctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module);
            if (!hflctx->header_sent) {
                hflctx->header_sent = 1;
                ngx_http_flv_live_send_header(s);
            }
        }

        if (meta == NULL && meta_version != gctx->meta_version) {
            meta = handler->meta_message_pt(s, gctx->meta);
            if (meta == NULL) {
                return;
            }
        }

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

        /* send metadata */
        if (meta && meta_version != ctx->meta_version) {
            ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                    "gop cache send: meta");

            if (handler->send_message_pt(s, meta, 0) == NGX_ERROR) {
                ngx_rtmp_finalize_session(s);
                return;
            }

            ctx->meta_version = meta_version;
            handler->free_message_pt(s, meta);
        }

        for (gf = cache->frame_head; gf; gf = gf->next) {
            csidx = !(lacf->interleave || gf->h.type == NGX_RTMP_MSG_VIDEO);

            cs = &ctx->cs[csidx];

            lh = ch = gf->h;

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

            delta = ch.timestamp - lh.timestamp;

            if (!cs->active) {
                switch (gf->h.type) {
                    case NGX_RTMP_MSG_VIDEO:
                        header = gctx->video_seq_header;
                        break;
                    default:
                        header = gctx->audio_seq_header;
                }

                if (header) {
                    apkt = handler->append_message_pt(s, &lh, NULL, header);
                    if (apkt == NULL) {
                        return;
                    }
                }

                if (apkt && handler->send_message_pt(s, apkt, 0) != NGX_OK) {
                    continue;
                }

                cs->timestamp = lh.timestamp;
                cs->active = 1;
                s->current_time = cs->timestamp;
            }

            pkt = handler->append_message_pt(s, &ch, &lh, gf->frame);
            if (pkt == NULL) {
                return;
            }

            if (handler->send_message_pt(s, pkt, gf->prio) != NGX_OK) {
                ++pub_ctx->ndropped;

                cs->dropped += delta;

                ngx_rtmp_finalize_session(s);
                return;
            }

            ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                    "gop cache send: tag type='%s' prio='%d' ctimestamp='%uD' "
                    "ltimestamp='%uD'",
                    gf->h.type == NGX_RTMP_MSG_AUDIO ? "audio" : "video",
                    gf->prio, ch.timestamp, lh.timestamp);

            cs->timestamp += delta;
            s->current_time = cs->timestamp;

            if (pkt) {
                handler->free_message_pt(s, pkt);
                pkt = NULL;
            }

            if (apkt) {
                handler->free_message_pt(s, apkt);
                apkt = NULL;
            }
        }
    }
}
예제 #27
0
static void
ngx_rtmp_handshake_send(ngx_event_t *wev)
{
    ngx_int_t                   n;
    ngx_connection_t           *c;
    ngx_rtmp_session_t         *s;
    ngx_buf_t                  *b;

    c = wev->data;
    s = c->hls ? c->hls_data : c->data;

    if (c->destroyed) {
        return;
    }

    if (wev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                "handshake_send: send: client timed out, finish session.");
        c->timedout = 1;
        ngx_rtmp_finalize_session(s);
        return;
    }

    if (wev->timer_set) {
        ngx_del_timer(wev);
    }

    b = s->hs_buf;

    while(b->pos != b->last) {
        n = c->send(c, b->pos, b->last - b->pos);

        if (n == NGX_ERROR) {
            ngx_rtmp_finalize_session(s);
            ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                    "handshake_send: c->send: error");
            return;
        }

        if (n == NGX_AGAIN || n == 0) {
            ngx_add_timer(c->write, s->timeout);
            if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
                ngx_rtmp_finalize_session(s);
                ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                        "handshake_send: NGX_AGAIN ");
            }
            return;
        }

        b->pos += n;
    }

    if (wev->active) {
        ngx_del_event(wev, NGX_WRITE_EVENT, 0);
    }

    ++s->hs_stage;
    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
            "ngx_rtmp_handshake_send:handshake: stage %ui", s->hs_stage);

    switch (s->hs_stage) {
        case NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE:
            if (s->hs_old) {
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                        "handshake_send: old-style response");
                s->hs_buf->pos = s->hs_buf->start + 1;
                s->hs_buf->last = s->hs_buf->end;
            } else if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                        "handshake_send: response error");
                ngx_rtmp_finalize_session(s);
                return;
            }
            ngx_rtmp_handshake_send(wev);
            break;

        case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE:
            s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
            ngx_rtmp_handshake_recv(c->read);
            break;

        case NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE:
            s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start;
            ngx_rtmp_handshake_recv(c->read);
            break;

        case NGX_RTMP_HANDSHAKE_CLIENT_DONE:
            ngx_rtmp_handshake_done(s);
            break;
    }
}
예제 #28
0
static ngx_int_t
ngx_rtmp_live_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
{
    ngx_rtmp_session_t             *ss;
    ngx_rtmp_live_ctx_t            *ctx, **cctx, *pctx;
	//**rcctx;modify for warning
    ngx_rtmp_live_stream_t        **stream;
    ngx_rtmp_live_app_conf_t       *lacf;

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        goto next;
    }

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (ctx == NULL) {
        goto next;
    }

    if (ctx->stream == NULL) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                       "live: not joined");
        goto next;
    }

    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "live: leave '%s', publisher=%i, ctx->stream->publishing=%i",
                   ctx->stream->name, ctx->publishing, ctx->stream->publishing);

    if (ctx->stream->publishing && ctx->publishing) {
        ctx->stream->publishing = 0;
    }

    for (cctx = &ctx->stream->ctx; *cctx; cctx = &(*cctx)->next) {
        if (*cctx == ctx) {
            *cctx = ctx->next;
            break;
        }
    }

    if (ctx->publishing || ctx->stream->active) {
        ngx_rtmp_live_stop(s);
    }

    if (ctx->publishing) {
        ngx_rtmp_send_status(s, "NetStream.Unpublish.Success",
                             "status", "Stop publishing");
		ctx->stream->bw_in.bandwidth = 0;
		ctx->stream->bw_real.bandwidth = 0;
		ctx->stream->bw_out.bandwidth = 0;
        if (ngx_rtmp_publishing > 0) {
			
            --ngx_rtmp_publishing;
        }

        if (!lacf->idle_streams) {
            for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {
                if (pctx->publishing == 0) {
                    ss = pctx->session;
                    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                                   "live: no publisher");
                    ngx_rtmp_finalize_session(ss);
                }
            }
        } else {
        	ngx_uint_t nplayer = 0;
            for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {
				if (pctx->publishing == 0) {
	                if (ngx_memcmp(pctx->session->flashver.data, NGX_RTMP_RELAY_NAME, ngx_strlen(NGX_RTMP_RELAY_NAME)) == 0) {
	                    ss = pctx->session;
	                    ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
	                                   "live: close relay session");
	                    ngx_rtmp_finalize_session(ss);
	                }
					nplayer++;
				}
            }

			if (nplayer > 0) {
				ngx_rtmp_live_checking_publish(s, ctx->stream);
			}
        }
    }else {       

		if (ctx->stream->ctx != NULL &&
			ctx->stream->ctx->next == NULL &&
			ctx->stream->ctx->publishing) {
			ngx_str_t strname;
			strname.data = ctx->stream->name;
			strname.len  = ngx_strlen(ctx->stream->name);
			ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
					"live: last player close his connection.");
			ngx_rtmp_relay_player_dry(s, &strname);
		}

	    if (ngx_rtmp_playing > 0) {
			
			--ngx_rtmp_playing;
	    }
    }

    if (ctx->stream->ctx) {
        ctx->stream = NULL;
        goto next;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "live: delete empty stream '%s'",
                   ctx->stream->name);

	ngx_del_timer(&(ctx->stream->check_evt));

    stream = ngx_rtmp_live_get_stream(s, ctx->stream->name, 0);
    if (stream == NULL) {
        goto next;
    }
    *stream = (*stream)->next;

    ctx->stream->next = lacf->free_streams;
    lacf->free_streams = ctx->stream;
    ctx->stream = NULL;

    if (!ctx->silent && !ctx->publishing && !lacf->play_restart) {
        ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", "Stop live");
    }

next:
    return next_close_stream(s, v);
}
static void
ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev)
{
    u_char               buf[107], *p, *pp, *text;
    size_t               len;
    ssize_t              n;
    ngx_err_t            err;
    ngx_int_t            i;
    ngx_addr_t           addr;
    ngx_connection_t    *c;
    ngx_rtmp_session_t  *s;

    c = rev->data;
    s = c->data;

    if (c->destroyed) {
        return;
    }

    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                "proxy_protocol: recv: client timed out");
        c->timedout = 1;
        ngx_rtmp_finalize_session(s);
        return;
    }

    if (rev->timer_set) {
        ngx_del_timer(rev);
    }

    n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK);

    err = ngx_socket_errno;

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, "recv(): %d", n);

    if (n == -1) {

        if (err == NGX_EAGAIN) {
            ngx_add_timer(rev, s->timeout);

            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                ngx_rtmp_finalize_session(s);
            }

            return;
        }

        ngx_rtmp_finalize_session(s);

        return;
    }

    p = buf;

    if (n <= 8 && ngx_strncmp(p, "PROXY ", 6) != 0) {
        goto bad_header;
    }

    n -= 6;
    p += 6;

    ngx_memzero(&addr, sizeof(ngx_addr_t));

    if (n >= 7 && ngx_strncmp(p, "UNKNOWN", 7) == 0) {
        n -= 7;
        p += 7;
        goto skip;
    }

    if (n < 5 || ngx_strncmp(p, "TCP", 3) != 0
        || (p[3] != '4' && p[3] != '6') || p[4] != ' ')
    {
        goto bad_header;
    }

    n -= 5;
    p += 5;

    pp = ngx_strlchr(p, p + n, ' ');

    if (pp == NULL) {
        goto bad_header;
    }

    if (ngx_parse_addr(s->connection->pool, &addr, p, pp - p) != NGX_OK) {
        goto bad_header;
    }

    n -= pp - p;
    p = pp;

skip:

    for (i = 0; i + 1 < n; i++) {
        if (p[i] == CR && p[i + 1] == LF) {
            break;
        }
    }

    if (i + 1 >= n) {
        goto bad_header;
    }

    n = p - buf + i + 2;

    if (c->recv(c, buf, n) != n) {
        goto failed;
    }

    if (addr.socklen) {
        text = ngx_palloc(s->connection->pool, NGX_SOCKADDR_STRLEN);

        if (text == NULL) {
            goto failed;
        }

        len = ngx_sock_ntop(addr.sockaddr,
#if (nginx_version >= 1005003)
                            addr.socklen,
#endif
                            text, NGX_SOCKADDR_STRLEN, 0);
        if (len == 0) {
            goto failed;
        }

        c->sockaddr = addr.sockaddr;
        c->socklen = addr.socklen;
        c->addr_text.data = text;
        c->addr_text.len = len;

        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
                       "proxy_protocol: remote_addr:'%V'", &c->addr_text);
    }

    ngx_rtmp_handshake(s);

    return;

bad_header:

    ngx_log_error(NGX_LOG_INFO, c->log, 0, "proxy_protocol: bad header");

failed:

    ngx_rtmp_finalize_session(s);
}
/*  设置流状态  */
static void
ngx_rtmp_live_set_status(ngx_rtmp_session_t *s, ngx_chain_t *control,
                         ngx_chain_t **status, size_t nstatus,
                         unsigned active)
{
    ngx_rtmp_live_app_conf_t   *lacf;
    ngx_rtmp_live_ctx_t        *ctx, *pctx;
    ngx_chain_t               **cl;
    ngx_event_t                *e;
    size_t                      n;

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "live: set active=%ui", active);

    if (ctx->active == active) {
        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                       "live: unchanged active=%ui", active);
        return;
    }

    ctx->active = active;

    if (ctx->publishing) {

        /* publisher */

        if (lacf->idle_timeout) {
            e = &ctx->idle_evt;

            if (active && !ctx->idle_evt.timer_set) {
                e->data = s->connection;
                e->log = s->connection->log;
                e->handler = ngx_rtmp_live_idle;

                ngx_add_timer(e, lacf->idle_timeout);

            } else if (!active && ctx->idle_evt.timer_set) {
            	/* 需要启动流且启动空闲定时器,需要删除该定时器 */
                ngx_del_timer(e);
            }
        }

        ctx->stream->active = active;

        for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {
        	/* 通知播放端 */
            if (pctx->publishing == 0) {
                ngx_rtmp_live_set_status(pctx->session, control, status,
                                         nstatus, active);
            }
        }

        return;
    }

    /* subscriber */

    if (control && ngx_rtmp_send_message(s, control, 0) != NGX_OK) {
        ngx_rtmp_finalize_session(s);
        return;
    }

    if (!ctx->silent) {
        cl = status;

        for (n = 0; n < nstatus; ++n, ++cl) {
            if (*cl && ngx_rtmp_send_message(s, *cl, 0) != NGX_OK) {
                ngx_rtmp_finalize_session(s);
                return;
            }
        }
    }

    ctx->cs[0].active = 0;
    ctx->cs[0].dropped = 0;

    ctx->cs[1].active = 0;
    ctx->cs[1].dropped = 0;
}