1308 lines
35 KiB
C
1308 lines
35 KiB
C
/*
|
|
* Copyright (C) AlexWoo(Wu Jie) wj19840501@gmail.com
|
|
*/
|
|
|
|
|
|
#include <ngx_config.h>
|
|
#include <ngx_core.h>
|
|
#include "ngx_rtmp.h"
|
|
#include "ngx_rtmp_cmd_module.h"
|
|
#include "ngx_rtmp_codec_module.h"
|
|
#include "ngx_live_record.h"
|
|
|
|
|
|
ngx_live_record_start_pt ngx_live_record_start;
|
|
ngx_live_record_update_pt ngx_live_record_update;
|
|
ngx_live_record_done_pt ngx_live_record_done;
|
|
|
|
static ngx_live_record_start_pt next_record_start;
|
|
static ngx_live_record_update_pt next_record_update;
|
|
static ngx_live_record_done_pt next_record_done;
|
|
|
|
static ngx_rtmp_publish_pt next_publish;
|
|
static ngx_rtmp_close_stream_pt next_close_stream;
|
|
|
|
|
|
static ngx_int_t ngx_live_record_postconfiguration(ngx_conf_t *cf);
|
|
static void * ngx_live_record_create_app_conf(ngx_conf_t *cf);
|
|
static char * ngx_live_record_merge_app_conf(ngx_conf_t *cf,
|
|
void *parent, void *child);
|
|
|
|
|
|
#define NGX_LIVE_RECORD_BUFSIZE (10*1024*1024)
|
|
|
|
|
|
typedef struct {
|
|
ngx_flag_t record;
|
|
ngx_str_t path;
|
|
ngx_msec_t interval;
|
|
|
|
ngx_msec_t min_fraglen;
|
|
ngx_msec_t max_fraglen;
|
|
|
|
size_t buffer;
|
|
} ngx_live_record_app_conf_t;
|
|
|
|
|
|
static ngx_command_t ngx_live_record_commands[] = {
|
|
|
|
{ ngx_string("live_record"),
|
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
|
ngx_conf_set_flag_slot,
|
|
NGX_RTMP_APP_CONF_OFFSET,
|
|
offsetof(ngx_live_record_app_conf_t, record),
|
|
NULL },
|
|
|
|
{ ngx_string("live_record_path"),
|
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
|
ngx_conf_set_str_slot,
|
|
NGX_RTMP_APP_CONF_OFFSET,
|
|
offsetof(ngx_live_record_app_conf_t, path),
|
|
NULL },
|
|
|
|
{ ngx_string("live_record_interval"),
|
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
|
ngx_conf_set_msec_slot,
|
|
NGX_RTMP_APP_CONF_OFFSET,
|
|
offsetof(ngx_live_record_app_conf_t, interval),
|
|
NULL },
|
|
|
|
{ ngx_string("live_record_min_fragment"),
|
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
|
ngx_conf_set_msec_slot,
|
|
NGX_RTMP_APP_CONF_OFFSET,
|
|
offsetof(ngx_live_record_app_conf_t, min_fraglen),
|
|
NULL },
|
|
|
|
{ ngx_string("live_record_max_fragment"),
|
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
|
ngx_conf_set_msec_slot,
|
|
NGX_RTMP_APP_CONF_OFFSET,
|
|
offsetof(ngx_live_record_app_conf_t, max_fraglen),
|
|
NULL },
|
|
|
|
{ ngx_string("live_record_buffer"),
|
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
|
ngx_conf_set_size_slot,
|
|
NGX_RTMP_APP_CONF_OFFSET,
|
|
offsetof(ngx_live_record_app_conf_t, buffer),
|
|
NULL },
|
|
|
|
ngx_null_command
|
|
};
|
|
|
|
|
|
static ngx_rtmp_module_t ngx_live_record_module_ctx = {
|
|
NULL, /* preconfiguration */
|
|
ngx_live_record_postconfiguration, /* postconfiguration */
|
|
NULL, /* create main configuration */
|
|
NULL, /* init main configuration */
|
|
NULL, /* create server configuration */
|
|
NULL, /* merge server configuration */
|
|
ngx_live_record_create_app_conf, /* create app configuration */
|
|
ngx_live_record_merge_app_conf /* merge app configuration */
|
|
};
|
|
|
|
|
|
ngx_module_t ngx_live_record_module = {
|
|
NGX_MODULE_V1,
|
|
&ngx_live_record_module_ctx, /* module context */
|
|
ngx_live_record_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_live_record_create_app_conf(ngx_conf_t *cf)
|
|
{
|
|
ngx_live_record_app_conf_t *racf;
|
|
|
|
racf = ngx_pcalloc(cf->pool, sizeof(ngx_live_record_app_conf_t));
|
|
|
|
if (racf == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
racf->record = NGX_CONF_UNSET;
|
|
racf->interval = NGX_CONF_UNSET_MSEC;
|
|
racf->min_fraglen = NGX_CONF_UNSET_MSEC;
|
|
racf->max_fraglen = NGX_CONF_UNSET_MSEC;
|
|
racf->buffer = NGX_CONF_UNSET_SIZE;
|
|
|
|
return racf;
|
|
}
|
|
|
|
|
|
static char *
|
|
ngx_live_record_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
|
|
{
|
|
ngx_err_t err;
|
|
ngx_live_record_app_conf_t *prev;
|
|
ngx_live_record_app_conf_t *conf;
|
|
u_char path[NGX_MAX_PATH + 1];
|
|
|
|
prev = parent;
|
|
conf = child;
|
|
|
|
ngx_conf_merge_value(conf->record, prev->record, 0);
|
|
ngx_conf_merge_str_value(conf->path, prev->path, "record");
|
|
ngx_conf_merge_msec_value(conf->interval, prev->interval, 10 * 60 * 1000);
|
|
ngx_conf_merge_msec_value(conf->min_fraglen, prev->min_fraglen, 8 * 1000);
|
|
ngx_conf_merge_msec_value(conf->max_fraglen, prev->max_fraglen, 12 * 1000);
|
|
ngx_conf_merge_msec_value(conf->buffer, prev->buffer, 1024 * 1024);
|
|
|
|
if (conf->path.data[conf->path.len - 1] == '/') {
|
|
--conf->path.len;
|
|
}
|
|
|
|
if (ngx_get_full_name(cf->pool, &cf->cycle->prefix, &conf->path)
|
|
!= NGX_OK)
|
|
{
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
*ngx_snprintf(path, sizeof(path) - 1, "%V/", &conf->path) = 0;
|
|
err = ngx_create_full_path(path, 0755);
|
|
if (err) {
|
|
ngx_conf_log_error(NGX_LOG_CRIT, cf, err,
|
|
ngx_create_dir_n " \"%s\" failed", path);
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
ngx_live_record_flush(ngx_rtmp_mpegts_file_t *file)
|
|
{
|
|
ssize_t rc;
|
|
|
|
rc = ngx_write_fd(file->fd, file->wbuf.pos,
|
|
file->wbuf.last - file->wbuf.pos);
|
|
if (rc < 0) {
|
|
ngx_log_error(NGX_LOG_ERR, file->log, ngx_errno,
|
|
"flush record buf error");
|
|
return rc;
|
|
}
|
|
|
|
file->file_size += rc;
|
|
file->wbuf.last = file->wbuf.pos;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
ngx_live_record_write_buf(ngx_rtmp_mpegts_file_t *file, u_char *in,
|
|
size_t in_size)
|
|
{
|
|
u_char *p, *end;
|
|
size_t len;
|
|
ssize_t rc, n;
|
|
|
|
end = in + in_size;
|
|
n = 0;
|
|
|
|
for (p = in; p != end; /* void */ ) {
|
|
len = ngx_min(file->wbuf.end - file->wbuf.last, end - p);
|
|
file->wbuf.last = ngx_cpymem(file->wbuf.last, p, len);
|
|
|
|
p += len;
|
|
n += len;
|
|
|
|
if (file->wbuf.last == file->wbuf.end) {
|
|
rc = ngx_live_record_flush(file);
|
|
if (rc < 0) {
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_open_file(ngx_rtmp_session_t *s)
|
|
{
|
|
ngx_live_record_app_conf_t *lracf;
|
|
ngx_live_record_ctx_t *ctx;
|
|
ngx_err_t err;
|
|
off_t file_size;
|
|
size_t len;
|
|
struct tm tm;
|
|
u_char *p;
|
|
ngx_rtmp_codec_ctx_t *codec_ctx;
|
|
|
|
lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_record_module);
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module);
|
|
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
|
|
|
|
len = lracf->path.len + sizeof("/") - 1 + s->serverid.len + sizeof("/") - 1
|
|
+ s->app.len + sizeof("/") - 1 + s->name.len + sizeof("/") - 1
|
|
+ sizeof("YYYYMMDD/") - 1 + s->name.len
|
|
+ NGX_OFF_T_LEN + sizeof("_.ts") - 1;
|
|
|
|
if (ctx->file.name.len == 0) { // first create in current session
|
|
ctx->file.name.data = ngx_pcalloc(s->pool, len + 1);
|
|
if (ctx->file.name.data == NULL) {
|
|
ngx_log_error(NGX_LOG_CRIT, s->log, 0,
|
|
"record: alloc for ts name failed");
|
|
return NGX_ERROR;
|
|
}
|
|
}
|
|
|
|
// fill file name
|
|
ngx_libc_localtime(ctx->last_time, &tm);
|
|
|
|
p = ngx_snprintf(ctx->file.name.data, len,
|
|
"%V/%V/%V/%V/%04d%02d%02d/%V_%d.ts",
|
|
&lracf->path, &s->serverid, &s->app, &s->name,
|
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
|
&s->name, ctx->last_time);
|
|
*p = 0;
|
|
ctx->file.name.len = p - ctx->file.name.data;
|
|
|
|
// create dir
|
|
err = ngx_create_full_path(ctx->file.name.data, 0755);
|
|
if (err) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, err,
|
|
ngx_create_dir_n " \"%V\" failed", &ctx->index.name);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
// open file
|
|
ctx->file.fd = ngx_open_file(ctx->file.name.data, NGX_FILE_RDWR,
|
|
NGX_FILE_CREATE_OR_OPEN, NGX_FILE_DEFAULT_ACCESS);
|
|
if (ctx->file.fd == NGX_INVALID_FILE) {
|
|
ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno,
|
|
"record: failed to open file '%V'", &ctx->file.name);
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
file_size = lseek(ctx->file.fd, 0, SEEK_END);
|
|
if (file_size == (off_t) -1) {
|
|
ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno,
|
|
"record: %V seek failed", &ctx->file.name);
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ctx->ts.wbuf.start == NULL) {
|
|
ctx->ts.wbuf.start = ngx_pcalloc(s->pool, lracf->buffer);
|
|
if (ctx->ts.wbuf.start == NULL) {
|
|
ngx_log_error(NGX_LOG_CRIT, s->log, 0,
|
|
"record: alloc write buffer error");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ctx->ts.wbuf.pos = ctx->ts.wbuf.last = ctx->ts.wbuf.start;
|
|
ctx->ts.wbuf.end = ctx->ts.wbuf.start + lracf->buffer;
|
|
|
|
ctx->ts.whandle = ngx_live_record_write_buf;
|
|
}
|
|
ctx->ts.fd = ctx->file.fd;
|
|
ctx->ts.log = s->log;
|
|
ctx->ts.file_size = file_size;
|
|
ctx->ts.vcodec = codec_ctx->video_codec_id;
|
|
ctx->ts.acodec = codec_ctx->audio_codec_id;
|
|
|
|
if (file_size == 0) { // empty file
|
|
if (ngx_rtmp_mpegts_write_header(&ctx->ts) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno,
|
|
"record: error writing fragment header");
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
ngx_live_record_flush(&ctx->ts);
|
|
}
|
|
|
|
ctx->startsize = ctx->ts.file_size;
|
|
ctx->endsize = ctx->ts.file_size;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static void
|
|
ngx_live_record_write_index(ngx_rtmp_session_t *s, ngx_live_record_ctx_t *ctx,
|
|
ngx_msec_t curr_time)
|
|
{
|
|
u_char *p, buf[1024];
|
|
|
|
ngx_live_record_flush(&ctx->ts);
|
|
|
|
ctx->endsize = ctx->ts.file_size - 1;
|
|
|
|
p = ngx_snprintf(buf, sizeof(buf) - 1,
|
|
"%V-%D.ts?startsize=%O&endsize=%O&starttime=%M&endtime=%M\n",
|
|
&s->name, ctx->last_time, ctx->startsize, ctx->endsize,
|
|
ctx->starttime, ctx->endtime);
|
|
*p = 0;
|
|
|
|
if (ngx_write_fd(ctx->index.fd, buf, p - buf) < 0) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, ngx_errno,
|
|
"record, write %V failed: %s", &ctx->index.name, buf);
|
|
}
|
|
|
|
ctx->startsize = ctx->ts.file_size;
|
|
ctx->starttime = curr_time;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_open_index(ngx_rtmp_session_t *s)
|
|
{
|
|
ngx_live_record_app_conf_t *lracf;
|
|
ngx_live_record_ctx_t *ctx;
|
|
ngx_err_t err;
|
|
size_t len;
|
|
struct tm tm;
|
|
u_char *p;
|
|
|
|
lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_record_module);
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module);
|
|
|
|
len = lracf->path.len + sizeof("/") - 1 + s->serverid.len + sizeof("/") - 1
|
|
+ s->app.len + sizeof("/") - 1 + s->name.len + sizeof("/") - 1
|
|
+ sizeof("index/YYYYMMDD/") - 1 + s->name.len
|
|
+ NGX_OFF_T_LEN + sizeof("-.m3u8") - 1;
|
|
|
|
if (ctx->index.name.len == 0) { // first create in current session
|
|
ctx->index.name.data = ngx_pcalloc(s->pool, len + 1);
|
|
if (ctx->index.name.data == NULL) {
|
|
ngx_log_error(NGX_LOG_CRIT, s->log, 0,
|
|
"record: alloc for index name failed");
|
|
return NGX_ERROR;
|
|
}
|
|
}
|
|
|
|
// fill index and file name
|
|
ngx_libc_localtime(ctx->last_time, &tm);
|
|
|
|
p = ngx_snprintf(ctx->index.name.data, len,
|
|
"%V/%V/%V/%V/index/%04d%02d%02d/%V-%D.m3u8",
|
|
&lracf->path, &s->serverid, &s->app, &s->name,
|
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
|
&s->name, ctx->last_time);
|
|
*p = 0;
|
|
ctx->index.name.len = p - ctx->index.name.data;
|
|
|
|
// create dir
|
|
err = ngx_create_full_path(ctx->index.name.data, 0755);
|
|
if (err) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, err,
|
|
ngx_create_dir_n " \"%V\" failed", &ctx->index.name);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
// open index
|
|
ctx->index.fd = ngx_open_file(ctx->index.name.data, NGX_FILE_RDWR,
|
|
NGX_FILE_CREATE_OR_OPEN, NGX_FILE_DEFAULT_ACCESS);
|
|
if (ctx->index.fd == NGX_INVALID_FILE) {
|
|
ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno,
|
|
"record: failed to open index '%V'", &ctx->index.name);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (lseek(ctx->index.fd, 0, SEEK_END) == (off_t) -1) {
|
|
ngx_log_error(NGX_LOG_CRIT, s->log, ngx_errno,
|
|
"record, %V seek failed", &ctx->index.name);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
return ngx_live_record_open_file(s);
|
|
}
|
|
|
|
|
|
static void
|
|
ngx_live_record_close_index(ngx_rtmp_session_t *s, ngx_live_record_ctx_t *ctx)
|
|
{
|
|
if (ctx->index.fd == -1 || ctx->file.fd == -1) {
|
|
return;
|
|
}
|
|
|
|
ngx_live_record_write_index(s, ctx, 0);
|
|
|
|
ngx_close_file(ctx->file.fd);
|
|
ctx->file.fd = -1;
|
|
|
|
ngx_close_file(ctx->index.fd);
|
|
ctx->index.fd = -1;
|
|
}
|
|
|
|
|
|
static void
|
|
ngx_live_record_reopen_index(ngx_rtmp_session_t *s, ngx_live_record_ctx_t *ctx,
|
|
ngx_msec_t curr_time, time_t last_time)
|
|
{
|
|
// close old index and file
|
|
ngx_live_record_close_index(s, ctx);
|
|
|
|
ngx_live_record_update(s);
|
|
|
|
ctx->last_time = last_time;
|
|
|
|
ctx->begintime = curr_time;
|
|
ctx->starttime = curr_time;
|
|
ctx->endtime = curr_time;
|
|
|
|
// open new index and file
|
|
if (ngx_live_record_open_index(s) == NGX_ERROR) {
|
|
ctx->last_time = 0;
|
|
|
|
if (ctx->index.fd != -1) {
|
|
ngx_close_file(ctx->index.fd);
|
|
}
|
|
|
|
if (ctx->file.fd != -1) {
|
|
ngx_close_file(ctx->file.fd);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_copy(ngx_rtmp_session_t *s, void *dst, u_char **src, size_t n,
|
|
ngx_chain_t **in)
|
|
{
|
|
u_char *last;
|
|
size_t pn;
|
|
|
|
if (*in == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
for ( ;; ) {
|
|
last = (*in)->buf->last;
|
|
|
|
if ((size_t)(last - *src) >= n) {
|
|
if (dst) {
|
|
ngx_memcpy(dst, *src, n);
|
|
}
|
|
|
|
*src += n;
|
|
|
|
while (*in && *src == (*in)->buf->last) {
|
|
*in = (*in)->next;
|
|
if (*in) {
|
|
*src = (*in)->buf->pos;
|
|
}
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
pn = last - *src;
|
|
|
|
if (dst) {
|
|
ngx_memcpy(dst, *src, pn);
|
|
dst = (u_char *)dst + pn;
|
|
}
|
|
|
|
n -= pn;
|
|
*in = (*in)->next;
|
|
|
|
if (*in == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"hls: failed to read %uz byte(s)", n);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
*src = (*in)->buf->pos;
|
|
}
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_parse_aac_header(ngx_rtmp_session_t *s, ngx_uint_t *objtype,
|
|
ngx_uint_t *srindex, ngx_uint_t *chconf)
|
|
{
|
|
ngx_rtmp_codec_ctx_t *codec_ctx;
|
|
ngx_chain_t *cl;
|
|
u_char *p, b0, b1;
|
|
|
|
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
|
|
|
|
cl = codec_ctx->aac_header->chain;
|
|
|
|
p = cl->buf->pos;
|
|
|
|
if (ngx_live_record_copy(s, NULL, &p, 2, &cl) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_live_record_copy(s, &b0, &p, 1, &cl) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_live_record_copy(s, &b1, &p, 1, &cl) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
*objtype = b0 >> 3;
|
|
if (*objtype == 0 || *objtype == 0x1f) {
|
|
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"record: unsupported adts object type:%ui", *objtype);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (*objtype > 4) {
|
|
|
|
/*
|
|
* Mark all extended profiles as LC
|
|
* to make Android as happy as possible.
|
|
*/
|
|
|
|
*objtype = 2;
|
|
}
|
|
|
|
*srindex = ((b0 << 1) & 0x0f) | ((b1 & 0x80) >> 7);
|
|
if (*srindex == 0x0f) {
|
|
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"record: unsupported adts sample rate:%ui", *srindex);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
*chconf = (b1 >> 3) & 0x0f;
|
|
|
|
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"record: aac object_type:%ui, sample_rate_index:%ui, "
|
|
"channel_config:%ui", *objtype, *srindex, *chconf);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_append_aud(ngx_rtmp_session_t *s, ngx_buf_t *out)
|
|
{
|
|
static u_char aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 };
|
|
|
|
if (out->last + sizeof(aud_nal) > out->end) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal));
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_append_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out)
|
|
{
|
|
ngx_rtmp_codec_ctx_t *codec_ctx;
|
|
u_char *p;
|
|
ngx_chain_t *in;
|
|
int8_t nnals;
|
|
uint16_t len, rlen;
|
|
ngx_int_t n;
|
|
|
|
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
|
|
|
|
if (codec_ctx == NULL || codec_ctx->avc_header == NULL) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
in = codec_ctx->avc_header->chain;
|
|
if (in == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
p = in->buf->pos;
|
|
|
|
/*
|
|
* Skip bytes:
|
|
* - flv fmt
|
|
* - H264 CONF/PICT (0x00)
|
|
* - 0
|
|
* - 0
|
|
* - 0
|
|
* - version
|
|
* - profile
|
|
* - compatibility
|
|
* - level
|
|
* - nal bytes
|
|
*/
|
|
|
|
if (ngx_live_record_copy(s, NULL, &p, 10, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
/* number of SPS NALs */
|
|
if (ngx_live_record_copy(s, &nnals, &p, 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
nnals &= 0x1f; /* 5lsb */
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"record: SPS number: %uz", nnals);
|
|
|
|
/* SPS */
|
|
for (n = 0; ; ++n) {
|
|
for (; nnals; --nnals) {
|
|
|
|
/* NAL length */
|
|
if (ngx_live_record_copy(s, &rlen, &p, 2, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_rtmp_rmemcpy(&len, &rlen, 2);
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"record: header NAL length: %uz", (size_t) len);
|
|
|
|
/* AnnexB prefix */
|
|
if (out->end - out->last < 4) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"record: too small buffer for header NAL size");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
*out->last++ = 0;
|
|
*out->last++ = 0;
|
|
*out->last++ = 0;
|
|
*out->last++ = 1;
|
|
|
|
/* NAL body */
|
|
if (out->end - out->last < len) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"record: too small buffer for header NAL");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_live_record_copy(s, out->last, &p, len, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
out->last += len;
|
|
}
|
|
|
|
if (n == 1) {
|
|
break;
|
|
}
|
|
|
|
/* number of PPS NALs */
|
|
if (ngx_live_record_copy(s, &nnals, &p, 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"record: PPS number: %uz", nnals);
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_aac(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|
ngx_chain_t *in)
|
|
{
|
|
ngx_live_record_ctx_t *ctx;
|
|
ngx_rtmp_codec_ctx_t *codec_ctx;
|
|
uint64_t pts;
|
|
ngx_rtmp_mpegts_frame_t frame;
|
|
ngx_buf_t out;
|
|
u_char *p;
|
|
ngx_uint_t objtype, srindex, chconf, size;
|
|
static u_char buffer[NGX_LIVE_RECORD_BUFSIZE];
|
|
ngx_live_record_app_conf_t *lracf;
|
|
ngx_msec_t curr_time;
|
|
time_t last_time;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module);
|
|
|
|
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
|
|
if (codec_ctx == NULL || codec_ctx->aac_header == NULL) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (h->mlen < 2) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (ctx->open == 2) {
|
|
if (codec_ctx->avc_header == NULL) { // pure audio
|
|
ctx->open = 1;
|
|
} else {
|
|
return NGX_OK;
|
|
}
|
|
}
|
|
|
|
lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_record_module);
|
|
|
|
if (ctx->last_time == 0) {
|
|
ctx->publish_epoch = ngx_current_msec;
|
|
ctx->last_time = ngx_time() - ngx_time() % (lracf->interval / 1000);
|
|
ctx->basetime = ctx->publish_epoch - h->timestamp;
|
|
|
|
ctx->begintime = ngx_current_msec;
|
|
ctx->starttime = ngx_current_msec;
|
|
ctx->endtime = ngx_current_msec;
|
|
|
|
// open new index and file
|
|
if (ngx_live_record_open_index(s) == NGX_ERROR) {
|
|
ctx->last_time = 0;
|
|
|
|
if (ctx->index.fd != -1) {
|
|
ngx_close_file(ctx->index.fd);
|
|
}
|
|
|
|
if (ctx->file.fd != -1) {
|
|
ngx_close_file(ctx->file.fd);
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* FLV Audio data config
|
|
* SoundFormat 4bits, SoundRate 2bits, SoundSize 1bit, SoundType 1bit
|
|
* AACPacketType 1byte
|
|
*
|
|
* mpegts ADTS 7 bytes
|
|
*/
|
|
size = h->mlen - 2 + 7;
|
|
pts = (uint64_t) h->timestamp * 90;
|
|
|
|
p = in->buf->pos;
|
|
|
|
/* skip FLV Audio data config */
|
|
if (ngx_live_record_copy(s, NULL, &p, 2, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_memzero(&out, sizeof(out));
|
|
|
|
out.start = buffer;
|
|
out.end = buffer + sizeof(buffer);
|
|
out.pos = out.start;
|
|
out.last = out.pos;
|
|
|
|
/* make up ADTS */
|
|
if (ngx_live_record_parse_aac_header(s, &objtype, &srindex, &chconf)
|
|
!= NGX_OK)
|
|
{
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0, "record: aac header error");
|
|
return NGX_OK;
|
|
}
|
|
|
|
*out.last++ = 0xff;
|
|
*out.last++ = 0xf1;
|
|
*out.last++ = (u_char) (((objtype - 1) << 6) | (srindex << 2) |
|
|
((chconf & 0x04) >> 2));
|
|
*out.last++ = (u_char) (((chconf & 0x03) << 6) | ((size >> 11) & 0x03));
|
|
*out.last++ = (u_char) (size >> 3);
|
|
*out.last++ = (u_char) ((size << 5) | 0x1f);
|
|
*out.last++ = 0xfc;
|
|
|
|
/* copy payload */
|
|
while (in) {
|
|
if (in->buf->last - p) {
|
|
out.last = ngx_cpymem(out.last, p, in->buf->last - p);
|
|
}
|
|
|
|
in = in->next;
|
|
if (in) {
|
|
p = in->buf->pos;
|
|
}
|
|
}
|
|
|
|
// reopen index and ts file
|
|
curr_time = ctx->basetime + h->timestamp;
|
|
if (codec_ctx->avc_header == NULL) { // no video
|
|
last_time = curr_time / 1000 - (curr_time / 1000)
|
|
% (lracf->interval / 1000);
|
|
if (curr_time > ctx->starttime + lracf->min_fraglen) {
|
|
if (last_time > ctx->last_time) {
|
|
ngx_live_record_reopen_index(s, ctx, curr_time, last_time);
|
|
} else {
|
|
ngx_live_record_write_index(s, ctx, curr_time);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* write frame */
|
|
ngx_memzero(&frame, sizeof(frame));
|
|
|
|
frame.cc = ctx->audio_cc;
|
|
frame.dts = pts;
|
|
frame.pts = frame.dts;
|
|
frame.pid = 0x101;
|
|
frame.sid = 0xc0;
|
|
|
|
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"record: audio pts=%uL, dts=%uL", frame.pts, frame.dts);
|
|
|
|
if (ngx_rtmp_mpegts_write_frame(&ctx->ts, &frame, &out) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0, "record: audio frame failed");
|
|
}
|
|
|
|
ctx->endtime = curr_time;
|
|
ctx->audio_cc = frame.cc;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_avc(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|
ngx_chain_t *in)
|
|
{
|
|
ngx_live_record_ctx_t *ctx;
|
|
ngx_rtmp_codec_ctx_t *codec_ctx;
|
|
u_char *p;
|
|
uint8_t fmt, ftype, nal_type, src_nal_type;
|
|
uint32_t len, rlen;
|
|
ngx_buf_t out;
|
|
uint32_t cts;
|
|
ngx_rtmp_mpegts_frame_t frame;
|
|
ngx_uint_t nal_bytes;
|
|
ngx_int_t aud_sent, sps_pps_sent;
|
|
static u_char buffer[NGX_LIVE_RECORD_BUFSIZE];
|
|
ngx_live_record_app_conf_t *lracf;
|
|
ngx_msec_t curr_time;
|
|
time_t last_time;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module);
|
|
|
|
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
|
|
|
|
p = in->buf->pos;
|
|
if (ngx_live_record_copy(s, &fmt, &p, 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
/*
|
|
* frame type:
|
|
* 1: keyframe (for AVC, a seekable frame)
|
|
* 2: inter frame (for AVC, a non- seekable frame)
|
|
* 3: disposable inter frame (H.263 only)
|
|
* 4: generated keyframe (reserved for server use only)
|
|
* 5: video info/command frame
|
|
*/
|
|
ftype = (fmt & 0xf0) >> 4;
|
|
|
|
if (ctx->open == 2) { // wait for key frame
|
|
if (ftype == 1) {
|
|
ctx->open = 1;
|
|
} else {
|
|
return NGX_OK;
|
|
}
|
|
}
|
|
|
|
lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_record_module);
|
|
|
|
if (ctx->last_time == 0) {
|
|
ctx->publish_epoch = ngx_current_msec;
|
|
ctx->last_time = ngx_time() - ngx_time() % (lracf->interval / 1000);
|
|
ctx->basetime = ctx->publish_epoch - h->timestamp;
|
|
|
|
ctx->begintime = ngx_current_msec;
|
|
ctx->starttime = ngx_current_msec;
|
|
ctx->endtime = ngx_current_msec;
|
|
|
|
// open new index and file
|
|
if (ngx_live_record_open_index(s) == NGX_ERROR) {
|
|
ctx->last_time = 0;
|
|
|
|
if (ctx->index.fd != -1) {
|
|
ngx_close_file(ctx->index.fd);
|
|
}
|
|
|
|
if (ctx->file.fd != -1) {
|
|
ngx_close_file(ctx->file.fd);
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
}
|
|
|
|
if (ngx_live_record_copy(s, NULL, &p, 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_live_record_copy(s, &cts, &p, 3, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
/* CompositionTime */
|
|
cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) |
|
|
(cts & 0x0000FF00);
|
|
|
|
/* Data */
|
|
ngx_memzero(&out, sizeof(out));
|
|
|
|
out.start = buffer;
|
|
out.end = buffer + sizeof(buffer);
|
|
out.pos = out.start;
|
|
out.last = out.pos;
|
|
|
|
nal_bytes = codec_ctx->avc_nal_bytes;
|
|
aud_sent = 0;
|
|
sps_pps_sent = 0;
|
|
|
|
while (in) {
|
|
if (ngx_live_record_copy(s, &rlen, &p, nal_bytes, &in) != NGX_OK) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
len = 0;
|
|
ngx_rtmp_rmemcpy(&len, &rlen, nal_bytes);
|
|
|
|
if (len == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (ngx_live_record_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
nal_type = src_nal_type & 0x1f;
|
|
|
|
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"record: h264 NAL type=%ui, len=%uD",
|
|
(ngx_uint_t) nal_type, len);
|
|
|
|
if (nal_type >= 7 && nal_type <= 9) {
|
|
if (ngx_live_record_copy(s, NULL, &p, len - 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!aud_sent) {
|
|
switch (nal_type) {
|
|
case 1:
|
|
case 5:
|
|
case 6:
|
|
if (ngx_live_record_append_aud(s, &out) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"record: error appending AUD NAL");
|
|
}
|
|
aud_sent = 1;
|
|
break;
|
|
|
|
case 9:
|
|
aud_sent = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (nal_type) {
|
|
case 1:
|
|
sps_pps_sent = 0;
|
|
break;
|
|
case 5:
|
|
if (sps_pps_sent) {
|
|
break;
|
|
}
|
|
if (ngx_live_record_append_sps_pps(s, &out) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"record: error appenging SPS/PPS NALs");
|
|
}
|
|
sps_pps_sent = 1;
|
|
break;
|
|
}
|
|
|
|
/* AnnexB prefix */
|
|
|
|
if (out.end - out.last < 5) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"record: not enough buffer for AnnexB prefix");
|
|
return NGX_OK;
|
|
}
|
|
|
|
/* first AnnexB prefix is long (4 bytes) */
|
|
|
|
if (out.last == out.pos) {
|
|
*out.last++ = 0;
|
|
}
|
|
|
|
*out.last++ = 0;
|
|
*out.last++ = 0;
|
|
*out.last++ = 1;
|
|
*out.last++ = src_nal_type;
|
|
|
|
/* NAL body */
|
|
|
|
if (out.end - out.last < (ngx_int_t) len) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"record: not enough buffer for NAL");
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (ngx_live_record_copy(s, out.last, &p, len - 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
out.last += (len - 1);
|
|
}
|
|
|
|
// reopen index and ts file
|
|
curr_time = ctx->basetime + h->timestamp;
|
|
last_time = curr_time / 1000 - (curr_time / 1000)
|
|
% (lracf->interval / 1000);
|
|
if (ftype == 1) { // key frame
|
|
if (curr_time > ctx->starttime + lracf->min_fraglen) {
|
|
if (last_time > ctx->last_time) {
|
|
ngx_live_record_reopen_index(s, ctx, curr_time, last_time);
|
|
} else {
|
|
ngx_live_record_write_index(s, ctx, curr_time);
|
|
}
|
|
}
|
|
} else if (curr_time > ctx->starttime + lracf->max_fraglen) { // force slice
|
|
if (last_time > ctx->last_time) {
|
|
ngx_log_error(NGX_LOG_INFO, s->log, 0, "record: force slice, "
|
|
"curr_time:%M, starttime:%M, max_fraglen:%M",
|
|
curr_time, ctx->starttime, lracf->max_fraglen);
|
|
|
|
ngx_live_record_reopen_index(s, ctx, curr_time, last_time);
|
|
} else {
|
|
ngx_live_record_write_index(s, ctx, curr_time);
|
|
}
|
|
}
|
|
|
|
/* write frame */
|
|
ngx_memzero(&frame, sizeof(frame));
|
|
|
|
frame.cc = ctx->video_cc;
|
|
frame.dts = (uint64_t) h->timestamp * 90;
|
|
frame.pts = (h->timestamp + cts) * 90;
|
|
frame.pid = 0x100;
|
|
frame.sid = 0xe0;
|
|
frame.key = (ftype == 1);
|
|
|
|
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"record: video pts=%uL, dts=%uL", frame.pts, frame.dts);
|
|
|
|
if (ngx_rtmp_mpegts_write_frame(&ctx->ts, &frame, &out) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0, "record: video frame failed");
|
|
}
|
|
|
|
ctx->endtime = curr_time;
|
|
ctx->video_cc = frame.cc;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|
ngx_chain_t *in)
|
|
{
|
|
ngx_live_record_ctx_t *ctx;
|
|
ngx_rtmp_codec_ctx_t *codec_ctx;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module);
|
|
if (ctx == NULL || !ctx->open) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (ngx_rtmp_is_codec_header(in)) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
|
|
|
|
if (h->type == NGX_RTMP_MSG_AUDIO) {
|
|
switch (codec_ctx->audio_codec_id) {
|
|
case NGX_RTMP_AUDIO_AAC:
|
|
return ngx_live_record_aac(s, h, in);
|
|
}
|
|
} else {
|
|
switch (codec_ctx->video_codec_id) {
|
|
case NGX_RTMP_VIDEO_H264:
|
|
return ngx_live_record_avc(s, h, in);
|
|
}
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_start_handle(ngx_rtmp_session_t *s)
|
|
{
|
|
ngx_live_record_ctx_t *ctx;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module);
|
|
|
|
return next_publish(s, &ctx->pubv);
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_update_handle(ngx_rtmp_session_t *s)
|
|
{
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_done_handle(ngx_rtmp_session_t *s)
|
|
{
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
const char *
|
|
ngx_live_record_open(ngx_rtmp_session_t *s)
|
|
{
|
|
ngx_live_record_ctx_t *ctx;
|
|
|
|
if (s->interprocess) {
|
|
return "interprocess";
|
|
}
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module);
|
|
|
|
if (ctx->open) {
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_INFO, s->log, 0, "record: open %V:", &s->stream);
|
|
|
|
ctx->open = 2;
|
|
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
|
|
const char *
|
|
ngx_live_record_close(ngx_rtmp_session_t *s)
|
|
{
|
|
ngx_live_record_ctx_t *ctx;
|
|
|
|
if (s->interprocess) {
|
|
return "interprocess";
|
|
}
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module);
|
|
|
|
if (ctx->open == 0) {
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_INFO, s->log, 0, "record: close %V:", &s->stream);
|
|
|
|
ngx_live_record_done(s);
|
|
|
|
ngx_live_record_close_index(s, ctx);
|
|
|
|
ctx->open = 0;
|
|
ctx->last_time = 0;
|
|
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
|
|
{
|
|
ngx_live_record_app_conf_t *lracf;
|
|
ngx_live_record_ctx_t *ctx;
|
|
|
|
if (s->interprocess) {
|
|
return next_publish(s, v);
|
|
}
|
|
|
|
ctx = ngx_pcalloc(s->pool, sizeof(ngx_live_record_ctx_t));
|
|
if (ctx == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
ngx_rtmp_set_ctx(s, ctx, ngx_live_record_module);
|
|
|
|
lracf = ngx_rtmp_get_module_app_conf(s, ngx_live_record_module);
|
|
|
|
if (lracf->record) {
|
|
ctx->open = 1;
|
|
}
|
|
ctx->pubv = *v;
|
|
ctx->index.fd = -1;
|
|
ctx->file.fd = -1;
|
|
|
|
return ngx_live_record_start(s);
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
|
|
{
|
|
ngx_live_record_ctx_t *ctx;
|
|
|
|
if (s->interprocess) {
|
|
goto next;
|
|
}
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_live_record_module);
|
|
if (ctx == NULL) {
|
|
goto next;
|
|
}
|
|
|
|
if (ctx->open == 0) {
|
|
goto next;
|
|
}
|
|
|
|
ngx_live_record_done(s);
|
|
|
|
ngx_live_record_close_index(s, ctx);
|
|
ctx->open = 0;
|
|
|
|
next:
|
|
return next_close_stream(s, v);
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_live_record_postconfiguration(ngx_conf_t *cf)
|
|
{
|
|
ngx_rtmp_core_main_conf_t *cmcf;
|
|
ngx_rtmp_handler_pt *h;
|
|
|
|
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_live_record_av;
|
|
|
|
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);
|
|
*h = ngx_live_record_av;
|
|
|
|
next_record_start = ngx_live_record_start;
|
|
ngx_live_record_start = ngx_live_record_start_handle;
|
|
|
|
next_record_update = ngx_live_record_update;
|
|
ngx_live_record_update = ngx_live_record_update_handle;
|
|
|
|
next_record_done = ngx_live_record_done;
|
|
ngx_live_record_done = ngx_live_record_done_handle;
|
|
|
|
next_publish = ngx_rtmp_publish;
|
|
ngx_rtmp_publish = ngx_live_record_publish;
|
|
|
|
next_close_stream = ngx_rtmp_close_stream;
|
|
ngx_rtmp_close_stream = ngx_live_record_close_stream;
|
|
|
|
return NGX_OK;
|
|
}
|