static ngx_int_t
ngx_rtmp_codec_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_chain_t *in)
{
    ngx_rtmp_codec_ctx_t               *ctx;
    ngx_rtmp_core_srv_conf_t           *cscf;

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
    if (ctx == NULL) {
        return NGX_OK;
    }

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    if (ctx->avc_header) {
        ngx_rtmp_free_shared_chain(cscf, ctx->avc_header);
        ctx->avc_header = NULL;
    }

    if (ctx->aac_header) {
        ngx_rtmp_free_shared_chain(cscf, ctx->aac_header);
        ctx->aac_header = NULL;
    }

    if (ctx->meta) {
        ngx_rtmp_free_shared_chain(cscf, ctx->meta);
        ctx->meta = NULL;
    }

    return NGX_OK;
}
ngx_chain_t *
ngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                    ngx_rtmp_amf_elt_t *elts, size_t nelts)
{
    ngx_chain_t                *first;
    ngx_int_t                   rc;
    ngx_rtmp_core_srv_conf_t   *cscf;

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "create: amf nelts=%ui", nelts);

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    first = NULL;

    rc = ngx_rtmp_append_amf(s, &first, NULL, elts, nelts);

    if (rc != NGX_OK && first) {
        ngx_rtmp_free_shared_chain(cscf, first);
        first = NULL;
    }

    if (first) {
        ngx_rtmp_prepare_message(s, h, NULL, first);
    }

    return first;
}
Beispiel #3
0
ngx_int_t
ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_rtmp_amf_elt_t *elts, size_t nelts)
{
    ngx_chain_t                *first;
    ngx_int_t                   rc;
    ngx_rtmp_core_srv_conf_t   *cscf;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    first = NULL;
    rc = ngx_rtmp_append_amf(s, &first, NULL, elts, nelts);
    if (rc != NGX_OK || first == NULL) {
        goto done;
    }

    ngx_rtmp_prepare_message(s, h, NULL, first);

    rc = ngx_rtmp_send_message(s, first, 0);

done:
    ngx_rtmp_free_shared_chain(cscf, first);

    return rc;
}
static ngx_int_t
ngx_rtmp_codec_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_chain_t *in)
{
    ngx_rtmp_codec_ctx_t               *ctx;
    ngx_rtmp_core_srv_conf_t           *cscf;

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
    if (ctx == NULL) {
        return NGX_OK;
    }

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    if (ctx->avc_header) {
        ngx_rtmp_free_shared_chain(cscf, ctx->avc_header);
        ctx->avc_header = NULL;
    }

    if (ctx->aac_header) {
        ngx_rtmp_free_shared_chain(cscf, ctx->aac_header);
        ctx->aac_header = NULL;
    }

    if (ctx->store_key_size) {
        ngx_uint_t i;
        for (i = 0; i < ctx->store_key_size; i++)
        {
            if (ctx->storeframes[i])
            {
                ngx_rtmp_free_shared_chain(cscf, ctx->storeframes[i]);
                ctx->storeframes[i] = NULL;
            }
        }
    }

    if (ctx->meta) {
        ngx_rtmp_free_shared_chain(cscf, ctx->meta);
        ctx->meta = NULL;
    }

    return NGX_OK;
}
/* 
启动发布或播放 

*/
static void
ngx_rtmp_live_start(ngx_rtmp_session_t *s)
{
    ngx_rtmp_core_srv_conf_t   *cscf;
    ngx_rtmp_live_app_conf_t   *lacf;
    ngx_chain_t                *control;
    ngx_chain_t                *status[3];
    size_t                      n, nstatus;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
	/* 控制报文 */
    control = ngx_rtmp_create_stream_begin(s, NGX_RTMP_MSID);

    nstatus = 0; /* status报文个数 */

    if (lacf->play_restart) {
        status[nstatus++] = ngx_rtmp_create_status(s, 
        										  "NetStream.Play.Start",
                                                   "status", 
                                                   "Start live");
        status[nstatus++] = ngx_rtmp_create_sample_access(s);
    }

    if (lacf->publish_notify) {
        status[nstatus++] = ngx_rtmp_create_status(s,
                                                 "NetStream.Play.PublishNotify",
                                                 "status", 
                                                 "Start publishing");
    }

    ngx_rtmp_live_set_status(s, control, status, nstatus, 1);

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

    for (n = 0; n < nstatus; ++n) {
        ngx_rtmp_free_shared_chain(cscf, status[n]);
    }
}
/*
停止直播发布或播放
*/
static void
ngx_rtmp_live_stop(ngx_rtmp_session_t *s)
{
    ngx_rtmp_core_srv_conf_t   *cscf;
    ngx_rtmp_live_app_conf_t   *lacf;
    ngx_chain_t                *control;
    ngx_chain_t                *status[3];
    size_t                      n, nstatus;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);

    control = ngx_rtmp_create_stream_eof(s, NGX_RTMP_MSID);

    nstatus = 0;

    if (lacf->play_restart) {
        status[nstatus++] = ngx_rtmp_create_status(s, "NetStream.Play.Stop",
                                                   "status", "Stop live");
    }

    if (lacf->publish_notify) {
        status[nstatus++] = ngx_rtmp_create_status(s,
                                               "NetStream.Play.UnpublishNotify",
                                               "status", "Stop publishing");
    }

    ngx_rtmp_live_set_status(s, control, status, nstatus, 0);

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

    for (n = 0; n < nstatus; ++n) {
        ngx_rtmp_free_shared_chain(cscf, status[n]);
    }
}
static ngx_int_t
ngx_rtmp_send_shared_packet(ngx_rtmp_session_t *s, ngx_chain_t *cl)
{
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_int_t                       rc;

    if (cl == NULL) {
        return NGX_ERROR;
    }

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    rc = ngx_rtmp_send_message(s, cl, 0);

    ngx_rtmp_free_shared_chain(cscf, cl);

    return rc;
}
static void
ngx_rtmp_close_session_handler(ngx_event_t *e)
{
    ngx_rtmp_session_t                 *s;
    ngx_connection_t                   *c;
    ngx_rtmp_core_srv_conf_t           *cscf;

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

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close session");

    if (s) {
        ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL);

        if (s->ping_evt.timer_set) {
            ngx_del_timer(&s->ping_evt);
        }

        if (s->in_old_pool) {
            ngx_destroy_pool(s->in_old_pool);
        }

        if (s->in_pool) {
            ngx_destroy_pool(s->in_pool);
        }

        ngx_rtmp_free_handshake_buffers(s);

        while (s->out_pos != s->out_last) {
            ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos++]);
            s->out_pos %= s->out_queue;
        }
    }

    ngx_rtmp_close_connection(c);
}
static ngx_rtmp_gop_frame_t *
ngx_rtmp_gop_cache_free_frame(ngx_rtmp_session_t *s,
    ngx_rtmp_gop_frame_t *frame)
{
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_rtmp_gop_cache_ctx_t       *ctx;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
    if (cscf == NULL) {
        return NULL;
    }

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_cache_module);
    if (ctx == NULL) {
        return NULL;
    }

    if (frame->frame) {
        ngx_rtmp_free_shared_chain(cscf, frame->frame);
        frame->frame = NULL;
    }

    if (frame->h.type == NGX_RTMP_MSG_VIDEO) {
        ctx->video_frame_in_all--;
    } else if (frame->h.type == NGX_RTMP_MSG_AUDIO) {
        ctx->audio_frame_in_all--;
    }

    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
           "gop free frame: type='%s' video_frame_in_cache='%uD' "
           "audio_frame_in_cache='%uD'",
           frame->h.type == NGX_RTMP_MSG_VIDEO ? "video" : "audio",
           ctx->video_frame_in_all, ctx->audio_frame_in_all);

    return frame->next;
}
/*
直播音视频处理 
*/
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_rtmp_play_send(ngx_event_t *e)
{
    ngx_rtmp_session_t             *s;
    ngx_rtmp_play_ctx_t            *ctx;
    uint32_t                        last_timestamp;
    ngx_rtmp_header_t               h, lh;
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_chain_t                    *out, in;
    ngx_buf_t                       in_buf;
    ssize_t                         n;
    uint32_t                        buflen, end_timestamp, size;

    s = e->data;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
    if (ctx == NULL) {
        return;
    }

    if (ctx->offset == -1) {
        ctx->offset = ngx_rtmp_play_timestamp_to_offset(s,
                                                        ctx->start_timestamp);
        ctx->start_timestamp = -1; /* set later from actual timestamp */
    }

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: read tag at offset=%i", ctx->offset);

    /* read tag header */
    n = ngx_read_file(&ctx->file, ngx_rtmp_play_header, 
                      sizeof(ngx_rtmp_play_header), ctx->offset);
    if (n != sizeof(ngx_rtmp_play_header)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: could not read flv tag header");
        ngx_rtmp_send_user_stream_eof(s, 1);
        return;
    }

    /* parse header fields */
    ngx_memzero(&h, sizeof(h));
    h.msid = NGX_RTMP_LIVE_MSID;
    h.type = ngx_rtmp_play_header[0];
    size = 0;
    ngx_rtmp_rmemcpy(&size, ngx_rtmp_play_header + 1, 3);
    ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_play_header + 4, 3);
    ((u_char *) &h.timestamp)[3] = ngx_rtmp_play_header[7];

    ctx->offset += (sizeof(ngx_rtmp_play_header) + size + 4);

    last_timestamp = 0;

    switch (h.type) {
        case NGX_RTMP_MSG_AUDIO:
            h.csid = NGX_RTMP_LIVE_CSID_AUDIO;
            last_timestamp = ctx->last_audio;
            ctx->last_audio = h.timestamp;
            break;

        case NGX_RTMP_MSG_VIDEO:
            h.csid = NGX_RTMP_LIVE_CSID_VIDEO;
            last_timestamp = ctx->last_video;
            ctx->last_video = h.timestamp;
            break;

        default:
            goto skip;
    }

    ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: read tag type=%i size=%uD timestamp=%uD "
                  "last_timestamp=%uD", 
                  (ngx_int_t) h.type,size, h.timestamp, last_timestamp);

    lh = h;
    lh.timestamp = last_timestamp;

    if (size > sizeof(ngx_rtmp_play_buffer)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: too big message: %D>%uz", size, 
                      sizeof(ngx_rtmp_play_buffer));
        goto next;
    }

    /* read tag body */
    n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, size, 
                      ctx->offset - size - 4);
    if (n != (ssize_t) size) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: could not read flv tag");
        return;
    }

    /* prepare input chain */
    ngx_memzero(&in, sizeof(in));
    ngx_memzero(&in_buf, sizeof(in_buf));
    in.buf = &in_buf;
    in_buf.pos = ngx_rtmp_play_buffer;
    in_buf.last = ngx_rtmp_play_buffer + size;

    /* output chain */
    out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
    ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ? 
                             &lh : NULL, out);
    ngx_rtmp_send_message(s, out, 0); /* TODO: priority */
    ngx_rtmp_free_shared_chain(cscf, out);

    ctx->msg_mask |= (1 << h.type);

