/* * Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com */ #include "ngx_rtmp_monitor_module.h" #include "ngx_rtmp_cmd_module.h" #include "ngx_rtmp_codec_module.h" static ngx_rtmp_close_stream_pt next_close_stream; static void *ngx_rtmp_monitor_create_app_conf(ngx_conf_t *cf); static char *ngx_rtmp_monitor_merge_app_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_rtmp_monitor_postconfiguration(ngx_conf_t *cf); static char *ngx_rtmp_monitor_dump(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_rtmp_monitor_buffered_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #define NGX_RTMP_MONITOR_BUFFER_SIZE 61 typedef struct { ngx_str_t dump_path; ngx_flag_t monitor; ngx_log_t *buffered_log; } ngx_rtmp_monitor_app_conf_t; typedef struct { ngx_event_t consume; ngx_msec_t buffered; ngx_uint_t nbuffered; double frame_rate; double nframes; double buffers[NGX_RTMP_MONITOR_BUFFER_SIZE]; ngx_uint_t fps[NGX_RTMP_MONITOR_BUFFER_SIZE]; ngx_uint_t curr; ngx_flag_t dump; unsigned publishing:1; } ngx_rtmp_monitor_ctx_t; static ngx_command_t ngx_rtmp_monitor_commands[] = { { ngx_string("dump"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, ngx_rtmp_monitor_dump, NGX_RTMP_APP_CONF_OFFSET, 0, NULL }, { ngx_string("buffered_log"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, ngx_rtmp_monitor_buffered_log, NGX_RTMP_APP_CONF_OFFSET, 0, NULL }, ngx_null_command }; static ngx_rtmp_module_t ngx_rtmp_monitor_module_ctx = { NULL, /* preconfiguration */ ngx_rtmp_monitor_postconfiguration, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_rtmp_monitor_create_app_conf, /* create app configuration */ ngx_rtmp_monitor_merge_app_conf /* merge app configuration */ }; ngx_module_t ngx_rtmp_monitor_module = { NGX_MODULE_V1, &ngx_rtmp_monitor_module_ctx, /* module context */ ngx_rtmp_monitor_commands, /* module directives */ NGX_RTMP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static void ngx_rtmp_monitor_dump_frame(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) { } static void ngx_rtmp_monitor_consume(ngx_event_t *ev) { ngx_rtmp_session_t *s, *ps; ngx_rtmp_monitor_ctx_t *ctx; ngx_rtmp_codec_ctx_t *cctx; ngx_rtmp_monitor_app_conf_t *macf; u_char peer[NGX_SOCKADDR_STRLEN]; u_char local[NGX_SOCKADDR_STRLEN]; struct sockaddr paddr, laddr; socklen_t plen, llen; s = ev->data; macf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_monitor_module); if (macf->monitor == 0) { return; } ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_monitor_module); if (ctx == NULL) { return; } if (ctx->publishing) { ps = s; } else { if (s->live_stream->publish_ctx) { ps = s->live_stream->publish_ctx->session; } else { goto next; } } cctx = ngx_rtmp_get_module_ctx(ps, ngx_rtmp_codec_module); if (cctx == NULL) { goto next; } if (cctx->frame_rate != 0) { ctx->frame_rate = cctx->frame_rate; } next: if (ctx->frame_rate == 0) { ngx_log_error(NGX_LOG_ERR, s->log, 0, "monitor, frame rate error, stream: %V, frame rate: %.2f", &s->stream, ctx->frame_rate); return; } ctx->nframes -= ctx->frame_rate; if (ctx->nframes <= 0) { ctx->nframes = 0; if (ctx->buffered == 0) { ++ctx->nbuffered; } ++ctx->buffered; ngx_memzero(local, sizeof(local)); ngx_memzero(peer, sizeof(peer)); plen = sizeof(paddr); llen = sizeof(laddr); if (s->connection) { getpeername(s->connection->fd, &paddr, &plen); getsockname(s->connection->fd, &laddr, &llen); ngx_sock_ntop(&paddr, plen, peer, NGX_SOCKADDR_STRLEN, 1); ngx_sock_ntop(&laddr, llen, local, NGX_SOCKADDR_STRLEN, 1); ngx_log_error(NGX_LOG_ERR, macf->buffered_log, 0, "%p %s, peer: %s, local: %s, " "stream: %V, buffered: %ui, time: %uis", s, ctx->publishing ? "publisher" : "player", peer, local, &s->stream, ctx->nbuffered, ctx->buffered); } } else { ctx->buffered = 0; } ctx->buffers[ctx->curr] = ctx->nframes; ctx->curr = (ctx->curr + 1) % NGX_RTMP_MONITOR_BUFFER_SIZE; ctx->fps[ctx->curr] = 0; ngx_add_timer(&ctx->consume, 1000); } static ngx_int_t ngx_rtmp_monitor_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) { ngx_rtmp_monitor_frame(s, h, in, ngx_rtmp_is_codec_header(in), 1); // if (h->type == NGX_RTMP_MSG_VIDEO) { // ngx_log_error(NGX_LOG_INFO, s->log, 0, // "!!!!!!video(%d): %02xD %02xD %02xD %02xD %02xD", // h->mlen, in->buf->pos[0], in->buf->pos[1], in->buf->pos[2], // in->buf->pos[3], in->buf->pos[4], in->buf->pos[5]); // } // // if (h->type == NGX_RTMP_MSG_AUDIO) { // ngx_log_error(NGX_LOG_INFO, s->log, 0, // "!!!!!!audio(%d): %02xD %02xD %02xD %02xD %02xD", // h->mlen, in->buf->pos[0], in->buf->pos[1], in->buf->pos[2], // in->buf->pos[3], in->buf->pos[4], in->buf->pos[5]); // } return NGX_OK; } static ngx_int_t ngx_rtmp_monitor_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) { if (s->first_metadata == 0) { s->stage = NGX_LIVE_AV; s->first_metadata = ngx_current_msec; s->first_data = s->first_data == 0? ngx_current_msec: s->first_data; } ngx_log_error(NGX_LOG_INFO, s->log, 0, "receive metadata"); return NGX_OK; } static ngx_int_t ngx_rtmp_monitor_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) { ngx_rtmp_monitor_ctx_t *ctx; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_monitor_module); if (ctx == NULL) { goto next; } if (ctx->consume.timer_set) { ngx_del_timer(&ctx->consume); } if (ctx->consume.posted) { ngx_delete_posted_event(&ctx->consume); } next: return next_close_stream(s, v); } void ngx_rtmp_monitor_frame(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in, ngx_flag_t is_header, ngx_flag_t publishing) { ngx_rtmp_monitor_app_conf_t *macf; ngx_rtmp_monitor_ctx_t *ctx; if (s->first_metadata == 0 && (h->type == NGX_RTMP_MSG_AMF_META || h->type == NGX_RTMP_MSG_AMF3_META)) { s->stage = NGX_LIVE_AV; s->first_metadata = ngx_current_msec; s->first_data = s->first_data == 0? ngx_current_msec: s->first_data; } if (s->first_audio == 0 && h->type == NGX_RTMP_MSG_AUDIO) { s->stage = NGX_LIVE_AV; s->first_audio = ngx_current_msec; s->first_data = s->first_data == 0? ngx_current_msec: s->first_data; } if (s->first_video == 0 && h->type == NGX_RTMP_MSG_VIDEO) { s->stage = NGX_LIVE_AV; s->first_video = ngx_current_msec; s->first_data = s->first_data == 0? ngx_current_msec: s->first_data; } if (h->type == NGX_RTMP_MSG_AUDIO && is_header) { if (s->publishing) { ngx_log_error(NGX_LOG_INFO, s->log, 0, "receive audio header"); } else { ngx_log_error(NGX_LOG_INFO, s->log, 0, "send audio header"); } } if (h->type == NGX_RTMP_MSG_VIDEO && is_header) { if (s->publishing) { ngx_log_error(NGX_LOG_INFO, s->log, 0, "receive video header"); } else { ngx_log_error(NGX_LOG_INFO, s->log, 0, "send video header"); } } if (h->type != NGX_RTMP_MSG_VIDEO) { return; } macf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_monitor_module); if (macf->monitor == 0) { return; } ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_monitor_module); if (ctx == NULL) { ctx = ngx_pcalloc(s->pool, sizeof(ngx_rtmp_monitor_ctx_t)); if (ctx == NULL) { return; } ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_monitor_module); ctx->dump = macf->dump_path.len > 0; ctx->publishing = publishing; ctx->consume.data = s; ctx->consume.log = s->log; ctx->consume.handler = ngx_rtmp_monitor_consume; ngx_add_timer(&ctx->consume, 1000); } if (publishing && ctx->dump) { ngx_rtmp_monitor_dump_frame(s, h, in); } if (is_header) { return; } ++ctx->nframes; ++ctx->fps[ctx->curr]; } static void * ngx_rtmp_monitor_create_app_conf(ngx_conf_t *cf) { ngx_rtmp_monitor_app_conf_t *macf; macf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_monitor_app_conf_t)); if (macf == NULL) { return NULL; } macf->monitor = NGX_CONF_UNSET; return macf; } static char * ngx_rtmp_monitor_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_rtmp_monitor_app_conf_t *prev = parent; ngx_rtmp_monitor_app_conf_t *conf = child; if (conf->dump_path.data == NULL) { conf->dump_path = prev->dump_path; } ngx_conf_merge_value(conf->monitor, prev->monitor, 0); if (conf->buffered_log == NULL) { if (prev->buffered_log) { conf->buffered_log = prev->buffered_log; } else { conf->buffered_log = &cf->cycle->new_log; } } return NGX_CONF_OK; } static char * ngx_rtmp_monitor_dump(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_rtmp_monitor_app_conf_t *macf; ngx_str_t *value; macf = conf; if (macf->dump_path.len) { return "is duplicate"; } value = cf->args->elts; macf->dump_path = value[1]; if (macf->dump_path.len > 0 && macf->dump_path.data[macf->dump_path.len - 1] == '/') { --macf->dump_path.len; } if (ngx_conf_full_name(cf->cycle, &macf->dump_path, 0) != NGX_OK) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } static char * ngx_rtmp_monitor_buffered_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_rtmp_monitor_app_conf_t *macf; macf = conf; macf->monitor = 1; return ngx_log_set_log(cf, &macf->buffered_log); } static ngx_int_t ngx_rtmp_monitor_postconfiguration(ngx_conf_t *cf) { ngx_rtmp_core_main_conf_t *cmcf; ngx_rtmp_handler_pt *h; ngx_rtmp_amf_handler_t *ch; cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); *h = ngx_rtmp_monitor_av; h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); *h = ngx_rtmp_monitor_av; /* register metadata handler */ ch = ngx_array_push(&cmcf->amf); if (ch == NULL) { return NGX_ERROR; } ngx_str_set(&ch->name, "@setDataFrame"); ch->handler = ngx_rtmp_monitor_meta_data; ch = ngx_array_push(&cmcf->amf); if (ch == NULL) { return NGX_ERROR; } ngx_str_set(&ch->name, "onMetaData"); ch->handler = ngx_rtmp_monitor_meta_data; next_close_stream = ngx_rtmp_close_stream; ngx_rtmp_close_stream = ngx_rtmp_monitor_close_stream; return NGX_OK; }