diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..5030bb870 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.idea +/.settings +/.project +/.cproject +/.vscode diff --git a/AUTHORS b/AUTHORS index e169dff0e..151f456f1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,3 +6,10 @@ Project author: Contacts: arut@qip.ru arutyunyan.roman@gmail.com + +Fork author: + Sergey Dryanzhinsky + Moscow, Russia + + Contacts: + sergey.dryabzhinsky@gmail.com diff --git a/README.md b/README.md index b4c9a69fd..d53d0e6b9 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,29 @@ # NGINX-based Media Streaming Server -## nginx-rtmp-module +## nginx-rtmp-module ### Project blog http://nginx-rtmp.blogspot.com -### Wiki manual +### Documentation + +* [Home](doc/README.md) +* [Control module](doc/control_modul.md) +* [Debug log](doc/debug_log.md) +* [Directives](doc/directives.md) +* [Examples](doc/examples.md) +* [Exec wrapper in bash](doc/exec_wrapper_in_bash.md) +* [FAQ](doc/faq.md) +* [Getting number of subscribers](doc/getting_number_of_subscribers.md) +* [Getting started with nginx rtmp](doc/getting_started.md) +* [Installing in Gentoo](doc/installing_in_gentoo.md) +* [Installing on Ubuntu using PPAs](doc/installing_ubuntu_using_ppas.md) +* [Tutorial](doc/tutorial.md) - https://github.com/arut/nginx-rtmp-module/wiki/Directives +*Source: https://github.com/arut/nginx-rtmp-module/wiki* + +* [Latest updates](doc/README.md#updates) ### Google group @@ -76,6 +91,12 @@ For building debug version of nginx add `--with-debug` [Read more about debug log](https://github.com/arut/nginx-rtmp-module/wiki/Debug-log) +### Contributing and Branch Policy + +The "dev" branch is the one where all contributions will be merged before reaching "master". +If you plan to propose a patch, please commit into the "dev" branch or its own feature branch. +Direct commit to "master" are not permitted. + ### Windows limitations Windows support is limited. These features are not supported @@ -97,10 +118,8 @@ name - interpreted by each application ### Multi-worker live streaming -Module supports multi-worker live -streaming through automatic stream pushing -to nginx workers. This option is toggled with -rtmp_auto_push directive. +This NGINX-RTMP module does not support multi-worker live +streaming. While this feature can be enabled through rtmp_auto_push on|off directive, it is ill advised because it is incompatible with NGINX versions starting 1.7.2 and up, there for it should not be used. ### Example nginx.conf @@ -315,18 +334,3 @@ rtmp_auto_push directive. } } } - - -### Multi-worker streaming example - - rtmp_auto_push on; - - rtmp { - server { - listen 1935; - - application mytv { - live on; - } - } - } diff --git a/config b/config index 968b63a3f..13f00e83c 100644 --- a/config +++ b/config @@ -1,6 +1,5 @@ ngx_addon_name="ngx_rtmp_module" - -CORE_MODULES="$CORE_MODULES +RTMP_CORE_MODULES=" \ ngx_rtmp_module \ ngx_rtmp_core_module \ ngx_rtmp_cmd_module \ @@ -15,21 +14,18 @@ CORE_MODULES="$CORE_MODULES ngx_rtmp_relay_module \ ngx_rtmp_exec_module \ ngx_rtmp_auto_push_module \ + ngx_rtmp_auto_push_index_module \ ngx_rtmp_notify_module \ ngx_rtmp_log_module \ ngx_rtmp_limit_module \ ngx_rtmp_hls_module \ ngx_rtmp_dash_module \ " - - -HTTP_MODULES="$HTTP_MODULES \ +RTMP_HTTP_MODULES=" \ ngx_rtmp_stat_module \ ngx_rtmp_control_module \ " - - -NGX_ADDON_DEPS="$NGX_ADDON_DEPS \ +RTMP_DEPS=" \ $ngx_addon_dir/ngx_rtmp_amf.h \ $ngx_addon_dir/ngx_rtmp_bandwidth.h \ $ngx_addon_dir/ngx_rtmp_cmd_module.h \ @@ -48,9 +44,7 @@ NGX_ADDON_DEPS="$NGX_ADDON_DEPS \ $ngx_addon_dir/hls/ngx_rtmp_mpegts.h \ $ngx_addon_dir/dash/ngx_rtmp_mp4.h \ " - - -NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ +RTMP_CORE_SRCS=" \ $ngx_addon_dir/ngx_rtmp.c \ $ngx_addon_dir/ngx_rtmp_init.c \ $ngx_addon_dir/ngx_rtmp_handshake.c \ @@ -70,8 +64,6 @@ NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ $ngx_addon_dir/ngx_rtmp_flv_module.c \ $ngx_addon_dir/ngx_rtmp_mp4_module.c \ $ngx_addon_dir/ngx_rtmp_netcall_module.c \ - $ngx_addon_dir/ngx_rtmp_stat_module.c \ - $ngx_addon_dir/ngx_rtmp_control_module.c \ $ngx_addon_dir/ngx_rtmp_relay_module.c \ $ngx_addon_dir/ngx_rtmp_bandwidth.c \ $ngx_addon_dir/ngx_rtmp_exec_module.c \ @@ -84,9 +76,43 @@ NGX_ADDON_SRCS="$NGX_ADDON_SRCS \ $ngx_addon_dir/hls/ngx_rtmp_hls_module.c \ $ngx_addon_dir/dash/ngx_rtmp_dash_module.c \ $ngx_addon_dir/hls/ngx_rtmp_mpegts.c \ + $ngx_addon_dir/hls/ngx_rtmp_mpegts_crc.c \ $ngx_addon_dir/dash/ngx_rtmp_mp4.c \ " -CFLAGS="$CFLAGS -I$ngx_addon_dir" +RTMP_HTTP_SRCS=" \ + $ngx_addon_dir/ngx_rtmp_stat_module.c \ + $ngx_addon_dir/ngx_rtmp_control_module.c \ + " +ngx_module_incs=$ngx_addon_dir +ngx_module_deps=$RTMP_DEPS + +if [ "$ngx_module_link" = "" ] ; then + # Old nginx version + ngx_module_link=NONE + + EVENT_MODULES="$EVENT_MODULES $RTMP_CORE_MODULES" + HTTP_MODULES="$HTTP_MODULES $RTMP_HTTP_MODULES" + NGX_ADDON_DEPS="$NGX_ADDON_DEPS $RTMP_DEPS" + NGX_ADDON_SRCS="$NGX_ADDON_SRCS $RTMP_CORE_SRCS $RTMP_HTTP_SRCS" +fi + +if [ $ngx_module_link = DYNAMIC ] ; then + ngx_module_name="$RTMP_CORE_MODULES $RTMP_HTTP_MODULES" + ngx_module_srcs="$RTMP_CORE_SRCS $RTMP_HTTP_SRCS" + . auto/module +elif [ $ngx_module_link = ADDON ] ; then + ngx_module_type=EVENT + ngx_module_name=$RTMP_CORE_MODULES + ngx_module_srcs=$RTMP_CORE_SRCS + . auto/module + ngx_module_type=HTTP + ngx_module_name=$RTMP_HTTP_MODULES + ngx_module_srcs=$RTMP_HTTP_SRCS + . auto/module +fi USE_OPENSSL=YES +CFLAGS="$CFLAGS -I$ngx_addon_dir" +# Debug build with all warnings as errors +# CFLAGS="$CFLAGS -I$ngx_addon_dir -Wall -Wpointer-arith -Wno-unused-parameter -Werror" diff --git a/dash/ngx_rtmp_dash_module.c b/dash/ngx_rtmp_dash_module.c index 772c7060f..f4d6b0b34 100644 --- a/dash/ngx_rtmp_dash_module.c +++ b/dash/ngx_rtmp_dash_module.c @@ -12,6 +12,7 @@ static ngx_rtmp_publish_pt next_publish; static ngx_rtmp_close_stream_pt next_close_stream; static ngx_rtmp_stream_begin_pt next_stream_begin; static ngx_rtmp_stream_eof_pt next_stream_eof; +static ngx_rtmp_playlist_pt next_playlist; static ngx_int_t ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf); @@ -19,12 +20,14 @@ static void * ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf); static char * ngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf, void *parent, void *child); 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) #define NGX_RTMP_DASH_MAX_SAMPLES 1024 +#define NGX_RTMP_DASH_GMT_LENGTH sizeof("1970-09-28T12:00:00+06:00") typedef struct { uint32_t timestamp; @@ -77,11 +80,29 @@ typedef struct { } ngx_rtmp_dash_cleanup_t; +#define NGX_RTMP_DASH_CLOCK_COMPENSATION_OFF 1 +#define NGX_RTMP_DASH_CLOCK_COMPENSATION_NTP 2 +#define NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_HEAD 3 +#define NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_ISO 4 + +static ngx_conf_enum_t ngx_rtmp_dash_clock_compensation_type_slots[] = { + { ngx_string("off"), NGX_RTMP_DASH_CLOCK_COMPENSATION_OFF }, + { ngx_string("ntp"), NGX_RTMP_DASH_CLOCK_COMPENSATION_NTP }, + { ngx_string("http_head"), NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_HEAD }, + { ngx_string("http_iso"), NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_ISO }, + { ngx_null_string, 0 } +}; + typedef struct { ngx_flag_t dash; ngx_msec_t fraglen; ngx_msec_t playlen; ngx_flag_t nested; + ngx_uint_t clock_compensation; // Try to compensate clock drift + // between client and server (on client side) + ngx_str_t clock_helper_uri; // Use uri to static file on HTTP server + // - same machine as RTMP/DASH) + // - or NTP server address ngx_str_t path; ngx_uint_t winfrags; ngx_flag_t cleanup; @@ -133,6 +154,20 @@ static ngx_command_t ngx_rtmp_dash_commands[] = { offsetof(ngx_rtmp_dash_app_conf_t, nested), NULL }, + { ngx_string("dash_clock_compensation"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, clock_compensation), + &ngx_rtmp_dash_clock_compensation_type_slots }, + + { ngx_string("dash_clock_helper_uri"), + 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_dash_app_conf_t, clock_helper_uri), + NULL }, + ngx_null_command }; @@ -211,6 +246,22 @@ ngx_rtmp_dash_rename_file(u_char *src, u_char *dst) } +static ngx_uint_t +ngx_rtmp_dash_gcd(ngx_uint_t m, ngx_uint_t n) +{ + /* greatest common divisor */ + + ngx_uint_t temp; + + while (n) { + temp=n; + n=m % n; + m=temp; + } + return m; +} + + static ngx_int_t ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s) { @@ -220,16 +271,25 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s) ngx_fd_t fd; struct tm tm; ngx_str_t noname, *name; - ngx_uint_t i; + ngx_uint_t i, frame_rate_num, frame_rate_denom; + ngx_uint_t depth_msec, depth_sec; + ngx_uint_t update_period, update_period_msec; + ngx_uint_t buffer_time, buffer_time_msec; + ngx_uint_t presentation_delay, presentation_delay_msec; + ngx_uint_t gcd, par_x, par_y; ngx_rtmp_dash_ctx_t *ctx; ngx_rtmp_codec_ctx_t *codec_ctx; ngx_rtmp_dash_frag_t *f; ngx_rtmp_dash_app_conf_t *dacf; ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_playlist_t v; + static u_char buffer[NGX_RTMP_DASH_BUFSIZE]; - static u_char start_time[sizeof("1970-09-28T12:00:00+06:00")]; - static u_char end_time[sizeof("1970-09-28T12:00:00+06:00")]; + static u_char avaliable_time[NGX_RTMP_DASH_GMT_LENGTH]; + static u_char publish_time[NGX_RTMP_DASH_GMT_LENGTH]; + static u_char buffer_depth[sizeof("P00Y00M00DT00H00M00.000S")]; + static u_char frame_rate[(NGX_INT_T_LEN * 2) + 2]; dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); @@ -260,34 +320,37 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s) " type=\"dynamic\"\n" \ " xmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n" \ " availabilityStartTime=\"%s\"\n" \ - " availabilityEndTime=\"%s\"\n" \ - " minimumUpdatePeriod=\"PT%uiS\"\n" \ - " minBufferTime=\"PT%uiS\"\n" \ - " timeShiftBufferDepth=\"PT0H0M0.00S\"\n" \ - " suggestedPresentationDelay=\"PT%uiS\"\n" \ + " publishTime=\"%s\"\n" \ + " minimumUpdatePeriod=\"PT%ui.%03uiS\"\n" \ + " minBufferTime=\"PT%ui.%03uiS\"\n" \ + " timeShiftBufferDepth=\"%s\"\n" \ + " suggestedPresentationDelay=\"PT%ui.%03uiS\"\n" \ " profiles=\"urn:hbbtv:dash:profile:isoff-live:2012," \ "urn:mpeg:dash:profile:isoff-live:2011\"\n" \ " xmlns:xsi=\"http://www.w3.org/2011/XMLSchema-instance\"\n" \ - " xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\">\n" \ + " xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\">\n" + +#define NGX_RTMP_DASH_MANIFEST_PERIOD \ " \n" #define NGX_RTMP_DASH_MANIFEST_VIDEO \ " \n" \ + " maxFrameRate=\"%s\"\n" \ + " par=\"%ui:%ui\">\n" \ " \n" \ " \n" \ " \n" \ " \n" +#define NGX_RTMP_DASH_PERIOD_FOOTER \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_CLOCK \ + " \n" + + #define NGX_RTMP_DASH_MANIFEST_FOOTER \ - " \n" \ "\n" - ngx_libc_localtime(ctx->start_time.sec + - ngx_rtmp_dash_get_frag(s, 0)->timestamp / 1000, &tm); - *ngx_sprintf(start_time, "%4d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d", - tm.tm_year + 1900, tm.tm_mon + 1, - tm.tm_mday, tm.tm_hour, - tm.tm_min, tm.tm_sec, - ctx->start_time.gmtoff < 0 ? '-' : '+', - ngx_abs(ctx->start_time.gmtoff / 60), - ngx_abs(ctx->start_time.gmtoff % 60)) = 0; - - ngx_libc_localtime(ctx->start_time.sec + - (ngx_rtmp_dash_get_frag(s, ctx->nfrags - 1)->timestamp + - ngx_rtmp_dash_get_frag(s, ctx->nfrags - 1)->duration) / - 1000, &tm); - - *ngx_sprintf(end_time, "%4d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d", - tm.tm_year + 1900, tm.tm_mon + 1, - tm.tm_mday, tm.tm_hour, +/** + * Availability time must be equal stream start time + * Cos segments time counting from it + */ + ngx_libc_gmtime(ctx->start_time.sec, &tm); + *ngx_sprintf(avaliable_time, "%4d-%02d-%02dT%02d:%02d:%02dZ", + tm.tm_year + 1900, tm.tm_mon + 1, + tm.tm_mday, tm.tm_hour, + tm.tm_min, tm.tm_sec + ) = 0; + + /* Stream publish time */ + *ngx_sprintf(publish_time, "%s", avaliable_time) = 0; + + depth_sec = (ngx_uint_t) ( + ngx_rtmp_dash_get_frag(s, ctx->nfrags - 1)->timestamp + + ngx_rtmp_dash_get_frag(s, ctx->nfrags - 1)->duration - + ngx_rtmp_dash_get_frag(s, 0)->timestamp); + + depth_msec = depth_sec % 1000; + depth_sec -= depth_msec; + depth_sec /= 1000; + + ngx_libc_gmtime(depth_sec, &tm); + + *ngx_sprintf(buffer_depth, "P%dY%02dM%02dDT%dH%02dM%02d.%03dS", + tm.tm_year - 70, tm.tm_mon, + tm.tm_mday - 1, tm.tm_hour, tm.tm_min, tm.tm_sec, - ctx->start_time.gmtoff < 0 ? '-' : '+', - ngx_abs(ctx->start_time.gmtoff / 60), - ngx_abs(ctx->start_time.gmtoff % 60)) = 0; + depth_msec) = 0; last = buffer + sizeof(buffer); + /** + * Calculate playlist minimal update period + * This should be more than biggest segment duration + * Cos segments rounded by keyframe/GOP. + * And that time not always equals to fragment length. + */ + update_period = dacf->fraglen; + + for (i = 0; i < ctx->nfrags; i++) { + f = ngx_rtmp_dash_get_frag(s, i); + if (f->duration > update_period) { + update_period = f->duration; + } + } + + // Reasonable delay for streaming + presentation_delay = update_period * 2 + 1000; + presentation_delay_msec = presentation_delay % 1000; + presentation_delay -= presentation_delay_msec; + presentation_delay /= 1000; + + // Calculate msec part and seconds + update_period_msec = update_period % 1000; + update_period -= update_period_msec; + update_period /= 1000; + + // Buffer length by default fragment length + buffer_time = dacf->fraglen; + buffer_time_msec = buffer_time % 1000; + buffer_time -= buffer_time_msec; + buffer_time /= 1000; + + // Fill DASH header p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_HEADER, - start_time, - end_time, - (ngx_uint_t) (dacf->fraglen / 1000), - (ngx_uint_t) (dacf->fraglen / 1000), - (ngx_uint_t) (dacf->fraglen / 500)); + // availabilityStartTime + avaliable_time, + // publishTime + publish_time, + // minimumUpdatePeriod + update_period, update_period_msec, + // minBufferTime + buffer_time, buffer_time_msec, + // timeShiftBufferDepth + buffer_depth, + // suggestedPresentationDelay + presentation_delay, presentation_delay_msec + ); + + p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_PERIOD); n = ngx_write_fd(fd, buffer, p - buffer); @@ -383,17 +505,46 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s) sep = (dacf->nested ? "" : "-"); if (ctx->has_video) { + frame_rate_num = (ngx_uint_t) (codec_ctx->frame_rate * 1000.); + + if (frame_rate_num % 1000 == 0) { + *ngx_sprintf(frame_rate, "%ui", frame_rate_num / 1000) = 0; + } else { + frame_rate_denom = 1000; + switch (frame_rate_num) { + case 23976: + frame_rate_num = 24000; + frame_rate_denom = 1001; + break; + case 29970: + frame_rate_num = 30000; + frame_rate_denom = 1001; + break; + case 59940: + frame_rate_num = 60000; + frame_rate_denom = 1001; + break; + } + + *ngx_sprintf(frame_rate, "%ui/%ui", frame_rate_num, frame_rate_denom) = 0; + } + + gcd = ngx_rtmp_dash_gcd(codec_ctx->width, codec_ctx->height); + par_x = codec_ctx->width / gcd; + par_y = codec_ctx->height / gcd; + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_VIDEO, codec_ctx->width, codec_ctx->height, - codec_ctx->frame_rate, + frame_rate, + par_x, par_y, &ctx->name, codec_ctx->avc_profile, codec_ctx->avc_compat, codec_ctx->avc_level, codec_ctx->width, codec_ctx->height, - codec_ctx->frame_rate, + frame_rate, (ngx_uint_t) (codec_ctx->video_data_rate * 1000), name, sep, name, sep); @@ -430,6 +581,34 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s) n = ngx_write_fd(fd, buffer, p - buffer); } + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_PERIOD_FOOTER); + n = ngx_write_fd(fd, buffer, p - buffer); + + /* UTCTiming value */ + switch (dacf->clock_compensation) { + case NGX_RTMP_DASH_CLOCK_COMPENSATION_NTP: + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_CLOCK, + "ntp", + &dacf->clock_helper_uri + ); + n = ngx_write_fd(fd, buffer, p - buffer); + break; + case NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_HEAD: + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_CLOCK, + "http-head", + &dacf->clock_helper_uri + ); + n = ngx_write_fd(fd, buffer, p - buffer); + break; + case NGX_RTMP_DASH_CLOCK_COMPENSATION_HTTP_ISO: + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_CLOCK, + "http-iso", + &dacf->clock_helper_uri + ); + n = ngx_write_fd(fd, buffer, p - buffer); + break; + } + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_FOOTER); n = ngx_write_fd(fd, buffer, p - buffer); @@ -451,7 +630,11 @@ ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s) return NGX_ERROR; } - return NGX_OK; + ngx_memzero(&v, sizeof(v)); + ngx_str_set(&(v.module), "dash"); + v.playlist.data = ctx->playlist.data; + v.playlist.len = ctx->playlist.len; + return next_playlist(s, &v); } @@ -733,6 +916,10 @@ ngx_rtmp_dash_open_fragments(ngx_rtmp_session_t *s) return NGX_OK; } + if (ngx_rtmp_dash_ensure_directory(s) != NGX_OK) { + return NGX_ERROR; + } + ngx_rtmp_dash_open_fragment(s, &ctx->video, ctx->id, 'v'); ngx_rtmp_dash_open_fragment(s, &ctx->audio, ctx->id, 'a'); @@ -1013,6 +1200,10 @@ ngx_rtmp_dash_update_fragments(ngx_rtmp_session_t *s, ngx_int_t boundary, ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); f = ngx_rtmp_dash_get_frag(s, ctx->nfrags); + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: update_fragments: timestamp=%ui, f-timestamp=%ui, boundary=%i, dacf-fraglen=%ui", + timestamp, f->timestamp, boundary, dacf->fraglen); + d = (int32_t) (timestamp - f->timestamp); if (d >= 0) { @@ -1027,26 +1218,44 @@ ngx_rtmp_dash_update_fragments(ngx_rtmp_session_t *s, ngx_int_t boundary, hit = (-d > 1000); } + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: update_fragments: d=%i, f-duration=%ui, hit=%i", + d, f->duration, hit); + if (ctx->has_video && !hit) { boundary = 0; + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: update_fragments: boundary=0 cos has_video && !hit"); } if (!ctx->has_video && ctx->has_audio) { boundary = hit; + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: update_fragments: boundary=hit cos !has_video && has_audio"); } if (ctx->audio.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) { boundary = 1; + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: update_fragments: boundary=1 cos audio max mdat"); } if (ctx->video.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) { boundary = 1; + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: update_fragments: boundary=1 cos video max mdat"); } if (!ctx->opened) { boundary = 1; + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: update_fragments: boundary=1 cos !opened"); } + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: update_fragments: boundary=%i", + boundary); + if (boundary) { ngx_rtmp_dash_close_fragments(s); ngx_rtmp_dash_open_fragments(s); @@ -1368,7 +1577,7 @@ ngx_rtmp_dash_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen) "dash: cleanup '%V' allowed, mpd missing '%s'", &name, mpd_path); - max_age = 0; + max_age = playlen / 500; } else if (name.len >= 4 && name.data[name.len - 4] == '.' && name.data[name.len - 3] == 'm' && @@ -1396,7 +1605,7 @@ ngx_rtmp_dash_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen) name.data[name.len - 2] == 'a' && name.data[name.len - 1] == 'w') { - max_age = playlen / 1000; + max_age = playlen / 500; } else { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, @@ -1424,17 +1633,31 @@ ngx_rtmp_dash_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen) } } - +#if (nginx_version >= 1011005) +static ngx_msec_t +#else static time_t +#endif ngx_rtmp_dash_cleanup(void *data) { ngx_rtmp_dash_cleanup_t *cleanup = data; ngx_rtmp_dash_cleanup_dir(&cleanup->path, cleanup->playlen); + // Next callback in doubled playlist length time to make sure what all + // players read all segments +#if (nginx_version >= 1011005) + return cleanup->playlen * 2; +#else return cleanup->playlen / 500; +#endif } +static ngx_int_t +ngx_rtmp_dash_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v) +{ + return next_playlist(s, v); +} static void * ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf) @@ -1451,6 +1674,7 @@ ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf) conf->playlen = NGX_CONF_UNSET_MSEC; conf->cleanup = NGX_CONF_UNSET; conf->nested = NGX_CONF_UNSET; + conf->clock_compensation = NGX_CONF_UNSET; return conf; } @@ -1468,6 +1692,9 @@ ngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000); ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1); ngx_conf_merge_value(conf->nested, prev->nested, 0); + ngx_conf_merge_uint_value(conf->clock_compensation, prev->clock_compensation, + NGX_RTMP_DASH_CLOCK_COMPENSATION_OFF); + ngx_conf_merge_str_value(conf->clock_helper_uri, prev->clock_helper_uri, ""); if (conf->fraglen) { conf->winfrags = conf->playlen / conf->fraglen; @@ -1536,5 +1763,8 @@ ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf) next_stream_eof = ngx_rtmp_stream_eof; ngx_rtmp_stream_eof = ngx_rtmp_dash_stream_eof; + next_playlist = ngx_rtmp_playlist; + ngx_rtmp_playlist = ngx_rtmp_dash_playlist; + return NGX_OK; } diff --git a/doc/README.md b/doc/README.md index 407efa7b6..f62919564 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,2 +1,29 @@ -Documentation is available here: -https://github.com/arut/nginx-rtmp-module/wiki +# Welcome to the nginx-rtmp-module documentation! + +## Pages + +* [Control module](control_modul.md) +* [Debug log](debug_log.md) +* [Directives](directives.md) +* [Examples](examples.md) +* [Exec wrapper in bash](exec_wrapper_in_bash.md) +* [FAQ](faq.md) +* [Getting number of subscribers](getting_number_of_subscribers.md) +* [Getting started with nginx rtmp](getting_started.md) +* [Installing in Gentoo](Installing_in_gentoo.md) +* [Installing on Ubuntu using PPAs](installing_ubuntu_using_ppas.md) +* [Tutorial](tutorial.md) + +*Source: https://github.com/arut/nginx-rtmp-module/wiki* + +## Updates + +* Directives + * Notify + * [on_playlist](directives.md#on_playlist) + * [notify_send_redirect](directives.md#notify_send_redirect) + * Client Caching + * [hls_allow_client_cache](directives.md#hls_allow_client_cache) + * Dash MPD generation + * [dash_clock_compensation](directives.md#dash_clock_compensation) + * [dash_clock_helper_uri](directives.md#dash_clock_helper_uri) diff --git a/doc/control_modul.md b/doc/control_modul.md new file mode 100644 index 000000000..dbee1f50a --- /dev/null +++ b/doc/control_modul.md @@ -0,0 +1,94 @@ +# Control module + +Control module is HTTP module which makes it possible to control rtmp module from outside using HTTP protocol. Here's an example of how to enable control. +```sh +http { +... + server { + listen 8080; + server_name localhost; + .... + location /control { + rtmp_control all; + } + } +} +``` + +There are several sub-modules within control module each controlling a different feature. + +# Record +This sub-module starts and stops recordings created with _manual_ flag. +Syntax: +```sh +http://server.com/control/record/start|stop?srv=SRV&app=APP&name=NAME&rec=REC +``` + +* srv=SRV - optional server{} block number within rtmp{} block, default to first server{} block +* app=APP - required application name +* name=NAME - required stream name +* rec=REC - optional recorder name, defaults to root (unnamed) recorder + +Example +```sh +rtmp { + server { + listen 1935; + application myapp { + live on; + recorder rec1 { + record all manual; + record_suffix all.flv; + record_path /tmp/rec; + record_unique on; + } + } + } +} +``` + +Publish the stream with the following command +```sh +$ ffmpeg -i http://someserver.com/mychannel.ts -c:v copy -c:a nellymoser -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream +``` + +Use the following commands to start and stop recording +```sh +$ curl "http://localhost:8080/control/record/start?app=myapp&name=mystream&rec=rec1" +ยง curl "http://localhost:8080/control/record/stop?app=myapp&name=mystream&rec=rec1" +``` + +if the record start/stop request returns nothing sometimes, you should check if you use multi workers. one worker works great. + +# Drop +This sub-module provides a simple way to drop client connection. +Syntax: +```sh +http://server.com/control/drop/publisher|subscriber|client? +srv=SRV&app=APP&name=NAME&addr=ADDR&clientid=CLIENTID +``` + +* srv, app, name - the same as above +* addr - optional client address (the same as returned by rtmp_stat) +* clientid - optional nginx client id (displayed in log and stat) + +The first method ```drop/publisher``` drops publisher connection. The second ```drop/client``` drops every connection matching ```addr``` argument or all clients (including publisher) if ```addr``` is not specified. + +Examples +```sh +$ curl http://localhost:8080/control/drop/publisher?app=myapp&name=mystream +$ curl http://localhost:8080/control/drop/client?app=myapp&name=mystream +$ curl http://localhost:8080/control/drop/client?app=myapp&name=mystream&addr=192.168.0.1 +$ curl http://localhost:8080/control/drop/client?app=myapp&name=mystream&clientid=1 +``` + +# Redirect +Redirect play/publish client to a new stream. +Syntax: +```sh +http://server.com/control/redirect/publisher|subscriber|client? +srv=SRV&app=APP&name=NAME&addr=ADDR&clientid=CLIENTID&newname=NEWNAME +``` + +* srv, app, name, addr, clients - the same as above +* newname - new stream name to redirect to diff --git a/doc/debug_log.md b/doc/debug_log.md new file mode 100644 index 000000000..d1bd57e49 --- /dev/null +++ b/doc/debug_log.md @@ -0,0 +1,16 @@ +# Debug log + +In case you need to solve a streaming problem you might need to watch debug log. +For that configure nginx with *--with-debug* flag. +```sh +$ cd nginx-X.Y.Z +$ ./configure --add-module=/path/to/nginx-rtmp-module --with-debug ... +``` + +After compiling set nginx error.log level to *debug* in nginx.conf +```sh +error_log logs/error.log debug; +``` + +After that you will have _a lot_ of debug info in error.log. Please grep +what your problem relates to (exec, notify etc) and post to [nginx-rtmp google group](https://groups.google.com/group/nginx-rtmp) to help with solving it. \ No newline at end of file diff --git a/doc/directives.md b/doc/directives.md new file mode 100644 index 000000000..9ad1f25db --- /dev/null +++ b/doc/directives.md @@ -0,0 +1,1824 @@ +# Directives + +Table of Contents +================= + +* [Core](#core) + * [rtmp](#rtmp) + * [server](#server) + * [listen](#listen) + * [application](#application) + * [timeout](#timeout) + * [ping](#ping) + * [ping_timeout](#ping_timeout) + * [max_streams](#max_streams) + * [ack_window](#ack_window) + * [chunk_size](#chunk_size) + * [max_queue](#max_queue) + * [max_message](#max_message) + * [buflen](#buflen) + * [out_queue](#out_queue) + * [out_cork](#out_cork) +* [Access](#access) + * [allow](#allow) + * [deny](#deny) +* [Exec](#exec) + * [exec_push](#exec_push) + * [exec_pull](#exec_pull) + * [exec](#exec) + * [exec_options](#exec_options) + * [exec_static](#exec_static) + * [exec_kill_signal](#exec_kill_signal) + * [respawn](#respawn) + * [respawn_timeout](#respawn_timeout) + * [exec_publish](#exec_publish) + * [exec_play](#exec_play) + * [exec_play_done](#exec_play_done) + * [exec_publish_done](#exec_publish_done) + * [exec_record_started](#exec_record_started) + * [exec_record_done](#exec_record_done) +* [Live](#live) + * [live](#live) + * [meta](#meta) + * [interleave](#interleave) + * [wait_key](#wait_key) + * [wait_video](#wait_video) + * [publish_notify](#publish_notify) + * [drop_idle_publisher](#drop_idle_publisher) + * [sync](#sync) + * [play_restart](#play_restart) + * [idle_streams](#idle_streams) +* [Record](#record) + * [record](#record) + * [record_path](#record_path) + * [record_suffix](#record_suffix) + * [record_unique](#record_unique) + * [record_append](#record_append) + * [record_lock](#record_lock) + * [record_max_size](#record_max_size) + * [record_max_frames](#record_max_frames) + * [record_interval](#record_interval) + * [recorder](#recorder) + * [record_notify](#record_notify) +* [Video on demand](#video-on-demand) + * [play](#play) + * [play_temp_path](#play_temp_path) + * [play_local_path](#play_local_path) +* [Relay](#relay) + * [pull](#pull) + * [push](#push) + * [push_reconnect](#push_reconnect) + * [session_relay](#session_relay) +* [Notify](#notify) + * [on_connect](#on_connect) + * [on_play](#on_play) + * [on_playlist](#on_playlist) + * [on_publish](#on_publish) + * [on_done](#on_done) + * [on_play_done](#on_play_done) + * [on_publish_done](#on_publish_done) + * [on_record_started](#on_record_started) + * [on_record_done](#on_record_done) + * [on_update](#on_update) + * [notify_update_timeout](#notify_update_timeout) + * [notify_update_strict](#notify_update_strict) + * [notify_relay_redirect](#notify_relay_redirect) + * [notify_send_redirect](#notify_send_redirect) + * [notify_method](#notify_method) +* [HLS](#hls) + * [hls](#hls) + * [hls_path](#hls_path) + * [hls_fragment](#hls_fragment) + * [hls_playlist_length](#hls_playlist_length) + * [hls_sync](#hls_sync) + * [hls_continuous](#hls_continuous) + * [hls_nested](#hls_nested) + * [hls_base_url](#hls_base_url) + * [hls_cleanup](#hls_cleanup) + * [hls_fragment_naming](#hls_fragment_naming) + * [hls_fragment_naming_granularity](#hls_fragment_naming_granularity) + * [hls_fragment_slicing](#hls_fragment_slicing) + * [hls_variant](#hls_variant) + * [hls_type](#hls_type) + * [hls_allow_client_cache](#hls_allow_client_cache) + * [hls_keys](#hls_keys) + * [hls_key_path](#hls_key_path) + * [hls_key_url](#hls_key_url) + * [hls_fragments_per_key](#hls_fragments_per_key) +* [MPEG-DASH](#mpeg-dash) + * [dash](#dash) + * [dash_path](#dash_path) + * [dash_fragment](#dash_fragment) + * [dash_playlist_length](#dash_playlist_length) + * [dash_nested](#dash_nested) + * [dash_cleanup](#dash_cleanup) + * [dash_clock_compensation](#dash_clock_compensation) + * [dash_clock_helper_uri](#dash_clock_helper_uri) +* [Access log](#access-log) + * [access_log](#access_log) + * [log_format](#log_format) +* [Limits](#limits) + * [max_connections](#max_connections) +* [Statistics](#statistics) + * [rtmp_stat](#rtmp_stat) + * [rtmp_stat_stylesheet](#rtmp_stat_stylesheet) +* [Multi-worker live streaming](#multi-worker-live-streaming) + * [rtmp_auto_push](#rtmp_auto_push) + * [rtmp_auto_push_reconnect](#rtmp_auto_push_reconnect) + * [rtmp_socket_dir](#rtmp_socket_dir) +* [Control](#control) + * [rtmp_control](#rtmp_control) + +## Core +#### rtmp +syntax: `rtmp { ... }` +context: root +The block which holds all RTMP settings + +#### server +syntax: `server { ... }` +context: rtmp +Declares RTMP server instance +```sh +rtmp { + server { + } +} +``` + +#### listen +syntax: `listen (addr[:port]|port|unix:path) [bind] [ipv6only=on|off] [so_keepalive=on|off|keepidle:keepintvl:keepcnt|proxy_protocol]` +context: server + +Adds listening socket to NGINX for accepting RTMP connections +```sh +server { + listen 1935; +} +``` + +#### application +syntax: `application name { ... }` +context: server + +Creates RTMP application. Unlike http location application name cannot +be a pattern. +```sh +server { + listen 1935; + application myapp { + } +} +``` + +#### timeout +syntax: `timeout value` +context: rtmp, server + +Socket timeout. This value is primarily used for writing. Most of time RTMP +module does not expect any activity on all sockets except for publisher socket. +If you want broken socket to get quickly disconnected use active tools like +keepalive or RTMP ping. Default is 1 minute. +```sh +timeout 60s; +``` + +#### ping +syntax: `ping value` +context: rtmp, server + +RTMP ping interval. Zero turns ping off. RTMP ping is a protocol feature for +active connection check. A special packet is sent to remote peer and a reply +is expected within a timeout specified with ping_timeout directive. If ping +reply is not received within this time then connection is closed. Default +value for ping is 1 minute. Default ping timeout is 30 seconds. +```sh +ping 3m; +ping_timeout 30s; +``` + +#### ping_timeout +syntax: `ping_timeout value` +context: rtmp, server + +See ping description above. + +#### max_streams +syntax: `max_streams value` +context: rtmp, server + +Sets maximum number of RTMP streams. Data streams are multiplexed into +a single data stream. Different channels are used for sending commands, +audio, video etc. Default value is 32 which is usually ok for many cases. +```sh +max_streams 32; +``` + +#### ack_window +syntax: `ack_window value` +context: rtmp, server + +Sets RTMP acknowledge window size. It's the number of bytes received after +which peer should send acknowledge packet to remote side. Default value is +5000000. +```sh +ack_window 5000000; +``` + +#### chunk_size +syntax: `chunk_size value` +context: rtmp, server + +Maximum chunk size for stream multiplexing. Default is 4096. The bigger +this value the lower CPU overhead. This value cannot be less than 128. +```sh +chunk_size 4096; +``` + +#### max_queue + +#### max_message +syntax: `max_queue value` +context: rtmp, server + +Maximum size of input data message. All input data comes split into +messages (and further in chunks). A partial message is kept in memory while +waiting for it to complete. In theory incoming message can be +very large which can be a problem for server stability. Default value +1M is enough for many cases. +```sh +max_message 1M; +``` + +### buflen +syntax: `buflen time` +context: rtmp, server + +Sets default buffer length. Usually client sends RTMP `set_buflen` command +before playing and resets this setting. Default is `1000 ms`. +```sh +buflen 5s; +```` + +#### out_queue + +#### out_cork + +## Access + +#### allow +Syntax: `allow [play|publish] address|subnet|all` +Context: rtmp, server, application + +Allow publishing/playing from addresses specified or from all addresses. +Allow/deny directives are checked in order of appearance. +```sh +allow publish 127.0.0.1; +deny publish all; +allow play 192.168.0.0/24; +deny play all; +``` + +#### deny +Syntax: `deny [play|publish] address|subnet|all` +Context: rtmp, server, application + +See allow for description. + +## Exec + +#### exec_push +Syntax: `exec_push command arg*` +Context: rtmp, server, application + +Specifies external command with arguments to be executed on +every stream published. When publishing stops the process +is terminated. Full path to binary should be specified as the +first argument. There are no assumptions about what this process should +do. However this feature is useful with ffmpeg for stream +transcoding. FFmpeg is supposed to connect to nginx-rtmp as a client +and output transcoded stream back to nginx-rtmp as publisher. Substitutions +of form $var/${var} can be used within command line: +* $name - stream name +* $app - application name +* $addr - client address +* $flashver - client flash version +* $swfurl - client swf url +* $tcurl - client tc url +* $pageurl - client page url + +Shell-style redirects can be specified in `exec_push` directive for writing output +and accepting input. Supported are +* truncating output `>file` +* appending output `>>file` +* descriptor redirects like `1>&2` +* input `>/var/log/ffmpeg-$name.log; +} + +application hls { + live on; + hls on; + hls_path /tmp/hls; + hls_fragment 15s; +} +``` + +#### exec_pull +Syntax: `exec_pull command arg*` +Context: rtmp, server, application + +Specifies external command with arguments to be executed on play event. +The command is executed when first client connects to the stream and is +killed when the last one disconnects. This directive makes it possible +to pull remote stream in any format for local clients. + +The feature works reliably only in single-worker mode. The reason for this +is we cannot make sure external process always connects to the right worker. +It will obviously connect to a random one. While this will still work in +most cases it's not a recommended architecture, it will be unstable and buggy. + +Directive arguments are the same as for `exec_push`. +```sh +application myapp { + live on; + exec_pull ffmpeg -i http://example.com/video_$name.ts -c copy -f flv rtmp://localhost/$app/$name; +} +``` + +In the above configuration `exec_pull` directive serves all streams. That leads +to certain limitations on remote stream name format. It should be possible to construct +the remote url using available variables like `$app`, `$name` etc. When it's not possible +you can add `exec_options on` directive which permits setting additional stream options +in exec-family directives. The only option supported now is `name` option. +```sh +application myapp { + live on; + exec_options on; + exec_pull ffmpeg -i http://example.com/tv1.ts -c copy -f flv rtmp://localhost/$app/$name name=mystream; + exec_pull ffmpeg -i http://another.example.com/video_plus.ts -c copy -f flv rtmp://localhost/$app/$name name=anotherstream; +} +``` + +#### exec +Syntax: `exec command arg*` +Context: rtmp, server, application + +`exec` is an alias of `exec_push` + +#### exec_options +Syntax: `exec_options on|off` +Context: rtmp, server, application + +The directive toggles exec options mode. When activated you can +add exec-family directive options. The only exec option supported is `name`. +This option makes it possible to apply exec only to specified stream. +Default if off. +```sh +exec_options on; +# call on_publish only for "mystream" +exec_publish http://localhost/on_publish name=mystream; + +# call on_play only for "another" +exec_play http://localhost/on_play name=another; + +# execute different ffmpeg's for different streams +exec_pull http://example.com/abc.ts -c copy -f flv rtmp://localhost/$name/$app name=mystream; +exec_pull http://my.example.com/tele.ts -c copy -f flv rtmp://localhost/$name/$app name=tv; +exec_pull http://enother.example.com/hello/f.ts -c copy -f flv rtmp://localhost/$name/$app name=fun; +``` + +#### exec_static +Syntax: `exec_static command arg*` +Context: rtmp, server, application + +Similar to `exec` but runs specified command at nginx start. +Does not support substitutions since has no session context. +```sh +exec_static ffmpeg -i http://example.com/video.ts -c copy -f flv rtmp://localhost/myapp/mystream; +``` + +#### exec_kill_signal +Syntax: `exec_kill_signal signal` +Context: rtmp, server, application + +Sets process termination signal. Default is kill (SIGKILL). +You can specify numeric or symbolic name (for POSIX.1-1990 signals). +```sh +exec_kill_signal term; +exec_kill_signal usr1; +exec_kill_signal 3; +``` + +#### respawn +Syntax: `respawn on|off` +Context: rtmp, server, application + +If turned on respawns child process when it's terminated while publishing +is still on. Default is on; +```sh +respawn off; +``` + +#### respawn_timeout +Syntax: `respawn_timeout timeout` +Context: rtmp, server, application + +Sets respawn timeout to wait before starting new child instance. +Default is 5 seconds. +```sh +respawn_timeout 10s; +``` + +#### exec_publish +Syntax: `exec_publish command arg*` +Context: rtmp, server, application + +Specifies external command with arguments to be executed on +publish event. Return code is not analyzed. Substitutions of `exec` +are supported here as well. In addition `args` variable is supported +holding query string arguments. + +#### exec_play +Syntax: `exec_play command arg*` +Context: rtmp, server, application + +Specifies external command with arguments to be executed on +play event. Return code is not analyzed. Substitution list +is the same as for `exec_publish`. + +#### exec_play_done +Syntax: `exec_play_done command arg*` +Context: rtmp, server, application + +Specifies external command with arguments to be executed on +play_done event. Return code is not analyzed. Substitution list +is the same as for `exec_publish`. + +#### exec_publish_done +Syntax: `exec_publish_done command arg*` +Context: rtmp, server, application + +Specifies external command with arguments to be executed on +publish_done event. Return code is not analyzed. Substitution list +is the same as for `exec_publish`. + +#### exec_record_started +Syntax: `exec_record_started command arg*` +Context: rtmp, server, application, recorder + +Specifies external command with arguments to be executed when +recording is started. +* `recorder` - recorder name +* `path` - recorded file path (`/tmp/rec/mystream-1389499351.flv`) +* `filename` - path with directory omitted (`mystream-1389499351.flv`) +* `basename` - file name with extension omitted (`mystream-1389499351`) +* `dirname` - directory path (`/tmp/rec`) + +#### exec_record_done +Syntax: `exec_record_done command arg*` +Context: rtmp, server, application, recorder + +Specifies external command with arguments to be executed when +recording is finished. Substitution of `exec_publish` are supported here +as well as additional variables +* `recorder` - recorder name +* `path` - recorded file path (`/tmp/rec/mystream-1389499351.flv`) +* `filename` - path with directory omitted (`mystream-1389499351.flv`) +* `basename` - file name with extension omitted (`mystream-1389499351`) +* `dirname` - directory path (`/tmp/rec`) + +Examples +```sh +# track client info +exec_play bash -c "echo $addr $pageurl >> /tmp/clients"; +exec_publish bash -c "echo $addr $flashver >> /tmp/publishers"; + +# convert recorded file to mp4 format +exec_record_done ffmpeg -y -i $path -acodec libmp3lame -ar 44100 -ac 1 -vcodec libx264 $dirname/$basename.mp4; +``` + +## Live + +#### live +Syntax: `live on|off` +Context: rtmp, server, application + +Toggles live mode i.e. one-to-many broadcasting. +```sh +live on; +``` + +#### meta +Syntax: `meta on|copy|off` +Context: rtmp, server, application + +Sets metadata sending mode. The value of `on` makes subscribers +receive reconstructed metadata packets containing predefined fields like +width, height etc. The value of `copy` makes clients receive exact copy of +publisher metadata block including both standard and specific fields. The +value of `off` turns off sending any RTMP metadata to subscribers. +Defaults to on. +```sh +meta copy; +``` + +#### interleave +Syntax: `interleave on|off` +Context: rtmp, server, application + +Toggles interleave mode. In this mode audio and video +data is transmitted on the same RTMP chunk stream. +Defaults to off. +```sh +interleave on; +``` + +#### wait_key +Syntax: `wait_key on|off` +Context: rtmp, server, application + +Makes video stream start with a key frame. Defaults to off. +```sh +wait_key on; +``` + +#### wait_video +Syntax: `wait_video on|off` +Context: rtmp, server, application + +Disable audio until first video frame is sent. Defaults to off. +Can be combined with `wait_key` to make client receive video +key frame with all other data following it. However this usually +increases connection delay. You can tune keyframe interval in your +encoder to reduce the delay. + +Recent versions of IE need this option to be enabled for normal playback. +```sh +wait_video on; +``` + +#### publish_notify +Syntax: `publish_notify on|off` +Context: rtmp, server, application + +Send `NetStream.Play.PublishNotify` and `NetStream.Play.UnpublishNotify` to +subscribers. Defaults to off. +```sh +publish_notify on; +``` + +#### drop_idle_publisher +Syntax: `drop_idle_publisher timeout` +Context: rtmp, server, application + +Drop publisher connection which has been idle (no audio/video data) +within specified time. Default is off. Note this only works when +connection is in publish mode (after sending `publish` command). +```sh +drop_idle_publisher 10s; +``` + +#### sync +Syntax: `sync timeout` +Context: rtmp, server, application + +Synchronize audio and video streams. If subscriber bandwidth +is not enough to receive data at publisher rate, some frames are +dropped by server. This leads to synchronization problem. When +timestamp difference exceeds the value specified as `sync` argument an +absolute frame is sent fixing that. Default is 300ms. +```sh +sync 10ms; +``` + +#### play_restart +Syntax: `play_restart on|off` +Context: rtmp, server, application + +If enabled nginx-rtmp sends NetStream.Play.Start and NetStream.Play.Stop +to each subscriber every time publisher starts or stops publishing. If disabled +each subscriber receives those notifications only at the start and end of +playback. Default is off. +```sh +play_restart off; +``` + +#### idle_streams +Syntax: `idle_streams on|off` +Context: rtmp, server, application + +If disabled nginx-rtmp prevents subscribers from connecting to idle/nonexistent +live streams and disconnects all subscribers when stream publisher disconnects. +Default is on. +```sh +idle_streams off; +``` + +## Record + +#### record +syntax: `record [off|all|audio|video|keyframes|manual]*` +context: rtmp, server, application, recorder + +Toggles record mode. Stream can be recorded in flv file. This directive +specifies what exactly should be recorded: +* off - no recording at all +* all - audio & video (everything) +* audio - audio +* video - video +* keyframes - only key video frames +* manual - never start recorder automatically, use control interface to start/stop + +There can be any compatible combination of keys in a single record directive. +```sh +record all; + +record audio keyframes; +``` + +#### record_path +syntax: `record_path path` +context: rtmp, server, application, recorder + +Specifies record path to put recorded flv files to. +```sh +record_path /tmp/rec; +``` + +#### record_suffix +syntax: `record_suffix value` +context: rtmp, server, application, recorder + +Sets record file suffix. Defaults to '.flv'. +```sh +record_suffix _recorded.flv; +``` + +Record suffix can be a pattern in `strftime` format. +The following directive +```sh +record_suffix -%d-%b-%y-%T.flv; +``` + +will produce files of the form `mystream-24-Apr-13-18:23:38.flv`. +All supported `strftime` format options can be found on +[strftime man page](http://pubs.opengroup.org/onlinepubs/009695399/functions/strftime.html). + +#### record_unique +syntax: `record_unique on|off` +context: rtmp, server, application, recorder + +If turned on appends current timestamp to recorded files. Otherwise the same file +is re-written each time new recording takes place. Default is off. +```sh +record_unique on; +``` + +#### record_append +syntax: `record_append on|off` +context: rtmp, server, application, recorder + +Toggles file append mode. When turned on recorder appends new data to the old file +or creates it when it's missing. There's no time gap between the old data and the new +data in file. Default is off. +```sh +record_append on; +``` + +#### record_lock +syntax: `record_lock on|off` +context: rtmp, server, application, recorder + +When turned on currently recorded file gets locked with `fcntl` call. +That can be checked from elsewhere to find out which file is being recorded. +Default is off. +```sh +record_lock on; +``` + +On FreeBSD you can use `flock` tool to check that. On Linux `flock` and `fcntl` +are unrelated so you are left with writing a simple script checking file lock status. +Here's an example of such script `isunlocked.py`. +```sh +#!/usr/bin/python + +import fcntl, sys + +sys.stderr.close() +fcntl.lockf(open(sys.argv[1], "a"), fcntl.LOCK_EX|fcntl.LOCK_NB) +``` + +#### record_max_size +syntax: `record_max_size size` +context: rtmp, server, application, recorder + +Set maximum recorded file size. +```sh +record_max_size 128K; +``` + +#### record_max_frames +syntax: `record_max_frames nframes` +context: rtmp, server, application, recorder + +Sets maximum number of video frames per recorded file. +```sh +record_max_frames 2; +``` + +#### record_interval +syntax: `record_interval time` +context: rtmp, server, application, recorder + +Restart recording after this number of (milli)seconds. +Off by default. Zero means no delay between recordings. If +record_unique is off then all record fragments are written to the +same file. Otherwise timestamp is appended which makes files +differ (given record_interval is longer than 1 second). +```sh +record_interval 1s; + +record_interval 15m; +``` + +#### recorder +syntax: `recorder name {...}` +context: application + +Create recorder block. Multiple recorders can be created withing +single application. All the above mentioned recording-related +directives can be specified in `recorder{}` block. All settings +are inherited from higher levels. +```sh +application { + live on; + + # default recorder + record all; + record_path /var/rec; + + recorder audio { + record audio; + record_suffix .audio.flv; + } + + recorder chunked { + record all; + record_interval 15s; + record_path /var/rec/chunked; + } +} +``` + +#### record_notify +syntax: `record_notify on|off` +context: rtmp, server, application, recorder + +Toggles sending NetStream.Record.Start and NetStream.Record.Stop +status messages (onStatus) to publisher when specific recorder +starts or stops recording file. Status description field holds +recorder name (empty for default recorder). Off by default. +```sh +recorder myrec { + record all manual; + record_path /var/rec; + record_notify on; +} +``` + +## Video on demand + +#### play +Syntax: `play dir|http://loc [dir|http://loc]*` +Context: rtmp, server, application + +Play flv or mp4 file from specified directory or HTTP location. +If the argument is prefixed with `http://` then it is assumed +that file should be downloaded from remote http location before +playing. Note playing is not started until the whole file is +downloaded. You can use local nginx to cache files on local machine. + +Multiple play locations can be specified in a single `play` directive. +When multiple `play` directives are specified the location lists +are merged and inherited from higher scopes. An attempt to play +each location is made until a successful location is found. +If such location is not found error status is sent to client. + +Indexed FLVs are played with random seek capability. +Unindexed FLVs are played with seek/pause disabled +(restart-only mode). Use FLV indexer (for example, yamdi) +for indexing. + +If you play FLVs recorded with the `record` directive please do not +forget to index them before playing. They are created unindexed. + +Mp4 files can only be played if both video and audio codec are supported +by RTMP. The most common case is H264/AAC. +```sh +application vod { + play /var/flvs; +} + +application vod_http { + play http://myserver.com/vod; +} + +application vod_mirror { + # try local location first, then access remote location + play /var/local_mirror http://myserver.com/vod; +} +``` + +Playing /var/flvs/dir/file.flv: +```sh +ffplay rtmp://localhost/vod//dir/file.flv +``` + +The two slashes after `vod` make ffplay use `vod` and application name +and the rest of the url as playpath. + +#### play_temp_path +Syntax: `play_temp_path dir` +Context: rtmp, server, application + +Sets location where remote VOD files are stored before playing. +Default is `/tmp`; +```sh +play_temp_path /www; +play http://example.com/videos; +``` + +#### play_local_path +Syntax: `play_local_path dir` +Context: rtmp, server, application + +Sets location where remote VOD files copied from `play_temp_path` +directory after they are completely downloaded. Empty value +disables the feature. By default it's empty. The feature can be used +for caching remote files locally. + +This path should be on the same device as `play_temp_path`. +```sh +# search file in /tmp/videos. +# if not found play from remote location +# and store in /tmp/videos + +play_local_path /tmp/videos; +play /tmp/videos http://example.com/videos; +``` + +## Relay + +#### pull +Syntax: `pull url [key=value]*` +Context: application + +Creates pull relay. Stream is pulled from remote machine +and becomes available locally. It only happens when at least +one player is playing the stream locally. + +Url syntax: `[rtmp://]host[:port][/app[/playpath]]`. If application +is missing then local application name is used. If playpath is missing +then current stream name is used instead. + +The following parameters are supported: +* app - explicit application name +* name - local stream name to bind relay to; if empty or non-specified then +all local streams within application are pulled +* tcUrl - auto-constructed if empty +* pageUrl - page url to pretend +* swfUrl - swf url to pretend +* flashVer - flash version to pretend, default is 'LNX.11,1,102,55' +* playPath - remote play path +* live - toggles special behavior for live streaming, values: 0,1 +* start - start time in seconds +* stop - stop time in seconds +* static - makes pull static, such pull is created at nginx start + +If a value for a parameter contains spaces then you should use quotes around +the **WHOLE** key=value pair like this : `'pageUrl=FAKE PAGE URL'`. +```sh +pull rtmp://cdn.example.com/main/ch?id=12563 name=channel_a; + +pull rtmp://cdn2.example.com/another/a?b=1&c=d pageUrl=http://www.example.com/video.html swfUrl=http://www.example.com/player.swf live=1; + +pull rtmp://cdn.example.com/main/ch?id=12563 name=channel_a static; +``` + +#### push +Syntax: `push url [key=value]*` +Context: application + +Push has the same syntax as pull. Unlike pull push directive publishes stream to remote server. + +#### push_reconnect +Syntax: `push_reconnect time` +Context: rtmp, server, application + +Timeout to wait before reconnecting pushed connection after disconnect. Default is 3 seconds. +```sh +push_reconnect 1s; +``` + +#### session_relay +Syntax: `session_relay on|off` +Context: rtmp, server, application + +Toggles session relay mode. In this mode relay is destroyed when connection is closed. +When the setting is off relay is destroyed when stream is closed so that another relay +could possibly be created later. Default is off. +```sh +session_relay on; +``` + +## Notify + +#### on_connect +Syntax: `on_connect url` +Context: rtmp, server + +Sets HTTP connection callback. When clients issues connect command +an HTTP request is issued asynchronously and command processing is +suspended until it returns result code. If HTTP 2xx code is returned +then RTMP session continues. The code of 3xx makes RTMP redirect +to another application whose name is taken from `Location` HTTP +response header. Otherwise connection is dropped. + +Note this directive is not allowed in application scope since +application is still unknown at connection stage. + +HTTP request receives a number of arguments. POST method is used with +application/x-www-form-urlencoded MIME type. The following arguments are +passed to caller: +* call=connect +* addr - client IP address +* app - application name +* flashVer - client flash version +* swfUrl - client swf url +* tcUrl - tcUrl +* pageUrl - client page url + +In addition to the above mentioned items all arguments passed explicitly to +connect command are also sent with the callback. You should distinguish +connect arguments from play/publish arguments. Players usually have a special +way of setting connection string separate from play/publish stream name. +As an example here's how these arguments are set in JWPlayer +```sh +... +streamer: "rtmp://localhost/myapp?connarg1=a&connarg2=b", +file: "mystream?strarg1=c&strarg2=d", +... +``` + +Ffplay (with librtmp) example +```sh +ffplay "rtmp://localhost app=myapp?connarg1=a&connarg2=b playpath=mystream?strarg1=c&strarg2=d" +``` + +Usage example +```sh +on_connect http://example.com/my_auth; +``` + +Redirect example +```sh +location /on_connect { + if ($arg_flashver != "my_secret_flashver") { + rewrite ^.*$ fallback? permanent; + } + return 200; +} +``` + +#### on_play +Syntax: `on_play url` +Context: rtmp, server, application + +Sets HTTP play callback. Each time a clients issues play command +an HTTP request is issued asynchronously and command processing is +suspended until it returns result code. HTTP result code is then +analyzed. + +* HTTP 2xx code continues RTMP session +* HTTP 3xx redirects RTMP to another stream whose name is taken from +`Location` HTTP response header. If new stream name is started with `rtmp://` +then remote relay is created instead. Relays require that IP address is +specified instead of domain name and only work with nginx versions +greater than 1.3.10. See also `notify_relay_redirect`. +* Otherwise RTMP connection is dropped + +Redirect example +```sh +http { + ... + location /local_redirect { + rewrite ^.*$ newname? permanent; + } + location /remote_redirect { + # no domain name here, only ip + rewrite ^.*$ rtmp://192.168.1.123/someapp/somename? permanent; + } + ... +} +rtmp { + ... + application myapp1 { + live on; + # stream will be redirected to 'newname' + on_play http://localhost:8080/local_redirect; + } + application myapp2 { + live on; + # stream will be pulled from remote location + # requires nginx >= 1.3.10 + on_play http://localhost:8080/remote_redirect; + } + ... +} +``` + +HTTP request receives a number of arguments. POST method is used with +application/x-www-form-urlencoded MIME type. The following arguments are +passed to caller: +* call=play +* addr - client IP address +* clientid - nginx client id (displayed in log and stat) +* app - application name +* flashVer - client flash version +* swfUrl - client swf url +* tcUrl - tcUrl +* pageUrl - client page url +* name - stream name + +In addition to the above mentioned items all arguments passed explicitly to +play command are also sent with the callback. For example if stream is +accessed with the url `rtmp://localhost/app/movie?a=100&b=face&foo=bar` then +`a`, `b` & `foo` are also sent with callback. +```sh +on_play http://example.com/my_callback; +``` + +#### on_playlist +- syntax: `on_playlist url` +- default: empty +- context: rtmp, server, application + +Set on_playlist callback. In addition to common HTTP callback +variables it receives the following values: +* path - recorded playlist-file path + +Triggered on every update of playlist written by HLS/DASH handler. + +Example +```sh +on_playlist http://example.com/on-playlist; +``` + +#### on_publish +Syntax: `on_publish url` +Context: rtmp, server, application + +The same as on_play above with the only difference that this directive sets +callback on publish command. Instead of remote pull push is performed in +this case. + +#### on_done +Syntax: `on_done url` +Context: rtmp, server, application + +Sets play/publish terminate callback. All the above applies here. However +HTTP status code is not checked for this callback. + +#### on_play_done +Syntax: `on_play_done url` +Context: rtmp, server, application + +Same behavior as `on_done` but only for play end event. + +#### on_publish_done +Syntax: `on_publish_done url` +Context: rtmp, server, application + +Same behavior as `on_done` but only for publish end event. + +#### on_record_started +syntax: `on_record_started url` +context: rtmp, server, application, recorder + +Set record_started callback. In addition to common HTTP callback +variables it receives the following values +* recorder - recorder name in config or empty string for inline recorder +* path - recording file path + + +#### on_record_done +syntax: `on_record_done url` +context: rtmp, server, application, recorder + +Set record_done callback. In addition to common HTTP callback +variables it receives the following values +* recorder - recorder name in config or empty string for inline recorder +* path - recorded file path + +Example +```sh +on_record_done http://example.com/recorded; +``` + +#### on_update +syntax: `on_update url` +context: rtmp, server, application + +Set update callback. This callback is called with period of +`notify_update_timeout`. If a request returns HTTP result other +than 2xx connection is terminated. This can be used to synchronize +expired sessions. Two additional arguments `time` and `timestamp` +are passed to this handler: +* `time` is the number of seconds since play/publish call +* `timestamp` is RTMP timestamp of the last audio/video packet sent to the client + +You can use `timestamp` argument to individually limit playback duration +for each user. +```sh +on_update http://example.com/update; +``` + +#### notify_update_timeout +syntax: `notify_update_timeout timeout` +context: rtmp, server, application + +Sets timeout between `on_update` callbacks. Default is 30 seconds. +```sh +notify_update_timeout 10s; +on_update http://example.com/update; +``` + +#### notify_update_strict +syntax: `notify_update_strict on|off` +context: rtmp, server, application + +Toggles strict mode for `on_update` callbacks. Default is off. +When turned on all connection errors, timeouts as well as HTTP parse +errors and empty responses are treated as update failures and lead +to connection termination. When off only valid HTTP response codes +other that 2xx lead to failure. +```sh +notify_update_strict on; +on_update http://example.com/update; +``` + +#### notify_relay_redirect +syntax: `notify_relay_redirect on|off` +context: rtmp, server, application + +Enables local stream redirect for `on_play` and `on_publish` remote +redirects. New stream name is MD5 hash of RTMP URL used for remote redirect. +Default is off. +```sh +notify_relay_redirect on; +``` + +#### notify_send_redirect +- syntax: `notify_send_redirect on|off` +- default: off +- context: rtmp, server, application + +Enables remote stream redirect by `on_publish` return value. +New stream name is get through HTTP redirect - `on_publish` +must return 30x code and Location header with new streaming URL. +Default is off. +```sh +notify_send_redirect on; +``` + +#### notify_method +syntax: `notify_method get|post` +context: rtmp, server, application, recorder + +Sets HTTP method for notifications. Default is POST with +`application/x-www-form-urlencoded` content type. In certain cases +GET is preferable, for example if you plan to handle the call +in `http{}` section of nginx. In this case you can use `arg_*` variables +to access arguments. +```sh +notify_method get; +``` + +With GET method handling notifications in `http{}` section can be done this way +```sh +location /on_play { + if ($arg_pageUrl ~* localhost) { + return 200; + } + return 500; +} +``` + +## HLS + +#### hls +Syntax: `hls on|off` +Context: rtmp, server, application + +Toggles HLS on the application. +```sh +hls on; +hls_path /tmp/hls; +hls_fragment 15s; +``` + +In `http{}` section set up the following location for clients to play HLS. +```sh +http { + ... + server { + ... + location /hls { + types { + application/vnd.apple.mpegurl m3u8; + } + root /tmp; + add_header Cache-Control no-cache; + + # To avoid issues with cross-domain HTTP requests (e.g. during development) + add_header Access-Control-Allow-Origin *; + } + } +} +``` + +#### hls_path +Syntax: `hls_path path` +Context: rtmp, server, application + +Sets HLS playlist and fragment directory. If the directory does not +exist it will be created. + +#### hls_fragment +Syntax: `hls_fragment time` +Context: rtmp, server, application + +Sets HLS fragment length. Defaults to 5 seconds. + +#### hls_playlist_length +Syntax: `hls_playlist_length time` +Context: rtmp, server, application + +Sets HLS playlist length. Defaults to 30 seconds. +```sh +hls_playlist_length 10m; +``` + +#### hls_sync +Syntax: `hls_sync time` +Context: rtmp, server, application + +Sets HLS timestamp synchronization threshold. Default is 2ms. +This feature prevents crackling noises after conversion +from low-resolution RTMP (1KHz) to high-resolution MPEG-TS (90KHz). +```sh +hls_sync 100ms; +``` + +#### hls_continuous +Syntax: `hls_continuous on|off` +Context: rtmp, server, application + +Toggles HLS continuous mode. In this mode HLS sequence number +is started from where it stopped last time. Old fragments are +keeped. Default is off. +```sh +hls_continuous on; +``` + +#### hls_nested +Syntax: `hls_nested on|off` +Context: rtmp, server, application + +Toggles HLS nested mode. In this mode a subdirectory +of `hls_path` is created for each stream. Playlist +and fragments are created in that subdirectory. +Default is off. +```sh +hls_nested on; +``` + +#### hls_base_url +Syntax: `hls_base_url url` +Context: rtmp, server, application + +Sets base url for HLS playlist items. When empty those +items have no prefix and assumed to be at the same location +as parent playlist or one level lower when `hls_nested` is +used. This feature applies both to master (variant) and slave +HLS playlists. It can let you download the playlist and play it +locally since it contains full references to child playlists or +fragments. Empty by default. +```sh +hls_base_url http://myserver.com/hls/; +``` + +#### hls_cleanup +Syntax: `hls_cleanup on|off` +Context: rtmp, server, application + +Toggles HLS cleanup. By default the feature is on. +In this mode nginx cache manager process removes old +HLS fragments and playlists from HLS directory. +```sh +hls_cleanup off; +``` + +#### hls_fragment_naming +Syntax: `hls_fragment_naming sequential|timestamp|system` +Context: rtmp, server, application + +Sets fragment naming mode. +* sequential - use increasing integers +* timestamp - use stream timestamp +* system - use system time + +Default is sequential. +```sh +hls_fragment_naming system; +``` + +### hls_fragment_naming_granularity +Syntax: `hls_fragment_naming_granularity number` +Context: rtmp, server, application + +Sets granularity for hls fragment ids. If above zero, changes ids +to divide the provided value. Default is zero. +```sh +# use system time rounded to 500ms as fragment names +hls_fragment_naming system; +hls_fragment_naming_granularity 500; +``` + +#### hls_fragment_slicing +Syntax: `hls_fragment_slicing plain|aligned` +Context: rtmp, server, application + +Sets fragment slicing mode. +* plain - switch fragment when target duration is reached +* aligned - switch fragment when incoming timestamp is a multiple of fragment duration. This mode makes it possible to generate identical fragments on different nginx instances + +Default is plain. +```sh +hls_fragment_slicing aligned; +``` + +#### hls_variant +Syntax: `hls_variant suffix [param*]` +Context: rtmp, server, application + +Adds HLS variant entry. When suffix is matched on stream name +then variant playlist is created for the current stream with all +entries specified by `hls_variant` directives in current application. +Stripped name without suffix is used as variant stream name. The original +stream is processed as usual. + +Optional parameters following the suffix are appended to `EXT-X-STREAM-INF` in +m3u8 playlist. See HLS spec. 3.3.10. EXT-X-STREAM-INF for the full list of supported +parameters. +```sh +rtmp { + server { + listen 1935; + + application src { + live on; + + exec ffmpeg -i rtmp://localhost/src/$name + -c:a libfdk_aac -b:a 32k -c:v libx264 -b:v 128K -f flv rtmp://localhost/hls/$name_low + -c:a libfdk_aac -b:a 64k -c:v libx264 -b:v 256k -f flv rtmp://localhost/hls/$name_mid + -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 512K -f flv rtmp://localhost/hls/$name_hi; + } + + application hls { + live on; + + hls on; + hls_path /tmp/hls; + hls_nested on; + + hls_variant _low BANDWIDTH=160000; + hls_variant _mid BANDWIDTH=320000; + hls_variant _hi BANDWIDTH=640000; + } + } +} +``` + +#### hls_type +Syntax: `hls_type live|event` +Context: rtmp, server, application + +Sets HLS playlist type specified in `X-PLAYLIST-TYPE` playlist directive. +Live HLS stream is usually played from the current live position which is +several fragments to the end of playlist. Event HLS stream is always played +from the start of playlist. When in `event` mode make sure playlist length +is enough for the whole event. Default is `live`; +```sh +hls_type event; +``` + +#### hls_allow_client_cache +Syntax: `hls_allow_client_cache enabled|disabled` +Context: rtmp, server, application + +Enables (or disables) client cache with `#EXT-X-ALLOW-CACHE` playlist +directive. Setting value to enabled allows supported clients to +cache segments in a live DVR manner. Setting value to disabled explicitly +tells supported clients to never cache segments. +Unset by default (playlist directive will be absent). +```sh +hls_allow_client_cache enabled; +``` + +#### hls_keys +Syntax: `hls_keys on|off` +Context: rtmp, server, application + +Enables HLS encryption. AES-128 method is used to encrypt the whole HLS fragments. +Off by default. +```sh +hls_keys on; +``` + +Here's the example configuration using the HLS encryption. This configuration +requires that nginx is built with `--with-http_ssl_module` for https support. +```sh +... + +http { + ... + server { + listen 443 ssl; + server_name example.com; + + ssl_certificate /var/ssl/example.com.cert; + ssl_certificate_key /var/ssl/example.com.key; + + location /keys { + root /tmp; + } + } + + server { + listen 80; + server_name example.com; + + location /hls { + root /tmp; + } + } +} +rtmp { + server { + listen 1935; + + application myapp { + live on; + + hls on; + hls_path /tmp/hls; + + hls_keys on; + hls_key_path /tmp/keys; + hls_key_url https://example.com/keys/; + hls_fragments_per_key 10; + } + } +} +``` + +#### hls_key_path +Syntax: `hls_key_path path` +Context: rtmp, server, application + +Sets the directory where auto-generated HLS keys are saved. +Key files have `.key` extension and pseudo-random 16-byte content +created with the OpenSSL `RAND_bytes()` routine. +If the directory does not exist it's created in runtime. +By default, `hls_path` directory is used for key files. +Remember however you should normally restrict access to key files which +is easier when these files are stored separately from playlist and fragments. +```sh +hls_key_path /tmp/keys; +``` + +#### hls_key_url +Syntax: `hls_key_url url` +Context: rtmp, server, application + +Sets url for HLS key file entries. When empty those +items have no prefix and keys are assumed to be at the same location +as the playlist. Empty by default. +```sh +hls_key_url https://myserver.com/keys/; +``` + +Example playlist entry with the above setting +```sh +#EXT-X-KEY:METHOD=AES-128,URI="https://myserver.com/keys/337.key",IV=0x00000000000000000000000000000151 +``` + +#### hls_fragments_per_key +Syntax: `hls_fragments_per_key value` +Context: rtmp, server, application + +Sets the number of HLS fragments encrypted with the same key. +Zero means only one key is created at the publish start +and all fragments within the session are encrypted with this key. +Default is zero. +```sh +hls_fragments_per_key 10; +``` + +## MPEG-DASH + +#### dash +Syntax: `dash on|off` +Context: rtmp, server, application + +Toggles MPEG-DASH on the application. +```sh +dash on; +dash_path /tmp/dash; +dash_fragment 15s; +``` + +In `http{}` section set up the following location for clients to play MPEG-DASH. +```sh +http { + ... + server { + ... + location /dash { + root /tmp; + add_header Cache-Control no-cache; + + # To avoid issues with cross-domain HTTP requests (e.g. during development) + add_header Access-Control-Allow-Origin *; + } + } +} +``` + +#### dash_path +Syntax: `dash_path path` +Context: rtmp, server, application + +Sets MPEG-DASH playlist and fragment directory. If the directory does not +exists it will be created. + +#### dash_fragment +Syntax: `dash_fragment time` +Context: rtmp, server, application + +Sets MPEG-DASH fragment length. Defaults to 5 seconds. + +#### dash_playlist_length +Syntax: `dash_playlist_length time` +Context: rtmp, server, application + +Sets MPEG-DASH playlist length. Defaults to 30 seconds. +```sh +dash_playlist_length 10m; +``` + +#### dash_nested +Syntax: `dash_nested on|off` +Context: rtmp, server, application + +Toggles MPEG-DASH nested mode. In this mode a subdirectory +of `dash_path` is created for each stream. Playlist +and fragments are created in that subdirectory. +Default is off. +```sh +dash_nested on; +``` + +#### dash_cleanup +Syntax: `dash_cleanup on|off` +Context: rtmp, server, application + +Toggles MPEG-DASH cleanup. By default the feature is on. +In this mode nginx cache manager process removes old +MPEG-DASH fragments and manifests from MPEG-DASH directory. +Init fragments are deleted after stream manifest is deleted. +```sh +dash_cleanup off; +``` + +#### dash\_clock_compensation +Syntax: `dash_clock_compensation off|ntp|http_head|http_iso` +Context: rtmp, server, application +Default: off + +Toggles MPEG-DASH clock compentation element output into MPD. +In this mode nginx provides `UTCTiming` element for MPEG-DASH manifest. +Clock compensation provided by DASH-client if possible. +- ntp - use NTP protocol +- http_head - client must fetch header `Date` from URI (`dash_clock_helper_uri`) +- http_iso - client must fetch date in ISO format from URI (`dash_clock_helper_uri`) + +Standard section: 4.7.2. Service Provider Requirements and Guidelines + +```sh +dash\_clock_compensation off; +``` + +#### dash\_clock_helper_uri +Syntax: `dash_clock_helper_uri URI` +Context: rtmp, server, application +Default: none + +URI helper resource for clock compensation for client. +Clock compensation type: +- ntp - address of NTP-server +- http\_head - full HTTP uri +- http\_iso - full HTTP uri + +Standard section: 4.7.2. Service Provider Requirements and Guidelines + +```sh +dash\_clock\_helper_uri http://rtmp-server/static/time.txt; + +_or_ + +dash\_clock\_helper_uri http://rtmp-server/lua/time-iso; +``` + +## Access log + +#### access_log +Syntax: `access_log off|path [format_name]` +Context: rtmp, server, application + +Sets access log parameters. Logging is turned on by default. +To turn it off use `access_log off` directive. By default access logging +is done to the same file as HTTP access logger (`logs/access.log`). +You can specify another log file path in `access_log` directive. +Second argument is optional. It can be used to specify logging format by name. +See `log_format` directive for more details about formats. +```sh +log_format new '$remote_addr'; +access_log logs/rtmp_access.log new; +access_log logs/rtmp_access.log; +access_log off; +``` + +#### log_format +Syntax: `log_format format_name format` +Context: rtmp + +Creates named log format. Log formats look very much the same as nginx HTTP log +formats. Several variables are supported within log format: +* `connection` - connection number +* `remote_addr` - client address +* `app` - application name +* `name` - last stream name +* `args` - last stream play/publish arguments +* `flashver` - client flashVer +* `swfurl` - client swfUrl +* `tcurl` - client tcUrl +* `pageurl` - client pageUrl +* `command` - play/publish commands sent by client: `NONE`, `PLAY`, `PUBLISH`, `PLAY+PUBLISH` +* `bytes_sent` - number of bytes sent to client +* `bytes_received` - number of bytes received from client +* `time_local` - local time at the end of client connection +* `session_time` - connection duration in seconds +* `session_readable_time` - connection duration in human-readable format +* `msec` - current unix timestamp in SEC.MSEC format + +Default log format has the name `combined`. Here's the definition of this format +```sh +$remote_addr [$time_local] $command "$app" "$name" "$args" - +$bytes_received $bytes_sent "$pageurl" "$flashver" ($session_readable_time) +``` + +## Limits + +#### max_connections +Syntax: `max_connections number` +Context: rtmp, server, application + +Sets maximum number of connections for rtmp engine. Off by default. +```sh +max_connections 100; +``` + +## Statistics + +Statistics module is NGINX HTTP module unlike all other modules listed +here. Hence statistics directives should be located within http{} block. + +#### rtmp_stat +Syntax: `rtmp_stat all` +Context: http, server, location + +Sets RTMP statistics handler to the current HTTP location. RTMP statistics is +dynamic XML document. To watch this document in browser as XHTML page +use rtmp_stat_stylesheet directive. +```sh +http { + server { + location /stat { + rtmp_stat all; + rtmp_stat_stylesheet stat.xsl; + } + location /stat.xsl { + root /path/to/stat/xsl/file; + } + } +} +``` + +#### rtmp_stat_stylesheet +Syntax: `rtmp_stat_stylesheet path` +Context: http, server, location + +Adds XML stylesheet reference to statistics XML to make it viewable +in browser. See rtmp_stat description and example for more information. + +## Multi-worker live streaming + +Multi-worker live streaming is implemented through pushing stream +to remaining nginx workers. + +#### rtmp_auto_push +Syntax: `rtmp_auto_push on|off` +Context: root + +Toggles auto-push (multi-worker live streaming) mode. +Default is off. + +#### rtmp_auto_push_reconnect +Syntax: `rtmp_auto_push_reconnect timeout` +Context: root + +Sets auto-push reconnect timeout when worker is killed. +Default is 100 milliseconds. + +#### rtmp_socket_dir +Syntax: `rtmp_socket_dir dir` +Context: root + +Sets directory for UNIX domains sockets used for stream pushing. +Default is `/tmp`. +```sh +rtmp_auto_push on; +rtmp_auto_push_reconnect 1s; +rtmp_socket_dir /var/sock; + +rtmp { + server { + listen 1935; + application myapp { + live on; + } + } +} +``` + +## Control + +Control module is NGINX HTTP module and should be located within http{} block. + +#### rtmp_control +Syntax: `rtmp_control all` +Context: http, server, location + +Sets RTMP control handler to the current HTTP location. +```sh +http { + server { + location /control { + rtmp_control all; + } + } +} +``` + +[More details about control module](control_modul.md) \ No newline at end of file diff --git a/doc/examples.md b/doc/examples.md new file mode 100644 index 000000000..f99bce167 --- /dev/null +++ b/doc/examples.md @@ -0,0 +1,68 @@ +# Exampled + +### Simple Video-on-Demand + +```sh +rtmp { + server { + listen 1935; + application vod { + play /var/flvs; + } + } +} +``` + +### Simple live broadcast service +```sh +rtmp { + server { + listen 1935; + application live { + live on; + } + } +} +``` + +### Re-translate remote stream +```sh +rtmp { + server { + listen 1935; + application tv { + live on; + pull rtmp://cdn.example.com:443/programs/main pageUrl=http://www.example.com/index.html name=maintv; + } + } +} + +### Re-translate remote stream with HLS support +```sh +rtmp { + server { + listen 1935; + application tv { + live on; + hls on; + hls_path /tmp/tv2; + hls_fragment 15s; + + pull rtmp://tv2.example.com:443/root/new name=tv2; + } + } +} +http { + server { + listen 80; + location /tv2 { + alias /tmp/tv2; + } + } +} +``` + +### Stream your X screen through RTMP +```sh +$ ffmpeg -f x11grab -follow_mouse centered -r 25 -s cif -i :0.0 -f flv rtmp://localhost/myapp/screen +``` diff --git a/doc/exec_wrapper_in_bash.md b/doc/exec_wrapper_in_bash.md new file mode 100644 index 000000000..b907e89b0 --- /dev/null +++ b/doc/exec_wrapper_in_bash.md @@ -0,0 +1,33 @@ +# Exec wrapper in bash + +You can write exec wrapper in any language. However you should pay attention to termination process. When publisher closes the stream all executed processed get terminated. If you specify wrapper in ```exec``` directive instead of real ffmpeg then you might end up with your ffmpeg still alive and orphaned until it times out reading input data. + +The solution is using signal traps. Here's an example of such wrapper in bash. + +```sh +#!/bin/bash + +on_die () +{ + # kill all children + pkill -KILL -P $$ +} + +trap 'on_die' TERM +ffmpeg -i rtmp://localhost/myapp/$1 -c copy -f flv rtmp://localhost/myapp2/$1 & +wait +``` + +The script registers SIGTERM handler which terminates child ffmpeg. Default signal sent by nginx-rtmp is SIGKILL which cannot be caught. For the above script to behave as expected you need to change exec kill signal with ```exec_kill_signal``` directive. It accept numeric or symbolic signal name (for POSIX.1-1990 signals). Here's example application. +```sh +application myapp { + live on; + + exec /var/scripts/exec_wrapper.sh $name; + exec_kill_signal term; +} + +application myapp2 { + live on; +} +``` diff --git a/doc/faq.md b/doc/faq.md new file mode 100644 index 000000000..cbf347726 --- /dev/null +++ b/doc/faq.md @@ -0,0 +1,26 @@ +# FAQ + +####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. + +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. + +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 + +Check if you use multiple workers in nginx (`worker_processes`). In such case you have to enable: +```sh +rtmp_auto_push on; +``` diff --git a/doc/getting_number_of_subscribers.md b/doc/getting_number_of_subscribers.md new file mode 100644 index 000000000..02df35784 --- /dev/null +++ b/doc/getting_number_of_subscribers.md @@ -0,0 +1,38 @@ +# Getting number of subscribers + +There's an easy way to display number of clients watching the stream. You need to + +Set up statistics page at location `/stat` +```sh +location /stat { + rtmp_stat all; + allow 127.0.0.1; +} +``` + +Create a simple xsl stylesheet `nclients.xsl` extracting number of stream subscribers +```xsl + + + + + + + + + + + + +``` + +Set up a location returning number of suscribers +```sh +location /nclients { + proxy_pass http://127.0.0.1/stat; + xslt_stylesheet /www/nclients.xsl app='$arg_app' name='$arg_name'; + add_header Refresh "3; $request_uri"; +} +``` + +Use HTTP request `http://myserver.com/nclients?app=myapp&name=mystream` to get the number of stream subscribers. This number will be automatically refreshed every 3 seconds when opened in browser or iframe. diff --git a/doc/getting_started.md b/doc/getting_started.md new file mode 100644 index 000000000..fe95a60b4 --- /dev/null +++ b/doc/getting_started.md @@ -0,0 +1,188 @@ +# Getting started with nginx rtmp + +## Download, build and install + +CD to build directory (home) +```sh +$ cd /usr/build +``` + +Download & unpack latest nginx-rtmp (you can also use http) +```sh +$ git clone git://github.com/sergey-dryabzhinsky/nginx-rtmp-module +``` + +Download & unpack nginx (you can also use svn) + +```sh +$ wget http://nginx.org/download/nginx-1.2.4.tar.gz +$ tar xzf nginx-1.2.4.tar.gz +$ cd nginx-1.2.4 +``` + +Build nginx with nginx-rtmp +```sh +$ ./configure --add-module=/usr/build/nginx-rtmp-module +$ make +$ make install +``` + +For nginx 1.3.4-1.5.0 more options are needed +```sh +$ ./configure --add-module=/usr/build/nginx-rtmp-module --with-http_ssl_module +$ make +$ make install +``` + +## Set up live streaming + +To set up RTMP support you need to add `rtmp{}` section to `nginx.conf` (can be found in PREFIX/conf/nginx.conf). Stock `nginx.conf` contains only `http{}` section. + +Use this `nginx.conf` instead of stock config: +```sh +#user nobody; +worker_processes 1; + +error_log logs/error.log debug; + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + + sendfile on; + keepalive_timeout 65; + + server { + listen 8080; + server_name localhost; + + # sample handlers + #location /on_play { + # if ($arg_pageUrl ~* localhost) { + # return 201; + # } + # return 202; + #} + #location /on_publish { + # return 201; + #} + + #location /vod { + # alias /var/myvideos; + #} + + # rtmp stat + location /stat { + rtmp_stat all; + rtmp_stat_stylesheet stat.xsl; + } + location /stat.xsl { + # you can move stat.xsl to a different location + root /usr/build/nginx-rtmp-module; + } + + # rtmp control + location /control { + rtmp_control all; + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + } +} +rtmp { + server { + listen 1935; + ping 30s; + notify_method get; + + application myapp { + live on; + + # sample play/publish handlers + #on_play http://localhost:8080/on_play; + #on_publish http://localhost:8080/on_publish; + + # sample recorder + #recorder rec1 { + # record all; + # record_interval 30s; + # record_path /tmp; + # record_unique on; + #} + + # sample HLS + #hls on; + #hls_path /tmp/hls; + #hls_sync 100ms; + } + + # Video on demand + #application vod { + # play /var/Videos; + #} + + # Video on demand over HTTP + #application vod_http { + # play http://localhost:8080/vod/; + #} + } +} +``` + +## Statistics + +Navigate your browser to `http://localhost:8080/stat` to see current +streaming statistics, connected clients, bandwidth etc. + +## Publishing with ffmpeg + +The easiest way to publish live video stream is using ffmpeg (or avconv). +It's already installed on most systems and easy to install on others. + +RTMP supports only a limited number of codecs. The most popular RTMP video +codecs are H264, Sorenson-H263 (aka flv) and audio codecs AAC, MP3, +Nellymoser, Speex. If your video is encoded with these codecs +(the most common pair is H264/AAC) then you do not need any conversion. +Otherwise you need to convert video to one of supported codecs. + +We'll stream test file `/var/videos/test.mp4` to server with ffmpeg. + +Streaming without conversion (given `test.mp4` codecs are compatible with RTMP) +```sh +$ ffmpeg -re -i /var/Videos/test.mp4 -c copy -f flv rtmp://localhost/myapp/mystream +``` + +Streaming and encoding audio (AAC) and video (H264), need `libx264` and `libfaac` +```sh +$ ffmpeg -re -i /var/Videos/test.mp4 -c:v libx264 -c:a libfaac -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream +``` + +Streaming and encoding audio (MP3) and video (H264), need `libx264` and `libmp3lame` +```sh +$ ffmpeg -re -i /var/Videos/test.mp4 -c:v libx264 -c:a libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream +``` + +Streaming and encoding audio (Nellymoser) and video (Sorenson H263) +```sh +$ ffmpeg -re -i /var/Videos/test.mp4 -c:v flv -c:a nellymoser -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream +``` + +## Publishing video from webcam +```sh +$ ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264 -an -f flv rtmp://localhost/myapp/mystream +``` + +## Playing with ffplay +```sh +$ ffplay rtmp://localhost/myapp/mystream +``` + +## Publishing and playing with flash + +See `test/rtmp-publisher` directory for test flash applets and html. diff --git a/doc/installing_in_gentoo.md b/doc/installing_in_gentoo.md new file mode 100644 index 000000000..19165f236 --- /dev/null +++ b/doc/installing_in_gentoo.md @@ -0,0 +1,24 @@ +# Installing in Gentoo + +## Download module source code +You have many options: +* Get the zip at https://github.com/arut/nginx-rtmp-module/archive/master.zip +* Or much better, do a git clone (see options in top of https://github.com/arut/nginx-rtmp-module) +* Or get an ebuild from [fem-overlay](http://subversion.fem.tu-ilmenau.de/repository/fem-overlay/trunk/www-servers/nginx/nginx-1.2.5-r1.ebuild). And set the USE flag "nginx_modules_rtmp" or "nginx_modules_rtmp_hls". + +## Emerge nginx with nginx-rtmp-module +> NGINX_ADD_MODULES="/path/to/nginx-rtmp-module" emerge -va nginx + +Replace `/path/to/` with the actual module's source path. +You can add with this method any number of custom modules. + +To make this change permanent see: +http://wiki.gentoo.org/wiki/Knowledge_Base:Overriding_environment_variables_per_package + +## Configure nginx +Don't forget to include a rtmp section inside your nginx configuration file located at `/etc/nginx/nginx.conf`. + +See: +* [Getting started](getting_started.md) We already have done _Download, build and install_ Gentoo style ;-) +* [More Examples](examples.md) +* [Reference of all directives](directives.md) diff --git a/doc/installing_ubuntu_using_ppas.md b/doc/installing_ubuntu_using_ppas.md new file mode 100644 index 000000000..90da6f8ef --- /dev/null +++ b/doc/installing_ubuntu_using_ppas.md @@ -0,0 +1,30 @@ +# Installing on Ubuntu using PPAs +```sh +$ sudo apt-get install dpkg-dev +$ sudo apt-get source nginx +$ cd /usr/src/nginx +$ sudo git clone https://github.com/arut/nginx-rtmp-module.git +$ cd nginx-[version-number] +$ sudo vi debian/rules +``` + +Edit the rules and at then end of the add-modules configuration string add +```sh +--add-module=/usr/src/nginx/nginx-rtmp-module \ +``` + +If installing for the first time build nginx dependancies. +```sh +$ sudo apt-get build-dep nginx +$ dpkg-buildpackage -b +``` + +(wait for a while while it builds... a really long while... like you might want to go grab a meal) + +```sh +$ cd .. && sudo dpkg --install nginx-common_1.3.13-1chl1~quantal1_all.deb nginx-full_1.3.13-1chl1~quantal1_amd64.deb +$ sudo service nginx status +$ sudo service nginx start (if nginx isn't running) +``` + +[Source](http://serverfault.com/questions/227480/installing-optional-nginx-modules-with-apt-get) diff --git a/doc/tutorial.md b/doc/tutorial.md new file mode 100644 index 000000000..baa90b8fc --- /dev/null +++ b/doc/tutorial.md @@ -0,0 +1,114 @@ +# Tutorial + +[This article is not finished yet] + +## RTMP +RTMP is a proprietary protocol developed by Adobe (Macromedia) for use +in flash player. Until 2009 it had no public specification. +A number of third-party RTMP-related products started in that period +were based on the results of reverse engineering. In 2009 +[RTMP specification](http://www.adobe.com/devnet/rtmp.html) has been +published which made developing such applications easier. However +the spec is not full and misses significant issues concerning streaming H264. + +## System requirements +The module has been tested on Linux x86-family platforms. +However it should work on FreeBSD too. + +## Licence +The module is distributed under BSD license. + +## Building NGINX with the module +Building is pretty obvious. Just cd to nginx source directory +and configure nginx this way: +```sh +$ ./configure --add-module=/path/to/nginx-rtmp-module +``` + +Then `make` and `make install`. + +## Configuration + +## Simple live application +Simple live application configuration: +```sh +application live { + + live on; + +} +``` + +You can add access list control: +```sh +application live { + + live on; + + allow publish 127.0.0.1; + deny publish all; + allow play all; + +} +``` + +And you can add record support for live streams: +```sh +application live { + + live on; + + allow publish 127.0.0.1; + deny publish all; + allow play all; + + record all; + record_path /path/to/record/dir; + record_max_size 100M; + record_unique off; + +} +``` + +## HLS (HTTP Live Streaming) + +## Choosing flash player +To watch RTMP stream in browser one should either develop +flash application for that or use one of available flash +players. The most popular players which are proved to have +no problems with the module are: + +* [JWPlayer](http://www.longtailvideo.com/) +* [FlowPlayer](http://flowplayer.org/) +* [Strobe Media Playback](http://www.osmf.org/strobe_mediaplayback.html) +* [Clappr](https://github.com/globocom/clappr) + +Old versions of JWPlayer (<=4.4) supported capturing video +from webcam. You can find that version in test/ subdirectory. +However audio is not captured by this version of player. +Recent free versions of JWPlayer have no capture capability at +all. + +## Transcoding streams +You can use exec directive and ffmpeg for transcoding streams. For example: +```sh +application big { + live on; + exec /usr/bin/ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec flv -acodec copy -s 32x32 -f flv rtmp://localhost:1935/small/${name}; +} +application small { + live on; +} +``` + +## Video on demand + +## Distributed streaming + +## Notifications & access control + +## Statistics + +## Verifying session + +## Utilizing multi-core CPUs diff --git a/hls/ngx_rtmp_hls_module.c b/hls/ngx_rtmp_hls_module.c index bbdd40439..1ed0faa7f 100644 --- a/hls/ngx_rtmp_hls_module.c +++ b/hls/ngx_rtmp_hls_module.c @@ -16,6 +16,7 @@ static ngx_rtmp_publish_pt next_publish; static ngx_rtmp_close_stream_pt next_close_stream; static ngx_rtmp_stream_begin_pt next_stream_begin; static ngx_rtmp_stream_eof_pt next_stream_eof; +static ngx_rtmp_playlist_pt next_playlist; static char * ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd, @@ -35,6 +36,7 @@ static ngx_int_t ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s, typedef struct { uint64_t id; uint64_t key_id; + ngx_str_t *datetime; double duration; unsigned active:1; unsigned discont:1; /* before */ @@ -100,12 +102,14 @@ typedef struct { ngx_flag_t nested; ngx_str_t path; ngx_uint_t naming; + ngx_uint_t datetime; ngx_uint_t slicing; ngx_uint_t type; ngx_path_t *slot; ngx_msec_t max_audio_delay; size_t audio_buffer_size; ngx_flag_t cleanup; + ngx_uint_t allow_client_cache; ngx_array_t *variant; ngx_str_t base_url; ngx_int_t granularity; @@ -121,6 +125,11 @@ typedef struct { #define NGX_RTMP_HLS_NAMING_SYSTEM 3 +#define NGX_RTMP_HLS_DATETIME_NONE 1 +#define NGX_RTMP_HLS_DATETIME_SYSTEM 2 +#define NGX_RTMP_HLS_DATETIME_TIMESTAMP 3 + + #define NGX_RTMP_HLS_SLICING_PLAIN 1 #define NGX_RTMP_HLS_SLICING_ALIGNED 2 @@ -128,6 +137,9 @@ typedef struct { #define NGX_RTMP_HLS_TYPE_LIVE 1 #define NGX_RTMP_HLS_TYPE_EVENT 2 +#define NGX_RTMP_HLS_CACHE_DISABLED 1 +#define NGX_RTMP_HLS_CACHE_ENABLED 2 + static ngx_conf_enum_t ngx_rtmp_hls_naming_slots[] = { { ngx_string("sequential"), NGX_RTMP_HLS_NAMING_SEQUENTIAL }, @@ -137,6 +149,14 @@ static ngx_conf_enum_t ngx_rtmp_hls_naming_slots[] = { }; +static ngx_conf_enum_t ngx_rtmp_hls_datetime_slots[] = { + { ngx_string("none"), NGX_RTMP_HLS_DATETIME_NONE }, + { ngx_string("system"), NGX_RTMP_HLS_DATETIME_SYSTEM }, + { ngx_string("timestamp"), NGX_RTMP_HLS_DATETIME_TIMESTAMP }, + { ngx_null_string, 0 } +}; + + static ngx_conf_enum_t ngx_rtmp_hls_slicing_slots[] = { { ngx_string("plain"), NGX_RTMP_HLS_SLICING_PLAIN }, { ngx_string("aligned"), NGX_RTMP_HLS_SLICING_ALIGNED }, @@ -150,6 +170,11 @@ static ngx_conf_enum_t ngx_rtmp_hls_type_slots[] = { { ngx_null_string, 0 } }; +static ngx_conf_enum_t ngx_rtmp_hls_cache[] = { + { ngx_string("enabled"), NGX_RTMP_HLS_CACHE_ENABLED }, + { ngx_string("disabled"), NGX_RTMP_HLS_CACHE_DISABLED }, + { ngx_null_string, 0 } +}; static ngx_command_t ngx_rtmp_hls_commands[] = { @@ -223,6 +248,13 @@ static ngx_command_t ngx_rtmp_hls_commands[] = { offsetof(ngx_rtmp_hls_app_conf_t, naming), &ngx_rtmp_hls_naming_slots }, + { ngx_string("hls_datetime"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, datetime), + &ngx_rtmp_hls_datetime_slots }, + { ngx_string("hls_fragment_slicing"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, ngx_conf_set_enum_slot, @@ -258,6 +290,13 @@ static ngx_command_t ngx_rtmp_hls_commands[] = { offsetof(ngx_rtmp_hls_app_conf_t, cleanup), NULL }, + { ngx_string("hls_allow_client_cache"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, allow_client_cache), + &ngx_rtmp_hls_cache }, + { ngx_string("hls_variant"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, ngx_rtmp_hls_variant, @@ -400,6 +439,8 @@ ngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s) ngx_rtmp_hls_app_conf_t *hacf; ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_playlist_t v; + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); @@ -433,7 +474,7 @@ ngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s) p = buffer; last = buffer + sizeof(buffer); - p = ngx_slprintf(p, last, "#EXT-X-STREAM-INF:PROGRAM-ID=1"); + p = ngx_slprintf(p, last, "#EXT-X-STREAM-INF:PROGRAM-ID=1,CLOSED-CAPTIONS=NONE"); arg = var->args.elts; for (k = 0; k < var->args.nelts; k++, arg++) { @@ -476,7 +517,11 @@ ngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s) return NGX_ERROR; } - return NGX_OK; + ngx_memzero(&v, sizeof(v)); + ngx_str_set(&(v.module), "hls"); + v.playlist.data = ctx->playlist.data; + v.playlist.len = ctx->playlist.len; + return next_playlist(s, &v); } @@ -496,6 +541,10 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) uint64_t prev_key_id; const char *sep, *key_sep; + ngx_rtmp_playlist_t v; + + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "hls: write playlist"); hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); @@ -531,16 +580,18 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) ctx->frag, max_frag); if (hacf->type == NGX_RTMP_HLS_TYPE_EVENT) { - p = ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE: EVENT\n"); + p = ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE:EVENT\n"); + } + + if (hacf->allow_client_cache == NGX_RTMP_HLS_CACHE_ENABLED) { + p = ngx_slprintf(p, end, "#EXT-X-ALLOW-CACHE:YES\n"); + } else if (hacf->allow_client_cache == NGX_RTMP_HLS_CACHE_DISABLED) { + p = ngx_slprintf(p, end, "#EXT-X-ALLOW-CACHE:NO\n"); } n = ngx_write_fd(fd, buffer, p - buffer); if (n < 0) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, - "hls: " ngx_write_fd_n " failed: '%V'", - &ctx->playlist_bak); - ngx_close_file(fd); - return NGX_ERROR; + goto write_err; } sep = hacf->nested ? (hacf->base_url.len ? "/" : "") : "-"; @@ -560,6 +611,21 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) for (i = 0; i < ctx->nfrags; i++) { f = ngx_rtmp_hls_get_frag(s, i); + if ((i == 0 || 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) { + goto write_err; + } + n = ngx_write_fd(fd, f->datetime->data, f->datetime->len); + if (n < 0) { + goto write_err; + } + n = ngx_write_fd(fd, "\n", 1); + if (n < 0) { + goto write_err; + } + } p = buffer; end = p + sizeof(buffer); @@ -589,11 +655,7 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) n = ngx_write_fd(fd, buffer, p - buffer); if (n < 0) { - ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, - "hls: " ngx_write_fd_n " failed '%V'", - &ctx->playlist_bak); - ngx_close_file(fd); - return NGX_ERROR; + goto write_err; } } @@ -612,7 +674,18 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) return ngx_rtmp_hls_write_variant_playlist(s); } - return NGX_OK; + ngx_memzero(&v, sizeof(v)); + ngx_str_set(&(v.module), "hls"); + v.playlist.data = ctx->playlist.data; + v.playlist.len = ctx->playlist.len; + return next_playlist(s, &v); + +write_err: + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: " ngx_write_fd_n " failed '%V'", + &ctx->playlist_bak); + ngx_close_file(fd); + return NGX_ERROR; } @@ -818,6 +891,52 @@ ngx_rtmp_hls_get_fragment_id(ngx_rtmp_session_t *s, uint64_t ts) } +static ngx_str_t * +ngx_rtmp_hls_get_fragment_datetime(ngx_rtmp_session_t *s, uint64_t ts) +{ + ngx_rtmp_hls_app_conf_t *hacf; + ngx_str_t *datetime; + ngx_tm_t tm; + uint64_t msec; + + datetime = (ngx_str_t *) ngx_pcalloc(s->connection->pool, sizeof(ngx_str_t)); + datetime->data = NULL; + datetime->len = 0; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + switch (hacf->datetime) { + + case NGX_RTMP_HLS_DATETIME_TIMESTAMP: + /* Timestamps in RTMP are given as an integer number of milliseconds + * relative to an unspecified epoch, so we clear the last 32 bits + * from system time, and add the timestamp from RTMP. */ + msec = ngx_cached_time->sec * 1000 + ngx_cached_time->msec; + msec /= 4294967296; //2**32 + msec *= 4294967296; + msec += (ts / 90); + ngx_gmtime(msec / 1000, &tm); + + datetime->data = (u_char *) ngx_pcalloc(s->connection->pool, ngx_cached_http_log_iso8601.len * sizeof(u_char)); + (void) ngx_sprintf(datetime->data, "%4d-%02d-%02dT%02d:%02d:%02d-00:00", + tm.ngx_tm_year, tm.ngx_tm_mon, + tm.ngx_tm_mday, tm.ngx_tm_hour, + tm.ngx_tm_min, tm.ngx_tm_sec); + datetime->len = ngx_cached_http_log_iso8601.len; + return datetime; + + case NGX_RTMP_HLS_DATETIME_SYSTEM: + datetime->data = (u_char *) ngx_pcalloc(s->connection->pool, ngx_cached_http_log_iso8601.len * sizeof(u_char)); + ngx_memcpy(datetime->data, ngx_cached_http_log_iso8601.data, ngx_cached_http_log_iso8601.len); + datetime->len = ngx_cached_http_log_iso8601.len; + return datetime; + + default: /* NGX_RTMP_HLS_DATETIME_NONE */ + return datetime; + } +} + + static ngx_int_t ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s) { @@ -847,13 +966,14 @@ static ngx_int_t ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts, ngx_int_t discont) { - uint64_t id; - ngx_fd_t fd; - ngx_uint_t g; - ngx_rtmp_hls_ctx_t *ctx; - ngx_rtmp_hls_frag_t *f; - ngx_rtmp_hls_app_conf_t *hacf; - ngx_rtmp_core_srv_conf_t *cscf; + uint64_t id; + ngx_fd_t fd; + ngx_str_t *datetime; + ngx_uint_t g, mpegts_cc; + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_rtmp_hls_frag_t *f; + ngx_rtmp_hls_app_conf_t *hacf; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); @@ -875,6 +995,7 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts, } id = ngx_rtmp_hls_get_fragment_id(s, ts); + datetime = ngx_rtmp_hls_get_fragment_datetime(s, ts); if (hacf->granularity) { g = (ngx_uint_t) hacf->granularity; @@ -932,12 +1053,15 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts, } } - ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + // This is continuity counter for TS header + mpegts_cc = (ngx_uint_t)(ctx->nfrags + ctx->frag); + + ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: open fragment file='%s', keyfile='%s', " - "frag=%uL, n=%ui, time=%uL, discont=%i", + "frag=%uL, n=%ui, time=%uL, discont=%i, tscc=%ui", ctx->stream.data, ctx->keyfile.data ? ctx->keyfile.data : (u_char *) "", - ctx->frag, ctx->nfrags, ts, discont); + ctx->frag, ctx->nfrags, ts, discont, mpegts_cc); if (hacf->keys && ngx_rtmp_mpegts_init_encryption(&ctx->file, ctx->key, 16, ctx->key_id) @@ -948,8 +1072,10 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts, return NGX_ERROR; } + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ngx_rtmp_mpegts_open_file(&ctx->file, ctx->stream.data, - cscf->file_access, s->connection->log) + s->connection->log, codec_ctx, mpegts_cc) != NGX_OK) { return NGX_ERROR; @@ -965,6 +1091,7 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts, f->discont = discont; f->id = id; f->key_id = ctx->key_id; + f->datetime = datetime; ctx->frag_ts = ts; @@ -1579,6 +1706,9 @@ ngx_rtmp_hls_update_fragment(ngx_rtmp_session_t *s, uint64_t ts, ngx_buf_t *b; int64_t d; + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "hls: update fragment"); + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); f = NULL; @@ -1696,7 +1826,7 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, size_t bsize; ngx_buf_t *b; u_char *p; - ngx_uint_t objtype, srindex, chconf, size; + ngx_uint_t objtype, srindex, chconf, size, samples_per_frame; hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); @@ -1710,8 +1840,14 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, return NGX_OK; } - if (codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC || - codec_ctx->aac_header == NULL || ngx_rtmp_is_codec_header(in)) + if (codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC && + codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_MP3) + { + return NGX_OK; + } + + if ((codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC && + codec_ctx->aac_header == NULL) || ngx_rtmp_is_codec_header(in)) { return NGX_OK; } @@ -1736,7 +1872,7 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, b->pos = b->last = b->start; } - size = h->mlen - 2 + 7; + size = codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_MP3 ? h->mlen - 1 : h->mlen - 2 + 7; pts = (uint64_t) h->timestamp * 90; if (b->start + size > b->end) { @@ -1760,14 +1896,22 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: audio pts=%uL", pts); - if (b->last + 7 > b->end) { - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "hls: not enough buffer for audio header"); - return NGX_OK; - } - p = b->last; - b->last += 5; + if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) { + if (b->last + 7 > b->end) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: not enough buffer for audio header"); + return NGX_OK; + } + b->last += 5; + } + else { + /* For some reason the pointer is already incremented past the rest + of the RTMP frame header. I'm not sure where in the code this is + being done. Regardless, there's an extra byte that needs to be skipped + for MP3. */ + in->buf->pos += 1; + } /* copy payload */ @@ -1783,28 +1927,30 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, /* make up ADTS header */ - if (ngx_rtmp_hls_parse_aac_header(s, &objtype, &srindex, &chconf) - != NGX_OK) - { - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "hls: aac header error"); - return NGX_OK; - } + if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) { + if (ngx_rtmp_hls_parse_aac_header(s, &objtype, &srindex, &chconf) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: aac header error"); + return NGX_OK; + } - /* we have 5 free bytes + 2 bytes of RTMP frame header */ + /* we have 5 free bytes + 2 bytes of RTMP frame header */ - p[0] = 0xff; - p[1] = 0xf1; - p[2] = (u_char) (((objtype - 1) << 6) | (srindex << 2) | - ((chconf & 0x04) >> 2)); - p[3] = (u_char) (((chconf & 0x03) << 6) | ((size >> 11) & 0x03)); - p[4] = (u_char) (size >> 3); - p[5] = (u_char) ((size << 5) | 0x1f); - p[6] = 0xfc; + p[0] = 0xff; + p[1] = 0xf1; + p[2] = (u_char) (((objtype - 1) << 6) | (srindex << 2) | + ((chconf & 0x04) >> 2)); + p[3] = (u_char) (((chconf & 0x03) << 6) | ((size >> 11) & 0x03)); + p[4] = (u_char) (size >> 3); + p[5] = (u_char) ((size << 5) | 0x1f); + p[6] = 0xfc; - if (p != b->start) { - ctx->aframe_num++; - return NGX_OK; + if (p != b->start) { + ctx->aframe_num++; + return NGX_OK; + } } ctx->aframe_pts = pts; @@ -1818,7 +1964,9 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, /* TODO: We assume here AAC frame size is 1024 * Need to handle AAC frames with frame size of 960 */ - est_pts = ctx->aframe_base + ctx->aframe_num * 90000 * 1024 / + samples_per_frame = codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC ? 1024 : 1152; + + est_pts = ctx->aframe_base + ctx->aframe_num * 90000 * samples_per_frame / codec_ctx->sample_rate; dpts = (int64_t) (est_pts - pts); @@ -1831,6 +1979,10 @@ ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, { ctx->aframe_num++; ctx->aframe_pts = est_pts; + + if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_MP3) { + ngx_rtmp_hls_flush_audio(s); + } return NGX_OK; } @@ -1960,6 +2112,7 @@ ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: error appending AUD NAL"); } + /* fall through */ case 9: aud_sent = 1; break; @@ -2065,6 +2218,9 @@ ngx_rtmp_hls_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) static ngx_int_t ngx_rtmp_hls_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "hls: stream eof"); + ngx_rtmp_hls_flush_audio(s); ngx_rtmp_hls_close_fragment(s); @@ -2183,7 +2339,7 @@ ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen) name.data[name.len - 2] == 'u' && name.data[name.len - 1] == '8') { - max_age = playlen / 1000; + max_age = playlen / 500; } else if (name.len >= 4 && name.data[name.len - 4] == '.' && name.data[name.len - 3] == 'k' && @@ -2218,15 +2374,23 @@ ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen) } } - +#if (nginx_version >= 1011005) +static ngx_msec_t +#else static time_t +#endif ngx_rtmp_hls_cleanup(void *data) { ngx_rtmp_hls_cleanup_t *cleanup = data; ngx_rtmp_hls_cleanup_dir(&cleanup->path, cleanup->playlen); - return cleanup->playlen / 500; + // Next callback in half of playlist length time +#if (nginx_version >= 1011005) + return cleanup->playlen / 2; +#else + return cleanup->playlen / 2000; +#endif } @@ -2282,6 +2446,13 @@ ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +static ngx_int_t +ngx_rtmp_hls_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v) +{ + return next_playlist(s, v); +} + + static void * ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf) { @@ -2301,11 +2472,13 @@ ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf) conf->continuous = NGX_CONF_UNSET; conf->nested = NGX_CONF_UNSET; conf->naming = NGX_CONF_UNSET_UINT; + conf->datetime = NGX_CONF_UNSET_UINT; conf->slicing = NGX_CONF_UNSET_UINT; conf->type = NGX_CONF_UNSET_UINT; conf->max_audio_delay = NGX_CONF_UNSET_MSEC; conf->audio_buffer_size = NGX_CONF_UNSET_SIZE; conf->cleanup = NGX_CONF_UNSET; + conf->allow_client_cache = NGX_CONF_UNSET_UINT; conf->granularity = NGX_CONF_UNSET; conf->keys = NGX_CONF_UNSET; conf->frags_per_key = NGX_CONF_UNSET_UINT; @@ -2324,7 +2497,7 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->hls, prev->hls, 0); ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000); ngx_conf_merge_msec_value(conf->max_fraglen, prev->max_fraglen, - conf->fraglen * 10); + conf->fraglen * 2); ngx_conf_merge_msec_value(conf->muxdelay, prev->muxdelay, 700); ngx_conf_merge_msec_value(conf->sync, prev->sync, 2); ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000); @@ -2332,6 +2505,8 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->nested, prev->nested, 0); ngx_conf_merge_uint_value(conf->naming, prev->naming, NGX_RTMP_HLS_NAMING_SEQUENTIAL); + ngx_conf_merge_uint_value(conf->datetime, prev->datetime, + NGX_RTMP_HLS_DATETIME_NONE); ngx_conf_merge_uint_value(conf->slicing, prev->slicing, NGX_RTMP_HLS_SLICING_PLAIN); ngx_conf_merge_uint_value(conf->type, prev->type, @@ -2455,5 +2630,8 @@ ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf) next_stream_eof = ngx_rtmp_stream_eof; ngx_rtmp_stream_eof = ngx_rtmp_hls_stream_eof; + next_playlist = ngx_rtmp_playlist; + ngx_rtmp_playlist = ngx_rtmp_hls_playlist; + return NGX_OK; } diff --git a/hls/ngx_rtmp_mpegts.c b/hls/ngx_rtmp_mpegts.c index 3904f3871..128aa94ae 100644 --- a/hls/ngx_rtmp_mpegts.c +++ b/hls/ngx_rtmp_mpegts.c @@ -7,18 +7,30 @@ #include #include #include "ngx_rtmp_mpegts.h" +#include "ngx_rtmp_mpegts_crc.h" +#include "ngx_rtmp_codec_module.h" static u_char ngx_rtmp_mpegts_header[] = { - /* TS */ - 0x47, 0x40, 0x00, 0x10, 0x00, - /* PSI */ - 0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, + /* https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet */ + + /* TS Header */ + 0x47, // Sync byte + 0x40, 0x00, // TEI(1) + PUS(1) + TP(1) + PID(13) + 0x10, // TSC(2) + AFF(1) + PF(1) + CC(4) + 0x00, // adaption_field_length(8) + /* PAT */ - 0x00, 0x01, 0xf0, 0x01, - /* CRC */ - 0x2e, 0x70, 0x19, 0x05, + 0x00, // table_id(8) + 0xb0, 0x0d, // 1011b(4) + section_length(12) + 0x00, 0x01, // transport_stream_id(16) + 0xc1, 0x00, 0x00, // 11b(2) + VN(5) + CNI(1), section_no(8), last_section_no(8) + /* PAT program loop */ + 0x00, 0x01, 0xef, 0xff, // program_number(16), reserved(3) + program_map_pid(13) + /* PAT crc (CRC-32-MPEG2) */ + 0x36, 0x90, 0xe2, 0x3d, // !!! Needs to be recalculated each time any bit in PAT is modified (which we dont do at the moment) !!! + /* stuffing 167 bytes */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, @@ -38,19 +50,27 @@ static u_char ngx_rtmp_mpegts_header[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - /* TS */ - 0x47, 0x50, 0x01, 0x10, 0x00, - /* PSI */ - 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00, + /* TS Header */ + 0x47, // Sync byte + 0x4f, 0xff, // TEI(1) + PUS(1) + TP(1) + PID(13) + 0x10, // TSC(2) + AFF(1) + PF(1) + CC(4) + 0x00, // adaption_field_length(8) + /* PMT */ - 0xe1, 0x00, - 0xf0, 0x00, - 0x1b, 0xe1, 0x00, 0xf0, 0x00, /* h264 */ - 0x0f, 0xe1, 0x01, 0xf0, 0x00, /* aac */ - /*0x03, 0xe1, 0x01, 0xf0, 0x00,*/ /* mp3 */ - /* CRC */ - 0x2f, 0x44, 0xb9, 0x9b, /* crc for aac */ - /*0x4e, 0x59, 0x3d, 0x1e,*/ /* crc for mp3 */ + 0x02, // table_id(8) + 0xb0, 0x12, // 1011b(4) + section_length(12) (section length set below. Ignore this value in here) + 0x00, 0x01, // program_number(16) + 0xc1, 0x00, 0x00, // 11b(2) + VN(5) + CNI(1), section_no(8), last_section_no(8) + 0xe1, 0x00, // reserved(3) + PCR_PID(13) + 0xf0, 0x00, // reserved(4) + program_info_length(12) + + /* PMT component loop, looped through when writing header */ + /* Max size of 14 bytes */ + /* Also includes the PMT CRC, calculated dynamically */ + 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + /* stuffing 157 bytes */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, @@ -70,6 +90,32 @@ static u_char ngx_rtmp_mpegts_header[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; +static u_char ngx_rtmp_mpegts_header_h264[] = { + //H.264 Video, PID 0x100 + 0x1b, // stream_type(8) + 0xe1, 0x00, // reserved(3) + elementary_PID(13) + 0xf0, 0x00 // reserved(4) + ES_info_length(12) +}; + +static u_char ngx_rtmp_mpegts_header_mp3[] = { + //MP3 Audio, PID 0x101 + 0x03, // stream_type(8) + 0xe1, 0x01, // reserved(3) + elementary_PID(13) + 0xf0, 0x00 // reserved(4) + ES_info_length(12) +}; + +static u_char ngx_rtmp_mpegts_header_aac[] = { + //ADTS AAC Audio, PID 0x101 + 0x0f, // stream_type(8) + 0xe1, 0x01, // reserved(3) + elementary_PID(13) + 0xf0, 0x00 // reserved(4) + ES_info_length(12) +}; + +#define NGX_RTMP_MPEGTS_PMT_CRC_START_OFFSET 193 +#define NGX_RTMP_MPEGTS_PMT_CRC_MIN_LENGTH 12 +#define NGX_RTMP_MPEGTS_PMT_SECTION_LENGTH_OFFSET 195 +#define NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET 205 +#define NGX_RTMP_MPEGTS_PID_SIZE 5 /* 700 ms PCR delay */ #define NGX_RTMP_HLS_DELAY 63000 @@ -155,10 +201,53 @@ ngx_rtmp_mpegts_write_file(ngx_rtmp_mpegts_file_t *file, u_char *in, static ngx_int_t -ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file) +ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file, ngx_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t mpegts_cc) { - return ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_header, - sizeof(ngx_rtmp_mpegts_header)); + ngx_int_t next_pid_offset = 0; //Used to track the number of PIDs we have and the offset in 5-byte chunks + + //MPEG-TS CC is 4 bits long. Need to truncate it here. + mpegts_cc %= 0x0f; + // And then put it in the headers + ngx_rtmp_mpegts_header[3] = (ngx_rtmp_mpegts_header[3] & 0xf0) + (u_char)mpegts_cc; + ngx_rtmp_mpegts_header[191] = (ngx_rtmp_mpegts_header[191] & 0xf0) + (u_char)mpegts_cc; + + //ngx_rtmp_mpegts_header + + if (codec_ctx->video_codec_id) + { + //Put h264 PID in the PMT + ngx_memcpy(ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset, ngx_rtmp_mpegts_header_h264, NGX_RTMP_MPEGTS_PID_SIZE); + + next_pid_offset += NGX_RTMP_MPEGTS_PID_SIZE; + } + + if (codec_ctx->audio_codec_id){ + //Put Audio PID in the PMT + if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) { + ngx_memcpy(ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset, ngx_rtmp_mpegts_header_aac, NGX_RTMP_MPEGTS_PID_SIZE); + } + else + { + ngx_memcpy(ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset, ngx_rtmp_mpegts_header_mp3, NGX_RTMP_MPEGTS_PID_SIZE); + } + next_pid_offset += NGX_RTMP_MPEGTS_PID_SIZE; + } + + //Set section length of PMT + //PMT is 13 bytes long without any programs in it. Add this in + ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_SECTION_LENGTH_OFFSET] = 13 + next_pid_offset; + + //Calculate CRC + ngx_rtmp_mpegts_crc_t crc = ngx_rtmp_mpegts_crc_init(); + crc = ngx_rtmp_mpegts_crc_update(crc, ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_CRC_START_OFFSET, NGX_RTMP_MPEGTS_PMT_CRC_MIN_LENGTH+next_pid_offset); + crc = ngx_rtmp_mpegts_crc_finalize(crc); + + ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset] = (crc >> 24) & 0xff; + ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset+1] = (crc >> 16) & 0xff; + ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset+2] = (crc >> 8) & 0xff; + ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset+3] = crc & 0xff; + + return ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_header, sizeof(ngx_rtmp_mpegts_header)); } @@ -229,14 +318,12 @@ ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file, if (first) { - if (f->key) { - packet[3] |= 0x20; /* adaptation */ + packet[3] |= 0x20; /* adaptation */ - *p++ = 7; /* size */ - *p++ = 0x50; /* random access + PCR */ + *p++ = 7; /* size */ + *p++ = 0x50; /* random access + PCR */ - p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY); - } + p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY); /* PES header */ @@ -350,7 +437,7 @@ ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file, ngx_int_t ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path, - ngx_uint_t access, ngx_log_t *log) + ngx_log_t *log, ngx_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t mpegts_cc) { file->log = log; @@ -364,7 +451,7 @@ ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path, file->size = 0; - if (ngx_rtmp_mpegts_write_header(file) != NGX_OK) { + if (ngx_rtmp_mpegts_write_header(file, codec_ctx, mpegts_cc) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, log, ngx_errno, "hls: error writing fragment header"); ngx_close_file(file->fd); diff --git a/hls/ngx_rtmp_mpegts.h b/hls/ngx_rtmp_mpegts.h index 0082ff97a..355b5a5d6 100644 --- a/hls/ngx_rtmp_mpegts.h +++ b/hls/ngx_rtmp_mpegts.h @@ -12,6 +12,8 @@ #include #include +#include + typedef struct { ngx_fd_t fd; @@ -37,7 +39,7 @@ typedef struct { ngx_int_t ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file, u_char *key, size_t key_len, uint64_t iv); ngx_int_t ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path, - ngx_uint_t access, ngx_log_t *log); + ngx_log_t *log, ngx_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t mpegts_cc); ngx_int_t ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file); ngx_int_t ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file, ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b); diff --git a/hls/ngx_rtmp_mpegts_crc.c b/hls/ngx_rtmp_mpegts_crc.c new file mode 100644 index 000000000..437d605d9 --- /dev/null +++ b/hls/ngx_rtmp_mpegts_crc.c @@ -0,0 +1,80 @@ +/** + * \file crc.c + * Functions and types for CRC checks. + * + * Generated on Thu May 5 15:32:31 2016, + * by pycrc v0.9, https://pycrc.org + * using the configuration: + * Width = 32 + * Poly = 0x04c11db7 + * Xor_In = 0xffffffff + * ReflectIn = False + * Xor_Out = 0x00000000 + * ReflectOut = False + * Algorithm = table-driven + *****************************************************************************/ +#include "ngx_rtmp_mpegts_crc.h" /* include the header file generated with pycrc */ +#include +#include + +/** + * Static table used for the table_driven implementation. + *****************************************************************************/ +static const ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_table[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_update(ngx_rtmp_mpegts_crc_t crc, const void *data, size_t data_len) +{ + const unsigned char *d = (const unsigned char *)data; + unsigned int tbl_idx; + + while (data_len--) { + tbl_idx = ((crc >> 24) ^ *d) & 0xff; + crc = (ngx_rtmp_mpegts_crc_table[tbl_idx] ^ (crc << 8)) & 0xffffffff; + + d++; + } + return crc & 0xffffffff; +} + + diff --git a/hls/ngx_rtmp_mpegts_crc.h b/hls/ngx_rtmp_mpegts_crc.h new file mode 100644 index 000000000..b3f41e3e5 --- /dev/null +++ b/hls/ngx_rtmp_mpegts_crc.h @@ -0,0 +1,83 @@ +/** + * \file crc.h + * Functions and types for CRC checks. + * + * Generated on Thu May 5 15:32:22 2016, + * by pycrc v0.9, https://pycrc.org + * using the configuration: + * Width = 32 + * Poly = 0x04c11db7 + * Xor_In = 0xffffffff + * ReflectIn = False + * Xor_Out = 0x00000000 + * ReflectOut = False + * Algorithm = table-driven + *****************************************************************************/ +#ifndef _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_ +#define _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_ + + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * The definition of the used algorithm. + * + * This is not used anywhere in the generated code, but it may be used by the + * application code to call algoritm-specific code, is desired. + *****************************************************************************/ +#define CRC_ALGO_TABLE_DRIVEN 1 + + +/** + * The type of the CRC values. + * + * This type must be big enough to contain at least 32 bits. + *****************************************************************************/ +typedef uint_fast32_t ngx_rtmp_mpegts_crc_t; + + +/** + * Calculate the initial crc value. + * + * \return The initial crc value. + *****************************************************************************/ +static inline ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_init(void) +{ + return 0xffffffff; +} + + +/** + * Update the crc value with new data. + * + * \param crc The current crc value. + * \param data Pointer to a buffer of \a data_len bytes. + * \param data_len Number of bytes in the \a data buffer. + * \return The updated crc value. + *****************************************************************************/ +ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_update(ngx_rtmp_mpegts_crc_t crc, const void *data, size_t data_len); + + +/** + * Calculate the final crc value. + * + * \param crc The current crc value. + * \return The final crc value. + *****************************************************************************/ +static inline ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_finalize(ngx_rtmp_mpegts_crc_t crc) +{ + return crc ^ 0x00000000; +} + + +#ifdef __cplusplus +} /* closing brace for extern "C" */ +#endif + +#endif /* _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_ */ diff --git a/ngx_rtmp.c b/ngx_rtmp.c index c83e9e197..ad671d475 100644 --- a/ngx_rtmp.c +++ b/ngx_rtmp.c @@ -32,7 +32,9 @@ static char * ngx_rtmp_merge_applications(ngx_conf_t *cf, static ngx_int_t ngx_rtmp_init_process(ngx_cycle_t *cycle); -#if (nginx_version >= 1007005) +#if (nginx_version >= 1007011) +ngx_queue_t ngx_rtmp_init_queue; +#elif (nginx_version >= 1007005) ngx_thread_volatile ngx_queue_t ngx_rtmp_init_queue; #else ngx_thread_volatile ngx_event_t *ngx_rtmp_init_queue; @@ -85,6 +87,7 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_uint_t i, m, mi, s; ngx_conf_t pcf; ngx_array_t ports; + ngx_module_t **modules; ngx_rtmp_listen_t *listen; ngx_rtmp_module_t *module; ngx_rtmp_conf_ctx_t *ctx; @@ -99,14 +102,18 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) *(ngx_rtmp_conf_ctx_t **) conf = ctx; /* count the number of the rtmp modules and set up their indices */ - +#if defined(nginx_version) && nginx_version >= 1009011 + modules = cf->cycle->modules; +#else + modules = ngx_modules; +#endif ngx_rtmp_max_module = 0; - for (m = 0; ngx_modules[m]; m++) { - if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { continue; } - ngx_modules[m]->ctx_index = ngx_rtmp_max_module++; + modules[m]->ctx_index = ngx_rtmp_max_module++; } @@ -146,13 +153,13 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) * of the all rtmp modules */ - for (m = 0; ngx_modules[m]; m++) { - if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { continue; } - module = ngx_modules[m]->ctx; - mi = ngx_modules[m]->ctx_index; + module = modules[m]->ctx; + mi = modules[m]->ctx_index; if (module->create_main_conf) { ctx->main_conf[mi] = module->create_main_conf(cf); @@ -179,12 +186,12 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) pcf = *cf; cf->ctx = ctx; - for (m = 0; ngx_modules[m]; m++) { - if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { continue; } - module = ngx_modules[m]->ctx; + module = modules[m]->ctx; if (module->preconfiguration) { if (module->preconfiguration(cf) != NGX_OK) { @@ -210,13 +217,13 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index]; cscfp = cmcf->servers.elts; - for (m = 0; ngx_modules[m]; m++) { - if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { continue; } - module = ngx_modules[m]->ctx; - mi = ngx_modules[m]->ctx_index; + module = modules[m]->ctx; + mi = modules[m]->ctx_index; /* init rtmp{} main_conf's */ @@ -281,12 +288,12 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - for (m = 0; ngx_modules[m]; m++) { - if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { continue; } - module = ngx_modules[m]->ctx; + module = modules[m]->ctx; if (module->postconfiguration) { if (module->postconfiguration(cf) != NGX_OK) { @@ -838,7 +845,7 @@ static ngx_int_t ngx_rtmp_init_process(ngx_cycle_t *cycle) { #if (nginx_version >= 1007005) - ngx_queue_init(&ngx_rtmp_init_queue); + ngx_queue_init((ngx_queue_t*) &ngx_rtmp_init_queue); #endif return NGX_OK; } diff --git a/ngx_rtmp.h b/ngx_rtmp.h index 6b505cc59..39c2e6eb8 100644 --- a/ngx_rtmp.h +++ b/ngx_rtmp.h @@ -60,16 +60,16 @@ typedef struct { } ngx_rtmp_addr_conf_t; typedef struct { - in_addr_t addr; ngx_rtmp_addr_conf_t conf; + in_addr_t addr; } ngx_rtmp_in_addr_t; #if (NGX_HAVE_INET6) typedef struct { - struct in6_addr addr6; ngx_rtmp_addr_conf_t conf; + struct in6_addr addr6; } ngx_rtmp_in6_addr_t; #endif @@ -231,6 +231,9 @@ typedef struct { ngx_msec_t base_time; uint32_t current_time; + /* ready for publishing? */ + unsigned ready_for_publish:1; + /* ping */ ngx_event_t ping_evt; unsigned ping_active:1; @@ -581,6 +584,12 @@ ngx_int_t ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code, ngx_int_t ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code, char* level, ngx_uint_t duration, ngx_uint_t bytes); ngx_int_t ngx_rtmp_send_sample_access(ngx_rtmp_session_t *s); +ngx_int_t ngx_rtmp_send_redirect_status(ngx_rtmp_session_t *s, + char *callMethod, char *desc, ngx_str_t to_url); +ngx_int_t ngx_rtmp_send_close_method(ngx_rtmp_session_t *s, char *methodName); +ngx_int_t ngx_rtmp_send_fcpublish(ngx_rtmp_session_t *s, u_char *desc); +ngx_int_t ngx_rtmp_send_fcunpublish(ngx_rtmp_session_t *s, u_char *desc); +ngx_int_t ngx_rtmp_send_fi(ngx_rtmp_session_t *s); /* Frame types */ @@ -608,7 +617,9 @@ extern ngx_rtmp_bandwidth_t ngx_rtmp_bw_in; extern ngx_uint_t ngx_rtmp_naccepted; -#if (nginx_version >= 1007005) +#if (nginx_version >= 1007011) +extern ngx_queue_t ngx_rtmp_init_queue; +#elif (nginx_version >= 1007005) extern ngx_thread_volatile ngx_queue_t ngx_rtmp_init_queue; #else extern ngx_thread_volatile ngx_event_t *ngx_rtmp_init_queue; diff --git a/ngx_rtmp_access_module.c b/ngx_rtmp_access_module.c index 06d3bc20b..0025373f3 100644 --- a/ngx_rtmp_access_module.c +++ b/ngx_rtmp_access_module.c @@ -409,9 +409,9 @@ ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (!all) { break; } - /* "all" passes through */ #endif + /* fall through */ default: /* AF_INET */ @@ -449,10 +449,17 @@ ngx_rtmp_access_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) static ngx_int_t ngx_rtmp_access_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "access: ngx_rtmp_access_play"); + if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PLAY) != NGX_OK) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "access: ngx_rtmp_access_play: error"); return NGX_ERROR; } + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "access: ngx_rtmp_access_play: next"); return next_play(s, v); } diff --git a/ngx_rtmp_amf.c b/ngx_rtmp_amf.c index f133d0ec1..b904651dc 100644 --- a/ngx_rtmp_amf.c +++ b/ngx_rtmp_amf.c @@ -27,7 +27,7 @@ ngx_rtmp_amf_reverse_copy(void *dst, void* src, size_t len) return dst; } -#define NGX_RTMP_AMF_DEBUG_SIZE 16 +#define NGX_RTMP_AMF_DEBUG_SIZE 72 #ifdef NGX_DEBUG static void @@ -331,6 +331,7 @@ ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, if (elts->type & NGX_RTMP_AMF_OPTIONAL) { return NGX_OK; } + /* fall through */ case NGX_ERROR: return NGX_ERROR; } @@ -398,6 +399,7 @@ ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, if (ngx_rtmp_amf_get(ctx, &max_index, 4) != NGX_OK) { return NGX_ERROR; } + /* fall through */ case NGX_RTMP_AMF_OBJECT: if (ngx_rtmp_amf_read_object(ctx, data, @@ -592,6 +594,7 @@ ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx, if (ngx_rtmp_amf_put(ctx, &max_index, 4) != NGX_OK) { return NGX_ERROR; } + /* fall through */ case NGX_RTMP_AMF_OBJECT: type8 = NGX_RTMP_AMF_END; diff --git a/ngx_rtmp_auto_push_module.c b/ngx_rtmp_auto_push_module.c index 2f3533db0..60c85d77b 100644 --- a/ngx_rtmp_auto_push_module.c +++ b/ngx_rtmp_auto_push_module.c @@ -93,6 +93,34 @@ ngx_module_t ngx_rtmp_auto_push_module = { }; +static ngx_rtmp_module_t ngx_rtmp_auto_push_index_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* 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_auto_push_index_module = { + NGX_MODULE_V1, + &ngx_rtmp_auto_push_index_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 +}; + + #define NGX_RTMP_AUTO_PUSH_SOCKNAME "nginx-rtmp" @@ -324,7 +352,7 @@ ngx_rtmp_auto_push_reconnect(ngx_event_t *ev) apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, ngx_rtmp_auto_push_module); - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module); if (ctx == NULL) { return; } @@ -461,14 +489,14 @@ ngx_rtmp_auto_push_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) goto next; } - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module); if (ctx == NULL) { ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_auto_push_ctx_t)); if (ctx == NULL) { goto next; } - ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_auto_push_module); + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_auto_push_index_module); } ngx_memzero(ctx, sizeof(*ctx)); @@ -508,7 +536,7 @@ ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s, goto next; } - ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module); if (ctx) { if (ctx->push_evt.timer_set) { ngx_del_timer(&ctx->push_evt); @@ -532,7 +560,7 @@ ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s, slot, &rctx->app, &rctx->name); pctx = ngx_rtmp_get_module_ctx(rctx->publish->session, - ngx_rtmp_auto_push_module); + ngx_rtmp_auto_push_index_module); if (pctx == NULL) { goto next; } diff --git a/ngx_rtmp_cmd_module.c b/ngx_rtmp_cmd_module.c index 13f6677a0..c879fc2d7 100644 --- a/ngx_rtmp_cmd_module.c +++ b/ngx_rtmp_cmd_module.c @@ -44,6 +44,7 @@ static ngx_int_t ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s, static ngx_int_t ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s, ngx_rtmp_set_buflen_t *v); +static ngx_int_t ngx_rtmp_cmd_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v); ngx_rtmp_connect_pt ngx_rtmp_connect; ngx_rtmp_disconnect_pt ngx_rtmp_disconnect; @@ -62,6 +63,7 @@ ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry; ngx_rtmp_recorded_pt ngx_rtmp_recorded; ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen; +ngx_rtmp_playlist_pt ngx_rtmp_playlist; static ngx_int_t ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf); @@ -574,6 +576,8 @@ ngx_rtmp_cmd_play_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, static ngx_int_t ngx_rtmp_cmd_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "cmd: ngx_rtmp_cmd_play"); return NGX_OK; } @@ -786,6 +790,14 @@ ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s, ngx_rtmp_set_buflen_t *v) } +static ngx_int_t +ngx_rtmp_cmd_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v) +{ + return NGX_OK; +} + + + static ngx_rtmp_amf_handler_t ngx_rtmp_cmd_map[] = { { ngx_string("connect"), ngx_rtmp_cmd_connect_init }, { ngx_string("createStream"), ngx_rtmp_cmd_create_stream_init }, @@ -852,5 +864,7 @@ ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf) ngx_rtmp_recorded = ngx_rtmp_cmd_recorded; ngx_rtmp_set_buflen = ngx_rtmp_cmd_set_buflen; + ngx_rtmp_playlist = ngx_rtmp_cmd_playlist; + return NGX_OK; } diff --git a/ngx_rtmp_cmd_module.h b/ngx_rtmp_cmd_module.h index 4a0b955d1..a419158fa 100644 --- a/ngx_rtmp_cmd_module.h +++ b/ngx_rtmp_cmd_module.h @@ -14,8 +14,8 @@ #include "ngx_rtmp.h" -#define NGX_RTMP_MAX_NAME 256 -#define NGX_RTMP_MAX_URL 256 +#define NGX_RTMP_MAX_NAME 2048 +#define NGX_RTMP_MAX_URL 4096 #define NGX_RTMP_MAX_ARGS NGX_RTMP_MAX_NAME @@ -59,6 +59,12 @@ typedef struct { } ngx_rtmp_publish_t; +typedef struct { + ngx_str_t playlist; + ngx_str_t module; +} ngx_rtmp_playlist_t; + + typedef struct { u_char name[NGX_RTMP_MAX_NAME]; u_char args[NGX_RTMP_MAX_ARGS]; @@ -130,6 +136,7 @@ typedef ngx_int_t (*ngx_rtmp_recorded_pt)(ngx_rtmp_session_t *s, typedef ngx_int_t (*ngx_rtmp_set_buflen_pt)(ngx_rtmp_session_t *s, ngx_rtmp_set_buflen_t *v); +typedef ngx_int_t (*ngx_rtmp_playlist_pt)(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v); extern ngx_rtmp_connect_pt ngx_rtmp_connect; extern ngx_rtmp_disconnect_pt ngx_rtmp_disconnect; @@ -147,5 +154,6 @@ extern ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry; extern ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen; extern ngx_rtmp_recorded_pt ngx_rtmp_recorded; +extern ngx_rtmp_playlist_pt ngx_rtmp_playlist; #endif /*_NGX_RTMP_CMD_H_INCLUDED_ */ diff --git a/ngx_rtmp_codec_module.c b/ngx_rtmp_codec_module.c index ddc9273d2..73d31a1a9 100644 --- a/ngx_rtmp_codec_module.c +++ b/ngx_rtmp_codec_module.c @@ -358,8 +358,10 @@ static void ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, ngx_chain_t *in) { ngx_uint_t profile_idc, width, height, crop_left, crop_right, - crop_top, crop_bottom, frame_mbs_only, n, cf_idc, - num_ref_frames; + crop_top, crop_bottom, frame_mbs_only, n, cf_n, cf_idc, +// num_ref_frames; + num_ref_frames, sl_size, sl_index, sl_udelta; + ngx_int_t sl_last, sl_next, sl_delta; ngx_rtmp_codec_ctx_t *ctx; ngx_rtmp_bit_reader_t br; @@ -432,16 +434,39 @@ ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, ngx_chain_t *in) /* seq scaling matrix present */ if (ngx_rtmp_bit_read(&br, 1)) { - for (n = 0; n < (cf_idc != 3 ? 8u : 12u); n++) { + for (n = 0, cf_n = (cf_idc != 3 ? 8u : 12u); n < cf_n; n++) { /* seq scaling list present */ if (ngx_rtmp_bit_read(&br, 1)) { - /* TODO: scaling_list() + /* scaling list */ if (n < 6) { + sl_size = 16; } else { + sl_size = 64; + } + + sl_last = 8; + sl_next = 8; + + for (sl_index = 0; sl_index < sl_size; sl_index++) { + + if (sl_next != 0) { + + /* convert to signed: (-1)**k+1 * ceil(k/2) */ + sl_udelta = (ngx_uint_t)ngx_rtmp_bit_read_golomb(&br); + sl_delta = (sl_udelta + 1) >> 1; + if ((sl_udelta & 1) == 0) { + sl_delta = -sl_delta; + } + + sl_next = (sl_last + sl_delta + 256) % 256; + + if (sl_next != 0) { + sl_last = sl_next; + } + } } - */ } } } @@ -581,7 +606,7 @@ ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s) { NGX_RTMP_AMF_STRING, ngx_string("Server"), - "NGINX RTMP (github.com/arut/nginx-rtmp-module)", 0 }, + "NGINX RTMP (github.com/sergey-dryabzhinsky/nginx-rtmp-module)", 0 }, { NGX_RTMP_AMF_NUMBER, ngx_string("width"), @@ -820,7 +845,8 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, static ngx_rtmp_amf_elt_t in_elts[] = { - { NGX_RTMP_AMF_STRING, + /* That string is passed by FFmpeg and possibly others (librtmp). It's skipped after at #880 */ + { NGX_RTMP_AMF_STRING, ngx_null_string, NULL, 0 }, @@ -839,11 +865,19 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_memzero(&v, sizeof(v)); - /* use -1 as a sign of unchanged data; - * 0 is a valid value for uncompressed audio */ + /* use -1 as a sign of unchanged data */ + v.width = -1; + v.height = -1; + v.duration = -1; + v.frame_rate = -1; + v.video_data_rate = -1; + v.video_codec_id_n = -1; + v.audio_data_rate = -1; v.audio_codec_id_n = -1; + v.profile[0] = '\0'; + v.level[0] = '\0'; - /* FFmpeg sends a string in front of actal metadata; ignore it */ + /* FFmpeg sends a string in front of actual metadata; ignore it */ skip = !(in->buf->last > in->buf->pos && *in->buf->pos == NGX_RTMP_AMF_STRING); if (ngx_rtmp_receive_amf(s, in, in_elts + skip, @@ -854,22 +888,21 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, return NGX_OK; } - ctx->width = (ngx_uint_t) v.width; - ctx->height = (ngx_uint_t) v.height; - ctx->duration = (ngx_uint_t) v.duration; - ctx->frame_rate = (ngx_uint_t) v.frame_rate; - ctx->video_data_rate = (ngx_uint_t) v.video_data_rate; - ctx->video_codec_id = (ngx_uint_t) v.video_codec_id_n; - ctx->audio_data_rate = (ngx_uint_t) v.audio_data_rate; - ctx->audio_codec_id = (v.audio_codec_id_n == -1 - ? 0 : v.audio_codec_id_n == 0 + if (v.width != -1) ctx->width = (ngx_uint_t) v.width; + if (v.height != -1) ctx->height = (ngx_uint_t) v.height; + if (v.duration != -1) ctx->duration = (double) v.duration; + if (v.frame_rate != -1) ctx->frame_rate = (double) v.frame_rate; + 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.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); - ngx_memcpy(ctx->profile, v.profile, sizeof(v.profile)); - ngx_memcpy(ctx->level, v.level, sizeof(v.level)); + if (v.profile[0] != '\0') ngx_memcpy(ctx->profile, v.profile, sizeof(v.profile)); + if (v.level[0] != '\0') ngx_memcpy(ctx->level, v.level, sizeof(v.level)); ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "codec: data frame: " - "width=%ui height=%ui duration=%ui frame_rate=%ui " + "width=%ui height=%ui duration=%.3f frame_rate=%.3f " "video=%s (%ui) audio=%s (%ui)", ctx->width, ctx->height, ctx->duration, ctx->frame_rate, ngx_rtmp_get_video_codec_name(ctx->video_codec_id), @@ -877,6 +910,12 @@ ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_rtmp_get_audio_codec_name(ctx->audio_codec_id), ctx->audio_codec_id); + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "codec: data frame: " + "video_rate=%.3f audio_rate=%.3f ", + ctx->video_data_rate, ctx->audio_data_rate + ); + switch (cacf->meta) { case NGX_RTMP_CODEC_META_ON: return ngx_rtmp_codec_reconstruct_meta(s); @@ -943,7 +982,15 @@ 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) { + return NGX_ERROR; + } + 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 ee48c1cd3..b8de10c61 100644 --- a/ngx_rtmp_codec_module.h +++ b/ngx_rtmp_codec_module.h @@ -52,11 +52,11 @@ u_char * ngx_rtmp_get_video_codec_name(ngx_uint_t id); typedef struct { ngx_uint_t width; ngx_uint_t height; - ngx_uint_t duration; - ngx_uint_t frame_rate; - ngx_uint_t video_data_rate; + double duration; + double frame_rate; + double video_data_rate; ngx_uint_t video_codec_id; - ngx_uint_t audio_data_rate; + double audio_data_rate; ngx_uint_t audio_codec_id; ngx_uint_t aac_profile; ngx_uint_t aac_chan_conf; diff --git a/ngx_rtmp_core_module.c b/ngx_rtmp_core_module.c index 607a54984..1b704c1bd 100644 --- a/ngx_rtmp_core_module.c +++ b/ngx_rtmp_core_module.c @@ -45,7 +45,7 @@ static ngx_command_t ngx_rtmp_core_commands[] = { NULL }, { ngx_string("listen"), - NGX_RTMP_SRV_CONF|NGX_CONF_TAKE12, + NGX_RTMP_SRV_CONF|NGX_CONF_1MORE, ngx_rtmp_core_listen, NGX_RTMP_SRV_CONF_OFFSET, 0, @@ -342,6 +342,7 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) void *mconf; ngx_uint_t m; ngx_conf_t pcf; + ngx_module_t **modules; ngx_rtmp_module_t *module; ngx_rtmp_conf_ctx_t *ctx, *rtmp_ctx; ngx_rtmp_core_srv_conf_t *cscf, **cscfp; @@ -367,12 +368,17 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - for (m = 0; ngx_modules[m]; m++) { - if (ngx_modules[m]->type != NGX_RTMP_MODULE) { +#if defined(nginx_version) && nginx_version >= 1009011 + modules = cf->cycle->modules; +#else + modules = ngx_modules; +#endif + for (m = 0; modules[m]; m++) { + if (modules[m]->type != NGX_RTMP_MODULE) { continue; } - module = ngx_modules[m]->ctx; + module = modules[m]->ctx; if (module->create_srv_conf) { mconf = module->create_srv_conf(cf); @@ -380,7 +386,7 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - ctx->srv_conf[ngx_modules[m]->ctx_index] = mconf; + ctx->srv_conf[modules[m]->ctx_index] = mconf; } if (module->create_app_conf) { @@ -389,7 +395,7 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - ctx->app_conf[ngx_modules[m]->ctx_index] = mconf; + ctx->app_conf[modules[m]->ctx_index] = mconf; } } @@ -429,6 +435,7 @@ ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_int_t i; ngx_str_t *value; ngx_conf_t save; + ngx_module_t **modules; ngx_rtmp_module_t *module; ngx_rtmp_conf_ctx_t *ctx, *pctx; ngx_rtmp_core_srv_conf_t *cscf; @@ -448,17 +455,21 @@ ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - for (i = 0; ngx_modules[i]; i++) { - if (ngx_modules[i]->type != NGX_RTMP_MODULE) { +#if defined(nginx_version) && nginx_version >= 1009011 + modules = cf->cycle->modules; +#else + modules = ngx_modules; +#endif + for (i = 0; modules[i]; i++) { + if (modules[i]->type != NGX_RTMP_MODULE) { continue; } - module = ngx_modules[i]->ctx; + module = modules[i]->ctx; if (module->create_app_conf) { - ctx->app_conf[ngx_modules[i]->ctx_index] = - module->create_app_conf(cf); - if (ctx->app_conf[ngx_modules[i]->ctx_index] == NULL) { + ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf); + if (ctx->app_conf[modules[i]->ctx_index] == NULL) { return NGX_CONF_ERROR; } } @@ -498,7 +509,7 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) in_port_t port; ngx_str_t *value; ngx_url_t u; - ngx_uint_t i, m; + ngx_uint_t i; struct sockaddr *sa; ngx_rtmp_listen_t *ls; struct sockaddr_in *sin; @@ -555,7 +566,11 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) break; } +#if (nginx_version >= 1011000) + if (ngx_memcmp(ls[i].sockaddr + off, (u_char *) &u.sockaddr + off, len) != 0) { +#else if (ngx_memcmp(ls[i].sockaddr + off, u.sockaddr + off, len) != 0) { +#endif continue; } @@ -575,18 +590,16 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_memzero(ls, sizeof(ngx_rtmp_listen_t)); +#if (nginx_version >= 1011000) + ngx_memcpy(ls->sockaddr, (u_char *) &u.sockaddr, u.socklen); +#else ngx_memcpy(ls->sockaddr, u.sockaddr, u.socklen); +#endif ls->socklen = u.socklen; ls->wildcard = u.wildcard; ls->ctx = cf->ctx; - for (m = 0; ngx_modules[m]; m++) { - if (ngx_modules[m]->type != NGX_RTMP_MODULE) { - continue; - } - } - for (i = 2; i < cf->args->nelts; i++) { if (ngx_strcmp(value[i].data, "bind") == 0) { @@ -596,7 +609,6 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) { #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) - struct sockaddr *sa; u_char buf[NGX_SOCKADDR_STRLEN]; sa = (struct sockaddr *) ls->sockaddr; diff --git a/ngx_rtmp_eval.c b/ngx_rtmp_eval.c index 24e1f8072..c658f14ef 100644 --- a/ngx_rtmp_eval.c +++ b/ngx_rtmp_eval.c @@ -154,6 +154,7 @@ ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out, name.len = p - name.data; ngx_rtmp_eval_append_var(ctx, &b, e, &name, log); + /* fall through */ case NORMAL: switch (c) { @@ -165,6 +166,7 @@ ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out, state = ESCAPE; continue; } + /* fall through */ case ESCAPE: ngx_rtmp_eval_append(&b, &c, 1, log); diff --git a/ngx_rtmp_exec_module.c b/ngx_rtmp_exec_module.c index fccd4ba70..ad9011d68 100644 --- a/ngx_rtmp_exec_module.c +++ b/ngx_rtmp_exec_module.c @@ -20,6 +20,7 @@ static ngx_rtmp_publish_pt next_publish; static ngx_rtmp_play_pt next_play; static ngx_rtmp_close_stream_pt next_close_stream; +static ngx_rtmp_record_started_pt next_record_started; static ngx_rtmp_record_done_pt next_record_done; #endif @@ -55,6 +56,7 @@ enum { NGX_RTMP_EXEC_PUBLISH_DONE, NGX_RTMP_EXEC_PLAY, NGX_RTMP_EXEC_PLAY_DONE, + NGX_RTMP_EXEC_RECORD_STARTED, NGX_RTMP_EXEC_RECORD_DONE, NGX_RTMP_EXEC_MAX, @@ -208,6 +210,15 @@ static ngx_command_t ngx_rtmp_exec_commands[] = { NGX_RTMP_EXEC_PLAY_DONE * sizeof(ngx_array_t), NULL }, + { ngx_string("exec_record_started"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_RTMP_REC_CONF| + NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_RECORD_STARTED * sizeof(ngx_array_t), + NULL }, + { ngx_string("exec_record_done"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_RTMP_REC_CONF| NGX_CONF_1MORE, @@ -1194,6 +1205,9 @@ ngx_rtmp_exec_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) static ngx_int_t ngx_rtmp_exec_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "exec: ngx_rtmp_exec_play"); + ngx_rtmp_exec_ctx_t *ctx; ngx_rtmp_exec_pull_ctx_t *pctx; ngx_rtmp_exec_app_conf_t *eacf; @@ -1224,6 +1238,8 @@ ngx_rtmp_exec_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) } next: + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "exec: ngx_rtmp_exec_play: next"); return next_play(s, v); } @@ -1302,6 +1318,31 @@ ngx_rtmp_exec_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) } +static ngx_int_t +ngx_rtmp_exec_record_started(ngx_rtmp_session_t *s, ngx_rtmp_record_started_t *v) +{ + ngx_rtmp_exec_app_conf_t *eacf; + + if (s->auto_pushed) { + goto next; + } + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + if (eacf == NULL || !eacf->active) { + goto next; + } + + ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_RECORD_STARTED], + "record_started"); + + ngx_str_null(&v->recorder); + ngx_str_null(&v->path); + +next: + return next_record_started(s, v); +} + + static ngx_int_t ngx_rtmp_exec_record_done(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v) { @@ -1359,6 +1400,7 @@ ngx_rtmp_exec_record_done(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v) next: return next_record_done(s, v); } + #endif /* NGX_WIN32 */ @@ -1598,6 +1640,9 @@ ngx_rtmp_exec_postconfiguration(ngx_conf_t *cf) next_record_done = ngx_rtmp_record_done; ngx_rtmp_record_done = ngx_rtmp_exec_record_done; + next_record_started = ngx_rtmp_record_started; + ngx_rtmp_record_started = ngx_rtmp_exec_record_started; + #endif /* NGX_WIN32 */ return NGX_OK; diff --git a/ngx_rtmp_handler.c b/ngx_rtmp_handler.c index ac78a6fde..a8bef611a 100644 --- a/ngx_rtmp_handler.c +++ b/ngx_rtmp_handler.c @@ -558,7 +558,11 @@ ngx_rtmp_send(ngx_event_t *wev) ngx_del_event(wev, NGX_WRITE_EVENT, 0); } +#if (nginx_version >= 1007012) + ngx_event_process_posted((ngx_cycle_t *) ngx_cycle,(ngx_queue_t *) &s->posted_dry_events); +#else ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &s->posted_dry_events); +#endif } @@ -868,6 +872,7 @@ ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size) bi->pos += (ngx_cpymem(bo->last, bi->pos, bo->end - bo->last) - bo->last); + bo->last = bo->end; lo->next = ngx_rtmp_alloc_in_buf(s); lo = lo->next; if (lo == NULL) { diff --git a/ngx_rtmp_handshake.c b/ngx_rtmp_handshake.c index d58fcff47..409d9a0dd 100644 --- a/ngx_rtmp_handshake.c +++ b/ngx_rtmp_handshake.c @@ -104,30 +104,37 @@ static ngx_int_t ngx_rtmp_make_digest(ngx_str_t *key, ngx_buf_t *src, u_char *skip, u_char *dst, ngx_log_t *log) { - static HMAC_CTX hmac; - static unsigned hmac_initialized; + static HMAC_CTX *hmac; unsigned int len; - if (!hmac_initialized) { - HMAC_CTX_init(&hmac); - hmac_initialized = 1; + if (hmac == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + static HMAC_CTX shmac; + hmac = &shmac; + HMAC_CTX_init(hmac); +#else + hmac = HMAC_CTX_new(); + if (hmac == NULL) { + return NGX_ERROR; + } +#endif } - HMAC_Init_ex(&hmac, key->data, key->len, EVP_sha256(), NULL); + HMAC_Init_ex(hmac, key->data, key->len, EVP_sha256(), NULL); if (skip && src->pos <= skip && skip <= src->last) { if (skip != src->pos) { - HMAC_Update(&hmac, src->pos, skip - src->pos); + HMAC_Update(hmac, src->pos, skip - src->pos); } if (src->last != skip + NGX_RTMP_HANDSHAKE_KEYLEN) { - HMAC_Update(&hmac, skip + NGX_RTMP_HANDSHAKE_KEYLEN, + HMAC_Update(hmac, skip + NGX_RTMP_HANDSHAKE_KEYLEN, src->last - skip - NGX_RTMP_HANDSHAKE_KEYLEN); } } else { - HMAC_Update(&hmac, src->pos, src->last - src->pos); + HMAC_Update(hmac, src->pos, src->last - src->pos); } - HMAC_Final(&hmac, dst, &len); + HMAC_Final(hmac, dst, &len); return NGX_OK; } diff --git a/ngx_rtmp_init.c b/ngx_rtmp_init.c index 97c7e9a84..9da711ab7 100644 --- a/ngx_rtmp_init.c +++ b/ngx_rtmp_init.c @@ -76,9 +76,9 @@ ngx_rtmp_init_connection(ngx_connection_t *c) break; #endif - case AF_UNIX: unix_socket = 1; + /* fall through */ default: /* AF_INET */ sin = (struct sockaddr_in *) sa; @@ -110,6 +110,7 @@ ngx_rtmp_init_connection(ngx_connection_t *c) case AF_UNIX: unix_socket = 1; + /* fall through */ default: /* AF_INET */ addr = port->addrs; diff --git a/ngx_rtmp_live_module.c b/ngx_rtmp_live_module.c index 5bebb9e2a..8380d94c5 100644 --- a/ngx_rtmp_live_module.c +++ b/ngx_rtmp_live_module.c @@ -40,16 +40,16 @@ static ngx_command_t ngx_rtmp_live_commands[] = { { ngx_string("stream_buckets"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, + ngx_conf_set_num_slot, NGX_RTMP_APP_CONF_OFFSET, offsetof(ngx_rtmp_live_app_conf_t, nbuckets), NULL }, { ngx_string("buffer"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, + ngx_conf_set_flag_slot, NGX_RTMP_APP_CONF_OFFSET, - offsetof(ngx_rtmp_live_app_conf_t, buflen), + offsetof(ngx_rtmp_live_app_conf_t, buffer), NULL }, { ngx_string("sync"), @@ -152,7 +152,7 @@ ngx_rtmp_live_create_app_conf(ngx_conf_t *cf) lacf->live = NGX_CONF_UNSET; lacf->nbuckets = NGX_CONF_UNSET; - lacf->buflen = NGX_CONF_UNSET_MSEC; + lacf->buffer = NGX_CONF_UNSET; lacf->sync = NGX_CONF_UNSET_MSEC; lacf->idle_timeout = NGX_CONF_UNSET_MSEC; lacf->interleave = NGX_CONF_UNSET; @@ -174,11 +174,11 @@ ngx_rtmp_live_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->live, prev->live, 0); ngx_conf_merge_value(conf->nbuckets, prev->nbuckets, 1024); - ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 0); + ngx_conf_merge_value(conf->buffer, prev->buffer, 0); ngx_conf_merge_msec_value(conf->sync, prev->sync, 300); ngx_conf_merge_msec_value(conf->idle_timeout, prev->idle_timeout, 0); ngx_conf_merge_value(conf->interleave, prev->interleave, 0); - ngx_conf_merge_value(conf->wait_key, prev->wait_key, 1); + ngx_conf_merge_value(conf->wait_key, prev->wait_key, 0); ngx_conf_merge_value(conf->wait_video, prev->wait_video, 0); ngx_conf_merge_value(conf->publish_notify, prev->publish_notify, 0); ngx_conf_merge_value(conf->play_restart, prev->play_restart, 0); @@ -357,6 +357,9 @@ ngx_rtmp_live_set_status(ngx_rtmp_session_t *s, ngx_chain_t *control, ctx->cs[1].active = 0; ctx->cs[1].dropped = 0; + + ctx->cs[2].active = 0; + ctx->cs[2].dropped = 0; } @@ -550,12 +553,13 @@ ngx_rtmp_live_join(ngx_rtmp_session_t *s, u_char *name, unsigned publisher) (*stream)->ctx = ctx; - if (lacf->buflen) { + if (lacf->buffer) { s->out_buffer = 1; } ctx->cs[0].csid = NGX_RTMP_CSID_VIDEO; ctx->cs[1].csid = NGX_RTMP_CSID_AUDIO; + ctx->cs[2].csid = NGX_RTMP_CSID_AMF; if (!ctx->publishing && ctx->stream->active) { ngx_rtmp_live_start(s); @@ -1036,6 +1040,351 @@ ngx_rtmp_live_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, return NGX_OK; } +static ngx_int_t +ngx_rtmp_live_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in, ngx_rtmp_amf_elt_t *out_elts, ngx_uint_t out_elts_size) +{ + ngx_rtmp_live_ctx_t *ctx, *pctx; + ngx_chain_t *data, *rpkt; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_session_t *ss; + ngx_rtmp_header_t ch; + ngx_int_t rc; + ngx_uint_t prio; + ngx_uint_t peers; + uint32_t delta; + ngx_rtmp_live_chunk_stream_t *cs; + +#ifdef NGX_DEBUG + u_char *msg_type; + + msg_type = (u_char *)out_elts[0].data; +#endif + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + return NGX_ERROR; + } + + if (!lacf->live || in == NULL || in->buf == NULL) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + if (ctx == NULL || ctx->stream == NULL) { + return NGX_OK; + } + + if (ctx->publishing == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: %s from non-publisher", msg_type); + return NGX_OK; + } + + /* drop the data packet if the stream is not active */ + if (!ctx->stream->active) { + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: %s packet timestamp=%uD", + msg_type, h->timestamp); + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + cs = &ctx->cs[2]; + cs->active = 1; + + peers = 0; + prio = 0; + data = NULL; + rc = ngx_rtmp_append_amf(s, &data, NULL, out_elts, out_elts_size); + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: data - can't append amf!"); + if (data) { + ngx_rtmp_free_shared_chain(cscf, data); + } + return NGX_ERROR; + } + + ngx_memzero(&ch, sizeof(ch)); + ch.timestamp = h->timestamp; + ch.msid = NGX_RTMP_MSID; + ch.csid = h->csid; + ch.type = NGX_RTMP_MSG_AMF_META; + + delta = ch.timestamp - cs->timestamp; + + rpkt = ngx_rtmp_append_shared_bufs(cscf, data, in); + ngx_rtmp_prepare_message(s, &ch, NULL, rpkt); + + for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) { + if (pctx == ctx || pctx->paused) { + continue; + } + + ss = pctx->session; + + if (ngx_rtmp_send_message(ss, rpkt, prio) != NGX_OK) { + ++pctx->ndropped; + cs->dropped += delta; + continue; + } + + cs->timestamp += delta; + ++peers; + ss->current_time = cs->timestamp; + } + + if (rpkt) { + ngx_rtmp_free_shared_chain(cscf, rpkt); + } + + ngx_rtmp_update_bandwidth(&ctx->stream->bw_in, h->mlen); + ngx_rtmp_update_bandwidth(&ctx->stream->bw_out, h->mlen * peers); + ngx_rtmp_update_bandwidth(&ctx->stream->bw_in_data, h->mlen); + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_live_on_cue_point(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onCuePoint", 0 } + }; + + return ngx_rtmp_live_data(s, h, in, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + +static ngx_int_t +ngx_rtmp_live_on_text_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onTextData", 0 } + }; + + return ngx_rtmp_live_data(s, h, in, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + +static ngx_int_t +ngx_rtmp_live_on_fi(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_live_app_conf_t *lacf; + ngx_int_t res; + + static struct { + u_char time[NGX_TIME_T_LEN + 1]; + u_char date[NGX_TIME_T_LEN + 1]; + } v; + + static ngx_rtmp_amf_elt_t in_dt_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("sd"), + &v.date, sizeof(v.date) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("st"), + &v.time, sizeof(v.time) }, + + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_MIXED_ARRAY, + ngx_null_string, + in_dt_elts, sizeof(in_dt_elts) }, + }; + + + static ngx_rtmp_amf_elt_t out_dt_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("sd"), + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("st"), + NULL, 0 }, + + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onFi", 0 }, + + { NGX_RTMP_AMF_MIXED_ARRAY, + ngx_null_string, + out_dt_elts, sizeof(out_dt_elts) }, + }; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: Fi - no live config!"); + return NGX_ERROR; + } + + if (!lacf->live || in == NULL || in->buf == NULL) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: Fi - no live or no buffer!"); + return NGX_OK; + } + + ngx_memzero(&v, sizeof(v)); + res = ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0])); + + if (res == NGX_OK) { + + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: onFi: date='%s', time='%s'", + v.date, v.time); + + out_dt_elts[0].data = v.date; + out_dt_elts[1].data = v.time; + + // Pass through datetime from publisher + return ngx_rtmp_live_data(s, h, in, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); + + } else { + // Send our server datetime + return ngx_rtmp_send_fi(s); + } +} + +static ngx_int_t +ngx_rtmp_live_on_fcpublish(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + + ngx_rtmp_live_app_conf_t *lacf; + + static struct { + double trans; + u_char action[128]; + u_char stream[1024]; + } v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + +// Already readed +/* { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.action, sizeof(v.action) }, +*/ + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.stream, sizeof(v.stream) }, + }; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: FCPublish - no live config!"); + return NGX_ERROR; + } + + if (!lacf->live || in == NULL || in->buf == NULL) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: FCPublish - no live or no buffer!"); + return NGX_OK; + } + + ngx_memzero(&v, sizeof(v)); + ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0])); + + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: onFCPublish: stream='%s'", + v.stream); + + return ngx_rtmp_send_fcpublish(s, v.stream); +} + + +static ngx_int_t +ngx_rtmp_live_on_fcunpublish(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + + ngx_rtmp_live_app_conf_t *lacf; + + static struct { + double trans; + u_char action[128]; + u_char stream[1024]; + } v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + +// Already readed +/* { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.action, sizeof(v.action) }, +*/ + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.stream, sizeof(v.stream) }, + }; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: FCUnpublish - no live config!"); + return NGX_ERROR; + } + + if (!lacf->live || in == NULL || in->buf == NULL) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: FCUnpublish - no live or no buffer!"); + return NGX_OK; + } + + ngx_memzero(&v, sizeof(v)); + ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0])); + + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: onFCUnpublish: stream='%s'", + v.stream); + + return ngx_rtmp_send_fcunpublish(s, v.stream); +} + static ngx_int_t ngx_rtmp_live_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) @@ -1077,6 +1426,9 @@ ngx_rtmp_live_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) static ngx_int_t ngx_rtmp_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: ngx_rtmp_live_play"); + ngx_rtmp_live_app_conf_t *lacf; ngx_rtmp_live_ctx_t *ctx; @@ -1109,6 +1461,8 @@ ngx_rtmp_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) } next: + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "live: ngx_rtmp_live_play: next"); return next_play(s, v); } @@ -1118,6 +1472,7 @@ ngx_rtmp_live_postconfiguration(ngx_conf_t *cf) { ngx_rtmp_core_main_conf_t *cmcf; ngx_rtmp_handler_pt *h; + ngx_rtmp_amf_handler_t *ch; cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); @@ -1149,5 +1504,25 @@ ngx_rtmp_live_postconfiguration(ngx_conf_t *cf) next_stream_eof = ngx_rtmp_stream_eof; ngx_rtmp_stream_eof = ngx_rtmp_live_stream_eof; + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "onTextData"); + ch->handler = ngx_rtmp_live_on_text_data; + + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "onCuePoint"); + ch->handler = ngx_rtmp_live_on_cue_point; + + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "onFi"); + ch->handler = ngx_rtmp_live_on_fi; + + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "FCPublish"); + ch->handler = ngx_rtmp_live_on_fcpublish; + + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "FCUnpublish"); + ch->handler = ngx_rtmp_live_on_fcunpublish; + return NGX_OK; } diff --git a/ngx_rtmp_live_module.h b/ngx_rtmp_live_module.h index 71eca36bc..2558ed55f 100644 --- a/ngx_rtmp_live_module.h +++ b/ngx_rtmp_live_module.h @@ -33,7 +33,7 @@ struct ngx_rtmp_live_ctx_s { ngx_rtmp_live_stream_t *stream; ngx_rtmp_live_ctx_t *next; ngx_uint_t ndropped; - ngx_rtmp_live_chunk_stream_t cs[2]; + ngx_rtmp_live_chunk_stream_t cs[3]; ngx_uint_t meta_version; ngx_event_t idle_evt; unsigned active:1; @@ -50,6 +50,7 @@ struct ngx_rtmp_live_stream_s { ngx_rtmp_bandwidth_t bw_in; ngx_rtmp_bandwidth_t bw_in_audio; ngx_rtmp_bandwidth_t bw_in_video; + ngx_rtmp_bandwidth_t bw_in_data; ngx_rtmp_bandwidth_t bw_out; ngx_msec_t epoch; unsigned active:1; @@ -71,7 +72,7 @@ typedef struct { ngx_flag_t publish_notify; ngx_flag_t play_restart; ngx_flag_t idle_streams; - ngx_msec_t buflen; + ngx_flag_t buffer; ngx_pool_t *pool; ngx_rtmp_live_stream_t *free_streams; } ngx_rtmp_live_app_conf_t; diff --git a/ngx_rtmp_log_module.c b/ngx_rtmp_log_module.c index 81016d026..7759d67e9 100644 --- a/ngx_rtmp_log_module.c +++ b/ngx_rtmp_log_module.c @@ -853,6 +853,9 @@ ngx_rtmp_log_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) static ngx_int_t ngx_rtmp_log_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "log: ngx_rtmp_log_play"); + ngx_rtmp_log_ctx_t *ctx; if (s->auto_pushed || s->relay) { @@ -867,6 +870,8 @@ ngx_rtmp_log_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) ctx->play = 1; next: + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "log: ngx_rtmp_log_play: next"); return next_play(s, v); } diff --git a/ngx_rtmp_netcall_module.c b/ngx_rtmp_netcall_module.c index f772c7233..24c001cbe 100644 --- a/ngx_rtmp_netcall_module.c +++ b/ngx_rtmp_netcall_module.c @@ -571,6 +571,7 @@ ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool) ngx_chain_t *cl; ngx_buf_t *b; ngx_str_t *addr_text; + size_t bsize; addr_text = &s->connection->addr_text; @@ -579,16 +580,32 @@ ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool) return NULL; } - b = ngx_create_temp_buf(pool, - sizeof("app=") - 1 + s->app.len * 3 + - sizeof("&flashver=") - 1 + s->flashver.len * 3 + - sizeof("&swfurl=") - 1 + s->swf_url.len * 3 + - sizeof("&tcurl=") - 1 + s->tc_url.len * 3 + - sizeof("&pageurl=") - 1 + s->page_url.len * 3 + - sizeof("&addr=") - 1 + addr_text->len * 3 + - sizeof("&clientid=") - 1 + NGX_INT_T_LEN - ); + /** + * @2016-04-20 sergey-dryabzhinsky + * Not all params may be filled in session + * So not override them with empty values + */ + bsize = sizeof("&addr=") - 1 + addr_text->len * 3 + + sizeof("&clientid=") - 1 + NGX_INT_T_LEN; + + if (s->app.len) { + bsize += sizeof("app=") - 1 + s->app.len * 3; + } + if (s->flashver.len) { + bsize += sizeof("&flashver=") - 1 + s->flashver.len * 3; + } + if (s->swf_url.len) { + bsize += sizeof("&swfurl=") - 1 + s->swf_url.len * 3; + } + if (s->tc_url.len) { + bsize += sizeof("&tcurl=") - 1 + s->tc_url.len * 3; + } + if (s->page_url.len) { + bsize += sizeof("&pageurl=") - 1 + s->page_url.len * 3; + } + + b = ngx_create_temp_buf(pool, bsize); if (b == NULL) { return NULL; } @@ -596,29 +613,35 @@ 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*) "app=", sizeof("app=") - 1); - b->last = (u_char*) ngx_escape_uri(b->last, s->app.data, s->app.len, - NGX_ESCAPE_ARGS); - - b->last = ngx_cpymem(b->last, (u_char*) "&flashver=", - sizeof("&flashver=") - 1); - b->last = (u_char*) ngx_escape_uri(b->last, s->flashver.data, - s->flashver.len, NGX_ESCAPE_ARGS); - - b->last = ngx_cpymem(b->last, (u_char*) "&swfurl=", - sizeof("&swfurl=") - 1); - b->last = (u_char*) ngx_escape_uri(b->last, s->swf_url.data, - s->swf_url.len, NGX_ESCAPE_ARGS); - - b->last = ngx_cpymem(b->last, (u_char*) "&tcurl=", - sizeof("&tcurl=") - 1); - b->last = (u_char*) ngx_escape_uri(b->last, s->tc_url.data, - s->tc_url.len, NGX_ESCAPE_ARGS); - - b->last = ngx_cpymem(b->last, (u_char*) "&pageurl=", - sizeof("&pageurl=") - 1); - b->last = (u_char*) ngx_escape_uri(b->last, s->page_url.data, - s->page_url.len, NGX_ESCAPE_ARGS); + if (s->app.len) { + 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); + } + if (s->flashver.len) { + b->last = ngx_cpymem(b->last, (u_char*) "&flashver=", + sizeof("&flashver=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, s->flashver.data, + s->flashver.len, NGX_ESCAPE_ARGS); + } + if (s->swf_url.len) { + b->last = ngx_cpymem(b->last, (u_char*) "&swfurl=", + sizeof("&swfurl=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, s->swf_url.data, + s->swf_url.len, NGX_ESCAPE_ARGS); + } + if (s->tc_url.len) { + b->last = ngx_cpymem(b->last, (u_char*) "&tcurl=", + sizeof("&tcurl=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, s->tc_url.data, + s->tc_url.len, NGX_ESCAPE_ARGS); + } + if (s->page_url.len) { + b->last = ngx_cpymem(b->last, (u_char*) "&pageurl=", + sizeof("&pageurl=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, s->page_url.data, + 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, diff --git a/ngx_rtmp_notify_module.c b/ngx_rtmp_notify_module.c index 2fcfffb1a..04ee64a5b 100644 --- a/ngx_rtmp_notify_module.c +++ b/ngx_rtmp_notify_module.c @@ -19,7 +19,9 @@ static ngx_rtmp_disconnect_pt next_disconnect; static ngx_rtmp_publish_pt next_publish; static ngx_rtmp_play_pt next_play; static ngx_rtmp_close_stream_pt next_close_stream; +static ngx_rtmp_record_started_pt next_record_started; static ngx_rtmp_record_done_pt next_record_done; +static ngx_rtmp_playlist_pt next_playlist; static char *ngx_rtmp_notify_on_srv_event(ngx_conf_t *cf, ngx_command_t *cmd, @@ -28,6 +30,8 @@ static char *ngx_rtmp_notify_on_app_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_rtmp_notify_method(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_rtmp_notify_send_redirect(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static ngx_int_t ngx_rtmp_notify_postconfiguration(ngx_conf_t *cf); static void * ngx_rtmp_notify_create_app_conf(ngx_conf_t *cf); static char * ngx_rtmp_notify_merge_app_conf(ngx_conf_t *cf, @@ -53,8 +57,10 @@ enum { NGX_RTMP_NOTIFY_PLAY_DONE, NGX_RTMP_NOTIFY_PUBLISH_DONE, NGX_RTMP_NOTIFY_DONE, + NGX_RTMP_NOTIFY_RECORD_STARTED, NGX_RTMP_NOTIFY_RECORD_DONE, NGX_RTMP_NOTIFY_UPDATE, + NGX_RTMP_NOTIFY_PLAYLIST, NGX_RTMP_NOTIFY_APP_MAX }; @@ -70,6 +76,7 @@ typedef struct { ngx_url_t *url[NGX_RTMP_NOTIFY_APP_MAX]; ngx_flag_t active; ngx_uint_t method; + ngx_flag_t send_redirect; ngx_msec_t update_timeout; ngx_flag_t update_strict; ngx_flag_t relay_redirect; @@ -79,6 +86,7 @@ typedef struct { typedef struct { ngx_url_t *url[NGX_RTMP_NOTIFY_SRV_MAX]; ngx_uint_t method; + ngx_flag_t send_redirect; } ngx_rtmp_notify_srv_conf_t; @@ -148,6 +156,14 @@ static ngx_command_t ngx_rtmp_notify_commands[] = { 0, NULL }, + { ngx_string("on_record_started"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_RTMP_REC_CONF| + NGX_CONF_TAKE1, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + { ngx_string("on_record_done"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_RTMP_REC_CONF| NGX_CONF_TAKE1, @@ -163,6 +179,13 @@ static ngx_command_t ngx_rtmp_notify_commands[] = { 0, NULL }, + { ngx_string("on_playlist"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_rtmp_notify_on_app_event, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + { ngx_string("notify_method"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, ngx_rtmp_notify_method, @@ -191,6 +214,13 @@ static ngx_command_t ngx_rtmp_notify_commands[] = { offsetof(ngx_rtmp_notify_app_conf_t, relay_redirect), NULL }, + { ngx_string("notify_send_redirect"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_rtmp_notify_send_redirect, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + ngx_null_command }; @@ -239,6 +269,7 @@ ngx_rtmp_notify_create_app_conf(ngx_conf_t *cf) } nacf->method = NGX_CONF_UNSET_UINT; + nacf->send_redirect = NGX_CONF_UNSET; nacf->update_timeout = NGX_CONF_UNSET_MSEC; nacf->update_strict = NGX_CONF_UNSET; nacf->relay_redirect = NGX_CONF_UNSET; @@ -267,6 +298,7 @@ ngx_rtmp_notify_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_uint_value(conf->method, prev->method, NGX_RTMP_NETCALL_HTTP_POST); + ngx_conf_merge_value(conf->send_redirect, prev->send_redirect, 0); ngx_conf_merge_msec_value(conf->update_timeout, prev->update_timeout, 30000); ngx_conf_merge_value(conf->update_strict, prev->update_strict, 0); @@ -292,6 +324,7 @@ ngx_rtmp_notify_create_srv_conf(ngx_conf_t *cf) } nscf->method = NGX_CONF_UNSET_UINT; + nscf->send_redirect = NGX_CONF_UNSET; return nscf; } @@ -310,6 +343,7 @@ ngx_rtmp_notify_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_uint_value(conf->method, prev->method, NGX_RTMP_NETCALL_HTTP_POST); + ngx_conf_merge_value(conf->send_redirect, prev->send_redirect, 0); return NGX_CONF_OK; } @@ -323,15 +357,35 @@ ngx_rtmp_notify_create_request(ngx_rtmp_session_t *s, ngx_pool_t *pool, ngx_chain_t *al, *bl, *cl; ngx_url_t *url; + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: create request: begin"); + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); url = nacf->url[url_idx]; + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: create request: netcall format session"); + al = ngx_rtmp_netcall_http_format_session(s, pool); if (al == NULL) { return NULL; } + // Swap args and fulled session params chain + // Because nginx-rtmp session params are higher priority + // And must be last in chain to prevent override. + // So. + // In args first symbol IS NOT '&', but LAST ONE + if (args) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: create request: swap formated args"); + + cl = args; + args = al; + al = cl; + } + al->next = args; bl = NULL; @@ -342,33 +396,90 @@ ngx_rtmp_notify_create_request(ngx_rtmp_session_t *s, ngx_pool_t *pool, bl = cl; } + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: create request: netcall format request"); + return ngx_rtmp_netcall_http_format_request(nacf->method, &url->host, &url->uri, al, bl, pool, &ngx_rtmp_notify_urlencoded); } +static ngx_chain_t * +ngx_rtmp_notify_create_srv_request(ngx_rtmp_session_t *s, ngx_pool_t *pool, + ngx_uint_t url_idx, ngx_chain_t *args) +{ + ngx_rtmp_notify_srv_conf_t *nscf; + ngx_chain_t *al, *bl, *cl; + ngx_url_t *url; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: create srv request: begin"); + + nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_notify_module); + + url = nscf->url[url_idx]; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: create srv request: netcall format session"); + + al = ngx_rtmp_netcall_http_format_session(s, pool); + if (al == NULL) { + return NULL; + } + + // Swap args and fulled session params chain + // Because nginx-rtmp session params are higher priority + // And must be last in chain to prevent override. + // So. + // In args first symbol IS NOT '&', but LAST ONE + if (args) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: create srv request: swap formated args"); + + cl = args; + args = al; + al = cl; + } + + al->next = args; + + bl = NULL; + + if (nscf->method == NGX_RTMP_NETCALL_HTTP_POST) { + cl = al; + al = bl; + bl = cl; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: create srv request: netcall format request"); + + return ngx_rtmp_netcall_http_format_request(nscf->method, &url->host, + &url->uri, al, bl, pool, + &ngx_rtmp_notify_urlencoded); +} + + static ngx_chain_t * ngx_rtmp_notify_connect_create(ngx_rtmp_session_t *s, void *arg, ngx_pool_t *pool) { ngx_rtmp_connect_t *v = arg; - ngx_rtmp_notify_srv_conf_t *nscf; - ngx_url_t *url; - ngx_chain_t *al, *bl; + ngx_chain_t *al; ngx_buf_t *b; - ngx_str_t *addr_text; size_t app_len, args_len, flashver_len, swf_url_len, tc_url_len, page_url_len; - nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_notify_module); - al = ngx_alloc_chain_link(pool); if (al == NULL) { return NULL; } + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: connect: begin"); + /* these values are still missing in session * so we have to construct the request from * connection struct */ @@ -380,18 +491,15 @@ ngx_rtmp_notify_connect_create(ngx_rtmp_session_t *s, void *arg, tc_url_len = ngx_strlen(v->tc_url); page_url_len = ngx_strlen(v->page_url); - addr_text = &s->connection->addr_text; - b = ngx_create_temp_buf(pool, - sizeof("call=connect") - 1 + + sizeof("call=connect") + sizeof("&app=") - 1 + app_len * 3 + sizeof("&flashver=") - 1 + flashver_len * 3 + sizeof("&swfurl=") - 1 + swf_url_len * 3 + sizeof("&tcurl=") - 1 + tc_url_len * 3 + sizeof("&pageurl=") - 1 + page_url_len * 3 + - sizeof("&addr=") - 1 + addr_text->len * 3 + sizeof("&epoch=") - 1 + NGX_INT32_LEN + - 1 + args_len + 1 + args_len + 1 ); if (b == NULL) { @@ -401,7 +509,15 @@ ngx_rtmp_notify_connect_create(ngx_rtmp_session_t *s, void *arg, al->buf = b; al->next = NULL; - b->last = ngx_cpymem(b->last, (u_char*) "app=", sizeof("app=") - 1); + if (args_len) { + b->last = (u_char *) ngx_cpymem(b->last, v->args, args_len); + *b->last++ = '&'; + } + + b->last = ngx_cpymem(b->last, (u_char*) "call=connect", + sizeof("call=connect") - 1); + + b->last = ngx_cpymem(b->last, (u_char*) "&app=", sizeof("&app=") - 1); b->last = (u_char*) ngx_escape_uri(b->last, v->app, app_len, NGX_ESCAPE_ARGS); @@ -425,33 +541,12 @@ ngx_rtmp_notify_connect_create(ngx_rtmp_session_t *s, void *arg, b->last = (u_char*) ngx_escape_uri(b->last, v->page_url, 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*) "&epoch=", sizeof("&epoch=") -1); b->last = ngx_sprintf(b->last, "%uD", (uint32_t) s->epoch); - b->last = ngx_cpymem(b->last, (u_char*) "&call=connect", - sizeof("&call=connect") - 1); - - if (args_len) { - *b->last++ = '&'; - b->last = (u_char *) ngx_cpymem(b->last, v->args, args_len); - } - - url = nscf->url[NGX_RTMP_NOTIFY_CONNECT]; - - bl = NULL; - - if (nscf->method == NGX_RTMP_NETCALL_HTTP_POST) { - bl = al; - al = NULL; - } + *b->last++ = '&'; - return ngx_rtmp_netcall_http_format_request(nscf->method, &url->host, - &url->uri, al, bl, pool, - &ngx_rtmp_notify_urlencoded); + return ngx_rtmp_notify_create_srv_request(s, pool, NGX_RTMP_NOTIFY_CONNECT, al); } @@ -459,22 +554,20 @@ static ngx_chain_t * ngx_rtmp_notify_disconnect_create(ngx_rtmp_session_t *s, void *arg, ngx_pool_t *pool) { - ngx_rtmp_notify_srv_conf_t *nscf; - ngx_url_t *url; - ngx_chain_t *al, *bl, *pl; + ngx_chain_t *pl; ngx_buf_t *b; - nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_notify_module); - pl = ngx_alloc_chain_link(pool); if (pl == NULL) { return NULL; } b = ngx_create_temp_buf(pool, - sizeof("&call=disconnect") + - sizeof("&app=") + s->app.len * 3 + - 1 + s->args.len); + sizeof("call=disconnect") + + sizeof("&bytes_in=") - 1 + NGX_INT32_LEN + + sizeof("&bytes_out=") - 1 + NGX_INT32_LEN + + 1 + s->args.len + 1); + if (b == NULL) { return NULL; } @@ -482,37 +575,23 @@ ngx_rtmp_notify_disconnect_create(ngx_rtmp_session_t *s, void *arg, pl->buf = b; pl->next = NULL; - b->last = ngx_cpymem(b->last, (u_char*) "&call=disconnect", - sizeof("&call=disconnect") - 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); - if (s->args.len) { - *b->last++ = '&'; b->last = (u_char *) ngx_cpymem(b->last, s->args.data, s->args.len); + *b->last++ = '&'; } - url = nscf->url[NGX_RTMP_NOTIFY_DISCONNECT]; + b->last = ngx_cpymem(b->last, (u_char*) "call=disconnect", + sizeof("call=disconnect") - 1); - al = ngx_rtmp_netcall_http_format_session(s, pool); - if (al == NULL) { - return NULL; - } + b->last = ngx_cpymem(b->last, (u_char*) "&bytes_in=", sizeof("&bytes_in=") -1); + b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->in_bytes); - al->next = pl; + b->last = ngx_cpymem(b->last, (u_char*) "&bytes_out=", sizeof("&bytes_out=") -1); + b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->out_bytes); - bl = NULL; + *b->last++ = '&'; - if (nscf->method == NGX_RTMP_NETCALL_HTTP_POST) { - bl = al; - al = NULL; - } - - return ngx_rtmp_netcall_http_format_request(nscf->method, &url->host, - &url->uri, al, bl, pool, - &ngx_rtmp_notify_urlencoded); + return ngx_rtmp_notify_create_srv_request(s, pool, NGX_RTMP_NOTIFY_DISCONNECT, pl); } @@ -536,10 +615,10 @@ ngx_rtmp_notify_publish_create(ngx_rtmp_session_t *s, void *arg, args_len = ngx_strlen(v->args); b = ngx_create_temp_buf(pool, - sizeof("&call=publish") + + sizeof("call=publish") + sizeof("&name=") + name_len * 3 + sizeof("&type=") + type_len * 3 + - 1 + args_len); + 1 + args_len + 1); if (b == NULL) { return NULL; } @@ -547,8 +626,13 @@ ngx_rtmp_notify_publish_create(ngx_rtmp_session_t *s, void *arg, pl->buf = b; pl->next = NULL; - b->last = ngx_cpymem(b->last, (u_char*) "&call=publish", - sizeof("&call=publish") - 1); + if (args_len) { + b->last = (u_char *) ngx_cpymem(b->last, v->args, args_len); + *b->last++ = '&'; + } + + b->last = ngx_cpymem(b->last, (u_char*) "call=publish", + sizeof("call=publish") - 1); b->last = ngx_cpymem(b->last, (u_char*) "&name=", sizeof("&name=") - 1); b->last = (u_char*) ngx_escape_uri(b->last, v->name, name_len, @@ -558,10 +642,7 @@ ngx_rtmp_notify_publish_create(ngx_rtmp_session_t *s, void *arg, b->last = (u_char*) ngx_escape_uri(b->last, v->type, type_len, NGX_ESCAPE_ARGS); - if (args_len) { - *b->last++ = '&'; - b->last = (u_char *) ngx_cpymem(b->last, v->args, args_len); - } + *b->last++ = '&'; return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_PUBLISH, pl); } @@ -586,10 +667,10 @@ ngx_rtmp_notify_play_create(ngx_rtmp_session_t *s, void *arg, args_len = ngx_strlen(v->args); b = ngx_create_temp_buf(pool, - sizeof("&call=play") + + sizeof("call=play") + sizeof("&name=") + name_len * 3 + sizeof("&start=&duration=&reset=") + - NGX_INT32_LEN * 3 + 1 + args_len); + NGX_INT32_LEN * 3 + 1 + args_len + 1); if (b == NULL) { return NULL; } @@ -597,8 +678,13 @@ ngx_rtmp_notify_play_create(ngx_rtmp_session_t *s, void *arg, pl->buf = b; pl->next = NULL; - b->last = ngx_cpymem(b->last, (u_char*) "&call=play", - sizeof("&call=play") - 1); + if (args_len) { + b->last = (u_char *) ngx_cpymem(b->last, v->args, args_len); + *b->last++ = '&'; + } + + b->last = ngx_cpymem(b->last, (u_char*) "call=play", + sizeof("call=play") - 1); b->last = ngx_cpymem(b->last, (u_char*) "&name=", sizeof("&name=") - 1); b->last = (u_char*) ngx_escape_uri(b->last, v->name, name_len, @@ -609,10 +695,7 @@ ngx_rtmp_notify_play_create(ngx_rtmp_session_t *s, void *arg, (uint32_t) v->start, (uint32_t) v->duration, v->reset & 1); - if (args_len) { - *b->last++ = '&'; - b->last = (u_char *) ngx_cpymem(b->last, v->args, args_len); - } + *b->last++ = '&'; return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_PLAY, pl); } @@ -641,9 +724,12 @@ ngx_rtmp_notify_done_create(ngx_rtmp_session_t *s, void *arg, args_len = ctx ? ngx_strlen(ctx->args) : 0; b = ngx_create_temp_buf(pool, - sizeof("&call=") + cbname_len + - sizeof("&name=") + name_len * 3 + - 1 + args_len); + sizeof("call=") + cbname_len + + sizeof("&name=") + name_len * 3 + + sizeof("&bytes_in=") - 1 + NGX_INT32_LEN + + sizeof("&bytes_out=") - 1 + NGX_INT32_LEN + + 1 + args_len + 1); + if (b == NULL) { return NULL; } @@ -651,7 +737,12 @@ ngx_rtmp_notify_done_create(ngx_rtmp_session_t *s, void *arg, pl->buf = b; pl->next = NULL; - b->last = ngx_cpymem(b->last, (u_char*) "&call=", sizeof("&call=") - 1); + if (args_len) { + b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len); + *b->last++ = '&'; + } + + b->last = ngx_cpymem(b->last, (u_char*) "call=", sizeof("call=") - 1); b->last = ngx_cpymem(b->last, ds->cbname, cbname_len); if (name_len) { @@ -660,10 +751,13 @@ ngx_rtmp_notify_done_create(ngx_rtmp_session_t *s, void *arg, NGX_ESCAPE_ARGS); } - if (args_len) { - *b->last++ = '&'; - b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len); - } + b->last = ngx_cpymem(b->last, (u_char*) "&bytes_in=", sizeof("&bytes_in=") -1); + b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->in_bytes); + + b->last = ngx_cpymem(b->last, (u_char*) "&bytes_out=", sizeof("&bytes_out=") -1); + b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->out_bytes); + + *b->last++ = '&'; return ngx_rtmp_notify_create_request(s, pool, ds->url_idx, pl); } @@ -698,11 +792,11 @@ ngx_rtmp_notify_update_create(ngx_rtmp_session_t *s, void *arg, args_len = ctx ? ngx_strlen(ctx->args) : 0; b = ngx_create_temp_buf(pool, - sizeof("&call=update") + sfx.len + + sizeof("call=update") + sfx.len + sizeof("&time=") + NGX_TIME_T_LEN + sizeof("×tamp=") + NGX_INT32_LEN + sizeof("&name=") + name_len * 3 + - 1 + args_len); + 1 + args_len + 1); if (b == NULL) { return NULL; } @@ -710,8 +804,13 @@ ngx_rtmp_notify_update_create(ngx_rtmp_session_t *s, void *arg, pl->buf = b; pl->next = NULL; - b->last = ngx_cpymem(b->last, (u_char*) "&call=update", - sizeof("&call=update") - 1); + if (args_len) { + b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len); + *b->last++ = '&'; + } + + b->last = ngx_cpymem(b->last, (u_char*) "call=update", + sizeof("call=update") - 1); b->last = ngx_cpymem(b->last, sfx.data, sfx.len); b->last = ngx_cpymem(b->last, (u_char *) "&time=", @@ -728,12 +827,72 @@ ngx_rtmp_notify_update_create(ngx_rtmp_session_t *s, void *arg, NGX_ESCAPE_ARGS); } + *b->last++ = '&'; + + return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_UPDATE, pl); +} + + +static ngx_chain_t * +ngx_rtmp_notify_record_started_create(ngx_rtmp_session_t *s, void *arg, + ngx_pool_t *pool) +{ + ngx_rtmp_record_started_t *v = arg; + + ngx_rtmp_notify_ctx_t *ctx; + ngx_chain_t *pl; + ngx_buf_t *b; + size_t name_len, args_len; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + + pl = ngx_alloc_chain_link(pool); + if (pl == NULL) { + return NULL; + } + + name_len = ngx_strlen(ctx->name); + args_len = ngx_strlen(ctx->args); + + b = ngx_create_temp_buf(pool, + sizeof("call=record_started") + + sizeof("&recorder=") + v->recorder.len + + sizeof("&name=") + name_len * 3 + + sizeof("&path=") + v->path.len * 3 + + 1 + args_len + 1); + + if (b == NULL) { + return NULL; + } + + pl->buf = b; + pl->next = NULL; + if (args_len) { - *b->last++ = '&'; b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len); + *b->last++ = '&'; } - return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_UPDATE, pl); + b->last = ngx_cpymem(b->last, (u_char*) "call=record_started", + sizeof("call=record_started") - 1); + + b->last = ngx_cpymem(b->last, (u_char *) "&recorder=", + sizeof("&recorder=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, v->recorder.data, + v->recorder.len, NGX_ESCAPE_ARGS); + + b->last = ngx_cpymem(b->last, (u_char*) "&name=", sizeof("&name=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, ctx->name, name_len, + NGX_ESCAPE_ARGS); + + b->last = ngx_cpymem(b->last, (u_char*) "&path=", sizeof("&path=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, v->path.data, v->path.len, + NGX_ESCAPE_ARGS); + + *b->last++ = '&'; + + return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_RECORD_STARTED, + pl); } @@ -759,11 +918,14 @@ ngx_rtmp_notify_record_done_create(ngx_rtmp_session_t *s, void *arg, args_len = ngx_strlen(ctx->args); b = ngx_create_temp_buf(pool, - sizeof("&call=record_done") + - sizeof("&recorder=") + v->recorder.len + - sizeof("&name=") + name_len * 3 + - sizeof("&path=") + v->path.len * 3 + - 1 + args_len); + sizeof("call=record_done") + + sizeof("&recorder=") + v->recorder.len + + sizeof("&name=") + name_len * 3 + + sizeof("&path=") + v->path.len * 3 + + sizeof("&bytes_in=") - 1 + NGX_INT32_LEN + + sizeof("&bytes_out=") - 1 + NGX_INT32_LEN + + 1 + args_len + 1); + if (b == NULL) { return NULL; } @@ -771,8 +933,13 @@ ngx_rtmp_notify_record_done_create(ngx_rtmp_session_t *s, void *arg, pl->buf = b; pl->next = NULL; - b->last = ngx_cpymem(b->last, (u_char*) "&call=record_done", - sizeof("&call=record_done") - 1); + if (args_len) { + b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len); + *b->last++ = '&'; + } + + b->last = ngx_cpymem(b->last, (u_char*) "call=record_done", + sizeof("call=record_done") - 1); b->last = ngx_cpymem(b->last, (u_char *) "&recorder=", sizeof("&recorder=") - 1); @@ -787,15 +954,74 @@ ngx_rtmp_notify_record_done_create(ngx_rtmp_session_t *s, void *arg, b->last = (u_char*) ngx_escape_uri(b->last, v->path.data, v->path.len, NGX_ESCAPE_ARGS); - if (args_len) { - *b->last++ = '&'; - b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len); - } + b->last = ngx_cpymem(b->last, (u_char*) "&bytes_in=", sizeof("&bytes_in=") -1); + b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->in_bytes); + + b->last = ngx_cpymem(b->last, (u_char*) "&bytes_out=", sizeof("&bytes_out=") -1); + b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->out_bytes); + + *b->last++ = '&'; return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_RECORD_DONE, pl); } +static ngx_chain_t * +ngx_rtmp_notify_playlist_create(ngx_rtmp_session_t *s, void *arg, + ngx_pool_t *pool) +{ + ngx_rtmp_playlist_t *v = arg; + + ngx_rtmp_notify_ctx_t *ctx; + ngx_chain_t *pl; + ngx_buf_t *b; + size_t name_len; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + + pl = ngx_alloc_chain_link(pool); + if (pl == NULL) { + return NULL; + } + + name_len = ngx_strlen(ctx->name); + + b = ngx_create_temp_buf(pool, + sizeof("call=playlist") + + sizeof("&module=") + v->module.len + + sizeof("&name=") + name_len * 3 + + sizeof("&path=") + v->playlist.len * 3 + + 1 + 1); + + if (b == NULL) { + return NULL; + } + + pl->buf = b; + pl->next = NULL; + + b->last = ngx_cpymem(b->last, (u_char*) "call=playlist", + sizeof("call=playlist") - 1); + + b->last = ngx_cpymem(b->last, (u_char *) "&module=", + sizeof("&module=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, v->module.data, + v->module.len, NGX_ESCAPE_ARGS); + + b->last = ngx_cpymem(b->last, (u_char*) "&name=", sizeof("&name=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, ctx->name, name_len, + NGX_ESCAPE_ARGS); + + b->last = ngx_cpymem(b->last, (u_char*) "&path=", sizeof("&path=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, v->playlist.data, v->playlist.len, + NGX_ESCAPE_ARGS); + + *b->last++ = '&'; + + return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_PLAYLIST, + pl); +} + static ngx_int_t ngx_rtmp_notify_parse_http_retcode(ngx_rtmp_session_t *s, @@ -820,6 +1046,8 @@ ngx_rtmp_notify_parse_http_retcode(ngx_rtmp_session_t *s, return NGX_OK; case (u_char) '3': return NGX_AGAIN; + case (u_char) '4': + return NGX_DECLINED; default: return NGX_ERROR; } @@ -892,6 +1120,7 @@ ngx_rtmp_notify_parse_http_header(ngx_rtmp_session_t *s, n = 0; state = parse_name; + /* fall through */ case parse_name: switch (c) { @@ -919,6 +1148,7 @@ ngx_rtmp_notify_parse_http_header(ngx_rtmp_session_t *s, break; } state = parse_value; + /* fall through */ case parse_value: if (c == '\n') { @@ -957,29 +1187,111 @@ ngx_rtmp_notify_connect_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in) { ngx_rtmp_connect_t *v = arg; - ngx_int_t rc; + ngx_int_t rc, send; + ngx_str_t local_name; + ngx_rtmp_notify_srv_conf_t *nscf; u_char app[NGX_RTMP_MAX_NAME]; static ngx_str_t location = ngx_string("location"); rc = ngx_rtmp_notify_parse_http_retcode(s, in); + + /* HTTP 5xx or unknown/unsupprted */ + if (rc == NGX_ERROR) { return NGX_ERROR; } - if (rc == NGX_AGAIN) { - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, - "notify: connect redirect received"); + /* HTTP 4xx */ + + if (rc == NGX_DECLINED) { + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: connection denyed by callback return code 4xx"); + + ngx_rtmp_send_status(s, "NetConnection.Connect.Rejected", "error", + "Cennection denyed by notify event handler and callback return code"); + + // Something by rtmpdump lib + send = ngx_rtmp_send_close_method(s, "close"); + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: connect send(e) close method = '%ui'", send == NGX_OK); + + return NGX_ERROR; + } + + if (rc != NGX_AGAIN) { + goto next; + } + + /* HTTP 3xx */ + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: connect redirect received"); + + rc = ngx_rtmp_notify_parse_http_header(s, in, &location, app, + sizeof(app) - 1); + if (rc <= 0) { + goto next; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: parsed location '%*s'", rc, app); + + /* switch app */ + + if (ngx_strncasecmp(app, (u_char *) "rtmp://", 7)) { + *ngx_cpymem(v->app, app, rc) = 0; + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: connect redirect to '%s'", v->app); + goto next; + } + + /* redirect */ + + nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_notify_module); + + if (nscf->send_redirect) { + // Send 302 redirect and go next + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: connect send 302 redirect"); + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: -- for app '%s' to new location '%*s'", v->app, rc, app); + + local_name.data = ngx_palloc(s->connection->pool, rc+1); + local_name.len = rc; + *ngx_cpymem(local_name.data, app, rc) = 0; + + /* MAGICK HERE */ + + if (!ngx_strncasecmp(s->flashver.data, (u_char *) "FMLE/", 5)) { + // Official method, by FMS SDK + send = ngx_rtmp_send_redirect_status(s, "onStatus", "Connect here", local_name); + send &= ngx_rtmp_send_redirect_status(s, "netStatus", "Connect here", local_name); - rc = ngx_rtmp_notify_parse_http_header(s, in, &location, app, - sizeof(app) - 1); - if (rc > 0) { - *ngx_cpymem(v->app, app, rc) = 0; ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, - "notify: connect redirect to '%s'", v->app); + "notify: connect send(o) status = '%ui'", send == NGX_OK); + } else { + // Something by rtmpdump lib + send = ngx_rtmp_send_redirect_status(s, "_error", "Connect here", local_name); + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: connect send(e) status = '%ui'", send == NGX_OK); } + + ngx_pfree(s->connection->pool, local_name.data); + + // Something by rtmpdump lib + send = ngx_rtmp_send_close_method(s, "close"); + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: connect send(e) close method = '%ui'", send == NGX_OK); + + return send; } +next: + return next_connect(s, v); } @@ -1005,7 +1317,7 @@ ngx_rtmp_notify_publish_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in) { ngx_rtmp_publish_t *v = arg; - ngx_int_t rc; + ngx_int_t rc, send; ngx_str_t local_name; ngx_rtmp_relay_target_t target; ngx_url_t *u; @@ -1015,18 +1327,41 @@ ngx_rtmp_notify_publish_handle(ngx_rtmp_session_t *s, static ngx_str_t location = ngx_string("location"); rc = ngx_rtmp_notify_parse_http_retcode(s, in); + + /* HTTP 5xx or unknown/unsupprted */ + if (rc == NGX_ERROR) { ngx_rtmp_notify_clear_flag(s, NGX_RTMP_NOTIFY_PUBLISHING); return NGX_ERROR; } + /* HTTP 4xx */ + + if (rc == NGX_DECLINED) { + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: publishing denyed by callback return code 4xx"); + + ngx_rtmp_send_status(s, "NetConnection.Connect.Rejected", "error", + "Publishing denyed by notify event handler and callback return code"); + + ngx_rtmp_notify_clear_flag(s, NGX_RTMP_NOTIFY_PUBLISHING); + + // Something by rtmpdump lib + send = ngx_rtmp_send_close_method(s, "close"); + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: connect send(e) close method = '%ui'", send == NGX_OK); + + return NGX_ERROR; + } + if (rc != NGX_AGAIN) { goto next; } /* HTTP 3xx */ - ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "notify: publish redirect received"); rc = ngx_rtmp_notify_parse_http_header(s, in, &location, name, @@ -1045,11 +1380,55 @@ ngx_rtmp_notify_publish_handle(ngx_rtmp_session_t *s, /* push */ nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); - if (nacf->relay_redirect) { + + if (nacf->send_redirect) { + // Send 302 redirect and go next + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: publish send 302 redirect"); + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: -- for stream '%s' to new location '%*s'", v->name, rc, name); + + local_name.data = ngx_palloc(s->connection->pool, rc+1); + local_name.len = rc; + *ngx_cpymem(local_name.data, name, rc) = 0; + + /* MAGICK HERE */ + + if (!ngx_strncasecmp(s->flashver.data, (u_char *) "FMLE/", 5)) { + // Official method, by FMS SDK + send = ngx_rtmp_send_redirect_status(s, "onStatus", "Connect here", local_name); + send &= ngx_rtmp_send_redirect_status(s, "netStatus", "Connect here", local_name); + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: publish send(o) status = '%ui'", send == NGX_OK); + } else { + + // Something by rtmpdump lib + send = ngx_rtmp_send_redirect_status(s, "_error", "Connect here", local_name); + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: publish send(e) status = '%ui'", send == NGX_OK); + } + + ngx_pfree(s->connection->pool, local_name.data); + + ngx_rtmp_notify_clear_flag(s, NGX_RTMP_NOTIFY_PUBLISHING); + + // Something by rtmpdump lib + send = ngx_rtmp_send_close_method(s, "close"); + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: publish send(e) close method = '%ui'", send == NGX_OK); + + return send; + + } else if (nacf->relay_redirect) { + // Relay local streams, change name + ngx_rtmp_notify_set_name(v->name, NGX_RTMP_MAX_NAME, name, (size_t) rc); } - ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "notify: push '%s' to '%*s'", v->name, rc, name); local_name.data = v->name; @@ -1083,8 +1462,11 @@ static ngx_int_t ngx_rtmp_notify_play_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in) { + ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0, + "notify: ngx_rtmp_notify_play_handle"); + ngx_rtmp_play_t *v = arg; - ngx_int_t rc; + ngx_int_t rc, send; ngx_str_t local_name; ngx_rtmp_relay_target_t target; ngx_url_t *u; @@ -1094,11 +1476,34 @@ ngx_rtmp_notify_play_handle(ngx_rtmp_session_t *s, static ngx_str_t location = ngx_string("location"); rc = ngx_rtmp_notify_parse_http_retcode(s, in); + + /* HTTP 5xx or unknown/unsupprted */ + if (rc == NGX_ERROR) { ngx_rtmp_notify_clear_flag(s, NGX_RTMP_NOTIFY_PLAYING); return NGX_ERROR; } + /* HTTP 4xx */ + + if (rc == NGX_DECLINED) { + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: playing denyed by callback return code 4xx"); + + ngx_rtmp_send_status(s, "NetConnection.Connect.Rejected", "error", + "Playing denyed by notify event handler and callback return code"); + + ngx_rtmp_notify_clear_flag(s, NGX_RTMP_NOTIFY_PLAYING); + + // Something by rtmpdump lib + send = ngx_rtmp_send_close_method(s, "close"); + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: connect send(e) close method = '%ui'", send == NGX_OK); + + return NGX_ERROR; + } + if (rc != NGX_AGAIN) { goto next; } @@ -1117,14 +1522,57 @@ ngx_rtmp_notify_play_handle(ngx_rtmp_session_t *s, if (ngx_strncasecmp(name, (u_char *) "rtmp://", 7)) { *ngx_cpymem(v->name, name, rc) = 0; ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, - "notify: play redirect to '%s'", v->name); + "notify: play internal redirect to '%s'", v->name); goto next; } /* pull */ nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); - if (nacf->relay_redirect) { + if (nacf->send_redirect) { + // Send 302 redirect and go next + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: play send 302 redirect"); + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: -- for stream '%s' to new location '%*s'", v->name, rc, name); + + local_name.data = ngx_palloc(s->connection->pool, rc+1); + local_name.len = rc; + *ngx_cpymem(local_name.data, name, rc) = 0; + + /* MAGICK HERE */ + + if (!ngx_strncasecmp(s->flashver.data, (u_char *) "FMLE/", 5)) { + // Official method, by FMS SDK + send = ngx_rtmp_send_redirect_status(s, "onStatus", "Connect here", local_name); + send &= ngx_rtmp_send_redirect_status(s, "netStatus", "Connect here", local_name); + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: play send(o) status = '%ui'", send == NGX_OK); + } else { + + // Something by rtmpdump lib + send = ngx_rtmp_send_redirect_status(s, "_error", "Connect here", local_name); + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: play send(e) status = '%ui'", send == NGX_OK); + } + + ngx_pfree(s->connection->pool, local_name.data); + + ngx_rtmp_notify_clear_flag(s, NGX_RTMP_NOTIFY_PLAYING); + + // Something by rtmpdump lib + send = ngx_rtmp_send_close_method(s, "close"); + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: play send(e) close method = '%ui'", send == NGX_OK); + + return send; + + } else if (nacf->relay_redirect) { + // Relay local streams, change name + ngx_rtmp_notify_set_name(v->name, NGX_RTMP_MAX_NAME, name, (size_t) rc); } @@ -1153,6 +1601,8 @@ ngx_rtmp_notify_play_handle(ngx_rtmp_session_t *s, ngx_rtmp_relay_pull(s, &local_name, &target); next: + ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0, + "notify: ngx_rtmp_notify_play_handle: next"); return next_play(s, v); } @@ -1170,7 +1620,7 @@ ngx_rtmp_notify_update_handle(ngx_rtmp_session_t *s, rc = ngx_rtmp_notify_parse_http_retcode(s, in); - if ((!nacf->update_strict && rc == NGX_ERROR) || + if ((!nacf->update_strict && (rc == NGX_ERROR || rc == NGX_DECLINED) ) || (nacf->update_strict && rc != NGX_OK)) { ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, @@ -1396,6 +1846,9 @@ ngx_rtmp_notify_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) static ngx_int_t ngx_rtmp_notify_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "notify: ngx_rtmp_notify_play"); + ngx_rtmp_notify_app_conf_t *nacf; ngx_rtmp_netcall_init_t ci; ngx_url_t *url; @@ -1431,6 +1884,9 @@ ngx_rtmp_notify_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) return ngx_rtmp_netcall_create(s, &ci); next: + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "notify: ngx_rtmp_notify_play: next"); + return next_play(s, v); } @@ -1481,6 +1937,39 @@ ngx_rtmp_notify_close_stream(ngx_rtmp_session_t *s, } +static ngx_int_t +ngx_rtmp_notify_record_started(ngx_rtmp_session_t *s, ngx_rtmp_record_started_t *v) +{ + ngx_rtmp_netcall_init_t ci; + ngx_rtmp_notify_app_conf_t *nacf; + + if (s->auto_pushed) { + goto next; + } + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + if (nacf == NULL || nacf->url[NGX_RTMP_NOTIFY_RECORD_STARTED] == NULL) { + goto next; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: record_started recorder=%V path='%V' url='%V'", + &v->recorder, &v->path, + &nacf->url[NGX_RTMP_NOTIFY_RECORD_STARTED]->url); + + ngx_memzero(&ci, sizeof(ci)); + + ci.url = nacf->url[NGX_RTMP_NOTIFY_RECORD_STARTED]; + ci.create = ngx_rtmp_notify_record_started_create; + ci.arg = v; + + ngx_rtmp_netcall_create(s, &ci); + +next: + return next_record_started(s, v); +} + + static ngx_int_t ngx_rtmp_notify_record_done(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v) { @@ -1579,6 +2068,35 @@ ngx_rtmp_notify_parse_url(ngx_conf_t *cf, ngx_str_t *url) } +static ngx_int_t +ngx_rtmp_notify_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v) +{ + ngx_rtmp_netcall_init_t ci; + ngx_rtmp_notify_app_conf_t *nacf; + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + if (nacf == NULL || nacf->url[NGX_RTMP_NOTIFY_PLAYLIST] == NULL) { + goto next; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: playlist url='%V'", + &nacf->url[NGX_RTMP_NOTIFY_PLAYLIST]->url); + + ngx_memzero(&ci, sizeof(ci)); + + ci.url = nacf->url[NGX_RTMP_NOTIFY_PLAYLIST]; + ci.create = ngx_rtmp_notify_playlist_create; + ci.arg = v; + + ngx_rtmp_netcall_create(s, &ci); + +next: + return next_playlist(s, v); +} + + + static char * ngx_rtmp_notify_on_srv_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { @@ -1648,6 +2166,10 @@ ngx_rtmp_notify_on_app_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) n = NGX_RTMP_NOTIFY_UPDATE; break; + case sizeof("on_playlist") - 1: + n = NGX_RTMP_NOTIFY_PLAYLIST; + break; + case sizeof("on_publish") - 1: n = NGX_RTMP_NOTIFY_PUBLISH; break; @@ -1656,6 +2178,10 @@ ngx_rtmp_notify_on_app_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) n = NGX_RTMP_NOTIFY_PLAY_DONE; break; + case sizeof("on_record_started") - 1: + n = NGX_RTMP_NOTIFY_RECORD_STARTED; + break; + case sizeof("on_record_done") - 1: n = NGX_RTMP_NOTIFY_RECORD_DONE; break; @@ -1702,6 +2228,37 @@ ngx_rtmp_notify_method(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_OK; } +static char * +ngx_rtmp_notify_send_redirect(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_notify_app_conf_t *nacf = conf; + + ngx_rtmp_notify_srv_conf_t *nscf; + ngx_str_t *value; + + value = cf->args->elts; + value++; + + if (value->len == sizeof("on") - 1 && + ngx_strncasecmp(value->data, (u_char *) "on", value->len) == 0) + { + nacf->send_redirect = 1; + + } else if (value->len == sizeof("off") - 1 && + ngx_strncasecmp(value->data, (u_char *) "off", value->len) == 0) + { + nacf->send_redirect = 0; + + } else { + return "got unexpected send_redirect value"; + } + + nscf = ngx_rtmp_conf_get_module_srv_conf(cf, ngx_rtmp_notify_module); + nscf->send_redirect = nacf->send_redirect; + + return NGX_CONF_OK; +} + static ngx_int_t ngx_rtmp_notify_postconfiguration(ngx_conf_t *cf) @@ -1721,8 +2278,14 @@ ngx_rtmp_notify_postconfiguration(ngx_conf_t *cf) next_close_stream = ngx_rtmp_close_stream; ngx_rtmp_close_stream = ngx_rtmp_notify_close_stream; + next_record_started = ngx_rtmp_record_started; + ngx_rtmp_record_started = ngx_rtmp_notify_record_started; + next_record_done = ngx_rtmp_record_done; ngx_rtmp_record_done = ngx_rtmp_notify_record_done; + next_playlist = ngx_rtmp_playlist; + ngx_rtmp_playlist = ngx_rtmp_notify_playlist; + return NGX_OK; } diff --git a/ngx_rtmp_play_module.c b/ngx_rtmp_play_module.c index 0acf6ffae..b5b237dc0 100644 --- a/ngx_rtmp_play_module.c +++ b/ngx_rtmp_play_module.c @@ -291,7 +291,11 @@ ngx_rtmp_play_send(ngx_event_t *e) ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: send buffer full"); +#if (nginx_version >= 1007012) + ngx_post_event(e, (ngx_queue_t *) &s->posted_dry_events); +#else ngx_post_event(e, &s->posted_dry_events); +#endif return; } @@ -481,6 +485,9 @@ ngx_rtmp_play_copy_local_file(ngx_rtmp_session_t *s, u_char *name) ngx_rtmp_play_ctx_t *ctx; u_char *path, *p; static u_char dpath[NGX_MAX_PATH + 1]; + u_char *d; + static u_char dir[NGX_MAX_PATH + 1]; + u_int l; pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); if (pacf == NULL) { @@ -496,6 +503,26 @@ ngx_rtmp_play_copy_local_file(ngx_rtmp_session_t *s, u_char *name) p = ngx_snprintf(dpath, NGX_MAX_PATH, "%V/%s%V", &pacf->local_path, name + ctx->pfx_size, &ctx->sfx); + + d = name + ctx->pfx_size; + while (*d != '\0') { + if (*d == '/') { + p = ngx_snprintf(dir, NGX_MAX_PATH, "%V/%s", &pacf->local_path, name + ctx->pfx_size, &ctx->sfx); + l = ngx_strlen(dir) - ngx_strlen(d); + dir[l]='\0'; + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: create dir '%s' for '%s'", dir, dpath); + if (ngx_create_dir(dir, 0700) == NGX_FILE_ERROR) { + if (ngx_errno != NGX_EEXIST) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "play: error creating dir '%s' for '%s'", dir, dpath); + break; + } + } + } + d++; + } + *p = 0; ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, @@ -689,6 +716,9 @@ ngx_rtmp_play_parse_index(char type, u_char *args) static ngx_int_t ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_play"); + ngx_rtmp_play_main_conf_t *pmcf; ngx_rtmp_play_app_conf_t *pacf; ngx_rtmp_play_ctx_t *ctx; @@ -800,9 +830,15 @@ ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: fmt=%V", &ctx->fmt->name); + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_play: next_entry"); + return ngx_rtmp_play_next_entry(s, v); next: + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_play: next"); + return next_play(s, v); } @@ -810,6 +846,9 @@ ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) static ngx_int_t ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_next_entry"); + ngx_rtmp_play_app_conf_t *pacf; ngx_rtmp_core_srv_conf_t *cscf; ngx_rtmp_play_ctx_t *ctx; @@ -856,6 +895,9 @@ ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) /* open remote */ if (pe->url) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_next_entry: open remote"); + return ngx_rtmp_play_open_remote(s, v); } @@ -868,9 +910,23 @@ ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) ctx->file.fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN, cscf->file_access); + /* try unsuffixed file name as fallback if adding suffix didn't work */ if (ctx->file.fd == NGX_INVALID_FILE) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, ngx_errno, - "play: error opening file '%s'", path); + "play: error opening file '%s', trying without suffix", + path); + + p = ngx_snprintf(path, NGX_MAX_PATH, "%V/%s", + pe->root, v->name + ctx->pfx_size); + *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_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, ngx_errno, + "play: error opening fallback file '%s'", path); continue; } @@ -878,12 +934,19 @@ ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) "play: open local file '%s'", path); if (ngx_rtmp_play_open(s, v->start) != NGX_OK) { + + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_next_entry: error open"); + return NGX_ERROR; } break; } + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_next_entry: next"); + return next_play(s, v); } @@ -1009,6 +1072,9 @@ ngx_rtmp_play_remote_create(ngx_rtmp_session_t *s, void *arg, ngx_pool_t *pool) static ngx_int_t ngx_rtmp_play_remote_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_remote_handle"); + ngx_rtmp_play_t *v = arg; ngx_rtmp_play_ctx_t *ctx; @@ -1016,6 +1082,8 @@ ngx_rtmp_play_remote_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in) ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); if (ctx->nbody == 0) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_remote_handle: next_entry"); return ngx_rtmp_play_next_entry(s, v); } @@ -1027,9 +1095,16 @@ ngx_rtmp_play_remote_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in) "play: open remote file"); if (ngx_rtmp_play_open(s, v->start) != NGX_OK) { + + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_remote_handle: error open"); + return NGX_ERROR; } + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "play: ngx_rtmp_play_remote_handle: next"); + return next_play(s, (ngx_rtmp_play_t *)arg); } diff --git a/ngx_rtmp_record_module.c b/ngx_rtmp_record_module.c index d48d2529b..d04e4cc45 100644 --- a/ngx_rtmp_record_module.c +++ b/ngx_rtmp_record_module.c @@ -13,6 +13,7 @@ #include "ngx_rtmp_record_module.h" +ngx_rtmp_record_started_pt ngx_rtmp_record_started; ngx_rtmp_record_done_pt ngx_rtmp_record_done; @@ -31,9 +32,9 @@ static char * ngx_rtmp_record_merge_app_conf(ngx_conf_t *cf, static ngx_int_t ngx_rtmp_record_write_frame(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx, ngx_rtmp_header_t *h, ngx_chain_t *in, ngx_int_t inc_nframes); -static ngx_int_t ngx_rtmp_record_av(ngx_rtmp_session_t *s, +static ngx_int_t ngx_rtmp_record_avd(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in); -static ngx_int_t ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, +static ngx_int_t ngx_rtmp_record_node_avd(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx, ngx_rtmp_header_t *h, ngx_chain_t *in); static ngx_int_t ngx_rtmp_record_node_open(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx); @@ -47,9 +48,13 @@ static ngx_int_t ngx_rtmp_record_init(ngx_rtmp_session_t *s); static ngx_conf_bitmask_t ngx_rtmp_record_mask[] = { { ngx_string("off"), NGX_RTMP_RECORD_OFF }, { ngx_string("all"), NGX_RTMP_RECORD_AUDIO | + NGX_RTMP_RECORD_VIDEO | + NGX_RTMP_RECORD_DATA }, + { ngx_string("av"), NGX_RTMP_RECORD_AUDIO | NGX_RTMP_RECORD_VIDEO }, { ngx_string("audio"), NGX_RTMP_RECORD_AUDIO }, { ngx_string("video"), NGX_RTMP_RECORD_VIDEO }, + { ngx_string("data"), NGX_RTMP_RECORD_DATA }, { ngx_string("keyframes"), NGX_RTMP_RECORD_KEYFRAMES }, { ngx_string("manual"), NGX_RTMP_RECORD_MANUAL }, { ngx_null_string, 0 } @@ -106,6 +111,14 @@ static ngx_command_t ngx_rtmp_record_commands[] = { offsetof(ngx_rtmp_record_app_conf_t, lock_file), NULL }, + { ngx_string("record_interval_size"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, interval_size), + NULL }, + { ngx_string("record_max_size"), NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, @@ -190,6 +203,7 @@ ngx_rtmp_record_create_app_conf(ngx_conf_t *cf) } racf->max_size = NGX_CONF_UNSET_SIZE; + racf->interval_size = NGX_CONF_UNSET_SIZE; racf->max_frames = NGX_CONF_UNSET_SIZE; racf->interval = NGX_CONF_UNSET_MSEC; racf->unique = NGX_CONF_UNSET; @@ -216,6 +230,7 @@ ngx_rtmp_record_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->path, prev->path, ""); ngx_conf_merge_str_value(conf->suffix, prev->suffix, ".flv"); ngx_conf_merge_size_value(conf->max_size, prev->max_size, 0); + ngx_conf_merge_size_value(conf->interval_size, prev->interval_size, 0); ngx_conf_merge_size_value(conf->max_frames, prev->max_frames, 0); ngx_conf_merge_value(conf->unique, prev->unique, 0); ngx_conf_merge_value(conf->append, prev->append, 0); @@ -845,6 +860,8 @@ ngx_rtmp_record_node_close(ngx_rtmp_session_t *s, v.recorder = rracf->id; ngx_rtmp_record_make_path(s, rctx, &v.path); + rctx->record_started = 0; + rc = ngx_rtmp_record_done(s, &v); s->app_conf = app_conf; @@ -889,10 +906,26 @@ ngx_rtmp_record_write_frame(ngx_rtmp_session_t *s, if (h->type == NGX_RTMP_MSG_VIDEO) { rctx->video = 1; - } else { + } + if (h->type == NGX_RTMP_MSG_AUDIO) { rctx->audio = 1; } + if (rctx->record_started == 0) + { + rctx->record_started = 1; + + ngx_rtmp_record_started_t v; + ngx_rtmp_record_app_conf_t *racf; + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); + + if (racf != NULL && racf->rec.nelts != 0) { + v.recorder = racf->id; + v.path = racf->path; + ngx_rtmp_record_started(s, &v); + } + } + timestamp = h->timestamp - rctx->epoch; if ((int32_t) timestamp < 0) { @@ -996,7 +1029,7 @@ ngx_rtmp_record_get_chain_mlen(ngx_chain_t *in) static ngx_int_t -ngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, +ngx_rtmp_record_avd(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) { ngx_rtmp_record_ctx_t *ctx; @@ -1012,7 +1045,7 @@ ngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, rctx = ctx->rec.elts; for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) { - ngx_rtmp_record_node_av(s, rctx, h, in); + ngx_rtmp_record_node_avd(s, rctx, h, in); } return NGX_OK; @@ -1020,7 +1053,7 @@ ngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, static ngx_int_t -ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx, +ngx_rtmp_record_node_avd(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx, ngx_rtmp_header_t *h, ngx_chain_t *in) { ngx_time_t next; @@ -1044,37 +1077,15 @@ ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx, ? keyframe : (rracf->flags & NGX_RTMP_RECORD_VIDEO) == 0; - if (brkframe && (rracf->flags & NGX_RTMP_RECORD_MANUAL) == 0) { - - if (rracf->interval != (ngx_msec_t) NGX_CONF_UNSET) { - - next = rctx->last; - next.msec += rracf->interval; - next.sec += (next.msec / 1000); - next.msec %= 1000; - - if (ngx_cached_time->sec > next.sec || - (ngx_cached_time->sec == next.sec && - ngx_cached_time->msec > next.msec)) - { - ngx_rtmp_record_node_close(s, rctx); - ngx_rtmp_record_node_open(s, rctx); - } - - } else if (!rctx->failed) { - ngx_rtmp_record_node_open(s, rctx); - } - } - if ((rracf->flags & NGX_RTMP_RECORD_MANUAL) && !brkframe && rctx->nframes == 0) { return NGX_OK; } - if (rctx->file.fd == NGX_INVALID_FILE) { + /*if (rctx->file.fd == NGX_INVALID_FILE) { return NGX_OK; - } + }*/ if (h->type == NGX_RTMP_MSG_AUDIO && (rracf->flags & NGX_RTMP_RECORD_AUDIO) == 0) @@ -1082,6 +1093,12 @@ ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx, return NGX_OK; } + if (h->type == NGX_RTMP_MSG_AMF_META && + (rracf->flags & NGX_RTMP_RECORD_DATA) == 0) + { + return NGX_OK; + } + if (h->type == NGX_RTMP_MSG_VIDEO && (rracf->flags & NGX_RTMP_RECORD_VIDEO) == 0 && ((rracf->flags & NGX_RTMP_RECORD_KEYFRAMES) == 0 || !keyframe)) @@ -1089,6 +1106,27 @@ ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx, return NGX_OK; } + if (rracf->interval != NGX_CONF_UNSET_MSEC) + { + // record interval should work if set, manual mode or not + next = rctx->last; + next.msec += rracf->interval; + next.sec += (next.msec / 1000); + next.msec %= 1000; + + if (ngx_cached_time->sec > next.sec || + (ngx_cached_time->sec == next.sec && + ngx_cached_time->msec > next.msec)) + { + ngx_rtmp_record_node_close(s, rctx); + ngx_rtmp_record_node_open(s, rctx); + } + } + else if (!rctx->failed) + { + ngx_rtmp_record_node_open(s, rctx); + } + if (!rctx->initialized) { rctx->initialized = 1; @@ -1184,6 +1222,12 @@ ngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx, } +static ngx_int_t +ngx_rtmp_record_started_init(ngx_rtmp_session_t *s, ngx_rtmp_record_started_t *v) +{ + return NGX_OK; +} + static ngx_int_t ngx_rtmp_record_done_init(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v) { @@ -1198,6 +1242,7 @@ ngx_rtmp_record_recorder(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_int_t i; ngx_str_t *value; ngx_conf_t save; + ngx_module_t **modules; ngx_rtmp_module_t *module; ngx_rtmp_core_app_conf_t *cacf, **pcacf, *rcacf; ngx_rtmp_record_app_conf_t *racf, **pracf, *rracf; @@ -1224,17 +1269,22 @@ ngx_rtmp_record_recorder(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - for (i = 0; ngx_modules[i]; i++) { - if (ngx_modules[i]->type != NGX_RTMP_MODULE) { +#if defined(nginx_version) && nginx_version >= 1009011 + modules = cf->cycle->modules; +#else + modules = ngx_modules; +#endif + for (i = 0; modules[i]; i++) { + if (modules[i]->type != NGX_RTMP_MODULE) { continue; } - module = ngx_modules[i]->ctx; + module = modules[i]->ctx; if (module->create_app_conf) { - ctx->app_conf[ngx_modules[i]->ctx_index] = + ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf); - if (ctx->app_conf[ngx_modules[i]->ctx_index] == NULL) { + if (ctx->app_conf[modules[i]->ctx_index] == NULL) { return NGX_CONF_ERROR; } } @@ -1278,15 +1328,20 @@ ngx_rtmp_record_postconfiguration(ngx_conf_t *cf) ngx_rtmp_core_main_conf_t *cmcf; ngx_rtmp_handler_pt *h; + ngx_rtmp_record_started = ngx_rtmp_record_started_init; + ngx_rtmp_record_done = ngx_rtmp_record_done_init; cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); - *h = ngx_rtmp_record_av; + *h = ngx_rtmp_record_avd; h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); - *h = ngx_rtmp_record_av; + *h = ngx_rtmp_record_avd; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AMF_META]); + *h = ngx_rtmp_record_avd; next_publish = ngx_rtmp_publish; ngx_rtmp_publish = ngx_rtmp_record_publish; diff --git a/ngx_rtmp_record_module.h b/ngx_rtmp_record_module.h index 6450dcbf9..a1a9706cf 100644 --- a/ngx_rtmp_record_module.h +++ b/ngx_rtmp_record_module.h @@ -16,15 +16,16 @@ #define NGX_RTMP_RECORD_OFF 0x01 #define NGX_RTMP_RECORD_AUDIO 0x02 #define NGX_RTMP_RECORD_VIDEO 0x04 -#define NGX_RTMP_RECORD_KEYFRAMES 0x08 -#define NGX_RTMP_RECORD_MANUAL 0x10 - +#define NGX_RTMP_RECORD_DATA 0x08 +#define NGX_RTMP_RECORD_KEYFRAMES 0x10 +#define NGX_RTMP_RECORD_MANUAL 0x20 typedef struct { ngx_str_t id; ngx_uint_t flags; ngx_str_t path; size_t max_size; + size_t interval_size; size_t max_frames; ngx_msec_t interval; ngx_str_t suffix; @@ -53,6 +54,7 @@ typedef struct { unsigned video_key_sent:1; unsigned audio:1; unsigned video:1; + unsigned record_started:1; } ngx_rtmp_record_rec_ctx_t; @@ -83,10 +85,23 @@ typedef struct { } ngx_rtmp_record_done_t; +typedef struct { + ngx_str_t recorder; + ngx_str_t path; +} ngx_rtmp_record_started_t; + + +typedef ngx_int_t (*ngx_rtmp_record_started_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_record_started_t *v); + + typedef ngx_int_t (*ngx_rtmp_record_done_pt)(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v); +extern ngx_rtmp_record_started_pt ngx_rtmp_record_started; + + extern ngx_rtmp_record_done_pt ngx_rtmp_record_done; diff --git a/ngx_rtmp_relay_module.c b/ngx_rtmp_relay_module.c index b0f5fecc1..e38a3699b 100644 --- a/ngx_rtmp_relay_module.c +++ b/ngx_rtmp_relay_module.c @@ -8,6 +8,7 @@ #include #include "ngx_rtmp_relay_module.h" #include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_codec_module.h" static ngx_rtmp_publish_pt next_publish; @@ -496,9 +497,11 @@ ngx_rtmp_relay_create_connection(ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name, } rs->app_conf = cctx->app_conf; rs->relay = 1; + rs->ready_for_publish = 0; rctx->session = rs; ngx_rtmp_set_ctx(rs, rctx, ngx_rtmp_relay_module); ngx_str_set(&rs->flashver, "ngx-local-relay"); + ngx_memcpy(&rs->app, &rctx->app, sizeof(rctx->app)); #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, 1); @@ -707,6 +710,9 @@ ngx_rtmp_relay_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) static ngx_int_t ngx_rtmp_relay_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) { + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "relay: ngx_rtmp_relay_play"); + ngx_rtmp_relay_app_conf_t *racf; ngx_rtmp_relay_target_t *target, **t; ngx_str_t name; @@ -748,6 +754,9 @@ ngx_rtmp_relay_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) } next: + ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0, + "relay: ngx_rtmp_relay_play: next"); + return next_play(s, v); } @@ -1231,12 +1240,160 @@ ngx_rtmp_relay_on_error(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, return NGX_OK; } +static ngx_int_t +ngx_rtmp_relay_send_set_data_frame(ngx_rtmp_session_t *s) +{ + ngx_rtmp_relay_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_rtmp_header_t hdr; + + static struct { + double width; + double height; + double duration; + double frame_rate; + double video_data_rate; + double video_codec_id; + double audio_data_rate; + double audio_codec_id; + u_char profile[32]; + u_char level[32]; + } v; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("Server"), + "NGINX RTMP (github.com/arut/nginx-rtmp-module)", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("width"), + &v.width, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("height"), + &v.height, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("displayWidth"), + &v.width, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("displayHeight"), + &v.height, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("duration"), + &v.duration, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("framerate"), + &v.frame_rate, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("fps"), + &v.frame_rate, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("videodatarate"), + &v.video_data_rate, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("videocodecid"), + &v.video_codec_id, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audiodatarate"), + &v.audio_data_rate, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audiocodecid"), + &v.audio_codec_id, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("profile"), + &v.profile, sizeof(v.profile) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + &v.level, sizeof(v.level) } + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "@setDataFrame", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onMetaData", 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, sizeof(out_inf) } + }; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL || !s->relay) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "relay: couldn't get relay context"); + return NGX_OK; + } + + /* we need to get the codec context from the incoming publisher in order to + * send the metadata along */ + codec_ctx = ngx_rtmp_get_module_ctx(ctx->publish->session, + ngx_rtmp_codec_module); + if (codec_ctx == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "relay: couldn't get codec context"); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "relay: data frame from codec context: " + "width=%ui height=%ui duration=%ui frame_rate=%ui " + "video_codec_id=%ui audio_codec_id=%ui", + codec_ctx->width, codec_ctx->height, codec_ctx->duration, + codec_ctx->frame_rate, codec_ctx->video_codec_id, + codec_ctx->audio_codec_id); + + /* we only want to send the metadata if the codec module has already + * parsed it -- is there a better way to check this? */ + if (codec_ctx->width > 0 && codec_ctx->height > 0) { + v.width = codec_ctx->width; + v.height = codec_ctx->height; + v.duration = codec_ctx->duration; + v.frame_rate = codec_ctx->frame_rate; + v.video_data_rate = codec_ctx->video_data_rate; + v.video_codec_id = codec_ctx->video_codec_id; + v.audio_data_rate = codec_ctx->audio_data_rate; + v.audio_codec_id = codec_ctx->audio_codec_id; + ngx_memcpy(v.profile, codec_ctx->profile, sizeof(codec_ctx->profile)); + ngx_memcpy(v.level, codec_ctx->level, sizeof(codec_ctx->level)); + + ngx_memzero(&hdr, sizeof(hdr)); + hdr.csid = NGX_RTMP_RELAY_CSID_AMF_INI; + hdr.msid = NGX_RTMP_RELAY_MSID; + hdr.type = NGX_RTMP_MSG_AMF_META; + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "relay: sending @setDataFrame"); + + return ngx_rtmp_send_amf(s, &hdr, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); + } + + return NGX_OK; +} static ngx_int_t ngx_rtmp_relay_on_status(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) { ngx_rtmp_relay_ctx_t *ctx; + static struct { double trans; u_char level[32]; @@ -1300,9 +1457,57 @@ ngx_rtmp_relay_on_status(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, "relay: onStatus: level='%s' code='%s' description='%s'", v.level, v.code, v.desc); + /* when doing a push to Adobe Media Server, we have to use the + * @setDataFrame command to send the metadata + * see: http://help.adobe.com/en_US/adobemediaserver/devguide/WS5b3ccc516d4fbf351e63e3d11a0773d56e-7ff6Dev.2.3.html + */ + if (!ngx_strncasecmp(v.code, (u_char *)"NetStream.Publish.Start", + ngx_strlen("NetStream.Publish.Start"))) { + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "relay: sending metadata from NetStream.Publish.Start from player"); + + s->ready_for_publish = 1; + + if (ngx_rtmp_relay_send_set_data_frame(s) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "relay: unable to send metadata via @setDataFrame"); + } + } + return NGX_OK; } +static ngx_int_t +ngx_rtmp_relay_on_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + /* when we receive onMetaData, the session (s) is our incoming publisher's + * session, so we need to send the @setDataFrame to our ctx->play->session */ + ngx_rtmp_relay_ctx_t *ctx; + ngx_rtmp_relay_ctx_t *pctx; + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "relay: got metadata from @setDataFrame invocation from publisher."); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL) { + return NGX_OK; + } + + for (pctx = ctx->play; pctx; pctx = pctx->next) { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "relay: %ssending metadata from @setDataFrame invocation from publisher to %V/%V/%V", + (pctx->session->relay && pctx->session->ready_for_publish) ? "" : "not ", &pctx->url, &pctx->app, &pctx->play_path); + if (!pctx->session->relay || !pctx->session->ready_for_publish) continue; + if (ngx_rtmp_relay_send_set_data_frame(pctx->session) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "relay: unable to send @setDataFrame to %V/%V", &pctx->url, &pctx->play_path); + } + } + + return NGX_OK; +} static ngx_int_t ngx_rtmp_relay_handshake_done(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, @@ -1686,5 +1891,9 @@ ngx_rtmp_relay_postconfiguration(ngx_conf_t *cf) ngx_str_set(&ch->name, "onStatus"); ch->handler = ngx_rtmp_relay_on_status; + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "@setDataFrame"); + ch->handler = ngx_rtmp_relay_on_meta_data; + return NGX_OK; } diff --git a/ngx_rtmp_send.c b/ngx_rtmp_send.c index c65deec1c..69dfed955 100644 --- a/ngx_rtmp_send.c +++ b/ngx_rtmp_send.c @@ -3,7 +3,6 @@ * Copyright (C) Roman Arutyunyan */ - #include #include #include "ngx_rtmp.h" @@ -100,7 +99,7 @@ ngx_rtmp_create_abort(ngx_rtmp_session_t *s, uint32_t csid) "create: abort csid=%uD", csid); { - NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE); + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ABORT); NGX_RTMP_USER_OUT4(csid); @@ -503,6 +502,7 @@ ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code, char* level, out_inf[0].data = level; out_inf[1].data = code; out_inf[2].data = desc; + trans = 0; memset(&h, 0, sizeof(h)); @@ -594,6 +594,358 @@ ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code, char* level, } +// ----------- Based on Adobe FMS 3 application.redirectConnection description --------- // + +ngx_chain_t * +ngx_rtmp_create_redirect_status(ngx_rtmp_session_t *s, char *callMethod, char *desc, ngx_str_t to_url) +{ + ngx_rtmp_header_t h; + static double dtrans; + static double dcode; + + ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0, + "create redirect status: got data"); + + ngx_log_debug5(NGX_LOG_DEBUG, s->connection->log, 0, + "create redirect status: method='%s', status code='%s' level='%s' " + "ex.code=%ui ex.redirect='%s'", callMethod, + "NetConnection.Connect.Rejected", "error", 302, to_url.data); + + static ngx_rtmp_amf_elt_t out_inf_ex_data[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_string("code"), + &dcode, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("redirect"), + NULL, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + "error", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + "NetConnection.Connect.Rejected", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_string("ex"), + out_inf_ex_data, + sizeof(out_inf_ex_data) }, + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &dtrans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, + sizeof(out_inf) }, + }; + + ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0, + "create redirect status: set structure data"); + + out_elts[0].data = callMethod; + out_inf[2].data = desc; + dcode = 302; + dtrans = 0; + out_inf_ex_data[1].data = to_url.data; + + ngx_memzero(&h, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_CMD; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + + return ngx_rtmp_create_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +ngx_int_t +ngx_rtmp_send_redirect_status(ngx_rtmp_session_t *s, + char *callMethod, char *desc, ngx_str_t to_url) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_redirect_status(s, callMethod, desc, to_url)); +} + + +ngx_chain_t * +ngx_rtmp_create_close_method(ngx_rtmp_session_t *s, char *methodName) +{ + ngx_rtmp_header_t h; + static double dtrans; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &dtrans, 0 }, + }; + + ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0, + "create close method: set structure data"); + + out_elts[0].data = methodName; + dtrans = 0; + + ngx_memzero(&h, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_CMD; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + + return ngx_rtmp_create_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +ngx_int_t +ngx_rtmp_send_close_method(ngx_rtmp_session_t *s, char *methodName) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_close_method(s, methodName)); +} + + +ngx_chain_t * +ngx_rtmp_create_fcpublish(ngx_rtmp_session_t *s, u_char *desc) +{ + ngx_rtmp_header_t h; + static double trans; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + "status", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + "NetStream.Publish.Start", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + NULL, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onFCPublish", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, + sizeof(out_inf) }, + }; + + ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0, + "create: fcpublish - set structure data"); + + out_inf[2].data = desc; +// trans = 3.0; // magick from ffmpeg + trans = 0; + + memset(&h, 0, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_CMD; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + + return ngx_rtmp_create_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +ngx_int_t +ngx_rtmp_send_fcpublish(ngx_rtmp_session_t *s, u_char *desc) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_fcpublish(s, desc)); +} + + +ngx_chain_t * +ngx_rtmp_create_fcunpublish(ngx_rtmp_session_t *s, u_char *desc) +{ + ngx_rtmp_header_t h; + static double trans; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + "status", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + "NetStream.Unpublish.Success", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + NULL, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onFCUnpublish", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, + sizeof(out_inf) }, + }; + + ngx_log_debug0(NGX_LOG_DEBUG, s->connection->log, 0, + "create: fcunpublish - set structure data"); + + out_inf[2].data = desc; +// trans = 5.0; // magick from ffmpeg + trans = 0; + + memset(&h, 0, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_CMD; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + + return ngx_rtmp_create_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +ngx_int_t +ngx_rtmp_send_fcunpublish(ngx_rtmp_session_t *s, u_char *desc) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_fcunpublish(s, desc)); +} + + +ngx_chain_t * +ngx_rtmp_create_fi(ngx_rtmp_session_t *s) +{ + ngx_rtmp_header_t h; + static double trans; + + struct tm tm; + struct timeval tv; + + static u_char buf_time[NGX_TIME_T_LEN*2 + 1]; + static u_char buf_date[NGX_TIME_T_LEN + 1]; + + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("st"), + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("sd"), + NULL, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onFi", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, + sizeof(out_inf) }, + }; + + trans = 0; + + ngx_gettimeofday(&tv); + + ngx_libc_localtime((time_t)tv.tv_sec, &tm); + + ngx_memzero(buf_time, sizeof(buf_time)); + ngx_memzero(buf_date, sizeof(buf_date)); + + ngx_sprintf(buf_time, "%02d:%02d:%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, (int)tv.tv_usec); + // Strange order, but FMLE send like this + ngx_sprintf(buf_date, "%02d-%02d-%04d", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900); + + out_inf[0].data = buf_time; + out_inf[1].data = buf_date; + + memset(&h, 0, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_CMD; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + + return ngx_rtmp_create_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +ngx_int_t +ngx_rtmp_send_fi(ngx_rtmp_session_t *s) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_fi(s)); +} + + ngx_chain_t * ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s) { diff --git a/ngx_rtmp_shared.c b/ngx_rtmp_shared.c index 6f6e4e8de..3bdc5beee 100644 --- a/ngx_rtmp_shared.c +++ b/ngx_rtmp_shared.c @@ -66,7 +66,8 @@ ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf, ngx_chain_t *in) } for (cl = in; ; cl = cl->next) { - if (cl->next == NULL) { + /* FIXME: Don't create circular chains in the first place */ + if (cl->next == NULL || cl->next == in) { cl->next = cscf->free; cscf->free = in; return; diff --git a/ngx_rtmp_stat_module.c b/ngx_rtmp_stat_module.c index 326a811e3..ba1c646c6 100644 --- a/ngx_rtmp_stat_module.c +++ b/ngx_rtmp_stat_module.c @@ -13,6 +13,7 @@ #include "ngx_rtmp_live_module.h" #include "ngx_rtmp_play_module.h" #include "ngx_rtmp_codec_module.h" +#include "ngx_rtmp_record_module.h" static ngx_int_t ngx_rtmp_stat_init_process(ngx_cycle_t *cycle); @@ -115,7 +116,7 @@ ngx_rtmp_stat_init_process(ngx_cycle_t *cycle) * so we can run posted events here */ - ngx_event_process_posted(cycle, &ngx_rtmp_init_queue); + ngx_event_process_posted(cycle, (ngx_queue_t*) &ngx_rtmp_init_queue); return NGX_OK; } @@ -329,6 +330,7 @@ ngx_rtmp_stat_client(ngx_http_request_t *r, ngx_chain_t ***lll, ngx_rtmp_session_t *s) { u_char buf[NGX_INT_T_LEN]; + struct sockaddr_in *sa; #ifdef NGX_RTMP_POOL_DEBUG ngx_rtmp_stat_dump_pool(r, lll, s->connection->pool); @@ -342,6 +344,20 @@ ngx_rtmp_stat_client(ngx_http_request_t *r, ngx_chain_t ***lll, NGX_RTMP_STAT_ES(&s->connection->addr_text); NGX_RTMP_STAT_L(""); + /* + ** Displays socket port number + */ + NGX_RTMP_STAT_L(""); + sa = (struct sockaddr_in *) s->connection->sockaddr; + if (sa) { + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", + (ngx_uint_t) ntohs(sa->sin_port)) - buf); + } else { + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", 0) - buf); + } + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L("