static ngx_int_t ngx_rtmp_record_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) { ngx_rtmp_record_app_conf_t *racf; ngx_rtmp_record_ctx_t *ctx; racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); if (racf == NULL || racf->flags & NGX_RTMP_RECORD_OFF) { goto next; } ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); if (ctx == NULL) { ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_record_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ctx->file.fd = NGX_INVALID_FILE; ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_record_module); } ngx_memcpy(ctx->name, v->name, sizeof(ctx->name)); ngx_memcpy(ctx->args, v->args, sizeof(ctx->args)); if (ngx_rtmp_record_open(s) != NGX_OK) { return NGX_ERROR; } next: return next_publish(s, v); }
static const char * ngx_rtmp_control_record_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s, ngx_rtmp_core_srv_conf_t *cscf, ngx_rtmp_core_app_conf_t **cacf) { ngx_int_t rc; ngx_str_t rec; ngx_uint_t rn; ngx_rtmp_control_ctx_t *ctx; ngx_rtmp_record_app_conf_t *racf; *cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); racf = (*cacf)->app_conf[ngx_rtmp_record_module.ctx_index]; if (ngx_http_arg(r, (u_char *) "rec", sizeof("rec") - 1, &rec) != NGX_OK) { rec.len = 0; } rn = ngx_rtmp_record_find(racf, &rec); if (rn == NGX_CONF_UNSET_UINT) { return "Recorder not found"; } ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); if (ctx->method.len == sizeof("start") - 1 && ngx_strncmp(ctx->method.data, "start", ctx->method.len) == 0) { rc = ngx_rtmp_record_open(s, rn, &ctx->path); } else if (ctx->method.len == sizeof("stop") - 1 && ngx_strncmp(ctx->method.data, "stop", ctx->method.len) == 0) { rc = ngx_rtmp_record_close(s, rn, &ctx->path); } else { return "Undefined method"; } if (rc == NGX_ERROR) { return "Recorder error"; } return NGX_CONF_OK; }
static ngx_int_t ngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) { ngx_rtmp_record_ctx_t *ctx; ngx_rtmp_record_app_conf_t *racf; ngx_time_t next; ngx_rtmp_header_t ch; ngx_rtmp_codec_ctx_t *codec_ctx; ngx_int_t keyframe; racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); if (racf == NULL || ctx == NULL || racf->flags & NGX_RTMP_RECORD_OFF) { return NGX_OK; } keyframe = (ngx_rtmp_get_video_frame_type(in) == NGX_RTMP_VIDEO_KEY_FRAME); if (keyframe && racf->interval != (ngx_msec_t)NGX_CONF_UNSET) { next = ctx->last; next.msec += racf->interval; next.sec += (next.msec / 1000); next.msec %= 1000; if (ngx_cached_time->sec > next.sec || (ngx_cached_time->sec == next.sec && ngx_cached_time->msec > next.msec)) { ngx_rtmp_record_close(s); if (ngx_rtmp_record_open(s) != NGX_OK) { ngx_log_error(NGX_LOG_CRIT, s->connection->log, 0, "record: failed"); } } } if (ctx->file.fd == NGX_INVALID_FILE) { return NGX_OK; } /* filter frames */ if (h->type == NGX_RTMP_MSG_AUDIO && (racf->flags & NGX_RTMP_RECORD_AUDIO) == 0) { return NGX_OK; } if (h->type == NGX_RTMP_MSG_VIDEO && (racf->flags & NGX_RTMP_RECORD_VIDEO) == 0 && ((racf->flags & NGX_RTMP_RECORD_KEYFRAMES) == 0 || !keyframe)) { return NGX_OK; } if (ctx->file.offset == 0) { ctx->epoch = h->timestamp; if (ngx_rtmp_record_write_header(&ctx->file) != NGX_OK) { ngx_rtmp_record_close(s); return NGX_OK; } codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); if (codec_ctx) { ch = *h; #if 0 /* metadata */ if (codec_ctx->meta) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "record: writing metadata"); ch.type = NGX_RTMP_MSG_AMF_META; ch.mlen = ngx_rtmp_record_get_chain_mlen(codec_ctx->meta); if (ngx_rtmp_record_write_frame(s, &ch, codec_ctx->meta) != NGX_OK) { return NGX_OK; } } #endif /* AAC header */ if (codec_ctx->aac_header && (racf->flags & NGX_RTMP_RECORD_AUDIO)) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "record: writing AAC header"); ch.type = NGX_RTMP_MSG_AUDIO; ch.mlen = ngx_rtmp_record_get_chain_mlen(codec_ctx->aac_header); if (ngx_rtmp_record_write_frame(s, &ch, codec_ctx->aac_header) != NGX_OK) { return NGX_OK; } } /* AVC header */ if (codec_ctx->avc_header && (racf->flags & (NGX_RTMP_RECORD_VIDEO|NGX_RTMP_RECORD_KEYFRAMES))) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "record: writing AVC header"); ch.type = NGX_RTMP_MSG_VIDEO; ch.mlen = ngx_rtmp_record_get_chain_mlen(codec_ctx->avc_header); if (ngx_rtmp_record_write_frame(s, &ch, codec_ctx->avc_header) != NGX_OK) { return NGX_OK; } } } } return ngx_rtmp_record_write_frame(s, h, in); }
static ngx_int_t ngx_rtmp_control_record(ngx_http_request_t *r, ngx_str_t *method) { ngx_rtmp_record_app_conf_t *racf; ngx_rtmp_core_main_conf_t *cmcf; ngx_rtmp_core_srv_conf_t **pcscf, *cscf; ngx_rtmp_core_app_conf_t **pcacf, *cacf; ngx_rtmp_live_app_conf_t *lacf; ngx_rtmp_live_stream_t *ls; ngx_rtmp_live_ctx_t *lctx; ngx_rtmp_session_t *s; ngx_chain_t cl; ngx_uint_t sn, rn, n; ngx_str_t srv, app, rec, name, path; ngx_str_t msg; ngx_buf_t *b; ngx_int_t rc; size_t len; sn = 0; if (ngx_http_arg(r, (u_char *) "srv", sizeof("srv") - 1, &srv) == NGX_OK) { sn = ngx_atoi(srv.data, srv.len); } if (ngx_http_arg(r, (u_char *) "app", sizeof("app") - 1, &app) != NGX_OK) { ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "rtmp_control: app not specified"); ngx_str_set(&msg, "Application not specified"); goto error; } ngx_memzero(&rec, sizeof(rec)); ngx_http_arg(r, (u_char *) "rec", sizeof("rec") - 1, &rec); ngx_memzero(&name, sizeof(name)); ngx_http_arg(r, (u_char *) "name", sizeof("name") - 1, &name); cmcf = ngx_rtmp_core_main_conf; if (cmcf == NULL) { ngx_str_set(&msg, "Missing main RTMP conf"); goto error; } /* find server */ if (sn >= cmcf->servers.nelts) { ngx_str_set(&msg, "Server index out of range"); goto error; } pcscf = cmcf->servers.elts; pcscf += sn; cscf = *pcscf; /* find application */ pcacf = cscf->applications.elts; cacf = NULL; for (n = 0; n < cscf->applications.nelts; ++n, ++pcacf) { if ((*pcacf)->name.len == app.len && ngx_strncmp((*pcacf)->name.data, app.data, app.len) == 0) { cacf = *pcacf; break; } } if (cacf == NULL) { ngx_str_set(&msg, "Application not found"); goto error; } lacf = cacf->app_conf[ngx_rtmp_live_module.ctx_index]; racf = cacf->app_conf[ngx_rtmp_record_module.ctx_index]; /* find live stream by name */ for (ls = lacf->streams[ngx_hash_key(name.data, name.len) % lacf->nbuckets]; ls; ls = ls->next) { len = ngx_strlen(ls->name); if (name.len == len && ngx_strncmp(name.data, ls->name, name.len) == 0) { break; } } if (ls == NULL) { ngx_str_set(&msg, "Live stream not found"); goto error; } /* find publisher context */ for (lctx = ls->ctx; lctx; lctx = lctx->next) { if (lctx->flags & NGX_RTMP_LIVE_PUBLISHING) { break; } } if (lctx == NULL) { ngx_str_set(&msg, "No publisher"); goto error; } s = lctx->session; /* find recorder */ rn = ngx_rtmp_record_find(racf, &rec); if (rn == NGX_CONF_UNSET_UINT) { ngx_str_set(&msg, "Recorder not found"); goto error; } ngx_memzero(&path, sizeof(path)); if (method->len == sizeof("start") - 1 && ngx_strncmp(method->data, "start", method->len) == 0) { rc = ngx_rtmp_record_open(s, rn, &path); } else if (method->len == sizeof("stop") - 1 && ngx_strncmp(method->data, "stop", method->len) == 0) { rc = ngx_rtmp_record_close(s, rn, &path); } else { ngx_str_set(&msg, "Undefined method"); goto error; } if (rc == NGX_ERROR) { ngx_str_set(&msg, "Recorder error"); goto error; } if (rc == NGX_AGAIN) { /* already opened/closed */ ngx_str_null(&path); r->header_only = 1; } r->headers_out.status = NGX_HTTP_OK; r->headers_out.content_length_n = path.len; b = ngx_create_temp_buf(r->pool, path.len); if (b == NULL) { return NGX_ERROR; } ngx_memzero(&cl, sizeof(cl)); cl.buf = b; b->last = ngx_cpymem(b->pos, path.data, path.len); b->last_buf = 1; ngx_http_send_header(r); return ngx_http_output_filter(r, &cl); error: r->headers_out.status = NGX_HTTP_BAD_REQUEST; r->headers_out.content_length_n = msg.len; b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_ERROR; } ngx_memzero(&cl, sizeof(cl)); cl.buf = b; b->start = b->pos = msg.data; b->end = b->last = msg.data + msg.len; b->memory = 1; b->last_buf = 1; ngx_http_send_header(r); return ngx_http_output_filter(r, &cl); }