static ngx_int_t ngx_rtmp_hls_update_playlist(ngx_rtmp_session_t *s) { static u_char buffer[1024]; int fd; u_char *p; ngx_rtmp_hls_ctx_t *ctx; ssize_t n; ngx_int_t ffrag; ngx_rtmp_hls_app_conf_t *hacf; hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); fd = ngx_open_file(ctx->playlist_bak.data, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); if (fd == NGX_INVALID_FILE) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "hls: open failed: '%V'", &ctx->playlist_bak); return NGX_ERROR; } ffrag = ctx->frag - hacf->nfrags; if (ffrag < 1) { ffrag = 1; } p = ngx_snprintf(buffer, sizeof(buffer), "#EXTM3U\r\n" "#EXT-X-TARGETDURATION:%i\r\n" "#EXT-X-MEDIA-SEQUENCE:%i\r\n\r\n", /*TODO: float*/(ngx_int_t)(hacf->fraglen / 1000), ffrag); n = write(fd, buffer, p - buffer); if (n < 0) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "hls: write failed: '%V'", &ctx->playlist_bak); ngx_close_file(fd); return NGX_ERROR; } for (; ffrag < ctx->frag; ++ffrag) { p = ngx_snprintf(buffer, sizeof(buffer), "#EXTINF:%i,\r\n" "%V-%i.ts\r\n", /*TODO:float*/(ngx_int_t)(hacf->fraglen / 1000), &ctx->name, ffrag); n = write(fd, buffer, p - buffer); if (n < 0) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "hls: write failed: '%V'", &ctx->playlist_bak); ngx_close_file(fd); return NGX_ERROR; } } ngx_close_file(fd); if (ngx_rename_file(ctx->playlist_bak.data, ctx->playlist.data)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "hls: rename failed: '%V'->'%V'", &ctx->playlist_bak, &ctx->playlist); return NGX_ERROR; } return NGX_OK; }
static void ngx_rtmp_relay_close(ngx_rtmp_session_t *s) { 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) { return; } if (s->static_relay) { ngx_add_timer(ctx->static_evt, racf->pull_reconnect); return; } if (ctx->publish == NULL) { return; } /* 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 (s->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->session->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; return; } /* 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; } }
static ngx_int_t ngx_rtmp_relay_send_connect(ngx_rtmp_session_t *s) { static double trans = NGX_RTMP_RELAY_CONNECT_TRANS; static double acodecs = 3575; static double vcodecs = 252; static ngx_rtmp_amf_elt_t out_cmd[] = { { NGX_RTMP_AMF_STRING, ngx_string("app"), NULL, 0 }, /* <-- fill */ { NGX_RTMP_AMF_STRING, ngx_string("tcUrl"), NULL, 0 }, /* <-- fill */ { NGX_RTMP_AMF_STRING, ngx_string("pageUrl"), NULL, 0 }, /* <-- fill */ { NGX_RTMP_AMF_STRING, ngx_string("swfUrl"), NULL, 0 }, /* <-- fill */ { NGX_RTMP_AMF_STRING, ngx_string("flashVer"), NULL, 0 }, /* <-- fill */ { NGX_RTMP_AMF_NUMBER, ngx_string("audioCodecs"), &acodecs, 0 }, { NGX_RTMP_AMF_NUMBER, ngx_string("videoCodecs"), &vcodecs, 0 } }; static ngx_rtmp_amf_elt_t out_elts[] = { { NGX_RTMP_AMF_STRING, ngx_null_string, "connect", 0 }, { NGX_RTMP_AMF_NUMBER, ngx_null_string, &trans, 0 }, { NGX_RTMP_AMF_OBJECT, ngx_null_string, out_cmd, sizeof(out_cmd) } }; ngx_rtmp_core_app_conf_t *cacf; ngx_rtmp_core_srv_conf_t *cscf; ngx_rtmp_relay_ctx_t *ctx; ngx_rtmp_header_t h; size_t len, url_len; u_char *p, *url_end; cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); if (cacf == NULL || ctx == NULL) { return NGX_ERROR; } /* app */ if (ctx->app.len) { out_cmd[0].data = ctx->app.data; out_cmd[0].len = ctx->app.len; } else { out_cmd[0].data = cacf->name.data; out_cmd[0].len = cacf->name.len; } /* tcUrl */ if (ctx->tc_url.len) { out_cmd[1].data = ctx->tc_url.data; out_cmd[1].len = ctx->tc_url.len; } else { len = sizeof("rtmp://") - 1 + ctx->url.len + sizeof("/") - 1 + ctx->app.len; p = ngx_palloc(s->connection->pool, len); if (p == NULL) { return NGX_ERROR; } out_cmd[1].data = p; p = ngx_cpymem(p, "rtmp://", sizeof("rtmp://") - 1); url_len = ctx->url.len; url_end = ngx_strlchr(ctx->url.data, ctx->url.data + ctx->url.len, '/'); if (url_end) { url_len = (size_t) (url_end - ctx->url.data); } p = ngx_cpymem(p, ctx->url.data, url_len); *p++ = '/'; p = ngx_cpymem(p, ctx->app.data, ctx->app.len); out_cmd[1].len = p - (u_char *)out_cmd[1].data; } /* pageUrl */ out_cmd[2].data = ctx->page_url.data; out_cmd[2].len = ctx->page_url.len; /* swfUrl */ out_cmd[3].data = ctx->swf_url.data; out_cmd[3].len = ctx->swf_url.len; /* flashVer */ if (ctx->flash_ver.len) { out_cmd[4].data = ctx->flash_ver.data; out_cmd[4].len = ctx->flash_ver.len; } else { out_cmd[4].data = NGX_RTMP_RELAY_FLASHVER; out_cmd[4].len = sizeof(NGX_RTMP_RELAY_FLASHVER) - 1; } ngx_memzero(&h, sizeof(h)); h.csid = NGX_RTMP_RELAY_CSID_AMF_INI; h.type = NGX_RTMP_MSG_AMF_CMD; return ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != NGX_OK || ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK || ngx_rtmp_send_amf(s, &h, out_elts, sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK ? NGX_ERROR : NGX_OK; }
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; ctx->cs[2].active = 0; ctx->cs[2].dropped = 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; 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_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { ngx_rtmp_play_main_conf_t *pmcf; ngx_rtmp_play_app_conf_t *pacf; ngx_rtmp_play_ctx_t *ctx; u_char *p; ngx_rtmp_play_fmt_t *fmt, **pfmt; ngx_str_t *pfx, *sfx; ngx_str_t name; ngx_uint_t n; static ngx_str_t nosfx; static u_char path[NGX_MAX_PATH]; pmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_play_module); pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); if (pacf == NULL || (pacf->root.len == 0 && pacf->url == NULL)) { goto next; } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: play name='%s' timestamp=%i", v->name, (ngx_int_t) v->start); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); if (ctx && ctx->file.fd != NGX_INVALID_FILE) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: already playing"); goto next; } /* check for double-dot in v->name; * we should not move out of play directory */ for (p = v->name; *p; ++p) { if (ngx_path_separator(p[0]) && p[1] == '.' && p[2] == '.' && ngx_path_separator(p[3])) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: bad name '%s'", v->name); return NGX_ERROR; } } if (ctx == NULL) { ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_play_ctx_t)); ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_play_module); } ngx_memzero(ctx, sizeof(*ctx)); ctx->file.log = s->connection->log; name.len = ngx_strlen(v->name); name.data = v->name; pfmt = pmcf->fmts.elts; for (n = 0; n < pmcf->fmts.nelts; ++n, ++pfmt) { fmt = *pfmt; pfx = &fmt->pfx; sfx = &fmt->sfx; if (pfx->len == 0 && ctx->fmt == NULL) { ctx->fmt = fmt; } if (pfx->len && name.len >= pfx->len && ngx_strncasecmp(pfx->data, name.data, pfx->len) == 0) { name.data += pfx->len; name.len -= pfx->len; ctx->fmt = fmt; break; } if (name.len >= sfx->len && ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len, sfx->len) == 0) { ctx->fmt = fmt; } } if (ctx->fmt == NULL) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: fmt not found"); goto next; } ctx->file.fd = NGX_INVALID_FILE; ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "play %s: %V", pacf->url ? "remote" : "local", &ctx->fmt->name); sfx = &ctx->fmt->sfx; if (name.len >= sfx->len && ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len, sfx->len) == 0) { sfx = &nosfx; } /* remote? */ if (pacf->url) { ctx->name.data = ngx_palloc(s->connection->pool, name.len + sfx->len); if (ctx->name.data == NULL) { return NGX_ERROR; } p = ngx_sprintf(ctx->name.data, "%V%V", &name, sfx); *p = 0; ctx->name.len = p - ctx->name.data; return ngx_rtmp_play_open_remote(s, v); } /* open local */ p = ngx_snprintf(path, sizeof(path), "%V/%V%V", &pacf->root, &name, sfx); *p = 0; ctx->file.fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS); if (ctx->file.fd == NGX_INVALID_FILE) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "play: error opening file '%s'", path); ngx_rtmp_send_status(s, "NetStream.Play.StreamNotFound", "error", "Video on demand stream not found"); return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: opened local file '%s'", path); if (ngx_rtmp_play_open(s, v->start) != NGX_OK) { return NGX_ERROR; } next: return next_play(s, v); }
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 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; // ngx_rtmp_session_t *ss; // ngx_rtmp_live_ctx_t *pctx; 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_error(NGX_LOG_INFO, s->connection->log, 0, "live: already joined '%s'", name); 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_error(NGX_LOG_INFO, s->connection->log, 0, "live: join '%s'", name); // 获取该name对应的stream,通常在publish的时候创建,在播放的时候直接获取就即可 stream = ngx_rtmp_live_get_stream(s, name, publisher || lacf->idle_streams); // 播放端 在拉流的时候,流还没有建立,则结束session if (stream == NULL || !(publisher || (*stream)->publishing || lacf->idle_streams)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "live: name = %s stream not found", name); ngx_rtmp_send_status(s, "NetStream.Play.StreamNotFound", "error", "No such stream"); ngx_rtmp_finalize_session(s); return; } // 上传端在推流的时候,发现流名称已经被占用,则发送status消息 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; } //这个stream对应的是publish时创建的流 ctx->stream = *stream; ctx->publishing = publisher; // 下面两个操作是将新创建的ctx插入stream的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; // 作为一个subscriber,会执行 if (!ctx->publishing && ctx->stream->active) { ngx_rtmp_live_start(s); } }
static ngx_int_t ngx_rtmp_exec_run(ngx_rtmp_session_t *s, size_t n) { #ifndef NGX_WIN32 ngx_rtmp_exec_app_conf_t *eacf; ngx_rtmp_exec_ctx_t *ctx; int pid; int pipefd[2]; int ret; ngx_rtmp_exec_conf_t *ec; ngx_rtmp_exec_t *e; ngx_str_t *arg; char **args; eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); ec = (ngx_rtmp_exec_conf_t *)eacf->execs.elts + n; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); e = ctx->execs + n; ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "exec: starting child '%V'", &ec->cmd); if (pipe(pipefd) == -1) { ngx_log_error(NGX_LOG_INFO, s->connection->log, ngx_errno, "exec: pipe failed"); return NGX_ERROR; } /* make pipe write end survive through exec */ ret = fcntl(pipefd[1], F_GETFD); if (ret != -1) { ret &= ~FD_CLOEXEC; ret = fcntl(pipefd[1], F_SETFD, ret); } if (ret == -1) { close(pipefd[0]); close(pipefd[1]); ngx_log_error(NGX_LOG_INFO, s->connection->log, ngx_errno, "exec: fcntl failed"); return NGX_ERROR; } pid = fork(); switch (pid) { case -1: close(pipefd[0]); close(pipefd[1]); ngx_log_error(NGX_LOG_INFO, s->connection->log, ngx_errno, "exec: fork failed"); return NGX_ERROR; case 0: /* child */ args = malloc((ec->args.nelts + 2) * sizeof(char *)); if (args == NULL) { exit(1); } arg = ec->args.elts; args[0] = (char *)ec->cmd.data; for (n = 0; n < ec->args.nelts; ++n, ++arg) { args[n + 1] = ngx_rtmp_exec_prepare_arg(s, arg); } args[n + 1] = NULL; if (execv((char *)ec->cmd.data, args) == -1) { exit(1); } break; default: /* parent */ close(pipefd[1]); e->session = s; e->index = n; e->active = 1; e->pid = pid; e->pipefd = pipefd[0]; e->dummy_conn.fd = e->pipefd; e->dummy_conn.data = e; e->dummy_conn.read = &e->read_evt; e->dummy_conn.write = &e->write_evt; e->read_evt.data = &e->dummy_conn; e->write_evt.data = &e->dummy_conn; e->read_evt.log = s->connection->log; e->read_evt.handler = ngx_rtmp_exec_child_dead; if (ngx_add_event(&e->read_evt, NGX_READ_EVENT, 0) != NGX_OK) { ngx_log_error(NGX_LOG_INFO, s->connection->log, ngx_errno, "exec: failed to add child control event"); } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "exec: child '%V' started pid=%ui", &ec->cmd, (ngx_uint_t)pid); break; } #endif /* NGX_WIN32 */ return NGX_OK; }
static ngx_int_t ngx_rtmp_exec_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) { ngx_rtmp_exec_main_conf_t *emcf; ngx_rtmp_exec_app_conf_t *eacf; ngx_rtmp_exec_t *e; ngx_rtmp_exec_conf_t *ec; ngx_rtmp_exec_ctx_t *ctx; size_t n; emcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_exec_module); eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); if (eacf == NULL || eacf->confs.nelts == 0) { goto next; } if (s->auto_pushed) { goto next; } ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); if (ctx == NULL) { ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_exec_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_exec_module); if (ngx_array_init(&ctx->execs, s->connection->pool, eacf->confs.nelts, sizeof(ngx_rtmp_exec_t)) != NGX_OK) { return NGX_ERROR; } e = ngx_array_push_n(&ctx->execs, eacf->confs.nelts); if (e == NULL) { return NGX_ERROR; } ec = eacf->confs.elts; for (n = 0; n < eacf->confs.nelts; ++n, ++e, ++ec) { ngx_memzero(e, sizeof(*e)); e->conf = ec; e->log = s->connection->log; e->session = s; e->kill_signal = emcf->kill_signal; e->respawn_timeout = (eacf->respawn ? emcf->respawn_timeout : NGX_CONF_UNSET_MSEC); } } ngx_memcpy(ctx->name, v->name, NGX_RTMP_MAX_NAME); ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "exec: run %uz command(s)", ctx->execs.nelts); e = ctx->execs.elts; for (n = 0; n < ctx->execs.nelts; ++n, ++e) { ngx_rtmp_exec_run(e); } next: return next_publish(s, v); }
static ngx_int_t ngx_live_relay_simple_relay(ngx_rtmp_session_t *s, ngx_live_relay_t *relay, unsigned publishing) { ngx_rtmp_session_t *rs; ngx_live_relay_ctx_t *ctx, *pctx; ngx_live_relay_app_conf_t *lracf; ngx_live_relay_simple_ctx_t *sctx; rs = ngx_rtmp_create_relay_session(s, &ngx_live_relay_simple_module); if (rs == NULL) { ngx_log_error(NGX_LOG_ERR, s->log, 0, "relay simple, create relay session failed"); return NGX_DECLINED; } rs->publishing = publishing; rs->live_stream = s->live_stream; ngx_live_create_ctx(rs, publishing); sctx = ngx_pcalloc(rs->pool, sizeof(ngx_live_relay_simple_ctx_t)); if (sctx == NULL) { ngx_log_error(NGX_LOG_ERR, rs->log, 0, "relay simple, create simple relay ctx failed"); ngx_rtmp_finalize_session(rs); return NGX_OK; } ngx_rtmp_set_ctx(rs, sctx, ngx_live_relay_simple_module); sctx->relay = relay; 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_simple_handler; if (s->publishing != rs->publishing) { ngx_post_event(&ctx->reconnect, &ngx_posted_events); return NGX_OK; } // normal publisher close, need to trigger pull if (s->publishing && !s->relay) { 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; }
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; 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); /*TODO: add push reconnect */ /* if (ctx->relay) { ngx_rtmp_relay_push(ctx-publish->session, &ctx->publish->name, &target); }*/ #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) { 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); 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; racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module); 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); }
static ngx_int_t ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) { ngx_rtmp_hls_app_conf_t *hacf; ngx_rtmp_hls_ctx_t *ctx; ngx_rtmp_codec_ctx_t *codec_ctx; AVPacket packet; u_char *p; uint8_t fmt, ftype, htype, llen; uint32_t len, rlen; ngx_buf_t out; static u_char buffer[NGX_RTMP_HLS_BUFSIZE]; hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); if (hacf == NULL || !hacf->hls || ctx == NULL || codec_ctx == NULL || h->mlen < 1) { return NGX_OK; } /* Only H264 is supported */ if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) { return NGX_OK; } p = in->buf->pos; if (ngx_rtmp_hls_copy(s, &fmt, &p, 1, &in) != NGX_OK) { return NGX_ERROR; } /* 1: keyframe (IDR) * 2: inter frame * 3: disposable inter frame */ ftype = (fmt & 0xf0) >> 4; /* H264 HDR/PICT */ if (ngx_rtmp_hls_copy(s, &htype, &p, 1, &in) != NGX_OK) { return NGX_ERROR; } /* proceed only with PICT */ if (htype != 1) { return NGX_OK; } /* 3 bytes: decoder delay */ if (ngx_rtmp_hls_copy(s, NULL, &p, 3, &in) != NGX_OK) { return NGX_ERROR; } out.pos = buffer; out.last = buffer + sizeof(buffer) - FF_INPUT_BUFFER_PADDING_SIZE; /* keyframe? */ if (ftype == 1) { if (ngx_current_msec - ctx->frag_start > hacf->fraglen) { ngx_rtmp_hls_restart(s); } /* Prepend IDR frame with H264 header for random seeks */ if (ngx_rtmp_hls_append_avc_header(s, &out) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: error appenging H264 header"); } } if (!ctx->opened || !ctx->video) { return NGX_OK; } if (ctx->nal_bytes == -1) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: nal length size is unknown, " "waiting for IDR to parse header"); return NGX_OK; } ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: parsing NALs"); while (in) { llen = ctx->nal_bytes; if (ngx_rtmp_hls_copy(s, &rlen, &p, llen, &in) != NGX_OK) { return NGX_OK; } len = 0; ngx_rtmp_rmemcpy(&len, &rlen, llen); ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: NAL type=%i llen=%i len=%uD unit_type=%i", (ngx_int_t)ftype, (ngx_int_t)llen, len, (ngx_int_t)(*p & 0x1f)); /* AnnexB prefix */ if (out.last - out.pos < 4) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: not enough buffer for AnnexB prefix"); return NGX_OK; } /* first AnnexB prefix is long (4 bytes) */ if (out.pos == buffer) { *out.pos++ = 0; } *out.pos++ = 0; *out.pos++ = 0; *out.pos++ = 1; /* NAL body */ if (out.last - out.pos < (ngx_int_t) len) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: not enough buffer for NAL"); return NGX_OK; } if (ngx_rtmp_hls_copy(s, out.pos, &p, len, &in) != NGX_OK) { return NGX_ERROR; } out.pos += len; } av_init_packet(&packet); packet.dts = h->timestamp * 90; packet.pts = packet.dts; packet.stream_index = ctx->out_vstream; /* if (ftype == 1) { packet.flags |= AV_PKT_FLAG_KEY; }*/ packet.data = buffer; packet.size = out.pos - buffer; if (av_interleaved_write_frame(ctx->out_format, &packet) < 0) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: av_interleaved_write_frame failed"); } return NGX_OK; }
static ngx_int_t ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) { ngx_rtmp_hls_app_conf_t *hacf; ngx_rtmp_hls_ctx_t *ctx; size_t len; u_char *p; hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); if (hacf == NULL || !hacf->hls || hacf->path.len == 0) { goto next; } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: publish: name='%s' type='%s'", v->name, v->type); /* create context */ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); if (ctx == NULL) { ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_hls_ctx_t)); ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_hls_module); } ngx_memzero(ctx, sizeof(ngx_rtmp_hls_ctx_t)); /* init names & paths */ len = ngx_strlen(v->name); ctx->name.len = len + (ngx_uint_t)ngx_escape_uri(NULL, v->name, len, NGX_ESCAPE_URI_COMPONENT); ctx->name.data = ngx_palloc(s->connection->pool, ctx->name.len); ngx_escape_uri(ctx->name.data, v->name, len, NGX_ESCAPE_URI_COMPONENT); ctx->playlist.data = ngx_palloc(s->connection->pool, hacf->path.len + 1 + ctx->name.len + sizeof(".m3u8")); p = ngx_cpymem(ctx->playlist.data, hacf->path.data, hacf->path.len); if (p[-1] != '/') { *p++ = '/'; } p = ngx_cpymem(p, ctx->name.data, ctx->name.len); /* ctx->stream_path holds initial part of stream file path * however the space for the whole stream path * is allocated */ ctx->stream.len = p - ctx->playlist.data; ctx->stream.data = ngx_palloc(s->connection->pool, ctx->stream.len + 1 + NGX_OFF_T_LEN + sizeof(".ts")); ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len); /* playlist path */ p = ngx_cpymem(p, ".m3u8", sizeof(".m3u8") - 1); ctx->playlist.len = p - ctx->playlist.data; *p = 0; /* playlist bak (new playlist) path */ ctx->playlist_bak.data = ngx_palloc(s->connection->pool, ctx->playlist.len + sizeof(".bak")); p = ngx_cpymem(ctx->playlist_bak.data, ctx->playlist.data, ctx->playlist.len); p = ngx_cpymem(p, ".bak", sizeof(".bak") - 1); ctx->playlist_bak.len = p - ctx->playlist_bak.data; *p = 0; ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: playlist='%V' playlist_bak='%V' stream_pattern='%V'", &ctx->playlist, &ctx->playlist_bak, &ctx->stream); /* schedule restart event */ ctx->publishing = 1; ctx->frag_start = ngx_current_msec - hacf->fraglen - 1; next: return next_publish(s, v); }
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); stream = ngx_rtmp_live_get_stream(s, name, publisher || lacf->idle_streams || s->relay_type != NGX_NONE_RELAY); if (stream == NULL || !(publisher || (*stream)->publishing || lacf->idle_streams || s->relay_type != NGX_NONE_RELAY )) { 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: '%V/%s', already publishing", &s->app, name); ngx_rtmp_send_status(s, "NetStream.Publish.BadName", "error", "Already publishing"); return; } (*stream)->publishing = 1; //del checking timer ngx_del_timer(&(*stream)->check_evt); } else { ngx_str_t strname; strname.data = name; strname.len = ngx_strlen(name); ngx_rtmp_relay_player_new(s, &strname); } ctx->stream = *stream; ctx->publishing = publisher; ctx->next = (*stream)->ctx; ctx->hls = s->hls; ngx_memcpy(s->name, name, ngx_strlen(name)); s->name[ngx_strlen(name)] = 0; (*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 && !ctx->hls) { ngx_rtmp_live_start(s); } if (!publisher && !(*stream)->publishing) { ngx_str_t strname; strname.data = name; strname.len = ngx_strlen(name); if (ngx_rtmp_relay_relaying(s, &strname) == NGX_ERROR) { ngx_rtmp_live_checking_publish(s, *stream); } } }
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_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_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; ngx_flag_t error; 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 (s->connection == NULL || s->connection->destroyed) { return; } if (ctx->protocol == NGX_RTMP_PROTOCOL_HTTP) { r = s->data; if (r == NULL) { 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) { ngx_rtmp_finalize_session(s); 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) { if (s->connection == NULL || s->connection->destroyed) { return; } 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; error = 0; 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) { error = 1; goto next; } } if (apkt && handler->send_message_pt(s, apkt, 0) != NGX_OK) { goto next; } 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) { error = 1; goto next; } if (handler->send_message_pt(s, pkt, gf->prio) != NGX_OK) { ++pub_ctx->ndropped; cs->dropped += delta; goto next; } 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; next: if (pkt) { handler->free_message_pt(s, pkt); pkt = NULL; } if (apkt) { handler->free_message_pt(s, apkt); apkt = NULL; } if (error) { ngx_rtmp_finalize_session(s); return; } } } }
ngx_int_t ngx_rtmp_netcall_create(ngx_rtmp_session_t *s, ngx_rtmp_netcall_init_t *ci) { ngx_rtmp_netcall_ctx_t *ctx; ngx_peer_connection_t *pc; ngx_rtmp_netcall_session_t *cs; ngx_rtmp_netcall_app_conf_t *cacf; ngx_connection_t *c, *cc; ngx_pool_t *pool; ngx_int_t rc; pool = NULL; c = s->connection; cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_netcall_module); if (cacf == NULL) { goto error; } /* get module context */ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module); if (ctx == NULL) { ctx = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_netcall_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_netcall_module); } /* Create netcall pool, connection, session. * Note we use shared (app-wide) log because * s->connection->log might be unavailable * in detached netcall when it's being closed */ pool = ngx_create_pool(4096, cacf->log); if (pool == NULL) { goto error; } pc = ngx_pcalloc(pool, sizeof(ngx_peer_connection_t)); if (pc == NULL) { goto error; } cs = ngx_pcalloc(pool, sizeof(ngx_rtmp_netcall_session_t)); if (cs == NULL) { goto error; } /* copy arg to connection pool */ if (ci->argsize) { cs->arg = ngx_pcalloc(pool, ci->argsize); if (cs->arg == NULL) { goto error; } ngx_memcpy(cs->arg, ci->arg, ci->argsize); } cs->timeout = cacf->timeout; cs->bufsize = cacf->bufsize; cs->url = ci->url; cs->session = s; cs->filter = ci->filter; cs->sink = ci->sink; cs->handle = ci->handle; if (cs->handle == NULL) { cs->detached = 1; } pc->log = cacf->log; pc->get = ngx_rtmp_netcall_get_peer; pc->free = ngx_rtmp_netcall_free_peer; pc->data = cs; /* connect */ rc = ngx_event_connect_peer(pc); if (rc != NGX_OK && rc != NGX_AGAIN ) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "netcall: connection failed"); goto error; } cc = pc->connection; cc->data = cs; cc->pool = pool; cs->pc = pc; cs->out = ci->create(s, ci->arg, pool); if (cs->out == NULL) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "netcall: creation failed"); ngx_close_connection(pc->connection); goto error; } cc->write->handler = ngx_rtmp_netcall_send; cc->read->handler = ngx_rtmp_netcall_recv; if (!cs->detached) { cs->next = ctx->cs; ctx->cs = cs; } ngx_rtmp_netcall_send(cc->write); return c->destroyed ? NGX_ERROR : NGX_OK; error: if (pool) { ngx_destroy_pool(pool); } return NGX_ERROR; }
static ngx_chain_t * ngx_rtmp_notify_play_create(ngx_rtmp_session_t *s, void *arg, ngx_pool_t *pool) { ngx_rtmp_play_t *v = arg; ngx_rtmp_notify_app_conf_t *nacf; ngx_chain_t *hl, *cl, *pl; ngx_buf_t *b; ngx_str_t *addr_text; size_t name_len, args_len; nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); /* common variables */ cl = ngx_rtmp_netcall_http_format_session(s, pool); if (cl == NULL) { return NULL; } /* play variables */ pl = ngx_alloc_chain_link(pool); if (pl == NULL) { return NULL; } name_len = ngx_strlen(v->name); args_len = ngx_strlen(v->args); addr_text = &s->connection->addr_text; b = ngx_create_temp_buf(pool, sizeof("&call=play") + sizeof("&addr=") + addr_text->len + sizeof("&name=") + name_len * 3 + sizeof("&start=&duration=&reset=") + NGX_OFF_T_LEN * 3 + 1 + args_len); if (b == NULL) { return NULL; } pl->buf = b; b->last = ngx_cpymem(b->last, (u_char*)"&call=play", sizeof("&call=play") - 1); b->last = ngx_cpymem(b->last, (u_char*)"&addr=", sizeof("&addr=") -1); b->last = (u_char*)ngx_escape_uri(b->last, addr_text->data, addr_text->len, 0); b->last = ngx_cpymem(b->last, (u_char*)"&name=", sizeof("&name=") - 1); b->last = (u_char*)ngx_escape_uri(b->last, v->name, name_len, 0); b->last = ngx_snprintf(b->last, b->end - b->last, "&start=%uD&duration=%uD&reset=%d", (uint32_t)v->start, (uint32_t)v->duration, v->reset & 1); if (args_len) { *b->last++ = '&'; b->last = (u_char *)ngx_cpymem(b->last, v->args, args_len); } /* HTTP header */ hl = ngx_rtmp_netcall_http_format_header(nacf->play_url, pool, cl->buf->last - cl->buf->pos + (pl->buf->last - pl->buf->pos), &ngx_rtmp_netcall_content_type_urlencoded); if (hl == NULL) { return NULL; } hl->next = cl; cl->next = pl; pl->next = NULL; return hl; }
static ngx_int_t ngx_rtmp_live_on_fi(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) { ngx_rtmp_live_app_conf_t *lacf; ngx_int_t res; static struct { u_char time[NGX_TIME_T_LEN + 1]; u_char date[NGX_TIME_T_LEN + 1]; } v; static ngx_rtmp_amf_elt_t in_dt_elts[] = { { NGX_RTMP_AMF_STRING, ngx_string("sd"), &v.date, sizeof(v.date) }, { NGX_RTMP_AMF_STRING, ngx_string("st"), &v.time, sizeof(v.time) }, }; static ngx_rtmp_amf_elt_t in_elts[] = { { NGX_RTMP_AMF_MIXED_ARRAY, ngx_null_string, in_dt_elts, sizeof(in_dt_elts) }, }; static ngx_rtmp_amf_elt_t out_dt_elts[] = { { NGX_RTMP_AMF_STRING, ngx_string("sd"), NULL, 0 }, { NGX_RTMP_AMF_STRING, ngx_string("st"), NULL, 0 }, }; static ngx_rtmp_amf_elt_t out_elts[] = { { NGX_RTMP_AMF_STRING, ngx_null_string, "onFi", 0 }, { NGX_RTMP_AMF_MIXED_ARRAY, ngx_null_string, out_dt_elts, sizeof(out_dt_elts) }, }; lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); if (lacf == NULL) { ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, "live: Fi - no live config!"); return NGX_ERROR; } if (!lacf->live || in == NULL || in->buf == NULL) { ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, "live: Fi - no live or no buffer!"); return NGX_OK; } ngx_memzero(&v, sizeof(v)); res = ngx_rtmp_receive_amf(s, in, in_elts, sizeof(in_elts) / sizeof(in_elts[0])); if (res == NGX_OK) { ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, "live: onFi: date='%s', time='%s'", v.date, v.time); out_dt_elts[0].data = v.date; out_dt_elts[1].data = v.time; // Pass through datetime from publisher return ngx_rtmp_live_data(s, h, in, out_elts, sizeof(out_elts) / sizeof(out_elts[0])); } else { // Send our server datetime return ngx_rtmp_send_fi(s); } }
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 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); 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->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; ctx->cs[2].csid = NGX_RTMP_CSID_AMF; if (!ctx->publishing && ctx->stream->active) { ngx_rtmp_live_start(s); } }
static ngx_int_t ngx_rtmp_play_open_remote(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { ngx_rtmp_play_app_conf_t *pacf; ngx_rtmp_play_ctx_t *ctx; ngx_rtmp_play_entry_t *pe; ngx_rtmp_netcall_init_t ci; u_char *path; ngx_err_t err; static ngx_uint_t file_id; pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); ctx->ncrs = 0; ctx->nheader = 0; ctx->nbody = 0; for ( ;; ) { ctx->file_id = ++file_id; /* no zero after overflow */ if (ctx->file_id == 0) { continue; } path = ngx_rtmp_play_get_local_file_path(s); ctx->file.fd = ngx_open_tempfile(path, pacf->local_path.len, 0); if (pacf->local_path.len == 0) { ctx->file_id = 0; } if (ctx->file.fd != NGX_INVALID_FILE) { break; } err = ngx_errno; if (err != NGX_EEXIST) { ctx->file_id = 0; ngx_log_error(NGX_LOG_INFO, s->connection->log, err, "play: failed to create temp file"); return NGX_ERROR; } } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: temp file '%s' file_id=%ui", path, ctx->file_id); pe = ngx_rtmp_play_get_current_entry(s); ngx_memzero(&ci, sizeof(ci)); ci.url = pe->url; ci.create = ngx_rtmp_play_remote_create; ci.sink = ngx_rtmp_play_remote_sink; ci.handle = ngx_rtmp_play_remote_handle; ci.arg = v; ci.argsize = sizeof(*v); return ngx_rtmp_netcall_create(s, &ci); }
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; #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 (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) { 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); 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 ngx_int_t ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { ngx_rtmp_play_main_conf_t *pmcf; ngx_rtmp_play_app_conf_t *pacf; ngx_rtmp_play_ctx_t *ctx; u_char *p; ngx_rtmp_play_fmt_t *fmt, **pfmt; ngx_str_t *pfx, *sfx; ngx_str_t name; ngx_uint_t n; pmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_play_module); pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); if (pacf == NULL || pacf->entries.nelts == 0) { goto next; } ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "play: play name='%s' timestamp=%i", v->name, (ngx_int_t) v->start); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); if (ctx && ctx->file.fd != NGX_INVALID_FILE) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: already playing"); goto next; } /* check for double-dot in v->name; * we should not move out of play directory */ for (p = v->name; *p; ++p) { if (ngx_path_separator(p[0]) && p[1] == '.' && p[2] == '.' && ngx_path_separator(p[3])) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: bad name '%s'", v->name); return NGX_ERROR; } } if (ctx == NULL) { ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_play_ctx_t)); ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_play_module); } ngx_memzero(ctx, sizeof(*ctx)); ctx->session = s; ctx->aindex = ngx_rtmp_play_parse_index('a', v->args); ctx->vindex = ngx_rtmp_play_parse_index('v', v->args); ctx->file.log = s->connection->log; ngx_memcpy(ctx->name, v->name, NGX_RTMP_MAX_NAME); name.len = ngx_strlen(v->name); name.data = v->name; pfmt = pmcf->fmts.elts; for (n = 0; n < pmcf->fmts.nelts; ++n, ++pfmt) { fmt = *pfmt; pfx = &fmt->pfx; sfx = &fmt->sfx; if (pfx->len == 0 && ctx->fmt == NULL) { ctx->fmt = fmt; } if (pfx->len && name.len >= pfx->len && ngx_strncasecmp(pfx->data, name.data, pfx->len) == 0) { ctx->pfx_size = pfx->len; ctx->fmt = fmt; break; } if (name.len >= sfx->len && ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len, sfx->len) == 0) { ctx->fmt = fmt; } } if (ctx->fmt == NULL) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: fmt not found"); goto next; } ctx->file.fd = NGX_INVALID_FILE; ctx->nentry = NGX_CONF_UNSET_UINT; ctx->post_seek = NGX_CONF_UNSET_UINT; sfx = &ctx->fmt->sfx; if (name.len < sfx->len || ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len, sfx->len)) { ctx->sfx = *sfx; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: fmt=%V", &ctx->fmt->name); return ngx_rtmp_play_next_entry(s, v); next: return next_play(s, v); }
static ngx_rtmp_relay_ctx_t * ngx_rtmp_relay_create_connection(ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name, ngx_rtmp_relay_target_t *target) { ngx_rtmp_relay_app_conf_t *racf; ngx_rtmp_relay_ctx_t *rctx; ngx_rtmp_addr_conf_t *addr_conf; ngx_rtmp_conf_ctx_t *addr_ctx; ngx_rtmp_session_t *rs; ngx_peer_connection_t *pc; ngx_connection_t *c; ngx_addr_t *addr; ngx_pool_t *pool; ngx_int_t rc; ngx_str_t v, *uri; u_char *first, *last, *p; racf = ngx_rtmp_get_module_app_conf(cctx, ngx_rtmp_relay_module); ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0, "relay: create remote context"); pool = NULL; pool = ngx_create_pool(4096, racf->log); if (pool == NULL) { return NULL; } rctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_relay_ctx_t)); if (rctx == NULL) { goto clear; } if (name && ngx_rtmp_relay_copy_str(pool, &rctx->name, name) != NGX_OK) { goto clear; } if (ngx_rtmp_relay_copy_str(pool, &rctx->url, &target->url.url) != NGX_OK) { goto clear; } rctx->tag = target->tag; rctx->data = target->data; #define NGX_RTMP_RELAY_STR_COPY(to, from) \ if (ngx_rtmp_relay_copy_str(pool, &rctx->to, &target->from) != NGX_OK) { \ goto clear; \ } NGX_RTMP_RELAY_STR_COPY(app, app); NGX_RTMP_RELAY_STR_COPY(tc_url, tc_url); NGX_RTMP_RELAY_STR_COPY(page_url, page_url); NGX_RTMP_RELAY_STR_COPY(swf_url, swf_url); NGX_RTMP_RELAY_STR_COPY(flash_ver, flash_ver); NGX_RTMP_RELAY_STR_COPY(play_path, play_path); rctx->live = target->live; rctx->start = target->start; rctx->stop = target->stop; #undef NGX_RTMP_RELAY_STR_COPY if (rctx->app.len == 0 || rctx->play_path.len == 0) { /* parse uri */ uri = &target->url.uri; first = uri->data; last = uri->data + uri->len; if (first != last && *first == '/') { ++first; } if (first != last) { /* deduce app */ p = ngx_strlchr(first, last, '/'); if (p == NULL) { p = last; } if (rctx->app.len == 0 && first != p) { v.data = first; v.len = p - first; if (ngx_rtmp_relay_copy_str(pool, &rctx->app, &v) != NGX_OK) { goto clear; } } /* deduce play_path */ if (p != last) { ++p; } if (rctx->play_path.len == 0 && p != last) { v.data = p; v.len = last - p; if (ngx_rtmp_relay_copy_str(pool, &rctx->play_path, &v) != NGX_OK) { goto clear; } } } } pc = ngx_pcalloc(pool, sizeof(ngx_peer_connection_t)); if (pc == NULL) { goto clear; } if (target->url.naddrs == 0) { ngx_log_error(NGX_LOG_ERR, racf->log, 0, "relay: no address"); goto clear; } /* get address */ addr = &target->url.addrs[target->counter % target->url.naddrs]; target->counter++; /* copy log to keep shared log unchanged */ rctx->log = *racf->log; pc->log = &rctx->log; pc->get = ngx_rtmp_relay_get_peer; pc->free = ngx_rtmp_relay_free_peer; pc->name = &addr->name; pc->socklen = addr->socklen; pc->sockaddr = (struct sockaddr *)ngx_palloc(pool, pc->socklen); if (pc->sockaddr == NULL) { goto clear; } ngx_memcpy(pc->sockaddr, addr->sockaddr, pc->socklen); rc = ngx_event_connect_peer(pc); if (rc != NGX_OK && rc != NGX_AGAIN ) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0, "relay: connection failed"); goto clear; } c = pc->connection; c->pool = pool; c->addr_text = rctx->url; addr_conf = ngx_pcalloc(pool, sizeof(ngx_rtmp_addr_conf_t)); if (addr_conf == NULL) { goto clear; } addr_ctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_conf_ctx_t)); if (addr_ctx == NULL) { goto clear; } addr_conf->ctx = addr_ctx; addr_ctx->main_conf = cctx->main_conf; addr_ctx->srv_conf = cctx->srv_conf; ngx_str_set(&addr_conf->addr_text, "ngx-relay"); rs = ngx_rtmp_init_session(c, addr_conf); if (rs == NULL) { /* no need to destroy pool */ return NULL; } rs->app_conf = cctx->app_conf; rs->relay = 1; rctx->session = rs; ngx_rtmp_set_ctx(rs, rctx, ngx_rtmp_relay_module); ngx_str_set(&rs->flashver, "ngx-local-relay"); #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, 1); #endif ngx_rtmp_client_handshake(rs, 1); return rctx; clear: if (pool) { ngx_destroy_pool(pool); } return NULL; }
static ngx_int_t ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { ngx_rtmp_play_app_conf_t *pacf; ngx_rtmp_play_ctx_t *ctx; ngx_rtmp_play_entry_t *pe; u_char *p; static u_char path[NGX_MAX_PATH + 1]; pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); for ( ;; ) { if (ctx->file.fd != NGX_INVALID_FILE) { ngx_close_file(ctx->file.fd); ctx->file.fd = NGX_INVALID_FILE; } if (ctx->file_id) { ngx_rtmp_play_cleanup_local_file(s); } ctx->nentry = (ctx->nentry == NGX_CONF_UNSET_UINT ? 0 : ctx->nentry + 1); if (ctx->nentry >= pacf->entries.nelts) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: all entries failed"); ngx_rtmp_send_status(s, "NetStream.Play.StreamNotFound", "error", "Video on demand stream not found"); break; } pe = ngx_rtmp_play_get_current_entry(s); ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: trying %s entry %ui/%uz '%V'", pe->url ? "remote" : "local", ctx->nentry + 1, pacf->entries.nelts, pe->url ? &pe->url->url : pe->root); /* open remote */ if (pe->url) { return ngx_rtmp_play_open_remote(s, v); } /* open local */ p = ngx_snprintf(path, NGX_MAX_PATH, "%V/%s%V", pe->root, v->name + ctx->pfx_size, &ctx->sfx); *p = 0; ctx->file.fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN, NGX_FILE_DEFAULT_ACCESS); if (ctx->file.fd == NGX_INVALID_FILE) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, ngx_errno, "play: error opening file '%s'", path); continue; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: open local file '%s'", path); if (ngx_rtmp_play_open(s, v->start) != NGX_OK) { return NGX_ERROR; } break; } return next_play(s, v); }
static ngx_int_t ngx_rtmp_relay_send_play(ngx_rtmp_session_t *s) { static double trans; static double start, duration; static ngx_rtmp_amf_elt_t out_elts[] = { { NGX_RTMP_AMF_STRING, ngx_null_string, "play", 0 }, { NGX_RTMP_AMF_NUMBER, ngx_null_string, &trans, 0 }, { NGX_RTMP_AMF_NULL, ngx_null_string, NULL, 0 }, { NGX_RTMP_AMF_STRING, ngx_null_string, NULL, 0 }, /* <- fill */ { NGX_RTMP_AMF_NUMBER, ngx_null_string, &start, 0 }, { NGX_RTMP_AMF_NUMBER, ngx_null_string, &duration, 0 }, }; ngx_rtmp_header_t h; ngx_rtmp_relay_ctx_t *ctx; ngx_rtmp_relay_app_conf_t *racf; racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); if (racf == NULL || ctx == NULL) { return NGX_ERROR; } if (ctx->play_path.len) { out_elts[3].data = ctx->play_path.data; out_elts[3].len = ctx->play_path.len; } else { out_elts[3].data = ctx->name.data; out_elts[3].len = ctx->name.len; } if (ctx->live) { start = -1000; duration = -1000; } else { start = (ctx->start ? ctx->start : -2000); duration = (ctx->stop ? ctx->stop - ctx->start : -1000); } ngx_memzero(&h, sizeof(h)); h.csid = NGX_RTMP_RELAY_CSID_AMF; h.msid = NGX_RTMP_RELAY_MSID; h.type = NGX_RTMP_MSG_AMF_CMD; return ngx_rtmp_send_amf(s, &h, out_elts, sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK || ngx_rtmp_send_set_buflen(s, NGX_RTMP_RELAY_MSID, racf->buflen) != NGX_OK ? NGX_ERROR : 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; 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; }