next:
    if (ctx->start_timestamp == -1) {
        ctx->start_timestamp = h.timestamp;
        ctx->epoch = ngx_current_msec;
        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                      "play: start_timestamp=%i", ctx->start_timestamp);
        goto skip;
    }

    buflen = (s->buflen ? s->buflen : NGX_RTMP_PLAY_DEFAULT_BUFLEN);
    end_timestamp = (ngx_current_msec - ctx->epoch) +
                     ctx->start_timestamp + buflen;

    ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
           "play: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i",
            h.timestamp > end_timestamp ? "schedule" : "advance",
            h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,
            h.timestamp, end_timestamp, (ngx_int_t) buflen);

    /* too much data sent; schedule timeout */
    if (h.timestamp > end_timestamp) {
        ngx_add_timer(e, h.timestamp - end_timestamp);
        return;
    }

skip:
    ngx_post_event(e, &ngx_posted_events);
}
static ngx_int_t
ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts)
{
    ngx_rtmp_flv_ctx_t             *ctx;
    uint32_t                        last_timestamp;
    ngx_rtmp_header_t               h, lh;
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_chain_t                    *out, in;
    ngx_buf_t                       in_buf;
    ngx_int_t                       rc;
    ssize_t                         n;
    uint32_t                        buflen, end_timestamp, size;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);

    if (ctx == NULL) {
        return NGX_ERROR;
    }

    if (ctx->offset == -1) {
        ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f,
                      ctx->start_timestamp);
        ctx->start_timestamp = -1; /* set later from actual timestamp */
    }

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "flv: read tag at offset=%i", ctx->offset);

    /* read tag header */
    n = ngx_read_file(f, ngx_rtmp_flv_header,
                      sizeof(ngx_rtmp_flv_header), ctx->offset);

    if (n != sizeof(ngx_rtmp_flv_header)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "flv: could not read flv tag header");
        return NGX_DONE;
    }

    /* parse header fields */
    ngx_memzero(&h, sizeof(h));

    h.msid = NGX_RTMP_MSID;
    h.type = ngx_rtmp_flv_header[0];

    size = 0;

    ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);
    ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_flv_header + 4, 3);

    ((u_char *) &h.timestamp)[3] = ngx_rtmp_flv_header[7];

    ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4);

    last_timestamp = 0;

    switch (h.type) {

    case NGX_RTMP_MSG_AUDIO:
        h.csid = NGX_RTMP_CSID_AUDIO;
        last_timestamp = ctx->last_audio;
        ctx->last_audio = h.timestamp;
        break;

    case NGX_RTMP_MSG_VIDEO:
        h.csid = NGX_RTMP_CSID_VIDEO;
        last_timestamp = ctx->last_video;
        ctx->last_video = h.timestamp;
        break;

    default:
        return NGX_OK;
    }

    ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "flv: read tag type=%i size=%uD timestamp=%uD "
                   "last_timestamp=%uD",
                   (ngx_int_t) h.type,size, h.timestamp, last_timestamp);

    lh = h;
    lh.timestamp = last_timestamp;

    if (size > sizeof(ngx_rtmp_flv_buffer)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "flv: too big message: %D>%uz", size,
                      sizeof(ngx_rtmp_flv_buffer));
        goto next;
    }

    /* read tag body */
    n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
                      ctx->offset - size - 4);

    if (n != (ssize_t) size) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "flv: could not read flv tag");
        return NGX_ERROR;
    }

    /* prepare input chain */
    ngx_memzero(&in, sizeof(in));
    ngx_memzero(&in_buf, sizeof(in_buf));

    in.buf = &in_buf;
    in_buf.pos  = ngx_rtmp_flv_buffer;
    in_buf.last = ngx_rtmp_flv_buffer + size;

    /* output chain */
    out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);

    ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ?
                             &lh : NULL, out);
    rc = ngx_rtmp_send_message(s, out, 0);
    ngx_rtmp_free_shared_chain(cscf, out);

    if (rc == NGX_AGAIN) {
        return NGX_AGAIN;
    }

    if (rc != NGX_OK) {
        return NGX_ERROR;
    }

    ctx->msg_mask |= (1 << h.type);

