diff --git a/config b/config index f5c2d00..16e74ef 100644 --- a/config +++ b/config @@ -7,6 +7,7 @@ CORE_MODULES="$CORE_MODULES ngx_rtmp_access_module \ ngx_rtmp_live_module \ ngx_rtmp_play_module \ + ngx_rtmp_flv_module \ ngx_rtmp_mp4_module \ ngx_rtmp_record_module \ ngx_rtmp_netcall_module \ @@ -36,6 +37,7 @@ NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ $ngx_addon_dir/ngx_rtmp_access_module.c \ $ngx_addon_dir/ngx_rtmp_live_module.c \ $ngx_addon_dir/ngx_rtmp_play_module.c \ + $ngx_addon_dir/ngx_rtmp_flv_module.c \ $ngx_addon_dir/ngx_rtmp_mp4_module.c \ $ngx_addon_dir/ngx_rtmp_record_module.c \ $ngx_addon_dir/ngx_rtmp_netcall_module.c \ diff --git a/ngx_rtmp_flv_module.c b/ngx_rtmp_flv_module.c new file mode 100644 index 0000000..9e8163e --- /dev/null +++ b/ngx_rtmp_flv_module.c @@ -0,0 +1,642 @@ +/* + * Copyright (c) 2012 Roman Arutyunyan + */ + + +#include "ngx_rtmp_play_module.h" +#include "ngx_rtmp_codec_module.h" +#include "ngx_rtmp_streams.h" + + +static ngx_int_t ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf); +static void ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s, + ngx_file_t *f, ngx_int_t timestamp); +static ngx_int_t ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f, + ngx_uint_t offset); +static ngx_int_t ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f); + + +typedef struct { + ngx_uint_t nelts; + ngx_uint_t offset; +} ngx_rtmp_flv_index_t; + + +typedef struct { + ngx_int_t offset; + ngx_int_t start_timestamp; + ngx_event_t write_evt; + uint32_t last_audio; + uint32_t last_video; + ngx_uint_t msg_mask; + uint32_t epoch; + + unsigned meta_read:1; + ngx_rtmp_flv_index_t filepositions; + ngx_rtmp_flv_index_t times; +} ngx_rtmp_flv_ctx_t; + + +#define NGX_RTMP_FLV_BUFFER (1024*1024) +#define NGX_RTMP_FLV_DEFAULT_BUFLEN 1000 +#define NGX_RTMP_FLV_TAG_HEADER 11 +#define NGX_RTMP_FLV_DATA_OFFSET 13 + + +static u_char ngx_rtmp_flv_buffer[ + NGX_RTMP_FLV_BUFFER]; +static u_char ngx_rtmp_flv_header[ + NGX_RTMP_FLV_TAG_HEADER]; + + +static ngx_rtmp_module_t ngx_rtmp_flv_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_flv_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + NULL, /* create app configuration */ + NULL /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_flv_module = { + NGX_MODULE_V1, + &ngx_rtmp_flv_module_ctx, /* module context */ + NULL, /* 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 ngx_int_t +ngx_rtmp_flv_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_flv_index_t *idx) +{ + uint32_t nelts; + ngx_buf_t *b; + + /* we have AMF array pointed by context; + * need to extract its size (4 bytes) & + * save offset of actual array data */ + + b = ctx->link->buf; + + if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) { + return NGX_ERROR; + } + + ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4); + + idx->nelts = nelts; + idx->offset = ctx->offset + 4; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in) +{ + ngx_rtmp_flv_ctx_t *ctx; + + static ngx_rtmp_amf_ctx_t filepositions_ctx; + static ngx_rtmp_amf_ctx_t times_ctx; + + static ngx_rtmp_amf_elt_t in_keyframes[] = { + + { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT, + ngx_string("filepositions"), + &filepositions_ctx, 0 }, + + { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT, + ngx_string("times"), + ×_ctx, 0 } + }; + + static ngx_rtmp_amf_elt_t in_inf[] = { + + { NGX_RTMP_AMF_OBJECT, + ngx_string("keyframes"), + in_keyframes, sizeof(in_keyframes) } + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL || in == NULL) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: init index"); + + ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx)); + ngx_memzero(×_ctx, sizeof(times_ctx)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: init index error"); + return NGX_OK; + } + + if (filepositions_ctx.link && ngx_rtmp_flv_fill_index(&filepositions_ctx, + &ctx->filepositions) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: failed to init filepositions"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: filepositions nelts=%ui offset=%ui", + ctx->filepositions.nelts, ctx->filepositions.offset); + + if (times_ctx.link && ngx_rtmp_flv_fill_index(×_ctx, + &ctx->times) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: failed to init times"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: times nelts=%ui offset=%ui", + ctx->times.nelts, ctx->times.offset); + + return NGX_OK; +} + + +static double +ngx_rtmp_flv_index_value(void *src) +{ + double v; + + ngx_rtmp_rmemcpy(&v, src, 8); + + return v; +} + + +static ngx_int_t +ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s, ngx_file_t *f, + ngx_int_t timestamp) +{ + ngx_rtmp_flv_ctx_t *ctx; + ssize_t n, size; + ngx_uint_t offset, index, ret, nelts; + double v; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + goto rewind; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: lookup index start timestamp=%i", + timestamp); + + if (ctx->meta_read == 0) { + ngx_rtmp_flv_read_meta(s, f); + ctx->meta_read = 1; + } + + if (timestamp <= 0 || ctx->filepositions.nelts == 0 + || ctx->times.nelts == 0) + { + goto rewind; + } + + /* read index table from file given offset */ + offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER + + ctx->times.offset; + + /* index should fit in the buffer */ + nelts = ngx_min(ctx->times.nelts, sizeof(ngx_rtmp_flv_buffer) / 9); + size = nelts * 9; + + n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, offset); + + if (n != size) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read times index"); + goto rewind; + } + + /*TODO: implement binary search */ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: lookup times nelts=%ui", nelts); + + for (index = 0; index < nelts - 1; ++index) { + v = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer + + index * 9 + 1) * 1000; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: lookup times index=%ui value=%ui", + index, (ngx_uint_t) v); + + if (timestamp < v) { + break; + } + } + + if (index >= ctx->filepositions.nelts) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: index out of bounds: %ui>=%ui", + index, ctx->filepositions.nelts); + goto rewind; + } + + /* take value from filepositions */ + offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER + + ctx->filepositions.offset + index * 9; + + n = ngx_read_file(f, ngx_rtmp_flv_buffer, 8, offset + 1); + + if (n != 8) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read filepositions index"); + goto rewind; + } + + ret = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer); + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: lookup index timestamp=%i offset=%ui", + timestamp, ret); + + return ret; + +rewind: + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: lookup index timestamp=%i offset=begin", + timestamp); + + return NGX_RTMP_FLV_DATA_OFFSET; +} + + +static void +ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_flv_ctx_t *ctx; + ssize_t n; + ngx_rtmp_header_t h; + ngx_chain_t *out, in; + ngx_buf_t in_buf; + ngx_rtmp_core_srv_conf_t *cscf; + uint32_t size; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: read meta"); + + /* read tag header */ + n = ngx_read_file(f, ngx_rtmp_flv_header, sizeof(ngx_rtmp_flv_header), + NGX_RTMP_FLV_DATA_OFFSET); + + if (n != sizeof(ngx_rtmp_flv_header)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read metadata tag header"); + return; + } + + if (ngx_rtmp_flv_header[0] != NGX_RTMP_MSG_AMF_META) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: first tag is not metadata, giving up"); + return; + } + + ngx_memzero(&h, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_META; + h.msid = NGX_RTMP_LIVE_MSID; + h.csid = NGX_RTMP_LIVE_CSID_META; + + size = 0; + ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: metadata size=%D", size); + + if (size > sizeof(ngx_rtmp_flv_buffer)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: too big metadata"); + return; + } + + /* read metadata */ + n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, + sizeof(ngx_rtmp_flv_header) + + NGX_RTMP_FLV_DATA_OFFSET); + + if (n != (ssize_t) size) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read metadata"); + return; + } + + /* prepare input chain */ + ngx_memzero(&in, sizeof(in)); + ngx_memzero(&in_buf, sizeof(in_buf)); + + in.buf = &in_buf; + in_buf.pos = ngx_rtmp_flv_buffer; + in_buf.last = ngx_rtmp_flv_buffer + size; + + ngx_rtmp_flv_init_index(s, &in); + + /* output chain */ + out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); + + ngx_rtmp_prepare_message(s, &h, NULL, out); + ngx_rtmp_send_message(s, out, 0); + ngx_rtmp_free_shared_chain(cscf, out); +} + + +static ngx_int_t +ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_flv_ctx_t *ctx; + uint32_t last_timestamp; + ngx_rtmp_header_t h, lh; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_chain_t *out, in; + ngx_buf_t in_buf; + ngx_int_t rc; + ssize_t n; + uint32_t buflen, end_timestamp, size; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + if (ctx->offset == -1) { + ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f, + ctx->start_timestamp); + ctx->start_timestamp = -1; /* set later from actual timestamp */ + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: read tag at offset=%i", ctx->offset); + + /* read tag header */ + n = ngx_read_file(f, ngx_rtmp_flv_header, + sizeof(ngx_rtmp_flv_header), ctx->offset); + + if (n != sizeof(ngx_rtmp_flv_header)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read flv tag header"); + return NGX_DONE; + } + + /* parse header fields */ + ngx_memzero(&h, sizeof(h)); + + h.msid = NGX_RTMP_LIVE_MSID; + h.type = ngx_rtmp_flv_header[0]; + + size = 0; + + ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3); + ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_flv_header + 4, 3); + + ((u_char *) &h.timestamp)[3] = ngx_rtmp_flv_header[7]; + + ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4); + + last_timestamp = 0; + + switch (h.type) { + + case NGX_RTMP_MSG_AUDIO: + h.csid = NGX_RTMP_CSID_AUDIO; + last_timestamp = ctx->last_audio; + ctx->last_audio = h.timestamp; + break; + + case NGX_RTMP_MSG_VIDEO: + h.csid = NGX_RTMP_CSID_VIDEO; + last_timestamp = ctx->last_video; + ctx->last_video = h.timestamp; + break; + + default: + return NGX_OK; + } + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: read tag type=%i size=%uD timestamp=%uD " + "last_timestamp=%uD", + (ngx_int_t) h.type,size, h.timestamp, last_timestamp); + + lh = h; + lh.timestamp = last_timestamp; + + if (size > sizeof(ngx_rtmp_flv_buffer)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: too big message: %D>%uz", size, + sizeof(ngx_rtmp_flv_buffer)); + goto next; + } + + /* read tag body */ + n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, + ctx->offset - size - 4); + + if (n != (ssize_t) size) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read flv tag"); + return NGX_ERROR; + } + + /* prepare input chain */ + ngx_memzero(&in, sizeof(in)); + ngx_memzero(&in_buf, sizeof(in_buf)); + + in.buf = &in_buf; + in_buf.pos = ngx_rtmp_flv_buffer; + in_buf.last = ngx_rtmp_flv_buffer + size; + + /* output chain */ + out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); + + ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ? + &lh : NULL, out); + rc = ngx_rtmp_send_message(s, out, 0); + ngx_rtmp_free_shared_chain(cscf, out); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + ctx->msg_mask |= (1 << h.type); + +next: + if (ctx->start_timestamp == -1) { + ctx->start_timestamp = h.timestamp; + ctx->epoch = ngx_current_msec; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: start_timestamp=%i", ctx->start_timestamp); + return NGX_OK; + } + + buflen = (s->buflen ? s->buflen : NGX_RTMP_FLV_DEFAULT_BUFLEN); + end_timestamp = (ngx_current_msec - ctx->epoch) + + ctx->start_timestamp + buflen; + + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i", + h.timestamp > end_timestamp ? "schedule" : "advance", + h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0, + h.timestamp, end_timestamp, (ngx_int_t) buflen); + + /* too much data sent; schedule timeout */ + if (h.timestamp > end_timestamp) { + return h.timestamp - end_timestamp; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_flv_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_flv_ctx_t)); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_flv_module); + } + + ngx_memzero(ctx, sizeof(*ctx)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp) +{ + ngx_rtmp_flv_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: start timestamp=%ui", timestamp); + + ctx->start_timestamp = timestamp; + ctx->offset = -1; + ctx->msg_mask = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_flv_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: stop"); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_play_main_conf_t *pmcf; + ngx_rtmp_play_fmt_t **pfmt, *fmt; + + pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module); + + pfmt = ngx_array_push(&pmcf->fmts); + + if (pfmt == NULL) { + return NGX_ERROR; + } + + fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t)); + + if (fmt == NULL) { + return NGX_ERROR; + } + + *pfmt = fmt; + + ngx_str_set(&fmt->name, "flv-format"); + + ngx_str_null(&fmt->pfx); /* default fmt */ + ngx_str_set(&fmt->sfx, ".flv"); + + fmt->init = ngx_rtmp_flv_init; + fmt->start = ngx_rtmp_flv_start; + fmt->stop = ngx_rtmp_flv_stop; + fmt->send = ngx_rtmp_flv_send; + + return NGX_OK; +} diff --git a/ngx_rtmp_mp4_module.c b/ngx_rtmp_mp4_module.c index 810232b..a7b61c3 100644 --- a/ngx_rtmp_mp4_module.c +++ b/ngx_rtmp_mp4_module.c @@ -3,29 +3,18 @@ */ -#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_play_module.h" #include "ngx_rtmp_codec_module.h" #include "ngx_rtmp_streams.h" -static ngx_rtmp_play_pt next_play; -static ngx_rtmp_close_stream_pt next_close_stream; -static ngx_rtmp_seek_pt next_seek; -static ngx_rtmp_pause_pt next_pause; - - static ngx_int_t ngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf); -static void * ngx_rtmp_mp4_create_app_conf(ngx_conf_t *cf); -static char * ngx_rtmp_mp4_merge_app_conf(ngx_conf_t *cf, - void *parent, void *child); -static void ngx_rtmp_mp4_send(ngx_event_t *e); -static ngx_int_t ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_int_t offset); -static ngx_int_t ngx_rtmp_mp4_stop(ngx_rtmp_session_t *s); - - -typedef struct { - ngx_str_t root; -} ngx_rtmp_mp4_app_conf_t; +static ngx_int_t ngx_rtmp_mp4_init(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_mp4_done(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_file_t *f, + ngx_uint_t offset); +static ngx_int_t ngx_rtmp_mp4_stop(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_mp4_send(ngx_rtmp_session_t *s, ngx_file_t *f); #pragma pack(push,4) @@ -165,8 +154,6 @@ typedef struct { typedef struct { - ngx_file_t file; - void *mmaped; size_t mmaped_size; @@ -183,8 +170,6 @@ typedef struct { ngx_uint_t sample_rate; uint32_t start_timestamp, epoch; - - ngx_event_t write_evt; } ngx_rtmp_mp4_ctx_t; @@ -312,22 +297,9 @@ typedef struct { static ngx_rtmp_mp4_descriptor_t ngx_rtmp_mp4_descriptors[] = { - { 0x03, ngx_rtmp_mp4_parse_es }, /* MPEG ES Descriptor */ - { 0x04, ngx_rtmp_mp4_parse_dc }, /* MPEG DecoderConfig Descriptor */ - { 0x05, ngx_rtmp_mp4_parse_ds } /* MPEG DecoderSpecific Descriptor */ -}; - - -static ngx_command_t ngx_rtmp_mp4_commands[] = { - - { ngx_string("play_mp4"), - 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_rtmp_mp4_app_conf_t, root), - NULL }, - - ngx_null_command + { 0x03, ngx_rtmp_mp4_parse_es }, /* MPEG ES Descriptor */ + { 0x04, ngx_rtmp_mp4_parse_dc }, /* MPEG DecoderConfig Descriptor */ + { 0x05, ngx_rtmp_mp4_parse_ds } /* MPEG DecoderSpec Descriptor */ }; @@ -338,15 +310,15 @@ static ngx_rtmp_module_t ngx_rtmp_mp4_module_ctx = { NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ - ngx_rtmp_mp4_create_app_conf, /* create app configuration */ - ngx_rtmp_mp4_merge_app_conf /* merge app configuration */ + NULL, /* create app configuration */ + NULL /* merge app configuration */ }; ngx_module_t ngx_rtmp_mp4_module = { NGX_MODULE_V1, &ngx_rtmp_mp4_module_ctx, /* module context */ - ngx_rtmp_mp4_commands, /* module directives */ + NULL, /* module directives */ NGX_RTMP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ @@ -359,33 +331,6 @@ ngx_module_t ngx_rtmp_mp4_module = { }; -static void * -ngx_rtmp_mp4_create_app_conf(ngx_conf_t *cf) -{ - ngx_rtmp_mp4_app_conf_t *pacf; - - pacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_mp4_app_conf_t)); - - if (pacf == NULL) { - return NULL; - } - - return pacf; -} - - -static char * -ngx_rtmp_mp4_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) -{ - ngx_rtmp_mp4_app_conf_t *prev = parent; - ngx_rtmp_mp4_app_conf_t *conf = child; - - ngx_conf_merge_str_value(conf->root, prev->root, ""); - - return NGX_CONF_OK; -} - - static ngx_int_t ngx_rtmp_mp4_parse_trak(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { @@ -1218,97 +1163,6 @@ ngx_rtmp_mp4_parse(ngx_rtmp_session_t *s, u_char *pos, u_char *last) } -static ngx_int_t -ngx_rtmp_mp4_init(ngx_rtmp_session_t *s) -{ - ngx_rtmp_mp4_ctx_t *ctx; - uint32_t hdr[2]; - ssize_t n; - size_t offset, page_offset, size; - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); - - if (ctx == NULL || ctx->mmaped || ctx->file.fd == NGX_INVALID_FILE) { - return NGX_OK; - } - - offset = 0; - size = 0; - - for ( ;; ) { - n = ngx_read_file(&ctx->file, (u_char *) &hdr, sizeof(hdr), offset); - - if (n != sizeof(hdr)) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, - "mp4: error reading file at offset=%uz " - "while searching for moov box", offset); - return NGX_ERROR; - } - - size = ngx_rtmp_r32(hdr[0]); - - if (hdr[1] == ngx_rtmp_mp4_make_tag('m','o','o','v')) { - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "mp4: found moov box"); - break; - } - - ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "mp4: skipping box '%*s'", 4, hdr + 1); - - offset += size; - } - - if (size < 8) { - return NGX_ERROR; - } - - size -= 8; - offset += 8; - - page_offset = offset & (ngx_pagesize - 1); - ctx->mmaped_size = page_offset + size; - - ctx->mmaped = mmap(NULL, ctx->mmaped_size, PROT_READ, MAP_SHARED, - ctx->file.fd, offset - page_offset); - - if (ctx->mmaped == MAP_FAILED) { - ctx->mmaped = NULL; - ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, - "mp4: mmap failed at offset=%ui, size=%uz", - offset, size); - return NGX_ERROR; - } - - return ngx_rtmp_mp4_parse(s, (u_char *) ctx->mmaped + page_offset, - (u_char *) ctx->mmaped + page_offset + size); -} - - -static ngx_int_t -ngx_rtmp_mp4_done(ngx_rtmp_session_t *s) -{ - ngx_rtmp_mp4_ctx_t *ctx; - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); - - if (ctx == NULL || ctx->mmaped == NULL) { - return NGX_OK; - } - - if (munmap(ctx->mmaped, ctx->mmaped_size)) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, - "mp4: munmap failed"); - return NGX_ERROR; - } - - ctx->mmaped = NULL; - ctx->mmaped_size = 0; - - return NGX_OK; -} - - static ngx_int_t ngx_rtmp_mp4_next_time(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { @@ -2026,17 +1880,40 @@ ngx_rtmp_mp4_send_meta(ngx_rtmp_session_t *s) h.type = NGX_RTMP_MSG_AMF_META; ngx_rtmp_prepare_message(s, &h, NULL, out); - ngx_rtmp_send_message(s, out, 0); + rc = ngx_rtmp_send_message(s, out, 0); ngx_rtmp_free_shared_chain(cscf, out); + return rc; +} + + +static ngx_int_t +ngx_rtmp_mp4_seek_track(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t, + ngx_int_t timestamp) +{ + ngx_rtmp_mp4_cursor_t *cr; + + cr = &t->cursor; + ngx_memzero(cr, sizeof(*cr)); + + if (ngx_rtmp_mp4_seek_time(s, t, ngx_rtmp_mp4_from_rtmp_timestamp( + t, timestamp)) != NGX_OK || + ngx_rtmp_mp4_seek_key(s, t) != NGX_OK || + ngx_rtmp_mp4_seek_chunk(s, t) != NGX_OK || + ngx_rtmp_mp4_seek_size(s, t) != NGX_OK || + ngx_rtmp_mp4_seek_delay(s, t) != NGX_OK) + { + return NGX_ERROR; + } + + cr->valid = 1; return NGX_OK; } -static void -ngx_rtmp_mp4_send(ngx_event_t *e) +static ngx_int_t +ngx_rtmp_mp4_send(ngx_rtmp_session_t *s, ngx_file_t *f) { - ngx_rtmp_session_t *s; ngx_rtmp_mp4_ctx_t *ctx; ngx_buf_t in_buf; ngx_rtmp_header_t h, lh; @@ -2052,21 +1929,22 @@ ngx_rtmp_mp4_send(ngx_event_t *e) ngx_int_t rc; ngx_uint_t n, abs_frame, active; - s = e->data; - cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx == NULL) { - return; + return NGX_ERROR; } if (!ctx->meta_sent) { - ngx_rtmp_mp4_send_meta(s); - ctx->meta_sent = 1; - active = 1; - goto again; + rc = ngx_rtmp_mp4_send_meta(s); + + if (rc == NGX_OK) { + ctx->meta_sent = 1; + } + + return rc; } buflen = (s->buflen ? s->buflen : NGX_RTMP_MP4_DEFAULT_BUFLEN); @@ -2144,7 +2022,7 @@ ngx_rtmp_mp4_send(ngx_event_t *e) ngx_rtmp_free_shared_chain(cscf, out); if (rc == NGX_AGAIN) { - goto full; + return NGX_AGAIN; } t->header_sent = 1; @@ -2191,7 +2069,7 @@ ngx_rtmp_mp4_send(ngx_event_t *e) continue; } - ret = ngx_read_file(&ctx->file, ngx_rtmp_mp4_buffer + fhdr_size, + ret = ngx_read_file(f, ngx_rtmp_mp4_buffer + fhdr_size, cr->size, cr->offset); if (ret != (ssize_t) cr->size) { @@ -2211,7 +2089,7 @@ ngx_rtmp_mp4_send(ngx_event_t *e) ngx_rtmp_free_shared_chain(cscf, out); if (rc == NGX_AGAIN) { - goto full; + return NGX_AGAIN; } if (ngx_rtmp_mp4_next(s, t) != NGX_OK) { @@ -2229,58 +2107,114 @@ next: } if (sched) { - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "mp4: scheduling %uD", sched); - ngx_add_timer(e, sched); - return; + return sched; } -again: - if (active) { - ngx_post_event(e, &ngx_posted_events); - return; - } - - ngx_rtmp_send_user_stream_eof(s, NGX_RTMP_MSID); - - ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", "Stopped"); - - return; - -full: - ngx_post_event(e, &s->posted_dry_events); - - return; - + return active ? NGX_OK : NGX_DONE; } static ngx_int_t -ngx_rtmp_mp4_seek_track(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t, - ngx_int_t timestamp) +ngx_rtmp_mp4_init(ngx_rtmp_session_t *s, ngx_file_t *f) { - ngx_rtmp_mp4_cursor_t *cr; + ngx_rtmp_mp4_ctx_t *ctx; + uint32_t hdr[2]; + ssize_t n; + size_t offset, page_offset, size; - cr = &t->cursor; - ngx_memzero(cr, sizeof(*cr)); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); - if (ngx_rtmp_mp4_seek_time(s, t, ngx_rtmp_mp4_from_rtmp_timestamp( - t, timestamp)) != NGX_OK || - ngx_rtmp_mp4_seek_key(s, t) != NGX_OK || - ngx_rtmp_mp4_seek_chunk(s, t) != NGX_OK || - ngx_rtmp_mp4_seek_size(s, t) != NGX_OK || - ngx_rtmp_mp4_seek_delay(s, t) != NGX_OK) - { + if (ctx == NULL) { + ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_mp4_ctx_t)); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_mp4_module); + } + + ngx_memzero(ctx, sizeof(*ctx)); + + offset = 0; + size = 0; + + for ( ;; ) { + n = ngx_read_file(f, (u_char *) &hdr, sizeof(hdr), offset); + + if (n != sizeof(hdr)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "mp4: error reading file at offset=%uz " + "while searching for moov box", offset); + return NGX_ERROR; + } + + size = ngx_rtmp_r32(hdr[0]); + + if (hdr[1] == ngx_rtmp_mp4_make_tag('m','o','o','v')) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: found moov box"); + break; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: skipping box '%*s'", 4, hdr + 1); + + offset += size; + } + + if (size < 8) { return NGX_ERROR; } - cr->valid = 1; + size -= 8; + offset += 8; + + page_offset = offset & (ngx_pagesize - 1); + ctx->mmaped_size = page_offset + size; + + ctx->mmaped = mmap(NULL, ctx->mmaped_size, PROT_READ, MAP_SHARED, + f->fd, offset - page_offset); + + if (ctx->mmaped == MAP_FAILED) { + ctx->mmaped = NULL; + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "mp4: mmap failed at offset=%ui, size=%uz", + offset, size); + return NGX_ERROR; + } + + return ngx_rtmp_mp4_parse(s, (u_char *) ctx->mmaped + page_offset, + (u_char *) ctx->mmaped + page_offset + size); +} + + +static ngx_int_t +ngx_rtmp_mp4_done(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_mp4_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx == NULL || ctx->mmaped == NULL) { + return NGX_OK; + } + + if (munmap(ctx->mmaped, ctx->mmaped_size)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "mp4: munmap failed"); + return NGX_ERROR; + } + + ctx->mmaped = NULL; + ctx->mmaped_size = 0; + return NGX_OK; } static ngx_int_t -ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_int_t timestamp) +ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp) { ngx_rtmp_mp4_ctx_t *ctx; ngx_uint_t n; @@ -2292,13 +2226,7 @@ ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_int_t timestamp) } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "mp4: start timestamp=%i", timestamp); - - ngx_rtmp_mp4_stop(s); - - if (timestamp < 0) { - timestamp = 0; - } + "mp4: start timestamp=%ui", timestamp); for (n = 0; n < ctx->ntracks; ++n) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, @@ -2310,225 +2238,52 @@ ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_int_t timestamp) ctx->epoch = ngx_current_msec; ctx->start_timestamp = timestamp; - ngx_post_event((&ctx->write_evt), &ngx_posted_events) - return NGX_OK; } static ngx_int_t -ngx_rtmp_mp4_stop(ngx_rtmp_session_t *s) +ngx_rtmp_mp4_stop(ngx_rtmp_session_t *s, ngx_file_t *f) { - ngx_rtmp_mp4_ctx_t *ctx; - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); - - if (ctx == NULL) { - return NGX_OK; - } - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: stop"); - if (ctx->write_evt.timer_set) { - ngx_del_timer(&ctx->write_evt); - } - - if (ctx->write_evt.prev) { - ngx_delete_posted_event((&ctx->write_evt)); - } - return NGX_OK; } -static ngx_int_t -ngx_rtmp_mp4_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) -{ - ngx_rtmp_mp4_ctx_t *ctx; - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); - - if (ctx == NULL) { - goto next; - } - - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "mp4: close_stream"); - - ngx_rtmp_mp4_stop(s); - - ngx_rtmp_mp4_done(s); - - if (ctx->file.fd != NGX_INVALID_FILE) { - ngx_close_file(ctx->file.fd); - ctx->file.fd = NGX_INVALID_FILE; - } - -next: - return next_close_stream(s, v); -} - - -static ngx_int_t -ngx_rtmp_mp4_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v) -{ - ngx_rtmp_mp4_ctx_t *ctx; - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); - - if (ctx == NULL || ctx->file.fd == NGX_INVALID_FILE) { - goto next; - } - - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "mp4: seek timestamp=%i", (ngx_int_t) v->offset); - - ngx_rtmp_mp4_start(s, v->offset); - -next: - return next_seek(s, v); -} - - -static ngx_int_t -ngx_rtmp_mp4_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v) -{ - ngx_rtmp_mp4_ctx_t *ctx; - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); - - if (ctx == NULL || ctx->file.fd == NGX_INVALID_FILE) { - goto next; - } - - ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "mp4: pause=%i timestamp=%i", - (ngx_int_t) v->pause, (ngx_int_t) v->position); - - if (v->pause) { - ngx_rtmp_mp4_stop(s); - } else { - ngx_rtmp_mp4_start(s, v->position); - } - -next: - return next_pause(s, v); -} - - -static ngx_int_t -ngx_rtmp_mp4_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) -{ - ngx_rtmp_mp4_app_conf_t *pacf; - ngx_rtmp_mp4_ctx_t *ctx; - u_char *p; - ngx_event_t *e; - size_t len; - static u_char path[NGX_MAX_PATH]; - u_char *name; - - pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_mp4_module); - - if (pacf == NULL || pacf->root.len == 0) { - goto next; - } - - if (ngx_strncasecmp(v->name, (u_char *) "mp4:", sizeof("mp4:") - 1) == 0) { - name = v->name + sizeof("mp4:") - 1; - goto ok; - } - - len = ngx_strlen(v->name); - - if (len >= sizeof(".mp4") && - ngx_strncasecmp(v->name + len - sizeof(".mp4") + 1, (u_char *) ".mp4", - sizeof(".mp4") - 1)) - { - name = v->name; - goto ok; - } - - goto next; -ok: - - ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "mp4: play name='%s' timestamp=%i", - name, (ngx_int_t) v->start); - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); - - if (ctx && ctx->file.fd != NGX_INVALID_FILE) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "mp4: already playing"); - goto next; - } - - /* check for double-dot in name; - * we should not move out of play directory */ - for (p = 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, - "mp4: bad name '%s'", name); - return NGX_ERROR; - } - } - - if (ctx == NULL) { - ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_mp4_ctx_t)); - ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_mp4_module); - } - ngx_memzero(ctx, sizeof(*ctx)); - - ctx->file.log = s->connection->log; - - p = ngx_snprintf(path, sizeof(path), "%V/%s", &pacf->root, name); - *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, 0, - "mp4: error opening file %s", path); - goto next; - } - - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "mp4: opened file '%s'", path); - - e = &ctx->write_evt; - e->data = s; - e->handler = ngx_rtmp_mp4_send; - e->log = s->connection->log; - - ngx_rtmp_send_user_recorded(s, 1); - - ngx_rtmp_mp4_init(s); - - ngx_rtmp_mp4_start(s, v->start); - -next: - return next_play(s, v); -} - - static ngx_int_t ngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf) { - next_play = ngx_rtmp_play; - ngx_rtmp_play = ngx_rtmp_mp4_play; + ngx_rtmp_play_main_conf_t *pmcf; + ngx_rtmp_play_fmt_t **pfmt, *fmt; - next_close_stream = ngx_rtmp_close_stream; - ngx_rtmp_close_stream = ngx_rtmp_mp4_close_stream; + pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module); - next_seek = ngx_rtmp_seek; - ngx_rtmp_seek = ngx_rtmp_mp4_seek; + pfmt = ngx_array_push(&pmcf->fmts); - next_pause = ngx_rtmp_pause; - ngx_rtmp_pause = ngx_rtmp_mp4_pause; + if (pfmt == NULL) { + return NGX_ERROR; + } + + fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t)); + + if (fmt == NULL) { + return NGX_ERROR; + } + + *pfmt = fmt; + + ngx_str_set(&fmt->name, "mp4-format"); + + ngx_str_set(&fmt->pfx, "mp4:"); + ngx_str_set(&fmt->sfx, ".mp4"); + + fmt->init = ngx_rtmp_mp4_init; + fmt->done = ngx_rtmp_mp4_done; + fmt->start = ngx_rtmp_mp4_start; + fmt->stop = ngx_rtmp_mp4_stop; + fmt->send = ngx_rtmp_mp4_send; return NGX_OK; } diff --git a/ngx_rtmp_play_module.c b/ngx_rtmp_play_module.c index c631ed5..960b19d 100644 --- a/ngx_rtmp_play_module.c +++ b/ngx_rtmp_play_module.c @@ -3,8 +3,9 @@ */ +#include "ngx_rtmp_play_module.h" #include "ngx_rtmp_cmd_module.h" -#include "ngx_rtmp_live_module.h" +#include "ngx_rtmp_streams.h" static ngx_rtmp_play_pt next_play; @@ -13,55 +14,16 @@ static ngx_rtmp_seek_pt next_seek; static ngx_rtmp_pause_pt next_pause; +static void *ngx_rtmp_play_create_main_conf(ngx_conf_t *cf); static ngx_int_t ngx_rtmp_play_postconfiguration(ngx_conf_t *cf); static void * ngx_rtmp_play_create_app_conf(ngx_conf_t *cf); static char * ngx_rtmp_play_merge_app_conf(ngx_conf_t *cf, - void *parent, void *child); -static void ngx_rtmp_play_send(ngx_event_t *e); -static void ngx_rtmp_play_read_meta(ngx_rtmp_session_t *s); -static ngx_int_t ngx_rtmp_play_start(ngx_rtmp_session_t *s, ngx_int_t offset); + void *parent, void *child); +static ngx_int_t ngx_rtmp_play_init(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_play_done(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_play_start(ngx_rtmp_session_t *s, double timestamp); static ngx_int_t ngx_rtmp_play_stop(ngx_rtmp_session_t *s); -static ngx_int_t ngx_rtmp_play_timestamp_to_offset(ngx_rtmp_session_t *s, - ngx_int_t timestamp); - - -typedef struct { - ngx_str_t root; -} ngx_rtmp_play_app_conf_t; - - -typedef struct { - ngx_uint_t nelts; - ngx_uint_t offset; -} ngx_rtmp_play_index_t; - - -typedef struct { - ngx_file_t file; - ngx_int_t offset; - ngx_int_t start_timestamp; - ngx_event_t write_evt; - uint32_t last_audio; - uint32_t last_video; - ngx_uint_t msg_mask; - uint32_t epoch; - - unsigned meta_read:1; - ngx_rtmp_play_index_t filepositions; - ngx_rtmp_play_index_t times; -} ngx_rtmp_play_ctx_t; - - -#define NGX_RTMP_PLAY_BUFFER (1024*1024) -#define NGX_RTMP_PLAY_DEFAULT_BUFLEN 1000 -#define NGX_RTMP_PLAY_TAG_HEADER 11 -#define NGX_RTMP_PLAY_DATA_OFFSET 13 - - -static u_char ngx_rtmp_play_buffer[ - NGX_RTMP_PLAY_BUFFER]; -static u_char ngx_rtmp_play_header[ - NGX_RTMP_PLAY_TAG_HEADER]; +static void ngx_rtmp_play_send(ngx_event_t *e); static ngx_command_t ngx_rtmp_play_commands[] = { @@ -80,7 +42,7 @@ static ngx_command_t ngx_rtmp_play_commands[] = { static ngx_rtmp_module_t ngx_rtmp_play_module_ctx = { NULL, /* preconfiguration */ ngx_rtmp_play_postconfiguration, /* postconfiguration */ - NULL, /* create main configuration */ + ngx_rtmp_play_create_main_conf, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ @@ -105,12 +67,35 @@ ngx_module_t ngx_rtmp_play_module = { }; +static void * +ngx_rtmp_play_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_play_main_conf_t *pmcf; + + pmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_main_conf_t)); + + if (pmcf == NULL) { + return NULL; + } + + if (ngx_array_init(&pmcf->fmts, cf->pool, 1, + sizeof(ngx_rtmp_play_fmt_t *)) + != NGX_OK) + { + return NULL; + } + + return pmcf; +} + + static void * ngx_rtmp_play_create_app_conf(ngx_conf_t *cf) { ngx_rtmp_play_app_conf_t *pacf; pacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_app_conf_t)); + if (pacf == NULL) { return NULL; } @@ -131,453 +116,123 @@ ngx_rtmp_play_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) } -static ngx_int_t -ngx_rtmp_play_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_play_index_t *idx) +static void +ngx_rtmp_play_send(ngx_event_t *e) { - uint32_t nelts; - ngx_buf_t *b; + ngx_rtmp_session_t *s = e->data; + ngx_rtmp_play_ctx_t *ctx; + ngx_int_t rc; - /* we have AMF array pointed by context; - * need to extract its size (4 bytes) & - * save offset of actual array data */ - b = ctx->link->buf; - if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) { + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx == NULL || ctx->fmt == NULL || ctx->fmt->send == NULL) { + return; + } + + rc = ctx->fmt->send(s, &ctx->file); + + if (rc > 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: send schedule %i", rc); + + ngx_add_timer(e, rc); + return; + } + + if (rc == NGX_AGAIN) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: send buffer full"); + + ngx_post_event(e, &s->posted_dry_events); + return; + } + + if (rc == NGX_OK) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: send restart"); + + ngx_post_event(e, &ngx_posted_events); + return; + } + + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: send done"); + + ngx_rtmp_send_user_stream_eof(s, NGX_RTMP_MSID); + + ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", "Stopped"); +} + + +static ngx_int_t +ngx_rtmp_play_init(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx == NULL) { return NGX_ERROR; } - ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4); - idx->nelts = nelts; - idx->offset = ctx->offset + 4; + if (ctx->fmt && ctx->fmt->init && + ctx->fmt->init(s, &ctx->file) != NGX_OK) + { + return NGX_ERROR; + } return NGX_OK; } static ngx_int_t -ngx_rtmp_play_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in) +ngx_rtmp_play_done(ngx_rtmp_session_t *s) { ngx_rtmp_play_ctx_t *ctx; - static ngx_rtmp_amf_ctx_t filepositions_ctx; - static ngx_rtmp_amf_ctx_t times_ctx; - - static ngx_rtmp_amf_elt_t in_keyframes[] = { - - { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT, - ngx_string("filepositions"), - &filepositions_ctx, 0 }, - - { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT, - ngx_string("times"), - ×_ctx, 0 } - }; - - static ngx_rtmp_amf_elt_t in_inf[] = { - - { NGX_RTMP_AMF_OBJECT, - ngx_string("keyframes"), - in_keyframes, sizeof(in_keyframes) } - }; - - static ngx_rtmp_amf_elt_t in_elts[] = { - - { NGX_RTMP_AMF_STRING, - ngx_null_string, - NULL, 0 }, - - { NGX_RTMP_AMF_OBJECT, - ngx_null_string, - in_inf, sizeof(in_inf) }, - }; - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); - if (ctx == NULL || in == NULL) { - return NGX_OK; - } - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: init index"); - - ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx)); - ngx_memzero(×_ctx, sizeof(times_ctx)); - - if (ngx_rtmp_receive_amf(s, in, in_elts, - sizeof(in_elts) / sizeof(in_elts[0]))) - { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: init index error"); - return NGX_OK; - } - - if (filepositions_ctx.link && ngx_rtmp_play_fill_index(&filepositions_ctx, - &ctx->filepositions) - != NGX_OK) - { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: failed to init filepositions"); + if (ctx == NULL) { return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: filepositions nelts=%ui offset=%ui", - ctx->filepositions.nelts, ctx->filepositions.offset); - if (times_ctx.link && ngx_rtmp_play_fill_index(×_ctx, - &ctx->times) - != NGX_OK) + if (ctx->fmt && ctx->fmt->done && + ctx->fmt->done(s, &ctx->file) != NGX_OK) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: failed to init times"); return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: times nelts=%ui offset=%ui", - ctx->times.nelts, ctx->times.offset); - return NGX_OK; -} - - -static double -ngx_rtmp_play_index_value(void *src) -{ - double v; - - ngx_rtmp_rmemcpy(&v, src, 8); - return v; + return NGX_OK; } static ngx_int_t -ngx_rtmp_play_timestamp_to_offset(ngx_rtmp_session_t *s, ngx_int_t timestamp) +ngx_rtmp_play_start(ngx_rtmp_session_t *s, double timestamp) { ngx_rtmp_play_ctx_t *ctx; - ssize_t n, size; - ngx_uint_t offset, index, ret, nelts; - double v; + ngx_uint_t ts; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + if (ctx == NULL) { - goto rewind; + return NGX_ERROR; } - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: lookup index start timestamp=%i", - timestamp); - - if (ctx->meta_read == 0) { - ngx_rtmp_play_read_meta(s); - ctx->meta_read = 1; - } - - if (timestamp <= 0 || ctx->filepositions.nelts == 0 - || ctx->times.nelts == 0) - { - goto rewind; - } - - /* read index table from file given offset */ - offset = NGX_RTMP_PLAY_DATA_OFFSET + NGX_RTMP_PLAY_TAG_HEADER - + ctx->times.offset; - - /* index should fit in the buffer */ - nelts = ngx_min(ctx->times.nelts, sizeof(ngx_rtmp_play_buffer) / 9); - size = nelts * 9; - n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, size, offset); - if (n != size) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: could not read times index"); - goto rewind; - } - - /*TODO: implement binary search */ - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: lookup times nelts=%ui", nelts); - - for (index = 0; index < nelts - 1; ++index) { - v = ngx_rtmp_play_index_value(ngx_rtmp_play_buffer - + index * 9 + 1) * 1000; - - ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: lookup times index=%ui value=%ui", - index, (ngx_uint_t) v); - - if (timestamp < v) { - break; - } - } - - if (index >= ctx->filepositions.nelts) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: index out of bounds: %ui>=%ui", - index, ctx->filepositions.nelts); - goto rewind; - } - - /* take value from filepositions */ - offset = NGX_RTMP_PLAY_DATA_OFFSET + NGX_RTMP_PLAY_TAG_HEADER - + ctx->filepositions.offset + index * 9; - n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, 8, offset + 1); - if (n != 8) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: could not read filepositions index"); - goto rewind; - } - ret = ngx_rtmp_play_index_value(ngx_rtmp_play_buffer); - - ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: lookup index timestamp=%i offset=%ui", - timestamp, ret); - - return ret; - -rewind: - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: lookup index timestamp=%i offset=begin", - timestamp); - - return NGX_RTMP_PLAY_DATA_OFFSET; -} - - -static void -ngx_rtmp_play_read_meta(ngx_rtmp_session_t *s) -{ - ngx_rtmp_play_ctx_t *ctx; - ssize_t n; - ngx_rtmp_header_t h; - ngx_chain_t *out, in; - ngx_buf_t in_buf; - ngx_rtmp_core_srv_conf_t *cscf; - uint32_t size; - - cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); - if (ctx == NULL) { - return; - } - - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: read meta"); - - /* read tag header */ - n = ngx_read_file(&ctx->file, ngx_rtmp_play_header, - sizeof(ngx_rtmp_play_header), NGX_RTMP_PLAY_DATA_OFFSET); - if (n != sizeof(ngx_rtmp_play_header)) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: could not read metadata tag header"); - return; - } - - if (ngx_rtmp_play_header[0] != NGX_RTMP_MSG_AMF_META) { - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: first tag is not metadata, giving up"); - return; - } - - ngx_memzero(&h, sizeof(h)); - h.type = NGX_RTMP_MSG_AMF_META; - h.msid = NGX_RTMP_LIVE_MSID; - h.csid = NGX_RTMP_LIVE_CSID_META; - size = 0; - ngx_rtmp_rmemcpy(&size, ngx_rtmp_play_header + 1, 3); - - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: metadata size=%D", size); - - if (size > sizeof(ngx_rtmp_play_buffer)) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: too big metadata"); - return; - } - - /* read metadata */ - n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, - size, sizeof(ngx_rtmp_play_header) + - NGX_RTMP_PLAY_DATA_OFFSET); - if (n != (ssize_t) size) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: could not read metadata"); - return; - } - - /* prepare input chain */ - ngx_memzero(&in, sizeof(in)); - ngx_memzero(&in_buf, sizeof(in_buf)); - in.buf = &in_buf; - in_buf.pos = ngx_rtmp_play_buffer; - in_buf.last = ngx_rtmp_play_buffer + size; - - ngx_rtmp_play_init_index(s, &in); - - /* output chain */ - out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); - ngx_rtmp_prepare_message(s, &h, NULL, out); - ngx_rtmp_send_message(s, out, 0); - ngx_rtmp_free_shared_chain(cscf, out); -} - - -static void -ngx_rtmp_play_send(ngx_event_t *e) -{ - ngx_rtmp_session_t *s; - ngx_rtmp_play_ctx_t *ctx; - uint32_t last_timestamp; - ngx_rtmp_header_t h, lh; - ngx_rtmp_core_srv_conf_t *cscf; - ngx_chain_t *out, in; - ngx_buf_t in_buf; - ssize_t n; - uint32_t buflen, end_timestamp, size; - - s = e->data; - - cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); - if (ctx == NULL) { - return; - } - - if (ctx->offset == -1) { - ctx->offset = ngx_rtmp_play_timestamp_to_offset(s, - ctx->start_timestamp); - ctx->start_timestamp = -1; /* set later from actual timestamp */ - } - - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: read tag at offset=%i", ctx->offset); - - /* read tag header */ - n = ngx_read_file(&ctx->file, ngx_rtmp_play_header, - sizeof(ngx_rtmp_play_header), ctx->offset); - if (n != sizeof(ngx_rtmp_play_header)) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: could not read flv tag header"); - ngx_rtmp_send_user_stream_eof(s, NGX_RTMP_MSID); - ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", "Stopped"); - return; - } - - /* parse header fields */ - ngx_memzero(&h, sizeof(h)); - h.msid = NGX_RTMP_LIVE_MSID; - h.type = ngx_rtmp_play_header[0]; - size = 0; - ngx_rtmp_rmemcpy(&size, ngx_rtmp_play_header + 1, 3); - ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_play_header + 4, 3); - ((u_char *) &h.timestamp)[3] = ngx_rtmp_play_header[7]; - - ctx->offset += (sizeof(ngx_rtmp_play_header) + size + 4); - - last_timestamp = 0; - - switch (h.type) { - case NGX_RTMP_MSG_AUDIO: - h.csid = NGX_RTMP_LIVE_CSID_AUDIO; - last_timestamp = ctx->last_audio; - ctx->last_audio = h.timestamp; - break; - - case NGX_RTMP_MSG_VIDEO: - h.csid = NGX_RTMP_LIVE_CSID_VIDEO; - last_timestamp = ctx->last_video; - ctx->last_video = h.timestamp; - break; - - default: - goto skip; - } - - ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: read tag type=%i size=%uD timestamp=%uD " - "last_timestamp=%uD", - (ngx_int_t) h.type,size, h.timestamp, last_timestamp); - - lh = h; - lh.timestamp = last_timestamp; - - if (size > sizeof(ngx_rtmp_play_buffer)) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: too big message: %D>%uz", size, - sizeof(ngx_rtmp_play_buffer)); - goto next; - } - - /* read tag body */ - n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, size, - ctx->offset - size - 4); - if (n != (ssize_t) size) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "play: could not read flv tag"); - return; - } - - /* prepare input chain */ - ngx_memzero(&in, sizeof(in)); - ngx_memzero(&in_buf, sizeof(in_buf)); - in.buf = &in_buf; - in_buf.pos = ngx_rtmp_play_buffer; - in_buf.last = ngx_rtmp_play_buffer + size; - - /* output chain */ - out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); - ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ? - &lh : NULL, out); - ngx_rtmp_send_message(s, out, 0); /* TODO: priority */ - ngx_rtmp_free_shared_chain(cscf, out); - - ctx->msg_mask |= (1 << h.type); - -next: - if (ctx->start_timestamp == -1) { - ctx->start_timestamp = h.timestamp; - ctx->epoch = ngx_current_msec; - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: start_timestamp=%i", ctx->start_timestamp); - goto skip; - } - - buflen = (s->buflen ? s->buflen : NGX_RTMP_PLAY_DEFAULT_BUFLEN); - end_timestamp = (ngx_current_msec - ctx->epoch) + - ctx->start_timestamp + buflen; - - ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i", - h.timestamp > end_timestamp ? "schedule" : "advance", - h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0, - h.timestamp, end_timestamp, (ngx_int_t) buflen); - - /* too much data sent; schedule timeout */ - if (h.timestamp > end_timestamp) { - ngx_add_timer(e, h.timestamp - end_timestamp); - return; - } - -skip: - ngx_post_event(e, &ngx_posted_events); -} - - -static ngx_int_t -ngx_rtmp_play_start(ngx_rtmp_session_t *s, ngx_int_t timestamp) -{ - ngx_rtmp_play_ctx_t *ctx; - - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); - if (ctx == NULL) { - return NGX_OK; - } - - ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: start timestamp=%i", timestamp); - ngx_rtmp_play_stop(s); - ctx->start_timestamp = timestamp; - ctx->offset = -1; - ctx->msg_mask = 0; + ts = (timestamp > 0 ? (ngx_uint_t) timestamp : 0); - ngx_post_event((&ctx->write_evt), &ngx_posted_events) + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: start timestamp=%ui", ts); + + if (ctx->fmt && ctx->fmt->start && + ctx->fmt->start(s, &ctx->file, ts) != NGX_OK) + { + return NGX_ERROR; + } + + ngx_post_event((&ctx->send_evt), &ngx_posted_events); return NGX_OK; } @@ -589,19 +244,26 @@ ngx_rtmp_play_stop(ngx_rtmp_session_t *s) ngx_rtmp_play_ctx_t *ctx; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + if (ctx == NULL) { - return NGX_OK; + return NGX_ERROR; } ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: stop"); + "play: stop"); - if (ctx->write_evt.timer_set) { - ngx_del_timer(&ctx->write_evt); + if (ctx->send_evt.timer_set) { + ngx_del_timer(&ctx->send_evt); } - if (ctx->write_evt.prev) { - ngx_delete_posted_event((&ctx->write_evt)); + if (ctx->send_evt.prev) { + ngx_delete_posted_event((&ctx->send_evt)); + } + + if (ctx->fmt && ctx->fmt->stop && + ctx->fmt->stop(s, &ctx->file) != NGX_OK) + { + return NGX_ERROR; } return NGX_OK; @@ -614,15 +276,18 @@ ngx_rtmp_play_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) ngx_rtmp_play_ctx_t *ctx; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + if (ctx == NULL) { goto next; } ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: close_stream"); + "play: close_stream"); ngx_rtmp_play_stop(s); + ngx_rtmp_play_done(s); + if (ctx->file.fd != NGX_INVALID_FILE) { ngx_close_file(ctx->file.fd); ctx->file.fd = NGX_INVALID_FILE; @@ -644,7 +309,7 @@ ngx_rtmp_play_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v) } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: seek timestamp=%i", (ngx_int_t) v->offset); + "play: seek offset=%f", v->offset); ngx_rtmp_play_start(s, v->offset); @@ -659,13 +324,14 @@ ngx_rtmp_play_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v) ngx_rtmp_play_ctx_t *ctx; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + if (ctx == NULL || ctx->file.fd == NGX_INVALID_FILE) { goto next; } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: pause=%i timestamp=%i", - (ngx_int_t) v->pause, (ngx_int_t) v->position); + "play: pause=%i timestamp=%f", + (ngx_int_t) v->pause, v->position); if (v->pause) { ngx_rtmp_play_stop(s); @@ -681,20 +347,28 @@ next: 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_event_t *e; - size_t len, slen; + 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) { goto next; } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: play name='%s' timestamp=%i", + "play: play name='%s' timestamp=%i", v->name, (ngx_int_t) v->start); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); @@ -722,38 +396,92 @@ ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) 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; - /* make file path */ - len = ngx_strlen(v->name); - slen = sizeof(".flv") - 1; - p = ngx_snprintf(path, sizeof(path), "%V/%s%s", &pacf->root, v->name, - len > slen && ngx_strncasecmp((u_char *) ".flv", - v->name + len - slen, slen) == 0 ? "" : ".flv"); - *p = 0; + name.len = ngx_strlen(v->name); + name.data = v->name; - /* open file */ - ctx->file.fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN, - NGX_FILE_DEFAULT_ACCESS); - if (ctx->file.fd == NGX_INVALID_FILE) { + 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: error opening file %s", path); + "play: fmt not found"); goto next; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "play: opened file '%s'", path); + "play: fmt found: '%V'", &ctx->fmt->name); - e = &ctx->write_evt; + sfx = &ctx->fmt->sfx; + + if (name.len >= sfx->len && + ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len, + sfx->len) + == 0) + { + sfx = &nosfx; + } + + 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, 0, + "play: error opening file '%s'", path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: opened file '%s'", path); + + e = &ctx->send_evt; e->data = s; e->handler = ngx_rtmp_play_send; e->log = s->connection->log; ngx_rtmp_send_user_recorded(s, 1); - ngx_rtmp_play_start(s, v->start); + if (ngx_rtmp_play_init(s) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_play_start(s, v->start) != NGX_OK) { + return NGX_ERROR; + } next: return next_play(s, v); diff --git a/ngx_rtmp_play_module.h b/ngx_rtmp_play_module.h new file mode 100644 index 0000000..77d7856 --- /dev/null +++ b/ngx_rtmp_play_module.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2012 Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_PLAY_H_INCLUDED_ +#define _NGX_RTMP_PLAY_H_INCLUDED_ + + +#include "ngx_rtmp.h" + + +typedef ngx_int_t (*ngx_rtmp_play_init_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f); +typedef ngx_int_t (*ngx_rtmp_play_done_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f); +typedef ngx_int_t (*ngx_rtmp_play_start_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f, ngx_uint_t offs); +typedef ngx_int_t (*ngx_rtmp_play_stop_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f); +typedef ngx_int_t (*ngx_rtmp_play_send_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f); + + +typedef struct { + ngx_str_t name; + ngx_str_t pfx; + ngx_str_t sfx; + + ngx_rtmp_play_init_pt init; + ngx_rtmp_play_done_pt done; + ngx_rtmp_play_start_pt start; + ngx_rtmp_play_stop_pt stop; + ngx_rtmp_play_send_pt send; +} ngx_rtmp_play_fmt_t; + + +typedef struct { + ngx_file_t file; + ngx_rtmp_play_fmt_t *fmt; + ngx_event_t send_evt; +} ngx_rtmp_play_ctx_t; + + +typedef struct { + ngx_str_t root; +} ngx_rtmp_play_app_conf_t; + + +typedef struct { + ngx_array_t fmts; /* ngx_rtmp_play_fmt_t * */ +} ngx_rtmp_play_main_conf_t; + + +extern ngx_module_t ngx_rtmp_play_module; + + +#endif /* _NGX_RTMP_PLAY_H_INCLUDED_ */