diff --git a/dash/ngx_rtmp_dash_module.c b/dash/ngx_rtmp_dash_module.c index 1038ae285..21bc73c91 100644 --- a/dash/ngx_rtmp_dash_module.c +++ b/dash/ngx_rtmp_dash_module.c @@ -22,11 +22,14 @@ static char * ngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf, static ngx_int_t ngx_rtmp_dash_write_init_segments(ngx_rtmp_session_t *s); static ngx_int_t ngx_rtmp_dash_ensure_directory(ngx_rtmp_session_t *s); - -#define NGX_RTMP_DASH_BUFSIZE (1024*1024) -#define NGX_RTMP_DASH_MAX_MDAT (10*1024*1024) +/* Big buffer for 8k (QHD) cameras */ +#ifndef NGX_RTMP_DASH_BUFSIZE +#define NGX_RTMP_DASH_BUFSIZE (16*1024*1024) +#endif +#define NGX_RTMP_DASH_MAX_MDAT (16*1024*1024) #define NGX_RTMP_DASH_MAX_SAMPLES 1024 -#define NGX_RTMP_DASH_DIR_ACCESS 0744 +/* Allow access to www-data (web-server) and others too */ +#define NGX_RTMP_DASH_DIR_ACCESS 0755 #define NGX_RTMP_DASH_GMT_LENGTH sizeof("1970-09-28T12:00:00+06:00") diff --git a/doc/examples.md b/doc/examples.md index f99bce167..94e6b8ff0 100644 --- a/doc/examples.md +++ b/doc/examples.md @@ -36,6 +36,7 @@ rtmp { } } } +``` ### Re-translate remote stream with HLS support ```sh diff --git a/doc/faq.md b/doc/faq.md index cbf347726..0ac892f84 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -1,24 +1,24 @@ # FAQ -####RTMP stream is not played normally in IE, stream stops after several seconds. +#### RTMP stream is not played normally in IE, stream stops after several seconds. Add this directive to fix the problem ```sh wait_video on; ``` -####I use `pull` directive to get stream from remote location. That works for RTMP clients but does not work for HLS. +#### I use `pull` directive to get stream from remote location. That works for RTMP clients but does not work for HLS. Currently HLS clients do not trigger any events. You cannot pull or exec when HLS client connects to server. However you can use static directives `exec_static`, `pull ... static` to pull the stream always. -####Seek does not work with flv files recorded by the module. +#### Seek does not work with flv files recorded by the module. To make the files seekable add flv metadata with external software like yamdi, flvmeta or ffmpeg. ```sh exec_record_done yamdi -i $path -o /var/videos/$basename; ``` -####Published stream is missing from stats page after some time and clients fail to connect +#### Published stream is missing from stats page after some time and clients fail to connect Check if you use multiple workers in nginx (`worker_processes`). In such case you have to enable: ```sh diff --git a/hls/ngx_rtmp_hls_module.c b/hls/ngx_rtmp_hls_module.c index 48e8e3ba8..ac219f3d4 100644 --- a/hls/ngx_rtmp_hls_module.c +++ b/hls/ngx_rtmp_hls_module.c @@ -30,8 +30,12 @@ static ngx_int_t ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s, ngx_str_t *path); -#define NGX_RTMP_HLS_BUFSIZE (1024*1024) -#define NGX_RTMP_HLS_DIR_ACCESS 0744 +/* Big buffer for 8k (QHD) cameras */ +#ifndef NGX_RTMP_HLS_BUFSIZE +#define NGX_RTMP_HLS_BUFSIZE (16*1024*1024) +#endif +/* Allow access to www-data (web-server) and others too */ +#define NGX_RTMP_HLS_DIR_ACCESS 0755 typedef struct { @@ -39,8 +43,8 @@ typedef struct { uint64_t key_id; ngx_str_t *datetime; double duration; - unsigned active:1; - unsigned discont:1; /* before */ + u_char active; /* small int, 0/1 */ + u_char discont; /* small int, 0/1 */ } ngx_rtmp_hls_frag_t; @@ -51,7 +55,7 @@ typedef struct { typedef struct { - unsigned opened:1; + u_char opened; /* small int, 0/1 */ ngx_rtmp_mpegts_file_t file; @@ -69,6 +73,7 @@ typedef struct { uint64_t key_id; ngx_uint_t nfrags; ngx_rtmp_hls_frag_t *frags; /* circular 2 * winfrags + 1 */ + uint64_t mediaseq; ngx_uint_t audio_cc; ngx_uint_t video_cc; @@ -525,7 +530,7 @@ ngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s) static ngx_int_t -ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) +ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s, int final) { static u_char buffer[1024]; ngx_fd_t fd; @@ -534,7 +539,9 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) ssize_t n; ngx_rtmp_hls_app_conf_t *hacf; ngx_rtmp_hls_frag_t *f; - ngx_uint_t i, max_frag; + ngx_int_t i, start_i; + ngx_uint_t max_frag; + double fragments_length; ngx_str_t name_part, key_name_part; uint64_t prev_key_id; const char *sep, *key_sep; @@ -559,7 +566,32 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) max_frag = hacf->fraglen / 1000; - for (i = 0; i < ctx->nfrags; i++) { + /** + * Need to check fragments length sum and playlist max length + * Do backward search + */ + start_i = 0; + fragments_length = 0.; + for (i = ctx->nfrags-1; i >= 0; i--) { + f = ngx_rtmp_hls_get_frag(s, i); + if (f->duration) { + fragments_length += f->duration; + } + /** + * Think that sum of frag length is more than playlist desired length - half minimal frag length + * XXX: sometimes sum of frag lengths are almost playlist length + * but key-frames come at random rate... + */ + if (fragments_length >= hacf->playlen/1000. - max_frag/2) { + start_i = i; + break; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: found starting fragment=%i", start_i); + + for (i = start_i; i < (ngx_int_t)ctx->nfrags; i++) { f = ngx_rtmp_hls_get_frag(s, i); if (f->duration > max_frag) { max_frag = (ngx_uint_t) (f->duration + .5); @@ -574,7 +606,7 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) "#EXT-X-VERSION:3\n" "#EXT-X-MEDIA-SEQUENCE:%uL\n" "#EXT-X-TARGETDURATION:%ui\n", - ctx->frag, max_frag); + ctx->mediaseq++, max_frag); if (hacf->type == NGX_RTMP_HLS_TYPE_EVENT) { p = ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE:EVENT\n"); @@ -606,9 +638,9 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) prev_key_id = 0; - for (i = 0; i < ctx->nfrags; i++) { + for (i = start_i; i < (ngx_int_t)ctx->nfrags; i++) { f = ngx_rtmp_hls_get_frag(s, i); - if ((i == 0 || f->discont) && f->datetime && f->datetime->len > 0) { + if ((i == start_i || f->discont) && f->datetime && f->datetime->len > 0) { p = ngx_snprintf(buffer, sizeof(buffer), "#EXT-X-PROGRAM-DATE-TIME:"); n = ngx_write_fd(fd, buffer, p - buffer); if (n < 0) { @@ -648,8 +680,17 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: fragment frag=%uL, n=%ui/%ui, duration=%.3f, " "discont=%i", - ctx->frag, i + 1, ctx->nfrags, f->duration, f->discont); + ctx->frag, i + 1, ctx->nfrags, f->duration, (ngx_int_t)f->discont); + + n = ngx_write_fd(fd, buffer, p - buffer); + if (n < 0) { + goto write_err; + } + } + if (final) + { + p = ngx_slprintf(p, end, "#EXT-X-ENDLIST\n"); n = ngx_write_fd(fd, buffer, p - buffer); if (n < 0) { goto write_err; @@ -936,7 +977,7 @@ ngx_rtmp_hls_get_fragment_datetime(ngx_rtmp_session_t *s, uint64_t ts) static ngx_int_t -ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s) +ngx_rtmp_hls_close_final_fragment(ngx_rtmp_session_t *s, int final) { ngx_rtmp_hls_ctx_t *ctx; @@ -954,12 +995,19 @@ ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s) ngx_rtmp_hls_next_frag(s); - ngx_rtmp_hls_write_playlist(s); + ngx_rtmp_hls_write_playlist(s, final); return NGX_OK; } +static ngx_int_t +ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s) +{ + return ngx_rtmp_hls_close_final_fragment(s, 0); +} + + static ngx_int_t ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts, ngx_int_t discont) @@ -1620,7 +1668,7 @@ ngx_rtmp_hls_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: close stream"); - ngx_rtmp_hls_close_fragment(s); + ngx_rtmp_hls_close_final_fragment(s, 1); next: return next_close_stream(s, v); diff --git a/ngx_rtmp_amf.c b/ngx_rtmp_amf.c index b904651dc..920d931fa 100644 --- a/ngx_rtmp_amf.c +++ b/ngx_rtmp_amf.c @@ -328,7 +328,7 @@ ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, } else { switch (ngx_rtmp_amf_get(ctx, &type8, 1)) { case NGX_DONE: - if (elts->type & NGX_RTMP_AMF_OPTIONAL) { + if (elts && elts->type & NGX_RTMP_AMF_OPTIONAL) { return NGX_OK; } /* fall through */ diff --git a/ngx_rtmp_cmd_module.h b/ngx_rtmp_cmd_module.h index a419158fa..09bf4c0a7 100644 --- a/ngx_rtmp_cmd_module.h +++ b/ngx_rtmp_cmd_module.h @@ -25,7 +25,7 @@ typedef struct { double trans; u_char app[NGX_RTMP_MAX_NAME]; u_char args[NGX_RTMP_MAX_ARGS]; - u_char flashver[32]; + u_char flashver[64]; u_char swf_url[NGX_RTMP_MAX_URL]; u_char tc_url[NGX_RTMP_MAX_URL]; double acodecs; diff --git a/ngx_rtmp_codec_module.c b/ngx_rtmp_codec_module.c index 73d31a1a9..7c377ca15 100644 --- a/ngx_rtmp_codec_module.c +++ b/ngx_rtmp_codec_module.c @@ -306,7 +306,7 @@ ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, ngx_chain_t *in) ctx->aac_chan_conf = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4); if (ctx->aac_profile == 5 || ctx->aac_profile == 29) { - + if (ctx->aac_profile == 29) { ctx->aac_ps = 1; } @@ -343,7 +343,7 @@ ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, ngx_chain_t *in) 5 bits: object type if (object type == 31) 6 bits + 32: object type - + var bits: AOT Specific Config */ @@ -415,7 +415,7 @@ ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, ngx_chain_t *in) { /* chroma format idc */ cf_idc = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); - + if (cf_idc == 3) { /* separate color plane */ @@ -595,6 +595,7 @@ ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s) double duration; double frame_rate; double video_data_rate; + double video_keyframe_frequency; double video_codec_id; double audio_data_rate; double audio_codec_id; @@ -640,6 +641,10 @@ ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s) ngx_string("videodatarate"), &v.video_data_rate, 0 }, + { NGX_RTMP_AMF_NUMBER, + ngx_string("videokeyframe_frequency"), + &v.video_keyframe_frequency, 0 }, + { NGX_RTMP_AMF_NUMBER, ngx_string("videocodecid"), &v.video_codec_id, 0 }, @@ -689,6 +694,7 @@ ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s) v.duration = ctx->duration; v.frame_rate = ctx->frame_rate; v.video_data_rate = ctx->video_data_rate; + v.video_keyframe_frequency = ctx->video_keyframe_frequency; v.video_codec_id = ctx->video_codec_id; v.audio_data_rate = ctx->audio_data_rate; v.audio_codec_id = ctx->audio_codec_id; @@ -765,6 +771,7 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, double duration; double frame_rate; double video_data_rate; + double video_keyframe_frequency; double video_codec_id_n; u_char video_codec_id_s[32]; double audio_data_rate; @@ -822,6 +829,10 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_string("videodatarate"), &v.video_data_rate, 0 }, + { NGX_RTMP_AMF_NUMBER, + ngx_string("videokeyframe_frequency"), + &v.video_keyframe_frequency, 0 }, + { NGX_RTMP_AMF_VARIANT, ngx_string("videocodecid"), in_video_codec_id, sizeof(in_video_codec_id) }, @@ -871,6 +882,7 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, v.duration = -1; v.frame_rate = -1; v.video_data_rate = -1; + v.video_keyframe_frequency = -1; v.video_codec_id_n = -1; v.audio_data_rate = -1; v.audio_codec_id_n = -1; @@ -895,6 +907,7 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, if (v.video_data_rate != -1) ctx->video_data_rate = v.video_data_rate; if (v.video_codec_id_n != -1) ctx->video_codec_id = (ngx_uint_t) v.video_codec_id_n; if (v.audio_data_rate != -1) ctx->audio_data_rate = v.audio_data_rate; + if (v.video_keyframe_frequency != -1) ctx->video_keyframe_frequency = v.video_keyframe_frequency; if (v.audio_codec_id_n != -1) ctx->audio_codec_id = (v.audio_codec_id_n == 0 ? NGX_RTMP_AUDIO_UNCOMPRESSED : (ngx_uint_t) v.audio_codec_id_n); if (v.profile[0] != '\0') ngx_memcpy(ctx->profile, v.profile, sizeof(v.profile)); @@ -982,7 +995,7 @@ ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf) } ngx_str_set(&ch->name, "@setDataFrame"); ch->handler = ngx_rtmp_codec_meta_data; - + // some encoders send setDataFrame instead of @setDataFrame ch = ngx_array_push(&cmcf->amf); if (ch == NULL) { @@ -990,7 +1003,7 @@ ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf) } ngx_str_set(&ch->name, "setDataFrame"); ch->handler = ngx_rtmp_codec_meta_data; - + ch = ngx_array_push(&cmcf->amf); if (ch == NULL) { return NGX_ERROR; diff --git a/ngx_rtmp_codec_module.h b/ngx_rtmp_codec_module.h index b8de10c61..768c0f813 100644 --- a/ngx_rtmp_codec_module.h +++ b/ngx_rtmp_codec_module.h @@ -55,6 +55,7 @@ typedef struct { double duration; double frame_rate; double video_data_rate; + double video_keyframe_frequency; ngx_uint_t video_codec_id; double audio_data_rate; ngx_uint_t audio_codec_id; diff --git a/ngx_rtmp_eval.c b/ngx_rtmp_eval.c index c658f14ef..d4eeb9522 100644 --- a/ngx_rtmp_eval.c +++ b/ngx_rtmp_eval.c @@ -165,6 +165,9 @@ ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out, case '\\': state = ESCAPE; continue; + /* fall through */ + default: + break; } /* fall through */ diff --git a/ngx_rtmp_handler.c b/ngx_rtmp_handler.c index a8bef611a..1b13b198f 100644 --- a/ngx_rtmp_handler.c +++ b/ngx_rtmp_handler.c @@ -241,7 +241,9 @@ ngx_rtmp_recv(ngx_event_t *rev) "reusing formerly read data: %d", old_size); b->pos = b->start; - b->last = ngx_movemem(b->pos, old_pos, old_size); + + size = ngx_min((size_t) (b->end - b->start), old_size); + b->last = ngx_movemem(b->pos, old_pos, size); if (s->in_chunk_size_changing) { ngx_rtmp_finalize_set_chunk_size(s); diff --git a/ngx_rtmp_netcall_module.c b/ngx_rtmp_netcall_module.c index 24c001cbe..9ffadde7d 100644 --- a/ngx_rtmp_netcall_module.c +++ b/ngx_rtmp_netcall_module.c @@ -586,11 +586,13 @@ ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool) * So not override them with empty values */ - bsize = sizeof("&addr=") - 1 + addr_text->len * 3 + + bsize = sizeof("addr=") - 1 + addr_text->len * 3 + sizeof("&clientid=") - 1 + NGX_INT_T_LEN; + // Indicator of additional vars from session + // Event `connect` don't have them, for example if (s->app.len) { - bsize += sizeof("app=") - 1 + s->app.len * 3; + bsize += sizeof("&app=") - 1 + s->app.len * 3; } if (s->flashver.len) { bsize += sizeof("&flashver=") - 1 + s->flashver.len * 3; @@ -613,8 +615,16 @@ ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool) cl->buf = b; cl->next = NULL; + b->last = ngx_cpymem(b->last, (u_char*) "addr=", sizeof("addr=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, addr_text->data, + addr_text->len, NGX_ESCAPE_ARGS); + + b->last = ngx_cpymem(b->last, (u_char*) "&clientid=", + sizeof("&clientid=") - 1); + b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->connection->number); + if (s->app.len) { - b->last = ngx_cpymem(b->last, (u_char*) "app=", sizeof("app=") - 1); + b->last = ngx_cpymem(b->last, (u_char*) "&app=", sizeof("&app=") - 1); b->last = (u_char*) ngx_escape_uri(b->last, s->app.data, s->app.len, NGX_ESCAPE_ARGS); } @@ -643,14 +653,6 @@ ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool) s->page_url.len, NGX_ESCAPE_ARGS); } - b->last = ngx_cpymem(b->last, (u_char*) "&addr=", sizeof("&addr=") - 1); - b->last = (u_char*) ngx_escape_uri(b->last, addr_text->data, - addr_text->len, NGX_ESCAPE_ARGS); - - b->last = ngx_cpymem(b->last, (u_char*) "&clientid=", - sizeof("&clientid=") - 1); - b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->connection->number); - return cl; } diff --git a/ngx_rtmp_record_module.c b/ngx_rtmp_record_module.c index c7db8edfa..67253a35f 100644 --- a/ngx_rtmp_record_module.c +++ b/ngx_rtmp_record_module.c @@ -1103,7 +1103,7 @@ ngx_rtmp_record_node_avd(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx, return NGX_OK; } - if (rracf->interval != NGX_CONF_UNSET_MSEC) + if (brkframe && rracf->interval != NGX_CONF_UNSET_MSEC) { // record interval should work if set, manual mode or not next = rctx->last; diff --git a/ngx_rtmp_stat_module.c b/ngx_rtmp_stat_module.c index ba1c646c6..265d9513a 100644 --- a/ngx_rtmp_stat_module.c +++ b/ngx_rtmp_stat_module.c @@ -555,7 +555,17 @@ ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll, NGX_RTMP_STAT_L(""); NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%.3f", codec->frame_rate) - buf); - NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%.0f", codec->video_data_rate) - buf); + NGX_RTMP_STAT_L(""); + + if(codec->video_keyframe_frequency) { + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%.0f", codec->video_keyframe_frequency) - buf); + NGX_RTMP_STAT_L(""); + } cname = ngx_rtmp_get_video_codec_name(codec->video_codec_id); if (*cname) { @@ -615,6 +625,12 @@ ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll, "%ui", codec->sample_rate) - buf); NGX_RTMP_STAT_L(""); } + if (codec->audio_data_rate) { + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%0.0f", codec->audio_data_rate) - buf); + NGX_RTMP_STAT_L(""); + } NGX_RTMP_STAT_L(""); NGX_RTMP_STAT_L("\r\n"); @@ -817,7 +833,10 @@ ngx_rtmp_stat_handler(ngx_http_request_t *r) #ifdef NGX_COMPILER NGX_RTMP_STAT_L("" NGX_COMPILER "\r\n"); #endif +/* This may prevent reproducible builds. If you need that info - pass `-DNGX_BUILD_DATEITIME=1` to CFLAGS */ +#ifdef NGX_BUILD_DATEITIME NGX_RTMP_STAT_L("" __DATE__ " " __TIME__ "\r\n"); +#endif NGX_RTMP_STAT_L(""); NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf), diff --git a/ngx_rtmp_version.h b/ngx_rtmp_version.h index 2aebec705..5a6073560 100644 --- a/ngx_rtmp_version.h +++ b/ngx_rtmp_version.h @@ -8,8 +8,8 @@ #define _NGX_RTMP_VERSION_H_INCLUDED_ -#define nginx_rtmp_version 1001007 -#define NGINX_RTMP_VERSION "1.1.7.11-dev" +#define nginx_rtmp_version 1002002 +#define NGINX_RTMP_VERSION "1.2.x-dev" #endif /* _NGX_RTMP_VERSION_H_INCLUDED_ */