next:
    if (ctx->start_timestamp == -1) {
        ctx->start_timestamp = h.timestamp;
        ctx->epoch = ngx_current_msec;

        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                       "flv: start_timestamp=%i", ctx->start_timestamp);
        return NGX_OK;
    }

    buflen = s->buflen + NGX_RTMP_FLV_BUFLEN_ADDON;

    end_timestamp = (ngx_current_msec - ctx->epoch) +
                    ctx->start_timestamp + buflen;

    ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i",
                   h.timestamp > end_timestamp ? "schedule" : "advance",
                   h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,
                   h.timestamp, end_timestamp, (ngx_int_t) buflen);

    s->current_time = h.timestamp;

    /* too much data sent; schedule timeout */
    if (h.timestamp > end_timestamp) {
        return h.timestamp - end_timestamp;
    }

    return NGX_OK;
}
static void
ngx_rtmp_gop_cache_frame(ngx_rtmp_session_t *s, ngx_uint_t prio,
    ngx_rtmp_header_t *ch, ngx_chain_t *frame)
{
    ngx_rtmp_gop_cache_ctx_t       *ctx;
    ngx_rtmp_codec_ctx_t           *codec_ctx;
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_rtmp_gop_cache_app_conf_t  *gacf;
    ngx_rtmp_gop_frame_t           *gf;

    gacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_gop_cache_module);
    if (gacf == NULL || !gacf->gop_cache) {
        return;
    }

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
    if (cscf == NULL) {
        return;
    }

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_gop_cache_module);
    if (ctx == NULL) {
        return;
    }

    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
    if (codec_ctx == NULL) {
        return;
    }

    if (ch->type == NGX_RTMP_MSG_VIDEO) {
        // drop video when not H.264
        if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) {
            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                    "drop video non-H.264 encode type timestamp='%uD'",
                    ch->timestamp);

            return;
        }

        // drop non-IDR
        if (prio != NGX_RTMP_VIDEO_KEY_FRAME && ctx->cache_head == NULL) {
            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                    "drop video non-keyframe timestamp='%uD'",
                    ch->timestamp);

            return;
        }
    }

    // pure audio
    if (ctx->video_frame_in_all == 0 && ch->type == NGX_RTMP_MSG_AUDIO) {
            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                    "drop audio frame timestamp='%uD'",
                    ch->timestamp);

        return;
    }

    if (ch->type == NGX_RTMP_MSG_VIDEO && prio == NGX_RTMP_VIDEO_KEY_FRAME) {
        if (ngx_rtmp_gop_cache_alloc_cache(s) != NGX_OK) {
            return;
        }
    }

    gf = ngx_rtmp_gop_cache_alloc_frame(s);
    if (gf == NULL) {
        return;
    }

    gf->h = *ch;
    gf->prio = prio;
    gf->next = NULL;
    gf->frame = ngx_rtmp_append_shared_bufs(cscf, NULL, frame);

    if (ngx_rtmp_gop_cache_link_frame(s, gf) != NGX_OK) {
        ngx_rtmp_free_shared_chain(cscf, gf->frame);
        return;
    }

    if (ctx->video_frame_in_all > gacf->gop_max_video_count ||
        ctx->audio_frame_in_all > gacf->gop_max_audio_count ||
        (ctx->video_frame_in_all + ctx->audio_frame_in_all)
        > gacf->gop_max_frame_count)
    {
        ngx_log_error(NGX_LOG_WARN, s->connection->log, 0,
               "gop cache: video_frame_in_cache='%uD' "
               "audio_frame_in_cache='%uD' max_video_count='%uD' "
               "max_audio_count='%uD' gop_max_frame_count='%uD'",
               ctx->video_frame_in_all, ctx->audio_frame_in_all,
               gacf->gop_max_video_count, gacf->gop_max_audio_count,
               gacf->gop_max_frame_count);

        ngx_rtmp_gop_cache_cleanup(s);
        return;
    }

    ngx_rtmp_gop_cache_update(s);

    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
           "gop cache: cache packet type='%s' timestamp='%uD'",
           gf->h.type == NGX_RTMP_MSG_AUDIO ? "audio" : "video",
           gf->h.timestamp);
}
static ngx_int_t
ngx_rtmp_live_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                   ngx_chain_t *in, ngx_rtmp_amf_elt_t *out_elts, ngx_uint_t out_elts_size)
{
    ngx_rtmp_live_ctx_t            *ctx, *pctx;
    ngx_chain_t                    *data, *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;
    ngx_int_t                       rc;
    ngx_uint_t                      prio;
    ngx_uint_t                      peers;
    uint32_t                        delta;
    ngx_rtmp_live_chunk_stream_t   *cs;

    u_char                         *msg_type;

    msg_type = (u_char *)out_elts[0].data;

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

    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", msg_type);
        return NGX_OK;
    }

    /* drop the data packet if the stream is not active */
    if (!ctx->stream->active) {
        return NGX_OK;
    }

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

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    cs  = &ctx->cs[2];
    cs->active = 1;

    peers = 0;
    prio = 0;
    data = NULL;
    rc = ngx_rtmp_append_amf(s, &data, NULL, out_elts, out_elts_size);
    if (rc != NGX_OK) {
        if (data) {
            ngx_rtmp_free_shared_chain(cscf, data);
        }
        return NGX_ERROR;
    }

    ngx_memzero(&ch, sizeof(ch));
    ch.timestamp = h->timestamp;
    ch.msid = NGX_RTMP_MSID;
    ch.csid = h->csid;
    ch.type = NGX_RTMP_MSG_AMF_META;

    delta = ch.timestamp - cs->timestamp;

    rpkt = ngx_rtmp_append_shared_bufs(cscf, data, in);
    ngx_rtmp_prepare_message(s, &ch, NULL, rpkt);

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

        ss = pctx->session;

        if (ngx_rtmp_send_message(ss, rpkt, prio) != NGX_OK) {
            ++pctx->ndropped;
            cs->dropped += delta;
            continue;
        }

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

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

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

    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(&ctx->stream->bw_in_data, h->mlen);

    return NGX_OK;
}
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                    *out, *peer_out, *header_out, *pheader_out;
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_rtmp_live_app_conf_t       *lacf;
    ngx_rtmp_session_t             *ss;
    ngx_rtmp_header_t               ch, lh;
    ngx_uint_t                      prio, peer_prio;
    ngx_uint_t                      peers, dropped_peers;
    size_t                          header_offset;
    ngx_uint_t                      header_version;

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 
                "live: NULL application");
        return NGX_ERROR;
    }

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (!lacf->live 
            || in == NULL  || in->buf == NULL
            || ctx == NULL || ctx->stream == NULL
            || (h->type != NGX_RTMP_MSG_VIDEO
                && h->type != NGX_RTMP_MSG_AUDIO))
    {
        return NGX_OK;
    }

    if ((ctx->flags & NGX_RTMP_LIVE_PUBLISHING) == 0) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "live: received audio/video from non-publisher");
        return NGX_OK;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "live: av: %s timestamp=%uD",
            h->type == NGX_RTMP_MSG_VIDEO ? "video" : "audio",
            h->timestamp);

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    /* prepare output header */
    ngx_memzero(&ch, sizeof(ch));
    ngx_memzero(&lh, sizeof(lh));
    ch.timestamp = h->timestamp;
    ch.msid = NGX_RTMP_LIVE_MSID;
    ch.type = h->type;
    lh.msid = ch.msid;
    if (h->type == NGX_RTMP_MSG_VIDEO) {
        prio = ngx_rtmp_get_video_frame_type(in);
        ch.csid = NGX_RTMP_LIVE_CSID_VIDEO;
        lh.timestamp = ctx->last_video;
        ctx->last_video = ch.timestamp;
    } else {
        /* audio priority is the same as video key frame's */
        prio = NGX_RTMP_VIDEO_KEY_FRAME;
        ch.csid = NGX_RTMP_LIVE_CSID_AUDIO;
        lh.timestamp = ctx->last_audio;
        ctx->last_audio = ch.timestamp;
    }
    lh.csid = ch.csid;

    out = ngx_rtmp_append_shared_bufs(cscf, NULL, in);
    ngx_rtmp_prepare_message(s, &ch, &lh, out);

    peers = 0;
    dropped_peers = 0;

    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
    header_out = NULL;
    pheader_out = NULL;
    header_offset = 0;
    header_version = 0;
    if (codec_ctx) {
        if (h->type == NGX_RTMP_MSG_AUDIO) {
            if (codec_ctx->aac_pheader) {
                header_out = codec_ctx->aac_header;
                pheader_out = codec_ctx->aac_pheader;
                header_offset = offsetof(ngx_rtmp_live_ctx_t, aac_version);
                header_version = codec_ctx->aac_version;
            }
        } else {
            if (codec_ctx->avc_pheader) {
                header_out = codec_ctx->avc_header;
                pheader_out = codec_ctx->avc_pheader;
                header_offset = offsetof(ngx_rtmp_live_ctx_t, avc_version);
                header_version = codec_ctx->avc_version;
            }
        }
    }

    /* broadcast to all subscribers */
    for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {
        if (pctx == ctx) {
            continue;
        }
        ++peers;
        ss = pctx->session;

        /* send absolute frame */
        if ((pctx->msg_mask & (1 << h->type)) == 0) {
            ch.timestamp = ngx_current_msec - ss->epoch;
            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                    "live: av: abs %s timestamp=%uD",
                    h->type == NGX_RTMP_MSG_VIDEO ? "video" : "audio",
                    ch.timestamp);
            /* send codec header as abs frame if any */
            peer_out = ngx_rtmp_append_shared_bufs(cscf, NULL, 
                    header_out ? header_out : in);
            ngx_rtmp_prepare_message(s, &ch, NULL, peer_out);
            pctx->msg_mask |= (1 << h->type);
            if (ngx_rtmp_send_message(ss, peer_out, prio) == NGX_OK
                    && header_out)
            {
                *(ngx_uint_t *)((u_char *)pctx + header_offset) 
                    = header_version;
            }
            ngx_rtmp_free_shared_chain(cscf, peer_out);
            continue;
        }

        /* send AVC/H264 header if newer header has arrived  */
        if (pheader_out && *(ngx_uint_t *)((u_char *)pctx + header_offset) 
                != header_version) 
        {
            ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                    "live: sending codec header");
            if (ngx_rtmp_send_message(ss, pheader_out, prio) == NGX_OK) {
                *(ngx_uint_t *)((u_char *)pctx + header_offset) 
                    = header_version;
            }
        }

        /* push buffered data */
        peer_prio = prio;
        if (ngx_rtmp_send_message(ss, out, peer_prio) != NGX_OK) {
            ++pctx->dropped;
            ++dropped_peers;
        }
    }
    ngx_rtmp_free_shared_chain(cscf, out);

    ngx_rtmp_update_bandwidth(&ctx->stream->bw_in, h->mlen);
    ngx_rtmp_update_bandwidth(&ctx->stream->bw_out, 
            h->mlen * (peers - dropped_peers));

    return NGX_OK;
}
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;                   //指向某一个peer对应的session
    ngx_rtmp_header_t               ch, lh, clh;
    ngx_int_t                       rc, mandatory, dummy_audio;
    ngx_uint_t                      prio;                 //Frame Type
    ngx_uint_t                      peers;                //记录订阅的用户数目
    ngx_uint_t                      meta_version;
    ngx_uint_t                      csidx;
    uint32_t                        delta;
    ngx_rtmp_live_chunk_stream_t   *cs;
    const char                     *type_s;               //类型

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

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

    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_error(NGX_LOG_INFO, s->connection->log, 0,
                       "live: name = %s, %s from non-publisher", 
                       ctx->stream->name, 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);
    }

    s->current_time = h->timestamp;

    peers = 0;            //记录所有的peers数目
    apkt = NULL;
    aapkt = NULL;
    acopkt = NULL;
    header = NULL;        //aac_header or avc_header
    coheader = NULL;      //avc_header or aac_header
    meta = NULL;          //meta data
    meta_version = 0;     //meta version
    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);
    // 如果是video,并且不允许video跟audio transmitted on the same RTMP 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;        /* message stream id */
    ch.csid = cs->csid;             /* chunk stream id,video = 0,audio = 1*/
    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;
            }

            //AAC sequence 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;
            }

            // AVC sequence 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) {
            continue;
        }

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

        /* send metadata */

        if (meta && meta_version != pctx->meta_version) {
            ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
                           "live: av name = %s send meta",
                           ctx->stream->name);

            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_error(NGX_LOG_INFO, ss->connection->log, 0,
                               "live: av name = %s, skipping header",
                               ctx->stream->name);
                continue;
            }

            if (lacf->wait_video && h->type == NGX_RTMP_MSG_AUDIO &&
                !pctx->cs[0].active)
            {
                ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
                               "live: av name = %s waiting for video",
                               ctx->stream->name);
                continue;
            }
            // 对于video,先从关键帧开始推送
            if (lacf->wait_key && prio != NGX_RTMP_VIDEO_KEY_FRAME &&
               (lacf->interleave || h->type == NGX_RTMP_MSG_VIDEO))
            {
                ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
                               "live: av name = %s skip non-key",
                               ctx->stream->name);
                continue;
            }

            dummy_audio = 0;
            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);
                    ngx_rtmp_prepare_message(s, &clh, NULL, aapkt);
                }
            }

            if (header || coheader) {

                /* send absolute codec header */

                ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
                               "live: av name = %s abs %s codec header timestamp=%uD",
                               ctx->stream->name, 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_error(NGX_LOG_INFO, ss->connection->log, 0,
                               "live: av name = %s mandatory packet failed",
                               ctx->stream->name);
                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);

    // ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
    //        "live: name = %s ,%s packet timestamp = %uD ,pees number = %d, bw_in_bytes = %uD, bw_out_bytes = %uD",
    //         ctx->stream->name ,type_s, h->timestamp,
    //         peers, ctx->stream->bw_in.bytes, ctx->stream->bw_out.bytes);

    return NGX_OK;
}
static void
ngx_rtmp_play_read_meta(ngx_rtmp_session_t *s)
{
    ngx_rtmp_play_ctx_t            *ctx;
    ssize_t                         n;
    ngx_rtmp_header_t               h;
    ngx_chain_t                    *out, in;
    ngx_buf_t                       in_buf;
    ngx_rtmp_core_srv_conf_t       *cscf;
    uint32_t                        size;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
    if (ctx == NULL) {
        return;
    }

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: read meta");
    
    /* read tag header */
    n = ngx_read_file(&ctx->file, ngx_rtmp_play_header, 
                      sizeof(ngx_rtmp_play_header), NGX_RTMP_PLAY_DATA_OFFSET);
    if (n != sizeof(ngx_rtmp_play_header)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: could not read metadata tag header");
        return;
    }

    if (ngx_rtmp_play_header[0] != NGX_RTMP_MSG_AMF_META) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                      "play: first tag is not metadata, giving up");
        return;
    }

    ngx_memzero(&h, sizeof(h));
    h.type = NGX_RTMP_MSG_AMF_META;
    h.msid = NGX_RTMP_LIVE_MSID;
    h.csid = NGX_RTMP_LIVE_CSID_META;
    size = 0;
    ngx_rtmp_rmemcpy(&size, ngx_rtmp_play_header + 1, 3);

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: metadata size=%D", size);

    if (size > sizeof(ngx_rtmp_play_buffer)) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: too big metadata");
        return;
    }

    /* read metadata */
    n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, 
                      size, sizeof(ngx_rtmp_play_header) + 
                      NGX_RTMP_PLAY_DATA_OFFSET);
    if (n != (ssize_t) size) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "play: could not read metadata");
        return;
    }

    /* prepare input chain */
    ngx_memzero(&in, sizeof(in));
    ngx_memzero(&in_buf, sizeof(in_buf));
    in.buf = &in_buf;
    in_buf.pos = ngx_rtmp_play_buffer;
    in_buf.last = ngx_rtmp_play_buffer + size;

    ngx_rtmp_play_init_index(s, &in);

    /* output chain */
    out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
    ngx_rtmp_prepare_message(s, &h, NULL, out);
    ngx_rtmp_send_message(s, out, 0);
    ngx_rtmp_free_shared_chain(cscf, out);
}
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, lh, clh;
    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;
    ngx_uint_t                      i;
#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;
    }

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

    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 (lacf->store_key > 0 && codec_ctx->store_key_size == 0)
        {
            codec_ctx->store_key_size = lacf->store_key;
            codec_ctx->store_key_index = -1;
            if (codec_ctx->storeframes == NULL)
            {
              codec_ctx->storeframes = (ngx_chain_t **)ngx_pcalloc(cscf->pool, sizeof(ngx_chain_t *) * lacf->store_key);  
              codec_ctx->storeHeader = (ngx_rtmp_header_t *)ngx_pcalloc(cscf->pool, sizeof(ngx_rtmp_header_t) * lacf->store_key);            
            }
        }

        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 (lacf->store_key > 0 && codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264 &&
                prio == NGX_RTMP_VIDEO_KEY_FRAME && !mandatory)
            {
                if (codec_ctx->store_key > 0){
                    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,
                                   "live: store key -> keyframes received  last timestamp=%s current timestamp=%uD",
                                   codec_ctx->storeHeader[codec_ctx->store_key-1].timestamp,
                                   ch.timestamp);
                }
                for (i=0;i<codec_ctx->store_key_size;i++)
                {
                    if (codec_ctx->storeframes[i] != NULL){
                        ngx_rtmp_free_shared_chain(cscf, codec_ctx->storeframes[i]);
                        codec_ctx->storeframes[i] = NULL;
                    }
                }
                codec_ctx->store_key_index++;
                codec_ctx->store_key = 0;
            }
        }

        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) {
            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;
            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);
                    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);
                        if (lacf->store_key > 0)
                        {
                            lh.timestamp = 0;
                        }
                        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);
                        if (lacf->store_key > 0)
                        {
                            clh.timestamp = 0;
                        }
                        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);
                    if (lacf->store_key > 0)
                    {
                        ch.timestamp = 0;
                    }
                    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);
                }

                if (lacf->store_key == 0 || codec_ctx->store_key == 0 || cs->store_key_sent)
                  continue;
            }
        }

        if (lacf->store_key > 0 && codec_ctx->store_key && !cs->store_key_sent)
        {
            rc = NGX_OK;

            if (cs->store_key_sent_id != 0 && cs->store_key_index != codec_ctx->store_key_index)
            {
                // This frame is the new keyframes, direct sent the frames
                rc = NGX_AGAIN;
                ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
                               "live: ignore stored frames, sent the new key frames.");
            }

            if (rc == NGX_OK)
            {
                ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
                               "live: send stored frames packet start=%uD",
                               cs->store_key_sent_id);

                cs->store_key_index = codec_ctx->store_key_index;
                for (i=cs->store_key_sent_id;i<codec_ctx->store_key;i++)
                {
                    if (codec_ctx->storeframes[i]==NULL) continue;
                    rc = ngx_rtmp_send_message(ss, codec_ctx->storeframes[i], 0);
                    if (rc != NGX_OK) {
                        // retry at next packet cycle
                        cs->store_key_sent_id = i;
                        ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
                                       "live: \033[31msend sub key frames index=%uD timestamp=%uD failed\033[0m",
                                       i, codec_ctx->storeHeader[i].timestamp);
                        break;
                    }
                }
                ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
                               "live: send stored %uD frames to client timestamp=%uD - %uD",
                               codec_ctx->store_key, codec_ctx->storeHeader[0].timestamp, 
                               codec_ctx->storeHeader[i>0?i-1:0].timestamp);
            }else
            {
                rc = NGX_OK;
            }

            if (rc != NGX_OK) {
                continue;
            }else
            {
                cs->store_key_sent = 1;
            }
        }

        /* 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 (lacf->store_key > 0 && codec_ctx && !mandatory && rpkt)
    {
        if (codec_ctx->store_key > 0 || 
          (h->type == NGX_RTMP_MSG_VIDEO && codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264 && prio == NGX_RTMP_VIDEO_KEY_FRAME))
        {
            if (h->type == NGX_RTMP_MSG_AUDIO || h->type == NGX_RTMP_MSG_VIDEO) {
                ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                             "live: store frames index=%uD timestamp=%uD", codec_ctx->store_key,
                             codec_ctx->storeframes[codec_ctx->store_key]);

                codec_ctx->storeframes[codec_ctx->store_key] = ngx_rtmp_append_shared_bufs(cscf, codec_ctx->storeframes[codec_ctx->store_key], in);
                ngx_rtmp_prepare_message(s, &ch, NULL, codec_ctx->storeframes[codec_ctx->store_key]);
                
                codec_ctx->storeHeader[codec_ctx->store_key] = ch;
            }
            // && prio == NGX_RTMP_VIDEO_KEY_FRAME
            if (codec_ctx->store_key < (ngx_uint_t)lacf->store_key - 1)
            {
                codec_ctx->store_key++;
            }else
            {
                // Overflow
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                             "live: store frames overflow");

                codec_ctx->store_key = 0; // release code will execute at next keyframse
            }
        }
    }

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